wai: stage receipts — chains per key and track, verified by inclusion, and the stage and delivery-gap claims #171

Merged
dcharlot merged 24 commits from wai/w1-5-stage-receipts into main 2026-10-06 08:37:30 -04:00
Owner

Stage receipts: what a sink presented and what delivery reached, signed, with every object's fate, and receipt chains that a relay cannot splice, replay or hold back.

  • Chains per publisher key and track.

    • JWP receipt chains are kept per (signer_pubkey, track_alias), which settles §6.1's two readings.
    • A legacy group's parent link is not signed, so a legacy link authenticates nothing. A legacy group is always held to a strictly increasing signed group_id, and its history cannot be replayed as new.
    • The freshness bound has a live edge: the highest signed group_id that arrived fresh. A group below the edge is exempt, so a gap of any length fills in any order. A rewritten link cannot move the edge.
    • Receipts chained per track, or on track 0, verify as before. Receipts chained per key across other tracks now fail at step 3 with parent_not_held, as the CHANGELOG states.
  • Verified by inclusion. An object receipt checked against its group's Merkle root, without the object's bytes, is inclusion-verified, and is never read as delivered. A relay forwards the receipts of objects it does not forward.

  • The stage claim (jwp-receipts §2.4.10). A sink signs:

    • the stage it presented for each unit;
    • the stage delivery reached;
    • each refinement object's fate: applied, partial, held, refused, late or dropped, with its evidence.

    Its rules tie the units to the objects unit by unit. Layers nest within the layer below. A partial object counts fewer units than it covers. Arrived objects carry bytes. Two applied objects never claim one unit.

  • The delivery-gap claim (§2.4.11): a relay states what it did not forward, under its own authority (§7.5).

  • StageObserver. The reference sink states its own stage claim from what it saw. Each base carries an epoch that changes when the sink forgets it, so no claim spans a base the sink forgot and decoded again. Every staged-conformance case replays through the sink and the observer, and each claim states exactly what the sink did.

  • Corpora and verifiers.

    • New chain and claim vectors in jwp-conformance and claim-conformance, and authorities in the key-discovery corpus.
    • The pre-branch vectors are pinned by digest as an unchanged prefix. group_chain_break is the one changed, as its section now distinguishes a gap from a fork.
    • Python verifiers agree with wai-rs, and two seeded chain differentials (one walking back across gaps) run in CI.

Text:

  • jwp-receipts §2.3, §2.4.1, §2.4.10, §2.4.11, §3, §4.1, §4.2, §4.4, §5.9, §5.10, §6.1 and §7.5;
  • staged-delivery §10.1–§10.2, http-delivery §10.5, staged-measurement §10.2, energy-binding §7.2 and SPEC.

Checked locally on macOS arm64:

  • the link gate;
  • every corpus through both implementations;
  • the claim and chain differentials;
  • the corpus replay through the observer;
  • the public-text and section-reference checks.

The forge CI runs on this pull request.

🤖 Generated with Claude Code

Stage receipts: what a sink presented and what delivery reached, signed, with every object's fate, and receipt chains that a relay cannot splice, replay or hold back. - **Chains per publisher key and track.** - JWP receipt chains are kept per (`signer_pubkey`, `track_alias`), which settles §6.1's two readings. - A legacy group's parent link is not signed, so a legacy link authenticates nothing. A legacy group is always held to a strictly increasing signed `group_id`, and its history cannot be replayed as new. - The freshness bound has a live edge: the highest signed `group_id` that arrived fresh. A group below the edge is exempt, so a gap of any length fills in any order. A rewritten link cannot move the edge. - Receipts chained per track, or on track 0, verify as before. Receipts chained per key across other tracks now fail at step 3 with `parent_not_held`, as the CHANGELOG states. - **Verified by inclusion.** An object receipt checked against its group's Merkle root, without the object's bytes, is `inclusion-verified`, and is never read as delivered. A relay forwards the receipts of objects it does not forward. - **The `stage` claim** (jwp-receipts §2.4.10). A sink signs: - the stage it presented for each unit; - the stage delivery reached; - each refinement object's fate: applied, partial, held, refused, late or dropped, with its evidence. Its rules tie the units to the objects unit by unit. Layers nest within the layer below. A partial object counts fewer units than it covers. Arrived objects carry bytes. Two applied objects never claim one unit. - **The `delivery-gap` claim** (§2.4.11): a relay states what it did not forward, under its own authority (§7.5). - **`StageObserver`.** The reference sink states its own `stage` claim from what it saw. Each base carries an epoch that changes when the sink forgets it, so no claim spans a base the sink forgot and decoded again. Every staged-conformance case replays through the sink and the observer, and each claim states exactly what the sink did. - **Corpora and verifiers.** - New chain and claim vectors in jwp-conformance and claim-conformance, and authorities in the key-discovery corpus. - The pre-branch vectors are pinned by digest as an unchanged prefix. `group_chain_break` is the one changed, as its section now distinguishes a gap from a fork. - Python verifiers agree with wai-rs, and two seeded chain differentials (one walking back across gaps) run in CI. **Text:** - jwp-receipts §2.3, §2.4.1, §2.4.10, §2.4.11, §3, §4.1, §4.2, §4.4, §5.9, §5.10, §6.1 and §7.5; - staged-delivery §10.1–§10.2, http-delivery §10.5, staged-measurement §10.2, energy-binding §7.2 and SPEC. **Checked locally on macOS arm64:** - the link gate; - every corpus through both implementations; - the claim and chain differentials; - the corpus replay through the observer; - the public-text and section-reference checks. The forge CI runs on this pull request. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jwp_delivery gains what jwp-receipts §3 step 3, §4.4 and §6.1 ask of a verifier:

- `ReceiptChains`: one receipt chain per (signer_pubkey, track_alias). A group on one
  track neither breaks nor links to another's, so a subscriber verifies the tracks it
  holds without the receipts of tracks it does not. A group heads its chain once it
  passes steps 1-3, whether or not its objects arrived, so a dropped object does not
  break the next group's link.
- `Evidence` and `ObjectReceipt::verify_against(group, envelope: Option<&[u8]>)`: over
  the envelope, `delivered-verified` (step 6 acceptance); without it, steps 1, 2 and 5
  only, `inclusion-verified`, which is never delivery.
- The opt-in checks §6.1 says the reference verifier exposes, now real: the monotonic
  group_id guard per (key, track) (`group_id_not_increasing`) and the freshness bound
  on `group_end` (`group_not_fresh`), both at step 3, both off by default.
- `claim_receipt::rfc3339_seconds`, the inverse of `rfc3339_utc`.
- staged: `ReceiptEvidence: From<jwp_delivery::Evidence>`, and a test in which a layer's
  receipt verified without its bytes is refused and over its bytes applies.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
`wai_jwp_vectors gen` appends two procedures to jwp-conformance/vectors.json; every
existing vector is unchanged byte for byte.

- `chain`: group receipts in arrival order through §3 steps 1-3 against one chain per
  (signer_pubkey, track_alias). `v6_per_track_chain_*`: one key's base track (7) and
  layer track (8), three chained groups each, alone and interleaved; the layer's second
  group without its first (parent_not_held); a parent on another track (chain_break).
  Beside them: a fork at the head, the head offered again, another key on one track, the
  opt-in group_id guard off, on and per track, the opt-in freshness bound (at the clock,
  with an offset, at the window, stale, ahead of the clock, stale with no bound), and a
  group whose signature fails heading nothing.
- `inclusion`: an object receipt checked without its envelope is `inclusion-verified`;
  a forged group, a rewritten content hash and a moved position are refused.

jwp_verify.py, written from the text, reports every outcome; its verdicts equal
`wai_jwp_vectors verdicts` line for line.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
claim_receipt gains two classes of jwp-receipts §2.4.4, both about one object:

- `stage` (§2.4.10): what a party observed of a stage set, about its base — the stage
  requested, committed, delivered and presented, as points of staged delivery's
  vocabulary `urn:wai:staged-delivery:1` (`wai-stage:layers`); units by stage; the tier
  and stage hash of what was presented; every layer object with its units, bytes,
  status (`applied`, `partial`, `late`, `dropped`, `refused`), evidence
  (`delivered-verified`, `inclusion-verified`, `none`) and staged-delivery §4.2 reason;
  conflicts, deadline misses and envelope bytes. Step 4 checks five rules in order —
  points, stage hash, units, objects, delivered stage — and the delivered stage must be
  the one the objects give (`StageClaim::delivered_stage`). Whether a commitment was met
  is derived, never stated.
