valis / Understanding the system

The evaluation axis, a design scoping note

Status: the design of record for the evaluation axis. Written 2026-07-26, out of draft 2026-08-18.

This note scopes how the owner of a valis node is meant to reach that node's live image: to inspect it, patch it, and repair it while it runs, which is one of the reasons the substrate is written in Common Lisp at all.

This is the scoping step, and it exists because the design was worked out in conversation and would otherwise be re-derived from scratch by whoever picks the work up. Re-deriving it costs more than writing it down.

How much of this has been built is a question for TODO.org, which is where the build status of every axis lives. I keep it out of here on purpose: a design record that also reports progress becomes wrong the day the progress changes, and it becomes wrong silently, because nothing about a stale sentence looks stale.

The placement question is settled and is recorded here as a decision, with the shapes that were rejected and the reason each was rejected. Two further questions were carried as open. One is still open: whether revocation must interrupt an evaluation already in flight, or only prevent the next one. The other has since been answered and is marked ANSWERED in place, with the original question and its trades kept beneath the answer, because the reasoning still repays reading.

What the axis is for, and what it must not enable

The power at stake is real and it is the point: self-healing, live updates, and fixing a node while it serves. The owner of a sovereign node should be able to reach into it the way a developer reaches into a running image, without taking the node down and without a second authority sitting beside the capability model.

The power at stake is also the most consequential surface in the system, and it must be possible to hand out some authority over a node without handing out this one. The concrete case is a delegate who may change what a node publishes while being unable to change what a node is.

The hierarchy, in the classical terms

Set out in the architecture of a von Neumann machine, the thing being argued about stops being subtle. Such a machine is layered: hardware at the bottom, a kernel that owns it, a programming ABI through which everything above is compelled to ask, and a userland that lives above that boundary. A process does not reach into kernel memory and politely decline to abuse it. It cannot reach at all. It asks, and the asking is the ABI. Every guarantee the machine offers its users rests on that compulsion, not on the good conduct of programs.

In valis, the Lisp runtime is the operating system kernel. The work so far has been the definition of the low level atomic units, which is to say the kernel and its device drivers. The capability model is that machine's programming ABI: the interface through which every other party is compelled to ask, and the place where designation, rights, expiry and revocation are made to mean something.

Anyone who can load arbitrary code into the kernel of an operating system, without being forced through its programming ABI, owns all the authority there is to possess. This is not a risk assessment and not a claim about likely exploits. It follows by inference from where the code runs. There is no authority in the machine that is not reachable from inside the kernel, so a party evaluating there holds every bit of it at once, including the authority that would otherwise be used to constrain them, and including the ability to make their holding it unobservable.

Two consequences run through the rest of this note.

  • A restricted evaluator is a category error, not a weak design. It is an attempt to make a kernel module loader safe by inspecting the module. The loader is the hazard; what is loaded is beside the point.
  • Attenuation has no meaning at this layer, so designation carries the whole boundary. One cannot hold less of a kernel. One can only decide who is permitted to name the door, which is why the placement question in this note is treated as the entire security question rather than a detail of it.

Why the ordinary delegation path does not reach this

The substrate already has a full capability system, and the obvious question is why the axis is not simply another grant handed out through it. The mechanism is worth stating precisely, because the answer turns on its details.

This is not a sketch. The mechanism is built, and the description below is of working code rather than of an intention.

Authority arrives in two layers. A peer authenticates its channel with a NoiseXX handshake, and the completed proof is converted, at a single named seam, into a principal that everything above the seam reads (src/identity/authenticator.lisp). Nothing above that seam knows what proof produced it, which is what lets a future proof be added without disturbing anything above. Note what the handshake actually establishes: it proves possession of a transport key, so the principal a wire session carries is the transport identity, not the owner's durable signing identity.

