Skip to content

Relay: remember a Subgroup's lowest forwarded Object across contributors (FIRST_OBJECT) - #108

Merged
floatdrop merged 1 commit into
draft-20from
fix/first-object-ledger
Sep 26, 2026
Merged

floatdrop merged 1 commit into
draft-20from
fix/first-object-ledger

Conversation

@floatdrop

Copy link
Copy Markdown
Owner

Summary

#103 made the relay honour a publisher's FIRST_OBJECT claim (§11.4.2) only if no lower Object ID was already forwarded in the Subgroup. Objects are published in ascending order (§2.2), so a lower ID proves the claim wrong. That record lived in the Subgroup's writer set, which is torn down when the Subgroup's last publisher leaves. A publisher arriving later could then claim FIRST_OBJECT above an Object already forwarded, and the relay passed the claim on. #103 listed this as a known gap.

Change

  • The ledger remembers the lowest Object: the dedup ledger (registry.TrackEntry) records the lowest Object ID forwarded in each Subgroup. Status Objects count; datagrams don't, since FIRST_OBJECT exists only on subgroup streams. TrackEntry.LowestForwarded exposes the value.
  • New writer sets start from it: a new writer set takes its starting value from the ledger, so the check holds across publishers within the ledger's 32-Group window.
  • Read, then take the minimum: the set reads the ledger before taking the Subgroup lock, then keeps the minimum of that value and its own. A publisher that joined in between may already have forwarded a lower Object. The review found this race; it can't be reproduced reliably in a test.
  • Cost: the ledger is read once per new writer set, and each Object costs one compare in the ledger, with no allocation.
  • STATUS.md: the §9.3 row states the rule.

Tests (verified red first)

  • TestFanout_MultiPublisher_FirstObjectAfterTeardown:
    1. Publisher A sends Objects 0–2, then resets its stream. It resets rather than FINs, because a FIN would end the Subgroup and make B's Object 3 malformed under §2.4.2.
    2. The downstream stream ends, which tears the writer set down.
    3. Publisher B claims FIRST_OBJECT at Object 3.
    4. The subscriber's new stream must have FIRST_OBJECT clear.
  • TestTrackEntry_LowestForwarded: the lowest Object per Subgroup, with status Objects counted and datagrams excluded.
  • Checks: lint, the full suite, -race and make bench-quick all pass, with no allocs/op increase.

Note

A duplicate whose first copy was in another Subgroup, or was a datagram, also lowers the recorded minimum. That can only clear FIRST_OBJECT, never set it wrongly. It's documented on the field.

🤖 Generated with Claude Code

…ributors

#103 honours a contributor's FIRST_OBJECT claim (§11.4.2) only if no
lower Object ID was forwarded in the Subgroup (§2.2), but kept that in
the Subgroup's writer set, which is torn down when its last contributor
leaves: a later contributor claiming FIRST_OBJECT above an Object
already forwarded was then honoured.

The dedup ledger now records the lowest Object ID forwarded per
Subgroup (status Objects included, datagrams not), and a new writer set
starts from it (TrackEntry.LowestForwarded), within the ledger's
32-Group window. It reads the ledger before taking sg.Mu and keeps the
minimum, since a contributor that joined meanwhile may already have
forwarded a lower Object; that race is not reproducible in a test and
was found in review.

TestFanout_MultiPublisher_FirstObjectAfterTeardown and
TestTrackEntry_LowestForwarded were verified red first.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@floatdrop
floatdrop merged commit dbe571e into draft-20 Sep 26, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant