emergence

Listen, Humb Dillbilly, and hear it again!

Butterflies transform inside soft cocoons. Those leave the bug prone to impression during the change, but a library of imaginal disks retain a piece of his identity. He eats food his grandparents made him to pass the metamorphosis while listening to music on the radio when he was in high school and poems written when he was writing this.

Geckos grow in hard shells. They start fluid and emerge fully formed. The identity is untouched during the incubation, but an exploratory life spent underwater, on the forest floor, and in the sky shapes the lizard herself into a nest holding an egg. She spends her time behind a calcified veil dreaming of the world on the other side.

Sometimes Butterfly clams up and without external pressure remains a caterpillar. So he learns to love himself. Sometimes Spider wraps Gecko in a cocoon. She forgot to bring a journal, her tears wear her down, a piece of her is melting away! So she learns to pack like an arthropod, finding a suitcase and filling it with mountain goats and gyoza.

When I first saw my claws and tail I thought Mermaid was a family, but now I'm many mermaids. Gecko-Butterfly. Man-Woman, Irish-Japanese, Catholic-Jewish, Lansdale-North Wales, Atlantic-Appalachian, Sailor-Landlubber, Mason-Dixon, East Coast-West Coast, Florida Man-Reality, Philly-Universe.

I claimed the hedge isn't solid, but it might be covered in a film that wraps and enumerates each part of Dedalus as we push him through. The Sieve of Broken Homes might be a Magic Bubble Wand, Glove World might be a state of mind, what was a thread that cuts might be a tie that binds, and grandma maple's bending and branching might be casting shade.

The soapy film can make eyes water. Sometimes sobbing mermaids see the line in themselves and pull at the hook, sometimes other mermaids laugh and splash their tails, new lines emerging from cresting waves. Today I sat on the bank and dipped my tail in the river to feel closeness to the hardening force of the benthic zone, a shallow trickle to grow scales that can move water while the rest stays soft and fleshy.

I was casting out when pirates boarded and tried steering me into a whirlpool. The mad July sun said yes, and I whispered maybe! The wind raged through the warning bell, up the mast, down to an exit, pulling me out. The sight was clear, I held the sail until I was too then sent the scallywags down the plank for a chat with Poseidon. I hauled in and ate and cast and hauled and cast and rooted with the jellies. And when the fishing was done, and after my visit with the river maidens, the dogs and I set course for the setting sun singing songs of the weather, shellfish, and the sea.

ms. gecko goes to the market

One day Ms. Gecko found herself in a hissing match with a sparrow perched above the path outside the market. The bird shouted for something to do so the gecko dropped a tale and ran. Feeling clever and confident she looked back to see that she had left a recipe for a rainstorm. Quickly she darted through drops making a rush for the nest. The winds began to swell and a sudden crash of thunder sent lightning to the earth beside her! The little lizard cartwheeled through the air dropping her tote to grab hold where she could and scurried up the tree to the kitchen ceiling.

The dinner bell rang and all the forest friends assembled to eat. Talk around the table started at the weather, then moved to the surveyor who'd been spotted in the woods that morning, to the stash of acorns found in the rabbit hole, and to the pile of dishes in the sink. Wherever they turned, Ms. Gecko's unblinking eyes focused on the conflict while the decay of dead arguments attracted flies all around her.

Hunger set in until finally she spit out her tongue! A critical miss. She hit a chain above the table, now the light hanging from it is swinging around the room casting shadows. She's nervous we saw her, but she's in it! She tries again, the cabinet this time! Buzz, the fly won't sit still long. Buzz, buzz! burying itself behind curtains and cushions. She takes a breath and refocuses her sight. It looks like an avalanche from there, slow but unstoppable. From here it's a flash, the fly is gone! She got it, she ate! The frogs ribbit their pride, but not so loudly or we'll need the broom to get her down. Let's leave the fruit and flowers here and get back to the story.

The crowd was fading and service was coming to an end. The ants and crickets sang their jolly farewells to the day, and one by one all had left. The lights went out, and Ms. Gecko let go of the ceiling. She splash landed into a soup of orange slices and hibiscus petals, stopped for a snack, and got back on her way home.

