Skip to content

fix(sofi): the exercise carries the trader's balances before the trade; a lineage known invalid is Invalid (SoFi Amendments S12, S13) - #1064

Merged
cryptskii merged 2 commits into
mainfrom
fix/sofi-close-after-trade
Sep 29, 2026
Merged

cryptskii merged 2 commits into
mainfrom
fix/sofi-close-after-trade

Conversation

@cryptskii

@cryptskii cryptskii commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

The defect

Once one device had traded through a vault, no other device could judge that trade (CONFORMANCE §6.39).

  • TraderSideValid recomputes each balance the trade moves from the balance before it. T° states that balance only by the hash of its leaf.
  • A verifier that was not the trader had no source for the value (Acquired::NoSource).
  • So the owner's sofi.close of a traded vault never resolved, and no device could judge another trader's route.

The fix: SoFi Amendment S12 (owner ruling, 2026-09-29)

The exercise carries each such balance as a content-addressed TraderPreBalance (class 0x0061). The pre-E closure names it, so E commits it.

  • A value counts only because it hashes to the leaf the core states.
  • A balance the closure does not name is Invalid.
  • A named object not yet in hand is waited for.
  • Every verifier reads the trader's balances from these objects, the trader included.

Spec and requirements

  • SoFi §17.5 Amendment S12, the class and tag tables, and §36.
  • MASTER MR-SOFI-0338–0341 (MR-SOFI-0241 untouched); §1 re-pinned.
  • CONFORMANCE §6.40 and §8; MR-SOFI-0255 is now Met.
  • VERIFICATION_MATRIX rows with mutation controls, and intent manifest rows.

Core

  • TraderPreBalance: a strict decoder and its own address namespace. closure_content_address gives the class its address.
  • trader_pre_balances in validation: exactly one object per stated balance, each naming P's trader.
  • A relationship's leaf before the trade is its base, proven by the fold, so realize_root reads only T° and E.
  • Deleted: Evidence.trader_leaves, Missing::TraderLeaf, Acquired::NoSource, NotEstablished::RouteEvidenceHasNoSource, Verifier.local, and LocalLeaves::{trader_evidence, owns, pre}.

SDK

  • The draft builds the objects from the trader's leaves, names them in canonical order, and publishes them. The exercise carries them.

Also found and fixed

  • accepted_claim_at answered for any trader from this device's own admitted store, labelled with the other trader's identity. That is a fabricated claim, unreachable until foreign routes got past NoSource.
  • It now uses the peer walk for another trader. The walk keeps the claim advance_validated accepted.

Evidence (local, release)

  • dsm crate: 1,607 passed, 0 failed (cargo test --locked -p dsm --release).
  • dsm_sdk SoFi, node end-to-end and peer suites: 23 passed, 0 failed, on the storage node over Postgres. Includes:
    • a_vault_traded_through_closes_for_its_owner, the §6.39 reproduction, now Realized with both reserves paid;
    • every_sofi_route_reaches_its_producer, all eight sofi.* routes through the router.
  • make lint: exit 0.
  • Core negative tests: 9. Each gate was mutation-tested, with a named test red for each. The first round found two redundant checks; both are deleted.
  • Frozen wire vector: checked against the independent encoder.
  • Review gate: satisfied (round 2).

Still to do in this PR

  • Pins. The code map's evidence pins for the touched closures are stale until they are repinned from this commit's CI map and board log. The new rows (MR-SOFI-0338–0341) get pinned then too. The PR stays a draft until then.

SoFi Amendment S13: a lineage known invalid (owner ruling, 2026-09-29; second commit)

Once other traders' routes could be judged, one hole remained. A trader whose lineage validation finds Invalid never yields an accepted claim, and under Amendment S9 its setup was "not evaluated". Its trade would hold a vault key forever.

The ruling:

  • history not established yet: wait;
  • history valid: evaluate the setup as before;
  • history established invalid: the setup is Invalid, so the route is Invalid.
  • No accepted claim is synthesized: the negative fact is the lineage verdict itself.

