~bigbes/sr-ht-spec

7c82786681409f246f2f77110d659fa422267959 — bigbes 27 days ago fe91365
docs: align provenance section with the one-token decision

The section still described per-space tokens with reader/proposer/writer
roles, which the decisions table and the authorization section had already
retracted in favour of one token plus mandatory provenance.

Also records what implementation had to settle: the agent mailbox derives
from our own origin rather than the bare cookie domain, X-Agent-Base is
mandatory and must be an object name, identity travels as X-Agent and
X-Agent-Session with the base coming only from If-Match, a non-owner human
resolves to anonymous, and cookie expiry is not enforceable so network-key
rotation is the only logout-everywhere lever.
1 files changed, 28 insertions(+), 4 deletions(-)

M docs/DESIGN.md
M docs/DESIGN.md => docs/DESIGN.md +28 -4
@@ 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) <agent@srht.bigb.es>
Author:  claude-code/spec-writer (for bigbes) <agent@spec.srht.bigb.es>
Committer: bigbes <bigbes@gmail.com>

    Add storage model section


@@ 560,6 559,31 @@ Committer: bigbes <bigbes@gmail.com>
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@<host of
  [spec.sr.ht] origin>` — 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`,
`spec_status`. warren's `mcpsrv/` is the starting point.