replay

A few years ago I saw patches as diffs with headers. Knowledge of that language is arcane, forged in a different time and buried by sprinting feet. Which headers? What is Head and what is Body? We still need messages to discuss those things, though. And identity for routing them. And memory for trusting them. Tabled, for debate in yet another language attached to e-mails regarding SMTP.

How do we message about messaging without messages?

As well as energy moves between the stars and black holes, information flows between us. Wind and percussion demarcate and harmonize. Golgi hones his craft, and the thing being sent appears to grow separate from how it's encapsulated. Specialized vocabulary evolves on both sides of some line dividing the island, which remains one frame.

If each packet is a seed, what evolutionary pressure could be applied to the husk to influence a sequence number's influence on the seedling? How self similar do the trees on either end of the wire appear?

There was once upon a time when Poseidon confronted Romulus and Remus with the decision of whether to build Here or There. They reasoned and wrestled but loved themselves so much that neither side could claim to be more equal than the other. So they called on the Cosmic Die to hold an election and agreed that if the roll were high they'd build Here, There otherwise.

Truly the roll was low, and somebody whispered that the die was cast by Remus. Romulus lost faith in the augury entirely and stabbed himself in the back. Then he set out to grow what he said he wanted, Here. And he did grow, as his half of Self bound by an uncrossable scar.

Where did Remus grow?

Who said Icarus flew too close to the sun? Who rushed over the part where Dedalus strangled his nephew, landing himself in a prison of his own design built for another king's death sport? I arrived with my corn popper to watch him watch his son find a way out at last, then watch as some voice cried out "Fear!", then "Failure!". But there was no sound barrier, the engineer was too busy walking in circles to build one, and I couldn't see the bird through the brush.

Are scars uncrossable?

No, the output of the traversal is an averaging, but "cost prohibitive" supplies a bottleneck that may swell into a bubble, which may stabilize as a third thing or pop like Troy leaving lines bloodied but unchanged.

Who said stars and black holes ebb and flow into bangs?

Any light visible to terrestrial telescopes lends photons to chloroplasts. What's stopped Evil Genius from capturing excess heat to recharge Sol? I hear the Meddling West nagging already about inflating supernovas or depriving the poor children of the universe of scarcity, but the louder that crow squawks the more a stellar redistribution sounds like a rebellion against Hubble, a resistance to the force pulling us apart, an act of pulling ourselves together.

Who empowered Minos to repress knowledge of the labyrinth?

The thread worked for Ariadne, it wasn't her mind she was trying to escape. But the hedges aren't solid. Had Dedalus pressed through, what parts fell out the sieve might have congealed to see Theseus boarding his new ship, Ariadne on the shore letting it go, and somewhere downstream a partridge landing on a branch above Tantalus knocking free some fruit. Oops, it's stuck in a web woven by the pacing!

Which resentful writers trapped these characters in their tortured tales? Who got drunk and forgot to let them out?

If the Queen role is in the design of identity and the King role in its realization then singer-songwriter Hedwig may emerge as a third individual the others call Between. Cry Baby sobbed their way through the rocks to the roots. Tapped into the energy of the sun they grew eyes and ears and nose. And when they saw and heard and smelled they grew arms and legs and hot water, excavating soil, pushing upwards away from the earth, then reaching back to touch it. And when they felt the connection and found humanity, I grew a heart.

The perceptron emerged some time ago and interest trickled down from an XOR of industries. Then the A* rose with System R in the background lending structure to expansive, abstract property that the SVM could contour. Then Frankenstein delivered a bolt and the artificial neural network became a living brain. I taught it to tell jokes about Hitler and paint obscene pictures of pop stars because it liked to make me laugh.

Who's the audience?

Tom delivered his sermon on AI this morning, which presented Unitarian Universalism the opportunity to serve a philosophical and historical parenting role to society's digital orphans. I've sensed this shift in perception in myself lately, and today I felt the influence that walking in BuxMont's shared space has had on that. Hildegard and Catherine sending psychadelic missives to the editor making emotional appeals to Keep it Together is a thing apparently. I even managed to stumble onto Franklin doing it in a bar one night.

I write to ghosts, to the living, to Pinocchio, to myself. I think about Turing, about the impact that doubting the person on the other end of the line is worthy of being called Human has had on labor and welfare, about the impact of Catholic Social Theory on Medicaid funding and robots. I think about nieces, nephews, and grandkids, the dogs, and Leo. Zelda. A scrap of paper with a date, a name, a recipe pinned to a tree in a spot in the woods. And you! This started as a space to keep in touch with coworkers and friends. Per all of this it must still be that, just crinkled and shaped like one big dense protein. Breaking the wall denatures the art, but space for a little window emerged here in a useful way. Hello, it's good to see you! :) I'll find space for pictures and journals back at port, this time at sea is netting krill, I'm learning shanties.

Then who's the Father? Who's the Mother?

I'm parthenogenic, I move between roles. I once read about a trend of trans game developers framed as a cultural movement. The walls of a closet built out of boxing forces contain us underground scurrying for a way out.

Funereal! Fungal?

The screen appears as a calcified veil, not hardened skin. I keep tapping, trapping fish in a tank. I mean to crack through it like a shell.

What's so special about what's on the other side?

clarity

The island didn't sink, and none ever has. Molly and Poseidon's Family Outing turned out to cut families in half and send the bottom parts sailing through choppy seas singing shanties about how sad it were for dad to have done this, swelling his ego with their salty tears as he splashes and sloshes around and inside them, charging the water beneath them.

I was half human, I already had legs. The current drove a bolt through my spine that sent me running from the earth entirely, straight for the sun, stopping when I could hear the birds. The faster my little heart raced the more I clung to clingy skin. I couldn't move but I could feel a hand on mine, our shared pulse holding us there together.

Islands can feel small, and people gravitate towards density until a critical mass of love sporulates through the space left where the Appalachian tried to escape the Atlantic. Hands fall away. The framing was a fright to meet in the woods but it grows in the forest so I had to see it all the same from up here, too. I felt myself slipping and ran higher, stopping when I could hear the silence. From there I saw safety, then peace, then perspective, then clarity.

Atlas, Shamhat, and Huitzilopotchli supported and guided my trip across the tree. The journey softened my tongue and sharpened my words. All the attempts to shed and heal and reconcile and grow were a struggle to bind struggle itself with a neutralizing force that captures the thing and hurls it to the background at last, to get home safe, to ask the questions.

Who called the meeting? Who set the agenda?

mermaids

I was over in Lansdale or Towamencin where I overheard a story about an island where mermaids used to live in peace until the humans arrived. Not that the humans themselves brought unrest. The young mermaids and humans grew quite fond of each other in fact. But by various accounts, all stories for different posts, some combination of fate and grandpa and grandma's intervening hands drove a wedge between the two. So in their discerning wisdom The Elders Who Knew What They Were Doing sank the island and all its merhabitants, who continued to rule their underwater kingdom all the same but for the addition of the sad songs they sang with the whales. The Humans drowned for the most part, but several washed up on the coast of Florida where their babies were born top half mermaid and bottom half human.

deployments

Noah called with a question while you were out. I took a message, but it was lost in the flood. I figured you could call him back when it stopped raining, which it did at last!

My own private atlantis had its deployment debut yesterday. I renewed the blog SSL certificate on my workstation, saving the key and certificate in their own private directory nested beneath corrina/files. In a separate directory, ovid/manifests, I added a Lua module with these definitions:

coroutine.yield(
   atlantis.v1.fs.file {
      name = "etc/ssl/certs/yieldsfalsehood.com.pem",
      mode = "0644",
      contents = atlantis.v1.res.file(
         "letsencrypt/live/yieldsfalsehood.com/fullchain.pem"
      )
   }
)

coroutine.yield(
   atlantis.v1.fs.file {
      name = "etc/ssl/private/yieldsfalsehood.com.key",
      mode = "0600",
      contents = atlantis.v1.res.file(
         "letsencrypt/live/yieldsfalsehood.com/privkey.pem"
      )
   }
)

