Repository navigation
SoFi rig fixes (S16, S19, wallet records), ERA two decimals (S18), storage liveness and no clock - #1089
Merged
cryptskii merged 15 commits intoOct 1, 2026
Conversation
… base units
Owner ruling, 2026-10-01: an amount is entered and shown in whole tokens
with the token's decimals after a point, e.g. `10000.00` for a
two-decimal token, never as base units with the zeros typed by hand.
- `parse_token_units(text, decimals) -> u128`:
- whole tokens, plus up to `decimals` fraction digits;
- a bare integer is whole tokens;
- refused: empty input, any character that is not a digit or the
point, a second point, more fraction digits than the token has, and
overflow.
- `format_token_units(base, decimals)` always shows every decimal place,
and round-trips through the parser.
Every route and screen that takes or shows an amount goes through these,
with the decimals from the token's committed policy.
dsm_sdk's wallet_routes already owns the conversion between typed amounts and base units: parse_display_amount_to_base_units and format_base_units_for_display. It does exact string arithmetic against the token's decimals, and it already serves sends, balances, history and offline cash. 2d529fe added a second copy in Core, and two owners of one rule is what that module's comment forbids. The SoFi routes' amount fields go through the SDK's functions instead.
…ts time out
Owner ruling, 2026-10-01, from the phone-rig run: ERA is divisible to
hundredths.
- **Spec.** SoFi Amendment S18; MR-SOFI-0335 rewritten; MR-SOFI-0349
added (Partial until the SoFi routes take amounts in token units).
MASTER §1 re-pinned.
- **ERA's policy.** It carries decimals 2, and its supply in base units
(8,000,000,000,000, which is 80,000,000,000.00 ERA). Its commitment is
NNG176RZ6ACTWCDPRNYHXZK2DCZ72SPA9Q6XWGRGQ9JGKZYTESG0, a clean cut from
JXPMPGJH…. Nothing under the old commitment carries over.
- **The faucet** pays 10,000 base units (100.00 ERA), and the token
creation fee is 1,000 (10.00 ERA). The amounts people know are
unchanged.
- **Test fixtures.**
- The two-device fixture funds in whole ERA, one faucet claim per
payout.
- The reserve tests and the current-state test use the payout
constant instead of a literal.
- **The storage-member HTTP client** connects within 10 s and finishes a
request within 30 s. On the rig, a position write sat with no request
answered and no error. A member that never answers is now a member
that did not answer.
The dependent sweep for SoFi Amendment S18. Funding is in whole ERA through `economic_fixtures::whole_era`, which scales by ERA's committed decimals. A test that speaks in ERA therefore keeps its meaning. Sends, burns, trades and vault reserves stay in base units, as written. Comments that called a base-unit amount "10 ERA" now say so. - Balances that come from the faucet or the creation fee are stated as `whole_era(n) ± base units`. Covers the faucet, sender admission, bilateral finality, offline step, node e2e and frontier verification tests, plus the restart and projection-repair tests. - Display expectations are at two decimals: history "1.00" and "100.00"; the inbox preview "0.10 ERA"; the pending step "0.03"; the transaction render "-1.00"; the balance row and the frontend ERA fixture "264.00" / "80000000000.00". - ERA's reported facts: decimals 2, and the anchor is the new commitment NNG176RZ6ACTWCDPRNYHXZK2DCZ72SPA9Q6XWGRGQ9JGKZYTESG0. - The offline load is entered as "20.00", which is 2,000 base units. The 41 SDK tests the ERA change turned red now pass: 126 passed / 0 failed across their modules, release.
…-and-token-unit-amounts
…ead asks every seat at once
Node side:
- The set client (set_client::pinned_set_client) had no connect or
request timeout, and mirror_sync holds a node-wide one-at-a-time lock. A
set-mate that accepted the connection and never answered held the sync
and the lock open indefinitely, stalling every later mirror sync on the
node.
- It now connects within SET_MATE_CONNECT_TIMEOUT (5 s) and answers within
SET_MATE_REQUEST_TIMEOUT (10 s), or the fetch fails, as an unreachable
set-mate does: BAD_GATEWAY, with the lock released. These are transport
liveness bounds; a fetch that times out stores nothing and orders
nothing.
- MAX_SYNC_CYCLES drops from 1024 to 128, so one sync of a member far
behind stays well inside the SDK's 30 s per request. Every cycle is kept
as it is fetched, so later syncs continue.
SDK side: route_seats::read_cell asked the five seats one after another, to
read values, to close cycles and to sync mirrors. A seat that does not answer
cost one client timeout per seat in each loop. The three fan-outs now run at
once (join_all), with the answers kept in route order, so the evidence Core
evaluates is unchanged.
Test: bytecommit_chain::a_set_mate_that_never_answers_fails_the_sync_and_frees_it
(a set-mate that accepts and never answers; two syncs each answer
BAD_GATEWAY within 3× the request bound). Mutation control: the set-client
timeouts removed → red ("sync 0 hung on a set-mate that never answers"),
restored.
Release, on Postgres: bytecommit_chain 8/0. route_seats, faucet flows and the
SoFi trade and pay node e2e tests 21/0. fmt, clippy -D warnings (dsm_sdk,
dsm_storage_node), the real-code guard and the clockless gate are clean.
…ws re-verified MR-STOR-0131, 0133, 0138 and 0094 move Missing → Met and 0134 Missing → Partial: each Missing note described a codebase before route chains. The cited tests pass at this tree. 0134 stays Partial because the writer never records a taken empty. MR-STOR-0140 cites the silent set-mate test, and the matrix records its mutation control. MR-STOR-0008 notes the set client's transport bounds.
Every node source is parsed with syn, reading paths, `use` trees and macro bodies. Any clock or timer is refused: Instant, SystemTime, UNIX_EPOCH, chrono, tokio::time. A comment or string never counts. The self-test covers grouped, renamed and glob imports and a macro body; a first token-window draft missed the grouped import. Mutation controls, red and then restored: - a grouped-import tokio::time sleep in the spool; - Instant::now() in db/pg.rs. ci/no_clock_and_no_json.sh loses two node exemptions: the spool, which reads no clock, and a rate limiter that no longer exists. A clock read in the spool now fails it too. MR-STOR-0008 Partial → Met.
…(S16), routes that split (S19), the walk past another trader's SoFi parent, and the wallet's records of SoFi Phone-rig rulings, 2026-10-01. SoFi Amendment S16. A vault's genesis is indexed under each of its tokens; Verifier::vaults_of_token keeps a candidate only when vault_genesis accepts it and its market pairs the token, and an undecided candidate makes the discovery partial. sofi.findRoute reads the two tokens' indexes, needs no setup, and says when its search was partial. set_up_with admits the setup transaction ahead of the first trade, route or close through a vault, after the route is priced. sofi.setup is no longer a route; sofi.vaults lists the device's vaults at their heads, shown under Storage -> DLVs. Beta blocker: a walk that met another trader's exercise parented on that trader's SoFi position stopped (ParentUnresolved), so after any trader traded twice every other device's walk of the vault stalled. The walk now resolves that parent through peer_root_at (economic/peer_lineage.rs, reviewed by CORE), counted only as the position and fulfillment P names; resolving a position bounds each vault's chain at the parent its leg names (chain_until), and verified roots are kept per reads context. SoFi Amendment S19 (owner: "It should be no different. It just happens to be the same token"). A Swap route's hops chain or split across vaults of one pair, summing to the intent; the search proposes the split when it gives the most, and the trade plans it again at the heads it walks. The wallet: token creation, vault creation, setup, trade and close write a history row naming every token moved (TransactionInfo.moves) and rebuild the moved tokens' projections from the head, so a realized trade shows at once. SoFi request amounts are entered in token units and parsed against each token's decimals; reported amounts carry their display form. Records: MR-SOFI-0350-0359, CONFORMANCE §6.53, VERIFICATION_MATRIX, INTENT_MANIFEST; SoFi spec re-pinned.
…very-auto-setup-and-realize-records
The token_manager_balance_replay trace asserted 61 and 39, which are the faucet payout of 100 less the 39 it moves. ERA's payout is now 10,000 base units (SoFi Amendment S18), so the trace failed in CI (workspace-rest). The final balances are now the payout less the transfers' sum, and the transfers' sum, the same rule the per-step conservation check already states. dsm_vertical_validation: 28 passed / 0 failed, release.
…route-chain-records' into feat/sofi-vault-discovery-auto-setup-and-realize-records # Conflicts: # specs/requirements/CONFORMANCE_GAPS.md
…e SoFi PR One PR for the three branches: the SoFi rig fixes, STORAGE's mirror liveness and route-chain records (#1088), and CORE's ERA with two decimals (#1087). - MASTER: CORE's MR-SOFI-0349 beside MR-SOFI-0350–0359; §1 re-pinned to the merged SoFi specification (S16, S19 and S18); 928 canonical requirements. - CONFORMANCE: MR-SOFI-0349 is Met. The SoFi routes take amounts as text in token units and report their display form, which was the part S18 left Partial. Totals regenerated. - node tests: CORE's whole_era arithmetic, and the new SoFi tests' ERA balances moved to whole_era.
One repin for the SoFi, CORE and STORAGE changes together: - 25 new rows pinned; - 591 stale rows repinned (582 code-class, 9 with their evidence changed: the SoFi route rows whose cited tests moved); - the orphan pin of the removed `sofi_flow::setup` unpinned. Evidence, run at 83b6f61: the 15 tests of the new rows; 241 tests for the 425 stale keys from the SoFi and CORE changes; STORAGE's 105 tests (11 runs) for the 167 keys from its files. All passed. `make requirement-map-intent` on CI's code map: 0 failing rows, 633 pinned, 0 failing pins.
cryptskii
marked this pull request as ready for review
October 1, 2026 07:27
cryptskii
deleted the
feat/sofi-vault-discovery-auto-setup-and-realize-records
branch
October 1, 2026 08:13
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.
The 2026-10-01 phone-rig fixes from all three sessions, in one PR at the owner's direction. It replaces CORE's #1087 and STORAGE's #1088, both merged into this branch:
1. SoFi Amendment S16: a vault is found by its tokens, and its setup is the first step of the first trade through it
Rig finding.
sofi.findRoutesearched only the vaults the trader had already set up with, so the rig's first trade (9FF through 8XK's vault) went through only because 9FF set up by hand.The token index.
vault_token_locator(t)for each of its two tokens.TAG_DSM_SOFI_VAULT_TOKEN_LOCATORis new; the tag count tripwire goes from 351 to 352.Verifier::vaults_of_tokenkeeps a candidate only whenvault_genesisaccepts it and the accepted market pairs the token. Junk bytes, a forged genesis, and a real vault of another pair are passed over.Path search.
findRoutereads the input and output tokens' indexes, not the trader's relationships, and needs no setup.SofiFindRouteResponse.searchsays whether the search saw every vault. A partial search with no route is an error in the app, never "no route".Setup.
set_up_withadmits the setup transaction for any vault on the route that the trader has no setup with: one per vault, in hop order.sofi.setupis no longer a route (envelope field 121 reserved).sofi.vaults(new route, envelope field 126). Lists the vaults the device created, from the VaultCreation leaves of its validated root, each walked to its head: reserves, fee, generation and status. Storage → DLVs shows them above the dBTC vaults.2. Beta blocker: another trader's conditional parent stalled every walk
The bug.
ParentUnresolvedand stopped.The fix.
peer_root_at. It's a new entry point ineconomic/peer_lineage.rs, written to CORE's spec and reviewed by CORE.PeerPositionResolver, as an interior conditional position.parent_named).Two related changes.
chain_until). Unbounded, it walked into the very exercise whose parent it was resolving, and the stack overflowed.Known cost, recorded in CONFORMANCE §6.53. A walker without a frontier for a trader walks that trader's lineage from activation, with a 512-step budget. A trader with more than about 500 positions is
Incompletefor such walkers. Whether a walk should record frontiers is an owner question.3. SoFi Amendment S19: a route chains or splits
Owner ruling (2026-10-01): "It should be no different. It just happens to be the same token," and "It should work either way."
Core rule.
validate_swapdecides the shape against the intent; a split that doesn't sum isInvalid::SplitDoesNotSum.route_endpointsis the one rule the producer states the intent by.Producer.
4. The wallet's records of SoFi (rig rulings; display caches, not requirements)
TransactionInfo.moves, types 7–11). The wallet renders each line.balance.listat once, not after a restart.*_entered), parsed byparse_display_amount_to_base_unitsagainst each token's committed decimals. Reported amounts carry*_display.sofi.tradeandsofi.routename the output token, and the route must give it.5. ERA has two decimals: SoFi Amendment S18 (CORE, was #1087)
Owner ruling (2026-10-01): ERA is divisible to hundredths. The fee model is unchanged: the fee comes out of the input and the trader pays it.
NNG176RZ6ACTWCDPRNYHXZK2DCZ72SPA9Q6XWGRGQ9JGKZYTESG0, a clean cut. The ERA reserve id hashes the commitment, so the new ERA has its own reserve chain and the nodes need no wipe.parse_display_amount_to_base_unitsandformat_base_units_for_displayconvert amounts. The SoFi routes use them too (§4), so MR-SOFI-0349 is Met here.economic_fixtures::whole_era(n). Thevertical_validationtracetoken_manager_balance_replay(feat(core): ERA has two decimals (SoFi Amendment S18); storage requests time out #1087's red CI) is fixed.6. Storage: a silent set-mate cannot hold the mirror sync; no node source reads a clock (STORAGE, was #1088)
The liveness bug (CONFORMANCE §6.54).
mirror_syncholds a node-wide one-at-a-time lock. A set-mate that accepted a connection and never answered held that sync, and every later one, open indefinitely.set_clientgets a 5 s connect bound and a 10 s request bound. A fetch that times out fails like an unreachable set-mate (502, lock released) and stores and orders nothing.MAX_SYNC_CYCLESdrops from 1024 to 128, so one sync stays inside the SDK's 30 s bound.route_seats::read_cellasks every seat at once.a_set_mate_that_never_answers_fails_the_sync_and_frees_it. With the timeouts removed it goes red.MR-STOR-0008, Partial → Met.
dsm_storage_node::no_clock_readsparses every node source withsynand refusesInstant,SystemTime,UNIX_EPOCH,chronoandtokio::time.tokio::timesleep in the spool, andInstant::now()indb/pg.rs.ci/no_clock_and_no_json.shloses two node exemptions.Records only. MR-STOR-0131, 0133, 0138 and 0094 re-verified Met; 0134 stays Partial.
Tests (release, on the shipped Postgres node backend)
At the combined head 83b6f61 (all three branches merged):
dsm_sdknode_e2e, sender_admission, realized_records and sofi_flow: 35 passed, 0 failed.dsmlib, thesofi::,economic::,core::token,core::state_machineandcommon::domain_tagstests: 335 passed, 0 failed.dsm_vertical_validation: 28 passed, 0 failed (token_manager_balance_replayfixed; feat(core): ERA has two decimals (SoFi Amendment S18); storage requests time out #1087 was 27/1).dsm_storage_nodeno_clock_reads: 2 passed, 0 failed.tscclean.make lintexit 0.real_code_guardclean.ci/conformance_evidence.py: 805 rows, 0 failures.Before the merge, on the SoFi branch alone: the whole
dsmcrate, lib and integration, 1,616 passed and 0 failed. CORE's and STORAGE's boards are in their sections above.New SoFi tests.
discovery_passes_over_what_is_not_a_vault_of_the_token;a_route_search_that_cannot_see_its_vaults_says_so(renamed);every_sofi_route_reaches_its_producer(no manual setups,sofi.vaults, the owner's walk past B's second trade);a_realized_trade_shows_in_balances_and_history_at_once;one_order_fills_through_two_vaults_of_the_same_pair.a_route_chains_or_splits_and_its_endpoints_follow;a_split_whose_legs_sum_to_the_intent_is_not_refuted_by_its_shape;a_split_that_does_not_sum_to_the_intent_is_invalid;only_the_position_and_fulfillment_p_names_resolve_its_parent;a_frontier_recorded_at_the_position_is_its_root_and_nothing_is_read;the_activation_root_names_no_parent.Mutation controls (each restored byte for byte, each with a named test red):
tradewithoutset_up_with;parent_namedaccepting any fulfillment, or skipping the position;peer_root_atstanding below the position;Records
After merge
decimals: 0(practice mode, some jest mocks) are queued for the frontend sweep.Coordination
economic/peer_lineage.rs(peer_root_at,chain_through),core/bridge.rs(the response list) and the domain-tag count.