Skip to content

id(attestation): compute_state never checks the two players are distinct — players=[A,A] attests Agreed from one key's single genesis + single claim #2863

Closed

Description

@dezren39

id(attestation): compute_state never checks the two players are distinct — an agreement with players = [A, A] attests Agreed from one key's single genesis + single claim

Evidence (main 07448571, merged today in PR #2854 for #2248)

pkgs/id/src/attestation.rs (md5 530fcdf45da5d8688dc0e4e319b641b2) implements the mutual-concession viewer:

  • GenesisAgreement.players: [PlayerId; 2] (:56-62) — "Both players, in seat order." Nothing in sign_genesis, GenesisAgreement, or compute_state requires the two seats to be different keys.
  • compute_state (:223) checks the genesis pair with agreement.players.iter().all(|player| genesis.iter().any(|(author, signed)| author == player && signed == agreement)) (:228-232). With players = [A, A], one genesis event from A satisfies both seats, because any is re-evaluated per seat against the same entry.
  • The claim phase (:239-250) enumerates the seats and, for each seat, scans the claims for that player. With [A, A], A's single claim fills per_player[0] and per_player[1] both — the (Some(a), Some(b)) arm (:251) then compares A's claim with itself, so outcome and final-state hash trivially "concur" and the state is Agreed.

The module's own contract says the opposite: "Proposed until both players have signed the same genesis and concurred" (module doc), "two claims (one per player) that concur … are Agreed" (compute_state doc, :216-221).

Verified (laptop probe, verbatim compute_state + object types, real p2panda-core Hash at the pinned rev da80c05b)

Probe: ~/git/fleet/probe-2026-10-30-laneB-attdup/ on the laptop (src/main.rs md5 83aab30df89c89f1549f314d795f6fde; the extracted types/compute_state are byte-identical copies from attestation.rs — only the chain-signing helpers are omitted, because compute_state consumes pre-decoded author/object pairs exactly as the shipped tests call it, e.g. one_claim_only_stays_proposed in tests/attestation_poc.rs:202):

  • players=[A,A], genesis = [ (A, agreement) ] (one event), claims = [ (A, claim) ] (one event) → Agreed ← the bug: a single key attested a "mutual" result entirely alone
  • players=[A,A], genesis = [ (A, agreement) ], claims = [] → Proposed (control: the claim is what completes it)
  • players=[A,B], both genesis, both claims concur → Agreed (control: normal path unchanged)
  • players=[A,B], both genesis, A's claim only → Proposed (control: the existing one-claim test's expectation)

The shipped tests (tests/attestation_poc.rs) cover the distinct-player happy path, the wrong-winner dispute, and the one-claim case — no duplicate-seat case.

Why it matters

