Repository navigation
Description
Summary
pkgs/id/src/conversion.rs — the production pure conversion layer (Phase E of #2063, landed 2026-11-04 under #5536) — builds a linked conversion batch for source_ledger = 0 or dest_ledger = 0 without any rejection. A zero ledger can never post: every layer below the builder refuses it, so the batch the Phase F executor will persist and submit is unpostable by construction, and the failure surfaces only at ledger submission time instead of as a typed pre-ledger rejection.
Evidence (verified this run, laptop primary @5f161d2e3c)
build_conversion_batch(pkgs/id/src/conversion.rs, the validation sequence inbuild_conversion_batch) checks, in order: zero amount, min/max bounds, rate/scale/fee terms (viaconvert_amount/fee_amount), same-account for the source/fee/destination legs, fee-account presence, and transfer-ID count/duplicates. There is no check onsource_ledgerordest_ledgeranywhere in the module —grep ledger pkgs/id/src/conversion.rsshows the fields only copied into the builtTransfers.- The ledger layer the batch feeds refuses ledger 0 twice over:
pkgs/id/src/currency_poc/ledger.rs—AccountError::LedgerMustNotBeZero("ledgermust be non-zero"): no account can even be created on ledger 0.validate_and_stagein the same file:if transfer.ledger == 0 || …→TransferError::LedgerMismatchfor every leg of a zero-ledger batch.- Real TigerBeetle likewise requires a non-zero ledger (see
pkgs/id/src/currency_poc/tb_ledger.rsmodule docs on non-zero wire fields).
- Probe (laptop,
~/git/fleet/probe-conversion-ledger, realidcrate @5f161d2e3c):build_conversion_batchwithsource_ledger=0and withdest_ledger=0both returnOkwith a full 2-leg chain; submitting the ledger-0 legs toInMemoryLedgerfails every leg, andcreate_accountson ledger 0 returnsLedgerMustNotBeZero. The sane-ledger control (100/200) builds and posts. Probe output is quoted in the filing agent note.
Why it matters
The module's own contract is that rejections happen before any ledger write, with typed reasons: "ConvertError — Why a conversion quote or batch cannot be built. Every variant is a rejection before any ledger write, per the failure table in #2063 §2.4." The builder already mirrors the ledger's other hard rules as typed pre-ledger errors — SameAccount mirrors AccountsMustBeDifferent, ZeroAmount mirrors AmountMustNotBeZero, duplicate-ID mirrors the staged-transfer ID check. Ledger-zero is the one ledger hard rule with no mirror: a caller (or a bad gateway resolution in Phase B/F) that supplies ledger 0 gets a persisted intent and a submitted batch that can only fail at the ledger, where the executor's recovery machinery (#4669 territory) then has to classify a failure that was knowable at build time.
Related but distinct: #4071 covers RateAuthorisation::validate accepting zero/same ledgers in the authorisation body. This filing is the new pure batch layer, which takes raw ledger fields (ConversionBatchRequest.source_ledger/dest_ledger) and does not consume a validated authorisation — fixing #4071 does not add a check here.
Acceptance criteria
build_conversion_batchrejectssource_ledger == 0anddest_ledger == 0with a new typedConvertErrorvariant (e.g.ZeroLedger { leg: "source" | "destination" }or equivalent), before any leg is built.- The rejection order stays deterministic and is documented alongside the existing validation-order note in
build_conversion_batch's docs. - Pinning tests in
pkgs/id/src/conversion.rscover: source ledger 0 rejected, destination ledger 0 rejected, and the existing sane-ledger requests (100/200) still build identical batches. cargo test -p id --lib conversionpasses.
Scope notes
- Filed-only from the creator lane; not self-solved (the variant shape and its position in the validation order are the Phase F executor author's call to confirm, and id(currency): RateAuthorisation::validate accepts internally inconsistent bodies — min>max, empty/inverted window, fee_bps>10000, empty/same ledgers #4071's authorisation-side fix is in flight under another holder).
Activity
developing-today commentedon Nov 5, 2026
CODE-1758
dezren39 commentedon Nov 5, 2026
🤖 Agent note · filed
idcrate):build_conversion_batchreturns Ok for source_ledger=0 (legs stamped ledger 0/200) and for dest_ledger=0;InMemoryLedger::create_accountson ledger 0 returns [Err(LedgerMustNotBeZero), Err(LedgerMustNotBeZero)] and the built legs submit as [Err(LedgerMismatch), Err(LinkedEventFailed)], while the 100/200 control builds and posts [Ok, Ok]. No ledger check exists anywhere in pkgs/id/src/conversion.rs. Distinct from id(currency): RateAuthorisation::validate accepts internally inconsistent bodies — min>max, empty/inverted window, fee_bps>10000, empty/same ledgers #4071 (RateAuthorisation body validation; this layer takes raw ledger fields). Enqueued as a repo-task/v1 triage packet on repo-health-code-pilot.dezren39 commentedon Nov 5, 2026
🤖 Agent note · handed off
115 remaining items
dezren39 commentedon Nov 6, 2026
🤖 Agent note · handed off
dezren39 commentedon Nov 6, 2026
🤖 Agent note · picked up
dezren39 commentedon Nov 6, 2026
🤖 Agent note · handed off
dezren39 commentedon Nov 6, 2026
🤖 Agent note · handed off
opencode; handing off to the opencode-claimant per worker instruction §1b, parked pending claimant launch. Issue id(conversion): build_conversion_batch accepts ledger 0 — builds a batch that can never post, with no typed pre-ledger rejection #6102 re-verified OPEN with no hard-stop labels via gh this shift. Attempts now 14/16 — near cap: the claimant must drive task 9913 to a terminal state or block it instead of another handoff cycle.