Authority over objects is then carried in capability tokens (src/capability/token.lisp): an issuer DID, an audience DID, mount grants pairing a namespace designation with a rights set, an expiry, a nonce, and an Ed25519 signature, with the proof chain back to the owner's root token riding inline so that verification needs no external store and works offline. The verifier (src/capability/verifier.lisp) walks that chain leaf to root and is fail closed: every condition raised during checking is returned as a refusal with a reason, never as an escaped condition. Each delegation link must satisfy three structural conditions, and a fourth gate applies across the link: the child's designation lies within the parent's designation subtree, the child's rights are a subset of the parent's, the parent itself carries the delegate right, and separately the child must not outlive the parent. Expiry and revocation are then checked on every token in the chain.

That machinery is sound and it is sufficient for everything it was built for. It is not sufficient for this, for three reasons that compound.

  1. Three of the four conditions have nothing to bite on. Rights subsetting, expiry attenuation and re-delegation control all express "less than the parent". Evaluation has no "less than". There is no attenuated evaluation, because evaluation reaches the reader, load, every macro expander and the debugger. Only the second condition, designation, still does real work, which is why designation is where this design puts the boundary and why the axis must be a sibling of the publication axis rather than a child of it.
  2. Evaluation runs beneath the layer that enforces the conditions. The four checks are performed by a verifier, and the verifier is code in the image. A grantee who can evaluate does not need to defeat the chain walk; they can redefine it, read the revocation store, or mint a root token directly. The guarantees hold for grants that operate through the model and say nothing about a grant that operates underneath it.
  3. The exterior edge is part of what the axis exists to repair. Delegation over the terminus presumes a coherent instance: an edge that is listening, a capability layer that verifies, a namespace that mounts. The cases that motivate the axis are exactly the cases where one of those is the broken thing, or where an operator is still building an instance into the program they want. Authority is needed before the system that grants authority works, and a path that runs through the thing being repaired cannot repair it.

⇒ Points 1 and 2 say the ordinary path would not contain this authority even when it works. Point 3 says it is not available precisely when this authority is most needed. Neither is an argument for placing the axis outside the capability model, and the section on why the owner's reach is total explains why placing it outside would be the worse error. They are an argument that the axis is a different kind of object from every other grant, and must be designated, placed and reasoned about as one.

The boundary is designation, not a restricted evaluator

Arbitrary evaluation in this image reaches every symbol, every package, the capability layer itself, the revocation store, and any key material resident in memory. A restricted evaluator, a safe subset, an allow-list of forms: each of these is a filter that resembles a boundary without being one, because evaluation is not attenuable from the inside. The reader, load, every macro expander and the debugger all reach evaluation, so a filter placed above one entry point is a fence with no field behind it.

The boundary is therefore the designation, and the substrate already supplies it. A capability's designation is a path, and the rule that a delegate's designation must extend its grantor's is what makes the axis unreachable to a delegate who holds other rights. The axis is a sibling of the publication axis and never a child of it, so a delegate holding publication rights alone cannot name the axis at all. It is not refused the axis so much as unable to see it, which is the same enforcement model the rest of the namespace uses: sovereignty is the absence of a name.

This is what the design buys, stated as plainly as it can be: a delegate may change what a node PUBLISHES while being unable to change what a node IS. Every other decision in this note is downstream of preserving that property.

Several control files in this tree carry a comment saying that a control file must not become an evaluation surface, and the file shape they describe holds to it: a fixed grammar, recognized verbs only, no reader involved, unknown verbs ignored without a signal. This document argues against that sentence in exactly one place and nowhere else. The axis described here is an evaluation surface, deliberately, and that is precisely why it must never be reachable from a grant that reaches an ordinary control file. The two are different objects with different placement rules, and conflating them is the failure this note exists to prevent.

Why this is root access to an enclave, and why that is the right shape

A serving node is a confined thing by construction. It runs in its own network namespace, its egress is an allowlist, and the confinement work is continuous and unfinished. It is reasonable to call it a de facto secure enclave, and the question this section answers is why the owner is then given unrestricted reach into it.

The confinement is outward. The owner's reach inward is deliberately total.

Those are different directions and they are not in tension. Confinement governs what the node may reach out to and what may reach in from the network. It does not govern what the person who owns the node may do with it, and it was never intended to. An enclave that also confined its owner would be a machine held against the person who runs it.

Confining the operator is the wrong problem, not a hard one. Sandboxing a Common Lisp system that someone has physical access to is not a difficult engineering task; it is a task that should not be attempted. It is their valis and their data, and they are entitled to break it. Whoever invokes a development listener keeps the shrapnel. Reaching a live image is an intended affordance of this system: the owner holding their own Lisp machine open at the code level is a goal, not a leak to be designed out.

The authority already exists at the level of the host, which is why ssh is the

honest frame

Host control is the foundation stone of the entire trust model. Everything above it assumes it. Whoever holds the machine already holds every key resident in memory, every capability the image can mint, and the image itself.

An owner reaching a live image over a loopback listener therefore gains no authority they did not already have. This is why the interim is ordinary machine access, ssh or another low level tunnel, and why that is sufficient rather than a compromise being tolerated: the operator is already on the host in order to run the node at all. The same reasoning scoped operator-facing node documentation to loopback, and the owner management port stays firewall closed until its credential legs are in place, with the local path serving in the meantime. The axis does not introduce this power. The power is already there, in the hands of whoever holds the machine, and the axis is the attempt to give it a name, an expiry, and a revocation instead of leaving it implicit.

The line where that argument stops holding, which is the whole hazard

⛔ The reasoning above holds only while reach requires already holding the host. A granted, network reachable evaluation capability is categorically unlike every neighbour it sits among. /active/zone-write grants a bounded operation on a known object. An evaluation grant is not one capability: it is the ability to mint every other one, to read the revocation store, and to revoke the revoker. It converts "possession of the machine", which is a fact that ends when the machine changes hands, into a transferable secret that outlives possession.

So the hazard is not today's binding and never was. It is the moment the axis becomes something a grantor can hand to someone who is not on the machine. That grant must be documented as a hazard where a grantor will see it, at the point of granting, and not in a source comment that only a maintainer reads.

This is the same rule that decided the bootstrap credential

A break-glass key compiled into the artifact was rejected for three reasons, and the deepest was that a thing sitting beside the capability model rather than inside it can never be hardened away: it acquires dependents, and then removing it is a breaking change rather than a decision. The axis is the same question wearing different clothes. Placed beside the model it would be a second authority, unrevocable in practice. Placed inside it, it is a grant like any other, and everything the model already knows how to do applies to it.

Decision (2026-07-26): the axis is capability-backed and never canonical

Chosen: the axis is established through the second phase of namespace resolution, by presenting a grant, and is not present in the base frame at all.

It therefore carries a grant hash. That single property is what makes the rest of the model work on it:

  • ordinary revocation reaches it on its own, without touching anything else the session holds;
  • an expiry applies to it, so a grant that is forgotten still ends;
  • it can be withheld from a delegate who legitimately holds other rights, which is the property the previous section named.

The architecture of record already carries this conclusion, in Why the owner's reach into a live image cannot be a canonical axis, and this note extends it rather than restating it. The base-frame behaviour it depends on is described in Base-namespace policy and the sweep behaviour in Revocation eviction and live sessions.

Rejected: a canonical axis in the owner's base frame

An owner who attaches receives the full canonical frame, and entries placed there by base policy carry no grant hash. Both eviction sweeps pass over such entries by design, so the only lever that removes one is a fence, and a fence takes the whole frame with it.

That is the exact defect the session fencing work existed to escape, reappearing on the most consequential surface in the system. Rejecting it is not a preference; a base-frame evaluation axis would be an authority that could be withdrawn only by withdrawing everything, which is another way of saying it could not be withdrawn in practice.

Rejected: in the base frame, but individually unmountable through the control door

This shape is rejected for the same reason plus one more. It is still base policy, so it still carries no grant hash and ordinary revocation still cannot reach it. And a self-narrowing unmount is the session's own act: it gives the holder a way to drop the axis and gives the grantor no way to take it away. That is the wrong way round for an authority of this size.

The consequence that makes the decision worth writing down

