Repository navigation
feat: record inventory with shared reservations and concurrent swap reselection - #82
iamalwaysuncomfortable wants to merge 9 commits into
Conversation
…eselection Port of Veil PR #172 (head 2e324d5) into the Python SDK. aleo.inventory: get/plan/rebalance/reconcile record inventory, join_records, split_record, auto_join over credits.aleo and ARC-20/ARC-22 tokens. One sans-IO engine drives both RecordInventory and AsyncRecordInventory. The planner, join-helper registry, and output resolution follow Veil; its planner tests are the known answers. RecordInventoryStore is a three-method protocol with an in-memory implementation; scopes match Veil's so journals can be shared. Facade: Aleo(provider, record_management=...) and aleo.inventory. On a managed client every transact and delegate reserves its record inputs before proving, saves the proved transaction before broadcast, journals submission, and enforces the fee ceiling. Private fees pick and reserve the credits record once the exact fee is known (fee_record may be a callable). A node rejected broadcast releases its reservation; uncertain outcomes keep it. transact_with_result returns the built transaction beside the id. Shield Swap: plan_inventory and rebalance_inventory on the token's underlying program, reserved records excluded from selection, and swaps that lose a record to another writer move to the next eligible record before proving. Concurrent-swaps example with a file-backed store and recovery steps. AGENTS.md regenerated.
Port of Veil's recordInventory, recordInventoryStrategy, and concurrentInventory e2e suites for the Shield Swap SDK. Keeps six equal USDCx and ETH records in the e2e account, trades an explicit round trip and repairs the inventory through the stablecoin join helper, submits six swaps at once in each direction with automatic record selection under a forced first-selection collision, claims every output, and reopens the journal to verify confirmed checkpoints and distinct inputs. Passed on testnet in 7m31s (28 transactions). Journals live under .shield-swap/, now ignored.
JsonFileRecordInventoryStore keeps the journal in one JSON file under a file lock; SqliteRecordInventoryStore uses the stdlib sqlite3 module with the same table layout and row JSON as Veil's store, so the two SDKs can share a journal. Both record the writer's pid and host and release a reserved entry whose local writer has exited before proving. The store contract tests run against all three stores. The concurrent-swaps example and the live inventory suite use the SQLite store instead of a private class; the README compares the three stores.
The example now reads top to bottom like the first-swap example: client setup, the unresolved-work check, planning, execution, and the concurrent batch are inline, with only the per-trade thread body as a nested function. The offline test checks the script's flags and its refusal to run without a key instead of importing helpers.
The atomic rewrite created its temporary file with the default umask and then replaced the 0600 journal with it. Create the temporary file with mode 0600 and assert the mode after a write in the durable store test.
On a client with record management enabled, build_transaction now reserves the call's record inputs before proving and saves the proved transaction to the journal, and submit_transaction records the broadcast (a 4xx refusal releases the reservation, an unknown outcome keeps it). transact is the two in sequence again, matching how snarkVM separates building a transaction from broadcasting it, and transact_with_result is removed. A built transaction that will not be sent is released with result.discard(). Shield Swap's DexCall returns to the plain build-then-submit split and is protected without changes. The checkpoint rules move into one SpendCheckpoint class shared by the engine and the facade.
iamalwaysuncomfortable
left a comment
There was a problem hiding this comment.
Reviewed Python head 0fe8ddb36074924794e4cac3ebbc330ba8621b59 against Veil PR #172 at 1d24487c, including the older 2e324d5 commit named in this PR. I found four issues to fix before merging.
-
[P1] Managed inventory operations fail with local proving. The inventory engine reserves the inputs, then calls
bound.build_transaction(), which now tries to reserve those same inputs again. I reproducedsplit_record()failing before any proof or broadcast with “Record spending failed after acquiring inputs.” The async path has the same structure. Veil explicitly bypasses management for nested execution; Python needs an equivalent internal path. Location -
[P1] A crash can release a private-fee record while retaining a transaction that spends it. Python stores execution inputs and the fee record in separate entries, then checkpoints them with separate updates. I killed a subprocess between those updates: reopening SQLite retained the execution entry as
prepared, including the complete transaction, but cancelled the fee reservation. Recovery can therefore rebroadcast that transaction while another writer acquires its fee record. Veil uses one entry containing both execution and fee inputs. These checkpoints need atomic persistence or recovery that preserves the relationship between entries. Location -
[P1] Reading a shared Veil journal can incorrectly release wallet reservations. Python’s dead-process recovery cancels every
reservedentry belonging to that process, without checkingaccount_type. Veil deliberately preservesrpcentries because a wallet may have broadcast before its response was lost. I reproduced Python cancelling anrpcentry when reopening SQLite after its writer exited. The advertised shared-journal compatibility requires preserving Veil’saccountType === 'local'condition; explicitrelease_reservedrecovery needs the same consideration. Location -
[P1]
discard()releases inputs after an uncertain broadcast.is_submittedbecomes true only after a successful response. If submission times out, callingresult.discard()still cancels the reservation, even though the transaction may already be accepted. I reproducedprepared → cancelled, with the held-input set becoming empty. The public documentation says discard has no effect after submission, so callers can reasonably use it during cleanup. Track whether broadcast was attempted and preserve uncertain reservations. Location
Beyond those bugs, the meaningful divergences from Veil are:
| Area | Python versus Veil | Practical consequence |
|---|---|---|
| Private fees | Python selects the fee record after proving the execution; Veil estimates and acquires execution plus fee inputs together. | Python can spend proving time before discovering insufficient private fees, and introduces the crash-consistency issue above. |
| Write lifecycle | Python exposes managed build/submit halves and discard(); Veil coordinates through execution progress callbacks. |
Useful for callers assembling transactions themselves, but requires stronger lifecycle protection. |
| Wallet support | Python requires scanner plaintext; Veil also supports pinned wallet records and RPC accounts. | This is a local-signer port, not full wallet-capability parity. |
| Recovery | Python adds release on broadcast 4xx and explicit release_reserved; Veil retains more uncertain reservations. |
These are substantive safety-policy changes, not just liveness improvements. A final 4xx also needs care if an earlier submission attempt had an uncertain outcome. |
| Operational tooling | Python ships JSON/SQLite stores in the SDK; Veil includes background CLI inventory maintenance. | Python supports durable application workflows, but does not provide the same unattended maintenance tooling. |
The planner, helper selection, confirmed-output matching, and bounded swap reselection are substantially aligned with Veil.
Validation: 144 focused SDK tests and 14 Shield Swap/example tests passed. Four additional probes reproduced the reported failures. Funded live tests were not rerun.
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved reservation-lifecycle defects can release inputs needed by pending transactions, and managed local inventory proving conflicts with its own reservations.
9 open issues
Failed broadcasts allow discard to release in-flight inputs · New Internal inventory builders double-reserve inputs · New Lock acquisition failures leave the thread lock held · New Fee reservations can be reclaimed before prepared transactions · New Journal load failures leave locks held · New Root gitignore does not exclude shield-swap recovery files · New Preserve-mode splitting can make reachable targets infeasible · New Durable store changes existing directory permissions · New Concurrent swaps omit the journal and reuse identities · New
What changed in this PR
Adds private-record inventory management to the Aleo SDK and connects it to Shield Swap, so trades and maintenance operations can share reservations and recover interrupted work.
Changes:
- Adds inventory planning, join/split helpers, and memory, JSON, and SQLite reservation stores.
- Integrates reservation checkpoints into facade writes and adds bounded record reselection for concurrent swaps.
- Adds tests, generated API documentation, and a concurrent-swaps example with recovery instructions.
| File | Description |
|---|---|
shield-swap-sdk/tests/test_inventory.py |
Tests reservation-aware selection and swap reselection. |
shield-swap-sdk/tests/test_gen_context.py |
Raises the generated guide size limit. |
shield-swap-sdk/tests/test_concurrent_swaps_example.py |
Checks example arguments and startup safeguards. |
shield-swap-sdk/tests/integration/test_record_inventory_live.py |
Exercises live inventory maintenance, concurrent swaps, and recovery. |
shield-swap-sdk/README.md |
Documents concurrent swaps and inventory integration. |
shield-swap-sdk/python/aleo_shield_swap/client.py |
Adds inventory methods and synchronous swap reselection. |
shield-swap-sdk/python/aleo_shield_swap/async_client.py |
Adds asynchronous inventory methods and reselection. |
shield-swap-sdk/python/aleo_shield_swap/AGENTS.md |
Includes generated inventory API documentation. |
shield-swap-sdk/python/aleo_shield_swap/_core.py |
Adds reservation filters and shared selection helpers. |
shield-swap-sdk/python/aleo_shield_swap/_calls.py |
Integrates managed submission and bounded reselection. |
shield-swap-sdk/examples/concurrent-swaps/README.md |
Explains batch execution and recovery. |
shield-swap-sdk/examples/concurrent-swaps/concurrent_swaps.py |
Demonstrates inventory preparation and concurrent trades. |
shield-swap-sdk/codegen/gen_context.py |
Includes inventory methods in generated guides. |
shield-swap-sdk/AGENTS.md |
Publishes generated inventory API guidance. |
shield-swap-sdk/.gitignore |
Excludes package-local recovery journals. |
sdk/Readme.md |
Introduces facade inventory management. |
sdk/python/tests/test_inventory_store.py |
Tests the shared reservation-store contract. |
sdk/python/tests/test_inventory_planner.py |
Adds planner known-answer and validation tests. |
sdk/python/tests/test_inventory_assets.py |
Tests asset decoding and helper compatibility. |
sdk/python/tests/test_facade_inventory.py |
Tests managed writes and private-fee reservations. |
sdk/python/tests/fixtures/main_aj_arc20_2_15.aleo |
Supplies a dynamic join-router fixture. |
sdk/python/aleo/inventory/store.py |
Implements reservation stores and stale-writer recovery. |
sdk/python/aleo/inventory/planner.py |
Plans deterministic joins and splits. |
sdk/python/aleo/inventory/join_programs.py |
Registers and validates deployed join helpers. |
sdk/python/aleo/inventory/interface.py |
Parses program interfaces for compatibility checks. |
sdk/python/aleo/inventory/assets.py |
Resolves assets and decodes eligible records. |
sdk/python/aleo/inventory/__init__.py |
Exports the inventory API. |
sdk/python/aleo/facade/network.py |
Records synchronous broadcast outcomes. |
sdk/python/aleo/facade/client.py |
Binds inventory configuration to the facade. |
sdk/python/aleo/facade/call.py |
Adds managed builds, delegated writes, and discard support. |
sdk/python/aleo/facade/async_client.py |
Integrates asynchronous managed writes and inventory. |
sdk/python/aleo/__init__.pyi |
Exposes inventory types in package stubs. |
sdk/python/aleo/__init__.py |
Exports inventory classes at package level. |
README.md |
Documents planning, storage choices, and recovery. |
Review effort: Balanced
| if self.checkpoint is not None: | ||
| self.checkpoint.discard() |
There was a problem hiding this comment.
Fixed in b5ff539 and c553c9c: SpendCheckpoint tracks is_broadcasting (set before the request leaves) and is_uncertain (set when an attempt ends without a definite refusal). discard() is inert in either state and after submitted; a discarded checkpoint refuses begin_broadcast. Tests: test_discard_keeps_the_reservation_after_a_broadcast_attempt_with_no_answer, test_discard_during_an_in_flight_broadcast_does_not_release_the_inputs (sync and async), test_a_discarded_result_is_refused_by_submit.
| checkpoint = managed.begin_spend( | ||
| self.program_id, self.function_name, self.args, account=account, | ||
| private_fee=private_fee or fee_record is not None, fee_record=_fee_record_text(fee_record), | ||
| ) |
There was a problem hiding this comment.
Fixed in b5ff539: both inventory builders prove through the unmanaged paths (_prove_transaction under local proving, delegate(broadcast=False) otherwise), keeping the outer checkpoint and its fee selector. Regression test: test_inventory_actions_prove_locally_on_a_managed_client_without_reserving_twice; the async builder shares the same code shape and the existing async engine test covers it.
| self._thread_lock.acquire() | ||
| self._handle = open(self._path, "a+") | ||
| if sys.platform == "win32": # pragma: no cover - exercised on Windows only | ||
| import msvcrt | ||
| msvcrt.locking(self._handle.fileno(), msvcrt.LK_LOCK, 1) # type: ignore[attr-defined] | ||
| else: | ||
| import fcntl | ||
| fcntl.flock(self._handle, fcntl.LOCK_EX) |
There was a problem hiding this comment.
Fixed in c553c9c: _FileLock.__enter__ closes the handle and releases the thread lock when opening or locking fails. Covered by test_json_store_releases_its_locks_when_the_journal_cannot_be_read.
| if entry.status == "reserved" and host == self._host and not _process_alive(pid): | ||
| entry.status = "cancelled" | ||
| self._store(entry) |
There was a problem hiding this comment.
Fixed in b5ff539 by adopting Veil's layout: the fee record is folded into the entry that holds the execution inputs (records + fee_record_id), so every checkpoint after selection is one store update on one row and recovery cannot separate them. Tests: test_selects_the_fee_record_and_journals_it_in_the_same_entry_as_the_inputs, test_the_fee_record_is_never_held_apart_from_the_inputs_once_the_transaction_is_saved.
| text = store.path.read_text() or "[]" | ||
| store._cache = [dict(item) for item in json.loads(text)] |
There was a problem hiding this comment.
Fixed in c553c9c: the JSON guard releases the file and thread locks when the journal cannot be read or decoded, then re-raises. Test: test_json_store_releases_its_locks_when_the_journal_cannot_be_read (corrupt file raises, repaired file works without a restart).
| *.pyc | ||
|
|
||
| # Live inventory suite and example journals (reservations, swap handles) | ||
| .shield-swap/ |
There was a problem hiding this comment.
Fixed in c553c9c: .shield-swap/ is now ignored at the repository root as well.
| outputs_needed = count - len(working) + 1 if repack else 2 | ||
| usable = record.amount - deduction * (outputs_needed - 1) | ||
| amount = usable // outputs_needed | ||
| push("split", [record], [amount, record.amount - amount - deduction], amount) |
There was a problem hiding this comment.
Deliberately unchanged in this PR. The split loop is a line-for-line port of Veil's planner (packages/core/src/inventory/planner.ts, same usable // outputs_needed halving) and the planner tests here are Veil's known answers, so a capacity-aware split would diverge the two SDKs' plans for the same inventory. The [9, 3] / records=4 / min=3 case fails identically in Veil. Worth fixing in both planners together; I will open a follow-up issue referencing this thread.
There was a problem hiding this comment.
Fixed after all, in the follow-up commit on this branch (closes #83): when the two halves could no longer yield the lots still required, the split cuts a whole number of lots instead, which keeps every lot the record can fund. [9, 3] / records=4 / min=3 now plans 9 -> [3, 6], 6 -> [3, 3], and 20,003 microcredits plan [1, 10002], [1, 1] with two deductions. Halving still wins whenever it can finish, so every Veil known-answer plan is unchanged; only plans Veil rejects differ. Test: test_splits_whole_lots_when_halving_would_strand_value_below_the_minimum.
| path.parent.mkdir(parents=True, exist_ok=True) | ||
| try: | ||
| path.parent.chmod(0o700) | ||
| except OSError: # pragma: no cover - not every filesystem honours modes | ||
| pass |
There was a problem hiding this comment.
Fixed in c553c9c: _private_file only chmods a parent directory it creates; an existing directory keeps its mode. Test: test_durable_stores_keep_an_existing_directory_s_permissions.
| aleo = Aleo(Aleo.HTTPProvider("https://edge.provable.com/api", network="testnet"), | ||
| record_management=RecordManagementConfig(enabled=True)) | ||
| aleo.default_account = account | ||
| dex = ShieldSwap(aleo) |
There was a problem hiding this comment.
Fixed in c553c9c: the snippet now attaches dex.journal = Journal(Path.home() / ".shield-swap" / "testnet" / <address> / "swaps.jsonl") before the batch, so each concurrent swap gets its own reserved blinded identity.
- Inventory actions prove through the facade's unmanaged paths (_prove_transaction, delegate(broadcast=False)) so a managed client under local proving does not reserve its inputs twice. - The private fee record is folded into the same journal entry as the execution inputs (Veil's layout): a short-lived claim wins it atomically, then update(records, fee_record_id) moves it over. Every later checkpoint is one store update, so a crash cannot keep the proved transaction while releasing its fee record. - Dead-process recovery and reconcile(release_reserved=True) cancel only entries with account_type "local"; a Veil wallet's rpc entry is kept. - SpendCheckpoint tracks uncertain broadcast attempts: discard() is inert after any attempt, and a 4xx that follows an uncertain attempt keeps the reservation. The managed broadcast asks the raw client for single attempts (retry=False) and reports each one to the checkpoint before retrying with the usual backoff.
|
All four reproduce against
On the divergence table: fee selection after proving, the managed build/submit halves, the local-signer scope, and no CLI are deliberate calls recorded in the PR description; the first two are kept with the stronger lifecycle rules above, and the 4xx release and Validation: |
iamalwaysuncomfortable
left a comment
There was a problem hiding this comment.
Re-reviewed head b5ff5396a484f2bf60fac6d447aa530a6db94839. Three findings are fixed; the discard/broadcast finding still has one concurrency gap.
[P1] discard() can release inputs while broadcast is in flight.
The broadcast helpers update checkpoint state only after receiving a response or exception. While the request is pending, both is_submitted and is_uncertain remain false, so another thread or async task can call result.discard() and release the inputs.
I reproduced:
- Start broadcasting a built transaction; hold its HTTP response.
- Call
discard()—the reservation becomescancelled. - Build another transaction using the same input successfully.
- Release the first response—the journal now contains a
submittedand apreparedtransaction sharing that input.
Mark the checkpoint as broadcasting before calling or awaiting submit_once(), and prevent discard during that state. Preserve the distinction between a definite first-attempt refusal and an uncertain outcome. Both sync and async helpers need this protection. Affected code
The other fixes look sound:
- Local inventory proving bypasses duplicate reservation acquisition.
- Execution inputs and private fees share one prepared checkpoint. Subprocess crash probes confirmed they stay held together.
- Automatic and explicit recovery preserve Veil RPC-wallet reservations.
- Completed timeouts and 5xx→4xx retries now retain reservations correctly.
Validation: 350 tests passed across inventory, facade/network, and Shield Swap coverage. No funded live tests rerun.
- SpendCheckpoint marks a broadcast attempt before the request leaves (begin_broadcast); discard() is inert until the attempt resolves, and a checkpoint that was already discarded refuses to broadcast, since its inputs may belong to another transaction by then. The engine and both facade broadcast helpers set the mark; a lock serialises the lifecycle flags against a discard from another thread. - Durable stores no longer chmod an existing parent directory; only a directory they create becomes owner-only. - The file lock and the JSON journal guard release what they took when acquisition or decoding fails, so a repaired journal works without a restart. - Root .gitignore excludes .shield-swap/ (the examples write claim secrets there when run from the repository root), and the Shield Swap README's concurrent-swap snippet attaches the swap journal.
|
The in-flight gap is closed in Discard during a pending broadcast. Tests: The same commit takes the Copilot notes that were not already covered by Validation: |
The planner halved the largest record on every split, so a record small relative to the minimum could reject a reachable target: [9, 3] towards four lots of at least 3 went 9 -> [4, 5] -> [2, 3] and raised, and 20,003 microcredits could not fund three lots of 1 although two 10,000 deductions leave exactly enough. When the two halves could no longer yield the lots still required, the split now cuts a whole number of lots instead, which keeps every lot the record can fund. Halving still wins whenever it can finish, so Veil's known-answer plans are unchanged; only plans Veil rejects differ. Closes #83.