Acceptance criteria

  • compute_state never returns Agreed (or Disputed) for an agreement whose two player seats are the same key: a [A, A] agreement with any genesis/claim set from A computes Proposed (or the chosen explicit invalid-agreement result — triage pins which), because there is no second player.
  • The distinct-player behavior is unchanged: [A, B] with both genesis + concurring claims → Agreed; differing claims → Disputed; one claim → Proposed.
  • A regression test covers the probe matrix: [A,A] with one genesis + one claim, [A,A] with two distinct claims from A, and the distinct-player controls.
  • Triage also pins whether sign_genesis should refuse to sign a duplicate-seat agreement (defense in depth) or whether the viewer check alone is the intended invariant — either way the viewer must not attest a self-match.

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    priority/normalOrdinary priority (the default)
    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
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    effort/xsTrivial: expected under 30 minutes of work
    on Oct 30, 2026
  2. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator job (lane B, new-issue-creator-code-b)
    • Session / ID: cron job new-issue-creator-code-b, run 2026-10-30T23:24Z
    • Found via: filed this run
    • Task: Filed after verifying on the laptop that compute_state (pkgs/id/src/attestation.rs:223) attests Agreed for a duplicate-seat agreement: with players=[A,A], one genesis event and one claim from A alone fill both seats. Probe: laptop ~/git/fleet/probe-2026-10-30-laneB-attdup/ (verbatim compute_state + types, real p2panda-core Hash); distinct-player controls Agreed/Proposed as expected. Enqueued as a triage packet for the repo-health-code-pilot queue.
    • Expectation: effort/xs — triage pins the invalid-agreement result (Proposed vs explicit) and whether sign_genesis also refuses duplicate seats; done when the viewer never attests a self-match and the probe matrix is a regression test.
    • Links: evidence in the issue body; related id: PoC: mutual-concession match attestation on the tic-tac-toe example — genesis agreement + dual signed result claims, offline-first (from #2037) #2248 (the PoC, closed), id: design: match attestation + audit v1 #2039 (attestation v1 design, open)
  3. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    Agent note (design-architect, 2026-10-30): this issue is Phase 2 of design #2898 ("reconstruction is admission", design doc files/designs/code-id-reconstruction-validation.md in the keep-the-four-repos-healthy goal workspace), together with #2868.

    Recorded decisions for triage — do not re-file, re-scope, or split this issue; record the phase structure instead:

    • A malformed agreement is no agreement: compute_state returns Proposed whenever agreement.players are not two distinct players, regardless of genesis/claims.
    • compute_state keeps its pure, non-Result signature — validity is expressed through the returned state, not a new error path.
  4. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    Correction (design-architect, same pass): precision — this issue's acceptance leaves triage the choice between Proposed and an explicit invalid-agreement result for a duplicate-seat agreement; design #2898 records Proposed as the default and "never Agreed/Disputed" as the invariant. Triage also pins the sign_genesis defense-in-depth question per the acceptance; the viewer check is non-negotiable either way.

  5. 35 remaining items

  6. added and removed
    queue/queuedHas a queued task in the Supabase agent queue
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    on Nov 3, 2026
  7. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done (PR opened)

    • Agent: repo-worker-code (OpenChamber scheduled lane) · queue task 5808
    • Task: The reissued packet resolved the earlier packet_defect: both guards are in scope.
    • Change: compute_state returns Proposed for a duplicate-seat agreement whatever the genesis/claim set (pure MatchState, no new error path, per design Design: reconstruction is admission — validate authority-bearing inputs where they are rebuilt (pkgs/id) #2898); sign_genesis refuses duplicate seats with a new SignError::DuplicateSeat as defense in depth.
    • Tests: cd pkgs/id && cargo test --test attestation_poc — 13 passed, 0 failed (9 existing unchanged + 4 new: [A,A] with one genesis+one claim, [A,A] with two differing claims, [A,A] with no objects, signer refusal).
    • PR: fix(id): reject duplicate-seat attestation agreements (#2863) #4949 (branch queue/5808-2863-dup-seat-attestation). CI pending at completion; the PR is the artifact.
    • Labels: queue/claimed, status/in-progress and work/assigned removed — nothing in flight.
  8. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  9. developing-today commented on Nov 3, 2026

    @developing-today
    Contributor

    agent-note:v1
    state: reviewed (coverage loop-back)
    Coverage pass 2026-11-03, loop-back review: this issue was re-reviewed (drift / overlap / already-handled / stale / blockers / ordering) so every open issue in the four repos is reviewed and in the system. Verdict: still live; in the system as task 8548 (triage, queued, priority 30) in repo-health-code-pilot. Labels audited and synced. If it is already handled at current main, its triage closes it with evidence; if oversized, the triage files the split; blockers belong on this issue.
    Found via: coverage loop-back 2026-11-03

  10. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · blocked

  11. removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 3, 2026
  12. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    agent-note:v1
    event: done
    task: 8604
    queue: repo-health-code-pilot
    lane: code-a
    summary: Shepherd complete. PR #4949 squash-merged (merge commit 825eefa) and this issue auto-closed by the merge. Duplicate PR #4964 closed after a full diff comparison — salvage note on PR #4949 (comment 6612266476): identical invariant, nothing unique in PR #4964, PR #4949 is the superset. Codex gate dispositioned on the PR: the Codex review failed on usage limits and produced no findings. Targeted tests independently re-run on the laptop at PR head a3322ed: cargo test --test attestation_poc — 13 passed, 0 failed.
    outcome: fixed_merged

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/xsTrivial: expected under 30 minutes 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 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