Skip to content

Design: one signed set, one meaning — BoardGraph's gates hold at insert, projection and replication (pkgs/id) #4113

Closed
2 of 6 issues completed

Description

@dezren39

Summary

pkgs/id/src/boards.rs makes one promise in four places, and the fleet has now filed a counterexample at every one of them. The module header (boards.rs:3-9) says BoardGraph "checks signatures, canonical references, board scope, and the authority rules needed to accept an object". The has_role design note states the determinism rule the module is built around: peers holding the same signed set must reach the same verdict, in any arrival order. compose_view's own comment states the projection rule: "a superseded record … has no effect in the projection, whatever it once said." And the federation docs promise a lagging replica is "never silently presented as current". Five filings in three days span all three gates of the graph:

Pass 34 of the design architect assessed #3963/#3964 as a pair and named the trigger ("a third instance makes it a cluster"); #4003, #4081 and #4109 have since filed. This design is the cluster's origination — a sibling of #2898/#4000 (same crate, same validate-at-the-gate family, different surface: BoardGraph has no reconstruction path; objects are inserted, projected and replicated, never restored), not a phase of either.

Full design doc: files/designs/code-id-boardgraph-one-signed-set.md in the keep-the-four-repos-healthy goal workspace. Audit baseline: origin/main d9636237 (all sites read verbatim at HEAD this pass).

The contract

One signed set, one meaning. Every reference an object carries is validated at insert (existence + board scope, the kind rule stated). Where a kind carries a signed chain, the chain's head is unique at insert. Where a kind has no chain, competing versions are signed history and the projection resolves them by a stated deterministic rule — never by arrival order. A record contributes its edges to a projection only while it is itself active. And a board's replication set is exactly the objects scoped to it plus the closure those objects need to be accepted on a fresh graph — so Complete means the whole set, including its provenance records.

Recorded decisions

  1. embed_ref is a reference like the others (Phase 1, id(boards): Post embed_ref is never validated — dangling and cross-board game-post references are accepted into BoardGraph #3963). The Post arm validates it with require_scoped (existence + same-board), per id(boards): Post embed_ref is never validated — dangling and cross-board game-post references are accepted into BoardGraph #3963's acceptance. Kind rule stated, not implicit: in v1 the only game-post object kind in the graph is Post, so the target must be a Post; when a dedicated game-post kind lands, the kind check updates in the same change that adds the kind, and the module doc says so. Lazy validation at projection loses — the graph is the substrate later layers are told they can trust.
  2. A second "first" listing is refused (Phase 1, 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). Per 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's acceptance, including its convergence argument. Listings are where rejection is right: single-author chain (only the board creator signs), and the supersedes link is the ordering — a chain-less second head is a malformed chain, not a race between peers.
  3. Duplicate Rules revisions are history; projections resolve them deterministically (Phase 2, id(boards): Rules revision numbers are not unique per board — two different revision-1 rule texts coexist in one graph #3964 — option (b), recorded). Option (a), insert rejection, loses for the module's own stated reason: Rules has no supersedes chain and its authorised signers are plural (owner + any moderator, require_moderator). Two moderators can sign revision N concurrently in good faith; first-arriver-wins rejection makes acceptance depend on arrival order — the property the has_role note rejects for this module — and a graph holding both would stall replication instead of converging. Instead: duplicates are legal signed history; one resolution rule for every projection — for a given revision the greater content hash wins (the tie-break latest_listings already documents for signed forks); "current rules" is the highest revision, resolved the same way — exposed as one graph helper with the rule in the module doc, so the board-view layer consumes it instead of re-deriving a pick. Hash-pinned Thread.rules_ref is unaffected.
  4. Only active records contribute edges — attestations included (Phase 2, id(boards): compose_view applies supersedes edges from superseded attestations — a nullified revocation still revokes #4003). attestation_superseded is computed only from RoleAttestations not themselves in the generic superseded set — the fixed-point frontier compose_view already computes for Supersede records, whose comment already states this rule. Per id(boards): compose_view applies supersedes edges from superseded attestations — a nullified revocation still revokes #4003's acceptance and its three preserved-behaviour controls. Attestation-to-attestation supersession is unchanged; this governs generic-supersede nullification only.
  5. The fork record travels with the fork board (Phase 3, id(boards): BoardFork records are in no board's replication set — fork provenance never replicates and replicas still report Complete #4081). A BoardFork belongs to its fork board's set — not the source's, not both. A source replica does not need to learn it was forked to serve the source, and adding the record to the source's set would flip every source replica to Incomplete because of an object about a different board. The closure follows acceptance: the fork's set additionally pulls the source root and every source_frontier object (the record's validate_links arm requires both roots and the frontier's source scope), with frontier objects' own dependencies through the existing closure expansion. replica_status_against needs no special case once the record is in the set. Source membership is revisited only if a consumer that queries a source for its forks is ever filed.
  6. No wire-format or writer change. No struct, kind string or hash changes in any phase.

Alternatives that lose (in the doc): five independent fixes with the two open rulings left to triage; rejecting duplicate Rules (decision 3); fork record in both sets or source-only (decision 5); folding into #2898/#4000 (no reconstruction surface — see Summary); resolving the listings tie by hash instead of refusing the second first (leaves listed/withdrawn to hash luck where the single-author chain lets insert refuse outright).

Phased breakdown

Open questions stay in the design doc (source-side fork discovery if a consumer is ever filed; whether compose_view surfaces current rules via the Phase 2 helper now or the board-view layer adopts it on first use; replication stall diagnostics naming a two-first-listings equivocation — Phase 1 triage notes it, not a phase; the embed kind rule when a dedicated game-post kind lands).

Related: #2898 / #4000 (sibling designs — see Summary), #3806 (federation origin for Phases 1/3), #2813 (board views, Phase 2), #3231 (envelope gate in artifact.rs — no field overlap), #3455 (unrelated, same crate).

🤖 Design architect pass 35 (2026-11-01), audit at origin/main d9636237.

Acceptance criteria

Activity

  1. added
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    epicMeta-issue grouping multiple items (org Project Type=Epic)
    kind/designA design proposal (design-architect): multi-step, phased, decision-bearing
    on Nov 2, 2026
  2. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · design originated, pass 35

  3. added
    priority/normalOrdinary priority (the default)
    effort/lLarge: multi-day or open-ended; expect staged check-ins
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    on Nov 2, 2026
  4. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    Intake review

    🤖 Fleet monitor (repo-fleet-monitor) intake pass, 2026-11-02.

  5. added
    queue/queuedHas a queued task in the Supabase agent queue
    task/triageQueue packet kind: verify, reproduce, and route this item
    on Nov 3, 2026
  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
    work/assignedQueue worker lifecycle state
    queue/claimedQueue 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 · done

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/lLarge: multi-day or open-ended; expect staged check-insepicMeta-issue grouping multiple items (org Project Type=Epic)kind/designA design proposal (design-architect): multi-step, phased, decision-bearingmeta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/normalOrdinary priority (the default)source/design-architectFiled by the repo design architect jobtask/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