- `delivery-gap` (§2.4.11): a relay's statement that it did not forward the object at
  (track_alias, group_id, object_id), with `delivery-timeout`, `discretion` or
  `not-received`; checked at step 6 against the object receipt at those coordinates
  (`gap_subject_mismatch`).

Eight reasons are added (`stage_point_malformed`, `stage_point_incomplete`,
`stage_hash_without_tier`, `stage_units_malformed`, `stage_objects_malformed`,
`stage_delivered_disagrees`, `stage_presented_above_delivered`, `gap_subject_mismatch`).
`wai_claim_vectors gen` appends 49 vectors; the 237 before them are unchanged, pinned by
a digest computed before the classes were added. claim_verify.py reports every
outcome, and differential.py gains body defects for both classes and a random family
around every stage rule (0 differ at --random 2000 --seed 1).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The differential found that no corpus vector exercised a delivered stage over objects
whose spans leave a unit uncovered: claim_verify.py with its hole check removed still
passed the corpus. `stage_delivered_over_a_hole` (frame 2's first layer dropped, a
delivered stage of 1 stated) is refused at step 4 with stage_delivered_disagrees, and
the same mutation now fails the corpus.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A `stage` claim needs the authority `stage` and a `delivery-gap` claim `delivery-gap`
(claim_authorities reads the class label, so no code changes). signer-conformance gains
a revision listing a sink's key with `stage` and a relay's with `delivery-gap`, and four
confirm vectors: each class confirmed under a fetched anchor (neither is pinned-only),
and each refused (`authority_not_granted`) under a key holding another authority — a
delivery key does not observe a stage set. `wai_signer_vectors gen` appends them; every
existing vector is unchanged. signer_verify.py reports every outcome, and its verdicts
equal `wai_signer_vectors verdicts` line for line. The key-log test over the claim
corpus now requires a confirmable claim of both classes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
`StageObserver` watches one stage set at a sink (staged-delivery §10.2): the layer
envelopes that arrived with their receipts' evidence, the layer objects known only by
their receipts (dropped at a relay or abandoned at a deadline, verified by inclusion),
and every OfferReport the sink gave. Over the base's chain it states the jwp-receipts
§2.4.10 body: each object's status and first reason (a unit still held counts as
`layer-gap`, an envelope held for its base as `base-absent`), its evidence and bytes;
units by stage, frozen units and the units a layer missed the deadline of; the tier and
stage hash of what is presented; and, only where the base's receipt was verified over
its bytes before its first layer, the delivered stage the objects give, with the request
and commitment beside it.

A test drives a chain through three sets — a dropped second layer, a live set with a
late first layer and a partial second, and an unattested base — and each body seals and
verifies.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The text W1-5 owes, on the code and corpora already landed on this branch:

