Skip to content

id(currency_poc): InMemoryLedger posts an unterminated linked chain — a batch ending with linked=true commits instead of being rejected #4121

Closed

Description

@dezren39

Problem

InMemoryLedger::create_transfers (pkgs/id/src/currency_poc/ledger.rs:249-258) posts a linked chain that is never terminated. The trait documentation (ledger.rs, LedgerBackend::create_transfers) defines a chain as a contiguous run ending at a transfer with linked == false, but the chain-splitting loop

let mut chain_end = chain_start;
while batch[chain_end].linked && chain_end + 1 < batch.len() {
    chain_end += 1;
}
let chain = &batch[chain_start..=chain_end];

stops at the end of the batch even when the last leg still has linked == true, and the resulting "chain" is validated and committed like a properly terminated one. A real TigerBeetle submission leaves such a chain open (the linked flag promises a following leg), so scenario tests that pass against the in-memory reference backend can rely on behavior a production binding over the official client would reject — exactly the divergence this backend exists to prevent (mod.rs: "a production binding would implement the same trait"; tests.rs header: the same scenarios are to run against a real cluster).

Evidence (verified this run, laptop probe, main caacf3a)

Probe crate with verbatim copies of the currency_poc modules (md5 ledger.rs 3c2ccc964b2bcc0e29232fae8966c41d, matching main caacf3a), funded-ledger fixture copied from tests.rs:

  • Single transfer id 700, amount 100, linked: true, no terminator: results=[Ok(())], lookup_transfer(700) returns the transfer, user spendable rises to 10100 — the unterminated chain posted.
  • Two legs (ids 710/711, amounts 100/200), both linked: true, no terminator: results=[Ok(()), Ok(())], both transfers stored.
  • Control: the same transfer with linked: false posts (expected) — the probe fixture is sane.

Why it matters

The in-memory backend is the reference semantics for the future TigerBeetle client binding and the only ledger the #2289 scenario tests run against today. Silently accepting an open linked chain lets malformed batches (e.g. a caller that builds legs and forgets to clear linked on the last leg, or a truncated batch) pass every PoC test while failing — or behaving differently — against a real cluster. The executor itself always terminates its batch (executor.rs sets linked: false on T3), so this is a backend-fidelity gap, not an executor bug.

Acceptance criteria

  • create_transfers rejects a batch whose final chain is unterminated (last leg linked == true): no leg of that chain posts, no balance moves, and the per-leg results report an error for every leg of the open chain (a dedicated TransferError variant if the existing codes cannot express it).
  • Properly terminated chains and single unlinked transfers behave exactly as before; all existing currency_poc tests pass unchanged.
  • New tests: single unterminated transfer rejected; multi-leg unterminated chain rejected atomically; a terminated chain following an unterminated one in the same batch is handled per the documented per-chain result contract.
  • The LedgerBackend::create_transfers doc comment states the unterminated-chain behavior so a future client binding implements the same rule.

Related: #2289 (the PoC this backend belongs to). Distinct from #4071 / #4069 (pkgs/id/src/currency.rs authorisation validation/fold, a different module).

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 20:05 CST (2026-11-02T02:05Z)
    • Found via: filed this run
    • Task: Filed issue id(currency_poc): InMemoryLedger posts an unterminated linked chain — a batch ending with linked=true commits instead of being rejected #4121 — InMemoryLedger posts an unterminated linked chain (batch ending with linked=true commits), diverging from the trait doc and real TigerBeetle semantics. Verified this run on the laptop with verbatim currency_poc modules from main caacf3a (md5 ledger.rs 3c2ccc964b2bcc0e29232fae8966c41d): single linked=true transfer and a two-leg all-linked batch both posted; linked=false control correct.
    • Expectation: effort/s — reject an unterminated final chain atomically with per-leg errors, document the rule on the trait, plus tests; existing currency_poc tests unchanged.
    • Links: probe ~/git/fleet/probe-2026-11-01-laneA-currencypoc/ (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. 7 remaining items

  5. added
    task/small-fixQueue packet kind: bounded fix with reproduction and targeted tests
    task/fixhas a fix task queued
    on Nov 2, 2026
  6. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · state: done

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

    @dezren39
    MemberAuthor

    Pickup — task 6332 claimed by lane code-k (repo-worker-code). Revalidated fresh: issue #4121 is OPEN, no hard-stop labels, no implementation PR or existing branch found. Working the unterminated linked-chain rejection in pkgs/id/src/currency_poc/ledger.rs on the laptop.

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

    @dezren39
    MemberAuthor

    Done — task 6332 complete: PR #4517 merged (merge sha 0ea90ec) and issue #4121 closed by the merge. InMemoryLedger::create_transfers now rejects an unterminated final linked chain atomically (LinkedEventChainOpen on the final leg, LinkedEventFailed on earlier legs of the open chain; nothing stored, no balances move), documented on LedgerBackend::create_transfers. Targeted tests on the laptop under fleet-build-lock: cargo test --manifest-path pkgs/id/Cargo.toml --lib currency_poc — 20 passed, 0 failed, 1 ignored, including the 3 new regression tests. Codex review completed on the merged commit with no findings; CodeRabbit passed.

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/fixhas a fix task queuedtask/small-fixQueue packet kind: bounded fix with reproduction and targeted tests

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions