authn: accept tokens.sr.ht working tokens beside the agent token A second agent credential plane, next to the existing one rather than in place of it. The agent_token table, every agent configured with it, and the refs rule and provenance requirement around it are untouched; the local plane is removed in a later phase, not this one. The resolver tries the instance plane first and falls back to the local store on exactly two refusals, bearer.ErrInvalid and bearer.ErrNotOurs. spec's local token has no prefix to discriminate on — it is 32 random bytes in base64, which is precisely what "did not decode as one of ours" looks like — so the fallback replaces the shape test bench and cover can afford. ErrRevoked, ErrForbidden and ErrUnavailable are terminal: a withdrawn credential must not get a second chance at the old door, and an unreachable daemon must not silently degrade into the legacy plane. Grants ride on the principal and are checked where the action is known, never in the middleware, which runs upstream of the router: spec:propose in service.Propose, below both write surfaces, and spec:read in each read surface's gate. /mcp checks per tool rather than at its Gate, because one endpoint carries both kinds and a surface-wide read grant would refuse a propose-only token at initialize. Principal.Authorize is a no-op off the instance plane, which is what keeps the local token working. The instance plane brings an owner where the local token had none, so a working token belonging to anybody but [sr.ht] owner-name is refused rather than admitted as a second identity: Principal.Owner is read by the provenance committer, the refs rule's principal kind and the coreauth AuthContext, all written for one human. StatusFor is the one status table. ErrUnavailable is 503 and never 401 — reading "I could not ask tokens.sr.ht" as "revoked" would refuse every live instance token while a daemon that is deliberately off the hot path restarts. An instance with no [tokens.sr.ht] section builds no instance plane and starts anyway, serving its own agent token as before.
feat(mcpsrv): spec_comment closes the agent half of the review loop (spec-by6.3.4) An agent can now read the review threads on a proposal and reply to them. It cannot open a thread or resolve one, and that is enforced by the type rather than by the handler remembering: spec_comment is written against a narrow Commenter interface naming only Threads, ReplyTo, GetProposal and ProposalDiff, so service.CommentOn and service.ResolveThread are unreachable from it however service/ later grows. An unresolved thread suppresses policy auto-merge, so an agent able to open or resolve one would hold the gate that exists to hold its own output back. Writer is now the union of Proposer and Commenter, one narrow interface per write tool, and each handler takes only its own half. Every listed thread carries its anchor state, resolved against the branch as it stands now rather than as it stood when the comment was written — often the same agent has revised it since. An agent told only "fix this paragraph", with no signal that the critique no longer describes any block, edits the wrong thing. The tool description spells out what anchored/edited/outdated mean and says plainly that replying does not close a thread, so an agent answers the critique and pushes a revision instead of replying and waiting. Replying requires the proposal as well as the thread. A thread id is a global integer and service.ReplyTo needs nothing else, so a mistyped id would post a reply onto a stranger's proposal, out of sight of the agent that wrote it; the membership check reuses the threads already read for the ACL and costs nothing. Reads stay on the uniform owner+agents gate rather than being narrowed to the proposal an agent authored. Agent identity is self-declared in X-Agent headers and all agents share one token, so an authorship check would constrain a string the caller picks — stricter on paper than the read plane it sits in, and enforcing nothing. No wiring change was needed outside this package: main.go already passes Write: svc, and *service.Service satisfies the widened Writer. spec-by6.3.4