valis / Reference / API reference
Fabric - API reference
Exported surface for the fabric subsystem. Part of the API reference.
Package valis/src/fabric
Conditions
owner-key-required
Signalled fail-closed when start-fabric finds an existing durable store (a head in any form) but no owner keyfile. No new DID is minted on this path: the owner key must be acquired out of band. A clean install (no store head AND no keyfile) auto-creates the keyfile instead.
vouch-store-required
Signalled fail-closed when start-fabric must re-admit recorded modules and finds no vouch store at the path it names. PATH is that path. Nothing is written.
Generic functions
owner-key-required-keyfile
(owner-key-required-keyfile condition)
Undocumented: this exported symbol needs a docstring.
vouch-store-required-path
(vouch-store-required-path condition)
Undocumented: this exported symbol needs a docstring.
Functions
loopback-host-p
(loopback-host-p host)
Return T when HOST names a loopback interface: IPv4 127.0.0.1, IPv6 ::1, or the symbolic name localhost (case-insensitive). Mirrors seven's own loopback allowlist; kept here so valis does not import from seven's CLI package.
register-module-mailbox
(register-module-mailbox name)
Register a mailbox named NAME on the live valis bus and return the mailbox-dir. Signals an error if the fabric is not running (valis-bus is nil). That is a programmer error: a module should not try to register its mailbox before start-fabric has run.
start-fabric
(start-fabric port &key (host "127.0.0.1") (keyfile (merge-pathnames "keyfile" (%valis-data-root))) (revocation-path (merge-pathnames "revocation" (%valis-data-root))) (pub-data-dir nil) (store-data-dir nil) (pub-store-backend :store) (evacuative nil) (local-domains nil) (dns-zone-source nil) (dns-descriptor-version +descriptor-admission-contract-version+) (held-descriptors nil) (dns-udp-port +dns-service-port+) (dns-tcp-port +dns-service-port+) (dns-serve-make-exchange nil) (mail-resolver (make-mail-resolver-source (%configured-mail-resolver))) (edge-tcp-port nil) (edge-credential-cell nil) (mail-tcp-port nil) (owner-port +owner-terminus-port+) (session-max-idle *session-max-idle-seconds*) (module-source-root nil) (vouch-store-path nil) (publisher-custody-directory nil))
Bring up the 9P fabric: build the namespace tree (the /proto, /bus, /id, /cap, /pub children), then start seven's listener on HOST:PORT against a fresh valis-root. PORT 0 selects an ephemeral port; the bound port is readable via (listener-port nine-p-listener).
SESSION-MAX-IDLE bounds how long an owner session may go untouched before
its next operation fences it, in seconds, or NIL for no bound. It defaults
to whatever the deployment has already configured, and that is NIL out of
the box: an upgrade must not start cutting sessions off that nobody asked it
to cut off. A node that wants the bound sets it, and
RECOMMENDED-SESSION-MAX-IDLE-SECONDS is the value to reach for absent a
reason to differ.
MODULE-SOURCE-ROOT is the directory the modules recorded in the durable manifest are read from, one identity directory per module, when a boot over an existing head re-admits them, whether or not it evacuates. The resident passes the root its operator configured; it defaults to NIL, and a stand-up that then finds a module to re-admit refuses rather than reading one from anywhere else. A non-resident record is still refused at re-admission until something supplies its publisher's vouch.
KEYFILE is the path the factotum custody store is rooted at; it carries the owner's Ed25519 secret seed so the same did:key survives image restarts. REVOCATION-PATH is the persistence path of the capability revocation store. Both default to keyfile and revocation under the XDG valis data root (<xdg-data-home>/valis/), so a bare (start-fabric port) roots durable state where a launcher can point it with $XDGDATAHOME. Tests pass scratch paths so a test run never reads or writes the operator's real owner key.
VOUCH-STORE-PATH is where the owner's vouch store is kept: the publishers this node trusts, their vouches and lists, and the owner's grants. It defaults to NIL, which resolves to the revocation store's path with the type vouch, so a scratch revocation path keeps a test's vouch store scratch too. When no vouch store is there, the node begins and writes an empty one, unless its durable module manifest names installed modules: their credentials are missing, so it refuses with vouch-store-required, whatever kind of boot this is.
PUBLISHER-CUSTODY-DIRECTORY is where mercer keeps the publisher's signing key for the owner's signing door, cap/sign. It defaults to NIL, which resolves to publisher in the revocation store's directory, the node's data root in production. Nothing is written there until the owner provisions a key, and no node backup carries it: a lost signing key is replaced and its delegation revoked, never restored.
PUB-DATA-DIR is the root directory for the publication filesystem store. Defaults to nil, which resolves to <xdg-data-home>/valis/publications/ at start time. Tests pass a temporary directory so no test run touches the operator's real publication data.
PUB-STORE-BACKEND selects which backend serves /pub. :store (the default) wires the durable content-addressed store rooted at its own data directory. :fs wires the POSIX filesystem store under PUB-DATA-DIR, retained so an operator can fall back to the v1 backend. Any other value signals.
STORE-DATA-DIR is the durable store's own root, distinct from PUB-DATA-DIR (the head and blocks are decoupled from the POSIX publication layout). Defaults to nil, which resolves to <xdg-data-home>/valis/pub-store/ at start time. Tests pass a temporary directory so no test run accretes into the operator's durable store.
The durable default starts empty on first boot: a fresh content-addressed store serves an empty /pub. Existing fs-store publications under PUB-DATA-DIR are NOT migrated into the durable store (migration is a later phase). An operator who needs continuity of existing /pub content on the first durable boot should pass :pub-store-backend :fs.
For a non-loopback HOST, fabric-auth-enabled-p must be T: seven's start-listener does not gate the host string itself, so this is the in-process equivalent of seven's CLI validate-loopback-host check.
EVACUATIVE selects the stand-up regime over an existing durable head. Default nil is the coincident regime: a stand-up over an existing head shares the store unfenced, performing no head bump and no fence: a scaling peer must never fence a sibling. Non-nil is the evacuative regime: the stand-up fences the superseded instance as its first durable act, appending the prior generation's fence to the revocation log, advancing the head over the same root tree, then sweeping the prior writer's live grants. A genesis create takes no fence: there is no prior generation to supersede. So a bare (start-fabric port) keeps the prior coincident behavior. Either regime over an existing head stands the installed modules back up; only the fence and the head bump belong to evacuation.
LOCAL-DOMAINS is the set of mail domains this fabric delivers locally (a list of domain strings). It answers the router's recipient-local-p seam: a recipient in one of these domains delivers into the owner's inbox, and any other recipient is non-local. Defaults to nil, fail-closed: with no configured local domain every recipient is non-local, so nothing is locally delivered and the open-relay guard governs every relay attempt.
MAIL-RESOLVER is the outbound MX resolver the /mail axis threads into the /ctl drain, defaulting to one built from the node's configuration by %configured-mail-resolver. It resolves a relay recipient's MX over the real wire (DoT) at drain time. Defaults fail-closed: with no DoT upstream configured (or runciter's real-wire resolver absent), build-mail-resolver yields nil, and a nil resolver makes the drain DEFER every relay entry (there is no smarthost fallback), so mail never leaves over an unresolved or unauthenticated channel. A caller may inject a resolver directly.
DNS-ZONE-SOURCE is the zone-data-source the DNS view binds over at boot (in production a pg-zone-source over the operator's own zone data). Defaults to nil: a bare (start-fabric port) binds no DNS adapter and keeps the prior DNS-seam behavior exactly, and (because this boot bound nothing) leaves any same-name adapter bound elsewhere untouched on every teardown path. A non-nil source first clears the descriptor-admission precondition, then binds the DNS view. The precondition runs inside this bring-up's unwind, so a refused precondition leaves no DNS adapter behind. No :53 socket is opened here; this wires the bind path only, and no foreign module code is admitted on it.
DNS-DESCRIPTOR-VERSION is the descriptor-admission contract version threaded into that precondition. Defaults to this valis's own supported version, so a bare boot always clears; a caller (a descriptor steered in from upstream) supplying a version that skews from the supported set refuses the boot bind fail-closed through the same precondition. The precondition also refuses when the pinned admission chokepoint symbol is absent. The owner-vouched-hash admission of a steered descriptor runs on the path where the descriptor is actually steered in, upstream of which this precondition is the gate.
HELD-DESCRIPTORS is what this node was handed: the already-interrogated sockets it holds, each carrying the port it is bound to and whether it is a stream or a datagram. The fabric wires a service to the one that answers for that service's own port, so which argument a descriptor arrived in means nothing here and no parameter name encodes a service. Defaults to nil, holding nothing, which is the bare boot: every service falls to whatever it does when nobody handed it a socket.
How a port reaches this process is the host agent's question, settled on host-OS grounds this node holds no opinion about. What the fabric asks is only whether a socket for the port a service answers on is already in hand.
The transport is part of that question rather than a detail beside it: :53 answers over UDP and TCP at once, and the two take deliberately different concurrency paths, so a query reaching one socket whose reply left by the other would be answered by nobody.
DNS-UDP-PORT and DNS-TCP-PORT name the port the DNS service answers on, per transport, and default to the protocol's own 53. DNS has no self-bind at all: it serves only over descriptors handed to it, and holding one without the other wires nothing.
OWNER-PORT names the port the routable owner terminus answers on and defaults to
- Hold a descriptor for it and the terminus comes up over that socket beside
the loopback management fabric, gated on the auth seam being engaged. Hold none and no terminus comes up, which is the default-closed posture.
EDGE-TCP-PORT is the dev loopback fallback for the :443 edge, retained unchanged: nil selects an ephemeral loopback port, and the public edge's own descriptor is recognised by the port a public edge answers on rather than by this value. EDGE-CREDENTIAL-CELL is the shared mercer server credential the :443 listener holds by reference, the same cell a renewal reloads, so the public surface never drops. The :443 edge is cert-gated: it wires only when the cell holds a usable chain+key, and start-tls-serve refuses otherwise, so a bare boot (nil cell, no issued cert) leaves :443 dark and still completes bring-up. Defaults nil so the prior behavior of a boot that wires no public edge is unchanged.
Returns the listener handle. On failure, the namespace seam variables and nine-p-listener are left nil so a subsequent stop-fabric or start-fabric sees a clean slate.
stop-fabric
(stop-fabric)
Stop the 9P fabric cleanly. Idempotent: calling on an already-stopped fabric is a no-op. Clears nine-p-listener, the edge seam's cached sessions and grant, and all namespace seam variables so a subsequent start-fabric begins from a known state. The edge seam is reset first, before the 9P listener stops, so the seam never holds a session into a partially-torn-down tree.
Variables
*fabric-auth-enabled-p*
Policy flag for non-loopback binds. When T, start-fabric will bind a non-loopback host because valis-root's node-auth returns a NoiseXX responder and seven's Tauth/Tattach path will use it to require a real handshake before admitting the attach. When NIL, start-fabric refuses a non-loopback bind. A full capability check will eventually replace the meaning of this flag; until then it is the policy gate.
*nine-p-listener*
The seven listener handle for the 9P fabric port, or nil when stopped. Set by start-fabric; cleared by stop-fabric. Read by stop-fabric and by run-foreground to advertise the bound port.
*owner-terminus-listener*
The keyed listener serving the owner 9P fabric over an inherited owner-port descriptor, or nil when no owner terminus is up. It is a SECOND keyed listener against the same valis-root as nine-p-listener: the loopback fabric stays the local management path and the owner terminus is the routable addition, present only when the node holds a descriptor answering for the owner port it serves. Set by start-fabric; stopped and cleared by stop-fabric and by start-fabric's own failure unwind.
+dns-service-port+
The port the DNS service of the :names group answers on, over both transports. A protocol's own number, the same fact every resolver on the wire already holds. It says nothing about how a socket for it reaches this process, which is the host agent's question and settled on host-OS grounds this node holds no opinion about.
+owner-terminus-port+
The port valis serves the routable owner 9P terminus on. 564 is IANA's registered 9P (styx/p9) port.
This number is now what the fabric looks the owner descriptor up under, so it is a declaration rather than a record: the node serves the terminus here, and a host agent that means the terminus to come up must make this port arrive. Hand down a descriptor bound to some other number and the node holds it and serves nothing on it, which is visible as a terminus that never opened.
That is the reverse of the arrangement this constant used to describe, where the host agent's config chose the number and valis adopted whatever descriptor it was given. A deployment whose agent names a different owner port needs valis told the same number through OWNER-PORT, and a node that has to be told at boot wants a declaration of its own rather than the launcher's word for it.