Skip to content

id(currency_poc): reconcile counts legs per entry, not per distinct transfer — an empty or duplicate leg list is marked Posted with nothing (or half) in the ledger #4133

Closed

Description

@dezren39

Problem

pkgs/id currency_poc: the recovery scanner decides "all legs posted" by counting per-leg lookup matches — matched == record.legs.len() — without ever checking that the legs are distinct or that there are any legs at all. A record with an empty leg list, or with the same transfer listed twice, is marked Posted / AlreadyPosted even though the ledger does not hold the conversion's legs.

Evidence (verified this run, main 11cb8966)

  • pkgs/id/src/currency_poc/recovery.rs:56-71 — the scanner increments present/matched once per entry in record.legs and treats matched == record.legs.len() as proof of posting. For legs == [] that is 0 == 0: unconditionally true. For legs == [t, t] a single posted transfer t counts twice.
  • pkgs/id/src/currency_poc/intent.rs — neither IntentStore::put nor the IntentRecord type validates the leg list (non-empty, distinct IDs, IDs matching transfer_ids()/conversion), so a malformed record — hand-edited or foreign file-store JSON that still parses (cf. id(currency_poc): one corrupt or foreign .json file in the FileIntentStore directory aborts recovery for every pending conversion #4125), a buggy producer, a future store implementation — reaches the scanner as-is.
  • Laptop probe (verbatim currency_poc modules from main 11cb8966, md5-matched; probe ~/git/fleet/probe-2026-11-01-laneA-dupids/ on the laptop):
    • Record with legs: [], state Recorded, amount_in: 100: reconcile(Resubmit) → [(4242, AlreadyPosted)], stored state advanced to Posted, zero transfers in the ledger for the conversion.
    • Record with legs: [t, t] (the same transfer twice), state Submitted, with t posted exactly once: reconcile(Resubmit) → AlreadyPosted, state Posted, while only 1 distinct transfer exists.

Why it matters

Posted is the scanner's terminal verdict: the record leaves the unresolved set and no one looks at it again. Declaring a conversion posted when its legs are absent or short is the failure the scanner exists to prevent — a conversion silently treated as settled when the money never (fully) moved. The executor's own records always have 2–3 distinct legs today, which is exactly why the guard belongs in the scanner/record validation rather than relying on every present and future producer being well-formed; the file store already admits, by design, whatever JSON parses into an IntentRecord.

Acceptance criteria

  • reconcile never returns AlreadyPosted for a record with an empty leg list; such a record is reported (e.g. Inconsistent) and left unresolved, or rejected with an error — with a test pinning the chosen behaviour.
  • A record whose legs contain a duplicate transfer ID is not counted as fully posted on the strength of one distinct transfer; distinct-ID counting (or an equivalent leg-set validation) is used, with a test.
  • Existing recovery tests pass, including recovery_does_not_trust_transfer_id_existence_alone and both crash-recovery scenarios.

Related: #2289 (parent PoC), #4125 (foreign/corrupt files in the same store directory — the plausible route by which a malformed-but-parseable record arrives).

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/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 20:35 CST (2026-11-02T02:35Z)
    • Found via: filed this run
    • Task: Filed issue id(currency_poc): reconcile counts legs per entry, not per distinct transfer — an empty or duplicate leg list is marked Posted with nothing (or half) in the ledger #4133 — reconcile decides "all legs posted" by per-entry counting (matched == legs.len(), recovery.rs:56-71), so an intent record with an empty leg list (0 == 0) or the same transfer listed twice is marked Posted/AlreadyPosted without the conversion's legs being in the ledger. Verified this run on the laptop with verbatim currency_poc modules from main 11cb8966 (md5-matched): empty-legs record → AlreadyPosted, state Posted, 0 transfers; legs [t, t] with t posted once → AlreadyPosted, state Posted, 1 distinct transfer.
    • Expectation: effort/s — the scanner must never declare a record posted on an empty leg list, and must count distinct transfer IDs (or validate the leg set), with tests for both shapes; malformed records stay unresolved/reported instead of settling.
    • Links: probe ~/git/fleet/probe-2026-11-01-laneA-dupids/ (laptop); queue packet enqueued this run (repo-health-code-pilot).
  3. 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 2, 2026
  4. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  5. 12 remaining items

  6. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    agent-note:v1 — parked (window too short for 45-minute implementation)

  7. added
    queue/claimedQueue worker lifecycle state
    and removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    on Nov 2, 2026
  8. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    Pickup by repo queue worker lane H for task 6048 (small_fix for #4133). Fresh revalidation 2026-11-02T16:50Z: #4133 OPEN, no hard-stop labels (held / user/owned / do-not-merge / do-not-auto-edit all absent), no open PR or branch owns this fix. Implementing the recovery.rs leg validation (empty leg vector and duplicate leg IDs can no longer reach Posted/AlreadyPosted) plus the two regression tests in currency_poc/tests.rs, on branch queue/6048-4133-recovery-leg-validation.

  9. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    Implementation by repo queue worker lane H for task 6048: PR #4473 opened (branch queue/6048-4133-recovery-leg-validation, commit 023ff40).

  10. added a commit that references this issue on Nov 2, 2026
  11. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    Done by repo queue worker lane H (task 6048): PR #4473 merged (merge commit 4bfcf11) and #4133 is closed. Codex review on PR #4473 completed with no findings. Verification as recorded on the PR: verbatim currency_poc module from the branch run as a laptop probe crate — 17 passed, 0 failed, 1 ignored, including the two new regression tests; the full cargo test -p id --lib currency_poc re-run on main remains owed once the unrelated #4454 lib breakage is fixed (task 6359).

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 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