What changed:

  • Spec and requirements: SoFi Amendment S13, beside S9. MR-SOFI-0331 rewritten, MR-SOFI-0342 added, the spec re-pinned.
  • Reader: SofiReads::accepted_claim_at returns the walk's result in its own class.
  • Core: setup_lineage reads that class. Invalid and Quarantined become the verdict. Quarantined is a divergent register cell, the same reading the existing vault-owner check uses. Incomplete and Unresolved wait.
  • Setup check: setup_valid refuses a setup on a lineage known invalid (Invalid::SetupLineageIsInvalid). A verdict about another trader or position establishes nothing.
  • Tests: three Core tests; four mutations, each turning a named test red.
  • Local runs: dsm 1,610 passed, 0 failed; dsm_sdk SoFi and node end-to-end 23 passed, 0 failed; make lint 0. Review gate satisfied (round 4).

Open (CONFORMANCE §6.40)

  • P15-9, unchanged: the peer walk refuses a resolved SoFi position. A trader whose setup follows its own earlier SoFi trade has no setup claim another verifier can accept. This is a separate evidence and replay problem, not part of S13.

…e (SoFi Amendment S12)

Once one device had traded through a vault, no other device could judge
that trade. TraderSideValid recomputes each balance the trade moves from
the balance before it, and T° states that balance only by the hash of its
leaf. A verifier that was not the trader had no source for the value
(Acquired::NoSource), so the owner's close of a traded vault never
resolved (CONFORMANCE §6.39).

Owner ruling (2026-09-29): the exercise carries each such balance as a
content-addressed TraderPreBalance (class 0x0061) that the pre-E closure
names, so E commits it. A value counts only because it hashes to the leaf
the core states. A balance the closure does not name is Invalid; a named
object not yet in hand is waited for.

Spec and requirements:
- SoFi Amendment S12 (§17.5), the class and tag tables, §36.
- MASTER MR-SOFI-0338..0341; §1 re-pinned.
- CONFORMANCE §6.40, §8 rows, MR-SOFI-0255 now Met.
- VERIFICATION_MATRIX rows with mutation controls; intent manifest rows.

Core:
- TraderPreBalance with a strict decoder, and its address namespace.
- closure_content_address gives the new class its address.
- Every verifier, the trader included, reads the trader's balances from
  these objects (trader_pre_balances).
- A relationship entry's leaf before the trade is its base, proven by the
  fold, so realize_root reads only T° and E.
- Deleted: Evidence.trader_leaves, Missing::TraderLeaf, Acquired::NoSource,
  NotEstablished::RouteEvidenceHasNoSource, Verifier.local and
  LocalLeaves::{trader_evidence, owns, pre}.
- The peer walk keeps the claim advance_validated accepted.

SDK:
- The draft builds the objects from the trader's leaves, names them in the
  closure and publishes them; the exercise carries them.
- accepted_claim_at no longer labels this device's own admitted claim with
  another trader's identity: another trader's claim comes from the peer walk.

Tests:
- Nine Core negatives, each gate mutation-tested.
- A frozen vector against the independent encoder.
- On nodes: a_vault_traded_through_closes_for_its_owner, and
  every_sofi_route_reaches_its_producer (all eight sofi.* routes).
…dment S13)

With other traders' routes now judgeable (Amendment S12), a trader whose
lineage validation finds Invalid never yielded an accepted claim. Under
Amendment S9 its setup was never evaluated, so its trade held a vault key
forever.

Owner ruling (2026-09-29):
- history not established yet waits;
- history valid yields the accepted claim, checked as before;
- history established invalid makes the setup, and so the route, Invalid.
- No accepted claim is synthesized: the negative fact is the lineage
  verdict itself.

Spec and requirements:
- SoFi Amendment S13, beside S9.
- MR-SOFI-0331 rewritten; MR-SOFI-0342 added; §1 re-pinned.
- CONFORMANCE §6.40 and §8, the verification matrix and the intent manifest.

Core:
- `SofiReads::accepted_claim_at` returns the walk's result in its own class.
- `setup_lineage` reads that class: Invalid and Quarantined become the
  verdict `SetupLineage::Invalid`; Incomplete and Unresolved establish
  nothing.
- `Evidence.accepted_claims` becomes `Evidence.setup_lineages`.
- `setup_valid` refuses a setup whose trader's lineage is known invalid at
  its position (`Invalid::SetupLineageIsInvalid`). A verdict about another
  trader or position establishes nothing.

SDK: the reader returns the peer walk's result unchanged for another trader,
and the admitted store's for this device's own positions.

Tests: three Core tests, each gate mutation-tested.
@cryptskii cryptskii changed the title fix(sofi): the exercise carries the trader's balances before the trade (SoFi Amendment S12) fix(sofi): the exercise carries the trader's balances before the trade; a lineage known invalid is Invalid (SoFi Amendments S12, S13) Sep 29, 2026
@cryptskii
cryptskii marked this pull request as ready for review September 29, 2026 21:52
@cryptskii
cryptskii merged commit afc1770 into main Sep 29, 2026
26 of 27 checks passed
cryptskii added a commit that referenced this pull request Sep 29, 2026
…vidence pins refreshed (#1064 follow-up) (#1067)

* fix(sofi): a lineage not yet established carries its reason; a recognized exercise is built only by recognition; main's evidence pins refreshed

Follow-up to #1064, which merged before its pin refresh landed.

- INTENT_PINS.tsv refreshed at 1d58ffa (main's tree): 216/216 evidence
  tests, 571/571 PINNED and 0 failing rows against CI's map of that commit.
  Clears main's 353 failing Code map pins (343 stale, 10 unpinned).
- SetupLineage::NotEstablished { reason }: setup_lineage returns a variant
  for every PeerLineageFailure class instead of Option plus a log line;
  setup_valid waits with Missing::LineageNotEstablished { position, reason };
  gather inserts every result. SetupLineageIsInvalid carries its reason.
- RecognizedExercise fields private with read-only accessors, so
  recognize_exercise is its only constructor and closure_objects' one object
  per reference holds for every value.
- facts.rs test helper registration_read takes the root cell's contents,
  Option<SofiResolutionClaim>, instead of a boolean flag.

* fix(map): repin the 240 rows 0524597's code changes moved

The RecognizedExercise accessors, the setup_lineage reason and the facts.rs
helper moved the closures of 240 pinned rows, code class only. Each was
repinned with ci/intent_pins.py repin against CI's code-map artifact of
0524597 (run 36640004247, tree fingerprint equal to this tree's) and the
evidence log at 0524597 (216/216 passed). 240 repinned, 0 refused;
the comparator reads 0 failing rows and 571/571 pins PINNED.
cryptskii added a commit that referenced this pull request Oct 1, 2026
…(A9), and the DSM core row sweep (#1083)

* test(core): the receiving device refuses a non-transferable token, whatever its sender checked

a_non_transferable_token_refuses_its_transfer now also drives B's own
canonical apply (apply_incoming_transfer_staged) with a transfer A
signed, of the non-transferable token B adopted, from B's pinned head
for A. It is refused by the token's operation restriction before any
acceptance is built.

A probe first showed this holds end to end: with the sender's check
removed, B's sync refused the transfer ("Token policy violation ...
Operation not permitted") and nothing was credited. The CONFORMANCE §5A
note that the recipient never checks was wrong.

Mutation: the policy check skipped only for transfers addressed to this
device turns the test red with "reached acceptance: the policy did not
refuse".

* docs(core): nine DSM core rows about SoFi behaviour re-verified now that SoFi is reachable

MR-DSM-0205, 0209, 0211, 0212, 0213 and 0214 are Met on the SoFi
end-to-end tests and the Core resolution tests. MR-DSM-0219, 0265 and
0267 stay Partial, their gaps restated. All nine had recorded SoFi as
unreachable, which has not been true since #1056 and #1064.

* test(core): a send to a device that is not a contact moves nothing

MR-DSM-0074, Amendment A3: the sender sends only over relationships it
has pre-added. wallet.sendSmart to a device A never added is refused
with "recipient must be an added contact before online send"; nothing
is debited, no position is admitted and nothing is left pending. The
check is structural (the send is built from the contact record's keys),
so it has no mutation control that leaves the send buildable.

* docs(core): MR-DSM-0039, 0074, 0079 and 0093 re-verified, all Met

- 0039: acceptance needs a claim final at the payer's next root cell.
- 0074: both ends only use pre-added relationships (new sender-side
  test).
- 0079: Core counts a link only once a ByteCommit following its parent
  commits it.
- 0093: only the named counterparty takes a step.

* fix(online): a transfer's token policy is checked before the sender's register is read

MR-DSM-0029, G13: the receiver reads the payer's register only after
every check it can decide from what it holds. The sync prevalidated each
bound pair, which walks the sender's lineage over the network, and only
the apply then checked the token's committed policy, which this device
holds. The policy check now runs first, before prevalidation. The apply
keeps its own check under the state-machine lock. Test to follow.

* test(online): a transfer its policy refuses is refused before the sender's register is read

MR-DSM-0029, G13, for f1723e3. A hostile sender signs a transfer of
its non-transferable token to B and advances its own head over it. It
then signs the step's receipt with its per-step EK, as its wallet signs
a send's receipt. Both halves reach B's boundary and bind. B's sync
refuses the pair by the token's committed policy, and no member is asked
for a cell while it does.

Mutation: remove the in-hand policy check and the test goes red. B asks
dsm-node-1 for a register cell of the sender before it refuses.

The non-transferable token request moves into a helper, which the new
test and a_non_transferable_token_refuses_its_transfer share.

* docs(core): §6.50 records the batch; MR-DSM-0029 and MR-SOFI-0311 re-verified

- §6.50 records the batch: the receiver's policy check before any read,
  the receiving device's refusal of a non-transferable token, the
  contact check, and the fifteen rows re-verified on this branch.
- MR-DSM-0029 now cites the sync's in-hand policy check and its
  mutation-controlled test. It stays Partial: adoption and the
  relationship tip are still decided after the register read.
- MR-SOFI-0311 and the §5A Transferable check row no longer say the
  recipient never checks. Both stay open: offline transfers are outside
  this round, and vault creation and SoFi legs are tested in Core only.
- VERIFICATION_MATRIX gains both gates, each with its mutation control.

* docs(spec): the relationship-key tag is DSM/smt-key (DSM Amendment A9)

Owner ruling, 2026-09-30. DSM/smt-key is the canonical domain tag for
relationship SMT keys in beta. The /v1 the explainer carried in §26's
formula and §16's example was an error. The code and its golden vector
are unchanged, and no key migrates.

- Explainer: §26's formula and §16's example corrected, with Amendment
  A9 after §26.
- MASTER: §1 re-pinned, a §7.1 entry, and MR-DSM-0115 rewritten.
- CONFORMANCE: MR-DSM-0115 and MR-DSM-0249 go Partial → Met; §6.14's
  finding is marked resolved, and §6.50 records it. DSM core totals:
  90 Met, 96 Partial, 39 Missing, 0 Violated.

* style(core): rustfmt the sender admission tests

* chore(pins): repin the 71 rows #1083 moves

- 66 code-class rows, whose closures reach storage_routes.rs,
  core_sdk.rs and the sender admission tests.
- 5 status-class rows, which this batch moved Partial → Met and accepts
  with `--accept status`: MR-DSM-0115 (two symbols), MR-DSM-0209,
  MR-DSM-0212 and MR-DSM-0249.

All taken from CI's code map of 0d1228f. Their 59 evidence tests ran
and passed at that commit. `make requirement-map-intent` against that
map reads 1,111 rows, 0 failing, and 598 pins, 0 failing.

* chore(pins): repin MR-STOR-0146's two rows after merging main

The merge with #1084 took main's pins for MR-STOR-0146, and this
branch's changes move both rows' closures. Taken from CI's code map of
61b776a. Their 3 evidence tests passed at that commit. `make
requirement-map-intent` against that map reads 1,111 rows, 0 failing,
and 598 pins, 0 failing.
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