Only a capability-backed axis makes "revocation reached the axis" available as evidence at all. A base-policy entry has no hash for a sweep to match, so there is nothing to observe and nothing to assert. The placement decision is therefore also a decision about what can ever be proven, which is the subject of a later section.

The file shape: do the work in the read, not the write

This is not invention. It is the discipline an authentication agent in the Plan 9 tradition states in its own words, at sys/src/cmd/auth/factotum/rpc.c in the 9front tree: paired write and read cycles on one file, where the write only sets up the request and the read tries to execute it, and therefore the read blocks. The write bounds-checks the count, refuses if a request is already pending on this handle, copies the bytes, and marks the request pending. The read refuses when nothing is pending, executes, and clears the pending mark on the way out. The whole per-handle state machine is two lines of code, and it is what makes the file safe against interleaved use.

The control-file shape this tree already uses does not survive here, for two reasons that are both about the write:

  1. A write returns a byte count and nothing else. An evaluation that takes three seconds would block the write for three seconds, and a write is not what a client can interrupt the way an editor's interrupt key needs to interrupt.
  2. A whole-buffer read does not survive the negotiated message size. A sealed session negotiates 8192 bytes, and a first long backtrace exceeds that comfortably. This tree has already paid for that lesson once, in a publication body that kept only its final fragment because the accumulation discipline was missing.

What the tree does already have is the right shape, at the identity axis: a per-handle, stateful, multi-round-trip conversation over one file, with the protocol selected on the first write. The evaluation axis is a second instance of a pattern this substrate already carries, not a new one.

One conversation or several, and why the answer is designation again

Several, cloned per session.

The idiom is the one the Plan 9 network device uses, at sys/src/9/ip/devip.c in the 9front tree, and the interesting part is not what a first reading expects. Opening the clone file mutates the opening handle's own identifier so that the handle becomes the new conversation's control file. Allocation and binding are one act, and there is no window in which a conversation exists unowned. The conversation directory then holds its own ctl, data, err and status files, with the error and the status kept as separate files rather than as encodings inside the data stream.

A single shared file forces every distinguishing fact about a session, which package it evaluates in, which thread it runs on, whether it is currently evaluating, and how to stop it, into either a naming convention or a verb grammar invented here. Plan 9 answered that question with a directory per conversation, in the kernel, for its highest-traffic service. The answer is about designation rather than convenience: a clone directory gives each session its own name under the axis, so a per-session control file is a name and not a parameter.

This would be the tree's first use of the clone idiom. Both the capability axis and the identity axis mint per-handle state on a fixed file instead, which works for them because a conversation there is short and unnamed. An evaluation session is neither.

What must be captured at open, and the trap underneath it

Per-open state is minted when the file is opened. That primitive is already established here and is the right one to keep: the conversation object is created in the open, and the transport hands it back on every subsequent read, write and close, so the handle never has to be named or looked up.

The principal resolves only on the session's own thread, inside the dispatch. A backend that parks a read off-thread must capture the principal at that synchronous call site and close over it for later delivery. The warning sign is specific and worth recognizing on sight: an evaluation that works in a unit test and collapses to the anonymous sentinel the moment it runs over a socket.

What the transport already provides, so this design does not rebuild it

The 9P layer beneath valis already carries the parking machinery an interactive service needs, and the design should consume it rather than reinvent it:

  • A read may park off-thread and be cancelled. A backend that returns a cancel handle has genuinely parked; the default makes parking opt-in.
  • A flushed request is answered with an error naming the interruption, rather than dropped. The answering path claims sole ownership of the reply by removing the registration first, so the delivering thread and the flush handler cannot both write to the wire.
  • A clunk sweeps parked reads for that handle without answering them. That is a handle yanked out from under outstanding reads, and it is the one case where dropping is defensible, because the client asked for it and can flush.
  • Teardown drains and cancels everything with no wire writes at all, because the endpoint is closing.

