Skip to content

fix(sofi): the relay carries the bytes that hold a leg cell; the §6.39 owner rulings - #1056

Merged
cryptskii merged 1 commit into
mainfrom
fix/section-6-39-owner-rulings
Sep 29, 2026
Merged

cryptskii merged 1 commit into
mainfrom
fix/section-6-39-owner-rulings

Conversation

@cryptskii

Copy link
Copy Markdown
Collaborator

The owner's rulings of 2026-09-29 on CONFORMANCE §6.39's open items, and the SoFi relay defect that tracing MR-SOFI-0255 found.

The relay never carried an exercise to a leg key

The defect. sofi_relay::relay_fulfillment took the id of the value holding a leg cell, then looked its bytes up by entry digest (value_of). An attempt cell names its holder by E (the external commitment), which is no digest of any bytes. So the lookup never matched, and every relay wrote the position pair alone.

The evidence. A throwaway probe on nodes, after a realized trade:

  • the leg cell read Held { id: E, Final };
  • every carried value's entry_digest differed from E, and each recognized as the exercise with that E;
  • value_of found nothing.

The fix (owner's choice): Core keeps the exact bytes it recognized as holding the cell, instead of anyone looking them up again by id.

  • CellReading::Held carries value, the leader link's bytes, which evaluate previously dropped.
  • AttemptCellRead::value() exposes them.
  • The relay carries exactly those bytes.
  • sofi_advance::final_root_cell reads a final root claim's bytes the same way.
  • value_of is deleted.

Regressions:

  • a_held_attempt_cell_carries_the_exact_bytes_that_hold_it (id E). The same F and P under signatures that do not verify arrive first; the read carries the signed exercise's bytes, and E is not their entry digest.
  • a_held_root_cell_carries_the_exact_bytes_that_hold_it (id = entry digest). A claim naming the next position's key arrives first; the read carries this key's claim.
  • On nodes, a two-hop relay by a device that is not the trader wrote 2 cells before the fix and 4 after (local runs).

§6.39 rulings

  • removeContact: deleted in every layer:

    • the JNI export;
    • the Kotlin declaration and wrappers (UnifiedNativeApi, Unified, UnifiedContactBridge);
    • client_db::delete_contact_by_id and remove_contact;
    • sdk_remove_contact;
    • Core's DsmContactManager::remove_contact, with their tests.

    A future removal is a defined retirement or tombstone flow.

  • dsm_init_runtime: the undeclared C export is deleted; get_runtime stays lazy.

  • dBTC builtins guard: the load-time #[ctor] and the ctor dependency are removed, in all four lockfiles that carry dsm_sdk. No dBTC code changed. assert_builtins_sound runs in no shipped build and belongs to the owner's dBTC redo; its manifest row carries the dBTC exception.

  • MR-STOR-0146, now Met.

    • Every SDK spool write goes through B0xSDK::deliver with bytes seal_for made.
    • A node's spool holds only what its submit route receives: spool_insert has one caller.
    • a_transfer_reaches_the_nodes_only_sealed_and_arrives passes on Postgres.
  • MR-DSM-0272, now Partial. Payloads are sealed end to end: XChaCha20-Poly1305, a domain-tagged key per message id, and one seal kept per id. But the key comes from a fresh ML-KEM encapsulation per payload, not the step's own Kyber exchange that Amendment A7 names. spool_seal's module doc claimed the step binding; it now says what the code does.

  • MR-SOFI-0255, now Partial.

    • The router sends exactly the eight sofi.* methods to their routes, and each route reaches its producer.
    • A test driving all eight on nodes found the relay defect above, and a second one:
      • the owner's sofi.close of a vault that has been traded through never resolves (RouteEvidenceHasNoSource for three trader leaves);
      • a fresh vault closes fine;
      • it is recorded in §6.39 as confirmed, remediation deferred (owner).
    • The eight-route test lands with the close fix; no red test lands here.

Map and manifest

  • Entry points. Three removed; 67 Android entry points remain, plus the node's main.
  • Tables. One sentinel removed. The load rule has no real example any more; its unit tests remain. The Android counts are updated.
  • Intent manifest: 1,074 rows.
    • +8 MR-SOFI-0255, +2 MR-STOR-0146, +2 MR-DSM-0272, and a dBTC fate row for assert_builtins_sound.
    • −1: the guard's row.
    • Every new row reads SATISFIED, and no Android row fails.
    • Locally the node's rows read not-built; CI's --built android,node reads them.

Verification (local, macOS)

  • Targeted --release tests:
    • Core route_chain, sofi, economic::register and spool_seal: 54/0.
    • dsm_sdk contacts, runtime and policy.
    • The full node_e2e_tests module on Postgres, except the held test.
    • sofi_advance.
    • The two regressions.
  • make lint exit 0 (fmt and clippy); the real-code guard is clean.
  • ci/conformance_evidence.py: 779 rows, totals regenerated.
  • make requirement-map and requirement-map-check: 0 contradictions, 24 sentinels, 0 failures.
  • requirement-map-fixture: 0 unexpected outcomes.
  • requirement-map-mutations: 40 cases, 0 failed.
  • cargo test -p requirement_map: 97/0.
  • Gemini gate: SATISFIED (round 2).

…9 owner rulings

The owner's rulings of 2026-09-29 on CONFORMANCE §6.39's open items, and
the relay defect that tracing MR-SOFI-0255 found.

The relay never carried an exercise to a leg key.
- `relay_fulfillment` took the id of the value holding a leg cell and
  looked its bytes up by entry digest (`value_of`).
- An attempt cell names its holder by `E`, which is no digest of any bytes,
  so the lookup never matched: every relay wrote the position pair alone.
- A probe on nodes showed it after a realized trade. The leg cell was Final
  under `E`, and every carried value's entry digest differed from `E`.
- `evaluate` had the leader link's exact bytes and dropped them. The fix
  (owner's choice):
  - `CellReading::Held` carries `value`;
  - `AttemptCellRead::value` exposes it;
  - the relay carries exactly those bytes;
  - `sofi_advance` reads a final root claim's bytes the same way;
  - `value_of`, which rebuilt a value from its id, is deleted.
- Regressions:
  - a_held_attempt_cell_carries_the_exact_bytes_that_hold_it (`E`, behind
    the same F and P under signatures that do not verify);
  - a_held_root_cell_carries_the_exact_bytes_that_hold_it (the entry
    digest, behind a claim naming the next position's key).
- On nodes, a two-hop relay by a device that is not the trader wrote 2 cells
  before the fix and 4 after.

removeContact is deleted in every layer:
- the JNI export;
- its Kotlin declaration and wrappers;
- the raw row deletes `client_db::delete_contact_by_id` and `remove_contact`;
- `sdk_remove_contact`;
- Core's `DsmContactManager::remove_contact`, with their tests.

A future removal is a defined retirement or tombstone flow.

dsm_init_runtime, an undeclared C export, is deleted; `get_runtime` stays
lazy.

The dBTC builtins guard no longer runs at library load. The `#[ctor]` and
the `ctor` dependency are gone from all four lockfiles that carry dsm_sdk. No
dBTC code changed: `assert_builtins_sound` runs in no shipped build and
belongs to the owner's dBTC redo. Its manifest row carries the dBTC
exception.

Three §8 rows traced:
- MR-STOR-0146 is Met.
  - Every SDK spool write goes through `B0xSDK::deliver` with bytes
    `seal_for` made.
  - A node's spool holds only what its submit route receives.
  - a_transfer_reaches_the_nodes_only_sealed_and_arrives passes on Postgres.
- MR-DSM-0272 is Partial.
  - Payloads are sealed under a fresh encapsulation per payload, not the
    step's own Kyber exchange as Amendment A7 requires.
  - `spool_seal`'s module doc claimed otherwise and now says what the code
    does.
- MR-SOFI-0255 is Partial.
  - Every route reaches its producer.
  - A test driving all eight on nodes found the relay defect (fixed here)
    and a second one, recorded as confirmed and deferred: the owner's
    `sofi.close` of a vault that has been traded through never resolves
    (`RouteEvidenceHasNoSource` for three trader leaves).
  - That test lands with the close fix.

The map tables follow the removals:
- three entry points, one sentinel, the load rule's example and the Android
  counts;
- 13 manifest rows added and one removed: 1,074 rows, no Android row failing.
@cryptskii
cryptskii merged commit 40a2de1 into main Sep 29, 2026
23 checks passed
@cryptskii
cryptskii deleted the fix/section-6-39-owner-rulings branch September 29, 2026 08:39
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