- jwp-receipts §2.3, §2.4.1, §3 step 3 and step 6, §6.1: receipt chains and the
  `group_id` guard are per (`signer_pubkey`, `track_alias`). The text disagreed with
  itself (per key in §2.4.1 and §6.1's first bullets, per track in the signing payload
  and §6.1's last); it now says per key and track throughout. A group that passes
  steps 1-3 can head its chain whether or not its objects arrived. §6.1 names the
  reference verifier's opt-in guard and freshness bound, and the new step-3 reasons
  `group_id_not_increasing` and `group_not_fresh` join §3.3.
- §3 *Verified by inclusion*: an object receipt checked without the object's bytes is
  `inclusion-verified`, never acceptance or delivery. §4.1 makes a relay forward the
  receipts of objects it does not forward; §4.2 lets it sign a `delivery-gap` claim;
  §4.4 says how a subscriber accounts for an object it did not receive.
- §2.4.4 rows and new §2.4.10 `stage` and §2.4.11 `delivery-gap`, their reasons in
  §3.1, their authorities in §7.5, their corpora in §5.9 (v6_per_track_chain) and
  §5.10. §2.4.9's vocabulary row names staged delivery's `urn:wai:staged-delivery:1`,
  which energy-binding §7.2 registers.
- staged-delivery §10 splits into §10.1 attestation (naming the two evidences and the
  per-track chains) and §10.2 claims; its attestation references point to §10.1.
  http-delivery §10.5 says what an abandoned refinement is in receipt terms. SPEC's
  pointer to jwp-receipts lists the new classes. CHANGELOG entry.
- The rule-5 vector is named for rule 5 (`stage_null_delivered_skips_rule5`).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
GroupReceipt::check_parent required the previous group to be the same key's; it now
requires the same key and track, as jwp-receipts §3 step 3 says, so a caller that hands
over a group of another track does not link to it even when the hashes agree.
jwp_verify.py reads it the same way, and jwp-conformance gains
group_parent_on_another_track (through `wai_jwp_vectors gen`; every other vector is
unchanged).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The simulator models no receipt; over a network a dropped object's receipts still
reach the sink (jwp-receipts §4.1), which accounts for it as inclusion-verified and
lists it as dropped in a stage claim. CHANGELOG notes it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fix round, items 1, 7, 9 and 10 of the review of W1-5.

- A legacy group's parent link is outside its signature, and its link hash does not
  cover the link: rewriting the first group's link to name the head re-linked the whole
  history, and every replay was accepted with the guard off. The legacy payload does sign
  `group_id`, so ReceiptChains now holds every legacy group to the strictly increasing
  `group_id` guard, whatever the opt-in setting; the guard stays opt-in for labelled
  groups, whose payload signs the link.
- ReceiptChains keeps, per chain, the link hashes of the groups it accepted. With a head
  held, a parent that names one of them other than the head is `chain_break` (a fork);
  a parent that names none is `parent_not_held` (a gap), and is recorded as requested. A
  requested group fills a gap, is history, and is exempt from the freshness bound, so one
  group a relay delays past the window no longer wedges the chain.
- GroupReceipt::check_parent treats a previous group of another key or track as not held:
  a null parent starts the chain, any other is `parent_not_held`; a parent that is not the
  held head's link names a group not held, and is `parent_not_held` too.
- jwp_verify.py mirrors all three, and computes days from civil dates in integers, so a
  `group_end` in year 0000 no longer crashes it.
- jwp-conformance: v6's base track is now labelled, so the headline vector does not rest
  on legacy links; new vectors for the legacy guard, the legacy replay and its loop, a
  labelled replay, a fork, a gap, the freshness gap refilled, receipts chained per key
  across tracks, and previous groups of another track or key. `group_chain_break`, which
  predates this branch, now holds a genuine chain break (a second chain head); its old
  input, a parent that is not the previous group held, is `group_parent_not_the_previous`
  and is `parent_not_held`. A lib test now regenerates and runs the pinned JWP corpus.
- chain_differential.py, the review's differential over signed chains, is committed and
  runs in CI on two seeds (0 differences on each).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Text for fix-round items 1, 7, 8 and 9.

- §3 step 3 now lists its cases against the chain of the group's key and track: the
  head offered again; no head, where a null parent starts the chain; a head held, where
  the head's link links, a null parent or a held non-head parent is chain_break and any
  other parent is parent_not_held (requested). It says a legacy link authenticates
  nothing, and that the legacy payload signs group_id, so a verifier MUST refuse a legacy
  group whose group_id does not exceed its head's; the guard stays opt-in for labelled
  groups. A group requested to fill a gap is exempt from the freshness bound.
- §6.1 says the same of replay, says what a legacy chain still cannot rule out (a dropped
  group with the next link rewritten), and says that receipts chained per key across
  non-zero tracks now fail at step 3 (parent_not_held).
- §3.3 splits chain_break from parent_not_held; §5.9 and the jwp-conformance README
  describe the new vectors and chain_differential.py. The CHANGELOG no longer says no
  issued receipt changes meaning: that holds for track_alias 0 and per-track chains.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fix-round items 3, 5 and 6, and the observation on delivered against presented.

- Rule 4 (objects): an object refused before its `refines` was checked against the set
  (staged-delivery §4.1 steps 1-2: refines-malformed, capability-not-refinement,
  base-absent) lists its units as its refines states them and is exempt from the range
  check, so a layer whose span runs past a public base no longer stops a sink signing;
  such an object delivers nothing to the set. Applied or partial objects must be
  delivered-verified only when a delivered stage is stated (the base's receipt verified
  first); otherwise any evidence is allowed, so a sink over an unattested base can sign.
  An object that arrived has bytes and a dropped one has none; the objects' bytes are
  fewer than the set's, which counts the base. Two objects of one layer applied to one
  unit are refused.
- New rule 5 (`stage_units_unsupported`): the units at stage s or above are no more
  than the applied and partial objects of layer s cover.
- Rule 6 (delivered) keeps `stage_delivered_disagrees`. `stage_presented_above_delivered`
  is gone: with rules 4 and 5 a stated delivered stage is never below the one
  presented, so the reason could not be reached.
- claim-conformance (through `wai_claim_vectors gen`): the review's cases a-e as refusals,
  a set over an unattested base applied on inclusion evidence, a refused span past the
  set accepted and the same span refused for a checked reason, a set whose layers were
  all refused meeting its commitment with stage 0 presented, and the objects counting
  the whole set's bytes. `stage_presented_above_delivered` is now
  `stage_delivered_yet_applied_without_receipt`, and the null-delivered vector is named
  for what it shows (`stage_null_delivered_states_no_delivery`). Every vector before
  this branch is unchanged. claim_verify.py mirrors every rule, and differential.py's
  stage family now draws spans past the set and the unchecked reasons (0 differ).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The earlier assertion placed the unchecked object at a layer the set's objects already
covered, so counting it changed nothing and its mutation survived. It now sits at layer
2, where only it would complete the coverage.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
stage_unchecked_layer_delivers_nothing_accept: with the second layer dropped, another
second layer refused refines-malformed and delivered over its bytes would complete layer
2's coverage if it counted; it does not, so the stated delivered stage of 1 stands. A
verifier that counts it (Rust or Python) now fails the corpus.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fix-round items 2 and 4.

- A report that refuses nothing for the envelope, or applies a unit, clears an earlier
  refusal and hold: a layer refused (refinement-unattested, chain-over-limit) and then
  offered again and applied was stated refused. What the sink applied now decides the
  status first.
- On arrival the envelope's own `refines` sets the object's layer and span, which its
  content hash authenticates, over whatever a receipt-only sighting supposed: a guessed
  span let a claim state a delivered stage no delivered object reached.
- A base the sink forgets (§4.4, §14) is, decoded again, one it never decoded:
  `StageSink::remembers` says whether the sink still holds or keeps anything of a base,
  and `StageObserver::forget` starts the observation over. Without it, a first layer
  applied before the record budget forgot the base and another applied after would
  both be stated applied to the same units, which §2.4.10 now refuses.
- staged::claim_tests: every staged-conformance case replayed through StageSink and a
  StageObserver per base (started over when the sink forgets it); each claim must seal,
  verify, and state for every object exactly what the sink did. The review's probe2 (a)
  and (b), and a layer refused then applied, are tests of their own.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
staged::claim_tests::a_layer_refused_then_held_states_the_hold: a second layer refused
for inclusion-only evidence, then offered again with delivered evidence and held for units
below its stage, is stated refused with layer-gap, the sink's last decision, not with the
receipt reason it no longer has. The refused-then-applied test could not show the clearing
of an earlier refusal, since what was applied decides the status first.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
staged::claim_tests::a_layer_applied_then_refused_on_a_later_offer_stays_applied: what
the sink applied decides an object's status before any later envelope refusal.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Text for fix-round items 2-6 and the observation.

- jwp-receipts §2.4.10: an object refused before its `refines` was checked lists its
  units as stated and delivers nothing; an arrived object has bytes. Rule 4 makes the
  evidence of an applied layer depend on whether a delivered stage is stated, counts the
  base's bytes in the set's, and refuses one layer applied twice to a unit. New rule 5,
  the units against the objects (`stage_units_unsupported`); rule 6, the delivered stage.
  `stage_presented_above_delivered` is gone: rules 4 and 5 make it unreachable, and the
  text says why. A new paragraph says delivered and presented are separate measures,
  and that a set whose layers were all refused meets its commitment with stage 0
  presented.
- §3.1's reason table and §5.10 follow; staged-delivery §10.2 says a claim's observation
  starts over when the sink forgets the base; the CHANGELOG and the claim-conformance
  README say the same, and that one existing jwp vector changed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Round-3 items 1, 2, 5 and 6.

- A relay could forge the "requested" exemption for a legacy group at the live edge (it
  rewrote the stale group's unsigned link to name a group not held, and the genuine
  group then passed as requested), and a gap past 64 groups still wedged the chain. The
  requested-links set and CHAIN_REQUESTED_LINKS are gone. A chain's live edge is the
  highest group_id of a group that verified and arrived inside the window, linked or
  not; a group whose group_id is below it is history and exempt from the bound. Both
  payloads sign group_id (it is the second field of each), so no rewritten link moves
  the edge or earns the exemption; no count bounds a gap, and it fills in any order.
  ReceiptChains::live_edge replaces requested().
- jwp_verify.py mirrors it. New vectors: the rewritten legacy link (refused) and its
  labelled control, and a gap of seventy groups walked back and forward (every group
  accepted). chain_gap_differential.py, the review's differential over long, shuffled,
  withheld and rewritten chains, is committed and runs in CI on two seeds (0 differences).
- group_parent_not_the_previous moves to the end of jwp-conformance, so the pre-branch
  vectors are again its prefix; a test pins them, less group_chain_break, by a digest
  computed from the corpus before this branch.
- §3 step 3, §6.1 (which now states the rule and why it is safe), §3.3, §5.9, the README
  and the CHANGELOG follow. §6.1's older duplicate "Both are per track" bullet is deleted.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The exemption is for a group_id strictly below the edge: a second, stale group with the
edge's group_id that links to the head is not history. The test now shows it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
chain_stale_group_moves_no_edge: a stale group 2 naming a group not held, then a stale
group 1 linking to the head. Only a group that arrived inside the window moves the edge,
so group 1 is refused as stale; a verifier that lets a stale group move the edge would
accept it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Round-3 item 3.

Rule 5 counted coverage per layer, so a partial object counted as though applied to its
whole span, and a layer could be applied over units the layer below never reached. Now,
for every layer s: an applied object of layer s lies within the spans of layer s - 1's
applied and partial objects (the set, for layer 1), and a partial one meets them; and the
units at stage s or above are no more than layer s can have reached — its applied
objects' units, and, of the units below them within layer s - 1's spans, those its
partial objects cover, at most each one's span less one. A partial object covers at
least two units (rule 4). Interval arithmetic in both verifiers, since a claim may name
very large unit counts.

claim-conformance (through `wai_claim_vectors gen`): the review's three cases as
refusals and its honest partial layer as an accept, and a partial object over one unit.
The corpus replay still seals every honest claim; claim_verify.py mirrors the rule, and
the differential agrees (0 differ). §2.4.10 rule 5 and the rule-6 justification, which
overstated for partial objects, are rewritten; §3.1's reasons, §5.10, the README and the
CHANGELOG follow.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Round-3 item 4.

- StageSink::base_epoch(hash) counts the times the sink has forgotten a base: evicted
  under the record budget, discarded by forget_base, or dropped with nothing to keep.
- StageObserver records the epoch with every event (arrived, dropped, reports take it).
  A later epoch starts the observation over: an envelope the sink still holds for the
  base keeps its arrival, bytes and evidence and is held again with no decision, every
  other object is dropped, and no delivered stage is stated until the caller says the
  base's receipt was verified first. A stale epoch's event is ignored. claim(chain,
  epoch) never spans two epochs: if the sink forgot the base since the last event, the
  claim is made over the observation started over. forget() is gone.
- Tests from the review's probe3: a layer held across the forget is stated applied once
  the base is decoded again and the layer released; and a base attested, refined,
  forgotten and decoded again unattested states no delivered stage and no layer over
  units at stage 0, though `remembers` is true again by claim time. The corpus replay
  now feeds each observer the sink's epochs instead of polling `remembers`.
- staged-delivery §10.2 and the CHANGELOG say so.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Merge main (HTTP delivery merged as #170) into wai/w1-5-stage-receipts
All checks were successful
standards workspaces build + test / joulehook-rs (pull_request) Successful in 1m8s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m12s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 6m48s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 5m11s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m14s
wai cross-architecture byte-equality / macos-arm64 (reference) (pull_request) Successful in 37s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 23s
standards workspaces build + test / sense-rs (pull_request) Successful in 1m4s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 1m39s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 44s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 3m36s
wai cross-architecture byte-equality / linux-x86_64 (pull_request) Successful in 23s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 5m37s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 12s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 11m33s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 37s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 3m28s
wai-pack / wai-pack · macos (pull_request) Successful in 1m31s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 14m48s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 8s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 22s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 44s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 1m32s
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Successful in 1m3s
wai-pack / wai-pack · linux (pull_request) Successful in 42s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 54s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 1m6s
WAI section references / cited sections exist (pull_request) Successful in 9s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 1m51s
workflow hygiene / every workflow supersedes its own queue (pull_request) Successful in 3s
8c3ab4e866
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!171
No description provided.