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

  1. 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.
  2. 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 v9fs mounts it so ordinary tools traverse it.
  3. Transport identity and factotum: a NoiseXX handshake binds a keyed peer to a did:key principal with no explicit login; a keyless attach resolves to anonymous. 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.
  4. 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.
  5. 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).
  6. 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.
  7. 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 /pub view and can name nothing of the owner's authority.
  8. Privileged host agent and real steering: fulcrum, a privileged sibling agent holding CAP_BPF=/=CAP_NET_ADMIN (the only privileged component), provisions eBPF sk_lookup steering. 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's AF_LOCAL control socket, declares the port set it answers on, and pushes that descriptor to fulcrum over SCM_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 (:53 UDP and TCP, the public :443 edge, and :80 for 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, not SCM_RIGHTS. valis runs unprivileged and binds only loopback.
  9. 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.
  10. 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.
  11. The mail transport spine. A capability-scoped /mail axis 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.
  12. Operator state and authoritative names. PostgreSQL holds the durable operator records; zones are authored through the capability-gated :names door 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.
  13. The public-TLS spine. An ACME certificate lifecycle with custody and issuance in mercer, server-TLS termination, and a cert-gated :443 edge that holds one credential per name it serves.
  14. 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 GET304 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.0 or later (the floor that carries the close/EOF and node-qid capabilities valis requires)
  • The mercer sibling, version 0.6.0 or later. The factotum: the NoiseXX transport authenticator, the post-attach session cipher, and the ACME certificate custody the :443 edge draws on
  • The runciter sibling, version 0.8.0 or later. The authoritative-DNS answer logic the :53 serving path binds to
  • A PostgreSQL instance for the operator-state tests. Without VALIS_PG_DSN the 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

License

AGPL-3.0-or-later, see LICENSE.

Copyright (c) 2026 Brian O'Reilly.