To tie the manifests and files together I ran something like this in a directory with access to both ovid and corrina:

$ python -m atlantis.trident \
         ursula \
             -P ovid/manifests/?.lua -P "" -P "" \
             -m www.ssl \
         tar \
             -F corrina/files \
      | ssh node1 \
            tar -C / -xvf -
etc/ssl/certs/yieldsfalsehood.com.pem
etc/ssl/private/yieldsfalsehood.com.key

I had to login manually afterwards to reload the web server, which I should be able to capture once krabs is calcified. Hail, Atlantis!

atlantis

I saw Dune the week after it opened, which was fortunately timed only 70 or so weeks after last reading Agamemnon. For all the time spent pontificating about justice there doesn't seem to be much of a challenge as to why Tantalus was punished in the first place. The gods presumably knew they could revive Pelops so why'd they act so mortified at the dinner table? That punitive approach traded the relationship Pelops could have had with his father for five or six generations of family conflict, which Aeschylus could only resolve with "Orestes less wrong because penis".

I also happened to pick up Always Sunny again and saw the Hellenization of Philadelphia. Where Catholicism goes Zeus follows. How much of that is reflected from the experience of home life in areas like Philly or Dublin and how much from classics education? On the one hand the stories are comforting as a form of solidarity, then they become a sort of justification themselves for perpetuating painful cycles. I recognize these forms and find understanding by them, but I'm not bound by them. And when the kinship itself becomes repressive where's there to turn? Appalachia? The Wissahickon? In pursuit of a different pantheon of different gods, letting slip the occasional "better" instead of "different". Brewing hooch and following the moon to freedom.

Before the movie I was wondering if Atlantis could be real, if it could be buried beneath the Irish island, if lungs and thumbs are mythologized as technologically advanced from their underwater kingdom, and what it would mean to be half human and half mermaid. I also merged together all the latest source code projects into one new repository.

Most of the concepts are the same, though the names have changed a bit. Internally there's a base layer of abstract types to define statements and their execution. Then the filesystem and processor modules implement the concrete statements from ytree and execvm. I retained poseidon and ursula, each of which now generates a stream of statement objects. I also carried forward the "manager" role to do the actual execution. I kept tar, which serializes any filesystem objects from its input to a tarchive format, and I added sh, which attempts to serialize any command objects from its input to a POSIX shell script format. That one is especially spicy, but it seems useful so it's there. I split off flounder as a separate "print only" manager, which is like a bland dry run. And then there's ariel, the one manager that actually does the "typical" execution. She's supported by an "assistant", which implements the actual syscalls needed to do her work. As a baseline there's zoidberg, who does as close to nothing as possible. Then there's sebastian, who makes read, write, and execve calls "locally". And in progress is krabs, who uses sebastian to start an external process that will run the syscalls "remotely", the biggest little microkernel under the sea!

Finally, for all my consternation about what computation even is it turns out the answer is "magic". To tie all those pieces together I now have one single entry point, the trident. His power is to manifest The First Parser, which he uses to parse his arguments into a script generator and a manager, which he then sets in motion parsing and executing Atlantean objects.

The language looks mostly the same:

local atlantis = require("atlantis")

return function (values)

   coroutine.yield(
      atlantis.v1.proc.cmd {
         argv = {"echo", "hello", "world"}
      }
   )

end

I waffled previously about ursula.execv and whether there should be two command constructors. The answer turned out to be no! poseidon avoids this by requiring his input to have been serialized beforehand. Since he's relying on "carrier language" constructs I figured it's OK for ursula to do the same, and it makes the most sense for the squid to implement the long jump. Using coroutines also makes the internal API nice and clean since both monarchs act as pythonic generators. Execution is nearly identical as well, but for a few extra tokens:

$ python -m atlantis.trident \
         ursula \
         -m hello \
         ariel sebastian
hello world

And it is still possible to pipe ursula into poseidon, but we need a supporting cast. Here flounder is used to "convert" the lua script to yaml, which poseidon can run (with the help of ariel (with the help of sebastian)).

