fix(test): seed fixture balances instead of minting; repair the faucet-dependent suites - #722
Merged
Conversation
…t-dependent suites PR #721 put the builtin-issuance gate at the accepting transition, and that refusal is deliberately total: it applies in tests exactly as in production, which is what makes it worth anything. Fixtures could therefore no longer obtain ERA by minting. 15 of 51 dsm_sdk integration suites broke and main went red. My verification for #721 ran `cargo test --lib` on both crates, which neither compiles nor runs tests/*.rs, so ~51 integration binaries were never exercised. CI runs `cargo test --locked --workspace`. The faucet consequence was even written into that PR's own body and not chased to its dependents. Fixtures now SEED state rather than mint. Two seams, narrowest first: DeviceState::with_balance_for_testing in dsm under its existing `testing` feature (private fields reachable, so no new public accessors; balances live outside the SMT so root() is unchanged, exactly as in restore), wrapped by install_balance_for_testing in dsm_sdk under `test-utils`. It installs ONE balance rather than accepting an arbitrary DeviceState, so a test needing funding does not thereby gain head replacement. Integration tests reach it through a self dev-dependency: they are external consumers, so cfg(test) is false for them and the feature would otherwise never be on. Production isolation is proven, not assumed. `cargo tree -e features` finds `test-utils` and `dsm feature "testing"` WITH dev-dependencies and finds neither under --no-dev-dependencies; cargo build and cargo install do not resolve dev-dependencies, so no shipping artifact compiles either seam. The positive control is what makes that evidence rather than an empty grep. The repairs fall into three categories, and they are not interchangeable: setup only 14 files seed 100 base units directly, matching the old faucet claim_amount so downstream balance assertions are unchanged subject is the forbidden thing faucet_claim.rs changes SEMANTICS: it asserts the claim is refused with the specific reason, and that check_nearby reports unavailable rather than advertising a grant that must fail. The companion tip-isolation test is retired with the grant, its premise being that a claim advances state needed a real transition the_durable_probes_detect_a_real_commit asserts three probes are alive: head root moves, balance moves, a chain-state row appears. Seeding would have made it pass while producing no row, leaving probe 3 asserting on an impossibility. It got a legal VEHICLE instead, a debiting Transfer, and the assertion flipped +7 to -7 The last three broken suites never touched the faucet at all: they called advance with Operation::Mint directly. A route-level guard would have missed every one of them, which is the clearest argument available for having put the gate at the accepting transition. Enabling `test-utils` also woke online_chain_tip_selection.rs, gated `cfg(all(test, feature = "test-utils"))` and previously compiled to an empty crate because nothing enabled the feature. It passes, but lacked the disallowed-methods allow its neighbours carry, and turned make lint red after the test board was already green. It now carries that allow. No other file was dormant behind the feature. The mint mutation control is unchanged and remains the security proof: remove the central gate and the test fails because the advance SUCCEEDS, with 18446744073709551615 ERA in the balance witness; restore it and the mint is refused. Consequence: fresh devices cannot be funded until an authenticated issuance predicate exists. rig_dlv_market.py drives the UI and does not call the faucet API, so phones already holding legitimate ERA still run the DLV flow. workspace: 63 suites / 0 failed / exit 0. make lint: exit 0, 0 errors.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes main, which went red after #721.
What broke, and why my verification missed it
#721 put the builtin-issuance gate at the accepting transition, and that refusal is deliberately total — it applies in tests exactly as in production, which is what makes it worth anything. Fixtures could therefore no longer obtain ERA by minting. 15 of 51
dsm_sdkintegration suites broke.I verified #721 with
cargo test --libon both crates.--libneither compiles nor runstests/*.rs, so ~51 integration binaries were never exercised; CI runscargo test --locked --workspace. Worse, the faucet consequence was written into #721's own body ("device funding needs an authenticated issuance predicate") and never chased to its dependents. A consequence you can state is one you can grep for.The seam
Fixtures now seed state rather than mint. Two layers, narrowest first:
DeviceState::with_balance_for_testingindsm, under its existingtestingfeature. It lives there because private fields are reachable, so no new public accessors; balances live outside the SMT, soroot()is unchanged, exactly as inrestore.install_balance_for_testing(router, policy_commit, amount)indsm_sdkundertest-utils— installs one balance, never an arbitraryDeviceState, so a test that needs funding doesn't thereby gain head replacement.Integration tests reach it via a self dev-dependency: they're external consumers, so
cfg(test)is false for them and the feature would otherwise never be on.I reused the existing
test-utilsfeature rather than introducing a second near-identical one — the crate already usescfg(any(test, feature = "test-utils"))in four places for exactly this.Production isolation — proven, with a positive control
--no-dev-dependenciesdsm_sdktest-utilsdsmtestingdsm feature "testing"cargo buildandcargo installdon't resolve dev-dependencies, and both seams arecfg(any(test, feature = ...)), so no shipping artifact compiles either one. The positive control is what makes this evidence rather than an empty grep.Three repair categories — not interchangeable
claim_amountso downstream balance assertions are unchangedfaucet_claim.rschanges semantics: asserts the claim is refused with the specific reason, and thatcheck_nearbyreports unavailable rather than advertising a grant that must fail. The companion tip-isolation test is retired with the grant — its premise was that a claim advances statethe_durable_probes_detect_a_real_commitasserts three probes are alive: head root moves, balance moves, chain-state row appears. Seeding would have made it pass while producing no row, leaving probe 3 asserting on an impossibility — a green test that had stopped testing. It got a legal vehicle instead (a debitingTransfer), assertion flipped+7→-7The last three broken suites never touched the faucet — they called
advancewithOperation::Mintdirectly. A route-level guard would have missed every one, which is the clearest argument available for having put the gate at the accepting transition.A dormant test woke up
Enabling
test-utilsactivatedonline_chain_tip_selection.rs, gated#![cfg(all(test, feature = "test-utils"))]and previously compiled to an empty crate because nothing enabled the feature. It passes, but lacked thedisallowed_methodsallow its neighbours carry, and turnedmake lintred after the test board was already green. It now carries that allow. No other file was dormant behind the feature.The security proof is unchanged
Consequence
Fresh devices cannot be funded until an authenticated issuance predicate exists.
rig_dlv_market.pydrives the UI and does not call the faucet API, so phones already holding legitimate ERA still run the DLV flow. Fresh-funding coverage is blocked by the intentional shutdown, not by a failure.Verification
cargo test --locked --workspace --exclude dsm_storage_node -- --nocapture --test-threads=1(the CI command): 63 suites / 0 failed / exit 0make lintfrom the repo root: exit 0, 0 errors