Skip to content

fix(token): the ERA policy stand-in is deleted and the hole left; the enforcer is keyed by the policy commitment and registers a policy only from its committed bytes - #1016

Merged
cryptskii merged 1 commit into
mainfrom
fix/era-policy-stand-in-deleted
Sep 26, 2026
Merged

cryptskii merged 1 commit into
mainfrom
fix/era-policy-stand-in-deleted

Conversation

@cryptskii

@cryptskii cryptskii commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

What

The ERA policy stand-in is deleted and the hole is left visible; the token policy enforcer is keyed by the policy commitment and registers a policy only from the committed bytes it hashed itself. Placeholder sweep, the last item (owner decision 2026-09-25: delete the fake, leave the hole, last commit after the clean checkpoint — #1015's board). CONFORMANCE_GAPS.md §6.32 records the findings, the state and the hole.

This PR is red by design on every suite that transfers or burns ERA, and the expected-red manifest below names each test. From here a board is judged against that list: a red outside it is a regression, and a listed test that goes green without ERA's policy is a finding.

The finding

CoreSDK::new_with_device preloaded an "ERA Token Policy" PolicyFile of three metadata strings, registered under the ticker "ERA" at an anchor that is the hash of that projection — not ERA_POLICY_COMMIT, which has no preimage: no TokenPolicyV3 bytes hash to it, because ERA's release rule is its emission schedule (SoFi §51) and no such blob exists. Around it: the enforcer was keyed by the operation's token_id string; register_token_policy_with_anchor bound a caller-built PolicyFile to a caller-supplied anchor on the caller's word; a validator checked semver strings over that projection; and four condition kinds the policy grammar (§47–§54) does not name — an identity allowlist over caller-stated strings, an emission schedule, a credit bundle, a custom constraint — plus role-based access control lived in the type, the enforcer and the proto, three of them "configuration-only" arms that allowed unconditionally.

How

  • dsm/src/core/token/policy/mod.rs: TokenPolicySystem::register_policy(bytes) recomputes BLAKE3(TAG_DSM_POLICY, bytes), reads the blob with Core's one parser and derives the enforcer's view from what it says (policy_enforcement::enforced_policy, moved in from the SDK's derive_policy_file); policy_at(commit) takes the durable bytes the SDK resolver answers only when they re-hash to the commitment asked for; enforce_policy(commit, ..) denies where no policy is committed. The preload, create_root_token_policy, ticker-keyed registration, the caller-asserted anchor binding, PolicyCommitResolver, the validator module and the cache's ticker index are gone.
  • dsm_sdk: enforcement is keyed by the policy_commit the operation carries (Transfer, Burn, CreateToken); a lock operation, which carries none, is refused for it (§9.1). Creation and rehydration hand Core the bytes (CoreSDK::register_policy_bytes) and take the commitment Core answers; the resolver answers load_policy_verified(commit).
  • policy_types.rs / policy_enforcement.rs / proto/dsm_app.proto: IdentityConstraint, EmissionsSchedule, CreditBundlePolicy, Custom, roles and StoredPolicy deleted; PolicyConditionProto fields 1, 5, 6, 7 and CanonicalPolicy.roles reserved; TypeScript bindings regenerated (npm run proto:gen). BitcoinTapConstraint untouched (Bitcoin is out of this round), recorded as the configuration-only arm it is.
  • Consequence, recorded as the hole (§6.32 Open): ERA has no committed policy, so every ERA transfer and burn is refused at enforcement — "no policy is committed at the commitment the operation names" — until ERA's policy blob exists (its release rule needs an encoding for the emission schedule; emissions are out of this round). SoFi validation's "pre-rooted" exemption of ERA and dBTC in market_legs_permitted is the same absence and is recorded, not changed.

Verification (release, dsm_client/deterministic_state_machine)

  • cargo test --locked --release -p dsm --lib -- core::token::policy types::policy_types core::token::token_state_manager --test-threads=1 → dsm 29/0 (four new tests: a policy registered from its committed bytes alone; bytes at another commitment are not the policy asked for; a cache miss takes the durable bytes that re-hash to the commitment; an operation naming a commitment without a policy is denied; plus the projection test moved from the SDK).

  • cargo test --locked --release -p dsm_sdk --lib -- handlers::token_routes --test-threads=1 → 12/0; --test supply_cap_enforcement --test supply_cap_partial_history → 4/0, 2/0.

  • make lint → exit 0. Frontend tsc --noEmit on the regenerated bindings → exit 0.

  • ci/production_safety_checks.sh (incl. the conformance-evidence check; totals regenerated: MR-SOFI-0304 and MR-SOFI-0315 Met), ci/sofi_reachability.py, ci/sofi_validated_root_constructors.sh, ci/sofi_no_default_evidence.sh, ci/admitted_predecessor_readers_fenced.sh → pass. cargo ndk -t arm64-v8a --platform 23 check --locked --package dsm_sdk --features=jni,bluetooth → exit 0.

Mutation controls (each run, restored, the named tests green again on the restored tree: 4/0)

  • policy_at taking the durable bytes without re-hashing them to the commitment asked for → bytes_at_another_commitment_are_not_the_policy_asked_for red.
  • enforce_policy allowing where no policy is committed → an_operation_naming_a_commitment_without_a_policy_is_denied red.
  • The parser's zero-genesis-supply check removed (the deleted validator's check was the matrix's gate for MR-SOFI-0306) → dsm::economic::token_policy::tests::a_zero_genesis_supply_is_refused red.
  • burn permitted by every policy in the projection moved into Core → the_policy_permits_exactly_what_its_flags_name red.

Expected-red manifest

From the Rust tests (dsm_sdk) job of this commit's board, run 36226702605: 1020 tests, 46 failed). The 45 below fail on the ERA refusal — "Token policy violation for NW9MKEFNZ6GTD8209QN3DQ6996DWP9E9NQ0H5DYCKA9WNS0Z69H0: no policy is committed at the commitment the operation names" — at an ERA transfer or burn. Every other job of the board is green (Rust tests (dsm), workspace-rest, Storage Node (Postgres), the gates, Lean). A red outside this list is a regression; a listed test that goes green without ERA's policy is a finding.

  • dsm_sdk::bluetooth::offline_step_tests::an_online_send_waits_for_the_offline_step_in_flight
  • dsm_sdk::handlers::bilateral_finality_tests::a_send_before_the_previous_step_finalizes_is_gated_never_marked_for_resync
  • dsm_sdk::handlers::bilateral_finality_tests::harness_carries_one_generation_a_to_b_through_production_code
  • dsm_sdk::handlers::bilateral_finality_tests::r11_only_the_checkpoint_sweep_clears_the_gate
  • dsm_sdk::handlers::bilateral_finality_tests::r1_role_reversal_applies_once_on_a_and_finalizes_on_b
  • dsm_sdk::handlers::bilateral_finality_tests::r2a_recipient_cannot_originate_before_the_peer_finalized
  • dsm_sdk::handlers::bilateral_finality_tests::r2b_the_certificate_releases_the_recipient
  • dsm_sdk::handlers::bilateral_finality_tests::r3_sender_stays_gated_until_the_checkpoint_reaches_quorum
  • dsm_sdk::handlers::bilateral_finality_tests::r4_calibration_cannot_release_the_sender_gate
  • dsm_sdk::handlers::bilateral_finality_tests::r7_a_frozen_checkpoint_is_replayed_byte_identically_after_the_fleet_returns
  • dsm_sdk::handlers::bilateral_finality_tests::r8_a_next_generation_transfer_is_held_until_the_certificate_lands
  • dsm_sdk::handlers::bilateral_finality_tests::r9_the_barrier_is_relationship_local
  • dsm_sdk::handlers::bilateral_finality_tests::the_harness_defers_the_background_poller_while_a_pair_lives
  • dsm_sdk::handlers::node_e2e_tests::a_transfer_reaches_the_nodes_only_sealed_and_arrives
  • dsm_sdk::handlers::node_e2e_tests::an_inbox_read_that_did_not_cover_every_delivery_is_not_a_complete_sync
  • dsm_sdk::handlers::recipient_accept::tests::a_bad_receipt_sig_is_refused_and_never_accepts
  • dsm_sdk::handlers::recipient_accept::tests::a_bad_sig_a_is_refused_and_never_accepts
  • dsm_sdk::handlers::recipient_accept::tests::verification_is_unreachable_from_a_single_half
  • dsm_sdk::handlers::recipient_admission_tests::a_transfer_admits_on_both_sides_with_a_register_backed_release
  • dsm_sdk::handlers::recipient_admission_tests::an_outage_holds_the_transfer_cleanly_and_it_recovers
  • dsm_sdk::handlers::recipient_admission_tests::fabricated_sender_coordinates_are_refused_before_any_durable_state
  • dsm_sdk::handlers::recipient_admission_tests::the_same_sender_debit_cannot_fund_a_second_credit
  • dsm_sdk::handlers::recipient_dispatch::tests::a_single_half_or_an_unbound_half_never_completes
  • dsm_sdk::handlers::recipient_dispatch::tests::a_tampered_evidence_arriving_first_cannot_lock_out_the_honest_copy
  • dsm_sdk::handlers::recipient_dispatch::tests::a_tampered_transfer_arriving_first_cannot_lock_out_the_honest_copy
  • dsm_sdk::handlers::recipient_dispatch::tests::every_arrival_order_converges
  • dsm_sdk::handlers::recipient_dispatch::tests::staging_freezes_the_exact_bytes_and_the_frozen_pair_is_what_applies
  • dsm_sdk::handlers::relationship_finalized::tests::a_certificate_for_a_transition_never_journaled_is_not_ours
  • dsm_sdk::handlers::relationship_finalized::tests::a_verified_certificate_releases_the_recipient_once_and_forgeries_do_not
  • dsm_sdk::handlers::sender_admission_tests::a_failed_finish_holds_the_outbox_and_resume_completes_the_same_admission
  • dsm_sdk::handlers::sender_admission_tests::a_stale_admission_snapshot_is_refused_not_committed
  • dsm_sdk::handlers::sender_admission_tests::a_stale_resume_returns_the_admitted_outcome_and_leaves_a_newer_admission_alone
  • dsm_sdk::handlers::sender_admission_tests::a_transfer_registers_the_senders_root_at_the_next_position
  • dsm_sdk::handlers::sender_admission_tests::an_admitted_burn_advances_the_lineage_and_is_foreign_walkable
  • dsm_sdk::handlers::sender_admission_tests::sequential_admissions_stay_monotonic_across_operation_kinds
  • dsm_sdk::handlers::sender_admission_tests::token_routes_admit_create_and_burn_end_to_end
  • dsm_sdk::handlers::storage_routes::tests::a_delta_for_a_step_with_no_retained_evidence_is_a_terminal_invariant_violation
  • dsm_sdk::handlers::storage_routes::tests::a_full_receipt_on_the_countersign_method_is_refused_at_the_wire
  • dsm_sdk::handlers::storage_routes::tests::a_poisoned_delta_parks_the_step_and_the_honest_delta_still_finalizes
  • dsm_sdk::handlers::storage_routes::tests::a_release_of_another_step_cannot_finalize_this_one
  • dsm_sdk::handlers::storage_routes::tests::settled_outbox_rows_are_never_resubmitted
  • dsm_sdk::sdk::b0x_sdk::tests::a_send_lands_on_exactly_the_register_quorum_of_members
  • dsm_sdk::sdk::b0x_sdk::tests::delivery_below_the_quorum_is_refused_however_many_members_are_marked_failed
  • dsm_sdk::sdk::core_sdk::tests::a_redelivered_transfer_applies_once_and_is_never_rebuilt
  • dsm_sdk::sdk::core_sdk::tests::an_apply_consults_the_signed_pair_never_the_projection

Not in the manifest: dsm_sdk::sdk::sofi_reads::tests::an_unestablished_genesis_candidate_is_not_read_as_unpublished, red on main since #1013 for a reason of its own (it booted no device) and fixed by #1017.

… enforcer is keyed by the policy commitment and registers a policy only from its committed bytes

Placeholder sweep, the last item (owner decision 2026-09-25: delete the
fake, leave the hole). CONFORMANCE_GAPS §6.32.

- TokenPolicySystem::register_policy(bytes) recomputes BLAKE3(TAG_DSM_POLICY,
  bytes), reads the TokenPolicyV3 blob with Core's one parser and derives the
  enforcer's view from it (enforced_policy, moved in from the SDK);
  policy_at(commit) takes the durable bytes the SDK resolver answers only when
  they re-hash to the commitment asked for; enforce_policy(commit, ..) denies
  where no policy is committed. The ERA preload, create_root_token_policy,
  ticker-keyed registration, the caller-asserted anchor binding,
  PolicyCommitResolver, the validator module and the cache's ticker index are
  deleted.
- The SDK keys enforcement by the policy_commit the operation carries
  (Transfer, Burn, CreateToken); a lock operation, which carries none, is
  refused for it (§9.1). Creation and rehydration hand Core the bytes
  (CoreSDK::register_policy_bytes); the resolver answers
  load_policy_verified(commit).
- IdentityConstraint, EmissionsSchedule, CreditBundlePolicy, Custom, roles and
  StoredPolicy deleted; PolicyConditionProto fields 1, 5, 6, 7 and
  CanonicalPolicy.roles reserved; TypeScript bindings regenerated.
  BitcoinTapConstraint untouched, recorded.
- The hole: ERA_POLICY_COMMIT has no preimage, so every ERA transfer and burn
  is refused at enforcement until ERA's policy blob exists. The tests this
  turns red are the expected-red manifest, recorded from this commit's board.
@cryptskii
cryptskii merged commit b46880f into main Sep 26, 2026
19 of 21 checks passed
@cryptskii
cryptskii deleted the fix/era-policy-stand-in-deleted branch September 26, 2026 07:40
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