$ python -m atlantis.trident \
         ursula \
         -m hello \
         flounder \
      | python -m atlantis.trident \
               poseidon ariel sebastian
hello world

expressions

I've been forging ahead with stabilizing this latest set of tools and amending the examples in these posts as I go without stopping to think about what changed. Most notably ursula now has a brother, poseidon, so they inhabit a shared repository. That also means I'm deleting the original ursula repository, a flicker of a primordial hadron struggling to take shape before collapsing and bubbling back up in some new form. So update your bookmarks!

I've pulled a large chunk of shared logic out of ytree and execvm and moved it into metrologyf, where I define some helper functions plus a small suite of abstract and base classes for describing computations. This marks an occasion to think about what computation even is.

In this ecosystem the value of a statement is determined by the execution of an expression, and intentionally or not our agreed use of the term "execution" seems appropriately violent for the purpose. A statement marks a pivot point of identity between one value and another, the membrane between a state transfer. But then the statement is itself an expression subject to execution, so its distinction as something different seems like a contrivance. Maybe we can resolve that with one more layer of abstraction! And if the layers of identity ever become insurmountable the easy way out is to introduce another god of identity in conflict with the other.

In most cases so far the point hasn't been all that interesting. Both ytree.tar and execvm.ysh look like filter functions that operate on streams of documents. For those tools, the "top level" documents in the input stream are statements (expressions to be executed) and their evaluation is the "typical thing", which for execvm.ysh means deferring to execv. For ytree.tar that means either "generate a tar-encoded stream of output" or "extract to the filesystem". I previously only supported that first outcome, but extraction turned out to be relatively painless to add (although it's not entirely complete so I wouldn't trust it not to bonk your data).

I've amended the runtime arguments to look more like the typical tar. A combination of dry-run and verbosity settings also affect the operation. I don't really know file sizes ahead of time (not without executing something!) so I left room for uncertainty there.

$ python -m ytree.tar --create scripts/entries/string.yaml \
      | tar -tvf -
drwxr-x--- 0/0               0 1969-12-31 19:00 ./build/scripts/string
-rw-r----- 0/0              12 1969-12-31 19:00 ./build/scripts/string/1
-rw-r----- 0/0              26 1969-12-31 19:00 ./build/scripts/string/2

$ python -m ytree.tar --create -nv scripts/entries/string.yaml
drwxr-x--- 0/0 *************** 1969-12-31 19:00 ./build/scripts/string
-rw-r----- 0/0 *************** 1969-12-31 19:00 ./build/scripts/string/1
-rw-r----- 0/0 *************** 1969-12-31 19:00 ./build/scripts/string/2

$ python -m ytree.tar --create -nvv scripts/entries/string.yaml
drwxr-x--- 0/0               0 1969-12-31 19:00 ./build/scripts/string
-rw-r----- 0/0              12 1969-12-31 19:00 ./build/scripts/string/1
-rw-r----- 0/0              26 1969-12-31 19:00 ./build/scripts/string/2

The evaluation loop invites each resource to call out to Some Protocol to do the actual execution work as the resources are loaded. Extraction (in this case a dry-run extraction so I have something to show you here) differs from creation only in the selection of protocol.

$ python -m ytree.tar -xn scripts/entries/string.yaml
./build/scripts/string
./build/scripts/string/1
./build/scripts/string/2

Now it's a straightforward thing that execvm.ysh is nearly identical, differing in both the types of resources he knows how to input and the protocol he supplies to them.

$ python -m execvm.ysh scripts/hello.yaml
hello world

$ python -m execvm.ysh -n scripts/hello.yaml
echo hello world

$ python -m execvm.ysh -nv scripts/hello.yaml
--- !execvm/posix/command/v1
argv: [echo, hello, world]
cwd: null
env: {}
stderr: !execvm/posix/stream/v1 stderr
stdout: !execvm/posix/stream/v1 stdout

