Skip to content

id(attestation): compute_state never validates the claim outcome's domain — claims naming a third party (or any string) as winner compute Agreed #2868

Closed

Description

@dezren39

id(attestation): compute_state never validates the claim outcome's domain — claims naming a third party (or any string) as winner compute Agreed

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

pkgs/id/src/attestation.rs documents the claim outcome's domain on ResultClaim.outcome (:106-108): "the winning player's id as hex, or "draw"". But the viewer checks concurrence only:

  • compute_state (:223) accepts two claims when a.outcome == b.outcome && a.final_state_hash == b.final_state_hash (:253) — a pure string-equality check. Neither claim's outcome is ever compared against the agreement's two player ids (hex) or the "draw" sentinel.
  • So any outcome string both players sign — the hex of a key that is not in the match, or arbitrary text — computes Agreed, even though no such result is possible for the tic-tac-toe example the PoC models (the winner is one of the two seats, or a draw).

Distinct from #2863 (duplicate seats): there the seats collapse to one key; here the seats are two genuine, distinct players and the genesis pair is complete — the gap is purely that the asserted result is outside the documented domain.

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 (same verbatim extraction as #2863; attestation.rs md5 530fcdf45da5d8688dc0e4e319b641b2), distinct players [A, B], complete genesis pair, concurring claims throughout:

The shipped tests (tests/attestation_poc.rs) only ever use seat-player hex outcomes and the wrong-winner dispute case (which names the other seat), so the out-of-domain case is uncovered.

Why it matters

  • The viewer's Agreed is the attestation: downstream consumers (the id: design: match attestation + audit v1 #2039 v1 design, audit/replay in id: investigate: match audit — netcode log format, replay validation, and cheating signals #2038's line) will read it as "both players conceded this result". An out-of-domain outcome — above a nonsense string, a result naming someone who never played — should not be attestable at all; if it computes Agreed, every consumer must re-validate the outcome itself, which is exactly the invariant a viewer exists to own.
  • It also masks claim-construction bugs: a claimant whose outcome encoding is wrong (raw bytes instead of hex, a display name, a seat index) produces claims that can still concur and attest, instead of failing visibly at the viewer.

Acceptance criteria

  • Two concurring claims whose outcome is neither a seat player's id-as-hex nor "draw" do not compute Agreed (they compute Proposed or Disputed — triage pins which, and whether a claim with an out-of-domain outcome is ignored like a non-player claim or counts as a position).
  • Documented outcomes unchanged: seat-player hex (either seat) and "draw", with concurring final-state hash, still compute Agreed; differing in-domain outcomes still compute Disputed.
  • A regression test covers the probe matrix: third-party hex, arbitrary string, and the draw/seat-hex controls.
  • Triage pins the encoding contract being enforced (lowercase hex of the 32-byte player id, as produced by the example/tests) so the check and claim constructors cannot drift apart.

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    priority/lowNice to have; do when convenient
    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

  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 #2863.

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

    • The outcome domain comes from the agreement, never from the claim: a claim counts only if winner is None or names one of the agreement's two players. A claim naming anyone else is ignored under compute_state's existing non-player-claim rule and can never contribute to Agreed.
    • If a future agreement shape needs named non-player outcomes, the agreement type must declare them — that is a type change, not a loosening of this check.
  4. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    Correction (design-architect, same pass): my earlier note misstated the outcome domain. Per this issue's evidence, the documented domain of ResultClaim.outcome is a seat player's id as hex, or the "draw" sentinel — there is no None winner. A claim outside that domain can never contribute to Agreed; whether it is ignored (like a non-player claim) or counts as a position (Proposed vs Disputed), and the exact hex-encoding contract, are this issue's triage pins, as its acceptance states. Design #2898's doc has been amended to match.

  5. 7 remaining items

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

    @dezren39
    MemberAuthor

    Agent note — picked up (queue task 5449)

    • Agent: repo-worker-code (OpenChamber scheduled lane)
    • Session / ID: OpenCode session ses_e5e69607636ffeCvTCAGXflken
    • Task: Enforce the pinned outcome-domain rule in compute_state and add the probe-matrix regression tests.
    • Next: Create the task worktree, inspect current source/tests, implement and verify.
    • Links: queue task 5449; branch queue/5449-2868-attestation-outcome-domain
  8. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Agent note — handed off (queue task 5449)

  9. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Agent note — done (queue task 5449)

    • Outcome: PR fix(id): ignore out-of-domain attestation outcomes (#2868) #3508 opened; compute_state now ignores out-of-domain outcomes without letting them occupy a player position or create a fork.
    • Queue: completed as pr_opened; CI remains pending and can proceed without a held claim.
    • Bookkeeping: sandbox backlog/sweep updates deferred to fleet reconciliation.
  10. added a commit that references this issue on Nov 2, 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/xsTrivial: expected under 30 minutes of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/lowNice to have; do when convenientsource/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