Skip to content

fix(map): the node's committed rows; a reading names only definitions its build reads - #1054

Merged
cryptskii merged 1 commit into
mainfrom
fix/code-map-node-rows-and-lookups
Sep 29, 2026
Merged

cryptskii merged 1 commit into
mainfrom
fix/code-map-node-rows-and-lookups

Conversation

@cryptskii

Copy link
Copy Markdown
Collaborator

The Code map job is red on main since #1053; this makes it green

On its first Linux run, #1053's adversarial check failed for four reasons. The first two are what this PR fixes; the other two are consequences:

  1. The node's rows were never committed. The storage node is indexed only on Linux, so its entry point and counts were not in ci/requirement_map.{roots,counts}.tsv. This PR adds them, taken from that run's committed.tsv:
    • one entry point, main;
    • 283 reached (50 of them by dispatch), 5,394 dead, 299 indeterminate and 1,977 not in the build.
    • The node's own crate reads fully Reached. The Dead readings are core code it never uses. 282 of the 289 IND_UNLINKED_IMPL readings flow from three seeds (impls of outside traits on [u8; N] and dyn).
  2. The checker read a path that two definitions share. The sentinel node dsm_storage_node::main read ['None None', 'reached REACHED']: the second definition was the build script's main, which is outside the node build. Now:
    • A sentinel names a reading in one build, and a fixture key does the same, so only definitions that build reads are candidates. Two such definitions under one path still fail.
    • The fixture gains the shape (probe::main, beside its build script's main) and a committed fixture sentinel. With the old lookup, it reproduces CI's failure exactly.
  3. The mutation step stopped before running any case, because the real map was dirty. Now a dirty map fails only its own planted cases, and every other case runs.
  4. The harness counted two same-reading definitions as one. It now keeps them as two readings.

Also fixed, found through the Gemini gate

  • A real latent defect.
    • cargo tree prints dsm-anchor-core and dsm-anchor-verifier with hyphens, and the features parser stored them under _. So those crates' call gates were read with no features, and their feature-leak exclusions were skipped.
    • There is now one normalization (cfgs::feature_key), and an unknown package is an error instead of an empty set.
    • The new tests went red first. The Android map had no live instance: all 22,146 readings are identical.
  • A malformed cargo tree line is an error, not skipped.
  • compiled() drops an edge only when no site compiles.
  • The fixture's own test had rotted: it panicked when run. It is fixed, and make requirement-map-fixture now builds the fixture and runs its test.
  • Edge endpoints the map has no definition for are counted per build and pinned as no-definition, so the checks' skips are accounted for. They are other crates' symbols, and the workspace's generated or macro-written code. The counts are Android 1,917 / 1,950 and node 1,838 / 1,883. A jump fails.
  • An unlinked impl's reason names its whole type ([u8; 32], not [u8;).

Verification

  • Tree: byte-identical to the commit verified before feat(map): stage 1c, the adversarial layer over the code map #1053 merged, cherry-picked onto main (646aa4e3c).
  • requirement_map: 96/96 (--release); clippy -D warnings, fmt and the guard are clean.
  • make requirement-map-fixture: the fixture builds, its test passes, and all 87 readings are as expected.
  • make requirement-map-check (local, Android): 0 contradictions, 25 sentinels, 70 entry points, counts unchanged.
  • The same check on feat(map): stage 1c, the adversarial layer over the code map #1053's CI Linux map (the downloaded artifact): 0 failures, all 26 sentinels, and both builds' entry points and counts.
  • make requirement-map-mutations: 33/33 cases. The 17 planted cases also pass against the CI Linux map.
  • Gemini gate: satisfied.

…s committed rows

CI's first Linux run of the check found the storage node's sentinel
`dsm_storage_node::main` reading two definitions. The second was the build
script's `main` (dsm_storage_node/build.rs), which shares the Rust path but
lies outside the node build's sources, so it has no node reading. A sentinel,
and a fixture reading key, names a reading in one build. Only definitions that
build reads are candidates now. Two such definitions under one path still
fail, as naming two.

- ci/requirement_map.py: the sentinel lookup ignores definitions with no
  reading in the sentinel's artifact.
- `requirement_map fixture`: a key matches only definitions the fixture build
  reads. `absent` means the build reads none.
- ci/requirement_map_mutations.py: two definitions that read alike stay two
  readings and are never folded into one. A map that is not clean before
  planting fails its own planted cases while every other case still runs
  (the first CI run stopped before any case).
- The fixture gains the same shape: `probe::main`, one path shared with the
  fixture's build script. Its committed sentinel
  (tools/requirement_map/fixture/sentinels.tsv) is checked before planting.
  With the old lookup it reads ['None None', 'reached REACHED'], CI's
  failure exactly.
- The node's entry point and counts, taken from CI's Linux map
  (committed.tsv): main is the one entry point. The counts are 283 reached
  (50 of them by dispatch), 5,394 dead, 299 indeterminate and 1,977 not in
  the build. 282 of the 289 IND_UNLINKED_IMPL readings flow from three seeds
  (impls of outside traits on `[u8; N]` and `dyn`) into core code the node
  otherwise never reaches.
- An unlinked impl's reason names its whole type (`[u8; 32]`, not `[u8;`).

Found while answering the gate:
- `cargo tree` prints `dsm-anchor-core` and `dsm-anchor-verifier` with hyphens,
  and the features parser stored them under `_`. compiled_out read those
  crates' calls with no features, and exclusions skipped them. There is one
  normalization now (`cfgs::feature_key`), and `graph::package_features`
  returns an error for an unknown package instead of an empty set. The
  Android map had no instance (readings identical before and after), and
  both new tests went red first.
- A `cargo tree` line whose source is not `(…)` is an error, not skipped.
- compiled() sorts each compiled site once into certain and undecided (with
  the gate's reason). The only way it drops an edge is when no site
  compiles.
- The fixture's own test asserted a sum `entry()` no longer returns, and it
  panicked when run. It is fixed, and `make requirement-map-fixture` now
  builds the fixture as shipped and runs its test.
- The edge endpoints the map holds no definition of are counted per build
  and pinned in counts.tsv as `no-definition`: other crates' symbols, and the
  workspace's generated or macro-written code (Android 1917/1950, node
  1838/1883). A jump fails like an Indeterminate jump, so the checks' skips
  are accounted for, never silent.
@cryptskii
cryptskii merged commit 20f63cb into main Sep 29, 2026
23 checks passed
cryptskii added a commit that referenced this pull request Sep 29, 2026
CI's first run of #1055 (the first to index the node with the manifest's root
questions) refused: `a root query's entry point dsm_storage_node::main names 2
definitions the node build compiles`. The node's index also holds its build
script's `main`, which shares the binary's path and is not in the build. That
is the class #1054 fixed for sentinel and fixture lookups. A root query now
looks paths up only among definitions in the build's own crates.