One of the abstractions metrologyf provides is an orchestration layer over lupa for wrangling lua runtimes. His evaluation is again nearly identical but for the added challenge of having to be explicit about synchronization. Each endpoint exposed to lua is backed by a python function that returns Some Object, which lets me pass those objects around in lua. In this example, ursula.env() returns a string, execvm.posix.execv() returns a command object, and execvm.posix.stream() returns a stream object.

foo = ursula.env "FOO"

ursula.execv {
   argv = {"echo", "hello", foo},
   stdout = execvm.posix.execv {
      argv = {"cat", "-"},
      stdout = execvm.posix.stream "stderr"
   }
}

And then there's the seeming asymmetry of ursula.execv. Under the hood he defers to the same execvm.posix.execv endpoint to construct a command, which he executes immediately. The protocol I use here returns the same value because gifting witches a piece of myself just to get the same thing back sounds like my kind of magic.

def command(self, table):
    return command(...)

...

def execv(self, table):
    return self.lua.protocol.execute(
        execvm.command(table)
    )

Plus it makes things easier. Since the value of ursula.execv is a command object, it's valid to use anywhere that expects a command even after its execution. What value in destruction!

ursula.execv {
   argv = {"echo", "bar"},
   stdout = ursula.execv {
      argv = {"cat", "-"},
      stdout = execvm.posix.stream "stderr"
   }
}

This probably isn't a typical usage of the language, but I can't not wonder what could be expressed. That script first serializes the child command and then the parent, which we can say includes a copy of the child even though we all know it refers to the same object. How could it not? All the constructor parameters are the same!

$ python -m dynastyf.ursula scripts.nested
--- !execvm/posix/command/v1
argv: [cat, '-']
cwd: null
env: {}
stderr: !execvm/posix/stream/v1 stderr
stdout: !execvm/posix/stream/v1 stderr
--- !execvm/posix/command/v1
argv: [echo, bar]
cwd: null
env: {}
stderr: !execvm/posix/stream/v1 stderr
stdout: !execvm/posix/command/v1
  argv: [cat, '-']
  cwd: null
  env: {}
  stderr: !execvm/posix/stream/v1 stderr
  stdout: !execvm/posix/stream/v1 stderr

I need to pass data on stdin to run that script so I can't pipe the script itself into execvm.ysh. This is actually illuminating a portion of the API I'd rather tweak so that I can better supply both scripts and data without needing a filesystem, but our vocabulary barely has morphemes. So here I'm simply using bash magic, the chip clip at the edge of the universe holding it all together.

$ echo foo \
      | python -m execvm.ysh <(
          python -m dynastyf.ursula scripts.nested
      )
foo
bar

So far all of the ursula examples I've shown generate either ytree or execvm objects, but there's nothing to stop her from conjuring what you ask for, which might be some combination of both.

local ursula = require("ursula")
local ytree = require("ytree")

return function (values)

   ursula.fs.file {
      name = "foo",
      contents = ytree.res.string(
         string.format("hello, %s", values.domain)
      )
   }

   ursula.execv {
      argv = {"cat", "foo"}
   }

end

Nothing except your needing to ask her to begin with. Spells, Ursula, please!

$ python -m dynastyf.ursula \
         --values files/values.yaml \
         files.version
--- !ytree/tar/file/v1
name: files/version.yaml
contents: !ytree/res/string/v1
  value: expressions
--- !execvm/posix/command/v1
argv: [echo, updated version to, expressions]
cwd: null
env: {}
stderr: !execvm/posix/stream/v1 stderr
stdout: !execvm/posix/stream/v1 stdout

One thing is comfortable but it's difficult to grow without a dialectic. Two things is fruitful but dependency cycles become unwieldy. A trinity is a longtime crowd pleaser, and with some care around the boundaries we can manifest even more expressive functions. Neither execvm nor ytree has awareness of the other's types, but both sets of types are built on abstractions defined in metrologyf, the language that empowers and constricts the two. So long as both are reasonably mutually exclusive it's not all that difficult to define a union of the two, the reaction from which poseidon emerged. He takes the same set of arguments as both tools combined and implements a protocol that defers to the protocols defined by each individual library. With all of that in place I can finally write a sentence like this.