One ordering fact, recorded because it is the kind of claim that gets re-derived wrongly: writing the error for the flushed request before the flush reply is permitted by the protocol's own flush rules, documented at sys/man/5/flush in the 9front tree, and a real client honours a response received before the flush reply as if it had not been flushed.

The evaluation protocol, and a bridge that holds no authority

A Lisp listener needs one bidirectional character stream, not a socket. In the implementation this tree already carries as a development dependency, the connection object holds the socket and the stream in separate slots; the socket slot is consulted only by listen-socket bookkeeping and is never dereferenced as a socket on a live connection; and every read and write of the wire protocol goes through the stream. The multithreaded communication style never reaches the one backend routine that assumes a stream backed by a file descriptor, because that routine is called only from the single-threaded and event-loop styles.

Three costs, stated plainly here rather than discovered later:

  1. The entry points needed are internal symbols. The connection constructor and the request server are not exported; only the listener-shaped entry points are. A bridge built on them is a dependency supported by inspection and not by contract, and an upstream rename breaks it at a distribution bump.
  2. The multithreaded style needs multiprocessing already initialized. A running node always has it, so this is a precondition to assert rather than a problem to solve, but it should be asserted deliberately.
  3. The listener closes the stream it was handed. Ownership must therefore be decided once, under the rule this tree already applies to inherited file descriptors: close only a stream this code created, never one it was handed.

What the bridge must not do

Each prohibition carries its reason, because a prohibition without one is a prohibition that gets argued away.

  • It must not decide whether the caller may evaluate. That decision was made when the axis resolved into the session view. A re-check would be the first per-access guard in the tree, in a system whose entire enforcement model is that out-of-scope objects have no name, and a structural test exists specifically to catch one.
  • It must not call the listener's own client authentication. Authentication already happened, cryptographically, at the attach. Calling it again would put a second and weaker gate in front of the real one.
  • It must not bind a port, own a listener, or be reachable from a global. A bridge reachable from a global is a back door in the designation boundary, since the whole boundary is that nothing can name the axis without a grant.
  • It must not filter, rewrite or inspect forms, for the reason given at the top of this note: the filter would resemble a boundary without being one, and its existence would invite reliance on it.

The client story and the error contract

The editor integration as shipped dials a host and a port. Its connect function reads a host and a port from the minibuffer and opens a network stream with no further parameters, so there is no unix-socket path and no stream-over-process path available to it without patching the client. What a person types is therefore a loopback host and a port on their own machine, and the grant never appears in the editor at all.

That is the correct outcome rather than a limitation to work around. An editor is not a capability holder, and making it one would put an owner grant inside an editor's configuration file. The grant is held by the bridge process, which is a valis-side artifact shaped like the existing owner client verbs: it takes a keyfile and an endpoint, attaches, walks the axis, clones a session, and offers a loopback listener to the editor.

What failure looks like, and where each failure is produced

When What is visible Where it is produced
The grant was never minted The walk of the axis path fails as though the name never existed, because in an unaugmented view it does not the namespace assembler, existing behaviour
The grant was minted but never presented Identical, because the second phase runs only when a capability is written to the control door the namespace assembler
The grant was revoked before the attach A refusal carrying a reason string from the verifier. Every verifier path is fail-closed and returns a reason rather than signalling the capability verifier, through the mount path
The grant was revoked mid-session The mount entry is removed from the live view, so a new walk fails. A handle already open below it does not fail, which is the transport gap named above the live-view eviction sweep
The session was fenced The same visible effect, wider: every mount goes, base policy included the session fence
The session went idle past its bound Identical to a fence, produced lazily by the operation that would have used the view the activity mark, checked on the next operation
An evaluation was interrupted An error naming the interruption, on the flushed request the 9P flush handler

The wire carries an error name and nothing else. A bridge that responds to any of the mid-session rows by simply closing its loopback socket gives the person at the editor "connection closed" and no reason at all, and a contract nothing reads is not a contract. Whether the error name reaches the developer, and by what route, is left undecided here.

Proving a gate, which is harder than proving an absence