- The fixture gains the question: MR-FIX-0008 asks whether probe::main, a path
  the fixture's build script shares, is reached from the entry point. Before
  the fix the fixture refused with CI's exact error; it now reads SATISFIED.
- ci/conformance_evidence.py requirement_statuses refuses a canonical ID §8
  gives no row, where it used to map the ID to None. The comparator reports
  that refusal on stderr.

A macOS host cannot index the node, which is why the local run missed this.
The Android root answers are byte-identical before and after. The map check
is clean, 40/40 mutation cases pass, and the fixture reads 90/90 with 0
unexpected intent outcomes.
cryptskii added a commit that referenced this pull request Sep 29, 2026
…ap, and the conformance it found (#1055)

* feat(map): the intent comparator: the specifications' intent against the map

The map establishes what the code does. It never decides what the code ought
to do. The intent manifest (specs/requirements/INTENT_MANIFEST.tsv, the
owner-authorized home) says that, one row per requirement, symbol and build.
ci/intent_comparator.py maps each row's intent and the map's reading to an
outcome through one table, and the outcome to an action. The owner's policy
says which outcomes fail.

- MUST_REACH is satisfied by REACHED and REACHED_VIA_DISPATCH alike: dispatch
  is evidence provenance, not a correctness level (owner ruling). The
  spec-only MUST_REACH_DIRECT accepts a direct path alone, and a
  dispatch-only reading is DIRECT_PATH_GAP.
- The outcomes:
  - SATISFIED and CORRECT;
  - WIRING_GAP, ARTIFACT_GAP, UNDECIDED, DIRECT_PATH_GAP and ROOT_MISMATCH;
  - UNEXPECTED_LIVE_PATH and PRODUCTION_LEAK;
  - REMOVAL_CANDIDATE and DELETE;
  - MISSING_SYMBOL, and AMBIGUOUS_SYMBOL for a path several definitions
    share (two `From` impls on one type);
  - UNSPECIFIED, every production definition no row names.

  A gap fails unless its requirement is Partial, Missing or Violated in
  CONFORMANCE §8, where it is reported as the known hole. REMOVAL_CANDIDATE,
  DELETE and UNSPECIFIED are only reported.
- A manifest that cannot be read is refused, never guessed at. It is refused
  for:
  - an unknown value;
  - a requirement MASTER does not define;
  - a missing exception for an exclusion, a deprecation or a row with no
    requirement;
  - a row with no requirement that demands reach;
  - test-only code that is not MUST_NOT_REACH;
  - a missing citation;
  - a duplicate row;
  - a root that is not an entry point.
- `root` is the map's fact, never the comparator's. `requirement_map index`
  and `fixture` answer `--root-queries` (is this symbol reached from this one
  entry point?) with the same reach, direct-path and classify rules, and
  write root-queries.tsv. Asking nothing is `None`, never an empty answer.
- The requirements come from MASTER and CONFORMANCE §8 through
  ci/conformance_evidence.py, still the only Markdown parser. It now exposes
  canonical_ids, status_rows and requirement_statuses, which its own checks
  use.

Tests and gates:
- The fixture gains an intent manifest with a row for every outcome, and the
  outcome each must produce (intent.tsv, requirements.tsv,
  intent-expected.tsv), run by `make requirement-map-fixture`. It also gains
  two `From` impls on one type.
- Seven `manifest` mutation cases each change one thing in a copy of the
  manifest. The comparator must refuse the row, or move exactly the outcomes
  named, with every other row held to its expected outcome.
- rules.tsv gains the `outcome` kind. src/rules.rs reads OUTCOMES from Python
  (as it reads CHECK_RULES) and checks that each outcome's positive fixture
  row has it and its negative does not.
- `make requirement-map` asks the manifest's root questions. `make
  requirement-map-intent` runs the comparator, and CI runs it with both
  builds required. intent.tsv, unspecified.tsv and root-queries.tsv are kept.

The manifest holds its header only. Its rows are the next chunk.

From the gate:
- The root answers come back in the order asked, whatever the builds' order.
- A root is checked to be an entry point in each build this host indexed.
- The harness checks every table's header and cell count, and tells a
  comparator crash (stderr) from a failing row. It writes the changed
  manifest and the report to separate places.

* docs(requirements): the intent manifest's requirement rows; 16 Met citations traced (§6.39)

The manifest's first rows declare the specifications' intent for the code §8
cites.
- **What is covered.** Every canonical code reference of a Met or Partial
  row (Violated rows cite violating code, not code that must run) becomes a
  MUST_REACH row.
- **Build.** A row goes in the build that reaches its code, preferring its
  requirement's family (storage to node, the rest to Android). It goes in
  the family's build only when nothing reaches the code, since
  storage-spec obligations the client carries run in the Android build.
- **Evidence and source.** Each row names the §8 row's tests as its evidence
  and cites the §8 row.
- **Node pins.** MR-SOFI-0070 and MR-STOR-0042 add node MUST_NOT_REACH rows
  for Route::of and Route::leader: a storage node never computes a cell's
  route or its leader, and the map shows the node's build reaches neither.

Before landing, the comparator read those citations against the canonical
Linux map (CI's map of main at 20f63cb). Sixteen Met rows, over fourteen
requirements, cited code that no shipped build reaches. Each was traced again
from the requirement's text (owner ruling):
- 12 stay Met, citing the production code:
  - a leader comes from position_seed or storage_seed, through
    RoutedCell::new, Route::of (permute) and Route::leader;
  - SoFi signatures: verify_precommit and verify_fulfillment;
  - a setup's identity: setup_ref and verify_setup;
  - the conjunction: validate's Verdict;
  - waiting on an unread leader: evaluate;
  - determinism: dsm_domain_hasher.

  The dead helpers cited before (position_leader, first_member,
  verify_signed_object, SignedSofiBody::object_id, Validation::and/all,
  CanonicalEncode) are named in each note.
- MR-STOR-0156 is now Partial. No shipped build checks a completion proof:
  production builds one and keeps its digest, and nothing reads a kept proof
  back or checks it.
- MR-DSM-0100 is now Partial. The uniform CanonicalEncode is never called,
  and only per-type canonical encodings exist.

CONFORMANCE §6.39 records all of it. Also recorded there: 101 references that
name no definition the map holds (module paths, method shorthand, deleted
code), which are Stage 3's to canonicalize and are not rows yet. The §7 totals
are regenerated: Met 325, Partial 236.

Against the canonical map the manifest reads 539 SATISFIED, 4 CORRECT, and 6
WIRING_GAPs that are Partial requirements' known holes: 0 failing rows. On a
host that cannot index the node, its 76 rows read not-built (UNDECIDED), and
CI requires both builds. ci/conformance_evidence.py passes.

Also: ci/intent_comparator.py loses an unused `import csv` (it reads its tables
with its own strict reader).

* feat(map): entry points bound to their requirements; out-of-scope and test-only fates

The manifest gains the owner's remaining first scope. Entry points are bound
to what they serve, and production symbols no build reaches get the
conservative class fates. The map's uncertainty never becomes intent.

Root queries follow the process, not one call.
- A path must start at the named entry point. A dispatch on it may rest on
  a value any entry point made, since the process holds it: the app router
  is built at startup (JNI_OnLoad, SDK start) and every later request
  dispatches to it through dispatchIngress.
- `reach::reached_with` starts from known type evidence and returns what it
  gathered. A build's reach starts from none; a root query starts from the
  whole build's.
- `reached` had no production caller left and is gone.
- New unit test: a_root_query_dispatches_on_a_value_another_entry_point_made.
- New fixture shape: a router JNI_OnLoad installs in a global and
  `Probe.query()` calls through a trait object (installed.rs). Its manifest
  row is SATISFIED from `query`. It reads UNDECIDED if the query starts from
  no evidence (mutation-checked).

Entry points (owner ruling: bind each root to the requirements it serves).
- A read-only trace of all 71, confirmed by the map:
  - dispatchIngress reaches the code of 208 of the 213 Android symbols the
    manifest names; the other 5 are known holes that no entry point reaches.
  - Those rows, 468 of them, take dispatchIngress as their root.
  - The node's 72 requirement rows take dsm_storage_node::main, its only
    entry point.
- 67 more entry points get rows saying why each exists:
  - 36 offline BLE, 6 NFC recovery and 7 dBTC (the six undeclared bitcoin*
    exports and the builtins guard), as MAY_REACH with the scope exception;
  - 18 infrastructure (library load, SDK start, UI state and status), as
    MAY_REACH with its reason.
- removeContact and dsm_init_runtime stay unspecified, with no stated
  purpose, for the owner.

Unreached production symbols (owner ruling: conservative class rules).
- Dead code in out-of-scope subsystems is MAY_REACH with the scope exception
  and stays active, never deprecated. That covers dBTC (§8.3 Deferred),
  emissions, recovery, the hardware anchor crates and the offline BLE
  transport.
- The testing tags module, which declares itself never used by production
  protocol paths, is test-only MUST_NOT_REACH.
- Everything else stays UNSPECIFIED. The comparator's report now gives the
  action by reading:
  - dead: a removal candidate pending review;
  - indeterminate: undecided;
  - reached: declare its intent.

  It is reported only. 83 paths that name several definitions in a build
  stay unspecified rather than become ambiguous rows.

CONFORMANCE §6.39 records the rest of what the trace found:
- the two ingress points, and removeContact and dsm_init_runtime;
- the parked dBTC policy's load-time assert, which can abort the Android
  library's load;
- a committed C header whose nine declarations have no Rust definition;
- three §8 rows stale the other way: MR-SOFI-0255's sofi_routes.rs exists,
  and spool payloads are sealed (MR-DSM-0272, MR-STOR-0146). They are left
  until traced.

Found while verifying:
- The comparator counted a definition the build does not compile (another
  profile's host-only twin of the same path, which reads not-in-artifact)
  as a candidate. On macOS that made three anchor rows AMBIGUOUS_SYMBOL.
- Only compiled definitions are candidates now. With none compiled, a row
  reads not-in-artifact: ARTIFACT_GAP under MUST_REACH, CORRECT under
  MUST_NOT_REACH.

Against CI's Linux map of main, all 1,062 rows read 0 failing:
- Android: 468 SATISFIED, 376 CORRECT, 5 known holes;
- node: 71 SATISFIED, 141 CORRECT, 1 known hole.

Locally the Android rows read the same, and the node's 92 read not-built,
which CI's --built android,node evaluates.

From the gate: the fixture's Probe.query() expects its installed router (JNI_OnLoad
installs it first) instead of turning a missing one into 0.

* docs(requirements): each open §6.39 finding carries its status; refusals reach the terminal

Owner ruling (2026-09-29): a finding the investigation established stays in §6
even when its remediation is outside the change that found it, marked
deferred. Unfinished implementations of active requirements are never deleted
as dead code, and what awaits the owner is recorded as open, not settled.
§6.39's Open list now says which each item is:
- *confirmed, remediation deferred*: MR-STOR-0156, whose check_completion_proof
  and completion_proofs::get are kept to be wired; MR-DSM-0100, whose
  CanonicalEncode is kept; the stale C header; the 101 unresolved references
  (Stage 3);
- *owner decision*: removeContact and dsm_init_runtime (keep, wire or delete),
  and whether the parked dBTC policy's mismatch should stop the library
  loading;
- *unconfirmed*: the three Missing rows whose code exists, until traced;
- *recorded, not a defect*: the entry points, which the manifest binds.

ci/intent_comparator.py prints a refusal to stderr. `make` redirects
`root-queries`' stdout into a file, and a malformed manifest's refusal
disappeared into it while make aborted. ci/requirement_map_mutations.py reads
an expected refusal from stderr, and fails a case on any other stderr.

* fix(map): a root query names only definitions in its build's crates

CI's first run of #1055 (the first to index the node with the manifest's root
questions) refused: `a root query's entry point dsm_storage_node::main names 2
definitions the node build compiles`. The node's index also holds its build
script's `main`, which shares the binary's path and is not in the build. That
is the class #1054 fixed for sentinel and fixture lookups. A root query now
looks paths up only among definitions in the build's own crates.

- The fixture gains the question: MR-FIX-0008 asks whether probe::main, a path
  the fixture's build script shares, is reached from the entry point. Before
  the fix the fixture refused with CI's exact error; it now reads SATISFIED.
- ci/conformance_evidence.py requirement_statuses refuses a canonical ID §8
  gives no row, where it used to map the ID to None. The comparator reports
  that refusal on stderr.

A macOS host cannot index the node, which is why the local run missed this.
The Android root answers are byte-identical before and after. The map check
is clean, 40/40 mutation cases pass, and the fixture reads 90/90 with 0
unexpected intent outcomes.
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