docs: promote lease broker architecture contracts
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Developer Guide
|
||||
|
||||
> **Status:** Scaffold only. Architecture and contributor pages are not migrated yet.
|
||||
> **Status:** Partially migrated. The architecture chapter contains the promoted lease-broker security-contract pages; contributor chapters remain unmigrated.
|
||||
|
||||
This book is the canonical home for architecture, package and application guides, local development, testing, contribution workflow, and integration authoring. User-facing procedures belong in [`USER-GUIDE/`](../USER-GUIDE/); operator procedures belong in [`ADMIN-GUIDE/`](../ADMIN-GUIDE/); API contracts belong in [`API/`](../API/).
|
||||
|
||||
@@ -13,14 +13,14 @@ This book is the canonical home for architecture, package and application guides
|
||||
|
||||
## Chapter map
|
||||
|
||||
| Chapter | Scope | Status |
|
||||
| ----------------------------------------- | -------------------------------------------------------------------- | -------------- |
|
||||
| [`architecture/`](architecture/README.md) | System model, components, data flow, security model, ADRs, and RFCs. | Scaffold only. |
|
||||
| `packages/` | Package- and application-level contracts and guides. | Scaffold only. |
|
||||
| `local-development/` | Safe local setup and development routes. | Scaffold only. |
|
||||
| `testing/` | Test strategy, verification, and quality gates. | Scaffold only. |
|
||||
| `contributing/` | Contribution, review, and delivery workflow. | Scaffold only. |
|
||||
| `integrations/` | Plugin, provider, and adapter authoring. | Scaffold only. |
|
||||
| Chapter | Scope | Status |
|
||||
| ----------------------------------------- | -------------------------------------------------------------------- | ------------------- |
|
||||
| [`architecture/`](architecture/README.md) | System model, components, data flow, security model, ADRs, and RFCs. | Partially migrated. |
|
||||
| `packages/` | Package- and application-level contracts and guides. | Scaffold only. |
|
||||
| `local-development/` | Safe local setup and development routes. | Scaffold only. |
|
||||
| `testing/` | Test strategy, verification, and quality gates. | Scaffold only. |
|
||||
| `contributing/` | Contribution, review, and delivery workflow. | Scaffold only. |
|
||||
| `integrations/` | Plugin, provider, and adapter authoring. | Scaffold only. |
|
||||
|
||||
Every promoted page must be added to this index and to [`SITEMAP.md`](../SITEMAP.md) in the same migration slice.
|
||||
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
# Architecture
|
||||
|
||||
> **Status:** Scaffold only. No legacy architecture page has been promoted into this chapter yet.
|
||||
> **Status:** Partially migrated. The lease-broker security-contract pages below are current references; the remaining architecture pages are still being classified.
|
||||
|
||||
This chapter is the canonical home for Mosaic Stack's system model, component boundaries, data and control flow, security model, architecture decisions, and RFCs. It explains why the system has its shape; it does not replace [`PRD.md`](../../PRD.md), [`TASKS.md`](../../TASKS.md), or the API contract.
|
||||
|
||||
## Promoted pages
|
||||
|
||||
- [`lease-broker-protocol.md`](lease-broker-protocol.md) — authenticated Unix-socket protocol, identity binding, framing, persistence, and lease transitions.
|
||||
- [`compaction-revocation.md`](compaction-revocation.md) — Claude/Pi observer lifecycle, runtime generations, revocation, and the bounded residual stale window.
|
||||
|
||||
These pages are current security-contract references and are consumed by the lease-broker acceptance suites. Their live deployment gaps remain explicitly labeled in the pages; this migration does not change runtime behavior.
|
||||
|
||||
## Planned pages
|
||||
|
||||
| Path | Purpose | Status |
|
||||
@@ -22,7 +29,7 @@ Promoted pages must be linked here, from [`DEVELOPER-GUIDE/README.md`](../README
|
||||
- [`docs/README.md`](../../README.md) — current documentation contract and placement rules.
|
||||
- [`Documentation information architecture design`](../../plans/2026-08-10-docs-information-architecture-design.md) — approved documentation structure decision, not product architecture.
|
||||
- [`Documentation catalog audit`](../../reports/documentation/2026-08-10-docs-catalog-audit.md) — evidence and migration recommendations, not normative architecture.
|
||||
- [`_old_structure/architecture/`](../../_old_structure/architecture/) — historical sources; each page requires classification and claim verification before promotion.
|
||||
- [`_old_structure/architecture/`](../../_old_structure/architecture/) — remaining historical sources; each page requires classification and claim verification before promotion.
|
||||
|
||||
## Source-of-truth boundary
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# Compaction observer revocation and runtime generations
|
||||
|
||||
> **Status:** Current contract reference.
|
||||
> **Audience:** Developer and security reviewer.
|
||||
> **Evidence:** Mutator-gate and framework portability acceptance suites consume this page; live deployment gaps remain explicitly labeled below.
|
||||
|
||||
WI-3 connects Claude and Pi compaction/session lifecycle events to the existing authenticated lease-broker state machine. It does not add a second lease store or let runtime hooks assert identity. Each observer inherits the broker-minted session, resolves the current private runtime generation, and sends the existing `revoke_lease` action over the authenticated Unix socket.
|
||||
|
||||
## Observer matrix
|
||||
|
||||
| Runtime | Lifecycle signal | Action |
|
||||
| ---------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Claude / Claudex | `PreCompact` | Revoke the current lease before compaction. A non-zero hook result blocks the lifecycle transition. |
|
||||
| Claude / Claudex | `SessionStart` with matcher `compact` | Revoke again after compacted context starts. |
|
||||
| Claude / Claudex | `SessionStart` with matcher `resume\|clear` | Atomically advance the private generation, then revoke the replacement incarnation. |
|
||||
| Pi | `session_before_compact` | Revoke before compaction; return `{ cancel: true }` if revocation cannot be confirmed. |
|
||||
| Pi | `session_compact` then the first `context` | Arm and run an independent post-compaction revoke. A failed post observer blocks later tools locally until a retry succeeds. |
|
||||
| Pi | `session_start` with reason `reload`, `new`, `resume`, or `fork` | Atomically advance the private generation, then revoke the replacement incarnation before reuse. |
|
||||
|
||||
The first observer that reaches the broker deletes pending promotion tokens and makes the lease `UNVERIFIED`. The second compaction observer is deliberate redundancy, not a prerequisite for the first. Claudex receives the same mandatory hooks in its isolated `CLAUDE_CONFIG_DIR`; hook merging preserves unrelated isolated settings and rejects malformed or symlinked settings fail-closed.
|
||||
|
||||
## Private generation authority
|
||||
|
||||
`launch-runtime.py` still registers before `exec`, preserving the kernel-authenticated PID/starttime anchor. It now also creates `generation-<broker-session>.state` beside the broker socket. The file is owner-only mode `0600` under the broker's mode-`0700` directory. Hook descendants read that file instead of relying only on an immutable inherited environment value.
|
||||
|
||||
Generation changes use an exclusive file lock, validate owner/type/mode/size, increment monotonically, truncate and write the complete new value, and `fsync` before contacting the broker. Therefore reload, new-session, resume, and fork events may retain the same PID/starttime while still becoming a new broker incarnation. The higher generation causes the broker to atomically discard prior tokens and lease authority; the replacement generation inherits no VERIFIED lease.
|
||||
|
||||
If an observer fires while broker transport is unavailable, `revoke-lease.py` advances the private generation as a local fence before returning non-zero. Every later all-tools gate reads that higher value. When the broker is reachable again, authentication of that value performs the same old-generation revocation before authorization. Pi also keeps a process-local post-compaction/rollover failure latch that blocks tool calls. An unsafe or unreadable generation file itself makes both lifecycle revocation and tool authorization fail closed.
|
||||
|
||||
## Threat contract and stopping boundary
|
||||
|
||||
### BOUNDED RESIDUAL STALE WINDOW
|
||||
|
||||
If **both** pre- and post-compaction observers are missed entirely, no revocation signal exists. During the remaining unexpired lease, **within-TTL consequential actions are allowed**. Their count and timing are **bounded by lease expiry, not by the mutator gate**. WI-3 makes no claim that it bounds mutator actions inside this stale interval. The broker's monotonic lease TTL is capped at 300 seconds; after expiry, the next consequential tool is denied with `LEASE_EXPIRED`.
|
||||
|
||||
This is the named D2-v5 T-A residual. It is distinct from an observer that fires but cannot contact the broker: the latter creates a local generation fence and fails closed. It is also distinct from T-C total rot, where the lifecycle observers and the all-tools gate are both absent or replaced. Server-side branch protection, required CI, and independent review remain the irreducible backstop for T-C.
|
||||
|
||||
| Condition | Result |
|
||||
| --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
||||
| Either compaction observer succeeds | Existing lease and pending promotion tokens are revoked immediately. |
|
||||
| Observer runs but broker confirmation fails | Lifecycle transition is denied where supported; local generation fence and runtime latch prevent inherited authority. |
|
||||
| Both observers are missed, lease unexpired | **ALLOWED** inside the bounded residual stale window. No within-window mutator bound is claimed. |
|
||||
| Both observers are missed, lease expired | **DENIED** by monotonic TTL expiry. |
|
||||
| Generation advances on reload/new/resume/fork | Prior incarnation revoked; replacement starts `UNVERIFIED`. |
|
||||
| Lifecycle observers and all-tools gate both fail or are removed | T-C total-hook-miss residual; protected-branch controls remain required. |
|
||||
|
||||
## T-C server-side branch-protection posture
|
||||
|
||||
The required posture is that `main` is push-blocked and PR-only-merge is **MANDATORY**, regardless
|
||||
of client-gate state. The client-side gate narrows the exposure window only; it is not the T-C
|
||||
guarantee. The server-side protected-branch configuration is the irreducible guarantee for protected
|
||||
repository actions. Status-check enforcement and approval enforcement are **RECOMMENDED**.
|
||||
|
||||
## Current-vs-required gap (recorded, not enacted)
|
||||
|
||||
The current empirical configuration is recorded here without re-probing or mutating live branch
|
||||
protection. `enable_push=False` (push-block present), so the mandatory push-block/PR-only-merge core
|
||||
holds. `require_approvals=0` (approvals not enforced), `enable_status_check=False` (status checks not
|
||||
enforced), and `block_on_official_review=False` (official review not enforced). Those recommended
|
||||
merge-quality controls are the current gap; changing them is a separate, owner-gated operations
|
||||
decision and is not enacted by this documentation change.
|
||||
|
||||
The permanent T12b/T30 acceptance case prints both required outcomes: dual-hook miss within TTL is **ALLOWED**, and the same lease after TTL is **DENIED**. Separate real-socket tests prove each Claude observer and same-PID generation rollover; Pi lifecycle tests exercise pre/post observers, all four replacement reasons, and local failure closure.
|
||||
@@ -0,0 +1,39 @@
|
||||
# Authenticated external lease broker protocol
|
||||
|
||||
> **Status:** Current contract reference.
|
||||
> **Audience:** Developer and security reviewer.
|
||||
> **Evidence:** Lease-broker acceptance suites consume this page; executable behavior remains authoritative in source and tests.
|
||||
|
||||
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`.
|
||||
- `begin_verification`: authenticated identity, runtime (`claude` or `pi`), cycle `binding`, and a TTL no greater than 300 seconds. The broker revokes existing authority first, enters `PENDING_VERIFICATION`, and returns a single-use promotion token.
|
||||
- `begin_recovery`: the constrained recovery entrypoint. It rejects caller-provided receipt/challenge fields and delegates to the same `begin_verification` transition, but reports `PENDING_DELIVERY` and marks the volatile cycle as recovery-owned.
|
||||
- `complete_recovery`: authenticated identity only. It rejects caller-provided receipt/challenge fields, obtains the current recovery challenge only from broker state, and delegates to the same trusted-observer → evidence → consume → promote sequence. An observation failure revokes recovery authority; retry starts a fresh challenge.
|
||||
|
||||
The daemon owns a second protected production observer socket (mode `0600`) unless a private `--test-observer-file` fixture is selected. That transport accepts only the exact `record_runtime_observation` schema after kernel `SO_PEERCRED` plus the existing anchor/ancestry authentication; it validates the pending runtime/generation before storing one finalized assistant entry for the in-process `RuntimeReceiptObserver`. It is **not** a broker request action. Claude sends its latest assistant entry from the Stop-hook transport; Pi sends only finalized `message_end` assistant content. The public broker socket continues to reject request-supplied `latest_assistant_message` in begin, observe, and complete paths.
|
||||
|
||||
- `promote_lease`: authenticated identity plus the exact pending promotion token. The broker commits token consumption before making `VERIFIED` visible.
|
||||
- `revoke_lease`: authenticated observer signal; deletes pending tokens and makes the session `UNVERIFIED` immediately. WI-3 Claude/Pi hooks send this existing action; `runtime` and bounded `reason` fields are diagnostic input only and never identity authority.
|
||||
- `authorize_tool`: authenticated identity, runtime, and exact runtime-reported tool name. The broker returns an explicit allow/deny decision from the whole-class policy and current lease.
|
||||
|
||||
A higher generation for the same anchor atomically replaces the stored incarnation and deletes all prior tokens and lease authority for that session. A lower generation is stale. Runtime descendants resolve the current generation from an owner-only, locked generation file created by the register-before-exec launcher; reload/new/resume/fork observers advance and `fsync` it before broker revocation. This supports generation replacement even when PID/starttime do not change. 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.
|
||||
|
||||
VERIFIED leases are volatile and monotonic-time bounded: broker restart, generation change, explicit observer revocation, or expiry returns the session to `UNVERIFIED`. `begin_verification` always revokes before minting a new prerequisite. `begin_recovery` reuses that exact transition and mints a new challenge, so a normal-path receipt/challenge cannot be replayed through recovery. `promote_lease` is valid only from the matching pending cycle; persistence failure rolls token and lease state back, while post-rename durability uncertainty terminates the broker. The WI-1 token is the atomic promotion prerequisite substrate.
|
||||
|
||||
## Receipt boundary and T-C residual (R1)
|
||||
|
||||
Receipt evidence is a T-A delivery/liveness prerequisite only; it cannot replace the mechanical
|
||||
mutator gate as safety authority. The receipt detects an **ABSENT** or **PREFIX-TRUNCATED** terminal
|
||||
token. A **MIDDLE-DROP** that preserves the tail is a T-C contract violation that is **NOT receipt-detectable**. It is covered by server-side protected-branch controls, **NOT** by the receipt; no category-wide receipt-detection claim is made for that tail-preserving transformation.
|
||||
|
||||
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.
|
||||
Reference in New Issue
Block a user