Skip to content

spec: disclosed gaps in a receipt chain, and the receipt_gap_disclosed outcome (#117) - #260

Open
lywinged wants to merge 1 commit into
agentrust-io:mainfrom
lywinged:spec/117-gap-disclosure
Open

spec: disclosed gaps in a receipt chain, and the receipt_gap_disclosed outcome (#117)#260
lywinged wants to merge 1 commit into
agentrust-io:mainfrom
lywinged:spec/117-gap-disclosure

Conversation

@lywinged

Copy link
Copy Markdown
Collaborator

Closes #117.

Opened per the maintainer-carry note there; all three asks from that comment are in,
each named below. Authorship per CONTRIBUTING step 3: carried by a maintainer, proposer credited
in the CHANGELOG entry.

What

Section 3.3.4, normative, marked <!-- CHANGED: #117 -->: a GapDisclosure is a signed chain
element stating that receipts which would have occupied its position were never emitted. Coverage
is structural rather than asserted: the disclosure links back to the element before the gap, the
next element emitted links back to the disclosure, and verification is two link checks a verifier
already performs on every ordinary element. There are no range fields. A hash chain cannot express
a range, and an emitter cannot know its successor's hash at write time; the design discussion in
#117 walks through why the issue's original field list does not survive that.

The action-receipt outcome receipt_missing_required is narrowed to silent absence, and
receipt_gap_disclosed is added beside it. Acceptance is a verifier policy input, with one
non-negotiable bound: it never satisfies a profile requiring independently proven completeness.

The tail case, as a test

A disclosure at the live tail, after the failure and before resumption, has no successor to seal
it. The spec text forbids granting it receipt_gap_disclosed and forbids receipt_invalid
equally: inability to check is not evidence of a defect, section 3.3.2's principle for unknown
issuer keys. It is surfaced unverified with a distinct advisory and upgraded on re-verification
once the chain resumes.

test_the_live_tail_is_not_granted_what_the_seal_earns pins it: fixtures 01 and 17 carry
byte-identical disclosures whose only difference is whether a successor exists, and the test
asserts the sealed one earns the outcome and the tail one does not. The adversarial reading is in
the test's docstring: a chain truncated immediately after a disclosure is byte-for-byte an honest
live tail, so this is also the assertion that truncation buys an advisory, never an outcome.
Fixtures 16 and 18 carry the two near misses: a contradiction standing at the tail still fails,
and a stale successor-link field with no successor is still a tail.

What a disclosed gap does not establish

That the missing receipts ever existed, how many were lost, or that the emitter did not omit them
selectively. What the splice proves is where the gap sits in the chain, and nothing else. The
section states this in its own text, and cause and receipts_lost_estimate are constrained to
descriptive self-reports a verifier MUST NOT condition any outcome on. The failure mode this
guards is a reader treating disclosure as though it restored what was lost; the text is written so
that no requirement can be read as making a disclosed gap benign.

The interaction with the undecidable state

receipt_gap_disclosed and the tail's unverified state are two more instances of the pattern
being unified across #246, #190, trace-tests#82 and agent-manifest#348: "this was not established"
as a first-class outcome rather than a silent pass. Both names this PR introduces,
receipt_gap_disclosed and the tail's gap_disclosure_unverified, are candidates for that
vocabulary. Each is held in one constant in the generator and one in the test; outside those, the
disclosed name appears in one spec section and one docs table row. A rename is those sites and one
regeneration. Not blocked on it, per the carry note.

Evidence

  • Twenty conformance vectors under examples/action-receipts/gap-disclosure/, two independent
    vectors per rule, enforced by a margin test rather than remembered. The second vector of each
    pair sits against an implementation shortcut the first cannot detect: case-normalised key and
    session lookups, digest comparisons truncated to a prefix, sealed-ness derived from a link field
    rather than from the chain, a contradiction check gated on the sealed path, and a type check
    that only tests presence.
  • The verifier consumes a named-rule registry (RULES) in the shape test(conformance): single-vector coverage has no margin — define and enforce independence #124 settled next door, so an
    unregistered check is a check that never runs.
  • The generator reproduces every fixture byte for byte, enforced by the existing
    tests/test_generators_reproduce_fixtures.py discovery.
  • Fourteen hand-run mutants against the verifier: thirteen killed, each by the fixture built for
    it; the one survivor widens an exception catch that no JSON-expressible fixture can distinguish,
    recorded here rather than left to be found.
  • Two known gaps from the issue thread are closed rather than carried: the type equality check
    the draft recorded as unimplemented now has a rule and two vectors (19, 20), and the Reporting
    obligations are echoed in every fixture's expected result rather than left unpinned.

Housekeeping

Two cross-references that predate the renumbering which introduced section 3.3.1 are updated to
name section 3.3.2, where the text they cite now lives: one in section 3.3.3's profile paragraph,
one in the receipt_unverified row of docs/verification.md.

Backward compatibility

Non-breaking. The section binds under a profile requiring action receipts; no current profile
does, and section 3.3.3 already contemplates such profiles as future work. Existing verifiers and
records are untouched; the outcome addition follows #114's decision shape, widening under opt-in
rather than in place. No schema change in this PR: per CONTRIBUTING, the schema PR tracks the
merged spec change.

Checklist

Production origin, for the record: the mechanism is operated in a per-action receipt emitter whose
crash recovery writes a functionally equivalent disclosure; relying parties treat a disclosed gap
differently from a silent one, which is the distinction this section makes portable.

Tool-assisted: drafting, the mutant battery and the fixture regeneration. The derivation is in
the section text, the commit message and the test docstrings, where the next reader can find it.

…d outcome (agentrust-io#117)

Under a profile requiring action receipts, a specification that offers only
complete and broken rewards concealment: an operator who backfills a lost
receipt scores better than one who reports the loss. A GapDisclosure is a
signed chain element stating that receipts which would have occupied its
position were never emitted.

Coverage is structural, never asserted. The disclosure links back to the
element before the gap and the next element links back to the disclosure, so
verification is two link checks a verifier already performs on every ordinary
element. No range fields exist: a hash chain cannot express a range, and an
emitter cannot know its successor's hash at write time.

The outcome receipt_missing_required is narrowed to silent absence and
receipt_gap_disclosed is added beside it, with acceptance a verifier policy
input that never satisfies a profile requiring independently proven
completeness. A disclosure at the live tail, where no successor exists to
seal it, is unverified rather than disclosed or invalid: a chain truncated
immediately after a disclosure is indistinguishable from an honest tail, so
whatever the tail is granted, truncation is granted too. That case is pinned
by its own test over byte-identical disclosures whose only difference is the
seal.

Twenty conformance vectors, two per rule, from a generator that reproduces
them byte for byte; the verifier consumes a named-rule registry in the shape
of agentrust-io#124. Two cross-references that predate the renumbering which introduced
section 3.3.1 are updated to name section 3.3.2, where the text they cite
now lives.

Refs agentrust-io#117

Signed-off-by: Louielunz <48041247+lywinged@users.noreply.github.com>
@lywinged
lywinged requested a review from a team as a code owner August 31, 2026 02:52
@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Contributor Check: UNKNOWN

Check Result
Profile UNKNOWN
Credential LOW
Overall UNKNOWN

Automated check by AgenTrust Contributor Check.

@github-actions github-actions Bot added the needs-review:UNKNOWN Contributor check flagged UNKNOWN risk label Aug 31, 2026
Comment thread examples/action-receipts/gap-disclosure/gen_gap_disclosure_vectors.py Dismissed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-review:UNKNOWN Contributor check flagged UNKNOWN risk

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec: disclosed receipt gaps — GapDisclosure claim + receipt_gap_disclosed verifier outcome

2 participants