feat(web,service): the owner mints and revokes agent tokens in a browser Issuing a credential required SSH to the host, which made the remote agent write plane unusable from anywhere else: to hand an agent a token the owner had to be at the machine. /tokens is that page — list, mint, revoke — behind the same owner-only gate and same-origin guard as approve/reject. The mint is owner-only, and that rule is what revocation depends on: an agent allowed to mint would survive having its own credential revoked by issuing itself another, and "revoke the token" is the entire incident response this design has. An agent asking for the page gets 403 rather than the read plane's login redirect — it is authenticated already, so bouncing it to meta would answer a question it did not ask. The plaintext is rendered in the response to the POST rather than after a redirect. A redirect would either drop the secret or carry it in a URL, where it lands in history and in every proxy log on the way; the cost is that a reload re-submits and mints a second token, which is one click to revoke on that same page, whereas a lost token is not recoverable. service.IssueAgentToken/ListAgentTokens/RevokeAgentToken hold the ACL and the mint, and `specsrht token` now goes through them too, so the CLI and the page cannot drift into two ideas of what issuing a token is. spec-ejq.3
feat: service — wiring, space lifecycle, read paths, push validation, reconciler Phase 1 of the implementation plan: the orchestration layer's read, validate and repair halves. The write plane (propose/merge) is Phase 3 and is absent. - Config/New assemble a Service from the shared config.ini, reporting every missing key in one message; TokenStore adapts db/ to authn.TokenStore, mapping db.ErrNotFound onto authn.ErrUnknownToken so an unknown credential is a 401 rather than a 503. - CreateSpace writes the repository first and the row second, removing the repository again if the insert fails; a crash between the two leaves content that is merely unlisted rather than a phantom space. - ReadDocument/ListDocuments/Policy/ResolveRev resolve the approved head or a pinned revision through one code path. - ValidatePush checks the refs rule first and unconditionally, then frontmatter and document-id uniqueness, which --push-option=skip-validation waives. The rejection is a structured, terminal-shaped message naming the document. - PlanRepairs is the repair table as a pure, table-tested function; Reconcile gathers the facts and applies them, listing stale-index spaces for Phase 2. Two departures from the design's repair table, both to stop the reconciler destroying live state, documented at their definitions: an open row with no branch is left alone inside a grace window (every propose passes through that state), and a branch still sitting on its recorded base is never treated as merged (its tip is trivially an ancestor of the approved head).