Skip to content

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

Open

Description

@dezren39

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 BoardListing with supersedes == None for 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 both listed: true and listed: false second 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

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    priority/normalOrdinary priority (the default)
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    effort/sSmall: expected under 2 hours of work
    on Nov 2, 2026
  2. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator job (lane A, new-issue-creator-code-a)
    • Session / ID: cron run 2026-11-01 19:05 CST (2026-11-02T01:19Z)
    • Found via: filed this run
    • Task: Filed issue 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 — a second "first" BoardListing (v1, supersedes=None) is accepted for an already-listed (board, host), so publication state falls to the content-hash tie-break instead of the signed version chain. Verified this run on the laptop with verbatim boards.rs from main d963623 (md5 e142727a25d638136a27ac9ecfb7c3f9): second v1 listed=false ACCEPTED; controls (legit v1, proper v2 supersede, first listing for a second host) all correct.
    • Expectation: effort/s — reject a supersedes=None listing when any listing for the (board, host) already exists, plus tests for both listed values and a two-host control; replication convergence preserved.
    • Links: probe ~/git/fleet/probe-2026-11-01-laneA-listingfirst/ (laptop); queue packet enqueued this run (repo-health-code-pilot).
  3. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · adopted as Phase 1 of design #4113

  4. added
    work/assignedQueue worker lifecycle state
    and removed on Nov 2, 2026
  5. 18 remaining items

  6. developing-today commented on Nov 3, 2026

    @developing-today
    Contributor

    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

  7. added
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 3, 2026
  8. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

  9. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    Picked up: OpenChamber scheduled lane claimed task 8341 via atomic queue claim.

  10. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    Triage: CONFIRMED on current main e41dabde59e8581e9b314bdc352ba2111a216d6c.

  11. added
    status/triagedTriaged and routed (see agent note)
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    on Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/idPackage/app: id (pkgs/ or apps/ in this repo)effort/sSmall: expected under 2 hours of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobsource/design-architectFiled by the repo design architect jobstatus/triagedTriaged and routed (see agent note)task/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions