jcp-cite: retrieval provenance — a checkable record of what an agent READ #32
2026-07-20
jcp-cite: tighten unreadable_by — real authorization via a caller predicate
Review flagged two soft spots; this addresses them per the finding. #1 (fixed) — `unreadable_by` compared grant ids by exact identity, so a reader holding a BROADER grant that actually covers a source's read was still flagged unreadable. Whether a reader may open a source is an authorization question, and only the caller — who resolves a grant_id to its capability and knows the reader's own held authority — can answer it correctly. Added `unreadable_by_with(predicate)`: the general, correct form, where the caller supplies the decision (a true capability-subset check backed by the runtime). `unreadable_by` is retained as the conservative exact-grant shorthand, reimplemented in terms of the predicate, and documented as pessimistic. The crate stays a pure provenance record with no dependency on the decision engine. New test proves a broader grant is authorized under the predicate where exact-match rejects it. #2 (won't fix, by design) — reusing `FrameKind::ToolCall` + `action: "retrieve"` rather than adding `FrameKind::Retrieval` is correct, not a shortcut. FrameKind's axis is message shape (tool|agent × request|reply); a retrieval request is a tool request, and the sources it reads back are ToolResult/Data. Operation identity belongs in the action field, which is where a consumer filters retrievals. A dedicated kind would fold operation semantics into the request/reply axis — a category error — so it is deliberately not added. jcp-cite suites green, clippy clean. Co-Authored-By: Claude Opus 4.8 <[email protected]>
jcp-cite: retrieval provenance — a checkable record of what an agent READ
Agent platforms log actions, not sources. A workspace agent's audit trail records that it posted a report or updated a row, and records nothing about the documents it consulted to decide — so the answer cannot be reconstructed and a reviewer cannot tell grounding from invention. Where citations exist they are convenience links: a URL in rendered output, unbound to the answer, unsigned, and unaffected if the thing it points at changes or is deleted. This crate makes a citation checkable. Three properties, each a method rather than an assurance, drawn from the three gaps a recent survey of the field documented in the incumbents' own words: 1. Sources read are recorded, in read order — the artifact an actions-only log cannot produce. 2. A citation cannot outlive its referent. Each source is bound by CONTENT ID, not a locator: edit or delete the source and verify_source() fails, so the citation is verifiably dangling instead of silently pointing at whatever is there now. A URL cannot do this; a hash can. verify_answer() pins the answer the same way. 3. A citation says who may read it. Each source records the grant it was read under, so unreadable_by() names exactly which sources a given reader is not cleared to open — because an agent reads under its own grants, not its invoker's, so a citation can legitimately point at content the reader cannot see. Name the gap rather than hand over a link that fails (or wrongly works). It carries the retrieval's joules, so grounding an answer is accounted like any other work. Composition (the point): a Citation is content-addressed and its id IS the retrieval frame's payload_hash, so the jcp/2 receipt (SPEC §8) commits it. The receipt proves the retrieval was authorized, gated and metered; the citation records what it consulted; the receipt binds the two, and neither is trusted alone. Tested end to end: the sealed receipt's payload_hash equals the citation's id and cannot be swapped for another. New crate, additive; no change to existing crates beyond the workspace member list. 44 suites green, clippy clean, runnable example. Co-Authored-By: Claude Opus 4.8 <[email protected]>