From 58b7320be831c892c5945de501ea20ffa98eda4e Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 14 May 2026 01:20:18 -0400 Subject: [PATCH 1/5] =?UTF-8?q?WIP:=20R3=20F-=CE=B1=20post-merge=20paralle?= =?UTF-8?q?lism=20cementing-receipt=20(gate=20#87=20corpus=20extensi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/v3/compiler/src/cementing_dispatch.rs | 6 ++++ src/v3/compiler/src/test_runner.rs | 21 ++++++++++-- ...r3_gate_87_cementing_regen_parallelism.dag | 33 +++++++++++++++++++ ...e_87_lens_cementing_regen_receipts_test.rs | 6 +++- .../cementing_dispatch.rs.in | 6 ++++ 5 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_parallelism.dag diff --git a/src/v3/compiler/src/cementing_dispatch.rs b/src/v3/compiler/src/cementing_dispatch.rs index 6a9de3c3324..af7d614f4c7 100644 --- a/src/v3/compiler/src/cementing_dispatch.rs +++ b/src/v3/compiler/src/cementing_dispatch.rs @@ -498,6 +498,12 @@ pub mod r3_gate_87_cementing_regen_runner_suites { "r3_gate_87_cementing_regen_lower_helpers_suite", &["cementing_regen_lower_helpers"], ), + ( + include_str!("../tests/dag/t_r3_gate_87_cementing_regen_parallelism.dag"), + "src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_parallelism.dag", + "r3_gate_87_cementing_regen_parallelism_suite", + &["cementing_regen_parallelism"], + ), ( include_str!("../tests/dag/t_r3_gate_87_cementing_regen_provenance.dag"), "src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_provenance.dag", diff --git a/src/v3/compiler/src/test_runner.rs b/src/v3/compiler/src/test_runner.rs index 6da9116fccd..646a987afa7 100644 --- a/src/v3/compiler/src/test_runner.rs +++ b/src/v3/compiler/src/test_runner.rs @@ -6,7 +6,8 @@ use std::time::{Duration, Instant}; use crate::cementing_dispatch; use crate::dag::{ AtomPayload, Behavior, BindNode, Dag, Declaration, DeclarationId, FieldValue, LiteralBits, - NodeId, Path, PortId, PortState, SymbolicCost, TypeConnective, ValueBody, + NodeId, ParallelismUnsupportedKind, Path, PortId, PortState, SymbolicCost, TypeConnective, + ValueBody, WorkflowParallelismReport, }; use crate::diagnostics::Diagnostic; use crate::emit::python_target::last_emit_python_program_top_level_value_bind_name; @@ -31,8 +32,9 @@ use crate::lens_structural_resolution; use crate::lens_unused_parameters::{UnusedParametersConfig, UnusedParametersLens}; use crate::types::TypeShape; use crate::{ - analyze_symbolic_cost_dimension, compare_stage_snapshots, compile_stage_snapshots, - compile_to_dag, default_fixed_point_source, CompileError, DimensionReport, + analyze_parallelism, analyze_symbolic_cost_dimension, compare_stage_snapshots, + compile_stage_snapshots, compile_to_dag, default_fixed_point_source, CompileError, + DimensionReport, }; const SG0_CENSUS_SOURCE: &str = include_str!(concat!( @@ -3151,6 +3153,19 @@ impl<'a> TestRunner<'a> { .query(&UnusedParametersConfig::default()) .is_empty(), ), + "gate87_parallelism_literal_no_workflow_projection" => { + let Some(bind) = find_bind(program_dag, "lit", file_name) else { + return Some(ClaimResult::Fail(format!( + "LensOutputEquals({lens_name}): bind `lit` not found in `{file_name}`" + ))); + }; + let report = analyze_parallelism(program_dag, bind.id); + i64::from(matches!( + report, + WorkflowParallelismReport::ParallelismUnsupported(detail) + if detail.kind == ParallelismUnsupportedKind::NoWorkflowProjection + )) + } _ => return None, }; diff --git a/src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_parallelism.dag b/src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_parallelism.dag new file mode 100644 index 00000000000..09415839483 --- /dev/null +++ b/src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_parallelism.dag @@ -0,0 +1,33 @@ +// R3 gate #87 — `LensRegistryEntry` `parallelism` (PARTIAL / v3-native per `verification.dag`). +// Narrow Int projection: a literal bind without Lane-2 workflow staging yields +// `ParallelismUnsupportedKind::NoWorkflowProjection` from `analyze_parallelism` (runner seam: +// `eval_gate_87_cementing_projection`). Dissolution: widen to a full `WorkflowParallelismReport` +// `LensOutputEquals` once report literals are authorable as `.dag` data. + +module v3.compiler.tests.t_r3.gate_87_cementing_regen_parallelism + +import std.substrate { Dag } +import std.verification { LensOutputEquals, ProgramInput, TestClaim, TestSuite } + +fn gate87_parallelism_literal_no_workflow_projection(d: Dag) -> Int = 0 + +data gate87_program_input: ProgramInput = {} + +data gate87_expected_true: Int = 1 + +data cementing_regen_parallelism_claim: TestClaim = { + name: "cementing_regen_parallelism", + source: "let lit: Int = 7", + file_name: "r3_gate_87_parallelism_smoke.v3", + predicate: LensOutputEquals( + gate87_parallelism_literal_no_workflow_projection, + gate87_program_input, + gate87_expected_true + ), + requires: [] +} + +data r3_gate_87_cementing_regen_parallelism_suite: TestSuite = { + name: "r3-gate-87/cementing/regen/parallelism", + claims: [Enumerated(cementing_regen_parallelism_claim)] +} diff --git a/src/v3/compiler/tests/integration/r3_gate_87_lens_cementing_regen_receipts_test.rs b/src/v3/compiler/tests/integration/r3_gate_87_lens_cementing_regen_receipts_test.rs index 3af95961b11..e158b2d3e31 100644 --- a/src/v3/compiler/tests/integration/r3_gate_87_lens_cementing_regen_receipts_test.rs +++ b/src/v3/compiler/tests/integration/r3_gate_87_lens_cementing_regen_receipts_test.rs @@ -40,8 +40,12 @@ use std::path::PathBuf; use v3_compiler::r3_gate_87_cementing_regen_runner_suites::r3_gate_87_cementing_regen_lens_names_for_runner_table; +use v3_compiler::analyze_parallelism; use v3_compiler::compile_to_dag; -use v3_compiler::dag::{Behavior, Declaration, FieldValue, LiteralBits, ValueBody}; +use v3_compiler::dag::{ + Behavior, Declaration, FieldValue, LiteralBits, ParallelismUnsupportedKind, ValueBody, + WorkflowParallelismReport, +}; use v3_compiler::lens_cost_target_realization::type_realization_meta; use v3_compiler::lens_effect_enumeration::{enumerate_effects, TransactionalPattern}; use v3_compiler::lens_provenance::{origin_of, Origin}; diff --git a/tools/pb0_cycle4_emit_templates/cementing_dispatch.rs.in b/tools/pb0_cycle4_emit_templates/cementing_dispatch.rs.in index affd27617d5..acd6ce806ed 100644 --- a/tools/pb0_cycle4_emit_templates/cementing_dispatch.rs.in +++ b/tools/pb0_cycle4_emit_templates/cementing_dispatch.rs.in @@ -492,6 +492,12 @@ pub mod r3_gate_87_cementing_regen_runner_suites { "r3_gate_87_cementing_regen_lower_helpers_suite", &["cementing_regen_lower_helpers"], ), + ( + include_str!("../tests/dag/t_r3_gate_87_cementing_regen_parallelism.dag"), + "src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_parallelism.dag", + "r3_gate_87_cementing_regen_parallelism_suite", + &["cementing_regen_parallelism"], + ), ( include_str!("../tests/dag/t_r3_gate_87_cementing_regen_provenance.dag"), "src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_provenance.dag", From 55c6416d68e3d289edb3a76f5ef7a1ca0fa9f300 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 14 May 2026 01:21:52 -0400 Subject: [PATCH 2/5] =?UTF-8?q?WIP:=20R3=20F-=CE=B1=20post-merge=20paralle?= =?UTF-8?q?lism=20cementing-receipt=20(gate=20#87=20corpus=20extensi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...e_87_lens_cementing_regen_receipts_test.rs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/v3/compiler/tests/integration/r3_gate_87_lens_cementing_regen_receipts_test.rs b/src/v3/compiler/tests/integration/r3_gate_87_lens_cementing_regen_receipts_test.rs index e158b2d3e31..9d279582102 100644 --- a/src/v3/compiler/tests/integration/r3_gate_87_lens_cementing_regen_receipts_test.rs +++ b/src/v3/compiler/tests/integration/r3_gate_87_lens_cementing_regen_receipts_test.rs @@ -162,6 +162,29 @@ fn r3_gate_87_effect_enumeration_rust_receipt_on_minimal_program() { ); } +#[test] +fn r3_gate_87_parallelism_rust_receipt_literal_no_workflow_projection() { + let dag = + compile_to_dag("let lit: Int = 7", "r3_gate_87_parallelism_receipt.v3").expect("compile"); + let bind = dag + .nodes() + .iter() + .find_map(|n| match n { + Behavior::Bind(b) if b.name == "lit" => Some(b), + _ => None, + }) + .expect("lit bind"); + let report = analyze_parallelism(&dag, bind.id); + assert!( + matches!( + &report, + WorkflowParallelismReport::ParallelismUnsupported(detail) + if detail.kind == ParallelismUnsupportedKind::NoWorkflowProjection + ), + "unstaged literal should classify as NoWorkflowProjection, got {report:?}" + ); +} + #[test] fn r3_gate_87_provenance_origin_rust_receipt_on_literal_bind() { let dag = From 1f50c9fbce6719474a1f58c56b4a4b6405b34e80 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 14 May 2026 02:35:23 -0400 Subject: [PATCH 3/5] =?UTF-8?q?WIP:=20R3=20F-=CE=B1=20post-merge=20paralle?= =?UTF-8?q?lism=20cementing-receipt=20(gate=20#87=20corpus=20extensi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/r3_gate_87_cementing_regen_runner_suites.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/v3/compiler/src/r3_gate_87_cementing_regen_runner_suites.rs b/src/v3/compiler/src/r3_gate_87_cementing_regen_runner_suites.rs index 88a856e107f..b0f4c1790eb 100644 --- a/src/v3/compiler/src/r3_gate_87_cementing_regen_runner_suites.rs +++ b/src/v3/compiler/src/r3_gate_87_cementing_regen_runner_suites.rs @@ -101,6 +101,12 @@ pub const R3_GATE_87_CEMENTING_REGEN_SUITES: &[(&str, &str, &str, &[&str])] = &[ "r3_gate_87_cementing_regen_lower_helpers_suite", &["cementing_regen_lower_helpers"], ), + ( + include_str!("../tests/dag/t_r3_gate_87_cementing_regen_parallelism.dag"), + "src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_parallelism.dag", + "r3_gate_87_cementing_regen_parallelism_suite", + &["cementing_regen_parallelism"], + ), ( include_str!("../tests/dag/t_r3_gate_87_cementing_regen_provenance.dag"), "src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_provenance.dag", From 5e5816a02da0e0dc135753d3de3bef8a4cdf27e4 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 14 May 2026 02:36:54 -0400 Subject: [PATCH 4/5] =?UTF-8?q?WIP:=20R3=20F-=CE=B1=20post-merge=20paralle?= =?UTF-8?q?lism=20cementing-receipt=20(gate=20#87=20corpus=20extensi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cementing_dispatch.rs.in | 1596 ----------------- 1 file changed, 1596 deletions(-) delete mode 100644 tools/pb0_cycle4_emit_templates/cementing_dispatch.rs.in diff --git a/tools/pb0_cycle4_emit_templates/cementing_dispatch.rs.in b/tools/pb0_cycle4_emit_templates/cementing_dispatch.rs.in deleted file mode 100644 index acd6ce806ed..00000000000 --- a/tools/pb0_cycle4_emit_templates/cementing_dispatch.rs.in +++ /dev/null @@ -1,1596 +0,0 @@ -//! Band-C cementing dispatch — [`crate::test_runner::TestPredicate::CementingDispatchMatchesProjection`]. -//! -//! SG-0 / INVARIANTS P5: PB-0 generated compiler module. The authoritative body lives in -//! `tools/pb0_cycle4_emit_templates/cementing_dispatch.rs.in`; `build.rs` -//! emits `src/v3/compiler/src/cementing_dispatch.rs` and registers it in -//! `REGEN_OUTPUTS` / `GENERATED_FILES`. -//! -//! Projects `LensRegistryEntry` rows from the bootstrapped `regen.dag` authority against -//! `std.verification` `lens_capability_register_rows` (closed coproduct status axes per -//! `docs/design-tests-as-data-completeness.md` §8.3), then validates the Band-C receipt list -//! in `cementing_dispatch.dag` and on-disk harness artifacts. -//! -//! **Interim host coupling:** validating `temporary-rust` receipts scans `tests/integration.rs` -//! once per predicate evaluation (see `std::fs::read_to_string` below). Dissolution: consume -//! reflected wiring facts at the runner edge so Band-C dispatch does not depend on crate layout -//! + host FS for this check. -//! -//! **Interim projection bridge:** `expected_cementing_receipt_triples` is a fail-closed Rust match -//! from `(registry_name, regen lens_file basename)` to the canonical receipt triples the -//! `cementing_band_c_v2_complete_receipts` list must equal. Dissolution: move that expansion into -//! `.dag` data (fixture or `std.verification`) so the predicate reads a single structural receipt -//! roster keyed off the register ∩ `regen.dag` projection, with no parallel Rust roster. - -pub mod integration_rs_wiring_scan { - //! `tests/integration.rs` wiring scanner (Band-C cementing dispatch). - //! - //! SG-0 / INVARIANTS P5: this scanner is hosted by the PB-0 generated - //! `cementing_dispatch.rs` output. Edit the template under - //! `tools/pb0_cycle4_emit_templates/`; `build.rs` refreshes the emitted - //! compiler module. - //! - //! **ROADMAP / dissolution:** same **v3 lens capability honesty pass** row as - //! `src/v3/compiler/tests/integration/common/wiring_scanner_test.rs` in `INVARIANTS.md` § SG-0 — - //! Band-C `#[path]` / `mod` wiring is enforced without `syn` until reflected integration facts exist. - //! **Dissolution:** remove this scanner when `tests/integration.rs` cementing module wiring can be - //! validated structurally (no line-oriented scan). Raw/byte string openers in **Code** return - //! `Err` so `CementingDispatchMatchesProjection` surfaces a claim failure instead of aborting the runner. - //! - //! Lives in the compiler crate so `test_runner` can validate - //! `#[path = "integration/cementing/.rs"]` + `mod ;` pairings without - //! duplicating the scanner across test binaries. - - /// `true` when `bytes[i..]` opens a `b"…"`, `br#"…"#`, `r#"…"#`, or `r"…"` literal in - /// Rust source. The wiring scanner does not model these — return an error if they appear - /// in `Code` so Band-C ratchets surface `ClaimResult::Fail` instead of aborting. - 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, - } - } - - /// Byte index after the last byte of a Rust escape sequence starting at `backslash` - /// (`bytes[backslash] == b'\\'`). Matches suffix forms used in character and byte - /// literals (`\n`, `\t`, `\xNN`, `\u{…}`, …). Unicode escapes use **only** the braced - /// `\u{…}` form in modern Rust; there is no separate `\uXXXX` tail to scan. - fn integration_rs_escape_sequence_end(bytes: &[u8], backslash: usize) -> usize { - debug_assert_eq!(bytes.get(backslash), Some(&b'\\')); - let i = backslash + 1; - if i >= bytes.len() { - return bytes.len(); - } - match bytes[i] { - b'x' => (backslash + 4).min(bytes.len()), - b'u' if bytes.get(i + 1) == Some(&b'{') => { - let mut j = i + 2; - while j < bytes.len() && bytes[j] != b'}' { - j += 1; - } - (j + 1).min(bytes.len()) - } - _ => (backslash + 2).min(bytes.len()), - } - } - - #[derive(Clone, Copy, PartialEq, Eq)] - enum IntegrationRsScan { - Code, - LineComment, - BlockComment(u32), - String, - /// Inside `b'…'` after consuming the `b'` prefix. - ByteCharLiteral, - /// Inside `'…'` after consuming the opening `'`. - CharLiteral, - } - - fn integration_rs_reject_raw_string_in_code(bytes: &[u8], i: usize) -> Result<(), String> { - if code_opens_raw_or_byte_string_literal(bytes, i) { - return Err(format!( - "integration_rs wiring scan: raw/byte string literal at byte offset {i} \ - (e.g. r\"…\", r#\"…\"#, b\"…\", br#\"…\"#). Extend IntegrationRsScan before scanning this file." - )); - } - Ok(()) - } - - /// Byte offsets of `needle` in `integration_rs` that occur in **Code** (outside - /// `//`, `/* */`, `"…"` strings, `b'…'` byte literals, and `'…'` character literals). - /// Lifetimes and labels (`'a`, `'static`) are skipped without entering string/literal - /// states. See [`integration_rs_active_line_contains`]. - fn integration_rs_code_substring_positions( - integration_rs: &str, - needle: &str, - ) -> Result, String> { - let mut hits = Vec::new(); - if needle.is_empty() { - hits.push(0); - return Ok(hits); - } - let bytes = integration_rs.as_bytes(); - let mut i = 0usize; - let mut state = IntegrationRsScan::Code; - - while i < bytes.len() { - match state { - IntegrationRsScan::Code => { - integration_rs_reject_raw_string_in_code(bytes, i)?; - 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.get(i) == Some(&b'b') && bytes.get(i + 1) == Some(&b'\'') { - state = IntegrationRsScan::ByteCharLiteral; - i += 2; - continue; - } - if bytes[i] == b'\'' { - if bytes.get(i + 1) == Some(&b'\\') { - state = IntegrationRsScan::CharLiteral; - i += 1; - continue; - } - if let Some(rest) = integration_rs.get(i + 1..) { - let mut chars = rest.chars(); - if let Some(c) = chars.next() { - let after_char = i + 1 + c.len_utf8(); - if bytes.get(after_char) == Some(&b'\'') { - i = after_char + 1; - continue; - } - } - } - i += 1; - while i < bytes.len() - && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') - { - i += 1; - } - continue; - } - if bytes[i] == b'"' { - state = IntegrationRsScan::String; - i += 1; - continue; - } - if integration_rs[i..].starts_with(needle) { - hits.push(i); - } - 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; - } - }, - IntegrationRsScan::ByteCharLiteral => { - if bytes.get(i) == Some(&b'\\') { - i = integration_rs_escape_sequence_end(bytes, i); - } else { - i += 1; - } - if bytes.get(i) == Some(&b'\'') { - i += 1; - } - state = IntegrationRsScan::Code; - } - IntegrationRsScan::CharLiteral => { - if bytes.get(i) == Some(&b'\\') { - i = integration_rs_escape_sequence_end(bytes, i); - } else if let Some(rest) = integration_rs.get(i..) { - let mut ci = rest.chars(); - if let Some(c) = ci.next() { - i += c.len_utf8(); - } else { - state = IntegrationRsScan::Code; - continue; - } - } else { - state = IntegrationRsScan::Code; - continue; - } - if bytes.get(i) == Some(&b'\'') { - i += 1; - } - state = IntegrationRsScan::Code; - } - } - } - Ok(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; - } - if !tail.starts_with(&prefix) { - return false; - } - let after_prefix = &tail[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, - ) -> Result { - 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 Ok(true); - } - } - Ok(false) - } - - /// True when `needle` appears in `integration_rs` **outside** Rust line comments - /// (`//` … including `///` / `//!`), **nested** block comments (`/* … */`), normal - /// `"…"` string literals, `b'…'` byte character literals, and `'…'` character - /// literals (excluding lifetimes / labels such as `'a` or `'static`). - /// - /// 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#"…"#`). If those appear in - /// scanned **Code**, this returns `Err` with an explicit message (so `TestPredicate` callers can - /// map to `ClaimResult::Fail`) until `IntegrationRsScan` is widened for those forms. - pub fn integration_rs_active_line_contains( - integration_rs: &str, - needle: &str, - ) -> Result { - Ok(!integration_rs_code_substring_positions(integration_rs, needle)?.is_empty()) - } -} - -pub mod r3_gate_87_cementing_regen_runner_suites { - //! R3 gate #87 — PB-B-1 runner table for `tests/dag/t_r3_gate_87_cementing_regen_*.dag` harnesses. - //! - //! **INVARIANTS P2 / single authority:** `R3_GATE_87_CEMENTING_REGEN_SUITES` is the only merge-visible - //! inventory for which harnesses `t_pb_b_1_dag_runner_test` executes. `cementing_dispatch` and - //! `r3_gate_87_lens_cementing_regen_receipts_test` consume the same table so `.dag` Band-C receipts - //! cannot pass `CementingDispatchMatchesProjection` without a matching runner row. - - use std::collections::BTreeSet; - use std::path::Path; - - /// `LensRegistryEntry.name` values implied by [`R3_GATE_87_CEMENTING_REGEN_SUITES`] harness `file` - /// paths (`t_r3_gate_87_cementing_regen_.dag`). **Single authority** for the gate-#87 - /// inventory ratchet: `r3_gate_87_lens_cementing_regen_receipts_test` compares live `regen.dag` - /// names against this set so a new registry row cannot ship without a matching runner row. - pub fn r3_gate_87_cementing_regen_lens_names_for_runner_table() -> BTreeSet { - R3_GATE_87_CEMENTING_REGEN_SUITES - .iter() - .map(|(_, file, _, _)| lens_name_from_gate_87_harness_path(file)) - .collect() - } - - /// Module stems (`tests/dag/.dag`) enumerated in [`R3_GATE_87_CEMENTING_REGEN_SUITES`]. - /// [`crate::cementing_dispatch::evaluate_cementing_dispatch_projection`] requires every - /// `kind == "dag"` receipt in `cementing_dispatch.dag` to use one of these stems so the dispatch - /// list cannot drift from the T-PB-B-1 runner. - pub fn r3_gate_87_cementing_regen_pb_b1_dag_module_stems() -> BTreeSet { - R3_GATE_87_CEMENTING_REGEN_SUITES - .iter() - .map(|(_, file, _, _)| { - Path::new(file) - .file_stem() - .and_then(|s| s.to_str()) - .unwrap_or_else(|| { - panic!("R3_GATE_87_CEMENTING_REGEN_SUITES: invalid harness path `{file}`") - }) - .to_string() - }) - .collect() - } - - // R3 gate #87 — every `LensRegistryEntry` in `src/v3/compiler/regen.dag` has a - // `src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_.dag` harness evaluated here. - const R3_GATE_87_CEMENTING_HARNESS_PATH_PREFIX: &str = - "src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_"; - const R3_GATE_87_CEMENTING_HARNESS_PATH_SUFFIX: &str = ".dag"; - - fn lens_name_from_gate_87_harness_path(file: &str) -> String { - file.strip_prefix(R3_GATE_87_CEMENTING_HARNESS_PATH_PREFIX) - .and_then(|rest| rest.strip_suffix(R3_GATE_87_CEMENTING_HARNESS_PATH_SUFFIX)) - .unwrap_or_else(|| { - panic!( - "R3_GATE_87_CEMENTING_REGEN_SUITES: harness path must be \ - `{prefix}{suffix}`, got `{file}`", - prefix = R3_GATE_87_CEMENTING_HARNESS_PATH_PREFIX, - suffix = R3_GATE_87_CEMENTING_HARNESS_PATH_SUFFIX, - ) - }) - .to_string() - } - - pub const R3_GATE_87_CEMENTING_REGEN_SUITES: &[(&str, &str, &str, &[&str])] = &[ - ( - include_str!("../tests/dag/t_r3_gate_87_cementing_regen_cost.dag"), - "src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_cost.dag", - "r3_gate_87_cementing_regen_cost_suite", - &[ - "cementing_regen_cost_merge_sort_lens_output_equals", - "cementing_regen_cost", - ], - ), - ( - include_str!("../tests/dag/t_r3_gate_87_cementing_regen_cost_symbolic.dag"), - "src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_cost_symbolic.dag", - "r3_gate_87_cementing_regen_cost_symbolic_suite", - &[ - "cementing_regen_cost_symbolic", - "cementing_regen_cost_symbolic_countdown", - ], - ), - ( - include_str!("../tests/dag/t_r3_gate_87_cementing_regen_cost_target_realization.dag"), - "src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_cost_target_realization.dag", - "r3_gate_87_cementing_regen_cost_target_realization_suite", - &["cementing_regen_cost_target_realization"], - ), - ( - include_str!("../tests/dag/t_r3_gate_87_cementing_regen_effect_enumeration.dag"), - "src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_effect_enumeration.dag", - "r3_gate_87_cementing_regen_effect_enumeration_suite", - &["cementing_regen_effect_enumeration"], - ), - ( - include_str!("../tests/dag/t_r3_gate_87_cementing_regen_infer_helpers.dag"), - "src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_infer_helpers.dag", - "r3_gate_87_cementing_regen_infer_helpers_suite", - &["cementing_regen_infer_helpers"], - ), - ( - include_str!("../tests/dag/t_r3_gate_87_cementing_regen_lower_helpers.dag"), - "src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_lower_helpers.dag", - "r3_gate_87_cementing_regen_lower_helpers_suite", - &["cementing_regen_lower_helpers"], - ), - ( - include_str!("../tests/dag/t_r3_gate_87_cementing_regen_parallelism.dag"), - "src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_parallelism.dag", - "r3_gate_87_cementing_regen_parallelism_suite", - &["cementing_regen_parallelism"], - ), - ( - include_str!("../tests/dag/t_r3_gate_87_cementing_regen_provenance.dag"), - "src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_provenance.dag", - "r3_gate_87_cementing_regen_provenance_suite", - &["cementing_regen_provenance"], - ), - ( - include_str!("../tests/dag/t_r3_gate_87_cementing_regen_structural_resolution.dag"), - "src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_structural_resolution.dag", - "r3_gate_87_cementing_regen_structural_resolution_suite", - &["cementing_regen_structural_resolution"], - ), - ( - include_str!("../tests/dag/t_r3_gate_87_cementing_regen_unused_parameters.dag"), - "src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_unused_parameters.dag", - "r3_gate_87_cementing_regen_unused_parameters_suite", - &["cementing_regen_unused_parameters"], - ), - ( - include_str!("../tests/dag/t_r3_gate_87_cementing_regen_variant_payload.dag"), - "src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_variant_payload.dag", - "r3_gate_87_cementing_regen_variant_payload_suite", - &["cementing_regen_variant_payload"], - ), - ]; -} - -pub mod gunbc_ci { - //! `gunbc.ci` workflow gate selection — **Layer 1** affected-set dispatch substrate. - //! - //! **Authority** (Phase B): `docs/briefs/r3-wave1-s6-slice7-affected-set-impl-worker.md`; - //! fail-closed behavior aligned with - //! `docs/design-t-wad-slice-7-binary-shim-affected-set-selection-canvas.md` §3 (superset - //! table) applied to **gate-id** touch seeds (see §4 for how end-state runners join - //! `NodeRef` / dimension receipts to obligations — **not implemented in this module**); - //! carrier shape: `dsl/gunbc/ci.dag` (`CIWorkflowDag`, `CIGateEdge`: `from` is a prerequisite of `to`). - //! - //! **Layer 1 vs canvas §7 / ROADMAP gate #103:** program row `ci_uses_affected_set_selection` - //! eventually requires the Slice-5 **BinaryShim** runner to wire PR #2713 affected-set - //! receipts through `CIWorkflowDag` / `TestClaim` metadata (canvas §§1.1–1.3, §4, §7). - //! This crate surface **does not** close that hand-off: there is no typed PR #2713 - //! receipt type here and no runner entry mapping `Set` into these APIs. - //! What lands here is the **pure graph planner** — [`select_affected_gates`] — plus a - //! thin [`CiBinaryShimAffectedSetReceipt`] adapter for **gate-id seeds only** (tests + - //! future runner glue once upstream produces `CIGate.id` touches). - //! - //! This module is intentionally **pure**: every decision is a function of - //! [`CiWorkflowDagInput`] + [`CiWorkflowDiff`] only. Git diffs, path-regex, env, - //! and PR #2713 lens output must be **mapped upstream** into [`CiWorkflowDiff`] (or a - //! superset-equivalent touch set) before calling [`select_affected_gates`]. - //! - //! **Verifier ratchet witness (Phase C scaffolding)** — `docs/design-ci-workflow-substrate-shape-2026-05-12.md` - //! S5 + prequeue §5.2: monotone **inclusion** under enlarging the touched-id - //! set (smaller touch ⊆ larger touch ⇒ selected subset ⊆ selected superset). - //! See [`selection_subset_under_touch_set_growth`]. - //! - //! **Expansion semantics:** [`select_affected_gates`] walks each `CIGateEdge` **symmetrically** - //! (treat `from → to` as an undirected adjacency) to fixpoint, so a downstream obligation - //! pulls in its sibling prerequisites (e.g. touching `lint` selects `l1-ratchet`, which - //! forces `tests` because both parents are prerequisites of `l1-ratchet`). The returned - //! order is a **topological sort** of that vertex set on the directed prerequisite subgraph. - //! - //! **Global carrier validity:** before expanding any touch seed, [`select_affected_gates`] requires - //! the **entire** directed prerequisite graph on [`CiWorkflowDagInput::gates`] to be acyclic. A cycle - //! confined to an unselected weak component must still fail closed (P3), not succeed in “narrow” mode. - - use std::collections::{BTreeSet, HashMap, HashSet, VecDeque}; - - /// Structural mirror of `CIGate.id` in `dsl/gunbc/ci.dag`. - pub type CiGateId = String; - - /// Structural mirror of `CIGate` in `dsl/gunbc/ci.dag`. - /// - /// [`select_affected_gates`] consults only [`CiGateMeta::id`]. [`CiGateMeta::blocking`] is carried - /// for **carrier parity** with the DSL record so runner-side dispatch can map rows without - /// a forked struct; merge-blocking vs advisory semantics stay **outside** this selection substrate - /// until a consumer reads the field. - #[derive(Debug, Clone, PartialEq, Eq)] - pub struct CiGateMeta { - pub id: CiGateId, - pub blocking: bool, - } - - /// Structural mirror of `CIWorkflowDag`: gate roster + prerequisite edges. - /// - /// Each edge `(from, to)` means **`from` must complete before `to`** (matches - /// `CIGateEdge { from, to }` in `ci.dag`). - #[derive(Debug, Clone, Default, PartialEq, Eq)] - pub struct CiWorkflowDagInput { - pub gates: Vec, - /// Prerequisite edges: `from` runs before `to`. - pub edges: Vec<(CiGateId, CiGateId)>, - } - - // Practice 4 (coproduct checkpoint, `docs/modeling-discipline.md` §4): - // 🟢 GREEN — terminal upstream-policy boundary for gate-id touch semantics before - // `select_affected_gates`: `TouchAll` is the conservative in-DAG superset seed; - // `TouchedGates` is the explicit finite set upstream mapped from lens/git facts. - // Ledger: no third authority here — path-regex, env, and PR #2713 lens receipts stay - // outside this module (Slice 7 brief + canvas §§1.1–4 end-state runner; Layer 1 only here). - /// Diff-against-base input projected to **gate ids** (single authority at this boundary). - #[derive(Debug, Clone, PartialEq, Eq)] - pub enum CiWorkflowDiff { - /// Every gate id in [`CiWorkflowDagInput::gates`] is treated as directly touched. - TouchAll, - /// Upstream bundle names gate ids with proven or assumed direct impact. - TouchedGates(BTreeSet), - } - - // Practice 4 (coproduct checkpoint, `docs/modeling-discipline.md` §4): - // 🟢 GREEN — terminal typed failure surface for [`select_affected_gates`]: malformed `CIWorkflowDag` - // carrier vs non-acyclic directed prerequisites (full roster graph and/or selected subset; no silent success). - // Ledger: (1) fact placement — not split across consumers; this `Result` boundary owns the `Err` - // coproduct. (2) variant-is-data — rejected; payloads differ structurally. (3) algebraic — not - // `std/` refs. (4) dimensional — not one decomposed axis space; each variant is an irreducible - // outcome at the `dsl/gunbc/ci.dag` structural mirror until `.dag`-owned dispatch absorbs diagnostics. - /// [`select_affected_gates`] failed: malformed `CIWorkflowDag` carrier or non-acyclic prerequisites. - #[derive(Debug, Clone, PartialEq, Eq)] - pub enum CiAffectedGatesError { - /// The same `CIGate.id` appears more than once in [`CiWorkflowDagInput::gates`] (malformed carrier; must not collapse into a `HashSet` silently). - DuplicateGateRosterId { id: CiGateId }, - /// `CIGateEdge` names a gate id not present in [`CiWorkflowDagInput::gates`] (violates single roster authority in `dsl/gunbc/ci.dag`). - UnknownEdgeEndpoint { from: CiGateId, to: CiGateId }, - /// The directed prerequisite graph is not a DAG: either the **full** roster graph from - /// [`CiWorkflowDagInput::edges`] contains a cycle, or the selected vertex set’s induced subgraph - /// does (broken indegree accounting). - NonAcyclicPrerequisiteGraph, - } - - /// On success, returns gate ids to execute: the **connected component** of the seed under the symmetric - /// closure of prerequisite edges (iterate `from ↔ to` adjacency to fixpoint), then - /// **topologically sorted** on the directed subgraph (prerequisites first). - /// - /// **Fail-closed** (`design-t-wad-slice-7-binary-shim-affected-set-selection-canvas.md` §3): - /// any touched id not present in `dag.gates` forces **all** gates listed in `dag.gates` - /// into the plan (superset within this DAG). - /// - /// **Empty diff** (`TouchedGates` empty): returns `Ok` of an **empty** plan — callers map that - /// to “no merge-blocking re-verify” vs “full superset” per repository policy **outside** - /// this function (this module does not read policy carriers). - /// - /// **Malformed carrier:** duplicate [`CiGateMeta::id`] values in [`CiWorkflowDagInput::gates`] - /// yield [`CiAffectedGatesError::DuplicateGateRosterId`] (roster must be unique before any `HashSet` - /// view so gates are not dropped silently). Any edge whose `from` or `to` is absent from that roster - /// yields [`CiAffectedGatesError::UnknownEdgeEndpoint`]. Any directed cycle in the **full** - /// prerequisite graph on the roster (not only the touch-selected component) yields - /// [`CiAffectedGatesError::NonAcyclicPrerequisiteGraph`] instead of returning `Ok` in narrow mode. - pub fn select_affected_gates( - dag: &CiWorkflowDagInput, - diff: &CiWorkflowDiff, - ) -> Result, CiAffectedGatesError> { - validate_unique_gate_roster(dag)?; - let known: HashSet<&str> = dag.gates.iter().map(|g| g.id.as_str()).collect(); - for (from, to) in &dag.edges { - if !known.contains(from.as_str()) || !known.contains(to.as_str()) { - return Err(CiAffectedGatesError::UnknownEdgeEndpoint { - from: from.clone(), - to: to.clone(), - }); - } - } - - // Fail-closed on the whole carrier: a cycle in an unselected component must not yield `Ok` - // for a disjoint touch seed (codex / P3). - topo_sort_subset(dag, &known)?; - - let force_superset = match diff { - CiWorkflowDiff::TouchAll => false, - CiWorkflowDiff::TouchedGates(ids) => ids.iter().any(|id| !known.contains(id.as_str())), - }; - - let seed: HashSet<&str> = if force_superset { - known.clone() - } else { - match diff { - CiWorkflowDiff::TouchAll => known.clone(), - CiWorkflowDiff::TouchedGates(ids) => ids.iter().map(|s| s.as_str()).collect(), - } - }; - - if seed.is_empty() { - return Ok(Vec::new()); - } - - // Symmetric edge closure: expand along `from ↔ to` until stable, then topo-sort - // the induced set on directed prerequisite edges. - let mut selected: HashSet<&str> = seed.clone(); - loop { - let before = selected.len(); - for (from, to) in &dag.edges { - let from = from.as_str(); - let to = to.as_str(); - if selected.contains(from) { - selected.insert(to); - } - if selected.contains(to) { - selected.insert(from); - } - } - if selected.len() == before { - break; - } - } - - topo_sort_subset(dag, &selected) - } - - fn validate_unique_gate_roster(dag: &CiWorkflowDagInput) -> Result<(), CiAffectedGatesError> { - let mut seen: HashSet<&str> = HashSet::new(); - for g in &dag.gates { - if !seen.insert(g.id.as_str()) { - return Err(CiAffectedGatesError::DuplicateGateRosterId { id: g.id.clone() }); - } - } - Ok(()) - } - - /// Set inclusion witness for ratchet tests. - /// - /// Returns whether `select_affected_gates(dag, narrow)` ⊆ `select_affected_gates(dag, wide)` as sets - /// when **both** calls succeed; if either returns `Err`, returns `false`. - /// Intended use: both arguments are [`CiWorkflowDiff::TouchedGates`] with `narrow_ids ⊆ wide_ids`, or - /// `wide` is [`CiWorkflowDiff::TouchAll`] (superset selection). Other pairings are not a refinement - /// partial order and may return `false` even when both plans are individually valid. - pub fn selection_subset_under_touch_set_growth( - dag: &CiWorkflowDagInput, - narrow: &CiWorkflowDiff, - wide: &CiWorkflowDiff, - ) -> bool { - match ( - select_affected_gates(dag, narrow), - select_affected_gates(dag, wide), - ) { - (Ok(a), Ok(b)) => { - let a: BTreeSet<_> = a.into_iter().collect(); - let b: BTreeSet<_> = b.into_iter().collect(); - a.is_subset(&b) - } - _ => false, - } - } - - /// **Layer 1** gate-id touch seed for [`select_affected_gates_for_binary_shim`]. - /// - /// Canvas `design-t-wad-slice-7-binary-shim-affected-set-selection-canvas.md` §1.1 - /// steps 3–5 and §4 require the **runner** to consume PR #2713 structured output, join - /// it with `CIWorkflowDag` / `TestClaim` metadata, and **then** emit executable work. - /// This struct is **not** that receipt: it only carries `CIGate.id` strings (and a - /// narrowing flag) **after** any `NodeRef` → gate-id mapping has happened elsewhere. - /// - /// When `narrowing_available` is `false`, selection fails closed to the full gate - /// roster ([`CiWorkflowDiff::TouchAll`]; canvas §3: unknown dimension / missing - /// receipt / unbuildable DAG pair). - #[derive(Debug, Clone, PartialEq, Eq, Default)] - pub struct CiBinaryShimAffectedSetReceipt { - /// `false` forces [`CiWorkflowDiff::TouchAll`] regardless of `proven_direct_gate_touches`. - pub narrowing_available: bool, - /// Gate ids proven directly touched **after** upstream mapping (seed before symmetric expansion). - pub proven_direct_gate_touches: BTreeSet, - } - - /// Layer 1 entry: map a **gate-id-only** [`CiBinaryShimAffectedSetReceipt`] into [`select_affected_gates`]. - pub fn select_affected_gates_for_binary_shim( - dag: &CiWorkflowDagInput, - receipt: &CiBinaryShimAffectedSetReceipt, - ) -> Result, CiAffectedGatesError> { - let diff = if receipt.narrowing_available { - CiWorkflowDiff::TouchedGates(receipt.proven_direct_gate_touches.clone()) - } else { - CiWorkflowDiff::TouchAll - }; - select_affected_gates(dag, &diff) - } - - fn topo_sort_subset( - dag: &CiWorkflowDagInput, - subset: &HashSet<&str>, - ) -> Result, CiAffectedGatesError> { - let mut indegree: HashMap<&str, usize> = HashMap::new(); - for id in subset.iter().copied() { - indegree.entry(id).or_insert(0); - } - for (from, to) in &dag.edges { - let from = from.as_str(); - let to = to.as_str(); - if subset.contains(from) && subset.contains(to) { - *indegree.entry(to).or_insert(0) += 1; - } - } - - let mut queue: VecDeque<&str> = indegree - .iter() - .filter(|(_, d)| **d == 0) - .map(|(n, _)| *n) - .collect::>() - .into_iter() - .collect(); - - let mut out: Vec = Vec::new(); - while let Some(n) = queue.pop_front() { - out.push(n.to_string()); - for (from, to) in &dag.edges { - if from.as_str() != n { - continue; - } - let to = to.as_str(); - if !subset.contains(to) { - continue; - } - if let Some(d) = indegree.get_mut(to) { - *d -= 1; - if *d == 0 { - queue.push_back(to); - } - } - } - } - - if out.len() != subset.len() { - return Err(CiAffectedGatesError::NonAcyclicPrerequisiteGraph); - } - Ok(out) - } - - #[cfg(test)] - mod tests { - use super::*; - - fn demo_ci_dag() -> CiWorkflowDagInput { - CiWorkflowDagInput { - gates: vec![ - CiGateMeta { - id: "compile-gates".into(), - blocking: true, - }, - CiGateMeta { - id: "lint".into(), - blocking: true, - }, - CiGateMeta { - id: "tests".into(), - blocking: true, - }, - CiGateMeta { - id: "l1-ratchet".into(), - blocking: true, - }, - ], - edges: vec![ - ("compile-gates".into(), "lint".into()), - ("compile-gates".into(), "tests".into()), - ("lint".into(), "l1-ratchet".into()), - ("tests".into(), "l1-ratchet".into()), - ], - } - } - - #[test] - fn select_affected_gates_empty_diff_returns_empty_plan() { - let dag = demo_ci_dag(); - let diff = CiWorkflowDiff::TouchedGates(BTreeSet::new()); - assert_eq!(select_affected_gates(&dag, &diff), Ok(Vec::new())); - } - - #[test] - fn select_affected_gates_touch_all_runs_full_dag_in_topo_order() { - let dag = demo_ci_dag(); - let got = - select_affected_gates(&dag, &CiWorkflowDiff::TouchAll).expect("demo dag is valid"); - assert_eq!( - got, - vec![ - "compile-gates".to_string(), - "lint".to_string(), - "tests".to_string(), - "l1-ratchet".to_string(), - ] - ); - } - - #[test] - fn select_affected_gates_isolated_gate_no_edges() { - let dag = CiWorkflowDagInput { - gates: vec![CiGateMeta { - id: "solo".into(), - blocking: true, - }], - edges: vec![], - }; - let diff = CiWorkflowDiff::TouchedGates(BTreeSet::from(["solo".into()])); - assert_eq!( - select_affected_gates(&dag, &diff), - Ok(vec!["solo".to_string()]) - ); - } - - #[test] - fn select_affected_gates_unknown_touch_id_forces_superset() { - let dag = demo_ci_dag(); - let diff = CiWorkflowDiff::TouchedGates(BTreeSet::from(["no-such-gate".into()])); - let got = select_affected_gates(&dag, &diff).expect("superset path is acyclic"); - let expect = select_affected_gates(&dag, &CiWorkflowDiff::TouchAll).expect("demo dag"); - assert_eq!(got, expect); - } - - #[test] - fn select_affected_gates_rejects_unknown_edge_endpoint() { - let dag = CiWorkflowDagInput { - gates: vec![CiGateMeta { - id: "solo".into(), - blocking: true, - }], - edges: vec![("solo".into(), "phantom".into())], - }; - let diff = CiWorkflowDiff::TouchedGates(BTreeSet::from(["solo".into()])); - assert_eq!( - select_affected_gates(&dag, &diff), - Err(CiAffectedGatesError::UnknownEdgeEndpoint { - from: "solo".into(), - to: "phantom".into(), - }) - ); - } - - #[test] - fn select_affected_gates_rejects_duplicate_gate_roster_ids() { - let dag = CiWorkflowDagInput { - gates: vec![ - CiGateMeta { - id: "dup".into(), - blocking: true, - }, - CiGateMeta { - id: "dup".into(), - blocking: false, - }, - ], - edges: vec![], - }; - assert_eq!( - select_affected_gates(&dag, &CiWorkflowDiff::TouchAll), - Err(CiAffectedGatesError::DuplicateGateRosterId { id: "dup".into() }) - ); - } - - #[test] - fn select_affected_gates_rejects_prerequisite_cycle() { - let dag = CiWorkflowDagInput { - gates: vec![ - CiGateMeta { - id: "a".into(), - blocking: true, - }, - CiGateMeta { - id: "b".into(), - blocking: true, - }, - ], - edges: vec![("a".into(), "b".into()), ("b".into(), "a".into())], - }; - assert_eq!( - select_affected_gates(&dag, &CiWorkflowDiff::TouchAll), - Err(CiAffectedGatesError::NonAcyclicPrerequisiteGraph) - ); - } - - #[test] - fn select_affected_gates_rejects_global_cycle_even_when_touch_isolated() { - let dag = CiWorkflowDagInput { - gates: vec![ - CiGateMeta { - id: "a".into(), - blocking: true, - }, - CiGateMeta { - id: "b".into(), - blocking: true, - }, - CiGateMeta { - id: "c".into(), - blocking: true, - }, - ], - edges: vec![("a".into(), "b".into()), ("b".into(), "a".into())], - }; - let diff = CiWorkflowDiff::TouchedGates(BTreeSet::from(["c".into()])); - assert_eq!( - select_affected_gates(&dag, &diff), - Err(CiAffectedGatesError::NonAcyclicPrerequisiteGraph) - ); - } - - #[test] - fn select_affected_gates_empty_touch_rejects_when_global_graph_has_cycle() { - let dag = CiWorkflowDagInput { - gates: vec![ - CiGateMeta { - id: "a".into(), - blocking: true, - }, - CiGateMeta { - id: "b".into(), - blocking: true, - }, - ], - edges: vec![("a".into(), "b".into()), ("b".into(), "a".into())], - }; - let diff = CiWorkflowDiff::TouchedGates(BTreeSet::new()); - assert_eq!( - select_affected_gates(&dag, &diff), - Err(CiAffectedGatesError::NonAcyclicPrerequisiteGraph) - ); - } - - #[test] - fn select_affected_gates_lint_only_pulls_l1_and_shared_prereqs() { - let dag = demo_ci_dag(); - let diff = CiWorkflowDiff::TouchedGates(BTreeSet::from(["lint".into()])); - let got = select_affected_gates(&dag, &diff).expect("demo dag"); - let set: BTreeSet<_> = got.iter().cloned().collect(); - assert_eq!( - set, - BTreeSet::from([ - "compile-gates".into(), - "lint".into(), - "tests".into(), - "l1-ratchet".into(), - ]) - ); - assert_eq!(got.first().map(String::as_str), Some("compile-gates")); - } - - #[test] - fn selection_subset_under_touch_set_growth_holds_on_demo_dag() { - let dag = demo_ci_dag(); - let narrow = CiWorkflowDiff::TouchedGates(BTreeSet::from(["lint".into()])); - let wide = - CiWorkflowDiff::TouchedGates(BTreeSet::from(["lint".into(), "tests".into()])); - assert!(selection_subset_under_touch_set_growth( - &dag, &narrow, &wide - )); - } - - #[test] - fn selection_subset_empty_diff_is_subset_of_nonempty() { - let dag = demo_ci_dag(); - let empty = CiWorkflowDiff::TouchedGates(BTreeSet::new()); - let wide = CiWorkflowDiff::TouchedGates(BTreeSet::from(["tests".into()])); - assert!(selection_subset_under_touch_set_growth(&dag, &empty, &wide)); - } - - #[test] - fn binary_shim_receipt_unknown_narrowing_matches_touch_all() { - let dag = demo_ci_dag(); - let receipt = CiBinaryShimAffectedSetReceipt { - narrowing_available: false, - proven_direct_gate_touches: BTreeSet::from(["lint".into()]), - }; - let got = select_affected_gates_for_binary_shim(&dag, &receipt).expect("valid dag"); - let expect = select_affected_gates(&dag, &CiWorkflowDiff::TouchAll).expect("valid dag"); - assert_eq!(got, expect); - } - - #[test] - fn binary_shim_receipt_narrow_lint_matches_touched_gates_lint() { - let dag = demo_ci_dag(); - let receipt = CiBinaryShimAffectedSetReceipt { - narrowing_available: true, - proven_direct_gate_touches: BTreeSet::from(["lint".into()]), - }; - let got = select_affected_gates_for_binary_shim(&dag, &receipt).expect("valid dag"); - let expect = select_affected_gates( - &dag, - &CiWorkflowDiff::TouchedGates(BTreeSet::from(["lint".into()])), - ) - .expect("valid dag"); - assert_eq!(got, expect); - } - - #[test] - fn binary_shim_receipt_empty_narrow_seed_yields_empty_plan() { - let dag = demo_ci_dag(); - let receipt = CiBinaryShimAffectedSetReceipt { - narrowing_available: true, - proven_direct_gate_touches: BTreeSet::new(), - }; - assert_eq!( - select_affected_gates_for_binary_shim(&dag, &receipt), - Ok(Vec::new()) - ); - } - } -} - -use self::integration_rs_wiring_scan::integration_rs_cementing_path_attr_binds_mod_stem; -use self::r3_gate_87_cementing_regen_runner_suites::r3_gate_87_cementing_regen_pb_b1_dag_module_stems; -use std::collections::{BTreeSet, HashSet}; -use std::path::Path as FsPath; - -use crate::dag::{Dag, FieldValue, LiteralBits, TypeConnective, ValueBody}; - -fn record_field<'a>(fields: &'a [(String, FieldValue)], label: &str) -> Option<&'a FieldValue> { - fields - .iter() - .find(|(candidate, _)| candidate == label) - .map(|(_, value)| value) -} - -fn string_field(fields: &[(String, FieldValue)], label: &str) -> Result { - match record_field(fields, label) { - Some(FieldValue::Literal(LiteralBits::String(value))) => Ok(value.clone()), - Some(other) => Err(format!("`{label}` must be a string literal, got {other:?}")), - None => Err(format!("record is missing string field `{label}`")), - } -} - -fn disj_variants( - dag: &Dag, - type_name: &str, -) -> Result, String> { - let sum_decl = dag.declaration_by_name(type_name).ok_or_else(|| { - format!( - "merged DAG missing nullary sum `{type_name}` — expected `std.verification` for lens \ - capability axes, or `tests/dag/cementing_dispatch.dag` for `CementingBandCReceiptKind`" - ) - })?; - let TypeConnective::Disj { variants } = &sum_decl.connective else { - return Err(format!("`{type_name}` must be a Disj coproduct")); - }; - Ok(variants.iter().map(|v| (v.label.clone(), v.ty)).collect()) -} - -fn decode_nullary_sum_variant( - dag: &Dag, - sum_type_name: &str, - value: &FieldValue, - field_label: &str, - record_role: &str, -) -> Result { - let variants = disj_variants(dag, sum_type_name)?; - let allowed: HashSet = variants.iter().map(|(_, id)| *id).collect(); - let FieldValue::Variant { - constructor, - payload, - } = value - else { - return Err(format!( - "{record_role} `{field_label}` must be a closed sum variant of `{sum_type_name}`; got {value:?}" - )); - }; - if !payload.is_empty() { - return Err(format!( - "{record_role} `{field_label}`: unexpected variant payload (expected nullary `{sum_type_name}` arm)" - )); - } - if !allowed.contains(constructor) { - return Err(format!( - "{record_role} `{field_label}`: constructor id {:?} is not a declared variant of `{sum_type_name}`", - constructor - )); - } - variants - .iter() - .find(|(_, id)| id == constructor) - .map(|(label, _)| label.clone()) - .ok_or_else(|| { - format!( - "{record_role} `{field_label}`: internal mismatch resolving `{sum_type_name}` variant" - ) - }) -} - -/// Maps `CementingBandCReceiptKind` variant labels to the stable wire tags used in -/// `expected_cementing_receipt_triples` and on-disk dispatch branches. -fn cementing_receipt_kind_wire_tag(sum_variant_label: &str) -> Result<&'static str, String> { - match sum_variant_label { - "DagHarness" => Ok("dag"), - "TemporaryRustModule" => Ok("temporary-rust"), - other => Err(format!( - "cementing receipt `kind`: unknown `CementingBandCReceiptKind` variant `{other}`" - )), - } -} - -fn decode_cementing_band_c_receipt_kind( - dag: &Dag, - value: &FieldValue, - field_label: &str, -) -> Result<&'static str, String> { - let label = decode_nullary_sum_variant( - dag, - "CementingBandCReceiptKind", - value, - field_label, - "cementing receipt row", - )?; - cementing_receipt_kind_wire_tag(&label) -} - -fn record_fields(value: &FieldValue) -> Option<&[(String, FieldValue)]> { - match value { - FieldValue::Record(fields) => Some(fields.as_slice()), - _ => None, - } -} - -fn resolve_declaration_ref_id( - value: &FieldValue, - field_label: &str, -) -> Result { - match value { - FieldValue::Reference(id) => Ok(*id), - FieldValue::Record(fields) if fields.is_empty() => Err(format!( - "`{field_label}`: DeclarationRef is the empty record literal {{}} — use an identifier \ - so lowering emits FieldValue::Reference(DeclarationId), not an empty record", - )), - other => Err(format!( - "`{field_label}`: expected FieldValue::Reference(DeclarationId) \ - for a DeclarationRef edge, got {other:?}" - )), - } -} - -fn list_items_of_declaration( - dag: &Dag, - id: crate::dag::DeclarationId, - role: &str, -) -> Result, String> { - let decl = dag.declaration(id); - match &decl.value_body { - Some(ValueBody::List(items)) => Ok(items.clone()), - other => Err(format!( - "`{role}` must resolve to a `List<…>` data declaration; got {:?} for `{}`", - other, - decl.name.as_deref().unwrap_or("") - )), - } -} - -fn read_lens_registry_name_lens_file_pairs(dag: &Dag) -> Result, String> { - let entry_type_id = dag - .declaration_by_name("LensRegistryEntry") - .map(|decl| decl.id) - .ok_or_else(|| "bootstrap must declare `LensRegistryEntry` (regen.dag)".to_string())?; - - let mut pairs = Vec::new(); - for decl in dag.declarations() { - if decl.meta_tag != Some(entry_type_id) { - continue; - } - let Some(ValueBody::Structural { fields }) = &decl.value_body else { - return Err(format!( - "lens registry entry `{}` must carry a structural value body", - decl.name.as_deref().unwrap_or("") - )); - }; - let binding = decl - .name - .clone() - .unwrap_or_else(|| "".to_string()); - pairs.push(( - string_field(fields, "name") - .map_err(|e| format!("lens registry entry `{binding}`: {e}"))?, - string_field(fields, "lens_file") - .map_err(|e| format!("lens registry entry `{binding}`: {e}"))?, - )); - } - Ok(pairs) -} - -/// Closed roster of Band-C `(registry_name, module_stem, kind)` receipts implied by the -/// current `regen.dag` × v2-complete register projection. When a new v2-complete lens -/// lands in `regen.dag`, extend this expansion in the same PR as `cementing_dispatch.dag` -/// receipt rows — otherwise the dispatch predicate fail-closes with an explicit error. -fn expected_cementing_receipt_triples( - registry_pairs: &[(String, String)], - basenames: &BTreeSet, -) -> Result, String> { - let mut out = BTreeSet::new(); - for (name, lens_file) in registry_pairs { - let basename = FsPath::new(lens_file) - .file_name() - .and_then(|s| s.to_str()) - .ok_or_else(|| { - format!("registry entry `{name}` has lens_file without basename: {lens_file}") - })? - .to_string(); - if !basenames.contains(&basename) { - continue; - } - match (name.as_str(), basename.as_str()) { - ("cost", "complexity.dag") => { - out.insert(( - name.clone(), - "t_r3_gate_87_cementing_regen_cost".to_string(), - "dag".to_string(), - )); - out.insert(( - name.clone(), - "complexity_lens_behavioral_completion".to_string(), - "temporary-rust".to_string(), - )); - } - ("cost_symbolic", "cost.dag") => { - out.insert(( - name.clone(), - "t_r3_gate_87_cementing_regen_cost_symbolic".to_string(), - "dag".to_string(), - )); - out.insert(( - name.clone(), - "cost_lens_symbolic_consumer_test".to_string(), - "temporary-rust".to_string(), - )); - } - _ => { - return Err(format!( - "Band-C v2 cementing projection includes `LensRegistryEntry` \ - (`name={name}`, `lens_file` basename `{basename}`) but \ - `expected_cementing_receipt_triples` has no receipt expansion — \ - land matching `cementing_band_c_v2_complete_receipts` rows in \ - `cementing_dispatch.dag` and extend the expansion table in \ - `cementing_dispatch.rs` in the same PR." - )); - } - } - } - Ok(out) -} - -fn v2_cementing_basenames_from_capability_rows( - dag: &Dag, - capability_rows: &[FieldValue], -) -> Result, String> { - let mut basenames = BTreeSet::new(); - for row in capability_rows { - let Some(fields) = record_fields(row) else { - return Err(format!( - "capability_register list: expected record row, got {row:?}" - )); - }; - let lens_basename = string_field(fields, "lens_basename")?; - let structural = record_field(fields, "structural").ok_or_else(|| { - "capability_register row: missing `structural` field (expected \ - `LensCapabilityStructuralStatus` variant)" - .to_string() - })?; - let behavioral = record_field(fields, "behavioral").ok_or_else(|| { - "capability_register row: missing `behavioral` field (expected \ - `LensCapabilityBehavioralStatus` variant)" - .to_string() - })?; - let v2 = record_field(fields, "v2_counterpart").ok_or_else(|| { - "capability_register row: missing `v2_counterpart` field (expected \ - `LensCapabilityV2Counterpart` variant)" - .to_string() - })?; - decode_nullary_sum_variant( - dag, - "LensCapabilityStructuralStatus", - structural, - "structural", - "capability_register row", - )?; - let behavioral_label = decode_nullary_sum_variant( - dag, - "LensCapabilityBehavioralStatus", - behavioral, - "behavioral", - "capability_register row", - )?; - let v2_label = decode_nullary_sum_variant( - dag, - "LensCapabilityV2Counterpart", - v2, - "v2_counterpart", - "capability_register row", - )?; - if behavioral_label == "LensCapabilityBehavioralComplete" - && v2_label == "LensCapabilityV2RealV2" - { - basenames.insert(lens_basename); - } - } - Ok(basenames) -} - -/// Lens basenames that participate in Band-C v2 cementing: `LensCapabilityBehavioralComplete` -/// plus `LensCapabilityV2RealV2` in the canonical `std.verification` `lens_capability_register_rows` -/// list (same projection `CementingDispatchMatchesProjection` uses before intersecting `regen.dag`). -/// -/// Exposed for integration tests that mechanically ratchet the markdown capability table against -/// this structural authority (`lens_register_correspondence_test`). -pub fn lens_capability_register_v2_cementing_basenames( - dag: &Dag, -) -> Result, String> { - let reg_decl = dag - .declaration_by_name("lens_capability_register_rows") - .ok_or_else(|| { - "bootstrap must declare `lens_capability_register_rows` (std.verification)".to_string() - })?; - let capability_rows = - list_items_of_declaration(dag, reg_decl.id, "lens_capability_register_rows")?; - v2_cementing_basenames_from_capability_rows(dag, &capability_rows) -} - -fn cementing_dispatch_two_refs( - payload: &[FieldValue], -) -> Result<(&FieldValue, &FieldValue), String> { - match payload { - [a, b] => Ok((a, b)), - [FieldValue::Record(fields)] => { - let a = record_field(fields, "capability_register").ok_or_else(|| { - "CementingDispatchMatchesProjection record missing `capability_register`" - .to_string() - })?; - let b = record_field(fields, "cementing_receipts").ok_or_else(|| { - "CementingDispatchMatchesProjection record missing `cementing_receipts`".to_string() - })?; - Ok((a, b)) - } - _ => Err(format!( - "CementingDispatchMatchesProjection payload should be two DeclarationRef edges \ - or a record {{ capability_register, cementing_receipts }}; got {} slot(s)", - payload.len() - )), - } -} - -/// Evaluate `CementingDispatchMatchesProjection` for a claim declared in `declaration_file`. -/// -/// Returns `Ok(())` on pass; `Err(reason)` maps to [`crate::test_runner::ClaimResult::Fail`]. -pub(crate) fn evaluate_cementing_dispatch_projection( - dag: &Dag, - declaration_file: &str, - payload: &[FieldValue], -) -> Result<(), String> { - const CEMENTING_DISPATCH_DECL_PATH: &str = "cementing_dispatch.dag"; - if !declaration_file.ends_with(CEMENTING_DISPATCH_DECL_PATH) { - return Err(format!( - "CementingDispatchMatchesProjection is only accepted for claims declared in `**/{CEMENTING_DISPATCH_DECL_PATH}`; got `{declaration_file}`" - )); - } - - let (reg_ref, recv_ref) = cementing_dispatch_two_refs(payload)?; - let reg_id = resolve_declaration_ref_id(reg_ref, "capability_register")?; - let recv_id = resolve_declaration_ref_id(recv_ref, "cementing_receipts")?; - - let capability_rows = list_items_of_declaration(dag, reg_id, "capability_register")?; - let receipt_rows = list_items_of_declaration(dag, recv_id, "cementing_receipts")?; - - let basenames = v2_cementing_basenames_from_capability_rows(dag, &capability_rows)?; - - let registry_pairs = read_lens_registry_name_lens_file_pairs(dag)?; - - let mut matched_basenames = BTreeSet::new(); - for (name, lens_file) in ®istry_pairs { - let Some(basename) = FsPath::new(lens_file).file_name().and_then(|s| s.to_str()) else { - return Err(format!( - "registry entry `{name}` has lens_file without basename: {lens_file}" - )); - }; - if basenames.contains(basename) { - matched_basenames.insert(basename.to_string()); - } - } - - let missing: Vec<_> = basenames.difference(&matched_basenames).cloned().collect(); - if !missing.is_empty() { - return Err(format!( - "`lens_capability_register` escalates v2 cementing for lens basenames {missing:?}, \ - but no `LensRegistryEntry` in bootstrap names those files — fix the register data or add registry entries." - )); - } - - let mut receipt_triples = BTreeSet::new(); - - for row in &receipt_rows { - let Some(fields) = record_fields(row) else { - return Err(format!( - "cementing_receipts list: expected record row, got {row:?}" - )); - }; - let registry_name = string_field(fields, "registry_name")?; - let module_stem = string_field(fields, "module_stem")?; - let kind_field = record_field(fields, "kind").ok_or_else(|| { - "cementing receipt row: missing `kind` field (expected `CementingBandCReceiptKind` \ - variant)" - .to_string() - })?; - let kind_str = decode_cementing_band_c_receipt_kind(dag, kind_field, "kind")?; - let triple = ( - registry_name.clone(), - module_stem.clone(), - kind_str.to_string(), - ); - if !receipt_triples.insert(triple.clone()) { - return Err(format!( - "cementing_receipts contains duplicate receipt identity {triple:?}; each \ - `(registry_name, module_stem, kind)` must be unique." - )); - } - } - - let expected_triples = expected_cementing_receipt_triples(®istry_pairs, &basenames)?; - if receipt_triples != expected_triples { - return Err(format!( - "cementing_receipts `(registry_name, module_stem, kind)` triples must exactly match \ - the Band-C expansion of the register ∩ regen projection — expected {expected_triples:?}, \ - got {receipt_triples:?}" - )); - } - - let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let pb_b1_gate87_dag_stems = r3_gate_87_cementing_regen_pb_b1_dag_module_stems(); - let integration_rs = match std::fs::read_to_string(manifest_dir.join("tests/integration.rs")) { - Ok(text) => text, - Err(err) => { - return Err(format!( - "read tests/integration.rs for cementing wiring ratchet: {err}" - )); - } - }; - - for row in &receipt_rows { - let Some(fields) = record_fields(row) else { - return Err(format!( - "cementing_receipts list: expected record row, got {row:?}" - )); - }; - let registry_name = string_field(fields, "registry_name")?; - let module_stem = string_field(fields, "module_stem")?; - let kind_field = record_field(fields, "kind").ok_or_else(|| { - "cementing receipt row: missing `kind` field (expected `CementingBandCReceiptKind` \ - variant)" - .to_string() - })?; - let kind_str = decode_cementing_band_c_receipt_kind(dag, kind_field, "kind")?; - match kind_str { - "dag" => { - if !pb_b1_gate87_dag_stems.contains(&module_stem) { - return Err(format!( - "registry lens `{registry_name}` lists v2-complete cementing receipt `{module_stem}` \ - as a `.dag` harness, but that stem is not wired in \ - `r3_gate_87_cementing_regen_runner_suites::R3_GATE_87_CEMENTING_REGEN_SUITES` \ - (T-PB-B-1 runner) — extend the shared table or fix the receipt list." - )); - } - let path = manifest_dir - .join("tests") - .join("dag") - .join(format!("{module_stem}.dag")); - if !path.is_file() { - return Err(format!( - "registry lens `{registry_name}` lists v2-complete cementing receipt `{module_stem}` \ - as a `.dag` harness; expected file {}", - path.display() - )); - } - } - "temporary-rust" => { - let path = manifest_dir - .join("tests") - .join("integration") - .join("cementing") - .join(format!("{module_stem}.rs")); - if !path.is_file() { - return Err(format!( - "registry lens `{registry_name}` lists temporary Rust cementing receipt `{module_stem}`; \ - expected file {}", - path.display() - )); - } - if !integration_rs_cementing_path_attr_binds_mod_stem( - &integration_rs, - &module_stem, - )? { - let expected = format!(r#"#[path = "integration/cementing/{module_stem}.rs"]"#); - return Err(format!( - "registry lens `{registry_name}` lists temporary Rust cementing stem `{module_stem}` but \ - tests/integration.rs does not bind `{expected}` to `mod {module_stem};` in the same item \ - (Band-C dispatch)." - )); - } - } - _ => { - return Err( - "cementing receipt `kind`: internal error — wire tag not `dag` or `temporary-rust`" - .to_string(), - ); - } - } - } - - Ok(()) -} From 2fd6203b6426d7499f2168a5f2200e4236b313fa Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 14 May 2026 03:03:55 -0400 Subject: [PATCH 5/5] =?UTF-8?q?WIP:=20R3=20F-=CE=B1=20post-merge=20paralle?= =?UTF-8?q?lism=20cementing-receipt=20(gate=20#87=20corpus=20extensi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/dag/t_r3_gate_87_cementing_regen_parallelism.dag | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_parallelism.dag b/src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_parallelism.dag index 09415839483..57afd8c45b0 100644 --- a/src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_parallelism.dag +++ b/src/v3/compiler/tests/dag/t_r3_gate_87_cementing_regen_parallelism.dag @@ -1,8 +1,13 @@ // R3 gate #87 — `LensRegistryEntry` `parallelism` (PARTIAL / v3-native per `verification.dag`). +// **INVARIANTS §P5(b) / `docs/r3-program-plan.md` §1.8 row #87** (`lens_cementing_test_discipline_complete`, +// T-Tests-As-Data-Completeness): this harness + `r3_gate_87_cementing_regen_runner_suites.rs` + +// `test_runner::eval_gate_87_cementing_projection` + `r3_gate_87_lens_cementing_regen_receipts_test` +// are the single merge-visible receipt bundle for the new registry row (no parallel hand list). // Narrow Int projection: a literal bind without Lane-2 workflow staging yields // `ParallelismUnsupportedKind::NoWorkflowProjection` from `analyze_parallelism` (runner seam: // `eval_gate_87_cementing_projection`). Dissolution: widen to a full `WorkflowParallelismReport` -// `LensOutputEquals` once report literals are authorable as `.dag` data. +// `LensOutputEquals` once report literals are authorable as `.dag` data (T-PB-B tests-as-data +// carrier lane; full behavioral completion remains `parallelism_lens_behaviorally_complete` / row #81). module v3.compiler.tests.t_r3.gate_87_cementing_regen_parallelism