From 8adb1705df67e3f416ae9f2269da883435819a0a Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 01:29:42 -0400 Subject: [PATCH 01/26] WIP: test generalization --- TESTING.md | 53 ++++++++++++++ docs/v3-lens-capability-register.md | 1 + src/v3/compiler/regen.dag | 9 +++ src/v3/compiler/tests/integration.rs | 16 +++-- .../cementing_lens_registry_dispatch_test.rs | 69 +++++++++++++++++++ 5 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs diff --git a/TESTING.md b/TESTING.md index fd4d377985a..5ef41a48786 100644 --- a/TESTING.md +++ b/TESTING.md @@ -331,6 +331,57 @@ See `docs/design-test-infra.md` and ROADMAP §Lane 2 Stage 2c for the DB-15 trajectory. This document is the near-term discipline for Rust-side tests while that runtime matures. +## Cementing tests (Band C — lens subsumption) + +**Where this applies.** Whenever a brief, roadmap line, or review +claims that v3 **subsumes** or is **behaviorally equivalent** to a +v2 analysis for the same program text, that claim is a Band-C +statement about the *function* of the lens, not about whether the +`.dag` file is structurally terminal. The authoritative inventory +is `docs/v3-lens-capability-register.md` (behavioral axis + v2 +counterpart column). Any `LensRegistryEntry` in +`src/v3/compiler/regen.dag` whose row moves to +`BEHAVIORALLY COMPLETE` while still naming a real v2 counterpart +(not `None (v3-native)` / not `N/A`) inherits the same obligation. + +**What “cementing” means.** A cementing test is a **behavioral** +regression that would fail if v3 silently changed the analysis +while prose still claimed parity. Concretely: + +- **v2 counterpart exists:** run the same minimal fixture through + both implementations (v2 oracle + v3 lens output) and assert + semantic equality on the published carrier shape — or assert a + documented, reviewed projection when the types differ but the + claim is about a specific homomorphism. +- **No v2 counterpart (`N/A` / v3-native) but the register marks + `BEHAVIORALLY COMPLETE`:** pin the lens’s published behavioral + contract on **minimal constructed `Dag` shapes** (or a single + tiny `compile_to_dag` fixture when the contract genuinely spans + the pipeline). This is still a cementing test: it cements the + `COMPLETE` row against accidental semantic drift. + +**Where tests live.** Add modules under +`src/v3/compiler/tests/integration/cementing/` and register each +file from `src/v3/compiler/tests/integration.rs` via `#[path = +"integration/cementing/.rs"]`. Prefer one focused module per +lens (or per v2 parity claim), not a kitchen-sink file. + +**Dispatch (same PR as the claim).** Editing +`src/v3/compiler/regen.dag` or promoting a register row to +`COMPLETE` with v2 parity is not complete until the matching +cementing module exists and `tests/integration.rs` wires it. Extend +`CEMENTING_MODULES_FOR_V2_COMPLETE_CLAIMS` in +`tests/integration/cementing/cementing_lens_registry_dispatch_test.rs` +when the register row is upgraded; the test +`cementing_test_modules_exist_for_escalated_v2_complete_registry_claims` +requires each listed stem to resolve to an on-disk `.rs` file under +`cementing/`. + +**Anti-scope.** This section is not a mandate to prove full lens +equivalence for every `.dag` file, and it does not require new +substrate accessors or emitters. It only governs **explicit** +subsumption or `COMPLETE`-with-counterpart claims. + ## Related - `INVARIANTS.md` — project-level invariants; some have direct @@ -339,3 +390,5 @@ for Rust-side tests while that runtime matures. the modeled concepts, not rebuild them - `docs/design-test-infra.md` (DB-15) — the `.dag`-native test infrastructure this document is the stopgap for +- `docs/v3-lens-capability-register.md` — Band-C behavioral axis; + cross-check before dispatching “v3 replaces v2 X” briefs diff --git a/docs/v3-lens-capability-register.md b/docs/v3-lens-capability-register.md index 647af16e539..6f140a87820 100644 --- a/docs/v3-lens-capability-register.md +++ b/docs/v3-lens-capability-register.md @@ -78,6 +78,7 @@ The register is a tool, not a document. If it is stale, it is worse than not exi 3. **Review at every ROADMAP dispatch.** Before queueing a brief that depends on a lens being equivalent to its v2 counterpart (e.g., "compare v3 complexity output against v2 complexity output"), check the row. If the row says `PROXY` or `STUB`, the brief is premature and must either wait for substrate/emit work or narrow its scope to what the lens actually computes. 4. **Downgrades are first-class.** A lens that regresses from `COMPLETE` to `PROXY` (e.g., because a cementing test failed and was weakened rather than fixed) belongs in this register immediately. Silent downgrade is the failure mode this document exists to catch. 5. **The two axes do not trade off.** `STRUCTURALLY TERMINAL` + `BEHAVIORALLY PROXY` is a valid, honest state — a well-formed file computing a weaker function than its name implies. It is not a contradiction. Claiming `COMPLETE` because the file is structurally clean is the mistake this register is named against. +6. **Cementing tests have a canonical home in-tree.** In-repo dispatch (fixture layout, v2-oracle vs minimal-`Dag` contract tests, and the `regen.dag` → register → test-module ratchet) lives in `TESTING.md` under *Cementing tests (Band C — lens subsumption)* and in `src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs`. Promoting a row to `BEHAVIORALLY COMPLETE` with a non-`N/A` v2 counterpart without landing the cementing module in the same PR is a process failure, not a modeling disagreement. ## Related docs diff --git a/src/v3/compiler/regen.dag b/src/v3/compiler/regen.dag index 1ba63e9e8e6..4b7bc291c1c 100644 --- a/src/v3/compiler/regen.dag +++ b/src/v3/compiler/regen.dag @@ -24,6 +24,15 @@ import std.types { FilePath, NonEmptyStr } // means empty-string entries cannot be lowered into the registry, // closing the "illegal states representable" hole that a raw // `String` would leave open across all three fields. +// +// **Band-C cementing dispatch.** Each new `LensRegistryEntry` must +// update `docs/v3-lens-capability-register.md` (see its Discipline +// section). If the register row is `BEHAVIORALLY COMPLETE` and names +// a concrete v2 counterpart, add a matching behavioral cementing +// module under `src/v3/compiler/tests/integration/cementing/` and +// extend the ratchet slice in +// `cementing_lens_registry_dispatch_test.rs` in the same PR. See +// `TESTING.md` ("Cementing tests (Band C — lens subsumption)"). type LensRegistryEntry { name: NonEmptyStr diff --git a/src/v3/compiler/tests/integration.rs b/src/v3/compiler/tests/integration.rs index fb3bd31848b..efaba86e8dd 100644 --- a/src/v3/compiler/tests/integration.rs +++ b/src/v3/compiler/tests/integration.rs @@ -15,13 +15,13 @@ //! test-binary production. //! //! **Module discipline.** Each file under `tests/integration/*.rs`, -//! `tests/boundary/*.rs`, or `tests/unit/*.rs` is a sibling module at this -//! crate root, reached via `#[path]` because Rust's default module -//! resolution for a crate-root file looks in the containing directory -//! (`tests/`) rather than a same-named subdirectory. Shared helpers live -//! under `tests/integration/common/`. Inside a test module, -//! `use crate::common::…` reaches those helpers; there is no per-file -//! `mod common;` declaration. +//! `tests/integration/cementing/*.rs`, `tests/boundary/*.rs`, or +//! `tests/unit/*.rs` is a sibling module at this crate root, reached via +//! `#[path]` because Rust's default module resolution for a crate-root file +//! looks in the containing directory (`tests/`) rather than a same-named +//! subdirectory. Shared helpers live under `tests/integration/common/`. +//! Inside a test module, `use crate::common::…` reaches those helpers; there +//! is no per-file `mod common;` declaration. //! //! **Layer taxonomy (TESTING.md § test layers).** Files are partitioned //! by directory: @@ -123,6 +123,8 @@ mod sg3_surface_reflection_consumer_test; mod sg6_hand_authored_census_test; #[path = "integration/sg7_prep_variant_payload_freshness_test.rs"] mod sg7_prep_variant_payload_freshness_test; +#[path = "integration/cementing/cementing_lens_registry_dispatch_test.rs"] +mod cementing_lens_registry_dispatch_test; #[path = "integration/thesis_parallelism_test.rs"] mod thesis_parallelism_test; #[path = "integration/thesis_validation_test.rs"] diff --git a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs new file mode 100644 index 00000000000..37580c921a3 --- /dev/null +++ b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs @@ -0,0 +1,69 @@ +//! **Layer:** integration +//! +//! Band-C cementing dispatch for lenses enumerated in `regen.dag` and +//! described in `docs/v3-lens-capability-register.md`. +//! +//! - `TESTING.md` — *Cementing tests (Band C — lens subsumption)* +//! - `src/v3/compiler/regen.dag` — header comment on cementing dispatch + +use std::path::PathBuf; + +use v3_compiler::dag::{Dag, LiteralBits}; +use v3_compiler::diagnostics::SourceSpan; +use v3_compiler::lens_provenance::{origin_of, Origin}; + +fn span() -> SourceSpan { + SourceSpan::new("", 0, 0) +} + +fn origin_label(origin: &Origin) -> &'static str { + match origin { + Origin::NoProducer => "NoProducer", + Origin::MissingPort => "MissingPort", + Origin::MissingBehavior => "MissingBehavior", + Origin::Source { .. } => "Source", + Origin::Computed { .. } => "Computed", + Origin::Selected { .. } => "Selected", + Origin::Accumulated { .. } => "Accumulated", + } +} + +/// Pairs of (`regen_lens --lens ` registry key, cementing module stem +/// under `tests/integration/cementing/` without `.rs`). +/// +/// Append an entry when `docs/v3-lens-capability-register.md` promotes a +/// lens to `BEHAVIORALLY COMPLETE` **and** the v2 counterpart column names a +/// concrete v2 artifact (not `None (v3-native)` / not `N/A`). Land the new +/// `cementing/.rs` module and a `#[path = ...]` line in +/// `tests/integration.rs` in the same PR. +const CEMENTING_MODULES_FOR_V2_COMPLETE_CLAIMS: &[(&str, &str)] = &[]; + +#[test] +fn provenance_origin_of_cements_behavior_complete_row_on_minimal_ports() { + // Register row: `provenance.dag` — BEHAVIORALLY COMPLETE, v3-native. + // Cementing here pins the public `Origin` classification contract the + // same way a future v2-oracle test would pin cross-implementation parity. + let mut dag = Dag::new(); + let int_shape = dag.int_shape().expect("bootstrap Int"); + let param = dag.alloc_port_with_shape(int_shape); + assert_eq!(origin_label(&origin_of(&dag, ¶m)), "NoProducer"); + + let literal = dag.push_value(LiteralBits::Int(7), span()); + assert_eq!(origin_label(&origin_of(&dag, &literal)), "Source"); +} + +#[test] +fn cementing_test_modules_exist_for_escalated_v2_complete_registry_claims() { + let cementing_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("integration") + .join("cementing"); + for (registry_name, stem) in CEMENTING_MODULES_FOR_V2_COMPLETE_CLAIMS { + let path = cementing_dir.join(format!("{stem}.rs")); + assert!( + path.is_file(), + "registry lens `{registry_name}` is listed for v2-complete cementing; expected cementing module at {}", + path.display() + ); + } +} From 5e0c1a005b1ed30ccba6b6647f975986846d4ea6 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 01:29:50 -0400 Subject: [PATCH 02/26] chore: apply cargo fmt --- src/v3/compiler/tests/integration.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/v3/compiler/tests/integration.rs b/src/v3/compiler/tests/integration.rs index efaba86e8dd..8c674eff29d 100644 --- a/src/v3/compiler/tests/integration.rs +++ b/src/v3/compiler/tests/integration.rs @@ -37,6 +37,8 @@ #[path = "integration/common/mod.rs"] mod common; +#[path = "integration/cementing/cementing_lens_registry_dispatch_test.rs"] +mod cementing_lens_registry_dispatch_test; #[path = "integration/four_fixture_regression_test.rs"] mod four_fixture_regression_test; #[path = "integration/l1_5_fixed_point_test.rs"] @@ -123,8 +125,6 @@ mod sg3_surface_reflection_consumer_test; mod sg6_hand_authored_census_test; #[path = "integration/sg7_prep_variant_payload_freshness_test.rs"] mod sg7_prep_variant_payload_freshness_test; -#[path = "integration/cementing/cementing_lens_registry_dispatch_test.rs"] -mod cementing_lens_registry_dispatch_test; #[path = "integration/thesis_parallelism_test.rs"] mod thesis_parallelism_test; #[path = "integration/thesis_validation_test.rs"] From 0d8f66f8ebff8a38ac09ec592c5555079da7b9ac Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 01:33:32 -0400 Subject: [PATCH 03/26] WIP: test generalization --- .../cementing_lens_registry_dispatch_test.rs | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs index 37580c921a3..4411e87119b 100644 --- a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs +++ b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs @@ -8,14 +8,10 @@ use std::path::PathBuf; -use v3_compiler::dag::{Dag, LiteralBits}; -use v3_compiler::diagnostics::SourceSpan; +use v3_compiler::compile_to_dag; +use v3_compiler::dag::Behavior; use v3_compiler::lens_provenance::{origin_of, Origin}; -fn span() -> SourceSpan { - SourceSpan::new("", 0, 0) -} - fn origin_label(origin: &Origin) -> &'static str { match origin { Origin::NoProducer => "NoProducer", @@ -38,18 +34,33 @@ fn origin_label(origin: &Origin) -> &'static str { /// `tests/integration.rs` in the same PR. const CEMENTING_MODULES_FOR_V2_COMPLETE_CLAIMS: &[(&str, &str)] = &[]; +fn find_bind_value_port(dag: &v3_compiler::dag::Dag, name: &str) -> v3_compiler::dag::PortId { + dag.nodes() + .iter() + .filter_map(Behavior::as_bind) + .find(|bind| bind.name == name) + .unwrap_or_else(|| panic!("bind `{name}` not found")) + .value +} + #[test] fn provenance_origin_of_cements_behavior_complete_row_on_minimal_ports() { // Register row: `provenance.dag` — BEHAVIORALLY COMPLETE, v3-native. - // Cementing here pins the public `Origin` classification contract the - // same way a future v2-oracle test would pin cross-implementation parity. - let mut dag = Dag::new(); - let int_shape = dag.int_shape().expect("bootstrap Int"); - let param = dag.alloc_port_with_shape(int_shape); - assert_eq!(origin_label(&origin_of(&dag, ¶m)), "NoProducer"); + // Integration crate cannot reach `Dag`’s `pub(crate)` builder helpers + // (`alloc_port_with_shape`); `compile_to_dag` fixtures still cement the + // shipped `origin_of` contract on the live lowering path. Exhaustive + // `NoProducer` / `Missing*` cases stay in `lib.rs::lens_provenance::tests`. + let dag = compile_to_dag("let lit: Int = 7", "cementing_provenance_lit.v3").expect("compiles"); + assert_eq!( + origin_label(&origin_of(&dag, &find_bind_value_port(&dag, "lit"))), + "Source" + ); - let literal = dag.push_value(LiteralBits::Int(7), span()); - assert_eq!(origin_label(&origin_of(&dag, &literal)), "Source"); + let dag = compile_to_dag("let sum: Int = 1 + 2", "cementing_provenance_sum.v3").expect("compiles"); + assert_eq!( + origin_label(&origin_of(&dag, &find_bind_value_port(&dag, "sum"))), + "Computed" + ); } #[test] From ebda567abb424390a795335cbab34b2d1df02d85 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 01:33:56 -0400 Subject: [PATCH 04/26] chore: apply cargo fmt --- .../cementing/cementing_lens_registry_dispatch_test.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs index 4411e87119b..544890a551e 100644 --- a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs +++ b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs @@ -56,7 +56,8 @@ fn provenance_origin_of_cements_behavior_complete_row_on_minimal_ports() { "Source" ); - let dag = compile_to_dag("let sum: Int = 1 + 2", "cementing_provenance_sum.v3").expect("compiles"); + let dag = + compile_to_dag("let sum: Int = 1 + 2", "cementing_provenance_sum.v3").expect("compiles"); assert_eq!( origin_label(&origin_of(&dag, &find_bind_value_port(&dag, "sum"))), "Computed" From d609bbc5ac4bcbc3c5242c0a9640bc30567160d7 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 01:37:20 -0400 Subject: [PATCH 05/26] WIP: test generalization --- src/v3/compiler/tests/integration/sg0_census_test.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/v3/compiler/tests/integration/sg0_census_test.rs b/src/v3/compiler/tests/integration/sg0_census_test.rs index cdf7c7fc330..f364748bfab 100644 --- a/src/v3/compiler/tests/integration/sg0_census_test.rs +++ b/src/v3/compiler/tests/integration/sg0_census_test.rs @@ -180,6 +180,7 @@ const EXPECTED_HAND_AUTHORED: &[&str] = &[ "src/v3/compiler/tests/boundary/m2_emit_multi_field_struct_variant_test.rs", "src/v3/compiler/tests/determinism_test.rs", "src/v3/compiler/tests/integration.rs", + "src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs", "src/v3/compiler/tests/integration/common/budgeted.rs", "src/v3/compiler/tests/integration/common/cached_compile.rs", "src/v3/compiler/tests/integration/common/determinism_fixtures.rs", From c98e93f59d3646dfd3a71c3f89193f22cc2544aa Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 01:41:43 -0400 Subject: [PATCH 06/26] WIP: test generalization --- .../cementing/cementing_lens_registry_dispatch_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs index 544890a551e..6c2440297f8 100644 --- a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs +++ b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs @@ -46,7 +46,7 @@ fn find_bind_value_port(dag: &v3_compiler::dag::Dag, name: &str) -> v3_compiler: #[test] fn provenance_origin_of_cements_behavior_complete_row_on_minimal_ports() { // Register row: `provenance.dag` — BEHAVIORALLY COMPLETE, v3-native. - // Integration crate cannot reach `Dag`’s `pub(crate)` builder helpers + // Integration crate cannot reach `Dag`'s `pub(crate)` builder helpers // (`alloc_port_with_shape`); `compile_to_dag` fixtures still cement the // shipped `origin_of` contract on the live lowering path. Exhaustive // `NoProducer` / `Missing*` cases stay in `lib.rs::lens_provenance::tests`. From 74204844b465c2dcc9cd1140b282ec8ce746586f Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 01:48:01 -0400 Subject: [PATCH 07/26] WIP: test generalization --- TESTING.md | 4 +- docs/v3-lens-capability-register.md | 2 +- src/v3/compiler/regen.dag | 5 ++- .../cementing_lens_registry_dispatch_test.rs | 38 +++++++++++++++++++ 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/TESTING.md b/TESTING.md index 5ef41a48786..629ab479d05 100644 --- a/TESTING.md +++ b/TESTING.md @@ -375,7 +375,9 @@ cementing module exists and `tests/integration.rs` wires it. Extend when the register row is upgraded; the test `cementing_test_modules_exist_for_escalated_v2_complete_registry_claims` requires each listed stem to resolve to an on-disk `.rs` file under -`cementing/`. +`cementing/` **and** to appear in `tests/integration.rs` behind an +exact `#[path = "integration/cementing/.rs"]` attribute (so an +orphan file cannot satisfy the ratchet). **Anti-scope.** This section is not a mandate to prove full lens equivalence for every `.dag` file, and it does not require new diff --git a/docs/v3-lens-capability-register.md b/docs/v3-lens-capability-register.md index 6f140a87820..a544fe955bf 100644 --- a/docs/v3-lens-capability-register.md +++ b/docs/v3-lens-capability-register.md @@ -78,7 +78,7 @@ The register is a tool, not a document. If it is stale, it is worse than not exi 3. **Review at every ROADMAP dispatch.** Before queueing a brief that depends on a lens being equivalent to its v2 counterpart (e.g., "compare v3 complexity output against v2 complexity output"), check the row. If the row says `PROXY` or `STUB`, the brief is premature and must either wait for substrate/emit work or narrow its scope to what the lens actually computes. 4. **Downgrades are first-class.** A lens that regresses from `COMPLETE` to `PROXY` (e.g., because a cementing test failed and was weakened rather than fixed) belongs in this register immediately. Silent downgrade is the failure mode this document exists to catch. 5. **The two axes do not trade off.** `STRUCTURALLY TERMINAL` + `BEHAVIORALLY PROXY` is a valid, honest state — a well-formed file computing a weaker function than its name implies. It is not a contradiction. Claiming `COMPLETE` because the file is structurally clean is the mistake this register is named against. -6. **Cementing tests have a canonical home in-tree.** In-repo dispatch (fixture layout, v2-oracle vs minimal-`Dag` contract tests, and the `regen.dag` → register → test-module ratchet) lives in `TESTING.md` under *Cementing tests (Band C — lens subsumption)* and in `src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs`. Promoting a row to `BEHAVIORALLY COMPLETE` with a non-`N/A` v2 counterpart without landing the cementing module in the same PR is a process failure, not a modeling disagreement. +6. **Cementing tests have a canonical home in-tree.** In-repo dispatch (fixture layout, v2-oracle vs minimal-`Dag` contract tests, and the `regen.dag` → register → test-module ratchet) lives in `TESTING.md` under *Cementing tests (Band C — lens subsumption)* and in `src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs`. The ratchet checks both an on-disk `cementing/.rs` and a matching `#[path = ...]` line in `tests/integration.rs` for each escalated claim. Promoting a row to `BEHAVIORALLY COMPLETE` with a non-`N/A` v2 counterpart without landing the cementing module in the same PR is a process failure, not a modeling disagreement. ## Related docs diff --git a/src/v3/compiler/regen.dag b/src/v3/compiler/regen.dag index 4b7bc291c1c..cf47ecf9ecf 100644 --- a/src/v3/compiler/regen.dag +++ b/src/v3/compiler/regen.dag @@ -29,8 +29,9 @@ import std.types { FilePath, NonEmptyStr } // update `docs/v3-lens-capability-register.md` (see its Discipline // section). If the register row is `BEHAVIORALLY COMPLETE` and names // a concrete v2 counterpart, add a matching behavioral cementing -// module under `src/v3/compiler/tests/integration/cementing/` and -// extend the ratchet slice in +// module under `src/v3/compiler/tests/integration/cementing/`, wire it +// in `tests/integration.rs` via `#[path = "integration/cementing/.rs"]`, +// and extend the ratchet slice in // `cementing_lens_registry_dispatch_test.rs` in the same PR. See // `TESTING.md` ("Cementing tests (Band C — lens subsumption)"). diff --git a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs index 6c2440297f8..7ad1c4d9f35 100644 --- a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs +++ b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs @@ -34,6 +34,32 @@ fn origin_label(origin: &Origin) -> &'static str { /// `tests/integration.rs` in the same PR. const CEMENTING_MODULES_FOR_V2_COMPLETE_CLAIMS: &[(&str, &str)] = &[]; +/// Ratchet owner module — kept wired so `cargo test … cementing` always +/// exercises this file; see `cementing_lens_registry_dispatch_test_is_wired_in_integration_rs`. +const CEMENTING_REGISTRY_DISPATCH_STEM: &str = "cementing_lens_registry_dispatch_test"; + +fn integration_rs_text() -> String { + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("integration.rs"); + std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display())) +} + +fn assert_cementing_stem_wired_in_integration_rs( + integration_rs: &str, + stem: &str, + registry_name: &str, +) { + let expected = format!(r#"#[path = "integration/cementing/{stem}.rs"]"#); + assert!( + integration_rs.contains(&expected), + "registry lens `{registry_name}` lists cementing stem `{stem}` but \ + `tests/integration.rs` does not contain `{expected}` — add a \ + `#[path = \"integration/cementing/{stem}.rs\"]` module declaration \ + in the same PR as the on-disk module (API-level wiring, not convention-only)." + ); +} + fn find_bind_value_port(dag: &v3_compiler::dag::Dag, name: &str) -> v3_compiler::dag::PortId { dag.nodes() .iter() @@ -64,12 +90,23 @@ fn provenance_origin_of_cements_behavior_complete_row_on_minimal_ports() { ); } +#[test] +fn cementing_lens_registry_dispatch_test_is_wired_in_integration_rs() { + let integration_rs = integration_rs_text(); + assert_cementing_stem_wired_in_integration_rs( + &integration_rs, + CEMENTING_REGISTRY_DISPATCH_STEM, + "", + ); +} + #[test] fn cementing_test_modules_exist_for_escalated_v2_complete_registry_claims() { let cementing_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("tests") .join("integration") .join("cementing"); + let integration_rs = integration_rs_text(); for (registry_name, stem) in CEMENTING_MODULES_FOR_V2_COMPLETE_CLAIMS { let path = cementing_dir.join(format!("{stem}.rs")); assert!( @@ -77,5 +114,6 @@ fn cementing_test_modules_exist_for_escalated_v2_complete_registry_claims() { "registry lens `{registry_name}` is listed for v2-complete cementing; expected cementing module at {}", path.display() ); + assert_cementing_stem_wired_in_integration_rs(&integration_rs, stem, registry_name); } } From 2187b3195602f75b47e825e935f69dfd30492dfc Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 02:06:16 -0400 Subject: [PATCH 08/26] WIP: test generalization --- src/v3/compiler/src/bootstrap_generated.rs | 18 +++++++++--------- ...tstrap_generated_without_runtime_mirrors.rs | 18 +++++++++--------- .../integration/parse_corpus_manifest.txt | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/v3/compiler/src/bootstrap_generated.rs b/src/v3/compiler/src/bootstrap_generated.rs index c9a184f4a47..993f33ab532 100644 --- a/src/v3/compiler/src/bootstrap_generated.rs +++ b/src/v3/compiler/src/bootstrap_generated.rs @@ -1037,15 +1037,15 @@ pub(crate) fn bootstrapped_fixture_dag() -> Dag { Declaration { id: DeclarationId(1029), name: Some("from_symbol".to_string()), connective: TypeConnective::Arrow { inputs: vec![DeclarationId(182)], output: DeclarationId(1400), body: ArrowBody::Unparsed(SourceSpan::new("src/v3/compiler/operators.dag", 156, 771)) }, type_params: vec![], meta_tag: None, inhabits: None, value_body: None, refinement: None, span: SourceSpan::new("src/v3/compiler/operators.dag", 108, 771) }, Declaration { id: DeclarationId(1030), name: Some("symbol".to_string()), connective: TypeConnective::Arrow { inputs: vec![DeclarationId(565)], output: DeclarationId(182), body: ArrowBody::UserDefined(NodeId(243)) }, type_params: vec![], meta_tag: None, inhabits: None, value_body: None, refinement: None, span: SourceSpan::new("src/v3/compiler/operators.dag", 773, 1214) }, Declaration { id: DeclarationId(1031), name: Some("algebra_field_name".to_string()), connective: TypeConnective::Arrow { inputs: vec![DeclarationId(565)], output: DeclarationId(182), body: ArrowBody::UserDefined(NodeId(260)) }, type_params: vec![], meta_tag: None, inhabits: None, value_body: None, refinement: None, span: SourceSpan::new("src/v3/compiler/operators.dag", 1216, 2017) }, - Declaration { id: DeclarationId(1032), name: Some("LensRegistryEntry".to_string()), connective: TypeConnective::Conj { children: vec![Field { label: "name".to_string(), ty: DeclarationId(107) }, Field { label: "lens_file".to_string(), ty: DeclarationId(116) }, Field { label: "generated_file".to_string(), ty: DeclarationId(116) }] }, type_params: vec![], meta_tag: None, inhabits: None, value_body: None, refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 1355, 1450) }, - Declaration { id: DeclarationId(1033), name: Some("lens_cost_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("cost".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/complexity.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lens_cost_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 1452, 1619) }, - Declaration { id: DeclarationId(1034), name: Some("lens_cost_symbolic_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("cost_symbolic".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/cost.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lens_cost_symbolic_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 1621, 1809) }, - Declaration { id: DeclarationId(1035), name: Some("lens_provenance_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("provenance".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/provenance.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lens_provenance_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 1811, 1996) }, - Declaration { id: DeclarationId(1036), name: Some("lens_structural_resolution_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("structural_resolution".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/structural_resolution.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lens_structural_resolution_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 1998, 2227) }, - Declaration { id: DeclarationId(1037), name: Some("lens_unused_parameters_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("unused_parameters".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/unused_parameters.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lens_unused_parameters_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 2229, 2442) }, - Declaration { id: DeclarationId(1038), name: Some("lens_infer_helpers_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("infer_helpers".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/infer_helpers.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/infer_helpers_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 3069, 3261) }, - Declaration { id: DeclarationId(1039), name: Some("lens_variant_payload_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("variant_payload".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/variant_payload.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/variant_payload_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 3263, 3463) }, - Declaration { id: DeclarationId(1040), name: Some("lens_lower_helpers_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("lower_helpers".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/lower_helpers.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lower_helpers_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 3713, 3905) }, + Declaration { id: DeclarationId(1032), name: Some("LensRegistryEntry".to_string()), connective: TypeConnective::Conj { children: vec![Field { label: "name".to_string(), ty: DeclarationId(107) }, Field { label: "lens_file".to_string(), ty: DeclarationId(116) }, Field { label: "generated_file".to_string(), ty: DeclarationId(116) }] }, type_params: vec![], meta_tag: None, inhabits: None, value_body: None, refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 1950, 2045) }, + Declaration { id: DeclarationId(1033), name: Some("lens_cost_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("cost".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/complexity.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lens_cost_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 2047, 2214) }, + Declaration { id: DeclarationId(1034), name: Some("lens_cost_symbolic_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("cost_symbolic".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/cost.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lens_cost_symbolic_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 2216, 2404) }, + Declaration { id: DeclarationId(1035), name: Some("lens_provenance_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("provenance".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/provenance.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lens_provenance_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 2406, 2591) }, + Declaration { id: DeclarationId(1036), name: Some("lens_structural_resolution_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("structural_resolution".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/structural_resolution.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lens_structural_resolution_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 2593, 2822) }, + Declaration { id: DeclarationId(1037), name: Some("lens_unused_parameters_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("unused_parameters".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/unused_parameters.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lens_unused_parameters_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 2824, 3037) }, + Declaration { id: DeclarationId(1038), name: Some("lens_infer_helpers_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("infer_helpers".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/infer_helpers.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/infer_helpers_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 3664, 3856) }, + Declaration { id: DeclarationId(1039), name: Some("lens_variant_payload_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("variant_payload".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/variant_payload.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/variant_payload_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 3858, 4058) }, + Declaration { id: DeclarationId(1040), name: Some("lens_lower_helpers_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("lower_helpers".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/lower_helpers.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lower_helpers_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 4308, 4500) }, Declaration { id: DeclarationId(1041), name: Some("DagDifference".to_string()), connective: TypeConnective::Conj { children: vec![Field { label: "detail".to_string(), ty: DeclarationId(182) }] }, type_params: vec![], meta_tag: None, inhabits: None, value_body: None, refinement: None, span: SourceSpan::new("src/v3/compiler/runtime_mirrors.dag", 115, 154) }, Declaration { id: DeclarationId(1042), name: Some("SurfaceModule".to_string()), connective: TypeConnective::Conj { children: vec![Field { label: "items".to_string(), ty: DeclarationId(1401) }] }, type_params: vec![], meta_tag: None, inhabits: None, value_body: None, refinement: None, span: SourceSpan::new("src/v3/compiler/runtime_mirrors.dag", 580, 629) }, Declaration { id: DeclarationId(1043), name: Some("SurfaceParam".to_string()), connective: TypeConnective::Conj { children: vec![Field { label: "name".to_string(), ty: DeclarationId(182) }, Field { label: "ty".to_string(), ty: DeclarationId(1047) }, Field { label: "refinement".to_string(), ty: DeclarationId(1402) }] }, type_params: vec![], meta_tag: None, inhabits: None, value_body: None, refinement: None, span: SourceSpan::new("src/v3/compiler/runtime_mirrors.dag", 631, 712) }, diff --git a/src/v3/compiler/src/bootstrap_generated_without_runtime_mirrors.rs b/src/v3/compiler/src/bootstrap_generated_without_runtime_mirrors.rs index 5416a6ffdd6..ac2a7fcc25e 100644 --- a/src/v3/compiler/src/bootstrap_generated_without_runtime_mirrors.rs +++ b/src/v3/compiler/src/bootstrap_generated_without_runtime_mirrors.rs @@ -1037,15 +1037,15 @@ pub(crate) fn bootstrapped_fixture_without_runtime_mirrors_dag() -> Dag { Declaration { id: DeclarationId(1029), name: Some("from_symbol".to_string()), connective: TypeConnective::Arrow { inputs: vec![DeclarationId(182)], output: DeclarationId(1386), body: ArrowBody::Unparsed(SourceSpan::new("src/v3/compiler/operators.dag", 156, 771)) }, type_params: vec![], meta_tag: None, inhabits: None, value_body: None, refinement: None, span: SourceSpan::new("src/v3/compiler/operators.dag", 108, 771) }, Declaration { id: DeclarationId(1030), name: Some("symbol".to_string()), connective: TypeConnective::Arrow { inputs: vec![DeclarationId(565)], output: DeclarationId(182), body: ArrowBody::UserDefined(NodeId(243)) }, type_params: vec![], meta_tag: None, inhabits: None, value_body: None, refinement: None, span: SourceSpan::new("src/v3/compiler/operators.dag", 773, 1214) }, Declaration { id: DeclarationId(1031), name: Some("algebra_field_name".to_string()), connective: TypeConnective::Arrow { inputs: vec![DeclarationId(565)], output: DeclarationId(182), body: ArrowBody::UserDefined(NodeId(260)) }, type_params: vec![], meta_tag: None, inhabits: None, value_body: None, refinement: None, span: SourceSpan::new("src/v3/compiler/operators.dag", 1216, 2017) }, - Declaration { id: DeclarationId(1032), name: Some("LensRegistryEntry".to_string()), connective: TypeConnective::Conj { children: vec![Field { label: "name".to_string(), ty: DeclarationId(107) }, Field { label: "lens_file".to_string(), ty: DeclarationId(116) }, Field { label: "generated_file".to_string(), ty: DeclarationId(116) }] }, type_params: vec![], meta_tag: None, inhabits: None, value_body: None, refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 1355, 1450) }, - Declaration { id: DeclarationId(1033), name: Some("lens_cost_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("cost".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/complexity.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lens_cost_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 1452, 1619) }, - Declaration { id: DeclarationId(1034), name: Some("lens_cost_symbolic_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("cost_symbolic".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/cost.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lens_cost_symbolic_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 1621, 1809) }, - Declaration { id: DeclarationId(1035), name: Some("lens_provenance_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("provenance".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/provenance.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lens_provenance_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 1811, 1996) }, - Declaration { id: DeclarationId(1036), name: Some("lens_structural_resolution_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("structural_resolution".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/structural_resolution.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lens_structural_resolution_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 1998, 2227) }, - Declaration { id: DeclarationId(1037), name: Some("lens_unused_parameters_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("unused_parameters".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/unused_parameters.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lens_unused_parameters_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 2229, 2442) }, - Declaration { id: DeclarationId(1038), name: Some("lens_infer_helpers_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("infer_helpers".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/infer_helpers.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/infer_helpers_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 3069, 3261) }, - Declaration { id: DeclarationId(1039), name: Some("lens_variant_payload_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("variant_payload".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/variant_payload.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/variant_payload_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 3263, 3463) }, - Declaration { id: DeclarationId(1040), name: Some("lens_lower_helpers_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("lower_helpers".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/lower_helpers.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lower_helpers_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 3713, 3905) }, + Declaration { id: DeclarationId(1032), name: Some("LensRegistryEntry".to_string()), connective: TypeConnective::Conj { children: vec![Field { label: "name".to_string(), ty: DeclarationId(107) }, Field { label: "lens_file".to_string(), ty: DeclarationId(116) }, Field { label: "generated_file".to_string(), ty: DeclarationId(116) }] }, type_params: vec![], meta_tag: None, inhabits: None, value_body: None, refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 1950, 2045) }, + Declaration { id: DeclarationId(1033), name: Some("lens_cost_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("cost".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/complexity.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lens_cost_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 2047, 2214) }, + Declaration { id: DeclarationId(1034), name: Some("lens_cost_symbolic_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("cost_symbolic".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/cost.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lens_cost_symbolic_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 2216, 2404) }, + Declaration { id: DeclarationId(1035), name: Some("lens_provenance_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("provenance".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/provenance.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lens_provenance_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 2406, 2591) }, + Declaration { id: DeclarationId(1036), name: Some("lens_structural_resolution_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("structural_resolution".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/structural_resolution.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lens_structural_resolution_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 2593, 2822) }, + Declaration { id: DeclarationId(1037), name: Some("lens_unused_parameters_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("unused_parameters".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/unused_parameters.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lens_unused_parameters_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 2824, 3037) }, + Declaration { id: DeclarationId(1038), name: Some("lens_infer_helpers_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("infer_helpers".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/infer_helpers.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/infer_helpers_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 3664, 3856) }, + Declaration { id: DeclarationId(1039), name: Some("lens_variant_payload_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("variant_payload".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/variant_payload.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/variant_payload_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 3858, 4058) }, + Declaration { id: DeclarationId(1040), name: Some("lens_lower_helpers_entry".to_string()), connective: TypeConnective::Instantiation { template: DeclarationId(1032), arguments: vec![] }, type_params: vec![], meta_tag: Some(DeclarationId(1032)), inhabits: None, value_body: Some(ValueBody::Structural { fields: vec![("name".to_string(), FieldValue::Literal(LiteralBits::String("lower_helpers".to_string()))), ("lens_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/lenses/lower_helpers.dag".to_string()))), ("generated_file".to_string(), FieldValue::Literal(LiteralBits::String("src/v3/compiler/src/lower_helpers_generated.rs".to_string())))] }), refinement: None, span: SourceSpan::new("src/v3/compiler/regen.dag", 4308, 4500) }, Declaration { id: DeclarationId(1041), name: None, connective: TypeConnective::Instantiation { template: DeclarationId(455), arguments: vec![TemplateArgument { parameter: DeclarationId(456), value: DeclarationId(458) }] }, type_params: vec![], meta_tag: None, inhabits: None, value_body: None, refinement: None, span: SourceSpan::new("src/v3/std/list.dag", 3259, 3272) }, Declaration { id: DeclarationId(1042), name: None, connective: TypeConnective::Instantiation { template: DeclarationId(455), arguments: vec![TemplateArgument { parameter: DeclarationId(456), value: DeclarationId(460) }] }, type_params: vec![], meta_tag: None, inhabits: None, value_body: None, refinement: None, span: SourceSpan::new("src/v3/std/list.dag", 3421, 3434) }, Declaration { id: DeclarationId(1043), name: None, connective: TypeConnective::Instantiation { template: DeclarationId(455), arguments: vec![TemplateArgument { parameter: DeclarationId(456), value: DeclarationId(460) }] }, type_params: vec![], meta_tag: None, inhabits: None, value_body: None, refinement: None, span: SourceSpan::new("src/v3/std/list.dag", 3439, 3452) }, diff --git a/src/v3/compiler/tests/integration/parse_corpus_manifest.txt b/src/v3/compiler/tests/integration/parse_corpus_manifest.txt index db2faa455ea..c3f60355270 100644 --- a/src/v3/compiler/tests/integration/parse_corpus_manifest.txt +++ b/src/v3/compiler/tests/integration/parse_corpus_manifest.txt @@ -11,7 +11,7 @@ dsl/std/types.dag 108 137815 42478679b0276003 src/v3/compiler/operators.dag 5 47160 b75a91c65bf8a242 src/v3/compiler/parse_tables.dag 29 69841 c2db27c9e6efa073 src/v3/compiler/pipeline.dag 26 47032 a13b41aa379fd167 -src/v3/compiler/regen.dag 11 30786 01fdfda0b031563d +src/v3/compiler/regen.dag 11 30786 49f47c59c626d776 src/v3/compiler/runtime_mirrors.dag 17 95346 ec2f9c233bfc9d26 src/v3/compiler/tests/four_fixture_pressure/drop.v3 2 2517 00953bc6b6d8c811 src/v3/compiler/tests/four_fixture_pressure/id.v3 2 2452 ec8b40edc3d3c017 From b58c37bebf12739cb4b22b0e66d66386cce56230 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 02:17:13 -0400 Subject: [PATCH 09/26] test: align cementing ratchet slice with capability register Cross-check CEMENTING_MODULES_FOR_V2_COMPLETE_CLAIMS against docs/v3-lens-capability-register.md (COMPLETE + non-v3-native v2) joined with regen.dag LensRegistryEntry lens_file basenames. Addresses api-review REQUEST_CHANGES on manual slice drift; wiring checks for #[path] remain. Made-with: Cursor --- TESTING.md | 6 +- docs/v3-lens-capability-register.md | 2 +- .../cementing_lens_registry_dispatch_test.rs | 169 +++++++++++++++++- 3 files changed, 170 insertions(+), 7 deletions(-) diff --git a/TESTING.md b/TESTING.md index 629ab479d05..a35a32d0ff9 100644 --- a/TESTING.md +++ b/TESTING.md @@ -373,8 +373,12 @@ cementing module exists and `tests/integration.rs` wires it. Extend `CEMENTING_MODULES_FOR_V2_COMPLETE_CLAIMS` in `tests/integration/cementing/cementing_lens_registry_dispatch_test.rs` when the register row is upgraded; the test +`cementing_escalation_slice_matches_capability_register` derives the +required registry `name` keys from `docs/v3-lens-capability-register.md` +plus `regen.dag` and asserts the slice matches **exactly** (the slice +is a projection, not a parallel inventory). The test `cementing_test_modules_exist_for_escalated_v2_complete_registry_claims` -requires each listed stem to resolve to an on-disk `.rs` file under +then requires each listed stem to resolve to an on-disk `.rs` file under `cementing/` **and** to appear in `tests/integration.rs` behind an exact `#[path = "integration/cementing/.rs"]` attribute (so an orphan file cannot satisfy the ratchet). diff --git a/docs/v3-lens-capability-register.md b/docs/v3-lens-capability-register.md index a544fe955bf..a261526a139 100644 --- a/docs/v3-lens-capability-register.md +++ b/docs/v3-lens-capability-register.md @@ -78,7 +78,7 @@ The register is a tool, not a document. If it is stale, it is worse than not exi 3. **Review at every ROADMAP dispatch.** Before queueing a brief that depends on a lens being equivalent to its v2 counterpart (e.g., "compare v3 complexity output against v2 complexity output"), check the row. If the row says `PROXY` or `STUB`, the brief is premature and must either wait for substrate/emit work or narrow its scope to what the lens actually computes. 4. **Downgrades are first-class.** A lens that regresses from `COMPLETE` to `PROXY` (e.g., because a cementing test failed and was weakened rather than fixed) belongs in this register immediately. Silent downgrade is the failure mode this document exists to catch. 5. **The two axes do not trade off.** `STRUCTURALLY TERMINAL` + `BEHAVIORALLY PROXY` is a valid, honest state — a well-formed file computing a weaker function than its name implies. It is not a contradiction. Claiming `COMPLETE` because the file is structurally clean is the mistake this register is named against. -6. **Cementing tests have a canonical home in-tree.** In-repo dispatch (fixture layout, v2-oracle vs minimal-`Dag` contract tests, and the `regen.dag` → register → test-module ratchet) lives in `TESTING.md` under *Cementing tests (Band C — lens subsumption)* and in `src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs`. The ratchet checks both an on-disk `cementing/.rs` and a matching `#[path = ...]` line in `tests/integration.rs` for each escalated claim. Promoting a row to `BEHAVIORALLY COMPLETE` with a non-`N/A` v2 counterpart without landing the cementing module in the same PR is a process failure, not a modeling disagreement. +6. **Cementing tests have a canonical home in-tree.** In-repo dispatch (fixture layout, v2-oracle vs minimal-`Dag` contract tests, and the `regen.dag` → register → test-module ratchet) lives in `TESTING.md` under *Cementing tests (Band C — lens subsumption)* and in `src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs`. `cementing_escalation_slice_matches_capability_register` mechanically aligns `CEMENTING_MODULES_FOR_V2_COMPLETE_CLAIMS` with this table plus `regen.dag` (the slice is a projection, not a second authority). The follow-on ratchet checks both an on-disk `cementing/.rs` and a matching `#[path = ...]` line in `tests/integration.rs` for each escalated claim. Promoting a row to `BEHAVIORALLY COMPLETE` with a non-`N/A` v2 counterpart without landing the cementing module in the same PR is a process failure, not a modeling disagreement. ## Related docs diff --git a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs index 7ad1c4d9f35..1cc2ca7ccdc 100644 --- a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs +++ b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs @@ -5,11 +5,18 @@ //! //! - `TESTING.md` — *Cementing tests (Band C — lens subsumption)* //! - `src/v3/compiler/regen.dag` — header comment on cementing dispatch +//! +//! **Authority cross-check.** `CEMENTING_MODULES_FOR_V2_COMPLETE_CLAIMS` is not a +//! second source of truth: `cementing_escalation_slice_matches_capability_register` +//! derives the required registry `name` keys from `docs/v3-lens-capability-register.md` +//! (capability table) plus `regen.dag` (`LensRegistryEntry` rows) and asserts the +//! slice matches exactly. -use std::path::PathBuf; +use std::collections::BTreeSet; +use std::path::{Path, PathBuf}; use v3_compiler::compile_to_dag; -use v3_compiler::dag::Behavior; +use v3_compiler::dag::{Behavior, Dag, Declaration, FieldValue, LiteralBits, ValueBody}; use v3_compiler::lens_provenance::{origin_of, Origin}; fn origin_label(origin: &Origin) -> &'static str { @@ -27,9 +34,10 @@ fn origin_label(origin: &Origin) -> &'static str { /// Pairs of (`regen_lens --lens ` registry key, cementing module stem /// under `tests/integration/cementing/` without `.rs`). /// -/// Append an entry when `docs/v3-lens-capability-register.md` promotes a -/// lens to `BEHAVIORALLY COMPLETE` **and** the v2 counterpart column names a -/// concrete v2 artifact (not `None (v3-native)` / not `N/A`). Land the new +/// **Must match register + regen mechanically** — see +/// `cementing_escalation_slice_matches_capability_register`. Append when the +/// capability table row is plain `COMPLETE` (not `**PROXY**` / `**STUB**` / `N/A`) +/// **and** the v2 counterpart cell is not `None (v3-native)`. Land the new /// `cementing/.rs` module and a `#[path = ...]` line in /// `tests/integration.rs` in the same PR. const CEMENTING_MODULES_FOR_V2_COMPLETE_CLAIMS: &[(&str, &str)] = &[]; @@ -38,6 +46,140 @@ const CEMENTING_MODULES_FOR_V2_COMPLETE_CLAIMS: &[(&str, &str)] = &[]; /// exercises this file; see `cementing_lens_registry_dispatch_test_is_wired_in_integration_rs`. const CEMENTING_REGISTRY_DISPATCH_STEM: &str = "cementing_lens_registry_dispatch_test"; +fn workspace_root() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(3) + .expect("expected src/v3/compiler -> workspace root") + .to_path_buf() +} + +fn md_table_cells(line: &str) -> Vec { + // Capability rows escape `|` inside cells as `\|` (see v3 output column). + let tmp = line.replace("\\|", "\u{241f}"); + tmp.split('|') + .map(|s| s.replace('\u{241f}', "|").trim().to_string()) + .collect() +} + +/// Lens basenames (e.g. `complexity.dag`) whose register row is `COMPLETE` with a +/// v2 counterpart beyond `None (v3-native)`. +fn lens_basenames_requiring_v2_cementing_from_register_md() -> BTreeSet { + let md_path = workspace_root().join("docs/v3-lens-capability-register.md"); + let md = std::fs::read_to_string(&md_path) + .unwrap_or_else(|e| panic!("read {}: {e}", md_path.display())); + let mut basenames = BTreeSet::new(); + for raw in md.lines() { + let line = raw.trim(); + if !line.starts_with('|') || line.starts_with("|---") || line.contains("---|---") { + continue; + } + let cells = md_table_cells(line); + if cells.len() < 5 { + continue; + } + let lens_cell = cells[1].trim(); + if lens_cell == "Lens" || !lens_cell.contains(".dag") { + continue; + } + let behavioral = cells[3].trim(); + let v2 = cells[4].trim(); + if behavioral != "COMPLETE" { + continue; + } + if v2.contains("None (v3-native)") { + continue; + } + let basename = lens_cell.trim_matches('`').trim(); + if !basename.ends_with(".dag") { + continue; + } + basenames.insert(basename.to_string()); + } + basenames +} + +fn structural_fields(decl: &Declaration) -> &[(String, FieldValue)] { + let Some(ValueBody::Structural { fields }) = &decl.value_body else { + panic!( + "lens registry entry `{}` must carry a structural value body", + decl.name.as_deref().unwrap_or("") + ); + }; + fields.as_slice() +} + +fn string_field(fields: &[(String, FieldValue)], label: &str, binding: &str) -> String { + fields + .iter() + .find(|(field_label, _)| field_label == label) + .and_then(|(_, value)| match value { + FieldValue::Literal(LiteralBits::String(s)) => Some(s.clone()), + _ => None, + }) + .unwrap_or_else(|| { + panic!("lens registry entry `{binding}` is missing a String `{label}` field") + }) +} + +fn read_lens_registry_name_lens_file_pairs(dag: &Dag) -> Vec<(String, String)> { + let entry_type_id = dag + .declaration_by_name("LensRegistryEntry") + .map(|decl| decl.id) + .expect("regen.dag must declare `LensRegistryEntry`"); + + dag.declarations() + .iter() + .filter(|decl| decl.meta_tag == Some(entry_type_id)) + .map(|decl| { + let binding = decl + .name + .clone() + .unwrap_or_else(|| "".to_string()); + let fields = structural_fields(decl); + ( + string_field(fields, "name", &binding), + string_field(fields, "lens_file", &binding), + ) + }) + .collect() +} + +/// `regen_lens --lens ` keys that must have cementing modules per the +/// capability register escalation rule. +fn registry_names_required_by_register_and_regen() -> BTreeSet { + let basenames = lens_basenames_requiring_v2_cementing_from_register_md(); + let dag = Dag::new(); + assert!( + dag.diagnostics().is_empty(), + "bootstrap should load `src/v3/compiler/regen.dag` cleanly, got {:?}", + dag.diagnostics().iter().collect::>() + ); + let rows = read_lens_registry_name_lens_file_pairs(&dag); + let mut names = BTreeSet::new(); + let mut matched_basenames = BTreeSet::new(); + for (name, lens_file) in rows { + let basename = Path::new(&lens_file) + .file_name() + .and_then(|s| s.to_str()) + .unwrap_or_else(|| { + panic!("registry entry `{name}` has lens_file without basename: {lens_file}") + }); + if basenames.contains(basename) { + names.insert(name); + matched_basenames.insert(basename.to_string()); + } + } + let missing: Vec<_> = basenames.difference(&matched_basenames).cloned().collect(); + assert!( + missing.is_empty(), + "docs/v3-lens-capability-register.md escalates v2 cementing for lens basenames {missing:?}, \ + but no `LensRegistryEntry` in src/v3/compiler/regen.dag names those files — \ + fix the register table or add registry entries." + ); + names +} + fn integration_rs_text() -> String { let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("tests") @@ -100,6 +242,23 @@ fn cementing_lens_registry_dispatch_test_is_wired_in_integration_rs() { ); } +#[test] +fn cementing_escalation_slice_matches_capability_register() { + let expected = registry_names_required_by_register_and_regen(); + let declared: BTreeSet<&str> = CEMENTING_MODULES_FOR_V2_COMPLETE_CLAIMS + .iter() + .map(|(name, _)| *name) + .collect(); + let expected_refs: BTreeSet<&str> = expected.iter().map(String::as_str).collect(); + assert_eq!( + declared, expected_refs, + "`CEMENTING_MODULES_FOR_V2_COMPLETE_CLAIMS` must list exactly the registry `name` keys \ + for rows where docs/v3-lens-capability-register.md marks `COMPLETE` with a v2 counterpart \ + other than `None (v3-native)` — update the slice (and cementing modules + `#[path]` wiring) \ + in the same PR as the register promotion." + ); +} + #[test] fn cementing_test_modules_exist_for_escalated_v2_complete_registry_claims() { let cementing_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")) From 51adfcd413f7f82ed43b90ea0980e4069d4bc7af Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 02:21:08 -0400 Subject: [PATCH 10/26] test: cement provenance Origin with NodeId witnesses Replace string-label collapse with structural checks against the value port's producing Behavior so wrong producer ids fail. Addresses PR #638 inline review (carrier shape / API-level contract). Made-with: Cursor --- .../cementing_lens_registry_dispatch_test.rs | 61 +++++++++++++------ 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs index 1cc2ca7ccdc..e85af38d320 100644 --- a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs +++ b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs @@ -19,15 +19,48 @@ use v3_compiler::compile_to_dag; use v3_compiler::dag::{Behavior, Dag, Declaration, FieldValue, LiteralBits, ValueBody}; use v3_compiler::lens_provenance::{origin_of, Origin}; -fn origin_label(origin: &Origin) -> &'static str { - match origin { - Origin::NoProducer => "NoProducer", - Origin::MissingPort => "MissingPort", - Origin::MissingBehavior => "MissingBehavior", - Origin::Source { .. } => "Source", - Origin::Computed { .. } => "Computed", - Origin::Selected { .. } => "Selected", - Origin::Accumulated { .. } => "Accumulated", +/// Assert `origin_of` returns the full published `Origin` carrier for the bind's +/// value port — variant **and** `NodeId` witness — not a collapsed string label. +fn assert_provenance_origin_matches_producer_witness(dag: &Dag, bind_name: &str, context: &str) { + let port = find_bind_value_port(dag, bind_name); + let got = origin_of(dag, &port); + let produced_by = dag + .port_opt(&port) + .unwrap_or_else(|| panic!("{context}: missing port for bind `{bind_name}`")) + .produced_by + .unwrap_or_else(|| { + panic!("{context}: bind `{bind_name}` value port has no producer (expected one)") + }); + let behavior = dag + .nodes() + .iter() + .find(|b| b.id() == produced_by) + .unwrap_or_else(|| panic!("{context}: missing producer node {produced_by:?}")); + + match behavior { + Behavior::Value(v) => match &got { + Origin::Source { _0 } => assert_eq!( + *_0, + v.id, + "{context}: Source origin must carry the Value producer's NodeId" + ), + other => panic!( + "{context}: literal-fed bind `{bind_name}` should classify as Source(value.id); got {other:?}" + ), + }, + Behavior::Transform(t) => match &got { + Origin::Computed { _0 } => assert_eq!( + *_0, + t.id, + "{context}: Computed origin must carry the Transform producer's NodeId" + ), + other => panic!( + "{context}: transform-fed bind `{bind_name}` should classify as Computed(transform.id); got {other:?}" + ), + }, + other => panic!( + "{context}: unexpected producer kind for bind `{bind_name}`: {other:?}" + ), } } @@ -219,17 +252,11 @@ fn provenance_origin_of_cements_behavior_complete_row_on_minimal_ports() { // shipped `origin_of` contract on the live lowering path. Exhaustive // `NoProducer` / `Missing*` cases stay in `lib.rs::lens_provenance::tests`. let dag = compile_to_dag("let lit: Int = 7", "cementing_provenance_lit.v3").expect("compiles"); - assert_eq!( - origin_label(&origin_of(&dag, &find_bind_value_port(&dag, "lit"))), - "Source" - ); + assert_provenance_origin_matches_producer_witness(&dag, "lit", "cementing_provenance_lit"); let dag = compile_to_dag("let sum: Int = 1 + 2", "cementing_provenance_sum.v3").expect("compiles"); - assert_eq!( - origin_label(&origin_of(&dag, &find_bind_value_port(&dag, "sum"))), - "Computed" - ); + assert_provenance_origin_matches_producer_witness(&dag, "sum", "cementing_provenance_sum"); } #[test] From 3153543a93f920847580b53effb36ead7f6d48c7 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 02:22:50 -0400 Subject: [PATCH 11/26] test: cement provenance Origin against origin_for_behavior authority Re-export origin_for_behavior and assert full enum equality (all variants) vs origin_of on compile fixtures so the harness tracks provenance.dag, not a duplicated projection. Addresses PR #638 blocking review on full Origin carrier contract. Made-with: Cursor --- src/v3/compiler/src/lib.rs | 2 +- .../cementing_lens_registry_dispatch_test.rs | 63 +++++++++---------- 2 files changed, 32 insertions(+), 33 deletions(-) diff --git a/src/v3/compiler/src/lib.rs b/src/v3/compiler/src/lib.rs index c6662c3cded..3ec7a119a6d 100644 --- a/src/v3/compiler/src/lib.rs +++ b/src/v3/compiler/src/lib.rs @@ -380,7 +380,7 @@ pub mod lens_provenance { include!("lens_provenance_generated.rs"); } - pub use generated::{origin_of, Origin}; + pub use generated::{origin_for_behavior, origin_of, Origin}; #[cfg(test)] mod tests { diff --git a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs index e85af38d320..ce875281c09 100644 --- a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs +++ b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs @@ -17,11 +17,34 @@ use std::path::{Path, PathBuf}; use v3_compiler::compile_to_dag; use v3_compiler::dag::{Behavior, Dag, Declaration, FieldValue, LiteralBits, ValueBody}; -use v3_compiler::lens_provenance::{origin_of, Origin}; +use v3_compiler::lens_provenance::{origin_for_behavior, origin_of, Origin}; -/// Assert `origin_of` returns the full published `Origin` carrier for the bind's -/// value port — variant **and** `NodeId` witness — not a collapsed string label. -fn assert_provenance_origin_matches_producer_witness(dag: &Dag, bind_name: &str, context: &str) { +/// Structural equality for the full published `Origin` carrier (every variant). +/// `Origin` is generated without `PartialEq`; this is the integration-test oracle. +fn assert_origin_carriers_equal(a: &Origin, b: &Origin, context: &str) { + match (a, b) { + (Origin::NoProducer, Origin::NoProducer) => {} + (Origin::MissingPort, Origin::MissingPort) => {} + (Origin::MissingBehavior, Origin::MissingBehavior) => {} + (Origin::Source { _0: x }, Origin::Source { _0: y }) => { + assert_eq!(x, y, "{context}: Source NodeId mismatch") + } + (Origin::Computed { _0: x }, Origin::Computed { _0: y }) => { + assert_eq!(x, y, "{context}: Computed NodeId mismatch") + } + (Origin::Selected { _0: x }, Origin::Selected { _0: y }) => { + assert_eq!(x, y, "{context}: Selected NodeId mismatch") + } + (Origin::Accumulated { _0: x }, Origin::Accumulated { _0: y }) => { + assert_eq!(x, y, "{context}: Accumulated NodeId mismatch") + } + _ => panic!("{context}: full Origin carrier mismatch\n got: {a:?}\n exp: {b:?}"), + } +} + +/// `origin_of` must agree with `origin_for_behavior` on the value port's producer — +/// the same decomposition `provenance.dag` uses after the `produced_by` walk. +fn assert_provenance_origin_matches_lens_authority(dag: &Dag, bind_name: &str, context: &str) { let port = find_bind_value_port(dag, bind_name); let got = origin_of(dag, &port); let produced_by = dag @@ -36,32 +59,8 @@ fn assert_provenance_origin_matches_producer_witness(dag: &Dag, bind_name: &str, .iter() .find(|b| b.id() == produced_by) .unwrap_or_else(|| panic!("{context}: missing producer node {produced_by:?}")); - - match behavior { - Behavior::Value(v) => match &got { - Origin::Source { _0 } => assert_eq!( - *_0, - v.id, - "{context}: Source origin must carry the Value producer's NodeId" - ), - other => panic!( - "{context}: literal-fed bind `{bind_name}` should classify as Source(value.id); got {other:?}" - ), - }, - Behavior::Transform(t) => match &got { - Origin::Computed { _0 } => assert_eq!( - *_0, - t.id, - "{context}: Computed origin must carry the Transform producer's NodeId" - ), - other => panic!( - "{context}: transform-fed bind `{bind_name}` should classify as Computed(transform.id); got {other:?}" - ), - }, - other => panic!( - "{context}: unexpected producer kind for bind `{bind_name}`: {other:?}" - ), - } + let expected = origin_for_behavior(behavior); + assert_origin_carriers_equal(&got, &expected, context); } /// Pairs of (`regen_lens --lens ` registry key, cementing module stem @@ -252,11 +251,11 @@ fn provenance_origin_of_cements_behavior_complete_row_on_minimal_ports() { // shipped `origin_of` contract on the live lowering path. Exhaustive // `NoProducer` / `Missing*` cases stay in `lib.rs::lens_provenance::tests`. let dag = compile_to_dag("let lit: Int = 7", "cementing_provenance_lit.v3").expect("compiles"); - assert_provenance_origin_matches_producer_witness(&dag, "lit", "cementing_provenance_lit"); + assert_provenance_origin_matches_lens_authority(&dag, "lit", "cementing_provenance_lit"); let dag = compile_to_dag("let sum: Int = 1 + 2", "cementing_provenance_sum.v3").expect("compiles"); - assert_provenance_origin_matches_producer_witness(&dag, "sum", "cementing_provenance_sum"); + assert_provenance_origin_matches_lens_authority(&dag, "sum", "cementing_provenance_sum"); } #[test] From dad52a3c4d6236318cd0bb5e4611369c77f98a1a Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 02:23:56 -0400 Subject: [PATCH 12/26] chore: rename provenance cementing test to match compile fixture TESTING.md naming: the test uses compile_to_dag, not minimal Dag ports. Made-with: Cursor --- .../cementing/cementing_lens_registry_dispatch_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs index ce875281c09..cb87319d0d2 100644 --- a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs +++ b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs @@ -244,7 +244,7 @@ fn find_bind_value_port(dag: &v3_compiler::dag::Dag, name: &str) -> v3_compiler: } #[test] -fn provenance_origin_of_cements_behavior_complete_row_on_minimal_ports() { +fn provenance_origin_of_cements_complete_row_via_compile_to_dag_fixture() { // Register row: `provenance.dag` — BEHAVIORALLY COMPLETE, v3-native. // Integration crate cannot reach `Dag`'s `pub(crate)` builder helpers // (`alloc_port_with_shape`); `compile_to_dag` fixtures still cement the From 2d12c445adc0555bb18aa87d8261a3e92d39490d Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 02:37:04 -0400 Subject: [PATCH 13/26] docs: clarify provenance cementing seam vs unit-test oracle Codex APPROVE_WITH_COMMENTS: origin_for_behavior is same artifact as origin_of; document split with lib.rs minimal-Dag mapping pins. Made-with: Cursor --- .../cementing_lens_registry_dispatch_test.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs index cb87319d0d2..87be3aa97bc 100644 --- a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs +++ b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs @@ -11,6 +11,14 @@ //! derives the required registry `name` keys from `docs/v3-lens-capability-register.md` //! (capability table) plus `regen.dag` (`LensRegistryEntry` rows) and asserts the //! slice matches exactly. +//! +//! **Provenance cementing split.** The `compile_to_dag` exemplar below compares +//! `origin_of` to `origin_for_behavior` on the resolved producer — both are emitted +//! from the same `provenance.dag`, so this is a **seam** check (`port` → +//! `produced_by` → `node` → classification) on the live lowering path, not an +//! independent second oracle for the `Behavior → Origin` table. That table is +//! already pinned under `#[cfg(test)]` in `lib.rs::lens_provenance::tests` with +//! minimal hand-built `Dag` shapes (see `TESTING.md` — v3-native `COMPLETE` path). use std::collections::BTreeSet; use std::path::{Path, PathBuf}; @@ -43,7 +51,11 @@ fn assert_origin_carriers_equal(a: &Origin, b: &Origin, context: &str) { } /// `origin_of` must agree with `origin_for_behavior` on the value port's producer — -/// the same decomposition `provenance.dag` uses after the `produced_by` walk. +/// the decomposition `provenance.dag` documents after the `produced_by` walk. +/// +/// This does **not** independently re-prove the `Behavior → Origin` mapping; a +/// coordinated regression in both emitted functions could still pass. The +/// contract for that mapping lives in `lens_provenance::tests` (minimal `Dag`). fn assert_provenance_origin_matches_lens_authority(dag: &Dag, bind_name: &str, context: &str) { let port = find_bind_value_port(dag, bind_name); let got = origin_of(dag, &port); @@ -248,8 +260,9 @@ fn provenance_origin_of_cements_complete_row_via_compile_to_dag_fixture() { // Register row: `provenance.dag` — BEHAVIORALLY COMPLETE, v3-native. // Integration crate cannot reach `Dag`'s `pub(crate)` builder helpers // (`alloc_port_with_shape`); `compile_to_dag` fixtures still cement the - // shipped `origin_of` contract on the live lowering path. Exhaustive - // `NoProducer` / `Missing*` cases stay in `lib.rs::lens_provenance::tests`. + // shipped `origin_of` **walk + glue** on the live lowering path. Mapping pins + // (Value/Transform/Branch/Loop/Bind → `Origin`) stay in-crate unit tests. + // Exhaustive `NoProducer` / `Missing*` cases stay in `lib.rs::lens_provenance::tests`. let dag = compile_to_dag("let lit: Int = 7", "cementing_provenance_lit.v3").expect("compiles"); assert_provenance_origin_matches_lens_authority(&dag, "lit", "cementing_provenance_lit"); From 3a3628fb64542fd779117158fd22d519d61a43f2 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 02:51:52 -0400 Subject: [PATCH 14/26] WIP: test generalization --- src/v3/compiler/src/lib.rs | 2 +- .../cementing_lens_registry_dispatch_test.rs | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/v3/compiler/src/lib.rs b/src/v3/compiler/src/lib.rs index 3ec7a119a6d..c6662c3cded 100644 --- a/src/v3/compiler/src/lib.rs +++ b/src/v3/compiler/src/lib.rs @@ -380,7 +380,7 @@ pub mod lens_provenance { include!("lens_provenance_generated.rs"); } - pub use generated::{origin_for_behavior, origin_of, Origin}; + pub use generated::{origin_of, Origin}; #[cfg(test)] mod tests { diff --git a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs index 87be3aa97bc..99ec0d81bbb 100644 --- a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs +++ b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs @@ -13,19 +13,21 @@ //! slice matches exactly. //! //! **Provenance cementing split.** The `compile_to_dag` exemplar below compares -//! `origin_of` to `origin_for_behavior` on the resolved producer — both are emitted -//! from the same `provenance.dag`, so this is a **seam** check (`port` → -//! `produced_by` → `node` → classification) on the live lowering path, not an -//! independent second oracle for the `Behavior → Origin` table. That table is -//! already pinned under `#[cfg(test)]` in `lib.rs::lens_provenance::tests` with -//! minimal hand-built `Dag` shapes (see `TESTING.md` — v3-native `COMPLETE` path). +//! `origin_of` to a **local** five-way `Behavior → Origin` projection that mirrors +//! `provenance.dag`’s emitted `origin_for_behavior` (kept crate-private). That keeps +//! the public `lens_provenance` surface to `origin_of` / `Origin` only. This is a +//! **seam** check (`port` → `produced_by` → `node` → classification) on the live +//! lowering path; the `Behavior → Origin` table is also pinned under +//! `#[cfg(test)]` in `lib.rs::lens_provenance::tests` with minimal hand-built `Dag` +//! shapes (see `TESTING.md` — v3-native `COMPLETE` path). Regenerate / update the +//! local mirror when `provenance.dag` changes that table. use std::collections::BTreeSet; use std::path::{Path, PathBuf}; use v3_compiler::compile_to_dag; use v3_compiler::dag::{Behavior, Dag, Declaration, FieldValue, LiteralBits, ValueBody}; -use v3_compiler::lens_provenance::{origin_for_behavior, origin_of, Origin}; +use v3_compiler::lens_provenance::{origin_of, Origin}; /// Structural equality for the full published `Origin` carrier (every variant). /// `Origin` is generated without `PartialEq`; this is the integration-test oracle. From 816b20e9fc1c6cd57305dab753a909ef186c6be9 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 02:52:42 -0400 Subject: [PATCH 15/26] fix: keep origin_for_behavior crate-private for cementing test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove public re-export; duplicate the five-way Behavior→Origin table in the integration test with an explicit regen-sync note. Addresses PR #638 blocking review (API surface / TESTING.md boundary). Made-with: Cursor --- .../cementing_lens_registry_dispatch_test.rs | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs index 99ec0d81bbb..3ad38e205d8 100644 --- a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs +++ b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs @@ -52,12 +52,13 @@ fn assert_origin_carriers_equal(a: &Origin, b: &Origin, context: &str) { } } -/// `origin_of` must agree with `origin_for_behavior` on the value port's producer — -/// the decomposition `provenance.dag` documents after the `produced_by` walk. +/// `origin_of` must agree with the provenance lens’s `Behavior → Origin` table on +/// the value port's producer after the `produced_by` walk. /// -/// This does **not** independently re-prove the `Behavior → Origin` mapping; a -/// coordinated regression in both emitted functions could still pass. The -/// contract for that mapping lives in `lens_provenance::tests` (minimal `Dag`). +/// The table is duplicated here (not re-exported from the crate) so integration +/// tests do not widen the public API with `origin_for_behavior`. Keep this match in +/// lockstep with `src/v3/lenses/provenance.dag` / `lens_provenance_generated.rs`. +/// Independent minimal-`Dag` pins live in `lens_provenance::tests`. fn assert_provenance_origin_matches_lens_authority(dag: &Dag, bind_name: &str, context: &str) { let port = find_bind_value_port(dag, bind_name); let got = origin_of(dag, &port); @@ -73,10 +74,21 @@ fn assert_provenance_origin_matches_lens_authority(dag: &Dag, bind_name: &str, c .iter() .find(|b| b.id() == produced_by) .unwrap_or_else(|| panic!("{context}: missing producer node {produced_by:?}")); - let expected = origin_for_behavior(behavior); + let expected = expected_origin_from_producer_behavior(behavior); assert_origin_carriers_equal(&got, &expected, context); } +/// Mirrors `origin_for_behavior` from `provenance.dag` (see `lens_provenance_generated.rs`). +fn expected_origin_from_producer_behavior(behavior: &Behavior) -> Origin { + match behavior { + Behavior::Value(v) => Origin::Source { _0: v.id }, + Behavior::Transform(t) => Origin::Computed { _0: t.id }, + Behavior::Branch(b) => Origin::Selected { _0: b.id }, + Behavior::Loop(l) => Origin::Accumulated { _0: l.id }, + Behavior::Bind(bind) => Origin::Source { _0: bind.id }, + } +} + /// Pairs of (`regen_lens --lens ` registry key, cementing module stem /// under `tests/integration/cementing/` without `.rs`). /// @@ -262,8 +274,9 @@ fn provenance_origin_of_cements_complete_row_via_compile_to_dag_fixture() { // Register row: `provenance.dag` — BEHAVIORALLY COMPLETE, v3-native. // Integration crate cannot reach `Dag`'s `pub(crate)` builder helpers // (`alloc_port_with_shape`); `compile_to_dag` fixtures still cement the - // shipped `origin_of` **walk + glue** on the live lowering path. Mapping pins - // (Value/Transform/Branch/Loop/Bind → `Origin`) stay in-crate unit tests. + // shipped `origin_of` **walk + glue** on the live lowering path. The five-way + // `Behavior → Origin` mirror in this file stays in sync with `provenance.dag`; + // richer mapping pins stay in-crate unit tests. // Exhaustive `NoProducer` / `Missing*` cases stay in `lib.rs::lens_provenance::tests`. let dag = compile_to_dag("let lit: Int = 7", "cementing_provenance_lit.v3").expect("compiles"); assert_provenance_origin_matches_lens_authority(&dag, "lit", "cementing_provenance_lit"); From 8fa3bce8c4e953e922dabc009aa46e5faea59cc0 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 03:05:35 -0400 Subject: [PATCH 16/26] test(v3): enforce cementing dispatch #[path] from SG-6 census The in-module wiring ratchet could not run if integration.rs dropped the mod line. Move the integration.rs #[path] + mod assertion into sg6_hand_authored_census_test so CI always exercises it. Made-with: Cursor --- .../cementing_lens_registry_dispatch_test.rs | 14 ----------- .../sg6_hand_authored_census_test.rs | 23 +++++++++++++++++++ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs index 3ad38e205d8..a7815db615f 100644 --- a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs +++ b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs @@ -100,10 +100,6 @@ fn expected_origin_from_producer_behavior(behavior: &Behavior) -> Origin { /// `tests/integration.rs` in the same PR. const CEMENTING_MODULES_FOR_V2_COMPLETE_CLAIMS: &[(&str, &str)] = &[]; -/// Ratchet owner module — kept wired so `cargo test … cementing` always -/// exercises this file; see `cementing_lens_registry_dispatch_test_is_wired_in_integration_rs`. -const CEMENTING_REGISTRY_DISPATCH_STEM: &str = "cementing_lens_registry_dispatch_test"; - fn workspace_root() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) .ancestors() @@ -286,16 +282,6 @@ fn provenance_origin_of_cements_complete_row_via_compile_to_dag_fixture() { assert_provenance_origin_matches_lens_authority(&dag, "sum", "cementing_provenance_sum"); } -#[test] -fn cementing_lens_registry_dispatch_test_is_wired_in_integration_rs() { - let integration_rs = integration_rs_text(); - assert_cementing_stem_wired_in_integration_rs( - &integration_rs, - CEMENTING_REGISTRY_DISPATCH_STEM, - "", - ); -} - #[test] fn cementing_escalation_slice_matches_capability_register() { let expected = registry_names_required_by_register_and_regen(); diff --git a/src/v3/compiler/tests/integration/sg6_hand_authored_census_test.rs b/src/v3/compiler/tests/integration/sg6_hand_authored_census_test.rs index 696e69ea750..076b8bf5eb2 100644 --- a/src/v3/compiler/tests/integration/sg6_hand_authored_census_test.rs +++ b/src/v3/compiler/tests/integration/sg6_hand_authored_census_test.rs @@ -53,6 +53,7 @@ // `lens_path()` helpers, or the `include_str!` targets diverge. use std::collections::{BTreeSet, HashMap}; +use std::fs; use std::io::Write; use std::path::PathBuf; use std::process::{Command, Stdio}; @@ -455,6 +456,28 @@ fn sg6_lens_registry_names_resolve_to_singleton_entry() { } } +// Band-C cementing: `cementing_lens_registry_dispatch_test.rs` cannot assert its own +// `#[path = ...]` wiring from inside the module — if the `mod` line is deleted, the +// whole subtree is dropped before any in-module test runs. SG-6 owns integration +// harness glue checks; this ratchet lives here so CI stays honest. +#[test] +fn sg6_cementing_lens_registry_dispatch_module_is_wired_in_integration_rs() { + let path = manifest_dir().join("tests").join("integration.rs"); + let text = fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display())); + let attr = r#"#[path = "integration/cementing/cementing_lens_registry_dispatch_test.rs"]"#; + assert!( + text.contains(attr), + "{} must declare `{attr}` so `cementing_lens_registry_dispatch_test` is linked; \ + otherwise its ratchet tests never run and CI gives a false green.", + path.display(), + ); + assert!( + text.contains("mod cementing_lens_registry_dispatch_test"), + "{} must declare `mod cementing_lens_registry_dispatch_test` after the #[path] attribute", + path.display(), + ); +} + #[test] fn sg6_infer_helpers_generated_module_matches_checked_in_snapshot() { let (_dag, rows) = load_registry(); From 0f9c9c6ea3e73aca03273968d08fc8893941bb24 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 03:07:34 -0400 Subject: [PATCH 17/26] test(v3): pin md_table_cells for capability register rows Addresses exploratory review feedback: the register ratchet treats markdown as data; lock \| escaping and column indices with a focused unit test. Made-with: Cursor --- .../cementing_lens_registry_dispatch_test.rs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs index a7815db615f..c986e788b18 100644 --- a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs +++ b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs @@ -116,6 +116,26 @@ fn md_table_cells(line: &str) -> Vec { .collect() } +#[test] +fn md_table_cells_preserves_escaped_pipes_for_register_capability_rows() { + // Pin the markdown-as-data split: cementing escalation reads + // `docs/v3-lens-capability-register.md` through this helper (see review on #638). + let row = "| a.dag | TERMINAL | COMPLETE | v2 path | FoundCost(Int) \\| MissingCost | note |"; + let cells = md_table_cells(row); + assert!( + cells.len() >= 5, + "capability ratchet expects Lens, Structural, Behavioral, v2, … columns; got {cells:?}" + ); + assert_eq!(cells[1].trim(), "a.dag"); + assert_eq!(cells[3].trim(), "COMPLETE"); + assert_eq!(cells[4].trim(), "v2 path"); + assert_eq!( + cells[5].trim(), + "FoundCost(Int) | MissingCost", + "cell-internal `|` must survive `\\|` escaping (v3 output column)" + ); +} + /// Lens basenames (e.g. `complexity.dag`) whose register row is `COMPLETE` with a /// v2 counterpart beyond `None (v3-native)`. fn lens_basenames_requiring_v2_cementing_from_register_md() -> BTreeSet { From eb2ff4a577647f2a943ccd0f21f2bc2f32d49216 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 03:23:36 -0400 Subject: [PATCH 18/26] fix(v3): align v2-cementing register filter with TESTING.md Treat v2 counterpart cell N/A like None (v3-native) so COMPLETE rows without a real v2 path do not trip the v2 parity module ratchet. Adds register_row_has_real_v2_counterpart + unit pin. Made-with: Cursor --- .../cementing_lens_registry_dispatch_test.rs | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs index c986e788b18..66b6525dac9 100644 --- a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs +++ b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs @@ -95,7 +95,8 @@ fn expected_origin_from_producer_behavior(behavior: &Behavior) -> Origin { /// **Must match register + regen mechanically** — see /// `cementing_escalation_slice_matches_capability_register`. Append when the /// capability table row is plain `COMPLETE` (not `**PROXY**` / `**STUB**` / `N/A`) -/// **and** the v2 counterpart cell is not `None (v3-native)`. Land the new +/// **and** the v2 counterpart cell names a real v2 path (not `None (v3-native)` / +/// not `N/A` per `TESTING.md` Band-C). Land the new /// `cementing/.rs` module and a `#[path = ...]` line in /// `tests/integration.rs` in the same PR. const CEMENTING_MODULES_FOR_V2_COMPLETE_CLAIMS: &[(&str, &str)] = &[]; @@ -116,6 +117,27 @@ fn md_table_cells(line: &str) -> Vec { .collect() } +/// Band-C register column: v2 counterpart exists for cross-implementation cementing. +/// Must match `TESTING.md` (Cementing tests): treat `None (v3-native)` and `N/A` +/// as absent — same policy as prose, not a second authority. +fn register_row_has_real_v2_counterpart(v2_cell: &str) -> bool { + let v2 = v2_cell.trim().trim_matches('`'); + !(v2.contains("None (v3-native)") || v2 == "N/A") +} + +#[test] +fn register_row_has_real_v2_counterpart_matches_testing_md_band_c() { + assert!(!register_row_has_real_v2_counterpart("None (v3-native)")); + assert!(!register_row_has_real_v2_counterpart( + " None (v3-native) " + )); + assert!(!register_row_has_real_v2_counterpart("N/A")); + assert!(!register_row_has_real_v2_counterpart(" `N/A` ")); + assert!(register_row_has_real_v2_counterpart( + "src/v2/complexity.dag (5488L)" + )); +} + #[test] fn md_table_cells_preserves_escaped_pipes_for_register_capability_rows() { // Pin the markdown-as-data split: cementing escalation reads @@ -137,7 +159,7 @@ fn md_table_cells_preserves_escaped_pipes_for_register_capability_rows() { } /// Lens basenames (e.g. `complexity.dag`) whose register row is `COMPLETE` with a -/// v2 counterpart beyond `None (v3-native)`. +/// real v2 counterpart (excludes `None (v3-native)` and `N/A` — `TESTING.md`). fn lens_basenames_requiring_v2_cementing_from_register_md() -> BTreeSet { let md_path = workspace_root().join("docs/v3-lens-capability-register.md"); let md = std::fs::read_to_string(&md_path) @@ -161,7 +183,7 @@ fn lens_basenames_requiring_v2_cementing_from_register_md() -> BTreeSet if behavioral != "COMPLETE" { continue; } - if v2.contains("None (v3-native)") { + if !register_row_has_real_v2_counterpart(v2) { continue; } let basename = lens_cell.trim_matches('`').trim(); @@ -313,9 +335,9 @@ fn cementing_escalation_slice_matches_capability_register() { assert_eq!( declared, expected_refs, "`CEMENTING_MODULES_FOR_V2_COMPLETE_CLAIMS` must list exactly the registry `name` keys \ - for rows where docs/v3-lens-capability-register.md marks `COMPLETE` with a v2 counterpart \ - other than `None (v3-native)` — update the slice (and cementing modules + `#[path]` wiring) \ - in the same PR as the register promotion." + for rows where docs/v3-lens-capability-register.md marks `COMPLETE` with a real v2 \ + counterpart (not `None (v3-native)` / not `N/A` per `TESTING.md`) — update the slice \ + (and cementing modules + `#[path]` wiring) in the same PR as the register promotion." ); } From 1884fb6e81d4c79c8cd09edfea1de6d4169119dc Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 08:07:47 -0400 Subject: [PATCH 19/26] WIP: test generalization --- .../cementing_lens_registry_dispatch_test.rs | 1 + .../compiler/tests/integration/common/mod.rs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs index 66b6525dac9..827e3cacf9c 100644 --- a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs +++ b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs @@ -25,6 +25,7 @@ use std::collections::BTreeSet; use std::path::{Path, PathBuf}; +use crate::common::integration_rs_active_line_contains; use v3_compiler::compile_to_dag; use v3_compiler::dag::{Behavior, Dag, Declaration, FieldValue, LiteralBits, ValueBody}; use v3_compiler::lens_provenance::{origin_of, Origin}; diff --git a/src/v3/compiler/tests/integration/common/mod.rs b/src/v3/compiler/tests/integration/common/mod.rs index a287820c6d7..1f0ba223703 100644 --- a/src/v3/compiler/tests/integration/common/mod.rs +++ b/src/v3/compiler/tests/integration/common/mod.rs @@ -75,6 +75,24 @@ pub fn find_current_rlib(crate_name: &str) -> PathBuf { .expect("compiled rlib for current crate") } +/// True when `line` is not a whole-line outer doc / line comment in Rust source. +/// +/// Band-C wiring ratchets scan `tests/integration.rs` for `#[path = …]` / `mod …;` +/// declarations. A raw substring match false-greens on commented-out copies +/// (`// #[path = …]`). Ignore any line whose first non-whitespace token starts +/// a line comment (`//`, `///`, `//!`). +pub fn integration_rs_line_is_active_for_wiring_check(line: &str) -> bool { + let s = line.trim_start(); + !(s.is_empty() || s.starts_with("//")) +} + +/// True when some **active** (non-`//`-leading) line in `integration_rs` contains `needle`. +pub fn integration_rs_active_line_contains(integration_rs: &str, needle: &str) -> bool { + integration_rs.lines().any(|line| { + integration_rs_line_is_active_for_wiring_check(line) && line.contains(needle) + }) +} + /// Harness for spawning rustc against generated-Rust harnesses. /// Holds the per-test-binary scratch root and a counter so parallel /// tests within the same binary get distinct temp dirs. From 0b9bf1ef8d1adf1efdcb0c9cdc3145d6bf1fee62 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 08:08:29 -0400 Subject: [PATCH 20/26] fix(v3): harden integration.rs wiring ratchets against commented lines Raw substring matches on tests/integration.rs could false-green when #[path] / mod declarations were only present in // comments. Scan only active lines (trimmed line does not start with //) and require an active mod line for each cementing stem. Shared helper lives in tests/integration/common; pin with a small cementing-module test. Made-with: Cursor --- .../cementing_lens_registry_dispatch_test.rs | 32 ++++++++++++++++--- .../compiler/tests/integration/common/mod.rs | 8 ++--- .../sg6_hand_authored_census_test.rs | 12 ++++--- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs index 827e3cacf9c..e259a052b5f 100644 --- a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs +++ b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs @@ -139,6 +139,24 @@ fn register_row_has_real_v2_counterpart_matches_testing_md_band_c() { )); } +#[test] +fn integration_rs_active_line_contains_rejects_commented_cementing_path() { + let src = r#" +// #[path = "integration/cementing/ghost.rs"] +// mod ghost; +#[path = "integration/cementing/real.rs"] +mod real; +"#; + assert!(!integration_rs_active_line_contains( + src, + r#"#[path = "integration/cementing/ghost.rs"]"#, + )); + assert!(integration_rs_active_line_contains( + src, + r#"#[path = "integration/cementing/real.rs"]"#, + )); +} + #[test] fn md_table_cells_preserves_escaped_pipes_for_register_capability_rows() { // Pin the markdown-as-data split: cementing escalation reads @@ -291,11 +309,17 @@ fn assert_cementing_stem_wired_in_integration_rs( ) { let expected = format!(r#"#[path = "integration/cementing/{stem}.rs"]"#); assert!( - integration_rs.contains(&expected), + integration_rs_active_line_contains(integration_rs, &expected), + "registry lens `{registry_name}` lists cementing stem `{stem}` but \ + `tests/integration.rs` has no **active** (non-`//`-comment) line with `{expected}` — add \ + `#[path = \"integration/cementing/{stem}.rs\"]` plus `mod {stem};` in the same PR as the \ + on-disk module. Commented-out `// #[path = …]` does not count." + ); + let mod_needle = format!("mod {stem}"); + assert!( + integration_rs_active_line_contains(integration_rs, &mod_needle), "registry lens `{registry_name}` lists cementing stem `{stem}` but \ - `tests/integration.rs` does not contain `{expected}` — add a \ - `#[path = \"integration/cementing/{stem}.rs\"]` module declaration \ - in the same PR as the on-disk module (API-level wiring, not convention-only)." + `tests/integration.rs` has no active line declaring `{mod_needle};` — wire the module." ); } diff --git a/src/v3/compiler/tests/integration/common/mod.rs b/src/v3/compiler/tests/integration/common/mod.rs index 1f0ba223703..edaa68ae453 100644 --- a/src/v3/compiler/tests/integration/common/mod.rs +++ b/src/v3/compiler/tests/integration/common/mod.rs @@ -81,16 +81,16 @@ pub fn find_current_rlib(crate_name: &str) -> PathBuf { /// declarations. A raw substring match false-greens on commented-out copies /// (`// #[path = …]`). Ignore any line whose first non-whitespace token starts /// a line comment (`//`, `///`, `//!`). -pub fn integration_rs_line_is_active_for_wiring_check(line: &str) -> bool { +fn integration_rs_line_is_active_for_wiring_check(line: &str) -> bool { let s = line.trim_start(); !(s.is_empty() || s.starts_with("//")) } /// True when some **active** (non-`//`-leading) line in `integration_rs` contains `needle`. pub fn integration_rs_active_line_contains(integration_rs: &str, needle: &str) -> bool { - integration_rs.lines().any(|line| { - integration_rs_line_is_active_for_wiring_check(line) && line.contains(needle) - }) + integration_rs + .lines() + .any(|line| integration_rs_line_is_active_for_wiring_check(line) && line.contains(needle)) } /// Harness for spawning rustc against generated-Rust harnesses. diff --git a/src/v3/compiler/tests/integration/sg6_hand_authored_census_test.rs b/src/v3/compiler/tests/integration/sg6_hand_authored_census_test.rs index 076b8bf5eb2..e2e4984b4e4 100644 --- a/src/v3/compiler/tests/integration/sg6_hand_authored_census_test.rs +++ b/src/v3/compiler/tests/integration/sg6_hand_authored_census_test.rs @@ -58,6 +58,7 @@ use std::io::Write; use std::path::PathBuf; use std::process::{Command, Stdio}; +use crate::common::integration_rs_active_line_contains; use v3_compiler::compile_to_dag; use v3_compiler::dag::{Dag, Declaration, FieldValue, LiteralBits, ValueBody}; use v3_compiler::emit_rust::emit_rust_module; @@ -466,14 +467,15 @@ fn sg6_cementing_lens_registry_dispatch_module_is_wired_in_integration_rs() { let text = fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display())); let attr = r#"#[path = "integration/cementing/cementing_lens_registry_dispatch_test.rs"]"#; assert!( - text.contains(attr), - "{} must declare `{attr}` so `cementing_lens_registry_dispatch_test` is linked; \ - otherwise its ratchet tests never run and CI gives a false green.", + integration_rs_active_line_contains(&text, attr), + "{} must declare `{attr}` on an active (non-`//`-comment) line so \ + `cementing_lens_registry_dispatch_test` is linked; commented-out text does not count.", path.display(), ); assert!( - text.contains("mod cementing_lens_registry_dispatch_test"), - "{} must declare `mod cementing_lens_registry_dispatch_test` after the #[path] attribute", + integration_rs_active_line_contains(&text, "mod cementing_lens_registry_dispatch_test"), + "{} must declare `mod cementing_lens_registry_dispatch_test` on an active line after the \ + #[path] attribute", path.display(), ); } From a8a2d28c4e2d29063d28a6fc330191eb840c489e Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 08:12:07 -0400 Subject: [PATCH 21/26] WIP: test generalization --- src/v3/compiler/tests/integration/common/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/v3/compiler/tests/integration/common/mod.rs b/src/v3/compiler/tests/integration/common/mod.rs index edaa68ae453..1eb7d43e5de 100644 --- a/src/v3/compiler/tests/integration/common/mod.rs +++ b/src/v3/compiler/tests/integration/common/mod.rs @@ -81,6 +81,10 @@ pub fn find_current_rlib(crate_name: &str) -> PathBuf { /// declarations. A raw substring match false-greens on commented-out copies /// (`// #[path = …]`). Ignore any line whose first non-whitespace token starts /// a line comment (`//`, `///`, `//!`). +/// +/// **Not handled:** `/* … */` block comments — today’s `integration.rs` uses only +/// line / outer-doc comments; extend this helper if that file ever nests mod +/// declarations inside block comments. fn integration_rs_line_is_active_for_wiring_check(line: &str) -> bool { let s = line.trim_start(); !(s.is_empty() || s.starts_with("//")) From 7255543cd4f9cbfb1b9c4293f1f3e94033cb7e2f Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 08:16:09 -0400 Subject: [PATCH 22/26] fix(v3): scan integration.rs wiring outside block comments and strings Replace line-prefix heuristic with a small lexer (//, nested /* */, and " strings) so /* #[path] */ cannot satisfy Band-C wiring ratchets. Pins block-comment and string-literal behavior in cementing tests. Made-with: Cursor --- .../cementing_lens_registry_dispatch_test.rs | 37 ++++++ .../compiler/tests/integration/common/mod.rs | 105 +++++++++++++++--- 2 files changed, 125 insertions(+), 17 deletions(-) diff --git a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs index e259a052b5f..56de50cac5f 100644 --- a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs +++ b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs @@ -157,6 +157,43 @@ mod real; )); } +#[test] +fn integration_rs_active_line_contains_rejects_block_commented_cementing_path() { + let src = r#"/* +#[path = "integration/cementing/ghost.rs"] +mod ghost; +*/ +#[path = "integration/cementing/real.rs"] +mod real; +"#; + assert!(!integration_rs_active_line_contains( + src, + r#"#[path = "integration/cementing/ghost.rs"]"#, + )); + assert!(integration_rs_active_line_contains( + src, + r#"#[path = "integration/cementing/real.rs"]"#, + )); +} + +#[test] +fn integration_rs_active_line_ignores_needle_inside_string_literal() { + // Tombstone text appears only inside a `"..."` string — must not satisfy the ratchet. + let src = concat!( + "let _ = \"#[path = \\\"integration/cementing/decoy.rs\\\"]\";\n", + "#[path = \"integration/cementing/real.rs\"]\n", + "mod real;\n", + ); + assert!(!integration_rs_active_line_contains( + src, + r#"#[path = "integration/cementing/decoy.rs"]"#, + )); + assert!(integration_rs_active_line_contains( + src, + r#"#[path = "integration/cementing/real.rs"]"#, + )); +} + #[test] fn md_table_cells_preserves_escaped_pipes_for_register_capability_rows() { // Pin the markdown-as-data split: cementing escalation reads diff --git a/src/v3/compiler/tests/integration/common/mod.rs b/src/v3/compiler/tests/integration/common/mod.rs index 1eb7d43e5de..245c9af061b 100644 --- a/src/v3/compiler/tests/integration/common/mod.rs +++ b/src/v3/compiler/tests/integration/common/mod.rs @@ -75,26 +75,97 @@ pub fn find_current_rlib(crate_name: &str) -> PathBuf { .expect("compiled rlib for current crate") } -/// True when `line` is not a whole-line outer doc / line comment in Rust source. -/// -/// Band-C wiring ratchets scan `tests/integration.rs` for `#[path = …]` / `mod …;` -/// declarations. A raw substring match false-greens on commented-out copies -/// (`// #[path = …]`). Ignore any line whose first non-whitespace token starts -/// a line comment (`//`, `///`, `//!`). -/// -/// **Not handled:** `/* … */` block comments — today’s `integration.rs` uses only -/// line / outer-doc comments; extend this helper if that file ever nests mod -/// declarations inside block comments. -fn integration_rs_line_is_active_for_wiring_check(line: &str) -> bool { - let s = line.trim_start(); - !(s.is_empty() || s.starts_with("//")) +#[derive(Clone, Copy, PartialEq, Eq)] +enum IntegrationRsScan { + Code, + LineComment, + BlockComment(u32), + String, } -/// True when some **active** (non-`//`-leading) line in `integration_rs` contains `needle`. +/// True when `needle` appears in `integration_rs` **outside** Rust line comments +/// (`//` … including `///` / `//!`), **nested** block comments (`/* … */`), and +/// normal `"…"` string literals. +/// +/// Band-C wiring ratchets use this so `#[path = …]` / `mod …;` matches cannot +/// false-green on commented-out or string-embedded copies. Needles are ASCII +/// (`#[path`, `mod foo`); the scan is byte-oriented on UTF-8 boundaries. +/// +/// **Not handled:** raw strings (`r#"…"#`), byte strings, or char literals — none +/// appear in today’s `tests/integration.rs` module list; extend if those surfaces +/// start carrying `#[path`-shaped text outside normal strings. pub fn integration_rs_active_line_contains(integration_rs: &str, needle: &str) -> bool { - integration_rs - .lines() - .any(|line| integration_rs_line_is_active_for_wiring_check(line) && line.contains(needle)) + if needle.is_empty() { + return true; + } + let bytes = integration_rs.as_bytes(); + let mut i = 0usize; + let mut state = IntegrationRsScan::Code; + + while i < bytes.len() { + match state { + IntegrationRsScan::Code => { + if bytes[i] == b'/' && i + 1 < bytes.len() { + if bytes[i + 1] == b'/' { + state = IntegrationRsScan::LineComment; + i += 2; + continue; + } + if bytes[i + 1] == b'*' { + state = IntegrationRsScan::BlockComment(1); + i += 2; + continue; + } + } + if bytes[i] == b'"' { + state = IntegrationRsScan::String; + i += 1; + continue; + } + if integration_rs[i..].starts_with(needle) { + return true; + } + i += 1; + } + IntegrationRsScan::LineComment => { + if bytes[i] == b'\n' { + state = IntegrationRsScan::Code; + } + i += 1; + } + IntegrationRsScan::BlockComment(depth) => { + if bytes[i] == b'/' && i + 1 < bytes.len() && bytes[i + 1] == b'*' { + state = IntegrationRsScan::BlockComment(depth + 1); + i += 2; + continue; + } + if bytes[i] == b'*' && i + 1 < bytes.len() && bytes[i + 1] == b'/' { + let d = depth - 1; + i += 2; + state = if d == 0 { + IntegrationRsScan::Code + } else { + IntegrationRsScan::BlockComment(d) + }; + continue; + } + i += 1; + } + IntegrationRsScan::String => match bytes[i] { + b'\\' => { + i = (i + 2).min(bytes.len()); + } + b'"' => { + i += 1; + state = IntegrationRsScan::Code; + } + _ => { + i += 1; + } + }, + } + } + false } /// Harness for spawning rustc against generated-Rust harnesses. From 8e682e3fddf470011b8851233bfc71e1feb15f87 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 08:30:14 -0400 Subject: [PATCH 23/26] WIP: test generalization --- .../cementing_lens_registry_dispatch_test.rs | 7 +++ .../compiler/tests/integration/common/mod.rs | 44 +++++++++++++++++-- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs index 56de50cac5f..2c070884e86 100644 --- a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs +++ b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs @@ -176,6 +176,13 @@ mod real; )); } +#[test] +#[should_panic(expected = "raw/byte string literal")] +fn integration_rs_active_line_contains_panics_on_raw_string_in_code() { + let src = concat!("fn f() { r#\"", "hi", "\"#; }"); + integration_rs_active_line_contains(src, "nope"); +} + #[test] fn integration_rs_active_line_ignores_needle_inside_string_literal() { // Tombstone text appears only inside a `"..."` string — must not satisfy the ratchet. diff --git a/src/v3/compiler/tests/integration/common/mod.rs b/src/v3/compiler/tests/integration/common/mod.rs index 245c9af061b..4223f696867 100644 --- a/src/v3/compiler/tests/integration/common/mod.rs +++ b/src/v3/compiler/tests/integration/common/mod.rs @@ -75,6 +75,35 @@ pub fn find_current_rlib(crate_name: &str) -> PathBuf { .expect("compiled rlib for current crate") } +/// `true` when `bytes[i..]` opens a `b"…"`, `br#"…"#`, `r#"…"#`, or `r"…"` literal in +/// Rust source. The wiring scanner does not model these — fail loud if they appear +/// in `Code` so Band-C ratchets cannot silently mis-scan `tests/integration.rs`. +fn code_opens_raw_or_byte_string_literal(bytes: &[u8], i: usize) -> bool { + match bytes.get(i) { + Some(b'b') => { + if bytes.get(i + 1) == Some(&b'"') { + return true; + } + if bytes.get(i + 1) == Some(&b'r') { + let mut j = i + 2; + while bytes.get(j) == Some(&b'#') { + j += 1; + } + return bytes.get(j) == Some(&b'"'); + } + false + } + Some(b'r') => { + let mut j = i + 1; + while bytes.get(j) == Some(&b'#') { + j += 1; + } + bytes.get(j) == Some(&b'"') + } + _ => false, + } +} + #[derive(Clone, Copy, PartialEq, Eq)] enum IntegrationRsScan { Code, @@ -91,9 +120,11 @@ enum IntegrationRsScan { /// false-green on commented-out or string-embedded copies. Needles are ASCII /// (`#[path`, `mod foo`); the scan is byte-oriented on UTF-8 boundaries. /// -/// **Not handled:** raw strings (`r#"…"#`), byte strings, or char literals — none -/// appear in today’s `tests/integration.rs` module list; extend if those surfaces -/// start carrying `#[path`-shaped text outside normal strings. +/// **Not handled:** raw strings (`r#"…"#`), byte strings (`b"…"`, `br#"…"#`), or +/// char literals — none appear in today’s `tests/integration.rs` module list. If +/// they do, extend [`IntegrationRsScan`] **or** the scan will **panic** when +/// [`code_opens_raw_or_byte_string_literal`] fires in `Code` (loud failure vs a +/// silent false green). pub fn integration_rs_active_line_contains(integration_rs: &str, needle: &str) -> bool { if needle.is_empty() { return true; @@ -105,6 +136,13 @@ pub fn integration_rs_active_line_contains(integration_rs: &str, needle: &str) - while i < bytes.len() { match state { IntegrationRsScan::Code => { + if code_opens_raw_or_byte_string_literal(bytes, i) { + panic!( + "integration_rs_active_line_contains: raw/byte string literal at byte offset {i} \ + (e.g. r\"…\", r#\"…\"#, b\"…\", br#\"…\"#). The wiring scanner does not model these \ + yet — extend IntegrationRsScan before relying on Band-C integration.rs ratchets." + ); + } if bytes[i] == b'/' && i + 1 < bytes.len() { if bytes[i + 1] == b'/' { state = IntegrationRsScan::LineComment; From 102050740cc43f47b5c9dace3618d959930fc7a1 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 08:30:38 -0400 Subject: [PATCH 24/26] fix(v3): panic wiring scanner on raw/byte string openers in Code Addresses exploratory review: if tests/integration.rs gains r#" / b" / br" literals the naive string state would desync silently. Detect openers in Code and panic with an explicit extend-scanner message; pin with should_panic test. Made-with: Cursor --- src/v3/compiler/tests/integration/common/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/v3/compiler/tests/integration/common/mod.rs b/src/v3/compiler/tests/integration/common/mod.rs index 4223f696867..a5ec2bfd26f 100644 --- a/src/v3/compiler/tests/integration/common/mod.rs +++ b/src/v3/compiler/tests/integration/common/mod.rs @@ -122,9 +122,8 @@ enum IntegrationRsScan { /// /// **Not handled:** raw strings (`r#"…"#`), byte strings (`b"…"`, `br#"…"#`), or /// char literals — none appear in today’s `tests/integration.rs` module list. If -/// they do, extend [`IntegrationRsScan`] **or** the scan will **panic** when -/// [`code_opens_raw_or_byte_string_literal`] fires in `Code` (loud failure vs a -/// silent false green). +/// they do, extend `IntegrationRsScan` **or** the scan will **panic** when the +/// raw/byte-string opener probe fires in `Code` (loud failure vs a silent false green). pub fn integration_rs_active_line_contains(integration_rs: &str, needle: &str) -> bool { if needle.is_empty() { return true; From 05f71c154ac701f230c4b9dcd9f094da7b857aff Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 08:36:59 -0400 Subject: [PATCH 25/26] fix(v3): require #[path] bound to mod stem for cementing wiring Band-C dispatch must not pass on unrelated #[path] + mod pairs. Add integration_rs_cementing_path_attr_binds_mod_stem (code-only path match + mod ; tail), refactor substring scan to collect positions, and pin mismatched-mod / stem_extra cases in cementing tests. Made-with: Cursor --- .../cementing_lens_registry_dispatch_test.rs | 72 ++++++-- .../compiler/tests/integration/common/mod.rs | 163 +++++++++++++++--- .../sg6_hand_authored_census_test.rs | 18 +- 3 files changed, 209 insertions(+), 44 deletions(-) diff --git a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs index 2c070884e86..627f36c0832 100644 --- a/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs +++ b/src/v3/compiler/tests/integration/cementing/cementing_lens_registry_dispatch_test.rs @@ -25,7 +25,9 @@ use std::collections::BTreeSet; use std::path::{Path, PathBuf}; -use crate::common::integration_rs_active_line_contains; +use crate::common::{ + integration_rs_active_line_contains, integration_rs_cementing_path_attr_binds_mod_stem, +}; use v3_compiler::compile_to_dag; use v3_compiler::dag::{Behavior, Dag, Declaration, FieldValue, LiteralBits, ValueBody}; use v3_compiler::lens_provenance::{origin_of, Origin}; @@ -183,6 +185,60 @@ fn integration_rs_active_line_contains_panics_on_raw_string_in_code() { integration_rs_active_line_contains(src, "nope"); } +#[test] +fn integration_rs_cementing_path_attr_binds_mod_accepts_multiline() { + let src = concat!( + "#[path = \"integration/cementing/real.rs\"]\n", + "mod real;\n", + ); + assert!(integration_rs_cementing_path_attr_binds_mod_stem( + src, "real" + )); +} + +#[test] +fn integration_rs_cementing_path_attr_binds_mod_accepts_same_line() { + let src = "#[path = \"integration/cementing/real.rs\"] mod real;\n"; + assert!(integration_rs_cementing_path_attr_binds_mod_stem( + src, "real" + )); +} + +#[test] +fn integration_rs_cementing_path_attr_binds_mod_accepts_interleaved_attribute() { + let src = concat!( + "#[path = \"integration/cementing/real.rs\"]\n", + "#[allow(dead_code)]\n", + "mod real;\n", + ); + assert!(integration_rs_cementing_path_attr_binds_mod_stem( + src, "real" + )); +} + +#[test] +fn integration_rs_cementing_path_attr_binds_mod_rejects_mismatched_mod_name() { + let src = concat!( + "#[path = \"integration/cementing/real.rs\"]\n", + "mod decoy;\n\n", + "mod real;\n", + ); + assert!(!integration_rs_cementing_path_attr_binds_mod_stem( + src, "real" + )); +} + +#[test] +fn integration_rs_cementing_path_attr_binds_mod_rejects_stem_extra_mod_name() { + let src = concat!( + "#[path = \"integration/cementing/stem.rs\"]\n", + "mod stem_extra;\n", + ); + assert!(!integration_rs_cementing_path_attr_binds_mod_stem( + src, "stem" + )); +} + #[test] fn integration_rs_active_line_ignores_needle_inside_string_literal() { // Tombstone text appears only inside a `"..."` string — must not satisfy the ratchet. @@ -353,17 +409,11 @@ fn assert_cementing_stem_wired_in_integration_rs( ) { let expected = format!(r#"#[path = "integration/cementing/{stem}.rs"]"#); assert!( - integration_rs_active_line_contains(integration_rs, &expected), - "registry lens `{registry_name}` lists cementing stem `{stem}` but \ - `tests/integration.rs` has no **active** (non-`//`-comment) line with `{expected}` — add \ - `#[path = \"integration/cementing/{stem}.rs\"]` plus `mod {stem};` in the same PR as the \ - on-disk module. Commented-out `// #[path = …]` does not count." - ); - let mod_needle = format!("mod {stem}"); - assert!( - integration_rs_active_line_contains(integration_rs, &mod_needle), + integration_rs_cementing_path_attr_binds_mod_stem(integration_rs, stem), "registry lens `{registry_name}` lists cementing stem `{stem}` but \ - `tests/integration.rs` has no active line declaring `{mod_needle};` — wire the module." + `tests/integration.rs` does not bind `{expected}` to `mod {stem};` in the same item \ + (Band-C dispatch — not a stray `#[path]` plus an unrelated `mod` line). \ + Land both lines in the same PR as the on-disk `cementing/{stem}.rs` module." ); } diff --git a/src/v3/compiler/tests/integration/common/mod.rs b/src/v3/compiler/tests/integration/common/mod.rs index a5ec2bfd26f..12830175a4e 100644 --- a/src/v3/compiler/tests/integration/common/mod.rs +++ b/src/v3/compiler/tests/integration/common/mod.rs @@ -112,21 +112,22 @@ enum IntegrationRsScan { String, } -/// True when `needle` appears in `integration_rs` **outside** Rust line comments -/// (`//` … including `///` / `//!`), **nested** block comments (`/* … */`), and -/// normal `"…"` string literals. -/// -/// Band-C wiring ratchets use this so `#[path = …]` / `mod …;` matches cannot -/// false-green on commented-out or string-embedded copies. Needles are ASCII -/// (`#[path`, `mod foo`); the scan is byte-oriented on UTF-8 boundaries. -/// -/// **Not handled:** raw strings (`r#"…"#`), byte strings (`b"…"`, `br#"…"#`), or -/// char literals — none appear in today’s `tests/integration.rs` module list. If -/// they do, extend `IntegrationRsScan` **or** the scan will **panic** when the -/// raw/byte-string opener probe fires in `Code` (loud failure vs a silent false green). -pub fn integration_rs_active_line_contains(integration_rs: &str, needle: &str) -> bool { +fn integration_rs_panic_on_raw_string(bytes: &[u8], i: usize) { + if code_opens_raw_or_byte_string_literal(bytes, i) { + panic!( + "integration_rs wiring scan: raw/byte string literal at byte offset {i} \ + (e.g. r\"…\", r#\"…\"#, b\"…\", br#\"…\"#). Extend IntegrationRsScan before scanning this file." + ); + } +} + +/// Byte offsets of `needle` in `integration_rs` that occur in **Code** (outside +/// `//`, `/* */`, and `"…"` strings). See [`integration_rs_active_line_contains`]. +fn integration_rs_code_substring_positions(integration_rs: &str, needle: &str) -> Vec { + let mut hits = Vec::new(); if needle.is_empty() { - return true; + hits.push(0); + return hits; } let bytes = integration_rs.as_bytes(); let mut i = 0usize; @@ -135,13 +136,7 @@ pub fn integration_rs_active_line_contains(integration_rs: &str, needle: &str) - while i < bytes.len() { match state { IntegrationRsScan::Code => { - if code_opens_raw_or_byte_string_literal(bytes, i) { - panic!( - "integration_rs_active_line_contains: raw/byte string literal at byte offset {i} \ - (e.g. r\"…\", r#\"…\"#, b\"…\", br#\"…\"#). The wiring scanner does not model these \ - yet — extend IntegrationRsScan before relying on Band-C integration.rs ratchets." - ); - } + integration_rs_panic_on_raw_string(bytes, i); if bytes[i] == b'/' && i + 1 < bytes.len() { if bytes[i + 1] == b'/' { state = IntegrationRsScan::LineComment; @@ -160,7 +155,7 @@ pub fn integration_rs_active_line_contains(integration_rs: &str, needle: &str) - continue; } if integration_rs[i..].starts_with(needle) { - return true; + hits.push(i); } i += 1; } @@ -202,9 +197,133 @@ pub fn integration_rs_active_line_contains(integration_rs: &str, needle: &str) - }, } } + hits +} + +/// Consume one outer `#[ … ]` attribute (handles quoted `"` regions and nested `[` `]`). +fn skip_rust_attribute(s: &str) -> Option<&str> { + if !s.starts_with("#[") { + return None; + } + let bytes = s.as_bytes(); + let mut i = 1usize; + let mut depth = 0usize; + let mut in_string = false; + let mut escape = false; + while i < bytes.len() { + let b = bytes[i]; + if in_string { + if escape { + escape = false; + i += 1; + continue; + } + if b == b'\\' { + escape = true; + i += 1; + continue; + } + if b == b'"' { + in_string = false; + } + i += 1; + continue; + } + match b { + b'"' => { + in_string = true; + i += 1; + } + b'[' => { + depth += 1; + i += 1; + } + b']' => { + depth = depth.saturating_sub(1); + i += 1; + if depth == 0 { + return Some(&s[i..]); + } + } + _ => { + i += 1; + } + } + } + None +} + +/// After a `#[path = "integration/cementing/.rs"]` attribute in Code, the next +/// Rust item must be `mod ;` (optionally separated by whitespace, newlines, or +/// additional `#[…]` attributes). +fn mod_decl_follows_cementing_path_tail(mut tail: &str, stem: &str) -> bool { + let prefix = format!("mod {stem}"); + loop { + tail = tail.trim_start(); + if tail.is_empty() { + return false; + } + if tail.starts_with("#[") { + let Some(rest) = skip_rust_attribute(tail) else { + return false; + }; + tail = rest; + continue; + } + let rest = tail.trim_start(); + if !rest.starts_with(&prefix) { + return false; + } + let after_prefix = &rest[prefix.len()..]; + if after_prefix + .chars() + .next() + .is_some_and(|c| c.is_ascii_alphanumeric() || c == '_') + { + // e.g. `mod stem` must not match `mod stem_extra`. + return false; + } + let after = after_prefix.trim_start(); + return after.starts_with(';'); + } +} + +/// `true` when `integration_rs` contains `#[path = "integration/cementing/.rs"]` +/// in **Code** and that attribute is immediately followed (per +/// [`mod_decl_follows_cementing_path_tail`]) by `mod ;`. +/// +/// This is stricter than substring checks for `#[path]` and `mod` separately — it +/// rejects a mismatched `#[path]` on one module paired with a stray `mod ;` +/// elsewhere (`TESTING.md` Band-C dispatch). +pub fn integration_rs_cementing_path_attr_binds_mod_stem(integration_rs: &str, stem: &str) -> bool { + let path_attr = format!(r#"#[path = "integration/cementing/{stem}.rs"]"#); + for pos in integration_rs_code_substring_positions(integration_rs, &path_attr) { + let Some(tail) = integration_rs.get(pos + path_attr.len()..) else { + continue; + }; + if mod_decl_follows_cementing_path_tail(tail, stem) { + return true; + } + } false } +/// True when `needle` appears in `integration_rs` **outside** Rust line comments +/// (`//` … including `///` / `//!`), **nested** block comments (`/* … */`), and +/// normal `"…"` string literals. +/// +/// Band-C wiring ratchets use this so `#[path = …]` / `mod …;` matches cannot +/// false-green on commented-out or string-embedded copies. Needles are ASCII +/// (`#[path`, `mod foo`); the scan is byte-oriented on UTF-8 boundaries. +/// +/// **Not handled:** raw strings (`r#"…"#`), byte strings (`b"…"`, `br#"…"#`), or +/// char literals — none appear in today’s `tests/integration.rs` module list. If +/// they do, extend `IntegrationRsScan` **or** the scan will **panic** when the +/// raw/byte-string opener probe fires in `Code` (loud failure vs a silent false green). +pub fn integration_rs_active_line_contains(integration_rs: &str, needle: &str) -> bool { + !integration_rs_code_substring_positions(integration_rs, needle).is_empty() +} + /// Harness for spawning rustc against generated-Rust harnesses. /// Holds the per-test-binary scratch root and a counter so parallel /// tests within the same binary get distinct temp dirs. diff --git a/src/v3/compiler/tests/integration/sg6_hand_authored_census_test.rs b/src/v3/compiler/tests/integration/sg6_hand_authored_census_test.rs index e2e4984b4e4..c1e339a9650 100644 --- a/src/v3/compiler/tests/integration/sg6_hand_authored_census_test.rs +++ b/src/v3/compiler/tests/integration/sg6_hand_authored_census_test.rs @@ -58,7 +58,7 @@ use std::io::Write; use std::path::PathBuf; use std::process::{Command, Stdio}; -use crate::common::integration_rs_active_line_contains; +use crate::common::integration_rs_cementing_path_attr_binds_mod_stem; use v3_compiler::compile_to_dag; use v3_compiler::dag::{Dag, Declaration, FieldValue, LiteralBits, ValueBody}; use v3_compiler::emit_rust::emit_rust_module; @@ -465,17 +465,13 @@ fn sg6_lens_registry_names_resolve_to_singleton_entry() { fn sg6_cementing_lens_registry_dispatch_module_is_wired_in_integration_rs() { let path = manifest_dir().join("tests").join("integration.rs"); let text = fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display())); - let attr = r#"#[path = "integration/cementing/cementing_lens_registry_dispatch_test.rs"]"#; assert!( - integration_rs_active_line_contains(&text, attr), - "{} must declare `{attr}` on an active (non-`//`-comment) line so \ - `cementing_lens_registry_dispatch_test` is linked; commented-out text does not count.", - path.display(), - ); - assert!( - integration_rs_active_line_contains(&text, "mod cementing_lens_registry_dispatch_test"), - "{} must declare `mod cementing_lens_registry_dispatch_test` on an active line after the \ - #[path] attribute", + integration_rs_cementing_path_attr_binds_mod_stem( + &text, + "cementing_lens_registry_dispatch_test", + ), + "{} must bind `#[path = \"integration/cementing/cementing_lens_registry_dispatch_test.rs\"]` \ + to `mod cementing_lens_registry_dispatch_test;` as one item (Band-C dispatch).", path.display(), ); } From a855ac6ab4b1c2126cbd05bed14fbfe27a729e7e Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 22 Apr 2026 08:46:33 -0400 Subject: [PATCH 26/26] refactor(v3): drop redundant trim in mod_decl_follows_cementing_path_tail Made-with: Cursor --- src/v3/compiler/tests/integration/common/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/v3/compiler/tests/integration/common/mod.rs b/src/v3/compiler/tests/integration/common/mod.rs index 12830175a4e..0d7af70ea60 100644 --- a/src/v3/compiler/tests/integration/common/mod.rs +++ b/src/v3/compiler/tests/integration/common/mod.rs @@ -270,11 +270,10 @@ fn mod_decl_follows_cementing_path_tail(mut tail: &str, stem: &str) -> bool { tail = rest; continue; } - let rest = tail.trim_start(); - if !rest.starts_with(&prefix) { + if !tail.starts_with(&prefix) { return false; } - let after_prefix = &rest[prefix.len()..]; + let after_prefix = &tail[prefix.len()..]; if after_prefix .chars() .next()