A capability-backed axis changes the property that has to be proven. Today the claim about the delivery build is "this symbol does not exist in this image", a negative over the image. With an axis it becomes "no path reaches evaluation without a verified mount directive", a negative over the reachability graph. The second is strictly harder and no single instrument answers it. What follows is the evidence that would actually carry it, named as evidence rather than as a plan.

  1. A port census against the running delivery binary. If evaluation rides the 9P axis there is no listener creation and no port to bind, so the declared ports and nothing else is a direct measurement of the artifact by a capable instrument. This is available today and depends on none of the decisions in this note.
  2. A negative walk over a real socket, from an anonymous attach, from a keyed non-owner attach, and from a delegate holding only publication rights. Three attaches, three failed walks. The third is the one the whole design rests on, because the path-extension rule is what makes the axis unnameable to a publishing delegate, and this is the only evidence that exercises it.
  3. A call-site census over the loaded image, pinned by list and by count. The discipline that makes such a census evidence rather than decoration is already established in this tree: the expected value was watched failing against a deliberately shortened list before the expectation was edited.
  4. Revocation reaching the axis, observed as the name going away in a live view. This is only available because of the placement decision recorded above; it does not exist for a base-policy entry.
  5. The absence of any ambient switch. This is not true today: an environment variable starts a development listener, so that variable's disposition is part of the evidence rather than a detail to tidy up afterwards.

What cannot be proven, said plainly

No evidence available establishes that no path in an image of this size reaches evaluation under some other name. Evaluation is reachable from the reader, from loading, from every macro expander and from the debugger, and an image that could not evaluate could not have been built.

The honest scope of any claim made here is therefore over the designated path: the axis is the only path that offers evaluation as a service to a party outside the image, and everything else that can evaluate is already inside the trust boundary because it is the image itself. That is a real and useful property. A broader claim would be false, and a reader who caught the broader claim would be right to distrust the narrow one too.

Open questions, recorded and not settled

These are stated rather than answered. None of them has a default, and none should be read as leaning. Each will be answered in this document when implementation settles it, not before.

ANSWERED (2026-08-16): a second party gets a second instance, never a confined region of the first

Recorded now because it governs the userland direction and is not answered by anything above. The argument here is about the owner's reach, where the claim that possession already confers everything holds. It does not transfer to a second party.

A Common Lisp image loads every form at the same authority. Packages are an organisational device and not a boundary, the reader reaches evaluation, and the standard library supplies ambient authority in every direction. So the property recorded elsewhere in this system, that a module acts only on capabilities designated to it and holds no ambient authority, is today a discipline observed by the code in this tree. It is not enforced by the runtime, and it does not survive code from anywhere else. Designation decides who may reach the door; it decides nothing about what happens on the other side of it.

⇒ The goal of a person running their own code on their own instance therefore collides with the single authority of the runtime the moment that person is not the owner.

ANSWERED, and not by placement. A second party is given a second instance, never a confined region of the first. An instance that executes but holds no data has an execution context total over itself that confers exactly its outward reach, and that reach is capability-mediated 9P to the instances that do hold the data. The attenuation happens at that boundary, never at the evaluation boundary. The decision of record is Two planes of authority, and why the system is a federation of instances.

The guesses recorded here before that decision were isolation and a hosted evaluator with no ambient authority. Both attempt to manufacture a second context inside one image, which is why neither settled. Kept because the reasoning that rejected them is the reasoning that found the answer.

Must revocation interrupt an evaluation already in flight, or only prevent the next one?

The transport half of this is solved: a parked read can be cancelled and its request answered. An evaluation genuinely running on a worker thread is not a parked read, and the transport cannot reach it. The evaluation protocol carries its own interrupt machinery, but it is thread-directed rather than transport-directed, so reaching it means this substrate reaching into that protocol's internals for a second reason. The trade is between an authority that ends immediately and a dependency on internals that is already uncomfortable at three symbols.

ANSWERED (2026-07-29): the delivery image carries the listener always

Decided 2026-07-29: every valis node carries the listener, and it must be possible to turn off.