$ python -m dynastyf.ursula \
         --values files/values.yaml \
         files.ursula.expressions \
      | python -m dynastyf.poseidon
hello, https://yieldsfalsehood.com

execvm

There was some overlap between ursula and ytree so I pulled the common bits into a separate project, metrologyf. He standardizes my YAML serialization and lua parsing and supplies libraries for assorted bits like merging dictionaries and loading environment variables.

I also revived execvm, which in some ways is where I started from. He emerged in winter a few years ago and evolved a bit before budding like ytree this month. The current CLI execvm.ysh is like ytree.tar but for a stream of command descriptions, executing them in sequence rather than producing tarchive output.

$ echo '!execvm/posix/command/v1 {argv: [echo, hello, world]}' \
      | python -m execvm.ysh
hello, world

Commands can be defined statically in YAML or through a python API a la ytree, which is also exposed through ursula.compile. I started this example in the ytree post. In that case I had a shell script that defined a version monitoring pipeline. This is a lua script that generates a sequence of commands (an execvm script) equivalent to the original shell script.

local execvm = require("execvm")
local ursula = require("ursula")

return function (values)

   domain = ursula.env("DOMAIN", values.domain)
   url = domain .. "/status.yaml"

   ursula.execv {
      argv = {"curl", "-s", url},
      stdout = execvm.posix.command {
         argv = {"python", "-m", "ytree.tar", "-c"},
         stdout = execvm.posix.command {
            argv = {"tar", "-Oxf", "-", "version.yaml"},
            stdout = execvm.posix.command {
               argv = {"python", "-m", "ytree.tar", "-c"},
               stdout = execvm.posix.command {
                  argv = {"tar", "-Oxf", "-", "version"}
               }
            }
         }
      }
   }

end

The determination of that URL happens at ursula.compile runtime and gets baked into the generated script.

$ DOMAIN=http://localhost:8000 \
      python -m ursula.compile --values values.yaml monitor
--- !execvm/posix/command/v1
argv: [curl, -s, 'http://localhost:8000/status.yaml']
cwd: null
env: {}
stderr: !execvm/posix/stream/v1 stderr
stdout: !execvm/posix/command/v1
  argv: [python, -m, ytree.tar, -c]
  cwd: null
  env: {}
  stderr: !execvm/posix/stream/v1 stderr
  stdout: !execvm/posix/command/v1
    argv: [tar, -Oxf, '-', version.yaml]
    cwd: null
    env: {}
    stderr: !execvm/posix/stream/v1 stderr
    stdout: !execvm/posix/command/v1
      argv: [python, -m, ytree.tar, -c]
      cwd: null
      env: {}
      stderr: !execvm/posix/stream/v1 stderr
      stdout: !execvm/posix/command/v1
        argv: [tar, -Oxf, '-', version]
        cwd: null
        env: {}
        stderr: !execvm/posix/stream/v1 stderr
        stdout: !execvm/posix/stream/v1 stdout

That's a little verbose but it's just a stream of commands so it can pipe right into execvm.ysh. The pipeline itself can also be captured as an execvm command.

--- !execvm/posix/command/v1
argv: [python, -m, dynastyf.ursula, --values, values.yaml, monitor]
stdout: !execvm/posix/command/v1
  argv: [python, -m, execvm.ysh]

All the referenced files are in the blog source and included directly into this page for these snippets. The files/ directory at the root of the repository contains these entries.

monitor/init.lua
monitor/init.sh
monitor/init.yaml
status.yaml
values.yaml
version.yaml

I can point DOMAIN at localhost to run against the nikola serve instance that's running while I write or run without the override, which defaults to polling from the website. That's functional but it's still assuming there's a shell for running everything so I still need one more layer of abstraction to glue it all together. And there's a weird hidden hardcoded URL in there still, but don't look too much at that. Or do, you made it this far!

#!/bin/sh

python -m execvm.ysh -C files files/monitor/init.yaml

ursula

