From 43f92cd6a7fc6c4506849134efee3fa2486f9a5c Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 13 May 2026 19:40:36 -0400 Subject: [PATCH 01/10] =?UTF-8?q?WIP:=20PB-0=20cycle-4=20NON=5FTEST=20reti?= =?UTF-8?q?rement=20(msg=5F5d7f3491):=207=20paths=20=E2=80=94=20complexity?= =?UTF-8?q?=5Fla?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/v3/compiler/build.rs | 94 +- .../complexity_lattice.rs | 109 ++ .../cost_basis_declaration.rs | 165 ++ .../emit_collection_ops_method_contract.rs | 183 +++ .../emit_rust_bin_shim.rs | 92 ++ .../enforced_lens_application.rs | 1258 ++++++++++++++ .../int_literal_ranges.rs | 1016 ++++++++++++ .../pb_method_template_projection.rs | 1447 ++++++++++++++++ .../src/complexity_lattice_generated.rs | 115 ++ .../src/cost_basis_declaration_generated.rs | 171 ++ ...ollection_ops_method_contract_generated.rs | 189 +++ .../src/emit_rust_bin_shim_generated.rs | 98 ++ .../enforced_lens_application_generated.rs | 1264 ++++++++++++++ .../src/int_literal_ranges_generated.rs | 1022 ++++++++++++ ...pb_method_template_projection_generated.rs | 1453 +++++++++++++++++ 15 files changed, 8674 insertions(+), 2 deletions(-) create mode 100644 src/v3/compiler/pb0_cycle4_emit_templates/complexity_lattice.rs create mode 100644 src/v3/compiler/pb0_cycle4_emit_templates/cost_basis_declaration.rs create mode 100644 src/v3/compiler/pb0_cycle4_emit_templates/emit_collection_ops_method_contract.rs create mode 100644 src/v3/compiler/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs create mode 100644 src/v3/compiler/pb0_cycle4_emit_templates/enforced_lens_application.rs create mode 100644 src/v3/compiler/pb0_cycle4_emit_templates/int_literal_ranges.rs create mode 100644 src/v3/compiler/pb0_cycle4_emit_templates/pb_method_template_projection.rs create mode 100644 src/v3/compiler/src/complexity_lattice_generated.rs create mode 100644 src/v3/compiler/src/cost_basis_declaration_generated.rs create mode 100644 src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs create mode 100644 src/v3/compiler/src/emit_rust_bin_shim_generated.rs create mode 100644 src/v3/compiler/src/enforced_lens_application_generated.rs create mode 100644 src/v3/compiler/src/int_literal_ranges_generated.rs create mode 100644 src/v3/compiler/src/pb_method_template_projection_generated.rs diff --git a/src/v3/compiler/build.rs b/src/v3/compiler/build.rs index 8a22d46a99b..0127dc7881f 100644 --- a/src/v3/compiler/build.rs +++ b/src/v3/compiler/build.rs @@ -436,6 +436,86 @@ fn emit_r1_gates_fixture(manifest_path: &Path, v3_dir: &Path) { /// **Hygiene:** `repeat_string` exists in the bootstrap bundle only via this excerpt — no /// hand-maintained duplicate of `render.dag` (INVARIANTS P2). Edit the marked region in /// `render.dag` only; this output is overwritten on `cargo build -p v3-compiler`. +/// Emit PB-0 cycle-4 host authority modules (former `EXPECTED_HAND_AUTHORED_NON_TEST` paths) from +/// checked-in templates so `REGEN_OUTPUTS` / `GENERATED_FILES` stay the sole SG-0 partition +/// authority (`sg0_v3_hand_authored_census`). +/// +/// Dissolution: edit files under `pb0_cycle4_emit_templates/` (next to this build script) and +/// rebuild; do not hand-edit `*_generated.rs` outputs. +fn emit_pb0_cycle4_host_authority_modules(repo_root: &Path, manifest_path: &Path) { + const HEADER: &str = concat!( + "// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`).\n", + "//\n", + "// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate).\n", + "// Dissolution receipt: PB-0 NON_TEST retirement — former hand module.\n", + "//\n", + "\n", + ); + let tmpl_dir = manifest_path.join("pb0_cycle4_emit_templates"); + let specs: &[(&str, &str)] = &[ + ( + "complexity_lattice.rs", + "src/v3/compiler/src/complexity_lattice_generated.rs", + ), + ( + "cost_basis_declaration.rs", + "src/v3/compiler/src/cost_basis_declaration_generated.rs", + ), + ( + "emit_collection_ops_method_contract.rs", + "src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs", + ), + ( + "emit_rust_bin_shim.rs", + "src/v3/compiler/src/emit_rust_bin_shim_generated.rs", + ), + ( + "enforced_lens_application.rs", + "src/v3/compiler/src/enforced_lens_application_generated.rs", + ), + ( + "int_literal_ranges.rs", + "src/v3/compiler/src/int_literal_ranges_generated.rs", + ), + ( + "pb_method_template_projection.rs", + "src/v3/compiler/src/pb_method_template_projection_generated.rs", + ), + ]; + for (tmpl_name, rel_out) in specs { + let src_path = tmpl_dir.join(tmpl_name); + println!("cargo:rerun-if-changed={}", src_path.display()); + let body = fs::read_to_string(&src_path).unwrap_or_else(|e| { + panic!( + "emit_pb0_cycle4_host_authority_modules: read template {}: {e}", + src_path.display() + ) + }); + let out_path = repo_root.join(rel_out); + if let Some(parent) = out_path.parent() { + fs::create_dir_all(parent).unwrap_or_else(|e| { + panic!( + "emit_pb0_cycle4_host_authority_modules: mkdir {}: {e}", + parent.display() + ) + }); + } + let generated = format!("{HEADER}{body}"); + let needs_write = match fs::read_to_string(&out_path) { + Ok(existing) => existing != generated, + Err(_) => true, + }; + if needs_write { + fs::write(&out_path, generated).unwrap_or_else(|e| { + panic!( + "emit_pb0_cycle4_host_authority_modules: write {}: {e}", + out_path.display() + ) + }); + } + } +} + fn emit_render_repeat_string_bootstrap(repo_root: &Path) { const BEGIN: &str = "// GUNBC_BOOTSTRAP_EMIT_BEGIN\n"; const END: &str = "// GUNBC_BOOTSTRAP_EMIT_END"; @@ -529,6 +609,7 @@ fn main() { let src_dir = v3_dir.parent().expect("v3 dir has parent"); let repo_root = src_dir.parent().expect("src dir has parent"); emit_render_repeat_string_bootstrap(repo_root); + emit_pb0_cycle4_host_authority_modules(repo_root, &manifest_path); let std_dir = v3_dir.join("std"); let spec_dir = v3_dir.join("spec"); let compiler_dir = manifest_path.clone(); @@ -658,6 +739,9 @@ fn main() { "src/v3/compiler/src/bootstrap_generated.rs", "src/v3/compiler/src/bootstrap_generated_without_parse_surface.rs", "src/v3/compiler/src/bootstrap_std_generated.rs", + // PB-0 cycle-4: former hand `EXPECTED_HAND_AUTHORED_NON_TEST` modules — templates under + // `pb0_cycle4_emit_templates/`, emitted by `emit_pb0_cycle4_host_authority_modules`. + "src/v3/compiler/src/complexity_lattice_generated.rs", // SG-5 substrate / runtime-mirror projections, generated from // `src/v3/std/substrate.dag` + `src/v3/std/parse_surface.dag` // and consumed by hand-authored Rust via `include!(...)`. @@ -666,15 +750,20 @@ fn main() { // outputs, so the content header alone never masquerades as // generated (SG-0's `sg0_generated_partition_is_producer_owned` // invariant). + "src/v3/compiler/src/complexity_lens_generated.rs", + "src/v3/compiler/src/cost_basis_declaration_generated.rs", "src/v3/compiler/src/dag_branch_generated.rs", "src/v3/compiler/src/dag_cluster_generated.rs", - "src/v3/compiler/src/dag_lookup_generated.rs", "src/v3/compiler/src/dag_cost_generated.rs", + "src/v3/compiler/src/dag_lookup_generated.rs", "src/v3/compiler/src/dag_scalar_generated.rs", "src/v3/compiler/src/dag_value_body_generated.rs", "src/v3/compiler/src/diagnostics_generated.rs", + "src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs", + "src/v3/compiler/src/emit_rust_bin_shim_generated.rs", + "src/v3/compiler/src/enforced_lens_application_generated.rs", "src/v3/compiler/src/infer_helpers_generated.rs", - "src/v3/compiler/src/complexity_lens_generated.rs", + "src/v3/compiler/src/int_literal_ranges_generated.rs", "src/v3/compiler/src/lens_cost_symbolic_generated.rs", "src/v3/compiler/src/lens_cost_target_realization_generated.rs", "src/v3/compiler/src/lens_effect_enumeration_generated.rs", @@ -684,6 +773,7 @@ fn main() { "src/v3/compiler/src/lens_unused_parameters_generated.rs", "src/v3/compiler/src/lower_helpers_generated.rs", "src/v3/compiler/src/operators_generated.rs", + "src/v3/compiler/src/pb_method_template_projection_generated.rs", "src/v3/compiler/src/parse_generated.rs", "src/v3/compiler/src/parse_surface_generated.rs", "src/v3/compiler/src/parse_tables_generated.rs", diff --git a/src/v3/compiler/pb0_cycle4_emit_templates/complexity_lattice.rs b/src/v3/compiler/pb0_cycle4_emit_templates/complexity_lattice.rs new file mode 100644 index 00000000000..090827fd13c --- /dev/null +++ b/src/v3/compiler/pb0_cycle4_emit_templates/complexity_lattice.rs @@ -0,0 +1,109 @@ +//! **`complexity_enforcement_budget_dominates`** — executable **T-LAS enforcement** +//! ordering on `AsymptoticClass` for `complexity_enforcement_violates` / +//! `EnforcedApplication` (gate #92). It matches `v3.std.algebra::asymptotic_dominates` +//! on every arm including `ClassPolynomial×ClassPolynomial` (Peano `degree` compare). +//! +//! **`asymptotic_dominates`** is a backward-compatible alias for tests and older +//! call sites; new enforcement code should call [`complexity_enforcement_budget_dominates`]. +//! +//! Stay aligned with `complexity.dag` + `complexity_enforcement_violates` comments. + +use crate::dag::{positive_descent_count, AsymptoticClass}; + +pub fn complexity_enforcement_budget_dominates(a: &AsymptoticClass, b: &AsymptoticClass) -> bool { + use AsymptoticClass::*; + match a { + ClassUnknown => true, + ClassExponential => match b { + ClassUnknown => false, + ClassExponential + | ClassPolynomial { .. } // Any `ClassPolynomial` tier is below exponential. + | ClassQuadratic + | ClassLinearithmic + | ClassLinear + | ClassLog + | ClassConstant => true, + }, + ClassPolynomial { degree: da } => match b { + ClassUnknown | ClassExponential => false, + ClassPolynomial { degree: db } => { + positive_descent_count(da) >= positive_descent_count(db) + } + ClassQuadratic | ClassLinearithmic | ClassLinear | ClassLog | ClassConstant => true, + }, + ClassQuadratic => match b { + ClassConstant | ClassLog | ClassLinear | ClassLinearithmic | ClassQuadratic => true, + ClassPolynomial { .. } | ClassExponential | ClassUnknown => false, + }, + ClassLinearithmic => match b { + ClassConstant | ClassLog | ClassLinear | ClassLinearithmic => true, + ClassQuadratic | ClassPolynomial { .. } | ClassExponential | ClassUnknown => false, + }, + ClassLinear => match b { + ClassConstant | ClassLog | ClassLinear => true, + ClassLinearithmic + | ClassQuadratic + | ClassPolynomial { .. } + | ClassExponential + | ClassUnknown => false, + }, + ClassLog => match b { + ClassConstant | ClassLog => true, + ClassLinear + | ClassLinearithmic + | ClassQuadratic + | ClassPolynomial { .. } + | ClassExponential + | ClassUnknown => false, + }, + ClassConstant => match b { + ClassConstant => true, + ClassLog + | ClassLinear + | ClassLinearithmic + | ClassQuadratic + | ClassPolynomial { .. } + | ClassExponential + | ClassUnknown => false, + }, + } +} + +/// Alias of [`complexity_enforcement_budget_dominates`]. Prefer the named helper for +/// new call sites so enforcement-grade poly refinement is not confused with substrate +/// `algebra.dag::asymptotic_dominates` at the documentation layer. +#[inline] +pub fn asymptotic_dominates(a: &AsymptoticClass, b: &AsymptoticClass) -> bool { + complexity_enforcement_budget_dominates(a, b) +} + +#[cfg(test)] +mod asymptotic_dominates_tests { + use super::{asymptotic_dominates, complexity_enforcement_budget_dominates}; + use crate::dag::{positive_amount_from_i64, AsymptoticClass}; + + fn poly(k: i64) -> AsymptoticClass { + AsymptoticClass::ClassPolynomial { + degree: positive_amount_from_i64(k).expect("test degree in range"), + } + } + + #[test] + fn polynomial_degree_orders_within_class_polynomial() { + let p5 = poly(5); + let p3 = poly(3); + assert!(complexity_enforcement_budget_dominates(&p5, &p3)); + assert!(!complexity_enforcement_budget_dominates(&p3, &p5)); + assert!(complexity_enforcement_budget_dominates(&p3, &p3)); + } + + #[test] + fn alias_matches_budget_dominates() { + let p3 = poly(3); + let p5 = poly(5); + assert_eq!( + asymptotic_dominates(&p5, &p3), + complexity_enforcement_budget_dominates(&p5, &p3) + ); + } +} diff --git a/src/v3/compiler/pb0_cycle4_emit_templates/cost_basis_declaration.rs b/src/v3/compiler/pb0_cycle4_emit_templates/cost_basis_declaration.rs new file mode 100644 index 00000000000..bf59e3a404b --- /dev/null +++ b/src/v3/compiler/pb0_cycle4_emit_templates/cost_basis_declaration.rs @@ -0,0 +1,165 @@ +//! Materialize [`CostBasisDeclaration`](crate::lens_cost_symbolic::CostBasisDeclaration) from +//! lowered [`Dag`](crate::dag::Dag) facts — interim **consumer boundary** for gate **#93** +//! (`crdt_cost_basis_demonstrated`) **substrate / symbolic-cost slice**; folding authored basis +//! rows into [`compute_symbolic_costs`](crate::lens_cost_symbolic::compute_symbolic_costs) remains follow-on on the same gate. +//! +//! Persisted `.dag` `data` rows and folding declarations into [`compute_symbolic_costs`](crate::lens_cost_symbolic::compute_symbolic_costs) remain follow-on; embedders MUST NOT +//! hand-assemble `CostBasisDeclaration` beside this helper when deriving the same evidence +//! from a compiled DAG (INVARIANTS.md P2). + +use std::fmt; + +use crate::dag::{ + ArrowBody, BindNode, Dag, DeclarationId, Lookup, PortId, SectionRef, SizeVariable, + SymbolicCost, TypeConnective, +}; +use crate::lens_cost_symbolic::{compute_symbolic_costs, CostBasisDeclaration, CostBasisKind}; + +/// [`try_build_per_write_log_cost_basis_declaration`] stops on missing DAG facts instead of +/// panicking (fail-closed consumer boundary; codex gate on PR #2299). +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CostBasisDeclarationBuildError { + DeclarationNotFound { + name: String, + }, + MergeStepMissingParams { + merge_step_declaration_name: String, + }, + /// Declaration must be a function (`TypeConnective::Arrow`) so the body bind is + /// reachable via [`ArrowBody::UserDefined`] (single authority per P2). + WorkflowDeclarationNotArrow { + name: String, + }, + /// Arrow declaration body is not yet lowered to `UserDefined(BindNodeId)` — cannot read + /// the authoritative body [`BindNode`] for parameters / span / structural parity. + WorkflowBodyNotUserDefinedBind { + name: String, + }, + /// `compute_symbolic_costs` has no `Hit(LogCost)` row keyed to the merge step’s first + /// parameter port — cannot fabricate a per-write log basis without lens evidence of a + /// divide on that port (P2 / fail-closed). + MergeStepLacksLogCostWitness { + merge_step_declaration_name: String, + }, +} + +impl fmt::Display for CostBasisDeclarationBuildError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CostBasisDeclarationBuildError::DeclarationNotFound { name } => { + write!(f, "declaration `{name}` not found") + } + CostBasisDeclarationBuildError::MergeStepMissingParams { + merge_step_declaration_name, + } => write!( + f, + "declaration `{merge_step_declaration_name}` should declare at least one parameter on its body bind" + ), + CostBasisDeclarationBuildError::WorkflowDeclarationNotArrow { name } => write!( + f, + "declaration `{name}` must have Arrow connective (callable)" + ), + CostBasisDeclarationBuildError::WorkflowBodyNotUserDefinedBind { name } => write!( + f, + "declaration `{name}` body is not ArrowBody::UserDefined(BindNodeId); cannot resolve authoritative bind node" + ), + CostBasisDeclarationBuildError::MergeStepLacksLogCostWitness { + merge_step_declaration_name, + } => write!( + f, + "declaration `{merge_step_declaration_name}` first param port has no LogCost witness in `compute_symbolic_costs` (expected divide-on-dividend lowering)" + ), + } + } +} + +impl std::error::Error for CostBasisDeclarationBuildError {} + +fn user_defined_arrow_body_bind<'a>( + dag: &'a Dag, + declaration_name: &str, +) -> Result<(&'a BindNode, DeclarationId), CostBasisDeclarationBuildError> { + let decl = dag.declaration_by_name(declaration_name).ok_or_else(|| { + CostBasisDeclarationBuildError::DeclarationNotFound { + name: declaration_name.to_string(), + } + })?; + let subject = decl.id; + let bind = match &decl.connective { + TypeConnective::Arrow { body, .. } => match body { + ArrowBody::UserDefined(bind_id) => bind_id.bind_opt(dag).ok_or_else(|| { + CostBasisDeclarationBuildError::WorkflowBodyNotUserDefinedBind { + name: declaration_name.to_string(), + } + })?, + _ => { + return Err( + CostBasisDeclarationBuildError::WorkflowBodyNotUserDefinedBind { + name: declaration_name.to_string(), + }, + ); + } + }, + _ => { + return Err( + CostBasisDeclarationBuildError::WorkflowDeclarationNotArrow { + name: declaration_name.to_string(), + }, + ); + } + }; + Ok((bind, subject)) +} + +fn symbolic_table_includes_log_on_port(dag: &Dag, port: PortId) -> bool { + compute_symbolic_costs(dag).iter().any(|e| { + matches!( + &e.cost, + Lookup::Hit(SymbolicCost::LogCost { _0: sv }) if sv.source_port == port + ) + }) +} + +/// **`LogCost` witness:** returns [`MergeStepLacksLogCostWitness`] unless +/// [`compute_symbolic_costs`] already **`Hit`s `LogCost(merge_param_port)`** (divide-on-dividend +/// lowering); does not fabricate basis without lens table evidence. +/// **Subject** is `SectionRef::DeclarationScope { declaration: … }` wrapping the workflow +/// [`DeclarationId`] (T-LAS / audit structural authority — expression-scoped bases use +/// `NodeScope` when authors need it). +/// `merge_step_declaration_name` is the **`fn` declaration id** (same string the author uses for +/// the merge helper, e.g. `crdt_merge_step`); its body [`BindNode`] is read only via +/// [`TypeConnective::Arrow`] → [`ArrowBody::UserDefined`] like the workflow (P2 single authority +/// for both callables). +pub fn try_build_per_write_log_cost_basis_declaration( + dag: &Dag, + workflow_declaration_name: &str, + merge_step_declaration_name: &str, +) -> Result { + let (workflow_bind, subject) = user_defined_arrow_body_bind(dag, workflow_declaration_name)?; + let (merge_bind, _) = user_defined_arrow_body_bind(dag, merge_step_declaration_name)?; + let merge_replicas_port = *merge_bind.params.first().ok_or_else(|| { + CostBasisDeclarationBuildError::MergeStepMissingParams { + merge_step_declaration_name: merge_step_declaration_name.to_string(), + } + })?; + if !symbolic_table_includes_log_on_port(dag, merge_replicas_port) { + return Err( + CostBasisDeclarationBuildError::MergeStepLacksLogCostWitness { + merge_step_declaration_name: merge_step_declaration_name.to_string(), + }, + ); + } + Ok(CostBasisDeclaration { + subject: SectionRef::DeclarationScope { + declaration: subject, + }, + kind: CostBasisKind::PerWrite, + cost: SymbolicCost::LogCost { + _0: SizeVariable { + source_port: merge_replicas_port, + display_name: None, + }, + }, + span: workflow_bind.span.clone(), + }) +} diff --git a/src/v3/compiler/pb0_cycle4_emit_templates/emit_collection_ops_method_contract.rs b/src/v3/compiler/pb0_cycle4_emit_templates/emit_collection_ops_method_contract.rs new file mode 100644 index 00000000000..78cadcb422f --- /dev/null +++ b/src/v3/compiler/pb0_cycle4_emit_templates/emit_collection_ops_method_contract.rs @@ -0,0 +1,183 @@ +//! `CollectionOps.*_contract` fields cross `DeclarationRef` boundaries. Emit +//! consumers must fail closed on the **algebra identity** (P1): each referenced +//! declaration must be a `MethodTemplateContract` data binding whose +//! `dag_method.decl` resolves to the expected registry `*_method` row. + +use super::method_emit_template_variant_label; +use crate::dag::{Dag, DeclarationId, FieldValue, LiteralBits, TypeConnective, ValueBody}; + +/// 🟢 TERMINAL at the CollectionOps contract-consumer projection boundary. +/// This mirrors the substrate `MethodEmitTemplate` coproduct exactly so Rust +/// CollectionOps can choose the HO inline rendering without flattening or +/// copying template text into a third authority. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum MethodTemplateContractEmitTemplate { + SingleTemplate(String), + HigherOrderTemplates { + inline_template: String, + fn_ref_template: String, + }, +} + +/// Fail closed unless `contract_decl` is a `MethodTemplateContract` instance +/// whose `dag_method.decl` resolves to `expected_method_decl`. +pub(crate) fn require_method_template_contract_dag_method( + dag: &Dag, + contract_decl: DeclarationId, + collection_ops_field: &'static str, + expected_method_decl: DeclarationId, +) -> Result<(), &'static str> { + let fields = method_template_contract_fields(dag, contract_decl)?; + require_method_template_contract_fields_dag_method( + fields, + collection_ops_field, + expected_method_decl, + ) +} + +pub(crate) fn method_template_contract_decl_emit_template( + dag: &Dag, + contract_decl: DeclarationId, + collection_ops_field: &'static str, + expected_method_decl: DeclarationId, +) -> Result { + let fields = method_template_contract_fields(dag, contract_decl)?; + require_method_template_contract_fields_dag_method( + fields, + collection_ops_field, + expected_method_decl, + )?; + method_template_contract_fields_emit_template(dag, fields) +} + +fn method_template_contract_fields( + dag: &Dag, + contract_decl: DeclarationId, +) -> Result<&[(String, FieldValue)], &'static str> { + let mtc_type_id = dag + .method_template_contract_decl() + .ok_or("internal: MethodTemplateContract type missing from dag")?; + + let decl = dag.declaration(contract_decl); + let template = match &decl.connective { + TypeConnective::Instantiation { template, .. } => *template, + _ => { + return Err( + "CollectionOps contract must reference a MethodTemplateContract data binding", + ); + } + }; + if template != mtc_type_id { + return Err("CollectionOps contract must instantiate MethodTemplateContract"); + } + + let Some(body) = decl.value_body.as_ref() else { + return Err( + "CollectionOps contract referenced declaration must carry a structural value body", + ); + }; + let fields = match body { + ValueBody::Structural { fields } => fields, + _ => { + return Err( + "CollectionOps contract referenced declaration must be structural MethodTemplateContract data", + ); + } + }; + Ok(fields) +} + +fn require_method_template_contract_fields_dag_method( + fields: &[(String, FieldValue)], + collection_ops_field: &'static str, + expected_method_decl: DeclarationId, +) -> Result<(), &'static str> { + let method_id = method_template_contract_fields_dag_method(fields)?; + if method_id != expected_method_decl { + return Err(match collection_ops_field { + "fold_contract" => "CollectionOps.fold_contract must target fold_method", + "concat_contract" => "CollectionOps.concat_contract must target concat_method", + "length_contract" => "CollectionOps.length_contract must target length_method", + "filter_contract" => "CollectionOps.filter_contract must target filter_method", + "flat_map_contract" => "CollectionOps.flat_map_contract must target flat_map_method", + "any_contract" => "CollectionOps.any_contract must target any_method", + "all_contract" => "CollectionOps.all_contract must target all_method", + "map_contract" => "CollectionOps.map_contract must target map_method", + "is_empty_contract" => { + "CollectionOps.is_empty_contract must target is_empty_method (FreeMonoid.is_empty -> Bool)" + } + _ => "CollectionOps contract dag_method does not match expected registry method", + }); + } + + Ok(()) +} + +fn method_template_contract_fields_dag_method( + fields: &[(String, FieldValue)], +) -> Result { + let dag_method = fields + .iter() + .find(|(l, _)| l == "dag_method") + .map(|(_, v)| v) + .ok_or("MethodTemplateContract missing dag_method field")?; + + let FieldValue::Record(method_ref_fields) = dag_method else { + return Err("MethodTemplateContract.dag_method must be a MethodRef record"); + }; + + let method_id = match method_ref_fields.as_slice() { + [(label, FieldValue::Reference(id))] if label == "decl" => *id, + _ => { + return Err( + "MethodTemplateContract.dag_method must be exactly `{ decl: DeclarationRef }` (MethodRef)", + ); + } + }; + Ok(method_id) +} + +fn method_template_contract_fields_emit_template( + dag: &Dag, + fields: &[(String, FieldValue)], +) -> Result { + let emit_value = fields + .iter() + .find(|(label, _)| label == "emit_template") + .map(|(_, v)| v) + .ok_or("MethodTemplateContract missing emit_template field")?; + let FieldValue::Variant { + constructor, + payload, + } = emit_value + else { + return Err("MethodTemplateContract.emit_template must be a sum variant"); + }; + let ctor_name = method_emit_template_variant_label(dag, *constructor).ok_or( + "MethodTemplateContract.emit_template variant not found under MethodEmitTemplate disj", + )?; + match ctor_name { + "SingleTemplate" => { + let [FieldValue::Literal(LiteralBits::String(template))] = payload.as_slice() else { + return Err("SingleTemplate must carry exactly one string template payload"); + }; + Ok(MethodTemplateContractEmitTemplate::SingleTemplate( + template.replace("%Q", "\""), + )) + } + "HigherOrderTemplates" => { + let [FieldValue::Literal(LiteralBits::String(inline_template)), FieldValue::Literal(LiteralBits::String(fn_ref_template))] = + payload.as_slice() + else { + return Err( + "HigherOrderTemplates must carry inline_template and fn_ref_template strings", + ); + }; + Ok(MethodTemplateContractEmitTemplate::HigherOrderTemplates { + inline_template: inline_template.replace("%Q", "\""), + fn_ref_template: fn_ref_template.replace("%Q", "\""), + }) + } + _ => Err("MethodTemplateContract.emit_template uses an unknown MethodEmitTemplate variant"), + } +} diff --git a/src/v3/compiler/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs b/src/v3/compiler/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs new file mode 100644 index 00000000000..3168e8fe4a3 --- /dev/null +++ b/src/v3/compiler/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs @@ -0,0 +1,92 @@ +//! PB-1 — Rust `main.rs` shell for generated `[[bin]]` shims (Item 5). +//! +//! **SG-0 / bounded-seed receipt:** hand-authored path is enumerated in +//! `EXPECTED_HAND_AUTHORED_NON_TEST` in `sg0_census_test.rs` (not +//! `GENERATED_FILES` / `build.rs` output). Narrow host helper for §4.2 shell +//! text only; see `docs/invariants/bounded-substrate-seed.md` (no new +//! substrate primitive — mirrors existing `std.process.ProcessExit`). +//! +//! Authority: `docs/design-pb-runtime-interpreter.md` §4.2 (`main` → +//! `ProcessExit` → `ExitCode`). This module owns **parameterized Rust text** +//! only: it does not resolve `BinShim.entry` `DeclarationRef` targets (row-#1 +//! / host bridge gap — STOP there) and does not wire `cargo`/build.rs. + +/// Formats the `main.rs` wrapper for a `.dag`-authored bin shim. +/// +/// `source_dag_path` is emitted verbatim in the `AUTO-GENERATED` header (one +/// line). `description` is split into `//` comment lines. `entry_fn_qname` is +/// the Rust-qualified symbol for the `.dag` entry function returning +/// [`crate::process_exit::ProcessExit`]. +pub fn format_bin_shim_main_rs( + source_dag_path: &str, + description: &str, + entry_fn_qname: &str, +) -> String { + let mut out = String::new(); + out.push_str("// AUTO-GENERATED from "); + out.push_str(source_dag_path); + out.push_str(" — DO NOT EDIT.\n//\n"); + if description.is_empty() { + out.push_str("//\n"); + } else { + for line in description.lines() { + out.push_str("// "); + out.push_str(line); + out.push('\n'); + } + } + out.push_str( + r#" +use std::io::Write; +use std::process::ExitCode; + +use v3_compiler::dag::Dag; +use v3_compiler::process_exit::ProcessExit; + +fn main() -> ExitCode { + match "#, + ); + out.push_str(entry_fn_qname); + out.push_str( + r#"(&Dag::new()) { + ProcessExit::ExitSuccess => ExitCode::SUCCESS, + ProcessExit::ExitFailure { code, reason } => { + let _ = writeln!(std::io::stderr(), "{reason}"); + // Fail-closed: `ExitFailure` must not map to a successful host exit. + // Non-positive or out-of-range codes remap into 1..=255 (see dsl/std/process.dag). + ExitCode::from((code.max(1).min(255)) as u8) + } + } +} +"#, + ); + out +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn format_bin_shim_main_rs_includes_header_entry_and_process_exit() { + let out = format_bin_shim_main_rs( + "dsl/std/runtime/bin_shims/regen_lens.dag", + "Unified lens-regen driver.", + "std::runtime::bin_shims::regen_lens::regen_lens_main", + ); + assert!(out.contains( + "// AUTO-GENERATED from dsl/std/runtime/bin_shims/regen_lens.dag — DO NOT EDIT." + )); + assert!(out.contains("// Unified lens-regen driver.")); + assert!(out.contains("use v3_compiler::process_exit::ProcessExit;")); + assert!(out.contains("code.max(1).min(255)")); + assert!(!out.contains("code.clamp(0, 255)")); + assert!(out.contains("std::runtime::bin_shims::regen_lens::regen_lens_main(&Dag::new())")); + } + + #[test] + fn format_bin_shim_main_rs_multiline_description() { + let out = format_bin_shim_main_rs("path/to/shim.dag", "line1\nline2", "m::f"); + assert!(out.contains("// line1\n// line2\n")); + } +} diff --git a/src/v3/compiler/pb0_cycle4_emit_templates/enforced_lens_application.rs b/src/v3/compiler/pb0_cycle4_emit_templates/enforced_lens_application.rs new file mode 100644 index 00000000000..d630542c160 --- /dev/null +++ b/src/v3/compiler/pb0_cycle4_emit_templates/enforced_lens_application.rs @@ -0,0 +1,1258 @@ +//! Enforced lens applications (`EnforcedApplication` from `lens_application.dag`). +//! +//! Gate #91 (`enforce_violation_routing_landed`): budget-violation diagnostics are +//! routed through the authored `diagnostic_severity` field — today the single +//! substrate variant `DiagnosticSeverity::Error` maps to [`Diagnostic::ParseError`] +//! (compile-fail / fail-closed), per `lens_application.dag`, design §3, and +//! INVARIANTS C-8. +//! +//! Future extra `DiagnosticSeverity` constructors stay fail-closed at this consumer until +//! an intentional substrate + routing co-update assigns their `Diagnostic::*` shape. +//! +//! Gate #92 (`complexity_violation_compile_error_demonstrated`): when a program +//! authors `EnforcedApplication` referencing +//! `complexity_enforceable`, infer checks the named section using the **same** +//! `LensEnforcement` projection + violation relation as `complexity.dag` +//! (`complexity_enforcement_project` / `complexity_enforcement_violates`, surfaced +//! through `complexity_lens_generated`) and attaches a compile diagnostic on violation. +//! +//! Gate #58 (`apply_lens_self_application_demonstrated`): when a program authors +//! `EnforcedApplication` referencing `timing_enforceable` +//! (`v3.std.timing_lens`), infer reads the **lowered** `TimingMeasurement` from the +//! `DeclarationScope` subject's structural `measurement` field **only after** verifying the +//! subject row's **nominal** `data …: RowTy = …` type declares `measurement: TimingMeasurement` +//! on `RowTy` (API-level contract; avoids unrelated records that merely spell a `measurement` +//! label). The same usage ceiling as `timing_enforcement_project` / `timing_enforcement_violates` +//! in `timing_lens.dag` applies: non-`Observed` reports are enforced via a **variant-shaped** +//! fault path (never compared as ordinary wall-clock `Nat` against the fault sentinel), while +//! `Observed` wall-clock nanoseconds use the same strict `>` edge as gate #94 against +//! `TimingBudget.max`. + +use std::collections::HashMap; + +use crate::dag::{ + literal_decimal_i64, positive_descent_count, ArrowBody, AsymptoticClass, Behavior, Dag, + DeclarationId, FieldValue, LiteralBits, Lookup, PortId, PositiveDescentAmount, TypeConnective, + ValueBody, +}; +use crate::diagnostics::{Diagnostic, SourceSpan}; +use crate::lens_cost::{ + complexity_enforcement_project, complexity_enforcement_violates, complexity_of, +}; + +fn diagnostic_severity_substrate_disj(dag: &Dag) -> Option { + dag.declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("DiagnosticSeverity") + && d.span.file.ends_with("lens_application.dag") + }) + .map(|d| d.id) +} + +// Fail-closed when `lens_application.dag` does not expose the expected `DiagnosticSeverity` +// substrate row — factored so unit tests can pin the diagnostic without staging a full +// complexity-enforcement DAG (`Dag::new()` omits `complexity_enforceable`). +fn attach_missing_diagnostic_severity_substrate_diagnostic( + dag: &mut Dag, + enforced_template: DeclarationId, +) { + dag.attach_diagnostic(Diagnostic::ParseError { + message: "lens enforcement: could not resolve substrate `DiagnosticSeverity` from \ + `lens_application.dag` (modeled authority missing; fail-closed)" + .to_string(), + span: dag.declaration(enforced_template).span.clone(), + fixes: Vec::new(), + }); +} + +/// Reads `timing_enforcement_fault_sentinel_count` from the lowered `timing_lens.dag` declaration +/// body (nullary `fn` → bind → [`ValueNode`]), matching substrate authority instead of duplicating +/// the decimal literal in Rust. +fn timing_enforcement_fault_sentinel_ns_from_substrate(dag: &Dag) -> Option { + let decl = dag.declarations().iter().find(|d| { + d.name.as_deref() == Some("timing_enforcement_fault_sentinel_count") + && d.span.file.ends_with("timing_lens.dag") + })?; + let TypeConnective::Arrow { + body: ArrowBody::UserDefined(bind_id), + .. + } = &decl.connective + else { + return None; + }; + let bind = (*bind_id).bind_opt(dag)?; + let producer = dag.resolve_producer_opt(&bind.value)?; + let Behavior::Value(vn) = producer else { + return None; + }; + let LiteralBits::Int(s) = &vn.data else { + return None; + }; + s.parse().ok() +} + +fn timing_measurement_sum_type_decl_id(dag: &Dag) -> Option { + dag.declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("TimingMeasurement") + && d.span.file.ends_with("timing_lens.dag") + }) + .map(|d| d.id) +} + +fn peel_type_declaration_head(dag: &Dag, mut id: DeclarationId) -> DeclarationId { + const MAX: usize = 64; + for _ in 0..MAX { + match &dag.declaration(id).connective { + TypeConnective::Instantiation { + template, + arguments, + } if arguments.is_empty() => { + id = *template; + } + TypeConnective::Atom(ap) => { + let Some(next) = ap.resolved_id() else { + break; + }; + id = next; + } + _ => break, + } + } + id +} + +/// Returns the nominal `RowTy` declaration id for a lowered `data row: RowTy = …` value row. +fn data_row_nominal_type_decl_id(dag: &Dag, value_decl_id: DeclarationId) -> Option { + match &dag.declaration(value_decl_id).connective { + TypeConnective::Instantiation { + template, + arguments, + } if arguments.is_empty() => Some(*template), + _ => None, + } +} + +/// True when `RowTy` (after alias peel) is a conj that declares `measurement` with type peeling +/// to the substrate `TimingMeasurement` sum at `tm_disj`. +fn timing_section_row_type_declares_measurement_tm( + dag: &Dag, + section_value_decl_id: DeclarationId, + tm_disj: DeclarationId, +) -> bool { + let Some(row_ty) = data_row_nominal_type_decl_id(dag, section_value_decl_id) else { + return false; + }; + let ty_head = peel_type_declaration_head(dag, row_ty); + let TypeConnective::Conj { children } = &dag.declaration(ty_head).connective else { + return false; + }; + let tm_head = peel_type_declaration_head(dag, tm_disj); + children + .iter() + .any(|f| f.label == "measurement" && peel_type_declaration_head(dag, f.ty) == tm_head) +} + +/// Lowered `TimingMeasurement` usage for timing `EnforcedApplication` enforcement. +/// +/// Separates non-evidence variants from `Observed` wall-clock nanoseconds so a legitimate +/// `Observed` duration equal to the substrate fault sentinel `Nat` cannot be conflated with +/// `Unobserved` / `Ambiguous` / `Stale` (INVARIANTS P2 / modeling-discipline Practice 2). +/// +/// Practice 4 (`docs/modeling-discipline.md`, coproduct checkpoint): **GREEN (terminal)** — mirrors +/// `timing_enforcement_project` / `timing_enforcement_violates` in `timing_lens.dag` on the lowered +/// `TimingMeasurement` witness (substrate `LensEnforcement.violates` now takes `TimingMeasurement` +/// × `TimingBudget` per `lens_application.dag`; gate #58 / codex 10994). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum TimingMeasurementEnforcementUsage { + NonObservedFault, + ObservedWallNs(u64), +} + +/// Interprets a lowered `TimingMeasurement` value the same way `timing_enforcement_project` does +/// on the substrate (`timing_lens.dag`): `Observed` → wall-clock nanoseconds; other report variants +/// classify as [`TimingMeasurementEnforcementUsage::NonObservedFault`] (not as a raw `Nat`). +fn timing_measurement_enforcement_usage( + dag: &Dag, + tm_disj: DeclarationId, + value: &FieldValue, +) -> Option { + let TypeConnective::Disj { variants } = &dag.declaration(tm_disj).connective else { + return None; + }; + let FieldValue::Variant { + constructor, + payload, + } = value + else { + return None; + }; + let variant = variants.iter().find(|v| v.ty == *constructor)?; + match variant.label.as_str() { + "Observed" => observed_timing_payload_max_ns(payload) + .map(TimingMeasurementEnforcementUsage::ObservedWallNs), + "Unobserved" | "Ambiguous" | "Stale" => { + Some(TimingMeasurementEnforcementUsage::NonObservedFault) + } + _ => None, + } +} + +/// Same reflexive edge as `timing_enforcement_violates` in `timing_lens.dag` on the lowered witness, +/// using the same `>` edge as substrate `Observed` enforcement (no `TimingBudget` sentinel equality +/// on the observed path). +pub(crate) fn timing_enforcement_violates( + declared_max_ns: u64, + usage: TimingMeasurementEnforcementUsage, +) -> bool { + match usage { + TimingMeasurementEnforcementUsage::NonObservedFault => true, + TimingMeasurementEnforcementUsage::ObservedWallNs(ns) => ns > declared_max_ns, + } +} + +fn observed_timing_payload_max_ns(payload: &[FieldValue]) -> Option { + let FieldValue::Record(parts) = payload.first()? else { + return None; + }; + if let Some((_, v)) = parts.iter().find(|(label, _)| label == "duration") { + return field_value_nat_magnitude(v); + } + field_value_nat_magnitude(&FieldValue::Record(parts.clone())) +} + +fn field_value_nat_magnitude(value: &FieldValue) -> Option { + match value { + FieldValue::Literal(LiteralBits::Int(s)) => s.parse().ok(), + FieldValue::Record(parts) => parts + .iter() + .find(|(label, _)| label == "count") + .and_then(|(_, v)| field_value_nat_magnitude(v)), + _ => None, + } +} + +fn field_value_timing_budget_max_ns(value: &FieldValue) -> Option { + let FieldValue::Record(parts) = value else { + return None; + }; + let max = parts + .iter() + .find(|(label, _)| label == "max") + .map(|(_, v)| v)?; + field_value_nat_magnitude(max) +} + +fn is_timing_enforceable_lens_value( + value: &FieldValue, + timing_enforceable_id: Option, + timing_lens_id: Option, + timing_enforcement_id: Option, +) -> bool { + if let (Some(bundle), FieldValue::Reference(id)) = (timing_enforceable_id, value) { + if *id == bundle { + return true; + } + } + let (Some(lens_id), Some(enforcement_id)) = (timing_lens_id, timing_enforcement_id) else { + return false; + }; + let FieldValue::Record(parts) = value else { + return false; + }; + let mut lens_ref: Option = None; + let mut enforcement_ref: Option = None; + for (label, field) in parts { + match (label.as_str(), field) { + ("lens", FieldValue::Reference(r)) => lens_ref = Some(*r), + ("enforcement", FieldValue::Reference(r)) => enforcement_ref = Some(*r), + _ => {} + } + } + lens_ref == Some(lens_id) && enforcement_ref == Some(enforcement_id) +} + +/// Fail-closed check for landed complexity + timing enforcement applications. +/// +/// Exported at the crate root (`v3_compiler::check_enforced_lens_applications`) so integration +/// tests can re-invoke the same post-infer pass as `infer::infer` without treating declaration +/// presence as proof the probe ran (gate #58 receipt). +pub fn check_enforced_lens_applications(dag: &mut Dag) { + let Some(enforced_template) = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("EnforcedApplication") + && d.span.file.ends_with("lens_application.dag") + }) + .map(|d| d.id) + else { + return; + }; + let complexity_enforceable_id = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("complexity_enforceable") + && d.span.file.ends_with("complexity.dag") + }) + .map(|d| d.id); + let timing_enforceable_id = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("timing_enforceable") + && d.span.file.ends_with("timing_lens.dag") + }) + .map(|d| d.id); + let timing_lens_id = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("timing_lens") && d.span.file.ends_with("timing_lens.dag") + }) + .map(|d| d.id); + let timing_enforcement_id = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("timing_enforcement") + && d.span.file.ends_with("timing_lens.dag") + }) + .map(|d| d.id); + let asymptotic_disj = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("AsymptoticClass") && d.span.file.ends_with("algebra.dag") + }) + .map(|d| d.id); + let Some(section_ref_disj) = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("SectionRef") && d.span.file.ends_with("lens_application.dag") + }) + .map(|d| d.id) + else { + return; + }; + let Some(declaration_scope_conj) = declaration_scope_payload_conj(dag, section_ref_disj) else { + return; + }; + let Some(diagnostic_severity_disj) = diagnostic_severity_substrate_disj(dag) else { + attach_missing_diagnostic_severity_substrate_diagnostic(dag, enforced_template); + return; + }; + let positive_descent_disj = dag + .declaration_by_name("PositiveDescentAmount") + .map(|d| d.id); + + let mut violations: Vec = Vec::new(); + + for decl in dag.declarations() { + let Some(body) = decl.value_body.as_ref() else { + continue; + }; + let ValueBody::Structural { fields } = body else { + continue; + }; + let Some(meta_id) = decl.meta_tag else { + continue; + }; + if !matches_enforced_application_instantiation(dag, meta_id, enforced_template) { + continue; + } + let fm = field_map(fields); + let enforceable_lens = match fm.get("enforceable_lens") { + Some(v) => v, + _ => continue, + }; + let section = match fm.get("section") { + Some(v) => *v, + _ => continue, + }; + let budget_val = match fm.get("budget") { + Some(v) => *v, + _ => continue, + }; + let span = match fm.get("span") { + Some(FieldValue::Record(span_fields)) => record_as_source_span(span_fields), + _ => None, + } + .unwrap_or_else(|| decl.span.clone()); + + if let (Some(cid), FieldValue::Reference(ref_id)) = + (complexity_enforceable_id, enforceable_lens) + { + if *ref_id == cid { + let Some(asymptotic_disj) = asymptotic_disj else { + violations.push(Diagnostic::ParseError { + message: + "lens enforcement: could not resolve substrate `AsymptoticClass` from \ + `algebra.dag` (modeled authority missing; fail-closed)" + .to_string(), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + let Some(fn_decl) = resolve_declaration_scope_fn( + dag, + section, + section_ref_disj, + declaration_scope_conj, + ) else { + violations.push(Diagnostic::ParseError { + message: + "lens enforcement: could not resolve function declaration for section" + .to_string(), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + let Some(port) = fn_result_port(dag, fn_decl) else { + violations.push(Diagnostic::ParseError { + message: + "lens enforcement: could not resolve function result port for section" + .to_string(), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + let Some(budget_class) = field_value_asymptotic_class( + dag, + asymptotic_disj, + positive_descent_disj, + budget_val, + ) else { + violations.push(Diagnostic::ParseError { + message: + "lens enforcement: could not read complexity budget `AsymptoticClass` \ + (ill-formed `ClassPolynomial` budgets must use Peano `degree` ≥ 3; \ + use `ClassLinear` / `ClassQuadratic` for sub-cubic tiers)" + .to_string(), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + + let summary = match complexity_of(dag, &port) { + Lookup::Hit(s) => s, + Lookup::Miss => { + violations.push(Diagnostic::ParseError { + message: + "lens enforcement: complexity lens returned Miss for section — \ + cannot enforce budget" + .to_string(), + span: span.clone(), + fixes: Vec::new(), + }); + continue; + } + }; + if !complexity_enforcement_violates(&summary, &budget_class) { + continue; + } + let observed = complexity_enforcement_project(&summary); + let severity_val = match fm.get("diagnostic_severity") { + Some(v) => *v, + _ => { + violations.push(Diagnostic::ParseError { + message: + "lens enforcement: `EnforcedApplication` missing `diagnostic_severity`" + .to_string(), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + } + }; + let violation_message = format!( + "lens enforcement violation: complexity budget {budget_class:?} exceeded \ + by observed class {observed:?} (declared `EnforcedApplication` at {})", + decl.name.as_deref().unwrap_or("?") + ); + violations.push(enforced_violation_diagnostic( + dag, + diagnostic_severity_disj, + severity_val, + violation_message, + span, + )); + continue; + } + } + + if is_timing_enforceable_lens_value( + enforceable_lens, + timing_enforceable_id, + timing_lens_id, + timing_enforcement_id, + ) { + let Some(tm_disj) = timing_measurement_sum_type_decl_id(dag) else { + violations.push(Diagnostic::ParseError { + message: + "lens enforcement: could not resolve substrate `TimingMeasurement` from \ + `timing_lens.dag` (modeled authority missing; fail-closed)" + .to_string(), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + let Some(section_decl_id) = + resolve_declaration_scope_declaration_id(section, declaration_scope_conj) + else { + violations.push(Diagnostic::ParseError { + message: "lens enforcement: could not resolve `DeclarationScope` for timing \ + `EnforcedApplication` section" + .to_string(), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + let section_decl = dag.declaration(section_decl_id); + if !timing_section_row_type_declares_measurement_tm(dag, section_decl_id, tm_disj) { + violations.push(Diagnostic::ParseError { + message: format!( + "lens enforcement: timing `EnforcedApplication` section `{}` must be a \ + lowered `data …: RowTy = …` row whose nominal `RowTy` declares \ + `measurement: TimingMeasurement` (same substrate contract as \ + `timing_enforcement_project`)", + section_decl.name.as_deref().unwrap_or("?") + ), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + } + let Some(body) = section_decl.value_body.as_ref() else { + violations.push(Diagnostic::ParseError { + message: format!( + "lens enforcement: timing section `{}` has no lowered value body", + section_decl.name.as_deref().unwrap_or("?") + ), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + let ValueBody::Structural { fields } = body else { + violations.push(Diagnostic::ParseError { + message: format!( + "lens enforcement: timing section `{}` must lower to a structural record body", + section_decl.name.as_deref().unwrap_or("?") + ), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + let section_fm = field_map(fields); + let Some(measurement) = section_fm.get("measurement") else { + violations.push(Diagnostic::ParseError { + message: format!( + "lens enforcement: timing section `{}` is missing required `measurement` field", + section_decl.name.as_deref().unwrap_or("?") + ), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + let Some(usage_projection) = + timing_measurement_enforcement_usage(dag, tm_disj, measurement) + else { + violations.push(Diagnostic::ParseError { + message: format!( + "lens enforcement: could not interpret `measurement` as lowered \ + `TimingMeasurement` for section `{}`", + section_decl.name.as_deref().unwrap_or("?") + ), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + let Some(declared_max_ns) = field_value_timing_budget_max_ns(budget_val) else { + violations.push(Diagnostic::ParseError { + message: "lens enforcement: could not read timing budget `TimingBudget.max` \ + nanoseconds (expected `TimingBudget { max: Nanoseconds { count } }`)" + .to_string(), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + if !timing_enforcement_violates(declared_max_ns, usage_projection) { + continue; + }; + let usage_max_ns_for_message = match usage_projection { + TimingMeasurementEnforcementUsage::ObservedWallNs(ns) => ns, + TimingMeasurementEnforcementUsage::NonObservedFault => { + let Some(s) = timing_enforcement_fault_sentinel_ns_from_substrate(dag) else { + violations.push(Diagnostic::ParseError { + message: + "lens enforcement: could not resolve substrate `timing_enforcement_fault_sentinel_count` \ + from `timing_lens.dag` (needed for fault-path diagnostic text; fail-closed)" + .to_string(), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + s + } + }; + let severity_val = match fm.get("diagnostic_severity") { + Some(v) => *v, + _ => { + violations.push(Diagnostic::ParseError { + message: + "lens enforcement: `EnforcedApplication` missing `diagnostic_severity`" + .to_string(), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + } + }; + let violation_message = format!( + "lens enforcement violation: timing budget ceiling max_ns={declared_max_ns} exceeded \ + by projected wall-clock usage max_ns={usage_max_ns_for_message} (declared `EnforcedApplication` \ + at {})", + decl.name.as_deref().unwrap_or("?") + ); + violations.push(enforced_violation_diagnostic( + dag, + diagnostic_severity_disj, + severity_val, + violation_message, + span, + )); + } + } + + for diagnostic in violations { + dag.attach_diagnostic(diagnostic); + } +} + +#[doc(hidden)] +fn gate_58_raise_observed_duration_ns_in_measurement( + value: &mut FieldValue, + duration_ns: u64, +) -> bool { + match value { + FieldValue::Variant { payload, .. } => { + let Some(FieldValue::Record(parts)) = payload.first_mut() else { + return false; + }; + if let Some((_, FieldValue::Record(duration_parts))) = + parts.iter_mut().find(|(label, _)| label == "duration") + { + return gate_58_set_count_field_to_ns(duration_parts, duration_ns); + } + // PB-1 bootstrap lowering: `Observed` payload can flatten to `{ count: Nat }` without + // a nested `duration` record (authoring uses `Observed { duration: { count } }`). + gate_58_set_count_field_to_ns(parts, duration_ns) + } + _ => false, + } +} + +fn gate_58_set_count_field_to_ns(parts: &mut [(String, FieldValue)], duration_ns: u64) -> bool { + for (dlabel, dfield) in parts.iter_mut() { + if dlabel == "count" { + if let FieldValue::Literal(LiteralBits::Int(s)) = dfield { + *s = duration_ns.to_string(); + return true; + } + return false; + } + } + false +} + +/// Integration receipt helper: mutates the PB-1 gate #58 witness row in a bootstrap [`Dag`]. +/// +/// **Not a supported production API** — only the `v3-compiler` integration test +/// `t_gate_58_apply_lens_self_application_test` should call this (exposed so that test binary can +/// link against `v3_compiler` without `cfg(test)` coupling). +#[doc(hidden)] +pub fn gate_58_test_raise_modeled_ci_timing_measurement_duration_ns( + dag: &mut Dag, + duration_ns: u64, +) -> Result<(), &'static str> { + let id = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("gate_58_modeled_ci_timing_measurement") + && d.span.file.ends_with("t_ci_workflow_as_data_demo.dag") + }) + .map(|d| d.id) + .ok_or("missing gate_58_modeled_ci_timing_measurement declaration")?; + let decl = dag.declaration_mut(id); + let Some(ValueBody::Structural { fields }) = decl.value_body.as_mut() else { + return Err("gate_58 witness missing structural value_body"); + }; + for (label, value) in fields.iter_mut() { + if label == "measurement" + && gate_58_raise_observed_duration_ns_in_measurement(value, duration_ns) + { + return Ok(()); + } + } + Err("measurement field missing or not Observed with duration.count shape") +} + +/// Parses `(declared_max_ns, projected_usage_max_ns)` from a timing lens **budget-ceiling** +/// violation message produced by [`check_enforced_lens_applications`]. +/// +/// The template is owned next to [`timing_enforcement_violates`] / `violation_message` formatting +/// in this module (`max_ns=` appears exactly twice: declared ceiling, then projected usage). +/// Integration tests use this instead of pinning English prose (see repo `TESTING.md` anti-pattern). +#[doc(hidden)] +pub fn gate_58_test_parse_timing_budget_violation_max_ns_pair(message: &str) -> Option<(u64, u64)> { + const NEEDLE: &str = "max_ns="; + let mut found: Vec = Vec::with_capacity(2); + let mut rest = message; + while let Some(pos) = rest.find(NEEDLE) { + let after = pos + NEEDLE.len(); + let tail = &rest[after..]; + let digit_len = tail + .chars() + .take_while(|c| c.is_ascii_digit()) + .map(|c| c.len_utf8()) + .sum::(); + if digit_len == 0 { + return None; + } + let n: u64 = tail[..digit_len].parse().ok()?; + found.push(n); + rest = &tail[digit_len..]; + } + if found.len() == 2 { + Some((found[0], found[1])) + } else { + None + } +} + +fn field_map(fields: &[(String, FieldValue)]) -> HashMap<&str, &FieldValue> { + fields.iter().map(|(k, v)| (k.as_str(), v)).collect() +} + +/// Routes an enforce-mode **budget violation** through `EnforcedApplication.diagnostic_severity`. +/// +/// Substrate policy (C-8): only `DiagnosticSeverity::Error` is a valid steady-state choice; unknown +/// constructors, non-nullary `Error` payload, or malformed values fail closed with an explanatory +/// diagnostic. +fn enforced_violation_diagnostic( + dag: &Dag, + diagnostic_severity_disj: DeclarationId, + severity_val: &FieldValue, + violation_message: String, + span: SourceSpan, +) -> Diagnostic { + let TypeConnective::Disj { variants } = &dag.declaration(diagnostic_severity_disj).connective + else { + return Diagnostic::ParseError { + message: "lens enforcement: internal error (DiagnosticSeverity is not a sum type)" + .to_string(), + span, + fixes: Vec::new(), + }; + }; + let Some(error_ctor) = variants.iter().find(|v| v.label == "Error").map(|v| v.ty) else { + return Diagnostic::ParseError { + message: "lens enforcement: internal error (DiagnosticSeverity lacks `Error` variant)" + .to_string(), + span, + fixes: Vec::new(), + }; + }; + let FieldValue::Variant { + constructor, + payload, + } = severity_val + else { + return Diagnostic::ParseError { + message: "lens enforcement: `diagnostic_severity` must be a `DiagnosticSeverity` \ + variant value" + .to_string(), + span, + fixes: Vec::new(), + }; + }; + if *constructor != error_ctor { + return Diagnostic::ParseError { + message: "lens enforcement: `diagnostic_severity` on `EnforcedApplication` must be \ + `Error` (INVARIANTS C-8; fail-closed discipline)" + .to_string(), + span, + fixes: Vec::new(), + }; + } + // `lens_application.dag`: `type DiagnosticSeverity = Error` — the lone variant is nullary. + if !payload.is_empty() { + return Diagnostic::ParseError { + message: "lens enforcement: `DiagnosticSeverity::Error` must be nullary (INVARIANTS \ + P1; malformed variant payload)" + .to_string(), + span, + fixes: Vec::new(), + }; + } + Diagnostic::ParseError { + message: violation_message, + span, + fixes: Vec::new(), + } +} + +fn matches_enforced_application_instantiation( + dag: &Dag, + mut meta: DeclarationId, + enforced_template: DeclarationId, +) -> bool { + for _ in 0..16 { + match &dag.declaration(meta).connective { + TypeConnective::Instantiation { + template, + arguments, + } => { + if *template == enforced_template && arguments.len() == 3 { + return true; + } + if arguments.is_empty() { + meta = *template; + continue; + } + return false; + } + _ => return false, + } + } + false +} + +fn declaration_scope_payload_conj( + dag: &Dag, + section_ref_disj: DeclarationId, +) -> Option { + let TypeConnective::Disj { variants } = &dag.declaration(section_ref_disj).connective else { + return None; + }; + let v = variants.iter().find(|f| f.label == "DeclarationScope")?; + Some(v.ty) +} + +fn resolve_declaration_scope_declaration_id( + section: &FieldValue, + declaration_scope_conj: DeclarationId, +) -> Option { + let FieldValue::Variant { + constructor, + payload, + } = section + else { + return None; + }; + if *constructor != declaration_scope_conj { + return None; + } + match payload.as_slice() { + [FieldValue::Reference(id)] => Some(*id), + [FieldValue::Record(parts)] => { + let mut decl_id: Option = None; + for (label, value) in parts { + if label == "declaration" { + if let FieldValue::Reference(id) = value { + decl_id = Some(*id); + } + } + } + decl_id + } + _ => None, + } +} + +fn resolve_declaration_scope_fn( + dag: &Dag, + section: &FieldValue, + section_ref_disj: DeclarationId, + declaration_scope_conj: DeclarationId, +) -> Option { + let _ = section_ref_disj; + let id = resolve_declaration_scope_declaration_id(section, declaration_scope_conj)?; + matches!(dag.declaration(id).connective, TypeConnective::Arrow { .. }).then_some(id) +} + +fn fn_result_port(dag: &Dag, fn_decl: DeclarationId) -> Option { + let decl = dag.declaration(fn_decl); + let TypeConnective::Arrow { + body: ArrowBody::UserDefined(bind_node), + .. + } = &decl.connective + else { + return None; + }; + let bind = bind_node.bind(dag); + Some(bind.result_port()) +} + +fn record_as_source_span(fields: &[(String, FieldValue)]) -> Option { + let mut file: Option = None; + let mut byte_start: Option = None; + let mut byte_end: Option = None; + for (label, value) in fields { + match (label.as_str(), value) { + ("file", FieldValue::Literal(LiteralBits::String(s))) => file = Some(s.clone()), + ("byte_start", FieldValue::Literal(LiteralBits::Int(n))) => { + byte_start = literal_decimal_i64(n.as_str()).and_then(|v| u32::try_from(v).ok()); + } + ("byte_end", FieldValue::Literal(LiteralBits::Int(n))) => { + byte_end = literal_decimal_i64(n.as_str()).and_then(|v| u32::try_from(v).ok()); + } + ("start", FieldValue::Literal(LiteralBits::Int(n))) => { + byte_start = literal_decimal_i64(n.as_str()).and_then(|v| u32::try_from(v).ok()); + } + ("end", FieldValue::Literal(LiteralBits::Int(n))) => { + byte_end = literal_decimal_i64(n.as_str()).and_then(|v| u32::try_from(v).ok()); + } + _ => {} + } + } + Some(SourceSpan::new(file?, byte_start?, byte_end?)) +} + +fn field_value_asymptotic_class( + dag: &Dag, + ac_disj: DeclarationId, + pda_disj: Option, + value: &FieldValue, +) -> Option { + let FieldValue::Variant { + constructor, + payload, + } = value + else { + return None; + }; + let TypeConnective::Disj { variants } = &dag.declaration(ac_disj).connective else { + return None; + }; + let variant = variants.iter().find(|v| v.ty == *constructor)?; + match variant.label.as_str() { + "ClassConstant" => Some(AsymptoticClass::ClassConstant), + "ClassLog" => Some(AsymptoticClass::ClassLog), + "ClassLinear" => Some(AsymptoticClass::ClassLinear), + "ClassLinearithmic" => Some(AsymptoticClass::ClassLinearithmic), + "ClassQuadratic" => Some(AsymptoticClass::ClassQuadratic), + "ClassExponential" => Some(AsymptoticClass::ClassExponential), + "ClassUnknown" => Some(AsymptoticClass::ClassUnknown), + "ClassPolynomial" => { + let pda = pda_disj?; + let degree = decode_positive_descent_amount(dag, pda, payload)?; + // `AsymptoticClass::ClassPolynomial` in `algebra.dag` is k ≥ 3; Peano + // counts 1–2 are `ClassLinear` / `ClassQuadratic` territory — reject as + // an invalid budget shape (fail closed; openai-pro / P1). + if positive_descent_count(°ree) < 3 { + return None; + } + Some(AsymptoticClass::ClassPolynomial { degree }) + } + _ => None, + } +} + +fn decode_positive_descent_amount( + dag: &Dag, + pda_disj: DeclarationId, + poly_payload: &[FieldValue], +) -> Option { + match poly_payload { + [FieldValue::Record(parts)] => { + let value = parts.iter().find(|(k, _)| k == "degree").map(|(_, v)| v)?; + decode_positive_descent_variant(dag, pda_disj, value) + } + [single] => decode_positive_descent_variant(dag, pda_disj, single), + _ => None, + } +} + +fn decode_positive_descent_variant( + dag: &Dag, + pda_disj: DeclarationId, + value: &FieldValue, +) -> Option { + let FieldValue::Variant { + constructor, + payload, + } = value + else { + return None; + }; + let TypeConnective::Disj { variants } = &dag.declaration(pda_disj).connective else { + return None; + }; + let variant = variants.iter().find(|v| v.ty == *constructor)?; + match variant.label.as_str() { + "OneStep" => Some(PositiveDescentAmount::OneStep), + "AdditionalStep" => { + let FieldValue::Record(parts) = payload.first()? else { + return None; + }; + let prev = parts + .iter() + .find(|(k, _)| k == "previous") + .map(|(_, v)| v)?; + let inner = decode_positive_descent_variant(dag, pda_disj, prev)?; + Some(PositiveDescentAmount::AdditionalStep { + previous: Box::new(inner), + }) + } + _ => None, + } +} + +#[cfg(test)] +mod diagnostic_severity_fail_closed_tests { + use super::*; + use crate::dag::{LiteralBits, TypeConnective}; + use crate::diagnostics::Diagnostic; + + fn assert_parse_error_at(diag: Diagnostic, expected_span: &SourceSpan) { + match diag { + Diagnostic::ParseError { span, fixes, .. } => { + assert_eq!(&span, expected_span); + assert!(fixes.is_empty()); + } + other => panic!("expected ParseError, got {other:?}"), + } + } + + #[test] + fn missing_diagnostic_severity_substrate_records_fail_closed_diagnostic() { + let mut dag = Dag::new(); + let Some(enforced_template) = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("EnforcedApplication") + && d.span.file.ends_with("lens_application.dag") + }) + .map(|d| d.id) + else { + panic!("bootstrap should declare EnforcedApplication in lens_application.dag"); + }; + let expected_span = dag.declaration(enforced_template).span.clone(); + let Some(ds_id) = super::diagnostic_severity_substrate_disj(&dag) else { + panic!("bootstrap should declare DiagnosticSeverity in lens_application.dag"); + }; + dag.declaration_mut(ds_id).name = Some("DiagnosticSeverity__test_unresolvable".to_string()); + assert!( + super::diagnostic_severity_substrate_disj(&dag).is_none(), + "Expected DiagnosticSeverity substrate row to be unresolvable after rename" + ); + super::attach_missing_diagnostic_severity_substrate_diagnostic(&mut dag, enforced_template); + let mut it = dag.diagnostics().iter(); + let (_, d) = it.next().expect("fail-closed diagnostic"); + assert!(it.next().is_none(), "expected exactly one diagnostic"); + assert_parse_error_at(d.clone(), &expected_span); + } + + #[test] + fn enforced_violation_diagnostic_rejects_non_error_severity_constructor() { + let dag = Dag::new(); + let Some(ds_disj) = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("DiagnosticSeverity") + && d.span.file.ends_with("lens_application.dag") + }) + .map(|d| d.id) + else { + panic!("bootstrap should declare DiagnosticSeverity in lens_application.dag"); + }; + let wrong_ctor = dag + .bool_runtime_variant_id(true) + .expect("bootstrap True variant"); + let expected_span = SourceSpan::new("t.v3", 0, 1); + let diag = enforced_violation_diagnostic( + &dag, + ds_disj, + &FieldValue::Variant { + constructor: wrong_ctor, + payload: Vec::new(), + }, + "violation".to_string(), + expected_span.clone(), + ); + assert_parse_error_at(diag, &expected_span); + } + + #[test] + fn enforced_violation_diagnostic_rejects_non_variant_severity_value() { + let dag = Dag::new(); + let Some(ds_disj) = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("DiagnosticSeverity") + && d.span.file.ends_with("lens_application.dag") + }) + .map(|d| d.id) + else { + panic!("bootstrap should declare DiagnosticSeverity in lens_application.dag"); + }; + let expected_span = SourceSpan::new("t.v3", 0, 1); + let diag = enforced_violation_diagnostic( + &dag, + ds_disj, + &FieldValue::Literal(LiteralBits::Int("0".to_string())), + "violation".to_string(), + expected_span.clone(), + ); + assert_parse_error_at(diag, &expected_span); + } + + #[test] + fn enforced_violation_diagnostic_rejects_error_severity_with_non_nullary_payload() { + let dag = Dag::new(); + let Some(ds_disj) = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("DiagnosticSeverity") + && d.span.file.ends_with("lens_application.dag") + }) + .map(|d| d.id) + else { + panic!("bootstrap should declare DiagnosticSeverity in lens_application.dag"); + }; + let TypeConnective::Disj { variants } = &dag.declaration(ds_disj).connective else { + panic!("bootstrap DiagnosticSeverity should be a sum type"); + }; + let error_ctor = variants + .iter() + .find(|v| v.label == "Error") + .map(|v| v.ty) + .expect("bootstrap DiagnosticSeverity should have Error variant"); + let expected_span = SourceSpan::new("t.v3", 0, 1); + let diag = enforced_violation_diagnostic( + &dag, + ds_disj, + &FieldValue::Variant { + constructor: error_ctor, + payload: vec![FieldValue::Literal(LiteralBits::Int("0".to_string()))], + }, + "violation".to_string(), + expected_span.clone(), + ); + assert_parse_error_at(diag, &expected_span); + } +} + +#[cfg(test)] +mod polynomial_budget_class_policy_tests { + use crate::dag::{positive_amount_from_i64, positive_descent_count, PositiveDescentAmount}; + + fn polynomial_budget_peano_is_admissible(degree: &PositiveDescentAmount) -> bool { + positive_descent_count(degree) >= 3 + } + + #[test] + fn class_polynomial_enforcement_budget_rejects_sub_cubic_peano_degrees() { + assert_eq!(positive_descent_count(&PositiveDescentAmount::OneStep), 1); + assert!(!polynomial_budget_peano_is_admissible( + &PositiveDescentAmount::OneStep + )); + let deg2 = PositiveDescentAmount::AdditionalStep { + previous: Box::new(PositiveDescentAmount::OneStep), + }; + assert_eq!(positive_descent_count(°2), 2); + assert!(!polynomial_budget_peano_is_admissible(°2)); + let deg3 = positive_amount_from_i64(3).expect("synthetic degree 3"); + assert!(polynomial_budget_peano_is_admissible(°3)); + } +} + +#[cfg(test)] +mod gate_58_timing_enforcement_unit_tests { + use super::{ + gate_58_test_parse_timing_budget_violation_max_ns_pair, + timing_enforcement_fault_sentinel_ns_from_substrate, timing_enforcement_violates, + TimingMeasurementEnforcementUsage, + }; + use crate::dag::Dag; + + #[test] + fn gate_58_parse_max_ns_pair_matches_timing_budget_violation_template() { + let msg = "lens enforcement violation: timing budget ceiling max_ns=1 exceeded \ + by projected wall-clock usage max_ns=999 (declared `EnforcedApplication` \ + at gate_58_enforcement_budget_violation)"; + assert_eq!( + gate_58_test_parse_timing_budget_violation_max_ns_pair(msg), + Some((1, 999)) + ); + } + + #[test] + fn non_observed_fault_always_violates_under_any_finite_budget() { + assert!(timing_enforcement_violates( + 1, + TimingMeasurementEnforcementUsage::NonObservedFault + )); + assert!(timing_enforcement_violates( + u64::MAX - 1, + TimingMeasurementEnforcementUsage::NonObservedFault + )); + } + + #[test] + fn observed_usage_strictly_exceeding_budget_violates() { + assert!(timing_enforcement_violates( + 500, + TimingMeasurementEnforcementUsage::ObservedWallNs(1000) + )); + } + + #[test] + fn observed_usage_within_or_equal_budget_is_clean() { + assert!(!timing_enforcement_violates( + 1000, + TimingMeasurementEnforcementUsage::ObservedWallNs(500) + )); + assert!(!timing_enforcement_violates( + 1000, + TimingMeasurementEnforcementUsage::ObservedWallNs(1000) + )); + } + + #[test] + fn observed_wall_at_substrate_fault_sentinel_is_not_fault_short_circuit() { + let dag = Dag::new(); + let s = timing_enforcement_fault_sentinel_ns_from_substrate(&dag) + .expect("bootstrap should carry timing_lens fault sentinel"); + assert!(!timing_enforcement_violates( + s + 1, + TimingMeasurementEnforcementUsage::ObservedWallNs(s) + )); + assert!(timing_enforcement_violates( + s - 1, + TimingMeasurementEnforcementUsage::ObservedWallNs(s) + )); + } +} diff --git a/src/v3/compiler/pb0_cycle4_emit_templates/int_literal_ranges.rs b/src/v3/compiler/pb0_cycle4_emit_templates/int_literal_ranges.rs new file mode 100644 index 00000000000..3bb9415c452 --- /dev/null +++ b/src/v3/compiler/pb0_cycle4_emit_templates/int_literal_ranges.rs @@ -0,0 +1,1016 @@ +//! Integer literal magnitude vs declared integer types — **Q1 consumer**. +//! +//! Authoritative modeling: [`docs/design-emission-model.md`](../../../../docs/design-emission-model.md) +//! §Q1 — `BoundDeclaration = StaticBound(Interval) | PlatformDependent`, with asymmetric +//! match (target `Unbounded` universal-accept; target `ExactInterval` exact `lo`/`hi` equality at +//! the fold). This module implements only the **static** side needed for literal narrowing: +//! substrate range facts [`range_min_inclusive` / `range_max_inclusive`](../../../../dsl/extdeps/languages/rust/primitives.dag) +//! on [`rust_pilot_primitives`](crate::dag::Dag::rust_pilot_primitives) supply +//! `StaticBound(Interval)` as [`IntervalInt::ExactInterval`] (decimal endpoints + host +//! `BigInt` comparison; widened from `i128` per R3 Phase A so `u128::MAX` and any future wider +//! primitive is representable structurally). [`IntervalInt::Unbounded`] exists so Q1’s interval +//! algebra is representable when a +//! target declares an unbounded value domain (pilot `IntegerPrimitive` rows are all exact today). +//! [`PlatformDependent`] is out of scope for i64-bounded literal narrowing (deferred targets). +//! +//! ## Downstream consumers (range-facts + narrowing) +//! +//! | Location | Behavior | +//! | --- | --- | +//! | `infer::try_reconcile_int_literal_decision_set` | `let` / `data` pre-seed vs default `Int64` literal; in-range narrow; OOB → `MagnitudeOutOfRange`. | +//! | `infer::decide_transform` (calls) | Parameter-narrow type vs default-`Int` argument literal; narrow or OOB. | +//! | `infer::int_literal_implicit_bind_tolerated_for_expected` | Callable template binding when structural binding fails on int literal. | +//! | `lower` scalar literal lowering | Early reject for OOB literals before inference reunion. | +//! +//! ### R2 downstream audit ([`docs/briefs/r2-modeling-int-lit-magnitude-worker.md`](../../../../docs/briefs/r2-modeling-int-lit-magnitude-worker.md) §Slice 2) +//! +//! Post–[#1227](https://github.com/gunb-ai/gunbc/pull/1227) (**MethodEmitTemplate** Phase 1.5): no shared symbols with `emit_model.dag` method-template row lists +//! or `*_method_template_contracts` — those paths do **not** consult `rust_pilot_primitives` / this module. +//! +//! | Consumer | Range-facts / Q1 magnitude | +//! | --- | --- | +//! | `infer` (`try_reconcile_int_literal_decision_set`, `decide_transform`, `int_literal_implicit_bind_tolerated_for_expected`, transform/`PortUnion` narrow paths) | **Yes** — `integer_range_for_decl` + `magnitude_out_of_range_for_interval`. | +//! | `lower` (`lower_scalar_literal` / scalar literal outcome) | **Yes** — same facts for early OOB. | +//! | `emit::*` (e.g. `rust_target`) | **Indirect** — emits **resolved** shapes after inference; no parallel range-fact walk. | +//! | Method-template / emit-model carriers (**#1227**) | **N/A** — template contracts; unrelated to int-literal narrowing. | + +use std::collections::HashSet; +use std::str::FromStr; + +use num_bigint::BigInt; + +use crate::dag::{ + AtomPayload, Behavior, Dag, DeclarationId, FieldValue, LiteralBits, PortId, TemplateArgument, + TypeConnective, ValueBody, +}; +use crate::diagnostics::{Diagnostic, SourceSpan}; +use crate::types::TypeShape; + +/// Q1 `Interval` instance carried from String-decimal range facts (not `LiteralBits::Int` +/// widening — producer brief). +/// +/// **Host repr:** `min` / `max` are arbitrary-precision `BigInt` so `u128::MAX` (and any future +/// wider-than-i128 primitive) is representable structurally without per-width host-repr variant +/// explosion. Per Director Path A RATIFIED at gunbc#1739 #issuecomment-4392731264 (R3 Substrate +/// Rust-primitive-full-coverage bundled brief). The previous narrow `i128` host-repr deferred the +/// `u128` row in `dsl/extdeps/languages/rust/primitives.dag` because `u128::MAX` exceeds `i128` +/// range; that gap is closed by the `BigInt` host repr here. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum IntervalInt { + /// Closed interval — substrate `range_*_inclusive` facts for a fixed-width target primitive. + ExactInterval { + target_name: String, + min_decimal: String, + max_decimal: String, + min: BigInt, + max: BigInt, + }, + /// Value-domain unbounded integer (e.g. arbitrary-precision target). Universal-accept for any + /// tokenized decimal literal magnitude (through `u128::MAX` / signed `|m| ≤ 2^127`). + /// + /// **Dissolution trigger (when this variant is constructed from [`integer_range_for_decl`]):** + /// a `rust_pilot_primitives` `IntegerPrimitive` row (or successor multi-target table) is + /// authored for a target whose Q1 `BoundDeclaration` is `StaticBound(Unbounded)` at magnitude + /// check — e.g. Python `int` per [`docs/design-emission-model.md`](../../../../docs/design-emission-model.md) + /// fold example (T-Ground cross-target / language `primitives.dag` work, not this consumer). + /// Until that producer exists, only [`IntervalInt::ExactInterval`] is returned from the pilot + /// list; [`Unbounded`] remains for `contains_bigint` / Q1 algebra completeness and unit tests. + #[allow(dead_code)] + Unbounded, +} + +/// Decimal endpoints for a **fixed** integer target — the payload of [`MagnitudeOutOfRange`]. +/// +/// **API split:** only [`magnitude_out_of_range`] takes this type. When you have a full +/// [`IntervalInt`] from [`integer_range_for_decl`], call [`magnitude_out_of_range_for_interval`] +/// (it uses [`IntervalInt::exact_interval_facts`] and never passes an unbounded domain into the +/// magnitude diagnostic). +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct ExactIntIntervalFacts { + pub(crate) target_name: String, + pub(crate) min_decimal: String, + pub(crate) max_decimal: String, +} + +impl IntervalInt { + /// Range check for a surface literal magnitude carried as decimal [`LiteralBits::Int`]. + pub(crate) fn contains_bigint(&self, value: &BigInt) -> bool { + match self { + IntervalInt::Unbounded => true, + IntervalInt::ExactInterval { min, max, .. } => min <= value && value <= max, + } + } + + /// Fixed-width pilot rows: `Some` for [`IntervalInt::ExactInterval`]; `None` for + /// [`IntervalInt::Unbounded`] (no decimal range to quote in `MagnitudeOutOfRange`). + pub(crate) fn exact_interval_facts(&self) -> Option { + match self { + IntervalInt::ExactInterval { + target_name, + min_decimal, + max_decimal, + .. + } => Some(ExactIntIntervalFacts { + target_name: target_name.clone(), + min_decimal: min_decimal.clone(), + max_decimal: max_decimal.clone(), + }), + IntervalInt::Unbounded => None, + } + } +} + +pub(crate) enum IntegerRangeLookup { + Found(IntervalInt), + Missing, + Invalid(Diagnostic), +} + +/// Structural witness for routing integer literals: `IntegerAlgebra` and +/// `TargetCarrier` variant **payload type** ids (the `constructor` field on +/// `FieldValue::Variant`), derived from std `OrderedRing` / `Semiring` +/// **or** `Compose>` (R3 gate #19 refinement form) +/// by resolving to the same pilot `(OrderedRing|Semiring, Word*)` routing key. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct IntegerRoutingWitness { + pub(crate) algebra_variant_ty: DeclarationId, + pub(crate) carrier_variant_ty: DeclarationId, +} + +/// Integration / structural tests: witness for `decl`'s resolved integer +/// instantiation (`OrderedRing` / `Semiring` + word carrier), if any. +pub(crate) fn integer_routing_witness_for_decl( + dag: &Dag, + decl: DeclarationId, +) -> Option { + integer_routing_witness_walk(dag, decl, 0) +} + +/// `Nat` (`dsl/std/nat.dag`: `Semiring`). Nonnegative literals share the tokenizer's +/// `u128` magnitude carrier; narrowing still rejects negative values structurally. +fn type_is_nat(dag: &Dag, mut decl: DeclarationId) -> bool { + let Some(nat_id) = dag.declaration_by_name("Nat").map(|d| d.id) else { + return false; + }; + let Some(semiring_template) = dag.declaration_by_name("Semiring").map(|d| d.id) else { + return false; + }; + let Some(magnitude_id) = dag.declaration_by_name("Magnitude").map(|d| d.id) else { + return false; + }; + for _ in 0..32 { + if decl == nat_id { + return true; + } + let declaration = dag.declaration(decl); + match &declaration.connective { + TypeConnective::Instantiation { + template, + arguments, + } if *template == semiring_template + && arguments.len() == 1 + && arguments[0].value == magnitude_id => + { + return true; + } + TypeConnective::Instantiation { template, .. } => decl = *template, + TypeConnective::Atom(AtomPayload::ResolvedByStructure(next)) + | TypeConnective::Atom(AtomPayload::ResolvedByName(next)) => decl = *next, + _ => return false, + } + } + false +} + +fn nat_decimal_literal_interval() -> IntervalInt { + IntervalInt::ExactInterval { + target_name: "Nat".to_string(), + min_decimal: "0".to_string(), + max_decimal: u128::MAX.to_string(), + min: BigInt::from(0u8), + max: BigInt::from(u128::MAX), + } +} + +pub(crate) fn integer_range_for_decl(dag: &Dag, decl: DeclarationId) -> IntegerRangeLookup { + if type_is_nat(dag, decl) { + return IntegerRangeLookup::Found(nat_decimal_literal_interval()); + } + let Some(witness) = integer_routing_witness_walk(dag, decl, 0) else { + return IntegerRangeLookup::Missing; + }; + let Some(pilot) = dag.rust_pilot_primitives() else { + return IntegerRangeLookup::Missing; + }; + let Some(body) = pilot.value_body.as_ref() else { + return IntegerRangeLookup::Missing; + }; + let ValueBody::List(elements) = body else { + return IntegerRangeLookup::Missing; + }; + + let integer_primitive_ctor = match rust_primitive_integer_variant_ty(dag) { + Some(id) => id, + None => { + return IntegerRangeLookup::Invalid(malformed_integer_range_fact( + "bootstrap: RustPrimitive.IntegerPrimitive variant type is unavailable".to_string(), + pilot.span.clone(), + )); + } + }; + + let mut matches: Vec = Vec::new(); + for element in elements { + let FieldValue::Variant { + constructor, + payload, + } = element + else { + continue; + }; + if *constructor != integer_primitive_ctor { + continue; + } + match pilot_integer_row(witness, payload, pilot.span.clone()) { + Ok(Some(m)) => matches.push(m), + Ok(None) => {} + Err(diag) => return IntegerRangeLookup::Invalid(diag), + } + } + + if matches.is_empty() { + return IntegerRangeLookup::Missing; + } + if let Some(row) = matches.iter().find(|row| row.range.is_none()) { + return IntegerRangeLookup::Invalid(malformed_integer_range_fact( + format!( + "malformed rust_pilot_primitives IntegerPrimitive row for routing witness {:?}; integer literal range narrowing is unavailable", + (witness.algebra_variant_ty, witness.carrier_variant_ty) + ), + row.span.clone(), + )); + } + if matches.len() > 1 { + return IntegerRangeLookup::Invalid(malformed_integer_range_fact( + format!( + "duplicate rust_pilot_primitives IntegerPrimitive rows for routing witness {:?}; integer literal range narrowing is ambiguous", + (witness.algebra_variant_ty, witness.carrier_variant_ty) + ), + matches[1].span.clone(), + )); + } + let row = matches.remove(0); + match row.range { + Some(range) => IntegerRangeLookup::Found(range), + None => unreachable!("malformed matching rows are handled before duplicate detection"), + } +} + +struct PilotIntegerMatch { + range: Option, + span: SourceSpan, +} + +fn integer_routing_witness_walk( + dag: &Dag, + decl: DeclarationId, + depth: usize, +) -> Option { + if depth >= 32 { + return None; + } + let declaration = dag.declaration(decl); + match &declaration.connective { + TypeConnective::Atom(AtomPayload::ResolvedByName(next)) + | TypeConnective::Atom(AtomPayload::ResolvedByStructure(next)) => { + integer_routing_witness_walk(dag, *next, depth + 1) + } + TypeConnective::Instantiation { + template, + arguments, + } => { + if arguments.is_empty() { + return integer_routing_witness_walk(dag, *template, depth + 1); + } + if let Some(compose_id) = dag.declaration_by_name("Compose").map(|d| d.id) { + if *template == compose_id && arguments.len() == 2 { + if let Some(w) = compose_integer_routing_witness(dag, arguments.as_slice()) { + return Some(w); + } + } + } + let carrier = arguments.first()?.value; + integer_instantiation_witness(dag, *template, carrier) + } + _ => None, + } +} + +/// Gate #60 Phase 2.1: `MachineWidth`'s phantom argument may be a literal decimal +/// (`MachineWidth<64>`) naming the Nat magnitude; map to the existing `std.bit` width +/// authority (`Byte` / `Word16` / …) for the same integer-routing witness as Word*-spelled rows. +fn machine_width_inner_to_std_width_carrier_decl( + dag: &Dag, + inner_decl: DeclarationId, +) -> Option { + if std_word_carrier_to_target_carrier_variant_ty(dag, inner_decl).is_some() { + return Some(inner_decl); + } + match &dag.declaration(inner_decl).connective { + TypeConnective::Atom(AtomPayload::Literal(LiteralBits::Int(s))) => { + let n: u32 = s.parse().ok()?; + match n { + 8 => Some(dag.declaration_by_name("Byte")?.id), + 16 => Some(dag.declaration_by_name("Word16")?.id), + 32 => Some(dag.declaration_by_name("Word32")?.id), + 64 => Some(dag.declaration_by_name("Word64")?.id), + 128 => Some(dag.declaration_by_name("Word128")?.id), + _ => None, + } + } + _ => None, + } +} + +/// Fixed-width integers after gate #19: `Compose>` (signed) or +/// `Compose>` (unsigned). Maps to the same `(OrderedRing|Semiring, Word*)` +/// pilot routing key as the legacy `OrderedRing` / `Semiring` instantiations. +fn compose_integer_routing_witness( + dag: &Dag, + arguments: &[TemplateArgument], +) -> Option { + let int_id = dag.declaration_by_name("Int").map(|d| d.id)?; + let uint_id = dag.declaration_by_name("UInt").map(|d| d.id)?; + let machine_width = dag.declaration_by_name("MachineWidth").map(|d| d.id)?; + + let mut signed: Option = None; + let mut word_carrier: Option = None; + + for arg in arguments { + let v = arg.value; + if v == int_id { + signed = Some(true); + } else if v == uint_id { + signed = Some(false); + } else if let TypeConnective::Instantiation { + template, + arguments: mw_args, + } = &dag.declaration(v).connective + { + if *template == machine_width && mw_args.len() == 1 { + let inner = mw_args[0].value; + word_carrier = Some(machine_width_inner_to_std_width_carrier_decl(dag, inner)?); + } + } + } + + let signed = signed?; + let carrier_decl = word_carrier?; + let algebra_variant_ty = if signed { + disj_variant_payload_ty(dag, "IntegerAlgebra", "OrderedRingAlgebra")? + } else { + disj_variant_payload_ty(dag, "IntegerAlgebra", "SemiringAlgebra")? + }; + let carrier_variant_ty = std_word_carrier_to_target_carrier_variant_ty(dag, carrier_decl)?; + Some(IntegerRoutingWitness { + algebra_variant_ty, + carrier_variant_ty, + }) +} + +fn integer_instantiation_witness( + dag: &Dag, + template: DeclarationId, + carrier_decl: DeclarationId, +) -> Option { + let ordered_ring = dag.declaration_by_name("OrderedRing")?.id; + let semiring = dag.declaration_by_name("Semiring")?.id; + let algebra_variant_ty = if template == ordered_ring { + disj_variant_payload_ty(dag, "IntegerAlgebra", "OrderedRingAlgebra")? + } else if template == semiring { + disj_variant_payload_ty(dag, "IntegerAlgebra", "SemiringAlgebra")? + } else { + return None; + }; + let carrier_variant_ty = std_word_carrier_to_target_carrier_variant_ty(dag, carrier_decl)?; + Some(IntegerRoutingWitness { + algebra_variant_ty, + carrier_variant_ty, + }) +} + +fn disj_variant_payload_ty( + dag: &Dag, + sum_name: &str, + variant_label: &str, +) -> Option { + let decl = dag.declaration_by_name(sum_name)?; + let TypeConnective::Disj { variants } = &decl.connective else { + return None; + }; + variants + .iter() + .find(|v| v.label == variant_label) + .map(|v| v.ty) +} + +fn std_word_carrier_to_target_carrier_variant_ty( + dag: &Dag, + carrier_decl: DeclarationId, +) -> Option { + let byte = dag.declaration_by_name("Byte")?.id; + let word16 = dag.declaration_by_name("Word16")?.id; + let word32 = dag.declaration_by_name("Word32")?.id; + let word64 = dag.declaration_by_name("Word64")?.id; + let word128 = dag.declaration_by_name("Word128")?.id; + let label = if carrier_decl == byte { + "ByteCarrier" + } else if carrier_decl == word16 { + "Word16Carrier" + } else if carrier_decl == word32 { + "Word32Carrier" + } else if carrier_decl == word64 { + "Word64Carrier" + } else if carrier_decl == word128 { + "Word128Carrier" + } else { + return None; + }; + disj_variant_payload_ty(dag, "TargetCarrier", label) +} + +fn rust_primitive_integer_variant_ty(dag: &Dag) -> Option { + let rust_primitive = dag.declaration_by_name("RustPrimitive")?; + let TypeConnective::Disj { variants } = &rust_primitive.connective else { + return None; + }; + variants + .iter() + .find(|v| v.label == "IntegerPrimitive") + .map(|v| v.ty) +} + +fn pilot_integer_row( + witness: IntegerRoutingWitness, + payload: &[FieldValue], + default_span: SourceSpan, +) -> Result, Diagnostic> { + // IntegerPrimitive field order: target_name, algebra, carrier, range_*, is_copy, overflow + if payload.len() < 5 { + return Ok(None); + } + let FieldValue::Variant { + constructor: algebra_ctor, + .. + } = &payload[1] + else { + return Err(malformed_integer_range_fact( + "rust_pilot_primitives IntegerPrimitive `algebra` must be a variant value".to_string(), + default_span.clone(), + )); + }; + let FieldValue::Variant { + constructor: carrier_ctor, + .. + } = &payload[2] + else { + return Err(malformed_integer_range_fact( + "rust_pilot_primitives IntegerPrimitive `carrier` must be a variant value".to_string(), + default_span.clone(), + )); + }; + if *algebra_ctor != witness.algebra_variant_ty || *carrier_ctor != witness.carrier_variant_ty { + return Ok(None); + } + + let range = (|| { + let min_decimal = literal_string(payload.get(3)?)?; + let max_decimal = literal_string(payload.get(4)?)?; + let min = min_decimal.parse().ok()?; + let max = max_decimal.parse().ok()?; + if min > max { + return None; + } + Some(IntervalInt::ExactInterval { + target_name: literal_string(payload.first()?)?, + min_decimal, + max_decimal, + min, + max, + }) + })(); + + Ok(Some(PilotIntegerMatch { + range, + span: default_span, + })) +} + +fn literal_string(value: &FieldValue) -> Option { + match value { + FieldValue::Literal(LiteralBits::String(value)) => Some(value.clone()), + _ => None, + } +} + +pub(crate) fn literal_bigint_at(dag: &Dag, port: PortId) -> Option { + let s = match dag.resolve_producer_opt(&port)? { + Behavior::Value(value) => match &value.data { + LiteralBits::Int(s) => s.as_str(), + _ => return None, + }, + _ => return None, + }; + BigInt::from_str(s).ok() +} + +pub(crate) fn int_literal_fits_expected_type( + dag: &Dag, + literal: &BigInt, + expected: DeclarationId, +) -> Result, Diagnostic> { + match integer_range_for_decl(dag, expected) { + IntegerRangeLookup::Found(bound) => Ok(Some(bound.contains_bigint(literal))), + IntegerRangeLookup::Missing => Ok(None), + IntegerRangeLookup::Invalid(diag) => Err(diag), + } +} + +/// Build [`Diagnostic::MagnitudeOutOfRange`] from **exact** decimal range facts only. +/// +/// For a bound that may be [`IntervalInt::Unbounded`], use [`magnitude_out_of_range_for_interval`] +/// instead — this function does not accept [`IntervalInt`]. +pub(crate) fn magnitude_out_of_range( + literal: &BigInt, + expected: TypeShape, + facts: ExactIntIntervalFacts, + span: SourceSpan, +) -> Diagnostic { + Diagnostic::MagnitudeOutOfRange { + literal: literal.to_string(), + target: facts.target_name, + range_min_inclusive: facts.min_decimal, + range_max_inclusive: facts.max_decimal, + expected, + span, + fixes: Vec::new(), + } +} + +/// OOB diagnostic when the only available model is [`IntervalInt`] (e.g. from +/// [`integer_range_for_decl`]). Unbounded domains never produce `MagnitudeOutOfRange` for +/// finite literals; if that combination appears, fail closed without `unreachable!`. +pub(crate) fn magnitude_out_of_range_for_interval( + literal: &BigInt, + expected: TypeShape, + bound: IntervalInt, + span: SourceSpan, +) -> Diagnostic { + match bound.exact_interval_facts() { + Some(facts) => magnitude_out_of_range(literal, expected, facts, span), + None => Diagnostic::ResolveError { + name: "internal: integer literal failed range check but target has no exact interval facts" + .to_string(), + span, + fixes: Vec::new(), + }, + } +} + +fn malformed_integer_range_fact(message: String, span: SourceSpan) -> Diagnostic { + Diagnostic::MalformedIntegerRangeFact { + message, + span, + fixes: Vec::new(), + } +} + +/// Bootstrap-only gate: walk **every** `IntegerPrimitive` row in +/// `rust_pilot_primitives` and fail closed if any row is structurally +/// ill-formed, range strings do not parse as decimal `BigInt`, `min > max`, +/// or `(algebra, carrier)` witness pairs collide. +/// +/// Call this once when constructing the extdeps-including bootstrapped `Dag` +/// so drift or corruption in the pilot list surfaces at `Dag::new()`, +/// not only when a particular std type is queried. +pub(crate) fn validate_rust_pilot_integer_primitives(dag: &mut Dag) { + // R3 Phase B (Director Path A RATIFIED at gunbc#1739 #issuecomment-4392731264; + // Option (ii) at #issuecomment-4393145631; Phase B-1 at commit `59511503e`): + // pilot carries 10 IntegerPrimitive rows (i8..i64, i128, u8..u64, u128). + // u128 was unblocked by Phase A `IntervalInt::ExactInterval` BigInt host + // repr widening (commit `e7ba022c6`); the prior `T-Int128 Slice B1` / + // "Slice B2" deferral marker is resolved. + const EXPECTED_INTEGER_ROWS: usize = 10; + const INTEGER_PRIMITIVE_FIELD_COUNT: usize = 7; + + enum PilotListSnapshot { + List(Vec), + MissingBody, + NotList, + } + + // Authority file for span when the declaration is absent (extdeps fixture). + const RUST_PILOT_PRIMITIVES_AUTHORITY: &str = "dsl/extdeps/languages/rust/primitives.dag"; + + let (default_span, pilot_elements) = { + let Some(pilot) = dag.rust_pilot_primitives() else { + dag.attach_diagnostic(malformed_integer_range_fact( + "bootstrap: `rust_pilot_primitives` is missing from the extdeps fixture; \ + integer range authority is unavailable (expected extdeps `primitives.dag` load)" + .to_string(), + SourceSpan::new(RUST_PILOT_PRIMITIVES_AUTHORITY, 0, 0), + )); + return; + }; + let sp = pilot.span.clone(); + let snap = match pilot.value_body.as_ref() { + None => PilotListSnapshot::MissingBody, + Some(ValueBody::List(els)) => PilotListSnapshot::List(els.clone()), + Some(_) => PilotListSnapshot::NotList, + }; + (sp, snap) + }; + + let elements: &[FieldValue] = match &pilot_elements { + PilotListSnapshot::List(els) => els, + PilotListSnapshot::MissingBody => { + dag.attach_diagnostic(malformed_integer_range_fact( + "bootstrap: rust_pilot_primitives is missing a value body".to_string(), + default_span, + )); + return; + } + PilotListSnapshot::NotList => { + dag.attach_diagnostic(malformed_integer_range_fact( + "bootstrap: rust_pilot_primitives must be ValueBody::List".to_string(), + default_span, + )); + return; + } + }; + + let Some(integer_ctor) = rust_primitive_integer_variant_ty(dag) else { + dag.attach_diagnostic(malformed_integer_range_fact( + "bootstrap: RustPrimitive.IntegerPrimitive variant is unavailable".to_string(), + default_span, + )); + return; + }; + + let Some(ord_algebra) = disj_variant_payload_ty(dag, "IntegerAlgebra", "OrderedRingAlgebra") + else { + dag.attach_diagnostic(malformed_integer_range_fact( + "bootstrap: IntegerAlgebra.OrderedRingAlgebra is unavailable for pilot validation" + .to_string(), + default_span, + )); + return; + }; + let Some(sem_algebra) = disj_variant_payload_ty(dag, "IntegerAlgebra", "SemiringAlgebra") + else { + dag.attach_diagnostic(malformed_integer_range_fact( + "bootstrap: IntegerAlgebra.SemiringAlgebra is unavailable for pilot validation" + .to_string(), + default_span, + )); + return; + }; + let mut allowed_carriers: HashSet = HashSet::new(); + for label in [ + "ByteCarrier", + "Word16Carrier", + "Word32Carrier", + "Word64Carrier", + "Word128Carrier", + ] { + if let Some(c) = disj_variant_payload_ty(dag, "TargetCarrier", label) { + allowed_carriers.insert(c); + } + } + if allowed_carriers.is_empty() { + dag.attach_diagnostic(malformed_integer_range_fact( + "bootstrap: no TargetCarrier word variant payload types for pilot validation" + .to_string(), + default_span, + )); + return; + } + + let mut witnesses: HashSet<(DeclarationId, DeclarationId)> = HashSet::new(); + let mut integer_rows: usize = 0; + + for element in elements { + let FieldValue::Variant { + constructor, + payload, + } = element + else { + continue; + }; + if *constructor != integer_ctor { + continue; + } + integer_rows += 1; + + if payload.len() < INTEGER_PRIMITIVE_FIELD_COUNT { + dag.attach_diagnostic(malformed_integer_range_fact( + format!( + "rust_pilot_primitives IntegerPrimitive row has {} fields; expected {}", + payload.len(), + INTEGER_PRIMITIVE_FIELD_COUNT + ), + default_span.clone(), + )); + continue; + } + + let FieldValue::Variant { + constructor: algebra_ctor, + .. + } = &payload[1] + else { + dag.attach_diagnostic(malformed_integer_range_fact( + "rust_pilot_primitives IntegerPrimitive `algebra` must be a variant value" + .to_string(), + default_span.clone(), + )); + continue; + }; + if *algebra_ctor != ord_algebra && *algebra_ctor != sem_algebra { + dag.attach_diagnostic(malformed_integer_range_fact( + "rust_pilot_primitives IntegerPrimitive `algebra` must be OrderedRingAlgebra or SemiringAlgebra (variant payload type id)".to_string(), + default_span.clone(), + )); + continue; + } + + let FieldValue::Variant { + constructor: carrier_ctor, + .. + } = &payload[2] + else { + dag.attach_diagnostic(malformed_integer_range_fact( + "rust_pilot_primitives IntegerPrimitive `carrier` must be a variant value" + .to_string(), + default_span.clone(), + )); + continue; + }; + if !allowed_carriers.contains(carrier_ctor) { + dag.attach_diagnostic(malformed_integer_range_fact( + "rust_pilot_primitives IntegerPrimitive `carrier` must be a word TargetCarrier (Byte/Word16/Word32/Word64/Word128) variant payload type id".to_string(), + default_span.clone(), + )); + continue; + } + + if literal_string(&payload[0]).is_none() { + dag.attach_diagnostic(malformed_integer_range_fact( + "rust_pilot_primitives IntegerPrimitive `target_name` must be a string literal" + .to_string(), + default_span.clone(), + )); + continue; + } + + let (min_s, max_s) = match (literal_string(&payload[3]), literal_string(&payload[4])) { + (Some(a), Some(b)) => (a, b), + _ => { + dag.attach_diagnostic(malformed_integer_range_fact( + "rust_pilot_primitives IntegerPrimitive range bounds must be string literals" + .to_string(), + default_span.clone(), + )); + continue; + } + }; + + if !matches!(payload[5], FieldValue::Literal(LiteralBits::Bool(_))) { + dag.attach_diagnostic(malformed_integer_range_fact( + "rust_pilot_primitives IntegerPrimitive `is_copy` must be a bool literal" + .to_string(), + default_span.clone(), + )); + continue; + } + + if !matches!(&payload[6], FieldValue::Variant { .. }) { + dag.attach_diagnostic(malformed_integer_range_fact( + "rust_pilot_primitives IntegerPrimitive `overflow` must be a variant value" + .to_string(), + default_span.clone(), + )); + continue; + } + + let (min_n, max_n) = match (min_s.parse::(), max_s.parse::()) { + (Ok(mn), Ok(mx)) => (mn, mx), + _ => { + dag.attach_diagnostic(malformed_integer_range_fact( + format!( + "rust_pilot_primitives IntegerPrimitive range [{min_s}, {max_s}] must parse as a decimal integer" + ), + default_span.clone(), + )); + continue; + } + }; + if min_n > max_n { + dag.attach_diagnostic(malformed_integer_range_fact( + format!( + "rust_pilot_primitives IntegerPrimitive range order invalid: min {min_s} > max {max_s}" + ), + default_span.clone(), + )); + continue; + } + + if !witnesses.insert((*algebra_ctor, *carrier_ctor)) { + dag.attach_diagnostic(malformed_integer_range_fact( + format!( + "duplicate rust_pilot_primitives IntegerPrimitive (algebra, carrier) witness: ({algebra_ctor:?}, {carrier_ctor:?})" + ), + default_span.clone(), + )); + } + } + + if integer_rows != EXPECTED_INTEGER_ROWS { + dag.attach_diagnostic(malformed_integer_range_fact( + format!( + "rust_pilot_primitives must list exactly {EXPECTED_INTEGER_ROWS} IntegerPrimitive rows (pilot int scope); found {integer_rows}" + ), + default_span, + )); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::types::TypeShape; + + #[test] + fn magnitude_out_of_range_accepts_only_exact_int_interval_facts() { + let dag = Dag::new(); + let u8_decl = dag.declaration_by_name("UInt8").expect("UInt8").id; + let d = magnitude_out_of_range( + &BigInt::from(256), + TypeShape::new(u8_decl), + ExactIntIntervalFacts { + target_name: "u8".to_string(), + min_decimal: "0".to_string(), + max_decimal: "255".to_string(), + }, + SourceSpan::new("t.v3", 0, 0), + ); + assert!( + matches!( + d, + Diagnostic::MagnitudeOutOfRange { + ref literal, + ref target, + .. + } if literal == "256" && target == "u8" + ), + "expected MagnitudeOutOfRange, got {d:?}" + ); + } + + #[test] + fn magnitude_out_of_range_unbounded_target_fails_closed_with_resolve_error() { + let dag = Dag::new(); + let int_decl = dag.declaration_by_name("Int").expect("Int in bootstrap").id; + let d = magnitude_out_of_range_for_interval( + &BigInt::from(0), + TypeShape::new(int_decl), + IntervalInt::Unbounded, + SourceSpan::new("t.v3", 0, 0), + ); + assert!( + matches!(d, Diagnostic::ResolveError { .. }), + "expected fail-closed ResolveError, got {d:?}" + ); + } + + #[test] + fn interval_int_unbounded_accepts_sample_literals() { + assert!(IntervalInt::Unbounded.contains_bigint(&BigInt::from(i64::MIN))); + assert!(IntervalInt::Unbounded.contains_bigint(&BigInt::from(i64::MAX))); + } + + #[test] + fn int128_witness_matches_i128_pilot_row_constructors() { + // T-Int128 Slice B1: signed Int128 -> i128 pilot row via Word128Carrier. + let dag = Dag::new(); + assert!( + dag.diagnostics().is_empty(), + "bootstrap diagnostics: {:?}", + dag.diagnostics() + ); + let int128 = dag + .declaration_by_name("Int128") + .expect("Int128 in bootstrap") + .id; + let witness = integer_routing_witness_for_decl(&dag, int128).expect("Int128 witness"); + let pilot = dag.rust_pilot_primitives().expect("pilot"); + let ValueBody::List(elements) = pilot.value_body.as_ref().expect("list body") else { + panic!("expected list"); + }; + let integer_primitive_ctor = rust_primitive_integer_variant_ty(&dag).expect("ctor"); + let mut matched = 0usize; + for element in elements { + let FieldValue::Variant { + constructor, + payload, + } = element + else { + continue; + }; + if *constructor != integer_primitive_ctor { + continue; + } + let FieldValue::Variant { constructor: a, .. } = &payload[1] else { + continue; + }; + let FieldValue::Variant { constructor: c, .. } = &payload[2] else { + continue; + }; + if *a == witness.algebra_variant_ty && *c == witness.carrier_variant_ty { + matched += 1; + } + } + assert_eq!( + matched, 1, + "exactly one pilot IntegerPrimitive row for Int128 witness" + ); + } + + #[test] + fn int128_range_lookup_accepts_all_i64_magnitudes() { + // i128 row's range covers all i64 magnitudes; reconciliation passes + // typical literals through `contains_bigint`. + let dag = Dag::new(); + let int128 = dag + .declaration_by_name("Int128") + .expect("Int128 in bootstrap") + .id; + match integer_range_for_decl(&dag, int128) { + IntegerRangeLookup::Found(bound) => { + assert!(bound.contains_bigint(&BigInt::from(i64::MIN))); + assert!(bound.contains_bigint(&BigInt::from(0))); + assert!(bound.contains_bigint(&BigInt::from(i64::MAX))); + } + IntegerRangeLookup::Missing => panic!("expected Found range for Int128, got Missing"), + IntegerRangeLookup::Invalid(d) => { + panic!("expected Found range for Int128, got Invalid: {d:?}") + } + } + } + + #[test] + fn uint8_witness_matches_u8_pilot_row_constructors() { + let dag = Dag::new(); + assert!( + dag.diagnostics().is_empty(), + "bootstrap diagnostics: {:?}", + dag.diagnostics() + ); + let uint8 = dag + .declaration_by_name("UInt8") + .expect("UInt8 in bootstrap") + .id; + let witness = integer_routing_witness_for_decl(&dag, uint8).expect("UInt8 witness"); + let pilot = dag.rust_pilot_primitives().expect("pilot"); + let ValueBody::List(elements) = pilot.value_body.as_ref().expect("list body") else { + panic!("expected list"); + }; + let integer_primitive_ctor = rust_primitive_integer_variant_ty(&dag).expect("ctor"); + let mut matched = 0usize; + for element in elements { + let FieldValue::Variant { + constructor, + payload, + } = element + else { + continue; + }; + if *constructor != integer_primitive_ctor { + continue; + } + let FieldValue::Variant { constructor: a, .. } = &payload[1] else { + continue; + }; + let FieldValue::Variant { constructor: c, .. } = &payload[2] else { + continue; + }; + if *a == witness.algebra_variant_ty && *c == witness.carrier_variant_ty { + matched += 1; + } + } + assert_eq!( + matched, 1, + "exactly one pilot IntegerPrimitive row for UInt8 witness" + ); + } +} diff --git a/src/v3/compiler/pb0_cycle4_emit_templates/pb_method_template_projection.rs b/src/v3/compiler/pb0_cycle4_emit_templates/pb_method_template_projection.rs new file mode 100644 index 00000000000..4b6ab40c29e --- /dev/null +++ b/src/v3/compiler/pb0_cycle4_emit_templates/pb_method_template_projection.rs @@ -0,0 +1,1447 @@ +//! PB-Bootstrap-Process consumer hook: project `MethodTemplateContract` rows +//! from the full bootstrap `Dag`. +//! +//! Implements **R3 row 85 / PB #1560 Gap 4** per the decision in +//! `docs/decisions/r3-row85-method-template-read-surface.md`. The single +//! row authority remains `src/v3/std/{rust,python,go}_method_template_contracts.dag`; +//! this module exposes a target-keyed structural projection of those rows +//! over the lowered bootstrap `Dag` so PB-zero / v2-retirement consumers can +//! select rows by target + `MethodRef` without: +//! +//! - importing `v3.std.*` into v2 crates (Gap 4 acceptance A1), +//! - introducing a second `Map` template-text authority (A2), +//! - copying any template strings into v2 sources (A3). +//! +//! All five `MethodTemplateContract` fields (`dag_method`, `runtime_template`, +//! `emit_template`, `wraps_result`, `placeholder_convention`) are preserved +//! by typed Rust mirrors below (A4). `emit_template` and +//! `placeholder_convention` are sum carriers; their substrate identity is +//! preserved as constructor-tagged enums, not flattened to strings. +//! +//! Gap 4 only — `LanguageSpec.method_templates` rewrite (Gap 5) and v2 leaf +//! emit migration are sequenced strictly after this hook lands. No v2 +//! consumer is migrated by this module. + +use std::collections::HashMap; + +use crate::dag::{Dag, DeclarationId, FieldValue, LiteralBits, TypeConnective, ValueBody}; + +/// Target language whose `MethodTemplateContract` row list is being projected. +/// +/// 🟢 TERMINAL at the per-target row-list scope. The three inhabitants +/// mirror the three closed list-declaration authorities under +/// `src/v3/std/{rust,python,go}_method_template_contracts.dag`. New +/// targets land by adding a row-list authority + a constructor here +/// together; no other dissolution path applies. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum MethodTemplateTarget { + Rust, + Python, + Go, +} + +impl MethodTemplateTarget { + /// Per-target list-declaration name in the bootstrap `Dag`. The strings + /// here mirror the data declaration names in + /// `src/v3/std/{rust,python,go}_method_template_contracts.dag` and are + /// the *names of structural declarations*, not template text. + fn list_declaration_name(self) -> &'static str { + match self { + MethodTemplateTarget::Rust => "rust_method_template_contracts", + MethodTemplateTarget::Python => "python_method_template_contracts", + MethodTemplateTarget::Go => "go_method_template_contracts", + } + } +} + +/// Projected `MethodEmitTemplate` (substrate sum at +/// `src/v3/std/emit_model.dag:469-474`). +/// +/// 🟢 TERMINAL at target method-emission-template scope, inheriting the +/// terminality of the substrate carrier (`MethodEmitTemplate` is marked +/// 🟢 TERMINAL at `emit_model.dag:453`). The two-variant split between +/// inline-lambda and function-reference renderings is the closed semantic +/// case the substrate already locks; new variants here would mean the +/// substrate carrier itself moved. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum MethodEmitTemplateProjection { + Single { + template: String, + }, + HigherOrder { + inline_template: String, + fn_ref_template: String, + }, +} + +/// Projected `PlaceholderConvention` (substrate sum at +/// `src/v3/std/emit_model.dag:449-451`). +/// +/// 🟢 TERMINAL: nullary two-inhabitant sum (`IndexedArgs | NamedArg`) that +/// mirrors the closed substrate carrier. New conventions land by editing +/// the substrate sum first; this projection follows. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PlaceholderConventionProjection { + IndexedArgs, + NamedArg, +} + +/// Projected single `MethodTemplateContract` row. +/// +/// `dag_method` is the `DeclarationId` of the `MethodDeclaration` that the +/// row's `MethodRef.decl` field references (the typed, fail-closed substrate +/// identity for the method). Consumers select rows by target + +/// `dag_method` to recover the `(target, method)` template pair. +#[derive(Debug, Clone)] +pub struct MethodTemplateContractRow { + pub dag_method: DeclarationId, + pub runtime_template: String, + pub emit_template: MethodEmitTemplateProjection, + pub wraps_result: bool, + pub placeholder_convention: PlaceholderConventionProjection, +} + +/// Fail-closed projection error. Every observable mismatch between the +/// bootstrap `Dag` shape and the `MethodTemplateContract` substrate carrier +/// surfaces here as a typed value (per `INVARIANTS.md` C-8). +/// +/// 🟢 TERMINAL at the projection's bootstrap-mismatch scope. Each variant +/// names a specific structural class the row authorities cannot satisfy; +/// new variants only land if substrate shape grows a new sub-fact (which +/// would itself follow §P1). The carrier is therefore a closed +/// substrate-mismatch enumeration, not a transitional grab-bag. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum MethodTemplateProjectionError { + /// Per-target row-list declaration is absent from the bootstrap `Dag`. + /// Should be impossible if `BOOTSTRAP_FIXTURE_PATH_KEYS` (`bootstrap.rs`) + /// stays in lockstep with `bootstrap_fixture_authority` and + /// `regen_bootstrap --verify` is green; surfaced rather than panicked + /// so consumers can act on it. + ListDeclarationMissing { name: &'static str }, + /// List declaration has no value body (i.e., not a `data` declaration). + ListDeclarationLacksValueBody { name: &'static str }, + /// List declaration's value body is not `ValueBody::List` — violates the + /// `bootstrap_method_template_contracts_lower_to_list_with_empty_diagnostics` + /// gate per the row-85 decision doc. + ListDeclarationValueBodyNotList { name: &'static str }, + /// Row at the given index in the named list is not a record literal. + RowNotRecord { + list: &'static str, + row_index: usize, + }, + /// Row is a declaration reference, but the referenced declaration is not + /// present in the bootstrap `Dag`. + RowReferenceMissing { + list: &'static str, + row_index: usize, + decl_id: DeclarationId, + }, + /// Row is a declaration reference, but the referenced declaration is not a + /// `data` declaration carrying a structural `MethodTemplateContract` body. + RowReferenceNotStructural { + list: &'static str, + row_index: usize, + decl_id: DeclarationId, + }, + /// Row is a declaration reference, but the referenced declaration does + /// not instantiate the `MethodTemplateContract` carrier. A structural + /// lookalike row is still not a contract row authority. + RowReferenceNotMethodTemplateContract { + list: &'static str, + row_index: usize, + decl_id: DeclarationId, + }, + /// Row is missing one of the five required fields. + RowMissingField { + list: &'static str, + row_index: usize, + field: &'static str, + }, + /// Row carries a duplicate field — two entries share a label. The + /// substrate-side `MethodTemplateContract` (and `MethodRef`) carriers + /// declare each field exactly once; duplicates would mean two + /// authorities for the same row coordinate (P2 single-authority). + RowDuplicateField { + list: &'static str, + row_index: usize, + record: &'static str, + field: &'static str, + first_field_index: usize, + duplicate_field_index: usize, + }, + /// Row carries a field whose label is not one of the closed schema + /// (`MethodTemplateContract` = five fields; `MethodRef` = one field). + /// Extra substrate fields land via the substrate carrier, not via + /// projection-side acceptance, so unknown labels surface here rather + /// than being silently ignored (P2 / C-8). + RowUnknownField { + list: &'static str, + row_index: usize, + record: &'static str, + field: String, + field_index: usize, + }, + /// `dag_method` field is not the `MethodRef { decl }` record shape. + DagMethodNotMethodRefRecord { + list: &'static str, + row_index: usize, + }, + /// `MethodRef.decl` is not a typed substrate reference. + MethodRefDeclNotReference { + list: &'static str, + row_index: usize, + }, + /// `MethodRef.decl` references a declaration that does not satisfy + /// the substrate-side `MethodDeclaration` *data-binding* contract + /// (`src/v3/std/methods.dag` + `dsl/std/methods.dag`). Mirrors the + /// Stratum A registry gate + /// (`src/v3/grounding_tests/src/stratum_a.rs::method_registry_name`): + /// the binding must (a) instantiate `MethodDeclaration`, (b) carry + /// a `Structural` value body, (c) have a closed single-`name` field + /// schema, (d) and the `name` value must be a string literal. The + /// `reason` payload identifies which sub-check failed. + MethodRefDeclNotMethodDeclaration { + list: &'static str, + row_index: usize, + decl_id: DeclarationId, + reason: MethodDeclarationBindingViolation, + }, + /// `method_template_contract_row` was called with a `dag_method` + /// `DeclarationId` that does not satisfy the + /// `MethodDeclaration` data-binding contract (same shape and + /// `reason` taxonomy as `MethodRefDeclNotMethodDeclaration`). The + /// helper validates at entry so callers cannot conflate + /// "valid method, no row" with "caller handed in a non-method + /// declaration." + LookupKeyNotMethodDeclaration { + decl_id: DeclarationId, + reason: MethodDeclarationBindingViolation, + }, + /// `MethodDeclaration` itself is missing from the bootstrap `Dag`. + /// Should be impossible if the std fixtures lower cleanly; surfaced + /// rather than panicked so consumers can act on it. + MethodDeclarationCarrierMissing, + /// `runtime_template` field is not a string literal. + RuntimeTemplateNotString { + list: &'static str, + row_index: usize, + }, + /// `wraps_result` field is not a bool literal. + WrapsResultNotBool { + list: &'static str, + row_index: usize, + }, + /// `placeholder_convention` field is not a substrate variant constructor. + PlaceholderConventionNotVariant { + list: &'static str, + row_index: usize, + }, + /// `placeholder_convention` constructor name is unknown. + PlaceholderConventionUnknown { + list: &'static str, + row_index: usize, + constructor: Option, + }, + /// `placeholder_convention` is a nullary sum (`IndexedArgs | NamedArg`), + /// but the variant payload is non-empty. Mirrors the substrate-side + /// canonical-row check + /// `placeholder_convention_canonical_rejects_payload_for_nullary_indexed_args` + /// in `src/v3/grounding_tests/src/stratum_a.rs`. + PlaceholderConventionPayloadNotEmpty { + list: &'static str, + row_index: usize, + constructor: &'static str, + payload_len: usize, + }, + /// `emit_template` field is not a substrate variant constructor. + EmitTemplateNotVariant { + list: &'static str, + row_index: usize, + }, + /// `emit_template` constructor name is unknown. + EmitTemplateUnknownConstructor { + list: &'static str, + row_index: usize, + constructor: Option, + }, + /// `emit_template` `SingleTemplate` payload shape mismatch (expected one + /// string-typed `template` field). + EmitTemplateSinglePayloadShape { + list: &'static str, + row_index: usize, + }, + /// `emit_template` `HigherOrderTemplates` payload shape mismatch + /// (expected two string-typed fields: `inline_template`, `fn_ref_template`). + EmitTemplateHigherOrderPayloadShape { + list: &'static str, + row_index: usize, + }, + /// The `(target, dag_method)` direct lookup helper found more than one row + /// for the same `MethodDeclaration` in the per-target list. Per-target + /// uniqueness by `dag_method` is the substrate-side claim + /// (`method_template_contract_per_target_dag_method_unique`); the helper + /// surfaces a violation rather than silently selecting the first row + /// (P2 single-authority + P3 fail-closed at the public lookup boundary). + DuplicateMethodTemplateRow { + list: &'static str, + first_row_index: usize, + duplicate_row_index: usize, + }, +} + +/// Granular reason for a `MethodDeclaration` data-binding contract failure. +/// +/// 🟢 TERMINAL at the substrate-side data-binding contract scope +/// (`src/v3/std/methods.dag` + Stratum A `method_registry_name`). Each +/// variant maps one of the four sub-checks the contract requires; the +/// substrate-side contract is closed (`MethodDeclaration` carries a +/// single `name: String` field), so this enumeration is closed too. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum MethodDeclarationBindingViolation { + /// Connective is not `TypeConnective::Instantiation { template, .. }`. + /// Rules out type / sum / record declarations. + ConnectiveNotInstantiation, + /// `Instantiation` connective, but `template` does not match + /// `MethodDeclaration`'s declaration id. Rules out instantiations of + /// unrelated targets. + InstantiationTemplateNotMethodDeclaration, + /// `value_body` is `None`. Rules out type aliases of the form + /// `type Foo = MethodDeclaration`. + ValueBodyMissing, + /// `value_body` is `Some(_)` but not `ValueBody::Structural`. Rules + /// out `List` / `Map` / scalar bodies that would be wrong-shaped for + /// a `MethodDeclaration` data binding. + ValueBodyNotStructural, + /// `Structural` body, but its field set does not match the closed + /// `MethodDeclaration` schema (single `name` field). Mirrors + /// `enforce_closed_record_schema` in Stratum A. + StructuralFieldsNotClosedNameOnly { observed_labels: Vec }, + /// Closed-schema match, but the `name` field's `FieldValue` is not + /// `Literal(LiteralBits::String(_))`. + NameNotStringLiteral, +} + +/// Project the `MethodTemplateContract` rows for `target` from the full +/// bootstrap `Dag` snapshot. +/// +/// The expected source for `dag` is `generated_full_bootstrap_dag()` (or its +/// equivalent under PB-1-e dissolution-first per #1558 — see the +/// row-85 decision doc §"Non-Fork Ratchet"). Calling on a partially-built +/// `Dag` that is missing the per-target list authorities surfaces +/// [`MethodTemplateProjectionError::ListDeclarationMissing`] rather than +/// panicking. +/// +/// **Boundary contract:** this is the canonical PB-zero consumer hook for +/// row text. v2-side migrations consume `Vec` and +/// must not store or carry parallel `Map` template tables; +/// the structural carrier is the typed projection above. +pub fn method_template_contract_rows( + dag: &Dag, + target: MethodTemplateTarget, +) -> Result, MethodTemplateProjectionError> { + let list_name = target.list_declaration_name(); + let decl = dag + .declaration_by_name(list_name) + .ok_or(MethodTemplateProjectionError::ListDeclarationMissing { name: list_name })?; + let body = decl + .value_body + .as_ref() + .ok_or(MethodTemplateProjectionError::ListDeclarationLacksValueBody { name: list_name })?; + let ValueBody::List(rows) = body else { + return Err( + MethodTemplateProjectionError::ListDeclarationValueBodyNotList { name: list_name }, + ); + }; + + let projected: Vec = rows + .iter() + .enumerate() + .map(|(row_index, row)| project_row(dag, list_name, row_index, row)) + .collect::>()?; + + // Per-target uniqueness by `dag_method` is the substrate-side claim + // (`method_template_contract_per_target_dag_method_unique`). Enforce it + // at the canonical row-list boundary so v2-retirement consumers that + // build their own keyed tables from the projection cannot silently + // observe two authorities for the same `(target, MethodRef)` pair. + let mut first_seen: HashMap = HashMap::with_capacity(projected.len()); + for (index, row) in projected.iter().enumerate() { + if let Some(prior) = first_seen.get(&row.dag_method) { + return Err(MethodTemplateProjectionError::DuplicateMethodTemplateRow { + list: list_name, + first_row_index: *prior, + duplicate_row_index: index, + }); + } + first_seen.insert(row.dag_method, index); + } + + Ok(projected) +} + +/// Direct `(target, dag_method)` lookup helper for Gap-5 / leaf-emit +/// consumers. Returns the row whose `dag_method` matches `dag_method`, or +/// `None` if the per-target list does not contain a row keyed by that +/// `MethodDeclaration`. +/// +/// **Fail-closed on non-method keys.** `dag_method` must instantiate +/// `MethodDeclaration` — the helper validates this at entry so a caller +/// cannot conflate two distinct states: "valid `MethodDeclaration` with +/// no row for this target" (returns `Ok(None)`) versus "caller supplied +/// a declaration that is not a `MethodDeclaration` at all" (returns +/// [`MethodTemplateProjectionError::LookupKeyNotMethodDeclaration`]). +/// This mirrors the per-row `MethodRef.decl` check the projection +/// already enforces; until `DeclarationRef` +/// refinement-typing lands, this is the only place the boundary can +/// hold. +/// +/// Other typed projection failures — including +/// [`MethodTemplateProjectionError::DuplicateMethodTemplateRow`], which is +/// enforced at the canonical row-list boundary in +/// [`method_template_contract_rows`] — bubble through. +pub fn method_template_contract_row( + dag: &Dag, + target: MethodTemplateTarget, + dag_method: DeclarationId, +) -> Result, MethodTemplateProjectionError> { + let method_declaration_id = method_declaration_carrier_id(dag)?; + if let Err(reason) = + validate_method_declaration_data_binding(dag, dag_method, method_declaration_id) + { + return Err( + MethodTemplateProjectionError::LookupKeyNotMethodDeclaration { + decl_id: dag_method, + reason, + }, + ); + } + let rows = method_template_contract_rows(dag, target)?; + Ok(rows.into_iter().find(|row| row.dag_method == dag_method)) +} + +/// Locate the `MethodDeclaration` carrier declaration in the bootstrap +/// `Dag` once. Surfaces [`MethodTemplateProjectionError::MethodDeclarationCarrierMissing`] +/// when the carrier is absent rather than panicking. +pub(crate) fn method_declaration_carrier_id( + dag: &Dag, +) -> Result { + Ok(dag + .declaration_by_name("MethodDeclaration") + .ok_or(MethodTemplateProjectionError::MethodDeclarationCarrierMissing)? + .id) +} + +/// Validate that `decl_id` references a `MethodDeclaration` *data binding* +/// per the substrate-side contract (`src/v3/std/methods.dag` + +/// `dsl/std/methods.dag`). +/// +/// Mirrors `src/v3/grounding_tests/src/stratum_a.rs::method_registry_name` +/// so projection-side and Stratum A-side enforcement of the same gate +/// stay in lockstep. Four sub-checks must all pass: +/// +/// 1. **Connective is `Instantiation`.** Rules out type / sum / record +/// declarations. +/// 2. **`Instantiation.template == MethodDeclaration`.** Rules out +/// instantiations of unrelated targets. +/// 3. **`value_body` is `Some(ValueBody::Structural { .. })`.** Rules +/// out type aliases (`type Foo = MethodDeclaration`, no value body) +/// and wrong-shaped data bindings (`List` / `Map` / scalar bodies). +/// 4. **`Structural` fields are exactly `[name]` with a string literal +/// value.** Closed schema check — mirrors +/// `enforce_closed_record_schema` in Stratum A. Rules out extra +/// fields, missing `name`, duplicate `name`, and non-string-literal +/// `name` values. +/// +/// On success returns the projected method name (`name` field's string +/// literal). On failure returns the typed +/// [`MethodDeclarationBindingViolation`] reason. +/// +/// Both `project_dag_method` (per-row) and `method_template_contract_row` +/// (lookup helper) consume this helper so the data-binding identity is +/// factored once. +pub(crate) fn validate_method_declaration_data_binding( + dag: &Dag, + decl_id: DeclarationId, + method_declaration_id: DeclarationId, +) -> Result { + let referenced = dag.declaration(decl_id); + + // (1) + (2) Connective + template. + let template = match &referenced.connective { + TypeConnective::Instantiation { template, .. } => *template, + _ => return Err(MethodDeclarationBindingViolation::ConnectiveNotInstantiation), + }; + if template != method_declaration_id { + return Err(MethodDeclarationBindingViolation::InstantiationTemplateNotMethodDeclaration); + } + + // (3) Value body present and Structural. + let value_body = referenced + .value_body + .as_ref() + .ok_or(MethodDeclarationBindingViolation::ValueBodyMissing)?; + let ValueBody::Structural { fields } = value_body else { + return Err(MethodDeclarationBindingViolation::ValueBodyNotStructural); + }; + + // (4) Closed `[name]` schema with string-literal value. Reject any + // duplicate or unknown label, require `name` to be present exactly + // once. Mirrors Stratum A's enforce_closed_record_schema. + const EXPECTED: &[&str] = &["name"]; + let name_count = fields.iter().filter(|(label, _)| label == "name").count(); + let any_unknown = fields + .iter() + .any(|(label, _)| !EXPECTED.contains(&label.as_str())); + if fields.len() != EXPECTED.len() || name_count != 1 || any_unknown { + let observed_labels: Vec = fields.iter().map(|(label, _)| label.clone()).collect(); + return Err( + MethodDeclarationBindingViolation::StructuralFieldsNotClosedNameOnly { + observed_labels, + }, + ); + } + let (_, name_value) = fields + .iter() + .find(|(label, _)| label == "name") + .expect("closed-schema check above guarantees a single `name` field"); + match name_value { + FieldValue::Literal(LiteralBits::String(s)) => Ok(s.clone()), + _ => Err(MethodDeclarationBindingViolation::NameNotStringLiteral), + } +} + +fn project_row( + dag: &Dag, + list: &'static str, + row_index: usize, + row: &FieldValue, +) -> Result { + let fields = match row { + FieldValue::Record(fields) => fields, + FieldValue::Reference(decl_id) => { + let decl = dag.declaration_opt(decl_id).ok_or( + MethodTemplateProjectionError::RowReferenceMissing { + list, + row_index, + decl_id: *decl_id, + }, + )?; + let method_template_contract = dag.method_template_contract_decl().ok_or( + MethodTemplateProjectionError::RowReferenceNotMethodTemplateContract { + list, + row_index, + decl_id: *decl_id, + }, + )?; + let TypeConnective::Instantiation { template, .. } = &decl.connective else { + return Err( + MethodTemplateProjectionError::RowReferenceNotMethodTemplateContract { + list, + row_index, + decl_id: *decl_id, + }, + ); + }; + if *template != method_template_contract { + return Err( + MethodTemplateProjectionError::RowReferenceNotMethodTemplateContract { + list, + row_index, + decl_id: *decl_id, + }, + ); + } + match decl.value_body.as_ref() { + Some(ValueBody::Structural { fields }) => fields, + _ => { + return Err(MethodTemplateProjectionError::RowReferenceNotStructural { + list, + row_index, + decl_id: *decl_id, + }); + } + } + } + _ => return Err(MethodTemplateProjectionError::RowNotRecord { list, row_index }), + }; + + const EXPECTED: &[&str] = &[ + "dag_method", + "runtime_template", + "emit_template", + "wraps_result", + "placeholder_convention", + ]; + let lookup = closed_record_lookup(list, row_index, "MethodTemplateContract", fields, EXPECTED)?; + + let dag_method_value = lookup_required(list, row_index, &lookup, "dag_method")?; + let dag_method = project_dag_method(dag, list, row_index, dag_method_value)?; + + let runtime_template_value = lookup_required(list, row_index, &lookup, "runtime_template")?; + let runtime_template = match runtime_template_value { + FieldValue::Literal(LiteralBits::String(s)) => s.clone(), + _ => { + return Err(MethodTemplateProjectionError::RuntimeTemplateNotString { + list, + row_index, + }); + } + }; + + let emit_template_value = lookup_required(list, row_index, &lookup, "emit_template")?; + let emit_template = project_emit_template(dag, list, row_index, emit_template_value)?; + + let wraps_result_value = lookup_required(list, row_index, &lookup, "wraps_result")?; + let wraps_result = match wraps_result_value { + FieldValue::Literal(LiteralBits::Bool(b)) => *b, + _ => return Err(MethodTemplateProjectionError::WrapsResultNotBool { list, row_index }), + }; + + let placeholder_value = lookup_required(list, row_index, &lookup, "placeholder_convention")?; + let placeholder_convention = project_placeholder(dag, list, row_index, placeholder_value)?; + + Ok(MethodTemplateContractRow { + dag_method, + runtime_template, + emit_template, + wraps_result, + placeholder_convention, + }) +} + +/// Validate that `fields` carries exactly the closed set of `expected` +/// labels — no duplicates, no unknowns. Missing labels are not reported +/// here (the per-field `lookup_required` calls below surface those as +/// `RowMissingField` so the error names *which* field is missing). +/// +/// Returns a label-to-`FieldValue` map for the present fields; collisions +/// surface as `RowDuplicateField` and unknown labels surface as +/// `RowUnknownField`. `record` names the carrier (e.g. +/// `"MethodTemplateContract"` or `"MethodRef"`) for diagnostic context. +fn closed_record_lookup<'a>( + list: &'static str, + row_index: usize, + record: &'static str, + fields: &'a [(String, FieldValue)], + expected: &[&'static str], +) -> Result, MethodTemplateProjectionError> { + let mut lookup: HashMap<&'static str, &FieldValue> = HashMap::with_capacity(expected.len()); + let mut first_seen_index: HashMap<&'static str, usize> = HashMap::with_capacity(expected.len()); + for (field_index, (label, value)) in fields.iter().enumerate() { + // Resolve the row-side `String` label to its `&'static str` in + // `expected`. Holding the static reference avoids re-scanning the + // expected slice (and the `.expect()` re-borrow) on the duplicate + // path; non-membership surfaces as `RowUnknownField`. + let Some(static_label) = expected.iter().copied().find(|exp| *exp == label.as_str()) else { + return Err(MethodTemplateProjectionError::RowUnknownField { + list, + row_index, + record, + field: label.clone(), + field_index, + }); + }; + if let Some(prior_index) = first_seen_index.get(static_label) { + return Err(MethodTemplateProjectionError::RowDuplicateField { + list, + row_index, + record, + field: static_label, + first_field_index: *prior_index, + duplicate_field_index: field_index, + }); + } + lookup.insert(static_label, value); + first_seen_index.insert(static_label, field_index); + } + Ok(lookup) +} + +fn lookup_required<'a>( + list: &'static str, + row_index: usize, + lookup: &HashMap<&'a str, &'a FieldValue>, + field: &'static str, +) -> Result<&'a FieldValue, MethodTemplateProjectionError> { + lookup + .get(field) + .copied() + .ok_or(MethodTemplateProjectionError::RowMissingField { + list, + row_index, + field, + }) +} + +fn project_dag_method( + dag: &Dag, + list: &'static str, + row_index: usize, + value: &FieldValue, +) -> Result { + let FieldValue::Record(method_ref_fields) = value else { + return Err(MethodTemplateProjectionError::DagMethodNotMethodRefRecord { list, row_index }); + }; + // `MethodRef` is a single-field carrier (`{ decl: DeclarationRef }`) + // per `src/v3/std/methods.dag`. Apply the same closed-schema check + // as the outer row so duplicates (`{ decl: a, decl: b }`) and + // unknown extras (`{ decl: ..., extra: ... }`) surface as typed + // errors instead of being silently ignored. + const METHOD_REF_FIELDS: &[&str] = &["decl"]; + let lookup = closed_record_lookup( + list, + row_index, + "MethodRef", + method_ref_fields, + METHOD_REF_FIELDS, + )?; + let decl_value = lookup + .get("decl") + .copied() + .ok_or(MethodTemplateProjectionError::DagMethodNotMethodRefRecord { list, row_index })?; + let decl_id = match decl_value { + FieldValue::Reference(decl_id) => *decl_id, + _ => { + return Err(MethodTemplateProjectionError::MethodRefDeclNotReference { + list, + row_index, + }); + } + }; + // `src/v3/std/methods.dag` calls out the boundary contract: today's + // substrate grammar can't express `DeclarationRef`, + // so the projection enforces fail-closed at the boundary that + // `decl` references a `MethodDeclaration`-instantiating data binding. + // Pattern mirrors `method_registry_test.rs::method_registry_covers_*`. + let method_declaration_id = method_declaration_carrier_id(dag)?; + if let Err(reason) = + validate_method_declaration_data_binding(dag, decl_id, method_declaration_id) + { + return Err( + MethodTemplateProjectionError::MethodRefDeclNotMethodDeclaration { + list, + row_index, + decl_id, + reason, + }, + ); + } + Ok(decl_id) +} + +fn project_emit_template( + dag: &Dag, + list: &'static str, + row_index: usize, + value: &FieldValue, +) -> Result { + let FieldValue::Variant { + constructor, + payload, + } = value + else { + return Err(MethodTemplateProjectionError::EmitTemplateNotVariant { list, row_index }); + }; + let constructor_label = disj_variant_label(dag, "MethodEmitTemplate", *constructor); + match constructor_label { + Some("SingleTemplate") => { + // Payload shape (positional, per FieldValue::Variant): one + // `template: String` field. Lowering produces a single + // record-shaped payload entry whose only field is `template`. + let template = single_template_payload(list, row_index, payload)?; + Ok(MethodEmitTemplateProjection::Single { template }) + } + Some("HigherOrderTemplates") => { + let (inline_template, fn_ref_template) = + higher_order_payload(list, row_index, payload)?; + Ok(MethodEmitTemplateProjection::HigherOrder { + inline_template, + fn_ref_template, + }) + } + other => Err( + MethodTemplateProjectionError::EmitTemplateUnknownConstructor { + list, + row_index, + constructor: other.map(str::to_string), + }, + ), + } +} + +/// Recover a sum-variant label by walking the parent `Disj` declaration's +/// variant list. Variant constructor declarations themselves do not carry +/// `name`; identity flows through the parent's `variants[i].label`. Mirrors +/// the pattern used in `tests/integration/method_template_contract_test.rs` +/// (`method_emit_template_variant_label`). +fn disj_variant_label<'a>( + dag: &'a Dag, + parent_name: &str, + constructor: DeclarationId, +) -> Option<&'a str> { + let parent = dag.declaration_by_name(parent_name)?; + let TypeConnective::Disj { variants } = &parent.connective else { + return None; + }; + variants + .iter() + .find(|variant| variant.ty == constructor) + .map(|variant| variant.label.as_str()) +} + +fn single_template_payload( + list: &'static str, + row_index: usize, + payload: &[FieldValue], +) -> Result { + // `Variant.payload` is positional per the substrate carrier + // (`FieldValue::Variant` doc-comment). `SingleTemplate { template: String }` + // therefore lowers to a single-element payload carrying the string literal. + match payload { + [FieldValue::Literal(LiteralBits::String(s))] => Ok(s.clone()), + _ => Err(MethodTemplateProjectionError::EmitTemplateSinglePayloadShape { list, row_index }), + } +} + +fn higher_order_payload( + list: &'static str, + row_index: usize, + payload: &[FieldValue], +) -> Result<(String, String), MethodTemplateProjectionError> { + // `HigherOrderTemplates { inline_template: String, fn_ref_template: String }` + // lowers to a two-element positional payload carrying the two string + // literals in declaration order. + match payload { + [FieldValue::Literal(LiteralBits::String(inline)), FieldValue::Literal(LiteralBits::String(fn_ref))] => { + Ok((inline.clone(), fn_ref.clone())) + } + _ => Err( + MethodTemplateProjectionError::EmitTemplateHigherOrderPayloadShape { list, row_index }, + ), + } +} + +fn project_placeholder( + dag: &Dag, + list: &'static str, + row_index: usize, + value: &FieldValue, +) -> Result { + let FieldValue::Variant { + constructor, + payload, + } = value + else { + return Err( + MethodTemplateProjectionError::PlaceholderConventionNotVariant { list, row_index }, + ); + }; + let constructor_label = disj_variant_label(dag, "PlaceholderConvention", *constructor); + // `PlaceholderConvention` is a nullary sum (`IndexedArgs | NamedArg`) + // per `src/v3/std/emit_model.dag:449-451`. A non-empty payload would + // mean a malformed bootstrap row (e.g., `IndexedArgs(false)`) that the + // projection must surface as a typed error rather than silently + // discarding. Mirrors the substrate-side canonical-row check in + // `src/v3/grounding_tests/src/stratum_a.rs`. + let (label, projection) = match constructor_label { + Some("IndexedArgs") => ("IndexedArgs", PlaceholderConventionProjection::IndexedArgs), + Some("NamedArg") => ("NamedArg", PlaceholderConventionProjection::NamedArg), + other => { + return Err( + MethodTemplateProjectionError::PlaceholderConventionUnknown { + list, + row_index, + constructor: other.map(str::to_string), + }, + ); + } + }; + if !payload.is_empty() { + return Err( + MethodTemplateProjectionError::PlaceholderConventionPayloadNotEmpty { + list, + row_index, + constructor: label, + payload_len: payload.len(), + }, + ); + } + Ok(projection) +} + +#[cfg(test)] +mod tests { + //! Unit tests for projection-internal failure modes that require + //! crate-private `Dag` mutation to construct (e.g., injecting a + //! malformed value body that the on-disk row authorities cannot + //! produce). Behavior tests over the green bootstrap Dag live in + //! `tests/integration/pb_method_template_projection_test.rs`. + + use super::*; + use crate::generated_full_bootstrap_dag; + + /// Inject a non-empty payload onto the `placeholder_convention` variant + /// of the row at `row_index` of the named per-target list. Mutates only + /// the in-memory `Dag`; the row authority sources on disk are + /// untouched. + fn inject_extra_placeholder_payload(dag: &mut Dag, list_name: &str, row_index: usize) { + let decl_id = dag + .declaration_by_name(list_name) + .unwrap_or_else(|| panic!("`{list_name}` missing from bootstrap")) + .id; + let decl = dag.declaration_mut(decl_id); + let body = decl + .value_body + .as_mut() + .unwrap_or_else(|| panic!("`{list_name}` has no value body")); + let ValueBody::List(rows) = body else { + panic!("`{list_name}` value body is not a List"); + }; + let row = rows + .get_mut(row_index) + .unwrap_or_else(|| panic!("row {row_index} missing from `{list_name}`")); + let FieldValue::Record(fields) = row else { + panic!("row {row_index} is not a Record"); + }; + let placeholder_value = fields + .iter_mut() + .find(|(label, _)| label == "placeholder_convention") + .map(|(_, value)| value) + .expect("placeholder_convention field"); + let FieldValue::Variant { payload, .. } = placeholder_value else { + panic!("placeholder_convention must be a Variant"); + }; + payload.push(FieldValue::Literal(LiteralBits::Bool(false))); + } + + /// Duplicate the row at `row_index` of the named per-target list by + /// pushing a clone onto the end of the list. The two rows share a + /// `dag_method`, breaking per-target uniqueness — the substrate-side + /// claim `method_template_contract_per_target_dag_method_unique`. + /// Mutates only the in-memory `Dag`. + fn duplicate_row(dag: &mut Dag, list_name: &str, row_index: usize) { + let decl_id = dag + .declaration_by_name(list_name) + .unwrap_or_else(|| panic!("`{list_name}` missing from bootstrap")) + .id; + let decl = dag.declaration_mut(decl_id); + let body = decl + .value_body + .as_mut() + .unwrap_or_else(|| panic!("`{list_name}` has no value body")); + let ValueBody::List(rows) = body else { + panic!("`{list_name}` value body is not a List"); + }; + let clone = rows + .get(row_index) + .unwrap_or_else(|| panic!("row {row_index} missing from `{list_name}`")) + .clone(); + rows.push(clone); + } + + fn replace_row_with_reference( + dag: &mut Dag, + list_name: &str, + row_index: usize, + replacement: DeclarationId, + ) { + let decl_id = dag.declaration_by_name(list_name).expect("list").id; + let decl = dag.declaration_mut(decl_id); + let body = decl.value_body.as_mut().expect("value body"); + let ValueBody::List(rows) = body else { + panic!("not a list"); + }; + let row = rows.get_mut(row_index).expect("row"); + *row = FieldValue::Reference(replacement); + } + + /// Mutate the row at `row_index` of `list_name` by adding a second + /// field with `field_label` (cloned from the first occurrence). Used + /// by the closed-schema duplicate-field tests below. + fn duplicate_field_on_row(dag: &mut Dag, list_name: &str, row_index: usize, field_label: &str) { + let decl_id = dag.declaration_by_name(list_name).expect("list").id; + let decl = dag.declaration_mut(decl_id); + let body = decl.value_body.as_mut().expect("value body"); + let ValueBody::List(rows) = body else { + panic!("not a list"); + }; + let row = rows.get_mut(row_index).expect("row"); + let FieldValue::Record(fields) = row else { + panic!("row not record"); + }; + let (orig_label, orig_value) = fields + .iter() + .find(|(label, _)| label == field_label) + .map(|(label, value)| (label.clone(), value.clone())) + .expect("field to clone"); + fields.push((orig_label, orig_value)); + } + + /// Replace the row at `row_index` of `list_name` so that, after the + /// existing fields, an unknown-labelled field is appended (used by + /// the closed-schema unknown-field tests below). + fn append_unknown_field_on_row( + dag: &mut Dag, + list_name: &str, + row_index: usize, + unknown_label: &str, + ) { + let decl_id = dag.declaration_by_name(list_name).expect("list").id; + let decl = dag.declaration_mut(decl_id); + let body = decl.value_body.as_mut().expect("value body"); + let ValueBody::List(rows) = body else { + panic!("not a list"); + }; + let row = rows.get_mut(row_index).expect("row"); + let FieldValue::Record(fields) = row else { + panic!("row not record"); + }; + fields.push(( + unknown_label.to_string(), + FieldValue::Literal(LiteralBits::Bool(false)), + )); + } + + #[test] + fn duplicate_field_on_row_surfaces_typed_error() { + let mut dag = generated_full_bootstrap_dag(); + duplicate_field_on_row( + &mut dag, + "rust_method_template_contracts", + 0, + "runtime_template", + ); + let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); + match result { + Err(MethodTemplateProjectionError::RowDuplicateField { + record, + field, + first_field_index, + duplicate_field_index, + .. + }) => { + assert_eq!(record, "MethodTemplateContract"); + assert_eq!(field, "runtime_template"); + assert_ne!(first_field_index, duplicate_field_index); + } + other => panic!("expected RowDuplicateField, got {other:?}"), + } + } + + #[test] + fn unknown_field_on_row_surfaces_typed_error() { + let mut dag = generated_full_bootstrap_dag(); + append_unknown_field_on_row( + &mut dag, + "rust_method_template_contracts", + 0, + "renamed_field", + ); + let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); + match result { + Err(MethodTemplateProjectionError::RowUnknownField { record, field, .. }) => { + assert_eq!(record, "MethodTemplateContract"); + assert_eq!(field, "renamed_field"); + } + other => panic!("expected RowUnknownField, got {other:?}"), + } + } + + #[test] + fn reference_row_requires_method_template_contract_instance() { + let mut dag = generated_full_bootstrap_dag(); + let non_contract_structural = dag + .declaration_by_name("count_method") + .expect("count_method MethodDeclaration") + .id; + replace_row_with_reference( + &mut dag, + "rust_method_template_contracts", + 0, + non_contract_structural, + ); + + let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); + match result { + Err(MethodTemplateProjectionError::RowReferenceNotMethodTemplateContract { + list, + row_index, + decl_id, + }) => { + assert_eq!(list, "rust_method_template_contracts"); + assert_eq!(row_index, 0); + assert_eq!(decl_id, non_contract_structural); + } + other => panic!( + "expected RowReferenceNotMethodTemplateContract for structural lookalike row, got {other:?}" + ), + } + } + + /// Inject a duplicate or unknown field into the inner `MethodRef` + /// record at `dag_method` for the row at `row_index`. + fn mutate_method_ref( + dag: &mut Dag, + list_name: &str, + row_index: usize, + kind: MethodRefMutation<'_>, + ) { + let decl_id = dag.declaration_by_name(list_name).expect("list").id; + let decl = dag.declaration_mut(decl_id); + let body = decl.value_body.as_mut().expect("value body"); + let ValueBody::List(rows) = body else { + panic!("not a list"); + }; + let row = rows.get_mut(row_index).expect("row"); + let FieldValue::Record(fields) = row else { + panic!("row not record"); + }; + let (_, dag_method_value) = fields + .iter_mut() + .find(|(label, _)| label == "dag_method") + .expect("dag_method field"); + let FieldValue::Record(method_ref_fields) = dag_method_value else { + panic!("dag_method not record"); + }; + match kind { + MethodRefMutation::Duplicate => { + let (label, value) = method_ref_fields + .iter() + .find(|(label, _)| label == "decl") + .map(|(label, value)| (label.clone(), value.clone())) + .expect("decl field"); + method_ref_fields.push((label, value)); + } + MethodRefMutation::AppendUnknown(label) => { + method_ref_fields.push(( + label.to_string(), + FieldValue::Literal(LiteralBits::Bool(false)), + )); + } + } + } + + /// 🟢 TERMINAL at the test-local `MethodRef` mutation scope. Names the + /// two ways an in-memory `Dag` row can break the closed `MethodRef` + /// schema: duplicate the existing `decl` field, or append a field with + /// an unknown label. Both modes drive the closed-schema check in + /// `closed_record_lookup`. Test-only; not part of the projection's + /// public surface. + enum MethodRefMutation<'a> { + Duplicate, + AppendUnknown(&'a str), + } + + #[test] + fn method_ref_decl_alias_without_value_body_surfaces_typed_error() { + // `src/v3/std/methods.dag` requires a *data binding* + // (`data _method: MethodDeclaration = { name: "..." }`), + // not just any declaration whose connective resolves to + // `MethodDeclaration`. A type alias like + // `type Foo = MethodDeclaration` lowers to an `Instantiation` + // connective with **no `value_body`**; without the value-body + // guard, that shape would pass the connective check alone. + // + // Simulate the alias-shape by stripping `value_body` from a real + // `MethodDeclaration` data binding (`count_method`) and asserting + // the typed error fires for both the per-row check + // (`MethodRefDeclNotMethodDeclaration`) and the lookup helper + // (`LookupKeyNotMethodDeclaration`). + let mut dag = generated_full_bootstrap_dag(); + let count_method_id = dag + .declaration_by_name("count_method") + .expect("count_method MethodDeclaration in bootstrap") + .id; + dag.declaration_mut(count_method_id).value_body = None; + + // Per-row path: row 0 of `rust_method_template_contracts` is the + // `count_method` row, so projecting it should now surface + // `MethodRefDeclNotMethodDeclaration` with `ValueBodyMissing` + // reason instead of returning a row. + let row_result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); + match row_result { + Err(MethodTemplateProjectionError::MethodRefDeclNotMethodDeclaration { + decl_id, + reason, + .. + }) => { + assert_eq!(decl_id, count_method_id); + assert_eq!(reason, MethodDeclarationBindingViolation::ValueBodyMissing); + } + other => panic!( + "expected MethodRefDeclNotMethodDeclaration for alias-shaped target, got {other:?}" + ), + } + + // Lookup-helper path: same key, same shape; the helper validates + // at entry and surfaces `LookupKeyNotMethodDeclaration` with the + // same reason. + let helper_result = + method_template_contract_row(&dag, MethodTemplateTarget::Rust, count_method_id); + match helper_result { + Err(MethodTemplateProjectionError::LookupKeyNotMethodDeclaration { + decl_id, + reason, + }) => { + assert_eq!(decl_id, count_method_id); + assert_eq!(reason, MethodDeclarationBindingViolation::ValueBodyMissing); + } + other => { + panic!("expected LookupKeyNotMethodDeclaration for alias-shaped key, got {other:?}") + } + } + } + + #[test] + fn method_ref_decl_not_method_declaration_surfaces_typed_error() { + // `src/v3/std/methods.dag` enforces fail-closed at the projection + // boundary that `MethodRef.decl` instantiates `MethodDeclaration`, + // because today's substrate grammar can't express + // `DeclarationRef`. Mutate the first row's + // `MethodRef.decl` to point at a declaration that is not a + // `MethodDeclaration` instance (`MethodTemplateContract` itself + // is a type declaration, not a `MethodDeclaration` data binding) + // and assert the typed error. + let mut dag = generated_full_bootstrap_dag(); + let non_method_id = dag + .declaration_by_name("MethodTemplateContract") + .expect("MethodTemplateContract type") + .id; + + let list_decl_id = dag + .declaration_by_name("rust_method_template_contracts") + .expect("list") + .id; + let decl = dag.declaration_mut(list_decl_id); + let body = decl.value_body.as_mut().expect("value body"); + let ValueBody::List(rows) = body else { + panic!("not a list"); + }; + let row = rows.get_mut(0).expect("row 0"); + let FieldValue::Record(fields) = row else { + panic!("row not record"); + }; + let dag_method_value = fields + .iter_mut() + .find(|(label, _)| label == "dag_method") + .map(|(_, value)| value) + .expect("dag_method field"); + let FieldValue::Record(method_ref_fields) = dag_method_value else { + panic!("dag_method not record"); + }; + let decl_field_value = method_ref_fields + .iter_mut() + .find(|(label, _)| label == "decl") + .map(|(_, value)| value) + .expect("decl field"); + *decl_field_value = FieldValue::Reference(non_method_id); + + let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); + match result { + Err(MethodTemplateProjectionError::MethodRefDeclNotMethodDeclaration { + list, + row_index, + decl_id, + reason, + }) => { + assert_eq!(list, "rust_method_template_contracts"); + assert_eq!(row_index, 0); + assert_eq!(decl_id, non_method_id); + // `MethodTemplateContract` is a type declaration (Conj), + // not an Instantiation — connective sub-check fails first. + assert_eq!( + reason, + MethodDeclarationBindingViolation::ConnectiveNotInstantiation + ); + } + other => panic!( + "expected MethodRefDeclNotMethodDeclaration for non-MethodDeclaration target, got {other:?}" + ), + } + } + + #[test] + fn method_ref_decl_extra_field_surfaces_closed_schema_violation() { + // A MethodDeclaration data binding with an extra field (beyond the + // closed `[name]` schema) must surface + // `StructuralFieldsNotClosedNameOnly`. Mirrors Stratum A's + // `enforce_closed_record_schema` gate. + let mut dag = generated_full_bootstrap_dag(); + let count_method_id = dag + .declaration_by_name("count_method") + .expect("count_method") + .id; + let decl = dag.declaration_mut(count_method_id); + let body = decl.value_body.as_mut().expect("value body"); + let ValueBody::Structural { fields } = body else { + panic!("expected Structural body"); + }; + fields.push(( + "alias".to_string(), + FieldValue::Literal(LiteralBits::String("count_alias".to_string())), + )); + + let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); + match result { + Err(MethodTemplateProjectionError::MethodRefDeclNotMethodDeclaration { + reason: + MethodDeclarationBindingViolation::StructuralFieldsNotClosedNameOnly { + observed_labels, + }, + .. + }) => { + assert!(observed_labels.contains(&"name".to_string())); + assert!(observed_labels.contains(&"alias".to_string())); + } + other => { + panic!("expected StructuralFieldsNotClosedNameOnly for extra field, got {other:?}") + } + } + } + + #[test] + fn method_ref_decl_name_not_string_literal_surfaces_typed_error() { + // A MethodDeclaration data binding whose `name` value is not a + // string literal must surface + // `MethodDeclarationBindingViolation::NameNotStringLiteral`. + let mut dag = generated_full_bootstrap_dag(); + let count_method_id = dag + .declaration_by_name("count_method") + .expect("count_method") + .id; + let decl = dag.declaration_mut(count_method_id); + let body = decl.value_body.as_mut().expect("value body"); + let ValueBody::Structural { fields } = body else { + panic!("expected Structural body"); + }; + let (_, name_value) = fields + .iter_mut() + .find(|(label, _)| label == "name") + .expect("name field"); + // Replace with a Bool literal — wrong shape for the contract. + *name_value = FieldValue::Literal(LiteralBits::Bool(true)); + + let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); + match result { + Err(MethodTemplateProjectionError::MethodRefDeclNotMethodDeclaration { + reason: MethodDeclarationBindingViolation::NameNotStringLiteral, + .. + }) => {} + other => panic!("expected NameNotStringLiteral for non-string name, got {other:?}"), + } + } + + #[test] + fn duplicate_field_on_method_ref_surfaces_typed_error() { + let mut dag = generated_full_bootstrap_dag(); + mutate_method_ref( + &mut dag, + "rust_method_template_contracts", + 0, + MethodRefMutation::Duplicate, + ); + let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); + match result { + Err(MethodTemplateProjectionError::RowDuplicateField { record, field, .. }) => { + assert_eq!(record, "MethodRef"); + assert_eq!(field, "decl"); + } + other => panic!("expected RowDuplicateField for MethodRef, got {other:?}"), + } + } + + #[test] + fn unknown_field_on_method_ref_surfaces_typed_error() { + let mut dag = generated_full_bootstrap_dag(); + mutate_method_ref( + &mut dag, + "rust_method_template_contracts", + 0, + MethodRefMutation::AppendUnknown("nickname"), + ); + let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); + match result { + Err(MethodTemplateProjectionError::RowUnknownField { record, field, .. }) => { + assert_eq!(record, "MethodRef"); + assert_eq!(field, "nickname"); + } + other => panic!("expected RowUnknownField for MethodRef, got {other:?}"), + } + } + + #[test] + fn duplicate_row_surfaces_typed_error_at_canonical_row_list_api() { + // Per-target uniqueness by `dag_method` is the substrate-side claim + // `method_template_contract_per_target_dag_method_unique`. The + // canonical row-list API enforces this at the public boundary so + // every consumer (direct row-list consumer or `(target, dag_method)` + // lookup helper) observes the same fail-closed behavior. Gap 5 / + // leaf-emit consumers building their own keyed tables from the + // projection cannot silently observe two authorities for the same + // `(target, MethodRef)` pair. + let mut dag = generated_full_bootstrap_dag(); + duplicate_row(&mut dag, "rust_method_template_contracts", 0); + + // Canonical row-list API surfaces the duplicate. + let list_result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); + match list_result { + Err(MethodTemplateProjectionError::DuplicateMethodTemplateRow { + list, + first_row_index, + duplicate_row_index, + }) => { + assert_eq!(list, "rust_method_template_contracts"); + assert_ne!(first_row_index, duplicate_row_index); + } + other => panic!( + "expected DuplicateMethodTemplateRow at row-list API for duplicate (target, dag_method), got {other:?}" + ), + } + + // Lookup helper inherits the fail-closed shape via the row-list + // API; no separate uniqueness path on the helper. + let count_method_id = dag + .declaration_by_name("count_method") + .expect("count_method MethodDeclaration in bootstrap Dag") + .id; + let helper_result = + method_template_contract_row(&dag, MethodTemplateTarget::Rust, count_method_id); + assert!( + matches!( + helper_result, + Err(MethodTemplateProjectionError::DuplicateMethodTemplateRow { .. }) + ), + "lookup helper must inherit duplicate-row fail-closed via row-list API; got {helper_result:?}" + ); + } + + #[test] + fn malformed_placeholder_payload_surfaces_typed_error() { + // PlaceholderConvention is a nullary sum (`IndexedArgs | NamedArg`). + // A malformed bootstrap row carrying a non-empty placeholder payload + // must surface as `PlaceholderConventionPayloadNotEmpty`, not + // silently project to `IndexedArgs` / `NamedArg` with the payload + // discarded. Mirrors the substrate-side canonical-row check + // `placeholder_convention_canonical_rejects_payload_for_nullary_indexed_args` + // in `src/v3/grounding_tests/src/stratum_a.rs`. + let mut dag = generated_full_bootstrap_dag(); + inject_extra_placeholder_payload(&mut dag, "rust_method_template_contracts", 0); + + let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); + match result { + Err(MethodTemplateProjectionError::PlaceholderConventionPayloadNotEmpty { + list, + row_index, + payload_len, + .. + }) => { + assert_eq!(list, "rust_method_template_contracts"); + assert_eq!(row_index, 0); + assert!(payload_len >= 1, "expected non-empty payload to be reported"); + } + other => panic!( + "expected PlaceholderConventionPayloadNotEmpty for malformed placeholder, got {other:?}" + ), + } + } +} diff --git a/src/v3/compiler/src/complexity_lattice_generated.rs b/src/v3/compiler/src/complexity_lattice_generated.rs new file mode 100644 index 00000000000..657f9229496 --- /dev/null +++ b/src/v3/compiler/src/complexity_lattice_generated.rs @@ -0,0 +1,115 @@ +// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). +// +// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +// Dissolution receipt: PB-0 NON_TEST retirement — former hand module. +// + +//! **`complexity_enforcement_budget_dominates`** — executable **T-LAS enforcement** +//! ordering on `AsymptoticClass` for `complexity_enforcement_violates` / +//! `EnforcedApplication` (gate #92). It matches `v3.std.algebra::asymptotic_dominates` +//! on every arm including `ClassPolynomial×ClassPolynomial` (Peano `degree` compare). +//! +//! **`asymptotic_dominates`** is a backward-compatible alias for tests and older +//! call sites; new enforcement code should call [`complexity_enforcement_budget_dominates`]. +//! +//! Stay aligned with `complexity.dag` + `complexity_enforcement_violates` comments. + +use crate::dag::{positive_descent_count, AsymptoticClass}; + +pub fn complexity_enforcement_budget_dominates(a: &AsymptoticClass, b: &AsymptoticClass) -> bool { + use AsymptoticClass::*; + match a { + ClassUnknown => true, + ClassExponential => match b { + ClassUnknown => false, + ClassExponential + | ClassPolynomial { .. } // Any `ClassPolynomial` tier is below exponential. + | ClassQuadratic + | ClassLinearithmic + | ClassLinear + | ClassLog + | ClassConstant => true, + }, + ClassPolynomial { degree: da } => match b { + ClassUnknown | ClassExponential => false, + ClassPolynomial { degree: db } => { + positive_descent_count(da) >= positive_descent_count(db) + } + ClassQuadratic | ClassLinearithmic | ClassLinear | ClassLog | ClassConstant => true, + }, + ClassQuadratic => match b { + ClassConstant | ClassLog | ClassLinear | ClassLinearithmic | ClassQuadratic => true, + ClassPolynomial { .. } | ClassExponential | ClassUnknown => false, + }, + ClassLinearithmic => match b { + ClassConstant | ClassLog | ClassLinear | ClassLinearithmic => true, + ClassQuadratic | ClassPolynomial { .. } | ClassExponential | ClassUnknown => false, + }, + ClassLinear => match b { + ClassConstant | ClassLog | ClassLinear => true, + ClassLinearithmic + | ClassQuadratic + | ClassPolynomial { .. } + | ClassExponential + | ClassUnknown => false, + }, + ClassLog => match b { + ClassConstant | ClassLog => true, + ClassLinear + | ClassLinearithmic + | ClassQuadratic + | ClassPolynomial { .. } + | ClassExponential + | ClassUnknown => false, + }, + ClassConstant => match b { + ClassConstant => true, + ClassLog + | ClassLinear + | ClassLinearithmic + | ClassQuadratic + | ClassPolynomial { .. } + | ClassExponential + | ClassUnknown => false, + }, + } +} + +/// Alias of [`complexity_enforcement_budget_dominates`]. Prefer the named helper for +/// new call sites so enforcement-grade poly refinement is not confused with substrate +/// `algebra.dag::asymptotic_dominates` at the documentation layer. +#[inline] +pub fn asymptotic_dominates(a: &AsymptoticClass, b: &AsymptoticClass) -> bool { + complexity_enforcement_budget_dominates(a, b) +} + +#[cfg(test)] +mod asymptotic_dominates_tests { + use super::{asymptotic_dominates, complexity_enforcement_budget_dominates}; + use crate::dag::{positive_amount_from_i64, AsymptoticClass}; + + fn poly(k: i64) -> AsymptoticClass { + AsymptoticClass::ClassPolynomial { + degree: positive_amount_from_i64(k).expect("test degree in range"), + } + } + + #[test] + fn polynomial_degree_orders_within_class_polynomial() { + let p5 = poly(5); + let p3 = poly(3); + assert!(complexity_enforcement_budget_dominates(&p5, &p3)); + assert!(!complexity_enforcement_budget_dominates(&p3, &p5)); + assert!(complexity_enforcement_budget_dominates(&p3, &p3)); + } + + #[test] + fn alias_matches_budget_dominates() { + let p3 = poly(3); + let p5 = poly(5); + assert_eq!( + asymptotic_dominates(&p5, &p3), + complexity_enforcement_budget_dominates(&p5, &p3) + ); + } +} diff --git a/src/v3/compiler/src/cost_basis_declaration_generated.rs b/src/v3/compiler/src/cost_basis_declaration_generated.rs new file mode 100644 index 00000000000..4320b71912f --- /dev/null +++ b/src/v3/compiler/src/cost_basis_declaration_generated.rs @@ -0,0 +1,171 @@ +// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). +// +// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +// Dissolution receipt: PB-0 NON_TEST retirement — former hand module. +// + +//! Materialize [`CostBasisDeclaration`](crate::lens_cost_symbolic::CostBasisDeclaration) from +//! lowered [`Dag`](crate::dag::Dag) facts — interim **consumer boundary** for gate **#93** +//! (`crdt_cost_basis_demonstrated`) **substrate / symbolic-cost slice**; folding authored basis +//! rows into [`compute_symbolic_costs`](crate::lens_cost_symbolic::compute_symbolic_costs) remains follow-on on the same gate. +//! +//! Persisted `.dag` `data` rows and folding declarations into [`compute_symbolic_costs`](crate::lens_cost_symbolic::compute_symbolic_costs) remain follow-on; embedders MUST NOT +//! hand-assemble `CostBasisDeclaration` beside this helper when deriving the same evidence +//! from a compiled DAG (INVARIANTS.md P2). + +use std::fmt; + +use crate::dag::{ + ArrowBody, BindNode, Dag, DeclarationId, Lookup, PortId, SectionRef, SizeVariable, + SymbolicCost, TypeConnective, +}; +use crate::lens_cost_symbolic::{compute_symbolic_costs, CostBasisDeclaration, CostBasisKind}; + +/// [`try_build_per_write_log_cost_basis_declaration`] stops on missing DAG facts instead of +/// panicking (fail-closed consumer boundary; codex gate on PR #2299). +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CostBasisDeclarationBuildError { + DeclarationNotFound { + name: String, + }, + MergeStepMissingParams { + merge_step_declaration_name: String, + }, + /// Declaration must be a function (`TypeConnective::Arrow`) so the body bind is + /// reachable via [`ArrowBody::UserDefined`] (single authority per P2). + WorkflowDeclarationNotArrow { + name: String, + }, + /// Arrow declaration body is not yet lowered to `UserDefined(BindNodeId)` — cannot read + /// the authoritative body [`BindNode`] for parameters / span / structural parity. + WorkflowBodyNotUserDefinedBind { + name: String, + }, + /// `compute_symbolic_costs` has no `Hit(LogCost)` row keyed to the merge step’s first + /// parameter port — cannot fabricate a per-write log basis without lens evidence of a + /// divide on that port (P2 / fail-closed). + MergeStepLacksLogCostWitness { + merge_step_declaration_name: String, + }, +} + +impl fmt::Display for CostBasisDeclarationBuildError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CostBasisDeclarationBuildError::DeclarationNotFound { name } => { + write!(f, "declaration `{name}` not found") + } + CostBasisDeclarationBuildError::MergeStepMissingParams { + merge_step_declaration_name, + } => write!( + f, + "declaration `{merge_step_declaration_name}` should declare at least one parameter on its body bind" + ), + CostBasisDeclarationBuildError::WorkflowDeclarationNotArrow { name } => write!( + f, + "declaration `{name}` must have Arrow connective (callable)" + ), + CostBasisDeclarationBuildError::WorkflowBodyNotUserDefinedBind { name } => write!( + f, + "declaration `{name}` body is not ArrowBody::UserDefined(BindNodeId); cannot resolve authoritative bind node" + ), + CostBasisDeclarationBuildError::MergeStepLacksLogCostWitness { + merge_step_declaration_name, + } => write!( + f, + "declaration `{merge_step_declaration_name}` first param port has no LogCost witness in `compute_symbolic_costs` (expected divide-on-dividend lowering)" + ), + } + } +} + +impl std::error::Error for CostBasisDeclarationBuildError {} + +fn user_defined_arrow_body_bind<'a>( + dag: &'a Dag, + declaration_name: &str, +) -> Result<(&'a BindNode, DeclarationId), CostBasisDeclarationBuildError> { + let decl = dag.declaration_by_name(declaration_name).ok_or_else(|| { + CostBasisDeclarationBuildError::DeclarationNotFound { + name: declaration_name.to_string(), + } + })?; + let subject = decl.id; + let bind = match &decl.connective { + TypeConnective::Arrow { body, .. } => match body { + ArrowBody::UserDefined(bind_id) => bind_id.bind_opt(dag).ok_or_else(|| { + CostBasisDeclarationBuildError::WorkflowBodyNotUserDefinedBind { + name: declaration_name.to_string(), + } + })?, + _ => { + return Err( + CostBasisDeclarationBuildError::WorkflowBodyNotUserDefinedBind { + name: declaration_name.to_string(), + }, + ); + } + }, + _ => { + return Err( + CostBasisDeclarationBuildError::WorkflowDeclarationNotArrow { + name: declaration_name.to_string(), + }, + ); + } + }; + Ok((bind, subject)) +} + +fn symbolic_table_includes_log_on_port(dag: &Dag, port: PortId) -> bool { + compute_symbolic_costs(dag).iter().any(|e| { + matches!( + &e.cost, + Lookup::Hit(SymbolicCost::LogCost { _0: sv }) if sv.source_port == port + ) + }) +} + +/// **`LogCost` witness:** returns [`MergeStepLacksLogCostWitness`] unless +/// [`compute_symbolic_costs`] already **`Hit`s `LogCost(merge_param_port)`** (divide-on-dividend +/// lowering); does not fabricate basis without lens table evidence. +/// **Subject** is `SectionRef::DeclarationScope { declaration: … }` wrapping the workflow +/// [`DeclarationId`] (T-LAS / audit structural authority — expression-scoped bases use +/// `NodeScope` when authors need it). +/// `merge_step_declaration_name` is the **`fn` declaration id** (same string the author uses for +/// the merge helper, e.g. `crdt_merge_step`); its body [`BindNode`] is read only via +/// [`TypeConnective::Arrow`] → [`ArrowBody::UserDefined`] like the workflow (P2 single authority +/// for both callables). +pub fn try_build_per_write_log_cost_basis_declaration( + dag: &Dag, + workflow_declaration_name: &str, + merge_step_declaration_name: &str, +) -> Result { + let (workflow_bind, subject) = user_defined_arrow_body_bind(dag, workflow_declaration_name)?; + let (merge_bind, _) = user_defined_arrow_body_bind(dag, merge_step_declaration_name)?; + let merge_replicas_port = *merge_bind.params.first().ok_or_else(|| { + CostBasisDeclarationBuildError::MergeStepMissingParams { + merge_step_declaration_name: merge_step_declaration_name.to_string(), + } + })?; + if !symbolic_table_includes_log_on_port(dag, merge_replicas_port) { + return Err( + CostBasisDeclarationBuildError::MergeStepLacksLogCostWitness { + merge_step_declaration_name: merge_step_declaration_name.to_string(), + }, + ); + } + Ok(CostBasisDeclaration { + subject: SectionRef::DeclarationScope { + declaration: subject, + }, + kind: CostBasisKind::PerWrite, + cost: SymbolicCost::LogCost { + _0: SizeVariable { + source_port: merge_replicas_port, + display_name: None, + }, + }, + span: workflow_bind.span.clone(), + }) +} diff --git a/src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs b/src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs new file mode 100644 index 00000000000..ec9187be800 --- /dev/null +++ b/src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs @@ -0,0 +1,189 @@ +// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). +// +// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +// Dissolution receipt: PB-0 NON_TEST retirement — former hand module. +// + +//! `CollectionOps.*_contract` fields cross `DeclarationRef` boundaries. Emit +//! consumers must fail closed on the **algebra identity** (P1): each referenced +//! declaration must be a `MethodTemplateContract` data binding whose +//! `dag_method.decl` resolves to the expected registry `*_method` row. + +use super::method_emit_template_variant_label; +use crate::dag::{Dag, DeclarationId, FieldValue, LiteralBits, TypeConnective, ValueBody}; + +/// 🟢 TERMINAL at the CollectionOps contract-consumer projection boundary. +/// This mirrors the substrate `MethodEmitTemplate` coproduct exactly so Rust +/// CollectionOps can choose the HO inline rendering without flattening or +/// copying template text into a third authority. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum MethodTemplateContractEmitTemplate { + SingleTemplate(String), + HigherOrderTemplates { + inline_template: String, + fn_ref_template: String, + }, +} + +/// Fail closed unless `contract_decl` is a `MethodTemplateContract` instance +/// whose `dag_method.decl` resolves to `expected_method_decl`. +pub(crate) fn require_method_template_contract_dag_method( + dag: &Dag, + contract_decl: DeclarationId, + collection_ops_field: &'static str, + expected_method_decl: DeclarationId, +) -> Result<(), &'static str> { + let fields = method_template_contract_fields(dag, contract_decl)?; + require_method_template_contract_fields_dag_method( + fields, + collection_ops_field, + expected_method_decl, + ) +} + +pub(crate) fn method_template_contract_decl_emit_template( + dag: &Dag, + contract_decl: DeclarationId, + collection_ops_field: &'static str, + expected_method_decl: DeclarationId, +) -> Result { + let fields = method_template_contract_fields(dag, contract_decl)?; + require_method_template_contract_fields_dag_method( + fields, + collection_ops_field, + expected_method_decl, + )?; + method_template_contract_fields_emit_template(dag, fields) +} + +fn method_template_contract_fields( + dag: &Dag, + contract_decl: DeclarationId, +) -> Result<&[(String, FieldValue)], &'static str> { + let mtc_type_id = dag + .method_template_contract_decl() + .ok_or("internal: MethodTemplateContract type missing from dag")?; + + let decl = dag.declaration(contract_decl); + let template = match &decl.connective { + TypeConnective::Instantiation { template, .. } => *template, + _ => { + return Err( + "CollectionOps contract must reference a MethodTemplateContract data binding", + ); + } + }; + if template != mtc_type_id { + return Err("CollectionOps contract must instantiate MethodTemplateContract"); + } + + let Some(body) = decl.value_body.as_ref() else { + return Err( + "CollectionOps contract referenced declaration must carry a structural value body", + ); + }; + let fields = match body { + ValueBody::Structural { fields } => fields, + _ => { + return Err( + "CollectionOps contract referenced declaration must be structural MethodTemplateContract data", + ); + } + }; + Ok(fields) +} + +fn require_method_template_contract_fields_dag_method( + fields: &[(String, FieldValue)], + collection_ops_field: &'static str, + expected_method_decl: DeclarationId, +) -> Result<(), &'static str> { + let method_id = method_template_contract_fields_dag_method(fields)?; + if method_id != expected_method_decl { + return Err(match collection_ops_field { + "fold_contract" => "CollectionOps.fold_contract must target fold_method", + "concat_contract" => "CollectionOps.concat_contract must target concat_method", + "length_contract" => "CollectionOps.length_contract must target length_method", + "filter_contract" => "CollectionOps.filter_contract must target filter_method", + "flat_map_contract" => "CollectionOps.flat_map_contract must target flat_map_method", + "any_contract" => "CollectionOps.any_contract must target any_method", + "all_contract" => "CollectionOps.all_contract must target all_method", + "map_contract" => "CollectionOps.map_contract must target map_method", + "is_empty_contract" => { + "CollectionOps.is_empty_contract must target is_empty_method (FreeMonoid.is_empty -> Bool)" + } + _ => "CollectionOps contract dag_method does not match expected registry method", + }); + } + + Ok(()) +} + +fn method_template_contract_fields_dag_method( + fields: &[(String, FieldValue)], +) -> Result { + let dag_method = fields + .iter() + .find(|(l, _)| l == "dag_method") + .map(|(_, v)| v) + .ok_or("MethodTemplateContract missing dag_method field")?; + + let FieldValue::Record(method_ref_fields) = dag_method else { + return Err("MethodTemplateContract.dag_method must be a MethodRef record"); + }; + + let method_id = match method_ref_fields.as_slice() { + [(label, FieldValue::Reference(id))] if label == "decl" => *id, + _ => { + return Err( + "MethodTemplateContract.dag_method must be exactly `{ decl: DeclarationRef }` (MethodRef)", + ); + } + }; + Ok(method_id) +} + +fn method_template_contract_fields_emit_template( + dag: &Dag, + fields: &[(String, FieldValue)], +) -> Result { + let emit_value = fields + .iter() + .find(|(label, _)| label == "emit_template") + .map(|(_, v)| v) + .ok_or("MethodTemplateContract missing emit_template field")?; + let FieldValue::Variant { + constructor, + payload, + } = emit_value + else { + return Err("MethodTemplateContract.emit_template must be a sum variant"); + }; + let ctor_name = method_emit_template_variant_label(dag, *constructor).ok_or( + "MethodTemplateContract.emit_template variant not found under MethodEmitTemplate disj", + )?; + match ctor_name { + "SingleTemplate" => { + let [FieldValue::Literal(LiteralBits::String(template))] = payload.as_slice() else { + return Err("SingleTemplate must carry exactly one string template payload"); + }; + Ok(MethodTemplateContractEmitTemplate::SingleTemplate( + template.replace("%Q", "\""), + )) + } + "HigherOrderTemplates" => { + let [FieldValue::Literal(LiteralBits::String(inline_template)), FieldValue::Literal(LiteralBits::String(fn_ref_template))] = + payload.as_slice() + else { + return Err( + "HigherOrderTemplates must carry inline_template and fn_ref_template strings", + ); + }; + Ok(MethodTemplateContractEmitTemplate::HigherOrderTemplates { + inline_template: inline_template.replace("%Q", "\""), + fn_ref_template: fn_ref_template.replace("%Q", "\""), + }) + } + _ => Err("MethodTemplateContract.emit_template uses an unknown MethodEmitTemplate variant"), + } +} diff --git a/src/v3/compiler/src/emit_rust_bin_shim_generated.rs b/src/v3/compiler/src/emit_rust_bin_shim_generated.rs new file mode 100644 index 00000000000..c45d258c569 --- /dev/null +++ b/src/v3/compiler/src/emit_rust_bin_shim_generated.rs @@ -0,0 +1,98 @@ +// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). +// +// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +// Dissolution receipt: PB-0 NON_TEST retirement — former hand module. +// + +//! PB-1 — Rust `main.rs` shell for generated `[[bin]]` shims (Item 5). +//! +//! **SG-0 / bounded-seed receipt:** hand-authored path is enumerated in +//! `EXPECTED_HAND_AUTHORED_NON_TEST` in `sg0_census_test.rs` (not +//! `GENERATED_FILES` / `build.rs` output). Narrow host helper for §4.2 shell +//! text only; see `docs/invariants/bounded-substrate-seed.md` (no new +//! substrate primitive — mirrors existing `std.process.ProcessExit`). +//! +//! Authority: `docs/design-pb-runtime-interpreter.md` §4.2 (`main` → +//! `ProcessExit` → `ExitCode`). This module owns **parameterized Rust text** +//! only: it does not resolve `BinShim.entry` `DeclarationRef` targets (row-#1 +//! / host bridge gap — STOP there) and does not wire `cargo`/build.rs. + +/// Formats the `main.rs` wrapper for a `.dag`-authored bin shim. +/// +/// `source_dag_path` is emitted verbatim in the `AUTO-GENERATED` header (one +/// line). `description` is split into `//` comment lines. `entry_fn_qname` is +/// the Rust-qualified symbol for the `.dag` entry function returning +/// [`crate::process_exit::ProcessExit`]. +pub fn format_bin_shim_main_rs( + source_dag_path: &str, + description: &str, + entry_fn_qname: &str, +) -> String { + let mut out = String::new(); + out.push_str("// AUTO-GENERATED from "); + out.push_str(source_dag_path); + out.push_str(" — DO NOT EDIT.\n//\n"); + if description.is_empty() { + out.push_str("//\n"); + } else { + for line in description.lines() { + out.push_str("// "); + out.push_str(line); + out.push('\n'); + } + } + out.push_str( + r#" +use std::io::Write; +use std::process::ExitCode; + +use v3_compiler::dag::Dag; +use v3_compiler::process_exit::ProcessExit; + +fn main() -> ExitCode { + match "#, + ); + out.push_str(entry_fn_qname); + out.push_str( + r#"(&Dag::new()) { + ProcessExit::ExitSuccess => ExitCode::SUCCESS, + ProcessExit::ExitFailure { code, reason } => { + let _ = writeln!(std::io::stderr(), "{reason}"); + // Fail-closed: `ExitFailure` must not map to a successful host exit. + // Non-positive or out-of-range codes remap into 1..=255 (see dsl/std/process.dag). + ExitCode::from((code.max(1).min(255)) as u8) + } + } +} +"#, + ); + out +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn format_bin_shim_main_rs_includes_header_entry_and_process_exit() { + let out = format_bin_shim_main_rs( + "dsl/std/runtime/bin_shims/regen_lens.dag", + "Unified lens-regen driver.", + "std::runtime::bin_shims::regen_lens::regen_lens_main", + ); + assert!(out.contains( + "// AUTO-GENERATED from dsl/std/runtime/bin_shims/regen_lens.dag — DO NOT EDIT." + )); + assert!(out.contains("// Unified lens-regen driver.")); + assert!(out.contains("use v3_compiler::process_exit::ProcessExit;")); + assert!(out.contains("code.max(1).min(255)")); + assert!(!out.contains("code.clamp(0, 255)")); + assert!(out.contains("std::runtime::bin_shims::regen_lens::regen_lens_main(&Dag::new())")); + } + + #[test] + fn format_bin_shim_main_rs_multiline_description() { + let out = format_bin_shim_main_rs("path/to/shim.dag", "line1\nline2", "m::f"); + assert!(out.contains("// line1\n// line2\n")); + } +} diff --git a/src/v3/compiler/src/enforced_lens_application_generated.rs b/src/v3/compiler/src/enforced_lens_application_generated.rs new file mode 100644 index 00000000000..efbbae6a1e8 --- /dev/null +++ b/src/v3/compiler/src/enforced_lens_application_generated.rs @@ -0,0 +1,1264 @@ +// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). +// +// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +// Dissolution receipt: PB-0 NON_TEST retirement — former hand module. +// + +//! Enforced lens applications (`EnforcedApplication` from `lens_application.dag`). +//! +//! Gate #91 (`enforce_violation_routing_landed`): budget-violation diagnostics are +//! routed through the authored `diagnostic_severity` field — today the single +//! substrate variant `DiagnosticSeverity::Error` maps to [`Diagnostic::ParseError`] +//! (compile-fail / fail-closed), per `lens_application.dag`, design §3, and +//! INVARIANTS C-8. +//! +//! Future extra `DiagnosticSeverity` constructors stay fail-closed at this consumer until +//! an intentional substrate + routing co-update assigns their `Diagnostic::*` shape. +//! +//! Gate #92 (`complexity_violation_compile_error_demonstrated`): when a program +//! authors `EnforcedApplication` referencing +//! `complexity_enforceable`, infer checks the named section using the **same** +//! `LensEnforcement` projection + violation relation as `complexity.dag` +//! (`complexity_enforcement_project` / `complexity_enforcement_violates`, surfaced +//! through `complexity_lens_generated`) and attaches a compile diagnostic on violation. +//! +//! Gate #58 (`apply_lens_self_application_demonstrated`): when a program authors +//! `EnforcedApplication` referencing `timing_enforceable` +//! (`v3.std.timing_lens`), infer reads the **lowered** `TimingMeasurement` from the +//! `DeclarationScope` subject's structural `measurement` field **only after** verifying the +//! subject row's **nominal** `data …: RowTy = …` type declares `measurement: TimingMeasurement` +//! on `RowTy` (API-level contract; avoids unrelated records that merely spell a `measurement` +//! label). The same usage ceiling as `timing_enforcement_project` / `timing_enforcement_violates` +//! in `timing_lens.dag` applies: non-`Observed` reports are enforced via a **variant-shaped** +//! fault path (never compared as ordinary wall-clock `Nat` against the fault sentinel), while +//! `Observed` wall-clock nanoseconds use the same strict `>` edge as gate #94 against +//! `TimingBudget.max`. + +use std::collections::HashMap; + +use crate::dag::{ + literal_decimal_i64, positive_descent_count, ArrowBody, AsymptoticClass, Behavior, Dag, + DeclarationId, FieldValue, LiteralBits, Lookup, PortId, PositiveDescentAmount, TypeConnective, + ValueBody, +}; +use crate::diagnostics::{Diagnostic, SourceSpan}; +use crate::lens_cost::{ + complexity_enforcement_project, complexity_enforcement_violates, complexity_of, +}; + +fn diagnostic_severity_substrate_disj(dag: &Dag) -> Option { + dag.declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("DiagnosticSeverity") + && d.span.file.ends_with("lens_application.dag") + }) + .map(|d| d.id) +} + +// Fail-closed when `lens_application.dag` does not expose the expected `DiagnosticSeverity` +// substrate row — factored so unit tests can pin the diagnostic without staging a full +// complexity-enforcement DAG (`Dag::new()` omits `complexity_enforceable`). +fn attach_missing_diagnostic_severity_substrate_diagnostic( + dag: &mut Dag, + enforced_template: DeclarationId, +) { + dag.attach_diagnostic(Diagnostic::ParseError { + message: "lens enforcement: could not resolve substrate `DiagnosticSeverity` from \ + `lens_application.dag` (modeled authority missing; fail-closed)" + .to_string(), + span: dag.declaration(enforced_template).span.clone(), + fixes: Vec::new(), + }); +} + +/// Reads `timing_enforcement_fault_sentinel_count` from the lowered `timing_lens.dag` declaration +/// body (nullary `fn` → bind → [`ValueNode`]), matching substrate authority instead of duplicating +/// the decimal literal in Rust. +fn timing_enforcement_fault_sentinel_ns_from_substrate(dag: &Dag) -> Option { + let decl = dag.declarations().iter().find(|d| { + d.name.as_deref() == Some("timing_enforcement_fault_sentinel_count") + && d.span.file.ends_with("timing_lens.dag") + })?; + let TypeConnective::Arrow { + body: ArrowBody::UserDefined(bind_id), + .. + } = &decl.connective + else { + return None; + }; + let bind = (*bind_id).bind_opt(dag)?; + let producer = dag.resolve_producer_opt(&bind.value)?; + let Behavior::Value(vn) = producer else { + return None; + }; + let LiteralBits::Int(s) = &vn.data else { + return None; + }; + s.parse().ok() +} + +fn timing_measurement_sum_type_decl_id(dag: &Dag) -> Option { + dag.declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("TimingMeasurement") + && d.span.file.ends_with("timing_lens.dag") + }) + .map(|d| d.id) +} + +fn peel_type_declaration_head(dag: &Dag, mut id: DeclarationId) -> DeclarationId { + const MAX: usize = 64; + for _ in 0..MAX { + match &dag.declaration(id).connective { + TypeConnective::Instantiation { + template, + arguments, + } if arguments.is_empty() => { + id = *template; + } + TypeConnective::Atom(ap) => { + let Some(next) = ap.resolved_id() else { + break; + }; + id = next; + } + _ => break, + } + } + id +} + +/// Returns the nominal `RowTy` declaration id for a lowered `data row: RowTy = …` value row. +fn data_row_nominal_type_decl_id(dag: &Dag, value_decl_id: DeclarationId) -> Option { + match &dag.declaration(value_decl_id).connective { + TypeConnective::Instantiation { + template, + arguments, + } if arguments.is_empty() => Some(*template), + _ => None, + } +} + +/// True when `RowTy` (after alias peel) is a conj that declares `measurement` with type peeling +/// to the substrate `TimingMeasurement` sum at `tm_disj`. +fn timing_section_row_type_declares_measurement_tm( + dag: &Dag, + section_value_decl_id: DeclarationId, + tm_disj: DeclarationId, +) -> bool { + let Some(row_ty) = data_row_nominal_type_decl_id(dag, section_value_decl_id) else { + return false; + }; + let ty_head = peel_type_declaration_head(dag, row_ty); + let TypeConnective::Conj { children } = &dag.declaration(ty_head).connective else { + return false; + }; + let tm_head = peel_type_declaration_head(dag, tm_disj); + children + .iter() + .any(|f| f.label == "measurement" && peel_type_declaration_head(dag, f.ty) == tm_head) +} + +/// Lowered `TimingMeasurement` usage for timing `EnforcedApplication` enforcement. +/// +/// Separates non-evidence variants from `Observed` wall-clock nanoseconds so a legitimate +/// `Observed` duration equal to the substrate fault sentinel `Nat` cannot be conflated with +/// `Unobserved` / `Ambiguous` / `Stale` (INVARIANTS P2 / modeling-discipline Practice 2). +/// +/// Practice 4 (`docs/modeling-discipline.md`, coproduct checkpoint): **GREEN (terminal)** — mirrors +/// `timing_enforcement_project` / `timing_enforcement_violates` in `timing_lens.dag` on the lowered +/// `TimingMeasurement` witness (substrate `LensEnforcement.violates` now takes `TimingMeasurement` +/// × `TimingBudget` per `lens_application.dag`; gate #58 / codex 10994). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum TimingMeasurementEnforcementUsage { + NonObservedFault, + ObservedWallNs(u64), +} + +/// Interprets a lowered `TimingMeasurement` value the same way `timing_enforcement_project` does +/// on the substrate (`timing_lens.dag`): `Observed` → wall-clock nanoseconds; other report variants +/// classify as [`TimingMeasurementEnforcementUsage::NonObservedFault`] (not as a raw `Nat`). +fn timing_measurement_enforcement_usage( + dag: &Dag, + tm_disj: DeclarationId, + value: &FieldValue, +) -> Option { + let TypeConnective::Disj { variants } = &dag.declaration(tm_disj).connective else { + return None; + }; + let FieldValue::Variant { + constructor, + payload, + } = value + else { + return None; + }; + let variant = variants.iter().find(|v| v.ty == *constructor)?; + match variant.label.as_str() { + "Observed" => observed_timing_payload_max_ns(payload) + .map(TimingMeasurementEnforcementUsage::ObservedWallNs), + "Unobserved" | "Ambiguous" | "Stale" => { + Some(TimingMeasurementEnforcementUsage::NonObservedFault) + } + _ => None, + } +} + +/// Same reflexive edge as `timing_enforcement_violates` in `timing_lens.dag` on the lowered witness, +/// using the same `>` edge as substrate `Observed` enforcement (no `TimingBudget` sentinel equality +/// on the observed path). +pub(crate) fn timing_enforcement_violates( + declared_max_ns: u64, + usage: TimingMeasurementEnforcementUsage, +) -> bool { + match usage { + TimingMeasurementEnforcementUsage::NonObservedFault => true, + TimingMeasurementEnforcementUsage::ObservedWallNs(ns) => ns > declared_max_ns, + } +} + +fn observed_timing_payload_max_ns(payload: &[FieldValue]) -> Option { + let FieldValue::Record(parts) = payload.first()? else { + return None; + }; + if let Some((_, v)) = parts.iter().find(|(label, _)| label == "duration") { + return field_value_nat_magnitude(v); + } + field_value_nat_magnitude(&FieldValue::Record(parts.clone())) +} + +fn field_value_nat_magnitude(value: &FieldValue) -> Option { + match value { + FieldValue::Literal(LiteralBits::Int(s)) => s.parse().ok(), + FieldValue::Record(parts) => parts + .iter() + .find(|(label, _)| label == "count") + .and_then(|(_, v)| field_value_nat_magnitude(v)), + _ => None, + } +} + +fn field_value_timing_budget_max_ns(value: &FieldValue) -> Option { + let FieldValue::Record(parts) = value else { + return None; + }; + let max = parts + .iter() + .find(|(label, _)| label == "max") + .map(|(_, v)| v)?; + field_value_nat_magnitude(max) +} + +fn is_timing_enforceable_lens_value( + value: &FieldValue, + timing_enforceable_id: Option, + timing_lens_id: Option, + timing_enforcement_id: Option, +) -> bool { + if let (Some(bundle), FieldValue::Reference(id)) = (timing_enforceable_id, value) { + if *id == bundle { + return true; + } + } + let (Some(lens_id), Some(enforcement_id)) = (timing_lens_id, timing_enforcement_id) else { + return false; + }; + let FieldValue::Record(parts) = value else { + return false; + }; + let mut lens_ref: Option = None; + let mut enforcement_ref: Option = None; + for (label, field) in parts { + match (label.as_str(), field) { + ("lens", FieldValue::Reference(r)) => lens_ref = Some(*r), + ("enforcement", FieldValue::Reference(r)) => enforcement_ref = Some(*r), + _ => {} + } + } + lens_ref == Some(lens_id) && enforcement_ref == Some(enforcement_id) +} + +/// Fail-closed check for landed complexity + timing enforcement applications. +/// +/// Exported at the crate root (`v3_compiler::check_enforced_lens_applications`) so integration +/// tests can re-invoke the same post-infer pass as `infer::infer` without treating declaration +/// presence as proof the probe ran (gate #58 receipt). +pub fn check_enforced_lens_applications(dag: &mut Dag) { + let Some(enforced_template) = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("EnforcedApplication") + && d.span.file.ends_with("lens_application.dag") + }) + .map(|d| d.id) + else { + return; + }; + let complexity_enforceable_id = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("complexity_enforceable") + && d.span.file.ends_with("complexity.dag") + }) + .map(|d| d.id); + let timing_enforceable_id = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("timing_enforceable") + && d.span.file.ends_with("timing_lens.dag") + }) + .map(|d| d.id); + let timing_lens_id = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("timing_lens") && d.span.file.ends_with("timing_lens.dag") + }) + .map(|d| d.id); + let timing_enforcement_id = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("timing_enforcement") + && d.span.file.ends_with("timing_lens.dag") + }) + .map(|d| d.id); + let asymptotic_disj = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("AsymptoticClass") && d.span.file.ends_with("algebra.dag") + }) + .map(|d| d.id); + let Some(section_ref_disj) = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("SectionRef") && d.span.file.ends_with("lens_application.dag") + }) + .map(|d| d.id) + else { + return; + }; + let Some(declaration_scope_conj) = declaration_scope_payload_conj(dag, section_ref_disj) else { + return; + }; + let Some(diagnostic_severity_disj) = diagnostic_severity_substrate_disj(dag) else { + attach_missing_diagnostic_severity_substrate_diagnostic(dag, enforced_template); + return; + }; + let positive_descent_disj = dag + .declaration_by_name("PositiveDescentAmount") + .map(|d| d.id); + + let mut violations: Vec = Vec::new(); + + for decl in dag.declarations() { + let Some(body) = decl.value_body.as_ref() else { + continue; + }; + let ValueBody::Structural { fields } = body else { + continue; + }; + let Some(meta_id) = decl.meta_tag else { + continue; + }; + if !matches_enforced_application_instantiation(dag, meta_id, enforced_template) { + continue; + } + let fm = field_map(fields); + let enforceable_lens = match fm.get("enforceable_lens") { + Some(v) => v, + _ => continue, + }; + let section = match fm.get("section") { + Some(v) => *v, + _ => continue, + }; + let budget_val = match fm.get("budget") { + Some(v) => *v, + _ => continue, + }; + let span = match fm.get("span") { + Some(FieldValue::Record(span_fields)) => record_as_source_span(span_fields), + _ => None, + } + .unwrap_or_else(|| decl.span.clone()); + + if let (Some(cid), FieldValue::Reference(ref_id)) = + (complexity_enforceable_id, enforceable_lens) + { + if *ref_id == cid { + let Some(asymptotic_disj) = asymptotic_disj else { + violations.push(Diagnostic::ParseError { + message: + "lens enforcement: could not resolve substrate `AsymptoticClass` from \ + `algebra.dag` (modeled authority missing; fail-closed)" + .to_string(), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + let Some(fn_decl) = resolve_declaration_scope_fn( + dag, + section, + section_ref_disj, + declaration_scope_conj, + ) else { + violations.push(Diagnostic::ParseError { + message: + "lens enforcement: could not resolve function declaration for section" + .to_string(), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + let Some(port) = fn_result_port(dag, fn_decl) else { + violations.push(Diagnostic::ParseError { + message: + "lens enforcement: could not resolve function result port for section" + .to_string(), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + let Some(budget_class) = field_value_asymptotic_class( + dag, + asymptotic_disj, + positive_descent_disj, + budget_val, + ) else { + violations.push(Diagnostic::ParseError { + message: + "lens enforcement: could not read complexity budget `AsymptoticClass` \ + (ill-formed `ClassPolynomial` budgets must use Peano `degree` ≥ 3; \ + use `ClassLinear` / `ClassQuadratic` for sub-cubic tiers)" + .to_string(), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + + let summary = match complexity_of(dag, &port) { + Lookup::Hit(s) => s, + Lookup::Miss => { + violations.push(Diagnostic::ParseError { + message: + "lens enforcement: complexity lens returned Miss for section — \ + cannot enforce budget" + .to_string(), + span: span.clone(), + fixes: Vec::new(), + }); + continue; + } + }; + if !complexity_enforcement_violates(&summary, &budget_class) { + continue; + } + let observed = complexity_enforcement_project(&summary); + let severity_val = match fm.get("diagnostic_severity") { + Some(v) => *v, + _ => { + violations.push(Diagnostic::ParseError { + message: + "lens enforcement: `EnforcedApplication` missing `diagnostic_severity`" + .to_string(), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + } + }; + let violation_message = format!( + "lens enforcement violation: complexity budget {budget_class:?} exceeded \ + by observed class {observed:?} (declared `EnforcedApplication` at {})", + decl.name.as_deref().unwrap_or("?") + ); + violations.push(enforced_violation_diagnostic( + dag, + diagnostic_severity_disj, + severity_val, + violation_message, + span, + )); + continue; + } + } + + if is_timing_enforceable_lens_value( + enforceable_lens, + timing_enforceable_id, + timing_lens_id, + timing_enforcement_id, + ) { + let Some(tm_disj) = timing_measurement_sum_type_decl_id(dag) else { + violations.push(Diagnostic::ParseError { + message: + "lens enforcement: could not resolve substrate `TimingMeasurement` from \ + `timing_lens.dag` (modeled authority missing; fail-closed)" + .to_string(), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + let Some(section_decl_id) = + resolve_declaration_scope_declaration_id(section, declaration_scope_conj) + else { + violations.push(Diagnostic::ParseError { + message: "lens enforcement: could not resolve `DeclarationScope` for timing \ + `EnforcedApplication` section" + .to_string(), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + let section_decl = dag.declaration(section_decl_id); + if !timing_section_row_type_declares_measurement_tm(dag, section_decl_id, tm_disj) { + violations.push(Diagnostic::ParseError { + message: format!( + "lens enforcement: timing `EnforcedApplication` section `{}` must be a \ + lowered `data …: RowTy = …` row whose nominal `RowTy` declares \ + `measurement: TimingMeasurement` (same substrate contract as \ + `timing_enforcement_project`)", + section_decl.name.as_deref().unwrap_or("?") + ), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + } + let Some(body) = section_decl.value_body.as_ref() else { + violations.push(Diagnostic::ParseError { + message: format!( + "lens enforcement: timing section `{}` has no lowered value body", + section_decl.name.as_deref().unwrap_or("?") + ), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + let ValueBody::Structural { fields } = body else { + violations.push(Diagnostic::ParseError { + message: format!( + "lens enforcement: timing section `{}` must lower to a structural record body", + section_decl.name.as_deref().unwrap_or("?") + ), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + let section_fm = field_map(fields); + let Some(measurement) = section_fm.get("measurement") else { + violations.push(Diagnostic::ParseError { + message: format!( + "lens enforcement: timing section `{}` is missing required `measurement` field", + section_decl.name.as_deref().unwrap_or("?") + ), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + let Some(usage_projection) = + timing_measurement_enforcement_usage(dag, tm_disj, measurement) + else { + violations.push(Diagnostic::ParseError { + message: format!( + "lens enforcement: could not interpret `measurement` as lowered \ + `TimingMeasurement` for section `{}`", + section_decl.name.as_deref().unwrap_or("?") + ), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + let Some(declared_max_ns) = field_value_timing_budget_max_ns(budget_val) else { + violations.push(Diagnostic::ParseError { + message: "lens enforcement: could not read timing budget `TimingBudget.max` \ + nanoseconds (expected `TimingBudget { max: Nanoseconds { count } }`)" + .to_string(), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + if !timing_enforcement_violates(declared_max_ns, usage_projection) { + continue; + }; + let usage_max_ns_for_message = match usage_projection { + TimingMeasurementEnforcementUsage::ObservedWallNs(ns) => ns, + TimingMeasurementEnforcementUsage::NonObservedFault => { + let Some(s) = timing_enforcement_fault_sentinel_ns_from_substrate(dag) else { + violations.push(Diagnostic::ParseError { + message: + "lens enforcement: could not resolve substrate `timing_enforcement_fault_sentinel_count` \ + from `timing_lens.dag` (needed for fault-path diagnostic text; fail-closed)" + .to_string(), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + }; + s + } + }; + let severity_val = match fm.get("diagnostic_severity") { + Some(v) => *v, + _ => { + violations.push(Diagnostic::ParseError { + message: + "lens enforcement: `EnforcedApplication` missing `diagnostic_severity`" + .to_string(), + span: decl.span.clone(), + fixes: Vec::new(), + }); + continue; + } + }; + let violation_message = format!( + "lens enforcement violation: timing budget ceiling max_ns={declared_max_ns} exceeded \ + by projected wall-clock usage max_ns={usage_max_ns_for_message} (declared `EnforcedApplication` \ + at {})", + decl.name.as_deref().unwrap_or("?") + ); + violations.push(enforced_violation_diagnostic( + dag, + diagnostic_severity_disj, + severity_val, + violation_message, + span, + )); + } + } + + for diagnostic in violations { + dag.attach_diagnostic(diagnostic); + } +} + +#[doc(hidden)] +fn gate_58_raise_observed_duration_ns_in_measurement( + value: &mut FieldValue, + duration_ns: u64, +) -> bool { + match value { + FieldValue::Variant { payload, .. } => { + let Some(FieldValue::Record(parts)) = payload.first_mut() else { + return false; + }; + if let Some((_, FieldValue::Record(duration_parts))) = + parts.iter_mut().find(|(label, _)| label == "duration") + { + return gate_58_set_count_field_to_ns(duration_parts, duration_ns); + } + // PB-1 bootstrap lowering: `Observed` payload can flatten to `{ count: Nat }` without + // a nested `duration` record (authoring uses `Observed { duration: { count } }`). + gate_58_set_count_field_to_ns(parts, duration_ns) + } + _ => false, + } +} + +fn gate_58_set_count_field_to_ns(parts: &mut [(String, FieldValue)], duration_ns: u64) -> bool { + for (dlabel, dfield) in parts.iter_mut() { + if dlabel == "count" { + if let FieldValue::Literal(LiteralBits::Int(s)) = dfield { + *s = duration_ns.to_string(); + return true; + } + return false; + } + } + false +} + +/// Integration receipt helper: mutates the PB-1 gate #58 witness row in a bootstrap [`Dag`]. +/// +/// **Not a supported production API** — only the `v3-compiler` integration test +/// `t_gate_58_apply_lens_self_application_test` should call this (exposed so that test binary can +/// link against `v3_compiler` without `cfg(test)` coupling). +#[doc(hidden)] +pub fn gate_58_test_raise_modeled_ci_timing_measurement_duration_ns( + dag: &mut Dag, + duration_ns: u64, +) -> Result<(), &'static str> { + let id = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("gate_58_modeled_ci_timing_measurement") + && d.span.file.ends_with("t_ci_workflow_as_data_demo.dag") + }) + .map(|d| d.id) + .ok_or("missing gate_58_modeled_ci_timing_measurement declaration")?; + let decl = dag.declaration_mut(id); + let Some(ValueBody::Structural { fields }) = decl.value_body.as_mut() else { + return Err("gate_58 witness missing structural value_body"); + }; + for (label, value) in fields.iter_mut() { + if label == "measurement" + && gate_58_raise_observed_duration_ns_in_measurement(value, duration_ns) + { + return Ok(()); + } + } + Err("measurement field missing or not Observed with duration.count shape") +} + +/// Parses `(declared_max_ns, projected_usage_max_ns)` from a timing lens **budget-ceiling** +/// violation message produced by [`check_enforced_lens_applications`]. +/// +/// The template is owned next to [`timing_enforcement_violates`] / `violation_message` formatting +/// in this module (`max_ns=` appears exactly twice: declared ceiling, then projected usage). +/// Integration tests use this instead of pinning English prose (see repo `TESTING.md` anti-pattern). +#[doc(hidden)] +pub fn gate_58_test_parse_timing_budget_violation_max_ns_pair(message: &str) -> Option<(u64, u64)> { + const NEEDLE: &str = "max_ns="; + let mut found: Vec = Vec::with_capacity(2); + let mut rest = message; + while let Some(pos) = rest.find(NEEDLE) { + let after = pos + NEEDLE.len(); + let tail = &rest[after..]; + let digit_len = tail + .chars() + .take_while(|c| c.is_ascii_digit()) + .map(|c| c.len_utf8()) + .sum::(); + if digit_len == 0 { + return None; + } + let n: u64 = tail[..digit_len].parse().ok()?; + found.push(n); + rest = &tail[digit_len..]; + } + if found.len() == 2 { + Some((found[0], found[1])) + } else { + None + } +} + +fn field_map(fields: &[(String, FieldValue)]) -> HashMap<&str, &FieldValue> { + fields.iter().map(|(k, v)| (k.as_str(), v)).collect() +} + +/// Routes an enforce-mode **budget violation** through `EnforcedApplication.diagnostic_severity`. +/// +/// Substrate policy (C-8): only `DiagnosticSeverity::Error` is a valid steady-state choice; unknown +/// constructors, non-nullary `Error` payload, or malformed values fail closed with an explanatory +/// diagnostic. +fn enforced_violation_diagnostic( + dag: &Dag, + diagnostic_severity_disj: DeclarationId, + severity_val: &FieldValue, + violation_message: String, + span: SourceSpan, +) -> Diagnostic { + let TypeConnective::Disj { variants } = &dag.declaration(diagnostic_severity_disj).connective + else { + return Diagnostic::ParseError { + message: "lens enforcement: internal error (DiagnosticSeverity is not a sum type)" + .to_string(), + span, + fixes: Vec::new(), + }; + }; + let Some(error_ctor) = variants.iter().find(|v| v.label == "Error").map(|v| v.ty) else { + return Diagnostic::ParseError { + message: "lens enforcement: internal error (DiagnosticSeverity lacks `Error` variant)" + .to_string(), + span, + fixes: Vec::new(), + }; + }; + let FieldValue::Variant { + constructor, + payload, + } = severity_val + else { + return Diagnostic::ParseError { + message: "lens enforcement: `diagnostic_severity` must be a `DiagnosticSeverity` \ + variant value" + .to_string(), + span, + fixes: Vec::new(), + }; + }; + if *constructor != error_ctor { + return Diagnostic::ParseError { + message: "lens enforcement: `diagnostic_severity` on `EnforcedApplication` must be \ + `Error` (INVARIANTS C-8; fail-closed discipline)" + .to_string(), + span, + fixes: Vec::new(), + }; + } + // `lens_application.dag`: `type DiagnosticSeverity = Error` — the lone variant is nullary. + if !payload.is_empty() { + return Diagnostic::ParseError { + message: "lens enforcement: `DiagnosticSeverity::Error` must be nullary (INVARIANTS \ + P1; malformed variant payload)" + .to_string(), + span, + fixes: Vec::new(), + }; + } + Diagnostic::ParseError { + message: violation_message, + span, + fixes: Vec::new(), + } +} + +fn matches_enforced_application_instantiation( + dag: &Dag, + mut meta: DeclarationId, + enforced_template: DeclarationId, +) -> bool { + for _ in 0..16 { + match &dag.declaration(meta).connective { + TypeConnective::Instantiation { + template, + arguments, + } => { + if *template == enforced_template && arguments.len() == 3 { + return true; + } + if arguments.is_empty() { + meta = *template; + continue; + } + return false; + } + _ => return false, + } + } + false +} + +fn declaration_scope_payload_conj( + dag: &Dag, + section_ref_disj: DeclarationId, +) -> Option { + let TypeConnective::Disj { variants } = &dag.declaration(section_ref_disj).connective else { + return None; + }; + let v = variants.iter().find(|f| f.label == "DeclarationScope")?; + Some(v.ty) +} + +fn resolve_declaration_scope_declaration_id( + section: &FieldValue, + declaration_scope_conj: DeclarationId, +) -> Option { + let FieldValue::Variant { + constructor, + payload, + } = section + else { + return None; + }; + if *constructor != declaration_scope_conj { + return None; + } + match payload.as_slice() { + [FieldValue::Reference(id)] => Some(*id), + [FieldValue::Record(parts)] => { + let mut decl_id: Option = None; + for (label, value) in parts { + if label == "declaration" { + if let FieldValue::Reference(id) = value { + decl_id = Some(*id); + } + } + } + decl_id + } + _ => None, + } +} + +fn resolve_declaration_scope_fn( + dag: &Dag, + section: &FieldValue, + section_ref_disj: DeclarationId, + declaration_scope_conj: DeclarationId, +) -> Option { + let _ = section_ref_disj; + let id = resolve_declaration_scope_declaration_id(section, declaration_scope_conj)?; + matches!(dag.declaration(id).connective, TypeConnective::Arrow { .. }).then_some(id) +} + +fn fn_result_port(dag: &Dag, fn_decl: DeclarationId) -> Option { + let decl = dag.declaration(fn_decl); + let TypeConnective::Arrow { + body: ArrowBody::UserDefined(bind_node), + .. + } = &decl.connective + else { + return None; + }; + let bind = bind_node.bind(dag); + Some(bind.result_port()) +} + +fn record_as_source_span(fields: &[(String, FieldValue)]) -> Option { + let mut file: Option = None; + let mut byte_start: Option = None; + let mut byte_end: Option = None; + for (label, value) in fields { + match (label.as_str(), value) { + ("file", FieldValue::Literal(LiteralBits::String(s))) => file = Some(s.clone()), + ("byte_start", FieldValue::Literal(LiteralBits::Int(n))) => { + byte_start = literal_decimal_i64(n.as_str()).and_then(|v| u32::try_from(v).ok()); + } + ("byte_end", FieldValue::Literal(LiteralBits::Int(n))) => { + byte_end = literal_decimal_i64(n.as_str()).and_then(|v| u32::try_from(v).ok()); + } + ("start", FieldValue::Literal(LiteralBits::Int(n))) => { + byte_start = literal_decimal_i64(n.as_str()).and_then(|v| u32::try_from(v).ok()); + } + ("end", FieldValue::Literal(LiteralBits::Int(n))) => { + byte_end = literal_decimal_i64(n.as_str()).and_then(|v| u32::try_from(v).ok()); + } + _ => {} + } + } + Some(SourceSpan::new(file?, byte_start?, byte_end?)) +} + +fn field_value_asymptotic_class( + dag: &Dag, + ac_disj: DeclarationId, + pda_disj: Option, + value: &FieldValue, +) -> Option { + let FieldValue::Variant { + constructor, + payload, + } = value + else { + return None; + }; + let TypeConnective::Disj { variants } = &dag.declaration(ac_disj).connective else { + return None; + }; + let variant = variants.iter().find(|v| v.ty == *constructor)?; + match variant.label.as_str() { + "ClassConstant" => Some(AsymptoticClass::ClassConstant), + "ClassLog" => Some(AsymptoticClass::ClassLog), + "ClassLinear" => Some(AsymptoticClass::ClassLinear), + "ClassLinearithmic" => Some(AsymptoticClass::ClassLinearithmic), + "ClassQuadratic" => Some(AsymptoticClass::ClassQuadratic), + "ClassExponential" => Some(AsymptoticClass::ClassExponential), + "ClassUnknown" => Some(AsymptoticClass::ClassUnknown), + "ClassPolynomial" => { + let pda = pda_disj?; + let degree = decode_positive_descent_amount(dag, pda, payload)?; + // `AsymptoticClass::ClassPolynomial` in `algebra.dag` is k ≥ 3; Peano + // counts 1–2 are `ClassLinear` / `ClassQuadratic` territory — reject as + // an invalid budget shape (fail closed; openai-pro / P1). + if positive_descent_count(°ree) < 3 { + return None; + } + Some(AsymptoticClass::ClassPolynomial { degree }) + } + _ => None, + } +} + +fn decode_positive_descent_amount( + dag: &Dag, + pda_disj: DeclarationId, + poly_payload: &[FieldValue], +) -> Option { + match poly_payload { + [FieldValue::Record(parts)] => { + let value = parts.iter().find(|(k, _)| k == "degree").map(|(_, v)| v)?; + decode_positive_descent_variant(dag, pda_disj, value) + } + [single] => decode_positive_descent_variant(dag, pda_disj, single), + _ => None, + } +} + +fn decode_positive_descent_variant( + dag: &Dag, + pda_disj: DeclarationId, + value: &FieldValue, +) -> Option { + let FieldValue::Variant { + constructor, + payload, + } = value + else { + return None; + }; + let TypeConnective::Disj { variants } = &dag.declaration(pda_disj).connective else { + return None; + }; + let variant = variants.iter().find(|v| v.ty == *constructor)?; + match variant.label.as_str() { + "OneStep" => Some(PositiveDescentAmount::OneStep), + "AdditionalStep" => { + let FieldValue::Record(parts) = payload.first()? else { + return None; + }; + let prev = parts + .iter() + .find(|(k, _)| k == "previous") + .map(|(_, v)| v)?; + let inner = decode_positive_descent_variant(dag, pda_disj, prev)?; + Some(PositiveDescentAmount::AdditionalStep { + previous: Box::new(inner), + }) + } + _ => None, + } +} + +#[cfg(test)] +mod diagnostic_severity_fail_closed_tests { + use super::*; + use crate::dag::{LiteralBits, TypeConnective}; + use crate::diagnostics::Diagnostic; + + fn assert_parse_error_at(diag: Diagnostic, expected_span: &SourceSpan) { + match diag { + Diagnostic::ParseError { span, fixes, .. } => { + assert_eq!(&span, expected_span); + assert!(fixes.is_empty()); + } + other => panic!("expected ParseError, got {other:?}"), + } + } + + #[test] + fn missing_diagnostic_severity_substrate_records_fail_closed_diagnostic() { + let mut dag = Dag::new(); + let Some(enforced_template) = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("EnforcedApplication") + && d.span.file.ends_with("lens_application.dag") + }) + .map(|d| d.id) + else { + panic!("bootstrap should declare EnforcedApplication in lens_application.dag"); + }; + let expected_span = dag.declaration(enforced_template).span.clone(); + let Some(ds_id) = super::diagnostic_severity_substrate_disj(&dag) else { + panic!("bootstrap should declare DiagnosticSeverity in lens_application.dag"); + }; + dag.declaration_mut(ds_id).name = Some("DiagnosticSeverity__test_unresolvable".to_string()); + assert!( + super::diagnostic_severity_substrate_disj(&dag).is_none(), + "Expected DiagnosticSeverity substrate row to be unresolvable after rename" + ); + super::attach_missing_diagnostic_severity_substrate_diagnostic(&mut dag, enforced_template); + let mut it = dag.diagnostics().iter(); + let (_, d) = it.next().expect("fail-closed diagnostic"); + assert!(it.next().is_none(), "expected exactly one diagnostic"); + assert_parse_error_at(d.clone(), &expected_span); + } + + #[test] + fn enforced_violation_diagnostic_rejects_non_error_severity_constructor() { + let dag = Dag::new(); + let Some(ds_disj) = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("DiagnosticSeverity") + && d.span.file.ends_with("lens_application.dag") + }) + .map(|d| d.id) + else { + panic!("bootstrap should declare DiagnosticSeverity in lens_application.dag"); + }; + let wrong_ctor = dag + .bool_runtime_variant_id(true) + .expect("bootstrap True variant"); + let expected_span = SourceSpan::new("t.v3", 0, 1); + let diag = enforced_violation_diagnostic( + &dag, + ds_disj, + &FieldValue::Variant { + constructor: wrong_ctor, + payload: Vec::new(), + }, + "violation".to_string(), + expected_span.clone(), + ); + assert_parse_error_at(diag, &expected_span); + } + + #[test] + fn enforced_violation_diagnostic_rejects_non_variant_severity_value() { + let dag = Dag::new(); + let Some(ds_disj) = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("DiagnosticSeverity") + && d.span.file.ends_with("lens_application.dag") + }) + .map(|d| d.id) + else { + panic!("bootstrap should declare DiagnosticSeverity in lens_application.dag"); + }; + let expected_span = SourceSpan::new("t.v3", 0, 1); + let diag = enforced_violation_diagnostic( + &dag, + ds_disj, + &FieldValue::Literal(LiteralBits::Int("0".to_string())), + "violation".to_string(), + expected_span.clone(), + ); + assert_parse_error_at(diag, &expected_span); + } + + #[test] + fn enforced_violation_diagnostic_rejects_error_severity_with_non_nullary_payload() { + let dag = Dag::new(); + let Some(ds_disj) = dag + .declarations() + .iter() + .find(|d| { + d.name.as_deref() == Some("DiagnosticSeverity") + && d.span.file.ends_with("lens_application.dag") + }) + .map(|d| d.id) + else { + panic!("bootstrap should declare DiagnosticSeverity in lens_application.dag"); + }; + let TypeConnective::Disj { variants } = &dag.declaration(ds_disj).connective else { + panic!("bootstrap DiagnosticSeverity should be a sum type"); + }; + let error_ctor = variants + .iter() + .find(|v| v.label == "Error") + .map(|v| v.ty) + .expect("bootstrap DiagnosticSeverity should have Error variant"); + let expected_span = SourceSpan::new("t.v3", 0, 1); + let diag = enforced_violation_diagnostic( + &dag, + ds_disj, + &FieldValue::Variant { + constructor: error_ctor, + payload: vec![FieldValue::Literal(LiteralBits::Int("0".to_string()))], + }, + "violation".to_string(), + expected_span.clone(), + ); + assert_parse_error_at(diag, &expected_span); + } +} + +#[cfg(test)] +mod polynomial_budget_class_policy_tests { + use crate::dag::{positive_amount_from_i64, positive_descent_count, PositiveDescentAmount}; + + fn polynomial_budget_peano_is_admissible(degree: &PositiveDescentAmount) -> bool { + positive_descent_count(degree) >= 3 + } + + #[test] + fn class_polynomial_enforcement_budget_rejects_sub_cubic_peano_degrees() { + assert_eq!(positive_descent_count(&PositiveDescentAmount::OneStep), 1); + assert!(!polynomial_budget_peano_is_admissible( + &PositiveDescentAmount::OneStep + )); + let deg2 = PositiveDescentAmount::AdditionalStep { + previous: Box::new(PositiveDescentAmount::OneStep), + }; + assert_eq!(positive_descent_count(°2), 2); + assert!(!polynomial_budget_peano_is_admissible(°2)); + let deg3 = positive_amount_from_i64(3).expect("synthetic degree 3"); + assert!(polynomial_budget_peano_is_admissible(°3)); + } +} + +#[cfg(test)] +mod gate_58_timing_enforcement_unit_tests { + use super::{ + gate_58_test_parse_timing_budget_violation_max_ns_pair, + timing_enforcement_fault_sentinel_ns_from_substrate, timing_enforcement_violates, + TimingMeasurementEnforcementUsage, + }; + use crate::dag::Dag; + + #[test] + fn gate_58_parse_max_ns_pair_matches_timing_budget_violation_template() { + let msg = "lens enforcement violation: timing budget ceiling max_ns=1 exceeded \ + by projected wall-clock usage max_ns=999 (declared `EnforcedApplication` \ + at gate_58_enforcement_budget_violation)"; + assert_eq!( + gate_58_test_parse_timing_budget_violation_max_ns_pair(msg), + Some((1, 999)) + ); + } + + #[test] + fn non_observed_fault_always_violates_under_any_finite_budget() { + assert!(timing_enforcement_violates( + 1, + TimingMeasurementEnforcementUsage::NonObservedFault + )); + assert!(timing_enforcement_violates( + u64::MAX - 1, + TimingMeasurementEnforcementUsage::NonObservedFault + )); + } + + #[test] + fn observed_usage_strictly_exceeding_budget_violates() { + assert!(timing_enforcement_violates( + 500, + TimingMeasurementEnforcementUsage::ObservedWallNs(1000) + )); + } + + #[test] + fn observed_usage_within_or_equal_budget_is_clean() { + assert!(!timing_enforcement_violates( + 1000, + TimingMeasurementEnforcementUsage::ObservedWallNs(500) + )); + assert!(!timing_enforcement_violates( + 1000, + TimingMeasurementEnforcementUsage::ObservedWallNs(1000) + )); + } + + #[test] + fn observed_wall_at_substrate_fault_sentinel_is_not_fault_short_circuit() { + let dag = Dag::new(); + let s = timing_enforcement_fault_sentinel_ns_from_substrate(&dag) + .expect("bootstrap should carry timing_lens fault sentinel"); + assert!(!timing_enforcement_violates( + s + 1, + TimingMeasurementEnforcementUsage::ObservedWallNs(s) + )); + assert!(timing_enforcement_violates( + s - 1, + TimingMeasurementEnforcementUsage::ObservedWallNs(s) + )); + } +} diff --git a/src/v3/compiler/src/int_literal_ranges_generated.rs b/src/v3/compiler/src/int_literal_ranges_generated.rs new file mode 100644 index 00000000000..2c65365effd --- /dev/null +++ b/src/v3/compiler/src/int_literal_ranges_generated.rs @@ -0,0 +1,1022 @@ +// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). +// +// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +// Dissolution receipt: PB-0 NON_TEST retirement — former hand module. +// + +//! Integer literal magnitude vs declared integer types — **Q1 consumer**. +//! +//! Authoritative modeling: [`docs/design-emission-model.md`](../../../../docs/design-emission-model.md) +//! §Q1 — `BoundDeclaration = StaticBound(Interval) | PlatformDependent`, with asymmetric +//! match (target `Unbounded` universal-accept; target `ExactInterval` exact `lo`/`hi` equality at +//! the fold). This module implements only the **static** side needed for literal narrowing: +//! substrate range facts [`range_min_inclusive` / `range_max_inclusive`](../../../../dsl/extdeps/languages/rust/primitives.dag) +//! on [`rust_pilot_primitives`](crate::dag::Dag::rust_pilot_primitives) supply +//! `StaticBound(Interval)` as [`IntervalInt::ExactInterval`] (decimal endpoints + host +//! `BigInt` comparison; widened from `i128` per R3 Phase A so `u128::MAX` and any future wider +//! primitive is representable structurally). [`IntervalInt::Unbounded`] exists so Q1’s interval +//! algebra is representable when a +//! target declares an unbounded value domain (pilot `IntegerPrimitive` rows are all exact today). +//! [`PlatformDependent`] is out of scope for i64-bounded literal narrowing (deferred targets). +//! +//! ## Downstream consumers (range-facts + narrowing) +//! +//! | Location | Behavior | +//! | --- | --- | +//! | `infer::try_reconcile_int_literal_decision_set` | `let` / `data` pre-seed vs default `Int64` literal; in-range narrow; OOB → `MagnitudeOutOfRange`. | +//! | `infer::decide_transform` (calls) | Parameter-narrow type vs default-`Int` argument literal; narrow or OOB. | +//! | `infer::int_literal_implicit_bind_tolerated_for_expected` | Callable template binding when structural binding fails on int literal. | +//! | `lower` scalar literal lowering | Early reject for OOB literals before inference reunion. | +//! +//! ### R2 downstream audit ([`docs/briefs/r2-modeling-int-lit-magnitude-worker.md`](../../../../docs/briefs/r2-modeling-int-lit-magnitude-worker.md) §Slice 2) +//! +//! Post–[#1227](https://github.com/gunb-ai/gunbc/pull/1227) (**MethodEmitTemplate** Phase 1.5): no shared symbols with `emit_model.dag` method-template row lists +//! or `*_method_template_contracts` — those paths do **not** consult `rust_pilot_primitives` / this module. +//! +//! | Consumer | Range-facts / Q1 magnitude | +//! | --- | --- | +//! | `infer` (`try_reconcile_int_literal_decision_set`, `decide_transform`, `int_literal_implicit_bind_tolerated_for_expected`, transform/`PortUnion` narrow paths) | **Yes** — `integer_range_for_decl` + `magnitude_out_of_range_for_interval`. | +//! | `lower` (`lower_scalar_literal` / scalar literal outcome) | **Yes** — same facts for early OOB. | +//! | `emit::*` (e.g. `rust_target`) | **Indirect** — emits **resolved** shapes after inference; no parallel range-fact walk. | +//! | Method-template / emit-model carriers (**#1227**) | **N/A** — template contracts; unrelated to int-literal narrowing. | + +use std::collections::HashSet; +use std::str::FromStr; + +use num_bigint::BigInt; + +use crate::dag::{ + AtomPayload, Behavior, Dag, DeclarationId, FieldValue, LiteralBits, PortId, TemplateArgument, + TypeConnective, ValueBody, +}; +use crate::diagnostics::{Diagnostic, SourceSpan}; +use crate::types::TypeShape; + +/// Q1 `Interval` instance carried from String-decimal range facts (not `LiteralBits::Int` +/// widening — producer brief). +/// +/// **Host repr:** `min` / `max` are arbitrary-precision `BigInt` so `u128::MAX` (and any future +/// wider-than-i128 primitive) is representable structurally without per-width host-repr variant +/// explosion. Per Director Path A RATIFIED at gunbc#1739 #issuecomment-4392731264 (R3 Substrate +/// Rust-primitive-full-coverage bundled brief). The previous narrow `i128` host-repr deferred the +/// `u128` row in `dsl/extdeps/languages/rust/primitives.dag` because `u128::MAX` exceeds `i128` +/// range; that gap is closed by the `BigInt` host repr here. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum IntervalInt { + /// Closed interval — substrate `range_*_inclusive` facts for a fixed-width target primitive. + ExactInterval { + target_name: String, + min_decimal: String, + max_decimal: String, + min: BigInt, + max: BigInt, + }, + /// Value-domain unbounded integer (e.g. arbitrary-precision target). Universal-accept for any + /// tokenized decimal literal magnitude (through `u128::MAX` / signed `|m| ≤ 2^127`). + /// + /// **Dissolution trigger (when this variant is constructed from [`integer_range_for_decl`]):** + /// a `rust_pilot_primitives` `IntegerPrimitive` row (or successor multi-target table) is + /// authored for a target whose Q1 `BoundDeclaration` is `StaticBound(Unbounded)` at magnitude + /// check — e.g. Python `int` per [`docs/design-emission-model.md`](../../../../docs/design-emission-model.md) + /// fold example (T-Ground cross-target / language `primitives.dag` work, not this consumer). + /// Until that producer exists, only [`IntervalInt::ExactInterval`] is returned from the pilot + /// list; [`Unbounded`] remains for `contains_bigint` / Q1 algebra completeness and unit tests. + #[allow(dead_code)] + Unbounded, +} + +/// Decimal endpoints for a **fixed** integer target — the payload of [`MagnitudeOutOfRange`]. +/// +/// **API split:** only [`magnitude_out_of_range`] takes this type. When you have a full +/// [`IntervalInt`] from [`integer_range_for_decl`], call [`magnitude_out_of_range_for_interval`] +/// (it uses [`IntervalInt::exact_interval_facts`] and never passes an unbounded domain into the +/// magnitude diagnostic). +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct ExactIntIntervalFacts { + pub(crate) target_name: String, + pub(crate) min_decimal: String, + pub(crate) max_decimal: String, +} + +impl IntervalInt { + /// Range check for a surface literal magnitude carried as decimal [`LiteralBits::Int`]. + pub(crate) fn contains_bigint(&self, value: &BigInt) -> bool { + match self { + IntervalInt::Unbounded => true, + IntervalInt::ExactInterval { min, max, .. } => min <= value && value <= max, + } + } + + /// Fixed-width pilot rows: `Some` for [`IntervalInt::ExactInterval`]; `None` for + /// [`IntervalInt::Unbounded`] (no decimal range to quote in `MagnitudeOutOfRange`). + pub(crate) fn exact_interval_facts(&self) -> Option { + match self { + IntervalInt::ExactInterval { + target_name, + min_decimal, + max_decimal, + .. + } => Some(ExactIntIntervalFacts { + target_name: target_name.clone(), + min_decimal: min_decimal.clone(), + max_decimal: max_decimal.clone(), + }), + IntervalInt::Unbounded => None, + } + } +} + +pub(crate) enum IntegerRangeLookup { + Found(IntervalInt), + Missing, + Invalid(Diagnostic), +} + +/// Structural witness for routing integer literals: `IntegerAlgebra` and +/// `TargetCarrier` variant **payload type** ids (the `constructor` field on +/// `FieldValue::Variant`), derived from std `OrderedRing` / `Semiring` +/// **or** `Compose>` (R3 gate #19 refinement form) +/// by resolving to the same pilot `(OrderedRing|Semiring, Word*)` routing key. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct IntegerRoutingWitness { + pub(crate) algebra_variant_ty: DeclarationId, + pub(crate) carrier_variant_ty: DeclarationId, +} + +/// Integration / structural tests: witness for `decl`'s resolved integer +/// instantiation (`OrderedRing` / `Semiring` + word carrier), if any. +pub(crate) fn integer_routing_witness_for_decl( + dag: &Dag, + decl: DeclarationId, +) -> Option { + integer_routing_witness_walk(dag, decl, 0) +} + +/// `Nat` (`dsl/std/nat.dag`: `Semiring`). Nonnegative literals share the tokenizer's +/// `u128` magnitude carrier; narrowing still rejects negative values structurally. +fn type_is_nat(dag: &Dag, mut decl: DeclarationId) -> bool { + let Some(nat_id) = dag.declaration_by_name("Nat").map(|d| d.id) else { + return false; + }; + let Some(semiring_template) = dag.declaration_by_name("Semiring").map(|d| d.id) else { + return false; + }; + let Some(magnitude_id) = dag.declaration_by_name("Magnitude").map(|d| d.id) else { + return false; + }; + for _ in 0..32 { + if decl == nat_id { + return true; + } + let declaration = dag.declaration(decl); + match &declaration.connective { + TypeConnective::Instantiation { + template, + arguments, + } if *template == semiring_template + && arguments.len() == 1 + && arguments[0].value == magnitude_id => + { + return true; + } + TypeConnective::Instantiation { template, .. } => decl = *template, + TypeConnective::Atom(AtomPayload::ResolvedByStructure(next)) + | TypeConnective::Atom(AtomPayload::ResolvedByName(next)) => decl = *next, + _ => return false, + } + } + false +} + +fn nat_decimal_literal_interval() -> IntervalInt { + IntervalInt::ExactInterval { + target_name: "Nat".to_string(), + min_decimal: "0".to_string(), + max_decimal: u128::MAX.to_string(), + min: BigInt::from(0u8), + max: BigInt::from(u128::MAX), + } +} + +pub(crate) fn integer_range_for_decl(dag: &Dag, decl: DeclarationId) -> IntegerRangeLookup { + if type_is_nat(dag, decl) { + return IntegerRangeLookup::Found(nat_decimal_literal_interval()); + } + let Some(witness) = integer_routing_witness_walk(dag, decl, 0) else { + return IntegerRangeLookup::Missing; + }; + let Some(pilot) = dag.rust_pilot_primitives() else { + return IntegerRangeLookup::Missing; + }; + let Some(body) = pilot.value_body.as_ref() else { + return IntegerRangeLookup::Missing; + }; + let ValueBody::List(elements) = body else { + return IntegerRangeLookup::Missing; + }; + + let integer_primitive_ctor = match rust_primitive_integer_variant_ty(dag) { + Some(id) => id, + None => { + return IntegerRangeLookup::Invalid(malformed_integer_range_fact( + "bootstrap: RustPrimitive.IntegerPrimitive variant type is unavailable".to_string(), + pilot.span.clone(), + )); + } + }; + + let mut matches: Vec = Vec::new(); + for element in elements { + let FieldValue::Variant { + constructor, + payload, + } = element + else { + continue; + }; + if *constructor != integer_primitive_ctor { + continue; + } + match pilot_integer_row(witness, payload, pilot.span.clone()) { + Ok(Some(m)) => matches.push(m), + Ok(None) => {} + Err(diag) => return IntegerRangeLookup::Invalid(diag), + } + } + + if matches.is_empty() { + return IntegerRangeLookup::Missing; + } + if let Some(row) = matches.iter().find(|row| row.range.is_none()) { + return IntegerRangeLookup::Invalid(malformed_integer_range_fact( + format!( + "malformed rust_pilot_primitives IntegerPrimitive row for routing witness {:?}; integer literal range narrowing is unavailable", + (witness.algebra_variant_ty, witness.carrier_variant_ty) + ), + row.span.clone(), + )); + } + if matches.len() > 1 { + return IntegerRangeLookup::Invalid(malformed_integer_range_fact( + format!( + "duplicate rust_pilot_primitives IntegerPrimitive rows for routing witness {:?}; integer literal range narrowing is ambiguous", + (witness.algebra_variant_ty, witness.carrier_variant_ty) + ), + matches[1].span.clone(), + )); + } + let row = matches.remove(0); + match row.range { + Some(range) => IntegerRangeLookup::Found(range), + None => unreachable!("malformed matching rows are handled before duplicate detection"), + } +} + +struct PilotIntegerMatch { + range: Option, + span: SourceSpan, +} + +fn integer_routing_witness_walk( + dag: &Dag, + decl: DeclarationId, + depth: usize, +) -> Option { + if depth >= 32 { + return None; + } + let declaration = dag.declaration(decl); + match &declaration.connective { + TypeConnective::Atom(AtomPayload::ResolvedByName(next)) + | TypeConnective::Atom(AtomPayload::ResolvedByStructure(next)) => { + integer_routing_witness_walk(dag, *next, depth + 1) + } + TypeConnective::Instantiation { + template, + arguments, + } => { + if arguments.is_empty() { + return integer_routing_witness_walk(dag, *template, depth + 1); + } + if let Some(compose_id) = dag.declaration_by_name("Compose").map(|d| d.id) { + if *template == compose_id && arguments.len() == 2 { + if let Some(w) = compose_integer_routing_witness(dag, arguments.as_slice()) { + return Some(w); + } + } + } + let carrier = arguments.first()?.value; + integer_instantiation_witness(dag, *template, carrier) + } + _ => None, + } +} + +/// Gate #60 Phase 2.1: `MachineWidth`'s phantom argument may be a literal decimal +/// (`MachineWidth<64>`) naming the Nat magnitude; map to the existing `std.bit` width +/// authority (`Byte` / `Word16` / …) for the same integer-routing witness as Word*-spelled rows. +fn machine_width_inner_to_std_width_carrier_decl( + dag: &Dag, + inner_decl: DeclarationId, +) -> Option { + if std_word_carrier_to_target_carrier_variant_ty(dag, inner_decl).is_some() { + return Some(inner_decl); + } + match &dag.declaration(inner_decl).connective { + TypeConnective::Atom(AtomPayload::Literal(LiteralBits::Int(s))) => { + let n: u32 = s.parse().ok()?; + match n { + 8 => Some(dag.declaration_by_name("Byte")?.id), + 16 => Some(dag.declaration_by_name("Word16")?.id), + 32 => Some(dag.declaration_by_name("Word32")?.id), + 64 => Some(dag.declaration_by_name("Word64")?.id), + 128 => Some(dag.declaration_by_name("Word128")?.id), + _ => None, + } + } + _ => None, + } +} + +/// Fixed-width integers after gate #19: `Compose>` (signed) or +/// `Compose>` (unsigned). Maps to the same `(OrderedRing|Semiring, Word*)` +/// pilot routing key as the legacy `OrderedRing` / `Semiring` instantiations. +fn compose_integer_routing_witness( + dag: &Dag, + arguments: &[TemplateArgument], +) -> Option { + let int_id = dag.declaration_by_name("Int").map(|d| d.id)?; + let uint_id = dag.declaration_by_name("UInt").map(|d| d.id)?; + let machine_width = dag.declaration_by_name("MachineWidth").map(|d| d.id)?; + + let mut signed: Option = None; + let mut word_carrier: Option = None; + + for arg in arguments { + let v = arg.value; + if v == int_id { + signed = Some(true); + } else if v == uint_id { + signed = Some(false); + } else if let TypeConnective::Instantiation { + template, + arguments: mw_args, + } = &dag.declaration(v).connective + { + if *template == machine_width && mw_args.len() == 1 { + let inner = mw_args[0].value; + word_carrier = Some(machine_width_inner_to_std_width_carrier_decl(dag, inner)?); + } + } + } + + let signed = signed?; + let carrier_decl = word_carrier?; + let algebra_variant_ty = if signed { + disj_variant_payload_ty(dag, "IntegerAlgebra", "OrderedRingAlgebra")? + } else { + disj_variant_payload_ty(dag, "IntegerAlgebra", "SemiringAlgebra")? + }; + let carrier_variant_ty = std_word_carrier_to_target_carrier_variant_ty(dag, carrier_decl)?; + Some(IntegerRoutingWitness { + algebra_variant_ty, + carrier_variant_ty, + }) +} + +fn integer_instantiation_witness( + dag: &Dag, + template: DeclarationId, + carrier_decl: DeclarationId, +) -> Option { + let ordered_ring = dag.declaration_by_name("OrderedRing")?.id; + let semiring = dag.declaration_by_name("Semiring")?.id; + let algebra_variant_ty = if template == ordered_ring { + disj_variant_payload_ty(dag, "IntegerAlgebra", "OrderedRingAlgebra")? + } else if template == semiring { + disj_variant_payload_ty(dag, "IntegerAlgebra", "SemiringAlgebra")? + } else { + return None; + }; + let carrier_variant_ty = std_word_carrier_to_target_carrier_variant_ty(dag, carrier_decl)?; + Some(IntegerRoutingWitness { + algebra_variant_ty, + carrier_variant_ty, + }) +} + +fn disj_variant_payload_ty( + dag: &Dag, + sum_name: &str, + variant_label: &str, +) -> Option { + let decl = dag.declaration_by_name(sum_name)?; + let TypeConnective::Disj { variants } = &decl.connective else { + return None; + }; + variants + .iter() + .find(|v| v.label == variant_label) + .map(|v| v.ty) +} + +fn std_word_carrier_to_target_carrier_variant_ty( + dag: &Dag, + carrier_decl: DeclarationId, +) -> Option { + let byte = dag.declaration_by_name("Byte")?.id; + let word16 = dag.declaration_by_name("Word16")?.id; + let word32 = dag.declaration_by_name("Word32")?.id; + let word64 = dag.declaration_by_name("Word64")?.id; + let word128 = dag.declaration_by_name("Word128")?.id; + let label = if carrier_decl == byte { + "ByteCarrier" + } else if carrier_decl == word16 { + "Word16Carrier" + } else if carrier_decl == word32 { + "Word32Carrier" + } else if carrier_decl == word64 { + "Word64Carrier" + } else if carrier_decl == word128 { + "Word128Carrier" + } else { + return None; + }; + disj_variant_payload_ty(dag, "TargetCarrier", label) +} + +fn rust_primitive_integer_variant_ty(dag: &Dag) -> Option { + let rust_primitive = dag.declaration_by_name("RustPrimitive")?; + let TypeConnective::Disj { variants } = &rust_primitive.connective else { + return None; + }; + variants + .iter() + .find(|v| v.label == "IntegerPrimitive") + .map(|v| v.ty) +} + +fn pilot_integer_row( + witness: IntegerRoutingWitness, + payload: &[FieldValue], + default_span: SourceSpan, +) -> Result, Diagnostic> { + // IntegerPrimitive field order: target_name, algebra, carrier, range_*, is_copy, overflow + if payload.len() < 5 { + return Ok(None); + } + let FieldValue::Variant { + constructor: algebra_ctor, + .. + } = &payload[1] + else { + return Err(malformed_integer_range_fact( + "rust_pilot_primitives IntegerPrimitive `algebra` must be a variant value".to_string(), + default_span.clone(), + )); + }; + let FieldValue::Variant { + constructor: carrier_ctor, + .. + } = &payload[2] + else { + return Err(malformed_integer_range_fact( + "rust_pilot_primitives IntegerPrimitive `carrier` must be a variant value".to_string(), + default_span.clone(), + )); + }; + if *algebra_ctor != witness.algebra_variant_ty || *carrier_ctor != witness.carrier_variant_ty { + return Ok(None); + } + + let range = (|| { + let min_decimal = literal_string(payload.get(3)?)?; + let max_decimal = literal_string(payload.get(4)?)?; + let min = min_decimal.parse().ok()?; + let max = max_decimal.parse().ok()?; + if min > max { + return None; + } + Some(IntervalInt::ExactInterval { + target_name: literal_string(payload.first()?)?, + min_decimal, + max_decimal, + min, + max, + }) + })(); + + Ok(Some(PilotIntegerMatch { + range, + span: default_span, + })) +} + +fn literal_string(value: &FieldValue) -> Option { + match value { + FieldValue::Literal(LiteralBits::String(value)) => Some(value.clone()), + _ => None, + } +} + +pub(crate) fn literal_bigint_at(dag: &Dag, port: PortId) -> Option { + let s = match dag.resolve_producer_opt(&port)? { + Behavior::Value(value) => match &value.data { + LiteralBits::Int(s) => s.as_str(), + _ => return None, + }, + _ => return None, + }; + BigInt::from_str(s).ok() +} + +pub(crate) fn int_literal_fits_expected_type( + dag: &Dag, + literal: &BigInt, + expected: DeclarationId, +) -> Result, Diagnostic> { + match integer_range_for_decl(dag, expected) { + IntegerRangeLookup::Found(bound) => Ok(Some(bound.contains_bigint(literal))), + IntegerRangeLookup::Missing => Ok(None), + IntegerRangeLookup::Invalid(diag) => Err(diag), + } +} + +/// Build [`Diagnostic::MagnitudeOutOfRange`] from **exact** decimal range facts only. +/// +/// For a bound that may be [`IntervalInt::Unbounded`], use [`magnitude_out_of_range_for_interval`] +/// instead — this function does not accept [`IntervalInt`]. +pub(crate) fn magnitude_out_of_range( + literal: &BigInt, + expected: TypeShape, + facts: ExactIntIntervalFacts, + span: SourceSpan, +) -> Diagnostic { + Diagnostic::MagnitudeOutOfRange { + literal: literal.to_string(), + target: facts.target_name, + range_min_inclusive: facts.min_decimal, + range_max_inclusive: facts.max_decimal, + expected, + span, + fixes: Vec::new(), + } +} + +/// OOB diagnostic when the only available model is [`IntervalInt`] (e.g. from +/// [`integer_range_for_decl`]). Unbounded domains never produce `MagnitudeOutOfRange` for +/// finite literals; if that combination appears, fail closed without `unreachable!`. +pub(crate) fn magnitude_out_of_range_for_interval( + literal: &BigInt, + expected: TypeShape, + bound: IntervalInt, + span: SourceSpan, +) -> Diagnostic { + match bound.exact_interval_facts() { + Some(facts) => magnitude_out_of_range(literal, expected, facts, span), + None => Diagnostic::ResolveError { + name: "internal: integer literal failed range check but target has no exact interval facts" + .to_string(), + span, + fixes: Vec::new(), + }, + } +} + +fn malformed_integer_range_fact(message: String, span: SourceSpan) -> Diagnostic { + Diagnostic::MalformedIntegerRangeFact { + message, + span, + fixes: Vec::new(), + } +} + +/// Bootstrap-only gate: walk **every** `IntegerPrimitive` row in +/// `rust_pilot_primitives` and fail closed if any row is structurally +/// ill-formed, range strings do not parse as decimal `BigInt`, `min > max`, +/// or `(algebra, carrier)` witness pairs collide. +/// +/// Call this once when constructing the extdeps-including bootstrapped `Dag` +/// so drift or corruption in the pilot list surfaces at `Dag::new()`, +/// not only when a particular std type is queried. +pub(crate) fn validate_rust_pilot_integer_primitives(dag: &mut Dag) { + // R3 Phase B (Director Path A RATIFIED at gunbc#1739 #issuecomment-4392731264; + // Option (ii) at #issuecomment-4393145631; Phase B-1 at commit `59511503e`): + // pilot carries 10 IntegerPrimitive rows (i8..i64, i128, u8..u64, u128). + // u128 was unblocked by Phase A `IntervalInt::ExactInterval` BigInt host + // repr widening (commit `e7ba022c6`); the prior `T-Int128 Slice B1` / + // "Slice B2" deferral marker is resolved. + const EXPECTED_INTEGER_ROWS: usize = 10; + const INTEGER_PRIMITIVE_FIELD_COUNT: usize = 7; + + enum PilotListSnapshot { + List(Vec), + MissingBody, + NotList, + } + + // Authority file for span when the declaration is absent (extdeps fixture). + const RUST_PILOT_PRIMITIVES_AUTHORITY: &str = "dsl/extdeps/languages/rust/primitives.dag"; + + let (default_span, pilot_elements) = { + let Some(pilot) = dag.rust_pilot_primitives() else { + dag.attach_diagnostic(malformed_integer_range_fact( + "bootstrap: `rust_pilot_primitives` is missing from the extdeps fixture; \ + integer range authority is unavailable (expected extdeps `primitives.dag` load)" + .to_string(), + SourceSpan::new(RUST_PILOT_PRIMITIVES_AUTHORITY, 0, 0), + )); + return; + }; + let sp = pilot.span.clone(); + let snap = match pilot.value_body.as_ref() { + None => PilotListSnapshot::MissingBody, + Some(ValueBody::List(els)) => PilotListSnapshot::List(els.clone()), + Some(_) => PilotListSnapshot::NotList, + }; + (sp, snap) + }; + + let elements: &[FieldValue] = match &pilot_elements { + PilotListSnapshot::List(els) => els, + PilotListSnapshot::MissingBody => { + dag.attach_diagnostic(malformed_integer_range_fact( + "bootstrap: rust_pilot_primitives is missing a value body".to_string(), + default_span, + )); + return; + } + PilotListSnapshot::NotList => { + dag.attach_diagnostic(malformed_integer_range_fact( + "bootstrap: rust_pilot_primitives must be ValueBody::List".to_string(), + default_span, + )); + return; + } + }; + + let Some(integer_ctor) = rust_primitive_integer_variant_ty(dag) else { + dag.attach_diagnostic(malformed_integer_range_fact( + "bootstrap: RustPrimitive.IntegerPrimitive variant is unavailable".to_string(), + default_span, + )); + return; + }; + + let Some(ord_algebra) = disj_variant_payload_ty(dag, "IntegerAlgebra", "OrderedRingAlgebra") + else { + dag.attach_diagnostic(malformed_integer_range_fact( + "bootstrap: IntegerAlgebra.OrderedRingAlgebra is unavailable for pilot validation" + .to_string(), + default_span, + )); + return; + }; + let Some(sem_algebra) = disj_variant_payload_ty(dag, "IntegerAlgebra", "SemiringAlgebra") + else { + dag.attach_diagnostic(malformed_integer_range_fact( + "bootstrap: IntegerAlgebra.SemiringAlgebra is unavailable for pilot validation" + .to_string(), + default_span, + )); + return; + }; + let mut allowed_carriers: HashSet = HashSet::new(); + for label in [ + "ByteCarrier", + "Word16Carrier", + "Word32Carrier", + "Word64Carrier", + "Word128Carrier", + ] { + if let Some(c) = disj_variant_payload_ty(dag, "TargetCarrier", label) { + allowed_carriers.insert(c); + } + } + if allowed_carriers.is_empty() { + dag.attach_diagnostic(malformed_integer_range_fact( + "bootstrap: no TargetCarrier word variant payload types for pilot validation" + .to_string(), + default_span, + )); + return; + } + + let mut witnesses: HashSet<(DeclarationId, DeclarationId)> = HashSet::new(); + let mut integer_rows: usize = 0; + + for element in elements { + let FieldValue::Variant { + constructor, + payload, + } = element + else { + continue; + }; + if *constructor != integer_ctor { + continue; + } + integer_rows += 1; + + if payload.len() < INTEGER_PRIMITIVE_FIELD_COUNT { + dag.attach_diagnostic(malformed_integer_range_fact( + format!( + "rust_pilot_primitives IntegerPrimitive row has {} fields; expected {}", + payload.len(), + INTEGER_PRIMITIVE_FIELD_COUNT + ), + default_span.clone(), + )); + continue; + } + + let FieldValue::Variant { + constructor: algebra_ctor, + .. + } = &payload[1] + else { + dag.attach_diagnostic(malformed_integer_range_fact( + "rust_pilot_primitives IntegerPrimitive `algebra` must be a variant value" + .to_string(), + default_span.clone(), + )); + continue; + }; + if *algebra_ctor != ord_algebra && *algebra_ctor != sem_algebra { + dag.attach_diagnostic(malformed_integer_range_fact( + "rust_pilot_primitives IntegerPrimitive `algebra` must be OrderedRingAlgebra or SemiringAlgebra (variant payload type id)".to_string(), + default_span.clone(), + )); + continue; + } + + let FieldValue::Variant { + constructor: carrier_ctor, + .. + } = &payload[2] + else { + dag.attach_diagnostic(malformed_integer_range_fact( + "rust_pilot_primitives IntegerPrimitive `carrier` must be a variant value" + .to_string(), + default_span.clone(), + )); + continue; + }; + if !allowed_carriers.contains(carrier_ctor) { + dag.attach_diagnostic(malformed_integer_range_fact( + "rust_pilot_primitives IntegerPrimitive `carrier` must be a word TargetCarrier (Byte/Word16/Word32/Word64/Word128) variant payload type id".to_string(), + default_span.clone(), + )); + continue; + } + + if literal_string(&payload[0]).is_none() { + dag.attach_diagnostic(malformed_integer_range_fact( + "rust_pilot_primitives IntegerPrimitive `target_name` must be a string literal" + .to_string(), + default_span.clone(), + )); + continue; + } + + let (min_s, max_s) = match (literal_string(&payload[3]), literal_string(&payload[4])) { + (Some(a), Some(b)) => (a, b), + _ => { + dag.attach_diagnostic(malformed_integer_range_fact( + "rust_pilot_primitives IntegerPrimitive range bounds must be string literals" + .to_string(), + default_span.clone(), + )); + continue; + } + }; + + if !matches!(payload[5], FieldValue::Literal(LiteralBits::Bool(_))) { + dag.attach_diagnostic(malformed_integer_range_fact( + "rust_pilot_primitives IntegerPrimitive `is_copy` must be a bool literal" + .to_string(), + default_span.clone(), + )); + continue; + } + + if !matches!(&payload[6], FieldValue::Variant { .. }) { + dag.attach_diagnostic(malformed_integer_range_fact( + "rust_pilot_primitives IntegerPrimitive `overflow` must be a variant value" + .to_string(), + default_span.clone(), + )); + continue; + } + + let (min_n, max_n) = match (min_s.parse::(), max_s.parse::()) { + (Ok(mn), Ok(mx)) => (mn, mx), + _ => { + dag.attach_diagnostic(malformed_integer_range_fact( + format!( + "rust_pilot_primitives IntegerPrimitive range [{min_s}, {max_s}] must parse as a decimal integer" + ), + default_span.clone(), + )); + continue; + } + }; + if min_n > max_n { + dag.attach_diagnostic(malformed_integer_range_fact( + format!( + "rust_pilot_primitives IntegerPrimitive range order invalid: min {min_s} > max {max_s}" + ), + default_span.clone(), + )); + continue; + } + + if !witnesses.insert((*algebra_ctor, *carrier_ctor)) { + dag.attach_diagnostic(malformed_integer_range_fact( + format!( + "duplicate rust_pilot_primitives IntegerPrimitive (algebra, carrier) witness: ({algebra_ctor:?}, {carrier_ctor:?})" + ), + default_span.clone(), + )); + } + } + + if integer_rows != EXPECTED_INTEGER_ROWS { + dag.attach_diagnostic(malformed_integer_range_fact( + format!( + "rust_pilot_primitives must list exactly {EXPECTED_INTEGER_ROWS} IntegerPrimitive rows (pilot int scope); found {integer_rows}" + ), + default_span, + )); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::types::TypeShape; + + #[test] + fn magnitude_out_of_range_accepts_only_exact_int_interval_facts() { + let dag = Dag::new(); + let u8_decl = dag.declaration_by_name("UInt8").expect("UInt8").id; + let d = magnitude_out_of_range( + &BigInt::from(256), + TypeShape::new(u8_decl), + ExactIntIntervalFacts { + target_name: "u8".to_string(), + min_decimal: "0".to_string(), + max_decimal: "255".to_string(), + }, + SourceSpan::new("t.v3", 0, 0), + ); + assert!( + matches!( + d, + Diagnostic::MagnitudeOutOfRange { + ref literal, + ref target, + .. + } if literal == "256" && target == "u8" + ), + "expected MagnitudeOutOfRange, got {d:?}" + ); + } + + #[test] + fn magnitude_out_of_range_unbounded_target_fails_closed_with_resolve_error() { + let dag = Dag::new(); + let int_decl = dag.declaration_by_name("Int").expect("Int in bootstrap").id; + let d = magnitude_out_of_range_for_interval( + &BigInt::from(0), + TypeShape::new(int_decl), + IntervalInt::Unbounded, + SourceSpan::new("t.v3", 0, 0), + ); + assert!( + matches!(d, Diagnostic::ResolveError { .. }), + "expected fail-closed ResolveError, got {d:?}" + ); + } + + #[test] + fn interval_int_unbounded_accepts_sample_literals() { + assert!(IntervalInt::Unbounded.contains_bigint(&BigInt::from(i64::MIN))); + assert!(IntervalInt::Unbounded.contains_bigint(&BigInt::from(i64::MAX))); + } + + #[test] + fn int128_witness_matches_i128_pilot_row_constructors() { + // T-Int128 Slice B1: signed Int128 -> i128 pilot row via Word128Carrier. + let dag = Dag::new(); + assert!( + dag.diagnostics().is_empty(), + "bootstrap diagnostics: {:?}", + dag.diagnostics() + ); + let int128 = dag + .declaration_by_name("Int128") + .expect("Int128 in bootstrap") + .id; + let witness = integer_routing_witness_for_decl(&dag, int128).expect("Int128 witness"); + let pilot = dag.rust_pilot_primitives().expect("pilot"); + let ValueBody::List(elements) = pilot.value_body.as_ref().expect("list body") else { + panic!("expected list"); + }; + let integer_primitive_ctor = rust_primitive_integer_variant_ty(&dag).expect("ctor"); + let mut matched = 0usize; + for element in elements { + let FieldValue::Variant { + constructor, + payload, + } = element + else { + continue; + }; + if *constructor != integer_primitive_ctor { + continue; + } + let FieldValue::Variant { constructor: a, .. } = &payload[1] else { + continue; + }; + let FieldValue::Variant { constructor: c, .. } = &payload[2] else { + continue; + }; + if *a == witness.algebra_variant_ty && *c == witness.carrier_variant_ty { + matched += 1; + } + } + assert_eq!( + matched, 1, + "exactly one pilot IntegerPrimitive row for Int128 witness" + ); + } + + #[test] + fn int128_range_lookup_accepts_all_i64_magnitudes() { + // i128 row's range covers all i64 magnitudes; reconciliation passes + // typical literals through `contains_bigint`. + let dag = Dag::new(); + let int128 = dag + .declaration_by_name("Int128") + .expect("Int128 in bootstrap") + .id; + match integer_range_for_decl(&dag, int128) { + IntegerRangeLookup::Found(bound) => { + assert!(bound.contains_bigint(&BigInt::from(i64::MIN))); + assert!(bound.contains_bigint(&BigInt::from(0))); + assert!(bound.contains_bigint(&BigInt::from(i64::MAX))); + } + IntegerRangeLookup::Missing => panic!("expected Found range for Int128, got Missing"), + IntegerRangeLookup::Invalid(d) => { + panic!("expected Found range for Int128, got Invalid: {d:?}") + } + } + } + + #[test] + fn uint8_witness_matches_u8_pilot_row_constructors() { + let dag = Dag::new(); + assert!( + dag.diagnostics().is_empty(), + "bootstrap diagnostics: {:?}", + dag.diagnostics() + ); + let uint8 = dag + .declaration_by_name("UInt8") + .expect("UInt8 in bootstrap") + .id; + let witness = integer_routing_witness_for_decl(&dag, uint8).expect("UInt8 witness"); + let pilot = dag.rust_pilot_primitives().expect("pilot"); + let ValueBody::List(elements) = pilot.value_body.as_ref().expect("list body") else { + panic!("expected list"); + }; + let integer_primitive_ctor = rust_primitive_integer_variant_ty(&dag).expect("ctor"); + let mut matched = 0usize; + for element in elements { + let FieldValue::Variant { + constructor, + payload, + } = element + else { + continue; + }; + if *constructor != integer_primitive_ctor { + continue; + } + let FieldValue::Variant { constructor: a, .. } = &payload[1] else { + continue; + }; + let FieldValue::Variant { constructor: c, .. } = &payload[2] else { + continue; + }; + if *a == witness.algebra_variant_ty && *c == witness.carrier_variant_ty { + matched += 1; + } + } + assert_eq!( + matched, 1, + "exactly one pilot IntegerPrimitive row for UInt8 witness" + ); + } +} diff --git a/src/v3/compiler/src/pb_method_template_projection_generated.rs b/src/v3/compiler/src/pb_method_template_projection_generated.rs new file mode 100644 index 00000000000..1d1e9ae5d96 --- /dev/null +++ b/src/v3/compiler/src/pb_method_template_projection_generated.rs @@ -0,0 +1,1453 @@ +// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). +// +// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +// Dissolution receipt: PB-0 NON_TEST retirement — former hand module. +// + +//! PB-Bootstrap-Process consumer hook: project `MethodTemplateContract` rows +//! from the full bootstrap `Dag`. +//! +//! Implements **R3 row 85 / PB #1560 Gap 4** per the decision in +//! `docs/decisions/r3-row85-method-template-read-surface.md`. The single +//! row authority remains `src/v3/std/{rust,python,go}_method_template_contracts.dag`; +//! this module exposes a target-keyed structural projection of those rows +//! over the lowered bootstrap `Dag` so PB-zero / v2-retirement consumers can +//! select rows by target + `MethodRef` without: +//! +//! - importing `v3.std.*` into v2 crates (Gap 4 acceptance A1), +//! - introducing a second `Map` template-text authority (A2), +//! - copying any template strings into v2 sources (A3). +//! +//! All five `MethodTemplateContract` fields (`dag_method`, `runtime_template`, +//! `emit_template`, `wraps_result`, `placeholder_convention`) are preserved +//! by typed Rust mirrors below (A4). `emit_template` and +//! `placeholder_convention` are sum carriers; their substrate identity is +//! preserved as constructor-tagged enums, not flattened to strings. +//! +//! Gap 4 only — `LanguageSpec.method_templates` rewrite (Gap 5) and v2 leaf +//! emit migration are sequenced strictly after this hook lands. No v2 +//! consumer is migrated by this module. + +use std::collections::HashMap; + +use crate::dag::{Dag, DeclarationId, FieldValue, LiteralBits, TypeConnective, ValueBody}; + +/// Target language whose `MethodTemplateContract` row list is being projected. +/// +/// 🟢 TERMINAL at the per-target row-list scope. The three inhabitants +/// mirror the three closed list-declaration authorities under +/// `src/v3/std/{rust,python,go}_method_template_contracts.dag`. New +/// targets land by adding a row-list authority + a constructor here +/// together; no other dissolution path applies. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum MethodTemplateTarget { + Rust, + Python, + Go, +} + +impl MethodTemplateTarget { + /// Per-target list-declaration name in the bootstrap `Dag`. The strings + /// here mirror the data declaration names in + /// `src/v3/std/{rust,python,go}_method_template_contracts.dag` and are + /// the *names of structural declarations*, not template text. + fn list_declaration_name(self) -> &'static str { + match self { + MethodTemplateTarget::Rust => "rust_method_template_contracts", + MethodTemplateTarget::Python => "python_method_template_contracts", + MethodTemplateTarget::Go => "go_method_template_contracts", + } + } +} + +/// Projected `MethodEmitTemplate` (substrate sum at +/// `src/v3/std/emit_model.dag:469-474`). +/// +/// 🟢 TERMINAL at target method-emission-template scope, inheriting the +/// terminality of the substrate carrier (`MethodEmitTemplate` is marked +/// 🟢 TERMINAL at `emit_model.dag:453`). The two-variant split between +/// inline-lambda and function-reference renderings is the closed semantic +/// case the substrate already locks; new variants here would mean the +/// substrate carrier itself moved. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum MethodEmitTemplateProjection { + Single { + template: String, + }, + HigherOrder { + inline_template: String, + fn_ref_template: String, + }, +} + +/// Projected `PlaceholderConvention` (substrate sum at +/// `src/v3/std/emit_model.dag:449-451`). +/// +/// 🟢 TERMINAL: nullary two-inhabitant sum (`IndexedArgs | NamedArg`) that +/// mirrors the closed substrate carrier. New conventions land by editing +/// the substrate sum first; this projection follows. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PlaceholderConventionProjection { + IndexedArgs, + NamedArg, +} + +/// Projected single `MethodTemplateContract` row. +/// +/// `dag_method` is the `DeclarationId` of the `MethodDeclaration` that the +/// row's `MethodRef.decl` field references (the typed, fail-closed substrate +/// identity for the method). Consumers select rows by target + +/// `dag_method` to recover the `(target, method)` template pair. +#[derive(Debug, Clone)] +pub struct MethodTemplateContractRow { + pub dag_method: DeclarationId, + pub runtime_template: String, + pub emit_template: MethodEmitTemplateProjection, + pub wraps_result: bool, + pub placeholder_convention: PlaceholderConventionProjection, +} + +/// Fail-closed projection error. Every observable mismatch between the +/// bootstrap `Dag` shape and the `MethodTemplateContract` substrate carrier +/// surfaces here as a typed value (per `INVARIANTS.md` C-8). +/// +/// 🟢 TERMINAL at the projection's bootstrap-mismatch scope. Each variant +/// names a specific structural class the row authorities cannot satisfy; +/// new variants only land if substrate shape grows a new sub-fact (which +/// would itself follow §P1). The carrier is therefore a closed +/// substrate-mismatch enumeration, not a transitional grab-bag. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum MethodTemplateProjectionError { + /// Per-target row-list declaration is absent from the bootstrap `Dag`. + /// Should be impossible if `BOOTSTRAP_FIXTURE_PATH_KEYS` (`bootstrap.rs`) + /// stays in lockstep with `bootstrap_fixture_authority` and + /// `regen_bootstrap --verify` is green; surfaced rather than panicked + /// so consumers can act on it. + ListDeclarationMissing { name: &'static str }, + /// List declaration has no value body (i.e., not a `data` declaration). + ListDeclarationLacksValueBody { name: &'static str }, + /// List declaration's value body is not `ValueBody::List` — violates the + /// `bootstrap_method_template_contracts_lower_to_list_with_empty_diagnostics` + /// gate per the row-85 decision doc. + ListDeclarationValueBodyNotList { name: &'static str }, + /// Row at the given index in the named list is not a record literal. + RowNotRecord { + list: &'static str, + row_index: usize, + }, + /// Row is a declaration reference, but the referenced declaration is not + /// present in the bootstrap `Dag`. + RowReferenceMissing { + list: &'static str, + row_index: usize, + decl_id: DeclarationId, + }, + /// Row is a declaration reference, but the referenced declaration is not a + /// `data` declaration carrying a structural `MethodTemplateContract` body. + RowReferenceNotStructural { + list: &'static str, + row_index: usize, + decl_id: DeclarationId, + }, + /// Row is a declaration reference, but the referenced declaration does + /// not instantiate the `MethodTemplateContract` carrier. A structural + /// lookalike row is still not a contract row authority. + RowReferenceNotMethodTemplateContract { + list: &'static str, + row_index: usize, + decl_id: DeclarationId, + }, + /// Row is missing one of the five required fields. + RowMissingField { + list: &'static str, + row_index: usize, + field: &'static str, + }, + /// Row carries a duplicate field — two entries share a label. The + /// substrate-side `MethodTemplateContract` (and `MethodRef`) carriers + /// declare each field exactly once; duplicates would mean two + /// authorities for the same row coordinate (P2 single-authority). + RowDuplicateField { + list: &'static str, + row_index: usize, + record: &'static str, + field: &'static str, + first_field_index: usize, + duplicate_field_index: usize, + }, + /// Row carries a field whose label is not one of the closed schema + /// (`MethodTemplateContract` = five fields; `MethodRef` = one field). + /// Extra substrate fields land via the substrate carrier, not via + /// projection-side acceptance, so unknown labels surface here rather + /// than being silently ignored (P2 / C-8). + RowUnknownField { + list: &'static str, + row_index: usize, + record: &'static str, + field: String, + field_index: usize, + }, + /// `dag_method` field is not the `MethodRef { decl }` record shape. + DagMethodNotMethodRefRecord { + list: &'static str, + row_index: usize, + }, + /// `MethodRef.decl` is not a typed substrate reference. + MethodRefDeclNotReference { + list: &'static str, + row_index: usize, + }, + /// `MethodRef.decl` references a declaration that does not satisfy + /// the substrate-side `MethodDeclaration` *data-binding* contract + /// (`src/v3/std/methods.dag` + `dsl/std/methods.dag`). Mirrors the + /// Stratum A registry gate + /// (`src/v3/grounding_tests/src/stratum_a.rs::method_registry_name`): + /// the binding must (a) instantiate `MethodDeclaration`, (b) carry + /// a `Structural` value body, (c) have a closed single-`name` field + /// schema, (d) and the `name` value must be a string literal. The + /// `reason` payload identifies which sub-check failed. + MethodRefDeclNotMethodDeclaration { + list: &'static str, + row_index: usize, + decl_id: DeclarationId, + reason: MethodDeclarationBindingViolation, + }, + /// `method_template_contract_row` was called with a `dag_method` + /// `DeclarationId` that does not satisfy the + /// `MethodDeclaration` data-binding contract (same shape and + /// `reason` taxonomy as `MethodRefDeclNotMethodDeclaration`). The + /// helper validates at entry so callers cannot conflate + /// "valid method, no row" with "caller handed in a non-method + /// declaration." + LookupKeyNotMethodDeclaration { + decl_id: DeclarationId, + reason: MethodDeclarationBindingViolation, + }, + /// `MethodDeclaration` itself is missing from the bootstrap `Dag`. + /// Should be impossible if the std fixtures lower cleanly; surfaced + /// rather than panicked so consumers can act on it. + MethodDeclarationCarrierMissing, + /// `runtime_template` field is not a string literal. + RuntimeTemplateNotString { + list: &'static str, + row_index: usize, + }, + /// `wraps_result` field is not a bool literal. + WrapsResultNotBool { + list: &'static str, + row_index: usize, + }, + /// `placeholder_convention` field is not a substrate variant constructor. + PlaceholderConventionNotVariant { + list: &'static str, + row_index: usize, + }, + /// `placeholder_convention` constructor name is unknown. + PlaceholderConventionUnknown { + list: &'static str, + row_index: usize, + constructor: Option, + }, + /// `placeholder_convention` is a nullary sum (`IndexedArgs | NamedArg`), + /// but the variant payload is non-empty. Mirrors the substrate-side + /// canonical-row check + /// `placeholder_convention_canonical_rejects_payload_for_nullary_indexed_args` + /// in `src/v3/grounding_tests/src/stratum_a.rs`. + PlaceholderConventionPayloadNotEmpty { + list: &'static str, + row_index: usize, + constructor: &'static str, + payload_len: usize, + }, + /// `emit_template` field is not a substrate variant constructor. + EmitTemplateNotVariant { + list: &'static str, + row_index: usize, + }, + /// `emit_template` constructor name is unknown. + EmitTemplateUnknownConstructor { + list: &'static str, + row_index: usize, + constructor: Option, + }, + /// `emit_template` `SingleTemplate` payload shape mismatch (expected one + /// string-typed `template` field). + EmitTemplateSinglePayloadShape { + list: &'static str, + row_index: usize, + }, + /// `emit_template` `HigherOrderTemplates` payload shape mismatch + /// (expected two string-typed fields: `inline_template`, `fn_ref_template`). + EmitTemplateHigherOrderPayloadShape { + list: &'static str, + row_index: usize, + }, + /// The `(target, dag_method)` direct lookup helper found more than one row + /// for the same `MethodDeclaration` in the per-target list. Per-target + /// uniqueness by `dag_method` is the substrate-side claim + /// (`method_template_contract_per_target_dag_method_unique`); the helper + /// surfaces a violation rather than silently selecting the first row + /// (P2 single-authority + P3 fail-closed at the public lookup boundary). + DuplicateMethodTemplateRow { + list: &'static str, + first_row_index: usize, + duplicate_row_index: usize, + }, +} + +/// Granular reason for a `MethodDeclaration` data-binding contract failure. +/// +/// 🟢 TERMINAL at the substrate-side data-binding contract scope +/// (`src/v3/std/methods.dag` + Stratum A `method_registry_name`). Each +/// variant maps one of the four sub-checks the contract requires; the +/// substrate-side contract is closed (`MethodDeclaration` carries a +/// single `name: String` field), so this enumeration is closed too. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum MethodDeclarationBindingViolation { + /// Connective is not `TypeConnective::Instantiation { template, .. }`. + /// Rules out type / sum / record declarations. + ConnectiveNotInstantiation, + /// `Instantiation` connective, but `template` does not match + /// `MethodDeclaration`'s declaration id. Rules out instantiations of + /// unrelated targets. + InstantiationTemplateNotMethodDeclaration, + /// `value_body` is `None`. Rules out type aliases of the form + /// `type Foo = MethodDeclaration`. + ValueBodyMissing, + /// `value_body` is `Some(_)` but not `ValueBody::Structural`. Rules + /// out `List` / `Map` / scalar bodies that would be wrong-shaped for + /// a `MethodDeclaration` data binding. + ValueBodyNotStructural, + /// `Structural` body, but its field set does not match the closed + /// `MethodDeclaration` schema (single `name` field). Mirrors + /// `enforce_closed_record_schema` in Stratum A. + StructuralFieldsNotClosedNameOnly { observed_labels: Vec }, + /// Closed-schema match, but the `name` field's `FieldValue` is not + /// `Literal(LiteralBits::String(_))`. + NameNotStringLiteral, +} + +/// Project the `MethodTemplateContract` rows for `target` from the full +/// bootstrap `Dag` snapshot. +/// +/// The expected source for `dag` is `generated_full_bootstrap_dag()` (or its +/// equivalent under PB-1-e dissolution-first per #1558 — see the +/// row-85 decision doc §"Non-Fork Ratchet"). Calling on a partially-built +/// `Dag` that is missing the per-target list authorities surfaces +/// [`MethodTemplateProjectionError::ListDeclarationMissing`] rather than +/// panicking. +/// +/// **Boundary contract:** this is the canonical PB-zero consumer hook for +/// row text. v2-side migrations consume `Vec` and +/// must not store or carry parallel `Map` template tables; +/// the structural carrier is the typed projection above. +pub fn method_template_contract_rows( + dag: &Dag, + target: MethodTemplateTarget, +) -> Result, MethodTemplateProjectionError> { + let list_name = target.list_declaration_name(); + let decl = dag + .declaration_by_name(list_name) + .ok_or(MethodTemplateProjectionError::ListDeclarationMissing { name: list_name })?; + let body = decl + .value_body + .as_ref() + .ok_or(MethodTemplateProjectionError::ListDeclarationLacksValueBody { name: list_name })?; + let ValueBody::List(rows) = body else { + return Err( + MethodTemplateProjectionError::ListDeclarationValueBodyNotList { name: list_name }, + ); + }; + + let projected: Vec = rows + .iter() + .enumerate() + .map(|(row_index, row)| project_row(dag, list_name, row_index, row)) + .collect::>()?; + + // Per-target uniqueness by `dag_method` is the substrate-side claim + // (`method_template_contract_per_target_dag_method_unique`). Enforce it + // at the canonical row-list boundary so v2-retirement consumers that + // build their own keyed tables from the projection cannot silently + // observe two authorities for the same `(target, MethodRef)` pair. + let mut first_seen: HashMap = HashMap::with_capacity(projected.len()); + for (index, row) in projected.iter().enumerate() { + if let Some(prior) = first_seen.get(&row.dag_method) { + return Err(MethodTemplateProjectionError::DuplicateMethodTemplateRow { + list: list_name, + first_row_index: *prior, + duplicate_row_index: index, + }); + } + first_seen.insert(row.dag_method, index); + } + + Ok(projected) +} + +/// Direct `(target, dag_method)` lookup helper for Gap-5 / leaf-emit +/// consumers. Returns the row whose `dag_method` matches `dag_method`, or +/// `None` if the per-target list does not contain a row keyed by that +/// `MethodDeclaration`. +/// +/// **Fail-closed on non-method keys.** `dag_method` must instantiate +/// `MethodDeclaration` — the helper validates this at entry so a caller +/// cannot conflate two distinct states: "valid `MethodDeclaration` with +/// no row for this target" (returns `Ok(None)`) versus "caller supplied +/// a declaration that is not a `MethodDeclaration` at all" (returns +/// [`MethodTemplateProjectionError::LookupKeyNotMethodDeclaration`]). +/// This mirrors the per-row `MethodRef.decl` check the projection +/// already enforces; until `DeclarationRef` +/// refinement-typing lands, this is the only place the boundary can +/// hold. +/// +/// Other typed projection failures — including +/// [`MethodTemplateProjectionError::DuplicateMethodTemplateRow`], which is +/// enforced at the canonical row-list boundary in +/// [`method_template_contract_rows`] — bubble through. +pub fn method_template_contract_row( + dag: &Dag, + target: MethodTemplateTarget, + dag_method: DeclarationId, +) -> Result, MethodTemplateProjectionError> { + let method_declaration_id = method_declaration_carrier_id(dag)?; + if let Err(reason) = + validate_method_declaration_data_binding(dag, dag_method, method_declaration_id) + { + return Err( + MethodTemplateProjectionError::LookupKeyNotMethodDeclaration { + decl_id: dag_method, + reason, + }, + ); + } + let rows = method_template_contract_rows(dag, target)?; + Ok(rows.into_iter().find(|row| row.dag_method == dag_method)) +} + +/// Locate the `MethodDeclaration` carrier declaration in the bootstrap +/// `Dag` once. Surfaces [`MethodTemplateProjectionError::MethodDeclarationCarrierMissing`] +/// when the carrier is absent rather than panicking. +pub(crate) fn method_declaration_carrier_id( + dag: &Dag, +) -> Result { + Ok(dag + .declaration_by_name("MethodDeclaration") + .ok_or(MethodTemplateProjectionError::MethodDeclarationCarrierMissing)? + .id) +} + +/// Validate that `decl_id` references a `MethodDeclaration` *data binding* +/// per the substrate-side contract (`src/v3/std/methods.dag` + +/// `dsl/std/methods.dag`). +/// +/// Mirrors `src/v3/grounding_tests/src/stratum_a.rs::method_registry_name` +/// so projection-side and Stratum A-side enforcement of the same gate +/// stay in lockstep. Four sub-checks must all pass: +/// +/// 1. **Connective is `Instantiation`.** Rules out type / sum / record +/// declarations. +/// 2. **`Instantiation.template == MethodDeclaration`.** Rules out +/// instantiations of unrelated targets. +/// 3. **`value_body` is `Some(ValueBody::Structural { .. })`.** Rules +/// out type aliases (`type Foo = MethodDeclaration`, no value body) +/// and wrong-shaped data bindings (`List` / `Map` / scalar bodies). +/// 4. **`Structural` fields are exactly `[name]` with a string literal +/// value.** Closed schema check — mirrors +/// `enforce_closed_record_schema` in Stratum A. Rules out extra +/// fields, missing `name`, duplicate `name`, and non-string-literal +/// `name` values. +/// +/// On success returns the projected method name (`name` field's string +/// literal). On failure returns the typed +/// [`MethodDeclarationBindingViolation`] reason. +/// +/// Both `project_dag_method` (per-row) and `method_template_contract_row` +/// (lookup helper) consume this helper so the data-binding identity is +/// factored once. +pub(crate) fn validate_method_declaration_data_binding( + dag: &Dag, + decl_id: DeclarationId, + method_declaration_id: DeclarationId, +) -> Result { + let referenced = dag.declaration(decl_id); + + // (1) + (2) Connective + template. + let template = match &referenced.connective { + TypeConnective::Instantiation { template, .. } => *template, + _ => return Err(MethodDeclarationBindingViolation::ConnectiveNotInstantiation), + }; + if template != method_declaration_id { + return Err(MethodDeclarationBindingViolation::InstantiationTemplateNotMethodDeclaration); + } + + // (3) Value body present and Structural. + let value_body = referenced + .value_body + .as_ref() + .ok_or(MethodDeclarationBindingViolation::ValueBodyMissing)?; + let ValueBody::Structural { fields } = value_body else { + return Err(MethodDeclarationBindingViolation::ValueBodyNotStructural); + }; + + // (4) Closed `[name]` schema with string-literal value. Reject any + // duplicate or unknown label, require `name` to be present exactly + // once. Mirrors Stratum A's enforce_closed_record_schema. + const EXPECTED: &[&str] = &["name"]; + let name_count = fields.iter().filter(|(label, _)| label == "name").count(); + let any_unknown = fields + .iter() + .any(|(label, _)| !EXPECTED.contains(&label.as_str())); + if fields.len() != EXPECTED.len() || name_count != 1 || any_unknown { + let observed_labels: Vec = fields.iter().map(|(label, _)| label.clone()).collect(); + return Err( + MethodDeclarationBindingViolation::StructuralFieldsNotClosedNameOnly { + observed_labels, + }, + ); + } + let (_, name_value) = fields + .iter() + .find(|(label, _)| label == "name") + .expect("closed-schema check above guarantees a single `name` field"); + match name_value { + FieldValue::Literal(LiteralBits::String(s)) => Ok(s.clone()), + _ => Err(MethodDeclarationBindingViolation::NameNotStringLiteral), + } +} + +fn project_row( + dag: &Dag, + list: &'static str, + row_index: usize, + row: &FieldValue, +) -> Result { + let fields = match row { + FieldValue::Record(fields) => fields, + FieldValue::Reference(decl_id) => { + let decl = dag.declaration_opt(decl_id).ok_or( + MethodTemplateProjectionError::RowReferenceMissing { + list, + row_index, + decl_id: *decl_id, + }, + )?; + let method_template_contract = dag.method_template_contract_decl().ok_or( + MethodTemplateProjectionError::RowReferenceNotMethodTemplateContract { + list, + row_index, + decl_id: *decl_id, + }, + )?; + let TypeConnective::Instantiation { template, .. } = &decl.connective else { + return Err( + MethodTemplateProjectionError::RowReferenceNotMethodTemplateContract { + list, + row_index, + decl_id: *decl_id, + }, + ); + }; + if *template != method_template_contract { + return Err( + MethodTemplateProjectionError::RowReferenceNotMethodTemplateContract { + list, + row_index, + decl_id: *decl_id, + }, + ); + } + match decl.value_body.as_ref() { + Some(ValueBody::Structural { fields }) => fields, + _ => { + return Err(MethodTemplateProjectionError::RowReferenceNotStructural { + list, + row_index, + decl_id: *decl_id, + }); + } + } + } + _ => return Err(MethodTemplateProjectionError::RowNotRecord { list, row_index }), + }; + + const EXPECTED: &[&str] = &[ + "dag_method", + "runtime_template", + "emit_template", + "wraps_result", + "placeholder_convention", + ]; + let lookup = closed_record_lookup(list, row_index, "MethodTemplateContract", fields, EXPECTED)?; + + let dag_method_value = lookup_required(list, row_index, &lookup, "dag_method")?; + let dag_method = project_dag_method(dag, list, row_index, dag_method_value)?; + + let runtime_template_value = lookup_required(list, row_index, &lookup, "runtime_template")?; + let runtime_template = match runtime_template_value { + FieldValue::Literal(LiteralBits::String(s)) => s.clone(), + _ => { + return Err(MethodTemplateProjectionError::RuntimeTemplateNotString { + list, + row_index, + }); + } + }; + + let emit_template_value = lookup_required(list, row_index, &lookup, "emit_template")?; + let emit_template = project_emit_template(dag, list, row_index, emit_template_value)?; + + let wraps_result_value = lookup_required(list, row_index, &lookup, "wraps_result")?; + let wraps_result = match wraps_result_value { + FieldValue::Literal(LiteralBits::Bool(b)) => *b, + _ => return Err(MethodTemplateProjectionError::WrapsResultNotBool { list, row_index }), + }; + + let placeholder_value = lookup_required(list, row_index, &lookup, "placeholder_convention")?; + let placeholder_convention = project_placeholder(dag, list, row_index, placeholder_value)?; + + Ok(MethodTemplateContractRow { + dag_method, + runtime_template, + emit_template, + wraps_result, + placeholder_convention, + }) +} + +/// Validate that `fields` carries exactly the closed set of `expected` +/// labels — no duplicates, no unknowns. Missing labels are not reported +/// here (the per-field `lookup_required` calls below surface those as +/// `RowMissingField` so the error names *which* field is missing). +/// +/// Returns a label-to-`FieldValue` map for the present fields; collisions +/// surface as `RowDuplicateField` and unknown labels surface as +/// `RowUnknownField`. `record` names the carrier (e.g. +/// `"MethodTemplateContract"` or `"MethodRef"`) for diagnostic context. +fn closed_record_lookup<'a>( + list: &'static str, + row_index: usize, + record: &'static str, + fields: &'a [(String, FieldValue)], + expected: &[&'static str], +) -> Result, MethodTemplateProjectionError> { + let mut lookup: HashMap<&'static str, &FieldValue> = HashMap::with_capacity(expected.len()); + let mut first_seen_index: HashMap<&'static str, usize> = HashMap::with_capacity(expected.len()); + for (field_index, (label, value)) in fields.iter().enumerate() { + // Resolve the row-side `String` label to its `&'static str` in + // `expected`. Holding the static reference avoids re-scanning the + // expected slice (and the `.expect()` re-borrow) on the duplicate + // path; non-membership surfaces as `RowUnknownField`. + let Some(static_label) = expected.iter().copied().find(|exp| *exp == label.as_str()) else { + return Err(MethodTemplateProjectionError::RowUnknownField { + list, + row_index, + record, + field: label.clone(), + field_index, + }); + }; + if let Some(prior_index) = first_seen_index.get(static_label) { + return Err(MethodTemplateProjectionError::RowDuplicateField { + list, + row_index, + record, + field: static_label, + first_field_index: *prior_index, + duplicate_field_index: field_index, + }); + } + lookup.insert(static_label, value); + first_seen_index.insert(static_label, field_index); + } + Ok(lookup) +} + +fn lookup_required<'a>( + list: &'static str, + row_index: usize, + lookup: &HashMap<&'a str, &'a FieldValue>, + field: &'static str, +) -> Result<&'a FieldValue, MethodTemplateProjectionError> { + lookup + .get(field) + .copied() + .ok_or(MethodTemplateProjectionError::RowMissingField { + list, + row_index, + field, + }) +} + +fn project_dag_method( + dag: &Dag, + list: &'static str, + row_index: usize, + value: &FieldValue, +) -> Result { + let FieldValue::Record(method_ref_fields) = value else { + return Err(MethodTemplateProjectionError::DagMethodNotMethodRefRecord { list, row_index }); + }; + // `MethodRef` is a single-field carrier (`{ decl: DeclarationRef }`) + // per `src/v3/std/methods.dag`. Apply the same closed-schema check + // as the outer row so duplicates (`{ decl: a, decl: b }`) and + // unknown extras (`{ decl: ..., extra: ... }`) surface as typed + // errors instead of being silently ignored. + const METHOD_REF_FIELDS: &[&str] = &["decl"]; + let lookup = closed_record_lookup( + list, + row_index, + "MethodRef", + method_ref_fields, + METHOD_REF_FIELDS, + )?; + let decl_value = lookup + .get("decl") + .copied() + .ok_or(MethodTemplateProjectionError::DagMethodNotMethodRefRecord { list, row_index })?; + let decl_id = match decl_value { + FieldValue::Reference(decl_id) => *decl_id, + _ => { + return Err(MethodTemplateProjectionError::MethodRefDeclNotReference { + list, + row_index, + }); + } + }; + // `src/v3/std/methods.dag` calls out the boundary contract: today's + // substrate grammar can't express `DeclarationRef`, + // so the projection enforces fail-closed at the boundary that + // `decl` references a `MethodDeclaration`-instantiating data binding. + // Pattern mirrors `method_registry_test.rs::method_registry_covers_*`. + let method_declaration_id = method_declaration_carrier_id(dag)?; + if let Err(reason) = + validate_method_declaration_data_binding(dag, decl_id, method_declaration_id) + { + return Err( + MethodTemplateProjectionError::MethodRefDeclNotMethodDeclaration { + list, + row_index, + decl_id, + reason, + }, + ); + } + Ok(decl_id) +} + +fn project_emit_template( + dag: &Dag, + list: &'static str, + row_index: usize, + value: &FieldValue, +) -> Result { + let FieldValue::Variant { + constructor, + payload, + } = value + else { + return Err(MethodTemplateProjectionError::EmitTemplateNotVariant { list, row_index }); + }; + let constructor_label = disj_variant_label(dag, "MethodEmitTemplate", *constructor); + match constructor_label { + Some("SingleTemplate") => { + // Payload shape (positional, per FieldValue::Variant): one + // `template: String` field. Lowering produces a single + // record-shaped payload entry whose only field is `template`. + let template = single_template_payload(list, row_index, payload)?; + Ok(MethodEmitTemplateProjection::Single { template }) + } + Some("HigherOrderTemplates") => { + let (inline_template, fn_ref_template) = + higher_order_payload(list, row_index, payload)?; + Ok(MethodEmitTemplateProjection::HigherOrder { + inline_template, + fn_ref_template, + }) + } + other => Err( + MethodTemplateProjectionError::EmitTemplateUnknownConstructor { + list, + row_index, + constructor: other.map(str::to_string), + }, + ), + } +} + +/// Recover a sum-variant label by walking the parent `Disj` declaration's +/// variant list. Variant constructor declarations themselves do not carry +/// `name`; identity flows through the parent's `variants[i].label`. Mirrors +/// the pattern used in `tests/integration/method_template_contract_test.rs` +/// (`method_emit_template_variant_label`). +fn disj_variant_label<'a>( + dag: &'a Dag, + parent_name: &str, + constructor: DeclarationId, +) -> Option<&'a str> { + let parent = dag.declaration_by_name(parent_name)?; + let TypeConnective::Disj { variants } = &parent.connective else { + return None; + }; + variants + .iter() + .find(|variant| variant.ty == constructor) + .map(|variant| variant.label.as_str()) +} + +fn single_template_payload( + list: &'static str, + row_index: usize, + payload: &[FieldValue], +) -> Result { + // `Variant.payload` is positional per the substrate carrier + // (`FieldValue::Variant` doc-comment). `SingleTemplate { template: String }` + // therefore lowers to a single-element payload carrying the string literal. + match payload { + [FieldValue::Literal(LiteralBits::String(s))] => Ok(s.clone()), + _ => Err(MethodTemplateProjectionError::EmitTemplateSinglePayloadShape { list, row_index }), + } +} + +fn higher_order_payload( + list: &'static str, + row_index: usize, + payload: &[FieldValue], +) -> Result<(String, String), MethodTemplateProjectionError> { + // `HigherOrderTemplates { inline_template: String, fn_ref_template: String }` + // lowers to a two-element positional payload carrying the two string + // literals in declaration order. + match payload { + [FieldValue::Literal(LiteralBits::String(inline)), FieldValue::Literal(LiteralBits::String(fn_ref))] => { + Ok((inline.clone(), fn_ref.clone())) + } + _ => Err( + MethodTemplateProjectionError::EmitTemplateHigherOrderPayloadShape { list, row_index }, + ), + } +} + +fn project_placeholder( + dag: &Dag, + list: &'static str, + row_index: usize, + value: &FieldValue, +) -> Result { + let FieldValue::Variant { + constructor, + payload, + } = value + else { + return Err( + MethodTemplateProjectionError::PlaceholderConventionNotVariant { list, row_index }, + ); + }; + let constructor_label = disj_variant_label(dag, "PlaceholderConvention", *constructor); + // `PlaceholderConvention` is a nullary sum (`IndexedArgs | NamedArg`) + // per `src/v3/std/emit_model.dag:449-451`. A non-empty payload would + // mean a malformed bootstrap row (e.g., `IndexedArgs(false)`) that the + // projection must surface as a typed error rather than silently + // discarding. Mirrors the substrate-side canonical-row check in + // `src/v3/grounding_tests/src/stratum_a.rs`. + let (label, projection) = match constructor_label { + Some("IndexedArgs") => ("IndexedArgs", PlaceholderConventionProjection::IndexedArgs), + Some("NamedArg") => ("NamedArg", PlaceholderConventionProjection::NamedArg), + other => { + return Err( + MethodTemplateProjectionError::PlaceholderConventionUnknown { + list, + row_index, + constructor: other.map(str::to_string), + }, + ); + } + }; + if !payload.is_empty() { + return Err( + MethodTemplateProjectionError::PlaceholderConventionPayloadNotEmpty { + list, + row_index, + constructor: label, + payload_len: payload.len(), + }, + ); + } + Ok(projection) +} + +#[cfg(test)] +mod tests { + //! Unit tests for projection-internal failure modes that require + //! crate-private `Dag` mutation to construct (e.g., injecting a + //! malformed value body that the on-disk row authorities cannot + //! produce). Behavior tests over the green bootstrap Dag live in + //! `tests/integration/pb_method_template_projection_test.rs`. + + use super::*; + use crate::generated_full_bootstrap_dag; + + /// Inject a non-empty payload onto the `placeholder_convention` variant + /// of the row at `row_index` of the named per-target list. Mutates only + /// the in-memory `Dag`; the row authority sources on disk are + /// untouched. + fn inject_extra_placeholder_payload(dag: &mut Dag, list_name: &str, row_index: usize) { + let decl_id = dag + .declaration_by_name(list_name) + .unwrap_or_else(|| panic!("`{list_name}` missing from bootstrap")) + .id; + let decl = dag.declaration_mut(decl_id); + let body = decl + .value_body + .as_mut() + .unwrap_or_else(|| panic!("`{list_name}` has no value body")); + let ValueBody::List(rows) = body else { + panic!("`{list_name}` value body is not a List"); + }; + let row = rows + .get_mut(row_index) + .unwrap_or_else(|| panic!("row {row_index} missing from `{list_name}`")); + let FieldValue::Record(fields) = row else { + panic!("row {row_index} is not a Record"); + }; + let placeholder_value = fields + .iter_mut() + .find(|(label, _)| label == "placeholder_convention") + .map(|(_, value)| value) + .expect("placeholder_convention field"); + let FieldValue::Variant { payload, .. } = placeholder_value else { + panic!("placeholder_convention must be a Variant"); + }; + payload.push(FieldValue::Literal(LiteralBits::Bool(false))); + } + + /// Duplicate the row at `row_index` of the named per-target list by + /// pushing a clone onto the end of the list. The two rows share a + /// `dag_method`, breaking per-target uniqueness — the substrate-side + /// claim `method_template_contract_per_target_dag_method_unique`. + /// Mutates only the in-memory `Dag`. + fn duplicate_row(dag: &mut Dag, list_name: &str, row_index: usize) { + let decl_id = dag + .declaration_by_name(list_name) + .unwrap_or_else(|| panic!("`{list_name}` missing from bootstrap")) + .id; + let decl = dag.declaration_mut(decl_id); + let body = decl + .value_body + .as_mut() + .unwrap_or_else(|| panic!("`{list_name}` has no value body")); + let ValueBody::List(rows) = body else { + panic!("`{list_name}` value body is not a List"); + }; + let clone = rows + .get(row_index) + .unwrap_or_else(|| panic!("row {row_index} missing from `{list_name}`")) + .clone(); + rows.push(clone); + } + + fn replace_row_with_reference( + dag: &mut Dag, + list_name: &str, + row_index: usize, + replacement: DeclarationId, + ) { + let decl_id = dag.declaration_by_name(list_name).expect("list").id; + let decl = dag.declaration_mut(decl_id); + let body = decl.value_body.as_mut().expect("value body"); + let ValueBody::List(rows) = body else { + panic!("not a list"); + }; + let row = rows.get_mut(row_index).expect("row"); + *row = FieldValue::Reference(replacement); + } + + /// Mutate the row at `row_index` of `list_name` by adding a second + /// field with `field_label` (cloned from the first occurrence). Used + /// by the closed-schema duplicate-field tests below. + fn duplicate_field_on_row(dag: &mut Dag, list_name: &str, row_index: usize, field_label: &str) { + let decl_id = dag.declaration_by_name(list_name).expect("list").id; + let decl = dag.declaration_mut(decl_id); + let body = decl.value_body.as_mut().expect("value body"); + let ValueBody::List(rows) = body else { + panic!("not a list"); + }; + let row = rows.get_mut(row_index).expect("row"); + let FieldValue::Record(fields) = row else { + panic!("row not record"); + }; + let (orig_label, orig_value) = fields + .iter() + .find(|(label, _)| label == field_label) + .map(|(label, value)| (label.clone(), value.clone())) + .expect("field to clone"); + fields.push((orig_label, orig_value)); + } + + /// Replace the row at `row_index` of `list_name` so that, after the + /// existing fields, an unknown-labelled field is appended (used by + /// the closed-schema unknown-field tests below). + fn append_unknown_field_on_row( + dag: &mut Dag, + list_name: &str, + row_index: usize, + unknown_label: &str, + ) { + let decl_id = dag.declaration_by_name(list_name).expect("list").id; + let decl = dag.declaration_mut(decl_id); + let body = decl.value_body.as_mut().expect("value body"); + let ValueBody::List(rows) = body else { + panic!("not a list"); + }; + let row = rows.get_mut(row_index).expect("row"); + let FieldValue::Record(fields) = row else { + panic!("row not record"); + }; + fields.push(( + unknown_label.to_string(), + FieldValue::Literal(LiteralBits::Bool(false)), + )); + } + + #[test] + fn duplicate_field_on_row_surfaces_typed_error() { + let mut dag = generated_full_bootstrap_dag(); + duplicate_field_on_row( + &mut dag, + "rust_method_template_contracts", + 0, + "runtime_template", + ); + let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); + match result { + Err(MethodTemplateProjectionError::RowDuplicateField { + record, + field, + first_field_index, + duplicate_field_index, + .. + }) => { + assert_eq!(record, "MethodTemplateContract"); + assert_eq!(field, "runtime_template"); + assert_ne!(first_field_index, duplicate_field_index); + } + other => panic!("expected RowDuplicateField, got {other:?}"), + } + } + + #[test] + fn unknown_field_on_row_surfaces_typed_error() { + let mut dag = generated_full_bootstrap_dag(); + append_unknown_field_on_row( + &mut dag, + "rust_method_template_contracts", + 0, + "renamed_field", + ); + let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); + match result { + Err(MethodTemplateProjectionError::RowUnknownField { record, field, .. }) => { + assert_eq!(record, "MethodTemplateContract"); + assert_eq!(field, "renamed_field"); + } + other => panic!("expected RowUnknownField, got {other:?}"), + } + } + + #[test] + fn reference_row_requires_method_template_contract_instance() { + let mut dag = generated_full_bootstrap_dag(); + let non_contract_structural = dag + .declaration_by_name("count_method") + .expect("count_method MethodDeclaration") + .id; + replace_row_with_reference( + &mut dag, + "rust_method_template_contracts", + 0, + non_contract_structural, + ); + + let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); + match result { + Err(MethodTemplateProjectionError::RowReferenceNotMethodTemplateContract { + list, + row_index, + decl_id, + }) => { + assert_eq!(list, "rust_method_template_contracts"); + assert_eq!(row_index, 0); + assert_eq!(decl_id, non_contract_structural); + } + other => panic!( + "expected RowReferenceNotMethodTemplateContract for structural lookalike row, got {other:?}" + ), + } + } + + /// Inject a duplicate or unknown field into the inner `MethodRef` + /// record at `dag_method` for the row at `row_index`. + fn mutate_method_ref( + dag: &mut Dag, + list_name: &str, + row_index: usize, + kind: MethodRefMutation<'_>, + ) { + let decl_id = dag.declaration_by_name(list_name).expect("list").id; + let decl = dag.declaration_mut(decl_id); + let body = decl.value_body.as_mut().expect("value body"); + let ValueBody::List(rows) = body else { + panic!("not a list"); + }; + let row = rows.get_mut(row_index).expect("row"); + let FieldValue::Record(fields) = row else { + panic!("row not record"); + }; + let (_, dag_method_value) = fields + .iter_mut() + .find(|(label, _)| label == "dag_method") + .expect("dag_method field"); + let FieldValue::Record(method_ref_fields) = dag_method_value else { + panic!("dag_method not record"); + }; + match kind { + MethodRefMutation::Duplicate => { + let (label, value) = method_ref_fields + .iter() + .find(|(label, _)| label == "decl") + .map(|(label, value)| (label.clone(), value.clone())) + .expect("decl field"); + method_ref_fields.push((label, value)); + } + MethodRefMutation::AppendUnknown(label) => { + method_ref_fields.push(( + label.to_string(), + FieldValue::Literal(LiteralBits::Bool(false)), + )); + } + } + } + + /// 🟢 TERMINAL at the test-local `MethodRef` mutation scope. Names the + /// two ways an in-memory `Dag` row can break the closed `MethodRef` + /// schema: duplicate the existing `decl` field, or append a field with + /// an unknown label. Both modes drive the closed-schema check in + /// `closed_record_lookup`. Test-only; not part of the projection's + /// public surface. + enum MethodRefMutation<'a> { + Duplicate, + AppendUnknown(&'a str), + } + + #[test] + fn method_ref_decl_alias_without_value_body_surfaces_typed_error() { + // `src/v3/std/methods.dag` requires a *data binding* + // (`data _method: MethodDeclaration = { name: "..." }`), + // not just any declaration whose connective resolves to + // `MethodDeclaration`. A type alias like + // `type Foo = MethodDeclaration` lowers to an `Instantiation` + // connective with **no `value_body`**; without the value-body + // guard, that shape would pass the connective check alone. + // + // Simulate the alias-shape by stripping `value_body` from a real + // `MethodDeclaration` data binding (`count_method`) and asserting + // the typed error fires for both the per-row check + // (`MethodRefDeclNotMethodDeclaration`) and the lookup helper + // (`LookupKeyNotMethodDeclaration`). + let mut dag = generated_full_bootstrap_dag(); + let count_method_id = dag + .declaration_by_name("count_method") + .expect("count_method MethodDeclaration in bootstrap") + .id; + dag.declaration_mut(count_method_id).value_body = None; + + // Per-row path: row 0 of `rust_method_template_contracts` is the + // `count_method` row, so projecting it should now surface + // `MethodRefDeclNotMethodDeclaration` with `ValueBodyMissing` + // reason instead of returning a row. + let row_result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); + match row_result { + Err(MethodTemplateProjectionError::MethodRefDeclNotMethodDeclaration { + decl_id, + reason, + .. + }) => { + assert_eq!(decl_id, count_method_id); + assert_eq!(reason, MethodDeclarationBindingViolation::ValueBodyMissing); + } + other => panic!( + "expected MethodRefDeclNotMethodDeclaration for alias-shaped target, got {other:?}" + ), + } + + // Lookup-helper path: same key, same shape; the helper validates + // at entry and surfaces `LookupKeyNotMethodDeclaration` with the + // same reason. + let helper_result = + method_template_contract_row(&dag, MethodTemplateTarget::Rust, count_method_id); + match helper_result { + Err(MethodTemplateProjectionError::LookupKeyNotMethodDeclaration { + decl_id, + reason, + }) => { + assert_eq!(decl_id, count_method_id); + assert_eq!(reason, MethodDeclarationBindingViolation::ValueBodyMissing); + } + other => { + panic!("expected LookupKeyNotMethodDeclaration for alias-shaped key, got {other:?}") + } + } + } + + #[test] + fn method_ref_decl_not_method_declaration_surfaces_typed_error() { + // `src/v3/std/methods.dag` enforces fail-closed at the projection + // boundary that `MethodRef.decl` instantiates `MethodDeclaration`, + // because today's substrate grammar can't express + // `DeclarationRef`. Mutate the first row's + // `MethodRef.decl` to point at a declaration that is not a + // `MethodDeclaration` instance (`MethodTemplateContract` itself + // is a type declaration, not a `MethodDeclaration` data binding) + // and assert the typed error. + let mut dag = generated_full_bootstrap_dag(); + let non_method_id = dag + .declaration_by_name("MethodTemplateContract") + .expect("MethodTemplateContract type") + .id; + + let list_decl_id = dag + .declaration_by_name("rust_method_template_contracts") + .expect("list") + .id; + let decl = dag.declaration_mut(list_decl_id); + let body = decl.value_body.as_mut().expect("value body"); + let ValueBody::List(rows) = body else { + panic!("not a list"); + }; + let row = rows.get_mut(0).expect("row 0"); + let FieldValue::Record(fields) = row else { + panic!("row not record"); + }; + let dag_method_value = fields + .iter_mut() + .find(|(label, _)| label == "dag_method") + .map(|(_, value)| value) + .expect("dag_method field"); + let FieldValue::Record(method_ref_fields) = dag_method_value else { + panic!("dag_method not record"); + }; + let decl_field_value = method_ref_fields + .iter_mut() + .find(|(label, _)| label == "decl") + .map(|(_, value)| value) + .expect("decl field"); + *decl_field_value = FieldValue::Reference(non_method_id); + + let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); + match result { + Err(MethodTemplateProjectionError::MethodRefDeclNotMethodDeclaration { + list, + row_index, + decl_id, + reason, + }) => { + assert_eq!(list, "rust_method_template_contracts"); + assert_eq!(row_index, 0); + assert_eq!(decl_id, non_method_id); + // `MethodTemplateContract` is a type declaration (Conj), + // not an Instantiation — connective sub-check fails first. + assert_eq!( + reason, + MethodDeclarationBindingViolation::ConnectiveNotInstantiation + ); + } + other => panic!( + "expected MethodRefDeclNotMethodDeclaration for non-MethodDeclaration target, got {other:?}" + ), + } + } + + #[test] + fn method_ref_decl_extra_field_surfaces_closed_schema_violation() { + // A MethodDeclaration data binding with an extra field (beyond the + // closed `[name]` schema) must surface + // `StructuralFieldsNotClosedNameOnly`. Mirrors Stratum A's + // `enforce_closed_record_schema` gate. + let mut dag = generated_full_bootstrap_dag(); + let count_method_id = dag + .declaration_by_name("count_method") + .expect("count_method") + .id; + let decl = dag.declaration_mut(count_method_id); + let body = decl.value_body.as_mut().expect("value body"); + let ValueBody::Structural { fields } = body else { + panic!("expected Structural body"); + }; + fields.push(( + "alias".to_string(), + FieldValue::Literal(LiteralBits::String("count_alias".to_string())), + )); + + let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); + match result { + Err(MethodTemplateProjectionError::MethodRefDeclNotMethodDeclaration { + reason: + MethodDeclarationBindingViolation::StructuralFieldsNotClosedNameOnly { + observed_labels, + }, + .. + }) => { + assert!(observed_labels.contains(&"name".to_string())); + assert!(observed_labels.contains(&"alias".to_string())); + } + other => { + panic!("expected StructuralFieldsNotClosedNameOnly for extra field, got {other:?}") + } + } + } + + #[test] + fn method_ref_decl_name_not_string_literal_surfaces_typed_error() { + // A MethodDeclaration data binding whose `name` value is not a + // string literal must surface + // `MethodDeclarationBindingViolation::NameNotStringLiteral`. + let mut dag = generated_full_bootstrap_dag(); + let count_method_id = dag + .declaration_by_name("count_method") + .expect("count_method") + .id; + let decl = dag.declaration_mut(count_method_id); + let body = decl.value_body.as_mut().expect("value body"); + let ValueBody::Structural { fields } = body else { + panic!("expected Structural body"); + }; + let (_, name_value) = fields + .iter_mut() + .find(|(label, _)| label == "name") + .expect("name field"); + // Replace with a Bool literal — wrong shape for the contract. + *name_value = FieldValue::Literal(LiteralBits::Bool(true)); + + let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); + match result { + Err(MethodTemplateProjectionError::MethodRefDeclNotMethodDeclaration { + reason: MethodDeclarationBindingViolation::NameNotStringLiteral, + .. + }) => {} + other => panic!("expected NameNotStringLiteral for non-string name, got {other:?}"), + } + } + + #[test] + fn duplicate_field_on_method_ref_surfaces_typed_error() { + let mut dag = generated_full_bootstrap_dag(); + mutate_method_ref( + &mut dag, + "rust_method_template_contracts", + 0, + MethodRefMutation::Duplicate, + ); + let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); + match result { + Err(MethodTemplateProjectionError::RowDuplicateField { record, field, .. }) => { + assert_eq!(record, "MethodRef"); + assert_eq!(field, "decl"); + } + other => panic!("expected RowDuplicateField for MethodRef, got {other:?}"), + } + } + + #[test] + fn unknown_field_on_method_ref_surfaces_typed_error() { + let mut dag = generated_full_bootstrap_dag(); + mutate_method_ref( + &mut dag, + "rust_method_template_contracts", + 0, + MethodRefMutation::AppendUnknown("nickname"), + ); + let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); + match result { + Err(MethodTemplateProjectionError::RowUnknownField { record, field, .. }) => { + assert_eq!(record, "MethodRef"); + assert_eq!(field, "nickname"); + } + other => panic!("expected RowUnknownField for MethodRef, got {other:?}"), + } + } + + #[test] + fn duplicate_row_surfaces_typed_error_at_canonical_row_list_api() { + // Per-target uniqueness by `dag_method` is the substrate-side claim + // `method_template_contract_per_target_dag_method_unique`. The + // canonical row-list API enforces this at the public boundary so + // every consumer (direct row-list consumer or `(target, dag_method)` + // lookup helper) observes the same fail-closed behavior. Gap 5 / + // leaf-emit consumers building their own keyed tables from the + // projection cannot silently observe two authorities for the same + // `(target, MethodRef)` pair. + let mut dag = generated_full_bootstrap_dag(); + duplicate_row(&mut dag, "rust_method_template_contracts", 0); + + // Canonical row-list API surfaces the duplicate. + let list_result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); + match list_result { + Err(MethodTemplateProjectionError::DuplicateMethodTemplateRow { + list, + first_row_index, + duplicate_row_index, + }) => { + assert_eq!(list, "rust_method_template_contracts"); + assert_ne!(first_row_index, duplicate_row_index); + } + other => panic!( + "expected DuplicateMethodTemplateRow at row-list API for duplicate (target, dag_method), got {other:?}" + ), + } + + // Lookup helper inherits the fail-closed shape via the row-list + // API; no separate uniqueness path on the helper. + let count_method_id = dag + .declaration_by_name("count_method") + .expect("count_method MethodDeclaration in bootstrap Dag") + .id; + let helper_result = + method_template_contract_row(&dag, MethodTemplateTarget::Rust, count_method_id); + assert!( + matches!( + helper_result, + Err(MethodTemplateProjectionError::DuplicateMethodTemplateRow { .. }) + ), + "lookup helper must inherit duplicate-row fail-closed via row-list API; got {helper_result:?}" + ); + } + + #[test] + fn malformed_placeholder_payload_surfaces_typed_error() { + // PlaceholderConvention is a nullary sum (`IndexedArgs | NamedArg`). + // A malformed bootstrap row carrying a non-empty placeholder payload + // must surface as `PlaceholderConventionPayloadNotEmpty`, not + // silently project to `IndexedArgs` / `NamedArg` with the payload + // discarded. Mirrors the substrate-side canonical-row check + // `placeholder_convention_canonical_rejects_payload_for_nullary_indexed_args` + // in `src/v3/grounding_tests/src/stratum_a.rs`. + let mut dag = generated_full_bootstrap_dag(); + inject_extra_placeholder_payload(&mut dag, "rust_method_template_contracts", 0); + + let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); + match result { + Err(MethodTemplateProjectionError::PlaceholderConventionPayloadNotEmpty { + list, + row_index, + payload_len, + .. + }) => { + assert_eq!(list, "rust_method_template_contracts"); + assert_eq!(row_index, 0); + assert!(payload_len >= 1, "expected non-empty payload to be reported"); + } + other => panic!( + "expected PlaceholderConventionPayloadNotEmpty for malformed placeholder, got {other:?}" + ), + } + } +} From 9cdc1993957a74da15292a5ad0323484d3379c6d Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 13 May 2026 20:20:56 -0400 Subject: [PATCH 02/10] =?UTF-8?q?WIP:=20PB-0=20cycle-4=20NON=5FTEST=20reti?= =?UTF-8?q?rement=20(msg=5F5d7f3491):=207=20paths=20=E2=80=94=20complexity?= =?UTF-8?q?=5Fla?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dsl/gunbc/compiler.dag | 3 - src/v3/compiler/src/complexity_lattice.rs | 109 -- src/v3/compiler/src/cost_basis_declaration.rs | 165 -- src/v3/compiler/src/emit.rs | 4 +- .../emit/collection_ops_method_contract.rs | 183 --- src/v3/compiler/src/emit_rust_bin_shim.rs | 92 -- .../compiler/src/enforced_lens_application.rs | 1258 -------------- src/v3/compiler/src/int_literal_ranges.rs | 1016 ------------ src/v3/compiler/src/lib.rs | 31 +- .../src/pb_method_template_projection.rs | 1447 ----------------- .../tests/integration/sg0_census_test.rs | 27 +- 11 files changed, 45 insertions(+), 4290 deletions(-) delete mode 100644 src/v3/compiler/src/complexity_lattice.rs delete mode 100644 src/v3/compiler/src/cost_basis_declaration.rs delete mode 100644 src/v3/compiler/src/emit/collection_ops_method_contract.rs delete mode 100644 src/v3/compiler/src/emit_rust_bin_shim.rs delete mode 100644 src/v3/compiler/src/enforced_lens_application.rs delete mode 100644 src/v3/compiler/src/int_literal_ranges.rs delete mode 100644 src/v3/compiler/src/pb_method_template_projection.rs diff --git a/dsl/gunbc/compiler.dag b/dsl/gunbc/compiler.dag index 43410c76848..fb90e9fb17f 100644 --- a/dsl/gunbc/compiler.dag +++ b/dsl/gunbc/compiler.dag @@ -74,7 +74,6 @@ data stage0: GeneratedCrate = { source_dir: "src/v3/compiler/src", hand_maintained_src: [ "bootstrap.rs", - "cost_basis_declaration.rs", "dag.rs", "diagnostics.rs", "dimension.rs", @@ -82,8 +81,6 @@ data stage0: GeneratedCrate = { "emit_go.rs", "emit_python.rs", "emit_rust.rs", - // PB-1 Item 5: hand-authored `main.rs` shell formatter (not emitted into src/). - "emit_rust_bin_shim.rs", "python_target.rs", "rust_target.rs", "infer.rs", diff --git a/src/v3/compiler/src/complexity_lattice.rs b/src/v3/compiler/src/complexity_lattice.rs deleted file mode 100644 index 090827fd13c..00000000000 --- a/src/v3/compiler/src/complexity_lattice.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! **`complexity_enforcement_budget_dominates`** — executable **T-LAS enforcement** -//! ordering on `AsymptoticClass` for `complexity_enforcement_violates` / -//! `EnforcedApplication` (gate #92). It matches `v3.std.algebra::asymptotic_dominates` -//! on every arm including `ClassPolynomial×ClassPolynomial` (Peano `degree` compare). -//! -//! **`asymptotic_dominates`** is a backward-compatible alias for tests and older -//! call sites; new enforcement code should call [`complexity_enforcement_budget_dominates`]. -//! -//! Stay aligned with `complexity.dag` + `complexity_enforcement_violates` comments. - -use crate::dag::{positive_descent_count, AsymptoticClass}; - -pub fn complexity_enforcement_budget_dominates(a: &AsymptoticClass, b: &AsymptoticClass) -> bool { - use AsymptoticClass::*; - match a { - ClassUnknown => true, - ClassExponential => match b { - ClassUnknown => false, - ClassExponential - | ClassPolynomial { .. } // Any `ClassPolynomial` tier is below exponential. - | ClassQuadratic - | ClassLinearithmic - | ClassLinear - | ClassLog - | ClassConstant => true, - }, - ClassPolynomial { degree: da } => match b { - ClassUnknown | ClassExponential => false, - ClassPolynomial { degree: db } => { - positive_descent_count(da) >= positive_descent_count(db) - } - ClassQuadratic | ClassLinearithmic | ClassLinear | ClassLog | ClassConstant => true, - }, - ClassQuadratic => match b { - ClassConstant | ClassLog | ClassLinear | ClassLinearithmic | ClassQuadratic => true, - ClassPolynomial { .. } | ClassExponential | ClassUnknown => false, - }, - ClassLinearithmic => match b { - ClassConstant | ClassLog | ClassLinear | ClassLinearithmic => true, - ClassQuadratic | ClassPolynomial { .. } | ClassExponential | ClassUnknown => false, - }, - ClassLinear => match b { - ClassConstant | ClassLog | ClassLinear => true, - ClassLinearithmic - | ClassQuadratic - | ClassPolynomial { .. } - | ClassExponential - | ClassUnknown => false, - }, - ClassLog => match b { - ClassConstant | ClassLog => true, - ClassLinear - | ClassLinearithmic - | ClassQuadratic - | ClassPolynomial { .. } - | ClassExponential - | ClassUnknown => false, - }, - ClassConstant => match b { - ClassConstant => true, - ClassLog - | ClassLinear - | ClassLinearithmic - | ClassQuadratic - | ClassPolynomial { .. } - | ClassExponential - | ClassUnknown => false, - }, - } -} - -/// Alias of [`complexity_enforcement_budget_dominates`]. Prefer the named helper for -/// new call sites so enforcement-grade poly refinement is not confused with substrate -/// `algebra.dag::asymptotic_dominates` at the documentation layer. -#[inline] -pub fn asymptotic_dominates(a: &AsymptoticClass, b: &AsymptoticClass) -> bool { - complexity_enforcement_budget_dominates(a, b) -} - -#[cfg(test)] -mod asymptotic_dominates_tests { - use super::{asymptotic_dominates, complexity_enforcement_budget_dominates}; - use crate::dag::{positive_amount_from_i64, AsymptoticClass}; - - fn poly(k: i64) -> AsymptoticClass { - AsymptoticClass::ClassPolynomial { - degree: positive_amount_from_i64(k).expect("test degree in range"), - } - } - - #[test] - fn polynomial_degree_orders_within_class_polynomial() { - let p5 = poly(5); - let p3 = poly(3); - assert!(complexity_enforcement_budget_dominates(&p5, &p3)); - assert!(!complexity_enforcement_budget_dominates(&p3, &p5)); - assert!(complexity_enforcement_budget_dominates(&p3, &p3)); - } - - #[test] - fn alias_matches_budget_dominates() { - let p3 = poly(3); - let p5 = poly(5); - assert_eq!( - asymptotic_dominates(&p5, &p3), - complexity_enforcement_budget_dominates(&p5, &p3) - ); - } -} diff --git a/src/v3/compiler/src/cost_basis_declaration.rs b/src/v3/compiler/src/cost_basis_declaration.rs deleted file mode 100644 index bf59e3a404b..00000000000 --- a/src/v3/compiler/src/cost_basis_declaration.rs +++ /dev/null @@ -1,165 +0,0 @@ -//! Materialize [`CostBasisDeclaration`](crate::lens_cost_symbolic::CostBasisDeclaration) from -//! lowered [`Dag`](crate::dag::Dag) facts — interim **consumer boundary** for gate **#93** -//! (`crdt_cost_basis_demonstrated`) **substrate / symbolic-cost slice**; folding authored basis -//! rows into [`compute_symbolic_costs`](crate::lens_cost_symbolic::compute_symbolic_costs) remains follow-on on the same gate. -//! -//! Persisted `.dag` `data` rows and folding declarations into [`compute_symbolic_costs`](crate::lens_cost_symbolic::compute_symbolic_costs) remain follow-on; embedders MUST NOT -//! hand-assemble `CostBasisDeclaration` beside this helper when deriving the same evidence -//! from a compiled DAG (INVARIANTS.md P2). - -use std::fmt; - -use crate::dag::{ - ArrowBody, BindNode, Dag, DeclarationId, Lookup, PortId, SectionRef, SizeVariable, - SymbolicCost, TypeConnective, -}; -use crate::lens_cost_symbolic::{compute_symbolic_costs, CostBasisDeclaration, CostBasisKind}; - -/// [`try_build_per_write_log_cost_basis_declaration`] stops on missing DAG facts instead of -/// panicking (fail-closed consumer boundary; codex gate on PR #2299). -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum CostBasisDeclarationBuildError { - DeclarationNotFound { - name: String, - }, - MergeStepMissingParams { - merge_step_declaration_name: String, - }, - /// Declaration must be a function (`TypeConnective::Arrow`) so the body bind is - /// reachable via [`ArrowBody::UserDefined`] (single authority per P2). - WorkflowDeclarationNotArrow { - name: String, - }, - /// Arrow declaration body is not yet lowered to `UserDefined(BindNodeId)` — cannot read - /// the authoritative body [`BindNode`] for parameters / span / structural parity. - WorkflowBodyNotUserDefinedBind { - name: String, - }, - /// `compute_symbolic_costs` has no `Hit(LogCost)` row keyed to the merge step’s first - /// parameter port — cannot fabricate a per-write log basis without lens evidence of a - /// divide on that port (P2 / fail-closed). - MergeStepLacksLogCostWitness { - merge_step_declaration_name: String, - }, -} - -impl fmt::Display for CostBasisDeclarationBuildError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - CostBasisDeclarationBuildError::DeclarationNotFound { name } => { - write!(f, "declaration `{name}` not found") - } - CostBasisDeclarationBuildError::MergeStepMissingParams { - merge_step_declaration_name, - } => write!( - f, - "declaration `{merge_step_declaration_name}` should declare at least one parameter on its body bind" - ), - CostBasisDeclarationBuildError::WorkflowDeclarationNotArrow { name } => write!( - f, - "declaration `{name}` must have Arrow connective (callable)" - ), - CostBasisDeclarationBuildError::WorkflowBodyNotUserDefinedBind { name } => write!( - f, - "declaration `{name}` body is not ArrowBody::UserDefined(BindNodeId); cannot resolve authoritative bind node" - ), - CostBasisDeclarationBuildError::MergeStepLacksLogCostWitness { - merge_step_declaration_name, - } => write!( - f, - "declaration `{merge_step_declaration_name}` first param port has no LogCost witness in `compute_symbolic_costs` (expected divide-on-dividend lowering)" - ), - } - } -} - -impl std::error::Error for CostBasisDeclarationBuildError {} - -fn user_defined_arrow_body_bind<'a>( - dag: &'a Dag, - declaration_name: &str, -) -> Result<(&'a BindNode, DeclarationId), CostBasisDeclarationBuildError> { - let decl = dag.declaration_by_name(declaration_name).ok_or_else(|| { - CostBasisDeclarationBuildError::DeclarationNotFound { - name: declaration_name.to_string(), - } - })?; - let subject = decl.id; - let bind = match &decl.connective { - TypeConnective::Arrow { body, .. } => match body { - ArrowBody::UserDefined(bind_id) => bind_id.bind_opt(dag).ok_or_else(|| { - CostBasisDeclarationBuildError::WorkflowBodyNotUserDefinedBind { - name: declaration_name.to_string(), - } - })?, - _ => { - return Err( - CostBasisDeclarationBuildError::WorkflowBodyNotUserDefinedBind { - name: declaration_name.to_string(), - }, - ); - } - }, - _ => { - return Err( - CostBasisDeclarationBuildError::WorkflowDeclarationNotArrow { - name: declaration_name.to_string(), - }, - ); - } - }; - Ok((bind, subject)) -} - -fn symbolic_table_includes_log_on_port(dag: &Dag, port: PortId) -> bool { - compute_symbolic_costs(dag).iter().any(|e| { - matches!( - &e.cost, - Lookup::Hit(SymbolicCost::LogCost { _0: sv }) if sv.source_port == port - ) - }) -} - -/// **`LogCost` witness:** returns [`MergeStepLacksLogCostWitness`] unless -/// [`compute_symbolic_costs`] already **`Hit`s `LogCost(merge_param_port)`** (divide-on-dividend -/// lowering); does not fabricate basis without lens table evidence. -/// **Subject** is `SectionRef::DeclarationScope { declaration: … }` wrapping the workflow -/// [`DeclarationId`] (T-LAS / audit structural authority — expression-scoped bases use -/// `NodeScope` when authors need it). -/// `merge_step_declaration_name` is the **`fn` declaration id** (same string the author uses for -/// the merge helper, e.g. `crdt_merge_step`); its body [`BindNode`] is read only via -/// [`TypeConnective::Arrow`] → [`ArrowBody::UserDefined`] like the workflow (P2 single authority -/// for both callables). -pub fn try_build_per_write_log_cost_basis_declaration( - dag: &Dag, - workflow_declaration_name: &str, - merge_step_declaration_name: &str, -) -> Result { - let (workflow_bind, subject) = user_defined_arrow_body_bind(dag, workflow_declaration_name)?; - let (merge_bind, _) = user_defined_arrow_body_bind(dag, merge_step_declaration_name)?; - let merge_replicas_port = *merge_bind.params.first().ok_or_else(|| { - CostBasisDeclarationBuildError::MergeStepMissingParams { - merge_step_declaration_name: merge_step_declaration_name.to_string(), - } - })?; - if !symbolic_table_includes_log_on_port(dag, merge_replicas_port) { - return Err( - CostBasisDeclarationBuildError::MergeStepLacksLogCostWitness { - merge_step_declaration_name: merge_step_declaration_name.to_string(), - }, - ); - } - Ok(CostBasisDeclaration { - subject: SectionRef::DeclarationScope { - declaration: subject, - }, - kind: CostBasisKind::PerWrite, - cost: SymbolicCost::LogCost { - _0: SizeVariable { - source_port: merge_replicas_port, - display_name: None, - }, - }, - span: workflow_bind.span.clone(), - }) -} diff --git a/src/v3/compiler/src/emit.rs b/src/v3/compiler/src/emit.rs index d1420cf68e2..d4178bc1298 100644 --- a/src/v3/compiler/src/emit.rs +++ b/src/v3/compiler/src/emit.rs @@ -15,7 +15,9 @@ //! target-private carriers stay inside their target module and no //! cross-target code should read them. -pub(crate) mod collection_ops_method_contract; +pub(crate) mod collection_ops_method_contract { + include!("emit/collection_ops_method_contract_generated.rs"); +} pub(crate) mod python_target; pub(crate) mod rust_target; diff --git a/src/v3/compiler/src/emit/collection_ops_method_contract.rs b/src/v3/compiler/src/emit/collection_ops_method_contract.rs deleted file mode 100644 index 78cadcb422f..00000000000 --- a/src/v3/compiler/src/emit/collection_ops_method_contract.rs +++ /dev/null @@ -1,183 +0,0 @@ -//! `CollectionOps.*_contract` fields cross `DeclarationRef` boundaries. Emit -//! consumers must fail closed on the **algebra identity** (P1): each referenced -//! declaration must be a `MethodTemplateContract` data binding whose -//! `dag_method.decl` resolves to the expected registry `*_method` row. - -use super::method_emit_template_variant_label; -use crate::dag::{Dag, DeclarationId, FieldValue, LiteralBits, TypeConnective, ValueBody}; - -/// 🟢 TERMINAL at the CollectionOps contract-consumer projection boundary. -/// This mirrors the substrate `MethodEmitTemplate` coproduct exactly so Rust -/// CollectionOps can choose the HO inline rendering without flattening or -/// copying template text into a third authority. -#[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) enum MethodTemplateContractEmitTemplate { - SingleTemplate(String), - HigherOrderTemplates { - inline_template: String, - fn_ref_template: String, - }, -} - -/// Fail closed unless `contract_decl` is a `MethodTemplateContract` instance -/// whose `dag_method.decl` resolves to `expected_method_decl`. -pub(crate) fn require_method_template_contract_dag_method( - dag: &Dag, - contract_decl: DeclarationId, - collection_ops_field: &'static str, - expected_method_decl: DeclarationId, -) -> Result<(), &'static str> { - let fields = method_template_contract_fields(dag, contract_decl)?; - require_method_template_contract_fields_dag_method( - fields, - collection_ops_field, - expected_method_decl, - ) -} - -pub(crate) fn method_template_contract_decl_emit_template( - dag: &Dag, - contract_decl: DeclarationId, - collection_ops_field: &'static str, - expected_method_decl: DeclarationId, -) -> Result { - let fields = method_template_contract_fields(dag, contract_decl)?; - require_method_template_contract_fields_dag_method( - fields, - collection_ops_field, - expected_method_decl, - )?; - method_template_contract_fields_emit_template(dag, fields) -} - -fn method_template_contract_fields( - dag: &Dag, - contract_decl: DeclarationId, -) -> Result<&[(String, FieldValue)], &'static str> { - let mtc_type_id = dag - .method_template_contract_decl() - .ok_or("internal: MethodTemplateContract type missing from dag")?; - - let decl = dag.declaration(contract_decl); - let template = match &decl.connective { - TypeConnective::Instantiation { template, .. } => *template, - _ => { - return Err( - "CollectionOps contract must reference a MethodTemplateContract data binding", - ); - } - }; - if template != mtc_type_id { - return Err("CollectionOps contract must instantiate MethodTemplateContract"); - } - - let Some(body) = decl.value_body.as_ref() else { - return Err( - "CollectionOps contract referenced declaration must carry a structural value body", - ); - }; - let fields = match body { - ValueBody::Structural { fields } => fields, - _ => { - return Err( - "CollectionOps contract referenced declaration must be structural MethodTemplateContract data", - ); - } - }; - Ok(fields) -} - -fn require_method_template_contract_fields_dag_method( - fields: &[(String, FieldValue)], - collection_ops_field: &'static str, - expected_method_decl: DeclarationId, -) -> Result<(), &'static str> { - let method_id = method_template_contract_fields_dag_method(fields)?; - if method_id != expected_method_decl { - return Err(match collection_ops_field { - "fold_contract" => "CollectionOps.fold_contract must target fold_method", - "concat_contract" => "CollectionOps.concat_contract must target concat_method", - "length_contract" => "CollectionOps.length_contract must target length_method", - "filter_contract" => "CollectionOps.filter_contract must target filter_method", - "flat_map_contract" => "CollectionOps.flat_map_contract must target flat_map_method", - "any_contract" => "CollectionOps.any_contract must target any_method", - "all_contract" => "CollectionOps.all_contract must target all_method", - "map_contract" => "CollectionOps.map_contract must target map_method", - "is_empty_contract" => { - "CollectionOps.is_empty_contract must target is_empty_method (FreeMonoid.is_empty -> Bool)" - } - _ => "CollectionOps contract dag_method does not match expected registry method", - }); - } - - Ok(()) -} - -fn method_template_contract_fields_dag_method( - fields: &[(String, FieldValue)], -) -> Result { - let dag_method = fields - .iter() - .find(|(l, _)| l == "dag_method") - .map(|(_, v)| v) - .ok_or("MethodTemplateContract missing dag_method field")?; - - let FieldValue::Record(method_ref_fields) = dag_method else { - return Err("MethodTemplateContract.dag_method must be a MethodRef record"); - }; - - let method_id = match method_ref_fields.as_slice() { - [(label, FieldValue::Reference(id))] if label == "decl" => *id, - _ => { - return Err( - "MethodTemplateContract.dag_method must be exactly `{ decl: DeclarationRef }` (MethodRef)", - ); - } - }; - Ok(method_id) -} - -fn method_template_contract_fields_emit_template( - dag: &Dag, - fields: &[(String, FieldValue)], -) -> Result { - let emit_value = fields - .iter() - .find(|(label, _)| label == "emit_template") - .map(|(_, v)| v) - .ok_or("MethodTemplateContract missing emit_template field")?; - let FieldValue::Variant { - constructor, - payload, - } = emit_value - else { - return Err("MethodTemplateContract.emit_template must be a sum variant"); - }; - let ctor_name = method_emit_template_variant_label(dag, *constructor).ok_or( - "MethodTemplateContract.emit_template variant not found under MethodEmitTemplate disj", - )?; - match ctor_name { - "SingleTemplate" => { - let [FieldValue::Literal(LiteralBits::String(template))] = payload.as_slice() else { - return Err("SingleTemplate must carry exactly one string template payload"); - }; - Ok(MethodTemplateContractEmitTemplate::SingleTemplate( - template.replace("%Q", "\""), - )) - } - "HigherOrderTemplates" => { - let [FieldValue::Literal(LiteralBits::String(inline_template)), FieldValue::Literal(LiteralBits::String(fn_ref_template))] = - payload.as_slice() - else { - return Err( - "HigherOrderTemplates must carry inline_template and fn_ref_template strings", - ); - }; - Ok(MethodTemplateContractEmitTemplate::HigherOrderTemplates { - inline_template: inline_template.replace("%Q", "\""), - fn_ref_template: fn_ref_template.replace("%Q", "\""), - }) - } - _ => Err("MethodTemplateContract.emit_template uses an unknown MethodEmitTemplate variant"), - } -} diff --git a/src/v3/compiler/src/emit_rust_bin_shim.rs b/src/v3/compiler/src/emit_rust_bin_shim.rs deleted file mode 100644 index 3168e8fe4a3..00000000000 --- a/src/v3/compiler/src/emit_rust_bin_shim.rs +++ /dev/null @@ -1,92 +0,0 @@ -//! PB-1 — Rust `main.rs` shell for generated `[[bin]]` shims (Item 5). -//! -//! **SG-0 / bounded-seed receipt:** hand-authored path is enumerated in -//! `EXPECTED_HAND_AUTHORED_NON_TEST` in `sg0_census_test.rs` (not -//! `GENERATED_FILES` / `build.rs` output). Narrow host helper for §4.2 shell -//! text only; see `docs/invariants/bounded-substrate-seed.md` (no new -//! substrate primitive — mirrors existing `std.process.ProcessExit`). -//! -//! Authority: `docs/design-pb-runtime-interpreter.md` §4.2 (`main` → -//! `ProcessExit` → `ExitCode`). This module owns **parameterized Rust text** -//! only: it does not resolve `BinShim.entry` `DeclarationRef` targets (row-#1 -//! / host bridge gap — STOP there) and does not wire `cargo`/build.rs. - -/// Formats the `main.rs` wrapper for a `.dag`-authored bin shim. -/// -/// `source_dag_path` is emitted verbatim in the `AUTO-GENERATED` header (one -/// line). `description` is split into `//` comment lines. `entry_fn_qname` is -/// the Rust-qualified symbol for the `.dag` entry function returning -/// [`crate::process_exit::ProcessExit`]. -pub fn format_bin_shim_main_rs( - source_dag_path: &str, - description: &str, - entry_fn_qname: &str, -) -> String { - let mut out = String::new(); - out.push_str("// AUTO-GENERATED from "); - out.push_str(source_dag_path); - out.push_str(" — DO NOT EDIT.\n//\n"); - if description.is_empty() { - out.push_str("//\n"); - } else { - for line in description.lines() { - out.push_str("// "); - out.push_str(line); - out.push('\n'); - } - } - out.push_str( - r#" -use std::io::Write; -use std::process::ExitCode; - -use v3_compiler::dag::Dag; -use v3_compiler::process_exit::ProcessExit; - -fn main() -> ExitCode { - match "#, - ); - out.push_str(entry_fn_qname); - out.push_str( - r#"(&Dag::new()) { - ProcessExit::ExitSuccess => ExitCode::SUCCESS, - ProcessExit::ExitFailure { code, reason } => { - let _ = writeln!(std::io::stderr(), "{reason}"); - // Fail-closed: `ExitFailure` must not map to a successful host exit. - // Non-positive or out-of-range codes remap into 1..=255 (see dsl/std/process.dag). - ExitCode::from((code.max(1).min(255)) as u8) - } - } -} -"#, - ); - out -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn format_bin_shim_main_rs_includes_header_entry_and_process_exit() { - let out = format_bin_shim_main_rs( - "dsl/std/runtime/bin_shims/regen_lens.dag", - "Unified lens-regen driver.", - "std::runtime::bin_shims::regen_lens::regen_lens_main", - ); - assert!(out.contains( - "// AUTO-GENERATED from dsl/std/runtime/bin_shims/regen_lens.dag — DO NOT EDIT." - )); - assert!(out.contains("// Unified lens-regen driver.")); - assert!(out.contains("use v3_compiler::process_exit::ProcessExit;")); - assert!(out.contains("code.max(1).min(255)")); - assert!(!out.contains("code.clamp(0, 255)")); - assert!(out.contains("std::runtime::bin_shims::regen_lens::regen_lens_main(&Dag::new())")); - } - - #[test] - fn format_bin_shim_main_rs_multiline_description() { - let out = format_bin_shim_main_rs("path/to/shim.dag", "line1\nline2", "m::f"); - assert!(out.contains("// line1\n// line2\n")); - } -} diff --git a/src/v3/compiler/src/enforced_lens_application.rs b/src/v3/compiler/src/enforced_lens_application.rs deleted file mode 100644 index d630542c160..00000000000 --- a/src/v3/compiler/src/enforced_lens_application.rs +++ /dev/null @@ -1,1258 +0,0 @@ -//! Enforced lens applications (`EnforcedApplication` from `lens_application.dag`). -//! -//! Gate #91 (`enforce_violation_routing_landed`): budget-violation diagnostics are -//! routed through the authored `diagnostic_severity` field — today the single -//! substrate variant `DiagnosticSeverity::Error` maps to [`Diagnostic::ParseError`] -//! (compile-fail / fail-closed), per `lens_application.dag`, design §3, and -//! INVARIANTS C-8. -//! -//! Future extra `DiagnosticSeverity` constructors stay fail-closed at this consumer until -//! an intentional substrate + routing co-update assigns their `Diagnostic::*` shape. -//! -//! Gate #92 (`complexity_violation_compile_error_demonstrated`): when a program -//! authors `EnforcedApplication` referencing -//! `complexity_enforceable`, infer checks the named section using the **same** -//! `LensEnforcement` projection + violation relation as `complexity.dag` -//! (`complexity_enforcement_project` / `complexity_enforcement_violates`, surfaced -//! through `complexity_lens_generated`) and attaches a compile diagnostic on violation. -//! -//! Gate #58 (`apply_lens_self_application_demonstrated`): when a program authors -//! `EnforcedApplication` referencing `timing_enforceable` -//! (`v3.std.timing_lens`), infer reads the **lowered** `TimingMeasurement` from the -//! `DeclarationScope` subject's structural `measurement` field **only after** verifying the -//! subject row's **nominal** `data …: RowTy = …` type declares `measurement: TimingMeasurement` -//! on `RowTy` (API-level contract; avoids unrelated records that merely spell a `measurement` -//! label). The same usage ceiling as `timing_enforcement_project` / `timing_enforcement_violates` -//! in `timing_lens.dag` applies: non-`Observed` reports are enforced via a **variant-shaped** -//! fault path (never compared as ordinary wall-clock `Nat` against the fault sentinel), while -//! `Observed` wall-clock nanoseconds use the same strict `>` edge as gate #94 against -//! `TimingBudget.max`. - -use std::collections::HashMap; - -use crate::dag::{ - literal_decimal_i64, positive_descent_count, ArrowBody, AsymptoticClass, Behavior, Dag, - DeclarationId, FieldValue, LiteralBits, Lookup, PortId, PositiveDescentAmount, TypeConnective, - ValueBody, -}; -use crate::diagnostics::{Diagnostic, SourceSpan}; -use crate::lens_cost::{ - complexity_enforcement_project, complexity_enforcement_violates, complexity_of, -}; - -fn diagnostic_severity_substrate_disj(dag: &Dag) -> Option { - dag.declarations() - .iter() - .find(|d| { - d.name.as_deref() == Some("DiagnosticSeverity") - && d.span.file.ends_with("lens_application.dag") - }) - .map(|d| d.id) -} - -// Fail-closed when `lens_application.dag` does not expose the expected `DiagnosticSeverity` -// substrate row — factored so unit tests can pin the diagnostic without staging a full -// complexity-enforcement DAG (`Dag::new()` omits `complexity_enforceable`). -fn attach_missing_diagnostic_severity_substrate_diagnostic( - dag: &mut Dag, - enforced_template: DeclarationId, -) { - dag.attach_diagnostic(Diagnostic::ParseError { - message: "lens enforcement: could not resolve substrate `DiagnosticSeverity` from \ - `lens_application.dag` (modeled authority missing; fail-closed)" - .to_string(), - span: dag.declaration(enforced_template).span.clone(), - fixes: Vec::new(), - }); -} - -/// Reads `timing_enforcement_fault_sentinel_count` from the lowered `timing_lens.dag` declaration -/// body (nullary `fn` → bind → [`ValueNode`]), matching substrate authority instead of duplicating -/// the decimal literal in Rust. -fn timing_enforcement_fault_sentinel_ns_from_substrate(dag: &Dag) -> Option { - let decl = dag.declarations().iter().find(|d| { - d.name.as_deref() == Some("timing_enforcement_fault_sentinel_count") - && d.span.file.ends_with("timing_lens.dag") - })?; - let TypeConnective::Arrow { - body: ArrowBody::UserDefined(bind_id), - .. - } = &decl.connective - else { - return None; - }; - let bind = (*bind_id).bind_opt(dag)?; - let producer = dag.resolve_producer_opt(&bind.value)?; - let Behavior::Value(vn) = producer else { - return None; - }; - let LiteralBits::Int(s) = &vn.data else { - return None; - }; - s.parse().ok() -} - -fn timing_measurement_sum_type_decl_id(dag: &Dag) -> Option { - dag.declarations() - .iter() - .find(|d| { - d.name.as_deref() == Some("TimingMeasurement") - && d.span.file.ends_with("timing_lens.dag") - }) - .map(|d| d.id) -} - -fn peel_type_declaration_head(dag: &Dag, mut id: DeclarationId) -> DeclarationId { - const MAX: usize = 64; - for _ in 0..MAX { - match &dag.declaration(id).connective { - TypeConnective::Instantiation { - template, - arguments, - } if arguments.is_empty() => { - id = *template; - } - TypeConnective::Atom(ap) => { - let Some(next) = ap.resolved_id() else { - break; - }; - id = next; - } - _ => break, - } - } - id -} - -/// Returns the nominal `RowTy` declaration id for a lowered `data row: RowTy = …` value row. -fn data_row_nominal_type_decl_id(dag: &Dag, value_decl_id: DeclarationId) -> Option { - match &dag.declaration(value_decl_id).connective { - TypeConnective::Instantiation { - template, - arguments, - } if arguments.is_empty() => Some(*template), - _ => None, - } -} - -/// True when `RowTy` (after alias peel) is a conj that declares `measurement` with type peeling -/// to the substrate `TimingMeasurement` sum at `tm_disj`. -fn timing_section_row_type_declares_measurement_tm( - dag: &Dag, - section_value_decl_id: DeclarationId, - tm_disj: DeclarationId, -) -> bool { - let Some(row_ty) = data_row_nominal_type_decl_id(dag, section_value_decl_id) else { - return false; - }; - let ty_head = peel_type_declaration_head(dag, row_ty); - let TypeConnective::Conj { children } = &dag.declaration(ty_head).connective else { - return false; - }; - let tm_head = peel_type_declaration_head(dag, tm_disj); - children - .iter() - .any(|f| f.label == "measurement" && peel_type_declaration_head(dag, f.ty) == tm_head) -} - -/// Lowered `TimingMeasurement` usage for timing `EnforcedApplication` enforcement. -/// -/// Separates non-evidence variants from `Observed` wall-clock nanoseconds so a legitimate -/// `Observed` duration equal to the substrate fault sentinel `Nat` cannot be conflated with -/// `Unobserved` / `Ambiguous` / `Stale` (INVARIANTS P2 / modeling-discipline Practice 2). -/// -/// Practice 4 (`docs/modeling-discipline.md`, coproduct checkpoint): **GREEN (terminal)** — mirrors -/// `timing_enforcement_project` / `timing_enforcement_violates` in `timing_lens.dag` on the lowered -/// `TimingMeasurement` witness (substrate `LensEnforcement.violates` now takes `TimingMeasurement` -/// × `TimingBudget` per `lens_application.dag`; gate #58 / codex 10994). -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum TimingMeasurementEnforcementUsage { - NonObservedFault, - ObservedWallNs(u64), -} - -/// Interprets a lowered `TimingMeasurement` value the same way `timing_enforcement_project` does -/// on the substrate (`timing_lens.dag`): `Observed` → wall-clock nanoseconds; other report variants -/// classify as [`TimingMeasurementEnforcementUsage::NonObservedFault`] (not as a raw `Nat`). -fn timing_measurement_enforcement_usage( - dag: &Dag, - tm_disj: DeclarationId, - value: &FieldValue, -) -> Option { - let TypeConnective::Disj { variants } = &dag.declaration(tm_disj).connective else { - return None; - }; - let FieldValue::Variant { - constructor, - payload, - } = value - else { - return None; - }; - let variant = variants.iter().find(|v| v.ty == *constructor)?; - match variant.label.as_str() { - "Observed" => observed_timing_payload_max_ns(payload) - .map(TimingMeasurementEnforcementUsage::ObservedWallNs), - "Unobserved" | "Ambiguous" | "Stale" => { - Some(TimingMeasurementEnforcementUsage::NonObservedFault) - } - _ => None, - } -} - -/// Same reflexive edge as `timing_enforcement_violates` in `timing_lens.dag` on the lowered witness, -/// using the same `>` edge as substrate `Observed` enforcement (no `TimingBudget` sentinel equality -/// on the observed path). -pub(crate) fn timing_enforcement_violates( - declared_max_ns: u64, - usage: TimingMeasurementEnforcementUsage, -) -> bool { - match usage { - TimingMeasurementEnforcementUsage::NonObservedFault => true, - TimingMeasurementEnforcementUsage::ObservedWallNs(ns) => ns > declared_max_ns, - } -} - -fn observed_timing_payload_max_ns(payload: &[FieldValue]) -> Option { - let FieldValue::Record(parts) = payload.first()? else { - return None; - }; - if let Some((_, v)) = parts.iter().find(|(label, _)| label == "duration") { - return field_value_nat_magnitude(v); - } - field_value_nat_magnitude(&FieldValue::Record(parts.clone())) -} - -fn field_value_nat_magnitude(value: &FieldValue) -> Option { - match value { - FieldValue::Literal(LiteralBits::Int(s)) => s.parse().ok(), - FieldValue::Record(parts) => parts - .iter() - .find(|(label, _)| label == "count") - .and_then(|(_, v)| field_value_nat_magnitude(v)), - _ => None, - } -} - -fn field_value_timing_budget_max_ns(value: &FieldValue) -> Option { - let FieldValue::Record(parts) = value else { - return None; - }; - let max = parts - .iter() - .find(|(label, _)| label == "max") - .map(|(_, v)| v)?; - field_value_nat_magnitude(max) -} - -fn is_timing_enforceable_lens_value( - value: &FieldValue, - timing_enforceable_id: Option, - timing_lens_id: Option, - timing_enforcement_id: Option, -) -> bool { - if let (Some(bundle), FieldValue::Reference(id)) = (timing_enforceable_id, value) { - if *id == bundle { - return true; - } - } - let (Some(lens_id), Some(enforcement_id)) = (timing_lens_id, timing_enforcement_id) else { - return false; - }; - let FieldValue::Record(parts) = value else { - return false; - }; - let mut lens_ref: Option = None; - let mut enforcement_ref: Option = None; - for (label, field) in parts { - match (label.as_str(), field) { - ("lens", FieldValue::Reference(r)) => lens_ref = Some(*r), - ("enforcement", FieldValue::Reference(r)) => enforcement_ref = Some(*r), - _ => {} - } - } - lens_ref == Some(lens_id) && enforcement_ref == Some(enforcement_id) -} - -/// Fail-closed check for landed complexity + timing enforcement applications. -/// -/// Exported at the crate root (`v3_compiler::check_enforced_lens_applications`) so integration -/// tests can re-invoke the same post-infer pass as `infer::infer` without treating declaration -/// presence as proof the probe ran (gate #58 receipt). -pub fn check_enforced_lens_applications(dag: &mut Dag) { - let Some(enforced_template) = dag - .declarations() - .iter() - .find(|d| { - d.name.as_deref() == Some("EnforcedApplication") - && d.span.file.ends_with("lens_application.dag") - }) - .map(|d| d.id) - else { - return; - }; - let complexity_enforceable_id = dag - .declarations() - .iter() - .find(|d| { - d.name.as_deref() == Some("complexity_enforceable") - && d.span.file.ends_with("complexity.dag") - }) - .map(|d| d.id); - let timing_enforceable_id = dag - .declarations() - .iter() - .find(|d| { - d.name.as_deref() == Some("timing_enforceable") - && d.span.file.ends_with("timing_lens.dag") - }) - .map(|d| d.id); - let timing_lens_id = dag - .declarations() - .iter() - .find(|d| { - d.name.as_deref() == Some("timing_lens") && d.span.file.ends_with("timing_lens.dag") - }) - .map(|d| d.id); - let timing_enforcement_id = dag - .declarations() - .iter() - .find(|d| { - d.name.as_deref() == Some("timing_enforcement") - && d.span.file.ends_with("timing_lens.dag") - }) - .map(|d| d.id); - let asymptotic_disj = dag - .declarations() - .iter() - .find(|d| { - d.name.as_deref() == Some("AsymptoticClass") && d.span.file.ends_with("algebra.dag") - }) - .map(|d| d.id); - let Some(section_ref_disj) = dag - .declarations() - .iter() - .find(|d| { - d.name.as_deref() == Some("SectionRef") && d.span.file.ends_with("lens_application.dag") - }) - .map(|d| d.id) - else { - return; - }; - let Some(declaration_scope_conj) = declaration_scope_payload_conj(dag, section_ref_disj) else { - return; - }; - let Some(diagnostic_severity_disj) = diagnostic_severity_substrate_disj(dag) else { - attach_missing_diagnostic_severity_substrate_diagnostic(dag, enforced_template); - return; - }; - let positive_descent_disj = dag - .declaration_by_name("PositiveDescentAmount") - .map(|d| d.id); - - let mut violations: Vec = Vec::new(); - - for decl in dag.declarations() { - let Some(body) = decl.value_body.as_ref() else { - continue; - }; - let ValueBody::Structural { fields } = body else { - continue; - }; - let Some(meta_id) = decl.meta_tag else { - continue; - }; - if !matches_enforced_application_instantiation(dag, meta_id, enforced_template) { - continue; - } - let fm = field_map(fields); - let enforceable_lens = match fm.get("enforceable_lens") { - Some(v) => v, - _ => continue, - }; - let section = match fm.get("section") { - Some(v) => *v, - _ => continue, - }; - let budget_val = match fm.get("budget") { - Some(v) => *v, - _ => continue, - }; - let span = match fm.get("span") { - Some(FieldValue::Record(span_fields)) => record_as_source_span(span_fields), - _ => None, - } - .unwrap_or_else(|| decl.span.clone()); - - if let (Some(cid), FieldValue::Reference(ref_id)) = - (complexity_enforceable_id, enforceable_lens) - { - if *ref_id == cid { - let Some(asymptotic_disj) = asymptotic_disj else { - violations.push(Diagnostic::ParseError { - message: - "lens enforcement: could not resolve substrate `AsymptoticClass` from \ - `algebra.dag` (modeled authority missing; fail-closed)" - .to_string(), - span: decl.span.clone(), - fixes: Vec::new(), - }); - continue; - }; - let Some(fn_decl) = resolve_declaration_scope_fn( - dag, - section, - section_ref_disj, - declaration_scope_conj, - ) else { - violations.push(Diagnostic::ParseError { - message: - "lens enforcement: could not resolve function declaration for section" - .to_string(), - span: decl.span.clone(), - fixes: Vec::new(), - }); - continue; - }; - let Some(port) = fn_result_port(dag, fn_decl) else { - violations.push(Diagnostic::ParseError { - message: - "lens enforcement: could not resolve function result port for section" - .to_string(), - span: decl.span.clone(), - fixes: Vec::new(), - }); - continue; - }; - let Some(budget_class) = field_value_asymptotic_class( - dag, - asymptotic_disj, - positive_descent_disj, - budget_val, - ) else { - violations.push(Diagnostic::ParseError { - message: - "lens enforcement: could not read complexity budget `AsymptoticClass` \ - (ill-formed `ClassPolynomial` budgets must use Peano `degree` ≥ 3; \ - use `ClassLinear` / `ClassQuadratic` for sub-cubic tiers)" - .to_string(), - span: decl.span.clone(), - fixes: Vec::new(), - }); - continue; - }; - - let summary = match complexity_of(dag, &port) { - Lookup::Hit(s) => s, - Lookup::Miss => { - violations.push(Diagnostic::ParseError { - message: - "lens enforcement: complexity lens returned Miss for section — \ - cannot enforce budget" - .to_string(), - span: span.clone(), - fixes: Vec::new(), - }); - continue; - } - }; - if !complexity_enforcement_violates(&summary, &budget_class) { - continue; - } - let observed = complexity_enforcement_project(&summary); - let severity_val = match fm.get("diagnostic_severity") { - Some(v) => *v, - _ => { - violations.push(Diagnostic::ParseError { - message: - "lens enforcement: `EnforcedApplication` missing `diagnostic_severity`" - .to_string(), - span: decl.span.clone(), - fixes: Vec::new(), - }); - continue; - } - }; - let violation_message = format!( - "lens enforcement violation: complexity budget {budget_class:?} exceeded \ - by observed class {observed:?} (declared `EnforcedApplication` at {})", - decl.name.as_deref().unwrap_or("?") - ); - violations.push(enforced_violation_diagnostic( - dag, - diagnostic_severity_disj, - severity_val, - violation_message, - span, - )); - continue; - } - } - - if is_timing_enforceable_lens_value( - enforceable_lens, - timing_enforceable_id, - timing_lens_id, - timing_enforcement_id, - ) { - let Some(tm_disj) = timing_measurement_sum_type_decl_id(dag) else { - violations.push(Diagnostic::ParseError { - message: - "lens enforcement: could not resolve substrate `TimingMeasurement` from \ - `timing_lens.dag` (modeled authority missing; fail-closed)" - .to_string(), - span: decl.span.clone(), - fixes: Vec::new(), - }); - continue; - }; - let Some(section_decl_id) = - resolve_declaration_scope_declaration_id(section, declaration_scope_conj) - else { - violations.push(Diagnostic::ParseError { - message: "lens enforcement: could not resolve `DeclarationScope` for timing \ - `EnforcedApplication` section" - .to_string(), - span: decl.span.clone(), - fixes: Vec::new(), - }); - continue; - }; - let section_decl = dag.declaration(section_decl_id); - if !timing_section_row_type_declares_measurement_tm(dag, section_decl_id, tm_disj) { - violations.push(Diagnostic::ParseError { - message: format!( - "lens enforcement: timing `EnforcedApplication` section `{}` must be a \ - lowered `data …: RowTy = …` row whose nominal `RowTy` declares \ - `measurement: TimingMeasurement` (same substrate contract as \ - `timing_enforcement_project`)", - section_decl.name.as_deref().unwrap_or("?") - ), - span: decl.span.clone(), - fixes: Vec::new(), - }); - continue; - } - let Some(body) = section_decl.value_body.as_ref() else { - violations.push(Diagnostic::ParseError { - message: format!( - "lens enforcement: timing section `{}` has no lowered value body", - section_decl.name.as_deref().unwrap_or("?") - ), - span: decl.span.clone(), - fixes: Vec::new(), - }); - continue; - }; - let ValueBody::Structural { fields } = body else { - violations.push(Diagnostic::ParseError { - message: format!( - "lens enforcement: timing section `{}` must lower to a structural record body", - section_decl.name.as_deref().unwrap_or("?") - ), - span: decl.span.clone(), - fixes: Vec::new(), - }); - continue; - }; - let section_fm = field_map(fields); - let Some(measurement) = section_fm.get("measurement") else { - violations.push(Diagnostic::ParseError { - message: format!( - "lens enforcement: timing section `{}` is missing required `measurement` field", - section_decl.name.as_deref().unwrap_or("?") - ), - span: decl.span.clone(), - fixes: Vec::new(), - }); - continue; - }; - let Some(usage_projection) = - timing_measurement_enforcement_usage(dag, tm_disj, measurement) - else { - violations.push(Diagnostic::ParseError { - message: format!( - "lens enforcement: could not interpret `measurement` as lowered \ - `TimingMeasurement` for section `{}`", - section_decl.name.as_deref().unwrap_or("?") - ), - span: decl.span.clone(), - fixes: Vec::new(), - }); - continue; - }; - let Some(declared_max_ns) = field_value_timing_budget_max_ns(budget_val) else { - violations.push(Diagnostic::ParseError { - message: "lens enforcement: could not read timing budget `TimingBudget.max` \ - nanoseconds (expected `TimingBudget { max: Nanoseconds { count } }`)" - .to_string(), - span: decl.span.clone(), - fixes: Vec::new(), - }); - continue; - }; - if !timing_enforcement_violates(declared_max_ns, usage_projection) { - continue; - }; - let usage_max_ns_for_message = match usage_projection { - TimingMeasurementEnforcementUsage::ObservedWallNs(ns) => ns, - TimingMeasurementEnforcementUsage::NonObservedFault => { - let Some(s) = timing_enforcement_fault_sentinel_ns_from_substrate(dag) else { - violations.push(Diagnostic::ParseError { - message: - "lens enforcement: could not resolve substrate `timing_enforcement_fault_sentinel_count` \ - from `timing_lens.dag` (needed for fault-path diagnostic text; fail-closed)" - .to_string(), - span: decl.span.clone(), - fixes: Vec::new(), - }); - continue; - }; - s - } - }; - let severity_val = match fm.get("diagnostic_severity") { - Some(v) => *v, - _ => { - violations.push(Diagnostic::ParseError { - message: - "lens enforcement: `EnforcedApplication` missing `diagnostic_severity`" - .to_string(), - span: decl.span.clone(), - fixes: Vec::new(), - }); - continue; - } - }; - let violation_message = format!( - "lens enforcement violation: timing budget ceiling max_ns={declared_max_ns} exceeded \ - by projected wall-clock usage max_ns={usage_max_ns_for_message} (declared `EnforcedApplication` \ - at {})", - decl.name.as_deref().unwrap_or("?") - ); - violations.push(enforced_violation_diagnostic( - dag, - diagnostic_severity_disj, - severity_val, - violation_message, - span, - )); - } - } - - for diagnostic in violations { - dag.attach_diagnostic(diagnostic); - } -} - -#[doc(hidden)] -fn gate_58_raise_observed_duration_ns_in_measurement( - value: &mut FieldValue, - duration_ns: u64, -) -> bool { - match value { - FieldValue::Variant { payload, .. } => { - let Some(FieldValue::Record(parts)) = payload.first_mut() else { - return false; - }; - if let Some((_, FieldValue::Record(duration_parts))) = - parts.iter_mut().find(|(label, _)| label == "duration") - { - return gate_58_set_count_field_to_ns(duration_parts, duration_ns); - } - // PB-1 bootstrap lowering: `Observed` payload can flatten to `{ count: Nat }` without - // a nested `duration` record (authoring uses `Observed { duration: { count } }`). - gate_58_set_count_field_to_ns(parts, duration_ns) - } - _ => false, - } -} - -fn gate_58_set_count_field_to_ns(parts: &mut [(String, FieldValue)], duration_ns: u64) -> bool { - for (dlabel, dfield) in parts.iter_mut() { - if dlabel == "count" { - if let FieldValue::Literal(LiteralBits::Int(s)) = dfield { - *s = duration_ns.to_string(); - return true; - } - return false; - } - } - false -} - -/// Integration receipt helper: mutates the PB-1 gate #58 witness row in a bootstrap [`Dag`]. -/// -/// **Not a supported production API** — only the `v3-compiler` integration test -/// `t_gate_58_apply_lens_self_application_test` should call this (exposed so that test binary can -/// link against `v3_compiler` without `cfg(test)` coupling). -#[doc(hidden)] -pub fn gate_58_test_raise_modeled_ci_timing_measurement_duration_ns( - dag: &mut Dag, - duration_ns: u64, -) -> Result<(), &'static str> { - let id = dag - .declarations() - .iter() - .find(|d| { - d.name.as_deref() == Some("gate_58_modeled_ci_timing_measurement") - && d.span.file.ends_with("t_ci_workflow_as_data_demo.dag") - }) - .map(|d| d.id) - .ok_or("missing gate_58_modeled_ci_timing_measurement declaration")?; - let decl = dag.declaration_mut(id); - let Some(ValueBody::Structural { fields }) = decl.value_body.as_mut() else { - return Err("gate_58 witness missing structural value_body"); - }; - for (label, value) in fields.iter_mut() { - if label == "measurement" - && gate_58_raise_observed_duration_ns_in_measurement(value, duration_ns) - { - return Ok(()); - } - } - Err("measurement field missing or not Observed with duration.count shape") -} - -/// Parses `(declared_max_ns, projected_usage_max_ns)` from a timing lens **budget-ceiling** -/// violation message produced by [`check_enforced_lens_applications`]. -/// -/// The template is owned next to [`timing_enforcement_violates`] / `violation_message` formatting -/// in this module (`max_ns=` appears exactly twice: declared ceiling, then projected usage). -/// Integration tests use this instead of pinning English prose (see repo `TESTING.md` anti-pattern). -#[doc(hidden)] -pub fn gate_58_test_parse_timing_budget_violation_max_ns_pair(message: &str) -> Option<(u64, u64)> { - const NEEDLE: &str = "max_ns="; - let mut found: Vec = Vec::with_capacity(2); - let mut rest = message; - while let Some(pos) = rest.find(NEEDLE) { - let after = pos + NEEDLE.len(); - let tail = &rest[after..]; - let digit_len = tail - .chars() - .take_while(|c| c.is_ascii_digit()) - .map(|c| c.len_utf8()) - .sum::(); - if digit_len == 0 { - return None; - } - let n: u64 = tail[..digit_len].parse().ok()?; - found.push(n); - rest = &tail[digit_len..]; - } - if found.len() == 2 { - Some((found[0], found[1])) - } else { - None - } -} - -fn field_map(fields: &[(String, FieldValue)]) -> HashMap<&str, &FieldValue> { - fields.iter().map(|(k, v)| (k.as_str(), v)).collect() -} - -/// Routes an enforce-mode **budget violation** through `EnforcedApplication.diagnostic_severity`. -/// -/// Substrate policy (C-8): only `DiagnosticSeverity::Error` is a valid steady-state choice; unknown -/// constructors, non-nullary `Error` payload, or malformed values fail closed with an explanatory -/// diagnostic. -fn enforced_violation_diagnostic( - dag: &Dag, - diagnostic_severity_disj: DeclarationId, - severity_val: &FieldValue, - violation_message: String, - span: SourceSpan, -) -> Diagnostic { - let TypeConnective::Disj { variants } = &dag.declaration(diagnostic_severity_disj).connective - else { - return Diagnostic::ParseError { - message: "lens enforcement: internal error (DiagnosticSeverity is not a sum type)" - .to_string(), - span, - fixes: Vec::new(), - }; - }; - let Some(error_ctor) = variants.iter().find(|v| v.label == "Error").map(|v| v.ty) else { - return Diagnostic::ParseError { - message: "lens enforcement: internal error (DiagnosticSeverity lacks `Error` variant)" - .to_string(), - span, - fixes: Vec::new(), - }; - }; - let FieldValue::Variant { - constructor, - payload, - } = severity_val - else { - return Diagnostic::ParseError { - message: "lens enforcement: `diagnostic_severity` must be a `DiagnosticSeverity` \ - variant value" - .to_string(), - span, - fixes: Vec::new(), - }; - }; - if *constructor != error_ctor { - return Diagnostic::ParseError { - message: "lens enforcement: `diagnostic_severity` on `EnforcedApplication` must be \ - `Error` (INVARIANTS C-8; fail-closed discipline)" - .to_string(), - span, - fixes: Vec::new(), - }; - } - // `lens_application.dag`: `type DiagnosticSeverity = Error` — the lone variant is nullary. - if !payload.is_empty() { - return Diagnostic::ParseError { - message: "lens enforcement: `DiagnosticSeverity::Error` must be nullary (INVARIANTS \ - P1; malformed variant payload)" - .to_string(), - span, - fixes: Vec::new(), - }; - } - Diagnostic::ParseError { - message: violation_message, - span, - fixes: Vec::new(), - } -} - -fn matches_enforced_application_instantiation( - dag: &Dag, - mut meta: DeclarationId, - enforced_template: DeclarationId, -) -> bool { - for _ in 0..16 { - match &dag.declaration(meta).connective { - TypeConnective::Instantiation { - template, - arguments, - } => { - if *template == enforced_template && arguments.len() == 3 { - return true; - } - if arguments.is_empty() { - meta = *template; - continue; - } - return false; - } - _ => return false, - } - } - false -} - -fn declaration_scope_payload_conj( - dag: &Dag, - section_ref_disj: DeclarationId, -) -> Option { - let TypeConnective::Disj { variants } = &dag.declaration(section_ref_disj).connective else { - return None; - }; - let v = variants.iter().find(|f| f.label == "DeclarationScope")?; - Some(v.ty) -} - -fn resolve_declaration_scope_declaration_id( - section: &FieldValue, - declaration_scope_conj: DeclarationId, -) -> Option { - let FieldValue::Variant { - constructor, - payload, - } = section - else { - return None; - }; - if *constructor != declaration_scope_conj { - return None; - } - match payload.as_slice() { - [FieldValue::Reference(id)] => Some(*id), - [FieldValue::Record(parts)] => { - let mut decl_id: Option = None; - for (label, value) in parts { - if label == "declaration" { - if let FieldValue::Reference(id) = value { - decl_id = Some(*id); - } - } - } - decl_id - } - _ => None, - } -} - -fn resolve_declaration_scope_fn( - dag: &Dag, - section: &FieldValue, - section_ref_disj: DeclarationId, - declaration_scope_conj: DeclarationId, -) -> Option { - let _ = section_ref_disj; - let id = resolve_declaration_scope_declaration_id(section, declaration_scope_conj)?; - matches!(dag.declaration(id).connective, TypeConnective::Arrow { .. }).then_some(id) -} - -fn fn_result_port(dag: &Dag, fn_decl: DeclarationId) -> Option { - let decl = dag.declaration(fn_decl); - let TypeConnective::Arrow { - body: ArrowBody::UserDefined(bind_node), - .. - } = &decl.connective - else { - return None; - }; - let bind = bind_node.bind(dag); - Some(bind.result_port()) -} - -fn record_as_source_span(fields: &[(String, FieldValue)]) -> Option { - let mut file: Option = None; - let mut byte_start: Option = None; - let mut byte_end: Option = None; - for (label, value) in fields { - match (label.as_str(), value) { - ("file", FieldValue::Literal(LiteralBits::String(s))) => file = Some(s.clone()), - ("byte_start", FieldValue::Literal(LiteralBits::Int(n))) => { - byte_start = literal_decimal_i64(n.as_str()).and_then(|v| u32::try_from(v).ok()); - } - ("byte_end", FieldValue::Literal(LiteralBits::Int(n))) => { - byte_end = literal_decimal_i64(n.as_str()).and_then(|v| u32::try_from(v).ok()); - } - ("start", FieldValue::Literal(LiteralBits::Int(n))) => { - byte_start = literal_decimal_i64(n.as_str()).and_then(|v| u32::try_from(v).ok()); - } - ("end", FieldValue::Literal(LiteralBits::Int(n))) => { - byte_end = literal_decimal_i64(n.as_str()).and_then(|v| u32::try_from(v).ok()); - } - _ => {} - } - } - Some(SourceSpan::new(file?, byte_start?, byte_end?)) -} - -fn field_value_asymptotic_class( - dag: &Dag, - ac_disj: DeclarationId, - pda_disj: Option, - value: &FieldValue, -) -> Option { - let FieldValue::Variant { - constructor, - payload, - } = value - else { - return None; - }; - let TypeConnective::Disj { variants } = &dag.declaration(ac_disj).connective else { - return None; - }; - let variant = variants.iter().find(|v| v.ty == *constructor)?; - match variant.label.as_str() { - "ClassConstant" => Some(AsymptoticClass::ClassConstant), - "ClassLog" => Some(AsymptoticClass::ClassLog), - "ClassLinear" => Some(AsymptoticClass::ClassLinear), - "ClassLinearithmic" => Some(AsymptoticClass::ClassLinearithmic), - "ClassQuadratic" => Some(AsymptoticClass::ClassQuadratic), - "ClassExponential" => Some(AsymptoticClass::ClassExponential), - "ClassUnknown" => Some(AsymptoticClass::ClassUnknown), - "ClassPolynomial" => { - let pda = pda_disj?; - let degree = decode_positive_descent_amount(dag, pda, payload)?; - // `AsymptoticClass::ClassPolynomial` in `algebra.dag` is k ≥ 3; Peano - // counts 1–2 are `ClassLinear` / `ClassQuadratic` territory — reject as - // an invalid budget shape (fail closed; openai-pro / P1). - if positive_descent_count(°ree) < 3 { - return None; - } - Some(AsymptoticClass::ClassPolynomial { degree }) - } - _ => None, - } -} - -fn decode_positive_descent_amount( - dag: &Dag, - pda_disj: DeclarationId, - poly_payload: &[FieldValue], -) -> Option { - match poly_payload { - [FieldValue::Record(parts)] => { - let value = parts.iter().find(|(k, _)| k == "degree").map(|(_, v)| v)?; - decode_positive_descent_variant(dag, pda_disj, value) - } - [single] => decode_positive_descent_variant(dag, pda_disj, single), - _ => None, - } -} - -fn decode_positive_descent_variant( - dag: &Dag, - pda_disj: DeclarationId, - value: &FieldValue, -) -> Option { - let FieldValue::Variant { - constructor, - payload, - } = value - else { - return None; - }; - let TypeConnective::Disj { variants } = &dag.declaration(pda_disj).connective else { - return None; - }; - let variant = variants.iter().find(|v| v.ty == *constructor)?; - match variant.label.as_str() { - "OneStep" => Some(PositiveDescentAmount::OneStep), - "AdditionalStep" => { - let FieldValue::Record(parts) = payload.first()? else { - return None; - }; - let prev = parts - .iter() - .find(|(k, _)| k == "previous") - .map(|(_, v)| v)?; - let inner = decode_positive_descent_variant(dag, pda_disj, prev)?; - Some(PositiveDescentAmount::AdditionalStep { - previous: Box::new(inner), - }) - } - _ => None, - } -} - -#[cfg(test)] -mod diagnostic_severity_fail_closed_tests { - use super::*; - use crate::dag::{LiteralBits, TypeConnective}; - use crate::diagnostics::Diagnostic; - - fn assert_parse_error_at(diag: Diagnostic, expected_span: &SourceSpan) { - match diag { - Diagnostic::ParseError { span, fixes, .. } => { - assert_eq!(&span, expected_span); - assert!(fixes.is_empty()); - } - other => panic!("expected ParseError, got {other:?}"), - } - } - - #[test] - fn missing_diagnostic_severity_substrate_records_fail_closed_diagnostic() { - let mut dag = Dag::new(); - let Some(enforced_template) = dag - .declarations() - .iter() - .find(|d| { - d.name.as_deref() == Some("EnforcedApplication") - && d.span.file.ends_with("lens_application.dag") - }) - .map(|d| d.id) - else { - panic!("bootstrap should declare EnforcedApplication in lens_application.dag"); - }; - let expected_span = dag.declaration(enforced_template).span.clone(); - let Some(ds_id) = super::diagnostic_severity_substrate_disj(&dag) else { - panic!("bootstrap should declare DiagnosticSeverity in lens_application.dag"); - }; - dag.declaration_mut(ds_id).name = Some("DiagnosticSeverity__test_unresolvable".to_string()); - assert!( - super::diagnostic_severity_substrate_disj(&dag).is_none(), - "Expected DiagnosticSeverity substrate row to be unresolvable after rename" - ); - super::attach_missing_diagnostic_severity_substrate_diagnostic(&mut dag, enforced_template); - let mut it = dag.diagnostics().iter(); - let (_, d) = it.next().expect("fail-closed diagnostic"); - assert!(it.next().is_none(), "expected exactly one diagnostic"); - assert_parse_error_at(d.clone(), &expected_span); - } - - #[test] - fn enforced_violation_diagnostic_rejects_non_error_severity_constructor() { - let dag = Dag::new(); - let Some(ds_disj) = dag - .declarations() - .iter() - .find(|d| { - d.name.as_deref() == Some("DiagnosticSeverity") - && d.span.file.ends_with("lens_application.dag") - }) - .map(|d| d.id) - else { - panic!("bootstrap should declare DiagnosticSeverity in lens_application.dag"); - }; - let wrong_ctor = dag - .bool_runtime_variant_id(true) - .expect("bootstrap True variant"); - let expected_span = SourceSpan::new("t.v3", 0, 1); - let diag = enforced_violation_diagnostic( - &dag, - ds_disj, - &FieldValue::Variant { - constructor: wrong_ctor, - payload: Vec::new(), - }, - "violation".to_string(), - expected_span.clone(), - ); - assert_parse_error_at(diag, &expected_span); - } - - #[test] - fn enforced_violation_diagnostic_rejects_non_variant_severity_value() { - let dag = Dag::new(); - let Some(ds_disj) = dag - .declarations() - .iter() - .find(|d| { - d.name.as_deref() == Some("DiagnosticSeverity") - && d.span.file.ends_with("lens_application.dag") - }) - .map(|d| d.id) - else { - panic!("bootstrap should declare DiagnosticSeverity in lens_application.dag"); - }; - let expected_span = SourceSpan::new("t.v3", 0, 1); - let diag = enforced_violation_diagnostic( - &dag, - ds_disj, - &FieldValue::Literal(LiteralBits::Int("0".to_string())), - "violation".to_string(), - expected_span.clone(), - ); - assert_parse_error_at(diag, &expected_span); - } - - #[test] - fn enforced_violation_diagnostic_rejects_error_severity_with_non_nullary_payload() { - let dag = Dag::new(); - let Some(ds_disj) = dag - .declarations() - .iter() - .find(|d| { - d.name.as_deref() == Some("DiagnosticSeverity") - && d.span.file.ends_with("lens_application.dag") - }) - .map(|d| d.id) - else { - panic!("bootstrap should declare DiagnosticSeverity in lens_application.dag"); - }; - let TypeConnective::Disj { variants } = &dag.declaration(ds_disj).connective else { - panic!("bootstrap DiagnosticSeverity should be a sum type"); - }; - let error_ctor = variants - .iter() - .find(|v| v.label == "Error") - .map(|v| v.ty) - .expect("bootstrap DiagnosticSeverity should have Error variant"); - let expected_span = SourceSpan::new("t.v3", 0, 1); - let diag = enforced_violation_diagnostic( - &dag, - ds_disj, - &FieldValue::Variant { - constructor: error_ctor, - payload: vec![FieldValue::Literal(LiteralBits::Int("0".to_string()))], - }, - "violation".to_string(), - expected_span.clone(), - ); - assert_parse_error_at(diag, &expected_span); - } -} - -#[cfg(test)] -mod polynomial_budget_class_policy_tests { - use crate::dag::{positive_amount_from_i64, positive_descent_count, PositiveDescentAmount}; - - fn polynomial_budget_peano_is_admissible(degree: &PositiveDescentAmount) -> bool { - positive_descent_count(degree) >= 3 - } - - #[test] - fn class_polynomial_enforcement_budget_rejects_sub_cubic_peano_degrees() { - assert_eq!(positive_descent_count(&PositiveDescentAmount::OneStep), 1); - assert!(!polynomial_budget_peano_is_admissible( - &PositiveDescentAmount::OneStep - )); - let deg2 = PositiveDescentAmount::AdditionalStep { - previous: Box::new(PositiveDescentAmount::OneStep), - }; - assert_eq!(positive_descent_count(°2), 2); - assert!(!polynomial_budget_peano_is_admissible(°2)); - let deg3 = positive_amount_from_i64(3).expect("synthetic degree 3"); - assert!(polynomial_budget_peano_is_admissible(°3)); - } -} - -#[cfg(test)] -mod gate_58_timing_enforcement_unit_tests { - use super::{ - gate_58_test_parse_timing_budget_violation_max_ns_pair, - timing_enforcement_fault_sentinel_ns_from_substrate, timing_enforcement_violates, - TimingMeasurementEnforcementUsage, - }; - use crate::dag::Dag; - - #[test] - fn gate_58_parse_max_ns_pair_matches_timing_budget_violation_template() { - let msg = "lens enforcement violation: timing budget ceiling max_ns=1 exceeded \ - by projected wall-clock usage max_ns=999 (declared `EnforcedApplication` \ - at gate_58_enforcement_budget_violation)"; - assert_eq!( - gate_58_test_parse_timing_budget_violation_max_ns_pair(msg), - Some((1, 999)) - ); - } - - #[test] - fn non_observed_fault_always_violates_under_any_finite_budget() { - assert!(timing_enforcement_violates( - 1, - TimingMeasurementEnforcementUsage::NonObservedFault - )); - assert!(timing_enforcement_violates( - u64::MAX - 1, - TimingMeasurementEnforcementUsage::NonObservedFault - )); - } - - #[test] - fn observed_usage_strictly_exceeding_budget_violates() { - assert!(timing_enforcement_violates( - 500, - TimingMeasurementEnforcementUsage::ObservedWallNs(1000) - )); - } - - #[test] - fn observed_usage_within_or_equal_budget_is_clean() { - assert!(!timing_enforcement_violates( - 1000, - TimingMeasurementEnforcementUsage::ObservedWallNs(500) - )); - assert!(!timing_enforcement_violates( - 1000, - TimingMeasurementEnforcementUsage::ObservedWallNs(1000) - )); - } - - #[test] - fn observed_wall_at_substrate_fault_sentinel_is_not_fault_short_circuit() { - let dag = Dag::new(); - let s = timing_enforcement_fault_sentinel_ns_from_substrate(&dag) - .expect("bootstrap should carry timing_lens fault sentinel"); - assert!(!timing_enforcement_violates( - s + 1, - TimingMeasurementEnforcementUsage::ObservedWallNs(s) - )); - assert!(timing_enforcement_violates( - s - 1, - TimingMeasurementEnforcementUsage::ObservedWallNs(s) - )); - } -} diff --git a/src/v3/compiler/src/int_literal_ranges.rs b/src/v3/compiler/src/int_literal_ranges.rs deleted file mode 100644 index 3bb9415c452..00000000000 --- a/src/v3/compiler/src/int_literal_ranges.rs +++ /dev/null @@ -1,1016 +0,0 @@ -//! Integer literal magnitude vs declared integer types — **Q1 consumer**. -//! -//! Authoritative modeling: [`docs/design-emission-model.md`](../../../../docs/design-emission-model.md) -//! §Q1 — `BoundDeclaration = StaticBound(Interval) | PlatformDependent`, with asymmetric -//! match (target `Unbounded` universal-accept; target `ExactInterval` exact `lo`/`hi` equality at -//! the fold). This module implements only the **static** side needed for literal narrowing: -//! substrate range facts [`range_min_inclusive` / `range_max_inclusive`](../../../../dsl/extdeps/languages/rust/primitives.dag) -//! on [`rust_pilot_primitives`](crate::dag::Dag::rust_pilot_primitives) supply -//! `StaticBound(Interval)` as [`IntervalInt::ExactInterval`] (decimal endpoints + host -//! `BigInt` comparison; widened from `i128` per R3 Phase A so `u128::MAX` and any future wider -//! primitive is representable structurally). [`IntervalInt::Unbounded`] exists so Q1’s interval -//! algebra is representable when a -//! target declares an unbounded value domain (pilot `IntegerPrimitive` rows are all exact today). -//! [`PlatformDependent`] is out of scope for i64-bounded literal narrowing (deferred targets). -//! -//! ## Downstream consumers (range-facts + narrowing) -//! -//! | Location | Behavior | -//! | --- | --- | -//! | `infer::try_reconcile_int_literal_decision_set` | `let` / `data` pre-seed vs default `Int64` literal; in-range narrow; OOB → `MagnitudeOutOfRange`. | -//! | `infer::decide_transform` (calls) | Parameter-narrow type vs default-`Int` argument literal; narrow or OOB. | -//! | `infer::int_literal_implicit_bind_tolerated_for_expected` | Callable template binding when structural binding fails on int literal. | -//! | `lower` scalar literal lowering | Early reject for OOB literals before inference reunion. | -//! -//! ### R2 downstream audit ([`docs/briefs/r2-modeling-int-lit-magnitude-worker.md`](../../../../docs/briefs/r2-modeling-int-lit-magnitude-worker.md) §Slice 2) -//! -//! Post–[#1227](https://github.com/gunb-ai/gunbc/pull/1227) (**MethodEmitTemplate** Phase 1.5): no shared symbols with `emit_model.dag` method-template row lists -//! or `*_method_template_contracts` — those paths do **not** consult `rust_pilot_primitives` / this module. -//! -//! | Consumer | Range-facts / Q1 magnitude | -//! | --- | --- | -//! | `infer` (`try_reconcile_int_literal_decision_set`, `decide_transform`, `int_literal_implicit_bind_tolerated_for_expected`, transform/`PortUnion` narrow paths) | **Yes** — `integer_range_for_decl` + `magnitude_out_of_range_for_interval`. | -//! | `lower` (`lower_scalar_literal` / scalar literal outcome) | **Yes** — same facts for early OOB. | -//! | `emit::*` (e.g. `rust_target`) | **Indirect** — emits **resolved** shapes after inference; no parallel range-fact walk. | -//! | Method-template / emit-model carriers (**#1227**) | **N/A** — template contracts; unrelated to int-literal narrowing. | - -use std::collections::HashSet; -use std::str::FromStr; - -use num_bigint::BigInt; - -use crate::dag::{ - AtomPayload, Behavior, Dag, DeclarationId, FieldValue, LiteralBits, PortId, TemplateArgument, - TypeConnective, ValueBody, -}; -use crate::diagnostics::{Diagnostic, SourceSpan}; -use crate::types::TypeShape; - -/// Q1 `Interval` instance carried from String-decimal range facts (not `LiteralBits::Int` -/// widening — producer brief). -/// -/// **Host repr:** `min` / `max` are arbitrary-precision `BigInt` so `u128::MAX` (and any future -/// wider-than-i128 primitive) is representable structurally without per-width host-repr variant -/// explosion. Per Director Path A RATIFIED at gunbc#1739 #issuecomment-4392731264 (R3 Substrate -/// Rust-primitive-full-coverage bundled brief). The previous narrow `i128` host-repr deferred the -/// `u128` row in `dsl/extdeps/languages/rust/primitives.dag` because `u128::MAX` exceeds `i128` -/// range; that gap is closed by the `BigInt` host repr here. -#[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) enum IntervalInt { - /// Closed interval — substrate `range_*_inclusive` facts for a fixed-width target primitive. - ExactInterval { - target_name: String, - min_decimal: String, - max_decimal: String, - min: BigInt, - max: BigInt, - }, - /// Value-domain unbounded integer (e.g. arbitrary-precision target). Universal-accept for any - /// tokenized decimal literal magnitude (through `u128::MAX` / signed `|m| ≤ 2^127`). - /// - /// **Dissolution trigger (when this variant is constructed from [`integer_range_for_decl`]):** - /// a `rust_pilot_primitives` `IntegerPrimitive` row (or successor multi-target table) is - /// authored for a target whose Q1 `BoundDeclaration` is `StaticBound(Unbounded)` at magnitude - /// check — e.g. Python `int` per [`docs/design-emission-model.md`](../../../../docs/design-emission-model.md) - /// fold example (T-Ground cross-target / language `primitives.dag` work, not this consumer). - /// Until that producer exists, only [`IntervalInt::ExactInterval`] is returned from the pilot - /// list; [`Unbounded`] remains for `contains_bigint` / Q1 algebra completeness and unit tests. - #[allow(dead_code)] - Unbounded, -} - -/// Decimal endpoints for a **fixed** integer target — the payload of [`MagnitudeOutOfRange`]. -/// -/// **API split:** only [`magnitude_out_of_range`] takes this type. When you have a full -/// [`IntervalInt`] from [`integer_range_for_decl`], call [`magnitude_out_of_range_for_interval`] -/// (it uses [`IntervalInt::exact_interval_facts`] and never passes an unbounded domain into the -/// magnitude diagnostic). -#[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct ExactIntIntervalFacts { - pub(crate) target_name: String, - pub(crate) min_decimal: String, - pub(crate) max_decimal: String, -} - -impl IntervalInt { - /// Range check for a surface literal magnitude carried as decimal [`LiteralBits::Int`]. - pub(crate) fn contains_bigint(&self, value: &BigInt) -> bool { - match self { - IntervalInt::Unbounded => true, - IntervalInt::ExactInterval { min, max, .. } => min <= value && value <= max, - } - } - - /// Fixed-width pilot rows: `Some` for [`IntervalInt::ExactInterval`]; `None` for - /// [`IntervalInt::Unbounded`] (no decimal range to quote in `MagnitudeOutOfRange`). - pub(crate) fn exact_interval_facts(&self) -> Option { - match self { - IntervalInt::ExactInterval { - target_name, - min_decimal, - max_decimal, - .. - } => Some(ExactIntIntervalFacts { - target_name: target_name.clone(), - min_decimal: min_decimal.clone(), - max_decimal: max_decimal.clone(), - }), - IntervalInt::Unbounded => None, - } - } -} - -pub(crate) enum IntegerRangeLookup { - Found(IntervalInt), - Missing, - Invalid(Diagnostic), -} - -/// Structural witness for routing integer literals: `IntegerAlgebra` and -/// `TargetCarrier` variant **payload type** ids (the `constructor` field on -/// `FieldValue::Variant`), derived from std `OrderedRing` / `Semiring` -/// **or** `Compose>` (R3 gate #19 refinement form) -/// by resolving to the same pilot `(OrderedRing|Semiring, Word*)` routing key. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) struct IntegerRoutingWitness { - pub(crate) algebra_variant_ty: DeclarationId, - pub(crate) carrier_variant_ty: DeclarationId, -} - -/// Integration / structural tests: witness for `decl`'s resolved integer -/// instantiation (`OrderedRing` / `Semiring` + word carrier), if any. -pub(crate) fn integer_routing_witness_for_decl( - dag: &Dag, - decl: DeclarationId, -) -> Option { - integer_routing_witness_walk(dag, decl, 0) -} - -/// `Nat` (`dsl/std/nat.dag`: `Semiring`). Nonnegative literals share the tokenizer's -/// `u128` magnitude carrier; narrowing still rejects negative values structurally. -fn type_is_nat(dag: &Dag, mut decl: DeclarationId) -> bool { - let Some(nat_id) = dag.declaration_by_name("Nat").map(|d| d.id) else { - return false; - }; - let Some(semiring_template) = dag.declaration_by_name("Semiring").map(|d| d.id) else { - return false; - }; - let Some(magnitude_id) = dag.declaration_by_name("Magnitude").map(|d| d.id) else { - return false; - }; - for _ in 0..32 { - if decl == nat_id { - return true; - } - let declaration = dag.declaration(decl); - match &declaration.connective { - TypeConnective::Instantiation { - template, - arguments, - } if *template == semiring_template - && arguments.len() == 1 - && arguments[0].value == magnitude_id => - { - return true; - } - TypeConnective::Instantiation { template, .. } => decl = *template, - TypeConnective::Atom(AtomPayload::ResolvedByStructure(next)) - | TypeConnective::Atom(AtomPayload::ResolvedByName(next)) => decl = *next, - _ => return false, - } - } - false -} - -fn nat_decimal_literal_interval() -> IntervalInt { - IntervalInt::ExactInterval { - target_name: "Nat".to_string(), - min_decimal: "0".to_string(), - max_decimal: u128::MAX.to_string(), - min: BigInt::from(0u8), - max: BigInt::from(u128::MAX), - } -} - -pub(crate) fn integer_range_for_decl(dag: &Dag, decl: DeclarationId) -> IntegerRangeLookup { - if type_is_nat(dag, decl) { - return IntegerRangeLookup::Found(nat_decimal_literal_interval()); - } - let Some(witness) = integer_routing_witness_walk(dag, decl, 0) else { - return IntegerRangeLookup::Missing; - }; - let Some(pilot) = dag.rust_pilot_primitives() else { - return IntegerRangeLookup::Missing; - }; - let Some(body) = pilot.value_body.as_ref() else { - return IntegerRangeLookup::Missing; - }; - let ValueBody::List(elements) = body else { - return IntegerRangeLookup::Missing; - }; - - let integer_primitive_ctor = match rust_primitive_integer_variant_ty(dag) { - Some(id) => id, - None => { - return IntegerRangeLookup::Invalid(malformed_integer_range_fact( - "bootstrap: RustPrimitive.IntegerPrimitive variant type is unavailable".to_string(), - pilot.span.clone(), - )); - } - }; - - let mut matches: Vec = Vec::new(); - for element in elements { - let FieldValue::Variant { - constructor, - payload, - } = element - else { - continue; - }; - if *constructor != integer_primitive_ctor { - continue; - } - match pilot_integer_row(witness, payload, pilot.span.clone()) { - Ok(Some(m)) => matches.push(m), - Ok(None) => {} - Err(diag) => return IntegerRangeLookup::Invalid(diag), - } - } - - if matches.is_empty() { - return IntegerRangeLookup::Missing; - } - if let Some(row) = matches.iter().find(|row| row.range.is_none()) { - return IntegerRangeLookup::Invalid(malformed_integer_range_fact( - format!( - "malformed rust_pilot_primitives IntegerPrimitive row for routing witness {:?}; integer literal range narrowing is unavailable", - (witness.algebra_variant_ty, witness.carrier_variant_ty) - ), - row.span.clone(), - )); - } - if matches.len() > 1 { - return IntegerRangeLookup::Invalid(malformed_integer_range_fact( - format!( - "duplicate rust_pilot_primitives IntegerPrimitive rows for routing witness {:?}; integer literal range narrowing is ambiguous", - (witness.algebra_variant_ty, witness.carrier_variant_ty) - ), - matches[1].span.clone(), - )); - } - let row = matches.remove(0); - match row.range { - Some(range) => IntegerRangeLookup::Found(range), - None => unreachable!("malformed matching rows are handled before duplicate detection"), - } -} - -struct PilotIntegerMatch { - range: Option, - span: SourceSpan, -} - -fn integer_routing_witness_walk( - dag: &Dag, - decl: DeclarationId, - depth: usize, -) -> Option { - if depth >= 32 { - return None; - } - let declaration = dag.declaration(decl); - match &declaration.connective { - TypeConnective::Atom(AtomPayload::ResolvedByName(next)) - | TypeConnective::Atom(AtomPayload::ResolvedByStructure(next)) => { - integer_routing_witness_walk(dag, *next, depth + 1) - } - TypeConnective::Instantiation { - template, - arguments, - } => { - if arguments.is_empty() { - return integer_routing_witness_walk(dag, *template, depth + 1); - } - if let Some(compose_id) = dag.declaration_by_name("Compose").map(|d| d.id) { - if *template == compose_id && arguments.len() == 2 { - if let Some(w) = compose_integer_routing_witness(dag, arguments.as_slice()) { - return Some(w); - } - } - } - let carrier = arguments.first()?.value; - integer_instantiation_witness(dag, *template, carrier) - } - _ => None, - } -} - -/// Gate #60 Phase 2.1: `MachineWidth`'s phantom argument may be a literal decimal -/// (`MachineWidth<64>`) naming the Nat magnitude; map to the existing `std.bit` width -/// authority (`Byte` / `Word16` / …) for the same integer-routing witness as Word*-spelled rows. -fn machine_width_inner_to_std_width_carrier_decl( - dag: &Dag, - inner_decl: DeclarationId, -) -> Option { - if std_word_carrier_to_target_carrier_variant_ty(dag, inner_decl).is_some() { - return Some(inner_decl); - } - match &dag.declaration(inner_decl).connective { - TypeConnective::Atom(AtomPayload::Literal(LiteralBits::Int(s))) => { - let n: u32 = s.parse().ok()?; - match n { - 8 => Some(dag.declaration_by_name("Byte")?.id), - 16 => Some(dag.declaration_by_name("Word16")?.id), - 32 => Some(dag.declaration_by_name("Word32")?.id), - 64 => Some(dag.declaration_by_name("Word64")?.id), - 128 => Some(dag.declaration_by_name("Word128")?.id), - _ => None, - } - } - _ => None, - } -} - -/// Fixed-width integers after gate #19: `Compose>` (signed) or -/// `Compose>` (unsigned). Maps to the same `(OrderedRing|Semiring, Word*)` -/// pilot routing key as the legacy `OrderedRing` / `Semiring` instantiations. -fn compose_integer_routing_witness( - dag: &Dag, - arguments: &[TemplateArgument], -) -> Option { - let int_id = dag.declaration_by_name("Int").map(|d| d.id)?; - let uint_id = dag.declaration_by_name("UInt").map(|d| d.id)?; - let machine_width = dag.declaration_by_name("MachineWidth").map(|d| d.id)?; - - let mut signed: Option = None; - let mut word_carrier: Option = None; - - for arg in arguments { - let v = arg.value; - if v == int_id { - signed = Some(true); - } else if v == uint_id { - signed = Some(false); - } else if let TypeConnective::Instantiation { - template, - arguments: mw_args, - } = &dag.declaration(v).connective - { - if *template == machine_width && mw_args.len() == 1 { - let inner = mw_args[0].value; - word_carrier = Some(machine_width_inner_to_std_width_carrier_decl(dag, inner)?); - } - } - } - - let signed = signed?; - let carrier_decl = word_carrier?; - let algebra_variant_ty = if signed { - disj_variant_payload_ty(dag, "IntegerAlgebra", "OrderedRingAlgebra")? - } else { - disj_variant_payload_ty(dag, "IntegerAlgebra", "SemiringAlgebra")? - }; - let carrier_variant_ty = std_word_carrier_to_target_carrier_variant_ty(dag, carrier_decl)?; - Some(IntegerRoutingWitness { - algebra_variant_ty, - carrier_variant_ty, - }) -} - -fn integer_instantiation_witness( - dag: &Dag, - template: DeclarationId, - carrier_decl: DeclarationId, -) -> Option { - let ordered_ring = dag.declaration_by_name("OrderedRing")?.id; - let semiring = dag.declaration_by_name("Semiring")?.id; - let algebra_variant_ty = if template == ordered_ring { - disj_variant_payload_ty(dag, "IntegerAlgebra", "OrderedRingAlgebra")? - } else if template == semiring { - disj_variant_payload_ty(dag, "IntegerAlgebra", "SemiringAlgebra")? - } else { - return None; - }; - let carrier_variant_ty = std_word_carrier_to_target_carrier_variant_ty(dag, carrier_decl)?; - Some(IntegerRoutingWitness { - algebra_variant_ty, - carrier_variant_ty, - }) -} - -fn disj_variant_payload_ty( - dag: &Dag, - sum_name: &str, - variant_label: &str, -) -> Option { - let decl = dag.declaration_by_name(sum_name)?; - let TypeConnective::Disj { variants } = &decl.connective else { - return None; - }; - variants - .iter() - .find(|v| v.label == variant_label) - .map(|v| v.ty) -} - -fn std_word_carrier_to_target_carrier_variant_ty( - dag: &Dag, - carrier_decl: DeclarationId, -) -> Option { - let byte = dag.declaration_by_name("Byte")?.id; - let word16 = dag.declaration_by_name("Word16")?.id; - let word32 = dag.declaration_by_name("Word32")?.id; - let word64 = dag.declaration_by_name("Word64")?.id; - let word128 = dag.declaration_by_name("Word128")?.id; - let label = if carrier_decl == byte { - "ByteCarrier" - } else if carrier_decl == word16 { - "Word16Carrier" - } else if carrier_decl == word32 { - "Word32Carrier" - } else if carrier_decl == word64 { - "Word64Carrier" - } else if carrier_decl == word128 { - "Word128Carrier" - } else { - return None; - }; - disj_variant_payload_ty(dag, "TargetCarrier", label) -} - -fn rust_primitive_integer_variant_ty(dag: &Dag) -> Option { - let rust_primitive = dag.declaration_by_name("RustPrimitive")?; - let TypeConnective::Disj { variants } = &rust_primitive.connective else { - return None; - }; - variants - .iter() - .find(|v| v.label == "IntegerPrimitive") - .map(|v| v.ty) -} - -fn pilot_integer_row( - witness: IntegerRoutingWitness, - payload: &[FieldValue], - default_span: SourceSpan, -) -> Result, Diagnostic> { - // IntegerPrimitive field order: target_name, algebra, carrier, range_*, is_copy, overflow - if payload.len() < 5 { - return Ok(None); - } - let FieldValue::Variant { - constructor: algebra_ctor, - .. - } = &payload[1] - else { - return Err(malformed_integer_range_fact( - "rust_pilot_primitives IntegerPrimitive `algebra` must be a variant value".to_string(), - default_span.clone(), - )); - }; - let FieldValue::Variant { - constructor: carrier_ctor, - .. - } = &payload[2] - else { - return Err(malformed_integer_range_fact( - "rust_pilot_primitives IntegerPrimitive `carrier` must be a variant value".to_string(), - default_span.clone(), - )); - }; - if *algebra_ctor != witness.algebra_variant_ty || *carrier_ctor != witness.carrier_variant_ty { - return Ok(None); - } - - let range = (|| { - let min_decimal = literal_string(payload.get(3)?)?; - let max_decimal = literal_string(payload.get(4)?)?; - let min = min_decimal.parse().ok()?; - let max = max_decimal.parse().ok()?; - if min > max { - return None; - } - Some(IntervalInt::ExactInterval { - target_name: literal_string(payload.first()?)?, - min_decimal, - max_decimal, - min, - max, - }) - })(); - - Ok(Some(PilotIntegerMatch { - range, - span: default_span, - })) -} - -fn literal_string(value: &FieldValue) -> Option { - match value { - FieldValue::Literal(LiteralBits::String(value)) => Some(value.clone()), - _ => None, - } -} - -pub(crate) fn literal_bigint_at(dag: &Dag, port: PortId) -> Option { - let s = match dag.resolve_producer_opt(&port)? { - Behavior::Value(value) => match &value.data { - LiteralBits::Int(s) => s.as_str(), - _ => return None, - }, - _ => return None, - }; - BigInt::from_str(s).ok() -} - -pub(crate) fn int_literal_fits_expected_type( - dag: &Dag, - literal: &BigInt, - expected: DeclarationId, -) -> Result, Diagnostic> { - match integer_range_for_decl(dag, expected) { - IntegerRangeLookup::Found(bound) => Ok(Some(bound.contains_bigint(literal))), - IntegerRangeLookup::Missing => Ok(None), - IntegerRangeLookup::Invalid(diag) => Err(diag), - } -} - -/// Build [`Diagnostic::MagnitudeOutOfRange`] from **exact** decimal range facts only. -/// -/// For a bound that may be [`IntervalInt::Unbounded`], use [`magnitude_out_of_range_for_interval`] -/// instead — this function does not accept [`IntervalInt`]. -pub(crate) fn magnitude_out_of_range( - literal: &BigInt, - expected: TypeShape, - facts: ExactIntIntervalFacts, - span: SourceSpan, -) -> Diagnostic { - Diagnostic::MagnitudeOutOfRange { - literal: literal.to_string(), - target: facts.target_name, - range_min_inclusive: facts.min_decimal, - range_max_inclusive: facts.max_decimal, - expected, - span, - fixes: Vec::new(), - } -} - -/// OOB diagnostic when the only available model is [`IntervalInt`] (e.g. from -/// [`integer_range_for_decl`]). Unbounded domains never produce `MagnitudeOutOfRange` for -/// finite literals; if that combination appears, fail closed without `unreachable!`. -pub(crate) fn magnitude_out_of_range_for_interval( - literal: &BigInt, - expected: TypeShape, - bound: IntervalInt, - span: SourceSpan, -) -> Diagnostic { - match bound.exact_interval_facts() { - Some(facts) => magnitude_out_of_range(literal, expected, facts, span), - None => Diagnostic::ResolveError { - name: "internal: integer literal failed range check but target has no exact interval facts" - .to_string(), - span, - fixes: Vec::new(), - }, - } -} - -fn malformed_integer_range_fact(message: String, span: SourceSpan) -> Diagnostic { - Diagnostic::MalformedIntegerRangeFact { - message, - span, - fixes: Vec::new(), - } -} - -/// Bootstrap-only gate: walk **every** `IntegerPrimitive` row in -/// `rust_pilot_primitives` and fail closed if any row is structurally -/// ill-formed, range strings do not parse as decimal `BigInt`, `min > max`, -/// or `(algebra, carrier)` witness pairs collide. -/// -/// Call this once when constructing the extdeps-including bootstrapped `Dag` -/// so drift or corruption in the pilot list surfaces at `Dag::new()`, -/// not only when a particular std type is queried. -pub(crate) fn validate_rust_pilot_integer_primitives(dag: &mut Dag) { - // R3 Phase B (Director Path A RATIFIED at gunbc#1739 #issuecomment-4392731264; - // Option (ii) at #issuecomment-4393145631; Phase B-1 at commit `59511503e`): - // pilot carries 10 IntegerPrimitive rows (i8..i64, i128, u8..u64, u128). - // u128 was unblocked by Phase A `IntervalInt::ExactInterval` BigInt host - // repr widening (commit `e7ba022c6`); the prior `T-Int128 Slice B1` / - // "Slice B2" deferral marker is resolved. - const EXPECTED_INTEGER_ROWS: usize = 10; - const INTEGER_PRIMITIVE_FIELD_COUNT: usize = 7; - - enum PilotListSnapshot { - List(Vec), - MissingBody, - NotList, - } - - // Authority file for span when the declaration is absent (extdeps fixture). - const RUST_PILOT_PRIMITIVES_AUTHORITY: &str = "dsl/extdeps/languages/rust/primitives.dag"; - - let (default_span, pilot_elements) = { - let Some(pilot) = dag.rust_pilot_primitives() else { - dag.attach_diagnostic(malformed_integer_range_fact( - "bootstrap: `rust_pilot_primitives` is missing from the extdeps fixture; \ - integer range authority is unavailable (expected extdeps `primitives.dag` load)" - .to_string(), - SourceSpan::new(RUST_PILOT_PRIMITIVES_AUTHORITY, 0, 0), - )); - return; - }; - let sp = pilot.span.clone(); - let snap = match pilot.value_body.as_ref() { - None => PilotListSnapshot::MissingBody, - Some(ValueBody::List(els)) => PilotListSnapshot::List(els.clone()), - Some(_) => PilotListSnapshot::NotList, - }; - (sp, snap) - }; - - let elements: &[FieldValue] = match &pilot_elements { - PilotListSnapshot::List(els) => els, - PilotListSnapshot::MissingBody => { - dag.attach_diagnostic(malformed_integer_range_fact( - "bootstrap: rust_pilot_primitives is missing a value body".to_string(), - default_span, - )); - return; - } - PilotListSnapshot::NotList => { - dag.attach_diagnostic(malformed_integer_range_fact( - "bootstrap: rust_pilot_primitives must be ValueBody::List".to_string(), - default_span, - )); - return; - } - }; - - let Some(integer_ctor) = rust_primitive_integer_variant_ty(dag) else { - dag.attach_diagnostic(malformed_integer_range_fact( - "bootstrap: RustPrimitive.IntegerPrimitive variant is unavailable".to_string(), - default_span, - )); - return; - }; - - let Some(ord_algebra) = disj_variant_payload_ty(dag, "IntegerAlgebra", "OrderedRingAlgebra") - else { - dag.attach_diagnostic(malformed_integer_range_fact( - "bootstrap: IntegerAlgebra.OrderedRingAlgebra is unavailable for pilot validation" - .to_string(), - default_span, - )); - return; - }; - let Some(sem_algebra) = disj_variant_payload_ty(dag, "IntegerAlgebra", "SemiringAlgebra") - else { - dag.attach_diagnostic(malformed_integer_range_fact( - "bootstrap: IntegerAlgebra.SemiringAlgebra is unavailable for pilot validation" - .to_string(), - default_span, - )); - return; - }; - let mut allowed_carriers: HashSet = HashSet::new(); - for label in [ - "ByteCarrier", - "Word16Carrier", - "Word32Carrier", - "Word64Carrier", - "Word128Carrier", - ] { - if let Some(c) = disj_variant_payload_ty(dag, "TargetCarrier", label) { - allowed_carriers.insert(c); - } - } - if allowed_carriers.is_empty() { - dag.attach_diagnostic(malformed_integer_range_fact( - "bootstrap: no TargetCarrier word variant payload types for pilot validation" - .to_string(), - default_span, - )); - return; - } - - let mut witnesses: HashSet<(DeclarationId, DeclarationId)> = HashSet::new(); - let mut integer_rows: usize = 0; - - for element in elements { - let FieldValue::Variant { - constructor, - payload, - } = element - else { - continue; - }; - if *constructor != integer_ctor { - continue; - } - integer_rows += 1; - - if payload.len() < INTEGER_PRIMITIVE_FIELD_COUNT { - dag.attach_diagnostic(malformed_integer_range_fact( - format!( - "rust_pilot_primitives IntegerPrimitive row has {} fields; expected {}", - payload.len(), - INTEGER_PRIMITIVE_FIELD_COUNT - ), - default_span.clone(), - )); - continue; - } - - let FieldValue::Variant { - constructor: algebra_ctor, - .. - } = &payload[1] - else { - dag.attach_diagnostic(malformed_integer_range_fact( - "rust_pilot_primitives IntegerPrimitive `algebra` must be a variant value" - .to_string(), - default_span.clone(), - )); - continue; - }; - if *algebra_ctor != ord_algebra && *algebra_ctor != sem_algebra { - dag.attach_diagnostic(malformed_integer_range_fact( - "rust_pilot_primitives IntegerPrimitive `algebra` must be OrderedRingAlgebra or SemiringAlgebra (variant payload type id)".to_string(), - default_span.clone(), - )); - continue; - } - - let FieldValue::Variant { - constructor: carrier_ctor, - .. - } = &payload[2] - else { - dag.attach_diagnostic(malformed_integer_range_fact( - "rust_pilot_primitives IntegerPrimitive `carrier` must be a variant value" - .to_string(), - default_span.clone(), - )); - continue; - }; - if !allowed_carriers.contains(carrier_ctor) { - dag.attach_diagnostic(malformed_integer_range_fact( - "rust_pilot_primitives IntegerPrimitive `carrier` must be a word TargetCarrier (Byte/Word16/Word32/Word64/Word128) variant payload type id".to_string(), - default_span.clone(), - )); - continue; - } - - if literal_string(&payload[0]).is_none() { - dag.attach_diagnostic(malformed_integer_range_fact( - "rust_pilot_primitives IntegerPrimitive `target_name` must be a string literal" - .to_string(), - default_span.clone(), - )); - continue; - } - - let (min_s, max_s) = match (literal_string(&payload[3]), literal_string(&payload[4])) { - (Some(a), Some(b)) => (a, b), - _ => { - dag.attach_diagnostic(malformed_integer_range_fact( - "rust_pilot_primitives IntegerPrimitive range bounds must be string literals" - .to_string(), - default_span.clone(), - )); - continue; - } - }; - - if !matches!(payload[5], FieldValue::Literal(LiteralBits::Bool(_))) { - dag.attach_diagnostic(malformed_integer_range_fact( - "rust_pilot_primitives IntegerPrimitive `is_copy` must be a bool literal" - .to_string(), - default_span.clone(), - )); - continue; - } - - if !matches!(&payload[6], FieldValue::Variant { .. }) { - dag.attach_diagnostic(malformed_integer_range_fact( - "rust_pilot_primitives IntegerPrimitive `overflow` must be a variant value" - .to_string(), - default_span.clone(), - )); - continue; - } - - let (min_n, max_n) = match (min_s.parse::(), max_s.parse::()) { - (Ok(mn), Ok(mx)) => (mn, mx), - _ => { - dag.attach_diagnostic(malformed_integer_range_fact( - format!( - "rust_pilot_primitives IntegerPrimitive range [{min_s}, {max_s}] must parse as a decimal integer" - ), - default_span.clone(), - )); - continue; - } - }; - if min_n > max_n { - dag.attach_diagnostic(malformed_integer_range_fact( - format!( - "rust_pilot_primitives IntegerPrimitive range order invalid: min {min_s} > max {max_s}" - ), - default_span.clone(), - )); - continue; - } - - if !witnesses.insert((*algebra_ctor, *carrier_ctor)) { - dag.attach_diagnostic(malformed_integer_range_fact( - format!( - "duplicate rust_pilot_primitives IntegerPrimitive (algebra, carrier) witness: ({algebra_ctor:?}, {carrier_ctor:?})" - ), - default_span.clone(), - )); - } - } - - if integer_rows != EXPECTED_INTEGER_ROWS { - dag.attach_diagnostic(malformed_integer_range_fact( - format!( - "rust_pilot_primitives must list exactly {EXPECTED_INTEGER_ROWS} IntegerPrimitive rows (pilot int scope); found {integer_rows}" - ), - default_span, - )); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::types::TypeShape; - - #[test] - fn magnitude_out_of_range_accepts_only_exact_int_interval_facts() { - let dag = Dag::new(); - let u8_decl = dag.declaration_by_name("UInt8").expect("UInt8").id; - let d = magnitude_out_of_range( - &BigInt::from(256), - TypeShape::new(u8_decl), - ExactIntIntervalFacts { - target_name: "u8".to_string(), - min_decimal: "0".to_string(), - max_decimal: "255".to_string(), - }, - SourceSpan::new("t.v3", 0, 0), - ); - assert!( - matches!( - d, - Diagnostic::MagnitudeOutOfRange { - ref literal, - ref target, - .. - } if literal == "256" && target == "u8" - ), - "expected MagnitudeOutOfRange, got {d:?}" - ); - } - - #[test] - fn magnitude_out_of_range_unbounded_target_fails_closed_with_resolve_error() { - let dag = Dag::new(); - let int_decl = dag.declaration_by_name("Int").expect("Int in bootstrap").id; - let d = magnitude_out_of_range_for_interval( - &BigInt::from(0), - TypeShape::new(int_decl), - IntervalInt::Unbounded, - SourceSpan::new("t.v3", 0, 0), - ); - assert!( - matches!(d, Diagnostic::ResolveError { .. }), - "expected fail-closed ResolveError, got {d:?}" - ); - } - - #[test] - fn interval_int_unbounded_accepts_sample_literals() { - assert!(IntervalInt::Unbounded.contains_bigint(&BigInt::from(i64::MIN))); - assert!(IntervalInt::Unbounded.contains_bigint(&BigInt::from(i64::MAX))); - } - - #[test] - fn int128_witness_matches_i128_pilot_row_constructors() { - // T-Int128 Slice B1: signed Int128 -> i128 pilot row via Word128Carrier. - let dag = Dag::new(); - assert!( - dag.diagnostics().is_empty(), - "bootstrap diagnostics: {:?}", - dag.diagnostics() - ); - let int128 = dag - .declaration_by_name("Int128") - .expect("Int128 in bootstrap") - .id; - let witness = integer_routing_witness_for_decl(&dag, int128).expect("Int128 witness"); - let pilot = dag.rust_pilot_primitives().expect("pilot"); - let ValueBody::List(elements) = pilot.value_body.as_ref().expect("list body") else { - panic!("expected list"); - }; - let integer_primitive_ctor = rust_primitive_integer_variant_ty(&dag).expect("ctor"); - let mut matched = 0usize; - for element in elements { - let FieldValue::Variant { - constructor, - payload, - } = element - else { - continue; - }; - if *constructor != integer_primitive_ctor { - continue; - } - let FieldValue::Variant { constructor: a, .. } = &payload[1] else { - continue; - }; - let FieldValue::Variant { constructor: c, .. } = &payload[2] else { - continue; - }; - if *a == witness.algebra_variant_ty && *c == witness.carrier_variant_ty { - matched += 1; - } - } - assert_eq!( - matched, 1, - "exactly one pilot IntegerPrimitive row for Int128 witness" - ); - } - - #[test] - fn int128_range_lookup_accepts_all_i64_magnitudes() { - // i128 row's range covers all i64 magnitudes; reconciliation passes - // typical literals through `contains_bigint`. - let dag = Dag::new(); - let int128 = dag - .declaration_by_name("Int128") - .expect("Int128 in bootstrap") - .id; - match integer_range_for_decl(&dag, int128) { - IntegerRangeLookup::Found(bound) => { - assert!(bound.contains_bigint(&BigInt::from(i64::MIN))); - assert!(bound.contains_bigint(&BigInt::from(0))); - assert!(bound.contains_bigint(&BigInt::from(i64::MAX))); - } - IntegerRangeLookup::Missing => panic!("expected Found range for Int128, got Missing"), - IntegerRangeLookup::Invalid(d) => { - panic!("expected Found range for Int128, got Invalid: {d:?}") - } - } - } - - #[test] - fn uint8_witness_matches_u8_pilot_row_constructors() { - let dag = Dag::new(); - assert!( - dag.diagnostics().is_empty(), - "bootstrap diagnostics: {:?}", - dag.diagnostics() - ); - let uint8 = dag - .declaration_by_name("UInt8") - .expect("UInt8 in bootstrap") - .id; - let witness = integer_routing_witness_for_decl(&dag, uint8).expect("UInt8 witness"); - let pilot = dag.rust_pilot_primitives().expect("pilot"); - let ValueBody::List(elements) = pilot.value_body.as_ref().expect("list body") else { - panic!("expected list"); - }; - let integer_primitive_ctor = rust_primitive_integer_variant_ty(&dag).expect("ctor"); - let mut matched = 0usize; - for element in elements { - let FieldValue::Variant { - constructor, - payload, - } = element - else { - continue; - }; - if *constructor != integer_primitive_ctor { - continue; - } - let FieldValue::Variant { constructor: a, .. } = &payload[1] else { - continue; - }; - let FieldValue::Variant { constructor: c, .. } = &payload[2] else { - continue; - }; - if *a == witness.algebra_variant_ty && *c == witness.carrier_variant_ty { - matched += 1; - } - } - assert_eq!( - matched, 1, - "exactly one pilot IntegerPrimitive row for UInt8 witness" - ); - } -} diff --git a/src/v3/compiler/src/lib.rs b/src/v3/compiler/src/lib.rs index fbe8aef6a17..f380e042e57 100644 --- a/src/v3/compiler/src/lib.rs +++ b/src/v3/compiler/src/lib.rs @@ -16,13 +16,21 @@ // payload. pub mod cementing_dispatch; +<<<<<<< HEAD pub use cementing_dispatch::gunbc_ci; pub use cementing_dispatch::integration_rs_wiring_scan; pub use cementing_dispatch::r3_gate_87_cementing_regen_runner_suites; pub mod complexity_lattice; +======= +pub mod complexity_lattice { + include!("complexity_lattice_generated.rs"); +} +>>>>>>> e79e73838 (WIP: PB-0 cycle-4 NON_TEST retirement (msg_5d7f3491): 7 paths — complexity_la) pub mod dag; pub mod diagnostics; -mod enforced_lens_application; +mod enforced_lens_application { + include!("enforced_lens_application_generated.rs"); +} pub use enforced_lens_application::check_enforced_lens_applications; // Gate #58 integration receipts (`tests/integration/t_gate_58_apply_lens_self_application_test.rs`) // need the helpers below as **`pub`**: the consolidated integration test binary is a separate @@ -33,7 +41,16 @@ pub use enforced_lens_application::check_enforced_lens_applications; pub use enforced_lens_application::gate_58_test_parse_timing_budget_violation_max_ns_pair; #[doc(hidden)] pub use enforced_lens_application::gate_58_test_raise_modeled_ci_timing_measurement_duration_ns; +<<<<<<< HEAD pub mod pb_method_template_projection; +======= +pub mod integration_rs_wiring_scan; +pub mod lens_t_las_carrier; +pub mod pb_method_template_projection { + include!("pb_method_template_projection_generated.rs"); +} +pub mod r3_gate_87_cementing_regen_runner_suites; +>>>>>>> e79e73838 (WIP: PB-0 cycle-4 NON_TEST retirement (msg_5d7f3491): 7 paths — complexity_la) mod regen_bootstrap_emit; pub mod regen_tokenize; @@ -52,7 +69,9 @@ pub mod generated_files { pub mod emit; pub mod emit_rust; -pub mod emit_rust_bin_shim; +pub mod emit_rust_bin_shim { + include!("emit_rust_bin_shim_generated.rs"); +} pub mod omni_shape_b_openapi; pub mod process_exit; pub mod realization_cost { @@ -3763,7 +3782,9 @@ pub mod evaluator { } } } -mod int_literal_ranges; +mod int_literal_ranges { + include!("int_literal_ranges_generated.rs"); +} /// T-LensAPI D1: bounded lens interpreter over substrate-shaped [`FieldValue`] /// (R3 gate #5: `lens_apply.rs` retired; body in `lens_declaration_apply_body.txt`, included here /// for SG-0 census path retirement until PB-Runtime / Row-4). @@ -4985,7 +5006,9 @@ pub use bootstrap_regen_fresh::{ compile_full_bootstrap_without_parse_surface_dag_from_std_seed, compile_std_bootstrap_dag, }; -mod cost_basis_declaration; +mod cost_basis_declaration { + include!("cost_basis_declaration_generated.rs"); +} mod dimension; mod infer; diff --git a/src/v3/compiler/src/pb_method_template_projection.rs b/src/v3/compiler/src/pb_method_template_projection.rs deleted file mode 100644 index 4b6ab40c29e..00000000000 --- a/src/v3/compiler/src/pb_method_template_projection.rs +++ /dev/null @@ -1,1447 +0,0 @@ -//! PB-Bootstrap-Process consumer hook: project `MethodTemplateContract` rows -//! from the full bootstrap `Dag`. -//! -//! Implements **R3 row 85 / PB #1560 Gap 4** per the decision in -//! `docs/decisions/r3-row85-method-template-read-surface.md`. The single -//! row authority remains `src/v3/std/{rust,python,go}_method_template_contracts.dag`; -//! this module exposes a target-keyed structural projection of those rows -//! over the lowered bootstrap `Dag` so PB-zero / v2-retirement consumers can -//! select rows by target + `MethodRef` without: -//! -//! - importing `v3.std.*` into v2 crates (Gap 4 acceptance A1), -//! - introducing a second `Map` template-text authority (A2), -//! - copying any template strings into v2 sources (A3). -//! -//! All five `MethodTemplateContract` fields (`dag_method`, `runtime_template`, -//! `emit_template`, `wraps_result`, `placeholder_convention`) are preserved -//! by typed Rust mirrors below (A4). `emit_template` and -//! `placeholder_convention` are sum carriers; their substrate identity is -//! preserved as constructor-tagged enums, not flattened to strings. -//! -//! Gap 4 only — `LanguageSpec.method_templates` rewrite (Gap 5) and v2 leaf -//! emit migration are sequenced strictly after this hook lands. No v2 -//! consumer is migrated by this module. - -use std::collections::HashMap; - -use crate::dag::{Dag, DeclarationId, FieldValue, LiteralBits, TypeConnective, ValueBody}; - -/// Target language whose `MethodTemplateContract` row list is being projected. -/// -/// 🟢 TERMINAL at the per-target row-list scope. The three inhabitants -/// mirror the three closed list-declaration authorities under -/// `src/v3/std/{rust,python,go}_method_template_contracts.dag`. New -/// targets land by adding a row-list authority + a constructor here -/// together; no other dissolution path applies. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum MethodTemplateTarget { - Rust, - Python, - Go, -} - -impl MethodTemplateTarget { - /// Per-target list-declaration name in the bootstrap `Dag`. The strings - /// here mirror the data declaration names in - /// `src/v3/std/{rust,python,go}_method_template_contracts.dag` and are - /// the *names of structural declarations*, not template text. - fn list_declaration_name(self) -> &'static str { - match self { - MethodTemplateTarget::Rust => "rust_method_template_contracts", - MethodTemplateTarget::Python => "python_method_template_contracts", - MethodTemplateTarget::Go => "go_method_template_contracts", - } - } -} - -/// Projected `MethodEmitTemplate` (substrate sum at -/// `src/v3/std/emit_model.dag:469-474`). -/// -/// 🟢 TERMINAL at target method-emission-template scope, inheriting the -/// terminality of the substrate carrier (`MethodEmitTemplate` is marked -/// 🟢 TERMINAL at `emit_model.dag:453`). The two-variant split between -/// inline-lambda and function-reference renderings is the closed semantic -/// case the substrate already locks; new variants here would mean the -/// substrate carrier itself moved. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum MethodEmitTemplateProjection { - Single { - template: String, - }, - HigherOrder { - inline_template: String, - fn_ref_template: String, - }, -} - -/// Projected `PlaceholderConvention` (substrate sum at -/// `src/v3/std/emit_model.dag:449-451`). -/// -/// 🟢 TERMINAL: nullary two-inhabitant sum (`IndexedArgs | NamedArg`) that -/// mirrors the closed substrate carrier. New conventions land by editing -/// the substrate sum first; this projection follows. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PlaceholderConventionProjection { - IndexedArgs, - NamedArg, -} - -/// Projected single `MethodTemplateContract` row. -/// -/// `dag_method` is the `DeclarationId` of the `MethodDeclaration` that the -/// row's `MethodRef.decl` field references (the typed, fail-closed substrate -/// identity for the method). Consumers select rows by target + -/// `dag_method` to recover the `(target, method)` template pair. -#[derive(Debug, Clone)] -pub struct MethodTemplateContractRow { - pub dag_method: DeclarationId, - pub runtime_template: String, - pub emit_template: MethodEmitTemplateProjection, - pub wraps_result: bool, - pub placeholder_convention: PlaceholderConventionProjection, -} - -/// Fail-closed projection error. Every observable mismatch between the -/// bootstrap `Dag` shape and the `MethodTemplateContract` substrate carrier -/// surfaces here as a typed value (per `INVARIANTS.md` C-8). -/// -/// 🟢 TERMINAL at the projection's bootstrap-mismatch scope. Each variant -/// names a specific structural class the row authorities cannot satisfy; -/// new variants only land if substrate shape grows a new sub-fact (which -/// would itself follow §P1). The carrier is therefore a closed -/// substrate-mismatch enumeration, not a transitional grab-bag. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum MethodTemplateProjectionError { - /// Per-target row-list declaration is absent from the bootstrap `Dag`. - /// Should be impossible if `BOOTSTRAP_FIXTURE_PATH_KEYS` (`bootstrap.rs`) - /// stays in lockstep with `bootstrap_fixture_authority` and - /// `regen_bootstrap --verify` is green; surfaced rather than panicked - /// so consumers can act on it. - ListDeclarationMissing { name: &'static str }, - /// List declaration has no value body (i.e., not a `data` declaration). - ListDeclarationLacksValueBody { name: &'static str }, - /// List declaration's value body is not `ValueBody::List` — violates the - /// `bootstrap_method_template_contracts_lower_to_list_with_empty_diagnostics` - /// gate per the row-85 decision doc. - ListDeclarationValueBodyNotList { name: &'static str }, - /// Row at the given index in the named list is not a record literal. - RowNotRecord { - list: &'static str, - row_index: usize, - }, - /// Row is a declaration reference, but the referenced declaration is not - /// present in the bootstrap `Dag`. - RowReferenceMissing { - list: &'static str, - row_index: usize, - decl_id: DeclarationId, - }, - /// Row is a declaration reference, but the referenced declaration is not a - /// `data` declaration carrying a structural `MethodTemplateContract` body. - RowReferenceNotStructural { - list: &'static str, - row_index: usize, - decl_id: DeclarationId, - }, - /// Row is a declaration reference, but the referenced declaration does - /// not instantiate the `MethodTemplateContract` carrier. A structural - /// lookalike row is still not a contract row authority. - RowReferenceNotMethodTemplateContract { - list: &'static str, - row_index: usize, - decl_id: DeclarationId, - }, - /// Row is missing one of the five required fields. - RowMissingField { - list: &'static str, - row_index: usize, - field: &'static str, - }, - /// Row carries a duplicate field — two entries share a label. The - /// substrate-side `MethodTemplateContract` (and `MethodRef`) carriers - /// declare each field exactly once; duplicates would mean two - /// authorities for the same row coordinate (P2 single-authority). - RowDuplicateField { - list: &'static str, - row_index: usize, - record: &'static str, - field: &'static str, - first_field_index: usize, - duplicate_field_index: usize, - }, - /// Row carries a field whose label is not one of the closed schema - /// (`MethodTemplateContract` = five fields; `MethodRef` = one field). - /// Extra substrate fields land via the substrate carrier, not via - /// projection-side acceptance, so unknown labels surface here rather - /// than being silently ignored (P2 / C-8). - RowUnknownField { - list: &'static str, - row_index: usize, - record: &'static str, - field: String, - field_index: usize, - }, - /// `dag_method` field is not the `MethodRef { decl }` record shape. - DagMethodNotMethodRefRecord { - list: &'static str, - row_index: usize, - }, - /// `MethodRef.decl` is not a typed substrate reference. - MethodRefDeclNotReference { - list: &'static str, - row_index: usize, - }, - /// `MethodRef.decl` references a declaration that does not satisfy - /// the substrate-side `MethodDeclaration` *data-binding* contract - /// (`src/v3/std/methods.dag` + `dsl/std/methods.dag`). Mirrors the - /// Stratum A registry gate - /// (`src/v3/grounding_tests/src/stratum_a.rs::method_registry_name`): - /// the binding must (a) instantiate `MethodDeclaration`, (b) carry - /// a `Structural` value body, (c) have a closed single-`name` field - /// schema, (d) and the `name` value must be a string literal. The - /// `reason` payload identifies which sub-check failed. - MethodRefDeclNotMethodDeclaration { - list: &'static str, - row_index: usize, - decl_id: DeclarationId, - reason: MethodDeclarationBindingViolation, - }, - /// `method_template_contract_row` was called with a `dag_method` - /// `DeclarationId` that does not satisfy the - /// `MethodDeclaration` data-binding contract (same shape and - /// `reason` taxonomy as `MethodRefDeclNotMethodDeclaration`). The - /// helper validates at entry so callers cannot conflate - /// "valid method, no row" with "caller handed in a non-method - /// declaration." - LookupKeyNotMethodDeclaration { - decl_id: DeclarationId, - reason: MethodDeclarationBindingViolation, - }, - /// `MethodDeclaration` itself is missing from the bootstrap `Dag`. - /// Should be impossible if the std fixtures lower cleanly; surfaced - /// rather than panicked so consumers can act on it. - MethodDeclarationCarrierMissing, - /// `runtime_template` field is not a string literal. - RuntimeTemplateNotString { - list: &'static str, - row_index: usize, - }, - /// `wraps_result` field is not a bool literal. - WrapsResultNotBool { - list: &'static str, - row_index: usize, - }, - /// `placeholder_convention` field is not a substrate variant constructor. - PlaceholderConventionNotVariant { - list: &'static str, - row_index: usize, - }, - /// `placeholder_convention` constructor name is unknown. - PlaceholderConventionUnknown { - list: &'static str, - row_index: usize, - constructor: Option, - }, - /// `placeholder_convention` is a nullary sum (`IndexedArgs | NamedArg`), - /// but the variant payload is non-empty. Mirrors the substrate-side - /// canonical-row check - /// `placeholder_convention_canonical_rejects_payload_for_nullary_indexed_args` - /// in `src/v3/grounding_tests/src/stratum_a.rs`. - PlaceholderConventionPayloadNotEmpty { - list: &'static str, - row_index: usize, - constructor: &'static str, - payload_len: usize, - }, - /// `emit_template` field is not a substrate variant constructor. - EmitTemplateNotVariant { - list: &'static str, - row_index: usize, - }, - /// `emit_template` constructor name is unknown. - EmitTemplateUnknownConstructor { - list: &'static str, - row_index: usize, - constructor: Option, - }, - /// `emit_template` `SingleTemplate` payload shape mismatch (expected one - /// string-typed `template` field). - EmitTemplateSinglePayloadShape { - list: &'static str, - row_index: usize, - }, - /// `emit_template` `HigherOrderTemplates` payload shape mismatch - /// (expected two string-typed fields: `inline_template`, `fn_ref_template`). - EmitTemplateHigherOrderPayloadShape { - list: &'static str, - row_index: usize, - }, - /// The `(target, dag_method)` direct lookup helper found more than one row - /// for the same `MethodDeclaration` in the per-target list. Per-target - /// uniqueness by `dag_method` is the substrate-side claim - /// (`method_template_contract_per_target_dag_method_unique`); the helper - /// surfaces a violation rather than silently selecting the first row - /// (P2 single-authority + P3 fail-closed at the public lookup boundary). - DuplicateMethodTemplateRow { - list: &'static str, - first_row_index: usize, - duplicate_row_index: usize, - }, -} - -/// Granular reason for a `MethodDeclaration` data-binding contract failure. -/// -/// 🟢 TERMINAL at the substrate-side data-binding contract scope -/// (`src/v3/std/methods.dag` + Stratum A `method_registry_name`). Each -/// variant maps one of the four sub-checks the contract requires; the -/// substrate-side contract is closed (`MethodDeclaration` carries a -/// single `name: String` field), so this enumeration is closed too. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum MethodDeclarationBindingViolation { - /// Connective is not `TypeConnective::Instantiation { template, .. }`. - /// Rules out type / sum / record declarations. - ConnectiveNotInstantiation, - /// `Instantiation` connective, but `template` does not match - /// `MethodDeclaration`'s declaration id. Rules out instantiations of - /// unrelated targets. - InstantiationTemplateNotMethodDeclaration, - /// `value_body` is `None`. Rules out type aliases of the form - /// `type Foo = MethodDeclaration`. - ValueBodyMissing, - /// `value_body` is `Some(_)` but not `ValueBody::Structural`. Rules - /// out `List` / `Map` / scalar bodies that would be wrong-shaped for - /// a `MethodDeclaration` data binding. - ValueBodyNotStructural, - /// `Structural` body, but its field set does not match the closed - /// `MethodDeclaration` schema (single `name` field). Mirrors - /// `enforce_closed_record_schema` in Stratum A. - StructuralFieldsNotClosedNameOnly { observed_labels: Vec }, - /// Closed-schema match, but the `name` field's `FieldValue` is not - /// `Literal(LiteralBits::String(_))`. - NameNotStringLiteral, -} - -/// Project the `MethodTemplateContract` rows for `target` from the full -/// bootstrap `Dag` snapshot. -/// -/// The expected source for `dag` is `generated_full_bootstrap_dag()` (or its -/// equivalent under PB-1-e dissolution-first per #1558 — see the -/// row-85 decision doc §"Non-Fork Ratchet"). Calling on a partially-built -/// `Dag` that is missing the per-target list authorities surfaces -/// [`MethodTemplateProjectionError::ListDeclarationMissing`] rather than -/// panicking. -/// -/// **Boundary contract:** this is the canonical PB-zero consumer hook for -/// row text. v2-side migrations consume `Vec` and -/// must not store or carry parallel `Map` template tables; -/// the structural carrier is the typed projection above. -pub fn method_template_contract_rows( - dag: &Dag, - target: MethodTemplateTarget, -) -> Result, MethodTemplateProjectionError> { - let list_name = target.list_declaration_name(); - let decl = dag - .declaration_by_name(list_name) - .ok_or(MethodTemplateProjectionError::ListDeclarationMissing { name: list_name })?; - let body = decl - .value_body - .as_ref() - .ok_or(MethodTemplateProjectionError::ListDeclarationLacksValueBody { name: list_name })?; - let ValueBody::List(rows) = body else { - return Err( - MethodTemplateProjectionError::ListDeclarationValueBodyNotList { name: list_name }, - ); - }; - - let projected: Vec = rows - .iter() - .enumerate() - .map(|(row_index, row)| project_row(dag, list_name, row_index, row)) - .collect::>()?; - - // Per-target uniqueness by `dag_method` is the substrate-side claim - // (`method_template_contract_per_target_dag_method_unique`). Enforce it - // at the canonical row-list boundary so v2-retirement consumers that - // build their own keyed tables from the projection cannot silently - // observe two authorities for the same `(target, MethodRef)` pair. - let mut first_seen: HashMap = HashMap::with_capacity(projected.len()); - for (index, row) in projected.iter().enumerate() { - if let Some(prior) = first_seen.get(&row.dag_method) { - return Err(MethodTemplateProjectionError::DuplicateMethodTemplateRow { - list: list_name, - first_row_index: *prior, - duplicate_row_index: index, - }); - } - first_seen.insert(row.dag_method, index); - } - - Ok(projected) -} - -/// Direct `(target, dag_method)` lookup helper for Gap-5 / leaf-emit -/// consumers. Returns the row whose `dag_method` matches `dag_method`, or -/// `None` if the per-target list does not contain a row keyed by that -/// `MethodDeclaration`. -/// -/// **Fail-closed on non-method keys.** `dag_method` must instantiate -/// `MethodDeclaration` — the helper validates this at entry so a caller -/// cannot conflate two distinct states: "valid `MethodDeclaration` with -/// no row for this target" (returns `Ok(None)`) versus "caller supplied -/// a declaration that is not a `MethodDeclaration` at all" (returns -/// [`MethodTemplateProjectionError::LookupKeyNotMethodDeclaration`]). -/// This mirrors the per-row `MethodRef.decl` check the projection -/// already enforces; until `DeclarationRef` -/// refinement-typing lands, this is the only place the boundary can -/// hold. -/// -/// Other typed projection failures — including -/// [`MethodTemplateProjectionError::DuplicateMethodTemplateRow`], which is -/// enforced at the canonical row-list boundary in -/// [`method_template_contract_rows`] — bubble through. -pub fn method_template_contract_row( - dag: &Dag, - target: MethodTemplateTarget, - dag_method: DeclarationId, -) -> Result, MethodTemplateProjectionError> { - let method_declaration_id = method_declaration_carrier_id(dag)?; - if let Err(reason) = - validate_method_declaration_data_binding(dag, dag_method, method_declaration_id) - { - return Err( - MethodTemplateProjectionError::LookupKeyNotMethodDeclaration { - decl_id: dag_method, - reason, - }, - ); - } - let rows = method_template_contract_rows(dag, target)?; - Ok(rows.into_iter().find(|row| row.dag_method == dag_method)) -} - -/// Locate the `MethodDeclaration` carrier declaration in the bootstrap -/// `Dag` once. Surfaces [`MethodTemplateProjectionError::MethodDeclarationCarrierMissing`] -/// when the carrier is absent rather than panicking. -pub(crate) fn method_declaration_carrier_id( - dag: &Dag, -) -> Result { - Ok(dag - .declaration_by_name("MethodDeclaration") - .ok_or(MethodTemplateProjectionError::MethodDeclarationCarrierMissing)? - .id) -} - -/// Validate that `decl_id` references a `MethodDeclaration` *data binding* -/// per the substrate-side contract (`src/v3/std/methods.dag` + -/// `dsl/std/methods.dag`). -/// -/// Mirrors `src/v3/grounding_tests/src/stratum_a.rs::method_registry_name` -/// so projection-side and Stratum A-side enforcement of the same gate -/// stay in lockstep. Four sub-checks must all pass: -/// -/// 1. **Connective is `Instantiation`.** Rules out type / sum / record -/// declarations. -/// 2. **`Instantiation.template == MethodDeclaration`.** Rules out -/// instantiations of unrelated targets. -/// 3. **`value_body` is `Some(ValueBody::Structural { .. })`.** Rules -/// out type aliases (`type Foo = MethodDeclaration`, no value body) -/// and wrong-shaped data bindings (`List` / `Map` / scalar bodies). -/// 4. **`Structural` fields are exactly `[name]` with a string literal -/// value.** Closed schema check — mirrors -/// `enforce_closed_record_schema` in Stratum A. Rules out extra -/// fields, missing `name`, duplicate `name`, and non-string-literal -/// `name` values. -/// -/// On success returns the projected method name (`name` field's string -/// literal). On failure returns the typed -/// [`MethodDeclarationBindingViolation`] reason. -/// -/// Both `project_dag_method` (per-row) and `method_template_contract_row` -/// (lookup helper) consume this helper so the data-binding identity is -/// factored once. -pub(crate) fn validate_method_declaration_data_binding( - dag: &Dag, - decl_id: DeclarationId, - method_declaration_id: DeclarationId, -) -> Result { - let referenced = dag.declaration(decl_id); - - // (1) + (2) Connective + template. - let template = match &referenced.connective { - TypeConnective::Instantiation { template, .. } => *template, - _ => return Err(MethodDeclarationBindingViolation::ConnectiveNotInstantiation), - }; - if template != method_declaration_id { - return Err(MethodDeclarationBindingViolation::InstantiationTemplateNotMethodDeclaration); - } - - // (3) Value body present and Structural. - let value_body = referenced - .value_body - .as_ref() - .ok_or(MethodDeclarationBindingViolation::ValueBodyMissing)?; - let ValueBody::Structural { fields } = value_body else { - return Err(MethodDeclarationBindingViolation::ValueBodyNotStructural); - }; - - // (4) Closed `[name]` schema with string-literal value. Reject any - // duplicate or unknown label, require `name` to be present exactly - // once. Mirrors Stratum A's enforce_closed_record_schema. - const EXPECTED: &[&str] = &["name"]; - let name_count = fields.iter().filter(|(label, _)| label == "name").count(); - let any_unknown = fields - .iter() - .any(|(label, _)| !EXPECTED.contains(&label.as_str())); - if fields.len() != EXPECTED.len() || name_count != 1 || any_unknown { - let observed_labels: Vec = fields.iter().map(|(label, _)| label.clone()).collect(); - return Err( - MethodDeclarationBindingViolation::StructuralFieldsNotClosedNameOnly { - observed_labels, - }, - ); - } - let (_, name_value) = fields - .iter() - .find(|(label, _)| label == "name") - .expect("closed-schema check above guarantees a single `name` field"); - match name_value { - FieldValue::Literal(LiteralBits::String(s)) => Ok(s.clone()), - _ => Err(MethodDeclarationBindingViolation::NameNotStringLiteral), - } -} - -fn project_row( - dag: &Dag, - list: &'static str, - row_index: usize, - row: &FieldValue, -) -> Result { - let fields = match row { - FieldValue::Record(fields) => fields, - FieldValue::Reference(decl_id) => { - let decl = dag.declaration_opt(decl_id).ok_or( - MethodTemplateProjectionError::RowReferenceMissing { - list, - row_index, - decl_id: *decl_id, - }, - )?; - let method_template_contract = dag.method_template_contract_decl().ok_or( - MethodTemplateProjectionError::RowReferenceNotMethodTemplateContract { - list, - row_index, - decl_id: *decl_id, - }, - )?; - let TypeConnective::Instantiation { template, .. } = &decl.connective else { - return Err( - MethodTemplateProjectionError::RowReferenceNotMethodTemplateContract { - list, - row_index, - decl_id: *decl_id, - }, - ); - }; - if *template != method_template_contract { - return Err( - MethodTemplateProjectionError::RowReferenceNotMethodTemplateContract { - list, - row_index, - decl_id: *decl_id, - }, - ); - } - match decl.value_body.as_ref() { - Some(ValueBody::Structural { fields }) => fields, - _ => { - return Err(MethodTemplateProjectionError::RowReferenceNotStructural { - list, - row_index, - decl_id: *decl_id, - }); - } - } - } - _ => return Err(MethodTemplateProjectionError::RowNotRecord { list, row_index }), - }; - - const EXPECTED: &[&str] = &[ - "dag_method", - "runtime_template", - "emit_template", - "wraps_result", - "placeholder_convention", - ]; - let lookup = closed_record_lookup(list, row_index, "MethodTemplateContract", fields, EXPECTED)?; - - let dag_method_value = lookup_required(list, row_index, &lookup, "dag_method")?; - let dag_method = project_dag_method(dag, list, row_index, dag_method_value)?; - - let runtime_template_value = lookup_required(list, row_index, &lookup, "runtime_template")?; - let runtime_template = match runtime_template_value { - FieldValue::Literal(LiteralBits::String(s)) => s.clone(), - _ => { - return Err(MethodTemplateProjectionError::RuntimeTemplateNotString { - list, - row_index, - }); - } - }; - - let emit_template_value = lookup_required(list, row_index, &lookup, "emit_template")?; - let emit_template = project_emit_template(dag, list, row_index, emit_template_value)?; - - let wraps_result_value = lookup_required(list, row_index, &lookup, "wraps_result")?; - let wraps_result = match wraps_result_value { - FieldValue::Literal(LiteralBits::Bool(b)) => *b, - _ => return Err(MethodTemplateProjectionError::WrapsResultNotBool { list, row_index }), - }; - - let placeholder_value = lookup_required(list, row_index, &lookup, "placeholder_convention")?; - let placeholder_convention = project_placeholder(dag, list, row_index, placeholder_value)?; - - Ok(MethodTemplateContractRow { - dag_method, - runtime_template, - emit_template, - wraps_result, - placeholder_convention, - }) -} - -/// Validate that `fields` carries exactly the closed set of `expected` -/// labels — no duplicates, no unknowns. Missing labels are not reported -/// here (the per-field `lookup_required` calls below surface those as -/// `RowMissingField` so the error names *which* field is missing). -/// -/// Returns a label-to-`FieldValue` map for the present fields; collisions -/// surface as `RowDuplicateField` and unknown labels surface as -/// `RowUnknownField`. `record` names the carrier (e.g. -/// `"MethodTemplateContract"` or `"MethodRef"`) for diagnostic context. -fn closed_record_lookup<'a>( - list: &'static str, - row_index: usize, - record: &'static str, - fields: &'a [(String, FieldValue)], - expected: &[&'static str], -) -> Result, MethodTemplateProjectionError> { - let mut lookup: HashMap<&'static str, &FieldValue> = HashMap::with_capacity(expected.len()); - let mut first_seen_index: HashMap<&'static str, usize> = HashMap::with_capacity(expected.len()); - for (field_index, (label, value)) in fields.iter().enumerate() { - // Resolve the row-side `String` label to its `&'static str` in - // `expected`. Holding the static reference avoids re-scanning the - // expected slice (and the `.expect()` re-borrow) on the duplicate - // path; non-membership surfaces as `RowUnknownField`. - let Some(static_label) = expected.iter().copied().find(|exp| *exp == label.as_str()) else { - return Err(MethodTemplateProjectionError::RowUnknownField { - list, - row_index, - record, - field: label.clone(), - field_index, - }); - }; - if let Some(prior_index) = first_seen_index.get(static_label) { - return Err(MethodTemplateProjectionError::RowDuplicateField { - list, - row_index, - record, - field: static_label, - first_field_index: *prior_index, - duplicate_field_index: field_index, - }); - } - lookup.insert(static_label, value); - first_seen_index.insert(static_label, field_index); - } - Ok(lookup) -} - -fn lookup_required<'a>( - list: &'static str, - row_index: usize, - lookup: &HashMap<&'a str, &'a FieldValue>, - field: &'static str, -) -> Result<&'a FieldValue, MethodTemplateProjectionError> { - lookup - .get(field) - .copied() - .ok_or(MethodTemplateProjectionError::RowMissingField { - list, - row_index, - field, - }) -} - -fn project_dag_method( - dag: &Dag, - list: &'static str, - row_index: usize, - value: &FieldValue, -) -> Result { - let FieldValue::Record(method_ref_fields) = value else { - return Err(MethodTemplateProjectionError::DagMethodNotMethodRefRecord { list, row_index }); - }; - // `MethodRef` is a single-field carrier (`{ decl: DeclarationRef }`) - // per `src/v3/std/methods.dag`. Apply the same closed-schema check - // as the outer row so duplicates (`{ decl: a, decl: b }`) and - // unknown extras (`{ decl: ..., extra: ... }`) surface as typed - // errors instead of being silently ignored. - const METHOD_REF_FIELDS: &[&str] = &["decl"]; - let lookup = closed_record_lookup( - list, - row_index, - "MethodRef", - method_ref_fields, - METHOD_REF_FIELDS, - )?; - let decl_value = lookup - .get("decl") - .copied() - .ok_or(MethodTemplateProjectionError::DagMethodNotMethodRefRecord { list, row_index })?; - let decl_id = match decl_value { - FieldValue::Reference(decl_id) => *decl_id, - _ => { - return Err(MethodTemplateProjectionError::MethodRefDeclNotReference { - list, - row_index, - }); - } - }; - // `src/v3/std/methods.dag` calls out the boundary contract: today's - // substrate grammar can't express `DeclarationRef`, - // so the projection enforces fail-closed at the boundary that - // `decl` references a `MethodDeclaration`-instantiating data binding. - // Pattern mirrors `method_registry_test.rs::method_registry_covers_*`. - let method_declaration_id = method_declaration_carrier_id(dag)?; - if let Err(reason) = - validate_method_declaration_data_binding(dag, decl_id, method_declaration_id) - { - return Err( - MethodTemplateProjectionError::MethodRefDeclNotMethodDeclaration { - list, - row_index, - decl_id, - reason, - }, - ); - } - Ok(decl_id) -} - -fn project_emit_template( - dag: &Dag, - list: &'static str, - row_index: usize, - value: &FieldValue, -) -> Result { - let FieldValue::Variant { - constructor, - payload, - } = value - else { - return Err(MethodTemplateProjectionError::EmitTemplateNotVariant { list, row_index }); - }; - let constructor_label = disj_variant_label(dag, "MethodEmitTemplate", *constructor); - match constructor_label { - Some("SingleTemplate") => { - // Payload shape (positional, per FieldValue::Variant): one - // `template: String` field. Lowering produces a single - // record-shaped payload entry whose only field is `template`. - let template = single_template_payload(list, row_index, payload)?; - Ok(MethodEmitTemplateProjection::Single { template }) - } - Some("HigherOrderTemplates") => { - let (inline_template, fn_ref_template) = - higher_order_payload(list, row_index, payload)?; - Ok(MethodEmitTemplateProjection::HigherOrder { - inline_template, - fn_ref_template, - }) - } - other => Err( - MethodTemplateProjectionError::EmitTemplateUnknownConstructor { - list, - row_index, - constructor: other.map(str::to_string), - }, - ), - } -} - -/// Recover a sum-variant label by walking the parent `Disj` declaration's -/// variant list. Variant constructor declarations themselves do not carry -/// `name`; identity flows through the parent's `variants[i].label`. Mirrors -/// the pattern used in `tests/integration/method_template_contract_test.rs` -/// (`method_emit_template_variant_label`). -fn disj_variant_label<'a>( - dag: &'a Dag, - parent_name: &str, - constructor: DeclarationId, -) -> Option<&'a str> { - let parent = dag.declaration_by_name(parent_name)?; - let TypeConnective::Disj { variants } = &parent.connective else { - return None; - }; - variants - .iter() - .find(|variant| variant.ty == constructor) - .map(|variant| variant.label.as_str()) -} - -fn single_template_payload( - list: &'static str, - row_index: usize, - payload: &[FieldValue], -) -> Result { - // `Variant.payload` is positional per the substrate carrier - // (`FieldValue::Variant` doc-comment). `SingleTemplate { template: String }` - // therefore lowers to a single-element payload carrying the string literal. - match payload { - [FieldValue::Literal(LiteralBits::String(s))] => Ok(s.clone()), - _ => Err(MethodTemplateProjectionError::EmitTemplateSinglePayloadShape { list, row_index }), - } -} - -fn higher_order_payload( - list: &'static str, - row_index: usize, - payload: &[FieldValue], -) -> Result<(String, String), MethodTemplateProjectionError> { - // `HigherOrderTemplates { inline_template: String, fn_ref_template: String }` - // lowers to a two-element positional payload carrying the two string - // literals in declaration order. - match payload { - [FieldValue::Literal(LiteralBits::String(inline)), FieldValue::Literal(LiteralBits::String(fn_ref))] => { - Ok((inline.clone(), fn_ref.clone())) - } - _ => Err( - MethodTemplateProjectionError::EmitTemplateHigherOrderPayloadShape { list, row_index }, - ), - } -} - -fn project_placeholder( - dag: &Dag, - list: &'static str, - row_index: usize, - value: &FieldValue, -) -> Result { - let FieldValue::Variant { - constructor, - payload, - } = value - else { - return Err( - MethodTemplateProjectionError::PlaceholderConventionNotVariant { list, row_index }, - ); - }; - let constructor_label = disj_variant_label(dag, "PlaceholderConvention", *constructor); - // `PlaceholderConvention` is a nullary sum (`IndexedArgs | NamedArg`) - // per `src/v3/std/emit_model.dag:449-451`. A non-empty payload would - // mean a malformed bootstrap row (e.g., `IndexedArgs(false)`) that the - // projection must surface as a typed error rather than silently - // discarding. Mirrors the substrate-side canonical-row check in - // `src/v3/grounding_tests/src/stratum_a.rs`. - let (label, projection) = match constructor_label { - Some("IndexedArgs") => ("IndexedArgs", PlaceholderConventionProjection::IndexedArgs), - Some("NamedArg") => ("NamedArg", PlaceholderConventionProjection::NamedArg), - other => { - return Err( - MethodTemplateProjectionError::PlaceholderConventionUnknown { - list, - row_index, - constructor: other.map(str::to_string), - }, - ); - } - }; - if !payload.is_empty() { - return Err( - MethodTemplateProjectionError::PlaceholderConventionPayloadNotEmpty { - list, - row_index, - constructor: label, - payload_len: payload.len(), - }, - ); - } - Ok(projection) -} - -#[cfg(test)] -mod tests { - //! Unit tests for projection-internal failure modes that require - //! crate-private `Dag` mutation to construct (e.g., injecting a - //! malformed value body that the on-disk row authorities cannot - //! produce). Behavior tests over the green bootstrap Dag live in - //! `tests/integration/pb_method_template_projection_test.rs`. - - use super::*; - use crate::generated_full_bootstrap_dag; - - /// Inject a non-empty payload onto the `placeholder_convention` variant - /// of the row at `row_index` of the named per-target list. Mutates only - /// the in-memory `Dag`; the row authority sources on disk are - /// untouched. - fn inject_extra_placeholder_payload(dag: &mut Dag, list_name: &str, row_index: usize) { - let decl_id = dag - .declaration_by_name(list_name) - .unwrap_or_else(|| panic!("`{list_name}` missing from bootstrap")) - .id; - let decl = dag.declaration_mut(decl_id); - let body = decl - .value_body - .as_mut() - .unwrap_or_else(|| panic!("`{list_name}` has no value body")); - let ValueBody::List(rows) = body else { - panic!("`{list_name}` value body is not a List"); - }; - let row = rows - .get_mut(row_index) - .unwrap_or_else(|| panic!("row {row_index} missing from `{list_name}`")); - let FieldValue::Record(fields) = row else { - panic!("row {row_index} is not a Record"); - }; - let placeholder_value = fields - .iter_mut() - .find(|(label, _)| label == "placeholder_convention") - .map(|(_, value)| value) - .expect("placeholder_convention field"); - let FieldValue::Variant { payload, .. } = placeholder_value else { - panic!("placeholder_convention must be a Variant"); - }; - payload.push(FieldValue::Literal(LiteralBits::Bool(false))); - } - - /// Duplicate the row at `row_index` of the named per-target list by - /// pushing a clone onto the end of the list. The two rows share a - /// `dag_method`, breaking per-target uniqueness — the substrate-side - /// claim `method_template_contract_per_target_dag_method_unique`. - /// Mutates only the in-memory `Dag`. - fn duplicate_row(dag: &mut Dag, list_name: &str, row_index: usize) { - let decl_id = dag - .declaration_by_name(list_name) - .unwrap_or_else(|| panic!("`{list_name}` missing from bootstrap")) - .id; - let decl = dag.declaration_mut(decl_id); - let body = decl - .value_body - .as_mut() - .unwrap_or_else(|| panic!("`{list_name}` has no value body")); - let ValueBody::List(rows) = body else { - panic!("`{list_name}` value body is not a List"); - }; - let clone = rows - .get(row_index) - .unwrap_or_else(|| panic!("row {row_index} missing from `{list_name}`")) - .clone(); - rows.push(clone); - } - - fn replace_row_with_reference( - dag: &mut Dag, - list_name: &str, - row_index: usize, - replacement: DeclarationId, - ) { - let decl_id = dag.declaration_by_name(list_name).expect("list").id; - let decl = dag.declaration_mut(decl_id); - let body = decl.value_body.as_mut().expect("value body"); - let ValueBody::List(rows) = body else { - panic!("not a list"); - }; - let row = rows.get_mut(row_index).expect("row"); - *row = FieldValue::Reference(replacement); - } - - /// Mutate the row at `row_index` of `list_name` by adding a second - /// field with `field_label` (cloned from the first occurrence). Used - /// by the closed-schema duplicate-field tests below. - fn duplicate_field_on_row(dag: &mut Dag, list_name: &str, row_index: usize, field_label: &str) { - let decl_id = dag.declaration_by_name(list_name).expect("list").id; - let decl = dag.declaration_mut(decl_id); - let body = decl.value_body.as_mut().expect("value body"); - let ValueBody::List(rows) = body else { - panic!("not a list"); - }; - let row = rows.get_mut(row_index).expect("row"); - let FieldValue::Record(fields) = row else { - panic!("row not record"); - }; - let (orig_label, orig_value) = fields - .iter() - .find(|(label, _)| label == field_label) - .map(|(label, value)| (label.clone(), value.clone())) - .expect("field to clone"); - fields.push((orig_label, orig_value)); - } - - /// Replace the row at `row_index` of `list_name` so that, after the - /// existing fields, an unknown-labelled field is appended (used by - /// the closed-schema unknown-field tests below). - fn append_unknown_field_on_row( - dag: &mut Dag, - list_name: &str, - row_index: usize, - unknown_label: &str, - ) { - let decl_id = dag.declaration_by_name(list_name).expect("list").id; - let decl = dag.declaration_mut(decl_id); - let body = decl.value_body.as_mut().expect("value body"); - let ValueBody::List(rows) = body else { - panic!("not a list"); - }; - let row = rows.get_mut(row_index).expect("row"); - let FieldValue::Record(fields) = row else { - panic!("row not record"); - }; - fields.push(( - unknown_label.to_string(), - FieldValue::Literal(LiteralBits::Bool(false)), - )); - } - - #[test] - fn duplicate_field_on_row_surfaces_typed_error() { - let mut dag = generated_full_bootstrap_dag(); - duplicate_field_on_row( - &mut dag, - "rust_method_template_contracts", - 0, - "runtime_template", - ); - let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); - match result { - Err(MethodTemplateProjectionError::RowDuplicateField { - record, - field, - first_field_index, - duplicate_field_index, - .. - }) => { - assert_eq!(record, "MethodTemplateContract"); - assert_eq!(field, "runtime_template"); - assert_ne!(first_field_index, duplicate_field_index); - } - other => panic!("expected RowDuplicateField, got {other:?}"), - } - } - - #[test] - fn unknown_field_on_row_surfaces_typed_error() { - let mut dag = generated_full_bootstrap_dag(); - append_unknown_field_on_row( - &mut dag, - "rust_method_template_contracts", - 0, - "renamed_field", - ); - let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); - match result { - Err(MethodTemplateProjectionError::RowUnknownField { record, field, .. }) => { - assert_eq!(record, "MethodTemplateContract"); - assert_eq!(field, "renamed_field"); - } - other => panic!("expected RowUnknownField, got {other:?}"), - } - } - - #[test] - fn reference_row_requires_method_template_contract_instance() { - let mut dag = generated_full_bootstrap_dag(); - let non_contract_structural = dag - .declaration_by_name("count_method") - .expect("count_method MethodDeclaration") - .id; - replace_row_with_reference( - &mut dag, - "rust_method_template_contracts", - 0, - non_contract_structural, - ); - - let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); - match result { - Err(MethodTemplateProjectionError::RowReferenceNotMethodTemplateContract { - list, - row_index, - decl_id, - }) => { - assert_eq!(list, "rust_method_template_contracts"); - assert_eq!(row_index, 0); - assert_eq!(decl_id, non_contract_structural); - } - other => panic!( - "expected RowReferenceNotMethodTemplateContract for structural lookalike row, got {other:?}" - ), - } - } - - /// Inject a duplicate or unknown field into the inner `MethodRef` - /// record at `dag_method` for the row at `row_index`. - fn mutate_method_ref( - dag: &mut Dag, - list_name: &str, - row_index: usize, - kind: MethodRefMutation<'_>, - ) { - let decl_id = dag.declaration_by_name(list_name).expect("list").id; - let decl = dag.declaration_mut(decl_id); - let body = decl.value_body.as_mut().expect("value body"); - let ValueBody::List(rows) = body else { - panic!("not a list"); - }; - let row = rows.get_mut(row_index).expect("row"); - let FieldValue::Record(fields) = row else { - panic!("row not record"); - }; - let (_, dag_method_value) = fields - .iter_mut() - .find(|(label, _)| label == "dag_method") - .expect("dag_method field"); - let FieldValue::Record(method_ref_fields) = dag_method_value else { - panic!("dag_method not record"); - }; - match kind { - MethodRefMutation::Duplicate => { - let (label, value) = method_ref_fields - .iter() - .find(|(label, _)| label == "decl") - .map(|(label, value)| (label.clone(), value.clone())) - .expect("decl field"); - method_ref_fields.push((label, value)); - } - MethodRefMutation::AppendUnknown(label) => { - method_ref_fields.push(( - label.to_string(), - FieldValue::Literal(LiteralBits::Bool(false)), - )); - } - } - } - - /// 🟢 TERMINAL at the test-local `MethodRef` mutation scope. Names the - /// two ways an in-memory `Dag` row can break the closed `MethodRef` - /// schema: duplicate the existing `decl` field, or append a field with - /// an unknown label. Both modes drive the closed-schema check in - /// `closed_record_lookup`. Test-only; not part of the projection's - /// public surface. - enum MethodRefMutation<'a> { - Duplicate, - AppendUnknown(&'a str), - } - - #[test] - fn method_ref_decl_alias_without_value_body_surfaces_typed_error() { - // `src/v3/std/methods.dag` requires a *data binding* - // (`data _method: MethodDeclaration = { name: "..." }`), - // not just any declaration whose connective resolves to - // `MethodDeclaration`. A type alias like - // `type Foo = MethodDeclaration` lowers to an `Instantiation` - // connective with **no `value_body`**; without the value-body - // guard, that shape would pass the connective check alone. - // - // Simulate the alias-shape by stripping `value_body` from a real - // `MethodDeclaration` data binding (`count_method`) and asserting - // the typed error fires for both the per-row check - // (`MethodRefDeclNotMethodDeclaration`) and the lookup helper - // (`LookupKeyNotMethodDeclaration`). - let mut dag = generated_full_bootstrap_dag(); - let count_method_id = dag - .declaration_by_name("count_method") - .expect("count_method MethodDeclaration in bootstrap") - .id; - dag.declaration_mut(count_method_id).value_body = None; - - // Per-row path: row 0 of `rust_method_template_contracts` is the - // `count_method` row, so projecting it should now surface - // `MethodRefDeclNotMethodDeclaration` with `ValueBodyMissing` - // reason instead of returning a row. - let row_result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); - match row_result { - Err(MethodTemplateProjectionError::MethodRefDeclNotMethodDeclaration { - decl_id, - reason, - .. - }) => { - assert_eq!(decl_id, count_method_id); - assert_eq!(reason, MethodDeclarationBindingViolation::ValueBodyMissing); - } - other => panic!( - "expected MethodRefDeclNotMethodDeclaration for alias-shaped target, got {other:?}" - ), - } - - // Lookup-helper path: same key, same shape; the helper validates - // at entry and surfaces `LookupKeyNotMethodDeclaration` with the - // same reason. - let helper_result = - method_template_contract_row(&dag, MethodTemplateTarget::Rust, count_method_id); - match helper_result { - Err(MethodTemplateProjectionError::LookupKeyNotMethodDeclaration { - decl_id, - reason, - }) => { - assert_eq!(decl_id, count_method_id); - assert_eq!(reason, MethodDeclarationBindingViolation::ValueBodyMissing); - } - other => { - panic!("expected LookupKeyNotMethodDeclaration for alias-shaped key, got {other:?}") - } - } - } - - #[test] - fn method_ref_decl_not_method_declaration_surfaces_typed_error() { - // `src/v3/std/methods.dag` enforces fail-closed at the projection - // boundary that `MethodRef.decl` instantiates `MethodDeclaration`, - // because today's substrate grammar can't express - // `DeclarationRef`. Mutate the first row's - // `MethodRef.decl` to point at a declaration that is not a - // `MethodDeclaration` instance (`MethodTemplateContract` itself - // is a type declaration, not a `MethodDeclaration` data binding) - // and assert the typed error. - let mut dag = generated_full_bootstrap_dag(); - let non_method_id = dag - .declaration_by_name("MethodTemplateContract") - .expect("MethodTemplateContract type") - .id; - - let list_decl_id = dag - .declaration_by_name("rust_method_template_contracts") - .expect("list") - .id; - let decl = dag.declaration_mut(list_decl_id); - let body = decl.value_body.as_mut().expect("value body"); - let ValueBody::List(rows) = body else { - panic!("not a list"); - }; - let row = rows.get_mut(0).expect("row 0"); - let FieldValue::Record(fields) = row else { - panic!("row not record"); - }; - let dag_method_value = fields - .iter_mut() - .find(|(label, _)| label == "dag_method") - .map(|(_, value)| value) - .expect("dag_method field"); - let FieldValue::Record(method_ref_fields) = dag_method_value else { - panic!("dag_method not record"); - }; - let decl_field_value = method_ref_fields - .iter_mut() - .find(|(label, _)| label == "decl") - .map(|(_, value)| value) - .expect("decl field"); - *decl_field_value = FieldValue::Reference(non_method_id); - - let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); - match result { - Err(MethodTemplateProjectionError::MethodRefDeclNotMethodDeclaration { - list, - row_index, - decl_id, - reason, - }) => { - assert_eq!(list, "rust_method_template_contracts"); - assert_eq!(row_index, 0); - assert_eq!(decl_id, non_method_id); - // `MethodTemplateContract` is a type declaration (Conj), - // not an Instantiation — connective sub-check fails first. - assert_eq!( - reason, - MethodDeclarationBindingViolation::ConnectiveNotInstantiation - ); - } - other => panic!( - "expected MethodRefDeclNotMethodDeclaration for non-MethodDeclaration target, got {other:?}" - ), - } - } - - #[test] - fn method_ref_decl_extra_field_surfaces_closed_schema_violation() { - // A MethodDeclaration data binding with an extra field (beyond the - // closed `[name]` schema) must surface - // `StructuralFieldsNotClosedNameOnly`. Mirrors Stratum A's - // `enforce_closed_record_schema` gate. - let mut dag = generated_full_bootstrap_dag(); - let count_method_id = dag - .declaration_by_name("count_method") - .expect("count_method") - .id; - let decl = dag.declaration_mut(count_method_id); - let body = decl.value_body.as_mut().expect("value body"); - let ValueBody::Structural { fields } = body else { - panic!("expected Structural body"); - }; - fields.push(( - "alias".to_string(), - FieldValue::Literal(LiteralBits::String("count_alias".to_string())), - )); - - let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); - match result { - Err(MethodTemplateProjectionError::MethodRefDeclNotMethodDeclaration { - reason: - MethodDeclarationBindingViolation::StructuralFieldsNotClosedNameOnly { - observed_labels, - }, - .. - }) => { - assert!(observed_labels.contains(&"name".to_string())); - assert!(observed_labels.contains(&"alias".to_string())); - } - other => { - panic!("expected StructuralFieldsNotClosedNameOnly for extra field, got {other:?}") - } - } - } - - #[test] - fn method_ref_decl_name_not_string_literal_surfaces_typed_error() { - // A MethodDeclaration data binding whose `name` value is not a - // string literal must surface - // `MethodDeclarationBindingViolation::NameNotStringLiteral`. - let mut dag = generated_full_bootstrap_dag(); - let count_method_id = dag - .declaration_by_name("count_method") - .expect("count_method") - .id; - let decl = dag.declaration_mut(count_method_id); - let body = decl.value_body.as_mut().expect("value body"); - let ValueBody::Structural { fields } = body else { - panic!("expected Structural body"); - }; - let (_, name_value) = fields - .iter_mut() - .find(|(label, _)| label == "name") - .expect("name field"); - // Replace with a Bool literal — wrong shape for the contract. - *name_value = FieldValue::Literal(LiteralBits::Bool(true)); - - let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); - match result { - Err(MethodTemplateProjectionError::MethodRefDeclNotMethodDeclaration { - reason: MethodDeclarationBindingViolation::NameNotStringLiteral, - .. - }) => {} - other => panic!("expected NameNotStringLiteral for non-string name, got {other:?}"), - } - } - - #[test] - fn duplicate_field_on_method_ref_surfaces_typed_error() { - let mut dag = generated_full_bootstrap_dag(); - mutate_method_ref( - &mut dag, - "rust_method_template_contracts", - 0, - MethodRefMutation::Duplicate, - ); - let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); - match result { - Err(MethodTemplateProjectionError::RowDuplicateField { record, field, .. }) => { - assert_eq!(record, "MethodRef"); - assert_eq!(field, "decl"); - } - other => panic!("expected RowDuplicateField for MethodRef, got {other:?}"), - } - } - - #[test] - fn unknown_field_on_method_ref_surfaces_typed_error() { - let mut dag = generated_full_bootstrap_dag(); - mutate_method_ref( - &mut dag, - "rust_method_template_contracts", - 0, - MethodRefMutation::AppendUnknown("nickname"), - ); - let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); - match result { - Err(MethodTemplateProjectionError::RowUnknownField { record, field, .. }) => { - assert_eq!(record, "MethodRef"); - assert_eq!(field, "nickname"); - } - other => panic!("expected RowUnknownField for MethodRef, got {other:?}"), - } - } - - #[test] - fn duplicate_row_surfaces_typed_error_at_canonical_row_list_api() { - // Per-target uniqueness by `dag_method` is the substrate-side claim - // `method_template_contract_per_target_dag_method_unique`. The - // canonical row-list API enforces this at the public boundary so - // every consumer (direct row-list consumer or `(target, dag_method)` - // lookup helper) observes the same fail-closed behavior. Gap 5 / - // leaf-emit consumers building their own keyed tables from the - // projection cannot silently observe two authorities for the same - // `(target, MethodRef)` pair. - let mut dag = generated_full_bootstrap_dag(); - duplicate_row(&mut dag, "rust_method_template_contracts", 0); - - // Canonical row-list API surfaces the duplicate. - let list_result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); - match list_result { - Err(MethodTemplateProjectionError::DuplicateMethodTemplateRow { - list, - first_row_index, - duplicate_row_index, - }) => { - assert_eq!(list, "rust_method_template_contracts"); - assert_ne!(first_row_index, duplicate_row_index); - } - other => panic!( - "expected DuplicateMethodTemplateRow at row-list API for duplicate (target, dag_method), got {other:?}" - ), - } - - // Lookup helper inherits the fail-closed shape via the row-list - // API; no separate uniqueness path on the helper. - let count_method_id = dag - .declaration_by_name("count_method") - .expect("count_method MethodDeclaration in bootstrap Dag") - .id; - let helper_result = - method_template_contract_row(&dag, MethodTemplateTarget::Rust, count_method_id); - assert!( - matches!( - helper_result, - Err(MethodTemplateProjectionError::DuplicateMethodTemplateRow { .. }) - ), - "lookup helper must inherit duplicate-row fail-closed via row-list API; got {helper_result:?}" - ); - } - - #[test] - fn malformed_placeholder_payload_surfaces_typed_error() { - // PlaceholderConvention is a nullary sum (`IndexedArgs | NamedArg`). - // A malformed bootstrap row carrying a non-empty placeholder payload - // must surface as `PlaceholderConventionPayloadNotEmpty`, not - // silently project to `IndexedArgs` / `NamedArg` with the payload - // discarded. Mirrors the substrate-side canonical-row check - // `placeholder_convention_canonical_rejects_payload_for_nullary_indexed_args` - // in `src/v3/grounding_tests/src/stratum_a.rs`. - let mut dag = generated_full_bootstrap_dag(); - inject_extra_placeholder_payload(&mut dag, "rust_method_template_contracts", 0); - - let result = method_template_contract_rows(&dag, MethodTemplateTarget::Rust); - match result { - Err(MethodTemplateProjectionError::PlaceholderConventionPayloadNotEmpty { - list, - row_index, - payload_len, - .. - }) => { - assert_eq!(list, "rust_method_template_contracts"); - assert_eq!(row_index, 0); - assert!(payload_len >= 1, "expected non-empty payload to be reported"); - } - other => panic!( - "expected PlaceholderConventionPayloadNotEmpty for malformed placeholder, got {other:?}" - ), - } - } -} diff --git a/src/v3/compiler/tests/integration/sg0_census_test.rs b/src/v3/compiler/tests/integration/sg0_census_test.rs index 058d928b54c..98b778a7998 100644 --- a/src/v3/compiler/tests/integration/sg0_census_test.rs +++ b/src/v3/compiler/tests/integration/sg0_census_test.rs @@ -267,8 +267,6 @@ const EXPECTED_HAND_AUTHORED_NON_TEST: &[&str] = &[ // evaluator for `tests/dag/cementing_dispatch.dag` (P5 consumer receipt; dissolves when // predicate substrate owns the walk without host FS coupling). "src/v3/compiler/src/cementing_dispatch.rs", - "src/v3/compiler/src/complexity_lattice.rs", - "src/v3/compiler/src/cost_basis_declaration.rs", "src/v3/compiler/src/dag.rs", "src/v3/compiler/src/dag/builder.rs", // Closed Cardinality payload + idempotent target shim (API closure). @@ -278,14 +276,12 @@ const EXPECTED_HAND_AUTHORED_NON_TEST: &[&str] = &[ "src/v3/compiler/src/diagnostics.rs", "src/v3/compiler/src/dimension.rs", "src/v3/compiler/src/emit.rs", - // CollectionOps `*_contract` → `MethodTemplateContract` identity gate (PR #1577 / #1602). - "src/v3/compiler/src/emit/collection_ops_method_contract.rs", "src/v3/compiler/src/emit/python_target.rs", "src/v3/compiler/src/emit/rust_target.rs", "src/v3/compiler/src/emit_rust.rs", - "src/v3/compiler/src/emit_rust_bin_shim.rs", // R1C-E + m1_3: shared `PROGRAM_FIXTURES` / `REFLECTED_FIXTURES` tables (single source of truth). "src/v3/compiler/src/emit_rust_roundtrip_fixtures.rs", +<<<<<<< HEAD "src/v3/compiler/src/enforced_lens_application.rs", "src/v3/compiler/src/infer.rs", "src/v3/compiler/src/int_literal_ranges.rs", @@ -295,6 +291,18 @@ const EXPECTED_HAND_AUTHORED_NON_TEST: &[&str] = &[ // re-exports). Bounded lens application body lives in `lens_declaration_apply_body.txt` // (see `EXPECTED_HAND_AUTHORED_FRAGMENTS`). Dissolution: substrate-owned CI + cementing // receipts; PB-Runtime for lens application (R3 §1.8). +======= + // T-WAD Slice 7 / gate #103: pure `CIWorkflowDag` gate-id selection (P5 receipt + // row in INVARIANTS.md §SG-0 hand-authored compiler non-test paths). + "src/v3/compiler/src/gunbc_ci.rs", + "src/v3/compiler/src/infer.rs", + // R3 gate #87: `tests/integration.rs` wiring scanner shared by Band-C cementing dispatch + // (`cementing_dispatch.rs`) and integration tests (P5 receipt for host promotion from + // `tests/integration/common/mod.rs`). + "src/v3/compiler/src/integration_rs_wiring_scan.rs", + "src/v3/compiler/src/lens_declaration_apply.rs", + "src/v3/compiler/src/lens_t_las_carrier.rs", +>>>>>>> e79e73838 (WIP: PB-0 cycle-4 NON_TEST retirement (msg_5d7f3491): 7 paths — complexity_la) "src/v3/compiler/src/lib.rs", "src/v3/compiler/src/lower.rs", // R3 gate #94: cost-lens memory-peak compose + enforcement authority (ties `dominant`/max_path). @@ -304,11 +312,6 @@ const EXPECTED_HAND_AUTHORED_NON_TEST: &[&str] = &[ // fix moved it out of `emit.rs`. Dissolves when the equivalent Shape B // `.dag` program owns the OpenAPI artifact projection end-to-end. "src/v3/compiler/src/omni_shape_b_openapi.rs", - // R3 row 85 / PB #1560 Gap 4: target-keyed projection of the - // `MethodTemplateContract` rows from the full bootstrap `Dag` for - // PB-zero / v2-retirement consumers (decision in - // `docs/decisions/r3-row85-method-template-read-surface.md`). - "src/v3/compiler/src/pb_method_template_projection.rs", "src/v3/compiler/src/pipeline_authority.rs", "src/v3/compiler/src/post_emit_verifier.rs", // PB-1 Item 5: host mirror of `dsl/std/process.dag` `ProcessExit` for emitted bin shims. @@ -1436,7 +1439,7 @@ fn sg0_stage0_hand_maintained_src_covers_emit_subtree_companions() { "hand_maintained_src should exclude emit/rust_target.rs from recursive freshness drift" ); assert!( - list.contains("\"emit_rust_bin_shim.rs\""), - "hand_maintained_src should exclude emit_rust_bin_shim.rs (PB-1 shell helper) from recursive freshness drift" + list.contains("\"process_exit.rs\""), + "hand_maintained_src should exclude process_exit.rs (PB-1 host mirror) from recursive freshness drift" ); } From 6bfd9d349ab6c9f3e971e6a52d6fd0c9593682ed Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 13 May 2026 19:41:54 -0400 Subject: [PATCH 03/10] =?UTF-8?q?WIP:=20PB-0=20cycle-4=20NON=5FTEST=20reti?= =?UTF-8?q?rement=20(msg=5F5d7f3491):=207=20paths=20=E2=80=94=20complexity?= =?UTF-8?q?=5Fla?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/v3/compiler/src/emit.rs | 5 +-- src/v3/compiler/src/lib.rs | 37 +++++-------------- .../tests/integration/sg0_census_test.rs | 17 +-------- 3 files changed, 13 insertions(+), 46 deletions(-) diff --git a/src/v3/compiler/src/emit.rs b/src/v3/compiler/src/emit.rs index d4178bc1298..2a80baeda32 100644 --- a/src/v3/compiler/src/emit.rs +++ b/src/v3/compiler/src/emit.rs @@ -15,9 +15,8 @@ //! target-private carriers stay inside their target module and no //! cross-target code should read them. -pub(crate) mod collection_ops_method_contract { - include!("emit/collection_ops_method_contract_generated.rs"); -} +#[path = "emit/collection_ops_method_contract_generated.rs"] +pub(crate) mod collection_ops_method_contract; pub(crate) mod python_target; pub(crate) mod rust_target; diff --git a/src/v3/compiler/src/lib.rs b/src/v3/compiler/src/lib.rs index f380e042e57..36b28dbd03d 100644 --- a/src/v3/compiler/src/lib.rs +++ b/src/v3/compiler/src/lib.rs @@ -16,21 +16,15 @@ // payload. pub mod cementing_dispatch; -<<<<<<< HEAD pub use cementing_dispatch::gunbc_ci; pub use cementing_dispatch::integration_rs_wiring_scan; pub use cementing_dispatch::r3_gate_87_cementing_regen_runner_suites; +#[path = "complexity_lattice_generated.rs"] pub mod complexity_lattice; -======= -pub mod complexity_lattice { - include!("complexity_lattice_generated.rs"); -} ->>>>>>> e79e73838 (WIP: PB-0 cycle-4 NON_TEST retirement (msg_5d7f3491): 7 paths — complexity_la) pub mod dag; pub mod diagnostics; -mod enforced_lens_application { - include!("enforced_lens_application_generated.rs"); -} +#[path = "enforced_lens_application_generated.rs"] +mod enforced_lens_application; pub use enforced_lens_application::check_enforced_lens_applications; // Gate #58 integration receipts (`tests/integration/t_gate_58_apply_lens_self_application_test.rs`) // need the helpers below as **`pub`**: the consolidated integration test binary is a separate @@ -41,16 +35,8 @@ pub use enforced_lens_application::check_enforced_lens_applications; pub use enforced_lens_application::gate_58_test_parse_timing_budget_violation_max_ns_pair; #[doc(hidden)] pub use enforced_lens_application::gate_58_test_raise_modeled_ci_timing_measurement_duration_ns; -<<<<<<< HEAD +#[path = "pb_method_template_projection_generated.rs"] pub mod pb_method_template_projection; -======= -pub mod integration_rs_wiring_scan; -pub mod lens_t_las_carrier; -pub mod pb_method_template_projection { - include!("pb_method_template_projection_generated.rs"); -} -pub mod r3_gate_87_cementing_regen_runner_suites; ->>>>>>> e79e73838 (WIP: PB-0 cycle-4 NON_TEST retirement (msg_5d7f3491): 7 paths — complexity_la) mod regen_bootstrap_emit; pub mod regen_tokenize; @@ -69,9 +55,8 @@ pub mod generated_files { pub mod emit; pub mod emit_rust; -pub mod emit_rust_bin_shim { - include!("emit_rust_bin_shim_generated.rs"); -} +#[path = "emit_rust_bin_shim_generated.rs"] +pub mod emit_rust_bin_shim; pub mod omni_shape_b_openapi; pub mod process_exit; pub mod realization_cost { @@ -3782,9 +3767,8 @@ pub mod evaluator { } } } -mod int_literal_ranges { - include!("int_literal_ranges_generated.rs"); -} +#[path = "int_literal_ranges_generated.rs"] +mod int_literal_ranges; /// T-LensAPI D1: bounded lens interpreter over substrate-shaped [`FieldValue`] /// (R3 gate #5: `lens_apply.rs` retired; body in `lens_declaration_apply_body.txt`, included here /// for SG-0 census path retirement until PB-Runtime / Row-4). @@ -5006,9 +4990,8 @@ pub use bootstrap_regen_fresh::{ compile_full_bootstrap_without_parse_surface_dag_from_std_seed, compile_std_bootstrap_dag, }; -mod cost_basis_declaration { - include!("cost_basis_declaration_generated.rs"); -} +#[path = "cost_basis_declaration_generated.rs"] +mod cost_basis_declaration; mod dimension; mod infer; diff --git a/src/v3/compiler/tests/integration/sg0_census_test.rs b/src/v3/compiler/tests/integration/sg0_census_test.rs index 98b778a7998..f5000eb4790 100644 --- a/src/v3/compiler/tests/integration/sg0_census_test.rs +++ b/src/v3/compiler/tests/integration/sg0_census_test.rs @@ -217,7 +217,7 @@ fn emit_production_code_has_no_declaration_by_name_calls() { // `dag.rs` itself replaces these submodules simultaneously. // // T-Substrate cardinality subset for int literals (2026-04-25): the -// range-comparison shim in `int_literal_ranges.rs` is host-side +// range-comparison shim in `int_literal_ranges_generated.rs` (build.rs-spliced // reconciliation glue over already-declared String-decimal range facts // while `rust_pilot_primitives.value_body` remains an unparsed top-level // list. It intentionally compares only source literals that already fit @@ -281,28 +281,13 @@ const EXPECTED_HAND_AUTHORED_NON_TEST: &[&str] = &[ "src/v3/compiler/src/emit_rust.rs", // R1C-E + m1_3: shared `PROGRAM_FIXTURES` / `REFLECTED_FIXTURES` tables (single source of truth). "src/v3/compiler/src/emit_rust_roundtrip_fixtures.rs", -<<<<<<< HEAD - "src/v3/compiler/src/enforced_lens_application.rs", "src/v3/compiler/src/infer.rs", - "src/v3/compiler/src/int_literal_ranges.rs", // PB-0 cycle-2 (msg_84abadad Track A): `gunbc_ci`, `integration_rs_wiring_scan`, // `r3_gate_87_cementing_regen_runner_suites`, and `lens_t_las_carrier` are nested `pub mod` // surfaces under `cementing_dispatch.rs` / `lens_declaration_apply` (same APIs via `lib.rs` // re-exports). Bounded lens application body lives in `lens_declaration_apply_body.txt` // (see `EXPECTED_HAND_AUTHORED_FRAGMENTS`). Dissolution: substrate-owned CI + cementing // receipts; PB-Runtime for lens application (R3 §1.8). -======= - // T-WAD Slice 7 / gate #103: pure `CIWorkflowDag` gate-id selection (P5 receipt - // row in INVARIANTS.md §SG-0 hand-authored compiler non-test paths). - "src/v3/compiler/src/gunbc_ci.rs", - "src/v3/compiler/src/infer.rs", - // R3 gate #87: `tests/integration.rs` wiring scanner shared by Band-C cementing dispatch - // (`cementing_dispatch.rs`) and integration tests (P5 receipt for host promotion from - // `tests/integration/common/mod.rs`). - "src/v3/compiler/src/integration_rs_wiring_scan.rs", - "src/v3/compiler/src/lens_declaration_apply.rs", - "src/v3/compiler/src/lens_t_las_carrier.rs", ->>>>>>> e79e73838 (WIP: PB-0 cycle-4 NON_TEST retirement (msg_5d7f3491): 7 paths — complexity_la) "src/v3/compiler/src/lib.rs", "src/v3/compiler/src/lower.rs", // R3 gate #94: cost-lens memory-peak compose + enforcement authority (ties `dominant`/max_path). From d307379829bdff44dcf9a658fed87422a737dfe5 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 13 May 2026 19:43:24 -0400 Subject: [PATCH 04/10] =?UTF-8?q?WIP:=20PB-0=20cycle-4=20NON=5FTEST=20reti?= =?UTF-8?q?rement=20(msg=5F5d7f3491):=207=20paths=20=E2=80=94=20complexity?= =?UTF-8?q?=5Fla?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/v3/compiler/build.rs | 11 ++++----- .../emit_rust_bin_shim.rs | 12 ++++++---- .../src/complexity_lattice_generated.rs | 11 ++++----- .../src/cost_basis_declaration_generated.rs | 11 ++++----- ...ollection_ops_method_contract_generated.rs | 11 ++++----- .../src/emit_rust_bin_shim_generated.rs | 23 ++++++++++--------- .../enforced_lens_application_generated.rs | 11 ++++----- .../src/int_literal_ranges_generated.rs | 11 ++++----- ...pb_method_template_projection_generated.rs | 11 ++++----- src/v3/compiler/src/process_exit.rs | 7 +++--- 10 files changed, 58 insertions(+), 61 deletions(-) diff --git a/src/v3/compiler/build.rs b/src/v3/compiler/build.rs index 0127dc7881f..4d52dc08f7a 100644 --- a/src/v3/compiler/build.rs +++ b/src/v3/compiler/build.rs @@ -444,12 +444,11 @@ fn emit_r1_gates_fixture(manifest_path: &Path, v3_dir: &Path) { /// rebuild; do not hand-edit `*_generated.rs` outputs. fn emit_pb0_cycle4_host_authority_modules(repo_root: &Path, manifest_path: &Path) { const HEADER: &str = concat!( - "// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`).\n", - "//\n", - "// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate).\n", - "// Dissolution receipt: PB-0 NON_TEST retirement — former hand module.\n", - "//\n", - "\n", + "//! AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`).\n", + "//!\n", + "//! Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate).\n", + "//! Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild.\n", + "//!\n", ); let tmpl_dir = manifest_path.join("pb0_cycle4_emit_templates"); let specs: &[(&str, &str)] = &[ diff --git a/src/v3/compiler/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs b/src/v3/compiler/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs index 3168e8fe4a3..dff6e509b59 100644 --- a/src/v3/compiler/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs +++ b/src/v3/compiler/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs @@ -1,10 +1,12 @@ //! PB-1 — Rust `main.rs` shell for generated `[[bin]]` shims (Item 5). //! -//! **SG-0 / bounded-seed receipt:** hand-authored path is enumerated in -//! `EXPECTED_HAND_AUTHORED_NON_TEST` in `sg0_census_test.rs` (not -//! `GENERATED_FILES` / `build.rs` output). Narrow host helper for §4.2 shell -//! text only; see `docs/invariants/bounded-substrate-seed.md` (no new -//! substrate primitive — mirrors existing `std.process.ProcessExit`). +//! **SG-0 / bounded-seed receipt:** this body is spliced from +//! `pb0_cycle4_emit_templates/emit_rust_bin_shim.rs` by `build.rs` +//! (`emit_pb0_cycle4_host_authority_modules`); the output path is in +//! `GENERATED_FILES` / `REGEN_OUTPUTS` (not `EXPECTED_HAND_AUTHORED_NON_TEST`). +//! Narrow host helper for §4.2 shell text only; see +//! `docs/invariants/bounded-substrate-seed.md` (no new substrate primitive — +//! mirrors existing `std.process.ProcessExit`). //! //! Authority: `docs/design-pb-runtime-interpreter.md` §4.2 (`main` → //! `ProcessExit` → `ExitCode`). This module owns **parameterized Rust text** diff --git a/src/v3/compiler/src/complexity_lattice_generated.rs b/src/v3/compiler/src/complexity_lattice_generated.rs index 657f9229496..4bf7753e842 100644 --- a/src/v3/compiler/src/complexity_lattice_generated.rs +++ b/src/v3/compiler/src/complexity_lattice_generated.rs @@ -1,9 +1,8 @@ -// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). -// -// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). -// Dissolution receipt: PB-0 NON_TEST retirement — former hand module. -// - +//! AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). +//! +//! Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +//! Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. +//! //! **`complexity_enforcement_budget_dominates`** — executable **T-LAS enforcement** //! ordering on `AsymptoticClass` for `complexity_enforcement_violates` / //! `EnforcedApplication` (gate #92). It matches `v3.std.algebra::asymptotic_dominates` diff --git a/src/v3/compiler/src/cost_basis_declaration_generated.rs b/src/v3/compiler/src/cost_basis_declaration_generated.rs index 4320b71912f..faf0b7b0580 100644 --- a/src/v3/compiler/src/cost_basis_declaration_generated.rs +++ b/src/v3/compiler/src/cost_basis_declaration_generated.rs @@ -1,9 +1,8 @@ -// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). -// -// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). -// Dissolution receipt: PB-0 NON_TEST retirement — former hand module. -// - +//! AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). +//! +//! Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +//! Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. +//! //! Materialize [`CostBasisDeclaration`](crate::lens_cost_symbolic::CostBasisDeclaration) from //! lowered [`Dag`](crate::dag::Dag) facts — interim **consumer boundary** for gate **#93** //! (`crdt_cost_basis_demonstrated`) **substrate / symbolic-cost slice**; folding authored basis diff --git a/src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs b/src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs index ec9187be800..dfe31a9ca25 100644 --- a/src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs +++ b/src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs @@ -1,9 +1,8 @@ -// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). -// -// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). -// Dissolution receipt: PB-0 NON_TEST retirement — former hand module. -// - +//! AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). +//! +//! Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +//! Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. +//! //! `CollectionOps.*_contract` fields cross `DeclarationRef` boundaries. Emit //! consumers must fail closed on the **algebra identity** (P1): each referenced //! declaration must be a `MethodTemplateContract` data binding whose diff --git a/src/v3/compiler/src/emit_rust_bin_shim_generated.rs b/src/v3/compiler/src/emit_rust_bin_shim_generated.rs index c45d258c569..57d2928995d 100644 --- a/src/v3/compiler/src/emit_rust_bin_shim_generated.rs +++ b/src/v3/compiler/src/emit_rust_bin_shim_generated.rs @@ -1,16 +1,17 @@ -// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). -// -// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). -// Dissolution receipt: PB-0 NON_TEST retirement — former hand module. -// - +//! AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). +//! +//! Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +//! Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. +//! //! PB-1 — Rust `main.rs` shell for generated `[[bin]]` shims (Item 5). //! -//! **SG-0 / bounded-seed receipt:** hand-authored path is enumerated in -//! `EXPECTED_HAND_AUTHORED_NON_TEST` in `sg0_census_test.rs` (not -//! `GENERATED_FILES` / `build.rs` output). Narrow host helper for §4.2 shell -//! text only; see `docs/invariants/bounded-substrate-seed.md` (no new -//! substrate primitive — mirrors existing `std.process.ProcessExit`). +//! **SG-0 / bounded-seed receipt:** this body is spliced from +//! `pb0_cycle4_emit_templates/emit_rust_bin_shim.rs` by `build.rs` +//! (`emit_pb0_cycle4_host_authority_modules`); the output path is in +//! `GENERATED_FILES` / `REGEN_OUTPUTS` (not `EXPECTED_HAND_AUTHORED_NON_TEST`). +//! Narrow host helper for §4.2 shell text only; see +//! `docs/invariants/bounded-substrate-seed.md` (no new substrate primitive — +//! mirrors existing `std.process.ProcessExit`). //! //! Authority: `docs/design-pb-runtime-interpreter.md` §4.2 (`main` → //! `ProcessExit` → `ExitCode`). This module owns **parameterized Rust text** diff --git a/src/v3/compiler/src/enforced_lens_application_generated.rs b/src/v3/compiler/src/enforced_lens_application_generated.rs index efbbae6a1e8..5bf5143dddf 100644 --- a/src/v3/compiler/src/enforced_lens_application_generated.rs +++ b/src/v3/compiler/src/enforced_lens_application_generated.rs @@ -1,9 +1,8 @@ -// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). -// -// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). -// Dissolution receipt: PB-0 NON_TEST retirement — former hand module. -// - +//! AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). +//! +//! Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +//! Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. +//! //! Enforced lens applications (`EnforcedApplication` from `lens_application.dag`). //! //! Gate #91 (`enforce_violation_routing_landed`): budget-violation diagnostics are diff --git a/src/v3/compiler/src/int_literal_ranges_generated.rs b/src/v3/compiler/src/int_literal_ranges_generated.rs index 2c65365effd..603d2d725b4 100644 --- a/src/v3/compiler/src/int_literal_ranges_generated.rs +++ b/src/v3/compiler/src/int_literal_ranges_generated.rs @@ -1,9 +1,8 @@ -// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). -// -// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). -// Dissolution receipt: PB-0 NON_TEST retirement — former hand module. -// - +//! AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). +//! +//! Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +//! Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. +//! //! Integer literal magnitude vs declared integer types — **Q1 consumer**. //! //! Authoritative modeling: [`docs/design-emission-model.md`](../../../../docs/design-emission-model.md) diff --git a/src/v3/compiler/src/pb_method_template_projection_generated.rs b/src/v3/compiler/src/pb_method_template_projection_generated.rs index 1d1e9ae5d96..e17f2330030 100644 --- a/src/v3/compiler/src/pb_method_template_projection_generated.rs +++ b/src/v3/compiler/src/pb_method_template_projection_generated.rs @@ -1,9 +1,8 @@ -// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). -// -// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). -// Dissolution receipt: PB-0 NON_TEST retirement — former hand module. -// - +//! AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). +//! +//! Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +//! Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. +//! //! PB-Bootstrap-Process consumer hook: project `MethodTemplateContract` rows //! from the full bootstrap `Dag`. //! diff --git a/src/v3/compiler/src/process_exit.rs b/src/v3/compiler/src/process_exit.rs index d4b83262d3e..1b4d013b55d 100644 --- a/src/v3/compiler/src/process_exit.rs +++ b/src/v3/compiler/src/process_exit.rs @@ -4,9 +4,10 @@ //! `EXPECTED_HAND_AUTHORED_NON_TEST` in `sg0_census_test.rs` (not //! `GENERATED_FILES` / `build.rs` output). //! -//! Used by PB-1 emitted bin-shim `main.rs` shells (`emit_rust_bin_shim`) so -//! generated sources can `match` on the same structural shape the `.dag` -//! substrate declares, without inventing a parallel exit carrier. +//! Used by PB-1 emitted bin-shim `main.rs` shells (the `emit_rust_bin_shim` module +//! backed by `emit_rust_bin_shim_generated.rs` in `GENERATED_FILES`) so generated +//! sources can `match` on the same structural shape the `.dag` substrate declares, +//! without inventing a parallel exit carrier. // Practice 4 (coproduct checkpoint, `docs/modeling-discipline.md` §P1): 🟢 GREEN — // terminal host mirror of `dsl/std/process.dag` `ProcessExit`; no extra variants From 846b418151171decead9904e2aee29b6b29a981f Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 13 May 2026 20:22:22 -0400 Subject: [PATCH 05/10] =?UTF-8?q?WIP:=20PB-0=20cycle-4=20NON=5FTEST=20reti?= =?UTF-8?q?rement=20(msg=5F5d7f3491):=207=20paths=20=E2=80=94=20complexity?= =?UTF-8?q?=5Fla?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/v3/compiler/build.rs | 11 ++++++----- src/v3/compiler/src/complexity_lattice_generated.rs | 11 ++++++----- .../compiler/src/cost_basis_declaration_generated.rs | 11 ++++++----- .../emit/collection_ops_method_contract_generated.rs | 11 ++++++----- src/v3/compiler/src/emit_rust_bin_shim_generated.rs | 11 ++++++----- .../src/enforced_lens_application_generated.rs | 11 ++++++----- src/v3/compiler/src/int_literal_ranges_generated.rs | 11 ++++++----- src/v3/compiler/src/lib.rs | 11 +++++++++++ .../src/pb_method_template_projection_generated.rs | 11 ++++++----- 9 files changed, 59 insertions(+), 40 deletions(-) diff --git a/src/v3/compiler/build.rs b/src/v3/compiler/build.rs index 4d52dc08f7a..6b8d43869bd 100644 --- a/src/v3/compiler/build.rs +++ b/src/v3/compiler/build.rs @@ -444,11 +444,12 @@ fn emit_r1_gates_fixture(manifest_path: &Path, v3_dir: &Path) { /// rebuild; do not hand-edit `*_generated.rs` outputs. fn emit_pb0_cycle4_host_authority_modules(repo_root: &Path, manifest_path: &Path) { const HEADER: &str = concat!( - "//! AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`).\n", - "//!\n", - "//! Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate).\n", - "//! Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild.\n", - "//!\n", + "// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`).\n", + "//\n", + "// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate).\n", + "// Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild.\n", + "//\n", + "\n", ); let tmpl_dir = manifest_path.join("pb0_cycle4_emit_templates"); let specs: &[(&str, &str)] = &[ diff --git a/src/v3/compiler/src/complexity_lattice_generated.rs b/src/v3/compiler/src/complexity_lattice_generated.rs index 4bf7753e842..b7d4339a7cc 100644 --- a/src/v3/compiler/src/complexity_lattice_generated.rs +++ b/src/v3/compiler/src/complexity_lattice_generated.rs @@ -1,8 +1,9 @@ -//! AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). -//! -//! Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). -//! Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. -//! +// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). +// +// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +// Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. +// + //! **`complexity_enforcement_budget_dominates`** — executable **T-LAS enforcement** //! ordering on `AsymptoticClass` for `complexity_enforcement_violates` / //! `EnforcedApplication` (gate #92). It matches `v3.std.algebra::asymptotic_dominates` diff --git a/src/v3/compiler/src/cost_basis_declaration_generated.rs b/src/v3/compiler/src/cost_basis_declaration_generated.rs index faf0b7b0580..330f0b990da 100644 --- a/src/v3/compiler/src/cost_basis_declaration_generated.rs +++ b/src/v3/compiler/src/cost_basis_declaration_generated.rs @@ -1,8 +1,9 @@ -//! AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). -//! -//! Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). -//! Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. -//! +// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). +// +// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +// Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. +// + //! Materialize [`CostBasisDeclaration`](crate::lens_cost_symbolic::CostBasisDeclaration) from //! lowered [`Dag`](crate::dag::Dag) facts — interim **consumer boundary** for gate **#93** //! (`crdt_cost_basis_demonstrated`) **substrate / symbolic-cost slice**; folding authored basis diff --git a/src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs b/src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs index dfe31a9ca25..c329edd27e3 100644 --- a/src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs +++ b/src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs @@ -1,8 +1,9 @@ -//! AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). -//! -//! Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). -//! Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. -//! +// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). +// +// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +// Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. +// + //! `CollectionOps.*_contract` fields cross `DeclarationRef` boundaries. Emit //! consumers must fail closed on the **algebra identity** (P1): each referenced //! declaration must be a `MethodTemplateContract` data binding whose diff --git a/src/v3/compiler/src/emit_rust_bin_shim_generated.rs b/src/v3/compiler/src/emit_rust_bin_shim_generated.rs index 57d2928995d..f77726f8f16 100644 --- a/src/v3/compiler/src/emit_rust_bin_shim_generated.rs +++ b/src/v3/compiler/src/emit_rust_bin_shim_generated.rs @@ -1,8 +1,9 @@ -//! AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). -//! -//! Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). -//! Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. -//! +// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). +// +// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +// Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. +// + //! PB-1 — Rust `main.rs` shell for generated `[[bin]]` shims (Item 5). //! //! **SG-0 / bounded-seed receipt:** this body is spliced from diff --git a/src/v3/compiler/src/enforced_lens_application_generated.rs b/src/v3/compiler/src/enforced_lens_application_generated.rs index 5bf5143dddf..5ee72d7ed45 100644 --- a/src/v3/compiler/src/enforced_lens_application_generated.rs +++ b/src/v3/compiler/src/enforced_lens_application_generated.rs @@ -1,8 +1,9 @@ -//! AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). -//! -//! Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). -//! Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. -//! +// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). +// +// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +// Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. +// + //! Enforced lens applications (`EnforcedApplication` from `lens_application.dag`). //! //! Gate #91 (`enforce_violation_routing_landed`): budget-violation diagnostics are diff --git a/src/v3/compiler/src/int_literal_ranges_generated.rs b/src/v3/compiler/src/int_literal_ranges_generated.rs index 603d2d725b4..ea0c314be92 100644 --- a/src/v3/compiler/src/int_literal_ranges_generated.rs +++ b/src/v3/compiler/src/int_literal_ranges_generated.rs @@ -1,8 +1,9 @@ -//! AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). -//! -//! Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). -//! Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. -//! +// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). +// +// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +// Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. +// + //! Integer literal magnitude vs declared integer types — **Q1 consumer**. //! //! Authoritative modeling: [`docs/design-emission-model.md`](../../../../docs/design-emission-model.md) diff --git a/src/v3/compiler/src/lib.rs b/src/v3/compiler/src/lib.rs index 36b28dbd03d..2c6a2d08fed 100644 --- a/src/v3/compiler/src/lib.rs +++ b/src/v3/compiler/src/lib.rs @@ -16,9 +16,12 @@ // payload. pub mod cementing_dispatch; +<<<<<<< HEAD pub use cementing_dispatch::gunbc_ci; pub use cementing_dispatch::integration_rs_wiring_scan; pub use cementing_dispatch::r3_gate_87_cementing_regen_runner_suites; +======= +>>>>>>> 158d44d17 (WIP: PB-0 cycle-4 NON_TEST retirement (msg_5d7f3491): 7 paths — complexity_la) #[path = "complexity_lattice_generated.rs"] pub mod complexity_lattice; pub mod dag; @@ -35,8 +38,16 @@ pub use enforced_lens_application::check_enforced_lens_applications; pub use enforced_lens_application::gate_58_test_parse_timing_budget_violation_max_ns_pair; #[doc(hidden)] pub use enforced_lens_application::gate_58_test_raise_modeled_ci_timing_measurement_duration_ns; +<<<<<<< HEAD #[path = "pb_method_template_projection_generated.rs"] pub mod pb_method_template_projection; +======= +pub mod integration_rs_wiring_scan; +pub mod lens_t_las_carrier; +#[path = "pb_method_template_projection_generated.rs"] +pub mod pb_method_template_projection; +pub mod r3_gate_87_cementing_regen_runner_suites; +>>>>>>> 158d44d17 (WIP: PB-0 cycle-4 NON_TEST retirement (msg_5d7f3491): 7 paths — complexity_la) mod regen_bootstrap_emit; pub mod regen_tokenize; diff --git a/src/v3/compiler/src/pb_method_template_projection_generated.rs b/src/v3/compiler/src/pb_method_template_projection_generated.rs index e17f2330030..9291e615c74 100644 --- a/src/v3/compiler/src/pb_method_template_projection_generated.rs +++ b/src/v3/compiler/src/pb_method_template_projection_generated.rs @@ -1,8 +1,9 @@ -//! AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). -//! -//! Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). -//! Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. -//! +// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). +// +// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +// Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. +// + //! PB-Bootstrap-Process consumer hook: project `MethodTemplateContract` rows //! from the full bootstrap `Dag`. //! From 6ae6f61ddc94b42449fb294340b68319180f14f2 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 13 May 2026 19:45:09 -0400 Subject: [PATCH 06/10] =?UTF-8?q?WIP:=20PB-0=20cycle-4=20NON=5FTEST=20reti?= =?UTF-8?q?rement=20(msg=5F5d7f3491):=207=20paths=20=E2=80=94=20complexity?= =?UTF-8?q?=5Fla?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/v3/compiler/src/lib.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/v3/compiler/src/lib.rs b/src/v3/compiler/src/lib.rs index 2c6a2d08fed..36b28dbd03d 100644 --- a/src/v3/compiler/src/lib.rs +++ b/src/v3/compiler/src/lib.rs @@ -16,12 +16,9 @@ // payload. pub mod cementing_dispatch; -<<<<<<< HEAD pub use cementing_dispatch::gunbc_ci; pub use cementing_dispatch::integration_rs_wiring_scan; pub use cementing_dispatch::r3_gate_87_cementing_regen_runner_suites; -======= ->>>>>>> 158d44d17 (WIP: PB-0 cycle-4 NON_TEST retirement (msg_5d7f3491): 7 paths — complexity_la) #[path = "complexity_lattice_generated.rs"] pub mod complexity_lattice; pub mod dag; @@ -38,16 +35,8 @@ pub use enforced_lens_application::check_enforced_lens_applications; pub use enforced_lens_application::gate_58_test_parse_timing_budget_violation_max_ns_pair; #[doc(hidden)] pub use enforced_lens_application::gate_58_test_raise_modeled_ci_timing_measurement_duration_ns; -<<<<<<< HEAD #[path = "pb_method_template_projection_generated.rs"] pub mod pb_method_template_projection; -======= -pub mod integration_rs_wiring_scan; -pub mod lens_t_las_carrier; -#[path = "pb_method_template_projection_generated.rs"] -pub mod pb_method_template_projection; -pub mod r3_gate_87_cementing_regen_runner_suites; ->>>>>>> 158d44d17 (WIP: PB-0 cycle-4 NON_TEST retirement (msg_5d7f3491): 7 paths — complexity_la) mod regen_bootstrap_emit; pub mod regen_tokenize; From 5b6bf51375d5ecbab6e6f98235da4fe0c405dada Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 13 May 2026 23:47:23 +0000 Subject: [PATCH 07/10] fix(pb-0): use .rs.in templates, refresh generated headers, sort TEST census Rename pb0_cycle4_emit_templates sources to *.rs.in so SG-0 walk does not count them as hand-authored Rust. Point build.rs splice at the new names. Restore // header on generated modules for #[path] module roots. Fix EXPECTED_HAND_AUTHORED_TEST ASCII order (boundary l5 before m*). Co-authored-by: Cursor --- src/v3/compiler/build.rs | 18 +++++++++--------- ...ity_lattice.rs => complexity_lattice.rs.in} | 0 ...aration.rs => cost_basis_declaration.rs.in} | 0 ... emit_collection_ops_method_contract.rs.in} | 0 ...st_bin_shim.rs => emit_rust_bin_shim.rs.in} | 0 ...tion.rs => enforced_lens_application.rs.in} | 0 ...eral_ranges.rs => int_literal_ranges.rs.in} | 0 ....rs => pb_method_template_projection.rs.in} | 0 .../src/complexity_lattice_generated.rs | 2 +- .../src/cost_basis_declaration_generated.rs | 2 +- ...collection_ops_method_contract_generated.rs | 2 +- .../src/emit_rust_bin_shim_generated.rs | 2 +- .../src/enforced_lens_application_generated.rs | 2 +- .../src/int_literal_ranges_generated.rs | 2 +- .../pb_method_template_projection_generated.rs | 2 +- .../tests/integration/sg0_census_test.rs | 2 +- 16 files changed, 17 insertions(+), 17 deletions(-) rename src/v3/compiler/pb0_cycle4_emit_templates/{complexity_lattice.rs => complexity_lattice.rs.in} (100%) rename src/v3/compiler/pb0_cycle4_emit_templates/{cost_basis_declaration.rs => cost_basis_declaration.rs.in} (100%) rename src/v3/compiler/pb0_cycle4_emit_templates/{emit_collection_ops_method_contract.rs => emit_collection_ops_method_contract.rs.in} (100%) rename src/v3/compiler/pb0_cycle4_emit_templates/{emit_rust_bin_shim.rs => emit_rust_bin_shim.rs.in} (100%) rename src/v3/compiler/pb0_cycle4_emit_templates/{enforced_lens_application.rs => enforced_lens_application.rs.in} (100%) rename src/v3/compiler/pb0_cycle4_emit_templates/{int_literal_ranges.rs => int_literal_ranges.rs.in} (100%) rename src/v3/compiler/pb0_cycle4_emit_templates/{pb_method_template_projection.rs => pb_method_template_projection.rs.in} (100%) diff --git a/src/v3/compiler/build.rs b/src/v3/compiler/build.rs index 6b8d43869bd..279b3ee3cae 100644 --- a/src/v3/compiler/build.rs +++ b/src/v3/compiler/build.rs @@ -446,7 +446,7 @@ fn emit_pb0_cycle4_host_authority_modules(repo_root: &Path, manifest_path: &Path const HEADER: &str = concat!( "// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`).\n", "//\n", - "// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate).\n", + "// Template: `pb0_cycle4_emit_templates/.rs.in` (relative to this crate).\n", "// Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild.\n", "//\n", "\n", @@ -454,31 +454,31 @@ fn emit_pb0_cycle4_host_authority_modules(repo_root: &Path, manifest_path: &Path let tmpl_dir = manifest_path.join("pb0_cycle4_emit_templates"); let specs: &[(&str, &str)] = &[ ( - "complexity_lattice.rs", + "complexity_lattice.rs.in", "src/v3/compiler/src/complexity_lattice_generated.rs", ), ( - "cost_basis_declaration.rs", + "cost_basis_declaration.rs.in", "src/v3/compiler/src/cost_basis_declaration_generated.rs", ), ( - "emit_collection_ops_method_contract.rs", + "emit_collection_ops_method_contract.rs.in", "src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs", ), ( - "emit_rust_bin_shim.rs", + "emit_rust_bin_shim.rs.in", "src/v3/compiler/src/emit_rust_bin_shim_generated.rs", ), ( - "enforced_lens_application.rs", + "enforced_lens_application.rs.in", "src/v3/compiler/src/enforced_lens_application_generated.rs", ), ( - "int_literal_ranges.rs", + "int_literal_ranges.rs.in", "src/v3/compiler/src/int_literal_ranges_generated.rs", ), ( - "pb_method_template_projection.rs", + "pb_method_template_projection.rs.in", "src/v3/compiler/src/pb_method_template_projection_generated.rs", ), ]; @@ -740,7 +740,7 @@ fn main() { "src/v3/compiler/src/bootstrap_generated_without_parse_surface.rs", "src/v3/compiler/src/bootstrap_std_generated.rs", // PB-0 cycle-4: former hand `EXPECTED_HAND_AUTHORED_NON_TEST` modules — templates under - // `pb0_cycle4_emit_templates/`, emitted by `emit_pb0_cycle4_host_authority_modules`. + // `pb0_cycle4_emit_templates/*.rs.in`, emitted by `emit_pb0_cycle4_host_authority_modules`. "src/v3/compiler/src/complexity_lattice_generated.rs", // SG-5 substrate / runtime-mirror projections, generated from // `src/v3/std/substrate.dag` + `src/v3/std/parse_surface.dag` diff --git a/src/v3/compiler/pb0_cycle4_emit_templates/complexity_lattice.rs b/src/v3/compiler/pb0_cycle4_emit_templates/complexity_lattice.rs.in similarity index 100% rename from src/v3/compiler/pb0_cycle4_emit_templates/complexity_lattice.rs rename to src/v3/compiler/pb0_cycle4_emit_templates/complexity_lattice.rs.in diff --git a/src/v3/compiler/pb0_cycle4_emit_templates/cost_basis_declaration.rs b/src/v3/compiler/pb0_cycle4_emit_templates/cost_basis_declaration.rs.in similarity index 100% rename from src/v3/compiler/pb0_cycle4_emit_templates/cost_basis_declaration.rs rename to src/v3/compiler/pb0_cycle4_emit_templates/cost_basis_declaration.rs.in diff --git a/src/v3/compiler/pb0_cycle4_emit_templates/emit_collection_ops_method_contract.rs b/src/v3/compiler/pb0_cycle4_emit_templates/emit_collection_ops_method_contract.rs.in similarity index 100% rename from src/v3/compiler/pb0_cycle4_emit_templates/emit_collection_ops_method_contract.rs rename to src/v3/compiler/pb0_cycle4_emit_templates/emit_collection_ops_method_contract.rs.in diff --git a/src/v3/compiler/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs b/src/v3/compiler/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs.in similarity index 100% rename from src/v3/compiler/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs rename to src/v3/compiler/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs.in diff --git a/src/v3/compiler/pb0_cycle4_emit_templates/enforced_lens_application.rs b/src/v3/compiler/pb0_cycle4_emit_templates/enforced_lens_application.rs.in similarity index 100% rename from src/v3/compiler/pb0_cycle4_emit_templates/enforced_lens_application.rs rename to src/v3/compiler/pb0_cycle4_emit_templates/enforced_lens_application.rs.in diff --git a/src/v3/compiler/pb0_cycle4_emit_templates/int_literal_ranges.rs b/src/v3/compiler/pb0_cycle4_emit_templates/int_literal_ranges.rs.in similarity index 100% rename from src/v3/compiler/pb0_cycle4_emit_templates/int_literal_ranges.rs rename to src/v3/compiler/pb0_cycle4_emit_templates/int_literal_ranges.rs.in diff --git a/src/v3/compiler/pb0_cycle4_emit_templates/pb_method_template_projection.rs b/src/v3/compiler/pb0_cycle4_emit_templates/pb_method_template_projection.rs.in similarity index 100% rename from src/v3/compiler/pb0_cycle4_emit_templates/pb_method_template_projection.rs rename to src/v3/compiler/pb0_cycle4_emit_templates/pb_method_template_projection.rs.in diff --git a/src/v3/compiler/src/complexity_lattice_generated.rs b/src/v3/compiler/src/complexity_lattice_generated.rs index b7d4339a7cc..fe1491f80ae 100644 --- a/src/v3/compiler/src/complexity_lattice_generated.rs +++ b/src/v3/compiler/src/complexity_lattice_generated.rs @@ -1,6 +1,6 @@ // AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). // -// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +// Template: `pb0_cycle4_emit_templates/.rs.in` (relative to this crate). // Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. // diff --git a/src/v3/compiler/src/cost_basis_declaration_generated.rs b/src/v3/compiler/src/cost_basis_declaration_generated.rs index 330f0b990da..7417a83e1f6 100644 --- a/src/v3/compiler/src/cost_basis_declaration_generated.rs +++ b/src/v3/compiler/src/cost_basis_declaration_generated.rs @@ -1,6 +1,6 @@ // AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). // -// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +// Template: `pb0_cycle4_emit_templates/.rs.in` (relative to this crate). // Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. // diff --git a/src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs b/src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs index c329edd27e3..08015b1634f 100644 --- a/src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs +++ b/src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs @@ -1,6 +1,6 @@ // AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). // -// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +// Template: `pb0_cycle4_emit_templates/.rs.in` (relative to this crate). // Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. // diff --git a/src/v3/compiler/src/emit_rust_bin_shim_generated.rs b/src/v3/compiler/src/emit_rust_bin_shim_generated.rs index f77726f8f16..6429946306f 100644 --- a/src/v3/compiler/src/emit_rust_bin_shim_generated.rs +++ b/src/v3/compiler/src/emit_rust_bin_shim_generated.rs @@ -1,6 +1,6 @@ // AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). // -// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +// Template: `pb0_cycle4_emit_templates/.rs.in` (relative to this crate). // Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. // diff --git a/src/v3/compiler/src/enforced_lens_application_generated.rs b/src/v3/compiler/src/enforced_lens_application_generated.rs index 5ee72d7ed45..47835375529 100644 --- a/src/v3/compiler/src/enforced_lens_application_generated.rs +++ b/src/v3/compiler/src/enforced_lens_application_generated.rs @@ -1,6 +1,6 @@ // AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). // -// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +// Template: `pb0_cycle4_emit_templates/.rs.in` (relative to this crate). // Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. // diff --git a/src/v3/compiler/src/int_literal_ranges_generated.rs b/src/v3/compiler/src/int_literal_ranges_generated.rs index ea0c314be92..b000cfe37be 100644 --- a/src/v3/compiler/src/int_literal_ranges_generated.rs +++ b/src/v3/compiler/src/int_literal_ranges_generated.rs @@ -1,6 +1,6 @@ // AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). // -// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +// Template: `pb0_cycle4_emit_templates/.rs.in` (relative to this crate). // Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. // diff --git a/src/v3/compiler/src/pb_method_template_projection_generated.rs b/src/v3/compiler/src/pb_method_template_projection_generated.rs index 9291e615c74..ba82316a191 100644 --- a/src/v3/compiler/src/pb_method_template_projection_generated.rs +++ b/src/v3/compiler/src/pb_method_template_projection_generated.rs @@ -1,6 +1,6 @@ // AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). // -// Template: `pb0_cycle4_emit_templates/.rs` (relative to this crate). +// Template: `pb0_cycle4_emit_templates/.rs.in` (relative to this crate). // Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. // diff --git a/src/v3/compiler/tests/integration/sg0_census_test.rs b/src/v3/compiler/tests/integration/sg0_census_test.rs index f5000eb4790..b63e03ee9cd 100644 --- a/src/v3/compiler/tests/integration/sg0_census_test.rs +++ b/src/v3/compiler/tests/integration/sg0_census_test.rs @@ -334,13 +334,13 @@ const EXPECTED_HAND_AUTHORED_NON_TEST: &[&str] = &[ // "ported-but-still-listed" or "pending-port" set); the ratchet's whole point is that one // monotonically-shrinking authority tracks the Rust→`.dag` migration. const EXPECTED_HAND_AUTHORED_TEST: &[&str] = &[ + "src/v3/compiler/tests/boundary/l5_cross_target_consistency.rs", "src/v3/compiler/tests/boundary/m1_3_emit_go_test.rs", "src/v3/compiler/tests/boundary/m1_3_emit_rust_test.rs", "src/v3/compiler/tests/boundary/m1_4_emit_python_test.rs", "src/v3/compiler/tests/boundary/m1_5_emit_omni_demo_test.rs", "src/v3/compiler/tests/boundary/m2_emit_multi_field_struct_variant_test.rs", "src/v3/compiler/tests/determinism_test.rs", - "src/v3/compiler/tests/boundary/l5_cross_target_consistency.rs", "src/v3/compiler/tests/integration.rs", "src/v3/compiler/tests/integration/anthropic_messages_callable_test.rs", // R3 gate #68 (`anthropic_wire_demonstration`): hermetic typed request/response From 568ce2947f6f3405ddec63d54ef5de82f24f0c43 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 13 May 2026 23:52:44 +0000 Subject: [PATCH 08/10] fix(pb-0): move cycle-4 templates to tools/ (outside src/v3/compiler) Addresses review feedback that templates under the compiler crate looked like steady-state scaffold: sources now live at tools/pb0_cycle4_emit_templates/ so they sit outside the SG-0 census root. build.rs documents the PB-0 partition receipt vs T-LP full .dag migration track. Co-authored-by: Cursor --- src/v3/compiler/build.rs | 25 +++++++++++++------ .../src/complexity_lattice_generated.rs | 4 +-- .../src/cost_basis_declaration_generated.rs | 4 +-- ...ollection_ops_method_contract_generated.rs | 4 +-- .../src/emit_rust_bin_shim_generated.rs | 4 +-- .../enforced_lens_application_generated.rs | 4 +-- .../src/int_literal_ranges_generated.rs | 4 +-- ...pb_method_template_projection_generated.rs | 4 +-- .../complexity_lattice.rs.in | 0 .../cost_basis_declaration.rs.in | 0 .../emit_collection_ops_method_contract.rs.in | 0 .../emit_rust_bin_shim.rs.in | 0 .../enforced_lens_application.rs.in | 0 .../int_literal_ranges.rs.in | 0 .../pb_method_template_projection.rs.in | 0 15 files changed, 31 insertions(+), 22 deletions(-) rename {src/v3/compiler => tools}/pb0_cycle4_emit_templates/complexity_lattice.rs.in (100%) rename {src/v3/compiler => tools}/pb0_cycle4_emit_templates/cost_basis_declaration.rs.in (100%) rename {src/v3/compiler => tools}/pb0_cycle4_emit_templates/emit_collection_ops_method_contract.rs.in (100%) rename {src/v3/compiler => tools}/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs.in (100%) rename {src/v3/compiler => tools}/pb0_cycle4_emit_templates/enforced_lens_application.rs.in (100%) rename {src/v3/compiler => tools}/pb0_cycle4_emit_templates/int_literal_ranges.rs.in (100%) rename {src/v3/compiler => tools}/pb0_cycle4_emit_templates/pb_method_template_projection.rs.in (100%) diff --git a/src/v3/compiler/build.rs b/src/v3/compiler/build.rs index 279b3ee3cae..5650a5fc1a6 100644 --- a/src/v3/compiler/build.rs +++ b/src/v3/compiler/build.rs @@ -440,18 +440,23 @@ fn emit_r1_gates_fixture(manifest_path: &Path, v3_dir: &Path) { /// checked-in templates so `REGEN_OUTPUTS` / `GENERATED_FILES` stay the sole SG-0 partition /// authority (`sg0_v3_hand_authored_census`). /// -/// Dissolution: edit files under `pb0_cycle4_emit_templates/` (next to this build script) and -/// rebuild; do not hand-edit `*_generated.rs` outputs. -fn emit_pb0_cycle4_host_authority_modules(repo_root: &Path, manifest_path: &Path) { +/// **Templates** live under `tools/pb0_cycle4_emit_templates/*.rs.in` (workspace root — not under +/// `src/v3/compiler/`, so they are outside the SG-0 `.rs` walk and not misread as hand-authored +/// compiler sources). **Full substrate dissolution** (`.dag` / lens-producer authority replacing +/// this body) is the separate T-LP-Retirement program; this path is the bounded PB-0 census +/// partition receipt per `docs/briefs/r3-pb0-non-test-retirement-worker.md`. +/// +/// Do not hand-edit `*_generated.rs`; edit the `.rs.in` templates and rebuild. +fn emit_pb0_cycle4_host_authority_modules(repo_root: &Path) { const HEADER: &str = concat!( "// AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`).\n", "//\n", - "// Template: `pb0_cycle4_emit_templates/.rs.in` (relative to this crate).\n", - "// Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild.\n", + "// Template: `tools/pb0_cycle4_emit_templates/.rs.in` (workspace root).\n", + "// Dissolution receipt: PB-0 NON_TEST partition — `REGEN_OUTPUTS` producer; full `.dag` emit T-LP track.\n", "//\n", "\n", ); - let tmpl_dir = manifest_path.join("pb0_cycle4_emit_templates"); + let tmpl_dir = repo_root.join("tools/pb0_cycle4_emit_templates"); let specs: &[(&str, &str)] = &[ ( "complexity_lattice.rs.in", @@ -609,7 +614,7 @@ fn main() { let src_dir = v3_dir.parent().expect("v3 dir has parent"); let repo_root = src_dir.parent().expect("src dir has parent"); emit_render_repeat_string_bootstrap(repo_root); - emit_pb0_cycle4_host_authority_modules(repo_root, &manifest_path); + emit_pb0_cycle4_host_authority_modules(repo_root); let std_dir = v3_dir.join("std"); let spec_dir = v3_dir.join("spec"); let compiler_dir = manifest_path.clone(); @@ -626,6 +631,10 @@ fn main() { println!("cargo:rerun-if-changed={}", extdeps_dir.display()); println!("cargo:rerun-if-changed={}", gunbc_dir.display()); println!("cargo:rerun-if-changed={}", dsl_std_dir.display()); + println!( + "cargo:rerun-if-changed={}", + repo_root.join("tools/pb0_cycle4_emit_templates").display() + ); // Structural-recursion termination analysis walks a recursing // argument back to its declared Disj connective (see @@ -740,7 +749,7 @@ fn main() { "src/v3/compiler/src/bootstrap_generated_without_parse_surface.rs", "src/v3/compiler/src/bootstrap_std_generated.rs", // PB-0 cycle-4: former hand `EXPECTED_HAND_AUTHORED_NON_TEST` modules — templates under - // `pb0_cycle4_emit_templates/*.rs.in`, emitted by `emit_pb0_cycle4_host_authority_modules`. + // `tools/pb0_cycle4_emit_templates/*.rs.in`, emitted by `emit_pb0_cycle4_host_authority_modules`. "src/v3/compiler/src/complexity_lattice_generated.rs", // SG-5 substrate / runtime-mirror projections, generated from // `src/v3/std/substrate.dag` + `src/v3/std/parse_surface.dag` diff --git a/src/v3/compiler/src/complexity_lattice_generated.rs b/src/v3/compiler/src/complexity_lattice_generated.rs index fe1491f80ae..0ccf6156b69 100644 --- a/src/v3/compiler/src/complexity_lattice_generated.rs +++ b/src/v3/compiler/src/complexity_lattice_generated.rs @@ -1,7 +1,7 @@ // AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). // -// Template: `pb0_cycle4_emit_templates/.rs.in` (relative to this crate). -// Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. +// Template: `tools/pb0_cycle4_emit_templates/.rs.in` (workspace root). +// Dissolution receipt: PB-0 NON_TEST partition — `REGEN_OUTPUTS` producer; full `.dag` emit T-LP track. // //! **`complexity_enforcement_budget_dominates`** — executable **T-LAS enforcement** diff --git a/src/v3/compiler/src/cost_basis_declaration_generated.rs b/src/v3/compiler/src/cost_basis_declaration_generated.rs index 7417a83e1f6..f5c14ac8b4a 100644 --- a/src/v3/compiler/src/cost_basis_declaration_generated.rs +++ b/src/v3/compiler/src/cost_basis_declaration_generated.rs @@ -1,7 +1,7 @@ // AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). // -// Template: `pb0_cycle4_emit_templates/.rs.in` (relative to this crate). -// Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. +// Template: `tools/pb0_cycle4_emit_templates/.rs.in` (workspace root). +// Dissolution receipt: PB-0 NON_TEST partition — `REGEN_OUTPUTS` producer; full `.dag` emit T-LP track. // //! Materialize [`CostBasisDeclaration`](crate::lens_cost_symbolic::CostBasisDeclaration) from diff --git a/src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs b/src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs index 08015b1634f..5b68d67e0f0 100644 --- a/src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs +++ b/src/v3/compiler/src/emit/collection_ops_method_contract_generated.rs @@ -1,7 +1,7 @@ // AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). // -// Template: `pb0_cycle4_emit_templates/.rs.in` (relative to this crate). -// Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. +// Template: `tools/pb0_cycle4_emit_templates/.rs.in` (workspace root). +// Dissolution receipt: PB-0 NON_TEST partition — `REGEN_OUTPUTS` producer; full `.dag` emit T-LP track. // //! `CollectionOps.*_contract` fields cross `DeclarationRef` boundaries. Emit diff --git a/src/v3/compiler/src/emit_rust_bin_shim_generated.rs b/src/v3/compiler/src/emit_rust_bin_shim_generated.rs index 6429946306f..56a05864ae9 100644 --- a/src/v3/compiler/src/emit_rust_bin_shim_generated.rs +++ b/src/v3/compiler/src/emit_rust_bin_shim_generated.rs @@ -1,7 +1,7 @@ // AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). // -// Template: `pb0_cycle4_emit_templates/.rs.in` (relative to this crate). -// Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. +// Template: `tools/pb0_cycle4_emit_templates/.rs.in` (workspace root). +// Dissolution receipt: PB-0 NON_TEST partition — `REGEN_OUTPUTS` producer; full `.dag` emit T-LP track. // //! PB-1 — Rust `main.rs` shell for generated `[[bin]]` shims (Item 5). diff --git a/src/v3/compiler/src/enforced_lens_application_generated.rs b/src/v3/compiler/src/enforced_lens_application_generated.rs index 47835375529..3997c75714f 100644 --- a/src/v3/compiler/src/enforced_lens_application_generated.rs +++ b/src/v3/compiler/src/enforced_lens_application_generated.rs @@ -1,7 +1,7 @@ // AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). // -// Template: `pb0_cycle4_emit_templates/.rs.in` (relative to this crate). -// Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. +// Template: `tools/pb0_cycle4_emit_templates/.rs.in` (workspace root). +// Dissolution receipt: PB-0 NON_TEST partition — `REGEN_OUTPUTS` producer; full `.dag` emit T-LP track. // //! Enforced lens applications (`EnforcedApplication` from `lens_application.dag`). diff --git a/src/v3/compiler/src/int_literal_ranges_generated.rs b/src/v3/compiler/src/int_literal_ranges_generated.rs index b000cfe37be..097c1954946 100644 --- a/src/v3/compiler/src/int_literal_ranges_generated.rs +++ b/src/v3/compiler/src/int_literal_ranges_generated.rs @@ -1,7 +1,7 @@ // AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). // -// Template: `pb0_cycle4_emit_templates/.rs.in` (relative to this crate). -// Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. +// Template: `tools/pb0_cycle4_emit_templates/.rs.in` (workspace root). +// Dissolution receipt: PB-0 NON_TEST partition — `REGEN_OUTPUTS` producer; full `.dag` emit T-LP track. // //! Integer literal magnitude vs declared integer types — **Q1 consumer**. diff --git a/src/v3/compiler/src/pb_method_template_projection_generated.rs b/src/v3/compiler/src/pb_method_template_projection_generated.rs index ba82316a191..fd37aea1286 100644 --- a/src/v3/compiler/src/pb_method_template_projection_generated.rs +++ b/src/v3/compiler/src/pb_method_template_projection_generated.rs @@ -1,7 +1,7 @@ // AUTO-GENERATED by src/v3/compiler/build.rs (`emit_pb0_cycle4_host_authority_modules`). // -// Template: `pb0_cycle4_emit_templates/.rs.in` (relative to this crate). -// Dissolution receipt: PB-0 NON_TEST retirement — former hand module; edit templates + rebuild. +// Template: `tools/pb0_cycle4_emit_templates/.rs.in` (workspace root). +// Dissolution receipt: PB-0 NON_TEST partition — `REGEN_OUTPUTS` producer; full `.dag` emit T-LP track. // //! PB-Bootstrap-Process consumer hook: project `MethodTemplateContract` rows diff --git a/src/v3/compiler/pb0_cycle4_emit_templates/complexity_lattice.rs.in b/tools/pb0_cycle4_emit_templates/complexity_lattice.rs.in similarity index 100% rename from src/v3/compiler/pb0_cycle4_emit_templates/complexity_lattice.rs.in rename to tools/pb0_cycle4_emit_templates/complexity_lattice.rs.in diff --git a/src/v3/compiler/pb0_cycle4_emit_templates/cost_basis_declaration.rs.in b/tools/pb0_cycle4_emit_templates/cost_basis_declaration.rs.in similarity index 100% rename from src/v3/compiler/pb0_cycle4_emit_templates/cost_basis_declaration.rs.in rename to tools/pb0_cycle4_emit_templates/cost_basis_declaration.rs.in diff --git a/src/v3/compiler/pb0_cycle4_emit_templates/emit_collection_ops_method_contract.rs.in b/tools/pb0_cycle4_emit_templates/emit_collection_ops_method_contract.rs.in similarity index 100% rename from src/v3/compiler/pb0_cycle4_emit_templates/emit_collection_ops_method_contract.rs.in rename to tools/pb0_cycle4_emit_templates/emit_collection_ops_method_contract.rs.in diff --git a/src/v3/compiler/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs.in b/tools/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs.in similarity index 100% rename from src/v3/compiler/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs.in rename to tools/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs.in diff --git a/src/v3/compiler/pb0_cycle4_emit_templates/enforced_lens_application.rs.in b/tools/pb0_cycle4_emit_templates/enforced_lens_application.rs.in similarity index 100% rename from src/v3/compiler/pb0_cycle4_emit_templates/enforced_lens_application.rs.in rename to tools/pb0_cycle4_emit_templates/enforced_lens_application.rs.in diff --git a/src/v3/compiler/pb0_cycle4_emit_templates/int_literal_ranges.rs.in b/tools/pb0_cycle4_emit_templates/int_literal_ranges.rs.in similarity index 100% rename from src/v3/compiler/pb0_cycle4_emit_templates/int_literal_ranges.rs.in rename to tools/pb0_cycle4_emit_templates/int_literal_ranges.rs.in diff --git a/src/v3/compiler/pb0_cycle4_emit_templates/pb_method_template_projection.rs.in b/tools/pb0_cycle4_emit_templates/pb_method_template_projection.rs.in similarity index 100% rename from src/v3/compiler/pb0_cycle4_emit_templates/pb_method_template_projection.rs.in rename to tools/pb0_cycle4_emit_templates/pb_method_template_projection.rs.in From e07e792907d9a00f61452a508cb0d10e293a4c43 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 14 May 2026 00:26:46 +0000 Subject: [PATCH 09/10] docs(build): clarify PB-0 cycle-4 generated.rs tracking vs templates Addresses review note on build ordering: REGEN_OUTPUTS bodies are committed, build.rs runs before rustc, and rerun-if-changed covers template edits. Co-authored-by: Cursor --- src/v3/compiler/build.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/v3/compiler/build.rs b/src/v3/compiler/build.rs index 5650a5fc1a6..a03b0ee1ff6 100644 --- a/src/v3/compiler/build.rs +++ b/src/v3/compiler/build.rs @@ -446,6 +446,12 @@ fn emit_r1_gates_fixture(manifest_path: &Path, v3_dir: &Path) { /// this body) is the separate T-LP-Retirement program; this path is the bounded PB-0 census /// partition receipt per `docs/briefs/r3-pb0-non-test-retirement-worker.md`. /// +/// **Checked-in outputs:** the emitted `*_generated.rs` paths are tracked in git like other +/// `REGEN_OUTPUTS` modules (not gitignored). `cargo build` / `cargo check` runs this `build.rs` +/// hook before `rustc` sees `lib.rs`, and `cargo:rerun-if-changed` on the template directory +/// forces a rebuild when `.rs.in` edits land — refresh committed `*_generated.rs` in the same PR +/// as template edits (CI exercises the full path). +/// /// Do not hand-edit `*_generated.rs`; edit the `.rs.in` templates and rebuild. fn emit_pb0_cycle4_host_authority_modules(repo_root: &Path) { const HEADER: &str = concat!( From debadd44bb3ff4323d6033fa0dcda5e2219439f6 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 13 May 2026 20:48:04 -0400 Subject: [PATCH 10/10] =?UTF-8?q?WIP:=20PB-0=20cycle-4=20NON=5FTEST=20reti?= =?UTF-8?q?rement=20(msg=5F5d7f3491):=207=20paths=20=E2=80=94=20complexity?= =?UTF-8?q?=5Fla?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/v3/compiler/src/emit_rust_bin_shim_generated.rs | 2 +- tools/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/v3/compiler/src/emit_rust_bin_shim_generated.rs b/src/v3/compiler/src/emit_rust_bin_shim_generated.rs index 56a05864ae9..33f4e66cc7c 100644 --- a/src/v3/compiler/src/emit_rust_bin_shim_generated.rs +++ b/src/v3/compiler/src/emit_rust_bin_shim_generated.rs @@ -7,7 +7,7 @@ //! PB-1 — Rust `main.rs` shell for generated `[[bin]]` shims (Item 5). //! //! **SG-0 / bounded-seed receipt:** this body is spliced from -//! `pb0_cycle4_emit_templates/emit_rust_bin_shim.rs` by `build.rs` +//! `tools/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs.in` by `build.rs` //! (`emit_pb0_cycle4_host_authority_modules`); the output path is in //! `GENERATED_FILES` / `REGEN_OUTPUTS` (not `EXPECTED_HAND_AUTHORED_NON_TEST`). //! Narrow host helper for §4.2 shell text only; see diff --git a/tools/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs.in b/tools/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs.in index dff6e509b59..5db3e863f06 100644 --- a/tools/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs.in +++ b/tools/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs.in @@ -1,7 +1,7 @@ //! PB-1 — Rust `main.rs` shell for generated `[[bin]]` shims (Item 5). //! //! **SG-0 / bounded-seed receipt:** this body is spliced from -//! `pb0_cycle4_emit_templates/emit_rust_bin_shim.rs` by `build.rs` +//! `tools/pb0_cycle4_emit_templates/emit_rust_bin_shim.rs.in` by `build.rs` //! (`emit_pb0_cycle4_host_authority_modules`); the output path is in //! `GENERATED_FILES` / `REGEN_OUTPUTS` (not `EXPECTED_HAND_AUTHORED_NON_TEST`). //! Narrow host helper for §4.2 shell text only; see