Skip to content

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

Description

@lywinged

Spec section affected

docs/verification.md — "Action receipts and embodied workflows" (the four-outcome table); spec/trace-v0.2.md §3.3 (verification protocol, receipt outcomes); schema/trace-claim.json (one new optional claim — schema PR to track the spec PR per CONTRIBUTING).

Problem

The four action-receipt verifier outcomes conflate two materially different missing-receipt cases under receipt_missing_required:

  1. an adversarial or negligent silence — a receipt for a consequential action is simply absent; and
  2. an operator-disclosed, bounded loss — the emitter crashed, lost a tail of receipts, and said so.

Under a profile with action_receipts: required (#66), completeness is the load-bearing property — but no emitter can be made gap-proof: any high-throughput writer has a crash window in which a tail of receipts is lost. As currently specified, an honest operator who loses 50 ms of receipts in a crash produces the same verifier outcome as an adversary who silently deleted the receipts for a consequential action.

This creates a perverse incentive: backfilling or fabricating receipts scores better than disclosing the loss. An evidence standard should reward honesty under failure, not punish it.

Proposed change

1. New optional, chain-bound claim GapDisclosure:

{
  "type": "GapDisclosure/1.0",
  "range_start_after": "<chain position of the last receipt before the gap>",
  "range_end_before": "<chain position of the first receipt after resumption>",
  "cause": "crash | shutdown | backpressure | unknown",
  "receipts_lost_estimate": 3,
  "disclosed_at": "<chain position where this disclosure is sealed>"
}
  • Gap boundaries MUST be expressed in chain/log order, not timestamps — a key that signs receipts also signs its own timestamps, so self-reported time cannot bound a gap (same rationale as the entry-ID anchoring in [pre-v1.0] Key revocation: TraceRevocation claim + entry-ID anchor #67).
  • The disclosure MUST be signed by the receipt-issuing key or its hierarchical parent, and MUST itself be sealed into the receipt chain at the resumption point, so the disclosure is as tamper-evident as the receipts it accounts for.
  • receipts_lost_estimate MAY be null when the emitter cannot bound the count; the range bounds are still normative.

2. Fifth verifier outcome, and a re-scope of the fourth:

Outcome Meaning
receipt_gap_disclosed The profile required receipts and one or more are missing, but a valid GapDisclosure covers the missing range. Bounded, operator-attested negative evidence — the evidence-layer analogue of receipt_valid_rejected's "valid rejection is not malformed evidence".
receipt_missing_required (re-scoped) A required receipt is missing and no valid disclosure covers it. Silent, presumptively adversarial.

3. Verifier obligations (normative):

  • The verifier MUST validate the disclosure's signature, its chain binding, and that the disclosed range covers the missing receipts.
  • The verification statement MUST report disclosed gaps (range, cause, estimate) rather than collapsing them into a boolean — the gap detail is exactly what a relying party's policy needs.
  • Whether receipt_gap_disclosed is accepted (degraded-with-disclosure) or rejected (fail-closed) MUST be a verifier policy input, not implementation-defined behavior.
  • A GapDisclosure that fails validation, or whose claimed coverage contradicts receipts actually present, MUST yield receipt_invalid — a forged or self-contradictory disclosure is worse than none.

Backward compatibility

  • Non-breaking (new optional field, informative addition)
  • Breaking

One new optional claim + one added outcome. Suggest gating the new outcome under the widened profile version already contemplated in #114, so existing v0.2 verifiers are untouched.

Motivation

Production precedent: I operate a per-action receipt system whose emitter writes a functionally equivalent disclosure on crash recovery (bounded tail loss, chain resumes, disclosure sealed into the chain) — and relying parties treat a disclosed gap very differently from a silent one. Without this distinction, TRACE profiles with action_receipts: required are either unachievable at production rates or quietly dishonest — "perfect or broken" chains pressure operators to backfill.

If accepted, I will submit the spec PR after the comment window, the schema PR tracking it, and conformance vectors for trace-tests:

  1. receipts dropped, no disclosure → receipt_missing_required
  2. receipts dropped, valid covering disclosure → receipt_gap_disclosed
  3. disclosure present but range does not cover the missing receipts → receipt_missing_required
  4. disclosure signature invalid or not chain-bound → receipt_invalid
  5. disclosure claims a gap while the "missing" receipts are in fact present → receipt_invalid (contradiction)

Related issues or PRs

#66 (action_receipts axis — this is the ADR offered there) · #76 (disclosure vs. silence at verify time) · #67 (log-order anchoring rationale) · #114 (profile-versioned enum widening) · #116 (verification-statement content)

Metadata

Metadata

Assignees

No one assigned

    Labels

    policy: take-internalIndividual-authored normative spec change; handled internally per policy

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions