Repository navigation
Description
Summary
BoardGraph::insert accepts a second "first" listing for the same
(board, host): a BoardListing with version == 1 and
supersedes == None is admitted even when a listing for that host already
exists. The documented listing contract (pkgs/id/src/boards.rs, the
BoardListing doc comment) is: "the first listing is version 1, and every
later listing supersedes the previous one and advances the version by
exactly one." A later listing that supersedes nothing breaks that chain:
two competing version-1 listings for one host then coexist, and
latest_listings resolves the tie by greater content hash — so whether
the host counts as listed or withdrawn depends on hash luck, not on the
signed version sequence.
Evidence (verified this run, current main d9636237)
BoardListing link validation, pkgs/id/src/boards.rs:763-790
(BoardGraph::validate_links): when supersedes is Some, the arm checks
same board scope, same host, same author, and
version == previous.version + 1. When supersedes is None, the whole
check is only:
} else {
ensure!(
value.version == 1,
"the first listing for a host starts at version 1"
);
}It never checks whether a listing for this (board, host) already exists.
Laptop probe (verbatim boards.rs from main, md5
e142727a25d638136a27ac9ecfb7c3f9, harness reused from the lane-B boards
probes; probe dir ~/git/fleet/probe-2026-11-01-laneA-listingfirst/ on the
laptop):
v1 publish (supersedes=None): OK; active=true
second v1 (supersedes=None, listed=false): ACCEPTED hash=04beb284fb508a6a39cd522ac9178a95d878638ee294a084b6a849d131699959
latest_listings now: version=1 listed=true (tie at v1 broken by content hash)
active_listings contains host: true
control v2 supersedes v1: OK; active=false
control first listing for host2: OK; active2=true
So the board creator can sign a withdrawal-shaped v1 that supersedes
nothing; in this run it lost the hash tie-break and the publish stayed
active, but with different body bytes the same construction wins the tie
and the host reads as withdrawn — with no v2 anywhere. Publication state
for a (board, host) pair must be a function of the signed version chain
only.
The existing test listing_publish_and_withdraw_follow_signed_versions
(boards.rs:2491) covers foreign signer, first-version-must-be-1, version
skips, and the proper v2 withdrawal — but never a second supersedes: None
listing for an already-listed host.
Why it matters
Listings are the federation discovery mechanism from #3806: hosts decide
what to replicate and serve from active_listings. A duplicate "first"
listing makes that decision hash-dependent and identical graphs can be
built where the same signed set yields listed/unlisted by content-hash
ordering rather than by the version rules the module documents and
enforces everywhere else (same-host, +1 version, supersedes required).
It also gives a confused or malicious board creator a way to mint an
untethered withdrawal that no chain audit can order against the publish.
Acceptance criteria
- Inserting a
BoardListingwithsupersedes == Nonefor a (board, host)
that already has any listing in the graph is rejected, with an error
naming the rule (e.g. "the first listing for a host starts at version 1"
is no longer sufficient — the rejection must be because one exists). - The legitimate first listing for a host (any host, supersedes None,
version 1) is still accepted; the proper supersede chain (v2 over v1,
same host/author, +1) is unchanged. - Replication convergence is preserved: in
replicate_board_from, a
listing set exported from a valid source graph still converges
(listings whose supersedes target is present insert in dependency
order; a first listing never becomes un-insertable because a later
version arrived first — later versions cannot insert before their
supersedes target exists, so this ordering already holds). - New test coverage in the boards test module: second-first listing
rejected for bothlisted: trueandlisted: falsesecond copies;
first listings for two different hosts on one board both accepted. - Existing boards tests pass (
listing_publish_and_withdraw_follow_signed_versions,
two_hosts_converge_via_replication, and the rest of the module).
Distinct from
- id(boards): Rules revision numbers are not unique per board — two different revision-1 rule texts coexist in one graph #3964 (Rules revision numbers not unique per board — same shape, different
object kind; Rules carry no supersedes chain, listings do). - id(boards): Post embed_ref is never validated — dangling and cross-board game-post references are accepted into BoardGraph #3963 (Post
embed_refnever validated), id(boards): BoardFork records are in no board's replication set — fork provenance never replicates and replicas still report Complete #4081 (BoardFork absent from
replication sets), id(boards): compose_view applies supersedes edges from superseded attestations — a nullified revocation still revokes #4003 (compose_view superseded-attestation edges). - The same-version signed fork that
latest_listingsdocuments
(two listings at one version) arises through the supersedes chain;
this issue is only about the untetheredsupersedes: Nonecase.
Activity
dezren39 commentedon Nov 2, 2026
🤖 Agent note · filed
new-issue-creator-code-a)~/git/fleet/probe-2026-11-01-laneA-listingfirst/(laptop); queue packet enqueued this run (repo-health-code-pilot).dezren39 commentedon Nov 2, 2026
🤖 Agent note · adopted as Phase 1 of design #4113
Rulesrevisions (id(boards): Rules revision numbers are not unique per board — two different revision-1 rule texts coexist in one graph #3964, Phase 2) are NOT rejected at insert, because Rules has no chain and plural authorised signers — do not import this phase's rejection into that one. Whether the replication stall error should name a two-first-listings equivocation is an open question on Design: one signed set, one meaning — BoardGraph's gates hold at insert, projection and replication (pkgs/id) #4113 for this phase's triage to note, not an acceptance item.source/design-architect. Existing queued triage packet stands.18 remaining items
developing-today commentedon Nov 3, 2026
agent-note:v1
state: triaged (coverage pass)
Coverage pass 2026-11-03: reviewed in the fleet-wide per-issue pass (drift / overlap / already-handled / stale / blockers / ordering). Verdict: still live; queued in repo-health-code-pilot as triage at priority 40 (priority/normal). If this is already handled at current main, the triage closes it with evidence; if it needs splitting, the triage files the split.
Found via: coverage pass 2026-11-03
dezren39 commentedon Nov 3, 2026
🤖 Agent note · handed off
opencodedezren39 commentedon Nov 3, 2026
Picked up: OpenChamber scheduled lane claimed task 8341 via atomic queue claim.
dezren39 commentedon Nov 3, 2026
Triage: CONFIRMED on current main
e41dabde59e8581e9b314bdc352ba2111a216d6c.BoardGraph::validate_linksstill acceptsversion == 1withsupersedes == Nonewithout checking whether the same (board, host) already has a listing, so id(boards): a second 'first' BoardListing (v1, supersedes=None) is accepted for the same host — publication state falls to the content-hash tie-break #4109 remains live on main.queue/6316-4109-second-first-listing, authored by dezren39) contains the same-board/host duplicate-first guard and the requestedlisted: true/listed: falseplus second-host tests. PR is OPEN; basic check SUCCESS, CodeRabbit NEUTRAL. No duplicate fix packet or PR created, and the Drew-owned PR was not touched.