Skip to content

fix(test): seed fixture balances instead of minting; repair the faucet-dependent suites - #722

Merged
cryptskii merged 1 commit into
mainfrom
fix/test-helpers-seam-for-faucet-free-fixtures
Aug 25, 2026
Merged

fix(test): seed fixture balances instead of minting; repair the faucet-dependent suites#722
cryptskii merged 1 commit into
mainfrom
fix/test-helpers-seam-for-faucet-free-fixtures

Conversation

@cryptskii

Copy link
Copy Markdown
Collaborator

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_sdk integration suites broke.

I verified #721 with cargo test --lib on both crates. --lib neither compiles nor runs tests/*.rs, so ~51 integration binaries were never exercised; CI runs cargo 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_testing in dsm, under its existing testing feature. It lives there because private fields are reachable, so no new public accessors; balances live outside the SMT, so root() is unchanged, exactly as in restore.
  • install_balance_for_testing(router, policy_commit, amount) in dsm_sdk under test-utils — installs one balance, never an arbitrary DeviceState, 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-utils feature rather than introducing a second near-identical one — the crate already uses cfg(any(test, feature = "test-utils")) in four places for exactly this.

Production isolation — proven, with a positive control

Feature with dev-deps --no-dev-dependencies
dsm_sdk test-utils present absent
dsm testing dsm feature "testing" absent

cargo build and cargo install don't resolve dev-dependencies, and both seams are cfg(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

Category Repair
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 behaviour faucet_claim.rs changes semantics: 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 was 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, 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 debiting Transfer), assertion flipped +7-7

The last three broken suites never touched the faucet — they called advance with Operation::Mint directly. 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-utils activated 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 security proof is unchanged

remove the central gate  ->  the test fails because the advance SUCCEEDS,
                             balance witness = 18446744073709551615 ERA
restore the gate         ->  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. 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 0
  • make lint from the repo root: exit 0, 0 errors
  • Production feature isolation: proven above, with positive controls

…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.
@cryptskii
cryptskii merged commit d719320 into main Aug 25, 2026
18 checks passed
@cryptskii
cryptskii deleted the fix/test-helpers-seam-for-faucet-free-fixtures branch August 25, 2026 01:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant