valis / Start here
valis
Valis: a Lisp Machine for network protocols. A sovereign, malleable computing substrate that multiplexes a person's data, identity, and protocols. The software half of DeepSky v2.
TL;DR
- What: one IP address answers on every designated port; valis is the userland process behind it. Every port a protocol, every protocol a plugin: each connection arrives carrying the port the client dialed, and a registry routes it to the protocol that claimed that port.
- How authority works: the durable unit is the person's namespace: a per-process mount table of 9P resources. A protocol module is an ephemeral viewer onto it, never the owner. Authority is enforced by what is mounted into a connection's view: a caller can name only what it was granted, and an ungranted resource is absent, not refused. Sovereignty is the absence of a name.
- State: valis serves the public internet from one node, and the substrate underneath is complete and proven end to end by one real protocol. TODO.org is the status board and says what has landed, what is deployed, and what remains.
Get going:
make test # the cold suite, fresh SBCL sbcl --eval '(asdf:load-system :valis)' \ --eval '(valis/src/main:main)' # run the listener + fabric + edges- Building on it: to build your own system on valis (a wire-protocol adapter, or a keyed authenticated service), see docs/BUILDING.org: the two extension surfaces and the authority model you inherit.
- Working on it: see docs/DEVELOPMENT.org for the REPL-driven workflow, the repository map, how to add a protocol, and the project conventions.
- Understanding it: see docs/ARCHITECTURE.org for how the substrate is shaped, layer by layer.
The idea, in code
Linux's eBPF can make one IP address answer on every port and forward the
traffic to a userland process. valis is that process. The concrete steering
mechanism is BPF_PROG_TYPE_SK_LOOKUP, written in Lisp via Whistler. Its
organising principle: every port a protocol, every protocol a plugin. Each
connection arrives carrying the port the client dialed, and the registry
routes it to the protocol that claimed that port.
The durable unit underneath is not the process but the person's namespace: a per-process mount table of 9P resources naming their data, identity, names, and the mailboxes of their running modules. A protocol module is an ephemeral viewer onto that namespace, never the owner of irreplaceable state. Authority is enforced by what is mounted into a connection's view, not by per-access guards: a caller can name only what it has been granted, and an ungranted resource is absent, not refused. Sovereignty is the absence of a name.
See docs/ARCHITECTURE.org for how the substrate is shaped, layer by layer.
Status
valis serves the public internet from one node: the primary authoritative nameserver
for four zones, answering HTTPS on the public :443 under a Let's Encrypt
certificate, supervised by systemd on a Debian host. The tree is ahead of that node,
which is running an older build.
That is the whole of the status this file carries. TODO.org is the status board: what has landed, what is built but not yet deployed, what remains, and the order it assembles in. I keep it in one place because a front page that also carries a changelog is a front page that goes quietly wrong, and takes the rest of the document down with it.
What valis is, layer by layer
- Event-loop listener: an iolib/epoll multiplexer accepts steered connections, recovers each connection's original destination port, and routes it through the registry. No synchronous accept loop in the path; the event source sits behind valis's own interface so an iouring backend can replace epoll later.
- 9P namespace fabric: a person's data, identity, and names are a
mountable 9P2000 namespace served by the seven sibling library; the same
fabric carries inter-module and inter-node dispatch as 9P file operations,
and the host kernel's
v9fsmounts it so ordinary tools traverse it. - Transport identity and factotum: a NoiseXX handshake binds a keyed
peer to a
did:keyprincipal with no explicit login; a keyless attach resolves toanonymous. A factotum agent (/id) is the sole holder of the owner's keys, handing modules only a resolved principal. The factotum and the transport cipher now live in the mercer sibling, the constellation's one home for transport authentication and custody; valis holds an opaque session and performs no crypto. Each post-attach 9P session is sealed end to end, and the responder is proven against an independent published NoiseXX transcript carried over the real wire, not only valis's own two-party self-interop. - Capability tokens and names: authority is carried as UCAN-mould tokens (delegable, attenuatable, public-key-rooted, offline-verifiable) with the owner as the authorization server; published data is named by a Tahoe-style capability that embeds its own access right and key.
- Enforcement by mounting: resolved authority is enforced by what is mounted into a connection's namespace, with no per-access guard and no ambient authority; authority resolves in two phases (a connect-time principal sets a base namespace, a per-request capability may mount further within it).
- Minimal semantic substrate: a publication exists as a semantic object in a 9P subtree, independent of any wire protocol; wire adapters project over it through capability-mounted views.
- Edge↔core seam and anonymous scoping: the recovered destination port
alone selects the edge module, which reaches the sovereign core only by
carrying its connection's principal and capabilities across a
9P/capability seam (co-located now, sandbox-ready later). An anonymous
caller reaches a narrow, capability-scoped
/pubview and can name nothing of the owner's authority. - Privileged host agent and real steering: fulcrum, a privileged
sibling agent holding
CAP_BPF=/=CAP_NET_ADMIN(the only privileged component), provisions eBPFsk_lookupsteering. Two descriptor handoffs run between the two, in opposite directions, and which one applies decides who made the bind. For a steered port, valis mints the listening socket on loopback, dials fulcrum'sAF_LOCALcontrol socket, declares the port set it answers on, and pushes that descriptor to fulcrum overSCM_RIGHTS; once the sockmap carries it the kernel selects valis's own socket and fulcrum leaves the data path. For a port only a privileged process can bind (:53UDP and TCP, the public:443edge, and:80for the documentation every node serves), the descriptor travels the other way: fulcrum binds it inside the namespace before the privilege drop and valis inherits it across the exec. That is socket activation, notSCM_RIGHTS. valis runs unprivileged and binds only loopback. - Durable content-addressed store and migration. A write-once, hash-addressed block store under a generation-fenced mutable head. A namespace manifest makes the mount table literal, so a fresh instance reassembles a person's namespace from owner-vouched hashes rather than receiving a copy of it, and evacuation fences the departing writer against the arriving one.
- Plugin composition. A protocol module is a content-addressed module admitted by capability, never fetched by hash alone, and a module manifest records the running protocol set so migration restores the code side as well as the data.
- The mail transport spine. A capability-scoped
/mailaxis where a capability-mounted 9P write is the authenticated submission, moving messages through a durable queue and a router that fails closed rather than relay for an unauthenticated sender. The body is an opaque block no operations path can name. - Operator state and authoritative names. PostgreSQL holds the durable operator
records; zones are authored through the capability-gated
:namesdoor and served from an in-memory view that answers off the database hot path and degrades to serve-last-good across an outage. The wire engine binds to a versioned, fail-closed adapter seam, and the RFC 1035 engine itself belongs to the runciter sibling. - The public-TLS spine. An ACME certificate lifecycle with custody and issuance
in mercer, server-TLS termination, and a cert-gated
:443edge that holds one credential per name it serves. - One door out. Every connection valis originates leaves through a single capability-gated verb, and there is no verb that only resolves. See docs/outbound-dial.org for the model and the limits it does not hide.
The capstone: HTTP on :80. valis answers HTTP as a conformant-but-bounded
HTTP/1.1 server on its steered port: Host required, GET + HEAD,
persistent keep-alive with a per-request read-deadline, conditional GET →
304 mapped onto the 9P qid.version as a strong ETag, and the real status
taxonomy with Date=/=Server headers. One URL space, identity selects the
mounted namespace. An anonymous GET resolves to the read-only published
/pub view; the owner proves possession of the Ed25519 custody key per
request via RFC 9421 HTTP Message Signatures, and an owner-signed request
reaches the full canonical frame as a browsable read projection. An anonymous
caller names nothing of management: 404, never 403. The same anonymous
/pub view is consistent across both edges: an unauthenticated HTTP GET and
an unauthenticated kernel v9fs mount see the same published floor, while
/proto /id /cap /edge /bus stay credential-gated.
That port is a capstone for a second reason, and it is the one that decides why
every node ships :80 enabled: valis serves its own documentation there, in the
clear. I want a person who has just stood a node up to read the manual for the
machine off the machine itself, from whatever device is to hand, before a
certificate exists and before anything else on the node is working. A node whose
manual you cannot read is not one you own. Plain HTTP is the reason and not a
shortcut, because nothing in that path asks for a modern client, a trusted root,
or a credential the node must first obtain from somebody else. See
what :80 is for for the whole of it.
Each further protocol is a self-contained wire-format adapter over an already-established semantic subtree, gated by capabilities. That is the prime collaborator surface: you add a protocol without inventing storage or identity machinery, because both are already there and already enforced. See docs/BUILDING.org to build one, and TODO.org for which protocols are sequenced.
Repository structure
valis.asd package-inferred ASDF system (seven >= 1.0.0, mercer >= 0.6.0,
runciter >= 0.8.0, c3po-http / c3po-gopher edge modules)
Makefile the out-of-band targets: build, dist, test, interop-test, gates
src/protocol.lisp the protocol class + handle-connection contract
src/registry.lisp port → protocol registry and dispatch-connection
src/listener.lisp the event-loop multiplexer over iolib/epoll
src/multiplexer.lisp the event-source interface; src/backends/ holds the epoll one
src/connection.lisp the per-connection record carrying the recovered dialed port
src/fabric.lisp the 9P namespace fabric (over seven) + start-fabric
src/transport/ the keyed accept loop + the sealed post-attach endpoint
src/capability/ UCAN-mould tokens, capability names, revocation, verification
src/edge/ the edge↔core seam, the per-port controller, the DNS / TLS / mail
serving paths, and the inherited-descriptor admission gate
src/substrate/ the semantic substrate (publication, store seam)
src/store/ the durable content-addressed store + module manifests
src/mail/ the mail transport spine: message model, durable queue,
fail-closed router, local-deliver, and the wire-adapter seam
src/namespace/ the /pub /mail /id /cap /edge /bus namespace axes + :names door
src/identity/ RFC 9421 signatures, key custody (Noise via mercer), and the
proof-of-reality client trust store
src/operator-state/ the PostgreSQL operator-state seam (durable records, DNS zone
source, the single-writer instance fence)
src/plugin/ plugin composition as content-addressed modules + the edge,
mail, DNS and active-module registries
src/active/ the active-module lifecycle: the scheduler, the ACME manager,
the certificate watch, and the controller supervising them
src/net/ the single door out: dial, resolve, policy, and the tripwire
that keeps it single
src/apply/ placing a delivery archive already on the host, refusing first
src/backup.lisp sealing and restoring the backup-critical set
src/dev/slynk.lisp the development listener every node carries and rarely opens
src/main.lisp process lifecycle and the CLI (resident / dev / zone / obtain /
publish / apply / backup / restore)
deploy/systemd/ the resident deployment unit: the Type=notify valis.service
supervising the fulcrum+valis pair, the valis.env bootstrap
config seed, and the legacy-operator drop-in
proving-ground/ the deployment proving ground: Nix fabric + Terraform-libvirt
two-location Debian topology + the in-image Lisp/SSH assertion
layer (its own valis-proving-ground ASDF system)
scripts/ out-of-band drivers: the dev image, the dist stage, the off-host
reachability probes, and the standing proofs
tools/api-doc.lisp generates docs/API.org from the loaded system
site/, landing/ the published documentation site and the landing page
docs/ the manual; docs/index.org is its table of contents
The HTTP/1.1 and Gopher wire engines are no longer in this tree: they are the
c3po-http and c3po-gopher modules from the c3po protocol sibling, adopted
as content-addressed edge-adapter modules that self-register onto the edge-adapter
registry (src/plugin/edge-adapter.lisp) and bind their ports through the edge
controller. valis owns the adapter seam; c3po owns the wire format.
Adding a protocol is adding a src/<proto>.lisp that subclasses protocol,
specialises handle-connection, and projects over a semantic subtree through
the capability-mounted namespace. See docs/DEVELOPMENT.org for the full
walk-through, or docs/BUILDING.org to build your own system on the substrate
from outside the tree.
Requirements
- SBCL 2.x
- ASDF 3.x
- The seven sibling 9P library, version
1.0.0or later (the floor that carries the close/EOF and node-qid capabilities valis requires) - The mercer sibling, version
0.6.0or later. The factotum: the NoiseXX transport authenticator, the post-attach session cipher, and the ACME certificate custody the:443edge draws on - The runciter sibling, version
0.8.0or later. The authoritative-DNS answer logic the:53serving path binds to - A PostgreSQL instance for the operator-state tests. Without
VALIS_PG_DSNthe suite stands those tests down and says so; it does not fail - Remaining deps available via
$LISP_WORKSPACE/or Quicklisp
Load and test
(asdf:load-system :valis)
The full cold suite runs out of band via the Makefile, which reports its own counts as it goes:
make test # the whole suite, fresh SBCL
make interop-test # drive a real Linux v9fs mount against the namespace
# (requires sudo / CAP_SYS_ADMIN)
make interop-test stands up an in-process fabric, publishes a sample item
under /pub, mounts it with -t 9p ... access=any (an anonymous attach), and
asserts the reconciled model: the mount names /pub and traverses the
published item, while the credential-gated base tree is absent, the same
published floor an unauthenticated HTTP GET sees.
The real-NIC steering acceptance gate lives in fulcrum, the privileged
sibling repo, as its make steer-test netns target: a client dialing the
designated :80 lands on valis's single steered socket with the original port
recovered.
Run
# Foreground (listener + 9P fabric + edge ports):
sbcl --eval '(asdf:load-system :valis)' --eval '(valis/src/main:main)'
# Build the shipped binary (bin/valis: valis plus the DNS serving composition):
make build
# Stage the versioned delivery tarball, fulcrum's artifacts included:
make dist
# Dev image with a Slynk listener (SLYNK_PORT, SLYNK_HOST):
./scripts/dev-boot.sh # foreground
./scripts/dev-boot.sh --background # detach; tail /tmp/valis-dev.log
The binary takes --resident for the steered production boot, --dev for a Slynk
listener, --backup / --restore for the backup-critical set, and the zone,
obtain, publish and apply verbs that drive a running resident owner-keyed over
the loopback fabric. dist and assess address the host instead: dist registers
the private dependency distribution as this node's dependency source, and assess
reads the host and reports whether it is fit to run a node, changing nothing on it.
valis --help prints the full grammar; --daemon survives as a no-op. To run
valis as a supervised resident on a real host (the fulcrum+valis pair
under a Type=notify systemd unit, serving public :53 and the cert-gated :443
edge), follow the operations runbook. The resident bring-up itself is driven by the
fulcrum launcher, not this argv.
Documentation
- docs/index.org: the manual's table of contents.
- docs/BUILDING.org: building your own system on valis: the two extension surfaces and the authority model you inherit.
- docs/DEVELOPMENT.org: working with the code: the REPL-driven workflow, editing, testing, adding a protocol, and the project conventions.
- docs/ARCHITECTURE.org: the design of record (how valis is shaped).
- docs/CONSTELLATION.org: the whole-system responsibility map (which repo owns what).
- docs/deployment/host-deployment-contract.org: how a node comes up on a real
host, framed as deploy
= condense-from-genesis; =proving-ground/is its harness. - docs/deployment/operations-runbook.org: the operator's runbook: install the
systemd unit, author the config seam, start/observe/renew the
:443edge, back up the durable state, and restart. - docs/deployment/first-boot-and-operator-moves.org: the node's first minutes: the genesis boot, the one host-specific launcher config, and the ordered first moves an operator makes.
- docs/outbound-dial.org: the one door every outbound connection leaves through, written for a reader in real danger.
- docs/security/backup-and-observability.org: the backup-critical set, the restore procedure, and the resident's health surface.
- docs/CONDITION-HANDLING.org: how valis handles failure, and why a blanket handler turns a defect into a plausible value.
- docs/API.org: the generated programmer's reference (every exported symbol).
- TODO.org: the delivery status board (authoritative for delivery order and progress).
License
AGPL-3.0-or-later, see LICENSE.
Copyright (c) 2026 Brian O'Reilly.