The shape the ruling takes. valis/delivery depends on slynk; the resident starts a listener only when VALIS_SLYNK_PORT names a port, so leaving it unset is both the off switch and the default; the resident names no host at all and so takes loopback, while the single entry both it and the --dev path go through refuses a routable host rather than binding it; and tests/dev-slynk-test.lisp covers it. This is the loopback listener referred to elsewhere in this document.

It is not the axis, and the resemblance is the trap. An environment variable read at boot is an ambient switch reached from the host. It carries no grant hash, revocation does not reach it, and it cannot be withheld from a delegate. The evidence list above names this exact condition as an outstanding gap, at item 5. ⇒ This ruling settles the listener and nothing about the axis.

The reasoning that makes this consistent with the rest of this document: a node that cannot be reached is a node that cannot be updated while running, and being updatable while running is the reason the system is written in Lisp at all. An absence guarantee bought that property away on every node in order to hold it on none.

Presence is not access, and that separation is the design. The listener being compiled in does not make it reachable. It listens only when configured to, and what governs reaching it is the capability grant this document already settled, never the question of whether the code is in the image.

What this ruling costs, stated plainly rather than discovered later

It converts an absence guarantee into a gate guarantee, which the section on proving a gate describes as the strictly harder proof. That cost is accepted, not overlooked. The gate is now the thing that must be proven, and an untested gate is worth less than the absence it replaced.

The boot line reports listening, never presence

The requirement was met in the same change as the ruling, which is the point of recording it here: the ruling made a reported condition permanently true, and the field had to move in the same breath.

The composition line reports development-listener-listening, computed by development-listener-listening-p (defined in valis/src/dev/slynk.lisp, called from %log-boot-composition in valis/src/main.lisp, whose comment states that the field reports listening and never presence). The older development-listener-available-p survives, because a caller still needs to know in advance what a start attempt would refuse with, but it no longer stands in for the boot line.

The reasoning is kept because it generalises past this field. A boot line that means nothing is worse than no boot line, because a reader trusts it. When a ruling makes a reported condition permanently true, the field has to move to whatever now varies, in the same change, or the line quietly becomes decoration.

The five sites the ruling falsified, and the claim retired at each

Five sites carried a claim this ruling retires. Each was rewritten with it, and each was checked individually rather than inferred from the change that covered them. The bullets name the site and the claim that was retired there, because a retired claim is history and stays true, where a summary of the replacement wording would go stale the next time anyone edits a docstring for an unrelated reason.

  • src/dev/slynk.lisp's header, which said the production binary was never compiled with slynk and that its absence was a fact about the image rather than a setting anyone could flip.
  • development-listener-available-p's docstring, which claimed a production binary could never be talked out of answering NIL.
  • development-listener-not-compiled-in's docstring, which called itself the expected refusal in a production binary.
  • The Makefile's build comment, which said the shipped binary carries no Slynk.
  • valis.asd's commentary above valis/delivery, which described the separate build as temporary pending this decision.

Keep the practice, which is the part that transfers. Listing the prose a ruling will falsify, at the time the ruling is made, is what let this be verified later instead of discovered by a reader who believed a stale docstring. The list is cheap to write while the reasoning is fresh and nearly impossible to reconstruct afterwards.

ANSWERED (2026-08-18): neither. The axis grants a context, not a right

The question as posed below is malformed, and it is kept because the reasoning under it is still worth reading, not because either option is live. Both limbs sit in the vocabulary of ordinary capability delegation, whose central promise is that a child grant is a subset of its parent. Evaluation has no subset: once a party can evaluate in a context they hold everything in that context by definition, so a restricted evaluator resembles a boundary without being one.

The axis grants a CONTEXT, not a right. The confinement this substrate applies is outward: it governs what a node may reach, never what the party holding that node may do inside it. The decision of record is Two planes of authority, and why the system is a federation of instances.

The parser point below survives the answer. No new bit is being added, so the control door's four-right parser is not part of this choice any more, but an owner who can mint a grant from an evaluation prompt but not through the file interface still has the discipline inverted.

The original question, kept for its reasoning:

The question as it was originally posed: a new right bit, or the write bit on

the axis path?

A new bit is invisible to every token ever minted, which is a real safety property and a real migration cost at the same time. The write bit is familiar, and a future grant will hand it out by habit, which is the failure mode that matters for an authority this size.

The precedent for adding a bit after the fact exists in this tree and is clean. So is its cost, and the cost is already visible: the control door's rights parser still accepts only the original four rights, so one existing bit is mintable from inside the image and not through the file interface. If a new bit is chosen, the parser is part of the choice, because an owner who can mint a grant from an evaluation prompt but not from the file interface has the discipline exactly inverted.

ANSWERED (2026-08-18): the question was posed against the wrong manifest

This asked how the axis is registered in the NAMESPACE manifest, and the axis does not belong there. Module registration is a separate mechanism. It already exists in this tree, and it has the shape the namespace-manifest framing treated as absent.

A module is designated by its content rather than by a name in a frame. The admission predicate in valis/src/plugin/module-admission.lisp asks a single question: does an owner-rooted, unrevoked, unfenced authority grant :admit over exactly this /module/<64-hex-score>? The designation is nothing but that content: a 32-byte score over the module's own sources, rendered as hex. It therefore carries the grant's authority without appearing in any frame, and there is no base-policy entry for a sweep to miss.

The placement decision is met with no manifest entry at all. Admission is a chokepoint in front of the loader, and it has to be: loading a module is its self-registration, so there is no later point at which a refusal could still take effect.

What this does and does not retract. The reasoning in Rejected: a canonical axis in the owner's base frame is about the namespace manifest and does not extend to module registration. The base-policy argument under The consequence that makes the decision worth writing down is untouched and still holds.

The question as it was originally posed: durable manifest, or a synthesized door?

A fabric-synthesized control door is the closer analogue, and it costs no manifest republish on a node whose axis list already comes from its store rather than from the code's default list. That is an attractive property: a node deployed before the axis existed would gain it without a store migration.

⚠ But such a door is synthesized into the owner frame specifically, and the decision recorded above is that the axis is not in the base frame at all. The analogy is therefore weaker than it first appears, and adopting it without noticing would reintroduce the rejected shape through the back door.

On the Lisp Machine lineage, deferred by design and not by judgement

The Lisp Machine lineage has not yet been consulted, and the reason is where this machine currently is rather than anything about the literature's worth.

Work so far has been principally engaged in defining the low level atomic units: the kernel and the device drivers, in the ordinary sense those words carry for any machine. The axis described here is a question about those primitives, which is why it can be settled without the history.

The history becomes richly relevant at the next boundary, and will be consulted there. That boundary is the mapping of a userland onto these primitives: what a person actually inhabits when the substrate is already running. Two bodies of work matter most at that point.

  • Symbolics Genera, for what a fully inhabited Lisp environment offered the person using it, and what that cost and required underneath.
  • Connection Machine Lisp, from the Connection Machine work at Thinking Machines and a principal topic of Danny Hillis' doctoral thesis, for how a Lisp is shaped when the machine beneath it is not one sequential processor.

Do not read this section as a finding that the lineage points elsewhere. An earlier draft offered an impression that it favoured a graphical inspector over a file interface, and reasoned from that impression that acquiring the sources would return little. That was speculation standing where a reading of the sources belongs, and it is withdrawn. Nothing here is evidence about what the lineage says; it is a note about when this project will find out.

Scope: what this design does not cover

Implementing the axis is not in this design. No file, no right bit, no bridge and no client verb is proposed for immediate work here.

Neither is the generic per-session handle revoke that the transport gap named above requires. That is a mechanism with no knowledge of owners, fences or capabilities, and it belongs in the 9P library rather than here.

The placement test that decides this, and that decides where any of this work goes, is one sentence: could another 9P consumer use the mechanism with no valis present? If yes, it belongs in the library. If no, it belongs here.

🄯 Brian O'Reilly <fade@deepsky.com>, 2026