Files
stack/docs/architecture/lease-broker-protocol.md
jason.woltje 8ec67a1126
All checks were successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
feat(mosaic): add authenticated external lease broker (#836)
2026-07-18 03:12:23 +00:00

3.6 KiB

Authenticated external lease broker protocol

The compaction-refresh lease broker is a Linux-only, newline-framed JSON protocol over a Unix stream socket. It is runtime-neutral; M1 consumers are limited to Claude and Pi. This is an internal process boundary, not an HTTP API, so it is intentionally absent from OpenAPI.

The broker, never the caller, obtains (pid, uid, gid) from kernel SO_PEERCRED. It correlates the PID with /proc/<pid>/stat field 22 (starttime) and mints session_id on register_anchor. Presence of session_id in that request is refused even when its value is null or empty. Later requests must originate from the anchor or a descendant. The broker walks parent PIDs to the (pid,starttime) anchor and then rereads every walked PID's starttime before accepting the chain.

Request and response boundary

Each connection carries exactly one UTF-8 JSON object followed by one newline, capped at 64 KiB. The protocol deliberately uses EOF to prove that there is exactly one frame: immediately after writing the newline, the client MUST half-close its write side with shutdown(SHUT_WR) (or Node socket.end()) before awaiting the response. A client that writes a newline but leaves its write side open receives no successful response; the broker's one-second connection deadline fails closed. Malformed, unterminated, multiple (including a delayed second frame), or oversized frames fail closed. Responses are one JSON object and one newline. Success has {"ok":true,...}; refusal has {"ok":false,"code":"TYPED_CODE"}. Requests are:

  • register_anchor: action, non-negative runtime_generation; no session_id field.
  • authenticate: action, broker-minted session_id, non-negative runtime_generation.
  • mint_token: authenticated identity plus binding containing exactly compaction_epoch, request_epoch, h_source, h_payload, and schema_version.
  • consume_token: authenticated identity plus token.

A higher generation for the same anchor atomically replaces the stored incarnation and deletes all prior tokens for that session. A lower generation is stale. Tokens are 256-bit values from the operating-system cryptographic RNG and are single use. At most 256 pending tokens may be persisted; another mint fails with TOKEN_CAPACITY before mutation. Successful consumption deletes the token, while a replay still fails with TOKEN_REPLAY. Live v1 token records retain the existing consumed: false schema. Their persisted binding is the WI-1 substrate for later receipt work; WI-1 does not implement receipts, promotion, hooks, payload builders, mutator gates, or recovery.

State replacement serializes and enforces the 4 MiB maximum before opening a temporary file, then uses a mode-0600 temporary file, fsync, atomic rename, and parent-directory fsync. Every broker mutation snapshots the prior v1 state. A commit failure before rename restores that snapshot and leaves durable state unchanged. A failure after rename makes durability uncertain, so the store is poisoned without rolling memory back and the daemon terminates rather than serving with divergent state. Existing state is opened without following symlinks, must be a bounded regular file at mode 0600, and is fully schema- and invariant-validated before use. Persisted tokens must be unconsumed, match their session's current generation, and remain within the 256-token cap. Session identity is uniquely keyed by (anchor_pid,anchor_starttime); duplicate logical sessions for one anchor refuse startup. State integrity or mode failures refuse startup. The daemon does not log session IDs or tokens.