The rules the daemon packages hold. They are facts about the machinery, not
advice — each is enforced in code or by the type system. The reasoning behind
them is in The daemon spine.
Boundary
| Rule |
Statement |
| Binary-only |
Only daemon binary code — serve and front — lives in @kolu/surface-daemon. |
| Client-only |
Only supervising code lives in @kolu/surface-daemon-supervisor; it runs in the client, never the daemon, so it is never a staleKey root. |
| The package boundary is the hash boundary |
A change to the supervisor cannot change what a daemon restart would load; that is the daemon half’s job alone. |
Lifecycle
| Rule |
Statement |
| Single-instance |
acquirePidGate is an atomic link(2) claim. If the gate is held, the process exits 0 — another live daemon already serves this scope. |
| Idempotent adopt |
The adopt-or-spawn poll is idempotent under the daemon’s own pid-gate, so a racing second claim is a clean no-op. |
| Teardown ordering |
Shutdown is always close socket → release gate → return DaemonExit. |
No process.exit in the skeleton |
daemonMain returns a DaemonExit; the bin maps it to a code. The socket close() is idempotent and safe to call from a process.on("exit") handler. |
| Serves without asking |
The daemon serves the router it is handed and asks no questions — no env or spawn policy lives in it. |
Identity and versioning
| Rule |
Statement |
| Version-agnostic control core |
Identity is read over a version-agnostic channel, before the versioned handshake, so it stays reachable at a skew. |
| Contracts are ordered |
Contract versions are ordered — contractIsCompatible / contractIsNewer reuse @kolu/surface’s predicate. |
| Builds are match-only |
Build ids are match-only, with no ordering exported — store hashes do not order. |
| Honest absence |
readBakedIdentity returns empty strings off-nix rather than inventing an identity. |
The three make-illegal pins
The supervisor’s convergence kit makes three classes of mistake unrepresentable
rather than merely discouraged:
- A drainless daemon cannot declare a drain policy. The
drain-and-replace policy arms exist only for a drain-capable handshake; a drainless daemon (kaval) declaring one is a compile error.
- Contracts order, builds do not. The ordering predicate exists for contract versions and is simply absent for build ids — you cannot ask whether one build “is newer than” another.
- Identity precedes the handshake. The control-core identity is probed pre-connect, so a version skew can never hide it.