diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 12232faae5fd4f8fa13c19883501a805dbbd49ca..f55701d2d3d7d9d2705843aa7451e5a56772254d 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -543,12 +543,11 @@ who did what. ### Provenance -Per-space (or per-project) tokens with a role — `reader` / `proposer` / -`writer` — and a **required agent identity string**. Every commit records it in -a way that survives clone: +One agent token (no roles, no per-space scopes — see above) plus a **required +agent identity string**. Every commit records it in a way that survives clone: ``` -Author: claude-code/spec-writer (for bigbes) +Author: claude-code/spec-writer (for bigbes) Committer: bigbes Add storage model section @@ -559,6 +558,31 @@ ``` Git trailers rather than a Postgres-only audit table, so provenance is visible in plain `git log` on any clone and cannot drift from the content it describes. + +Details settled during implementation, recorded here because the sketch above +left them open: + +- **The agent mailbox is derived, not configured.** `agent@` — so `agent@spec.srht.bigb.es`. Earlier drafts wrote + `agent@srht.bigb.es` (the bare cookie domain) with no stated source. Deriving + it from our own origin adds no config key and cannot drift from it. +- **`X-Agent-Base` is mandatory**, validated as 7–64 lowercase hex. An empty base + is a provenance claim with nothing behind it, which would make the trailer + decorative rather than auditable — the thing the read-contract decision was + supposed to buy. It must be an object name, never a ref name. +- **Agents transmit identity over `X-Agent` / `X-Agent-Session` request + headers**, named after the trailers they become. Deliberately **no** + `X-Agent-Base` header: the base is `If-Match`, and a second spelling of one + value is exactly how REST and MCP drift apart. The MCP tools map arguments onto + the same three values. +- **A logged-in human who is not the owner resolves to anonymous**, with the + cookie name kept for logs only. Single-user makes this correct today; it is the + line to revisit if anyone else ever gets an account. +- **Cookie expiry is not enforceable and is not enforced.** The unified-login + cookie carries no service-side TTL, and core-go's own `cookieAuth` uses + `DecryptWithoutExpiration`. We match it, so "expired" collapses into "sealed + with a key we no longer hold" — i.e. **rotating `[sr.ht] network-key` is the + only logout-everywhere lever**, instance-wide. MCP is a **first-class surface, not a wrapper** — it is how agents will actually consume this: `spec_search`, `spec_read`, `spec_propose`, `spec_comment`,