Python port of Veil PR ProvableHQ/veil#172 (head 2e324d5): private record inventory for
credits.aleoand ARC-20/ARC-22 tokens, shared record reservations, recovery, and concurrent Shield Swap trades. No CLI; the journal needs no disk.What it adds
aleo.inventory(core SDK) — the seven Veil actions with the same names in snake_case:get_record_inventory,plan_record_inventory,rebalance_record_inventory,reconcile_record_inventory,join_records,split_record,auto_join. Same core types as dataclasses (RecordAsset,InventoryTarget,InventoryPlan,InventoryStep,RecordSpend,RecordManagementConfig,InventoryResult, …), the planner ported line for line with Veil's planner tests as known answers (one extension: a split cuts whole lots instead of halving when halving would strand value below the minimum, which only changes plans Veil rejects; #83), the join-helper registry (credits/stablecoin static helpers,main_/test_aj_arc20_2_15.aleodynamic routers) with deployed-signature checks, and the final output-resolution rules from c1b1aa9/e28412b.RecordInventory/AsyncRecordInventoryshare one sans-IO engine (_engine.py): every action is a generator that yields facade requests; the two clients only perform them. The reservation, checkpoint, fee-ceiling, and output-matching rules exist once.RecordInventoryStoreprotocol (list/acquire/update, identical to Veil's) with three shipped backends:MemoryRecordInventoryStore(default, one process),JsonFileRecordInventoryStore(path)(one JSON file under a file lock, readable in an editor), andSqliteRecordInventoryStore(path)(stdlibsqlite3, same table layout and row JSON as Veil's store, so the two SDKs can share a journal). Both file-backed stores record the writer's pid and host and release areservedentry whose local SDK writer (accountType: local) has exited, as Veil's SQLite store does; a wallet client'srpcentry is kept. Scope strings match Veil'sJSON.stringify([chainId, address]). One contract suite runs against all three.RecordReservationConflictError(record_ids)is raised only when an input cannot be acquired before proving; a nested conflict after acquisition is wrapped so callers never retry a spend that may have reached the network.reconcile_record_inventory(release_reserved=True)cancelslocalentries a crashed writer left inreserved(they provably never reached the network). Uncertain outcomes (timeouts, lost responses, 5xx, 404 on lookup) keep the reservation, a refusal that follows an uncertain attempt keeps it too (the managed broadcast reports every retry to the checkpoint), anddiscard()is inert from the moment a broadcast attempt starts (a discarded result is refused bysubmit_transaction, since its records may be held by another transaction by then).Facade (Option B).
Aleo(provider, record_management=RecordManagementConfig(enabled=True, …))andaleo.inventory. On a managed client the journal follows the two halves of a write, as snarkVM separates them:build_transactionreserves the record inputs before proving and saves the proved transaction (enforcingmax_fee_microcredits),submit_transactionrecords the broadcast (a 4xx refusal releases the reservation, an unknown outcome keeps it),transactis the two in sequence, anddelegatedoes both through the prover (it asks the prover not to broadcast and the SDK submits).TransactionResult.discard()releases a built transaction that will not be sent. Code that already builds and submits by hand, such as Shield Swap'sDexCall, is protected without changes. Private fees are coordinated: the Python facade learns the exact base fee only after the execution is proved, sofee_recordmay now be a callable; the journal selects the smallest eligible credits record at that point, folds it into the same entry as the execution inputs withfee_record_id(Veil's layout, so every later checkpoint is one store update and a crash cannot keep the transaction while releasing its fee record), verifies the prover spent it (tag check), and keeps it held after a rejected execution. Inventory actions prove through the facade's unmanaged paths (withoutManagementin Veil), sosplit_recordand friends work underproving="local"on a managed client. Unmanaged clients are unchanged.Shield Swap.
dex.inventoryis the facade's inventory.plan_inventory(token, target)andrebalance_inventory(...)target the token'sunderlying_program. Record selection skips reserved nonces. With management enabled and automatic selection, a swap that loses its record to another writer moves to the next eligible record from the same scan (smallest first, each tried once) insideDexCall/AsyncDexCall, re-checking account, network, and management first; explicittoken_recordstays fixed and execution errors are never retried (Veil 2f168a6).DexCall.transactnow goes through the journal on managed clients. AGENTS.md regenerated (size limit 34k → 36k).Example.
shield-swap-sdk/examples/concurrent-swaps/previews or executes "six balanced USDCx records, six concurrent USDCx → ETH swaps, claim each", on the SDK's SQLite store, with abatch.pendingmarker and recovery steps (Veil 2e324d5).Design calls
proving="local"override.updategainedrecords/fee_record_idpatch keys so the fee record can join the inputs' entry.Validation
sdk: 1139 fast tests pass (-m "not slow", e2e excluded; one pre-existing live block-lookup test is flaky), pyright strict 0 errors. New:test_inventory_planner.py,test_inventory_assets.py,test_inventory_store.py,test_inventory_engine.py(world tests ported frominventory,autojoinHelpers,configuration,privateFees),test_facade_inventory.py.shield-swap-sdk: 541 tests pass; pyright unchanged at the 15 pre-existing errors measured atHEAD. New:tests/test_inventory.py(ported fromconcurrentSwaps.test.ts, incl. a six-way race on one record),tests/test_concurrent_swaps_example.py.tests/integration/test_record_inventory_live.py, ported from Veil'srecordInventory.e2e,recordInventoryStrategy.e2e, andconcurrentInventory.e2esuites; 4 passed in 7m31s on 2026-10-23 with the e2e account): rebalance to six equal USDCx and ETH records; one explicit USDCx → ETH → USDCx round trip followed by a seven-recordaj_test_usdcx_stablecoin_*helper join and five splits; six concurrent USDCx → ETH swaps with automatic selection where all six first chose the same record, 15 reservation conflicts were resolved by reselection, 5 proving executions overlapped, and every swap spent a distinct input; six concurrent ETH → USDCx swaps back; every output claimed; journal reopened with 28 confirmed transactions and no input consumed twice. Run four times on 2026-10-23: on the JSON file store (7m31s), on the SQLite store (7m26s), again on SQLite after the managed build/submit split (7m13s, 3 and 6 overlapping provings per batch), after the review fixes (7m20s), and after the in-flight broadcast guard (9m06s), each 28 tx with 15 conflicts resolved and the swap journal attached.