valis / Understanding the system
The DSMR Constellation: Architecture of Record & SDLC Orientation
This is the canonical responsibility map for the whole constellation of repos that make up the DSMR system. It exists so that a change always lands in the right repo and the right layer, and so that a reader (developer or agent) who is unsure where a piece of behaviour belongs has one authoritative place to settle it.
Read this first, before placing any new behaviour. If a change seems to put protocol service logic into the substrate, or sockets into the protocol library, or answer semantics anywhere but the service module, stop. It is against this document, and this document is the architecture of record. (For valis's own internal shape see ARCHITECTURE.org; this file is the layer above that: how the repos divide the world.)
The one-sentence shape
A privileged agent steers every designated port's traffic into valis, the userland Lisp machine that holds the operator's data, the object-capability system, and every process not delegated to a sister. valis multiplexes each connection to a registered service module; each module speaks its protocol through a shared protocol-definition library and answers from valis's data. No layer reaches across another's responsibility.
The actors
| Actor | One responsibility | Repo |
|---|---|---|
| fulcrum | Privileged host-agent. Runs the eBPF sk_lookup steering, and does not bind the steered ports - not binding them is what makes sk_lookup a selection rather than a proxy. valis sends fulcrum the listening descriptor it owns, once, over SCM_RIGHTS; once the sockmap carries it the kernel selects valis's socket directly and fulcrum is off the data path. Separately it does bind :53 UDP+TCP, and optionally the owner port, inside the namespace before the privilege drop, and valis inherits those descriptors - socket activation, whose descriptor travels the opposite way from the steer. Also owns the default-deny ruleset that is the real public-surface control (the steer widens, the firewall narrows), the namespace and public interface, and the privilege drop. Owns privileged inbound transport, and (at the same host boundary) the host-local enrolment intake: a root-only, AF_LOCAL, never-network-reachable channel that delivers operator-equivalent enrolment material up to valis, mirroring the inherited-descriptor handoff. It stamps that material with an un-forgeable host-local provenance and owns only that channel: no auth-mint policy. |
fulcrum |
| Whistler | Compiles the eBPF sk_lookup steering program from Lisp. Produces what fulcrum runs. |
Whistler |
| valis | The heart of the Lisp machine being reified, and the default home for every process not delegated to a sister below. Owns the object-capability (OCAP) system; manages all of the operator's data (the ndb - zones, identity, operator-state - is one axis of it); and multiplexes that data to the network service modules (the service-module registry + connection dispatch, over a capability-gated 9P namespace). Governs access and holds data and capabilities; holds no protocol service logic. Is the fail-closed admitter and dispatcher of the :53 descriptors it inherits from fulcrum into the serving path, and sends its own listening descriptor to fulcrum over SCM_RIGHTS for the steer, and owns the transfer-peer allowlist as capability-mediated operator-state together with the transfer authorizer that gates who may pull a zone. |
valis |
| service modules | One per protocol service. Each owns the transport for its connections (inbound fd dispatched by valis, which either inherited it from fulcrum or owns the socket the kernel selected; outbound via valis's dial seam), drives c3po's codec client over that transport, and implements the service logic of its protocol. Registered into valis. | per protocol |
| runciter | The DNS service module, the logical nameserver. The canonical example of a service module. Owns nameserver answer logic (authority, RFC 2308 negatives, CNAME chase, wildcard synthesis), resolver policy, BIND zone-file parsing, the DNS resource-record/rdata model, and the in-memory serving index it builds from valis's data and keeps current by pull. Also serves zone transfer (emitting a zone only after authorizing the requesting peer, and sending NOTIFY when the serial advances) and drives outbound resolution over an authenticated TLS stream (DNS-over-TLS). Reads zone data from valis's ndb; speaks the wire through c3po's codec. ⚠ Every one of those outbound paths (the transfer pull, the NOTIFY it sends, the resolution it drives) originates through valis's dial seam: runciter composes and signs, valis carries the bytes. It opens no socket and binds no source address (rule 3). | runciter |
| c3po | Protocol definitions + transport-agnostic codec clients. Encode/decode/framing for DNS, HTTP, SMTP, Gopher, … over an injected stream. The DNS codec now also frames zone transfer (AXFR/NOTIFY) and the DNS-over-TLS wire, still reading and writing an injected stream and still owning no socket. Never binds or owns a socket, never serves a port, never holds service logic or answer semantics. The library every service module speaks through. Also homes the multiformats leaf, the generic base-N codec layer (base58 / unsigned-varint / multicodec-prefixing), a standalone package with no protocol-module or iolib dependency, that higher marshalling layers build on. |
c3po |
| hekate | Crypto-free marshalling, the Plan 9 libauthsrv analog. Owns the structure↔bytes wire codecs for identity and capability: the did:key codec, the capability-token wire codec, and the capability-name wire codec. Each lays out canonical bytes alongside an opaque signature it never computes: mercer signs, valis verifies and attenuates, ubik decodes. Depends on the c3po multiformats leaf for the base58/varint mechanics; touches no crypto and no socket, and never reaches into valis internals. A pure library, and its own repo only because it earns it: ≥2 modules already link it. |
hekate |
| mercer | The factotum / credential authority. Sole holder of key material; the transport cipher and per-session sealing; certificate custody and ACME lifecycle. Supplies the authenticated TLS transport (the DNS-over-TLS stream a resolver rides, fail-closed on any certificate-verification failure) and holds the TSIG (HMAC-SHA256) transfer-authentication keys, signing and verifying the digest that gates zone transfer and NOTIFY. | mercer |
| seven | The 9P2000 library, the fabric and bus transport valis's namespace is served over. | seven |
| ubik | The owner-facing viewer, a CLIM application (McCLIM over the clim-clog backend) that mounts valis's owner-proven management view as a 9P client and renders each namespace axis (/pub /id /cap /edge /bus) as live, operable presentations: mint/attenuate/delegate/revoke a capability, publish an item, watch the edge, enrol or retire a key. A client, never in the serving path; it holds no authority the owner's key did not grant, names only what its session mounts, and drives the substrate solely through the same ctl / verify file operations every module uses. Depends on seven, mercer + hekate, never on valis internals. |
ubik |
| dsmr | The private Quicklisp distribution manager. Pins the constellation's whole dependency closure to exact commits in local checkouts and generates a valid Quicklisp dist from them: a tarball per release plus the distinfo / releases.txt / systems.txt indexes, read back and structurally validated before anything is served. The indexes come from walking the real ASDF graph out of the delivery roots, so package-inferred sub-systems map to their releases and the dist carries what a delivered image needs to run and what a test harness needs to run a suite against it. A subscriber installs by pointing a Quicklisp client at one stable pointer, which publish rewrites as it replaces the serving host's docroot with the validated tree; publication is fail-closed on the audit, refusing a tree whose verdict is missing, damaged, failing, or recorded for a different dist or version. Owns reproducible provisioning and none of the source it ships: trees are pinned, packaged and pin-audited here, never authored or patched here, and the delivery app's own source is walked to build the graph and then withheld, as is any release held back from serving. Holds no protocol logic, no key material, no capability, and sits in no serving path. Names no deployment internally and administers no host: serving host, docroot and dist name come only from a flag, the persisted store or an environment seed. |
dsmr |
valis is the centre; the sisters are delegations out of it. Everything not named in a row above lives in valis: the rows are responsibilities split off from the Lisp machine (privileged steering, protocol definitions, a protocol's service, key custody, the 9P fabric), each because it earns its own boundary. When in doubt about where a non-protocol process belongs, it belongs in valis; a sister is the exception that must justify itself.
ubik is a different kind of member: a consumer, not a delegation. Where fulcrum/runciter/c3po/mercer/seven are responsibilities carved out of the substrate, ubik sits outside and above it, an owner-facing client that only ever sees what the owner's key mounts into its session, exactly as any other 9P viewer would, with no privileged side-channel. Sovereignty is the absence of a name, and ubik does not get to cheat it.
dsmr is a third kind of member: supply, beside the constellation rather than inside it. It is neither a responsibility carved out of the substrate nor a client of it. Nothing it produces serves a request, and it holds no part of the operator's data. What it owns is the constellation's dependency closure - what every repository here is to be built from - which is why its boundary is drawn in terms of what it pins and publishes rather than what it answers. ⚠ Its absence would stop the constellation being reproducible, never stop it running - the opposite failure from every row that answers a request, and the reason it is easy to leave out of a map like this one. ⇒ A change belongs here when it is about pinning or shipping what the other repos are built from, never about what they do: source is authored in the repo that owns it and only packaged here.
The layering
the wire (a designated TCP/UDP port)
│
┌─────────────────────────────────────┐
│ fulcrum: eBPF sk_lookup steering. │ privileged
│ Does NOT bind the steered port. │ (Whistler compiles the program)
│ Binds :53 and the owner port only. │
└─────────────────────────────────────┘
▲ valis's LISTENING fd, once, over SCM_RIGHTS
│ (:53 travels the other way: fulcrum binds, valis inherits)
│
│ then the kernel selects valis's socket directly
┌─────────────────────────────────────┐
│ valis - substrate + ndb DATA │ userland
│ • connection dispatch (9P/seven) │
│ • service-module registry + seams │
│ • authoritative zone/operator data │
│ (no serving cache, no answers) │
└─────────────────────────────────────┘
│ dispatch by port/protocol
┌─────────────────────────────────────┐
│ service module (e.g. runciter = DNS) │ the protocol's service
│ • owns this connection's transport │
│ • SERVICE logic (answers, policy) │
│ • in-memory serving index, pull-fed │
│ • reads valis ndb data via a seam │
└─────────────────────────────────────┘
│ encode/decode over an injected stream
┌─────────────────────────────────────┐
│ c3po - protocol definition + codec │ pure wire framing
│ client (no socket, no logic) │
└─────────────────────────────────────┘
mercer (keys/cipher/custody) and seven (9P fabric) are cross-cutting, consumed by valis.
Plan 9 lineage
The split mirrors Plan 9 / 9front, which is the design ground:
- valis = ndb, the network database. It holds the facts (zones, hosts, services, operator state) and answers data questions. It does not interpret a protocol.
- runciter = ndb/dns, the logical nameserver that reads ndb and applies nameserver semantics: authority, negative-answer composition, CNAME, wildcards, resolver policy. It holds the in-memory serving index built from that data; valis keeps no serving cache.
- c3po = the protocol grammar/codec: how a DNS message (or HTTP request, …) is laid out on the wire. Definition, not service.
- fulcrum = the privileged network front: what binds the ports and steers traffic into userland.
- hekate = libauthsrv, Plan 9's dedicated auth-marshalling library, which lays out the wire form of authentication structures and is linked by factotum rather than embedded in it. hekate is its cleaner descendant: because our capability token is signed, not encrypted, she marshals structure to bytes and never touches crypto at all: every linker composes her instead of hand-copying the formats.
- ubik = acme / rio, the interactive viewer onto the namespace, not a new model of the world. It renders and operates what valis already exposes and holds no authority of its own.
Canonical flows
Inbound: serving DNS on :53
- fulcrum accepts/steers the
:53connection's fd to valis. - valis dispatches the connection to the registered DNS service module (runciter).
- runciter drives c3po's DNS codec client over the connection stream to decode the query.
- runciter computes the authoritative answer (RFC 2308 NXDOMAIN/NODATA/NOERROR, apex SOA on negatives, CNAME chase, wildcard synthesis) over valis's ndb zone data (read through the data seam valis implements).
- runciter encodes the response through c3po's codec and returns it out through valis → fulcrum.
The nameserver (runciter) is in the serving path. c3po frames bytes; it does not decide answers.
Outbound: resolving a recipient MX
- runciter (resolver policy) obtains mercer's authenticated TLS stream (the
DNS-over-TLS transport) and drives c3po's codec over it; a bare outbound socket via
iolibis the non-TLS fallback. - It drives c3po's DNS codec client over that transport to query upstream.
- It applies resolver policy (MX preference, budget, TTL cache, temperror/permerror) and returns preference-sorted targets, never losing mail on a transient failure.
- A substrate consumer (e.g. valis's
/mailoutbound drain) calls runciter for this; valis itself holds no resolver logic.
Zone transfer: serving a secondary (AXFR/NOTIFY)
Enrolling a secondary (valis zone secondary) mints a TSIG key into mercer custody,
records the (zone, peer, key-name) allowlist row and the also-notify recipient, and
prints the ready-to-paste BIND snippet.
⚠ This section is the shape of the exchange, not evidence that it verifies end to end. It describes which repo authorizes and which emits. The signing itself lives on the far side of the seam, in mercer, and whether a signed response verifies against a real secondary is a property of that crossing rather than of this map. Do not read this section as covering that half. TODO.org records what has actually been driven.
The shape:
- valis admits and dispatches the transfer connection into the serving path, the same
fail-closed admission that guards
:53. - runciter authorizes the requesting peer against valis's capability-mediated transfer-peer allowlist - TSIG-verified through mercer (HMAC-SHA256), and serial-monotonic so a stale serial never replaces a fresh one - emitting the zone only once the peer is authorized.
- runciter emits the zone's records through c3po's transfer framing (AXFR), out through valis.
- On a committed serial advance, runciter NOTIFYs the recipient set so each secondary knows to pull the fresh zone.
Authorization precedes emission: an unauthorized or TSIG-unverified peer is refused before a single record leaves.
Responsibility boundaries (the rules)
- Protocol service logic lives only in the service module. Answer semantics, resolver policy, protocol state machines: runciter for DNS, the HTTP module for HTTP, etc. Never in valis (substrate) and never in c3po (codec).
- Authoritative data lives only in valis (the ndb). Service modules read it through a
seam valis implements (for DNS, runciter authors the
zone-data-sourceread protocol and valis implements it over PostgreSQL). A module never owns the authoritative data. Transport ownership: privileged inbound = fulcrum (port bind + steer). Everything client-based or outbound originates through valis's dial seam, without exception: a service module composes and interprets, valis carries the bytes. A module opens no socket of its own, binds no source address, and resolves no name on the way out. Where a connection must be authenticated, the (DNS-over-TLS) outbound stream is supplied by mercer over that same seam and the module drives its codec across it. c3po never owns a socket: it operates on an injected stream. valis routes connections but does not interpret their bytes.
This is settled architecture, not a preference, and the reasoning is written up in full in outbound-dial.org. The short form: a capability model with several ways out is decorative. valis is built to run code its operator did not author, so a subsystem that decides for itself how to reach the network is a second door, and hosted code reaching the network however it likes cannot be found by reading the tree at all. The seam's job is not to forbid the shortcut but to leave it nothing to offer.
⚠ The trap is that every step toward a second door is locally reasonable. The founding defect was valis's own notify-the-secondaries path: a socket opened in the file, names resolved with a library already close to hand, and passing tests. On a live node inside a network namespace it could resolve nothing, and it surfaced as a complaint about polling a descriptor, so nothing in the failure said the word "name".
- c3po is definition + transport-agnostic codec client. It frames what the service module hands it and parses what the module reads; it derives no policy and composes no answer. (Where a codec needs the typed data model or grammar - e.g. the DNS resource-record model, rdata text↔typed, name canonicalization - c3po delegates to the owning service module; today c3po's DNS codec calls into runciter for exactly these.)
- Access is gated by valis's OCAP system; keys live only in mercer. valis owns the object-capability system: capability tokens and names mediate every access to the operator's data. The key material that backs identity and the transport cipher live only in mercer. A capability is not a key: valis decides what may be reached, mercer holds what proves who you are. No other repo holds either.
Adding a new protocol service (the pattern)
- Define the protocol in c3po: a codec client (encode/decode/framing) over an injected stream. No sockets, no service logic.
- Build the service module: owns its transport (inbound fd from valis dispatch; outbound
through valis's dial seam, never a socket of its own), drives c3po's codec, implements the
service logic, reads any authoritative data it needs through a valis-implemented seam. The
shape that satisfies this is an injected connector: the module takes a function that opens
the stream and a source address to originate from, and returns a refusal when it is handed
neither. runciter's
transfer-sink.lispandtransfer-notify.lispare the worked examples. - Register the module into valis's service seam so valis dispatches the protocol's connections to it.
- Open the port at the host agent. fulcrum's catchall
sk_lookupfans every TCP/UDP port on the unit's IP onto valis's single socket, so registration in valis (step 3) is what dispatches the protocol, but the reachable public surface is bounded separately by fulcrum's default-deny firewall, which admits only the designated ports. A new public port is therefore a privileged host-agent change (open it in the firewall allowlist), and that allowlist is fulcrum's alone, never derived from a valis module's declaration, so registering a module can never open a public port. A UDP protocol needs one step more: the steer cannot carry a UDP reply (it must source from the bound socket), so the agent binds the port and hands the descriptor down (socket activation), as:53already does.
runciter (DNS) is the worked example of all four steps.
Anti-patterns (the landmines)
These are the specific confusions that have actually occurred. Each is a boundary violation above; they are called out because they read as plausible until checked against this map.
- Answer semantics in the substrate. Putting NXDOMAIN/NODATA determination, apex-SOA-on- negatives, CNAME chase, or wildcard synthesis in valis. → It belongs in the service module (runciter). valis holds the zone data, not the DNS answer.
- Answer semantics in the codec. Putting RFC 2308 negative composition or negative-cache TTL policy in c3po's handler. → c3po frames the SOA/TTL the nameserver hands it; it does not reason about authority.
- Binding the service seam to the codec, skipping the module. Wiring valis's service seam directly to c3po (or to a substrate-resident lookup), so the service module (runciter) is not in the serving path. → The registered handler is the service module; it drives the codec.
- Sockets/ports in c3po. Giving c3po a
:53serving loop, UDP/TCP listeners, or a socket- owning resolver client. → Inbound transport is fulcrum's; outbound goes through valis's dial seam; c3po only ever sees an injected stream. - A second door out. Any subsystem opening its own outbound socket, binding its own source address, or resolving a name on the way out, however respectable the library and however local the reasoning. → It originates through valis's dial seam. See rule 3 and outbound-dial.org. ⚠ This one is listed because it is the anti-pattern that is hardest to see: the alternative is always nearer to hand, the tests pass, and the failure appears only on a live node, in language that names something other than the network.
- Resolver/service policy in c3po. MX preference, lookup budget, caching. → The module's.
- Duplicate protocol parsers across layers. One grammar/parser, owned by one layer (the DNS master-file/rdata grammar is runciter's; the wire framing is c3po's; they do not overlap).
- Duplicate marshalling across layers (resolved). The identity and capability wire codecs
(
did:key, the token wire, the bearer-name wire) once lived as a valis copy and a hand-copied client-side decoder that could silently drift apart. They now rest once, in hekate, and every linker composes that one codec over a clean API instead of hand-copying it, so valis and its clients decode the identical bytes because they run the identical code. The base-N mechanics beneath them rest once too, in the c3po multiformats leaf. This is the marshalling case of the one-parser-per-layer rule, and the drift hazard it names is closed. - Reducing valis to "seam + controller" with no data-owner role. valis is the ndb; its authoritative-data ownership is a first-class responsibility, not an afterthought.
SDLC notes: how development honours this map
- Each repo runs its own development lifecycle: discuss, plan, execute, verify. Cross-repo work is coordinated between the repos; merges and tags to a repo's default branch stay operator-gated.
- A seam between two repos is fixed before either side builds against it: versioned and fail-closed (the symmetric idiom used by valis's service-adapter seam and runciter's resolve-over-wire consumer seam). The freeze is the contract; a seam evolves additively, never by breaking a version already in use.
- When unsure where a piece belongs, this document settles it. If it cannot, the boundary is underspecified. Surface it and amend this document rather than drifting past it. This file is amended deliberately, not drifted.
Cross-references
- ARCHITECTURE.org - valis's internal architecture (the layer below this map).
- SEAM-CONTRACTS.org - the versioned interface contract for the cross-repo seams, A through F: runciter↔c3po, c3po↔mercer, the valis↔mercer server-TLS termination, valis↔ACME zone-write, consumer↔hekate marshalling, consumer↔c3po multiformats, and the valis outbound connector the sisters dial through.
- deployment/host-deployment-contract.org - how a valis node comes up on a real host: the fulcrum-privileged (below) → valis-condense (above) bring-up, framed as deploy == condense-from-genesis. The privileged substrate is fulcrum's; the condense that stands the node up is valis's.
- proving-ground/ - the migration-thesis regression harness (deploy / evacuate / restore over one mechanism) that validates that bring-up against a two-location Debian topology.
- TODO.org - where valis stands and the delivery order.
- The sister repos carry this same map in their own
docs/per the shared documentation pattern.