kolu
Docs

Daemon invariants

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:

  1. 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.
  2. 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.
  3. Identity precedes the handshake. The control-core identity is probed pre-connect, so a version skew can never hide it.