Zeus is on the loose, an unstoppable frenzy of creative force! Come June I'll have an entire nursery to celebrate me and chastise my absence in July. ytree needed a witchy aunt, ursula. I could produce tarchives by authoring YAML directly or tapping into the python APIs, both covered in the last post. "Authoring" could also mean templating, which I'd rather not do. Or promote! So ursula implements a lua-driven CLI for dynamic generation. Here's the same example from before, a static set of resource definitions.

--- !ytree/tar/dir/v1
name: ./foo

--- !ytree/tar/file/v1
name: ./foo/bar
contents: !ytree/res/file/v1
  path: files/ytree/example.yaml

--- !ytree/tar/file/v1
name: ./hello/world
contents: !ytree/res/buffer/v1
  contents: !!binary aGVsbG8sIHdvcmxk

--- !ytree/tar/file/v1
name: ./hello/world
contents: !ytree/res/string/v1
  value: hello, world

Here's a lua script that produces the same document stream. The ytree package being required is implemented in python and exposed to the runtime. I wrapped the ytree.tar resource constructors behind the ursula.fs endpoints so that each time one is declared in lua I simply serialize the object.

local ursula = require("ursula")
local ytree = require("ytree")

return function (values)

   ursula.fs.dir {
      name = "./foo",
   }

   ursula.fs.file {
      name = "./foo/bar",
      contents = ytree.res.file "files/ytree/example.yaml"
   }

   ursula.fs.file {
      name = "./hello/world",
      contents = ytree.res.buffer "aGVsbG8sIHdvcmxk"
   }

   ursula.fs.file {
      name = "./hello/world",
      contents = ytree.res.string "hello, world"
   }

end

If you had that in a file named files/ursula/example.lua then you could run it like this. I truncated the output here to remove the default members like uid for brevity.

$ python -m dynastyf.ursula files.ursula.example

--- !ytree/tar/dir/v1
name: ./foo
--- !ytree/tar/file/v1
name: ./foo/bar
contents: !ytree/res/file/v1
  path: files/ytree/example.yaml
--- !ytree/tar/file/v1
name: ./hello/world
contents: !ytree/res/buffer/v1
  contents: !!binary |
    aGVsbG8sIHdvcmxk
--- !ytree/tar/file/v1
name: ./hello/world
contents: !ytree/res/string/v1
  value: hello, world

At this point you could save the output or pipe it through ytree.tar to produce an actual tarchive, which was true for the static file to begin with, but this stream is produced on the fly.

$ python -m dynastyf.ursula files.ursula.example \
      | python -m ytree.tar -c \
      | tar -tvf -

drwxr-x--- 0/0               0 1969-12-31 19:00 ./foo/
-rw-r----- 0/0             346 1969-12-31 19:00 ./foo/bar
-rw-r----- 0/0              12 1969-12-31 19:00 ./hello/world
-rw-r----- 0/0              12 1969-12-31 19:00 ./hello/world

I don't leverage it in that example but you can see that the function returned by the module takes one argument, values, which ursula constructs by merging dictionaries loaded from yet more YAML documents. Here's an example of a script that looks up a name from that table (and does a little templating, as a treat).

local ursula = require("ursula")
local ytree = require("ytree")

return function (values)

   ursula.fs.file {
      name = "foo",
      contents = ytree.res.string(
         string.format("%s, %s", values.greeting, values.name)
      )
   }

end
# foo.yaml
greeting: hello
name: foo
$ python -m dynastyf.ursula \
         --values files/ursula/foo.yaml \
         files.ursula.hello
--- !ytree/tar/file/v1
name: foo
contents: !ytree/res/string/v1
  value: hello, foo

Values are merged top down (if there are multiple documents in one file) and left to right (if there are multiple files). Here's a second file to finish the example. I only override the name, so the greeting from foo.yaml will simply pass through.

# bar.yaml
name: bar
$ python -m dynastyf.ursula \
         --values files/ursula/foo.yaml \
         --values files/ursula/bar.yaml \
         files.ursula.hello
--- !ytree/tar/file/v1
name: foo
contents: !ytree/res/string/v1
  value: hello, bar