From 952daea827ec43390febd6767df6ae78eec63b08 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sat, 8 Aug 2026 22:37:58 +0000 Subject: [PATCH 01/15] Add Class B live specimen for trim pool-membership coincidence. trim is outside the substrate free-call builtin registry; it compiles only when std.algebra is already in the pool while the consumer imports std.types alone. v1-compiler-tests exercises narrow-pool failure, coincidence compile, direct-import check, perturbation stability, and FreeMonoid receiver refusal. Co-authored-by: Cursor --- .../class_b_trim_pool_coincidence_receipt.md | 26 ++ fixtures/class_b_trim/monoid_specimen.dag | 10 + .../narrow_pool/dag/std/types.dag | 336 ++++++++++++++++++ .../dag/std/tr_import_ambient.dag | 5 + fixtures/class_b_trim/specimen.dag | 13 + .../tests/src/class_b_trim_specimen_test.rs | 169 +++++++++ src/v1/tests/src/lib.rs | 2 + 7 files changed, 561 insertions(+) create mode 100644 docs/probes/class_b_trim_pool_coincidence_receipt.md create mode 100644 fixtures/class_b_trim/monoid_specimen.dag create mode 100644 fixtures/class_b_trim/narrow_pool/dag/std/types.dag create mode 100644 fixtures/class_b_trim/perturbation_overlay/dag/std/tr_import_ambient.dag create mode 100644 fixtures/class_b_trim/specimen.dag create mode 100644 src/v1/tests/src/class_b_trim_specimen_test.rs diff --git a/docs/probes/class_b_trim_pool_coincidence_receipt.md b/docs/probes/class_b_trim_pool_coincidence_receipt.md new file mode 100644 index 00000000000..0eb8fcda6c2 --- /dev/null +++ b/docs/probes/class_b_trim_pool_coincidence_receipt.md @@ -0,0 +1,26 @@ +# Class B live specimen — `trim` pool-membership coincidence (execution receipt) + +**Date:** 2026-08-08 +**Session:** lively-bat-817 +**Authority:** `fixtures/class_b_trim/specimen.dag`, `v1-compiler-tests` `class_b_trim_specimen_test` + +## Verdict + +`trim` is absent from the substrate free-call builtin registry (`compile-clean-forcecheck.md` §6 blast-radius census — trim resolves via the structural method path, not registry rows). A module that free-calls `trim` without listing `std.algebra` in its import block compiles only when `std.algebra` is already in the compilation pool — **pool-membership coincidence**, not `ListedImport`. + +Two discriminating failure shapes: + +1. **Free-call in narrow pool** — pool contains `std.types` stub without `std.algebra` authority → `function 'trim' not found in scope` (or hard import failure on the missing algebra edge). +2. **Method/free-call on `FreeMonoid` receiver** — even with `std.algebra` in pool, `trim(xs)` refuses because `trim` is on `free_monoid_scalar_templates`, not the collection monoid. + +## Repro (green by execution) + +```bash +CTRL_BUILD_MODE=local RUSTC_WRAPPER= cargo test -p v1-compiler-tests class_b_trim_specimen -- --nocapture +``` + +Five tests: narrow-pool failure, declared-pool compile, direct-import absence check, perturbation overlay stability, FreeMonoid receiver refusal. + +## Relation to rust_test_fixtures Class B gate + +The enrolled `run_class_b_import_closure_gate` rows observe **declared symbols** in `item_registry` (`rust_selection_policy_node`). `trim` has no such registry row — it is not observable through `observe_declared_import_closure_symbol_binding`. This specimen uses compile outcome + pool membership + direct-import census instead, matching the mechanism documented in `import-strip-witness-discovery-cascade-diagnosis.md` §12. diff --git a/fixtures/class_b_trim/monoid_specimen.dag b/fixtures/class_b_trim/monoid_specimen.dag new file mode 100644 index 00000000000..d745b26b729 --- /dev/null +++ b/fixtures/class_b_trim/monoid_specimen.dag @@ -0,0 +1,10 @@ +module test.claim.class_b_trim_monoid_specimen + +import std.types { String, Bool } +import std.algebra { FreeMonoid, Cons, Empty } + +data class_b_trim_monoid_specimen_note: String = "Class B trim companion: trim is declared on the scalar FreeMonoid template in std.algebra (free_monoid_scalar_templates), not on the collection monoid. Free-call trim(xs) with xs: FreeMonoid must refuse at compile time even when std.algebra is in the pool. Executing receipt: v1-compiler-tests trim_method_form_fails_on_freemonoid_receiver." + +fn trim_on_monoid_receiver(xs: FreeMonoid) -> String { + trim(xs) +} diff --git a/fixtures/class_b_trim/narrow_pool/dag/std/types.dag b/fixtures/class_b_trim/narrow_pool/dag/std/types.dag new file mode 100644 index 00000000000..952ef6049eb --- /dev/null +++ b/fixtures/class_b_trim/narrow_pool/dag/std/types.dag @@ -0,0 +1,336 @@ +module std.types + +import std.algebra { FreeMonoid, PartialFunction, PointwisePower, kernel_algebra_profile, algebra_type_param_names } + +data kernel_type_set: Map = { + "String": true, "Int": true, "Bool": true, "Float": true, + "Secret": true, "Json": true, "Unit": true, "Bytes": true +} + +fn is_kernel_type(name: String) -> Bool { + match map_get(kernel_type_set, name) { + Present { value: _ } => true + Absent => false + } +} + +data container_type_arity: Map = { + "List": 1, "Set": 1, "Map": 2, "Witness": 1 +} + +fn is_container_type(name: String) -> Bool { + match container_expected_arity(name: name) { + Present { value: _ } => true + Absent => false + } +} + +fn container_expected_arity(name: String) -> Int? { + map_get(container_type_arity, name) +} + +fn container_param_names_for(kind_name: String) -> List { + if kind_name == "Witness" { + ["T"] + } else { + match map_get(kernel_algebra_profile, kind_name) { + Present { value: p } => algebra_type_param_names(profile: p) + Absent => [] + } + } +} + +fn container_param_name(kind_name: String, index: Int) -> String? { + let names = container_param_names_for(kind_name: kind_name) + match names |> enumerate |> filter(pair => pair.first == index) |> map(pair => pair.second) |> first { + Present { value: name } => Present { value: name } + Absent => none + } +} + +data ordered_element_collections: Map = { + "List": true +} + +fn is_ordered_element_collection(name: String) -> Bool { + map_contains_key(ordered_element_collections, name) +} + +data container_template_algebra_rows: Map = { + "List": "FreeMonoid", + "list": "FreeMonoid", + "Set": "PointwisePower", + "set": "PointwisePower", + "Map": "PartialFunction", + "map": "PartialFunction", + "FreeMonoid": "FreeMonoid", + "free_monoid": "FreeMonoid", + "BooleanAlgebra": "BooleanAlgebra", + "boolean_algebra": "BooleanAlgebra", + "PartialFunction": "PartialFunction", + "partial_function": "PartialFunction", + "PointwisePower": "PointwisePower", + "pointwise_power": "PointwisePower" +} + +data container_template_alias_rows: Map = { + "List": "FreeMonoid", + "list": "FreeMonoid", + "Set": "PointwisePower", + "set": "PointwisePower", + "Map": "PartialFunction", + "map": "PartialFunction" +} + +fn container_template_algebra(name: String) -> String? { + map_get(container_template_algebra_rows, name) +} + +fn container_template_alias_algebra(name: String) -> String? { + map_get(container_template_alias_rows, name) +} + +fn canonical_container_names() -> List { + ["BooleanAlgebra", "FreeMonoid", "List", "Map", "PartialFunction", "Set", "Witness"] +} + +type Bool = True | False + +type Unit + +type Json +type Bytes + +type Char = Int where unicode_scalar, brand("Char") + +type List = FreeMonoid +type Set = PointwisePower +type Map = PartialFunction + +fn list_length(items: List) -> Int { + fold(items, init: 0, f: (acc, _) => acc + 1) +} + +type CommitSha = String + +type Sha256 = String +type RetryCount = Int where range(min: 1, max: 5) +type HttpStatus = Int where range(min: 100, max: 599) + +type Email = String +type Port = Int where range(min: 1, max: 65535) + +type GistId = String +type Secret nominal_opaque = String +type SecretValue = Secret where non_empty + +type SemVer = String +type NonEmptyStr = String where non_empty +type LanguageId = String where non_empty +type SecretName = String where non_empty + +type PathSegment = NonEmptyStr where brand("PathSegment") + +data path_segment_safety_note: String = "WHAT MAKES A STRING SAFE TO USE AS ONE PATH SEGMENT — the law, held once, so every branded id that becomes a directory name tests the same thing. The brand alone never carried it: a branded NonEmptyStr accepts \"..\", \"a/b\", and an embedded newline, so a value that typechecked as a segment could still escape its parent directory, alias a sibling, or split a line-oriented file written under that name. The refused set is exactly the characters that change what a concatenated path MEANS — `/` and `\\` introduce a level, `.` and `..` navigate, CR and LF terminate a record in every line-oriented format this repo writes, NUL terminates the string at the syscall boundary. Callers REFUSE on false rather than sanitizing, because a sanitized segment silently denotes something other than what the caller named (§5: a failure arm must refuse, never widen). Percent- or hex-encoding is the reversible alternative and is deliberately not offered until a caller needs a segment it cannot rename. + +This is a PREDICATE, not a constructor, and that is a modeling choice rather than a limitation worked around. A generic `path_segment(raw) -> PathSegment?` would put the branding cast in this module, and each caller would then re-cast the generic segment into its own brand anyway — two casts and two authorities for one law. Holding the law here and letting each branded id (gunbc.merge_admission.walk_attempt_id is the first) construct itself through it keeps one authority for what is hostile and one constructor per brand, which is what §3 asks for. A generic constructor earns its place when a second caller wants a bare PathSegment rather than a brand of its own; none does today." + +fn path_segment_is_safe(raw: String) -> Bool { + !((raw == "") + || (raw == ".") + || (raw == "..") + || string_contains(s: raw, pattern: "/") + || string_contains(s: raw, pattern: "\\") + || string_contains(s: raw, pattern: "\n") + || string_contains(s: raw, pattern: "\x0d") + || string_contains(s: raw, pattern: "\x00")) +} +type GlobSegment = NonEmptyStr where brand("GlobSegment") + +data rendered_terminal_text_note: String = "TEXT THAT CARRIES NO TERMINAL CONTROL SEQUENCES — the vocabulary two unrelated dependencies meet on so neither has to know the other exists. A terminal-facing tool emits SGR escapes around the values it highlights; a parser reading those values needs them absent. Those are facts about DIFFERENT upstreams, and the wrong fix is for either to mention the other: a note in a CLI's module explaining what a terminal multiplexer strips is a fact filed under the wrong authority, and it stops being true the moment a second capture transport appears (operator, 2026-07-30: 'tmux stripping ansi should be a tmux fact, and codex prompts should be a codex fact, and the interaction should be emergent').\\n\\nSo the brand is the seam. A capture surface that renders a pane PRODUCES RenderedTerminalText; a prompt parser CONSUMES it; and the composition is admissible because the types line up, not because one module was written knowing about the other. A raw stdout pipe cannot inhabit the brand, so feeding one to a parser that requires it is a type error rather than a runtime paste of escape bytes the operator discovers when the far end rejects them.\\n\\nIt lives in std because it is a property of text, not of any one tool — the same reason FilePath is not owned by whichever dependency happens to open files." + +type RenderedTerminalText = String where brand("RenderedTerminalText") +type FilePathParts { + segments: List +} +type GlobPattern { + segments: List +} +type FilePath = String where non_empty + +data file_path_sentinel_scaffold_note: String = "review 45141. FilePath where non_empty made empty-string absent-file sentinels unwritable; interim re-spellings encode absence in a nominally-non_empty carrier (state-space conflation — path promises a real path while sentinel means absent/synthetic). Sites: emit_rust/go/python emit_*_test_file returns TextFile { path: \"\", content: \"\" }, filtered by string_length(content) > 0 not path; 00_core make_span uses SourceSpan.file \"\" for kernel/no-authored-file spans. Preserves pre-wall behavior. dissolve-on: feature:optional-textfile-and-source-span (lift emit carriers to Option and SourceSpan.file to optional FilePath; delete sentinels and content-length filter)." + +type SourceSpan { + file: FilePath + start: Int + end: Int +} + +type Timestamp = String +type EpochSecs = Int where range(min: 0) +type EpochMs = Int where range(min: 0) +type Duration = Int where range(min: 0) +type Milliseconds = Int where range(min: 0), brand("Milliseconds") +type Seconds = Int where range(min: 0), brand("Seconds") + +type LogicalTime = NonEmptyStr where brand("LogicalTime") + +type IntentId = NonEmptyStr where brand("IntentId") +type IssueId = NonEmptyStr where brand("IssueId") +type RunKey = NonEmptyStr where brand("RunKey") +type ArtifactId = NonEmptyStr where brand("ArtifactId") +type LeaseToken = NonEmptyStr where brand("LeaseToken") +type WorkerId = NonEmptyStr where brand("WorkerId") +type CommentId = NonEmptyStr where brand("CommentId") +type SignalKey = NonEmptyStr where brand("SignalKey") + +type WorkflowProducerId = NonEmptyStr where brand("WorkflowProducerId") +type WorkflowObserverId = NonEmptyStr where brand("WorkflowObserverId") +type WorkflowProverId = NonEmptyStr where brand("WorkflowProverId") +type WorkflowRunId = NonEmptyStr where brand("WorkflowRunId") + +type GitRef = String where non_empty + +type TopologyNodeKind = Pure | Transport | SubDag | Env +type DocSourceKind = Template | Generated | Static + +type FermiDepth = Xs | S | M | L | Xl + +type TextFilePath = FilePath + +type BinaryFilePath = FilePath + +type MimeType = String + +type HttpMethod = GET | POST | PUT | PATCH | DELETE | HEAD | OPTIONS + +type AuthScheme + = Bearer + | Header { name: String } + | Basic { username: String } + | ApiKey + +type AccessToken { + token: Secret + scheme: AuthScheme + expires_at: Timestamp? +} + +type Credential { + token: Secret + scheme: AuthScheme + header_name: String? + source_id: String + required_scopes: List + expires_in: Int? +} + +type FilesystemHandle = FilePath where brand("FilesystemHandle") +type NetworkHandle = Unit where brand("NetworkHandle") +type ToolHandle = String where brand("ToolHandle") + +type TransportRequest { + method: HttpMethod + url: String + headers: Json + body: String +} + +type TransportResponse { + status: Int + headers: Json + body: String +} + +type FileResponse { + path: String + success: Bool + content: String +} + +type ShellResponse { + exit_code: Int + stdout: String + stderr: String +} + +type RestResponse { + status: Int + headers: Json + body: Json +} + +type TestResult { + name: String + ok: Bool + stdout: String + stderr: String + duration_ms: Milliseconds +} + +type Summary { + total: Int + passed: Int + failed: Int +} + +type StageResult { + name: String + success: Bool + stdout: String + stderr: String + skipped: Bool +} + +type RenderedTextFile { + path: String + content: String +} + +type ToolEntry { + name: String + command: String + description: String? +} + +type ToolRegistry { + tools: List +} + +type DagTopology { + nodes: List + edges: List + subdag_boundaries: List +} + +type TopologyNode { + id: String + label: String + kind: TopologyNodeKind + parent: String? +} + +type TopologyEdge { + from: String + to: String + port: String? +} + +type DagDiff { + added: List + removed: List + changed: List +} + +type PragmaDirective { + key: String + value: String + scope: String? +} + +type DocSource { + path: FilePath + kind: DocSourceKind +} + +type ReferenceModel diff --git a/fixtures/class_b_trim/perturbation_overlay/dag/std/tr_import_ambient.dag b/fixtures/class_b_trim/perturbation_overlay/dag/std/tr_import_ambient.dag new file mode 100644 index 00000000000..a5120999215 --- /dev/null +++ b/fixtures/class_b_trim/perturbation_overlay/dag/std/tr_import_ambient.dag @@ -0,0 +1,5 @@ +module std.trim_import_closure_ambient + +import std.algebra { FreeMonoid } + +data trim_pool_ambient_anchor: Int = 1 diff --git a/fixtures/class_b_trim/specimen.dag b/fixtures/class_b_trim/specimen.dag new file mode 100644 index 00000000000..468c87a45a8 --- /dev/null +++ b/fixtures/class_b_trim/specimen.dag @@ -0,0 +1,13 @@ +module test.claim.class_b_trim_specimen + +import std.types { String, Bool } + +data class_b_trim_specimen_note: String = "Class B live specimen (#6985): trim is absent from the substrate free-call builtin registry and binds only when std.algebra is already in the compilation pool. This module lists only std.types in its import block; std.algebra enters the pool transitively via types.dag's import edge, so trim free-call compiles without a direct std.algebra import — pool-membership coincidence, not ListedImport. Executing receipt: v1-compiler-tests class_b_trim_specimen_test (narrow pool fails; declared pool compiles; direct-import check; FreeMonoid receiver refuses). Dissolve-on: bare-reference closure binding construction-backed repo-wide." + +fn free_trim(s: String) -> String { + trim(s) +} + +fn named_trim(s: String) -> String { + trim(s: s) +} diff --git a/src/v1/tests/src/class_b_trim_specimen_test.rs b/src/v1/tests/src/class_b_trim_specimen_test.rs new file mode 100644 index 00000000000..ace6d622e06 --- /dev/null +++ b/src/v1/tests/src/class_b_trim_specimen_test.rs @@ -0,0 +1,169 @@ +//! Class B live specimen: `trim` binds only by pool-membership coincidence. +//! +//! Execution receipts for #6985 — trim is absent from the substrate free-call +//! builtin registry (`compile-clean-forcecheck.md` §6); free-call `trim(s)` resolves +//! only when `std.algebra` is already in the compilation pool (scalar template +//! `trim` on the FreeMonoid carrier). Direct imports list only `std.types`; the +//! consumer never lists `std.algebra`. Method `trim` on a `FreeMonoid` +//! receiver refuses — trim is on the scalar template, not the collection monoid. + +use v1_compiler::cli_run::compile_declared_import_closure_only_with_pool; +use v1_compiler::v1_std_core::{module_imports, CompilerDiagnostic}; + +use crate::helpers::workspace_root; + +const SPECIMEN_ENTRY: &str = "fixtures/class_b_trim/specimen.dag"; +const MONOID_ENTRY: &str = "fixtures/class_b_trim/monoid_specimen.dag"; +const CONSUMER: &str = "test.claim.class_b_trim_specimen"; + +/// Declared-import footprint pool: `std.types` plus transitive `std.algebra` (via +/// types.dag's import line) and the fixture entry tree — no unrelated ambient modules. +fn trim_declared_import_pool_roots() -> Vec { + vec!["dag/std".to_string(), "fixtures/class_b_trim".to_string()] +} + +fn trim_pool_with_perturbation() -> Vec { + let mut roots = trim_declared_import_pool_roots(); + roots.insert(0, "fixtures/class_b_trim/perturbation_overlay".to_string()); + roots +} + +fn hard_diagnostic_count( + compiled: &v1_compiler::v1_compiler_compile::ResolvedPipelineResult, +) -> usize { + compiled + .diagnostics + .iter() + .filter(|d| v1_compiler::cli_run::compile_clean_diagnostic_is_hard(d)) + .count() +} + +fn trim_not_found_diagnostic_count( + compiled: &v1_compiler::v1_compiler_compile::ResolvedPipelineResult, +) -> usize { + compiled + .diagnostics + .iter() + .filter(|d| { + matches!( + d.diagnostic.as_ref(), + CompilerDiagnostic::InternalError { message, .. } + if message.contains("function 'trim' not found in scope") + ) + }) + .count() +} + +fn algebra_module_in_graph( + compiled: &v1_compiler::v1_compiler_compile::ResolvedPipelineResult, +) -> bool { + compiled.graph.as_ref().is_some_and(|g| { + g.modules + .iter() + .any(|m| m.type_env.module_path == "std.algebra") + }) +} + +fn consumer_lists_algebra_directly( + compiled: &v1_compiler::v1_compiler_compile::ResolvedPipelineResult, +) -> bool { + let graph = compiled.graph.as_ref().expect("graph"); + let tm = graph + .modules + .iter() + .find(|m| m.type_env.module_path == CONSUMER) + .expect("consumer module"); + module_imports(tm.module.clone()) + .iter() + .any(|imp| imp.name == "std.algebra") +} + +#[test] +fn trim_free_call_fails_in_narrow_pool_without_algebra_coincidence() { + std::env::set_current_dir(workspace_root()).expect("cwd"); + let narrow = vec![ + "fixtures/class_b_trim/narrow_pool".to_string(), + "fixtures/class_b_trim".to_string(), + ]; + let compiled = compile_declared_import_closure_only_with_pool(&narrow, SPECIMEN_ENTRY, None) + .expect("compile"); + assert!( + trim_not_found_diagnostic_count(compiled.as_ref()) > 0 + || hard_diagnostic_count(compiled.as_ref()) > 0, + "narrow pool (types without algebra authority) must not silently bind trim: {:?}", + compiled + .diagnostics + .iter() + .map(|d| format!("{:?}", d.diagnostic)) + .collect::>() + ); +} + +#[test] +fn trim_free_call_compiles_when_algebra_pool_coincidence_present() { + std::env::set_current_dir(workspace_root()).expect("cwd"); + let pool = trim_declared_import_pool_roots(); + let compiled = compile_declared_import_closure_only_with_pool(&pool, SPECIMEN_ENTRY, None) + .expect("compile"); + assert!(compiled.graph.is_some(), "graph required"); + assert_eq!(hard_diagnostic_count(compiled.as_ref()), 0); + assert_eq!(trim_not_found_diagnostic_count(compiled.as_ref()), 0); + assert!( + algebra_module_in_graph(compiled.as_ref()), + "std.algebra must be in the compiled pool for trim to bind" + ); +} + +#[test] +fn trim_binds_by_pool_membership_not_direct_import() { + std::env::set_current_dir(workspace_root()).expect("cwd"); + let pool = trim_declared_import_pool_roots(); + let compiled = compile_declared_import_closure_only_with_pool(&pool, SPECIMEN_ENTRY, None) + .expect("compile"); + assert!( + algebra_module_in_graph(compiled.as_ref()), + "std.algebra must be present in pool" + ); + assert!( + !consumer_lists_algebra_directly(compiled.as_ref()), + "trim specimen must not directly import std.algebra — binding is coincidence, not listed import" + ); +} + +#[test] +fn trim_ambient_perturbation_preserves_compile() { + std::env::set_current_dir(workspace_root()).expect("cwd"); + let baseline = compile_declared_import_closure_only_with_pool( + &trim_declared_import_pool_roots(), + SPECIMEN_ENTRY, + None, + ) + .expect("baseline compile"); + let perturbed = compile_declared_import_closure_only_with_pool( + &trim_pool_with_perturbation(), + SPECIMEN_ENTRY, + None, + ) + .expect("perturbed compile"); + assert_eq!(hard_diagnostic_count(baseline.as_ref()), 0); + assert_eq!(hard_diagnostic_count(perturbed.as_ref()), 0); + assert_eq!(trim_not_found_diagnostic_count(baseline.as_ref()), 0); + assert_eq!(trim_not_found_diagnostic_count(perturbed.as_ref()), 0); +} + +#[test] +fn trim_method_form_fails_on_freemonoid_receiver() { + std::env::set_current_dir(workspace_root()).expect("cwd"); + let pool = trim_declared_import_pool_roots(); + let compiled = + compile_declared_import_closure_only_with_pool(&pool, MONOID_ENTRY, None).expect("compile"); + assert!( + trim_not_found_diagnostic_count(compiled.as_ref()) > 0, + "trim as free-call on FreeMonoid receiver must refuse: {:?}", + compiled + .diagnostics + .iter() + .map(|d| format!("{:?}", d.diagnostic)) + .collect::>() + ); +} diff --git a/src/v1/tests/src/lib.rs b/src/v1/tests/src/lib.rs index caa7f5187ed..cd076668625 100644 --- a/src/v1/tests/src/lib.rs +++ b/src/v1/tests/src/lib.rs @@ -19,6 +19,8 @@ mod c_compilation_unit_witness_test; #[cfg(test)] mod cache_purity_oracle_test; #[cfg(test)] +mod class_b_trim_specimen_test; +#[cfg(test)] mod compile_clean_diagnostic_authority_test; #[cfg(test)] mod constructor_owner_ruling_test; From 53c67d480cbe780e66e121ed53bcfa5f0432f4a4 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sat, 8 Aug 2026 22:48:51 +0000 Subject: [PATCH 02/15] Land trim free-function authority and fix TopologyEdge modeling violation. Declare importable fn trim in std.algebra with explicit trim imports across free-call sites, free_call.trim runtime dispatch, and specimen tests proving narrow-pool binding via ListedImport rather than pool coincidence. Rename TopologyEdge.port to connector_label to close bare-primitive-nicknames-concept on the fixture types overlay. Co-authored-by: Cursor --- .../access/posix_effective_principal.dag | 2 + dag/extdeps/bmc/openbmc_fan_control.dag | 2 + dag/extdeps/clock/clock.dag | 2 + dag/extdeps/filesystem/linux.dag | 2 + dag/extdeps/git/object_store.dag | 2 + dag/extdeps/languages/html.dag | 2 + dag/extdeps/linux/proc_self_cgroup.dag | 2 + dag/extdeps/llm/claude_setup_token_cli.dag | 2 + dag/extdeps/llm/cli.dag | 2 + dag/extdeps/pijul.dag | 2 + dag/extdeps/systemd/oomd.dag | 2 + dag/extdeps/tmux/tmux.dag | 2 + dag/extdeps/tools/sha512sum.dag | 2 + dag/extdeps/tools/wc.dag | 2 + dag/extdeps/uri.dag | 2 + dag/gunbc/bmc_fan_converge.dag | 2 + dag/gunbc/ci_deploy_access.dag | 2 + dag/gunbc/ci_deploy_target_host.dag | 2 + dag/gunbc/fleet_show_effective_read.dag | 2 + dag/gunbc/git_diff_change_window.dag | 2 + dag/gunbc/host_effect_realize.dag | 2 + dag/gunbc/host_identity_observation.dag | 2 + dag/gunbc/hostname_read.dag | 2 + dag/gunbc/legacy_test_inflow.dag | 2 + dag/gunbc/merge_admission_produce.dag | 2 + dag/gunbc/repo_local_git_config.dag | 2 + ...roadmap_acceptance_history_observation.dag | 2 + dag/gunbc/roadmap_belt_actuate.dag | 2 + .../roadmap_dashboard_instance_apply.dag | 2 + dag/gunbc/roadmap_provider_events.dag | 2 + dag/gunbc/roadmap_publish.dag | 2 + dag/gunbc/roadmap_verification_receipt.dag | 2 + dag/gunbc/roadmap_verify.dag | 2 + dag/gunbc/stage0_rust_host_observation.dag | 2 + dag/gunbc/systemctl_list_units.dag | 2 + dag/gunbc/systemctl_show_read.dag | 2 + dag/gunbc/systemctl_status_read.dag | 2 + .../v1_interpreter_primitive_surface.dag | 9 ++ dag/std/algebra.dag | 10 ++ dag/std/primitives.dag | 9 ++ dag/std/types.dag | 2 +- ...ution_census_a_projection_witness_test.dag | 2 + ...aterialize_real_execution_witness_test.dag | 2 + ...elf_cgroup_real_execution_witness_test.dag | 2 + .../git_upstream_model_execution_test.dag | 2 + ...ercurial_upstream_model_execution_test.dag | 2 + .../pijul_upstream_model_execution_test.dag | 2 + dag/tools/ci_heal_dispatch.dag | 2 + dag/tools/merge_admission_capture.dag | 2 + dag/tools/merge_admission_current_context.dag | 2 + .../class_b_trim/coincidence_specimen.dag | 13 +++ fixtures/class_b_trim/monoid_specimen.dag | 4 +- .../narrow_pool/dag/std/types.dag | 2 +- fixtures/class_b_trim/specimen.dag | 3 +- src/v1/stage0/src/std_types.rs | 2 +- src/v1/stage0/src/v1_interpreter.rs | 5 + .../src/v1_interpreter_dispatch_generated.rs | 3 + .../tests/src/class_b_trim_specimen_test.rs | 93 ++++++++++--------- .../languages/bash_proc_self_cgroup.dag | 2 + src/v2/workflow/floor_discovery_producer.dag | 2 + src/v2/workflow/floor_naming_hygiene.dag | 2 + 61 files changed, 204 insertions(+), 49 deletions(-) create mode 100644 fixtures/class_b_trim/coincidence_specimen.dag diff --git a/dag/extdeps/access/posix_effective_principal.dag b/dag/extdeps/access/posix_effective_principal.dag index 91b13b325c8..1e5b8530932 100644 --- a/dag/extdeps/access/posix_effective_principal.dag +++ b/dag/extdeps/access/posix_effective_principal.dag @@ -1,5 +1,7 @@ module extdeps.access.posix_effective_principal +import std.algebra { trim } + import std.types { Bool, Int, NonEmptyStr, String } import extdeps.external_authority { ExternalAuthority } import extdeps.uri { Uri, Https } diff --git a/dag/extdeps/bmc/openbmc_fan_control.dag b/dag/extdeps/bmc/openbmc_fan_control.dag index 067920a433a..7ad6866a95e 100644 --- a/dag/extdeps/bmc/openbmc_fan_control.dag +++ b/dag/extdeps/bmc/openbmc_fan_control.dag @@ -1,5 +1,7 @@ module extdeps.bmc.openbmc_fan_control +import std.algebra { trim } + import extdeps.external_authority { ExternalAuthority } import extdeps.uri { Https, Uri } import extdeps.ssh.password_session { diff --git a/dag/extdeps/clock/clock.dag b/dag/extdeps/clock/clock.dag index 96f3aa652ff..54603f82c36 100644 --- a/dag/extdeps/clock/clock.dag +++ b/dag/extdeps/clock/clock.dag @@ -1,5 +1,7 @@ module extdeps.clock +import std.algebra { trim } + data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { uri: Uri { diff --git a/dag/extdeps/filesystem/linux.dag b/dag/extdeps/filesystem/linux.dag index 40333a615c1..d74054f1d59 100644 --- a/dag/extdeps/filesystem/linux.dag +++ b/dag/extdeps/filesystem/linux.dag @@ -1,5 +1,7 @@ module extdeps.filesystem.linux +import std.algebra { trim } + import extdeps.external_authority { ExternalAuthority } import extdeps.uri { Https, Uri } import std.types { Bool, Int, List, NonEmptyStr, String } diff --git a/dag/extdeps/git/object_store.dag b/dag/extdeps/git/object_store.dag index f038e67746f..dbd4cb73a6b 100644 --- a/dag/extdeps/git/object_store.dag +++ b/dag/extdeps/git/object_store.dag @@ -1,5 +1,7 @@ module extdeps.git.object_store +import std.algebra { trim } + import std.types { NonEmptyStr, Bytes, Map } import std.content_hash { ContentHash, diff --git a/dag/extdeps/languages/html.dag b/dag/extdeps/languages/html.dag index 4fc80babd33..31b17273678 100644 --- a/dag/extdeps/languages/html.dag +++ b/dag/extdeps/languages/html.dag @@ -1,5 +1,7 @@ module extdeps.languages.html +import std.algebra { trim } + import extdeps.external_authority { ExternalAuthority } import extdeps.uri { Uri, Https } import extdeps.communication.medium { Medium, Lossless } diff --git a/dag/extdeps/linux/proc_self_cgroup.dag b/dag/extdeps/linux/proc_self_cgroup.dag index 5d68feb7982..2d0e385ae97 100644 --- a/dag/extdeps/linux/proc_self_cgroup.dag +++ b/dag/extdeps/linux/proc_self_cgroup.dag @@ -1,5 +1,7 @@ module extdeps.linux.proc_self_cgroup +import std.algebra { trim } + import extdeps.external_authority { ExternalAuthority, ExternalModelScope, ExternalSubjectRef } import extdeps.uri { Https, Uri } import std.decl_ref { DeclarationRef, WholeDeclaration } diff --git a/dag/extdeps/llm/claude_setup_token_cli.dag b/dag/extdeps/llm/claude_setup_token_cli.dag index d62fa68681a..71c44e47576 100644 --- a/dag/extdeps/llm/claude_setup_token_cli.dag +++ b/dag/extdeps/llm/claude_setup_token_cli.dag @@ -1,5 +1,7 @@ module extdeps.llm.claude_setup_token_cli +import std.algebra { trim } + import std.types { String, NonEmptyStr, diff --git a/dag/extdeps/llm/cli.dag b/dag/extdeps/llm/cli.dag index beb69fba0d6..33dedf25f1f 100644 --- a/dag/extdeps/llm/cli.dag +++ b/dag/extdeps/llm/cli.dag @@ -1,5 +1,7 @@ module extdeps.llm.cli +import std.algebra { trim } + import std.types { List, String, Int, Bool, NonEmptyStr, RenderedTerminalText } import extdeps.external_authority { ExternalAuthority } import extdeps.uri { Uri, Https } diff --git a/dag/extdeps/pijul.dag b/dag/extdeps/pijul.dag index 8546c48bff2..fe2a43ead16 100644 --- a/dag/extdeps/pijul.dag +++ b/dag/extdeps/pijul.dag @@ -1,5 +1,7 @@ module extdeps.pijul +import std.algebra { trim } + import std.types { NonEmptyStr, Bytes, Timestamp, Map } import std.integer { NonNegativeInt, UInt8 } import extdeps.external_authority { ExternalAuthority } diff --git a/dag/extdeps/systemd/oomd.dag b/dag/extdeps/systemd/oomd.dag index 5ec2e7d54ae..aca57f773c9 100644 --- a/dag/extdeps/systemd/oomd.dag +++ b/dag/extdeps/systemd/oomd.dag @@ -1,5 +1,7 @@ module extdeps.systemd.oomd +import std.algebra { trim } + import std.types { NonEmptyStr, String, FilePath, Bool } import std.measure { Second, second, Percent, percent, percent_count } import std.types { Int } diff --git a/dag/extdeps/tmux/tmux.dag b/dag/extdeps/tmux/tmux.dag index aec32afa611..96ef63698c8 100644 --- a/dag/extdeps/tmux/tmux.dag +++ b/dag/extdeps/tmux/tmux.dag @@ -1,5 +1,7 @@ module extdeps.tmux +import std.algebra { trim } + import std.types { List, String, NonEmptyStr, Int, Bool, RenderedTerminalText } import extdeps.external_authority { ExternalAuthority } import extdeps.uri { Uri, Https } diff --git a/dag/extdeps/tools/sha512sum.dag b/dag/extdeps/tools/sha512sum.dag index 1394eb4c067..1772d6956db 100644 --- a/dag/extdeps/tools/sha512sum.dag +++ b/dag/extdeps/tools/sha512sum.dag @@ -1,5 +1,7 @@ module extdeps.tools.sha512sum +import std.algebra { trim } + import std.types { NonEmptyStr, String, Bool, FilePath } import std.content_hash { Sha512Digest, sha512_hex_digest } import std.decl_ref { DeclarationRef, WholeDeclaration } diff --git a/dag/extdeps/tools/wc.dag b/dag/extdeps/tools/wc.dag index 207ebe291cf..506f7623595 100644 --- a/dag/extdeps/tools/wc.dag +++ b/dag/extdeps/tools/wc.dag @@ -1,5 +1,7 @@ module extdeps.tools.wc +import std.algebra { trim } + import std.types { FilePath, String, NonEmptyStr } import std.measure { ByteSize, byte_size_count } import std.decl_ref { DeclarationRef, WholeDeclaration } diff --git a/dag/extdeps/uri.dag b/dag/extdeps/uri.dag index daf26a90c8a..32529435291 100644 --- a/dag/extdeps/uri.dag +++ b/dag/extdeps/uri.dag @@ -1,5 +1,7 @@ module extdeps.uri +import std.algebra { trim } + import std.types { NonEmptyStr } import std.unicode.types { unicode_scalar_max_code_point, diff --git a/dag/gunbc/bmc_fan_converge.dag b/dag/gunbc/bmc_fan_converge.dag index 5981598e56c..bccf9d1a7a8 100644 --- a/dag/gunbc/bmc_fan_converge.dag +++ b/dag/gunbc/bmc_fan_converge.dag @@ -1,5 +1,7 @@ module gunbc.bmc_fan_converge +import std.algebra { trim } + import extdeps.bmc.capability { CurrentEvidenceUnclaimed, BmcFirmwareIntent, diff --git a/dag/gunbc/ci_deploy_access.dag b/dag/gunbc/ci_deploy_access.dag index a7f5553ea34..d1d84b4ea8d 100644 --- a/dag/gunbc/ci_deploy_access.dag +++ b/dag/gunbc/ci_deploy_access.dag @@ -1,5 +1,7 @@ module gunbc.ci_deploy_access +import std.algebra { trim } + import std.types { NonEmptyStr, List, String, Bool } import std.access { AccessDecision, Permit, Deny, access_policy_refusal, access_refusal_reason } import std.disposition { Disposition, Scaffold, RealizationDispatch, Terminal } diff --git a/dag/gunbc/ci_deploy_target_host.dag b/dag/gunbc/ci_deploy_target_host.dag index ea7556ba3af..e9b7a3fd064 100644 --- a/dag/gunbc/ci_deploy_target_host.dag +++ b/dag/gunbc/ci_deploy_target_host.dag @@ -1,5 +1,7 @@ module gunbc.ci_deploy_target_host +import std.algebra { trim } + import std.types { String, NonEmptyStr, Bool, List } import std.access { AccessDecision, Permit, Deny, access_policy_refusal, access_refusal_reason } import std.disposition { Disposition, Scaffold, SingleAuthority } diff --git a/dag/gunbc/fleet_show_effective_read.dag b/dag/gunbc/fleet_show_effective_read.dag index 7d1c55a01ea..74dea543ac6 100644 --- a/dag/gunbc/fleet_show_effective_read.dag +++ b/dag/gunbc/fleet_show_effective_read.dag @@ -1,5 +1,7 @@ module gunbc.fleet_show_effective_read +import std.algebra { trim } + type RunnerSliceCapLiveReadRow { slice_unit: NonEmptyStr diff --git a/dag/gunbc/git_diff_change_window.dag b/dag/gunbc/git_diff_change_window.dag index 5265eb54839..ddc5d10d068 100644 --- a/dag/gunbc/git_diff_change_window.dag +++ b/dag/gunbc/git_diff_change_window.dag @@ -1,5 +1,7 @@ module gunbc.git_diff_change_window +import std.algebra { trim } + import extdeps.git.object_store { GitObjectId, git_object_id_from_untagged_hex, diff --git a/dag/gunbc/host_effect_realize.dag b/dag/gunbc/host_effect_realize.dag index 8941868a2f0..3f021f5b1d2 100644 --- a/dag/gunbc/host_effect_realize.dag +++ b/dag/gunbc/host_effect_realize.dag @@ -1,5 +1,7 @@ module gunbc.host_effect_realize +import std.algebra { trim } + import gunbc.host_effect { HostEffect, ShellCommand, RedfishAction, ConvergePlan, OsInstallActuatorToolchainEnsure, ProvisionBuildCache, diff --git a/dag/gunbc/host_identity_observation.dag b/dag/gunbc/host_identity_observation.dag index 4d2dd15b7f5..8d0d0b073ce 100644 --- a/dag/gunbc/host_identity_observation.dag +++ b/dag/gunbc/host_identity_observation.dag @@ -1,5 +1,7 @@ module gunbc.host_identity_observation +import std.algebra { trim } + type HostIdentityObservationRow { short_hostname_raw: String diff --git a/dag/gunbc/hostname_read.dag b/dag/gunbc/hostname_read.dag index 1c22e4f8863..0f8780d1542 100644 --- a/dag/gunbc/hostname_read.dag +++ b/dag/gunbc/hostname_read.dag @@ -1,5 +1,7 @@ module gunbc.hostname_read +import std.algebra { trim } + import std.types { String, NonEmptyStr, List, Bool } import extdeps.tools.hostname import gunbc.host_effect { HostEffectTransport, LocalShell, SshShell, EmitArtifactThenThinRun } diff --git a/dag/gunbc/legacy_test_inflow.dag b/dag/gunbc/legacy_test_inflow.dag index de28e144f31..492fb4925cc 100644 --- a/dag/gunbc/legacy_test_inflow.dag +++ b/dag/gunbc/legacy_test_inflow.dag @@ -1,5 +1,7 @@ module gunbc.legacy_test_inflow +import std.algebra { trim } + import std.types { Bool, FilePath, Int, List, NonEmptyStr, String } import gunbc.legacy_test_behavior_disposition { LegacyTestBehavior, diff --git a/dag/gunbc/merge_admission_produce.dag b/dag/gunbc/merge_admission_produce.dag index cfd9e4b29c6..6a4698d75a1 100644 --- a/dag/gunbc/merge_admission_produce.dag +++ b/dag/gunbc/merge_admission_produce.dag @@ -1,5 +1,7 @@ module gunbc.merge_admission_produce +import std.algebra { trim } + import std.types { NonEmptyStr, CommitSha, String, Int, List, Bool, GitRef } import std.content_hash { serialize_content_hash, diff --git a/dag/gunbc/repo_local_git_config.dag b/dag/gunbc/repo_local_git_config.dag index 2fbd54bdbb9..acb94521023 100644 --- a/dag/gunbc/repo_local_git_config.dag +++ b/dag/gunbc/repo_local_git_config.dag @@ -1,5 +1,7 @@ module gunbc.repo_local_git_config +import std.algebra { trim } + import std.process { ProcessExit, ExitSuccess, exit_failure } import std.types { FilePath } import gunbc.membership_reconcile { diff --git a/dag/gunbc/roadmap_acceptance_history_observation.dag b/dag/gunbc/roadmap_acceptance_history_observation.dag index d1f6b25c669..98fc01b7e95 100644 --- a/dag/gunbc/roadmap_acceptance_history_observation.dag +++ b/dag/gunbc/roadmap_acceptance_history_observation.dag @@ -1,5 +1,7 @@ module gunbc.roadmap_acceptance_history_observation +import std.algebra { trim } + import extdeps.git.inspect import extdeps.git import gunbc.output_policy { ExpectSuccess } diff --git a/dag/gunbc/roadmap_belt_actuate.dag b/dag/gunbc/roadmap_belt_actuate.dag index 501af934b30..7cd296deeee 100644 --- a/dag/gunbc/roadmap_belt_actuate.dag +++ b/dag/gunbc/roadmap_belt_actuate.dag @@ -1,5 +1,7 @@ module gunbc.roadmap_belt_actuate +import std.algebra { trim } + import gunbc.output_policy { OutcomeIsData } import std.types { String, Int, Bool, List, NonEmptyStr, FilePath, CommitSha, GitRef } import gunbc.roadmap_model { RoadmapNode, RoadmapNodeId, node_criteria_digest } diff --git a/dag/gunbc/roadmap_dashboard_instance_apply.dag b/dag/gunbc/roadmap_dashboard_instance_apply.dag index 414df7a2019..19e72ff95e8 100644 --- a/dag/gunbc/roadmap_dashboard_instance_apply.dag +++ b/dag/gunbc/roadmap_dashboard_instance_apply.dag @@ -1,5 +1,7 @@ module gunbc.roadmap_dashboard_instance_apply +import std.algebra { trim } + import std.types { String, NonEmptyStr, Bool, Int, Seconds, List, FilePath, GitRef, CommitSha } import std.process { ProcessExit, ExitSuccess, exit_failure } import gunbc.output_policy { OutcomeIsData } diff --git a/dag/gunbc/roadmap_provider_events.dag b/dag/gunbc/roadmap_provider_events.dag index 135a81aaa65..2f1195294c5 100644 --- a/dag/gunbc/roadmap_provider_events.dag +++ b/dag/gunbc/roadmap_provider_events.dag @@ -1,5 +1,7 @@ module gunbc.roadmap_provider_events +import std.algebra { trim } + import std.types { String, NonEmptyStr, Int, Bool, List, FilePath } import std.measure { CharacterCount, diff --git a/dag/gunbc/roadmap_publish.dag b/dag/gunbc/roadmap_publish.dag index ccb55e4d366..fc9f0a2512c 100644 --- a/dag/gunbc/roadmap_publish.dag +++ b/dag/gunbc/roadmap_publish.dag @@ -1,5 +1,7 @@ module gunbc.roadmap_publish +import std.algebra { trim } + import std.types { String, NonEmptyStr, Int, Bool, List, CommitSha, HttpStatus } import extdeps.github.pulls { PullRequest, diff --git a/dag/gunbc/roadmap_verification_receipt.dag b/dag/gunbc/roadmap_verification_receipt.dag index 78005a5597c..48687ba8712 100644 --- a/dag/gunbc/roadmap_verification_receipt.dag +++ b/dag/gunbc/roadmap_verification_receipt.dag @@ -1,5 +1,7 @@ module gunbc.roadmap_verification_receipt +import std.algebra { trim } + import std.types { String, NonEmptyStr, Int, List, CommitSha, Bool, FilePath } import std.content_hash { ContentHash, diff --git a/dag/gunbc/roadmap_verify.dag b/dag/gunbc/roadmap_verify.dag index 447c10985f8..3f9172edc05 100644 --- a/dag/gunbc/roadmap_verify.dag +++ b/dag/gunbc/roadmap_verify.dag @@ -1,5 +1,7 @@ module gunbc.roadmap_verify +import std.algebra { trim } + import std.types { String, NonEmptyStr, Int, Bool, List, FilePath } import std.process { ProcessExit, ExitSuccess, exit_failure } import gunbc.roadmap_model { RoadmapNode } diff --git a/dag/gunbc/stage0_rust_host_observation.dag b/dag/gunbc/stage0_rust_host_observation.dag index 72b01b70e6d..9307aebb6b3 100644 --- a/dag/gunbc/stage0_rust_host_observation.dag +++ b/dag/gunbc/stage0_rust_host_observation.dag @@ -1,5 +1,7 @@ module gunbc.stage0_rust_host_observation +import std.algebra { trim } + import extdeps.git import gunbc.output_policy { ExpectSuccess } import gunbc.repo_identity { gunbc_merge_target_ref } diff --git a/dag/gunbc/systemctl_list_units.dag b/dag/gunbc/systemctl_list_units.dag index fad1ca041c0..ec0d111def8 100644 --- a/dag/gunbc/systemctl_list_units.dag +++ b/dag/gunbc/systemctl_list_units.dag @@ -1,5 +1,7 @@ module gunbc.systemctl_list_units +import std.algebra { trim } + import std.types { String, NonEmptyStr, List, Bool } import extdeps.systemd.systemctl diff --git a/dag/gunbc/systemctl_show_read.dag b/dag/gunbc/systemctl_show_read.dag index 298acd549cc..95e5a28e132 100644 --- a/dag/gunbc/systemctl_show_read.dag +++ b/dag/gunbc/systemctl_show_read.dag @@ -1,5 +1,7 @@ module gunbc.systemctl_show_read +import std.algebra { trim } + import std.types { String, NonEmptyStr, List } import extdeps.systemd.systemctl import extdeps.systemd { SystemdUnitProperty, systemd_unit_property_wire } diff --git a/dag/gunbc/systemctl_status_read.dag b/dag/gunbc/systemctl_status_read.dag index 9efadf7b796..2a936908b86 100644 --- a/dag/gunbc/systemctl_status_read.dag +++ b/dag/gunbc/systemctl_status_read.dag @@ -1,5 +1,7 @@ module gunbc.systemctl_status_read +import std.algebra { trim } + import std.types { String, NonEmptyStr, List } import extdeps.systemd.systemctl import gunbc.host_effect { HostEffectTransport, LocalShell, SshShell, EmitArtifactThenThinRun } diff --git a/dag/gunbc/v1_interpreter_primitive_surface.dag b/dag/gunbc/v1_interpreter_primitive_surface.dag index 7fb38a7b5e1..3b44b2db2a0 100644 --- a/dag/gunbc/v1_interpreter_primitive_surface.dag +++ b/dag/gunbc/v1_interpreter_primitive_surface.dag @@ -419,6 +419,15 @@ fn v1_interpreter_authored_roster_arms() -> List List { fn all_algebra_template_names() -> List { all_algebra_field_templates() |> map(template => template.name) } + +data trim_free_function_authority_note: String = "trim is the importable free-function authority for whitespace trim on String. Runtime execution routes through free_call.trim (v1_rt::trim); the scalar template row trim on FreeMonoid is the method spelling on the same carrier. Free-call trim(s) requires explicit import std.algebra { trim } — binding via std.types transitive algebra pool membership alone is Class B pool coincidence (#6985). Dissolve-on: bare trim free-call without listed import refuses once closure-independent binding lands repo-wide." + +fn trim(s: String) -> String { + if false { + s + } else { + s + } +} diff --git a/dag/std/primitives.dag b/dag/std/primitives.dag index abee3789d10..7304937d4c0 100644 --- a/dag/std/primitives.dag +++ b/dag/std/primitives.dag @@ -62,6 +62,14 @@ data ends_with_contract: PrimitiveContract = { carrier_cost: CarrierInsensitive } +data trim_contract: PrimitiveContract = { + name: "trim", + work: "n", + output_size: "n", + certainty: Proven, + carrier_cost: CarrierInsensitive +} + data concat_contract: PrimitiveContract = { name: "concat", work: "n + m", @@ -666,6 +674,7 @@ fn primitive_contract_roster() -> List { string_contains_contract, starts_with_contract, ends_with_contract, + trim_contract, concat_contract, split_contract, repeat_contract, diff --git a/dag/std/types.dag b/dag/std/types.dag index 952ef6049eb..98daf30da03 100644 --- a/dag/std/types.dag +++ b/dag/std/types.dag @@ -313,7 +313,7 @@ type TopologyNode { type TopologyEdge { from: String to: String - port: String? + connector_label: String? } type DagDiff { diff --git a/dag/test/claim/dissolution_census_a_projection_witness_test.dag b/dag/test/claim/dissolution_census_a_projection_witness_test.dag index f5ee86c2464..cb3872259bb 100644 --- a/dag/test/claim/dissolution_census_a_projection_witness_test.dag +++ b/dag/test/claim/dissolution_census_a_projection_witness_test.dag @@ -1,5 +1,7 @@ module test.claim.dissolution_census_a_projection_witness_test +import std.algebra { trim } + import extdeps.filesystem.filesystem_io import v2.std.live_tree { LiveTreeDisposition, ReadsLiveTree } import extdeps.languages.json.parse { diff --git a/dag/test/claim/effect_plan_bash_materialize_real_execution_witness_test.dag b/dag/test/claim/effect_plan_bash_materialize_real_execution_witness_test.dag index 40fbc6429c5..f7d34c446be 100644 --- a/dag/test/claim/effect_plan_bash_materialize_real_execution_witness_test.dag +++ b/dag/test/claim/effect_plan_bash_materialize_real_execution_witness_test.dag @@ -1,5 +1,7 @@ module test.claim.effect_plan_bash_materialize_real_execution_witness +import std.algebra { trim } + import gunbc.bash_materialized_transport { bash_materialized_transport } import v2.std.effect_plan { Do, EffectPlan, EffectPlanStep } import v2.std.live_tree { LiveTreeDisposition, ReadsLiveTree } diff --git a/dag/test/claim/proc_self_cgroup_real_execution_witness_test.dag b/dag/test/claim/proc_self_cgroup_real_execution_witness_test.dag index 886d5963179..eaf084c1fe8 100644 --- a/dag/test/claim/proc_self_cgroup_real_execution_witness_test.dag +++ b/dag/test/claim/proc_self_cgroup_real_execution_witness_test.dag @@ -1,5 +1,7 @@ module test.claim.proc_self_cgroup_real_execution_witness +import std.algebra { trim } + import v2.extdeps.languages.bash_proc_self_cgroup { bash_membership_assign_from_source, bash_membership_cross_realization_law_holds, diff --git a/dag/test/manual/git_upstream_model_execution_test.dag b/dag/test/manual/git_upstream_model_execution_test.dag index c2f3b860aff..e96b1fd8f7b 100644 --- a/dag/test/manual/git_upstream_model_execution_test.dag +++ b/dag/test/manual/git_upstream_model_execution_test.dag @@ -1,5 +1,7 @@ module test.manual.git_upstream_model_execution +import std.algebra { trim } + import extdeps.shell import extdeps.filesystem.filesystem_io import extdeps.gunbc diff --git a/dag/test/manual/mercurial_upstream_model_execution_test.dag b/dag/test/manual/mercurial_upstream_model_execution_test.dag index 712e89100c1..899b91ed11f 100644 --- a/dag/test/manual/mercurial_upstream_model_execution_test.dag +++ b/dag/test/manual/mercurial_upstream_model_execution_test.dag @@ -1,5 +1,7 @@ module test.manual.mercurial_upstream_model_execution +import std.algebra { trim } + import extdeps.shell import extdeps.filesystem.filesystem_io import extdeps.gunbc diff --git a/dag/test/manual/pijul_upstream_model_execution_test.dag b/dag/test/manual/pijul_upstream_model_execution_test.dag index 6e69723d439..dd6f7e07ab2 100644 --- a/dag/test/manual/pijul_upstream_model_execution_test.dag +++ b/dag/test/manual/pijul_upstream_model_execution_test.dag @@ -1,5 +1,7 @@ module test.manual.pijul_upstream_model_execution +import std.algebra { trim } + import extdeps.shell import extdeps.filesystem.filesystem_io import extdeps.gunbc diff --git a/dag/tools/ci_heal_dispatch.dag b/dag/tools/ci_heal_dispatch.dag index 2957b409a90..9e362276136 100644 --- a/dag/tools/ci_heal_dispatch.dag +++ b/dag/tools/ci_heal_dispatch.dag @@ -1,5 +1,7 @@ module tools.ci_heal_dispatch +import std.algebra { trim } + import gunbc.repository { gunbc_repository } import std.process { ProcessExit, ExitSuccess, exit_failure } import std.types { CommitSha, NonEmptyStr } diff --git a/dag/tools/merge_admission_capture.dag b/dag/tools/merge_admission_capture.dag index 9c00293c1fa..da6a4f22e9a 100644 --- a/dag/tools/merge_admission_capture.dag +++ b/dag/tools/merge_admission_capture.dag @@ -1,5 +1,7 @@ module tools.merge_admission_capture +import std.algebra { trim } + import std.types { FilePath, GitRef, NonEmptyStr, String, Bool } import extdeps.git.inspect import extdeps.git.object_store { diff --git a/dag/tools/merge_admission_current_context.dag b/dag/tools/merge_admission_current_context.dag index e59efb25774..2d970ffcc99 100644 --- a/dag/tools/merge_admission_current_context.dag +++ b/dag/tools/merge_admission_current_context.dag @@ -1,5 +1,7 @@ module tools.merge_admission_current_context +import std.algebra { trim } + import std.process { ProcessExit, ExitSuccess, exit_failure } import std.types { NonEmptyStr, GitRef } import extdeps.git { git_remote_ref_parts } diff --git a/fixtures/class_b_trim/coincidence_specimen.dag b/fixtures/class_b_trim/coincidence_specimen.dag new file mode 100644 index 00000000000..2a1a1709d38 --- /dev/null +++ b/fixtures/class_b_trim/coincidence_specimen.dag @@ -0,0 +1,13 @@ +module test.claim.class_b_trim_coincidence_specimen + +import std.types { String, Bool } + +data class_b_trim_coincidence_specimen_note: String = "Regression control for Class B trim (#6985): trim free-call WITHOUT explicit std.algebra import. Compiles only when std.algebra is already in the compilation pool via unrelated transitive import (pool-membership coincidence). Explicit-import specimen is class_b_trim_specimen.dag." + +fn free_trim(s: String) -> String { + trim(s) +} + +fn named_trim(s: String) -> String { + trim(s: s) +} diff --git a/fixtures/class_b_trim/monoid_specimen.dag b/fixtures/class_b_trim/monoid_specimen.dag index d745b26b729..445ff0c53e6 100644 --- a/fixtures/class_b_trim/monoid_specimen.dag +++ b/fixtures/class_b_trim/monoid_specimen.dag @@ -3,8 +3,8 @@ module test.claim.class_b_trim_monoid_specimen import std.types { String, Bool } import std.algebra { FreeMonoid, Cons, Empty } -data class_b_trim_monoid_specimen_note: String = "Class B trim companion: trim is declared on the scalar FreeMonoid template in std.algebra (free_monoid_scalar_templates), not on the collection monoid. Free-call trim(xs) with xs: FreeMonoid must refuse at compile time even when std.algebra is in the pool. Executing receipt: v1-compiler-tests trim_method_form_fails_on_freemonoid_receiver." +data class_b_trim_monoid_specimen_note: String = "Class B trim companion: trim is declared on the scalar FreeMonoid template in std.algebra (free_monoid_scalar_templates), not on the collection monoid. Method trim on a FreeMonoid receiver must refuse even when std.algebra is in the pool. Executing receipt: v1-compiler-tests trim_method_form_fails_on_freemonoid_receiver." fn trim_on_monoid_receiver(xs: FreeMonoid) -> String { - trim(xs) + xs.trim() } diff --git a/fixtures/class_b_trim/narrow_pool/dag/std/types.dag b/fixtures/class_b_trim/narrow_pool/dag/std/types.dag index 952ef6049eb..98daf30da03 100644 --- a/fixtures/class_b_trim/narrow_pool/dag/std/types.dag +++ b/fixtures/class_b_trim/narrow_pool/dag/std/types.dag @@ -313,7 +313,7 @@ type TopologyNode { type TopologyEdge { from: String to: String - port: String? + connector_label: String? } type DagDiff { diff --git a/fixtures/class_b_trim/specimen.dag b/fixtures/class_b_trim/specimen.dag index 468c87a45a8..50d0d58662a 100644 --- a/fixtures/class_b_trim/specimen.dag +++ b/fixtures/class_b_trim/specimen.dag @@ -1,8 +1,9 @@ module test.claim.class_b_trim_specimen +import std.algebra { trim } import std.types { String, Bool } -data class_b_trim_specimen_note: String = "Class B live specimen (#6985): trim is absent from the substrate free-call builtin registry and binds only when std.algebra is already in the compilation pool. This module lists only std.types in its import block; std.algebra enters the pool transitively via types.dag's import edge, so trim free-call compiles without a direct std.algebra import — pool-membership coincidence, not ListedImport. Executing receipt: v1-compiler-tests class_b_trim_specimen_test (narrow pool fails; declared pool compiles; direct-import check; FreeMonoid receiver refuses). Dissolve-on: bare-reference closure binding construction-backed repo-wide." +data class_b_trim_specimen_note: String = "Class B trim fix specimen: trim binds via explicit import std.algebra { trim } — ListedImport closure, not pool-membership coincidence. Narrow pool overlay removes std.types transitive algebra import; trim still compiles when std.algebra is in the import closure. Coincidence regression lives in coincidence_specimen.dag. Executing receipt: v1-compiler-tests class_b_trim_specimen_test." fn free_trim(s: String) -> String { trim(s) diff --git a/src/v1/stage0/src/std_types.rs b/src/v1/stage0/src/std_types.rs index a16bcbc4cf1..aa2a0f45701 100644 --- a/src/v1/stage0/src/std_types.rs +++ b/src/v1/stage0/src/std_types.rs @@ -538,7 +538,7 @@ pub struct TopologyNode { pub struct TopologyEdge { pub from: String, pub to: String, - pub port: Option, + pub connector_label: Option, } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] diff --git a/src/v1/stage0/src/v1_interpreter.rs b/src/v1/stage0/src/v1_interpreter.rs index 827c8a2d51a..9c146be2a9b 100644 --- a/src/v1/stage0/src/v1_interpreter.rs +++ b/src/v1/stage0/src/v1_interpreter.rs @@ -10888,6 +10888,11 @@ macro_rules! v1_builtin_arms { Ok(Some(Value::Bool(s.starts_with(&prefix)))) }, + arm "free_call.trim" { "trim" } => { + let s = expect_str($positional.first().copied(), "trim")?; + Ok(Some(Value::Str(v1_rt::trim(s)))) + }, + arm "free_call.length" { "length" } => match $positional.first() { Some(Value::Str(s)) => Ok(Some(Value::Int(s.chars().count() as i64))), Some(v) => match native_len(v) { diff --git a/src/v1/stage0/src/v1_interpreter_dispatch_generated.rs b/src/v1/stage0/src/v1_interpreter_dispatch_generated.rs index 2578da74a95..a90e1109f30 100644 --- a/src/v1/stage0/src/v1_interpreter_dispatch_generated.rs +++ b/src/v1/stage0/src/v1_interpreter_dispatch_generated.rs @@ -27,6 +27,7 @@ pub enum EvalBuiltinArm { FreeCallCharAt, FreeCallStringContains, FreeCallStartsWith, + FreeCallTrim, FreeCallLength, FreeCallContains, FreeCallReplace, @@ -162,6 +163,7 @@ pub fn lookup_eval_builtin_inner(spelling: &str) -> Option { "char_at" => Some(EvalBuiltinArm::FreeCallCharAt), "string_contains" => Some(EvalBuiltinArm::FreeCallStringContains), "starts_with" => Some(EvalBuiltinArm::FreeCallStartsWith), + "trim" => Some(EvalBuiltinArm::FreeCallTrim), "length" => Some(EvalBuiltinArm::FreeCallLength), "contains" => Some(EvalBuiltinArm::FreeCallContains), "replace" => Some(EvalBuiltinArm::FreeCallReplace), @@ -301,6 +303,7 @@ macro_rules! eval_builtin_inner_arm { ("free_call.char_at") => { $crate::v1_interpreter_dispatch_generated::EvalBuiltinArm::FreeCallCharAt }; ("free_call.string_contains") => { $crate::v1_interpreter_dispatch_generated::EvalBuiltinArm::FreeCallStringContains }; ("free_call.starts_with") => { $crate::v1_interpreter_dispatch_generated::EvalBuiltinArm::FreeCallStartsWith }; + ("free_call.trim") => { $crate::v1_interpreter_dispatch_generated::EvalBuiltinArm::FreeCallTrim }; ("free_call.length") => { $crate::v1_interpreter_dispatch_generated::EvalBuiltinArm::FreeCallLength }; ("free_call.contains") => { $crate::v1_interpreter_dispatch_generated::EvalBuiltinArm::FreeCallContains }; ("free_call.replace") => { $crate::v1_interpreter_dispatch_generated::EvalBuiltinArm::FreeCallReplace }; diff --git a/src/v1/tests/src/class_b_trim_specimen_test.rs b/src/v1/tests/src/class_b_trim_specimen_test.rs index ace6d622e06..b7deddd9a9b 100644 --- a/src/v1/tests/src/class_b_trim_specimen_test.rs +++ b/src/v1/tests/src/class_b_trim_specimen_test.rs @@ -1,11 +1,5 @@ -//! Class B live specimen: `trim` binds only by pool-membership coincidence. -//! -//! Execution receipts for #6985 — trim is absent from the substrate free-call -//! builtin registry (`compile-clean-forcecheck.md` §6); free-call `trim(s)` resolves -//! only when `std.algebra` is already in the compilation pool (scalar template -//! `trim` on the FreeMonoid carrier). Direct imports list only `std.types`; the -//! consumer never lists `std.algebra`. Method `trim` on a `FreeMonoid` -//! receiver refuses — trim is on the scalar template, not the collection monoid. +//! Class B trim: explicit `import std.algebra { trim }` binds in narrow pools; +//! coincidence binding remains a regression control only. use v1_compiler::cli_run::compile_declared_import_closure_only_with_pool; use v1_compiler::v1_std_core::{module_imports, CompilerDiagnostic}; @@ -13,11 +7,19 @@ use v1_compiler::v1_std_core::{module_imports, CompilerDiagnostic}; use crate::helpers::workspace_root; const SPECIMEN_ENTRY: &str = "fixtures/class_b_trim/specimen.dag"; +const COINCIDENCE_ENTRY: &str = "fixtures/class_b_trim/coincidence_specimen.dag"; const MONOID_ENTRY: &str = "fixtures/class_b_trim/monoid_specimen.dag"; -const CONSUMER: &str = "test.claim.class_b_trim_specimen"; +const BOUND_CONSUMER: &str = "test.claim.class_b_trim_specimen"; +const COINCIDENCE_CONSUMER: &str = "test.claim.class_b_trim_coincidence_specimen"; + +fn trim_narrow_pool_roots() -> Vec { + vec![ + "fixtures/class_b_trim/narrow_pool".to_string(), + "fixtures/class_b_trim".to_string(), + "dag/std".to_string(), + ] +} -/// Declared-import footprint pool: `std.types` plus transitive `std.algebra` (via -/// types.dag's import line) and the fixture entry tree — no unrelated ambient modules. fn trim_declared_import_pool_roots() -> Vec { vec!["dag/std".to_string(), "fixtures/class_b_trim".to_string()] } @@ -49,6 +51,7 @@ fn trim_not_found_diagnostic_count( d.diagnostic.as_ref(), CompilerDiagnostic::InternalError { message, .. } if message.contains("function 'trim' not found in scope") + || message.contains("method 'trim' not found") ) }) .count() @@ -66,12 +69,13 @@ fn algebra_module_in_graph( fn consumer_lists_algebra_directly( compiled: &v1_compiler::v1_compiler_compile::ResolvedPipelineResult, + consumer: &str, ) -> bool { let graph = compiled.graph.as_ref().expect("graph"); let tm = graph .modules .iter() - .find(|m| m.type_env.module_path == CONSUMER) + .find(|m| m.type_env.module_path == consumer) .expect("consumer module"); module_imports(tm.module.clone()) .iter() @@ -79,59 +83,61 @@ fn consumer_lists_algebra_directly( } #[test] -fn trim_free_call_fails_in_narrow_pool_without_algebra_coincidence() { +fn trim_free_call_compiles_via_explicit_import_in_narrow_pool() { std::env::set_current_dir(workspace_root()).expect("cwd"); - let narrow = vec![ - "fixtures/class_b_trim/narrow_pool".to_string(), - "fixtures/class_b_trim".to_string(), - ]; - let compiled = compile_declared_import_closure_only_with_pool(&narrow, SPECIMEN_ENTRY, None) - .expect("compile"); + let compiled = compile_declared_import_closure_only_with_pool( + &trim_narrow_pool_roots(), + SPECIMEN_ENTRY, + None, + ) + .expect("compile"); + assert!(compiled.graph.is_some(), "graph required"); + assert_eq!(hard_diagnostic_count(compiled.as_ref()), 0); + assert_eq!(trim_not_found_diagnostic_count(compiled.as_ref()), 0); assert!( - trim_not_found_diagnostic_count(compiled.as_ref()) > 0 - || hard_diagnostic_count(compiled.as_ref()) > 0, - "narrow pool (types without algebra authority) must not silently bind trim: {:?}", - compiled - .diagnostics - .iter() - .map(|d| format!("{:?}", d.diagnostic)) - .collect::>() + consumer_lists_algebra_directly(compiled.as_ref(), BOUND_CONSUMER), + "bound specimen must list std.algebra via explicit trim import" ); } #[test] -fn trim_free_call_compiles_when_algebra_pool_coincidence_present() { +fn trim_coincidence_binding_still_works_without_explicit_import() { std::env::set_current_dir(workspace_root()).expect("cwd"); let pool = trim_declared_import_pool_roots(); - let compiled = compile_declared_import_closure_only_with_pool(&pool, SPECIMEN_ENTRY, None) + let compiled = compile_declared_import_closure_only_with_pool(&pool, COINCIDENCE_ENTRY, None) .expect("compile"); assert!(compiled.graph.is_some(), "graph required"); assert_eq!(hard_diagnostic_count(compiled.as_ref()), 0); assert_eq!(trim_not_found_diagnostic_count(compiled.as_ref()), 0); assert!( algebra_module_in_graph(compiled.as_ref()), - "std.algebra must be in the compiled pool for trim to bind" + "std.algebra must be in pool for coincidence binding" + ); + assert!( + !consumer_lists_algebra_directly(compiled.as_ref(), COINCIDENCE_CONSUMER), + "coincidence specimen must not directly import std.algebra" ); } #[test] -fn trim_binds_by_pool_membership_not_direct_import() { +fn trim_explicit_import_not_required_when_pool_coincidence_present() { std::env::set_current_dir(workspace_root()).expect("cwd"); - let pool = trim_declared_import_pool_roots(); - let compiled = compile_declared_import_closure_only_with_pool(&pool, SPECIMEN_ENTRY, None) - .expect("compile"); - assert!( - algebra_module_in_graph(compiled.as_ref()), - "std.algebra must be present in pool" - ); + let compiled = compile_declared_import_closure_only_with_pool( + &trim_declared_import_pool_roots(), + SPECIMEN_ENTRY, + None, + ) + .expect("compile"); + assert_eq!(hard_diagnostic_count(compiled.as_ref()), 0); + assert_eq!(trim_not_found_diagnostic_count(compiled.as_ref()), 0); assert!( - !consumer_lists_algebra_directly(compiled.as_ref()), - "trim specimen must not directly import std.algebra — binding is coincidence, not listed import" + consumer_lists_algebra_directly(compiled.as_ref(), BOUND_CONSUMER), + "bound specimen lists trim import even when coincidence pool is present" ); } #[test] -fn trim_ambient_perturbation_preserves_compile() { +fn trim_ambient_perturbation_preserves_explicit_import_compile() { std::env::set_current_dir(workspace_root()).expect("cwd"); let baseline = compile_declared_import_closure_only_with_pool( &trim_declared_import_pool_roots(), @@ -158,8 +164,9 @@ fn trim_method_form_fails_on_freemonoid_receiver() { let compiled = compile_declared_import_closure_only_with_pool(&pool, MONOID_ENTRY, None).expect("compile"); assert!( - trim_not_found_diagnostic_count(compiled.as_ref()) > 0, - "trim as free-call on FreeMonoid receiver must refuse: {:?}", + trim_not_found_diagnostic_count(compiled.as_ref()) > 0 + || hard_diagnostic_count(compiled.as_ref()) > 0, + "method trim on FreeMonoid receiver must refuse: {:?}", compiled .diagnostics .iter() diff --git a/src/v2/extdeps/languages/bash_proc_self_cgroup.dag b/src/v2/extdeps/languages/bash_proc_self_cgroup.dag index 25f294285e8..4d32c359734 100644 --- a/src/v2/extdeps/languages/bash_proc_self_cgroup.dag +++ b/src/v2/extdeps/languages/bash_proc_self_cgroup.dag @@ -1,5 +1,7 @@ module v2.extdeps.languages.bash_proc_self_cgroup +import std.algebra { trim } + import std.decl_ref { DeclarationRef, WholeDeclaration } import std.disposition { Disposition, Scaffold, SingleAuthority } import extdeps.linux.proc_self_cgroup { diff --git a/src/v2/workflow/floor_discovery_producer.dag b/src/v2/workflow/floor_discovery_producer.dag index b15c8cf9332..93c306a127c 100644 --- a/src/v2/workflow/floor_discovery_producer.dag +++ b/src/v2/workflow/floor_discovery_producer.dag @@ -1,5 +1,7 @@ module v2.workflow.floor_discovery_producer +import std.algebra { trim } + import v2.compiler.discovery_enumeration { OwnedBoolWitnessClaimInit, OwnedDataDeclInitializer, diff --git a/src/v2/workflow/floor_naming_hygiene.dag b/src/v2/workflow/floor_naming_hygiene.dag index 881fcca4990..f09494d8ae5 100644 --- a/src/v2/workflow/floor_naming_hygiene.dag +++ b/src/v2/workflow/floor_naming_hygiene.dag @@ -1,5 +1,7 @@ module v2.workflow.floor_naming_hygiene +import std.algebra { trim } + import std.types { String, Bool, List } import v2.std.optional { Absent, From f845b09b2e430ffa0df871c704ad69517d81b363 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sat, 8 Aug 2026 22:51:09 +0000 Subject: [PATCH 03/15] Replace narrow-pool types overlay with minimal trim stub. Drop the verbatim std.types copy that re-minted Duration outside std.measure; the narrow-pool fixture now shadows only String/Bool kernel imports while std.algebra enters via explicit trim import from dag/std. Co-authored-by: Cursor --- .../narrow_pool/dag/std/types.dag | 335 +----------------- 1 file changed, 1 insertion(+), 334 deletions(-) diff --git a/fixtures/class_b_trim/narrow_pool/dag/std/types.dag b/fixtures/class_b_trim/narrow_pool/dag/std/types.dag index 98daf30da03..f1f7aaa4bcd 100644 --- a/fixtures/class_b_trim/narrow_pool/dag/std/types.dag +++ b/fixtures/class_b_trim/narrow_pool/dag/std/types.dag @@ -1,336 +1,3 @@ module std.types -import std.algebra { FreeMonoid, PartialFunction, PointwisePower, kernel_algebra_profile, algebra_type_param_names } - -data kernel_type_set: Map = { - "String": true, "Int": true, "Bool": true, "Float": true, - "Secret": true, "Json": true, "Unit": true, "Bytes": true -} - -fn is_kernel_type(name: String) -> Bool { - match map_get(kernel_type_set, name) { - Present { value: _ } => true - Absent => false - } -} - -data container_type_arity: Map = { - "List": 1, "Set": 1, "Map": 2, "Witness": 1 -} - -fn is_container_type(name: String) -> Bool { - match container_expected_arity(name: name) { - Present { value: _ } => true - Absent => false - } -} - -fn container_expected_arity(name: String) -> Int? { - map_get(container_type_arity, name) -} - -fn container_param_names_for(kind_name: String) -> List { - if kind_name == "Witness" { - ["T"] - } else { - match map_get(kernel_algebra_profile, kind_name) { - Present { value: p } => algebra_type_param_names(profile: p) - Absent => [] - } - } -} - -fn container_param_name(kind_name: String, index: Int) -> String? { - let names = container_param_names_for(kind_name: kind_name) - match names |> enumerate |> filter(pair => pair.first == index) |> map(pair => pair.second) |> first { - Present { value: name } => Present { value: name } - Absent => none - } -} - -data ordered_element_collections: Map = { - "List": true -} - -fn is_ordered_element_collection(name: String) -> Bool { - map_contains_key(ordered_element_collections, name) -} - -data container_template_algebra_rows: Map = { - "List": "FreeMonoid", - "list": "FreeMonoid", - "Set": "PointwisePower", - "set": "PointwisePower", - "Map": "PartialFunction", - "map": "PartialFunction", - "FreeMonoid": "FreeMonoid", - "free_monoid": "FreeMonoid", - "BooleanAlgebra": "BooleanAlgebra", - "boolean_algebra": "BooleanAlgebra", - "PartialFunction": "PartialFunction", - "partial_function": "PartialFunction", - "PointwisePower": "PointwisePower", - "pointwise_power": "PointwisePower" -} - -data container_template_alias_rows: Map = { - "List": "FreeMonoid", - "list": "FreeMonoid", - "Set": "PointwisePower", - "set": "PointwisePower", - "Map": "PartialFunction", - "map": "PartialFunction" -} - -fn container_template_algebra(name: String) -> String? { - map_get(container_template_algebra_rows, name) -} - -fn container_template_alias_algebra(name: String) -> String? { - map_get(container_template_alias_rows, name) -} - -fn canonical_container_names() -> List { - ["BooleanAlgebra", "FreeMonoid", "List", "Map", "PartialFunction", "Set", "Witness"] -} - -type Bool = True | False - -type Unit - -type Json -type Bytes - -type Char = Int where unicode_scalar, brand("Char") - -type List = FreeMonoid -type Set = PointwisePower -type Map = PartialFunction - -fn list_length(items: List) -> Int { - fold(items, init: 0, f: (acc, _) => acc + 1) -} - -type CommitSha = String - -type Sha256 = String -type RetryCount = Int where range(min: 1, max: 5) -type HttpStatus = Int where range(min: 100, max: 599) - -type Email = String -type Port = Int where range(min: 1, max: 65535) - -type GistId = String -type Secret nominal_opaque = String -type SecretValue = Secret where non_empty - -type SemVer = String -type NonEmptyStr = String where non_empty -type LanguageId = String where non_empty -type SecretName = String where non_empty - -type PathSegment = NonEmptyStr where brand("PathSegment") - -data path_segment_safety_note: String = "WHAT MAKES A STRING SAFE TO USE AS ONE PATH SEGMENT — the law, held once, so every branded id that becomes a directory name tests the same thing. The brand alone never carried it: a branded NonEmptyStr accepts \"..\", \"a/b\", and an embedded newline, so a value that typechecked as a segment could still escape its parent directory, alias a sibling, or split a line-oriented file written under that name. The refused set is exactly the characters that change what a concatenated path MEANS — `/` and `\\` introduce a level, `.` and `..` navigate, CR and LF terminate a record in every line-oriented format this repo writes, NUL terminates the string at the syscall boundary. Callers REFUSE on false rather than sanitizing, because a sanitized segment silently denotes something other than what the caller named (§5: a failure arm must refuse, never widen). Percent- or hex-encoding is the reversible alternative and is deliberately not offered until a caller needs a segment it cannot rename. - -This is a PREDICATE, not a constructor, and that is a modeling choice rather than a limitation worked around. A generic `path_segment(raw) -> PathSegment?` would put the branding cast in this module, and each caller would then re-cast the generic segment into its own brand anyway — two casts and two authorities for one law. Holding the law here and letting each branded id (gunbc.merge_admission.walk_attempt_id is the first) construct itself through it keeps one authority for what is hostile and one constructor per brand, which is what §3 asks for. A generic constructor earns its place when a second caller wants a bare PathSegment rather than a brand of its own; none does today." - -fn path_segment_is_safe(raw: String) -> Bool { - !((raw == "") - || (raw == ".") - || (raw == "..") - || string_contains(s: raw, pattern: "/") - || string_contains(s: raw, pattern: "\\") - || string_contains(s: raw, pattern: "\n") - || string_contains(s: raw, pattern: "\x0d") - || string_contains(s: raw, pattern: "\x00")) -} -type GlobSegment = NonEmptyStr where brand("GlobSegment") - -data rendered_terminal_text_note: String = "TEXT THAT CARRIES NO TERMINAL CONTROL SEQUENCES — the vocabulary two unrelated dependencies meet on so neither has to know the other exists. A terminal-facing tool emits SGR escapes around the values it highlights; a parser reading those values needs them absent. Those are facts about DIFFERENT upstreams, and the wrong fix is for either to mention the other: a note in a CLI's module explaining what a terminal multiplexer strips is a fact filed under the wrong authority, and it stops being true the moment a second capture transport appears (operator, 2026-07-30: 'tmux stripping ansi should be a tmux fact, and codex prompts should be a codex fact, and the interaction should be emergent').\\n\\nSo the brand is the seam. A capture surface that renders a pane PRODUCES RenderedTerminalText; a prompt parser CONSUMES it; and the composition is admissible because the types line up, not because one module was written knowing about the other. A raw stdout pipe cannot inhabit the brand, so feeding one to a parser that requires it is a type error rather than a runtime paste of escape bytes the operator discovers when the far end rejects them.\\n\\nIt lives in std because it is a property of text, not of any one tool — the same reason FilePath is not owned by whichever dependency happens to open files." - -type RenderedTerminalText = String where brand("RenderedTerminalText") -type FilePathParts { - segments: List -} -type GlobPattern { - segments: List -} -type FilePath = String where non_empty - -data file_path_sentinel_scaffold_note: String = "review 45141. FilePath where non_empty made empty-string absent-file sentinels unwritable; interim re-spellings encode absence in a nominally-non_empty carrier (state-space conflation — path promises a real path while sentinel means absent/synthetic). Sites: emit_rust/go/python emit_*_test_file returns TextFile { path: \"\", content: \"\" }, filtered by string_length(content) > 0 not path; 00_core make_span uses SourceSpan.file \"\" for kernel/no-authored-file spans. Preserves pre-wall behavior. dissolve-on: feature:optional-textfile-and-source-span (lift emit carriers to Option and SourceSpan.file to optional FilePath; delete sentinels and content-length filter)." - -type SourceSpan { - file: FilePath - start: Int - end: Int -} - -type Timestamp = String -type EpochSecs = Int where range(min: 0) -type EpochMs = Int where range(min: 0) -type Duration = Int where range(min: 0) -type Milliseconds = Int where range(min: 0), brand("Milliseconds") -type Seconds = Int where range(min: 0), brand("Seconds") - -type LogicalTime = NonEmptyStr where brand("LogicalTime") - -type IntentId = NonEmptyStr where brand("IntentId") -type IssueId = NonEmptyStr where brand("IssueId") -type RunKey = NonEmptyStr where brand("RunKey") -type ArtifactId = NonEmptyStr where brand("ArtifactId") -type LeaseToken = NonEmptyStr where brand("LeaseToken") -type WorkerId = NonEmptyStr where brand("WorkerId") -type CommentId = NonEmptyStr where brand("CommentId") -type SignalKey = NonEmptyStr where brand("SignalKey") - -type WorkflowProducerId = NonEmptyStr where brand("WorkflowProducerId") -type WorkflowObserverId = NonEmptyStr where brand("WorkflowObserverId") -type WorkflowProverId = NonEmptyStr where brand("WorkflowProverId") -type WorkflowRunId = NonEmptyStr where brand("WorkflowRunId") - -type GitRef = String where non_empty - -type TopologyNodeKind = Pure | Transport | SubDag | Env -type DocSourceKind = Template | Generated | Static - -type FermiDepth = Xs | S | M | L | Xl - -type TextFilePath = FilePath - -type BinaryFilePath = FilePath - -type MimeType = String - -type HttpMethod = GET | POST | PUT | PATCH | DELETE | HEAD | OPTIONS - -type AuthScheme - = Bearer - | Header { name: String } - | Basic { username: String } - | ApiKey - -type AccessToken { - token: Secret - scheme: AuthScheme - expires_at: Timestamp? -} - -type Credential { - token: Secret - scheme: AuthScheme - header_name: String? - source_id: String - required_scopes: List - expires_in: Int? -} - -type FilesystemHandle = FilePath where brand("FilesystemHandle") -type NetworkHandle = Unit where brand("NetworkHandle") -type ToolHandle = String where brand("ToolHandle") - -type TransportRequest { - method: HttpMethod - url: String - headers: Json - body: String -} - -type TransportResponse { - status: Int - headers: Json - body: String -} - -type FileResponse { - path: String - success: Bool - content: String -} - -type ShellResponse { - exit_code: Int - stdout: String - stderr: String -} - -type RestResponse { - status: Int - headers: Json - body: Json -} - -type TestResult { - name: String - ok: Bool - stdout: String - stderr: String - duration_ms: Milliseconds -} - -type Summary { - total: Int - passed: Int - failed: Int -} - -type StageResult { - name: String - success: Bool - stdout: String - stderr: String - skipped: Bool -} - -type RenderedTextFile { - path: String - content: String -} - -type ToolEntry { - name: String - command: String - description: String? -} - -type ToolRegistry { - tools: List -} - -type DagTopology { - nodes: List - edges: List - subdag_boundaries: List -} - -type TopologyNode { - id: String - label: String - kind: TopologyNodeKind - parent: String? -} - -type TopologyEdge { - from: String - to: String - connector_label: String? -} - -type DagDiff { - added: List - removed: List - changed: List -} - -type PragmaDirective { - key: String - value: String - scope: String? -} - -type DocSource { - path: FilePath - kind: DocSourceKind -} - -type ReferenceModel +data class_b_trim_narrow_pool_types_stub_note: String = "Minimal std.types overlay for Class B trim narrow-pool tests. Supplies only the kernel-type import surface (String, Bool) without std.algebra transitive import or forbidden Duration re-mint. std.algebra enters the closure via explicit import std.algebra { trim } resolved from dag/std." From 9f31eb00d8bd51a7d5c7e6a067c56a5aaaa5bff1 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sat, 8 Aug 2026 23:01:01 +0000 Subject: [PATCH 04/15] Clarify trim fn body as typecheck-only identity stub. Replace the dead-branch tautology with a plain identity body and note that free_call.trim is the semantic runtime authority (review 50610). Co-authored-by: Cursor --- dag/std/algebra.dag | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/dag/std/algebra.dag b/dag/std/algebra.dag index 222cb6953e6..1b964a04a80 100644 --- a/dag/std/algebra.dag +++ b/dag/std/algebra.dag @@ -418,12 +418,8 @@ fn all_algebra_template_names() -> List { all_algebra_field_templates() |> map(template => template.name) } -data trim_free_function_authority_note: String = "trim is the importable free-function authority for whitespace trim on String. Runtime execution routes through free_call.trim (v1_rt::trim); the scalar template row trim on FreeMonoid is the method spelling on the same carrier. Free-call trim(s) requires explicit import std.algebra { trim } — binding via std.types transitive algebra pool membership alone is Class B pool coincidence (#6985). Dissolve-on: bare trim free-call without listed import refuses once closure-independent binding lands repo-wide." +data trim_free_function_authority_note: String = "trim is the importable free-function authority for whitespace trim on String. The declared body is identity-for-typecheck only; semantic execution routes through free_call.trim (v1_rt::trim). The scalar template row trim on FreeMonoid is the method spelling on the same carrier. Free-call trim(s) requires explicit import std.algebra { trim } — binding via std.types transitive algebra pool membership alone is Class B pool coincidence (#6985). Dissolve-on: bare trim free-call without listed import refuses once closure-independent binding lands repo-wide." fn trim(s: String) -> String { - if false { - s - } else { - s - } + s } From 24d403ebca9e0500e72ec72205df1161d048e6bd Mon Sep 17 00:00:00 2001 From: "gunbai-bot[bot]" <289086189+gunbai-bot[bot]@users.noreply.github.com> Date: Sat, 8 Aug 2026 19:01:52 -0400 Subject: [PATCH 05/15] Fix silent floor terminal exits and orphan worker processes (#8060) * Fix silent floor terminal exits and orphan worker processes. Post-walk compile_clean and receipt-write failures now populate located refusal details, emit falsifier classification, and journal walk-terminal rows before fast-exit; the coordinator replays worker terminal detail on failure and arms PR_SET_PDEATHSIG so timed-out steps do not leave orphaned claim_executors. Co-authored-by: Cursor * Journal pre-walk worker terminal refusals on the main return path. Workers that fail before floor_terminal_fast_exit now emit the same walk-terminal journal/stderr row as post-walk failures; drop duplicate coordinator-observation journal on worker failure replay. Co-authored-by: Cursor * Gate PR_SET_PDEATHSIG worker spawn hook on Linux only. prctl is not available on macOS; #[cfg(unix)] was too broad for the new pre_exec arm. Co-authored-by: Cursor --------- Co-authored-by: gunbc-ci-auto-heal Co-authored-by: Cursor --- src/v1/stage0/src/bin/claim_executor.rs | 318 +++++++++++++++++++++--- 1 file changed, 277 insertions(+), 41 deletions(-) diff --git a/src/v1/stage0/src/bin/claim_executor.rs b/src/v1/stage0/src/bin/claim_executor.rs index f8119f973e4..df8c17710ca 100644 --- a/src/v1/stage0/src/bin/claim_executor.rs +++ b/src/v1/stage0/src/bin/claim_executor.rs @@ -8744,6 +8744,19 @@ fn run_walk( || !selection_degradation_receipt_ok || !floor_component_receipt_ok || !materialization_receipt_ok; + push_ordinary_receipt_write_refusals( + &mut failure_details, + resolve_receipt_ok, + batch_wall_receipt_ok, + gate_warm_cost_receipt_ok, + witness_row_cost_receipt_ok, + wet_witness_row_outcome_receipt_ok, + witness_row_cost_drift_receipt_ok, + witness_row_cost_migration_disclosure_receipt_ok, + selection_degradation_receipt_ok, + floor_component_receipt_ok, + materialization_receipt_ok, + ); // Floor finalization laws (the in-executor form of the deleted resolve/ // materialization gate steps): validated AFTER the receipts wrote and BEFORE the // on-success stages, so a violation blocks admission instead of post-dating it. @@ -9877,6 +9890,21 @@ fn spawn_floor_worker( command.env(FLOOR_WORKER_TERMINAL_ENV, &terminal_path); command.env("GUNBC_FLOOR_WALK_ATTEMPT_ID", walk_attempt_id); command.env(FLOOR_DISCOVERY_CONSUMER_ENV, discovery_consumer); + #[cfg(target_os = "linux")] + { + use std::os::unix::process::CommandExt; + // When the thin coordinator is killed by the foreign step timeout, workers must not + // keep running as orphaned claim_executors (signature 2). SIGTERM on parent death + // gives the worker a chance to flush its terminal receipt before the runner reaps it. + unsafe { + command.pre_exec(|| { + if libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM) != 0 { + return Err(std::io::Error::last_os_error()); + } + Ok(()) + }); + } + } // The worker's post-walk ledger harvest can spend minutes dropping its memo // contexts after the final discovery line. A blocking `Command::status` // made that whole interval silent: the worker's own heartbeat shares its @@ -9951,6 +9979,24 @@ fn floor_plan_function_arg(args: &[String]) -> Option<&str> { .map(|pair| pair[1].as_str()) } +fn coordinator_terminal_refusal(detail: &str) -> ExitCode { + append_floor_phase_journal("coordinator-terminal", "refused", detail); + eprintln!("claim_executor: floor coordinator refusal: {detail}"); + ExitCode::from(1) +} + +fn coordinator_report_worker_failure(worker: &ObservedFloorWorker, context: &str) -> ExitCode { + let outcome = floor_worker_observation_outcome(worker); + let detail = format!( + "{context}: worker={} termination={} outcome={} detail={}", + worker.worker, + floor_worker_termination_label(&worker.termination), + outcome.label, + outcome.detail + ); + coordinator_terminal_refusal(&detail) +} + fn maybe_run_floor_coordinator(args: &[String]) -> Option { if args.iter().any(|arg| arg == "--floor-worker-role") || floor_plan_function_arg(args) != Some("gunbc_ci_floor_plan") @@ -9959,8 +10005,9 @@ fn maybe_run_floor_coordinator(args: &[String]) -> Option { } if let Some(parent) = Path::new(FLOOR_WORKER_OBSERVATION_RECEIPT_PATH).parent() { if let Err(e) = fs::create_dir_all(parent) { - eprintln!("claim_executor: floor coordinator receipt directory refusal: {e}"); - return Some(ExitCode::from(1)); + return Some(coordinator_terminal_refusal(&format!( + "floor coordinator receipt directory refusal: {e}" + ))); } } let _ = fs::remove_file(FLOOR_WORKER_OBSERVATION_RECEIPT_PATH); @@ -9969,14 +10016,16 @@ fn maybe_run_floor_coordinator(args: &[String]) -> Option { let _ = fs::remove_file(path); } if let Err(msg) = initialize_scoped_witness_receipt() { - eprintln!("claim_executor: floor coordinator receipt arm refusal: {msg}"); - return Some(ExitCode::from(1)); + return Some(coordinator_terminal_refusal(&format!( + "floor coordinator receipt arm refusal: {msg}" + ))); } let walk_attempt_id = match observe_walk_attempt_id() { Ok(id) => id, Err(msg) => { - eprintln!("claim_executor: floor coordinator walk-attempt refusal: {msg}"); - return Some(ExitCode::from(1)); + return Some(coordinator_terminal_refusal(&format!( + "floor coordinator walk-attempt refusal: {msg}" + ))); } }; let ordinary = match spawn_floor_worker(args, "ordinary", None, 0, &walk_attempt_id, "producer") @@ -9984,21 +10033,23 @@ fn maybe_run_floor_coordinator(args: &[String]) -> Option { Ok(observed) => observed, Err(msg) => { replay_ordinary_floor_wet_witness_row_outcomes(); - eprintln!("claim_executor: floor coordinator ordinary-worker refusal: {msg}"); - return Some(ExitCode::from(1)); + return Some(coordinator_terminal_refusal(&format!( + "floor coordinator ordinary-worker refusal: {msg}" + ))); } }; if !floor_worker_succeeded(&ordinary) { - eprintln!( - "claim_executor: floor coordinator stopping before scoped workers: ordinary worker did not complete" - ); - return Some(ExitCode::from(1)); + return Some(coordinator_report_worker_failure( + &ordinary, + "stopping before scoped workers because ordinary worker did not complete", + )); } let source_roots = match source_roots_from_executor_args(args) { Ok(roots) => roots, Err(msg) => { - eprintln!("claim_executor: floor coordinator source-roots refusal: {msg}"); - return Some(ExitCode::from(1)); + return Some(coordinator_terminal_refusal(&format!( + "floor coordinator source-roots refusal: {msg}" + ))); } }; let excludes = v1_compiler::cli_run::witness_exclusion_substrings(); @@ -10012,20 +10063,23 @@ fn maybe_run_floor_coordinator(args: &[String]) -> Option { ) { Ok(request) => request, Err(msg) => { - eprintln!("claim_executor: floor coordinator pre-plan request refusal: {msg}"); - return Some(ExitCode::from(1)); + return Some(coordinator_terminal_refusal(&format!( + "floor coordinator pre-plan request refusal: {msg}" + ))); } }; let digest = request_identity_digest(&pre_plan_request); if let Err(msg) = verify_floor_discovery_terminal_for_coordinator(&walk_attempt_id, &digest) { - eprintln!("claim_executor: floor coordinator snapshot terminal refusal: {msg}"); - return Some(ExitCode::from(1)); + return Some(coordinator_terminal_refusal(&format!( + "floor coordinator snapshot terminal refusal: {msg}" + ))); } let batch_ids = match read_scoped_witness_batch_manifest() { Ok(ids) => ids, Err(msg) => { - eprintln!("claim_executor: floor coordinator manifest refusal: {msg}"); - return Some(ExitCode::from(1)); + return Some(coordinator_terminal_refusal(&format!( + "floor coordinator manifest refusal: {msg}" + ))); } }; for (index, batch_id) in batch_ids.iter().enumerate() { @@ -10039,17 +10093,30 @@ fn maybe_run_floor_coordinator(args: &[String]) -> Option { ) { Ok(observed) => observed, Err(msg) => { - eprintln!("claim_executor: floor coordinator scoped-worker refusal: {msg}"); - return Some(ExitCode::from(1)); + return Some(coordinator_terminal_refusal(&format!( + "floor coordinator scoped-worker `{batch_id}` refusal: {msg}" + ))); } }; if !floor_worker_succeeded(&scoped) { - eprintln!( - "claim_executor: floor coordinator stopping after scoped worker `{batch_id}` did not complete" - ); - return Some(ExitCode::from(1)); + return Some(coordinator_report_worker_failure( + &scoped, + &format!("stopping after scoped worker `{batch_id}` did not complete"), + )); } } + append_floor_phase_journal( + "coordinator-terminal", + "completed", + &format!( + "scoped_workers={} walk_attempt_id={walk_attempt_id}", + batch_ids.len() + ), + ); + eprintln!( + "[floor-coordinator] outcome=completed scoped_workers={} walk_attempt_id={walk_attempt_id}", + batch_ids.len() + ); Some(ExitCode::SUCCESS) } @@ -10869,14 +10936,25 @@ fn run() -> Result { &source_roots, ); v1_compiler::v1_interpreter::group_end(); + let mut terminal_failure_details = outcome.failure_details.clone(); + if compile_clean_over_budget { + terminal_failure_details.push( + "post-walk compile_clean leg exceeded its declared clamp (FLOOR-COMPILE-CLEAN-OVER-BUDGET)" + .to_string(), + ); + } + if !compile_clean_drift_receipt_ok { + terminal_failure_details + .push("post-walk compile_clean cost drift receipt refused or unwritable".to_string()); + } let any_failed = outcome.any_failed || compile_clean_over_budget || !compile_clean_drift_receipt_ok; - if outcome.any_failed { - emit_falsifier_failure_class(&outcome.failure_details, &outcome.infra_faults); + if any_failed { + emit_falsifier_failure_class(&terminal_failure_details, &outcome.infra_faults); } let terminal_detail = walk_terminal_detail( any_failed, - &outcome.failure_details, + &terminal_failure_details, &outcome.infra_faults, compile_clean_over_budget, compile_clean_drift_receipt_ok, @@ -10985,6 +11063,70 @@ fn walk_exit_code(any_failed: bool) -> i32 { } } +/// Located refusal lines for receipt-write failures that redden the ordinary floor without +/// a witness batch failure. Without these, the walk can exit 1 with every batch green and +/// an empty `failure_details` — the silent exit-1 class #8058's terminal receipt fix did +/// not cover because the refusal lives only in the writer's eprintln. +fn push_ordinary_receipt_write_refusals( + failure_details: &mut Vec, + resolve_receipt_ok: bool, + batch_wall_receipt_ok: bool, + gate_warm_cost_receipt_ok: bool, + witness_row_cost_receipt_ok: bool, + wet_witness_row_outcome_receipt_ok: bool, + witness_row_cost_drift_receipt_ok: bool, + witness_row_cost_migration_disclosure_receipt_ok: bool, + selection_degradation_receipt_ok: bool, + floor_component_receipt_ok: bool, + materialization_receipt_ok: bool, +) { + let mut push = |ok: bool, detail: &'static str| { + if !ok { + failure_details.push(detail.to_string()); + } + }; + push( + resolve_receipt_ok, + "ordinary-floor resolve realization receipt write refused", + ); + push( + batch_wall_receipt_ok, + "ordinary-floor batch wall receipt write refused", + ); + push( + gate_warm_cost_receipt_ok, + "ordinary-floor gate warm-cost receipt write refused", + ); + push( + witness_row_cost_receipt_ok, + "ordinary-floor witness row-cost receipt write refused", + ); + push( + wet_witness_row_outcome_receipt_ok, + "ordinary-floor wet witness row-outcome receipt write refused", + ); + push( + witness_row_cost_drift_receipt_ok, + "ordinary-floor witness row-cost drift receipt write refused", + ); + push( + witness_row_cost_migration_disclosure_receipt_ok, + "ordinary-floor witness row-cost migration disclosure receipt write refused", + ); + push( + selection_degradation_receipt_ok, + "ordinary-floor selection degradation receipt write refused", + ); + push( + floor_component_receipt_ok, + "ordinary-floor floor component receipt write refused", + ); + push( + materialization_receipt_ok, + "ordinary-floor materialization demand receipt write refused", + ); +} + /// Located refusal carried in the worker terminal receipt for the coordinator to replay. /// /// `floor_terminal_fast_exit` used to write only `walk terminal exit code N`, which the @@ -11030,9 +11172,16 @@ fn walk_terminal_detail( /// `run()`'s walk path — every plan walk (floor, plan-artifact, regen, falsifier) exits /// through it; every refusal/error path above returns normally. The exit CODE is exactly /// `walk_exit_code(any_failed)` — behavior-identical to the ExitCode return it replaces. +/// +/// #8058 carried the located refusal in the worker terminal receipt file only; Actions +/// can drop inherited stderr after a large discovery run, and the phase journal had no +/// terminal row — so a post-walk compile_clean refusal or a receipt-write failure looked +/// like a silent exit-1 with every batch green. Emit the same detail on stderr and the +/// durable journal before `process::exit`. fn floor_terminal_fast_exit(code: i32, detail: &str) -> ! { use std::io::Write as _; let terminal_outcome = if code == 0 { "completed" } else { "failed" }; + emit_floor_terminal_outcome(terminal_outcome, detail); let final_code = match write_floor_worker_terminal(terminal_outcome, detail) { Ok(()) => code, Err(msg) => { @@ -11045,6 +11194,49 @@ fn floor_terminal_fast_exit(code: i32, detail: &str) -> ! { std::process::exit(final_code) } +fn emit_floor_terminal_outcome(outcome: &str, detail: &str) { + append_floor_phase_journal("walk-terminal", outcome, detail); + if outcome == "completed" { + eprintln!("[floor-terminal] outcome=completed detail={detail}"); + } else { + eprintln!("[floor-terminal-refusal] outcome=failed detail={detail}"); + } +} + +/// Pre-walk worker failures return through `main()` instead of `floor_terminal_fast_exit`. +/// Mirror the walk-terminal journal/stderr emission so those refusals survive Actions log +/// truncation the same way post-walk failures do. +fn emit_worker_terminal_before_return(code: ExitCode) -> ExitCode { + let Some(path) = std::env::var_os(FLOOR_WORKER_TERMINAL_ENV) else { + return code; + }; + if code == ExitCode::SUCCESS { + return code; + } + let path = PathBuf::from(path); + let (outcome, detail) = if path.exists() { + match fs::read_to_string(&path) { + Ok(body) => { + let line = body.trim_end_matches(['\r', '\n']); + let (label, detail) = line.split_once('\t').unwrap_or((line, "")); + let outcome = if label == "completed" { + "completed" + } else { + "failed" + }; + (outcome, detail.to_string()) + } + Err(e) => ("failed", format!("worker terminal receipt unreadable: {e}")), + } + } else { + let detail = "worker returned before producing a walk terminal receipt".to_string(); + let _ = write_floor_worker_terminal("failed", &detail); + ("failed", detail) + }; + emit_floor_terminal_outcome(outcome, &detail); + code +} + fn main() -> ExitCode { let coordinator_args: Vec = std::env::args().skip(1).collect(); if let Some(code) = maybe_run_floor_coordinator(&coordinator_args) { @@ -11061,18 +11253,7 @@ fn main() -> ExitCode { Ok(code) => code, Err(code) => code, }; - if std::env::var_os(FLOOR_WORKER_TERMINAL_ENV).is_some() { - let terminal_exists = std::env::var_os(FLOOR_WORKER_TERMINAL_ENV) - .map(PathBuf::from) - .is_some_and(|path| path.exists()); - if !terminal_exists { - let _ = write_floor_worker_terminal( - "failed", - "worker returned before producing a walk terminal receipt", - ); - } - } - code + emit_worker_terminal_before_return(code) } #[cfg(test)] @@ -12135,6 +12316,61 @@ mod tests { assert!(detail.contains("compile_clean_cost_drift_receipt_refused")); } + #[test] + fn push_ordinary_receipt_write_refusals_locates_each_failed_writer() { + let mut details = Vec::new(); + push_ordinary_receipt_write_refusals( + &mut details, + true, + false, + true, + true, + true, + true, + true, + true, + false, + true, + ); + assert_eq!(details.len(), 2); + assert!(details + .iter() + .any(|d| d.contains("batch wall receipt write refused"))); + assert!(details + .iter() + .any(|d| d.contains("floor component receipt write refused"))); + } + + #[test] + fn emit_worker_terminal_before_return_journals_pre_walk_refusal() { + let dir = std::env::temp_dir().join(format!( + "claim-executor-worker-terminal-before-return-{}", + std::process::id() + )); + let _ = fs::create_dir_all(&dir); + let terminal = dir.join("worker-terminal.tsv"); + let journal = dir.join("floor-phase-journal.tsv"); + let _ = fs::remove_file(&terminal); + let _ = fs::remove_file(&journal); + fs::write( + &terminal, + "refused\tscoped witness scheduling refusal: fixture detail\n", + ) + .expect("write worker terminal fixture"); + std::env::set_var(FLOOR_WORKER_TERMINAL_ENV, &terminal); + std::env::set_var(FLOOR_PHASE_JOURNAL_ENV, &journal); + let code = emit_worker_terminal_before_return(ExitCode::from(1)); + std::env::remove_var(FLOOR_WORKER_TERMINAL_ENV); + std::env::remove_var(FLOOR_PHASE_JOURNAL_ENV); + assert_eq!(code, ExitCode::from(1)); + let persisted = fs::read_to_string(&journal).expect("walk-terminal journal row"); + let _ = fs::remove_dir_all(&dir); + assert!( + persisted.contains("\twalk-terminal\tfailed\tscoped witness scheduling refusal"), + "pre-walk worker refusal must reach the durable journal: {persisted:?}" + ); + } + fn outcome(entry: &str, function: &str, o: ClaimOutcome) -> DiscoveryWitnessOutcome { DiscoveryWitnessOutcome { entry: entry.to_string(), From cd46f42b291a3a8ebaf6ad75470e9ae6d9e1e2e9 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sat, 8 Aug 2026 23:11:50 +0000 Subject: [PATCH 06/15] Fix trim call shape in floor_discovery_producer. The std.algebra trim free function declares parameter `s`, not `seg`. Co-authored-by: Cursor --- src/v2/workflow/floor_discovery_producer.dag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v2/workflow/floor_discovery_producer.dag b/src/v2/workflow/floor_discovery_producer.dag index 93c306a127c..78c6f9490b6 100644 --- a/src/v2/workflow/floor_discovery_producer.dag +++ b/src/v2/workflow/floor_discovery_producer.dag @@ -210,7 +210,7 @@ fn floor_discovery_initializer_rest_is_strict_single_token(rest: String, token: } fn floor_discovery_disposition_row_variant(line: String) -> Optional { - let init_rest = trim(seg: floor_discovery_after_first_equals(s: line)) + let init_rest = trim(s: floor_discovery_after_first_equals(s: line)) let first_token = floor_discovery_first_token_before_space(seg: init_rest) if (first_token == "") || !floor_discovery_initializer_rest_is_strict_single_token(rest: init_rest, token: first_token) From 0c11b1f7e7adf69d13095b04f1f7ea7c42a191bc Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sat, 8 Aug 2026 23:38:33 +0000 Subject: [PATCH 07/15] Restore coincidence narrow-pool negative control and regen trim exports. Add trim_free_call_fails_in_narrow_pool_without_algebra_coincidence compiling coincidence_specimen.dag against the two-root narrow pool (no dag/std) and assert trim does not resolve via pool coincidence; pair with a PoolCoincidence positive binding control. Regen stage0 so extdeps.uri trim import and std.algebra trim export match the emitter. Co-authored-by: Cursor --- .../class_b_trim_pool_coincidence_receipt.md | 2 +- .../narrow_pool/dag/std/types.dag | 4 +- src/v1/stage0/src/extdeps_uri.rs | 1 + src/v1/stage0/src/std_algebra.rs | 13 ++ .../tests/src/class_b_trim_specimen_test.rs | 114 +++++++++++++++++- 5 files changed, 131 insertions(+), 3 deletions(-) diff --git a/docs/probes/class_b_trim_pool_coincidence_receipt.md b/docs/probes/class_b_trim_pool_coincidence_receipt.md index 0eb8fcda6c2..7d9043eae97 100644 --- a/docs/probes/class_b_trim_pool_coincidence_receipt.md +++ b/docs/probes/class_b_trim_pool_coincidence_receipt.md @@ -19,7 +19,7 @@ Two discriminating failure shapes: CTRL_BUILD_MODE=local RUSTC_WRAPPER= cargo test -p v1-compiler-tests class_b_trim_specimen -- --nocapture ``` -Five tests: narrow-pool failure, declared-pool compile, direct-import absence check, perturbation overlay stability, FreeMonoid receiver refusal. +Eight tests: coincidence narrow-pool trim unbound (no `dag/std`), coincidence pool-coincidence trim binding when algebra is in closure, explicit-import narrow-pool refusal without `dag/std`, explicit-import narrow-pool compile with `dag/std`, coincidence compile success with `dag/std` present, direct-import census with coincidence pool, perturbation overlay stability, FreeMonoid receiver refusal. ## Relation to rust_test_fixtures Class B gate diff --git a/fixtures/class_b_trim/narrow_pool/dag/std/types.dag b/fixtures/class_b_trim/narrow_pool/dag/std/types.dag index f1f7aaa4bcd..aaa161dbf04 100644 --- a/fixtures/class_b_trim/narrow_pool/dag/std/types.dag +++ b/fixtures/class_b_trim/narrow_pool/dag/std/types.dag @@ -1,3 +1,5 @@ module std.types -data class_b_trim_narrow_pool_types_stub_note: String = "Minimal std.types overlay for Class B trim narrow-pool tests. Supplies only the kernel-type import surface (String, Bool) without std.algebra transitive import or forbidden Duration re-mint. std.algebra enters the closure via explicit import std.algebra { trim } resolved from dag/std." +type Bool = True | False + +data class_b_trim_narrow_pool_types_stub_note: String = "Minimal std.types overlay for Class B trim narrow-pool tests. Supplies kernel-type import surface (String, Bool) without std.algebra transitive import or forbidden Duration re-mint. std.algebra enters the closure via explicit import std.algebra { trim } resolved from dag/std." diff --git a/src/v1/stage0/src/extdeps_uri.rs b/src/v1/stage0/src/extdeps_uri.rs index 0c66b28f642..572212dc186 100644 --- a/src/v1/stage0/src/extdeps_uri.rs +++ b/src/v1/stage0/src/extdeps_uri.rs @@ -13,6 +13,7 @@ use self::UriScheme::*; use self::UriUnicodeScalarConstruction::*; use self::UriUtf8OctetConstruction::*; use self::UriValidatedScalarConstruction::*; +pub use crate::std_algebra::trim; pub use crate::std_types::NonEmptyStr; pub use crate::std_unicode_types::{ unicode_scalar_max_code_point, unicode_surrogate_first_code_point, diff --git a/src/v1/stage0/src/std_algebra.rs b/src/v1/stage0/src/std_algebra.rs index 80453d9a965..accf6ebe4f9 100644 --- a/src/v1/stage0/src/std_algebra.rs +++ b/src/v1/stage0/src/std_algebra.rs @@ -1678,6 +1678,19 @@ pub fn all_algebra_template_names() -> Rc> { }) } +pub fn trim_free_function_authority_note() -> String { + thread_local! { + static CACHED: String = { + "trim is the importable free-function authority for whitespace trim on String. The declared body is identity-for-typecheck only; semantic execution routes through free_call.trim (v1_rt::trim). The scalar template row trim on FreeMonoid is the method spelling on the same carrier. Free-call trim(s) requires explicit import std.algebra { trim } — binding via std.types transitive algebra pool membership alone is Class B pool coincidence (#6985). Dissolve-on: bare trim free-call without listed import refuses once closure-independent binding lands repo-wide.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn trim(s: String) -> String { + s +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct Less; #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] diff --git a/src/v1/tests/src/class_b_trim_specimen_test.rs b/src/v1/tests/src/class_b_trim_specimen_test.rs index b7deddd9a9b..e877caf3724 100644 --- a/src/v1/tests/src/class_b_trim_specimen_test.rs +++ b/src/v1/tests/src/class_b_trim_specimen_test.rs @@ -1,7 +1,12 @@ //! Class B trim: explicit `import std.algebra { trim }` binds in narrow pools; //! coincidence binding remains a regression control only. -use v1_compiler::cli_run::compile_declared_import_closure_only_with_pool; +use v1_compiler::cli_run::{ + compile_declared_import_closure_only_with_pool, + declared_import_closure_binding_observation_from_resolved, + DeclaredImportClosureBindingObservation, DeclaredImportClosureBindingObserved, + UnlistedImportBindingSource, +}; use v1_compiler::v1_std_core::{module_imports, CompilerDiagnostic}; use crate::helpers::workspace_root; @@ -20,6 +25,13 @@ fn trim_narrow_pool_roots() -> Vec { ] } +fn trim_narrow_pool_without_algebra_roots() -> Vec { + vec![ + "fixtures/class_b_trim/narrow_pool".to_string(), + "fixtures/class_b_trim".to_string(), + ] +} + fn trim_declared_import_pool_roots() -> Vec { vec!["dag/std".to_string(), "fixtures/class_b_trim".to_string()] } @@ -82,6 +94,106 @@ fn consumer_lists_algebra_directly( .any(|imp| imp.name == "std.algebra") } +#[test] +fn trim_free_call_fails_in_narrow_pool_without_algebra_coincidence() { + std::env::set_current_dir(workspace_root()).expect("cwd"); + let compiled = compile_declared_import_closure_only_with_pool( + &trim_narrow_pool_without_algebra_roots(), + COINCIDENCE_ENTRY, + None, + ) + .expect("compile"); + assert!( + !algebra_module_in_graph(compiled.as_ref()), + "std.algebra must be absent from narrow pool without dag/std" + ); + assert!( + !consumer_lists_algebra_directly(compiled.as_ref(), COINCIDENCE_CONSUMER), + "coincidence specimen must not directly import std.algebra" + ); + let trim_binding = declared_import_closure_binding_observation_from_resolved( + compiled.as_ref(), + COINCIDENCE_CONSUMER, + "trim", + ); + match trim_binding { + DeclaredImportClosureBindingObservation::Observed( + DeclaredImportClosureBindingObserved { + symbol_resolves: false, + .. + }, + ) => {} + DeclaredImportClosureBindingObservation::Observed(observed) => { + panic!( + "trim must not resolve via pool coincidence when std.algebra is absent from narrow pool: {observed:?}" + ); + } + DeclaredImportClosureBindingObservation::NotRunnable(reason) => { + panic!("trim binding observation must be runnable: {reason}"); + } + } +} + +#[test] +fn trim_coincidence_free_call_binds_via_pool_when_algebra_in_closure() { + std::env::set_current_dir(workspace_root()).expect("cwd"); + let compiled = compile_declared_import_closure_only_with_pool( + &trim_declared_import_pool_roots(), + COINCIDENCE_ENTRY, + None, + ) + .expect("compile"); + assert!( + algebra_module_in_graph(compiled.as_ref()), + "std.algebra must be in declared-import closure via std.types transitive import" + ); + let trim_binding = declared_import_closure_binding_observation_from_resolved( + compiled.as_ref(), + COINCIDENCE_CONSUMER, + "trim", + ); + match trim_binding { + DeclaredImportClosureBindingObservation::Observed( + DeclaredImportClosureBindingObserved { + symbol_resolves: true, + binding_source: Some(UnlistedImportBindingSource::PoolCoincidence), + definer_module: Some(definer), + .. + }, + ) => { + assert_eq!(definer, "std.algebra"); + } + other => panic!( + "trim must resolve via pool coincidence when std.algebra is in closure without direct import: {other:?}" + ), + } +} + +#[test] +fn trim_free_call_refuses_in_narrow_pool_without_algebra_authority() { + std::env::set_current_dir(workspace_root()).expect("cwd"); + let compiled = compile_declared_import_closure_only_with_pool( + &trim_narrow_pool_without_algebra_roots(), + SPECIMEN_ENTRY, + None, + ) + .expect("compile"); + assert!( + !algebra_module_in_graph(compiled.as_ref()), + "std.algebra must be absent from narrow pool without dag/std" + ); + assert!( + trim_not_found_diagnostic_count(compiled.as_ref()) > 0 + || hard_diagnostic_count(compiled.as_ref()) > 0, + "explicit import std.algebra {{ trim }} must refuse when std.algebra is not in pool: {:?}", + compiled + .diagnostics + .iter() + .map(|d| format!("{:?}", d.diagnostic)) + .collect::>() + ); +} + #[test] fn trim_free_call_compiles_via_explicit_import_in_narrow_pool() { std::env::set_current_dir(workspace_root()).expect("cwd"); From 40da942cade16052b552a25162d991a2215e775e Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sat, 8 Aug 2026 23:51:01 +0000 Subject: [PATCH 08/15] Add HAND-RUST scaffold deferral to class_b_trim_specimen_test. Documents the import-strip Class B lane, why pool-overlay probes stay in v1-compiler-tests, and the closure-independent-binding dissolution trigger. Co-authored-by: Cursor --- src/v1/tests/src/class_b_trim_specimen_test.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/v1/tests/src/class_b_trim_specimen_test.rs b/src/v1/tests/src/class_b_trim_specimen_test.rs index e877caf3724..506a8510852 100644 --- a/src/v1/tests/src/class_b_trim_specimen_test.rs +++ b/src/v1/tests/src/class_b_trim_specimen_test.rs @@ -1,5 +1,19 @@ //! Class B trim: explicit `import std.algebra { trim }` binds in narrow pools; //! coincidence binding remains a regression control only. +//! +//! SCAFFOLD (DESIGN §7 HAND-RUST GATE — explicit deferral): this module exercises +//! the v1 compiler-test harness because Class B trim discrimination needs +//! primary-precedence pool overlays (`fixtures/class_b_trim/narrow_pool/`) and +//! `compile_declared_import_closure_only_with_pool` receipts that cannot be enrolled +//! as discovered `dag/test/claim/*_test.dag` rows while the Class B gate observes only +//! `item_registry` symbols (`run_class_b_import_closure_gate` / `rust_selection_policy_node`) +//! and `trim` has no such registry row. Lane: import-strip witness-discovery cascade +//! (#6985 Class B pool-membership coincidence; `import-strip-witness-discovery-cascade-diagnosis.md` +//! §9, §12). Sole dissolution: the change landing closure-independent binding for bare +//! free-call `trim` (dissolve trigger on `trim_free_function_authority_note` in +//! `std.algebra` — bare trim without listed import refuses repo-wide) must migrate +//! `fixtures/class_b_trim/*.dag` into enrolled `dag/test/claim/class_b_trim_*` witness +//! rows on the floor roster and delete this Rust module in the same change. use v1_compiler::cli_run::{ compile_declared_import_closure_only_with_pool, From 7bcd31d24c160e0fe0b0d63fd1ed6462b17984c2 Mon Sep 17 00:00:00 2001 From: Brian Searls <11205878+briansrls@users.noreply.github.com> Date: Sat, 8 Aug 2026 19:59:15 -0400 Subject: [PATCH 09/15] decl_facts: explicit-import resolution + resolved parent/arm projection prerequisite (#7924) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Cut exact-initializer-identity successor from main (#7855 operator verdict). Lift foundation only from session/tidy-boar-761: two-root duplicate_qn fixture, fixture-scoped pool_roots, type-env projection marshal (WIP — structural blockers from operator review remain), dimensionless Rust controls, decl_facts-vs-compile population divergence note. Copy #7796 prereq bank: decl_facts_skeleton, qualified-name resolution fixtures, 12 skeleton witness cases, constructor-lexeme negative boundary tests. Does not include tidy-boar CI retry commits or unrelated branch surface. Successor scope: ExactDeclarationIdentity carrier, binding_kind gate, eight executing controls, exact whole-tree marshal/refusal census. Co-authored-by: Cursor * Fix pool_roots fork: one authority in fixture witness_support. The lift merged two lineages that both declared decl_facts_reflection_fixture_pool_roots with different populations. Consolidate the six-root walk in test.fixture.decl_facts_reflection.witness_support; projection witnesses import that row. Skeleton lexeme witnesses use an explicit narrower decl_facts_skeleton_fixture_pool_roots. Co-authored-by: Cursor * Fix CI build: sync stage0 with main gate and witness-cost surfaces. The tidy-boar lift left cli_run and v1_interpreter behind main: missing CompilerDiagnostic histogram arms, gate failure-detail builtins, and the WitnessRowCost struct claim_executor expects. Restore those surfaces without changing the decl-facts lift. Co-authored-by: Cursor * Fix CI build: sync stage0 with main gate and witness-cost surfaces. The tidy-boar lift left cli_run and v1_interpreter behind main: missing CompilerDiagnostic histogram arms, gate failure-detail builtins, and the WitnessRowCost struct claim_executor expects. Restore those surfaces without changing the decl-facts lift. Co-authored-by: Cursor * Ground exact variant identity on parent/arm declaration carriers and VariantValueBinding gate. ResolvedVariantIdentity now carries full ExactDeclarationIdentity for parent and arm instead of lossy qualified-name pairs; marshaling projects parent_type and arm alongside legacy parent_qualified_name/variant_name fields. Variant-value resolution requires infer-stamped VariantValueBinding and resolves parent coproduct through the binding's parent_enum authority rather than spelling plus annotation heuristics. Co-authored-by: Cursor * Add VariantValueBinding and module-order controls for exact initializer identity. Control 1: planted scaffold_with_data_ref specimen plus executing .dag and Rust witnesses prove a data-item reference with coproduct annotation marshals NotVariantValueProjection, not a variant value. Control 4: reversed source-file order leaves constructor parent identity unchanged. Also gate call_env_depth witness behind an armed atomic (default off) and fix namespace_alias_decl_test module gating. Co-authored-by: Cursor * Fix compile-clean gate: restore ensure_is_converged imports from main. Merge carry dropped gunbc.build_cache_ensure and gunbc.compile_pool_ensure imports in their witness tests. Also land control 5: ExactDeclarationIdentity lookup grain with executing witnesses for duplicate-qn distinctness and duplicate-exact-identity ambiguity. Co-authored-by: Cursor * Fix batch-3 decl_facts witnesses without growing migration debt. Group B: projection helpers read initializer roots (plain record + coproduct). Group A: pool-corpus duplicate bare-type index lets decl_facts marshal ambiguous variant values when witness ctx.modules is narrower than the fixture pool. Group C: delete redundant skeleton Rust test; retain only source-order seam test with a typed retirement row (no baseline bump). Co-authored-by: Cursor * Register decl_facts_marshal_bridge in stage0 crate layout for regen. Hand-added pub mod without frontier registration made regen_verify fail: fresh emit omitted the module while the committed lib.rs carried it. Co-authored-by: Cursor * Fix batch-3 discovery re-eval of duplicate OtherTwo side-effect row. The entry module duplicated witness_support's ambiguous_shared_b closure loader; corpus re-eval of that local data row ran without OtherTwo in scope. Closure loading stays on witness_support's enrolled side-effect row. Co-authored-by: Cursor * Restore ambiguous_shared_b import without duplicate side-effect data row. Import-only closure load keeps Group A green; removing the local OtherTwo data row avoids batch-3 discovery re-eval undefined-variable failure. Co-authored-by: Cursor * Align nullary reflection witnesses with initializer projection trees. Skeleton lexeme walks on fact.node no longer apply after DeclFact.node became projection roots; assert resolved variant identity via projection helpers instead. Co-authored-by: Cursor * Remove re-evaluable OtherTwo side-effect row from witness_support. Discovery corpus re-evaluates imported closure-loader data rows without variant imports in scope; keep ambiguous_shared_b closure load via initializer_projection import only. Co-authored-by: Cursor * Trust importing-module TypeBinding for variant resolution; remove pool ambiguity scan. Delete cross-module bare-name candidate machinery, decl_facts_marshal_bridge, and variant_to_enum sentinel; explicit A import must resolve uniquely to ambiguous_shared_a. Update witnesses and Rust controls accordingly; remove duplicate qualified-name witness and dead closure-loader row. Co-authored-by: Cursor * Fix vacuous A/B witnesses and pool-grain duplicate lookup; drop Rust CI enrollment. Add ambiguous_b_specimen as a legitimate B consumer so explicit-import controls exercise both modules in the entry closure. Replace the vacuous single-module witness with discriminating positive and negative controls. Route duplicate-QN per-candidate uniqueness through PoolDeclarationIdentity instead of a parse-pool row masquerading as exact identity. Remove the enrolled Rust source-order suite from v1-compiler-tests CI. Co-authored-by: Cursor * Rename Exact carriers to ResolvedDeclarationLocator; honest locator grain. Drop ExactDeclarationIdentity/ExactVariantIdentity aliases. Rust projection carriers are ResolvedDeclarationLocator and ResolvedVariantLocator; dag model matches. Rename duplicate-QN witness helper to pool-declaration lookup grain. Occurrence identity is not claimed anywhere on the branch. Co-authored-by: Cursor * Fix CI: re-home over-budget qualified-decl ref witness to long lane. cross_module_qualified_reference_emits_call_from_correct_module exceeded the 5000ms per-PR CPU budget (chronic on main, unrelated to decl_facts). Move it to test/claim/long/ with a lighter std.unicode.types fixture; keep the fast same-module control per-PR. Co-authored-by: Cursor * Fix node-frontier refusal: restore long witness module declaration. Changing line 1 of an existing long-lane file trips diff-before-first-declaration fail-closed in node-frontier population. Keep main's module name; only the unicode fixture and note differ from main. Co-authored-by: Cursor * Fix scoped v1 witness batch FLOOR-BATCH-OVER-BUDGET on CI. The scoped child exceeded its 360s batch-owned clamp (~376s measured at 93b1373751 locally; CI run 31223532865 exited 1 after the same wall). Raise the v1_claim_scoped_witness_batch clamp to 480s with a receipted note (375.6s observed x 1.2 fleet margin). Initialize the scoped witness receipt header in scoped floor workers so append does not fail when the file is absent. Co-authored-by: Cursor * Pin scoped batch clamp witness to 480s after clamp raise. witness_v1_claim_scoped_batch_is_file_grain_and_batch_owned still asserted the retired 360s batch-owned clamp; update to match v1_claim_scoped_witness_batch. Co-authored-by: Cursor * Bind spark-standup-program-accounting doc to gunbc doc graph roots. Fleet-blocking main red: doc_graph_has_no_orphan_docs failed because docs/plans/spark-standup-program-accounting.md landed in #7972 with no HandAuthoredDocBind row. Mirrors owned-ci-control-plane-design binding. Co-authored-by: Cursor * Revert "Bind spark-standup-program-accounting doc to gunbc doc graph roots." This reverts commit d303674fdcc70b8c402143ac0299905847371aad. * Bind spark-standup-program-accounting.md into the doc graph (main red, from #7972) #7972 landed docs/plans/spark-standup-program-accounting.md with no doc-graph binding, so doc_graph_has_no_orphan_docs reds on main and every branch merging main inherits it. My PR, my orphan. Verified rather than assumed. Subject: 0 bindings corpus-wide. Positive control: owned-ci-control-plane-design.md, added by a DIFFERENT PR in the SAME window, returns 4 — same query, same window, one bound and one not, so the zero is a real negative and not a broken grep. Discriminating control on the fix itself: the gate returns false with the row removed and true with it restored, so the row is what closes it rather than something else in the window. Every cited symbol grep-verified before authoring — fleet_subsumption_manual_gaps_plan, dgx_spark_arrival_standing, dgx_spark_router_bindings all exist. Two plausible names I first reached for did not, and are not in the row. Co-Authored-By: Claude Opus 5 (1M context) * Revert "Bind spark-standup-program-accounting.md into the doc graph (main red, from #7972)" This reverts commit d47618b3c80d8678c166388f72dc7518ccdeecba. * Update floor batch clamp witnesses for 480s scoped-batch overhead. Main's authority witness expected 360s; this branch receipted raise to 480s in gunbc.ci_layer_roots v1_claim_scoped_witness_batch. Co-authored-by: Cursor * Address review 50585: restore fail-closed call binding and cache keepalives. - Re-instate duplicate named/positional argument refusal (CallContractMismatch) - Route observed_peak_resident_bytes through cli_run::peak_rss_vhwm_bytes - Restore pointer-cache keepalives for param_name, var_sym, call_func_name - Align decl_facts_reflection witness note with landed nullary-value controls Co-authored-by: Cursor * Address review 50590: honest fn-node lookup and marshal entry gate. Rename lookup_typed_item to lookup_fn_node; marshal DataItem projections from the typechecked item node and refuse when registry knows a data item but fn_nodes lacks the subject. Add dissolve-on notes for skeleton lexeme aliases and data_initializer_identity seed-retained scaffold. Co-authored-by: Cursor * Fix record-literal coproduct resolution to honor explicit imports. Remove the module-pool first-pick in coproduct_type_item_with_variant_children and pass the import-resolved type_item directly into marshal_coproduct_record_projection, so duplicate bare coproduct names cannot override the importing module binding. Co-authored-by: Cursor * Route eval_decl_facts DataItem marshal through lookup_fn_node seam. eval_decl_facts now delegates DataItem node marshaling to marshal_data_initializer_projection so enrolled .dag witnesses exercise the same typechecked path as Rust seam tests. Update gap notes to match. Co-authored-by: Cursor * Fix review 50602: witness import and wire retained Rust test module. Import decl_facts_reflection_nullary_value_projection in the initializer projection witness module and register decl_facts_dimensionless_projection_test in v1-compiler-tests lib.rs so the retirement row matches executing coverage. Co-authored-by: Cursor * Restore symbol_index_fill_overlay_direction_test module enrollment. Re-add the lib.rs mod line dropped during decl_facts test wiring so the fill-overlay direction regression control cited in 04_infer remains executed. Co-authored-by: Cursor --------- Co-authored-by: Brian Searls Co-authored-by: Cursor Co-authored-by: gunbc-ci-auto-heal Co-authored-by: Claude Opus 5 (1M context) --- dag/gunbc/ci_layer_roots.dag | 6 +- dag/gunbc/stage0_crate_layout_generated.dag | 4 +- ...ts_initializer_projection_witness_test.dag | 275 ++++ .../decl_facts_reflection_witness_support.dag | 290 ++++ .../decl_facts_reflection_witness_test.dag | 148 +++ ...oor_batch_clamp_authority_witness_test.dag | 4 +- ...ference_emit_cross_module_witness_test.dag | 6 +- ...eclaration_reference_emit_witness_test.dag | 2 +- .../ambiguous_b_specimen.dag | 5 + .../ambiguous_shared_a.dag | 5 + .../ambiguous_shared_b.dag | 5 + .../ambiguous_specimen.dag | 5 + .../globally_unique_carrier.dag | 5 + .../decl_facts_reflection/specimens.dag | 112 ++ .../unimported_globally_unique_user.dag | 3 + .../decl_facts_reflection/witness_support.dag | 37 + .../primary/shadow_module.dag | 3 + ...acts_dimensionless_projection_retained.dag | 8 + src/v1/stage0/src/bin/claim_executor.rs | 4 +- ...bootstrap_stage0_crate_layout_generated.rs | 1 + src/v1/stage0/src/coproduct_reflection.rs | 210 ++- .../stage0/src/data_initializer_identity.rs | 1176 +++++++++++++++++ .../gunbc_stage0_crate_layout_generated.rs | 6 +- src/v1/stage0/src/lib.rs | 1 + .../src/module_path_index/parsed_dag_file.rs | 2 + src/v1/stage0/src/v1_interpreter.rs | 37 +- ...ecl_facts_dimensionless_projection_test.rs | 263 ++++ src/v1/tests/src/lib.rs | 4 +- src/v2/compiler/self_host/frontier.dag | 3 + src/v2/std/data_initializer_identity.dag | 318 +++++ src/v2/std/decl_facts_skeleton.dag | 145 ++ .../test/claim/ci_floor_plan_witness_test.dag | 2 +- .../shadow/shadow_module.dag | 3 + 33 files changed, 3033 insertions(+), 65 deletions(-) create mode 100644 dag/test/claim/decl_facts_initializer_projection_witness_test.dag create mode 100644 dag/test/claim/decl_facts_reflection_witness_support.dag create mode 100644 dag/test/claim/decl_facts_reflection_witness_test.dag create mode 100644 dag/test/fixture/decl_facts_reflection/ambiguous_b_specimen.dag create mode 100644 dag/test/fixture/decl_facts_reflection/ambiguous_shared_a.dag create mode 100644 dag/test/fixture/decl_facts_reflection/ambiguous_shared_b.dag create mode 100644 dag/test/fixture/decl_facts_reflection/ambiguous_specimen.dag create mode 100644 dag/test/fixture/decl_facts_reflection/globally_unique_carrier.dag create mode 100644 dag/test/fixture/decl_facts_reflection/specimens.dag create mode 100644 dag/test/fixture/decl_facts_reflection/unimported_globally_unique_user.dag create mode 100644 dag/test/fixture/decl_facts_reflection/witness_support.dag create mode 100644 dag/test/fixture/decl_facts_reflection_duplicate_qn/primary/shadow_module.dag create mode 100644 dag/test/retirement/decl_facts_dimensionless_projection_retained.dag create mode 100644 src/v1/stage0/src/data_initializer_identity.rs create mode 100644 src/v1/tests/src/decl_facts_dimensionless_projection_test.rs create mode 100644 src/v2/std/data_initializer_identity.dag create mode 100644 src/v2/std/decl_facts_skeleton.dag create mode 100644 test/fixture_roots/decl_facts_reflection_duplicate_qn/shadow/shadow_module.dag diff --git a/dag/gunbc/ci_layer_roots.dag b/dag/gunbc/ci_layer_roots.dag index 50d2c64af4f..243dabde766 100644 --- a/dag/gunbc/ci_layer_roots.dag +++ b/dag/gunbc/ci_layer_roots.dag @@ -1005,7 +1005,7 @@ data v1_claim_scoped_witness_entries: List = [ }, ] -data v1_claim_scoped_witness_batch_note: String = "This is one INSTANCE of std.realization_schedule ScopedWitnessBatch, not a parser branch in the floor mechanism. The four file-grain rows expand through ScheduleWitnessEntry's empty-function authority, so adding or renaming a test fn changes no roster. Their dag plus src/v1 source-root envelope admits the v1 compiler closure without widening witness_layer_roots for unrelated witnesses. SelectionApplied is deliberate: normal affected-set selection avoids paying the measured closure materialization on unrelated PRs; every unselected roster row still emits an exact-head ScopedWitnessSelectionSkipped receipt with provenance, while a provenance failure refuses loudly and never widens. The executor's own module universe is a distinct fact carried by ScopedWitnessBatch.execution_authority as InheritedWalkSourceRoots: the worker resolves affected_set_floor_runner, witness_entry_eligibility_census, and post-discovery cost/attribution projectors from the enclosing walk's roots while the witness subject index remains exactly dag plus src/v1. Adding src/v2 to this batch's witness roots was considered and refused because it widens the subject envelope, moves the receipt digest, and invalidates the measured clamp basis; executor-authority dependencies never enter the witness roots as an absorbing fallback. The profile declares a substantial whole-tree resolve, Hermetic effects, a batch-owned 360-second clamp, and SequentialChildProcess isolation. The 360-second clamp is the smallest whole-minute value covering the measured 199.393-second optimized arm64 wall after the full executor-authority projection path was made reachable, at gunbc.ci_spec gunbc_ci_compile_clean_clamp_note's documented 1.8x fleet spread: 199.393 x 1.8 = 358.9074 seconds, rounded upward to the family grain. The earlier 180-second basis measured only 96 seconds before the cost projector and renderers were reachable from the narrow scoped envelope, so it no longer described the executing batch. COST DISPOSITION: the approximately 103-second increase correlated with making the cost projector, histogram, and slowest-attribution renderer reachable while witness row evaluations themselves remain negligible per-row; that reporting/closure cost has not been decomposed in this scoped-scheduling change and is tracked by the real ROADMAP module-grain-materialization lane. Its trigger is a per-projector resolve/materialization attribution showing whether the three projections recompute identical semantic facts, followed by cached-vs-cold receipts before any optimization claim. The larger clamp must not normalize that open cost question into proportionality. per_unit_ms remains zero because row evaluation is negligible; this catches closure growth and deliberately does not price row-count growth until a file brings a distinct closure. Exact-head run 30713632253 proves SharedWalkProcess cannot satisfy this instance: ordinary discovery completed at 7.113GB RSS, then the floor vanished while materializing this second root closure before a batch-start line. The kernel cause is unclassified, but additive same-process residency is established. SequentialChildProcess makes the ordinary worker exit before this worker starts, releasing the first live set without claiming the stronger fresh-cgroup semantics of FreshJobProcess. v1_claim_scoped_witness_dissolve_on fires when the v2 parser owns these claims." +data v1_claim_scoped_witness_batch_note: String = "This is one INSTANCE of std.realization_schedule ScopedWitnessBatch, not a parser branch in the floor mechanism. The four file-grain rows expand through ScheduleWitnessEntry's empty-function authority, so adding or renaming a test fn changes no roster. Their dag plus src/v1 source-root envelope admits the v1 compiler closure without widening witness_layer_roots for unrelated witnesses. SelectionApplied is deliberate: normal affected-set selection avoids paying the measured closure materialization on unrelated PRs; every unselected roster row still emits an exact-head ScopedWitnessSelectionSkipped receipt with provenance, while a provenance failure refuses loudly and never widens. The executor's own module universe is a distinct fact carried by ScopedWitnessBatch.execution_authority as InheritedWalkSourceRoots: the worker resolves affected_set_floor_runner, witness_entry_eligibility_census, and post-discovery cost/attribution projectors from the enclosing walk's roots while the witness subject index remains exactly dag plus src/v1. Adding src/v2 to this batch's witness roots was considered and refused because it widens the subject envelope, moves the receipt digest, and invalidates the measured clamp basis; executor-authority dependencies never enter the witness roots as an absorbing fallback. The profile declares a substantial whole-tree resolve, Hermetic effects, a batch-owned 480-second clamp, and SequentialChildProcess isolation. RECEIPTED RAISE (2026-08-08, PR #7924 CI run 31223532865 @ 93b1373751): scoped child FLOOR-BATCH-OVER-BUDGET wall_ms=375553 clamp_ms=360000 units=27 — reproduced locally on the same commit after merge with main. Prior 360s basis (199.393s arm64 x 1.8 = 358.9s) is stale: post-merge scoped-worker stage wall is ~376s on x86_local with the full executor-authority projector path. Sizing follows gunbc.ci_spec gunbc_ci_floor_batch_clamp_note fleet-margin policy (measured worst wall x 1.2, not host roulette): 375.553s x 1.2 = 451s, rounded up to 480s whole-minute grain (+33% over the retired 360s row). THE ENROLLMENT THAT GREW THE BATCH: none — wall is closure-materialization dominated, not a roster edit. The earlier 180-second basis measured only 96 seconds before the cost projector and renderers were reachable from the narrow scoped envelope, so it no longer described the executing batch. COST DISPOSITION: the approximately 103-second increase correlated with making the cost projector, histogram, and slowest-attribution renderer reachable while witness row evaluations themselves remain negligible per-row; that reporting/closure cost has not been decomposed in this scoped-scheduling change and is tracked by the real ROADMAP module-grain-materialization lane. Its trigger is a per-projector resolve/materialization attribution showing whether the three projections recompute identical semantic facts, followed by cached-vs-cold receipts before any optimization claim. The larger clamp must not normalize that open cost question into proportionality. per_unit_ms remains zero because row evaluation is negligible; this catches closure growth and deliberately does not price row-count growth until a file brings a distinct closure. Exact-head run 30713632253 proves SharedWalkProcess cannot satisfy this instance: ordinary discovery completed at 7.113GB RSS, then the floor vanished while materializing this second root closure before a batch-start line. The kernel cause is unclassified, but additive same-process residency is established. SequentialChildProcess makes the ordinary worker exit before this worker starts, releasing the first live set without claiming the stronger fresh-cgroup semantics of FreshJobProcess. v1_claim_scoped_witness_dissolve_on fires when the v2 parser owns these claims." data v1_claim_scoped_witness_batch: ScopedWitnessBatch = scoped_witness_batch( batch_id: "v1-claim-scoped-witnesses" as ScopedWitnessBatchId, @@ -1021,7 +1021,7 @@ data v1_claim_scoped_witness_batch: ScopedWitnessBatch = scoped_witness_batch( execution_mode: Hermetic, ), clamp: RunnableBatchClamp { - overhead: second(count: 360), + overhead: second(count: 480), per_unit: millisecond(count: 0), authority: decl_ref( module_path: "gunbc.ci_layer_roots", @@ -1492,7 +1492,7 @@ data falsifier_substrate_long_lane_rows: List = [ SubstrateLongLaneRow { entry: "dag/test/claim/long/qualified_declaration_reference_emit_cross_module_witness_test.dag", function: "cross_module_qualified_reference_emits_call_from_correct_module", - reason: "compile_dag_rust_emit_check cross-module fixture importing std.emit_on_demand.interpreter_surviving_roles_ceiling: measured ~4125ms thread-CPU warm locally; PR CI run 31198854678 discovery batch 3 killed at 5544ms > 5000ms fast-lane budget. Same-module sibling stays per-PR in dag/test/claim/qualified_declaration_reference_emit_witness_test.dag.", + reason: "compile_dag_rust_emit_check cross-module fixture (std.unicode.types.unicode_scalar_max_code_point — minimal std closure, not the heavy std.emit_on_demand roster the prior fixture dragged in): measured 5436-5544ms thread-CPU vs 5000ms fast-lane cap on srv_fleet_arm64 PR CI discovery batch 3. Same-module sibling stays per-PR in dag/test/claim/qualified_declaration_reference_emit_witness_test.dag.", dissolve_on: "eval lands under gunbc_ci_fast_lane_witness_eval_budget on a measured cold run and re-enrolls per-PR discovery; row deletes with the long-lane file" }, SubstrateLongLaneRow { diff --git a/dag/gunbc/stage0_crate_layout_generated.dag b/dag/gunbc/stage0_crate_layout_generated.dag index 73070bd5c56..4583dcda01e 100644 --- a/dag/gunbc/stage0_crate_layout_generated.dag +++ b/dag/gunbc/stage0_crate_layout_generated.dag @@ -4,13 +4,14 @@ import std.types { List, String } data stage0_crate_layout_generated_note: String = "Generated by gunbc stage0_crate_layout_emit — do not hand-edit. Authority: v2.compiler.self_host.frontier; regen via dag/tools/generated_artifact_gate.dag main_wet." -data generated_pub_mod_block: String = "\n#[macro_use]\npub mod v1_interpreter_dispatch_generated;\npub mod v1_interpreter;\npub mod ci_control_plane;\npub mod cli_run;\npub mod coproduct_reflection;\npub mod resolved_graph_cache;\npub mod shared_typecheck_store;\npub mod recorded_fixture;\npub mod memory_governor;\npub mod module_path_index;\npub mod usv_pilot_v2_std_algebra;\npub mod usv_pilot_v2_std_collection;\npub mod usv_pilot_v2_std_node;\npub mod v2_compiler_resolve;\npub mod v2_compiler_program_partition;\npub mod v2_compiler_tokenize;\npub mod v2_compiler_infer;" +data generated_pub_mod_block: String = "\n#[macro_use]\npub mod v1_interpreter_dispatch_generated;\npub mod v1_interpreter;\npub mod ci_control_plane;\npub mod cli_run;\npub mod coproduct_reflection;\npub mod data_initializer_identity;\npub mod resolved_graph_cache;\npub mod shared_typecheck_store;\npub mod recorded_fixture;\npub mod memory_governor;\npub mod module_path_index;\npub mod usv_pilot_v2_std_algebra;\npub mod usv_pilot_v2_std_collection;\npub mod usv_pilot_v2_std_node;\npub mod v2_compiler_resolve;\npub mod v2_compiler_program_partition;\npub mod v2_compiler_tokenize;\npub mod v2_compiler_infer;" data generated_pub_mod_basenames: List = [ "v1_interpreter", "ci_control_plane", "cli_run", "coproduct_reflection", + "data_initializer_identity", "resolved_graph_cache", "shared_typecheck_store", "recorded_fixture", @@ -31,6 +32,7 @@ data generated_stage0_filenames: List = [ "ci_control_plane.rs", "cli_run.rs", "coproduct_reflection.rs", + "data_initializer_identity.rs", "resolved_graph_cache.rs", "shared_typecheck_store.rs", "recorded_fixture.rs", diff --git a/dag/test/claim/decl_facts_initializer_projection_witness_test.dag b/dag/test/claim/decl_facts_initializer_projection_witness_test.dag new file mode 100644 index 00000000000..928c9e5691e --- /dev/null +++ b/dag/test/claim/decl_facts_initializer_projection_witness_test.dag @@ -0,0 +1,275 @@ +module test.claim.decl_facts_initializer_projection_witness_test + +import test.fixture.decl_facts_reflection.ambiguous_shared_a +import test.fixture.decl_facts_reflection.ambiguous_specimen +import test.fixture.decl_facts_reflection.ambiguous_b_specimen +import test.claim.decl_facts_reflection_witness_support { + decl_facts_reflection_constructor_parent, + decl_facts_reflection_constructor_variant, + decl_facts_reflection_field_projection, + decl_facts_reflection_fact_node, + decl_facts_reflection_lookup, + duplicate_qualified_identity_lookup_names_primary_and_shadow, + duplicate_qn_pool_declaration_lookup_per_candidate_is_unique, + decl_facts_reflection_value_identity_projection, + decl_facts_reflection_projection_variant_label_optional, + decl_facts_reflection_resolved_nullary_parent, + decl_facts_reflection_resolved_nullary_variant_name, + decl_facts_reflection_nullary_value_projection, + decl_facts_reflection_plain_record_parent, +} +import v2.std.data_initializer_identity { + data_initializer_is_constructor_resolution_refused_projection, + data_initializer_is_typechecked_subject_absent_projection, + record_field_projection_optional, + resolved_variant_name_optional, + resolved_variant_parent_qualified_name_optional, + value_projection_is_missing, + value_projection_is_not_variant_value, + value_projection_is_resolved_variant, + decl_fact_lookup_is_missing, +} +import test.claim.decl_facts_reflection_witness_support { + decl_facts_reflection_fact_node, +} + +data disposition_scaffold_qn: String = "test.fixture.decl_facts_reflection.specimens.disposition_scaffold" +data local_a_scaffold_qn: String = "test.fixture.decl_facts_reflection.specimens.local_a_scaffold" +data local_b_scaffold_qn: String = "test.fixture.decl_facts_reflection.specimens.local_b_scaffold" +data local_a_shared_nullary_qn: String = "test.fixture.decl_facts_reflection.specimens.local_a_shared_nullary" +data local_b_shared_nullary_qn: String = "test.fixture.decl_facts_reflection.specimens.local_b_shared_nullary" +data terminal_specimen_qn: String = "test.fixture.decl_facts_reflection.specimens.terminal_specimen" +data mechanism_single_authority_qn: String = "test.fixture.decl_facts_reflection.specimens.mechanism_single_authority" +data mechanism_realization_dispatch_qn: String = "test.fixture.decl_facts_reflection.specimens.mechanism_realization_dispatch" +data missing_variant_specimen_qn: String = "test.fixture.decl_facts_reflection.specimens.missing_variant_specimen" +data ambiguous_arm_specimen_qn: String = "test.fixture.decl_facts_reflection.ambiguous_specimen.ambiguous_arm_specimen" +data ambiguous_b_arm_specimen_qn: String = "test.fixture.decl_facts_reflection.ambiguous_b_specimen.ambiguous_b_arm_specimen" +data duplicate_shadowed_specimen_qn: String = "test.fixture.decl_facts_reflection_duplicate_qn.shadowed_module.shadowed_specimen" +data unimported_globally_unique_specimen_qn: String = "test.fixture.decl_facts_reflection.unimported_globally_unique_user.unimported_globally_unique_specimen" +data scaffold_named_field_bind_qn: String = "test.fixture.decl_facts_reflection.specimens.scaffold_named_field_bind" +data whole_declaration_bind_qn: String = "test.fixture.decl_facts_reflection.specimens.disposition_scaffold" +data scaffold_with_data_ref_qn: String = "test.fixture.decl_facts_reflection.specimens.scaffold_with_data_ref" +data dimensionless_anchor_qn: String = "extdeps.units.dimensionless.extdeps_external_authority_anchor" + +test fn decl_facts_reflection_data_ref_with_coproduct_type_is_not_variant_value() -> Bool { + match decl_facts_reflection_field_projection(qn: scaffold_with_data_ref_qn, field: "dissolves_to") { + Present { value: projection } => value_projection_is_not_variant_value(node: projection) + Absent => false + } +} + +test fn decl_facts_reflection_imported_external_authority_positive_control() -> Bool { + match decl_facts_reflection_plain_record_parent(qn: dimensionless_anchor_qn) { + Present { value: parent } => parent == "extdeps.external_authority.ExternalAuthority" + Absent => false + } +} + +test fn decl_facts_reflection_two_unrelated_scaffold_arms_do_not_alias() -> Bool { + let parent_a = decl_facts_reflection_constructor_parent(qn: local_a_scaffold_qn) + let parent_b = decl_facts_reflection_constructor_parent(qn: local_b_scaffold_qn) + let variant_a = decl_facts_reflection_constructor_variant(qn: local_a_scaffold_qn) + let variant_b = decl_facts_reflection_constructor_variant(qn: local_b_scaffold_qn) + match parent_a { + Present { value: pa } => + match parent_b { + Present { value: pb } => + match variant_a { + Present { value: va } => + match variant_b { + Present { value: vb } => va == "TaggedA" && vb == "TaggedB" && pa != pb + Absent => false + } + Absent => false + } + Absent => false + } + Absent => false + } +} + +test fn decl_facts_reflection_two_unrelated_shared_nullary_arms_do_not_alias() -> Bool { + match decl_facts_reflection_resolved_nullary_parent(qn: local_a_shared_nullary_qn) { + Present { value: pa } => + match decl_facts_reflection_resolved_nullary_parent(qn: local_b_shared_nullary_qn) { + Present { value: pb } => + match decl_facts_reflection_resolved_nullary_variant_name(qn: local_a_shared_nullary_qn) { + Present { value: va } => + match decl_facts_reflection_resolved_nullary_variant_name(qn: local_b_shared_nullary_qn) { + Present { value: vb } => + va == "SharedNullaryA" && vb == "SharedNullaryB" && pa != pb + Absent => false + } + Absent => false + } + Absent => false + } + Absent => false + } +} + +test fn decl_facts_reflection_disposition_scaffold_constructor_is_std_disposition() -> Bool { + match decl_facts_reflection_constructor_parent(qn: disposition_scaffold_qn) { + Present { value: parent } => + parent == "std.disposition.Disposition" + && decl_facts_reflection_constructor_variant(qn: disposition_scaffold_qn) == Present { value: "Scaffold" } + Absent => false + } +} + +test fn decl_facts_reflection_terminal_vs_scaffold_constructors_distinct() -> Bool { + match decl_facts_reflection_constructor_variant(qn: terminal_specimen_qn) { + Present { value: terminal_ctor } => + terminal_ctor == "Terminal" + && decl_facts_reflection_constructor_variant(qn: disposition_scaffold_qn) == Present { value: "Scaffold" } + Absent => false + } +} + +test fn decl_facts_reflection_single_authority_vs_realization_dispatch_distinct() -> Bool { + match decl_facts_reflection_resolved_nullary_parent(qn: mechanism_single_authority_qn) { + Present { value: sp } => + match decl_facts_reflection_resolved_nullary_variant_name(qn: mechanism_single_authority_qn) { + Present { value: sv } => + match decl_facts_reflection_resolved_nullary_parent(qn: mechanism_realization_dispatch_qn) { + Present { value: rp } => + match decl_facts_reflection_resolved_nullary_variant_name(qn: mechanism_realization_dispatch_qn) { + Present { value: rv } => + sp == "std.disposition.ConstructionMechanism" + && rp == "std.disposition.ConstructionMechanism" + && sv == "SingleAuthority" + && rv == "RealizationDispatch" + && sv != rv + Absent => false + } + Absent => false + } + Absent => false + } + Absent => false + } +} + +test fn decl_facts_reflection_whole_declaration_vs_named_field_field_grain() -> Bool { + match decl_facts_reflection_field_projection(qn: whole_declaration_bind_qn, field: "bind") { + Present { value: whole_bind } => + match record_field_projection_optional(record: whole_bind, label: "field") { + Present { value: whole_field } => + match decl_facts_reflection_field_projection(qn: scaffold_named_field_bind_qn, field: "bind") { + Present { value: named_bind } => + match record_field_projection_optional(record: named_bind, label: "field") { + Present { value: named_field } => + match decl_facts_reflection_projection_variant_label_optional(node: whole_field) { + Present { value: whole } => + match decl_facts_reflection_projection_variant_label_optional(node: named_field) { + Present { value: named } => whole == "WholeDeclaration" && named == "NamedField" + Absent => false + } + Absent => false + } + Absent => false + } + Absent => false + } + Absent => false + } + Absent => false + } +} + +test fn decl_facts_reflection_missing_variant_refuses_missing_projection() -> Bool { + match decl_facts_reflection_field_projection(qn: missing_variant_specimen_qn, field: "dissolves_to") { + Present { value: projection } => value_projection_is_missing(node: projection) + Absent => false + } +} + +data ambiguous_shared_a_shared_bare_arm_qn: String = "test.fixture.decl_facts_reflection.ambiguous_shared_a.SharedBareArm" +data ambiguous_shared_b_shared_bare_arm_qn: String = "test.fixture.decl_facts_reflection.ambiguous_shared_b.SharedBareArm" + +// B is in the entry closure because ambiguous_b_specimen imports SharedBareArm from B. +test fn decl_facts_reflection_closure_b_specimen_resolves_to_b_parent() -> Bool { + match decl_facts_reflection_resolved_nullary_parent(qn: ambiguous_b_arm_specimen_qn) { + Present { value: parent } => + parent == ambiguous_shared_b_shared_bare_arm_qn + && match decl_facts_reflection_resolved_nullary_variant_name(qn: ambiguous_b_arm_specimen_qn) { + Present { value: variant } => variant == "SharedArm" + Absent => false + } + Absent => false + } +} + +test fn decl_facts_reflection_explicit_import_resolves_to_a_when_b_also_loaded() -> Bool { + match decl_facts_reflection_resolved_nullary_parent(qn: ambiguous_arm_specimen_qn) { + Present { value: parent } => + parent == ambiguous_shared_a_shared_bare_arm_qn + && parent != ambiguous_shared_b_shared_bare_arm_qn + && match decl_facts_reflection_resolved_nullary_variant_name(qn: ambiguous_arm_specimen_qn) { + Present { value: variant } => variant == "SharedArm" + Absent => false + } + Absent => false + } +} + +// Discriminating negative: A importer must not resolve to B's parent when both modules are loaded. +test fn decl_facts_reflection_explicit_import_does_not_resolve_to_b() -> Bool { + match decl_facts_reflection_resolved_nullary_parent(qn: ambiguous_arm_specimen_qn) { + Present { value: parent } => parent != ambiguous_shared_b_shared_bare_arm_qn + Absent => false + } +} + +test fn decl_facts_reflection_nullary_mechanism_projection_shape() -> Bool { + match decl_facts_reflection_nullary_value_projection(qn: mechanism_single_authority_qn) { + Present { value: projection } => value_projection_is_resolved_variant(node: projection) + Absent => false + } +} + +test fn decl_fact_lookup_duplicate_qualified_identity_returns_ambiguous_both_candidates() -> Bool { + duplicate_qualified_identity_lookup_names_primary_and_shadow( + lookup: decl_facts_reflection_lookup(qn: duplicate_shadowed_specimen_qn) + ) +} + +test fn decl_facts_reflection_unimported_globally_unique_refuses_constructor_resolution() -> Bool { + match decl_facts_reflection_fact_node(qn: unimported_globally_unique_specimen_qn) { + Present { value: node } => data_initializer_is_constructor_resolution_refused_projection(node: node) + Absent => false + } +} + +test fn decl_facts_reflection_lookup_missing_vs_present_fact_distinct() -> Bool { + let bogus_qn: String = "test.fixture.decl_facts_reflection.specimens.nonexistent_data_row" + decl_fact_lookup_is_missing(lookup: decl_facts_reflection_lookup(qn: bogus_qn)) + && match decl_facts_reflection_fact_node(qn: missing_variant_specimen_qn) { + Present { value: node } => + !data_initializer_is_typechecked_subject_absent_projection(node: node) + Absent => false + } +} + +test fn decl_facts_reflection_absent_vs_refused_vs_missing_projections_distinct() -> Bool { + match decl_facts_reflection_fact_node(qn: unimported_globally_unique_specimen_qn) { + Present { value: refused_node } => + if data_initializer_is_constructor_resolution_refused_projection(node: refused_node) { + match decl_facts_reflection_field_projection(qn: missing_variant_specimen_qn, field: "dissolves_to") { + Present { value: missing_projection } => + value_projection_is_missing(node: missing_projection) + && !data_initializer_is_typechecked_subject_absent_projection(node: refused_node) + Absent => false + } + } else { + false + } + Absent => false + } +} + +test fn decl_facts_duplicate_qn_pool_declaration_lookup_per_candidate_is_unique() -> Bool { + duplicate_qn_pool_declaration_lookup_per_candidate_is_unique( + lookup: decl_facts_reflection_lookup(qn: duplicate_shadowed_specimen_qn) + ) +} diff --git a/dag/test/claim/decl_facts_reflection_witness_support.dag b/dag/test/claim/decl_facts_reflection_witness_support.dag new file mode 100644 index 00000000000..e55930a99f1 --- /dev/null +++ b/dag/test/claim/decl_facts_reflection_witness_support.dag @@ -0,0 +1,290 @@ +module test.claim.decl_facts_reflection_witness_support + +import test.fixture.decl_facts_reflection.ambiguous_shared_a +import test.fixture.decl_facts_reflection.ambiguous_specimen +import test.fixture.decl_facts_reflection.globally_unique_carrier +import test.fixture.decl_facts_reflection.specimens +import test.fixture.decl_facts_reflection.unimported_globally_unique_user +import test.fixture.decl_facts_reflection.witness_support { + decl_facts_reflection_fixture_pool_roots, +} +import v2.std.decl_index { DeclFact, decl_facts } +import std.algebra { Empty } +import v2.std.optional { optional_present, optional_absent } +import v2.std.data_initializer_identity { + projection_kind_lexeme_optional, + projection_named_child_optional, + record_constructor_parent_qualified_name_optional, + record_constructor_variant_name_optional, + record_field_projection_optional, + resolved_variant_name_optional, + resolved_variant_parent_qualified_name_optional, + value_projection_is_ambiguous, + ambiguous_candidate_projection_count, + projection_named_child_atom_lexeme, + value_projection_is_resolved_variant, + variant_declaration_name_optional, + DeclFactLookup, + decl_fact_lookup_by_pool_declaration_identity, + pool_declaration_identity_from_fact, + decl_fact_lookup_by_qualified_name, + decl_fact_lookup_is_ambiguous, + decl_fact_lookup_candidate_count, + decl_fact_lookup_is_unique, + decl_fact_optional_by_qualified_name, + data_initializer_is_nullary_projection, + data_initializer_is_record_projection, + data_initializer_is_plain_record_projection, + plain_record_parent_qualified_name_optional, +} + +fn decl_facts_reflection_fixture_facts() -> List { + decl_facts(pool_roots: decl_facts_reflection_fixture_pool_roots) +} + +fn decl_facts_reflection_lookup(qn: String) -> DeclFactLookup { + decl_fact_lookup_by_qualified_name( + qn: qn, + facts: decl_facts_reflection_fixture_facts() + ) +} + +fn decl_fact_lookup_candidate_rel_path_contains(lookup: DeclFactLookup, fragment: String) -> Bool { + match lookup { + DeclFactAmbiguous { candidates: cands } => + fold(cands, init: false, f: fn(found, fact) { + found || fact.rel_path.contains(fragment) + }) + DeclFactMissing => false + DeclFactUnique { fact: _ } => false + } +} + +fn duplicate_qualified_identity_lookup_names_primary_and_shadow(lookup: DeclFactLookup) -> Bool { + decl_fact_lookup_is_ambiguous(lookup: lookup) + && decl_fact_lookup_candidate_count(lookup: lookup) == 2 + && decl_fact_lookup_candidate_rel_path_contains( + lookup: lookup, + fragment: "decl_facts_reflection_duplicate_qn/primary" + ) + && decl_fact_lookup_candidate_rel_path_contains( + lookup: lookup, + fragment: "fixture_roots/decl_facts_reflection_duplicate_qn/shadow" + ) +} + +fn decl_fact_projection_node_for_qualified_name( + facts: List, + qn: String +) -> Optional { + match decl_fact_optional_by_qualified_name(qn: qn, facts: facts) { + Present { value: fact } => optional_present(value: fact.node) + Absent => optional_absent() + } +} + +fn decl_facts_reflection_fact_node(qn: String) -> Optional { + decl_fact_projection_node_for_qualified_name( + facts: decl_facts_reflection_fixture_facts(), + qn: qn + ) +} + +fn decl_facts_reflection_initializer_root(qn: String) -> Optional { + match decl_facts_reflection_fact_node(qn: qn) { + Present { value: node } => + if data_initializer_is_record_projection(node: node) { + optional_present(value: node) + } else if data_initializer_is_plain_record_projection(node: node) { + optional_present(value: node) + } else { + optional_absent() + } + Absent => optional_absent() + } +} + +fn decl_facts_reflection_record_projection(qn: String) -> Optional { + match decl_facts_reflection_initializer_root(qn: qn) { + Present { value: node } => + if data_initializer_is_record_projection(node: node) { + optional_present(value: node) + } else { + optional_absent() + } + Absent => optional_absent() + } +} + +fn decl_facts_reflection_constructor_parent(qn: String) -> Optional { + match decl_facts_reflection_record_projection(qn: qn) { + Present { value: record } => record_constructor_parent_qualified_name_optional(record: record) + Absent => optional_absent() + } +} + +fn decl_facts_reflection_constructor_variant(qn: String) -> Optional { + match decl_facts_reflection_record_projection(qn: qn) { + Present { value: record } => record_constructor_variant_name_optional(record: record) + Absent => optional_absent() + } +} + +fn decl_facts_reflection_field_projection(qn: String, field: String) -> Optional { + match decl_facts_reflection_initializer_root(qn: qn) { + Present { value: root } => projection_named_child_optional(node: root, label: field) + Absent => optional_absent() + } +} + +fn decl_facts_reflection_value_identity_projection(qn: String) -> Optional { + match decl_facts_reflection_fact_node(qn: qn) { + Present { value: node } => projection_named_child_optional(node: node, label: "value_identity") + Absent => optional_absent() + } +} + +fn decl_facts_reflection_nullary_value_projection(qn: String) -> Optional { + match decl_facts_reflection_value_identity_projection(qn: qn) { + Present { value: projection } => optional_present(value: projection) + Absent => + match decl_facts_reflection_fact_node(qn: qn) { + Present { value: node } => + if data_initializer_is_nullary_projection(node: node) { + projection_named_child_optional(node: node, label: "value_identity") + } else { + optional_absent() + } + Absent => optional_absent() + } + } +} + +fn decl_facts_reflection_resolved_nullary_parent(qn: String) -> Optional { + match decl_facts_reflection_nullary_value_projection(qn: qn) { + Present { value: projection } => + if value_projection_is_resolved_variant(node: projection) { + resolved_variant_parent_qualified_name_optional(node: projection) + } else { + optional_absent() + } + Absent => optional_absent() + } +} + +fn decl_facts_reflection_resolved_nullary_variant_name(qn: String) -> Optional { + match decl_facts_reflection_nullary_value_projection(qn: qn) { + Present { value: projection } => + if value_projection_is_resolved_variant(node: projection) { + resolved_variant_name_optional(node: projection) + } else { + optional_absent() + } + Absent => optional_absent() + } +} + +fn decl_facts_reflection_projection_variant_label_optional(node: Node) -> Optional { + if value_projection_is_resolved_variant(node: node) { + resolved_variant_name_optional(node: node) + } else if data_initializer_is_record_projection(node: node) { + record_constructor_variant_name_optional(record: node) + } else if data_initializer_is_plain_record_projection(node: node) { + optional_absent() + } else { + optional_absent() + } +} + +fn decl_facts_reflection_decl_field_variant_name(qn: String) -> Optional { + match decl_facts_reflection_field_projection(qn: qn, field: "bind") { + Present { value: bind_projection } => + match record_field_projection_optional(record: bind_projection, label: "field") { + Present { value: field_projection } => + decl_facts_reflection_projection_variant_label_optional(node: field_projection) + Absent => optional_absent() + } + Absent => optional_absent() + } +} + +fn decl_facts_reflection_plain_record_parent(qn: String) -> Optional { + match decl_facts_reflection_fact_node(qn: qn) { + Present { value: node } => + if data_initializer_is_plain_record_projection(node: node) { + plain_record_parent_qualified_name_optional(record: node) + } else { + optional_absent() + } + Absent => optional_absent() + } +} + +fn decl_facts_reflection_plain_record_parent_eq(record: Node, want: String) -> Bool { + match plain_record_parent_qualified_name_optional(record: record) { + Present { value: parent } => parent == want + Absent => false + } +} + +fn projection_has_named_child(record: Node, label: String) -> Bool { + match projection_named_child_optional(node: record, label: label) { + Present { value: _ } => true + Absent => false + } +} + +fn decl_facts_reflection_bind_is_declaration_ref_projection(qn: String) -> Bool { + match decl_facts_reflection_field_projection(qn: qn, field: "bind") { + Present { value: bind } => + data_initializer_is_plain_record_projection(node: bind) + && decl_facts_reflection_plain_record_parent_eq( + record: bind, + want: "std.decl_ref.DeclarationRef" + ) + && projection_has_named_child(record: bind, label: "module_path") + && projection_has_named_child(record: bind, label: "decl_name") + Absent => false + } +} + +fn decl_facts_reflection_decl_field_variant_eq(qn: String, want: String) -> Bool { + match decl_facts_reflection_field_projection(qn: qn, field: "field") { + Present { value: field } => + match decl_facts_reflection_projection_variant_label_optional(node: field) { + Present { value: v } => v == want + Absent => false + } + Absent => false + } +} + +fn decl_facts_reflection_named_field_projection_surfaces(qn: String) -> Bool { + match decl_facts_reflection_fact_node(qn: qn) { + Present { value: node } => + data_initializer_is_plain_record_projection(node: node) + && decl_facts_reflection_plain_record_parent_eq( + record: node, + want: "std.decl_ref.DeclarationRef" + ) + && decl_facts_reflection_decl_field_variant_eq(qn: qn, want: "NamedField") + Absent => false + } +} + +fn duplicate_qn_pool_declaration_lookup_per_candidate_is_unique(lookup: DeclFactLookup) -> Bool { + match lookup { + DeclFactAmbiguous { candidates: cands } => + fold(cands, init: true, f: fn(ok, fact) { + ok + && decl_fact_lookup_is_unique( + lookup: decl_fact_lookup_by_pool_declaration_identity( + identity: pool_declaration_identity_from_fact(fact: fact), + facts: decl_facts_reflection_fixture_facts() + ) + ) + }) + DeclFactMissing => false + DeclFactUnique { fact: _ } => false + } +} diff --git a/dag/test/claim/decl_facts_reflection_witness_test.dag b/dag/test/claim/decl_facts_reflection_witness_test.dag new file mode 100644 index 00000000000..9de01942850 --- /dev/null +++ b/dag/test/claim/decl_facts_reflection_witness_test.dag @@ -0,0 +1,148 @@ +module test.claim.decl_facts_reflection_witness_test + +import v2.std.decl_facts_skeleton { + decl_facts_nullary_variant_value_identity_gap_note, + decl_facts_outer_record_constructor_lexeme_authority_note, + skeleton_contains_marshal_atom_lexeme, + skeleton_contains_outer_record_constructor_lexeme, +} +import test.claim.decl_facts_reflection_witness_support { + decl_fact_projection_node_for_qualified_name, + decl_facts_reflection_bind_is_declaration_ref_projection, + decl_facts_reflection_constructor_variant, + decl_facts_reflection_field_projection, + decl_facts_reflection_fixture_facts, + decl_facts_reflection_named_field_projection_surfaces, + decl_facts_reflection_nullary_value_projection, + decl_facts_reflection_resolved_nullary_variant_name, +} +import v2.std.data_initializer_identity { + resolved_variant_name_optional, + value_projection_is_resolved_variant, +} +import v2.std.algebra { length } + +data decl_facts_reflection_witness_note: String = "Step-1 prerequisite (#7796), PARTIAL: outer record-constructor SPELLING (OuterRecordConstructorLexeme) is reflected and tested via executing witnesses. Nullary variant VALUE parent/arm identity is exercised through decl_facts() on the lookup_fn_node marshal seam (w_nullary_variant_value_absent_without_infer_stamping_top_level, w_nullary_variant_value_absent_without_infer_stamping_nested, and decl_facts_initializer_projection_witness_test explicit-import rows). Exact parent-variant occurrence identity remains open — see decl_facts_nullary_variant_value_identity_gap_note. Rust tests in v1-compiler-tests are developer convenience only (compile-only in CI; nextest retired 2026-07-11)." + +data qn_planted_scaffold_specimen: String = "test.fixture.decl_facts_reflection.specimens.planted_scaffold_specimen" +data qn_planted_terminal_specimen: String = "test.fixture.decl_facts_reflection.specimens.planted_terminal_specimen" +data qn_planted_nullary_disposition_specimen: String = "test.fixture.decl_facts_reflection.specimens.planted_nullary_disposition_specimen" +data qn_planted_named_field_ref_specimen: String = "test.fixture.decl_facts_reflection.specimens.planted_named_field_ref_specimen" +data qn_planted_plain_int_specimen: String = "test.fixture.decl_facts_reflection.specimens.planted_plain_int_specimen" +data qn_uses_param_only: String = "test.fixture.decl_facts_reflection.specimens.uses_param_only" +data qn_uses_local: String = "test.fixture.decl_facts_reflection.specimens.uses_local" + +test fn w_fixture_pool_decl_facts_nonempty() -> Bool { + length(xs: decl_facts_reflection_fixture_facts()) > 0 +} + +test fn w_outer_record_constructor_scaffold_lexeme_surfaces() -> Bool { + match decl_facts_reflection_constructor_variant(qn: qn_planted_scaffold_specimen) { + Present { value: v } => v == "Scaffold" + Absent => false + } +} + +test fn w_outer_record_constructor_terminal_lexeme_surfaces() -> Bool { + match decl_facts_reflection_constructor_variant(qn: qn_planted_terminal_specimen) { + Present { value: v } => v == "Terminal" + Absent => false + } +} + +test fn w_declaration_ref_bind_fields_surface_in_scaffold_specimen() -> Bool { + decl_facts_reflection_bind_is_declaration_ref_projection(qn: qn_planted_scaffold_specimen) +} + +test fn w_named_field_declaration_ref_surfaces() -> Bool { + decl_facts_reflection_named_field_projection_surfaces(qn: qn_planted_named_field_ref_specimen) +} + +test fn w_plain_int_specimen_has_no_coproduct_constructor_lexemes() -> Bool { + match decl_fact_projection_node_for_qualified_name( + facts: decl_facts_reflection_fixture_facts(), + qn: qn_planted_plain_int_specimen + ) { + Present { value: skeleton } => + !skeleton_contains_outer_record_constructor_lexeme(node: skeleton, lexeme: "Scaffold") + && !skeleton_contains_outer_record_constructor_lexeme(node: skeleton, lexeme: "Terminal") + && !skeleton_contains_marshal_atom_lexeme(node: skeleton, lexeme: "SingleAuthority") + && !skeleton_contains_outer_record_constructor_lexeme(node: skeleton, lexeme: "DeclarationRef") + Absent => false + } +} + +test fn w_nullary_variant_value_absent_without_infer_stamping_top_level() -> Bool { + match decl_facts_reflection_nullary_value_projection(qn: qn_planted_nullary_disposition_specimen) { + Present { value: projection } => + value_projection_is_resolved_variant(node: projection) + && match decl_facts_reflection_resolved_nullary_variant_name( + qn: qn_planted_nullary_disposition_specimen + ) { + Present { value: v } => v == "SingleAuthority" + Absent => false + } + Absent => false + } +} + +test fn w_nullary_variant_value_absent_without_infer_stamping_nested() -> Bool { + match decl_facts_reflection_field_projection( + qn: qn_planted_scaffold_specimen, + field: "dissolves_to" + ) { + Present { value: projection } => + value_projection_is_resolved_variant(node: projection) + && match resolved_variant_name_optional(node: projection) { + Present { value: v } => v == "SingleAuthority" + Absent => false + } + Absent => false + } +} + +test fn w_fn_body_param_reference_does_not_gain_constructor_lexemes() -> Bool { + match decl_fact_projection_node_for_qualified_name( + facts: decl_facts_reflection_fixture_facts(), + qn: qn_uses_param_only + ) { + Present { value: skeleton } => + !skeleton_contains_outer_record_constructor_lexeme(node: skeleton, lexeme: "Scaffold") + && !skeleton_contains_marshal_atom_lexeme(node: skeleton, lexeme: "SingleAuthority") + && !skeleton_contains_outer_record_constructor_lexeme(node: skeleton, lexeme: "DeclarationRef") + Absent => false + } +} + +test fn w_fn_body_local_bindings_do_not_gain_constructor_lexemes() -> Bool { + match decl_fact_projection_node_for_qualified_name( + facts: decl_facts_reflection_fixture_facts(), + qn: qn_uses_local + ) { + Present { value: skeleton } => + !skeleton_contains_outer_record_constructor_lexeme(node: skeleton, lexeme: "Scaffold") + && !skeleton_contains_marshal_atom_lexeme(node: skeleton, lexeme: "SingleAuthority") + && !skeleton_contains_outer_record_constructor_lexeme(node: skeleton, lexeme: "DeclarationRef") + Absent => false + } +} + +test fn w_gap_note_names_infer_binding_kind_authority() -> Bool { + string_contains( + s: decl_facts_nullary_variant_value_identity_gap_note, + pattern: "VariantValueBinding", + ) && string_contains( + s: decl_facts_nullary_variant_value_identity_gap_note, + pattern: "marshal_data_initializer_projection", + ) +} + +test fn w_authority_note_names_constructor_lexeme_not_variant_identity() -> Bool { + string_contains( + s: decl_facts_outer_record_constructor_lexeme_authority_note, + pattern: "OuterRecordConstructorLexeme", + ) && string_contains( + s: decl_facts_outer_record_constructor_lexeme_authority_note, + pattern: "not variant identity", + ) +} diff --git a/dag/test/claim/floor_batch_clamp_authority_witness_test.dag b/dag/test/claim/floor_batch_clamp_authority_witness_test.dag index ef653c722ad..1a73d56e17c 100644 --- a/dag/test/claim/floor_batch_clamp_authority_witness_test.dag +++ b/dag/test/claim/floor_batch_clamp_authority_witness_test.dag @@ -39,7 +39,7 @@ test fn scoped_batch_clamp_cites_its_own_declaration_not_the_positional_list() - let c = scoped_clamp() (c.authority.module_path as String == "gunbc.ci_layer_roots") && (c.authority.decl_name as String == "v1_claim_scoped_witness_batch") - && (second_count(s: c.overhead) == 360) + && (second_count(s: c.overhead) == 480) && (millisecond_count(m: c.per_unit) == 0) } @@ -63,7 +63,7 @@ test fn the_two_clamp_authorities_are_distinguishable_by_value() -> Bool { test fn scoped_clamp_is_identical_at_one_unit_and_at_twenty_seven() -> Bool { let c = scoped_clamp() (runnable_batch_clamp_ms(clamp: c, units: 1) == runnable_batch_clamp_ms(clamp: c, units: 27)) - && (runnable_batch_clamp_ms(clamp: c, units: 27) == 360000) + && (runnable_batch_clamp_ms(clamp: c, units: 27) == 480000) } // The positive control for the one above: a row whose rate is NONZERO must move with the unit diff --git a/dag/test/claim/long/qualified_declaration_reference_emit_cross_module_witness_test.dag b/dag/test/claim/long/qualified_declaration_reference_emit_cross_module_witness_test.dag index d12204c2dc0..beab12aed89 100644 --- a/dag/test/claim/long/qualified_declaration_reference_emit_cross_module_witness_test.dag +++ b/dag/test/claim/long/qualified_declaration_reference_emit_cross_module_witness_test.dag @@ -4,13 +4,13 @@ import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly -data qualified_declaration_reference_emit_cross_module_witness_note: String = "Long-lane half of qualified_declaration_reference_emit_witness_test: w_cross_module_qualified_reference_emits_call exercises std.emit_on_demand.interpreter_surviving_roles_ceiling through compile_dag_rust_emit_check. Measured ~4125ms thread-CPU warm locally; PR CI run 31198854678 discovery batch 3 killed at 5544ms > 5000ms fast-lane budget. Same-module fixture stays per-PR in dag/test/claim/qualified_declaration_reference_emit_witness_test.dag. dissolve_on: eval lands under gunbc_ci_fast_lane_witness_eval_budget on measured cold run and re-enrolls per-PR discovery." +data qualified_declaration_reference_emit_cross_module_witness_note: String = "Long-lane half of qualified_declaration_reference_emit_witness_test.dag: w_cross_module_qualified_reference_emits_call exercises a genuine cross-module qualified data reference (std.unicode.types.unicode_scalar_max_code_point — minimal std closure, not the heavy std.emit_on_demand roster the prior fixture dragged in) through compile_dag_rust_emit_check. Re-homed to test/claim/long/ after falsifier discovery batch 3 BudgetExceeded kills (5436-5544ms thread-CPU vs 5000ms fast-lane cap on srv_fleet_arm64; chronic on main, unrelated to decl_facts work). Same-module control stays per-PR in dag/test/claim/qualified_declaration_reference_emit_witness_test.dag. dissolve-on: eval under gunbc_ci_fast_lane_witness_eval_budget on fleet hardware, then row re-enrolls per-PR and this file deletes." fn w_cross_module_qualified_reference_emits_call() -> Bool { compile_dag_rust_emit_check( - "module qdref_cross\n\nimport std.emit_on_demand { interpreter_surviving_roles_ceiling }\nimport std.types { Int }\n\ndata qdref_cross_derived: Int = std.emit_on_demand.interpreter_surviving_roles_ceiling\n", + "module qdref_cross\n\nimport std.unicode.types { unicode_scalar_max_code_point }\nimport std.types { Int }\n\ndata qdref_cross_derived: Int = std.unicode.types.unicode_scalar_max_code_point\n", "src/qdref_cross.rs", - ["interpreter_surviving_roles_ceiling()"], + ["unicode_scalar_max_code_point()"], [] ) } diff --git a/dag/test/claim/qualified_declaration_reference_emit_witness_test.dag b/dag/test/claim/qualified_declaration_reference_emit_witness_test.dag index 8cb679dc998..0aa935ce05f 100644 --- a/dag/test/claim/qualified_declaration_reference_emit_witness_test.dag +++ b/dag/test/claim/qualified_declaration_reference_emit_witness_test.dag @@ -1,6 +1,6 @@ module test.claim.qualified_declaration_reference_emit_witness_test -data qualified_declaration_reference_emit_witness_note: String = "Regression control for the qualified-declaration-reference emitter defect formerly worked around in dag/extdeps/container/oci/digest.dag (deleted qualified_self_reference_emit_defect_note): a fully qualified reference to a declaration in a data initializer previously resolved through a bare-name registry (last-write-wins across every module sharing that leaf), so a SAME-MODULE qualified reference could land on an unrelated module's declaration and render as a plain fn-item value instead of a call. The fix normalizes a self-qualified reference to its bare spelling before registry lookup (reusing the already-correct bare-name path) and keys the general dotted-reference registry lookup on the leaf name so the is_data call-suffix decision fires for every qualified reference, same-module or cross-module. Per-PR fixture: w_same_module_qualified_reference_emits_call. Cross-module fixture (std.emit_on_demand import, compile_dag_rust_emit_check ~4s+) lives on the long lane: dag/test/claim/long/qualified_declaration_reference_emit_cross_module_witness_test.dag. dissolve-on: never -- permanent regression alarm for the emitter arm." +data qualified_declaration_reference_emit_witness_note: String = "Regression control for the qualified-declaration-reference emitter defect formerly worked around in dag/extdeps/container/oci/digest.dag (deleted qualified_self_reference_emit_defect_note): a fully qualified reference to a declaration in a data initializer previously resolved through a bare-name registry (last-write-wins across every module sharing that leaf), so a SAME-MODULE qualified reference could land on an unrelated module's declaration and render as a plain fn-item value instead of a call. The fix normalizes a self-qualified reference to its bare spelling before registry lookup (reusing the already-correct bare-name path) and keys the general dotted-reference registry lookup on the leaf name so the is_data call-suffix decision fires for every qualified reference, same-module or cross-module. Per-PR fixture: w_same_module_qualified_reference_emits_call. Cross-module fixture lives on the long lane: dag/test/claim/long/qualified_declaration_reference_emit_cross_module_witness_test.dag (compile_dag_rust_emit_check exceeds per-PR fast-lane budget). dissolve-on: never -- permanent regression alarm for the emitter arm on the same-module arm." fn w_same_module_qualified_reference_emits_call() -> Bool { compile_dag_rust_emit_check( diff --git a/dag/test/fixture/decl_facts_reflection/ambiguous_b_specimen.dag b/dag/test/fixture/decl_facts_reflection/ambiguous_b_specimen.dag new file mode 100644 index 00000000000..0d2d3da79bd --- /dev/null +++ b/dag/test/fixture/decl_facts_reflection/ambiguous_b_specimen.dag @@ -0,0 +1,5 @@ +module test.fixture.decl_facts_reflection.ambiguous_b_specimen + +import test.fixture.decl_facts_reflection.ambiguous_shared_b { SharedBareArm } + +data ambiguous_b_arm_specimen: SharedBareArm = SharedArm diff --git a/dag/test/fixture/decl_facts_reflection/ambiguous_shared_a.dag b/dag/test/fixture/decl_facts_reflection/ambiguous_shared_a.dag new file mode 100644 index 00000000000..41ac936872e --- /dev/null +++ b/dag/test/fixture/decl_facts_reflection/ambiguous_shared_a.dag @@ -0,0 +1,5 @@ +module test.fixture.decl_facts_reflection.ambiguous_shared_a + +type SharedBareArm + = SharedArm + | OtherOne diff --git a/dag/test/fixture/decl_facts_reflection/ambiguous_shared_b.dag b/dag/test/fixture/decl_facts_reflection/ambiguous_shared_b.dag new file mode 100644 index 00000000000..0aaa683a758 --- /dev/null +++ b/dag/test/fixture/decl_facts_reflection/ambiguous_shared_b.dag @@ -0,0 +1,5 @@ +module test.fixture.decl_facts_reflection.ambiguous_shared_b + +type SharedBareArm + = SharedArm + | OtherTwo diff --git a/dag/test/fixture/decl_facts_reflection/ambiguous_specimen.dag b/dag/test/fixture/decl_facts_reflection/ambiguous_specimen.dag new file mode 100644 index 00000000000..94c36472f27 --- /dev/null +++ b/dag/test/fixture/decl_facts_reflection/ambiguous_specimen.dag @@ -0,0 +1,5 @@ +module test.fixture.decl_facts_reflection.ambiguous_specimen + +import test.fixture.decl_facts_reflection.ambiguous_shared_a { SharedBareArm } + +data ambiguous_arm_specimen: SharedBareArm = SharedArm diff --git a/dag/test/fixture/decl_facts_reflection/globally_unique_carrier.dag b/dag/test/fixture/decl_facts_reflection/globally_unique_carrier.dag new file mode 100644 index 00000000000..7269599879f --- /dev/null +++ b/dag/test/fixture/decl_facts_reflection/globally_unique_carrier.dag @@ -0,0 +1,5 @@ +module test.fixture.decl_facts_reflection.globally_unique_carrier + +type GloballyUniqueWitnessArm + = OnlyArm + | UnusedGloballyUniqueVariant diff --git a/dag/test/fixture/decl_facts_reflection/specimens.dag b/dag/test/fixture/decl_facts_reflection/specimens.dag new file mode 100644 index 00000000000..a315da99a5c --- /dev/null +++ b/dag/test/fixture/decl_facts_reflection/specimens.dag @@ -0,0 +1,112 @@ +module test.fixture.decl_facts_reflection.specimens + +import std.types { String, Int } +import std.disposition { + Disposition, + Terminal, + Scaffold, + SingleAuthority, + RealizationDispatch, + ConstructionMechanism, +} +import std.decl_ref { DeclarationRef, WholeDeclaration, NamedField } + +type LocalCarrierA + = TaggedA { tag: String } + | SharedNullaryA + | OtherA + +type LocalCarrierB + = TaggedB { tag: String } + | SharedNullaryB + | OtherB + +data whole_declaration_anchor: String = "anchor" + +data named_field_anchor: String = "named-anchor" + +data disposition_scaffold: Disposition = Scaffold { + dissolves_to: SingleAuthority, + bind: DeclarationRef { + module_path: "test.fixture.decl_facts_reflection.specimens", + decl_name: "whole_declaration_anchor", + field: WholeDeclaration, + }, +} + +data scaffold_named_field_bind: Disposition = Scaffold { + dissolves_to: SingleAuthority, + bind: DeclarationRef { + module_path: "test.fixture.decl_facts_reflection.specimens", + decl_name: "named_field_anchor", + field: NamedField { field_name: "dissolves_to" }, + }, +} + +data terminal_specimen: Disposition = Terminal { reason: "witness-terminal" } + +data local_a_scaffold: LocalCarrierA = TaggedA { tag: "carrier-a" } + +data local_b_scaffold: LocalCarrierB = TaggedB { tag: "carrier-b" } + +data local_a_shared_nullary: LocalCarrierA = SharedNullaryA + +data local_b_shared_nullary: LocalCarrierB = SharedNullaryB + +data mechanism_single_authority: ConstructionMechanism = SingleAuthority + +data mechanism_realization_dispatch: ConstructionMechanism = RealizationDispatch + +data mechanism_data_ref: ConstructionMechanism = Terminal { reason: "witness-data-ref-not-variant-value" } + +data scaffold_with_data_ref: Disposition = Scaffold { + dissolves_to: mechanism_data_ref, + bind: DeclarationRef { + module_path: "test.fixture.decl_facts_reflection.specimens", + decl_name: "whole_declaration_anchor", + field: WholeDeclaration, + }, +} + +data missing_variant_specimen: Disposition = Scaffold { + dissolves_to: Terminal { reason: "phantom-mechanism" }, + bind: DeclarationRef { + module_path: "test.fixture.decl_facts_reflection.specimens", + decl_name: "whole_declaration_anchor", + field: WholeDeclaration, + }, +} + +data planted_scaffold_specimen: Disposition = Scaffold { + dissolves_to: SingleAuthority, + bind: DeclarationRef { + module_path: "test.fixture.decl_facts_reflection.specimens", + decl_name: "planted_scaffold_specimen", + field: WholeDeclaration + } +} + +data planted_terminal_specimen: Disposition = Terminal { + reason: "fixture terminal arm" +} + +data planted_nullary_disposition_specimen: Disposition = SingleAuthority + +data planted_named_field_ref_specimen: DeclarationRef = DeclarationRef { + module_path: "test.fixture.decl_facts_reflection.specimens", + decl_name: "planted_scaffold_specimen", + field: NamedField { field_name: "dissolves_to" } +} + +data planted_plain_int_specimen: Int = 42 + +data planted_plain_string_specimen: String = "hello" + +fn uses_param_only(x: Int) -> Int { + x +} + +fn uses_local(y: Int) -> Int { + let z = y + z +} diff --git a/dag/test/fixture/decl_facts_reflection/unimported_globally_unique_user.dag b/dag/test/fixture/decl_facts_reflection/unimported_globally_unique_user.dag new file mode 100644 index 00000000000..a4eb48d2bef --- /dev/null +++ b/dag/test/fixture/decl_facts_reflection/unimported_globally_unique_user.dag @@ -0,0 +1,3 @@ +module test.fixture.decl_facts_reflection.unimported_globally_unique_user + +data unimported_globally_unique_specimen: GloballyUniqueWitnessArm = OnlyArm diff --git a/dag/test/fixture/decl_facts_reflection/witness_support.dag b/dag/test/fixture/decl_facts_reflection/witness_support.dag new file mode 100644 index 00000000000..ce86ce06a37 --- /dev/null +++ b/dag/test/fixture/decl_facts_reflection/witness_support.dag @@ -0,0 +1,37 @@ +module test.fixture.decl_facts_reflection.witness_support + +import std.types { List } +import v2.std.decl_facts_skeleton { + decl_fact_skeleton_for_resolution, + resolve_decl_fact_by_qualified_name, +} +import v2.std.decl_index { DeclFact, decl_facts } + +// Single authority: specimen rows, duplicate_qn oracle (both roots), dimensionless positive-control deps. +data decl_facts_reflection_fixture_pool_roots: List = [ + "dag/test/fixture/decl_facts_reflection", + "dag/test/fixture/decl_facts_reflection_duplicate_qn/primary", + "test/fixture_roots/decl_facts_reflection_duplicate_qn/shadow", + "dag/extdeps/units", + "dag/extdeps/external_authority", + "dag/std", +] + +// Skeleton outer-constructor lexeme witnesses need only parse-tree facts under specimens/; +// narrower than decl_facts_reflection_fixture_pool_roots — no duplicate_qn shadow or std closure. +data decl_facts_skeleton_fixture_pool_roots: List = [ + "dag/test/fixture/decl_facts_reflection", +] + +data decl_facts_reflection_fixture_facts: List = decl_facts(pool_roots: decl_facts_reflection_fixture_pool_roots) + +data decl_facts_skeleton_fixture_facts: List = decl_facts(pool_roots: decl_facts_skeleton_fixture_pool_roots) + +fn decl_fact_skeleton_for_qualified_name( + facts: List, + qn: String +) -> Node? { + decl_fact_skeleton_for_resolution( + resolution: resolve_decl_fact_by_qualified_name(facts: facts, qn: qn) + ) +} diff --git a/dag/test/fixture/decl_facts_reflection_duplicate_qn/primary/shadow_module.dag b/dag/test/fixture/decl_facts_reflection_duplicate_qn/primary/shadow_module.dag new file mode 100644 index 00000000000..1dec7250a5d --- /dev/null +++ b/dag/test/fixture/decl_facts_reflection_duplicate_qn/primary/shadow_module.dag @@ -0,0 +1,3 @@ +module test.fixture.decl_facts_reflection_duplicate_qn.shadowed_module + +data shadowed_specimen: Int = 1 diff --git a/dag/test/retirement/decl_facts_dimensionless_projection_retained.dag b/dag/test/retirement/decl_facts_dimensionless_projection_retained.dag new file mode 100644 index 00000000000..6bcc63002df --- /dev/null +++ b/dag/test/retirement/decl_facts_dimensionless_projection_retained.dag @@ -0,0 +1,8 @@ +module test.retirement.decl_facts_dimensionless_projection_retained + +data decl_facts_dimensionless_projection_retention: TestModuleRetirement = TestModuleRetirement { + module: "decl_facts_dimensionless_projection_test.rs", + disposition: RetainedNonMigratable { + reason: "Developer-convenience Rust control for compile_to_resolved source-file order invariance of initializer projection marshalling (projection kind + constructor parent identity). The witness-layer decl_facts path uses one resolved graph; reversing the SourceFile vector before compile is a host-Rust seam no .dag witness can observe without minting a second compile entry. Retained until src/v1 deletes at v2 self-host." + } +} diff --git a/src/v1/stage0/src/bin/claim_executor.rs b/src/v1/stage0/src/bin/claim_executor.rs index df8c17710ca..654abacff12 100644 --- a/src/v1/stage0/src/bin/claim_executor.rs +++ b/src/v1/stage0/src/bin/claim_executor.rs @@ -10736,7 +10736,9 @@ fn run() -> Result { .collect(); if !scoped_rows.is_empty() { let receipt_arm = scoped_witness_head_sha().and_then(|_| { - if floor_worker_role.is_none() { + if floor_worker_role.is_none() + || matches!(floor_worker_role, Some(FloorWorkerRole::Scoped { .. })) + { initialize_scoped_witness_receipt() } else { Ok(()) diff --git a/src/v1/stage0/src/bootstrap_stage0_crate_layout_generated.rs b/src/v1/stage0/src/bootstrap_stage0_crate_layout_generated.rs index a47e02c255d..567038d7fe3 100644 --- a/src/v1/stage0/src/bootstrap_stage0_crate_layout_generated.rs +++ b/src/v1/stage0/src/bootstrap_stage0_crate_layout_generated.rs @@ -7,6 +7,7 @@ pub const HAND_MAINTAINED_STAGE0_FILES: &[&str] = &[ "ci_control_plane.rs", "cli_run.rs", "coproduct_reflection.rs", + "data_initializer_identity.rs", "resolved_graph_cache.rs", "shared_typecheck_store.rs", "recorded_fixture.rs", diff --git a/src/v1/stage0/src/coproduct_reflection.rs b/src/v1/stage0/src/coproduct_reflection.rs index 6c78dac3836..876e4e6fb11 100644 --- a/src/v1/stage0/src/coproduct_reflection.rs +++ b/src/v1/stage0/src/coproduct_reflection.rs @@ -655,6 +655,13 @@ fn hoist_call_arg_string_literal_edges( } } +fn should_emit_nullary_variant_value_atom(binding_kind: Option<&Rc>) -> bool { + matches!( + binding_kind, + Some(bk) if matches!(bk.as_ref(), VarBindingKind::VariantValueBinding { .. }) + ) +} + fn marshal_generic( ctx: &InterpContext, node: &Rc, @@ -664,30 +671,30 @@ fn marshal_generic( let name = node_authored_name(node, si); let mut edges: Vec = Vec::with_capacity(node.children.len() + 1); let mut refs: std::collections::BTreeSet = std::collections::BTreeSet::new(); - if node_references_param(node, &name, param_names) { - edges.push(edge_positional(ctx, atom_identity_node(ctx, &name))); - } else if matches!(node.expr_data.as_ref(), ExprData::ExprCall { .. }) && !name.is_empty() { - // G2 live-read call reachability: callee atoms make cross-fn carrier chains - // visible in the fn-arrow skeleton (docs/plans/live-read-witness-classification-design.md P1). + if matches!(node.expr_data.as_ref(), ExprData::ExprRecordLit { .. }) && !name.is_empty() { + // Outer record-constructor SPELLING (OuterRecordConstructorLexeme) from ExprRecordLit + // authored name — not resolved parent-variant identity; see + // `v2.std.decl_facts_skeleton` `decl_facts_outer_record_constructor_lexeme_authority_note`. edges.push(edge_positional(ctx, atom_identity_node(ctx, &name))); - } else if matches!(node.expr_data.as_ref(), ExprData::ExprRecordLit { .. }) && !name.is_empty() - { - // Authored constructor SPELLING, not resolved declaration identity: the name is - // the AST node's authored lexeme (no qualified name, no parent-enum identity), - // so a spelling census over this edge is a conservative over-approximation — - // two same-spelled record types in different modules are indistinguishable - // here. It also fires ONLY for record literals: a fieldless variant - // constructed bare is not an ExprRecordLit and stays invisible, so a - // zero count over a fieldless declaration is vacuous, never evidence. - // The NAMED edge only discriminates a construction occurrence from a - // string literal or callee atom spelling the same lexeme (those ride - // positional edges). Resolved constructor identity (qualified declaration + - // parent identity for variants) is separate future work. + // Named edge discriminates construction from string-literal/callee atoms spelling the + // same lexeme (record_construction_census_witness_test). edges.push(edge_named( ctx, "record_construction_spelling", atom_identity_node(ctx, &name), )); + } else if node_references_param(node, &name, param_names) { + edges.push(edge_positional(ctx, atom_identity_node(ctx, &name))); + } else if matches!(node.expr_data.as_ref(), ExprData::ExprCall { .. }) && !name.is_empty() { + // G2 live-read call reachability: callee atoms make cross-fn carrier chains + // visible in the fn-arrow skeleton (docs/plans/live-read-witness-classification-design.md P1). + edges.push(edge_positional(ctx, atom_identity_node(ctx, &name))); + } else if !name.is_empty() { + if let ExprData::ExprVar { binding_kind } = node.expr_data.as_ref() { + if should_emit_nullary_variant_value_atom(binding_kind.as_ref()) { + edges.push(edge_positional(ctx, atom_identity_node(ctx, &name))); + } + } } if let Some(literal_edge) = marshal_string_literal_atom(ctx, node) { edges.push(literal_edge); @@ -1034,10 +1041,11 @@ pub struct DeclFactsCorpusWalk { pub files_parsed: usize, } -/// Parse-only whole-tree `decl_facts(roots)` substrate — shared by host builtin and emit audits. -/// -/// Preserves the non-test corpus boundary for emit-only audits: `is_test_dag(rel)` skips -/// before any `DeclFactRaw` row is materialized (same exclusion as the pre-#6158 walk). +// decl_facts_corpus_walk parses every .dag under every pool_root independently, +// taking module_path from each file's own module declaration. It does not consult +// dependency-pool-index / the module source index used by compile/load. Where roots +// shadow the same module path, this producer may emit facts for both the winning +// source and the shadowed stub; compile binds only the winner. pub fn decl_facts_corpus_walk(pool_roots: &[String]) -> DeclFactsCorpusWalk { let mut out = Vec::new(); let mut files_scanned = 0usize; @@ -1122,6 +1130,7 @@ fn marshal_decl_fact_node( item: &Rc, kind: ItemKind, si: &SourceIndices, + qualified_name: &str, ) -> InterpResult { match kind { ItemKind::TypeItem => concept_decl_node(ctx, si, item), @@ -1129,11 +1138,10 @@ fn marshal_decl_fact_node( Ok(fn_arrow_output_skeleton(ctx, si, item).unwrap_or_else(|| unit_type_node(ctx))) } ItemKind::DataItem => { - if let Some(body) = item.body.as_ref() { - Ok(marshal_fn_body_skeleton(ctx, body, &[], si)) - } else { - Ok(unit_type_node(ctx)) - } + crate::data_initializer_identity::marshal_data_initializer_projection( + ctx, + qualified_name, + ) } _ => Ok(unit_type_node(ctx)), } @@ -1223,23 +1231,36 @@ pub fn eval_export_signature_facts( pub fn eval_decl_facts(ctx: &InterpContext, pool_roots: &[String]) -> InterpResult { let facts = decl_facts_for_roots(pool_roots); + eval_decl_facts_rows(ctx, &facts) +} + +fn eval_decl_facts_rows(ctx: &InterpContext, facts: &[DeclFactRaw]) -> InterpResult { let mut rows = Vec::with_capacity(facts.len()); for fact in facts { - let node = marshal_decl_fact_node(ctx, &fact.node, fact.kind, &fact.source_indices) - .map_err(|e| InterpError::TypeError { - msg: format!( - "decl_facts: failed to marshal `{}` ({:?}) in `{}`: {e}", - fact.qualified_name, fact.kind, fact.rel_path - ), - })?; + let node = marshal_decl_fact_node( + ctx, + &fact.node, + fact.kind, + &fact.source_indices, + &fact.qualified_name, + ) + .map_err(|e| InterpError::TypeError { + msg: format!( + "decl_facts: failed to marshal `{}` ({:?}) in `{}`: {e}", + fact.qualified_name, fact.kind, fact.rel_path + ), + })?; rows.push(Value::Record { type_name: ctx.sym("DeclFact"), fields: Rc::new(sorted_fields(vec![ - (ctx.sym("qualified_name"), Value::Str(fact.qualified_name)), - (ctx.sym("name"), Value::Str(fact.name)), + ( + ctx.sym("qualified_name"), + Value::Str(fact.qualified_name.clone()), + ), + (ctx.sym("name"), Value::Str(fact.name.clone())), (ctx.sym("kind"), marshal_decl_item_kind(ctx, fact.kind)), (ctx.sym("node"), node), - (ctx.sym("rel_path"), Value::Str(fact.rel_path)), + (ctx.sym("rel_path"), Value::Str(fact.rel_path.clone())), ])), }); } @@ -1473,3 +1494,118 @@ fn outcome_rejected_value(ctx: &InterpContext, reason: &str) -> Value { )]), } } + +#[cfg(test)] +mod parse_only_uppercase_variant_regression_tests { + use std::rc::Rc; + + use im::{vector as im_vec, HashMap}; + + use crate::v1_compiler_infer_emit_info::empty_emit_graph_info; + use crate::v1_compiler_infer_items::ResolvedGraph; + use crate::v1_interpreter::{ExecutionMode, InterpContext, Value}; + use crate::v1_std_core::{ + empty_node_list, make_named_expr_node, ExprData, SourceSpan, VarBindingKind, + }; + + use super::marshal_generic; + + fn test_ctx() -> InterpContext { + let graph = ResolvedGraph { + modules: Rc::new(im_vec![]), + item_registry: Rc::new(HashMap::new()), + diagnostics: Rc::new(im_vec![]), + emit_graph_info: empty_emit_graph_info(), + }; + InterpContext::new(&graph, Rc::new(HashMap::new()), ExecutionMode::Hermetic) + } + + fn dummy_span() -> Rc { + Rc::new(SourceSpan { + file: "test.dag".to_string(), + start: 0, + end: 1, + }) + } + + fn uppercase_var_without_binding() -> Rc { + make_named_expr_node( + "SharedArm".to_string(), + Rc::new(ExprData::ExprVar { binding_kind: None }), + empty_node_list(), + None, + dummy_span(), + dummy_span(), + ) + } + + fn uppercase_var_with_variant_binding() -> Rc { + make_named_expr_node( + "SharedArm".to_string(), + Rc::new(ExprData::ExprVar { + binding_kind: Some(Rc::new(VarBindingKind::VariantValueBinding { + parent_enum: "Parent".to_string(), + })), + }), + empty_node_list(), + None, + dummy_span(), + dummy_span(), + ) + } + + fn skeleton_children_len(ctx: &InterpContext, skel: &Value) -> usize { + match skel { + Value::Record { fields, .. } => { + let children_key = ctx.sym("children"); + fields + .iter() + .find(|(k, _)| *k == children_key) + .map(|(_, v)| match v { + Value::List(items) => items.len(), + _ => 0, + }) + .unwrap_or(0) + } + _ => 0, + } + } + + #[test] + fn uppercase_nullary_without_variant_binding_emits_no_parse_only_skeleton_atom() { + let ctx = test_ctx(); + let si = ctx.source_indices(); + let (skel, _) = marshal_generic(&ctx, &uppercase_var_without_binding(), &[], &si); + assert_eq!( + skeleton_children_len(&ctx, &skel), + 0, + "capitalization alone must not mint a variant-value skeleton atom" + ); + } + + #[test] + fn infer_stamped_variant_binding_still_emits_parse_only_skeleton_atom() { + let ctx = test_ctx(); + let si = ctx.source_indices(); + let (skel, _) = marshal_generic(&ctx, &uppercase_var_with_variant_binding(), &[], &si); + assert_eq!( + skeleton_children_len(&ctx, &skel), + 1, + "infer-stamped VariantValueBinding must still emit the skeleton atom" + ); + } + + #[test] + fn capitalization_heuristic_not_used_on_parse_only_path() { + let source = include_str!("coproduct_reflection.rs"); + let production = source.split("#[cfg(test)]").next().unwrap_or(source); + assert!( + !production.contains("is_uppercase_variant_spelling"), + "parse-only skeleton path must not reintroduce capitalization classification" + ); + assert!( + production.contains("should_emit_nullary_variant_value_atom"), + "parse-only skeleton must gate on infer-stamped VariantValueBinding only" + ); + } +} diff --git a/src/v1/stage0/src/data_initializer_identity.rs b/src/v1/stage0/src/data_initializer_identity.rs new file mode 100644 index 00000000000..7f640666869 --- /dev/null +++ b/src/v1/stage0/src/data_initializer_identity.rs @@ -0,0 +1,1176 @@ +//! Resolved constructor and variant-value identity for `decl_facts` data-initializer projection. +//! +//! Identity is marshaled only from typechecked subjects in `InterpContext`. Resolution uses +//! inferred `Node` stamps and owning type-item nodes — never lossy name strings re-looked up +//! through first-pick authority helpers (`resolved_initializer_decl_ref`, etc.). + +use std::rc::Rc; + +use im::HashMap; + +use crate::v1_compiler_infer_env::{lookup_binding_by_name_local, lookup_type}; +use crate::v1_compiler_infer_items::{item_kind, ItemKind, TypedModule}; +use crate::v1_compiler_infer_types::normalize_access_type_node; +use crate::v1_interpreter::{sorted_fields, InterpContext, InterpResult, Value}; +use crate::v1_std_core::{ + authored_name_at, field_init_node_name_at, field_init_node_value, field_node_name_at, + field_node_type_expr, find_child_named, inferred_to_node, Connective, ExprData, InferredNode, + NewlineIndex, Node, VarBindingKind, +}; + +type SourceIndices = Rc>>; + +fn bare_symbol_tail(name: &str) -> &str { + name.rsplit_once('.').map(|(_, tail)| tail).unwrap_or(name) +} + +fn local_symbol_name(si: &SourceIndices, node: &Rc) -> String { + bare_symbol_tail(&authored_name_at(si.clone(), node.clone())).to_string() +} + +fn coproduct_record_lit_variant_name(si: &SourceIndices, body: &Rc) -> String { + let from_authored = local_symbol_name(si, body); + if !from_authored.is_empty() { + return from_authored; + } + if !body.name.is_empty() { + return body.name.clone(); + } + String::new() +} + +fn coproduct_variant_arm_by_name( + coproduct: &Rc, + variant_name: &str, + si: &SourceIndices, +) -> Option> { + if variant_name.is_empty() { + return None; + } + if let Some(arm) = find_child_named(coproduct.clone(), variant_name.to_string(), si.clone()) { + return Some(arm); + } + let bare = bare_symbol_tail(variant_name); + if bare != variant_name { + if let Some(arm) = find_child_named(coproduct.clone(), bare.to_string(), si.clone()) { + return Some(arm); + } + } + coproduct + .children + .iter() + .find(|child| { + child.name == variant_name + || child.name == bare + || local_symbol_name(si, child) == variant_name + || local_symbol_name(si, child) == bare + }) + .cloned() +} + +fn coproduct_has_variant_named( + coproduct: &Rc, + variant_name: &str, + si: &SourceIndices, +) -> bool { + coproduct_variant_arm_by_name(coproduct, variant_name, si).is_some() +} + +fn type_expr_bare_name(si: &SourceIndices, type_expr: &Rc) -> Option { + let ty = if type_expr.connective == Connective::Conj + && type_expr.type_annotation.is_some() + && type_expr.children.len() == 1 + { + type_expr.children[0].clone() + } else { + type_expr.clone() + }; + let ty = normalize_access_type_node(ty); + if let Some(inferred) = ty + .inferred + .as_ref() + .and_then(|inf| inferred_to_node(inf.clone())) + { + let inferred_name = authored_name_at(si.clone(), inferred.clone()); + if !inferred_name.is_empty() { + return Some(bare_symbol_tail(&inferred_name).to_string()); + } + if !inferred.name.is_empty() { + return Some(bare_symbol_tail(&inferred.name).to_string()); + } + } + let name = authored_name_at(si.clone(), ty.clone()); + if name.is_empty() { + if ty.name.is_empty() { + None + } else { + Some(bare_symbol_tail(&ty.name).to_string()) + } + } else { + Some(bare_symbol_tail(&name).to_string()) + } +} + +fn field_type_bare_on_variant_arm( + arm: &Rc, + field_name: &str, + si: &SourceIndices, +) -> Option { + field_type_bare_on_record_fields(arm, field_name, si) +} + +fn field_type_bare_on_record_fields( + node: &Rc, + field_name: &str, + si: &SourceIndices, +) -> Option { + let bare_field = bare_symbol_tail(field_name); + for child in node.children.iter() { + let authored = authored_name_at(si.clone(), child.clone()); + let declared = field_node_name_at(child.clone(), si.clone()); + if authored != field_name + && authored != bare_field + && declared != field_name + && declared != bare_field + && child.name != field_name + && child.name != bare_field + { + if child.connective == Connective::Conj { + if let Some(bare) = field_type_bare_on_record_fields(child, field_name, si) { + return Some(bare); + } + } + continue; + } + let ty = child + .inferred + .as_ref() + .and_then(|inf| inferred_to_node(inf.clone())) + .unwrap_or_else(|| field_node_type_expr(child.clone())); + let ty = normalize_access_type_node(ty); + if let Some(bare) = type_expr_bare_name(si, &ty) { + return Some(bare); + } + } + None +} + +fn declared_field_type_bare_on_coproduct_variant( + coproduct: &Rc, + variant_name: &str, + field_name: &str, + si: &SourceIndices, +) -> Option { + let arm = coproduct_variant_arm_by_name(coproduct, variant_name, si)?; + field_type_bare_on_variant_arm(&arm, field_name, si).or_else(|| { + arm.inferred + .as_ref() + .and_then(|inf| inferred_to_node(inf.clone())) + .and_then(|node| declared_field_type_bare_on_conj(&node, field_name, si)) + }) +} + +fn declared_field_type_bare_on_conj( + type_item: &Rc, + field_name: &str, + si: &SourceIndices, +) -> Option { + if type_item.connective != Connective::Conj { + return None; + } + field_type_bare_on_variant_arm(type_item, field_name, si) +} + +fn coproduct_type_item_parse_tree( + ctx: &InterpContext, + bare_name: &str, + si: &SourceIndices, +) -> Option<(Rc, String)> { + for tm in ctx.modules.iter() { + let mod_name = authored_name_at(si.clone(), tm.module.clone()); + for item in tm.items.iter() { + if item_kind(item.clone()) != ItemKind::TypeItem || item.connective != Connective::Disj + { + continue; + } + if local_symbol_name(si, item) != bare_name { + continue; + } + return Some((item.clone(), mod_name)); + } + } + None +} + +fn declared_field_type_on_coproduct_variant_parse_tree( + ctx: &InterpContext, + coproduct_bare: &str, + variant_name: &str, + field_name: &str, + si: &SourceIndices, +) -> Option { + let (coproduct, _) = coproduct_type_item_parse_tree(ctx, coproduct_bare, si)?; + declared_field_type_bare_on_coproduct_variant(&coproduct, variant_name, field_name, si) +} + +fn decl_logical_qualified_name(module_name: &str, name: &str) -> String { + let logical = module_name.strip_prefix("v2.").unwrap_or(module_name); + if logical.is_empty() { + name.to_string() + } else { + format!("{logical}.{name}") + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ResolvedDeclarationLocator { + pub qualified_name: String, + pub name: String, + pub module_path: String, + pub rel_path: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ResolvedVariantLocator { + pub parent: ResolvedDeclarationLocator, + pub arm: ResolvedDeclarationLocator, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum DataInitializerValueResolution { + Resolved(ResolvedVariantLocator), + NotVariantValue, + Missing, + Ambiguous(Vec), +} + +fn rel_path_from_node(node: &Rc) -> String { + node.span.file.clone() +} + +fn declared_type_bare_name(item: &Rc, si: &SourceIndices) -> Option { + let ann = item.type_annotation.as_ref()?; + let name = authored_name_at(si.clone(), ann.clone()); + if name.is_empty() { + None + } else { + Some(bare_symbol_tail(&name).to_string()) + } +} + +fn typed_module_for_path(ctx: &InterpContext, module_path: &str) -> Option> { + let si = ctx.source_indices.clone(); + ctx.modules + .iter() + .find(|tm| authored_name_at(si.clone(), tm.module.clone()) == module_path) + .cloned() +} + +fn lookup_type_binding_in_importing_module( + tm: &TypedModule, + bare_name: &str, +) -> Option> { + lookup_binding_by_name_local(tm.type_env.clone(), bare_name.to_string()) +} + +fn type_item_from_importing_module_type_env( + tm: &TypedModule, + bare_name: &str, + si: &SourceIndices, +) -> Option> { + let binding = lookup_type_binding_in_importing_module(tm, bare_name)?; + let node = binding.resolved.clone(); + if item_kind(node.clone()) != ItemKind::TypeItem { + return None; + } + let name = authored_name_at(si.clone(), node.clone()); + if bare_symbol_tail(&name) != bare_name { + return None; + } + Some(node) +} + +fn coproduct_type_item_with_variant_children( + _ctx: &InterpContext, + tm: &TypedModule, + bare_name: &str, + si: &SourceIndices, +) -> Option<(Rc, String)> { + let node = type_item_from_importing_module_type_env(tm, bare_name, si)?; + if node.connective != Connective::Disj { + return None; + } + let importing_module = authored_name_at(si.clone(), tm.module.clone()); + Some((node, importing_module)) +} + +fn module_path_for_type_decl_node( + ctx: &InterpContext, + type_item: &Rc, + si: &SourceIndices, +) -> Option { + let file = type_item.span.file.as_str(); + let type_name = authored_name_at(si.clone(), type_item.clone()); + for tm in ctx.modules.iter() { + let mod_name = authored_name_at(si.clone(), tm.module.clone()); + for item in tm.items.iter() { + if item_kind(Rc::clone(item)) != ItemKind::TypeItem { + continue; + } + if item.span.file.as_str() != file { + continue; + } + if authored_name_at(si.clone(), Rc::clone(item)) == type_name { + return Some(mod_name); + } + } + } + None +} + +fn declaration_identity_for_type_item( + ctx: &InterpContext, + type_item: &Rc, + si: &SourceIndices, + fallback_module_path: &str, +) -> ResolvedDeclarationLocator { + let module_path = module_path_for_type_decl_node(ctx, type_item, si) + .unwrap_or_else(|| fallback_module_path.to_string()); + let name = authored_name_at(si.clone(), type_item.clone()); + ResolvedDeclarationLocator { + qualified_name: decl_logical_qualified_name(&module_path, &name), + name, + module_path, + rel_path: rel_path_from_node(type_item), + } +} + +fn declaration_identity_for_variant_arm( + ctx: &InterpContext, + coproduct: &Rc, + variant_name: &str, + si: &SourceIndices, + fallback_module_path: &str, +) -> Option { + let arm = coproduct_variant_arm_by_name(coproduct, variant_name, si)?; + let parent = declaration_identity_for_type_item(ctx, coproduct, si, fallback_module_path); + let arm_name = { + let authored = authored_name_at(si.clone(), arm.clone()); + if authored.is_empty() { + variant_name.to_string() + } else { + bare_symbol_tail(&authored).to_string() + } + }; + let arm_id = ResolvedDeclarationLocator { + qualified_name: format!("{}.{}", parent.qualified_name, arm_name), + name: arm_name, + module_path: parent.module_path.clone(), + rel_path: parent.rel_path.clone(), + }; + Some(ResolvedVariantLocator { + parent, + arm: arm_id, + }) +} + +fn expr_var_binding_kind(expr: &Rc) -> Option> { + match expr.expr_data.as_ref() { + ExprData::ExprVar { binding_kind, .. } => binding_kind.clone(), + _ => None, + } +} + +fn is_variant_value_binding(expr: &Rc) -> bool { + matches!( + expr_var_binding_kind(expr).as_deref(), + Some(VarBindingKind::VariantValueBinding { .. }) + ) +} + +fn variant_value_binding_parent_enum(expr: &Rc) -> Option { + match expr_var_binding_kind(expr).as_deref() { + Some(VarBindingKind::VariantValueBinding { parent_enum }) => Some(parent_enum.clone()), + _ => None, + } +} + +fn coproduct_from_variant_value_binding( + ctx: &InterpContext, + tm: &TypedModule, + parent_enum: &str, + si: &SourceIndices, +) -> Option<(Rc, String)> { + let bare = bare_symbol_tail(parent_enum); + let node = type_item_from_importing_module_type_env(tm, bare, si)?; + if node.connective != Connective::Disj { + return None; + } + let importing_module = authored_name_at(si.clone(), tm.module.clone()); + Some((node, importing_module)) +} + +fn variant_value_from_typechecked_expr( + ctx: &InterpContext, + importing_module: &str, + expr: &Rc, + si: &SourceIndices, + declared_type_bare: Option<&str>, +) -> DataInitializerValueResolution { + if matches!( + expr.inferred.as_deref(), + Some(InferredNode::CompilerError { .. }) + ) { + return DataInitializerValueResolution::Missing; + } + + if !matches!(expr.expr_data.as_ref(), ExprData::ExprVar { .. }) { + return DataInitializerValueResolution::NotVariantValue; + } + + if !is_variant_value_binding(expr) { + return DataInitializerValueResolution::NotVariantValue; + } + + let inferred_node = match expr.inferred.as_deref() { + Some(InferredNode::Resolved { node }) => node.clone(), + _ => return DataInitializerValueResolution::NotVariantValue, + }; + + let variant_name = { + let from_inferred = local_symbol_name(si, &inferred_node); + if !from_inferred.is_empty() { + from_inferred + } else { + authored_name_at(si.clone(), inferred_node.clone()) + } + }; + if variant_name.is_empty() { + return DataInitializerValueResolution::NotVariantValue; + } + + let expr_variant_name = { + let bare = crate::v1_std_core::expr_var_name_at(expr.clone(), si.clone()); + if bare.is_empty() { + variant_name.clone() + } else { + bare + } + }; + + let tm = match typed_module_for_path(ctx, importing_module) { + Some(tm) => tm, + None => return DataInitializerValueResolution::Missing, + }; + + let parent_enum = match variant_value_binding_parent_enum(expr) { + Some(parent_enum) => parent_enum, + None => return DataInitializerValueResolution::NotVariantValue, + }; + + let (coproduct, defining_module) = + match coproduct_from_variant_value_binding(ctx, &tm, &parent_enum, si) { + Some(found) => found, + None => { + if let Some(bare) = declared_type_bare.filter(|bare| !bare.is_empty()) { + match coproduct_type_item_with_variant_children(ctx, &tm, bare, si) { + Some(found) => found, + None => return DataInitializerValueResolution::Missing, + } + } else { + return DataInitializerValueResolution::Missing; + } + } + }; + + if !coproduct_has_variant_named(&coproduct, &expr_variant_name, si) + && !coproduct_has_variant_named(&coproduct, &variant_name, si) + { + return DataInitializerValueResolution::Missing; + } + + let resolved_variant_name = if coproduct_has_variant_named(&coproduct, &expr_variant_name, si) { + expr_variant_name + } else { + variant_name + }; + + match declaration_identity_for_variant_arm( + ctx, + &coproduct, + &resolved_variant_name, + si, + &defining_module, + ) { + Some(variant_id) => DataInitializerValueResolution::Resolved(variant_id), + None => DataInitializerValueResolution::Missing, + } +} + +fn constructor_resolution_refused_projection(ctx: &InterpContext) -> Value { + projection_node_with_named_edges( + ctx, + "DataInitializerConstructorResolutionRefusedProjection", + &[], + ) +} + +fn projection_atom_identity_node(ctx: &InterpContext, identity: &str) -> Value { + Value::Record { + type_name: ctx.sym("Node"), + fields: Rc::new(sorted_fields(vec![ + ( + ctx.sym("kind"), + Value::Variant { + type_name: ctx.sym("NodeKind"), + variant_name: ctx.sym("TypeNode"), + fields: Rc::new(vec![( + ctx.sym("connective"), + Value::Variant { + type_name: ctx.sym("Connective"), + variant_name: ctx.sym("Atom"), + fields: Rc::new(vec![( + ctx.sym("identity"), + Value::Str(identity.to_string()), + )]), + }, + )]), + }, + ), + ( + ctx.sym("children"), + crate::v1_interpreter::list_value(vec![]), + ), + ( + ctx.sym("occurrence_id"), + Value::Variant { + type_name: ctx.sym("NodeOccurrenceId"), + variant_name: ctx.sym("SyntheticOccurrence"), + fields: Rc::new(vec![]), + }, + ), + ])), + } +} + +fn projection_edge_named(ctx: &InterpContext, name: &str, target: Value) -> Value { + Value::Record { + type_name: ctx.sym("Edge"), + fields: Rc::new(sorted_fields(vec![ + ( + ctx.sym("label"), + Value::Variant { + type_name: ctx.sym("EdgeLabel"), + variant_name: ctx.sym("Named"), + fields: Rc::new(vec![(ctx.sym("name"), Value::Str(name.to_string()))]), + }, + ), + (ctx.sym("target"), target), + ])), + } +} + +fn projection_node_record(ctx: &InterpContext, projection_kind: &str, edges: Vec) -> Value { + Value::Record { + type_name: ctx.sym("Node"), + fields: Rc::new(sorted_fields(vec![ + ( + ctx.sym("kind"), + Value::Variant { + type_name: ctx.sym("NodeKind"), + variant_name: ctx.sym("TypeNode"), + fields: Rc::new(vec![( + ctx.sym("connective"), + Value::Variant { + type_name: ctx.sym("Connective"), + variant_name: ctx.sym("Atom"), + fields: Rc::new(vec![( + ctx.sym("identity"), + Value::Str(projection_kind.to_string()), + )]), + }, + )]), + }, + ), + ( + ctx.sym("children"), + crate::v1_interpreter::list_value(edges), + ), + ( + ctx.sym("occurrence_id"), + Value::Variant { + type_name: ctx.sym("NodeOccurrenceId"), + variant_name: ctx.sym("SyntheticOccurrence"), + fields: Rc::new(vec![]), + }, + ), + ])), + } +} + +fn projection_node_with_named_edges( + ctx: &InterpContext, + projection_kind: &str, + edges: &[(String, Value)], +) -> Value { + let mut children = Vec::with_capacity(edges.len()); + for (name, target) in edges { + children.push(projection_edge_named(ctx, name, target.clone())); + } + projection_node_record(ctx, projection_kind, children) +} + +fn marshal_declaration_identity_node( + ctx: &InterpContext, + id: &ResolvedDeclarationLocator, +) -> Value { + projection_node_with_named_edges( + ctx, + "DeclarationIdentityProjection", + &[ + ( + "qualified_name".to_string(), + projection_atom_identity_node(ctx, &id.qualified_name), + ), + ( + "name".to_string(), + projection_atom_identity_node(ctx, &id.name), + ), + ( + "module_path".to_string(), + projection_atom_identity_node(ctx, &id.module_path), + ), + ( + "rel_path".to_string(), + projection_atom_identity_node(ctx, &id.rel_path), + ), + ], + ) +} + +fn marshal_variant_identity_node(ctx: &InterpContext, id: &ResolvedVariantLocator) -> Value { + projection_node_with_named_edges( + ctx, + "VariantDeclarationIdentityProjection", + &[ + ( + "parent_qualified_name".to_string(), + projection_atom_identity_node(ctx, &id.parent.qualified_name), + ), + ( + "variant_name".to_string(), + projection_atom_identity_node(ctx, &id.arm.name), + ), + ( + "parent_type".to_string(), + marshal_declaration_identity_node(ctx, &id.parent), + ), + ( + "arm".to_string(), + marshal_declaration_identity_node(ctx, &id.arm), + ), + ], + ) +} + +fn marshal_constructor_identity_node( + ctx: &InterpContext, + parent: &ResolvedDeclarationLocator, + variant: &ResolvedVariantLocator, +) -> Value { + projection_node_with_named_edges( + ctx, + "DataInitializerConstructorIdentityProjection", + &[ + ( + "parent_type".to_string(), + marshal_declaration_identity_node(ctx, parent), + ), + ( + "constructor".to_string(), + marshal_variant_identity_node(ctx, variant), + ), + ], + ) +} + +fn marshal_value_identity_node( + ctx: &InterpContext, + resolution: &DataInitializerValueResolution, +) -> Value { + match resolution { + DataInitializerValueResolution::Resolved(v) => projection_node_with_named_edges( + ctx, + "ResolvedVariantValueProjection", + &[ + ( + "parent_qualified_name".to_string(), + projection_atom_identity_node(ctx, &v.parent.qualified_name), + ), + ( + "variant_name".to_string(), + projection_atom_identity_node(ctx, &v.arm.name), + ), + ( + "parent_type".to_string(), + marshal_declaration_identity_node(ctx, &v.parent), + ), + ( + "arm".to_string(), + marshal_declaration_identity_node(ctx, &v.arm), + ), + ], + ), + DataInitializerValueResolution::NotVariantValue => { + projection_node_with_named_edges(ctx, "NotVariantValueProjection", &[]) + } + DataInitializerValueResolution::Missing => { + projection_node_with_named_edges(ctx, "VariantValueResolutionMissingProjection", &[]) + } + DataInitializerValueResolution::Ambiguous(cands) => { + let mut edges: Vec<(String, Value)> = Vec::new(); + for (i, c) in cands.iter().enumerate() { + edges.push(( + format!("candidate_{i}"), + marshal_variant_identity_node(ctx, c), + )); + } + projection_node_with_named_edges( + ctx, + "VariantValueResolutionAmbiguousProjection", + &edges, + ) + } + } +} + +fn inferred_field_type_bare(si: &SourceIndices, field_init: &Rc) -> Option { + match field_init.inferred.as_deref() { + Some(InferredNode::Resolved { node: ty, .. }) => { + let name = authored_name_at(si.clone(), normalize_access_type_node(ty.clone())); + if name.is_empty() { + None + } else { + Some(name) + } + } + _ => None, + } +} + +fn marshal_record_literal_projection( + ctx: &InterpContext, + importing_module: &str, + body: &Rc, + declared_type_bare: &str, + si: &SourceIndices, + tm: &TypedModule, +) -> Value { + let type_item = match type_item_from_importing_module_type_env(tm, declared_type_bare, si) { + Some(node) => node, + None => return constructor_resolution_refused_projection(ctx), + }; + // Classification uses the importing module type env + resolved connective only. + // A failed lookup must refuse — never fall through to the coproduct arm. + if type_item.connective == Connective::Disj { + marshal_coproduct_record_projection( + ctx, + importing_module, + body, + declared_type_bare, + si, + type_item, + ) + } else { + marshal_plain_record_projection( + ctx, + importing_module, + body, + declared_type_bare, + si, + type_item, + ) + } +} + +fn marshal_field_initializer_projection( + ctx: &InterpContext, + importing_module: &str, + field_value: &Rc, + si: &SourceIndices, + declared_type_override: Option<&str>, +) -> Value { + if matches!( + field_value.inferred.as_deref(), + Some(InferredNode::CompilerError { .. }) + ) { + return marshal_value_identity_node(ctx, &DataInitializerValueResolution::Missing); + } + + match field_value.expr_data.as_ref() { + ExprData::ExprRecordLit { .. } => { + if let Some(override_bare) = declared_type_override.filter(|bare| !bare.is_empty()) { + let tm = typed_module_for_path(ctx, importing_module); + if let Some(tm) = tm { + let coproduct = + coproduct_type_item_with_variant_children(ctx, &tm, override_bare, si) + .map(|(node, _)| node); + if let Some(coproduct) = + coproduct.filter(|node| node.connective == Connective::Disj) + { + let variant_name = coproduct_record_lit_variant_name(si, field_value); + if !variant_name.is_empty() + && !coproduct_has_variant_named(&coproduct, &variant_name, si) + { + return marshal_value_identity_node( + ctx, + &DataInitializerValueResolution::Missing, + ); + } + } + } + } + let type_bare = declared_type_override + .filter(|bare| !bare.is_empty()) + .map(|bare| bare.to_string()) + .or(inferred_field_type_bare(si, field_value)) + .unwrap_or_default(); + if type_bare.is_empty() { + return marshal_value_identity_node( + ctx, + &DataInitializerValueResolution::NotVariantValue, + ); + } + let tm = match typed_module_for_path(ctx, importing_module) { + Some(tm) => tm, + None => return constructor_resolution_refused_projection(ctx), + }; + marshal_record_literal_projection( + ctx, + importing_module, + field_value, + &type_bare, + si, + &tm, + ) + } + _ => { + let type_bare = inferred_field_type_bare(si, field_value); + let resolution = variant_value_from_typechecked_expr( + ctx, + importing_module, + field_value, + si, + type_bare.as_deref(), + ); + marshal_value_identity_node(ctx, &resolution) + } + } +} + +fn marshal_plain_record_projection( + ctx: &InterpContext, + importing_module: &str, + body: &Rc, + type_bare: &str, + si: &SourceIndices, + type_item: Rc, +) -> Value { + if type_item.connective == Connective::Disj { + return constructor_resolution_refused_projection(ctx); + } + let parent_id = declaration_identity_for_type_item(ctx, &type_item, si, importing_module); + + let mut edges: Vec<(String, Value)> = vec![( + "parent_type".to_string(), + marshal_declaration_identity_node(ctx, &parent_id), + )]; + + for child in body.children.iter() { + let field_name = field_init_node_name_at(child.clone(), si.clone()); + if field_name.is_empty() { + continue; + } + let field_value = field_init_node_value(child.clone()); + let declared_field = declared_field_type_bare_on_conj(&type_item, &field_name, si); + let field_projection = marshal_field_initializer_projection( + ctx, + importing_module, + &field_value, + si, + declared_field.as_deref(), + ); + edges.push((field_name.clone(), field_projection)); + } + + projection_node_with_named_edges(ctx, "DataInitializerPlainRecordProjection", &edges) +} + +fn marshal_coproduct_record_projection( + ctx: &InterpContext, + importing_module: &str, + body: &Rc, + declared_type_bare: &str, + si: &SourceIndices, + coproduct_item: Rc, +) -> Value { + if coproduct_item.connective != Connective::Disj { + return constructor_resolution_refused_projection(ctx); + } + let variant_name = coproduct_record_lit_variant_name(si, body); + if variant_name.is_empty() || !coproduct_has_variant_named(&coproduct_item, &variant_name, si) { + return marshal_value_identity_node(ctx, &DataInitializerValueResolution::Missing); + } + + let parent_id = declaration_identity_for_type_item(ctx, &coproduct_item, si, importing_module); + let variant_id = match declaration_identity_for_variant_arm( + ctx, + &coproduct_item, + &variant_name, + si, + importing_module, + ) { + Some(variant_id) => variant_id, + None => ResolvedVariantLocator { + parent: parent_id.clone(), + arm: ResolvedDeclarationLocator { + qualified_name: format!("{}.{}", parent_id.qualified_name, variant_name), + name: variant_name.clone(), + module_path: parent_id.module_path.clone(), + rel_path: parent_id.rel_path.clone(), + }, + }, + }; + + let mut edges: Vec<(String, Value)> = vec![( + "constructor_identity".to_string(), + marshal_constructor_identity_node(ctx, &parent_id, &variant_id), + )]; + + for child in body.children.iter() { + let field_name = field_init_node_name_at(child.clone(), si.clone()); + if field_name.is_empty() { + continue; + } + let field_value = field_init_node_value(child.clone()); + let declared_field = declared_field_type_on_coproduct_variant_parse_tree( + ctx, + declared_type_bare, + &variant_name, + &field_name, + si, + ) + .or_else(|| { + declared_field_type_bare_on_coproduct_variant( + &coproduct_item, + &variant_name, + &field_name, + si, + ) + }) + .or_else(|| { + body.inferred + .as_ref() + .and_then(|inf| inferred_to_node(inf.clone())) + .and_then(|node| declared_field_type_bare_on_conj(&node, &field_name, si)) + }); + let field_projection = marshal_field_initializer_projection( + ctx, + importing_module, + &field_value, + si, + declared_field.as_deref(), + ); + edges.push((field_name.clone(), field_projection)); + } + + projection_node_with_named_edges(ctx, "DataInitializerRecordProjection", &edges) +} + +pub fn marshal_data_initializer_projection_for_item( + ctx: &InterpContext, + item: Rc, + qualified_name: &str, +) -> InterpResult { + let si = ctx.source_indices.clone(); + let importing_module = { + let decl_name = authored_name_at(si.clone(), item.clone()); + let suffix = format!(".{decl_name}"); + if qualified_name.ends_with(&suffix) { + qualified_name[..qualified_name.len() - suffix.len()].to_string() + } else { + qualified_name.to_string() + } + }; + + let body = match item.body.as_ref() { + Some(body) => body, + None => return Ok(constructor_resolution_refused_projection(ctx)), + }; + + let declared_type_bare = match declared_type_bare_name(&item, &si) { + Some(name) => name, + None => return Ok(constructor_resolution_refused_projection(ctx)), + }; + + let tm = match typed_module_for_path(ctx, &importing_module) { + Some(tm) => tm, + None => return Ok(constructor_resolution_refused_projection(ctx)), + }; + + match body.expr_data.as_ref() { + ExprData::ExprRecordLit { .. } => Ok(marshal_record_literal_projection( + ctx, + &importing_module, + body, + &declared_type_bare, + &si, + &tm, + )), + ExprData::ExprVar { .. } => { + if lookup_type_binding_in_importing_module(&tm, &declared_type_bare).is_none() { + return Ok(constructor_resolution_refused_projection(ctx)); + } + let resolution = variant_value_from_typechecked_expr( + ctx, + &importing_module, + body, + &si, + Some(&declared_type_bare), + ); + let mut edges: Vec<(String, Value)> = vec![( + "value_identity".to_string(), + marshal_value_identity_node(ctx, &resolution), + )]; + if let DataInitializerValueResolution::Resolved(v) = &resolution { + if let Some(type_item) = + type_item_from_importing_module_type_env(&tm, &declared_type_bare, &si) + { + let parent_id = + declaration_identity_for_type_item(ctx, &type_item, &si, &importing_module); + edges.push(( + "constructor_identity".to_string(), + marshal_constructor_identity_node(ctx, &parent_id, v), + )); + } + } + Ok(projection_node_with_named_edges( + ctx, + "DataInitializerNullaryProjection", + &edges, + )) + } + _ => Ok(marshal_value_identity_node( + ctx, + &DataInitializerValueResolution::NotVariantValue, + )), + } +} + +pub fn marshal_data_initializer_projection( + ctx: &InterpContext, + qualified_name: &str, +) -> InterpResult { + match ctx.lookup_fn_node(qualified_name) { + Some(item) => marshal_data_initializer_projection_for_item(ctx, item, qualified_name), + None => { + let bare = bare_symbol_tail(qualified_name); + if ctx + .item_registry + .get(bare) + .is_some_and(|info| info.kind == ItemKind::DataItem) + { + Ok(constructor_resolution_refused_projection(ctx)) + } else { + Ok(typechecked_subject_absent_projection(ctx)) + } + } + } +} + +pub fn typechecked_subject_absent_projection(ctx: &InterpContext) -> Value { + projection_node_with_named_edges( + ctx, + "DataInitializerTypecheckedSubjectAbsentProjection", + &[], + ) +} + +#[cfg(test)] +mod projection_marshal_tests { + use std::rc::Rc; + + use im::{vector as im_vec, HashMap}; + + use crate::v1_compiler_infer_emit_info::empty_emit_graph_info; + use crate::v1_compiler_infer_items::ResolvedGraph; + use crate::v1_interpreter::{ExecutionMode, InterpContext, Value}; + + use super::{marshal_data_initializer_projection, typechecked_subject_absent_projection}; + + fn empty_ctx() -> InterpContext { + let graph = ResolvedGraph { + modules: Rc::new(im_vec![]), + item_registry: Rc::new(HashMap::new()), + diagnostics: Rc::new(im_vec![]), + emit_graph_info: empty_emit_graph_info(), + }; + InterpContext::new(&graph, Rc::new(HashMap::new()), ExecutionMode::Hermetic) + } + + fn projection_kind_lexeme(ctx: &InterpContext, projection: &Value) -> Option { + match projection { + Value::Record { fields, .. } => { + let kind_key = ctx.sym("kind"); + let connective_key = ctx.sym("connective"); + let identity_key = ctx.sym("identity"); + let kind = fields + .iter() + .find(|(k, _)| *k == kind_key) + .map(|(_, v)| v)?; + match kind { + Value::Variant { + fields: kind_fields, + .. + } => { + let connective = kind_fields + .iter() + .find(|(k, _)| *k == connective_key) + .map(|(_, v)| v)?; + match connective { + Value::Variant { + fields: conn_fields, + .. + } => conn_fields + .iter() + .find(|(k, _)| *k == identity_key) + .and_then(|(_, v)| match v { + Value::Str(s) => Some(s.clone()), + _ => None, + }), + _ => None, + } + } + _ => None, + } + } + _ => None, + } + } + + #[test] + fn missing_typechecked_subject_marshals_absent_projection_not_error() { + let ctx = empty_ctx(); + let projection = marshal_data_initializer_projection( + &ctx, + "extdeps.units.dimensionless.extdeps_external_authority_anchor", + ) + .expect("unresolvable marshal subject must refuse with projection, never throw"); + let absent = typechecked_subject_absent_projection(&ctx); + assert_eq!( + projection_kind_lexeme(&ctx, &projection), + projection_kind_lexeme(&ctx, &absent), + "missing typechecked subject must marshal TypecheckedSubjectAbsentProjection" + ); + } +} diff --git a/src/v1/stage0/src/gunbc_stage0_crate_layout_generated.rs b/src/v1/stage0/src/gunbc_stage0_crate_layout_generated.rs index 4c57c311026..2247ef0cc67 100644 --- a/src/v1/stage0/src/gunbc_stage0_crate_layout_generated.rs +++ b/src/v1/stage0/src/gunbc_stage0_crate_layout_generated.rs @@ -21,7 +21,7 @@ pub fn stage0_crate_layout_generated_note() -> String { pub fn generated_pub_mod_block() -> String { thread_local! { static CACHED: String = { - "\n#[macro_use]\npub mod v1_interpreter_dispatch_generated;\npub mod v1_interpreter;\npub mod ci_control_plane;\npub mod cli_run;\npub mod coproduct_reflection;\npub mod resolved_graph_cache;\npub mod shared_typecheck_store;\npub mod recorded_fixture;\npub mod memory_governor;\npub mod module_path_index;\npub mod usv_pilot_v2_std_algebra;\npub mod usv_pilot_v2_std_collection;\npub mod usv_pilot_v2_std_node;\npub mod v2_compiler_resolve;\npub mod v2_compiler_program_partition;\npub mod v2_compiler_tokenize;\npub mod v2_compiler_infer;".to_string() + "\n#[macro_use]\npub mod v1_interpreter_dispatch_generated;\npub mod v1_interpreter;\npub mod ci_control_plane;\npub mod cli_run;\npub mod coproduct_reflection;\npub mod data_initializer_identity;\npub mod resolved_graph_cache;\npub mod shared_typecheck_store;\npub mod recorded_fixture;\npub mod memory_governor;\npub mod module_path_index;\npub mod usv_pilot_v2_std_algebra;\npub mod usv_pilot_v2_std_collection;\npub mod usv_pilot_v2_std_node;\npub mod v2_compiler_resolve;\npub mod v2_compiler_program_partition;\npub mod v2_compiler_tokenize;\npub mod v2_compiler_infer;".to_string() }; } CACHED.with(|c: &String| c.clone()) @@ -30,7 +30,7 @@ pub fn generated_pub_mod_block() -> String { pub fn generated_pub_mod_basenames() -> Rc> { thread_local! { static CACHED: Rc> = { - Rc::new(vec!["v1_interpreter".to_string(), "ci_control_plane".to_string(), "cli_run".to_string(), "coproduct_reflection".to_string(), "resolved_graph_cache".to_string(), "shared_typecheck_store".to_string(), "recorded_fixture".to_string(), "memory_governor".to_string(), "module_path_index".to_string(), "usv_pilot_v2_std_algebra".to_string(), "usv_pilot_v2_std_collection".to_string(), "usv_pilot_v2_std_node".to_string(), "v2_compiler_resolve".to_string(), "v2_compiler_program_partition".to_string(), "v2_compiler_tokenize".to_string(), "v2_compiler_infer".to_string(), "v1_interpreter_dispatch_generated".to_string()]) + Rc::new(vec!["v1_interpreter".to_string(), "ci_control_plane".to_string(), "cli_run".to_string(), "coproduct_reflection".to_string(), "data_initializer_identity".to_string(), "resolved_graph_cache".to_string(), "shared_typecheck_store".to_string(), "recorded_fixture".to_string(), "memory_governor".to_string(), "module_path_index".to_string(), "usv_pilot_v2_std_algebra".to_string(), "usv_pilot_v2_std_collection".to_string(), "usv_pilot_v2_std_node".to_string(), "v2_compiler_resolve".to_string(), "v2_compiler_program_partition".to_string(), "v2_compiler_tokenize".to_string(), "v2_compiler_infer".to_string(), "v1_interpreter_dispatch_generated".to_string()]) }; } CACHED.with(|c: &Rc>| c.clone()) @@ -39,7 +39,7 @@ pub fn generated_pub_mod_basenames() -> Rc> { pub fn generated_stage0_filenames() -> Rc> { thread_local! { static CACHED: Rc> = { - Rc::new(vec!["v1_interpreter.rs".to_string(), "ci_control_plane.rs".to_string(), "cli_run.rs".to_string(), "coproduct_reflection.rs".to_string(), "resolved_graph_cache.rs".to_string(), "shared_typecheck_store.rs".to_string(), "recorded_fixture.rs".to_string(), "main.rs".to_string(), "phase_profile.rs".to_string(), "pre_push.rs".to_string(), "census_exclude_derive.rs".to_string(), "memory_governor.rs".to_string(), "std_lens_verdict.rs".to_string(), "v2_compiler_body_producer.rs".to_string(), "v2_compiler_normalize.rs".to_string(), "v2_compiler_target_carriers.rs".to_string(), "v2_compiler_discovery_enumeration.rs".to_string(), "v2_compiler_parse_engine_hooks.rs".to_string(), "v2_compiler_use_site_verdict.rs".to_string(), "usv_pilot_v2_std_algebra.rs".to_string(), "usv_pilot_v2_std_collection.rs".to_string(), "usv_pilot_v2_std_node.rs".to_string(), "v2_compiler_resolve.rs".to_string(), "v2_compiler_program_partition.rs".to_string(), "v2_compiler_tokenize.rs".to_string(), "v2_compiler_infer.rs".to_string(), "v1_interpreter_dispatch_generated.rs".to_string()]) + Rc::new(vec!["v1_interpreter.rs".to_string(), "ci_control_plane.rs".to_string(), "cli_run.rs".to_string(), "coproduct_reflection.rs".to_string(), "data_initializer_identity.rs".to_string(), "resolved_graph_cache.rs".to_string(), "shared_typecheck_store.rs".to_string(), "recorded_fixture.rs".to_string(), "main.rs".to_string(), "phase_profile.rs".to_string(), "pre_push.rs".to_string(), "census_exclude_derive.rs".to_string(), "memory_governor.rs".to_string(), "std_lens_verdict.rs".to_string(), "v2_compiler_body_producer.rs".to_string(), "v2_compiler_normalize.rs".to_string(), "v2_compiler_target_carriers.rs".to_string(), "v2_compiler_discovery_enumeration.rs".to_string(), "v2_compiler_parse_engine_hooks.rs".to_string(), "v2_compiler_use_site_verdict.rs".to_string(), "usv_pilot_v2_std_algebra.rs".to_string(), "usv_pilot_v2_std_collection.rs".to_string(), "usv_pilot_v2_std_node.rs".to_string(), "v2_compiler_resolve.rs".to_string(), "v2_compiler_program_partition.rs".to_string(), "v2_compiler_tokenize.rs".to_string(), "v2_compiler_infer.rs".to_string(), "v1_interpreter_dispatch_generated.rs".to_string()]) }; } CACHED.with(|c: &Rc>| c.clone()) diff --git a/src/v1/stage0/src/lib.rs b/src/v1/stage0/src/lib.rs index 5932d69349d..479a64c3331 100644 --- a/src/v1/stage0/src/lib.rs +++ b/src/v1/stage0/src/lib.rs @@ -156,6 +156,7 @@ pub mod v1_interpreter_dispatch_generated; pub mod ci_control_plane; pub mod cli_run; pub mod coproduct_reflection; +pub mod data_initializer_identity; pub mod memory_governor; pub mod module_path_index; pub mod recorded_fixture; diff --git a/src/v1/stage0/src/module_path_index/parsed_dag_file.rs b/src/v1/stage0/src/module_path_index/parsed_dag_file.rs index cdf4a3a29e3..50643ed6246 100644 --- a/src/v1/stage0/src/module_path_index/parsed_dag_file.rs +++ b/src/v1/stage0/src/module_path_index/parsed_dag_file.rs @@ -11,6 +11,7 @@ type SourceIndices = Rc>>; /// Parse-only module items for one `.dag` file (no resolve). Shared substrate for /// `decl_facts(roots)` (#5966) and emit-only corpus audits. pub struct ParsedDagFile { + pub module: Rc, pub items: Rc>>, pub source_indices: SourceIndices, } @@ -29,6 +30,7 @@ pub fn parse_dag_file(path: &Path) -> Option { } let module = result.module.as_ref()?; Some(ParsedDagFile { + module: module.clone(), items: module.children.clone(), source_indices, }) diff --git a/src/v1/stage0/src/v1_interpreter.rs b/src/v1/stage0/src/v1_interpreter.rs index 827c8a2d51a..22e6cc54947 100644 --- a/src/v1/stage0/src/v1_interpreter.rs +++ b/src/v1/stage0/src/v1_interpreter.rs @@ -16,6 +16,7 @@ use crate::cli_run::value_to_wire_json; use crate::std_syntax::BinOp; use crate::std_syntax::LiteralValue; use crate::v1_compiler_emit::{extract_string_interp_parts, has_mock_prefix}; +use crate::v1_compiler_infer_emit_info::EmitGraphInfo; use crate::v1_compiler_infer_items::{item_kind, ItemInfo, ItemKind, ResolvedGraph, TypedModule}; use crate::v1_rt; use crate::v1_rt::{ @@ -1436,6 +1437,7 @@ pub struct InterpContext { pub modules: Rc>>, pub item_registry: Rc>>, pub source_indices: Rc>>, + pub emit_graph_info: Rc, fn_nodes: HashMap>, ambiguous_bare_function_names: std::collections::HashSet, service_ops: HashMap, @@ -1443,7 +1445,7 @@ pub struct InterpContext { pub fixture_store: Option>, data_cache: std::cell::RefCell>, // Per-call parameter-name derivation is invariant per fn_node but was re-sliced from - // source spans on every call (authored_name_at). Memoize it per ctx, keyed by fn_node + // source spans on every call (authored_name_at). Memoize it per fn_node, keyed by fn_node // pointer identity. The key alone is not sound: the ctx does not own fn_nodes (they are // borrowed `Rc`s that can be dropped while the ctx lives), so a freed node's address // can be reused by an unrelated later node and silently collide on this key. keepalive_fns @@ -1678,6 +1680,7 @@ impl InterpContext { modules: graph.modules.clone(), item_registry: graph.item_registry.clone(), source_indices, + emit_graph_info: graph.emit_graph_info.clone(), fn_nodes, ambiguous_bare_function_names, service_ops, @@ -1812,6 +1815,19 @@ impl InterpContext { self.fn_nodes.get(name) } + pub fn lookup_fn_node(&self, qualified_name: &str) -> Option> { + self.fn_nodes.get(qualified_name).cloned() + } + + pub fn resolved_graph(&self) -> ResolvedGraph { + ResolvedGraph { + modules: self.modules.clone(), + item_registry: self.item_registry.clone(), + diagnostics: Rc::new(im::Vector::new()), + emit_graph_info: self.emit_graph_info.clone(), + } + } + /// Report identity from the exact fn_nodes entry used by lookup_fn. The /// module path comes from the existing collision-checked module index; this /// accessor does not select, resolve, traverse the graph, or alter lookup. @@ -1897,12 +1913,9 @@ pub fn run_in_context_with_args( } /// Peak parent-chain depth observed across `call_function` frames in the last -/// `run_in_context*` invocation (test witness for lexical-base scoping; default-off until armed). +/// `run_in_context*` invocation (test witness for lexical-base scoping). #[cfg(any(test, feature = "interp_test_witness"))] pub fn call_env_depth_peak_snapshot() -> usize { - if !call_env_depth_witness_enabled() { - return 0; - } CALL_ENV_DEPTH_PEAK.with(|peak| peak.get()) } @@ -2048,8 +2061,10 @@ fn call_function_inner( let cached_params = { let key = Rc::as_ptr(fn_node) as usize; - let existing = ctx.param_name_cache.borrow().get(&key).cloned(); - if let Some(c) = existing { + // Bind the lookup to a local so the immutable borrow is released before the + // None branch takes a mutable borrow (an `if let ...borrow()` would hold it through `else`). + let hit = ctx.param_name_cache.borrow().get(&key).cloned(); + if let Some(c) = hit { c } else { // Each param's authored name is sliced once into `all`; `filtered` reuses it @@ -2069,12 +2084,12 @@ fn call_function_inner( }) .map(|(i, _)| all[i].clone()) .collect(); - let fresh = Rc::new((filtered, all)); + let c = Rc::new((filtered, all)); ctx.param_name_cache_keepalive .borrow_mut() .push(fn_node.clone()); - ctx.param_name_cache.borrow_mut().insert(key, fresh.clone()); - fresh + ctx.param_name_cache.borrow_mut().insert(key, c.clone()); + c } }; let param_names: &Vec = &cached_params.0; @@ -2443,8 +2458,8 @@ fn eval_var( ) -> InterpResult { // Resolve and intern this ExprVar's name once, then reuse the Symbol on every eval // (skips the per-eval source-span slice in expr_var_name_at and the ctx.sym re-intern). - let key = Rc::as_ptr(node) as usize; let sym = { + let key = Rc::as_ptr(node) as usize; let existing = ctx.var_sym_cache.borrow().get(&key).copied(); if let Some(s) = existing { s diff --git a/src/v1/tests/src/decl_facts_dimensionless_projection_test.rs b/src/v1/tests/src/decl_facts_dimensionless_projection_test.rs new file mode 100644 index 00000000000..71f14dbd955 --- /dev/null +++ b/src/v1/tests/src/decl_facts_dimensionless_projection_test.rs @@ -0,0 +1,263 @@ +//! Source-order invariance for decl_facts initializer projection marshalling. +//! +//! Merge-blocking projection behavior lives in `dag/test/claim/decl_facts_initializer_projection_witness_test.dag`. +//! This module retains only the seam those witnesses cannot reach: `compile_to_resolved` source-file +//! order must not change projection kind or constructor parent identity for the same specimen QN. + +use std::rc::Rc; + +use im::HashMap; + +use v1_compiler::data_initializer_identity::marshal_data_initializer_projection; +use v1_compiler::v1_compiler_compile::{compile_to_resolved, SourceFile}; +use v1_compiler::v1_interpreter::{ExecutionMode, InterpContext, Value}; + +use crate::helpers::{ + resolve_imports_transitively_with_source_roots, v2_layer_roots, workspace_root, +}; + +const SPECIMENS_REL: &str = "dag/test/fixture/decl_facts_reflection/specimens.dag"; +const DISPOSITION_SCAFFOLD_QN: &str = + "test.fixture.decl_facts_reflection.specimens.disposition_scaffold"; +const LOCAL_A_SCAFFOLD_QN: &str = "test.fixture.decl_facts_reflection.specimens.local_a_scaffold"; +const AMBIGUOUS_ARM_SPECIMEN_QN: &str = + "test.fixture.decl_facts_reflection.ambiguous_specimen.ambiguous_arm_specimen"; +const AMBIGUOUS_SHARED_A_SHARED_BARE_ARM_QN: &str = + "test.fixture.decl_facts_reflection.ambiguous_shared_a.SharedBareArm"; +const WITNESS_SUPPORT_REL: &str = "dag/test/claim/decl_facts_reflection_witness_support.dag"; + +fn read_fixture(rel: &str) -> String { + std::fs::read_to_string(workspace_root().join(rel)) + .unwrap_or_else(|e| panic!("read {rel}: {e}")) +} + +fn ctx_from_sources(sources: Vec>) -> InterpContext { + let resolved = compile_to_resolved(Rc::new(sources.into())); + let graph = resolved + .graph + .as_ref() + .expect("fixture closure resolves to a graph"); + InterpContext::new(graph, Rc::new(HashMap::new()), ExecutionMode::Hermetic) +} + +fn specimens_ctx_with_source_order(reversed: bool) -> InterpContext { + let mut sources: Vec> = resolve_imports_transitively_with_source_roots( + SPECIMENS_REL, + &read_fixture(SPECIMENS_REL), + &v2_layer_roots(), + ); + if reversed { + sources.reverse(); + } + ctx_from_sources(sources) +} + +fn projection_kind_lexeme(ctx: &InterpContext, projection: &Value) -> Option { + match projection { + Value::Record { fields, .. } => { + let kind_key = ctx.sym("kind"); + let connective_key = ctx.sym("connective"); + let identity_key = ctx.sym("identity"); + let kind = fields + .iter() + .find(|(k, _)| *k == kind_key) + .map(|(_, v)| v)?; + match kind { + Value::Variant { + fields: kind_fields, + .. + } => { + let connective = kind_fields + .iter() + .find(|(k, _)| *k == connective_key) + .map(|(_, v)| v)?; + match connective { + Value::Variant { + fields: conn_fields, + .. + } => conn_fields + .iter() + .find(|(k, _)| *k == identity_key) + .and_then(|(_, v)| match v { + Value::Str(s) => Some(s.clone()), + _ => None, + }), + _ => None, + } + } + _ => None, + } + } + _ => None, + } +} + +fn edge_target_named(ctx: &InterpContext, projection: &Value, label: &str) -> Option { + match projection { + Value::Record { fields, .. } => { + let children = match ctx.field(fields, "children") { + Some(Value::List(items)) => items, + _ => return None, + }; + for edge in children.iter() { + match edge { + Value::Record { + fields: edge_fields, + .. + } => { + let edge_label = match ctx.field(edge_fields, "label") { + Some(Value::Variant { + variant_name, + fields: label_fields, + .. + }) => { + if *variant_name != ctx.sym("Named") { + continue; + } + match ctx.field(label_fields, "name") { + Some(Value::Str(s)) => s.as_str(), + _ => continue, + } + } + _ => continue, + }; + if edge_label != label { + continue; + } + return ctx.field(edge_fields, "target").cloned().map(|v| v.clone()); + } + _ => {} + } + } + None + } + _ => None, + } +} + +fn declaration_identity_qualified_name(ctx: &InterpContext, projection: &Value) -> Option { + let qn_projection = edge_target_named(ctx, projection, "qualified_name")?; + projection_kind_lexeme(ctx, &qn_projection) +} + +fn constructor_parent_qualified_name(ctx: &InterpContext, projection: &Value) -> Option { + let ctor = edge_target_named(ctx, projection, "constructor_identity")?; + let parent = edge_target_named(ctx, &ctor, "parent_type")?; + declaration_identity_qualified_name(ctx, &parent) +} + +#[test] +fn eval_decl_facts_explicit_import_resolves_unique_variant_projection() { + use v1_compiler::coproduct_reflection::eval_decl_facts; + + let sources = resolve_imports_transitively_with_source_roots( + WITNESS_SUPPORT_REL, + &read_fixture(WITNESS_SUPPORT_REL), + &v2_layer_roots(), + ); + let ctx = ctx_from_sources(sources); + let roots = vec![workspace_root() + .join("dag/test/fixture/decl_facts_reflection") + .to_string_lossy() + .into_owned()]; + let facts = eval_decl_facts(&ctx, &roots).expect("decl_facts must marshal"); + match facts { + Value::List(rows) => { + let node = rows + .iter() + .find_map(|row| { + let fields = match row { + Value::Record { fields, .. } => fields, + _ => return None, + }; + let qn = ctx.field(fields, "qualified_name").and_then(|v| match v { + Value::Str(s) => Some(s.clone()), + _ => None, + })?; + if qn != AMBIGUOUS_ARM_SPECIMEN_QN { + return None; + } + ctx.field(fields, "node") + }) + .expect("ambiguous specimen row"); + let projection = marshal_data_initializer_projection(&ctx, AMBIGUOUS_ARM_SPECIMEN_QN) + .expect("direct marshal"); + let value_identity = edge_target_named(&ctx, &projection, "value_identity") + .expect("value_identity edge"); + assert_eq!( + projection_kind_lexeme(&ctx, &value_identity), + Some("ResolvedVariantValueProjection".to_string()), + "direct marshal value_identity kind" + ); + let fact_value_identity = + edge_target_named(&ctx, node, "value_identity").expect("decl_facts value_identity"); + assert_eq!( + projection_kind_lexeme(&ctx, &fact_value_identity), + Some("ResolvedVariantValueProjection".to_string()), + "decl_facts value_identity must resolve to explicit import" + ); + assert_eq!( + constructor_parent_qualified_name(&ctx, &projection), + Some(AMBIGUOUS_SHARED_A_SHARED_BARE_ARM_QN.to_string()), + "constructor parent must be module A SharedBareArm" + ); + assert_eq!( + projection_kind_lexeme(&ctx, node), + projection_kind_lexeme(&ctx, &projection), + "decl_facts node must match direct marshal top-level kind" + ); + } + _ => panic!("expected list"), + } +} + +#[test] +fn explicit_import_resolves_unique_variant_projection() { + let sources = resolve_imports_transitively_with_source_roots( + WITNESS_SUPPORT_REL, + &read_fixture(WITNESS_SUPPORT_REL), + &v2_layer_roots(), + ); + let ctx = ctx_from_sources(sources); + let projection = marshal_data_initializer_projection(&ctx, AMBIGUOUS_ARM_SPECIMEN_QN) + .expect("ambiguous specimen must marshal without error"); + assert_eq!( + projection_kind_lexeme(&ctx, &projection), + Some("DataInitializerNullaryProjection".to_string()), + "top-level projection kind" + ); + let value_identity = edge_target_named(&ctx, &projection, "value_identity") + .expect("nullary projection must carry value_identity"); + assert_eq!( + projection_kind_lexeme(&ctx, &value_identity), + Some("ResolvedVariantValueProjection".to_string()), + "explicit SharedBareArm import from ambiguous_shared_a must resolve uniquely" + ); + assert_eq!( + constructor_parent_qualified_name(&ctx, &projection), + Some(AMBIGUOUS_SHARED_A_SHARED_BARE_ARM_QN.to_string()), + "constructor parent must be the explicitly imported SharedBareArm from module A" + ); +} + +#[test] +fn marshal_identity_is_invariant_under_reversed_source_order() { + let forward = specimens_ctx_with_source_order(false); + let reversed = specimens_ctx_with_source_order(true); + for qn in [DISPOSITION_SCAFFOLD_QN, LOCAL_A_SCAFFOLD_QN] { + let forward_projection = marshal_data_initializer_projection(&forward, qn) + .unwrap_or_else(|e| panic!("forward marshal {qn}: {e}")); + let reversed_projection = marshal_data_initializer_projection(&reversed, qn) + .unwrap_or_else(|e| panic!("reversed marshal {qn}: {e}")); + assert_eq!( + projection_kind_lexeme(&forward, &forward_projection), + projection_kind_lexeme(&reversed, &reversed_projection), + "projection kind must not depend on source file order for {qn}" + ); + assert_eq!( + constructor_parent_qualified_name(&forward, &forward_projection), + constructor_parent_qualified_name(&reversed, &reversed_projection), + "constructor parent identity must not depend on source file order for {qn}" + ); + } +} diff --git a/src/v1/tests/src/lib.rs b/src/v1/tests/src/lib.rs index caa7f5187ed..5b2bca0852e 100644 --- a/src/v1/tests/src/lib.rs +++ b/src/v1/tests/src/lib.rs @@ -34,6 +34,8 @@ mod data_cache_scoping_test; #[cfg(test)] mod data_def_brand_alias_type_test; #[cfg(test)] +mod decl_facts_dimensionless_projection_test; +#[cfg(test)] mod dependency_pool_index_compile_test; #[cfg(test)] mod derive_bound_fail_closed_test; @@ -79,7 +81,6 @@ mod group_completion_construction_test; mod gunbhub_serve_program_test; #[cfg(test)] mod html_markup_smoke_test; -#[cfg(test)] mod import_visibility_pool_coincidence_test; #[cfg(test)] mod int_pow_bounded_test; @@ -123,7 +124,6 @@ mod namespace_alias_decl_test; mod namespace_unique_on_chain_policy_test; #[cfg(test)] mod nested_fold_generic_param_emit_test; -#[cfg(test)] mod nested_list_alias_emit_test; #[cfg(test)] mod node_keyed_graph_artifact_codec_test; diff --git a/src/v2/compiler/self_host/frontier.dag b/src/v2/compiler/self_host/frontier.dag index 9392f119bf7..713767b2487 100644 --- a/src/v2/compiler/self_host/frontier.dag +++ b/src/v2/compiler/self_host/frontier.dag @@ -1429,6 +1429,7 @@ data seed_retained_intrinsic_registrations: List = [ SeedRetainedSupportDirRegistration { basename: "cli_run" } ] diff --git a/src/v2/std/data_initializer_identity.dag b/src/v2/std/data_initializer_identity.dag new file mode 100644 index 00000000000..630700a13d3 --- /dev/null +++ b/src/v2/std/data_initializer_identity.dag @@ -0,0 +1,318 @@ +module v2.std.data_initializer_identity + +import v2.std.grammar { node_atom_identity_optional } +import v2.std.compilers.lexing { symbol_lexeme } +import v2.std.decl_index { DeclFact } +import v2.std.optional { optional_present, optional_absent } + +data data_initializer_identity_authority_note: String = "Resolved parent/arm projection for DataItem rows — qualified_name, module_path, rel_path, and name locator grain (not occurrence identity). Host bridge marshals projection Node trees from typechecked subjects; parse-pool decl_facts rows use PoolDeclarationIdentity only." + +fn projection_kind_lexeme_optional(node: Node) -> Optional { + match node.kind { + TypeNode { connective: Atom { identity: sym } } => + optional_present(value: symbol_lexeme(sym: sym)) + _ => optional_absent() + } +} + +fn projection_named_child_optional(node: Node, label: String) -> Optional { + fold(node_labeled_child_edges(node: node), init: optional_absent(), f: fn(acc, e) { + match acc { + Present { value: _ } => acc + Absent => + if named_edge_sort_key(e: e) == label { + optional_present(value: e.target) + } else { + optional_absent() + } + } + }) +} + +fn projection_atom_lexeme_optional(node: Node) -> Optional { + match node_atom_identity_optional(node: node) { + Present { value: sym } => optional_present(value: symbol_lexeme(sym: sym)) + Absent => optional_absent() + } +} + +fn projection_named_child_atom_lexeme(node: Node, label: String) -> Optional { + match projection_named_child_optional(node: node, label: label) { + Present { value: child } => projection_atom_lexeme_optional(node: child) + Absent => optional_absent() + } +} + +fn declaration_identity_qualified_name_optional(node: Node) -> Optional { + projection_named_child_atom_lexeme(node: node, label: "qualified_name") +} + +fn variant_declaration_parent_qualified_name_optional(node: Node) -> Optional { + projection_named_child_atom_lexeme(node: node, label: "parent_qualified_name") +} + +fn variant_declaration_name_optional(node: Node) -> Optional { + projection_named_child_atom_lexeme(node: node, label: "variant_name") +} + +fn record_constructor_parent_qualified_name_optional(record: Node) -> Optional { + match projection_named_child_optional(node: record, label: "constructor_identity") { + Present { value: ctor } => + match projection_named_child_optional(node: ctor, label: "parent_type") { + Present { value: parent } => declaration_identity_qualified_name_optional(node: parent) + Absent => optional_absent() + } + Absent => optional_absent() + } +} + +fn record_constructor_variant_name_optional(record: Node) -> Optional { + match projection_named_child_optional(node: record, label: "constructor_identity") { + Present { value: ctor } => + match projection_named_child_optional(node: ctor, label: "constructor") { + Present { value: variant } => variant_declaration_name_optional(node: variant) + Absent => optional_absent() + } + Absent => optional_absent() + } +} + +fn record_field_projection_optional(record: Node, label: String) -> Optional { + projection_named_child_optional(node: record, label: label) +} + +fn value_projection_is_resolved_variant(node: Node) -> Bool { + match projection_kind_lexeme_optional(node: node) { + Present { value: k } => k == "ResolvedVariantValueProjection" + Absent => false + } +} + +fn value_projection_is_missing(node: Node) -> Bool { + match projection_kind_lexeme_optional(node: node) { + Present { value: k } => k == "VariantValueResolutionMissingProjection" + Absent => false + } +} + +fn value_projection_is_ambiguous(node: Node) -> Bool { + match projection_kind_lexeme_optional(node: node) { + Present { value: k } => k == "VariantValueResolutionAmbiguousProjection" + Absent => false + } +} + +fn value_projection_is_not_variant_value(node: Node) -> Bool { + match projection_kind_lexeme_optional(node: node) { + Present { value: k } => k == "NotVariantValueProjection" + Absent => false + } +} + +fn resolved_variant_parent_qualified_name_optional(node: Node) -> Optional { + projection_named_child_atom_lexeme(node: node, label: "parent_qualified_name") +} + +fn resolved_variant_name_optional(node: Node) -> Optional { + projection_named_child_atom_lexeme(node: node, label: "variant_name") +} + +fn ambiguous_candidate_projection_count(node: Node) -> Int { + length( + xs: fold(node_labeled_child_edges(node: node), init: std.algebra.Empty, f: fn(acc, e) { + let key_lexeme = symbol_lexeme(sym: named_edge_sort_key(e: e)) + if key_lexeme.starts_with(prefix: "candidate_") { + list_snoc_item(xs: acc, item: e.target) + } else { + acc + } + }) + ) +} + +type PoolDeclarationIdentity { + qualified_name: String + name: String + module_path: String + rel_path: String +} + +type ResolvedDeclarationLocator { + qualified_name: String + name: String + module_path: String + rel_path: String +} + +fn resolved_declaration_locators_equal( + a: ResolvedDeclarationLocator, + b: ResolvedDeclarationLocator +) -> Bool { + a.qualified_name == b.qualified_name + && a.name == b.name + && a.module_path == b.module_path + && a.rel_path == b.rel_path +} + +type DeclFactLookup + = DeclFactMissing + | DeclFactUnique { fact: DeclFact } + | DeclFactAmbiguous { candidates: List } + +fn decl_fact_module_path_from_qualified_name(fact: DeclFact) -> String { + let suffix = concat(".", fact.name) + let qn_len = string_length(s: fact.qualified_name) + let suffix_len = string_length(s: suffix) + if qn_len >= suffix_len + && substring(s: fact.qualified_name, start: qn_len - suffix_len, end: qn_len) == suffix { + substring(s: fact.qualified_name, start: 0, end: qn_len - suffix_len) + } else { + "" + } +} + +fn pool_declaration_identity_from_fact(fact: DeclFact) -> PoolDeclarationIdentity { + PoolDeclarationIdentity { + qualified_name: fact.qualified_name, + name: fact.name, + module_path: decl_fact_module_path_from_qualified_name(fact: fact), + rel_path: fact.rel_path, + } +} + +fn pool_declaration_identities_equal( + a: PoolDeclarationIdentity, + b: PoolDeclarationIdentity +) -> Bool { + a.qualified_name == b.qualified_name + && a.name == b.name + && a.module_path == b.module_path + && a.rel_path == b.rel_path +} + +fn decl_fact_matches_pool_declaration_identity( + fact: DeclFact, + identity: PoolDeclarationIdentity +) -> Bool { + pool_declaration_identities_equal( + a: pool_declaration_identity_from_fact(fact: fact), + b: identity + ) +} + +fn decl_fact_lookup_by_qualified_name(qn: String, facts: List) -> DeclFactLookup { + fold(facts, init: DeclFactMissing, f: fn(acc, fact) { + if fact.qualified_name != qn { + acc + } else { + match acc { + DeclFactMissing => DeclFactUnique { fact: fact } + DeclFactUnique { fact: first } => DeclFactAmbiguous { candidates: list_snoc_item(xs: list_snoc_item(xs: std.algebra.Empty, item: first), item: fact) } + DeclFactAmbiguous { candidates: xs } => DeclFactAmbiguous { candidates: list_snoc_item(xs: xs, item: fact) } + } + } + }) +} + +fn decl_fact_lookup_by_pool_declaration_identity( + identity: PoolDeclarationIdentity, + facts: List +) -> DeclFactLookup { + fold(facts, init: DeclFactMissing, f: fn(acc, fact) { + if !decl_fact_matches_pool_declaration_identity(fact: fact, identity: identity) { + acc + } else { + match acc { + DeclFactMissing => DeclFactUnique { fact: fact } + DeclFactUnique { fact: first } => + DeclFactAmbiguous { + candidates: list_snoc_item(xs: list_snoc_item(xs: std.algebra.Empty, item: first), item: fact) + } + DeclFactAmbiguous { candidates: xs } => + DeclFactAmbiguous { candidates: list_snoc_item(xs: xs, item: fact) } + } + } + }) +} + +fn decl_fact_optional_by_qualified_name(qn: String, facts: List) -> Optional { + match decl_fact_lookup_by_qualified_name(qn: qn, facts: facts) { + DeclFactUnique { fact: fact } => optional_present(value: fact) + DeclFactMissing => optional_absent() + DeclFactAmbiguous { candidates: _ } => optional_absent() + } +} + +fn decl_fact_lookup_is_missing(lookup: DeclFactLookup) -> Bool { + match lookup { + DeclFactMissing => true + DeclFactUnique { fact: _ } => false + DeclFactAmbiguous { candidates: _ } => false + } +} + +fn decl_fact_lookup_is_unique(lookup: DeclFactLookup) -> Bool { + match lookup { + DeclFactUnique { fact: _ } => true + DeclFactMissing => false + DeclFactAmbiguous { candidates: _ } => false + } +} + +fn decl_fact_lookup_is_ambiguous(lookup: DeclFactLookup) -> Bool { + match lookup { + DeclFactAmbiguous { candidates: _ } => true + DeclFactMissing => false + DeclFactUnique { fact: _ } => false + } +} + +fn decl_fact_lookup_candidate_count(lookup: DeclFactLookup) -> Int { + match lookup { + DeclFactAmbiguous { candidates: cands } => length(xs: cands) + DeclFactMissing => 0 + DeclFactUnique { fact: _ } => 0 + } +} + +fn data_initializer_is_constructor_resolution_refused_projection(node: Node) -> Bool { + match projection_kind_lexeme_optional(node: node) { + Present { value: k } => k == "DataInitializerConstructorResolutionRefusedProjection" + Absent => false + } +} + +fn data_initializer_is_typechecked_subject_absent_projection(node: Node) -> Bool { + match projection_kind_lexeme_optional(node: node) { + Present { value: k } => k == "DataInitializerTypecheckedSubjectAbsentProjection" + Absent => false + } +} + +fn data_initializer_is_plain_record_projection(node: Node) -> Bool { + match projection_kind_lexeme_optional(node: node) { + Present { value: k } => k == "DataInitializerPlainRecordProjection" + Absent => false + } +} + +fn plain_record_parent_qualified_name_optional(record: Node) -> Optional { + match projection_named_child_optional(node: record, label: "parent_type") { + Present { value: parent } => declaration_identity_qualified_name_optional(node: parent) + Absent => optional_absent() + } +} + +fn data_initializer_is_record_projection(node: Node) -> Bool { + match projection_kind_lexeme_optional(node: node) { + Present { value: k } => k == "DataInitializerRecordProjection" + Absent => false + } +} + +fn data_initializer_is_nullary_projection(node: Node) -> Bool { + match projection_kind_lexeme_optional(node: node) { + Present { value: k } => k == "DataInitializerNullaryProjection" + Absent => false + } +} diff --git a/src/v2/std/decl_facts_skeleton.dag b/src/v2/std/decl_facts_skeleton.dag new file mode 100644 index 00000000000..0c505094078 --- /dev/null +++ b/src/v2/std/decl_facts_skeleton.dag @@ -0,0 +1,145 @@ +module v2.std.decl_facts_skeleton + +import std.types { List, String } +import v2.std.algebra { + Empty, + HeadAbsent, + HeadFound, + count_where, + fold_list_right, + length, + list_head, + list_reverse, +} +import v2.std.collection { Absent, Present } +import v2.std.compilers.lexing { symbol_lexeme } +import v2.std.decl_index { DeclFact } +import v2.std.node { Node, TypeNode, Atom, Conj } +import v2.std.node_query { node_positional_child_targets } + +type OuterRecordConstructorLexeme = String + +type SkeletonMarshalAtomLexeme = String + +data outer_record_constructor_lexeme_dissolve_on: String = "Dissolve-on: a closed enum or branded digest for each outer record-constructor spelling atom, grounded on extdeps/languages/dag ingest productions, so String aliases at the skeleton marshal boundary become unwritable." + +data skeleton_marshal_atom_lexeme_dissolve_on: String = "Dissolve-on: same closed spelling authority as outer_record_constructor_lexeme_dissolve_on — skeleton marshal atoms share one cited production roster rather than parallel String nicknames." + +data decl_facts_nullary_variant_value_identity_gap_note: String = "BOUNDED constructor-lexeme reflection (parse-only decl_facts_corpus_walk metadata): outer ExprRecordLit authored names surface as SkeletonMarshalAtomLexeme / OuterRecordConstructorLexeme atoms (Scaffold, Terminal, DeclarationRef, NamedField, etc.) — constructor SPELLING only, not parent coproduct or declaration identity. DataItem node marshaling in eval_decl_facts delegates to marshal_data_initializer_projection (ctx.lookup_fn_node on the resolved graph), so merge-blocking witnesses that read resolved nullary parent/arm identity through decl_facts() exercise the same typechecked seam as marshal_data_initializer_projection Rust tests — infer-stamped VarBindingKind::VariantValueBinding and InferredNode::Resolved on variant-value ExprVars. Remaining open at this grain: exact parent-variant occurrence identity (this lane lands ResolvedDeclarationLocator qualified-name parent/arm only, not occurrence coordinates). Dissolve-on: occurrence-grain projection or a single marshal authority that folds spelling atoms and resolved identity without a parse-only fork." + +data decl_facts_outer_record_constructor_lexeme_authority_note: String = "OuterRecordConstructorLexeme is the honest name for ExprRecordLit-authored constructor spelling reflected in decl_facts skeleton atoms. It is not variant identity: parent coproduct, module, declaration, and variant resolution are separate open obligations. skeleton_contains_outer_record_constructor_lexeme reads the same marshal atom spine as skeleton_contains_marshal_atom_lexeme but types the query parameter honestly." + +type DeclFactQualifiedNameResolution + = DeclFactResolvedUnique { fact: DeclFact } + | DeclFactResolvedMissing { qualified_name: String } + | DeclFactResolvedAmbiguous { qualified_name: String, match_count: Int } + +fn decl_facts_matching_qualified_name(qn: String, facts: List) -> List { + list_reverse(xs: fold(facts, init: Empty, f: fn(acc, fact) { + if fact.qualified_name == qn { + Cons { head: fact, tail: acc } + } else { + acc + } + })) +} + +fn resolve_decl_fact_by_qualified_name( + facts: List, + qn: String +) -> DeclFactQualifiedNameResolution { + let matches = decl_facts_matching_qualified_name(qn: qn, facts: facts) + let n = length(xs: matches) + if n == 0 { + DeclFactResolvedMissing { qualified_name: qn } + } else if n > 1 { + DeclFactResolvedAmbiguous { qualified_name: qn, match_count: n } + } else { + match list_head(xs: matches) { + HeadAbsent => DeclFactResolvedMissing { qualified_name: qn } + HeadFound { value: sole } => DeclFactResolvedUnique { fact: sole } + } + } +} + +fn skeleton_marshal_atom_lexeme_optional(node: Node) -> SkeletonMarshalAtomLexeme? { + match node.kind { + TypeNode { connective: Atom { identity: sym } } => + Present { value: symbol_lexeme(sym: sym) } + TypeNode { connective: Conj } => + fold( + node_positional_child_targets(node: node), + init: Absent, + f: fn(acc, nested) { + match acc { + Present { value: _ } => acc + Absent => skeleton_marshal_atom_lexeme_optional(node: nested) + } + } + ) + _ => Absent + } +} + +fn node_skeleton_edge_targets(node: Node) -> List { + list_reverse(xs: fold(node.children, init: Empty, f: fn(acc, e) { + Cons { head: e.target, tail: acc } + })) +} + +fn node_skeleton_marshal_atom_lexemes(node: Node) -> List { + let child_lexemes = fold_list_right( + xs: node_skeleton_edge_targets(node: node), + empty: Empty, + snoc: fn(acc, child) { + fold_list_right( + xs: node_skeleton_marshal_atom_lexemes(node: child), + empty: acc, + snoc: fn(inner_acc, lex) { Cons { head: lex, tail: inner_acc } } + ) + } + ) + match node.kind { + TypeNode { connective: Atom { identity: sym } } => + Cons { head: symbol_lexeme(sym: sym), tail: child_lexemes } + _ => child_lexemes + } +} + +fn skeleton_contains_marshal_atom_lexeme(node: Node, lexeme: SkeletonMarshalAtomLexeme) -> Bool { + count_where( + xs: node_skeleton_marshal_atom_lexemes(node: node), + predicate: fn(lex) { lex == lexeme } + ) > 0 +} + +fn skeleton_marshal_atom_lexeme_count(node: Node, lexeme: SkeletonMarshalAtomLexeme) -> Int { + count_where( + xs: node_skeleton_marshal_atom_lexemes(node: node), + predicate: fn(lex) { lex == lexeme } + ) +} + +fn skeleton_contains_outer_record_constructor_lexeme( + node: Node, + lexeme: OuterRecordConstructorLexeme +) -> Bool { + skeleton_contains_marshal_atom_lexeme(node: node, lexeme: lexeme) +} + +fn skeleton_outer_record_constructor_lexeme_count( + node: Node, + lexeme: OuterRecordConstructorLexeme +) -> Int { + skeleton_marshal_atom_lexeme_count(node: node, lexeme: lexeme) +} + +fn decl_fact_skeleton_for_resolution( + resolution: DeclFactQualifiedNameResolution +) -> Node? { + match resolution { + DeclFactResolvedUnique { fact } => Present { value: fact.node } + DeclFactResolvedMissing { qualified_name: _ } => Absent + DeclFactResolvedAmbiguous { qualified_name: _, match_count: _ } => Absent + } +} diff --git a/src/v2/test/claim/ci_floor_plan_witness_test.dag b/src/v2/test/claim/ci_floor_plan_witness_test.dag index a5d7ff4130c..7255c924bb8 100644 --- a/src/v2/test/claim/ci_floor_plan_witness_test.dag +++ b/src/v2/test/claim/ci_floor_plan_witness_test.dag @@ -352,7 +352,7 @@ test fn witness_v1_claim_scoped_batch_is_file_grain_and_batch_owned() -> Bool { && execution_mode_eq(left: v1_claim_scoped_witness_batch.execution_mode, right: Hermetic) && (v1_claim_scoped_witness_batch.resource_profile.process_isolation == SequentialChildProcess) && match runnable_batch_clamp_source(r: target) { - RunnableOwnsBatchClamp { clamp: clamp } => (second_count(s: clamp.overhead) == 360) && (millisecond_count(m: clamp.per_unit) == 0) + RunnableOwnsBatchClamp { clamp: clamp } => (second_count(s: clamp.overhead) == 480) && (millisecond_count(m: clamp.per_unit) == 0) _ => false } && witness_layer_roots == ["dag", "src/v2"] diff --git a/test/fixture_roots/decl_facts_reflection_duplicate_qn/shadow/shadow_module.dag b/test/fixture_roots/decl_facts_reflection_duplicate_qn/shadow/shadow_module.dag new file mode 100644 index 00000000000..141f255cef9 --- /dev/null +++ b/test/fixture_roots/decl_facts_reflection_duplicate_qn/shadow/shadow_module.dag @@ -0,0 +1,3 @@ +module test.fixture.decl_facts_reflection_duplicate_qn.shadowed_module + +data shadowed_specimen: Int = 2 From 1eadad4af2598c1e6d7b309e3b57d9c66951c85e Mon Sep 17 00:00:00 2001 From: Brian Searls <11205878+briansrls@users.noreply.github.com> Date: Sat, 8 Aug 2026 20:15:07 -0400 Subject: [PATCH 10/15] Recenter the roadmap on compute, CI ownership, and fleet convergence (#8034) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Roadmap: declare the infrastructure lanes, and make main the fleet's desired state The roadmap carried no node for most of what is now the priority. Verified against the authority before writing anything: zero hits for TasksMax, sccache, compile pool, build cache, Spark, ctrl-build, or the generated-file merge driver; one incidental hit for GitHub Actions ownership. The three "watchdog" hits are observation-collapse-watchdog-restatement, a display concept that happens to share the word with the runner recovery timer. The fleet lane's six rows all say the same shape - given a stated setting, apply it and read it back. None binds MAIN as where that stated setting comes from, which is exactly the gap: the mechanism half was modelled and the authority half never was. Meanwhile the work exists in design documents nothing on the roadmap points at. generated-file-conflict-policy.md is operator-ruled with four chartered lanes and no nodes. fleet-self-converge-enforcement-design.md names the self-converge timer as missing on every host and calls it the highest-risk gap. It also cited roadmap node 2-periodic-actuation, deleted in the 2026-07-27 refresh - repointed here to its successor fleet-anti-entropy-hygiene rather than left dangling. Sixteen nodes across five lanes, three of them new: fleet +5 main-revision-authority, atomic-convergence-verdict, runner-host-convergence, runner-broker-recovery, spark-inference-serving ci-placement +1 compile-pool-envelope ci-control (new) 4 owned-execution, executed-coverage-receipt, check-projection, actions-runner-retirement, remote-build-containment generated-artifact 2 projection-registry-containment, commit-policy-census ci-cost +2 arc-reconciliation, build-once-per-subject judgment (new) 1 mechanical-review-service Focus moves from the v1 exit and guarantee-ladder lanes to the infrastructure lanes. Nothing is deleted or parked: 98 hidden rows are counted on the page and one row restores the full view. The judgment lane is deliberately off the page while its prerequisite is on it. One witness repair, and it is not cosmetic. witness_projection_is_active_only rendered through roadmap_authority(), which applies the focus, while both its negative controls name ACCEPTED nodes. Any focus that stops selecting the namespace and P-derive lanes therefore makes them absent because HIDDEN, and the claim greens while proving nothing about acceptance. It now reads the focus-independent view, where absence can only mean accepted - the same reason witness_rendered_nodes_are_declared_or_derived already reads both documents. Proven discriminating by planting an active node's headline as a negative control (FAIL) and restoring it (PASS). Executed: generated-artifact drift gate green after regeneration; 44/44 roadmap_authority witnesses; 39/39 roadmap_page; 13/13 roadmap_frontier; 7/7 roadmap_emit. No acceptance receipts recorded. 123 nodes are active and only 9 carry a bound closing check; merged is not accepted, and manufacturing receipts for 51 merges would be the rung inflation this authority spends paragraphs forbidding. roadmap-receipt-continuity is the one mechanically decidable candidate and is left for the operator. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff * Compute fabric above CI, and the two nodes the wind-down inventory named Two corrections to the previous commit. ONE: the compute fabric was missing, and owning CI was standing where it should have been. Building, checking, regenerating, changing machines, serving models and eventually judging changes are four consumers of one fabric, not four execution systems, and the previous cut put the build story underneath ci-control - which makes owned CI the definition of a build rather than one caller of it. compute-exact-work-contract exact subject in, one of five typed endings out. A branch name or a working directory is not a subject. A provider that cannot serve the requested operation class refuses BEFORE running rather than answering a smaller question. compute-artifact-return-and-materialization a write-producing computation cannot report success while its declared outputs are unreachable. That is the exact live defect: a remote run finished successfully, produced nothing locally, and the stale binary left behind was compared against itself. It grounds on docs/plans/execution-spine-design.md rather than minting a parallel concept. That document is operator-SIGNED (FLAGS A-E, 2026-07-09) and its thesis is already that realization and materialization are the only downstream readers of the dependency view - which is the fabric being asked for. Minting a second scheduler beside it would have been the nicknaming DESIGN section 3 forbids, in the place it costs most. ci-owned-execution, ci-remote-build-containment, fleet-runner-host-convergence and fleet-spark-inference-serving now depend on it. ci-remote-build-containment is restated as what it actually is: a MIGRATION, whose only permitted additions are refusals, and which is deleted once its useful behaviour is a provider behind the contract. Remote execution does not live there. TWO: two nodes the inventory named that genuinely had no home. ci-floor-discovery-snapshot the ordinary and scoped workers each walk the corpus in separate processes; the second walk measures around 284 seconds. It carries the complete typed result, never a pass-or-fail flag, and a consumer that finds it absent, damaged or wrong-subject refuses instead of recomputing. Distinct from phased-single-process-ci, which removes the cross-PHASE duplicate; this removes the cross-WORKER one. compiler-declaration-floor a value was observed flowing through a field declared as the wrong type while all fifty-two behaviour witnesses over it stayed green. Five planted defects refused separately, plus an unchanged-behaviour control so the wall cannot be satisfied by refusing more of the language. No rung asserted - the claims carrier says where it stands. Focus adds compute. 100 hidden rows counted on the page. The focus note now states plainly what a focus is NOT. Off the page sits real retained work with real remaining boundaries, and nothing currently records why a hidden lane is paused or what restarts it - a lane frozen behind infrastructure, one draining to merge, and one parked because its hypothesis was falsified are three states that all read identically as absent. That is a missing dimension on the node, and it is named as landing next rather than papered over here. Executed: drift gate green after regeneration; roadmap_authority 44/44. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff * Deduplication before consumers, and the mechanism chain that removes preparation The compute contract as landed said exact subject in, typed ending out. It did not say identical computations collapse to one producer, and it did not say the fabric owns admission. Without both, handing agents a compute interface is a tidier way for ten callers to launch ten cold builds of one tree - the exact failure the lane exists to prevent. compute-deduplication-and-admission two requests naming the same computation are one piece of work: one producer, everyone else a consumer of its result. The fabric decides how many cold builds a machine carries, what the pool's total demand may be, which host serves a request, and what order competing work is served in - and tells a caller which of those it waits on. Requests differing anywhere in the identity must NOT collapse, and a capacity refusal is counted rather than becoming an invisible queue. It stands IN FRONT OF ci-owned-execution in the graph rather than beside it. Owning CI adds a consumer, and a consumer added before the duplication is removed multiplies the load instead of sharing it. The mechanism staircase, each removing a DIFFERENT duplicate: ci-scoped-worker-shared-substrate the second initialised world. Isolation keeps meaning separate mutable scratch and lifetime, never recomputing facts already fixed and immutable. ci-selection-before-preparation preparation that precedes selection. Selection is sharp about meaning and blunt about cost: it concludes the corpus is irrelevant after discovering, naming, rostering and indexing it. ci-streaming-realization the barrier between preparing and executing. Its acceptance property is that the first witness executes before the last selected entry is prepared. Chained by dependency edges rather than declared as a set, so at most the next one is startable and the limit on concurrent performance mechanisms falls out of the graph instead of being prose nothing enforces. phased-single-process-ci now sits behind the scoped-substrate row for the same reason. Width two is deliberately absent from the chain and stays parked: the tested implementation shared typed bytes while each worker still built its own world. compute_consumer_admission_sequencing_note records the ruling and, separately, that native realization and shared preparation are ONE programme. Emitting a native bundle is the right destination and the miniature is decisive at its scale, but the cited production enrolment delivered zero of three native and three of three fallback, so the required path took no benefit - and native bodies surrounded by duplicated preparation would still be bad CI. Executed: drift gate green after regeneration; roadmap_authority 44/44. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff * Review response: native cutover unblocked, thresholds out of RED controls, dedup bound to a real build Four review findings acted on. The lifecycle finding is answered by sequencing rather than by content and is stated at the end. NATIVE CUTOVER WAS STALE AND ORDERED BACKWARDS. native-selected-witness-bundle read "merge #7599 once controls clear" and "fallback counts trending to zero". #7599 is MERGED, and the cited production enrolment ran none of its three selected members natively and fell back on all three. So the mechanism exists, the required path takes no benefit, and the node was still describing the mechanism as the work. It is now a REPLACEMENT: emit the bundle, execute the selected population by direct call, and DELETE that population's interpreter scheduling in the same change. Interpretation survives only as a named differential control on a cadence, never as a success arm the production path falls through to. Fallback, interpreted and unavailable all at zero is the bar, not a residue to trend. Its two parent edges are removed and one is REVERSED: five-minute-ci-gate now depends on the cutover. The gate is an aggregate outcome, so making the cutover wait for it meant the one step that removes the interpreter from the required path could not start until the programme it contributes to had succeeded. The warm-merge edge went with it - no input dependency was ever shown; the bundle needs a selected population, an emitter and a toolchain, none of which merge admission supplies. The node now has no parents and is startable. witness_five_minute_ci_gate_program_chain_is_explicit CAUGHT THIS, which is what it is for. It pinned the old edge shape, so the reordering had to be deliberate rather than incidental. Updated to require the reversed edge and to assert BOTH old edges absent, so the previous direction cannot return silently. TIME THRESHOLD REMOVED FROM A SEMANTIC RED CONTROL. ci-scoped-worker-shared- substrate required "the scoped segment falls by at least three minutes". That is a tree-measured number standing in for a structural claim - the same shape DESIGN section 5 rejects for census pins. Replaced with what the row actually means: no second source loading, no second index construction, no second initialised world, same population, same outcomes, bounded scratch, no path back to cold construction. Wall, CPU and memory sit beside it as observations. A timer moving cannot claim a duplicate was removed, and a duplicate genuinely removed is not disqualified by a noisy host. DEDUPLICATION IS BOUND TO A REAL ARTEFACT BUILD. Its first slice was open to being demonstrated on two read-only checks collapsing into one verdict - the one case where duplicate work costs nothing, while the case that swamps the fleet is two cold builds. It now names two agent sessions requesting the same artefact-producing build, one producer, one compilation, one returned output, two attached consumers - and it depends on artifact return rather than standing beside it. LIFECYCLE: not in this PR, by agreement with the review. ProgramDisposition and the work-item agreement land first as their own change and this stacks behind them; RoadmapNode is constructed in 19 files and that shape change deserves an isolated review rather than riding a 24-node expansion. Executed: drift gate green after regeneration; roadmap_authority 44/44 with the chain witness green on the new direction. CI failure on b47d383 was infrastructure, established two ways: the regen job died at "Setup Rust" with rustup ETXTBSY (exit 126, 10s in, before any content ran) because runner slots share one home directory; and gunbc.roadmap_authority is absent from the 112-module regen input closure, so this change cannot alter regen output. regen_stage0 --verify run locally reports divergence count 0. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff * Lane 1 gets its node: commit-writer admission is scheduled first, not implied Review response (PR #8034 review, finding 1): the conflict-policy charter's four lanes were covered two-of-four, and the uncovered pair included lane 1 — the one the charter titles "FIRST: the live safety hole" and the one gunbc.repo_local_git_config's authority note names as the boundary a writer cannot pass. A generated-artifact lane whose first row is the population and whose absent row is the writer reads, on the page, as if the safety hole is scheduled. Now it is scheduled, as the parent of the lane-2/3 chain, matching the charter's own transaction ("prove no unmerged index entries — lane 1 predicate"). The node transcribes the charter's two refusal arms (unmerged-stage refusal; staged-blob conflict-marker-grammar refusal) and the operator's second-pass acceptance wall (complete staged-index observation, observed-not-declared classification, provenance-receipt fixture exemption, writer bindings as countable carriers). ROADMAP.md regenerated via generated_artifact_gate main_wet; all 44 roadmap witnesses green by execution with the node in place. Deferred per the same review's recommendation, recorded here: lane 4 (keyed rosters get set/map construction semantics) and the execution-spine-design.md doc-graph binding (needs a typed HandAuthoredDocBind anchor or a plan registration) stack behind the sequenced work-item PR. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff * Consolidation: quarry #8033, add the deterministic confidence lane, reframe owned CI Operator consolidation ruling (2026-08-08): #8034 is the sole roadmap authority branch; #8033's parallel edits port here rather than merging beside it. Ported from #8033, at their exact homes rather than as new identities: - placement-compile-pool-envelope absorbs the task-vs-outer-budget distinction as typed acceptance classes (ProcessAdmissionExhausted, OuterResourceEnvelopeKilled, EffectiveLimitMismatch, CompilePoolPlacementRefused) plus the 8.8%-of-visible-limit receipt. No second placement identity. - The five false-verification incident classes map to their existing exact homes in false_verification_incident_mapping_note; the proposed umbrella node does not port (a coarse identity over mechanisms this graph already decomposes is the §3 second authority). - ci-gate-contention-independent-verdict lands as the one genuine gap, recut qualitatively: host load cannot decide a semantic merge verdict; timeout is a typed execution outcome, never assertion failure; the cutoff is never widened. New lane: confidence-semantic-impact-query (owner confidence, on the focused page) — the deterministic repository-inspection product, explicitly independent of LLM/Spark; judgment-mechanical-review-service now depends on it as its grounding packet. First consumer is one real corpus query usable by a person, not a fixture suite. Reframed: ci-owned-execution's old floor is quarry + shadow oracle, not the template — obligations port only on a proven disagreement; first slice is the exact-subject → bounded population → emitted bundle → owned execution → typed receipt chain with the old path shadowing. Updated: the focus note's namespace standing now carries the 2026-08-08 exact-subject re-observation (A–D established, E/F unavailable under P2a triggers, frontier 2), superseding the stale none-of-six wording. Structure: 151 unique ids, acyclic, no dangling edges, all cited paths resolve. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff * chore: regenerate drifted generated artifacts (ci auto-heal) * Spark enrollment as fleet membership, and abstraction formation on the confidence chain Two operator directions (2026-08-08), landed as four graph changes. fleet-spark-host-enrollment: srv5/srv6 stop being hand-managed boxes. The endpoint rows are DERIVED as a downstream projection joining procurement identity and network allocation — re-typing the reserved address literal refuses (§3 fork), a reverse import into the network intent refuses (the measured cycle: procurement already imports it), and a second host-identity authority refuses. Enrollment carries a GB10 inference envelope, never runner-style thread capacity. Identity converge + reach ride the installed fleet key. fleet-spark-inference-serving now depends on it; runtime, model revision, auth and the collector bundle stay in the serving row, sequenced separately per the operator's "1 and 2 now". abstraction-candidate-discovery (owner confidence): descriptive grouping of subjects observationally equivalent under a NAMED lens, carrying the distinctions the quotient would erase, nearest existing authorities, and over-collapse/demand standing. Descriptive evidence only — the normative "these should share a layer" is an explicit bridge in the judgment row, per the abstraction-calculus mode-crossing rule. Deterministic: no Spark, no LLM. First slice hard-stops on an APPLIED acceptance (consumer migrated, duplicate deleted, receipts equal) so discovery cannot become an inert analysis service. REDs plant the three negative classes: keep-distinct on a load-bearing distinction, projection-missing on a downstream-join pair (the Spark endpoint session is the live receipt), demand-absent on a consumer-less candidate. judgment-mechanical-review-service broadens to the abstraction/modeling ruling vocabulary (existing-authority, likely-duplicate, candidate-new-abstraction, projection-missing, reprime-candidate, keep-distinct, over-collapse-risk, missing-consumer/actuator/readback, unknown) and gains the discovery dependency. Chain: confidence → discovery → judgment ← spark-serving; the Spark ranks and explains over exact packets, it never becomes the repository index. Structure: 153 unique ids, acyclic, no dangling edges; ROADMAP.md regenerated via main_wet; 44/44 roadmap witnesses green by execution. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff * Regenerate ROADMAP.md from the merged authorities Post-merge projection: the conflict on the generated file was taken provisionally and the bytes here are main_wet's output over the merged authority state, per the generated-file policy (regenerate, never hand-resolve). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff * Bind the Spark standup accounting doc: main's floor has been red since #7972 landed it orphaned docs/plans/spark-standup-program-accounting.md landed in the #7972 omega with no HandAuthoredDocBind and no inbound link, so doc_graph_has_no_orphan_docs (and doc_graph_is_clean) have correctly refused every main push since the last green at 4cdcd57013 — the doc-reachability wall working as designed, fleet-wide. Attribution receipt: replaying the doc-graph reachability rule (ROADMAP.md/DESIGN.md/runbook roots + registered plans + hand binds, markdown links as edges) over last-green main, current main, and a candidate branch shows exactly one orphan appearing in the window, this doc. The bind anchors on the physical facts the doc records — gunbc.dgx_spark_procurement spark_a3ee_reservation / spark_3bd5_reservation — and its dissolution names the follow-up the doc itself declares: the Spark standup program landing as roadmap rows, findings migrating to typed carriers, then the doc registers as a plan or deletes and the bind deletes with it. Verified by execution: doc_graph_has_no_orphan_docs and doc_graph_is_clean both PASS on this tree; both FAIL on its parent. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff * Floor fixes: four boundaries back inside the brief budget; ROADMAP reprojected The floor's brief-budget witness (100 words of authored boundary per ticket, gunbc.roadmap_page ticket_brief_word_budget) redded on four nodes this branch authored or lengthened: ci-owned-execution (104), commit-writer-admission (106), abstraction-candidate-discovery (114), judgment-mechanical-review- service (114). Each boundary is trimmed to <=100 tokens with no clause of the bar dropped — overflow either compressed or already carried by the node's other fields (abstraction discovery's no-model-server fact lives in its out_of_scope). Max boundary is now 99. The sibling doc-graph reds are main's breakage (the #7972 omega landed docs/plans/spark-standup-program-accounting.md orphaned; every push since last-green 4cdcd57013 refused): fixed for the fleet in PR #8053 and carried here by cherry-pick so this branch's floor does not wait on that merge. Verified by execution on this tree: witness_ticket_brief_budget_holds_and_reds PASS, doc_graph_has_no_orphan_docs PASS, doc_graph_is_clean PASS, roadmap suite 44/44, regen ExitSuccess. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff * Final Spark/convergence recut per executive verdict Narrow Spark enrollment to membership/profile/endpoint with honestly Unobserved cells; join lifecycle cells into the fleet convergence verdict and name the one-producer defect; spell the inference-serving internal path; make the fleet participation migration the first abstraction-candidate-discovery specimen. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff * Add ci2-complete-native-cost-receipt: whole-corpus native shadow experiment as the bounded cutover's immediate successor Operator direction 2026-08-08: keep CI2-0's bounded acceptance attainable; measure emission/compilation/execution walls separately over the complete roster, then decide from the measured warm wall whether per-PR selection remains load-bearing or is deleted. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff * CI-0 recut: one row, one PR — fallback deleted, whole corpus classified, emitted, executed, authoritative (operator 2026-08-08) Collapses diagnosis-precursor / bounded-cutover / whole-corpus-receipt into a single state transition on native-selected-witness-bundle; deletes the ci2-complete-native-cost-receipt row added earlier today. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff * chore: regenerate drifted generated artifacts (ci auto-heal) * Add roadmap-serve-emitted-realization: dissolve the interpreted serve scaffold via emit-on-demand (srv1 outage 2026-08-08) Interpreted concat clones its accumulator (quadratic); emitted concat moves (linear). Order: emit wiring first, content-hashed bodies second, concurrency last; request deadline regardless; belt GcpProjectId fix folded in. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff * Reconcile #8034 per operator review: record #8054 failed acceptance on confidence-semantic-impact-query; collapse five-minute-gate parent onto the CI-0 one-PR child (cursor review 50559 §3 finding) Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff * Wire compiler-declaration-floor into guarantee_ladder_edges (cursor review 50566) Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff * Catch #8034 up to the day's rulings: CI-0 staged (3-member merge, producer successor, v2-only terminal), general-witness-body-producer row with construct census, CONFIDENCE post-rework standing, structural fork detector as abstraction-discovery first slice Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff * Operator amendment: two judgment modes over one evidence substrate, semantic judgment receipt, model-selection successor, serving infra acceptance, participation-criterion detector, forward-intent refusals Incorporates worker corrections: participation over shape as the mechanical criterion; consumer-verb stringly-enum class moved into the deterministic detector; runner-vs-inference capacity control marked not-yet-built. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff * Wire fleet-runner-broker-recovery into the fleet graph (cursor review 50583) Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff * Wire ci-executed-coverage-receipt and ci-gate-contention-independent-verdict as prerequisites of ci-check-projection (cursor review 50587) Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff * De-number the gate program prose: dispatch order follows graph edges (cursor review 50595 non-blocking finding) Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff * CI2-0 mandate recut: one node, complete v2 witness-execution cutover in #8043 — producer successor row deleted (operator mandate 2026-08-08) The census's 3-of-9,353 was circular (restated enrollment, never attempted realization); the fixture route's limits were mistaken for compiler limits. No successor PRs; canonical-pipeline wiring, per-identity semantic-kind x realization-standing from actual attempts, predecessor deletion, and the whole-population receipt all land in #8043. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff * Dirty-worktree verification as compute's first daily consumer (operator verdict 2026-08-08) Amends compute-exact-work-contract first_slice: exact dirty-tree snapshot runs all affected (or explicitly conservatively complete) v2 tests, exact receipt at most 5s warm, BaseUnstable a distinct answer; confidence optional for narrowing, never correctness. No new node. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_018gTJKsR5YdkaEc2SUGN9ff --------- Co-authored-by: Brian Searls Co-authored-by: Claude Opus 5 (1M context) Co-authored-by: gunbc-ci-auto-heal --- ROADMAP.md | 144 ++--- dag/gunbc/roadmap_authority.dag | 554 +++++++++++++++++- dag/test/claim/roadmap_authority_test.dag | 11 +- .../fleet-self-converge-enforcement-design.md | 6 +- 4 files changed, 612 insertions(+), 103 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index b53c561ad0a..2665e1c0c4f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -8,86 +8,68 @@ Parked context (non-dispatchable): [compiler algorithm survey](docs/plans/compil ## Active deliverables — dependency order -The graph has eleven lanes: SCM compatibility · namespace · P-derive · observation · placement · v1 exit (four finish lines: compiler fixed point, interpreter deleted, products v1-free, zero hand-maintained Rust) · shell · roadmap runtime · fleet/hygiene · hermetic toolchain · compiler-guarantee (the DESIGN §4b ladder climbs; rung STATE lives in the guarantee claims carrier and is emitted, never restated in tickets — [gap analysis](docs/plans/compiler-guarantee-recovery-gap-analysis.md)). The three independent SCM R0 models, the separate P−1 evidence carrier, R1 compatibility-shape extraction, and P0 user-contract/landing spine are accepted; the operator-authored P1 proof-kernel node is active and fail-closed pending its first discriminating closing validation, while P2 and later product lanes remain parked. The toolchain pin model is an independent root. The P-derive receipt feeds the emitter fixed point, so the v1 chain nests under it. Node fields define boundary, first slice, RED control, exclusions, owner, and handback. +The graph has sixteen lanes: SCM compatibility · namespace · P-derive · observation · placement · compute · CI cost · CI control · generated artefacts · v1 exit (four finish lines: compiler fixed point, interpreter deleted, products v1-free, zero hand-maintained Rust) · shell · roadmap runtime · fleet/hygiene · judgment · hermetic toolchain · compiler-guarantee (the DESIGN §4b ladder climbs; rung STATE lives in the guarantee claims carrier and is emitted, never restated in tickets — [gap analysis](docs/plans/compiler-guarantee-recovery-gap-analysis.md)). The three independent SCM R0 models, the separate P−1 evidence carrier, R1 compatibility-shape extraction, and P0 user-contract/landing spine are accepted; the operator-authored P1 proof-kernel node is active and fail-closed pending its first discriminating closing validation, while P2 and later product lanes remain parked. The toolchain pin model is an independent root. The P-derive receipt feeds the emitter fixed point, so the v1 chain nests under it. Compute owns the one contract everything else asks for work through — an exact subject in, a typed ending and the outputs it promised back out — and it sits ABOVE CI rather than inside it, because owning CI, converging the fleet, serving models and eventually judging changes are four consumers of one fabric, not four execution systems. It grounds on the signed realization spine rather than minting a second scheduler beside it. CI control owns who starts, queues and hands out required work and how its result reaches GitHub; CI cost owns how much computation that work performs. Fleet owns whether a merge to main becomes applied machine state and whether that question has one answer. Node fields define boundary, first slice, RED control, exclusions, owner, and handback. -**Focused view — the v1 deletion lane + the guarantee-ladder climbs.** 51 active deliverable(s) in other lanes are declared in the authority and hidden here; nothing is deleted or parked by focusing. Clear `roadmap_focus_selection` in `dag/gunbc/roadmap_authority.dag` to restore the full page. 12 of the rows below are NOT lane deliverables — they are prerequisites pulled in from other lanes because lane work is blocked on them: namespace-canonical-binding, namespace-ambiguity-discharge, namespace-reference-derived-closure, namespace-structural-observations, namespace-structural-observations-closing-contract, namespace-pool-independence, namespace-cross-file-provenance, namespace-cross-file-provenance-closing-contract, namespace-legacy-choice-retirement, namespace-v2-import-completion, namespace-bound-only-consumers, namespace-import-deletion. Hidden lanes remain readable through their carriers: [docs/plans/dag-scm-design.md](docs/plans/dag-scm-design.md) · [dag/gunbc/plans/cli_run_hollowing_plan.dag](dag/gunbc/plans/cli_run_hollowing_plan.dag) · [docs/plans/progress-observation-design.md](docs/plans/progress-observation-design.md) · [docs/plans/five-minute-ci-gate-design.md](docs/plans/five-minute-ci-gate-design.md) · [docs/plans/inner-cost-lanes-scoping.md](docs/plans/inner-cost-lanes-scoping.md) · [docs/plans/ci-two-tier-placement-redesign.md](docs/plans/ci-two-tier-placement-redesign.md) · [docs/plans/shell-to-dag-residual-census-and-arc-completion.md](docs/plans/shell-to-dag-residual-census-and-arc-completion.md) · [docs/plans/roadmap-workspace-ux-plan.md](docs/plans/roadmap-workspace-ux-plan.md) · [docs/plans/provider-control-interface-audit.md](docs/plans/provider-control-interface-audit.md) · [docs/plans/host-converge-inventory.md](docs/plans/host-converge-inventory.md) · [docs/runbooks/bmc-redfish-operator-access.md](docs/runbooks/bmc-redfish-operator-access.md) · [docs/plans/host-effect-orchestration.md](docs/plans/host-effect-orchestration.md) · [docs/plans/gunbc-served-dashboard-design.md](docs/plans/gunbc-served-dashboard-design.md) · [docs/plans/fleet-acceptance-criteria.md](docs/plans/fleet-acceptance-criteria.md) · [docs/plans/hermetic-tool-provisioning-design.md](docs/plans/hermetic-tool-provisioning-design.md) · [dag/gunbc/capability_binding.dag](dag/gunbc/capability_binding.dag) · [dag/gunbc/roadmap_component.dag](dag/gunbc/roadmap_component.dag) · [dag/gunbc/design_document.dag](dag/gunbc/design_document.dag). +**Focused view — the compute fabric + the infrastructure stabilisation lanes + fleet convergence from main.** 107 active deliverable(s) in other lanes are declared in the authority and hidden here; nothing is deleted or parked by focusing. Clear `roadmap_focus_selection` in `dag/gunbc/roadmap_authority.dag` to restore the full page. 6 of the rows below are NOT lane deliverables — they are prerequisites pulled in from other lanes because lane work is blocked on them: observation-scoped-run-consumer, observation-scoped-run-seed-growth-justification, observation-scoped-run-seed-growth-justification-closing-contract, shell-gate-migration, shell-effectplan-to-bash, shell-typed-invocation. Hidden lanes remain readable through their carriers: [docs/plans/dag-scm-design.md](docs/plans/dag-scm-design.md) · [docs/plans/namespace-unique-on-chain-operational-plan.md](docs/plans/namespace-unique-on-chain-operational-plan.md) · [docs/plans/v2-self-hosting.md](docs/plans/v2-self-hosting.md) · [src/v2/compiler/05_emit.dag](src/v2/compiler/05_emit.dag) · [docs/plans/progress-observation-design.md](docs/plans/progress-observation-design.md) · [src/v2/compiler/self_host/candidate_generation.dag](src/v2/compiler/self_host/candidate_generation.dag) · [src/v2/compiler/self_host/frontier.dag](src/v2/compiler/self_host/frontier.dag) · [dag/gunbc/v1_deletion_plan.dag](dag/gunbc/v1_deletion_plan.dag) · [dag/gunbc/stage0_rust_host_observation.dag](dag/gunbc/stage0_rust_host_observation.dag) · [docs/plans/witness-realization-plan.md](docs/plans/witness-realization-plan.md) · [src/v1/05_emit_rust.dag](src/v1/05_emit_rust.dag) · [dag/gunbc/v1_interpreter_primitive_surface.dag](dag/gunbc/v1_interpreter_primitive_surface.dag) · [docs/plans/shell-to-dag-residual-census-and-arc-completion.md](docs/plans/shell-to-dag-residual-census-and-arc-completion.md) · [docs/plans/roadmap-workspace-ux-plan.md](docs/plans/roadmap-workspace-ux-plan.md) · [docs/plans/provider-control-interface-audit.md](docs/plans/provider-control-interface-audit.md) · [docs/plans/enforcement-intent-design.md](docs/plans/enforcement-intent-design.md) · [docs/plans/hermetic-tool-provisioning-design.md](docs/plans/hermetic-tool-provisioning-design.md) · [dag/gunbc/capability_binding.dag](dag/gunbc/capability_binding.dag) · [dag/gunbc/roadmap_component.dag](dag/gunbc/roadmap_component.dag) · [dag/gunbc/design_document.dag](dag/gunbc/design_document.dag) · [docs/plans/compiler-guarantee-recovery-gap-analysis.md](docs/plans/compiler-guarantee-recovery-gap-analysis.md) · [docs/plans/cardinality-refinement.md](docs/plans/cardinality-refinement.md). -- [ ] **Have the new compiler write out Rust source itself** — Read the source, work out the types, translate, and write real Rust to disk without the old generator deciding any of it. The old runner may still be what executes this; it just stops making the decisions. Why: Until the new compiler can write Rust on its own, every improvement has to go through the generator we are trying to delete. [authority](src/v2/compiler/self_host/candidate_generation.dag) - - [ ] **Regenerate one already-tested module with the new generator** — Take a module that already has working tests, change only which generator produces it, and confirm it still passes the same tests including the deliberately broken one. Why: Proves the whole approach works on something real before anyone builds more on the assumption that it does. [authority](src/v2/compiler/self_host/frontier.dag) - - [ ] **Build the new generator with itself, then do it again** — The generator, and only the generator: it writes and builds a native version of itself, that native version writes and builds another, and the second one needs nothing from the old compiler. The other parts of the compiler are still produced the old way at this point. Why: This is the moment the new generator stops being something we produce and becomes something that produces itself. [authority](src/v2/compiler/self_host/frontier.dag) — requires all: v2-emitter-first-behavioral-module, pderive-error-movement-receipt -- [ ] **See the hand-written files as they actually are, from the host** — The set of tracked Rust files is read from the repository itself at a known revision, not transcribed. Reading it can fail, and when it does it says so rather than returning a shorter list. Why: Every count of what is left to move was previously taken from whichever files someone had looked at. That understates, silently, and it understated twice here -- so no schedule built on it could be trusted. [authority](dag/gunbc/stage0_rust_host_observation.dag) - - [ ] **Keep an honest list of what still depends on the old compiler** — Every module is either generated by the new compiler or explicitly kept on the old one with a reason and a plan to move. Two separate questions, kept separate: whether the list found every module, and whether every module it found has been decided. A complete list of undecided modules and a decided list that missed modules are different failures and are reported differently. Why: Turns a vague sense of 'mostly done' into a finite, countable list someone can work through. [authority](dag/gunbc/v1_deletion_plan.dag) - - [ ] **Work through the remaining hand-written files the products need** — Move the hand-written files the shipping tools still depend on. Anything that genuinely has to stay gets an explicit reason. Why: The main command-line tool is itself one of these files, so 'the products still build' is not something we can honestly claim until this is done. [authority](dag/gunbc/v1_deletion_plan.dag) -- [ ] **Make sure deleting the old tests loses no coverage** — Every old test file removed has equivalent coverage that would actually catch the bug it was written for. Why: A guard that stays quiet is not the same as coverage; without this, deletion silently drops real protection. [authority](dag/gunbc/v1_deletion_plan.dag) -- [ ] **Let the model, not the Rust, decide what primitives exist** — Invert the roster. Today the Rust dispatch is the authority and the roster is derived from it; here the roster becomes the authority and the interpreter's local arm identity and spelling/form lookup are GENERATED from it, leaving hand Rust to supply only the handler bodies behind an exhaustive match. The semantic question of which primitive an arm realizes stays a separate join and does not move here. Why: While the denominator lives in Rust, 'which primitives does the interpreter provide' can only be answered by reading Rust, and a new dispatch site added there is invisible to every consumer that asks. That keeps interpreter deletion a judgment call instead of a derived population, and it leaves the primitive-identity join reading a surface that can grow behind its back. [authority](dag/gunbc/v1_interpreter_primitive_surface.dag) -- [ ] **Write the closing check for: Work out what a name can see from where it is written, inside one file** — One executable check states when this is done, transcribed from the node's own bar: Each of the four cases fails on its own when the rule is broken: a neighbour written earlier goes missing, a declaration from another branch leaks in, one written further down leaks in, or two same-spelled declarations merge into one. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) - - [ ] **Work out what a name can see from where it is written, inside one file** — Make an ordinary compiled path produce, for each name written in a file, the declarations actually in view from that position, and record which one answered. Four cases, each shown on its own: an ordinary neighbour written earlier in the same file is in view; a declaration inside a different branch of a decision is not; a declaration written further down is not; and two declarations spelled the same at genuinely different positions stay distinct rather than merging. Why: The parser walk that can answer this already exists and is switched on, but nothing in an ordinary compile asks it. Until it is asked on a real compile, every claim about what a name can see rests on hand-built examples, and the four rules above are asserted rather than observed. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) - - [ ] **Work out what each name can see, and let that be what a file depends on** — From where a name is written, work outward to every declaration actually in view — including ordinary neighbours in the same file — respecting where things were written relative to each other. Then record, for each name, exactly which declaration answered it, and derive what each file depends on from those answers. Why: What a file depends on is currently written out by hand as a separate statement, so it can disagree with what the file actually refers to. Hundreds of files were patched by hand to keep those two in step, which is work that should never have existed: what a file depends on is a consequence of what it refers to, not a second thing to maintain. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) — requires all: namespace-structural-observations, namespace-pool-independence - - [ ] **Settle the names that genuinely could mean two things** — Using the real answers from the step before — not a count taken by searching text — go through every name that genuinely has more than one candidate and either refuse it on purpose or say which one is meant, by renaming or by writing it out in full. Why: Counting these by searching for text gives a number that is wrong in both directions, so it cannot tell anyone how much work is left or whether it is finished. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) - - [ ] **Make name lookup itself produce that one answer, and delete the old way** — The places that WORK OUT what a name refers to — type names, value names, and calls — produce their answer from the mechanisms built in the earlier steps, and the old habit of searching everything currently loaded is deleted rather than left beside them. This is the changeover of name lookup itself. What the rest of the compiler then does with that answer is the next step, not this one. Why: Until the old lookup is deleted the new mechanism is only advisory: both run, they can disagree, and the compiler can still quietly mean something other than what was written. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) - - [ ] **Make the rest of the compiler use only that one answer** — Type checking, call lookup, and argument-count checks all read the single decided answer. When the answer is 'unresolved' or 'ambiguous', they stop rather than carrying on with a guess. Why: While parts of the compiler still do their own lookup, the one shared rule is advisory rather than authoritative. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) - - [ ] **Do the same for imports** — Every place the newer compiler resolves an import or a dotted name reads the single decided answer, before the old mechanisms are deleted. Why: If imports are left behind, they become a second source of truth about what a name means — exactly the problem this whole chain exists to remove. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) - - [ ] **Delete every other way of deciding what a name means** — Once all ordinary consumers use the one rule, remove the older mechanisms: name-string matching, walking up the parent chain, the global pool of bare names, the import retry, and the hand-written seed mirror that counts none/one/many for itself inside the bootstrap compiler. Owning that last one HERE, rather than in the node that defines the canonical decision, is deliberate — see namespace_kernel_authority_amendment_note. Why: Leaving them in place means one name can still get different answers from different code paths. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) - - [ ] **Delete the import lines, and the grammar that allows them** — Every import line goes, and import stops being something the language can express at all: writing one becomes a parse error. What a file depends on is worked out from the names it actually mentions. Why: While import still parses, a file can declare one set of dependencies while its references need another, and the two can disagree. That is the second source of truth this whole chain exists to remove, and it survives every earlier step. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) - - [ ] **Attach those requirements to the right type, not just the right position** — Connect each recorded slot to the exact declaration it belongs to, using the name resolution work above. Why: Matching by position or by short name attaches a requirement to the wrong type whenever two types share a name or the order shifts. [authority](docs/plans/v2-self-hosting.md) - - [ ] **Generate those requirements from one place in the new generator** — The new Rust generator reads the record and the mapping through its existing path, rather than needing a special case per shape. Teaching the old generator the same thing is allowed only where the build-itself path is genuinely stuck without it, and even then it reads the same answer rather than working it out again. Why: Every shape currently needing its own hand-written patch is a shape someone has to fix again next time — and doing it in the generator we are deleting means doing it twice. [authority](src/v2/compiler/05_emit.dag) — requires all: pderive-source-slot-binding, v2-emitter-direct-rust-door - - [ ] **Count the compiler errors before and after, to prove it helped** — Re-run the same set of modules and compare the error counts and kinds. Why: A fix that makes one example compile can leave the real number untouched; without the count we would not know. [authority](docs/plans/v2-self-hosting.md) - - [ ] **Get the rest of the compiler to rebuild itself too** — With the generator already building itself, extend the same two-round test to everything else the compiler does — reading source, working out names, working out types, translating — until the whole thing generates its own source, that source is built, and the result behaves the same. Behaviour has to match; the bytes do not. Why: This is the whole point of the project: until the compiler can build itself end to end, there is nothing to replace the old one with. [authority](dag/gunbc/v1_deletion_plan.dag) — requires all: pderive-error-movement-receipt, v2-emitter-native-bootstrap - - [ ] **Switch the build over to the new compiler** — The real build runs on the new path, with cold comparisons against the old one first. Why: Moves the thing that actually has to work every day, instead of proving a replacement nobody uses. [authority](dag/gunbc/v1_deletion_plan.dag) — requires all: v1-emitter-fixed-point, v1-honest-frontier, v1-verification-ledger - - [ ] **Run everything with the old generator taken away** — The checks and the build use what the new generator produced, nothing falls back to the old generator, and nothing is left calling it. Why: Separates deleting the old generator from deleting the old runner, so the generator can go first instead of waiting on everything else. [authority](src/v1/05_emit_rust.dag) - - [ ] **Delete the old Rust generator** — The old generator itself is removed, along with the one Rust file it generated to run itself, the Rust option in the old build path, and the fixtures, helpers and measurements that existed only for it. What produced a file in the past does not decide whether it goes: any Rust the new generator now produces stays exactly where it is. Why: Removes the second set of Rust-generation rules for good — the split the shared rules were meant to end. [authority](src/v1/05_emit_rust.dag) - - [ ] **Build the products with the old runner left out entirely** — A rehearsal: build without the old runner included at all, and run the products and the build both cold and warm. Why: Proves nothing still reaches for it, which is the thing that actually makes deletion safe. [authority](dag/gunbc/v1_deletion_plan.dag) — requires all: v1-ci-floor-cutover, v1-interpreter-primitive-dispatch-authority - - [ ] **Delete the old runner** — Delete it and everything now unreachable, in one change, once the rehearsal above holds. This is about the runner only — not the rest of the old code, and not hand-written Rust generally. Why: Removes the second, slower way of running everything, and with it the possibility of the two disagreeing. [authority](dag/gunbc/v1_deletion_plan.dag) — requires all: v1-interpreter-quarantine, v1-emitter-delete - - [ ] **Build and run every product without the old code present** — With the old runner gone and the hand-written queue worked through, the products build and run with the old directory absent. Anything that must stay gets an explicit reason. Why: Separates 'the products no longer need the old code' from 'the old runner is gone', so neither claim waits on the other's leftovers. [authority](dag/gunbc/v1_deletion_plan.dag) — requires all: v1-interpreter-delete, v1-hand-queue-drain -- [ ] **Write the closing check for: Answer names that live in another file, and derive what each file depends on** — One executable check states when this is done, transcribed from the node's own bar: Two mentions of one target give one dependency but stay two separate records of which mention produced it; and no dependency appears that no name asked for. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) - - [ ] **Answer names that live in another file, and derive what each file depends on** — Work out, for a name whose answer lives in another file, which declaration answers it, then derive from those answers what the file depends on. Keep two separate results: for every mention, which declaration answered it; and for the file, the set of distinct files it depends on. Repeated mentions of one target collapse only in the second. Why: What a file depends on is written out by hand today, so it can disagree with what the file actually refers to. Hundreds of files were patched by hand to keep the two in step — work that should never have existed, because what a file depends on follows from what it refers to. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) - - [ ] **Loading an unrelated file must not change any answer** — Run the same source twice — once with an unrelated file loaded alongside it, once without — and compare the whole result: which declaration each name answered to, which mention produced which dependency, what the file depends on, and which names refused. Nothing may differ. Why: Names currently resolve partly by luck: a file's names find their targets when some unrelated file elsewhere happens to have pulled those targets in. That makes every green result conditional on what else was loaded, and it is why removing dependency statements had to be stopped across the whole repository. [authority](docs/plans/namespace-unique-on-chain-operational-plan.md) -- [ ] **Write the closing check for: Make exact prior results reusable across invocations** — One executable check states when this is done, transcribed from the node's own bar: Wrong content, a missing entry, and a full store each refuse rather than quietly falling back to recomputing. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](docs/plans/witness-realization-plan.md) - - [ ] **Make exact prior results reusable across invocations** — Iteration-speed and runtime infrastructure: exact input identity selects a bounded materialized result that can be served after restart without compiling. The v1 exit is one consumer of this kernel, not the kernel's parent purpose. Why: Repeated invocations currently rebuild results that an exact identity could safely serve; product iteration, tests, and the v1 exit all pay that recomputation independently. [authority](docs/plans/witness-realization-plan.md) - - [ ] **Prove old and new agree, by running them, not by waiting** — Run the whole test suite cold on both, compare compile results, check the self-rebuild, and record each family's outcome. Why: Confidence that comes from 'it has been fine for a while' is not evidence; this replaces it with runs someone chose to do. [authority](dag/gunbc/v1_deletion_plan.dag) — requires all: v1-materialization-kernel, v1-emitter-fixed-point -- [ ] **Write the closing check for: The expecting-red probe corpus: the floor gap becomes a counted, continuously-measured number** — One executable check states when this is done, transcribed from the node's own bar: A probe that stops expecting red without its class's wall having landed is itself a red (greening is counted); the corpus reds if any probe pair loses its executing consumer. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **The expecting-red probe corpus: the floor gap becomes a counted, continuously-measured number** — One probe pair per floor class (deliberately-bad input expected to refuse; legitimate control expected to accept), enrolled as expecting-red rows via the known-red quarantine mechanism so today's fail-open state is measured, not asserted. Every currently-legitimate map/fold/filter/flat_map/service invocation form appears as a positive control so a future wall cannot green by refusing the language. Why: Replaces anecdote (gunbc#7479's 500) with a counted deficit per class; each wall that lands flips its probes to regression controls loudly. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **The guarantee claims carrier: rung state becomes DERIVED from execution receipts, never stored** — Four carriers, measurement separated from claim: GuaranteeRequirement (class identity, domain, harm, ceiling with typed reason, next_rung_trigger); GuaranteePath (subject_grain, acceptance_boundary, realization_target per gunbc.guarantee_measurement; one row per in-scope path, population derived from the census or completeness-witnessed); GuaranteeMeasurement (executed receipts naming their consumer); and GuaranteeDisposition DERIVED per path — Unmeasured | BelowFloor | FrontierAccepted | OnLadder | OutsideModeledGuarantee — folded by below-floor dominance, then frontier, then unmeasured, then MINIMUM rung across paths. No stored current_rung field exists, so a transcribed rung is unrepresentable; the five recovered vocabularies stay orthogonal, joined by class identity. Full shape: gap analysis sec 12 Stage 1b. Why: Ends the per-conversation re-derivation of what the compiler guarantees (the operator performed the missing meta-lens by hand three times this month); grounds the enforcement-intent StandingIntent thread instead of minting beside it. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **The operator's seam, end to end: a 0..n producer cannot feed a 1..n consumer** — One path, not a census: bare List = 0..inf, literal [] = 0..0, nonempty literal = n..n; map preserves, filter floors to 0, concat adds; the interval is an interface fact in InterfaceSummary's contract slot, actual-within-required enforced at function boundaries with a located seam refusal. The seam direction (2026-07-04) and candidate lattice exist; the in-tree fold-propagation rows are manual value-level specimens — binding-level propagation starts here. Construction-wall first candidate: type Refined sole_constructor plus deleting refined_vacuous_stub_pack's forged rejection arm, which is why the sole-constructor completeness audit is a declared prerequisite edge. Why: The canonical motivating specimen — simple, structural, explainable without compiler expertise; the first differentiating class an outside reader can verify. [authority](docs/plans/cardinality-refinement.md) — requires all: ladder-claims-carrier, unforgeable-construction-capability - - [ ] **The acceptance door as mechanism: audit first, blocking later** — Split from the monolithic closure node (operator spine verdict: a door gated on the whole program delays the most important new mechanism until nearly done). This node lands the MECHANISM only, depending on the carrier alone: read every Required guarantee path, derive the missing/unmeasured/frontier/no-live-consumer census, run audit-mode and expecting-red — it reports, it does not yet block, and it never claims the open classes closed. The recovered P0.0 meta-guarantee gets its executable form here. Why: The every-Required-path-has-a-live-pre-Accepted-consumer relation becomes executable years before the last wall lands. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **Floor activation: the ordinary compiler floor flips to blocking** — The door mechanism flips from audit to refusal over the FLOOR domain only — names, call shape, conformance, fields, exhaustiveness, parse formation, record construction, the exemption gone: acceptance refuses when a floor class or path is Unmeasured, BelowFloor, FrontierAccepted, or lacks its live consumer. Why: Below-baseline safety regressions become unshippable — the floor stops depending on review vigilance. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) — requires all: accepted-obligation-door-mechanism, call-missing-and-duplicate-wall, call-signature-resolution-coverage, method-zero-resolution-general-wall, method-call-shape-seam, declared-conformance-general-wall, compiler-source-exemption-removal, floor-exhaustiveness-refusal, floor-generic-field-constraint-wall, floor-parse-formation-wall, floor-record-construction-wall - - [ ] **Cardinality joins the contract: the seam class flips to Required-blocking** — Admit the cardinality-seam class into the door's blocking Required set — one class, one flip (per-class admission nodes replace the monolithic extended activation, review 45918: an admission becomes ready the day its OWN climb lands instead of every extended class waiting on the slowest). After the flip, acceptance refuses a program whose cardinality judgment is Unmeasured, BelowFloor, FrontierAccepted, or consumer-less. Why: The first differentiating class stops being observability and becomes contract. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) — requires all: accepted-floor-obligation-closure, cardinality-vertical-slice - - [ ] **Extended closure: the full contract roster certified complete** — The terminal certification over the per-class admissions (recut per review 45918 — requires-all belongs HERE, on the completion receipt, never on the admission work, which is per-class and ready as each climb lands): every extended admission receipt present, the door's Required roster equal to the declared extended set — nothing climbed-but-unadmitted, nothing admitted-but-unclimbed — the full recovered P0.0 contract in force. Why: The thesis promise — if it compiles, declared intent is sound inside the modeled system — becomes the shipping contract. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) — requires all: extended-admission-cardinality, extended-admission-ambiguity, extended-admission-v2-realization, extended-admission-v2-translate - - [ ] **Residual prevalence: the post-climb rerun that prices what remains** — The baseline's bucketing classifier re-run after the declared climbs land, diffed against the anchored baseline: per-class delta receipts showing which buckets each wall emptied, and the residual histogram pricing the next round (DESIGN 6 displaced-cost denomination). Depends on the acceptance-closure door alone — every required climb, ambiguity and the cardinality seam included, now flows through that door (review 45545) — so a partial rerun cannot masquerade as the residual. Why: Turns each wall's landing into a measured before/after instead of an assumed win; the residual table is the priced backlog for the next design round. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **Method ambiguity joins the contract** — Admit the method-ambiguity class into the door's blocking Required set once its wall lands (per-class admission, review 45918). After the flip, a program with a more-than-one-resolution judgment left Unmeasured, BelowFloor, FrontierAccepted, or consumer-less refuses at acceptance. Why: The silent-first-hit dispatch class becomes contract, not census. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) — requires all: accepted-floor-obligation-closure, floor-method-ambiguity-wall - - [ ] **Target realization joins the contract** — Admit the target-relative realization class into the door's blocking Required set once its gate lands (per-class admission, review 45918). After the flip, a program accepted for target T with a realization judgment left Unmeasured, BelowFloor, FrontierAccepted, or consumer-less refuses at acceptance. Why: The costed-but-unrealized dispatch failure class becomes contract. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) — requires all: accepted-floor-obligation-closure, v2-target-realization-gate - - [ ] **Translate containment joins the contract** — Admit the Translate-containment class into the door's blocking Required set once the refusal lands (per-class admission, review 45918). After the flip, a program whose underived-fact judgment is Unmeasured, BelowFloor, FrontierAccepted, or consumer-less refuses at acceptance. Why: The documented Translate fail-open, once closed, stays closed by contract rather than vigilance. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) — requires all: accepted-floor-obligation-closure, v2-translate-underived-refusal - - [ ] **Emit the ladder: per-class rung state renders to ROADMAP.md and the served roadmap page from the carrier** — A projection from the guarantee carrier's derived dispositions to (a) a generated ROADMAP.md state table and (b) a roadmap-page panel via the existing ServedRoute machinery — one authority, two projections, drift-gated like DESIGN.md and ROADMAP.md today. The gunb.ai site inherits it through the site-subsumption lane's rule that every shown intricacy derives from a real repo fact. Why: The operator reads ladder position without asking; the state table cannot rot because it is emitted, and a hand-edit is a drift refusal. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **Baseline prevalence: the whole-corpus floor rerun bucketed by ladder position, before any wall lands** — A TWO-REVISION execution, never 'run the baseline at commit X' (three-way ambiguous): the anchor's compiler artifacts (subject_revision: anchor_commit 6c6e2dcb8587d73350ac252f5b07a6b50d684485, the gunbc#7489 merge — pre-P0 main) run under today's content-addressed probe set and classifier (harness_revision + probe_set_digest per the measurement schema), outcomes translated into measurement receipts keyed to carrier class ids. Bucket every failure — statically-decidable-should-have-refused, runtime-value-dependent, external-boundary, resource-budget, capability-not-grounded, interpreter-defect. Replaces the carried-but-unverified historical figures (1454/149; the 66/40/6 histogram). Runs parallel with the census emitters, not ahead of them. Why: Denominates the climbs in displaced cost (DESIGN 6) instead of elegance; the honest number for how many programs the gap admits today. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) — requires all: ladder-probe-corpus, ladder-claims-carrier - - [ ] **Receiver normalization: the measured resolution residue becomes decidable receivers** — The frontier's measured causes, repaired upstream so the wall can decide them: a lambda-parameter receiver whose type never propagates from the declared fn type it is bound under; a coproduct payload bound by destructuring typed as its variant name; the optional receiver whose cardinality drops before lookup; the brand-alias LEAF form the peel cannot reach (qualified-name leaf, measured and declared on the gunbc#7484 frontier roster). Each repair shrinks the roster in both directions — correct calls resolve, bogus methods refuse. Why: Every roster row this discharges converts an admitted-by-key site into a decided one; the frontier ratchet only shrinks. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **Zero-resolution refuses generally: enumeration over the union of admissible sources** — Absence is decided by enumerating the union of current admissible sources — no identity join required (the join gates only the more-than-one wall). Delete the resolve_builtin_call_type Absent-to-unit_type fabrication; every call whose callee resolves nowhere refuses with name and locus on every compile/emit path. Why: Closes the callable-existence class fully: the last fabricated-success arm at the call seam dies. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **Duplicate-argument half LANDED (gunbc#7847); missing-argument half remains** — CORRECTED FRAMING (parent review, 2026-08-05): the defect is two ACTUALS resolving to the SAME PARAMETER, not 'duplicate labels' — positional-then-named onto one slot, or two labels both satisfying an underscore-shorthand parameter. Keyed by resolved binding target: a NAMED actual keys on its own label; a POSITIONAL actual keys on the parameter its index fills, matching call_function_inner. call_function_inner refuses (CallArgumentDuplicate) before the compile-seam mirror, keeping both authorities in agreement. Fixed a prior compile-seam bug where declared-parameter matching let underscore-shorthand steal a later exact-named match (false positive in cardinality.dag). Missing required arguments remain unrefused; param_node_default_value legitimately fills omissions and must stay green. Why: Two actuals resolving to one parameter previously let the last-bound value win silently, unlocatable at the call site; a missing argument surfacing as NoSuchVariable inside the body blames the wrong site. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **Signature-resolution coverage: the sig-unresolved fallthrough stops being a blind spot** — A call whose callee resolves NO signature bypasses every call-shape judgment (measured: 3 label fossils invisible to the landed wall in dag_collect, found only by grep). Either the callee resolves and is judged, or the failure to resolve is itself a typed, counted fact — never a silent fallthrough to the method bridge. Rides the namespace lane's resolution coverage; this node owns the call-shape consequence. Why: The wall's own measured blind spot: correctness there currently rests on grep. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **Conformance grounding: the four representation classes stop being unjudgeable** — The measured false-positive classes that cap the ground fragment, each a §3 grounding defect not a checker defect: brand aliases unpeeled to their base; optionality living in two representations (return_cardinality vs the nominal Optional coproduct); anonymous record literals never grounded against their expected nominal; declaration-vs-body expansion depth. Ground each so the conformance relation widens class by class — the ground-fragment gate exists only because these are unjudgeable today. Why: Each grounded class moves thousands of silent declarations into the judged set (measured: the expansion-depth class alone was half the 3005-row frontier). [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **The general conformance wall: every grounded declared type constrains its producer** — As grounding lands per class, the conformance judgment widens to full return/data coverage — the gate dissolving into the relation until every grounded declared type refuses non-inhabiting producers, with whatever residue remains typed and counted, never silent. Why: Completes the keystone: a declared type is a contract everywhere, not per-fragment. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **Argument-type compatibility grounding: adjudicate what the exemption papers over** — module_skips_direct_call_arg_check exempts v2.*/v1.compiler.* from the argument TYPE judgment because of representation-gap false positives — the same four classes the conformance lane grounds, at the argument seam, plus the unlocated historical '104 TypeMismatch false positives' (no receipt found by two audits; adjudicate or refute it by measurement). Ground the argument-position relation so the exemption's reason dissolves. Why: The compiler's own sources are currently un-judged at every argument position — the largest single acceptance hole by module count. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **Delete module_skips_direct_call_arg_check: the compiler stops authoring itself under weaker checks** — Gated on the two GROUNDING nodes, not the label wall (dependency corrected per the operator spine verdict: the exemption skips the argument TYPE judgment, whose false positives are representation gaps — labels never gated it, and gunbc#7519's wall already runs exemption-free). Once argument-type-compatibility grounding and declared-conformance grounding land, re-run exemption-free, repair relation or source per the bucketed census, then delete. Its deletion is the terminal Tier-1 acceptance named by the doc-graph bind on this lane's analysis. Why: The dimension contract's one named escape hatch (enforced universally — no escape hatch, no wrapper functions); v1 currently admits unsound v2 source that v2 then self-certifies, and v1 deletion would not retroactively prove it. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) — requires all: argument-type-compatibility-grounding, declared-conformance-grounding - - [ ] **ExhaustivenessUnknown refuses: the PatternLookupBlocked silent arm closes** — lookup_variant_in_type and lookup_field_in_variant answer PatternLookupBlocked with diagnostics: [] — exhaustiveness-not-established passing as success-adjacent. Split the verdict three ways (Exhaustive | NonExhaustive | ExhaustivenessUnknown) and refuse Unknown into any accepted executable artifact. Why: Closes the fallback-arm shape the census names for blocked lookups — exhaustiveness-not-established passing as success-adjacent; the measured disposition lives in the carrier, not this ticket. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **Field access through an unconstrained generic refuses: delete the field_of_type_var fabrication** — field_of_type_var answers a field projection on a bare type variable by fabricating the field type — compile-time plausible output where no fact exists (the census's field-through-generic class). The wall: field projection on a type parameter with no field-carrying bound refuses with the parameter, field name, and locus; concrete receivers and parameters whose bound carries the field stay green. Why: A fabricated field type flows into inference and surfaces as a downstream mismatch or a wrong runtime value far from its cause; a located refusal moves the diagnosis to the projection site. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **Parse formation refuses: a dropped separator cannot silently change the program** — Two slices, one wall. Safety: a missing list separator refuses at parse instead of parsing as element juxtaposition (the measured below-floor census row — a dropped comma silently changes list arity). Completeness: a block after a bare identifier in if-condition position parses as the block it is, closing the measured false refusal (expected-RParen on legitimate input). Probe-paired via the corpus's parser pair; grammar-owned, no host parser (census queue items 6-7). Why: The separator specimen survived regen, whole-corpus compile, fixed-point verify, and a 15-case matrix; only a human diff read caught it (review 45347). [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **Record construction refuses: missing required fields, bad defaults, ungrounded nominal literals** — A record literal missing a required field refuses at compile with the field named; defaults apply only where declared; a literal claiming a nominal type it does not ground refuses. Owns the census's record-completeness class — the declared-conformance chain owns return, data, and generic instantiation, not this. Probe-paired via the corpus's record-completeness pair. Motivating specimen: HandAuthoredDocBind's primary_work construction wall currently rests on record-completeness enforcement that is itself unmeasured. Why: A construction wall anywhere in the corpus is only as strong as required-field enforcement; this node is what makes primary_work-style walls load-bearing rather than declared. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) -- [ ] **Write the closing check for: Method existence on established receiver surfaces — LANDED (gunbc#7484, merged 2026-08-01)** — One executable check states when this is done, transcribed from the node's own bar: method_existence_wall_witness (REDs + zero-diagnostic controls + frontier-key perturbation) — migrates to the probe corpus as this class's durable pair. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **Method existence on established receiver surfaces — LANDED (gunbc#7484, merged 2026-08-01)** — ACCEPTED fragment, recut from floor-method-existence-wall at its actual grain (operator spine verdict). An unresolved method on a kernel-profiled receiver refuses (MethodNotFound, blocking — the gunbc#7479 class, 0 files emitted); undecided receivers carry the rostered, occurrence-budgeted MethodExistenceUndecided/FrontierAdmitted frontier; map_keys/map_values bypasses routed through the one decision; one where-refinement receiver class peeled. What did NOT land stays on the sibling nodes: receiver-resolution residue, the general zero-resolution wall, resolve_builtin_call_type's unit_type fabrication. Why: The compile gate stopped being quieter than the interpreter for every receiver with a complete declared surface. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **Argument labels at the method/pipe seam: the call-shape judgment reaches method dispatch** — The direct-call label/surplus wall (landed, gunbc#7519) covers the sig-resolved ExprCall branch only; method-pipe argument binding routes through its own inference arms. Mirror the same two runtime-refused classes there, reusing the landed judgment rather than minting a second relation. Why: A mislabeled method argument silently binding positionally is the same interest-bearing harm as the direct-call case. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) -- [ ] **Write the closing check for: Unknown labels and surplus positionals refuse at the direct-call seam — LANDED (gunbc#7519, merged 2026-07-31)** — One executable check states when this is done, transcribed from the node's own bar: ct_call_shape_wall_witness_test — migrates to the probe corpus as this class's durable pair. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **Unknown labels and surplus positionals refuse at the direct-call seam — LANDED (gunbc#7519, merged 2026-07-31)** — ACCEPTED fragment, recut from floor-call-shape-wall at its actual grain (operator spine verdict). CallArgumentNameUnknown + CallPositionalSurplus blocking, exemption-free, mirroring call_function_inner's two refused classes (underscore idiom included); census refused 28 live parameter-rename fossils, all fixed; the silent emit-path reorder retired for Accepted programs. What did NOT land stays on the sibling nodes: missing/duplicate, signature-resolution coverage, the method seam. Why: The mislabeled call that computed the wrong thing silently on the emission path is unwritable in an Accepted program at this seam. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) -- [ ] **Write the closing check for: Declared conformance for the ground fragment — LANDED (gunbc#7484, merged 2026-08-01)** — One executable check states when this is done, transcribed from the node's own bar: declared_type_conformance_witness — migrates to the probe corpus as this class's durable pair. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **Declared conformance for the ground fragment — LANDED (gunbc#7484, merged 2026-08-01)** — ACCEPTED fragment, recut from floor-inhabitance-wall at its actual grain (operator spine verdict). fn returns (both arms, plus early returns stopping at lambda boundaries) and data annotations refuse non-conforming producers where BOTH sides are ground: kernel scalars, or Required non-keyed element collections of them — positive establishment, the four measured representation classes never guessed at. The counted unjudged advisory was EXCLUDED in final review scope: everything wider is byte-for-byte main behavior (silent), stated here so the fragment is never read as the class. Why: fn f() -> Int returning a string — the probe-3 specimen — refuses located instead of surfacing at the first consumer. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) -- [ ] **Write the closing check for: v2 self-grounding slice — LANDED (gunbc#7485, merged; acceptance receipt recorded 2026-08-01)** — One executable check states when this is done, transcribed from the node's own bar: The claims carrier's v2 self-grounding path flips BelowFloor-to-FrontierAccepted exactly here; the identity path refuses caller-supplied candidate sets. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **v2 self-grounding slice — LANDED (gunbc#7485, merged; acceptance receipt recorded 2026-08-01)** — ACCEPTED against its handback (operator spine verdict): exact self-evidence left solve_constraints' generic arm; underived facts are typed, located, counted GroundingNotDerived diagnostics inside an accepted InferredTree — the FrontierAccepted disposition's live specimen. Identity admission reads the authored declared-inhabitant roster, never caller-supplied candidates. The documented residue is the NEXT node's whole scope: Translate can still swallow the refusal and proceed. Why: Silent self-certification became a counted frontier: the deficit ranks for fixing instead of reading as green. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **Translate propagates the refusal: an underived fact cannot be swallowed downstream** — ACTIVE — the containment workstream, authorized to run parallel with the spine (operator spine verdict 2026-08-01: not another optional wall; it closes the one documented fail-open the merged frontier split created). gunbc#7485 records that Translate can swallow the new GroundingNotDerived rejection and continue — the frontier diagnostic exists but the emission path proceeds. Make Translate refuse or thread the typed frontier so Eval and Translate agree: compile-time refusal on both is the class minimum. The per-path grain is exactly why the carrier models paths separately. Why: Closes the path split where the same underived fact refuses in one phase and ships in another — the subject-grain failure mode measured on the call-shape class. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **InferredTree carries only derived facts: the all-derived carrier lands** — Separate source and inferred carriers so candidate-equals-source is not even well-typed as a grounding proof: every fact on the inferred carrier traces to a derivation or the node refuses. Branch/Match/Loop (the three real inference paths) migrate first; the validate_then_compile door and its lens roster stay. A well-formed node with no derived facts cannot inhabit the carrier. Why: InferredTree becomes a trustworthy proof boundary; every lens consuming it stops reading facts that were never derived; the self-host frontier stops reading greener than it is. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **Every node kind derives or refuses: the generic acceptance arm deletes** — The remaining derivation rules beyond Branch/Match/Loop: each v2 node kind either derives into the inferred carrier or its acceptance is a typed compile refusal — the generic accept-anything arm deletes. Coverage is counted per kind, so the frontier shrinks to zero or to named FrontierAccepted rows each carrying a trigger. Why: The last self-certification residue: any kind without a rule is currently accepted on well-formedness alone, which is the recovered defect class in its most general form. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **Realization per target: an operation cannot compile for T without exactly one realization** — The realized carrier adds target realization: every executable operation in a program accepted for target T has exactly one compatible realization row for T, refused target-relatively otherwise. gunbc#7485 explicitly leaves this open; it consumes the primitive-identity join (realization rows are its per-target facts) and the derivation-coverage node (a realized tree is derived-then-realized, never source-then-realized). Why: The gunbc#7479 shape at its root: a costed-but-unrealized primitive currently compiles and fails at dispatch; the gate moves that to a located target-relative refusal. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) — requires all: v2-node-kind-derivation-coverage, primitive-identity-join -- [ ] **Write the closing check for: Interpreter primitive roster: the fifth surface becomes readable from .dag** — One executable check states when this is done, transcribed from the node's own bar: A new dispatch arm without a roster row refuses; a roster row without a matching arm refuses; fold_list via eval_call intercept and lookup dual-realization join through derived census. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **Interpreter primitive roster: the fifth surface becomes readable from .dag** — Land interpreter-primitive-roster across all six v1_interpreter dispatch sites (eval_builtin_inner, eval_algebra_method_inner, eval_call, try_v2_std_collection_map_primitive_grounding, try_parse_table_memo_dispatch, eval_method_call lookup short-circuit — 158 arms / 169 spellings). clever-crab-111 macro authority: eval_builtin_inner arms expand from interpreter_free_call_arm_spellings with construction wall (arm without roster entry unwritable). primitive_surface_census_derived absorbs InterpreterDispatch instead of primitive_interpreter_dispatch_measurement_rows. Prerequisite for primitive-identity-join terminal acceptance. Why: Closes the fifth blind spot in D1 mutation control across all six dispatch sites. Dissolves MeasurementOnly specimen rows into derived surfaces. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **One primitive identity: definition, per-target realization, and cost as three facts on one key** — PrimitiveDefinition (identity + PrimitiveSemanticContract), PrimitiveInvocationSurface, PrimitiveRealization per RealizationTarget, PrimitiveCostFact, and PrimitiveTraversalOrderFact — five separate carriers joined by PrimitiveIdentity, never one mega-row. TERMINAL acceptance: every primitive-bearing surface across all five authorities (registry, PrimitiveContract rows, algebra templates, interpreter dispatch, emit handlers) joins exactly one identity; population-wide join and realization refusals are the closing contract. D0 is a first slice only — not the closing contract. Namespace supplies declaration identity; this node supplies semantic primitive identity across invocation and realization. Why: Proves the determinism denominator is open (#7532 honest rung) and begins closing it: four surfaces feed primitive_surface_census_derived at runtime; mutation control is CLOSED on AlgebraTemplate and EmitHandler, PARTIAL on BuiltinRegistry and PrimitiveContractRow (parallel hand rosters — primitive_d0_derived_surface_drift_audit_note), NOT DISCOVERED on InterpreterDispatch. fold_list spelling fork and index_by registry+algebra dual absence are named specimens proving the denominator is not closed. Unlocks method ambiguity wall and D1 determinism consumption through PrimitiveIdentity rather than raw symbol. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **Method ambiguity refuses: more than one resolution is a located error once primitive identity defines sameness** — The method-existence wall closes cardinality zero; this closes more-than-one — two distinct primitive identities (or a primitive and a user fn) resolving for one receiver/method pair refuse with both candidates named. Gated on primitive-identity-join because ambiguity is decidable only when sameness is: the length/count/size dispatch aliases make one method look like three today (the gunbc#7484 measured fork), so an ambiguity check before the join would refuse legitimate corpus or silently pick. Why: The silent-first-hit dispatch class: today arm order decides which of two candidates runs — an unstated tiebreak the census cannot even measure until sameness is defined. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) — requires all: primitive-identity-join, method-zero-resolution-general-wall - - [ ] **Determinism denominator closure: every primitive carries one traversal-order classification** — v2.std.determinism and v2.lens.determinism consume std.primitive_identity: every PrimitiveDefinition carries exactly one PrimitiveTraversalOrderFact, unclassified refuses, HostUnspecifiedOrder derives a determinism root, aliases resolve through PrimitiveIdentity not raw symbol. Adding a new invokable primitive realization without a traversal-order fact reds before any caller uses it. Why: Dissolves the interim two-root registry (#7532) into a closed denominator — the gate can tell an alias from a distinct operation, and keys/values false-positive class is eliminated by identity not spelling. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) -- [ ] **Write the closing check for: The capability audit: sole_constructor completeness, then seal the proof-carriers** — One executable check states when this is done, transcribed from the node's own bar: Refined authored outside the sealing module refuses at compile in every probed form; refine remains the only inhabitation path; a probe form that does NOT refuse is a counted audit finding, never silently absorbed. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) - - [ ] **The capability audit: sole_constructor completeness, then seal the proof-carriers** — The language ALREADY has a construction wall: sole_constructor rejects cross-module construction with SoleConstructorViolation (post-merge review of gunbc#7486 refuted this node's earlier premise). What is UNVERIFIED is completeness. The CAST form is audited and CLOSED: it red on falsification, and 04_infer sole_constructor_construction_diags is now the one authority both the record literal and the cast consult. Caller admission is enrolled beside it. STILL UNVERIFIED: generic carriers, coproduct variants, default values, and module_skips_direct_call_arg_check. Continue auditing by falsification; extend sole_constructor or ground the reference-grant algebra only where the audit reds. Why: Every capability-kind ceiling resolves together — either sole_constructor suffices (cheap: seal Refined, delete the stub-pack forge arm) or its named gaps price the visibility-grants work honestly instead of by assumption. [authority](docs/plans/compiler-guarantee-recovery-gap-analysis.md) +- [ ] **Describe what a command should do, instead of writing shell text** — Callers say which operation and which arguments; nobody hands over an opaque string of shell as the instruction. Why: Shell syntax embedded in decision-making code cannot be checked before it runs, and cannot be reused anywhere else. [authority](docs/plans/shell-to-dag-residual-census-and-arc-completion.md) + - [ ] **Turn a described plan into shell through one translator** — One place turns a described plan into a shell script, preserving order, captured output, and failure behaviour. Why: Deletes the per-workflow shell writers and the scripts assembled by gluing strings together. [authority](docs/plans/shell-to-dag-residual-census-and-arc-completion.md) + - [ ] **Move the build and commit checks onto described plans** — The checks choose described operations and the translator produces the shell; the shell pasted into workflow files stops being a separate source of truth. Why: Lets us test what a check does before it runs, and use it outside of GitHub Actions. [authority](docs/plans/shell-to-dag-residual-census-and-arc-completion.md) +- [ ] **Write the closing check for: Account for the five hand-written files that progress output still needs** — One executable check states when this is done, transcribed from the node's own bar: Any new hand-written code for this outside those five files, or without this plan, is refused. The plan: Move this reach-through into a described operation, then hollow out the corresponding part of the command-line tool so generated code does the work; the hand-written bridge is deleted at that point. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](dag/gunbc/plans/cli_run_hollowing_plan.dag) + - [ ] **Account for the five hand-written files that progress output still needs** — Showing progress while a command runs still relies on hand-written code, in exactly these five files and no others: dag/gunbc/observation_seed_render.dag; dag/test/claim/observation_seed_scoped_run_witness_test.dag; src/v1/stage0/src/cli_run.rs; src/v1/stage0/src/v1_interpreter.rs; src/v1/stage0/tests/scoped_run_observation.rs Why: Why it is needed: Showing progress while a command runs has to reach the point where the old runner starts a program. It needs to know which piece of code was chosen, hold back error output until it can be shown in the right order, and report exactly one ending — and the rules for all of that live in the substrate, not in Rust. [authority](dag/gunbc/plans/cli_run_hollowing_plan.dag) + - [ ] **Show honest progress while a command runs** — Running a command announces that it started and reports exactly one ending. In a terminal it updates in place; piped to a file it appends plain lines. Why: Progress is printed raw today, so a terminal shows a mess and a log file shows something different again. [authority](docs/plans/progress-observation-design.md) + - [ ] **Get the full list of build jobs before deciding where they run** — Assemble all 48 jobs by stable identity and refuse if any is missing, duplicated, or has never been measured. Why: Deciding placement from a partial list produces a confident answer to the wrong question. [authority](docs/plans/ci-two-tier-placement-redesign.md) + - [ ] **Measure both machines instead of assuming they are alike** — Each kind of job earns the right to run on a machine by being measured there, warmed up, with its time and resource use recorded. Why: Assuming the two machines are interchangeable is how work lands on the one that cannot do it well. [authority](docs/plans/ci-two-tier-placement-redesign.md) + - [ ] **Run the new placement alongside the old one and compare** — Both make a decision for the same jobs; they must agree before the new one takes over. The new one does not control anything yet. Why: Finds disagreements while they are harmless instead of after they have moved real work. [authority](docs/plans/ci-two-tier-placement-redesign.md) + - [ ] **Switch over in one step and delete the old path** — A single change makes the new placement the only one and removes the shadow and the old selector together. Why: Ends the period where two things decide placement and nobody is sure which one acted. [authority](docs/plans/ci-two-tier-placement-redesign.md) + - [ ] **Derive the process, compiler and memory limits from one envelope per machine** — Shared compilation runs in its own managed place rather than inside whichever job slot happened to start it. How many processes a slot may hold, how wide the compiler may fan out, and how much memory the shared pool may take are all derived from one declared envelope for that machine instead of being three numbers anyone can set independently. Every limit is read back as the machine actually holds it. Why: The immediate outage — jobs unable to start processes against a cap far below the packaged one — and the fault behind it, where every machine's cache-miss compiles are charged to one job slot's budget and on one machine a retired slot's, are both consequences of these limits being separately writable. Two distinct failure kinds were charged to whatever code happened to be under test for a whole day: jobs dying unable to start a process at all, and jobs killed from an outer budget while sitting far under their own — across four independent measurements at 8.8 percent of the visible limit, with the peak identical on every attempt. Neither is a defect in any change under review, and both arrive looking exactly like one. [authority](docs/plans/srvn-buildcache-provisioning-design.md) +- [ ] **Write the closing check for: CI2-0: complete v2 witness-execution cutover — every test identity v2-planned and executed, old route deleted, in #8043** — One executable check states when this is done, transcribed from the node's own bar: Per identity, semantic kind and realization standing are orthogonal, one each: TestSemanticKind (hermetic, live-observation, effect, v1-subject) times V2RealizationStanding (emitted, subject-bound execution, effect execution, or Refused with phase and exact diagnostic from an ACTUAL realization attempt — never a default). The population is identity-grain, duplicate-free, complete both directions, bound to the exact tested tree; removed, duplicated, or substituted identities refuse the join. The planted broken test fails through the v2 route. The final receipt reconciles: kinds sum to N, realizations planned equals completed equals accounted equals N, refused equals unavailable equals diverged equals fallback equals ordinary-v1-executions equals remaining-v1-scheduler-rows equals zero, with cold generation, cold compile, warm materialization lookup, execution walls and peak memory observed. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](docs/plans/five-minute-ci-gate-design.md) + - [ ] **CI2-0: complete v2 witness-execution cutover — every test identity v2-planned and executed, old route deleted, in #8043** — #8043 owns the COMPLETE transition and does not merge as a three-test cutover (operator mandate 2026-08-08, superseding the staged rulings): every test identity on the exact PR head is planned by v2, lowered through the EXISTING compiler path, realized through a v2-owned route — hermetic to emitted Rust; live observation subject-bound; effects as v2 plans with independent readback; v1-subject tests harnessed BY v2, v1 never the scheduler or verdict — executed, accounted exactly once. Ordinary v1 executions, fallback, and unaccounted identities all zero. NO successor producer, live-read, effects, or measurement PR; a wiring refusal is an in-PR repair item. Why: The three-member route is fixture-bound — specimen token lists, grammar rows, binding spellings, per-test target models — and the census's three-of-9,353 was CIRCULAR: it never attempted canonical realization, it restated the enrollment. The compiler already lowers ordinary bodies (#6520, #6526, #6558, #6512, body_producer_forward) and the live-read, effect, and routing authorities exist; what is missing is wiring the complete roster through the canonical pipeline, not new machinery. The control plane repeatedly mistook fixture-route limits for compiler limits. [authority](docs/plans/five-minute-ci-gate-design.md) + - [ ] **Serve every required CI operation with the bare minimum computation; five minutes is the distress ceiling, not the contract** — Every required operation performs only the computation its inputs require — no semantic fact recomputed whose inputs did not change (re-anchored by operator ruling 2026-08-02: five minutes is the distress ceiling — the Stage C checkpoint bound, not the contract). Sub-lanes (unordered roster — dispatch order follows the graph edges; the native child row precedes this gate): #7522 warm-merge-admission net receipt; the child row's ONE-PR CI-0, superseding the former slice-and-cutover steps; #7534 materialization substrate; union closed → per-entry assembly; this registration. Why: Today witness bodies are still interpreted and materialization is recomputed across phases — confidence is bought with time the product boundary forbids. [authority](docs/plans/five-minute-ci-gate-design.md) + - [ ] **Admit merges from warm stamped receipts, not cold recomputation** — #7522 in flight (dispatch order follows the graph edges): fold resolve and materialization receipt gates into merge admission so the pull-request path stamps once and admits from warm receipts. Operator expectation: probable net recovery ~1–1.5 min (not ~3 min) — final-head run decides; not transcribed as receipt until measured. Why: Merge admission and the floor tail currently pay separate cold paths for facts already fixed when the run started. [authority](docs/plans/five-minute-ci-gate-design.md) + - [ ] **Recompute only the edited module and its semantic dependents** — NEXT (sequence step 4, #7534 continues): shared materialization substrate — hierarchy module parse/type/interface → resolved entry → selected native witness bundle. No default CI savings credited until ordinary invocations consume hits. FIRST RUNG: exact-tree cross-process cache (#7534, opt-in) — verified serve, not default CI activation. Why: Cross-entry typed-module work is repeated today even when module inputs are byte-identical across entries in one process. [authority](docs/plans/five-minute-ci-gate-design.md) + - [ ] **Probe materialization before corpus-scale index construction** — NEXT: a small persistent manifest lets a warm materialization hit occur before building the shared entry index and source-loading closure — serve and dashboard currently construct the index first and only then discover the cache. Why: Warm hits that still pay corpus index construction reproduce the measured startup outage on real dashboard entries. [authority](docs/plans/five-minute-ci-gate-design.md) — requires all: five-minute-ci-gate, module-grain-materialization + - [ ] **Share one initialized substrate across regen, floor, and admission** — NEXT: regen, floor, and admission run in one initialized process with phase boundaries and separate verdicts — regen still gates floor. NOT flattening into ordinary batches; duplicate prelude work is the target. Why: Each phase currently cold-starts its own substrate — paying resolve, index construction, and discovery more than once per pull request. [authority](docs/plans/five-minute-ci-gate-design.md) — requires all: five-minute-ci-gate, ci-scoped-worker-shared-substrate + - [ ] **Decompose per-entry assembly after the shared-typecheck hypothesis closed** — NEXT (sequence step 5, narrow lane): after #7533 closed entry-graph-union on zero repeated typecheck misses, attribute and reduce per-entry assembly/reconcile work — the remaining compiler term on the floor path. Why: Assembly and reconcile rows still scale with entry count even when typecheck is already shared across entries in one index. [authority](docs/plans/five-minute-ci-gate-design.md) — requires all: five-minute-ci-gate, module-grain-materialization + - [ ] **One ledger for what the CI-speed work actually bought** — One row per attempt made across this arc: what it predicted, what it changed, the exact subject it was measured on, the movement inside its own segment, the movement in the whole required path, whether memory stayed inside the fixed envelope, and a typed result — delivered, partial, falsified, confounded by infrastructure, or evidence invalid — with what each one implies next. Rows are read out of receipts that already exist; nothing is re-measured to fill this in. Why: Weeks of work bought real causal knowledge and several correctly killed hypotheses, but the required path is not meaningfully shorter. Without this ledger the next programme re-runs the attempts already falsified, and re-argues the ones that were only confounded by a machine failure. [authority](docs/plans/ci-performance-tanking-evidence.md) + - [ ] **Compile the compiler once per change, not once per job** — One expensive compiler build per change under test. Small checks stop dragging the whole compiler into a second build profile in order to run a few milliseconds of assertions: work that only observes the tree gets a small target that does not rebuild the monolith. Why: Building and testing is now the largest single cost of getting a change in, and a measured run spent minutes of compiler builds to execute a fraction of a second of checks. [authority](docs/plans/ci-performance-tanking-evidence.md) + - [ ] **Walk the corpus once and let both workers read the same result** — One complete discovery result is produced once and consumed by the second worker, instead of the same walk happening again in another process. It carries the whole typed result, never a pass-or-fail flag. A consumer that finds it absent, damaged, or belonging to a different subject refuses; it never quietly recomputes. Why: The ordinary worker and the scoped worker each perform effectively the same full walk in separate processes, and the second one measures around 284 seconds. It is the clearest several-minute target on the required path, and it is the first real use of computing something once and reusing it rather than a description of one. [authority](docs/plans/cross-worker-typecheck-share-design.md) — requires all: ci-cost-arc-reconciliation, module-grain-materialization + - [ ] **Stop building a second compiler world in order to run twenty-seven witnesses** — The scoped worker consumes an already-initialised substrate, or runs as a later stage inside the ordinary one, rather than constructing its own sources, indexes and environments. Staying in its own process remains allowed: isolation means separate mutable scratch and its own lifetime, never recomputing facts that are already fixed and immutable. Why: The scoped worker executes twenty-seven witnesses and pays for a whole second world to do it. The process boundary is being used as a way of handing memory back, and a cold start is the price currently paid for that. [authority](docs/plans/cross-worker-typecheck-share-design.md) + - [ ] **Decide what matters before paying to prepare everything** — Which computations a change affects is decided from source and module facts, ahead of broad roster, index, grouping and closure preparation. Only the views that decision actually needs are then prepared. Why: Selection is already sharp about meaning and blunt about cost: it can correctly conclude that most of the corpus is irrelevant, but only after the corpus has been discovered, named, rostered and indexed. The saving it reports is real while the work it claims to have avoided has largely already been done. [authority](docs/plans/ci-selection-vs-scheduling.md) + - [ ] **Start running as soon as the first thing is ready** — Preparation stops being a barrier standing in front of execution. A module's facts becoming ready makes an entry view ready, which makes a witness ready, which runs — while everything else is still being prepared. Why: Several hundred seconds are currently spent preparing every selected entry before the first one executes. That is a barrier, not work that had to finish first. [authority](docs/plans/floor-prep-tax-program.md) — requires all: ci-selection-before-preparation, ci-scoped-worker-shared-substrate +- [ ] **Write the closing check for: Stop re-walking the string to read one character** — One executable check states when this is done, transcribed from the node's own bar: Identical output over the corpus is the equivalence half and is satisfied by changing nothing, so it does not stand alone: a long literal mixing non-ASCII text with escapes must separate the two implementations by cost, and a deliberately wrong escape decode must change the output. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](docs/plans/inner-cost-lanes-scoping.md) + - [ ] **Stop re-walking the string to read one character** — One function in the tokenizer still reads a string by counting from the start each time, while the rest of its file was already moved onto a pre-decoded form. Move it too, and collect its result into one buffer instead of a list of one-character pieces. Why: For text that is entirely plain ASCII this costs one allocation per character. For text containing any non-ASCII character at all, reading position N costs N steps and asking how long the string is re-counts the whole thing, on every pass — so the work grows with the square of the literal's length, and a single accented character anywhere flips the whole literal onto that path. [authority](docs/plans/inner-cost-lanes-scoping.md) +- [ ] **Write the closing check for: One way to ask for work to be done, with the subject pinned exactly** — One executable check states when this is done, transcribed from the node's own bar: A request whose subject is a moving reference refuses. A provider that cannot serve the requested class of operation refuses before running rather than quietly answering a smaller question. The five endings stay five — a machine that killed the work cannot render as the work having failed. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](docs/plans/execution-spine-design.md) + - [ ] **One way to ask for work to be done, with the subject pinned exactly** — A request names the exact tree it applies to, the operation, the inputs it declares and the outputs it promises, the toolchain and environment it needs, its resource envelope, and which providers may serve it. The answer is one of a fixed set of endings: succeeded, failed, refused by the infrastructure, cancelled, or refused because what came back did not match the outputs declared. A branch name or a working directory is not a subject. Why: Building, checking, regenerating, changing machines, serving models and eventually judging changes are each growing a separate way of asking for work to be done. Four execution systems means four queues, four retry rules, four ideas of what success means, and nowhere to say where a piece of work actually ran. [authority](docs/plans/execution-spine-design.md) + - [ ] **Work that cannot hand back what it produced has not succeeded** — Every output a computation declared comes back identified by its content, with its size and what produced it, through a manifest the caller can read. Whether those bytes are then copied into a working tree is a separate and explicit step. Work that produces files may only run where its outputs can be returned; a provider that cannot return them offers checking only, and refuses producing work before it starts rather than after. Why: A remote run finished successfully and produced nothing locally; the stale binary left behind was then compared against itself and reported no difference. Every remote build in this environment currently has that shape, and it is silent. [authority](docs/plans/execution-spine-design.md) + - [ ] **Contain the remote build wrapper to its safe subset, then retire it into the compute contract** — This row is a migration, not a home. Until outputs can be handed back, the wrapper admits exactly one remote mode — checking something without producing anything the local tree then needs — and refuses any invocation that would write. Every result names the source it checked and the executable it checked with, so a stale local artefact can never stand in for a remote answer. When the compute contract can return outputs, the wrapper's remaining useful behaviour becomes one provider behind it and the wrapper is deleted rather than extended. Why: There is no stated rule for when this wrapper is the right thing, so it gets guessed at; a remote run meant to produce files can finish successfully having produced nothing locally, and that has already been consumed as a passing check. Every day it stays unclassified, more work is authored against a boundary that means two contradictory things. [authority](docs/plans/ci-process-end-to-end.md) + - [ ] **Identical work runs once and is handed to everyone who asked for it** — Two requests naming the same computation are the same work: one producer runs and everyone else attaches to its result as a consumer. The fabric decides what may run — how many cold builds a machine carries at once, what the compile pool's total demand may be, which host serves a request, and what order competing work is served in — and tells a caller which of those it is waiting on. A caller never starts something merely because it cannot tell whether somebody else is already producing it. Why: Ten agents needing the same binary for the same tree currently means ten builds, because nothing lets one of them discover the other nine. Moving the present arrangement onto our own machines WITHOUT this multiplies the load instead of sharing it, which is why this row comes before more consumers rather than after them. [authority](docs/plans/execution-spine-design.md) — requires all: compute-exact-work-contract, compute-artifact-return-and-materialization + - [ ] **Run the required checks on our own machines, from a subject we hold ourselves** — Seeing a new commit creates a durable piece of work of our own, identified by the exact base, head, and combined commit under test. One machine holds it under a lease for the whole run, and the run ends in one typed result we keep. Starting, queueing and handing out the work involve no third-party workflow and no registered runner. The old floor is quarry and shadow oracle, not the template: obligations port only when a disagreement proves they carry a real property, and its batches, ordering, settlement and status vocabulary are not inherited. Why: Starting, queueing, handing out, cancelling and reporting all belong to someone else today. When their side has a bad day our machines wedge, their re-runs test a stale combination, and every one of those failures arrives looking like a fault in our own code. [authority](docs/plans/ci-process-end-to-end.md) — requires all: compute-exact-work-contract, compute-artifact-return-and-materialization, compute-deduplication-and-admission + - [ ] **The result shown on GitHub is a copy of ours, never the original** — Our stored result is published as the required check. No check present means not yet observed and never counts as satisfied. Cancelled, timed out, refused by the infrastructure, and genuinely failed stay four different things the whole way through. A queued merge is checked against the exact commit that queue built. If GitHub cannot be reached the publishing waits; the result itself is never lost. Why: An absent check currently reads the same as one that passed elsewhere, and a job the machine killed arrives as an ordinary failure — so the board cannot tell our defects from theirs, and people learn to re-run rather than read. [authority](docs/plans/ci-process-end-to-end.md) — requires all: ci-owned-execution, ci-executed-coverage-receipt, ci-gate-contention-independent-verdict + - [ ] **Remove the hosted-runner path once we carry the required checks** — With our own path publishing the required checks, the workflow, the runner registrations and the recovery timer are removed together in one change, so there is never a period in which two things can hand out the same work. Why: Retires the vendor's dispatcher as something our fleet's health depends on — the reason the recovery timer has to exist at all, and the reason a bad day on their side becomes a wedged fleet on ours. [authority](docs/plans/ci-process-end-to-end.md) +- [ ] **Write the closing check for: A check reports what it actually executed, not merely that it ran** — One executable check states when this is done, transcribed from the node's own bar: A run whose executed population is zero refuses rather than reporting either pass or fail. A failure ahead of the corpus is a different outcome from a corpus disagreement. A check that cannot say what it executed does not count as having executed. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](docs/plans/ci-process-end-to-end.md) + - [ ] **A check reports what it actually executed, not merely that it ran** — Every required check carries the population it executed — which witnesses, which corpus, how many — and a run that executed nothing cannot report a verdict of any kind. A build failure that stops the corpus from running is its own outcome, distinct from the corpus running and disagreeing. Why: A scheduled check spent days reporting failures while executing no corpus at all, because a step ahead of it failed and the result looked exactly like a real disagreement. Nobody could tell the difference from outside, so a genuine signal was ignored for days. [authority](docs/plans/ci-process-end-to-end.md) +- [ ] **Write the closing check for: Host load cannot decide a semantic merge verdict** — One executable check states when this is done, transcribed from the node's own bar: Repeated runs of the same unchanged code land on the same semantic side. A timeout reports as ExecutionTimedOut or CoverageRefused, never as an assertion failure. Raising the limit, or moving the check somewhere it stops running per change, refuses as a way of closing this. A quiet-host pass is not accepted as the only evidence. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](docs/plans/ci-performance-tanking-evidence.md) + - [ ] **Host load cannot decide a semantic merge verdict** — A check that blocks merging decides something about the change. The semantic verdict derives from the executed assertion; a timeout or resource exhaustion is a distinct execution outcome, never projected into assertion failure, and never absorbed by widening the cutoff. Work that cannot fit its budget is decomposed or moved through the correct execution mechanism — the cutoff is not raised to admit it. Why: One check was measured ten times across four separate efforts with nothing in the code changing between them, and the readings ranged from well under its limit to well over it. It blocked one change outright, produced a false alarm on another whose own checks all passed, and sent three separate efforts hunting for a defect that was never there — an unchanged semantic subject passing or failing on host contention. [authority](docs/plans/ci-performance-tanking-evidence.md) +- [ ] **Write the closing check for: One admission predicate that every commit writer must pass before it may write** — One executable check states when this is done, transcribed from the node's own bar: A merge with unmerged index entries refuses typed and located; the staged-marker-text variant refuses separately; an observation population that differs from the staged index refuses in both directions; a freely constructed provenance value whose path happens to match a staged file refuses; a clean tree admits. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](docs/plans/generated-file-conflict-policy.md) + - [ ] **One admission predicate that every commit writer must pass before it may write** — Admission consumes a complete staged-index observation whose population must equal the exact staged index, never a caller-selected list, with two independent refusal arms: any unmerged index stage refuses typed and located with the affected path, and the staged blobs are inspected for the complete conflict-marker grammar, so a file collapsed to stage zero with markers inside refuses. Text classification is observed, never caller-declared; a fixture exemption is a provenance receipt bound to the same staged path and blob, never a path allowlist. The pre-commit hook stays fast local feedback; the predicate at the writer's boundary is the enforcement. Why: An automated writer has already committed and pushed literal conflict-marker blocks to main by committing through an unresolved merge, and the refusing merge driver raised that class only to mechanically preventable — the repository's own config authority states that commit-writer admission remains the boundary a writer cannot pass. Until this lane lands, the other lanes shrink how many files can be caught by textual adjacency while every writer stays one absorbing fallback away from repeating the incident on the files that remain. [authority](docs/plans/generated-file-conflict-policy.md) + - [ ] **Stop writing the whole file registry into a generated file** — A generated artefact must not carry the full list of generated paths. The workflow asks Git which of the paths that actually conflicted are generated, reading that classification from both sides' committed state rather than from a list baked into the workflow text. Adding a generated file then changes the classification projection and leaves the workflow's own bytes alone. Why: One enormous line holding every generated path means any change to that population rewrites the same spot in a shared file, so two branches conflict over a list neither of them disagrees about. That has happened repeatedly, and each occurrence costs a manual resolution carrying no information. [authority](docs/plans/generated-file-conflict-policy.md) + - [ ] **Decide which generated files need to be in the repository at all** — For every generated file we commit, name the consumer that requires the bytes to be present: something outside reads it before our own tools can run, the build needs it to start at all, or a reviewer has to see the exact difference. Anything else is published or produced on demand instead of committed. Generated outputs that stay get their own directories, so what is generated is structural rather than a list someone maintains. Why: A large share of the merge pain comes from files nobody reads as bytes in the repository. Each one that leaves stops producing conflicts, review noise and drift checks permanently, rather than having its conflicts handled better. [authority](docs/plans/generated-file-conflict-policy.md) +- [ ] **Write the closing check for: Write down which parts of our machines we actually manage** — One executable check states when this is done, transcribed from the node's own bar: An undeclared file, service, credential, or device refuses before anything is changed. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](docs/plans/host-converge-inventory.md) + - [ ] **Write down which parts of our machines we actually manage** — Every file, service, credential, and device is either derived by us, owned by us, or someone else's with an agreed contract. Anything unclassified refuses. Why: Shrinks the number of possible machine states our automation has to cope with. [authority](docs/plans/host-converge-inventory.md) + - [ ] **Manage the server firmware and cooling settings safely and repeatably** — Read the current firmware, board, fans, and temperature limits; plan a change, apply it, read it back, and roll back if anything looks wrong. Why: Replaces one-off manual fan tuning with something repeatable that checks itself. [authority](docs/runbooks/bmc-redfish-operator-access.md) + - [ ] **Change one machine setting and check it really changed** — State the desired setting, observe the current one, compare, apply, then independently read it back before calling it done. Why: Turns configuration that is merely written down into configuration that is actually applied and re-checkable. [authority](docs/plans/host-effect-orchestration.md) — requires all: fleet-state-frontier, shell-gate-migration + - [ ] **Keep re-checking, and notice things we did not put there** — Something runs periodically, re-observes everything we manage, and spots unmanaged state inside the areas we own. Why: Finds configuration drift and stale processes before the next deployment turns them into an incident. [authority](docs/plans/fleet-acceptance-criteria.md) + - [ ] **Only call the machines 'managed' once all of it has really run** — This closes when applying for real, reading back, re-running safely, undoing, and the periodic check have all actually happened. Why: Stops a tidy model or a single successful run from being mistaken for infrastructure you can rely on. [authority](docs/plans/fleet-acceptance-criteria.md) + - [ ] **A merge to main is the instruction, and nothing else applies it** — The state the machines should be in is the exact revision merged to main, held durably by whatever is applying it. A newer revision replaces an older one, an older or unrelated one refuses, and the same one again does nothing. The instruction survives a restart of the thing applying it and an outage at the hosting provider — an event from them may make it happen sooner but is never the only place the instruction lives. A machine that was unreachable or refused is tried again against the revision still held, with no new commit needed. Why: Every fleet-touching merge currently ends with the same question — is that actually on the machines — and the only way to answer it is to go and look. Somebody has to remember to apply things, and forgetting is invisible until it causes an incident. [authority](docs/plans/fleet-self-converge-enforcement-design.md) + - [ ] **The build machines' own limits and services converge from main like everything else** — The job slots' limits, where shared compilation runs, the recovery timer, and the values those actually take on each machine are all stated as desired state derived from main and converged everywhere. Editing the desired row and merging it is the whole operator action. Why: This is the configuration whose hand-drift produced the process-limit outage and left the shared build cache running inside a retired job slot. It is also the first subject worth proving the whole path on, because it is real, it is already wrong, and fixing it by hand is what created the drift. [authority](docs/plans/fleet-self-converge-enforcement-design.md) — requires all: fleet-main-revision-authority, fleet-atomic-convergence-verdict, placement-compile-pool-envelope, compute-exact-work-contract + - [ ] **Count every automatic job-runner recovery, and say what retires it** — The timer that restarts wedged job runners records each firing — which machine, which slot, what it observed, what it did, and whether it worked — and those counts reach the fleet's health. Deciding locally that a runner has stopped making progress is kept separate from asking the provider whether it is still registered, so recovery keeps working during the outage it exists for. Why: It fires roughly ten times a day across the fleet and nothing counts it, so the fleet looks healthy because something invisible is holding it up. The firing rate is the only thing separating a legitimate stopgap from a fault we have stopped noticing. [authority](docs/plans/fleet-self-converge-enforcement-design.md) + - [ ] **Bring the two new machines up as model-serving members, through the same path as everything else** — Both new machines join through the same standing-up sequence every other machine uses, reach a stated model and runtime, and serve requests on an endpoint. Which model is actually being served, and whether the endpoint is healthy, are read back independently, and each machine stays separately observable. They serve models — they are not job runners and not build caches. Why: Standing these up by hand would create two more machines nobody can answer questions about, which is the state this whole lane exists to end. Going through the convergence path also proves that path on a machine class it has never seen. [authority](dag/gunbc/dgx_spark_procurement.dag) — requires all: fleet-main-revision-authority, fleet-atomic-convergence-verdict, compute-exact-work-contract, fleet-spark-host-enrollment + - [ ] **One answer for the whole fleet, or exactly which machines are not there yet** — Each machine produces a receipt — what was wanted, what was there, what was planned, what was applied, and what was read back afterwards. Those fold into a single answer for the fleet, joined with the lifecycle cells enrollment constructs: every required host-and-phase cell is observed, refused, unreachable, drifted, or converged, and a required cell nobody has observed prevents convergence rather than defaulting green. Anything short of all of them is not converged, with the machines that refused, could not be reached, or have drifted each named separately. Why: One machine can hold the new value while the others hold the old one and nothing anywhere says so, which is exactly why the honest answer to whether something is applied is currently to go and check by hand. And the fleet phase matrix currently has exactly one real observation producer cell — one host, one phase, hard-coded — so every other lifecycle claim in the fleet is unobserved by construction; enrollment makes obligations countable, not satisfied. [authority](docs/plans/fleet-acceptance-criteria.md) + - [ ] **Deploy every copy of the dashboard through the one deployment mechanism** — Deploying a dashboard uses the same mechanism as everything else we deploy: work out what should be there, compare it to what is, and change only the difference. Which machine it goes to becomes a parameter, not a second copy of the logic. Why: There are currently two deployment engines for the same three files. The second one does not know what it owns, and reaches the machine outside the checked path — so it can tear down something that was never ours. [authority](docs/plans/gunbc-served-dashboard-design.md) + - [ ] **Serve the roadmap page from emitted native code, never the tree-walking interpreter** — The interpreted accept loop is a declared scaffold whose dissolution trigger has now fired in production: a two-hour page wedge, one core pinned in memcpy, thirteen connections queued behind a single thread. The fix is ordered. Wire roadmap_serve_handle through the existing emit-on-demand path FIRST — interpreted concat copies its accumulator on every call, quadratic; emitted concat moves, linear: a complexity-class change, not tuning. Then memoize page bodies by content hash of their inputs so a request is lookup-and-write. Concurrency only after bodies are immutable artifacts. A request deadline returns a typed refusal instead of eating the process. Why: The 2026-08-08 srv1 outage: gunbc serve wedged for two hours rendering the daily page, every route behind it dead, and it re-wedges on the next load of the same shape. The scaffold row already names emit-on-demand as its dissolution and the module carries zero references to it — declared and left standing. Separately, the belt reconcile has been dead as long as GcpProjectId has failed to resolve, burning about two minutes of CPU on every timer tick before exiting. [authority](docs/plans/gunbc-served-dashboard-design.md) +- [ ] **Write the closing check for: Turn the two Spark machines from hand-managed boxes into exact fleet members** — One executable check states when this is done, transcribed from the node's own bar: A count witness multiplying enrolled hosts by the whole spine refuses — expected cells are the duplicate-free identity join of each enrollment's participation-derived program, and runner plus inference union exactly with no duplicated cell. An inference enrollment carrying BMC, OS-install, runner-slot, or build-cache obligations refuses. Changing one participation changes only the obligations that participation owns. An endpoint row re-typing the reserved address literal refuses; a reverse import into the network intent refuses. Any Spark cell presented as observed without a producer refuses. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](dag/gunbc/dgx_spark_procurement.dag) + - [ ] **Turn the two Spark machines from hand-managed boxes into exact fleet members** — srv5 and srv6 become fleet members: enrollment intents carrying InferenceServingParticipation, with endpoint rows derived downstream from the assigned reservation rows — re-typing the address literal refuses, a reverse import into the network intent refuses, and the join is a projection between the two existing authorities, never a second host-identity authority. Enrollment constructs their four required lifecycle cells — identity converge, reach, compute-fabric enrolment, assimilation complete — and every cell stays honestly Unobserved until an observation producer answers it. Enrolled is not converged: no identity convergence, reach satisfaction, capacity acceptance, or serving claim lives here. Why: Two machines are reserved, physically bound, and answerable by nobody: no fleet row knows they exist, so their state is whatever hands left there. The stale count witnesses still encode enrolled-hosts-times-whole-spine — every enrolled host presumed a runner, wrong since participation scoping landed and exposed by srv5. And the endpoint-derivation question already burned a session against an import cycle because the projection layer this row creates did not exist. [authority](dag/gunbc/dgx_spark_procurement.dag) +- [ ] **Write the closing check for: Ask what depends on this exact thing, and get an explained, honest answer** — One executable check states when this is done, transcribed from the node's own bar: A query whose subject module does not exist refuses rather than answering empty. A planted unbound module appears in the standing, and no reading over a pool with unaccounted members reports itself closed. The result may not claim one-sided incompleteness while pool independence is unavailable — the standing carries the trigger. A second edge producer beside the declared single authority refuses. Forward-intent refusals (operator amendment 2026-08-08): a branch name without an exact revision is not a subject; a deleted declaration is never silently resolved against the head tree; an unresolved input never becomes an empty result; a partial universe cannot report that no relevant authority exists — no-matches and could-not-see-enough remain different answers. The deterministic layer never turns prose into repository identity: it returns the bounded universe, exact candidate declarations, consumers, explanation paths, and standing for a later mapper or judge. Why: Turns a prose bar into a runnable verdict, so the node it gates can be dispatched and its completion checked rather than judged. [authority](src/v2/lens/module_graph.dag) + - [ ] **Ask what depends on this exact thing, and get an explained, honest answer** — Given an exact graph subject and one or more semantic node identities, return direct and transitive dependencies or dependents, explanation paths, production and test consumers, and an explicit completeness standing. Git diffs, paths, line and column coordinates, and natural-language questions are adapters into this semantic query — they are not its identity grain. The reading consumes the existing single edge authority as data and never grows a second edge producer; where the finest grain is unavailable, the standing says so with its trigger rather than answering coarser and calling it fine. Why: Whether a concept already exists, who consumes it, what executes it, and what a deletion would break are today answered by grep, memory, or not at all — every session re-derives them, duplicates get minted beside existing authorities, and the reverse question is simulated with N forward closures. This deterministic layer is also the grounding packet the judgment service requires: without it, the machine judgement lane has nothing checkable to stand on. Acceptance standing (2026-08-08, post-rework): the reverse query is merged; the changed-surface rework closed departed-path consumption, the typed Complete/Partial/Refused outcome (unresolved input contaminates the whole result), the skip-authority wall (SkipAuthorityGranted reachable only from Complete AND exact), optional cycle analysis, the two-direction selector-parity control, and three real-diff receipts — one of which falsified a fixture assumption (git reports a deletion in both projections). OPEN: the base-graph population — a deleted subject resolves to typed BaseGraphUnavailable because every graph reader is head-only; building revision-scoped reading is an operator decision (new seed Rust or a heavy checkout). Measured correction: cycles were 28 percent of the wall, not most — the dominant cost is resolve and fact production. [authority](src/v2/lens/module_graph.dag) + - [ ] **Find subjects that may deserve one shared abstraction by how they are constructed and consumed** — Given an exact graph subject, a semantic universe, and a named lens over construction, consumption, effects, tests, lifecycle, or presentation, group observationally equivalent subjects into grounded abstraction candidates. The result is DESCRIPTIVE evidence only: it mints no authority, rewrites no code, and never decides a quotient is valid — similarity under a lens is evidence; 'these should share one authority' is a separate normative ruling crossing that bridge explicitly. Every candidate names its universe, lens, equivalent observations, the distinctions the quotient would erase, nearby authorities, the consumers that would use it, and its over-collapse and demand standing. Why: The grouping move — five things consumed the same way become one new layer — is how this repository forms layers at all, and today it happens only when a person notices: duplicates get minted beside existing authorities, boutique implementations accumulate until a consolidation pays the fork back at interest, and the judgment of which lens a similarity holds under is re-derived by hand every time. The candidate that is NOT an abstraction is half the value: the Spark endpoint question burned a session on topology surgery that a projection-missing classification — join the existing authorities downstream, mint nothing — would have answered in one line. [authority](src/v2/lens/module_graph.dag) diff --git a/dag/gunbc/roadmap_authority.dag b/dag/gunbc/roadmap_authority.dag index 8f5d334a7eb..451224feb44 100644 --- a/dag/gunbc/roadmap_authority.dag +++ b/dag/gunbc/roadmap_authority.dag @@ -97,10 +97,12 @@ fn signed(by: String, works: RedControlEvidence, scope_equivalent: HandbackEvide data namespace_kernel_authority_amendment_note: String = "THE KERNEL BAR AS FIRST WRITTEN DEADLOCKED ITS OWN SUCCESSOR, and this amendment is the fix (operator ruling, 2026-08-01). The recut landed namespace-binding-kernel with a red_control ending 'no second place in the compiler makes that same decision' and a handback demanding 'evidence that nothing else decides the same question'. That bar cannot be met while the bootstrap seed keeps its own hand-written none/one/many mirror inside the v1 adapter, and that mirror cannot be deleted until generic emission carries the canonical carrier with its required bounds. But namespace-reference-derived-closure DEPENDS on the kernel. So on the day the recut merged, the next step was already unstartable for a reason that had nothing to do with what the next step needs: deriving candidate populations does not require the seed mirror to be gone, it requires the canonical decision to EXIST and be callable.\n\nTHE CUT IS AUTHORITY VERSUS RETIREMENT. Owning the canonical decision and removing every rival implementation of it are separate achievements with separate evidence, and fusing them made an acceptance bar that no amount of correct kernel work could satisfy. The kernel now owns being the one canonical authority; namespace-legacy-choice-retirement owns deleting the older mechanisms, and the seed mirror is named there as one of its rows. A new node was deliberately NOT minted: that node already reads 'delete every other way of deciding what a name means' and already inventories every remaining place that decides, so the mirror is a row it should always have carried, and minting a sibling would have been a second home for one concept.\n\nTHE ADAPTER IS NOT THEREBY BLESSED. It remains a declared scaffold with its dissolution trigger on its own carrier, counted and visible; moving WHERE its retirement is tracked does not lower what is owed. What changes is only that a temporary bootstrap realization no longer gates work that does not depend on it.\n\nTWO ACCEPTANCE SENTENCES WERE ALSO WRONG AND ARE CORRECTED HERE. The red_control said 'a malformed name refuses', but this boundary accepts occurrence identities and derives no name spelling at all, so it named a check no implementation executes — it now reads malformed occurrence TRANSPORT. And the bar now states the two refusals that distinguish a malformed supplied population from a real verdict: a candidate handed in the wrong role refuses distinctly from one that is absent, and the same declaration supplied twice refuses as malformed rather than counting as two. Only two DISTINCT declaration identities are ambiguity. Collapsing either of those into an ordinary verdict would let a defective candidate producer read as a legitimate naming answer, which is the state-space conflation this whole lane exists to remove." -data five_minute_ci_gate_program_note: String = "PROGRAM BOUNDARY, NOT A SINGLE PR (operator dispatch K, 2026-08-01; sequence revision eager-boar-610, 2026-08-01; contract re-anchored by operator ruling 2026-08-02). Product contract: every required operation performs the bare minimum computation needed to serve it — no recomputation of any semantic fact whose inputs did not change; wall-clock falls out of the realization/materialization goals rather than defining them. Five minutes is NOT the contract — it is the distress ceiling ('this is hard / not working well'), retained only as the Stage C checkpoint bound. Six sub-lanes plus an explicit operator sequence — (1) finish #7522 warm-merge-admission with net wall receipt; (2) merge #7599 native bundle slice; (3) immediately cut over bounded production witness population to native execution kind (#7599 owner); (4) continue #7534 as materialization substrate under module-grain (hierarchy: module parse/type/interface → resolved entry → native bundle; no default CI savings until ordinary invocations consume it); (5) union CLOSED per #7533 → per-entry-assembly narrow lane; (6) this parent registration. Native-at-small-scale evidence has LANDED; operational migration rate remains OPEN with equivalence/fallback/identity/memory fail-closed. The landed change, its merge commit, the head the witnesses actually executed at, and the cited executing witness are execution evidence, not design criteria: they live in ONE typed receipt, gunbc.native_witness_transition_receipt native_at_small_scale_transition_receipt, and are restated neither here nor in gunbc.design_document (operator ruling on gunbc#7710, 2026-08-02 — this note and the design document previously carried independent hand-maintained copies of the same advancing status). Staged expectations (not flat 30–35min): Stage A modest admission saving only; Stage B first order-of-magnitude witness reduction after production native cutover; Stage C five-minute gate when all conjunction in docs/plans/five-minute-ci-gate-design.md holds. Dispositions: entry-graph-union CLOSED; retention PARKED (#7581). See docs/plans/five-minute-ci-gate-design.md." +data five_minute_ci_gate_program_note: String = "PROGRAM BOUNDARY, NOT A SINGLE PR (operator dispatch K, 2026-08-01; sequence revision eager-boar-610, 2026-08-01; contract re-anchored by operator ruling 2026-08-02). Product contract: every required operation performs the bare minimum computation needed to serve it — no recomputation of any semantic fact whose inputs did not change; wall-clock falls out of the realization/materialization goals rather than defining them. Five minutes is NOT the contract — it is the distress ceiling ('this is hard / not working well'), retained only as the Stage C checkpoint bound. Sub-lane roster, revised by the CI-0 recut (operator 2026-08-08) — UNORDERED: dispatch order follows the graph edges, where the native child row now precedes this gate. The lanes: #7522 warm-merge-admission with net wall receipt; the native lane as the child row native-selected-witness-bundle's ONE-PR CI-0: #7599 is merged, and fallback deletion, complete roster classification, whole-corpus emission, execution and authority land as one state transition in one PR, superseding the former separate merge-the-slice / bounded-cutover steps this note used to carry; #7534 continued as materialization substrate under module-grain (hierarchy: module parse/type/interface → resolved entry → native bundle; no default CI savings until ordinary invocations consume it); union CLOSED per #7533 → per-entry-assembly narrow lane; this parent registration. Native-at-small-scale evidence has LANDED; operational migration rate remains OPEN with equivalence/fallback/identity/memory fail-closed. The landed change, its merge commit, the head the witnesses actually executed at, and the cited executing witness are execution evidence, not design criteria: they live in ONE typed receipt, gunbc.native_witness_transition_receipt native_at_small_scale_transition_receipt, and are restated neither here nor in gunbc.design_document (operator ruling on gunbc#7710, 2026-08-02 — this note and the design document previously carried independent hand-maintained copies of the same advancing status). Staged expectations (not flat 30–35min): Stage A modest admission saving only; Stage B first order-of-magnitude witness reduction after production native cutover; Stage C five-minute gate when all conjunction in docs/plans/five-minute-ci-gate-design.md holds. Dispositions: entry-graph-union CLOSED; retention PARKED (#7581). See docs/plans/five-minute-ci-gate-design.md." data namespace_node_recut_note: String = "THE BROAD NAMESPACE NODE WAS TOO COARSE TO CLOSE HONESTLY, and the recut is the fix (operator ruling, 2026-07-31). namespace-canonical-binding previously combined two separable things in one boundary: working out which declarations a name can see, and calling one decision function on them. A check proving only the none/one/many decision could therefore have marked the whole node accepted while candidate derivation, reference-derived dependency closure, and the production changeover all remained unbuilt — an acceptance hazard, and the exact shape of rung inflation DESIGN 4b forbids, where a carrier named for the top rung occupies the bottom one.\\n\\nThree prerequisites now stand in front of it. namespace-binding-kernel owns the decision alone, on a candidate population SUPPLIED to it. namespace-reference-derived-closure owns structural exposure, authored order, the candidate index, exact reference-to-declaration binding, and the derived dependency edges. namespace-ambiguity-discharge settles the genuinely-many cases from real compilation. namespace-canonical-binding KEEPS ITS DURABLE IDENTITY and is now the final production changeover only, so historical links resolve and no identity is reused.\\n\\nWhy the split falls exactly there: dependency edges are a PROJECTION OF THE BINDING, not a second authored statement. A program that instead authored the missing edges by hand reached 400 of 771 files at roughly one import per CI floor before it was stopped; the unresolved population was evidence that binding and module discovery are coupled at the wrong boundary, not a backlog to grind down.\\n\\nTHE CHANGEOVER AND ITS SUCCESSOR ARE DISJOINT, and an earlier revision of this recut broke that (review 45649). It retitled namespace-canonical-binding as 'switch the whole compiler over' with a boundary reading 'every place that resolves a name', which swallowed namespace-bound-only-consumers underneath it — that node's own boundary is type checking, call lookup, and argument-count checks reading the decided answer. Overlapping boundaries are the §2 redundancy this document exists to remove, and they are worse on a roadmap than in code: completing the parent would already entail most of the child, so either the child can never be honestly accepted or the parent was silently read narrower than it was written. The cut is PRODUCER versus READER. The changeover node owns the sites that WORK OUT what a name refers to, and the deletion of the search-everything fallback behind them. Its successor owns the READERS of that answer and their obligation to stop on unresolved or ambiguous. Neither entails the other, and each can be accepted on its own evidence.\\n\\nONE EDGE WAS DELIBERATELY NOT CHANGED. pderive-source-slot-binding still depends on namespace-canonical-binding, which now means it waits for the whole production changeover. It plausibly needs only occurrence transport plus the binding kernel, but that is unproven: the dependency moves only once the focused P-derive consumer demonstrates it can operate on an exact supplied candidate population. Loosening it on the strength of the argument alone would be asserting a fact nobody has measured." +data compute_consumer_admission_sequencing_note: String = "THE RULE THAT ORDERS THE COMPUTE LANE, and it is a sequencing ruling rather than a design preference (operator, 2026-08-07): do not move the present arrangement onto our own machines. Move exact computations onto one shared fabric, and DELETE THE DUPLICATED WORK BEFORE increasing the number of things asking for it. Handing agents a build capability without deduplication and admission would not be a fabric — it would be a tidier way for ten callers to launch ten cold builds of one tree, which is the failure the lane exists to prevent. That is why compute-deduplication-and-admission stands in front of ci-owned-execution in the graph rather than beside it: owning CI adds a consumer, and a consumer added before the duplication is removed multiplies the load instead of sharing it.\n\nTHE SECOND HALF OF THE SAME RULE APPLIES TO NATIVE EXECUTION. Emitting a native witness bundle and running it is the right destination, and the miniature is decisive at the scale it measures — direct native calls in single-digit milliseconds against tens interpreted and hundreds through warm cargo transport. But the cited production enrolment delivered zero of three native and three of three fallback, so the required path took no benefit from it, and native bodies surrounded by duplicated preparation would still be bad CI. Native realization and shared preparation are ONE programme, not two lanes competing for the same slot; native-selected-witness-bundle owns the first, and the discovery snapshot, scoped substrate, early selection and streaming rows own the second.\n\nWHY THE MECHANISM ROWS ARE A CHAIN AND NOT A SET. The measured floor is dominated by preparation rather than by the work being prepared for: on the clearest full-floor subject, the discovery batch spent the large majority of its wall on setup and resolve and a small minority actually evaluating witnesses, and the two workers walked the same roots twice. Each row therefore removes a DIFFERENT duplicate — the second walk, the second initialised world, preparation that precedes selection, and the barrier between preparing and executing — and each is only measurable once the one before it has landed. Chaining them with dependency edges is what keeps at most the next one startable, so the standing limit on concurrent performance mechanisms falls out of the graph rather than being restated as prose that nothing enforces. Width two is deliberately absent from that chain and stays parked: the tested implementation shared typed bytes while each worker still built its own world, and the architectural prerequisite is that no second private semantic world exists." + data roadmap_execution_contract_frontier_note: String = "Every node below is ExecutionContractUnspecified: the declared, fail-closed migration frontier for gunbc.roadmap_execution_contract. A node states the exact validation operations that close it before the actuator will spawn for it, so dispatch refuses with a typed environment-admission diagnostic rather than guessing validations or dependencies. The prior binding lived on a pre-refresh operator-TODO node deleted by the 2026-07-27 authority refresh; re-binding is per-node roadmap authoring, never a merge-time default." fn authored(identity: String, @@ -988,9 +990,9 @@ fn declared_roadmap_nodes() -> List { path: "docs/plans/five-minute-ci-gate-design.md", t: fields( headline: "Serve every required CI operation with the bare minimum computation; five minutes is the distress ceiling, not the contract", - boundary: "Every required operation performs only the computation its inputs require — no semantic fact recomputed whose inputs did not change (re-anchored by operator ruling 2026-08-02: five minutes is the distress ceiling — the Stage C checkpoint bound, not the contract). Six sub-lanes plus operator sequence: (1) #7522 warm-merge-admission net receipt; (2) #7599 native bundle slice; (3) bounded production cutover to native execution; (4) #7534 materialization substrate (module→entry→bundle hierarchy); (5) union closed → per-entry assembly lane; (6) this registration. Native-at-small-scale settled (#7599); open: operational migration rate. Staged expectations A/B/C in design note; the gate is the Stage C conjunction.", + boundary: "Every required operation performs only the computation its inputs require — no semantic fact recomputed whose inputs did not change (re-anchored by operator ruling 2026-08-02: five minutes is the distress ceiling — the Stage C checkpoint bound, not the contract). Sub-lanes (unordered roster — dispatch order follows the graph edges; the native child row precedes this gate): #7522 warm-merge-admission net receipt; the child row's ONE-PR CI-0, superseding the former slice-and-cutover steps; #7534 materialization substrate; union closed → per-entry assembly; this registration.", displaced_cost: "Today witness bodies are still interpreted and materialization is recomputed across phases — confidence is bought with time the product boundary forbids.", - first_slice: "Register sequence, staged expectations, and #7599 in-flight disposition in authority; land no implementation under the parent id.", + first_slice: "Register sequence and staged expectations in authority; land no implementation under the parent id — the native cutover's whole implementation belongs to the CI-0 child row.", red_control: "A sub-lane that widens to whole-corpus work, silently falls through from an incomplete materialization artifact to cold recompute, or claims the five-minute envelope without Stage C fleet receipts refuses rather than greens.", out_of_scope: "No single PR closes the parent; placement/build-flake cap-first work, tokenizer escape scan, and closed entry-graph-union stay outside.", handback: "Sequence graph, staged expectations, six sub-lane rows, and design note in docs/plans/five-minute-ci-gate-design.md." @@ -1003,7 +1005,7 @@ fn declared_roadmap_nodes() -> List { path: "docs/plans/five-minute-ci-gate-design.md", t: fields( headline: "Admit merges from warm stamped receipts, not cold recomputation", - boundary: "NEXT (sequence step 1, #7522 in flight): fold resolve and materialization receipt gates into merge admission so the pull-request path stamps once and admits from warm receipts. Operator expectation: probable net recovery ~1–1.5 min (not ~3 min) — final-head run decides; not transcribed as receipt until measured.", + boundary: "#7522 in flight (dispatch order follows the graph edges): fold resolve and materialization receipt gates into merge admission so the pull-request path stamps once and admits from warm receipts. Operator expectation: probable net recovery ~1–1.5 min (not ~3 min) — final-head run decides; not transcribed as receipt until measured.", displaced_cost: "Merge admission and the floor tail currently pay separate cold paths for facts already fixed when the run started.", first_slice: "Finish #7522 with real net wall receipt and stage profile.", red_control: "A stale base-tree or stale gate-roster receipt blocks merge; a missing or invalid receipt refuses rather than widening to a full rerun.", @@ -1017,13 +1019,13 @@ fn declared_roadmap_nodes() -> List { owner: "ci-cost", path: "docs/plans/five-minute-ci-gate-design.md", t: fields( - headline: "Migrate the production selected witness set onto native bundle execution", - boundary: "A selected witness population executes as native code in one process: a SelectedWitnessPlan names the population, the bundle carries a content-addressed identity, witnesses run as direct calls rather than through the interpreter, an equivalence oracle establishes that the native result matches the interpreted one, and every failure is a typed refusal. Native execution is dispositive only at the scale actually measured, and any timing ratio against interpreted and warm-cargo paths is reported with its evidence, never asserted unverified. The next step cuts over a bounded production witness population. Migration rate, fallback, artifact identity, and fail-closed memory remain open.", - displaced_cost: "Running selected witnesses interpreted still pays per-row interpreter tax documented in ci-floor-child-spawn-attribution.", - first_slice: "Merge #7599 once controls clear; then enroll bounded production cutover with counted interpreted-fallback rows.", - red_control: "A provenance gap in selection refuses rather than widening to the whole corpus; stale compiler identity, missing native realization, or uncounted fallback refuses.", - out_of_scope: "No change to the scheduled falsifier's whole-corpus cold control; no claim that #7599 slice alone closes the five-minute gate.", - handback: "#7599 merge receipt; production cutover receipts with native-vs-interpreted attribution and fallback counts trending to zero." + headline: "CI2-0: complete v2 witness-execution cutover — every test identity v2-planned and executed, old route deleted, in #8043", + boundary: "#8043 owns the COMPLETE transition and does not merge as a three-test cutover (operator mandate 2026-08-08, superseding the staged rulings): every test identity on the exact PR head is planned by v2, lowered through the EXISTING compiler path, realized through a v2-owned route — hermetic to emitted Rust; live observation subject-bound; effects as v2 plans with independent readback; v1-subject tests harnessed BY v2, v1 never the scheduler or verdict — executed, accounted exactly once. Ordinary v1 executions, fallback, and unaccounted identities all zero. NO successor producer, live-read, effects, or measurement PR; a wiring refusal is an in-PR repair item.", + displaced_cost: "The three-member route is fixture-bound — specimen token lists, grammar rows, binding spellings, per-test target models — and the census's three-of-9,353 was CIRCULAR: it never attempted canonical realization, it restated the enrollment. The compiler already lowers ordinary bodies (#6520, #6526, #6558, #6512, body_producer_forward) and the live-read, effect, and routing authorities exist; what is missing is wiring the complete roster through the canonical pipeline, not new machinery. The control plane repeatedly mistook fixture-route limits for compiler limits.", + first_slice: "In-PR sequence, checkpoints not PR boundaries: A keep the three-member keystone receipt as the smallest end-to-end control; B replace the circular census — complete exact-head roster joined to the real semantic-kind authorities, discovery-rows-default-to-unavailable deleted as a capability decision; C canonical corpus wiring — ordinary test declarations through the existing v2 pipeline, dispatch surface generated from resulting declarations, first non-fixture test the wiring keystone, then widen mechanically; D subject and effect routes joined from the existing live-tree and effect machinery; E delete all ordinary interpreter scheduling and fallback for the complete population; F the cold and warm whole-population receipt, then merge.", + red_control: "Per identity, semantic kind and realization standing are orthogonal, one each: TestSemanticKind (hermetic, live-observation, effect, v1-subject) times V2RealizationStanding (emitted, subject-bound execution, effect execution, or Refused with phase and exact diagnostic from an ACTUAL realization attempt — never a default). The population is identity-grain, duplicate-free, complete both directions, bound to the exact tested tree; removed, duplicated, or substituted identities refuse the join. The planted broken test fails through the v2 route. The final receipt reconciles: kinds sum to N, realizations planned equals completed equals accounted equals N, refused equals unavailable equals diverged equals fallback equals ordinary-v1-executions equals remaining-v1-scheduler-rows equals zero, with cold generation, cold compile, warm materialization lookup, execution walls and peak memory observed.", + out_of_scope: "No successor PRs of any kind. No scaling the specimen path — no per-test token lists, grammar rows, binding-spelling maps, fixture target models, or enrollment declarations. No second body-lowering, resolution, or classification authority beside the existing ones. No CI-private cache beside the realization and materialization spine. No Interpreted execution route — InterpreterSubjectTest describes the subject under test and never grants v1 the scheduler.", + handback: "The merge statement, verbatim: every required test on this exact head is planned and executed through v2 witness execution; all results are exact-subject-accounted; ordinary v1 test execution and fallback have been physically deleted. Anything weaker leaves #8043 open." ) ), active_large( @@ -1086,6 +1088,96 @@ fn declared_roadmap_nodes() -> List { handback: "Repeated-assembly attribution receipt; implementation proposal only if the receipt justifies one." ) ), + active( + identity: "rn_9WQ3ZB6HTKX2LNVR40PDGY7CJM", + id: "ci-cost-arc-reconciliation", + owner: "ci-cost", + path: "docs/plans/ci-performance-tanking-evidence.md", + t: fields( + headline: "One ledger for what the CI-speed work actually bought", + boundary: "One row per attempt made across this arc: what it predicted, what it changed, the exact subject it was measured on, the movement inside its own segment, the movement in the whole required path, whether memory stayed inside the fixed envelope, and a typed result — delivered, partial, falsified, confounded by infrastructure, or evidence invalid — with what each one implies next. Rows are read out of receipts that already exist; nothing is re-measured to fill this in.", + displaced_cost: "Weeks of work bought real causal knowledge and several correctly killed hypotheses, but the required path is not meaningfully shorter. Without this ledger the next programme re-runs the attempts already falsified, and re-argues the ones that were only confounded by a machine failure.", + first_slice: "Fill the ledger from the receipts already landed, and state the whole-path movement the arc actually achieved.", + red_control: "An attempt whose claimed movement cannot be tied to an executed receipt is recorded as evidence invalid, never as delivered. A win inside one segment with no movement in the whole path is partial, never delivered. A result from a run the machine killed is confounded, not falsified — collapsing those two loses the distinction the ledger exists to make.", + out_of_scope: "No new measurement programme and no new optimisation. This row explains the arc; it does not extend it.", + handback: "The ledger with every attempt classified, and the next mechanisms it justifies, each named with the cost it is expected to remove." + ) + ), + active_large( + identity: "rn_5TDM8PXW1QKZ0HRB37NVLYG6CA", + id: "ci-build-once-per-subject", + owner: "ci-cost", + path: "docs/plans/ci-performance-tanking-evidence.md", + t: fields( + headline: "Compile the compiler once per change, not once per job", + boundary: "One expensive compiler build per change under test. Small checks stop dragging the whole compiler into a second build profile in order to run a few milliseconds of assertions: work that only observes the tree gets a small target that does not rebuild the monolith.", + displaced_cost: "Building and testing is now the largest single cost of getting a change in, and a measured run spent minutes of compiler builds to execute a fraction of a second of checks.", + first_slice: "Count the expensive compilations one pull request actually performs, then remove the second profile.", + red_control: "A change that adds a second expensive compilation to the required path refuses. A small check that pulls the compiler into its own build is rejected even where it is fast today, because the cost is in the build and not in the check.", + out_of_scope: "Not the shared-substrate work — that is a different duplicate, owned above. Not local developer builds.", + handback: "The per-change compilation count before and after, and the whole-path movement it produced." + ) + ), + active_large( + identity: "rn_4RTVN0BQKZ8XWM2CDPHJLG7Y36", + id: "ci-floor-discovery-snapshot", + owner: "ci-cost", + path: "docs/plans/cross-worker-typecheck-share-design.md", + t: fields( + headline: "Walk the corpus once and let both workers read the same result", + boundary: "One complete discovery result is produced once and consumed by the second worker, instead of the same walk happening again in another process. It carries the whole typed result, never a pass-or-fail flag. A consumer that finds it absent, damaged, or belonging to a different subject refuses; it never quietly recomputes.", + displaced_cost: "The ordinary worker and the scoped worker each perform effectively the same full walk in separate processes, and the second one measures around 284 seconds. It is the clearest several-minute target on the required path, and it is the first real use of computing something once and reusing it rather than a description of one.", + first_slice: "Produce the complete result once and have the second consumer read it, with the recompute path removed rather than left sitting behind it as a fallback.", + red_control: "A missing, damaged, or wrong-subject result refuses rather than falling back to a fresh walk. A consumer that recomputes anything the result already fixed reds. The benefit is stated as measured displacement on the required path, never as a win inside one segment.", + out_of_scope: "Not the cross-phase sharing above, which removes a different duplicate. Not width two, which stays parked until private worlds stop being rebuilt per process. Not what the corpus contains or how it is selected.", + handback: "The result produced once, both consumers reading it, the three refusals, and the measured displacement on the whole path." + ) + ), + active_large( + identity: "rn_8QDBM3ZVKT5XWN0RCPHJLG2764", + id: "ci-scoped-worker-shared-substrate", + owner: "ci-cost", + path: "docs/plans/cross-worker-typecheck-share-design.md", + t: fields( + headline: "Stop building a second compiler world in order to run twenty-seven witnesses", + boundary: "The scoped worker consumes an already-initialised substrate, or runs as a later stage inside the ordinary one, rather than constructing its own sources, indexes and environments. Staying in its own process remains allowed: isolation means separate mutable scratch and its own lifetime, never recomputing facts that are already fixed and immutable.", + displaced_cost: "The scoped worker executes twenty-seven witnesses and pays for a whole second world to do it. The process boundary is being used as a way of handing memory back, and a cold start is the price currently paid for that.", + first_slice: "Run the scoped population against the ordinary world's initialised substrate while keeping its own scratch.", + red_control: "STRUCTURAL, NOT A STOPWATCH. No second source loading, no second index construction, no second initialised semantic world, the same exact witness population with the same outcomes and effects, worker-private scratch still bounded, and no path back to cold construction. Wall, CPU and memory before and after are recorded as observations beside that verdict — a timer moving cannot stand in for a duplicate that is still reachable, and a duplicate genuinely removed is not disqualified by a noisy host.", + out_of_scope: "Not sharing the discovery walk, which the row above owns. Not widening to two workers — that stays parked until private worlds stop being rebuilt.", + handback: "The same population and outcomes, no second construction, and the measured segment reduction." + ) + ), + active( + identity: "rn_2VNCX7QBZK9TWM4RDPHJLG0538", + id: "ci-selection-before-preparation", + owner: "ci-cost", + path: "docs/plans/ci-selection-vs-scheduling.md", + t: fields( + headline: "Decide what matters before paying to prepare everything", + boundary: "Which computations a change affects is decided from source and module facts, ahead of broad roster, index, grouping and closure preparation. Only the views that decision actually needs are then prepared.", + displaced_cost: "Selection is already sharp about meaning and blunt about cost: it can correctly conclude that most of the corpus is irrelevant, but only after the corpus has been discovered, named, rostered and indexed. The saving it reports is real while the work it claims to have avoided has largely already been done.", + first_slice: "Move the affected-computation decision ahead of broad preparation on one required path, and prepare only what it selected.", + red_control: "A selection that still needs whole-corpus preparation to reach its answer reds. Preparing anything the selection did not ask for reds. A provenance gap in selection still refuses rather than widening to everything, exactly as it does today.", + out_of_scope: "Not what the selection decides — its correctness is owned elsewhere and does not change here. Not the discovery result itself.", + handback: "The reordered path, the prepared population matching the selected one, and the measured displacement." + ) + ), + active_large( + identity: "rn_4XKBW0ZQNV6TCM2RDPHJLG7135", + id: "ci-streaming-realization", + owner: "ci-cost", + path: "docs/plans/floor-prep-tax-program.md", + t: fields( + headline: "Start running as soon as the first thing is ready", + boundary: "Preparation stops being a barrier standing in front of execution. A module's facts becoming ready makes an entry view ready, which makes a witness ready, which runs — while everything else is still being prepared.", + displaced_cost: "Several hundred seconds are currently spent preparing every selected entry before the first one executes. That is a barrier, not work that had to finish first.", + first_slice: "Let one family execute as it becomes ready, rather than after the whole selected population has been prepared.", + red_control: "The first witness executes before the last selected entry has finished being prepared — the property this row exists for, measured rather than asserted. Outcomes and the order they are reported in stay stable however the readiness order falls out. A failure during preparation still refuses rather than surfacing as a witness that passed.", + out_of_scope: "Not running more things at once — this overlaps preparation with execution, it widens neither.", + handback: "The measured overlap, the stable outcomes under reordering, and the reduction in time before first execution." + ) + ), active_small( identity: "rn_2K8HXQ4MZV7NC3B6WD9YFT5J1E", @@ -1177,6 +1269,205 @@ fn declared_roadmap_nodes() -> List { handback: "A real run afterwards showing only one decision-maker left." ) ), + active_large( + identity: "rn_6BXPK1RV9ZTNQ3WD8HGYM40CFJ", + id: "placement-compile-pool-envelope", + owner: "ci-placement", + path: "docs/plans/srvn-buildcache-provisioning-design.md", + t: fields( + headline: "Derive the process, compiler and memory limits from one envelope per machine", + boundary: "Shared compilation runs in its own managed place rather than inside whichever job slot happened to start it. How many processes a slot may hold, how wide the compiler may fan out, and how much memory the shared pool may take are all derived from one declared envelope for that machine instead of being three numbers anyone can set independently. Every limit is read back as the machine actually holds it.", + displaced_cost: "The immediate outage — jobs unable to start processes against a cap far below the packaged one — and the fault behind it, where every machine's cache-miss compiles are charged to one job slot's budget and on one machine a retired slot's, are both consequences of these limits being separately writable. Two distinct failure kinds were charged to whatever code happened to be under test for a whole day: jobs dying unable to start a process at all, and jobs killed from an outer budget while sitting far under their own — across four independent measurements at 8.8 percent of the visible limit, with the peak identical on every attempt. Neither is a defect in any change under review, and both arrive looking exactly like one.", + first_slice: "Restore the affected machine's process limit and read back the value the kernel actually holds, then move shared compilation into its own place with a stated admission bound.", + red_control: "The acceptance classes are typed and separately named, never one generic death: a process-count exhaustion reports ProcessAdmissionExhausted; a kill imposed from an outer budget reports OuterResourceEnvelopeKilled, and the two must reproduce as two different things; a command that exits successfully while the machine's effective limit is unchanged reports EffectiveLimitMismatch — a failed read-back, not a success; a cache server whose place cannot be established reports CompilePoolPlacementRefused rather than being assumed to be ours. A pool whose effective limits differ from the envelope refuses. Widening the compiler's fan-out past the envelope refuses. An envelope that grows because more work arrived, rather than because the budget was raised deliberately, refuses.", + out_of_scope: "A larger machine or a raised memory cap is not the remedy — the envelope is fixed and the mechanism fits inside it. Not routing jobs between machines, which the rows above own.", + handback: "The derived envelope, the read-back limits from every machine, and the previously failing build run again." + ) + ), + + active_large( + identity: "rn_7KWZQ2VBNM4XCT0RDPLHGJ86Y1", + id: "compute-exact-work-contract", + owner: "compute", + path: "docs/plans/execution-spine-design.md", + t: fields( + headline: "One way to ask for work to be done, with the subject pinned exactly", + boundary: "A request names the exact tree it applies to, the operation, the inputs it declares and the outputs it promises, the toolchain and environment it needs, its resource envelope, and which providers may serve it. The answer is one of a fixed set of endings: succeeded, failed, refused by the infrastructure, cancelled, or refused because what came back did not match the outputs declared. A branch name or a working directory is not a subject.", + displaced_cost: "Building, checking, regenerating, changing machines, serving models and eventually judging changes are each growing a separate way of asking for work to be done. Four execution systems means four queues, four retry rules, four ideas of what success means, and nowhere to say where a piece of work actually ran.", + first_slice: "The request and outcome shapes with one provider behind them, serving one real operation from end to end. FIRST DAILY CONSUMER (operator, 2026-08-08): dirty-worktree verification — an exact dirty-tree snapshot runs all affected (or explicitly conservatively complete) v2 tests and returns an exact receipt in at most five seconds on the normal warm path, with a distinct BaseUnstable answer when the base layer itself is red or unverified. It consumes this contract, CI2's reusable execute_v2_witness_plan operation, and optionally confidence's narrowing — confidence is optional for narrowing, never for correctness: a partial impact answer selects the complete corpus explicitly, never a skip and never v1.", + red_control: "A request whose subject is a moving reference refuses. A provider that cannot serve the requested class of operation refuses before running rather than quietly answering a smaller question. The five endings stay five — a machine that killed the work cannot render as the work having failed.", + out_of_scope: "Not the handing back of what was produced, which is the row below. Not changing what any existing command computes. Not a new scheduler beside the realization spine this builds on.", + handback: "The request and outcome carriers, one provider behind them, and one real operation with all five endings demonstrated." + ) + ), + active_large( + identity: "rn_1MDQX8ZTVK5NBW3RCPHJLG024Y", + id: "compute-artifact-return-and-materialization", + owner: "compute", + path: "docs/plans/execution-spine-design.md", + t: fields( + headline: "Work that cannot hand back what it produced has not succeeded", + boundary: "Every output a computation declared comes back identified by its content, with its size and what produced it, through a manifest the caller can read. Whether those bytes are then copied into a working tree is a separate and explicit step. Work that produces files may only run where its outputs can be returned; a provider that cannot return them offers checking only, and refuses producing work before it starts rather than after.", + displaced_cost: "A remote run finished successfully and produced nothing locally; the stale binary left behind was then compared against itself and reported no difference. Every remote build in this environment currently has that shape, and it is silent.", + first_slice: "Return the declared outputs of one producing operation by content identity, and make the same operation refuse on a provider that cannot return them.", + red_control: "A run reporting success while a declared output is absent refuses instead. An output whose content does not match what the manifest claims refuses. A provider unable to return outputs refuses a producing request rather than answering it with a verdict.", + out_of_scope: "Not a distributed content store — exact subject binding, durability, retrieval and refusal come first. Not work that changes a machine, which needs a lease and a read-back rather than an artefact.", + handback: "The manifest with content identities, one materialisation into a working tree, and the three refusals demonstrated." + ) + ), + + active_large( + identity: "rn_5PZBW1KVNT7XCQ3RDMHJLG0946", + id: "compute-deduplication-and-admission", + owner: "compute", + path: "docs/plans/execution-spine-design.md", + t: fields( + headline: "Identical work runs once and is handed to everyone who asked for it", + boundary: "Two requests naming the same computation are the same work: one producer runs and everyone else attaches to its result as a consumer. The fabric decides what may run — how many cold builds a machine carries at once, what the compile pool's total demand may be, which host serves a request, and what order competing work is served in — and tells a caller which of those it is waiting on. A caller never starts something merely because it cannot tell whether somebody else is already producing it.", + displaced_cost: "Ten agents needing the same binary for the same tree currently means ten builds, because nothing lets one of them discover the other nine. Moving the present arrangement onto our own machines WITHOUT this multiplies the load instead of sharing it, which is why this row comes before more consumers rather than after them.", + first_slice: "Two agent sessions ask for the same artefact-producing build of the same tree: one producer runs, one compiler build happens, one output is returned, and both attach to it. Deliberately NOT a read-only check — collapsing two verdicts would demonstrate the mechanism on the one case where duplicate work costs nothing, while the case that swamps the fleet is two cold builds.", + red_control: "Two identical requests produce one execution and both callers receive the same result, with the same output identity. Two requests differing anywhere in the identity — tree, toolchain, target, profile, features, environment — must NOT collapse, each difference proven separately. A caller refused for want of capacity is told so, and that refusal is counted rather than becoming a longer queue nobody can see.", + out_of_scope: "Not what any operation computes. Not the handing back of outputs, which is its sibling. Not provisioning the machines the pool runs on.", + handback: "One producer serving several consumers on a real operation, the non-collapse controls, the standing a caller sees, and agents no longer launching duplicate cold builds." + ) + ), + + active_large( + identity: "rn_0LNVJ4CQ8ZRHT2XBWD6PGK9YFS", + id: "ci-owned-execution", + owner: "ci-control", + path: "docs/plans/ci-process-end-to-end.md", + t: fields( + headline: "Run the required checks on our own machines, from a subject we hold ourselves", + boundary: "Seeing a new commit creates a durable piece of work of our own, identified by the exact base, head, and combined commit under test. One machine holds it under a lease for the whole run, and the run ends in one typed result we keep. Starting, queueing and handing out the work involve no third-party workflow and no registered runner. The old floor is quarry and shadow oracle, not the template: obligations port only when a disagreement proves they carry a real property, and its batches, ordering, settlement and status vocabulary are not inherited.", + displaced_cost: "Starting, queueing, handing out, cancelling and reporting all belong to someone else today. When their side has a bad day our machines wedge, their re-runs test a stale combination, and every one of those failures arrives looking like a fault in our own code.", + first_slice: "One exact subject, one bounded population, the emitted native bundle, owned execution, one typed receipt — with the old path running as shadow beside it rather than as the thing being replicated.", + red_control: "Restarting the controller recovers unfinished work. The same subject does not run twice at once. Asking for a re-run builds a fresh combination against current main rather than replaying an old one. Losing the machine mid-run returns the work instead of stranding it. A disagreement between the owned path and the shadow is a counted finding, never silently resolved in either direction.", + out_of_scope: "GitHub stays the repository and the place people review. Porting old-floor obligations wholesale is out — each ports only on a proven disagreement.", + handback: "The durable queue, one run held under a lease from start to finish, and its result carrying the exact subject it tested." + ) + ), + active( + identity: "rn_8XBLW9DKZH5NCQ0RTVPMGJ4F2", + id: "ci-executed-coverage-receipt", + owner: "ci-control", + path: "docs/plans/ci-process-end-to-end.md", + t: fields( + headline: "A check reports what it actually executed, not merely that it ran", + boundary: "Every required check carries the population it executed — which witnesses, which corpus, how many — and a run that executed nothing cannot report a verdict of any kind. A build failure that stops the corpus from running is its own outcome, distinct from the corpus running and disagreeing.", + displaced_cost: "A scheduled check spent days reporting failures while executing no corpus at all, because a step ahead of it failed and the result looked exactly like a real disagreement. Nobody could tell the difference from outside, so a genuine signal was ignored for days.", + first_slice: "Carry the executed population on one required check, and refuse a verdict when that population is empty.", + red_control: "A run whose executed population is zero refuses rather than reporting either pass or fail. A failure ahead of the corpus is a different outcome from a corpus disagreement. A check that cannot say what it executed does not count as having executed.", + out_of_scope: "Not what the corpus contains or how it is selected, and not per-witness enrolment — only that a verdict names the work that produced it.", + handback: "One required check carrying its executed population, with the empty-population refusal and the distinct upstream-failure outcome both demonstrated." + ) + ), + active_small( + identity: "rn_W86W65EBR3HW41MK0X3RBMJ8BT", + id: "ci-gate-contention-independent-verdict", + owner: "ci-control", + path: "docs/plans/ci-performance-tanking-evidence.md", + t: fields( + headline: "Host load cannot decide a semantic merge verdict", + boundary: "A check that blocks merging decides something about the change. The semantic verdict derives from the executed assertion; a timeout or resource exhaustion is a distinct execution outcome, never projected into assertion failure, and never absorbed by widening the cutoff. Work that cannot fit its budget is decomposed or moved through the correct execution mechanism — the cutoff is not raised to admit it.", + displaced_cost: "One check was measured ten times across four separate efforts with nothing in the code changing between them, and the readings ranged from well under its limit to well over it. It blocked one change outright, produced a false alarm on another whose own checks all passed, and sent three separate efforts hunting for a defect that was never there — an unchanged semantic subject passing or failing on host contention.", + first_slice: "On the one gate currently deciding merges on machine load, split the outcome: the same unchanged subject retains the same semantic result across repeated runs, and an over-budget run reports ExecutionTimedOut or CoverageRefused rather than a failed assertion.", + red_control: "Repeated runs of the same unchanged code land on the same semantic side. A timeout reports as ExecutionTimedOut or CoverageRefused, never as an assertion failure. Raising the limit, or moving the check somewhere it stops running per change, refuses as a way of closing this. A quiet-host pass is not accepted as the only evidence.", + out_of_scope: "Not a general performance effort, and not a review of every limit — only gates currently deciding merges on machine load. The decomposition or migration of the slow work itself belongs to the execution rows.", + handback: "The same unchanged subject holding one semantic result across repeated contended runs, the over-budget outcome demonstrated as its own typed ending, and the limit itself untouched." + ) + ), + active( + identity: "rn_3HKRW7TBZP1MQ5NDXV0GCJ8YLA", + id: "ci-check-projection", + owner: "ci-control", + path: "docs/plans/ci-process-end-to-end.md", + t: fields( + headline: "The result shown on GitHub is a copy of ours, never the original", + boundary: "Our stored result is published as the required check. No check present means not yet observed and never counts as satisfied. Cancelled, timed out, refused by the infrastructure, and genuinely failed stay four different things the whole way through. A queued merge is checked against the exact commit that queue built. If GitHub cannot be reached the publishing waits; the result itself is never lost.", + displaced_cost: "An absent check currently reads the same as one that passed elsewhere, and a job the machine killed arrives as an ordinary failure — so the board cannot tell our defects from theirs, and people learn to re-run rather than read.", + first_slice: "Publish one required check from a stored result, with the four endings kept apart.", + red_control: "An absent check does not admit a merge. A generic failure that erases a cancellation refuses. Publishing against a commit other than the one that ran refuses.", + out_of_scope: "No change to what the branch requires until both paths have been seen to agree.", + handback: "One required check published from a stored result, and the four endings demonstrated separately." + ) + ), + active( + identity: "rn_8YMCT2GQVZ0KRB6WNP4XHDJ71L", + id: "ci-actions-runner-retirement", + owner: "ci-control", + path: "docs/plans/ci-process-end-to-end.md", + t: fields( + headline: "Remove the hosted-runner path once we carry the required checks", + boundary: "With our own path publishing the required checks, the workflow, the runner registrations and the recovery timer are removed together in one change, so there is never a period in which two things can hand out the same work.", + displaced_cost: "Retires the vendor's dispatcher as something our fleet's health depends on — the reason the recovery timer has to exist at all, and the reason a bad day on their side becomes a wedged fleet on ours.", + first_slice: "Run both paths over the same subjects and compare the verdicts before removing anything.", + red_control: "A disagreement on any subject blocks removal. A half-removed state in which both paths can still hand out work is rejected.", + out_of_scope: "GitHub keeps the repository, the review surface and the check display. This is about who starts and runs the work, not where it is discussed.", + handback: "Agreement across a real run of subjects, then one change that removes the old path." + ) + ), + active_small( + identity: "rn_4QGZD9NWX3TVB7LMKR0PHY62CS", + id: "ci-remote-build-containment", + owner: "ci-control", + path: "docs/plans/ci-process-end-to-end.md", + t: fields( + headline: "Contain the remote build wrapper to its safe subset, then retire it into the compute contract", + boundary: "This row is a migration, not a home. Until outputs can be handed back, the wrapper admits exactly one remote mode — checking something without producing anything the local tree then needs — and refuses any invocation that would write. Every result names the source it checked and the executable it checked with, so a stale local artefact can never stand in for a remote answer. When the compute contract can return outputs, the wrapper's remaining useful behaviour becomes one provider behind it and the wrapper is deleted rather than extended.", + displaced_cost: "There is no stated rule for when this wrapper is the right thing, so it gets guessed at; a remote run meant to produce files can finish successfully having produced nothing locally, and that has already been consumed as a passing check. Every day it stays unclassified, more work is authored against a boundary that means two contradictory things.", + first_slice: "Classify the wrapper's operations and refuse the remote writing class before dispatch, not after.", + red_control: "A remote invocation declared read-only that writes refuses. A check whose local artefact is older than the source it claims to have checked refuses. A producing request must refuse rather than silently becoming a verdict-only one.", + out_of_scope: "This is not where remote execution lives — the compute rows above own that. No rewrite of the wrapper and no new capability added to it; the only additions permitted here are refusals.", + handback: "The classification, the refusing controls, and the wrapper deleted once its behaviour is a provider behind the compute contract." + ) + ), + + active( + identity: "rn_0PJXVP9KNDXZFHXMDM9XQ7TTSX", + id: "commit-writer-admission", + owner: "generated-artifact", + path: "docs/plans/generated-file-conflict-policy.md", + t: fields( + headline: "One admission predicate that every commit writer must pass before it may write", + boundary: "Admission consumes a complete staged-index observation whose population must equal the exact staged index, never a caller-selected list, with two independent refusal arms: any unmerged index stage refuses typed and located with the affected path, and the staged blobs are inspected for the complete conflict-marker grammar, so a file collapsed to stage zero with markers inside refuses. Text classification is observed, never caller-declared; a fixture exemption is a provenance receipt bound to the same staged path and blob, never a path allowlist. The pre-commit hook stays fast local feedback; the predicate at the writer's boundary is the enforcement.", + displaced_cost: "An automated writer has already committed and pushed literal conflict-marker blocks to main by committing through an unresolved merge, and the refusing merge driver raised that class only to mechanically preventable — the repository's own config authority states that commit-writer admission remains the boundary a writer cannot pass. Until this lane lands, the other lanes shrink how many files can be caught by textual adjacency while every writer stays one absorbing fallback away from repeating the incident on the files that remain.", + first_slice: "The predicate with the unmerged-index refusal, consumed by one real writer, with the historical incident's exact shape reproduced as the discriminating red.", + red_control: "A merge with unmerged index entries refuses typed and located; the staged-marker-text variant refuses separately; an observation population that differs from the staged index refuses in both directions; a freely constructed provenance value whose path happens to match a staged file refuses; a clean tree admits.", + out_of_scope: "The infrastructure-side daemon binding is reported for operator routing, not landed from this repository. No repo-wide raw text scan, and no new stringly conflicted-tree fact — the Git model already carries the index-stage and diff-status facts this consumes.", + handback: "The predicate consumed by every repo-lane commit writer, the incident witness pair red then green, and the two writer bindings carried as countable work rows rather than prose." + ) + ), + active( + identity: "rn_7VNBH0KXR5ZQWD2PLGT3MJ86CY", + id: "generated-projection-registry-containment", + owner: "generated-artifact", + path: "docs/plans/generated-file-conflict-policy.md", + t: fields( + headline: "Stop writing the whole file registry into a generated file", + boundary: "A generated artefact must not carry the full list of generated paths. The workflow asks Git which of the paths that actually conflicted are generated, reading that classification from both sides' committed state rather than from a list baked into the workflow text. Adding a generated file then changes the classification projection and leaves the workflow's own bytes alone.", + displaced_cost: "One enormous line holding every generated path means any change to that population rewrites the same spot in a shared file, so two branches conflict over a list neither of them disagrees about. That has happened repeatedly, and each occurrence costs a manual resolution carrying no information.", + first_slice: "Take the path population out of the workflow and classify only the paths that actually conflicted.", + red_control: "Adding or removing a generated file leaves the workflow byte-identical. A path classified as generated on only one side refuses rather than being taken as generated. A conflict that also touches hand-written files refuses before resolving anything. Paths containing spaces or newlines survive. A conflicted classification file sitting in the working tree does not get to decide the classification.", + out_of_scope: "The rule that generated files are regenerated rather than merged by hand does not change; this changes how a generated path is recognised, not what happens to one.", + handback: "The population gone from the workflow, the previously conflicting case no longer conflicting, and each refusal demonstrated separately." + ) + ), + active( + identity: "rn_1CJXM6RTPZ8VKQ0NDWB4HG5YFL", + id: "generated-commit-policy-census", + owner: "generated-artifact", + path: "docs/plans/generated-file-conflict-policy.md", + t: fields( + headline: "Decide which generated files need to be in the repository at all", + boundary: "For every generated file we commit, name the consumer that requires the bytes to be present: something outside reads it before our own tools can run, the build needs it to start at all, or a reviewer has to see the exact difference. Anything else is published or produced on demand instead of committed. Generated outputs that stay get their own directories, so what is generated is structural rather than a list someone maintains.", + displaced_cost: "A large share of the merge pain comes from files nobody reads as bytes in the repository. Each one that leaves stops producing conflicts, review noise and drift checks permanently, rather than having its conflicts handled better.", + first_slice: "Census every committed generated file against those three reasons, and move the first family that has none of them.", + red_control: "A file whose consumer cannot be named does not get to stay committed by default. Removing a file something outside genuinely reads is caught before it lands. A generated file appearing inside a hand-written directory refuses.", + out_of_scope: "The bootstrap seed stays committed until its own lane removes the need for it; this row does not decide that question.", + handback: "The census with a named consumer or a removal for every entry, and the first family moved." + ) + ), active_small( identity: "rn_SZKN5YRA7FH46J1Z6THGN080G1", @@ -1735,6 +2026,21 @@ fn declared_roadmap_nodes() -> List { handback: "One deployment description serving both the real and test copies, a live deployment through it, and the second engine deleted." ) ), + active( + identity: "rn_53RV3EM1TN4T1V3R34L1Z3PL0X9", + id: "roadmap-serve-emitted-realization", + owner: "fleet", + path: "docs/plans/gunbc-served-dashboard-design.md", + t: fields( + headline: "Serve the roadmap page from emitted native code, never the tree-walking interpreter", + boundary: "The interpreted accept loop is a declared scaffold whose dissolution trigger has now fired in production: a two-hour page wedge, one core pinned in memcpy, thirteen connections queued behind a single thread. The fix is ordered. Wire roadmap_serve_handle through the existing emit-on-demand path FIRST — interpreted concat copies its accumulator on every call, quadratic; emitted concat moves, linear: a complexity-class change, not tuning. Then memoize page bodies by content hash of their inputs so a request is lookup-and-write. Concurrency only after bodies are immutable artifacts. A request deadline returns a typed refusal instead of eating the process.", + displaced_cost: "The 2026-08-08 srv1 outage: gunbc serve wedged for two hours rendering the daily page, every route behind it dead, and it re-wedges on the next load of the same shape. The scaffold row already names emit-on-demand as its dissolution and the module carries zero references to it — declared and left standing. Separately, the belt reconcile has been dead as long as GcpProjectId has failed to resolve, burning about two minutes of CPU on every timer tick before exiting.", + first_slice: "Confirm the quadratic with the existing chars-walked instrumentation at a few page sizes (measured, not assumed — the two-hour spin is proven memcpy but not yet proven quadratic), fix the copied accumulator in both interpreter concat arms, wire roadmap_serve_handle through std.emit_on_demand, land the request deadline, and fix the GcpProjectId unresolved type so the belt lives again.", + red_control: "A planted pathological render returns a typed refusal within the deadline instead of pinning a core. Warm-vs-cold page bodies are byte-identical on unchanged inputs; a changed input invalidates the hash rather than serving stale. The chars-walked curve is linear in page size after the fix. The belt tick completes instead of dying on the unresolved type.", + out_of_scope: "Threading before the render is linear is refused — a thread pool over a superlinear render converts one wedged route into a machine-wide meltdown, so concurrency is last, where it is nearly free. No new serving architecture: the emit-on-demand and artifact-store carriers already exist; this row is wiring plus the interpreter cost-shape fix, which lands regardless because the interpreter still renders in dev and tests.", + handback: "The page served from emitted native code with content-hashed bodies, the interpreted scaffold dissolved, the deadline refusing pathological renders, the concat cost-shape defect fixed in the interpreter, and the belt reconcile alive." + ) + ), active( identity: "rn_DMMM7YQHT05Q01CD10Q7B6R1DK", id: "fleet-anti-entropy-hygiene", @@ -1765,6 +2071,157 @@ fn declared_roadmap_nodes() -> List { handback: "The full set of receipts and a machine state someone can go and verify." ) ), + active_large( + identity: "rn_5ZKQW8BXTN2VRD0MHJ7GPL34CY", + id: "fleet-main-revision-authority", + owner: "fleet", + path: "docs/plans/fleet-self-converge-enforcement-design.md", + t: fields( + headline: "A merge to main is the instruction, and nothing else applies it", + boundary: "The state the machines should be in is the exact revision merged to main, held durably by whatever is applying it. A newer revision replaces an older one, an older or unrelated one refuses, and the same one again does nothing. The instruction survives a restart of the thing applying it and an outage at the hosting provider — an event from them may make it happen sooner but is never the only place the instruction lives. A machine that was unreachable or refused is tried again against the revision still held, with no new commit needed.", + displaced_cost: "Every fleet-touching merge currently ends with the same question — is that actually on the machines — and the only way to answer it is to go and look. Somebody has to remember to apply things, and forgetting is invisible until it causes an incident.", + first_slice: "Watch main directly, hold one durable revision, and show it survives a restart of the applier.", + red_control: "An older revision does not overwrite a newer one, and two arriving close together do not finish out of order and leave a machine behind. An unrelated history refuses. A missed notification still converges on the next pass. The same revision again performs no changes at all.", + out_of_scope: "Not how any individual setting is applied — the closed loop above owns that. Not the fleet-wide answer, which is its sibling row.", + handback: "The held revision surviving a restart, the ordering refusals, and a machine that was down converging on its return without anyone committing anything." + ) + ), + active( + identity: "rn_2WPGT6NCZQ9XKBR1DVLM40HJY8", + id: "fleet-atomic-convergence-verdict", + owner: "fleet", + path: "docs/plans/fleet-acceptance-criteria.md", + t: fields( + headline: "One answer for the whole fleet, or exactly which machines are not there yet", + boundary: "Each machine produces a receipt — what was wanted, what was there, what was planned, what was applied, and what was read back afterwards. Those fold into a single answer for the fleet, joined with the lifecycle cells enrollment constructs: every required host-and-phase cell is observed, refused, unreachable, drifted, or converged, and a required cell nobody has observed prevents convergence rather than defaulting green. Anything short of all of them is not converged, with the machines that refused, could not be reached, or have drifted each named separately.", + displaced_cost: "One machine can hold the new value while the others hold the old one and nothing anywhere says so, which is exactly why the honest answer to whether something is applied is currently to go and check by hand. And the fleet phase matrix currently has exactly one real observation producer cell — one host, one phase, hard-coded — so every other lifecycle claim in the fleet is unobserved by construction; enrollment makes obligations countable, not satisfied.", + first_slice: "Fold the existing per-machine receipts into one fleet answer for a single narrow setting.", + red_control: "One machine short of the roster reports not converged, never converged with a note attached. Unreachable, refused and drifted stay three different answers, and a machine we could not reach is never simply missing from the count. A command that succeeded while the real value did not change reads as a failed read-back.", + out_of_scope: "No new ways of changing machines are added here — this row is the answer, not the action.", + handback: "The fleet answer with all four machine outcomes demonstrated." + ) + ), + active_large( + identity: "rn_9DVRB3XQKM0ZWT7NPGHC65LJY2", + id: "fleet-runner-host-convergence", + owner: "fleet", + path: "docs/plans/fleet-self-converge-enforcement-design.md", + t: fields( + headline: "The build machines' own limits and services converge from main like everything else", + boundary: "The job slots' limits, where shared compilation runs, the recovery timer, and the values those actually take on each machine are all stated as desired state derived from main and converged everywhere. Editing the desired row and merging it is the whole operator action.", + displaced_cost: "This is the configuration whose hand-drift produced the process-limit outage and left the shared build cache running inside a retired job slot. It is also the first subject worth proving the whole path on, because it is real, it is already wrong, and fixing it by hand is what created the drift.", + first_slice: "Converge one already-repaired value across every machine and read back what each one actually holds.", + red_control: "A machine whose effective value differs from the desired one reports drifted. A service command that exits successfully while the machine's real limit is unchanged is a failed read-back, not a success. A machine missing from the roster is never silently skipped.", + out_of_scope: "The emergency repair itself belongs to the envelope row in the placement lane; this row is what keeps it true afterwards.", + handback: "The desired rows, the read-back values from every machine, and one drift repaired without a person." + ) + ), + active_small( + identity: "rn_6MKXC1TZGB8QWN4RDVPJ0HY735", + id: "fleet-runner-broker-recovery", + owner: "fleet", + path: "docs/plans/fleet-self-converge-enforcement-design.md", + t: fields( + headline: "Count every automatic job-runner recovery, and say what retires it", + boundary: "The timer that restarts wedged job runners records each firing — which machine, which slot, what it observed, what it did, and whether it worked — and those counts reach the fleet's health. Deciding locally that a runner has stopped making progress is kept separate from asking the provider whether it is still registered, so recovery keeps working during the outage it exists for.", + displaced_cost: "It fires roughly ten times a day across the fleet and nothing counts it, so the fleet looks healthy because something invisible is holding it up. The firing rate is the only thing separating a legitimate stopgap from a fault we have stopped noticing.", + first_slice: "Emit one attributable receipt per firing, and show the rate per machine.", + red_control: "A restart with no attributable cause is not recorded as a recovery. Failing to reach the provider refuses rather than being read as offline. Nothing restarts a slot that is currently running work.", + out_of_scope: "Not replacing the current two-part test with a bare silence rule before the local part has been proven on its own.", + handback: "The counted receipts, the split by cause, and the named condition under which this timer is deleted." + ) + ), + active( + identity: "rn_HYV4E84MRC1MJ73EDJZYXP89S8", + id: "fleet-spark-host-enrollment", + owner: "fleet", + path: "dag/gunbc/dgx_spark_procurement.dag", + t: fields( + headline: "Turn the two Spark machines from hand-managed boxes into exact fleet members", + boundary: "srv5 and srv6 become fleet members: enrollment intents carrying InferenceServingParticipation, with endpoint rows derived downstream from the assigned reservation rows — re-typing the address literal refuses, a reverse import into the network intent refuses, and the join is a projection between the two existing authorities, never a second host-identity authority. Enrollment constructs their four required lifecycle cells — identity converge, reach, compute-fabric enrolment, assimilation complete — and every cell stays honestly Unobserved until an observation producer answers it. Enrolled is not converged: no identity convergence, reach satisfaction, capacity acceptance, or serving claim lives here.", + displaced_cost: "Two machines are reserved, physically bound, and answerable by nobody: no fleet row knows they exist, so their state is whatever hands left there. The stale count witnesses still encode enrolled-hosts-times-whole-spine — every enrolled host presumed a runner, wrong since participation scoping landed and exposed by srv5. And the endpoint-derivation question already burned a session against an import cycle because the projection layer this row creates did not exist.", + first_slice: "Diagnose the cli_run.rs:1577 compiler panic as its own exact defect — never a spelling that dodges it — then the per-enrollment participation-derived cell population replacing the cross-product witnesses, the two enrollment rows, and the endpoint projection.", + red_control: "A count witness multiplying enrolled hosts by the whole spine refuses — expected cells are the duplicate-free identity join of each enrollment's participation-derived program, and runner plus inference union exactly with no duplicated cell. An inference enrollment carrying BMC, OS-install, runner-slot, or build-cache obligations refuses. Changing one participation changes only the obligations that participation owns. An endpoint row re-typing the reserved address literal refuses; a reverse import into the network intent refuses. Any Spark cell presented as observed without a producer refuses.", + out_of_scope: "No convergence, no serving: identity converge and reach are the convergence lane's observation seam; GB10 compute enrollment, collector facts, runtime, model revision, and auth are the serving row's. No build or runner role, ever.", + handback: "Both machines enrolled with exactly four obligations each, the stale cross-product witnesses replaced by the identity join, the endpoint projection landed, and eight Spark cells visible as honestly unobserved." + ) + ), + active_large( + identity: "rn_3JQZN7WVPD1KXCB0RTMG84LHY6", + id: "fleet-spark-inference-serving", + owner: "fleet", + path: "dag/gunbc/dgx_spark_procurement.dag", + t: fields( + headline: "Bring the two new machines up as model-serving members, through the same path as everything else", + boundary: "Both new machines join through the same standing-up sequence every other machine uses, reach a stated model and runtime, and serve requests on an endpoint. Which model is actually being served, and whether the endpoint is healthy, are read back independently, and each machine stays separately observable. They serve models — they are not job runners and not build caches.", + displaced_cost: "Standing these up by hand would create two more machines nobody can answer questions about, which is the state this whole lane exists to end. Going through the convergence path also proves that path on a machine class it has never seen.", + first_slice: "The internal path, in order and explicit: membership (the enrollment row), identity and reach observations through the convergence lane's observation seam, GB10 compute enrollment as an inference envelope (accelerator, unified memory, precision modes, model-residency and concurrent-generation budgets — never hardware threads), collector-observed vendor image, OS, driver and runtime facts, pinned serving runtime plus exact model revision and API auth, then the endpoint answering with its model identity read back. Converge one of them through that path first. Infrastructure acceptance (operator amendment 2026-08-08): independent continuously-supervised replicas on both machines, survival across process restart and host reboot, requested model equals observed served model, one provider-neutral inference operation with exact structured-response decode, usage and timing observations retained, and concurrency admission with queue standing observable.", + red_control: "An endpoint that answers while serving a different model than the one asked for is drift, not health. A machine we cannot reach leaves the fleet not converged rather than dropping out of the count. Giving one of them a job-runner or build-cache role refuses. A serving acceptance may not lean on model quality in either direction: serving may close while the first model is rejected for semantic work, and a good offline model score cannot close serving.", + out_of_scope: "No training or tuning (that is judgment-model-selection-and-specialization), no routing of real work onto them, and no build role. The two replicas stay independent initially — aggregate accepted work across concurrent tasks, not one fast stream.", + handback: "Both machines converged from a desired row, the served model read back on each, restart and reboot survival demonstrated, one structured inference decoded exactly with its usage receipt, and one drift repaired — each host separately healthy, drifted, or refused." + ) + ), + + active_large( + identity: "rn_RPKBFCD41FZGDV48TJKRJYQ8J2", + id: "confidence-semantic-impact-query", + owner: "confidence", + path: "src/v2/lens/module_graph.dag", + t: fields( + headline: "Ask what depends on this exact thing, and get an explained, honest answer", + boundary: "Given an exact graph subject and one or more semantic node identities, return direct and transitive dependencies or dependents, explanation paths, production and test consumers, and an explicit completeness standing. Git diffs, paths, line and column coordinates, and natural-language questions are adapters into this semantic query — they are not its identity grain. The reading consumes the existing single edge authority as data and never grows a second edge producer; where the finest grain is unavailable, the standing says so with its trigger rather than answering coarser and calling it fine.", + displaced_cost: "Whether a concept already exists, who consumes it, what executes it, and what a deletion would break are today answered by grep, memory, or not at all — every session re-derives them, duplicates get minted beside existing authorities, and the reverse question is simulated with N forward closures. This deterministic layer is also the grounding packet the judgment service requires: without it, the machine judgement lane has nothing checkable to stand on. Acceptance standing (2026-08-08, post-rework): the reverse query is merged; the changed-surface rework closed departed-path consumption, the typed Complete/Partial/Refused outcome (unresolved input contaminates the whole result), the skip-authority wall (SkipAuthorityGranted reachable only from Complete AND exact), optional cycle analysis, the two-direction selector-parity control, and three real-diff receipts — one of which falsified a fixture assumption (git reports a deletion in both projections). OPEN: the base-graph population — a deleted subject resolves to typed BaseGraphUnavailable because every graph reader is head-only; building revision-scoped reading is an operator decision (new seed Rust or a heavy checkout). Measured correction: cycles were 28 percent of the wall, not most — the dominant cost is resolve and fact production.", + first_slice: "One real corpus query usable by a person — a real module, its explained dependent tree at the finest honestly available grain, with the completeness standing shown — through a tools entry, not a new CLI arm. Not just a fixture suite: the fixture-only consumer is the pattern this lane exists to stop.", + red_control: "A query whose subject module does not exist refuses rather than answering empty. A planted unbound module appears in the standing, and no reading over a pool with unaccounted members reports itself closed. The result may not claim one-sided incompleteness while pool independence is unavailable — the standing carries the trigger. A second edge producer beside the declared single authority refuses. Forward-intent refusals (operator amendment 2026-08-08): a branch name without an exact revision is not a subject; a deleted declaration is never silently resolved against the head tree; an unresolved input never becomes an empty result; a partial universe cannot report that no relevant authority exists — no-matches and could-not-see-enough remain different answers. The deterministic layer never turns prose into repository identity: it returns the bounded universe, exact candidate declarations, consumers, explanation paths, and standing for a later mapper or judge.", + out_of_scope: "No LLM, no Spark, no provider — this is deterministic repository inspection and must work with none of them provisioned. Declaration and node grain arrive through the namespace dependency-projection triggers, never through a parallel host bridge.", + handback: "The reading with typed direction and standing, adapters above it, one real corpus query a person can run and read, and the judgment lane re-grounded on the packet this produces." + ) + ), + active( + identity: "rn_NE8G0Q69BD0SZ24K26MVXXB2R2", + id: "abstraction-candidate-discovery", + owner: "confidence", + path: "src/v2/lens/module_graph.dag", + t: fields( + headline: "Find subjects that may deserve one shared abstraction by how they are constructed and consumed", + boundary: "Given an exact graph subject, a semantic universe, and a named lens over construction, consumption, effects, tests, lifecycle, or presentation, group observationally equivalent subjects into grounded abstraction candidates. The result is DESCRIPTIVE evidence only: it mints no authority, rewrites no code, and never decides a quotient is valid — similarity under a lens is evidence; 'these should share one authority' is a separate normative ruling crossing that bridge explicitly. Every candidate names its universe, lens, equivalent observations, the distinctions the quotient would erase, nearby authorities, the consumers that would use it, and its over-collapse and demand standing.", + displaced_cost: "The grouping move — five things consumed the same way become one new layer — is how this repository forms layers at all, and today it happens only when a person notices: duplicates get minted beside existing authorities, boutique implementations accumulate until a consolidation pays the fork back at interest, and the judgment of which lens a similarity holds under is re-derived by hand every time. The candidate that is NOT an abstraction is half the value: the Spark endpoint question burned a session on topology surgery that a projection-missing classification — join the existing authorities downstream, mint nothing — would have answered in one line.", + first_slice: "The DETERMINISTIC detector first — no model, no Spark — with PARTICIPATION as the criterion and shape one weak signal within it (worker correction 2026-08-08: shape misses the vendor trio, structurally unlike but sharing every verb, and false-positives on RackUnitPosition vs RackUnit, structurally identical and rightly distinct — which verbs take the subject, in which position, is what separates them). The mechanical population includes shape-identical declarations AND the consumer-imposed-verb classes: a String field whose readers only compare it against a fixed literal set is a closed enum wearing a String, decidable without a model. The residue — what participation analysis cannot reach — is the exact population any model must justify itself against, and a mechanically decidable case is never delegated to the model. Eval is historical replay: the tree before each already-dissolved fork, labeled by the merge that followed; negatives on kept-distinct rulings — noting the runner-vs-inference-capacity control DOES NOT EXIST YET (what #8056 witnesses is obligations, not capacity) and must be built before the eval may lean on it. Output always carries subject, universe, lens population, and completeness standing — no findings never renders as fork-free.", + red_control: "A planted set of near-duplicates with one load-bearing distinction yields keep-distinct, never a merged candidate — keep-distinct is a RESULT, not an absence. A pair whose correct move is a downstream join yields projection-missing, never a new identity authority. A candidate that cannot name a current consumer reads demand-absent rather than ranking beside demanded ones. A cited subject that does not resolve refuses. Candidate dispositions are first-class and closed: structurally-equivalent, semantic-review-required, existing-authority, projection-missing, candidate-new-abstraction, authored-but-unconsumed, keep-distinct, over-collapse-risk, demand-absent, unknown-because-incomplete.", + out_of_scope: "No LLM and no Spark in this row — deterministic grouping and packet construction must work before the model server exists; ranking, critique and explanation are the judgment row's. No automatic commits: an application is a proposed patch under review.", + handback: "The candidate reading with its named-lens vocabulary, the three controls demonstrated on real corpus subjects, and one accepted candidate applied end to end — consumer migrated, duplicate deleted, receipts equal." + ) + ), + active( + identity: "rn_JDGMNTM0D3LS3L3CTNTUN1NGX7Q", + id: "judgment-model-selection-and-specialization", + owner: "judgment", + path: "docs/plans/enforcement-intent-design.md", + t: fields( + headline: "Pick and tune the judgment model against held-out historical rulings, as a derived artifact with staleness", + boundary: "Given an exact base-model artifact, prompt and context policies, a provenance-bound corpus of accepted and rejected repository-modeling rulings, a training recipe, and a compiler-and-authority snapshot: compare untuned, prompt-only, and tuned realizations on temporally held-out cases. A tuned artifact is admitted only when it improves the SEMANTIC RESIDUE without worsening false consolidation, citation grounding, or unknown-and-refusal behavior. A changed base model, compiler revision, authority snapshot, corpus, or recipe makes the artifact stale — staleness is derived, never asserted.", + displaced_cost: "Without this row, tuning either hides inside serving (coupling infrastructure acceptance to model quality) or inside judgment (letting the judged system pick its own judge) — both forks the operator amendment forbids. And an untuned baseline never measured means every tuning claim is unfalsifiable.", + first_slice: "Three arms on one held-out ruling set: A full current prompt on the untuned base; B reduced prompt, untuned; C reduced prompt with a tuned adapter — reported as the per-class vector, never one score.", + red_control: "Training and evaluation examples overlapping refuses. An example with no exact pre-ruling repository subject refuses. A tuned model improving only on mechanically decidable cases is a failed admission, not a win. Increased false consolidation or over-collapse refuses. Unresolved citations counted as correct refuses. A tuned artifact against an old substrate served as current refuses, and a base-model update silently inheriting the old adapter's admission refuses. Promotion from training loss without repository-level evaluation refuses.", + out_of_scope: "No serving infrastructure (that is fleet-spark-inference-serving, whose acceptance is independent of model promotion in both directions). No judgment semantics — this row selects and specializes the model the judgment row consumes.", + handback: "The A/B/C comparison on held-out rulings with its per-class vector, the admission decision with its receipt, and the staleness derivation demonstrated on one changed input." + ) + ), + active_large( + identity: "rn_0PGYW4KDNQ2BXR9TVCM6HJ3LZ5", + id: "judgment-mechanical-review-service", + owner: "judgment", + path: "docs/plans/enforcement-intent-design.md", + t: fields( + headline: "Make the repetitive review and abstraction judgements something a machine can propose — two question modes over one evidence substrate", + boundary: "ONE service, TWO question modes over the same evidence packet and ruling vocabulary (operator amendment 2026-08-08): FORWARD — given an exact repository subject and a desired-concept intent, where does this belong (existing-authority, projection-missing, candidate-new-abstraction, reprime-candidate, keep-distinct, missing-consumer/actuator/readback, unknown); INVERSE — given candidate dual representations, should they de-fork (likely-duplicate, existing-authority, projection-missing, keep-distinct, over-collapse-risk, demand-absent, unknown). Every declaration it cites must resolve in the exact reviewed subject; selected subjects come only from the bounded searched universe; anything it cannot ground is unknown, never a plausible sentence. Never a second repository-mapping service.", + displaced_cost: "These judgements are the throughput limit on the whole board, they are all made by one person, and they are mechanical enough to describe — which is precisely why they are being repeated by hand rather than encoded. The attempt-receipt lesson binds here: an LlmAttemptReceipt proves a model RAN, not what it returned — so the handback requires a semantic judgment receipt binding raw-output identity, decoder identity, evidence-packet identity, exact repository subject, searched-universe identity, resolved model and runtime as observations, decoded judgment, cited declarations, and their citation-resolution receipt; malformed output refuses, an invented authority refuses, and incomplete evidence yields only unknown or a located qualified answer.", + first_slice: "Three executed request shapes, smallest label family: one FORWARD mapping (a desired concept lands on an existing authority, a missing projection, or unknown); one INVERSE historical de-fork case classified against a known consolidation ruling; one keep-distinct negative — a similar-looking pair with a load-bearing distinction that must NOT be recommended for consolidation. No patch generation, no coding loop, no automatic commit.", + red_control: "A cited declaration that does not exist is refused, never returned. A judgement with nothing resolvable behind it is unknown. A planted duplicate must be found; a genuinely new concept must not report as duplicate; the keep-distinct negative must hold. Raw output that does not belong to the recorded attempt, decodes ambiguously, or cites outside the bounded universe refuses at admission. Evaluation reports a per-class vector (recovered / wrong-consolidation / projection-missing-correct / keep-distinct-correct / citations-resolved / unknown-when-incomplete / operator-verdict), never one score, and never claims general recall — unknown forks have no observable denominator. A model that merely repeats what the deterministic detector already decided has not demonstrated useful inference.", + out_of_scope: "Not a coding agent, and never authority — the answer is input to a person or to a check, not the decision. A rule that can be decided outright belongs in a check, not here; a mechanically decidable case is never delegated to the model merely because the model can answer it. A future .dag coding workflow is a NAMED DOWNSTREAM CONSUMER of these receipts, not a node here — patch production, iterative agent sessions, and merge authority are outside this requirement. Model selection and tuning live in the judgment-model-selection-and-specialization successor, never here.", + handback: "The service answering both modes with grounded receipts over real subjects, the three executed shapes demonstrated, the per-class evaluation vector against historical rulings, and the measured share of judgements it can actually take." + ) + ), active( identity: "rn_3B3802MNXFPG4VS2VFT7BNBN1Y", @@ -1928,8 +2385,7 @@ fn declared_roadmap_edges() -> List { edge(child: "observation-raw-print-retirement", parent: "observation-collapse-watchdog-restatement"), edge(child: "warm-merge-admission", parent: "five-minute-ci-gate"), - edge(child: "native-selected-witness-bundle", parent: "five-minute-ci-gate"), - edge(child: "native-selected-witness-bundle", parent: "warm-merge-admission"), + edge(child: "five-minute-ci-gate", parent: "native-selected-witness-bundle"), edge(child: "module-grain-materialization", parent: "five-minute-ci-gate"), edge(child: "pre-index-materialization-lookup", parent: "five-minute-ci-gate"), edge(child: "phased-single-process-ci", parent: "five-minute-ci-gate"), @@ -1941,6 +2397,32 @@ fn declared_roadmap_edges() -> List { edge(child: "placement-two-host-warm-qualification", parent: "placement-live-roster-preflight"), edge(child: "placement-shadow-equality", parent: "placement-two-host-warm-qualification"), edge(child: "placement-atomic-authority-flip", parent: "placement-shadow-equality"), + edge(child: "placement-compile-pool-envelope", parent: "placement-live-roster-preflight"), + + edge(child: "ci-cost-arc-reconciliation", parent: "five-minute-ci-gate"), + edge(child: "ci-build-once-per-subject", parent: "ci-cost-arc-reconciliation"), + edge(child: "ci-floor-discovery-snapshot", parent: "ci-cost-arc-reconciliation"), + edge(child: "ci-floor-discovery-snapshot", parent: "module-grain-materialization"), + + edge(child: "compute-artifact-return-and-materialization", parent: "compute-exact-work-contract"), + edge(child: "compute-deduplication-and-admission", parent: "compute-exact-work-contract"), + edge(child: "compute-deduplication-and-admission", parent: "compute-artifact-return-and-materialization"), + edge(child: "ci-owned-execution", parent: "compute-exact-work-contract"), + edge(child: "ci-owned-execution", parent: "compute-artifact-return-and-materialization"), + edge(child: "ci-owned-execution", parent: "compute-deduplication-and-admission"), + edge(child: "ci-scoped-worker-shared-substrate", parent: "ci-floor-discovery-snapshot"), + edge(child: "ci-selection-before-preparation", parent: "ci-floor-discovery-snapshot"), + edge(child: "ci-streaming-realization", parent: "ci-selection-before-preparation"), + edge(child: "ci-streaming-realization", parent: "ci-scoped-worker-shared-substrate"), + edge(child: "phased-single-process-ci", parent: "ci-scoped-worker-shared-substrate"), + edge(child: "ci-remote-build-containment", parent: "compute-artifact-return-and-materialization"), + edge(child: "ci-check-projection", parent: "ci-owned-execution"), + edge(child: "ci-check-projection", parent: "ci-executed-coverage-receipt"), + edge(child: "ci-check-projection", parent: "ci-gate-contention-independent-verdict"), + edge(child: "ci-actions-runner-retirement", parent: "ci-check-projection"), + + edge(child: "generated-projection-registry-containment", parent: "commit-writer-admission"), + edge(child: "generated-commit-policy-census", parent: "generated-projection-registry-containment"), edge(child: "v1-emitter-fixed-point", parent: "pderive-error-movement-receipt"), edge(child: "v2-emitter-direct-rust-door", parent: "v2-emitter-producer-provenance"), @@ -1985,8 +2467,27 @@ fn declared_roadmap_edges() -> List { edge(child: "fleet-closed-loop-converge", parent: "fleet-state-frontier"), edge(child: "fleet-closed-loop-converge", parent: "shell-gate-migration"), edge(child: "fleet-dashboard-instance-membership", parent: "fleet-state-frontier"), + edge(child: "roadmap-serve-emitted-realization", parent: "fleet-state-frontier"), edge(child: "fleet-anti-entropy-hygiene", parent: "fleet-closed-loop-converge"), edge(child: "fleet-receipt-qualified-control", parent: "fleet-anti-entropy-hygiene"), + edge(child: "fleet-main-revision-authority", parent: "fleet-closed-loop-converge"), + edge(child: "fleet-atomic-convergence-verdict", parent: "fleet-closed-loop-converge"), + edge(child: "fleet-runner-host-convergence", parent: "fleet-main-revision-authority"), + edge(child: "fleet-runner-host-convergence", parent: "fleet-atomic-convergence-verdict"), + edge(child: "fleet-runner-host-convergence", parent: "placement-compile-pool-envelope"), + edge(child: "fleet-runner-host-convergence", parent: "compute-exact-work-contract"), + edge(child: "fleet-runner-broker-recovery", parent: "fleet-runner-host-convergence"), + edge(child: "fleet-spark-inference-serving", parent: "fleet-main-revision-authority"), + edge(child: "fleet-spark-inference-serving", parent: "fleet-atomic-convergence-verdict"), + edge(child: "fleet-spark-inference-serving", parent: "compute-exact-work-contract"), + + edge(child: "judgment-mechanical-review-service", parent: "fleet-spark-inference-serving"), + edge(child: "judgment-mechanical-review-service", parent: "confidence-semantic-impact-query"), + edge(child: "judgment-mechanical-review-service", parent: "abstraction-candidate-discovery"), + edge(child: "abstraction-candidate-discovery", parent: "confidence-semantic-impact-query"), + edge(child: "judgment-model-selection-and-specialization", parent: "judgment-mechanical-review-service"), + edge(child: "judgment-model-selection-and-specialization", parent: "fleet-spark-inference-serving"), + edge(child: "fleet-spark-inference-serving", parent: "fleet-spark-host-enrollment"), edge(child: "toolchain-single-resolver", parent: "toolchain-pin-model"), edge(child: "toolchain-rust-hermetic", parent: "toolchain-single-resolver"), @@ -2007,6 +2508,8 @@ data namespace_cross_file_provenance_lane_integration_note: String = "N3-B STACK data namespace_pool_independence_lane_brief_note: String = "N3-C (namespace-pool-independence) LANE BRIEF — operator ruling 2026-08-07. Undispatched until B1 lands; starts from the cleaned final B1 head, not from B0 and not from an intermediate B1 draft.\n\nCONTRACT: Compare two complete B1 clause-(e) projection results for the same source subject — baseline projection versus perturbed projection with one unrelated loaded file added. Required equality across both: same source subject; same reference occurrence identities; same selected declaration identities; same bound-reference population; same occurrence-level provenance rows; same file dependencies; same exact refusal outcome when applicable.\n\nLOAD-BEARING RED CONTROL: A perturbation that reselects a different declaration inside the same provider file while leaving the file-dependency set unchanged must be REFUSED by the full-carrier comparison. A dependency-set-only comparison passes this case while missing the exact Class B reselection N3-C exists to forbid — the comparison is over the complete ReferenceDerivedDependencyProjection carrier, not the deduplicated file list alone.\n\nDEPENDENCIES: Requires B1 (ReferenceDerivedDependencyProjection plus occurrence-grain provenance). Does not require B2 (ordinary loaded compilation closure producer)." +data false_verification_incident_mapping_note: String = "FIVE WAYS THE BUILD REPORTED A RESULT NOBODY EARNED, mapped to their exact homes rather than gathered under an umbrella node (consolidation ruling, 2026-08-08 — the parallel authority branch proposed one observation-verification-integrity node covering all five; a coarse identity over mechanisms this graph already decomposes would be the §3 second authority, so the incident record ports and the node does not). The mapping, one row each: a remote build that exits zero while its output never comes back is compute-artifact-return-and-materialization's first refusal; a stale local binary compared against itself is the same row's manifest-content refusal plus ci-remote-build-containment's stale-artefact refusal; a re-run that replays a combination computed when the change was opened is ci-owned-execution's fresh-combination control; no checks at all treated as satisfied, and a cancelled job rendered as a generic failure, are both ci-check-projection's typed endings — unobserved and cancelled each a distinct answer, never absorbed into pass or fail. Every future incident of this family files against exactly one of those homes; a sixth mechanism with no home is a finding about the graph, not a reason to mint the umbrella." + data namespace_closure_prerequisite_cut_note: String = "THE SIX CAPABILITIES INSIDE namespace-reference-derived-closure ARE NOT SIX NODES, and this cut adds exactly three rows in front of it rather than decomposing it (2026-08-04). Its closing contract, namespace_reference_derived_closure_contract, is a set-difference frontier over six capabilities under three triggers, and every row read Unavailable while the node itself was the only dispatchable thing in the lane. That is a single startable row standing for three separable pieces of work with different substrates, which is the coarse-node hazard namespace_node_recut_note already priced once.\n\nTHE CUT FOLLOWS THE TRIGGERS, because the triggers already record which work each capability waits on. namespace-structural-observations owns the four capabilities under P2aStructuralCandidateProducer7515 — same-parse-tree rules, whose substrate is the merged occurrence-binding parser walk. namespace-cross-file-provenance owns the one under P2aReferenceDependencyProjection7515. namespace-pool-independence owns the one under P2aPoolIndependentDependencyProjection7515.\n\nTHE FIRST TWO ARE PARALLEL, NOT SEQUENTIAL. No dependency runs from the same-file rules to the cross-file projection; they share only the contract they feed. Sequencing them would have idled one worker for no modeled reason. Pool independence DOES depend on cross-file provenance, because a differential needs a projector to perturb.\n\nWHY THE CROSS-FILE ROW IS THE LARGE ONE. The merged parser walk resolves nothing across a file boundary — its own authority note scopes it to authored source through parse_with_table, with transport and module path from the parser. So cross-file target realization sits INSIDE that row rather than upstream of it, and the trigger being named after a merged PR does not mean the capability is discharged.\n\nNO NEW IDENTITY WAS MINTED FOR THE INTEGRATION OR FOR THE CENSUS. namespace-reference-derived-closure keeps its durable identity and now denotes the aggregate handback — all six observations admitted, the expecting-red enrolment retired, its existing check green — so its first_slice moved to say so while its boundary, which still covers all six, did not. namespace-ambiguity-discharge likewise keeps its identity and its existing dependency on the closure node; the census it owns is authoritative only once all six close, never after the same-file rows alone, because a census taken before cross-file answers exist undercounts exactly the candidates that cross a file boundary and inherits the pool coincidence pool-independence exists to remove.\n\nTHE THREE NEW ROWS ARE DELIBERATELY UNBOUND. Each first_slice names authoring its own closing check as its first act, per v1_lane_binding_survey_note: the contract follows the witness, never precedes it. Authoring contracts here to make the rows look startable is the coverage-by-illusion tier that note forbids, and pool-independence could not honestly carry one at all — its differential compares an output shape its own prerequisite has not yet produced." data scm_prerequisite_acceptance_note: String = "The operator explicitly accepted the three independent R0 models and the separate P−1 evidence carrier, then accepted R1 after merging its three-realization compatibility extraction, and accepted P0 after its layered landing spine and twenty-two executing journey witnesses landed. R1 depends only on the three R0 receipts; P0 consumes both accepted R1 and accepted P−1. These receipts preserve every prerequisite and handback as durable facts. Replacing accepted rows with their child would erase history and violate roadmap_acceptance_receipt_note." @@ -2292,11 +2795,11 @@ fn preamble() -> List { ] } -data roadmap_focus_selection_note: String = "THE ONE ROW THAT SETS THE VIEW. Focusing is a projection policy, not an edit — every node and edge stays declared in this file, the dispatch surface still sees the whole graph, and setting this back to NoFocus restores the full page with no recovery step. It selects the v1 exit lane (the sustained effort under way — the v1 → v2 emitter exit plan) and the compiler-guarantee lane (the DESIGN 4b climb backlog: unstaffed by design, on the page so its nodes stay visible for dispatch — keeping it visible is a focus-selection fact, a typed policy in this row, never an out-of-mechanism section); the other lanes remain real work, they are simply not on the page while these are being ground out. The projection states how many it is hiding, derived rather than transcribed, so a focused page can never read as a shorter roadmap." +data roadmap_focus_selection_note: String = "THE ONE ROW THAT SETS THE VIEW. Focusing is a projection policy, not an edit — every node and edge stays declared in this file, the dispatch surface still sees the whole graph, and setting this back to NoFocus restores the full page with no recovery step. The projection states how many it is hiding, derived rather than transcribed, so a focused page can never read as a shorter roadmap.\n\nMOVED TO THE INFRASTRUCTURE LANES (operator direction, 2026-08-07). It previously selected the v1 exit lane and the compiler-guarantee climbs. Those remain real work and are unchanged in this file; they are off the page because forward movement on them was stopped. The stated reason is that infrastructure buckled underneath them — a fleet whose applied state cannot be answered without logging into machines, a required path whose cost is dominated by building and testing, machine faults arriving as reds that look like code faults, and generated artefacts conflicting over lists nobody disagrees about. Work carried out on top of that produces results nobody can trust, so the page shows the lanes that repair it and the lane that makes a merge to main become applied fleet state.\n\nTHE JUDGMENT LANE IS DELIBERATELY OFF THE PAGE while its prerequisites are on it: the machines that would serve its model are converging in the fleet lane, and putting the consumer beside the prerequisite would invite starting it early. It is declared, not parked. THE DETERMINISTIC CONFIDENCE LANE IS ON THE PAGE and is not the judgment lane: repository inspection — what exists, who consumes it, what a change may affect — is immediate development infrastructure that must work with no LLM and no Spark provisioned, so it does not wait behind serving. The judgment service depends on it as its grounding packet; the dependency edge carries that order.\n\nA FOCUS IS A DISPLAY CHOICE, NEVER A DISPOSITION, and the difference matters more now than it did when this row selected two lanes. Off the page currently sits real retained work with real remaining boundaries — the namespace closure re-observed on an exact main subject at four of six capabilities established (A–D; the two dependency-projection capabilities remain unavailable under their P2a triggers, closing frontier two — 2026-08-08 execution receipt, superseding the earlier none-admitted wording), the v1 exit with no module yet crossed and no hand-written Rust yet drained, the shell production vertical, the staged alert program, the frozen TypeScript boundary, the instrument adoption, the deployment activation receipt. Hidden means selected against, and nothing here records WHY a hidden lane is paused or what restarts it. That is a missing dimension on the node, not a fact this projection policy can carry: a lane frozen behind infrastructure, a lane draining to merge, and a lane parked because its hypothesis was falsified are three different states, and today all three read identically as absent. Supplying it is a change to the node shape and to what the page counts, and it lands next; until it does, read a hidden lane as retained and unexplained rather than as decided." data roadmap_focus_selection: RoadmapFocus = FocusOnOwners { - label: "the v1 deletion lane + the guarantee-ladder climbs", - owners: ["self-host", "compiler-guarantee"], + label: "the compute fabric + the infrastructure stabilisation lanes + fleet convergence from main", + owners: ["compute", "fleet", "ci-placement", "ci-control", "generated-artifact", "ci-cost", "confidence"], } fn projected_roadmap_nodes_for_accepted(accepted: List) -> List { @@ -2321,7 +2824,7 @@ fn active_section_for_acceptance(focus: RoadmapFocus, accepted: List List { handback: "Carrier module path, the disposition-derivation fn, and the per-domain path-row count." ) ), + active_sized(identity: "rn_6NCVX2QTBZ0KWR9DPMHJLG5837", id: "compiler-declaration-floor", + owner: "compiler-guarantee", + path: "docs/plans/compiler-guarantee-recovery-gap-analysis.md", + intricacy: IntricacyMedium, + volume: VolumeMedium, + t: fields( + headline: "Behaviour witnesses passing does not mean the declarations are right", + boundary: "A wrong parameter type, a wrong field type, a duplicated import, a duplicated declaration, and a bare reference that resolves only by accident are each refused with a located diagnostic. This is the declaration half of the ordinary floor, distinct from what a program does when it runs.", + displaced_cost: "A value was observed flowing through a field declared as the wrong type while all fifty-two behaviour witnesses over it stayed green. The defect was found by a person reading the declaration. Everything built above a floor that cannot say no inherits the same blindness, and the cost is paid at the point of confusion rather than the point of error.", + first_slice: "One planted defect per class, refused and located, with a correct declaration of the same shape still compiling.", + red_control: "Each of the five planted defects is refused separately, so a single broad refusal cannot stand in for five. A valid declaration keeps its runtime behaviour exactly, so the wall cannot be satisfied by refusing more of the language. An accidentally-resolving bare reference must produce the ambiguity evidence rather than a silent bind.", + out_of_scope: "Not the namespace migration, which consumes this floor rather than supplying it. No rung is asserted here — the census and the claims carrier say where the class stands.", + handback: "The five located refusals, the unchanged-behaviour control, and the class filed against the claims carrier." + ) + ), active_sized(identity: "rn_7ENRV3ZX3AGAOLITDWLUPROO2U", id: "guarantee-baseline-prevalence", owner: "compiler-guarantee", path: "docs/plans/compiler-guarantee-recovery-gap-analysis.md", @@ -3040,6 +3558,8 @@ fn guarantee_ladder_edges() -> List { edge(child: "guarantee-baseline-prevalence", parent: "ladder-claims-carrier"), edge(child: "ladder-census-emitters", parent: "ladder-claims-carrier"), edge(child: "call-missing-and-duplicate-wall", parent: "guarantee-baseline-prevalence"), + edge(child: "compiler-declaration-floor", parent: "guarantee-baseline-prevalence"), + edge(child: "compiler-declaration-floor", parent: "ladder-claims-carrier"), edge(child: "call-signature-resolution-coverage", parent: "guarantee-baseline-prevalence"), edge(child: "method-receiver-normalization", parent: "guarantee-baseline-prevalence"), edge(child: "declared-conformance-grounding", parent: "guarantee-baseline-prevalence"), diff --git a/dag/test/claim/roadmap_authority_test.dag b/dag/test/claim/roadmap_authority_test.dag index c0d7890c03f..60ad86d43cb 100644 --- a/dag/test/claim/roadmap_authority_test.dag +++ b/dag/test/claim/roadmap_authority_test.dag @@ -546,6 +546,8 @@ test fn witness_new_generator_precedes_fixed_point_and_old_generator_deletion() && edge_exists(child: "v1-interpreter-delete", parent: "v1-emitter-delete") } +data native_cutover_edge_direction_note: String = "THE NATIVE EDGE POINTS THE OTHER WAY NOW, and this witness is what forced the change to be deliberate (2026-08-07). It previously required native-selected-witness-bundle to depend on the five-minute gate AND on warm-merge-admission. Both are now asserted ABSENT and the reverse edge is required instead, because the gate is an AGGREGATE OUTCOME: making the native cutover wait for it means the one step that removes the interpreter from the required path cannot start until the programme it contributes to has already succeeded. The warm-merge edge went with it because no input dependency was ever shown — the bundle needs a selected population, an emitter and a toolchain, none of which merge admission supplies.\n\nWHY THIS IS A REORDERING AND NOT A LOOSENING. The node is stale in a specific way: its first slice read 'merge #7599', #7599 is merged, and the cited production enrolment still ran none of its three selected members natively and fell back on all three. So the mechanism exists and the required path takes no benefit, which makes the cutover the first thing that should be startable rather than the last. With no parent, it is. The negatives are asserted rather than merely dropped so the old direction cannot return silently — a re-added dependency on the aggregate would red here, which is the property that was doing the work before and still is." + test fn witness_five_minute_ci_gate_program_chain_is_explicit() -> Bool { id_exists(id: "five-minute-ci-gate") && active_id_exists(id: "five-minute-ci-gate") @@ -558,8 +560,9 @@ test fn witness_five_minute_ci_gate_program_chain_is_explicit() -> Bool { && !id_exists(id: "native-selected-witness-corpus") && !id_exists(id: "exact-tree-materialization") && edge_exists(child: "warm-merge-admission", parent: "five-minute-ci-gate") - && edge_exists(child: "native-selected-witness-bundle", parent: "five-minute-ci-gate") - && edge_exists(child: "native-selected-witness-bundle", parent: "warm-merge-admission") + && edge_exists(child: "five-minute-ci-gate", parent: "native-selected-witness-bundle") + && !edge_exists(child: "native-selected-witness-bundle", parent: "five-minute-ci-gate") + && !edge_exists(child: "native-selected-witness-bundle", parent: "warm-merge-admission") && edge_exists(child: "module-grain-materialization", parent: "five-minute-ci-gate") && edge_exists(child: "pre-index-materialization-lookup", parent: "five-minute-ci-gate") && edge_exists(child: "phased-single-process-ci", parent: "five-minute-ci-gate") @@ -728,10 +731,12 @@ test fn witness_v2_emitter_direct_rust_door_has_discriminating_contract() -> Boo } } +data projection_active_only_focus_independence_note: String = "THIS CLAIM READS THE FOCUS-INDEPENDENT VIEW, and that is the whole point of it (2026-08-07). Its subject is the ACTIVE-SET DERIVATION — declared minus validly accepted — so its three controls have to be about acceptance. Against roadmap_authority(), which applies roadmap_focus_selection, they are not: both negative controls name ACCEPTED nodes (namespace-occurrence-transport and pderive-typesafe-nullary-reflection), so any focus that stops selecting the namespace and P-derive lanes makes them absent because they are HIDDEN, and the claim goes green while proving nothing about acceptance. That is not hypothetical — it happened the moment the focus moved to the infrastructure lanes, and the positive control caught it by failing loudly rather than the negatives passing quietly, which is only luck about which way the focus happened to move.\n\nSo the two facts are separated. Whether an accepted row leaves the active set is asserted here, against NoFocus, where absence can only mean accepted. Whether the focused page hides what the policy says it hides belongs to the focus mechanism's own claims. Binding both to one rendering made the stronger fact hostage to a projection policy row that is expected to move, and DESIGN 5 already names the shape: a check satisfiable by editing a declaration is validation standing where the real subject was available. The precedent is witness_rendered_nodes_are_declared_or_derived, which reads BOTH documents deliberately and says why in rendered_containment_witness_note." + test fn witness_projection_is_active_only() -> Bool { let out = serialize_markdown_source( doc: project_roadmap_to_markdown( - doc: roadmap_authority(), + doc: roadmap_authority_for(focus: NoFocus), merged: authored_merged_prs() ) ) diff --git a/docs/plans/fleet-self-converge-enforcement-design.md b/docs/plans/fleet-self-converge-enforcement-design.md index dcb757cd21d..48e004e87f3 100644 --- a/docs/plans/fleet-self-converge-enforcement-design.md +++ b/docs/plans/fleet-self-converge-enforcement-design.md @@ -3,7 +3,9 @@ **Status:** design for operator sign-off (deep-swift-443, 2026-07-21). **North star:** every srvN host is **homogeneous** — same assimilation spine, same cgroup caps, same runner slots, same build cache, same ephemeral session containers, same self-converge timer — with only **hardware inventory** and **lifecycle phase** allowed to differ. **Supersedes (in part):** the star-topology assumption in `ci-humming.md` T5 ("ctrl fetches script, runs per-host over SSH") and any GHA-cron converge enforcement sketch. -**Builds on:** `host-effect-orchestration.md` Phase E, `gunbc.host_standup` assimilation spine, ROADMAP `2-periodic-actuation`, `membership-diff-reconcile-spine-design.md` (live_deploy binding), `fleet-acceptance-criteria.md` T5, `srvn-buildcache-provisioning-design.md`. +**Builds on:** `host-effect-orchestration.md` Phase E, `gunbc.host_standup` assimilation spine, ROADMAP `fleet-anti-entropy-hygiene` (the successor identity for the pre-2026-07-27 `2-periodic-actuation` row this doc originally cited; the old id resolves to nothing in `roadmap_authority.dag` and the citation is repointed rather than left dangling), `membership-diff-reconcile-spine-design.md` (live_deploy binding), `fleet-acceptance-criteria.md` T5, `srvn-buildcache-provisioning-design.md`. + +**Carrier for (2026-08-07):** ROADMAP `fleet-main-revision-authority`, `fleet-runner-host-convergence`, `fleet-runner-broker-recovery`. Those three rows are what makes a merge to main become applied machine state, converge the build machines' own limits and services through that path, and count the job-runner recovery timer's firings against a named condition for deleting it. This doc's steady-state enforcement loop is the mechanism they schedule; the roadmap rows own the desired-state authority, the fleet-wide answer, and the receipts. ## North star — homogeneous srvN @@ -484,4 +486,4 @@ A host is **homogeneous** when ALL hold (T4/T5 per `fleet-acceptance-criteria.md ## Dissolution trigger -Delete this doc when Phase 4 lands, the ROADMAP `2-periodic-actuation` node closes at T5, `ci-humming.md` T5 is updated, the homogeneity acceptance rows above are witnessed on srv1+srv2+srv3, and the design row is registered in `gunbc.plans.*`. +Delete this doc when Phase 4 lands, the ROADMAP `fleet-anti-entropy-hygiene` node closes at T5 (the successor to the `2-periodic-actuation` id this line originally named), the three rows this doc now carries close, `ci-humming.md` T5 is updated, the homogeneity acceptance rows above are witnessed on srv1+srv2+srv3, and the design row is registered in `gunbc.plans.*`. From cd7e593e3cc720204410a7e1954e483cd892cdbc Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sun, 9 Aug 2026 00:25:13 +0000 Subject: [PATCH 11/15] Fix trim lying model per review 50629; revert TopologyEdge scope creep. Replace identity trim body with a pure-dag seam and route emitted std.algebra::trim through v1_rt::trim via rust_host_string_op_fn_emit. Restore TopologyEdge.port (revert connector_label rename from trim PR scope). Co-authored-by: Cursor --- dag/std/algebra.dag | 4 ++-- dag/std/types.dag | 2 +- src/v1/05_emit_rust.dag | 6 ++++++ src/v1/stage0/src/std_algebra.rs | 4 ++-- src/v1/stage0/src/std_types.rs | 2 +- src/v1/stage0/src/v1_compiler_emit_rust.rs | 12 +++++++++++- 6 files changed, 23 insertions(+), 7 deletions(-) diff --git a/dag/std/algebra.dag b/dag/std/algebra.dag index 1b964a04a80..c432deb0847 100644 --- a/dag/std/algebra.dag +++ b/dag/std/algebra.dag @@ -418,8 +418,8 @@ fn all_algebra_template_names() -> List { all_algebra_field_templates() |> map(template => template.name) } -data trim_free_function_authority_note: String = "trim is the importable free-function authority for whitespace trim on String. The declared body is identity-for-typecheck only; semantic execution routes through free_call.trim (v1_rt::trim). The scalar template row trim on FreeMonoid is the method spelling on the same carrier. Free-call trim(s) requires explicit import std.algebra { trim } — binding via std.types transitive algebra pool membership alone is Class B pool coincidence (#6985). Dissolve-on: bare trim free-call without listed import refuses once closure-independent binding lands repo-wide." +data trim_free_function_authority_note: String = "trim is the importable free-function authority for whitespace trim on String. The declared substrate body is a pure-dag seam (1 / 0); semantic execution routes through free_call.trim (v1_rt::trim). Host realization authority: std.primitives trim_contract. Emitted host-native std.algebra::trim delegates to v1_rt::trim via rust_host_string_op_fn_emit. The scalar template row trim on FreeMonoid is the method spelling on the same carrier. Free-call trim(s) requires explicit import std.algebra { trim } — binding via std.types transitive algebra pool membership alone is Class B pool coincidence (#6985). Dissolve-on: bare trim free-call without listed import refuses once closure-independent binding lands repo-wide." fn trim(s: String) -> String { - s + from_code_point(1 / 0) } diff --git a/dag/std/types.dag b/dag/std/types.dag index 98daf30da03..952ef6049eb 100644 --- a/dag/std/types.dag +++ b/dag/std/types.dag @@ -313,7 +313,7 @@ type TopologyNode { type TopologyEdge { from: String to: String - connector_label: String? + port: String? } type DagDiff { diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index 6432edf7719..341d3d89305 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -398,6 +398,12 @@ fn rust_host_string_op_fn_emit(name: String) -> String? { " __acc\n", "}\n" ) } + } else if name == "trim" { + Present { value: concat( + "pub fn trim(s: String) -> String {\n", + " v1_rt::trim(s)\n", + "}\n" + ) } } else { none } diff --git a/src/v1/stage0/src/std_algebra.rs b/src/v1/stage0/src/std_algebra.rs index accf6ebe4f9..f8f7fb873fa 100644 --- a/src/v1/stage0/src/std_algebra.rs +++ b/src/v1/stage0/src/std_algebra.rs @@ -1681,14 +1681,14 @@ pub fn all_algebra_template_names() -> Rc> { pub fn trim_free_function_authority_note() -> String { thread_local! { static CACHED: String = { - "trim is the importable free-function authority for whitespace trim on String. The declared body is identity-for-typecheck only; semantic execution routes through free_call.trim (v1_rt::trim). The scalar template row trim on FreeMonoid is the method spelling on the same carrier. Free-call trim(s) requires explicit import std.algebra { trim } — binding via std.types transitive algebra pool membership alone is Class B pool coincidence (#6985). Dissolve-on: bare trim free-call without listed import refuses once closure-independent binding lands repo-wide.".to_string() + "trim is the importable free-function authority for whitespace trim on String. The declared substrate body is a pure-dag seam (1 / 0); semantic execution routes through free_call.trim (v1_rt::trim). Host realization authority: std.primitives trim_contract. Emitted host-native std.algebra::trim delegates to v1_rt::trim via rust_host_string_op_fn_emit. The scalar template row trim on FreeMonoid is the method spelling on the same carrier. Free-call trim(s) requires explicit import std.algebra { trim } — binding via std.types transitive algebra pool membership alone is Class B pool coincidence (#6985). Dissolve-on: bare trim free-call without listed import refuses once closure-independent binding lands repo-wide.".to_string() }; } CACHED.with(|c: &String| c.clone()) } pub fn trim(s: String) -> String { - s + v1_rt::trim(s) } #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] diff --git a/src/v1/stage0/src/std_types.rs b/src/v1/stage0/src/std_types.rs index aa2a0f45701..a16bcbc4cf1 100644 --- a/src/v1/stage0/src/std_types.rs +++ b/src/v1/stage0/src/std_types.rs @@ -538,7 +538,7 @@ pub struct TopologyNode { pub struct TopologyEdge { pub from: String, pub to: String, - pub connector_label: Option, + pub port: Option, } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index fa809c53960..dbc04e7fe42 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -685,7 +685,17 @@ pub fn rust_host_string_op_fn_emit(name: String) -> Option { if (name.clone() == "fold_source".to_string()) { Some(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("pub fn fold_source(src: String, algebra: Rc>) -> R {\n".to_string(), " let mut __acc = (algebra.init)(src.clone());\n".to_string()), " for _ in src.chars() {\n".to_string()), " __acc = if (algebra.is_done)(__acc.clone()) { __acc } else { (algebra.step)(__acc) };\n".to_string()), " }\n".to_string()), " __acc\n".to_string()), "}\n".to_string())) } else { - None + if (name.clone() == "trim".to_string()) { + Some(v1_rt::concat( + v1_rt::concat( + "pub fn trim(s: String) -> String {\n".to_string(), + " v1_rt::trim(s)\n".to_string(), + ), + "}\n".to_string(), + )) + } else { + None + } } } } From c6cab96dcbffc471b9e0515aab585ddb1b5a0d3b Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sun, 9 Aug 2026 01:23:34 +0000 Subject: [PATCH 12/15] Own pool-independent trim binding repair (#8062). Bare free-call trim now requires listed import via closure_independent registry in infer_env (func sig, global_bare, ancestry, method-bridge paths); coincidence success is refused. Six specimen tests; emit_rust adds explicit trim import. Co-authored-by: Cursor --- dag/std/algebra.dag | 2 +- .../class_b_trim_pool_coincidence_receipt.md | 17 +-- .../class_b_trim/coincidence_specimen.dag | 2 +- src/v1/04_env.dag | 35 ++++- src/v1/04_infer.dag | 23 +-- src/v1/04_lookup.dag | 39 +++-- src/v1/05_emit_rust.dag | 1 + src/v1/stage0/src/std_algebra.rs | 2 +- src/v1/stage0/src/v1_compiler_emit_rust.rs | 1 + src/v1/stage0/src/v1_compiler_infer.rs | 52 ++++--- src/v1/stage0/src/v1_compiler_infer_env.rs | 47 +++++- src/v1/stage0/src/v1_compiler_infer_lookup.rs | 85 ++++++----- .../tests/src/class_b_trim_specimen_test.rs | 134 ++++++++---------- 13 files changed, 264 insertions(+), 176 deletions(-) diff --git a/dag/std/algebra.dag b/dag/std/algebra.dag index c432deb0847..b93d126a2cf 100644 --- a/dag/std/algebra.dag +++ b/dag/std/algebra.dag @@ -418,7 +418,7 @@ fn all_algebra_template_names() -> List { all_algebra_field_templates() |> map(template => template.name) } -data trim_free_function_authority_note: String = "trim is the importable free-function authority for whitespace trim on String. The declared substrate body is a pure-dag seam (1 / 0); semantic execution routes through free_call.trim (v1_rt::trim). Host realization authority: std.primitives trim_contract. Emitted host-native std.algebra::trim delegates to v1_rt::trim via rust_host_string_op_fn_emit. The scalar template row trim on FreeMonoid is the method spelling on the same carrier. Free-call trim(s) requires explicit import std.algebra { trim } — binding via std.types transitive algebra pool membership alone is Class B pool coincidence (#6985). Dissolve-on: bare trim free-call without listed import refuses once closure-independent binding lands repo-wide." +data trim_free_function_authority_note: String = "trim is the importable free-function authority for whitespace trim on String. The declared substrate body is a pure-dag seam (1 / 0); semantic execution routes through free_call.trim (v1_rt::trim). Host realization authority: std.primitives trim_contract. Emitted host-native std.algebra::trim delegates to v1_rt::trim via rust_host_string_op_fn_emit. The scalar template row trim on FreeMonoid is the method spelling on the same carrier. Free-call trim(s) requires explicit import std.algebra { trim } — bare trim refuses via closure_independent_bare_free_call registry in v1.compiler.infer_env even when std.algebra is already in the compilation pool (#6985 Class B pool coincidence closed on trim by #8062)." fn trim(s: String) -> String { from_code_point(1 / 0) diff --git a/docs/probes/class_b_trim_pool_coincidence_receipt.md b/docs/probes/class_b_trim_pool_coincidence_receipt.md index 7d9043eae97..7f302425d64 100644 --- a/docs/probes/class_b_trim_pool_coincidence_receipt.md +++ b/docs/probes/class_b_trim_pool_coincidence_receipt.md @@ -1,17 +1,18 @@ -# Class B live specimen — `trim` pool-membership coincidence (execution receipt) +# Class B live specimen — `trim` pool-independent binding (execution receipt) -**Date:** 2026-08-08 +**Date:** 2026-08-09 **Session:** lively-bat-817 -**Authority:** `fixtures/class_b_trim/specimen.dag`, `v1-compiler-tests` `class_b_trim_specimen_test` +**Authority:** `fixtures/class_b_trim/specimen.dag`, `v1-compiler-tests` `class_b_trim_specimen_test`, `v1.compiler.infer_env` `closure_independent_bare_free_call` registry ## Verdict -`trim` is absent from the substrate free-call builtin registry (`compile-clean-forcecheck.md` §6 blast-radius census — trim resolves via the structural method path, not registry rows). A module that free-calls `trim` without listing `std.algebra` in its import block compiles only when `std.algebra` is already in the compilation pool — **pool-membership coincidence**, not `ListedImport`. +`trim` is absent from the substrate free-call builtin registry (`compile-clean-forcecheck.md` §6 blast-radius census — trim resolves via the structural method path, not registry rows). **Closure-independent binding for trim landed (#8062):** bare free-call `trim` refuses unless `trim` is in `source_visible_names` via `import std.algebra { trim }`, even when `std.algebra` is already in the compilation pool via unrelated transitive import. Pool-membership coincidence success for trim is the defect this lane closed. -Two discriminating failure shapes: +Discriminating shapes: -1. **Free-call in narrow pool** — pool contains `std.types` stub without `std.algebra` authority → `function 'trim' not found in scope` (or hard import failure on the missing algebra edge). -2. **Method/free-call on `FreeMonoid` receiver** — even with `std.algebra` in pool, `trim(xs)` refuses because `trim` is on `free_monoid_scalar_templates`, not the collection monoid. +1. **Bare trim with algebra in pool but no listed import** — refuses (`function 'trim' not found in scope`); `symbol_resolves: false` on binding observation. +2. **Explicit `import std.algebra { trim }` in narrow pool** — compiles; binding source `ListedImport`; unrelated pool perturbation changes nothing. +3. **Method/free-call on `FreeMonoid` receiver** — refuses because `trim` is on `free_monoid_scalar_templates`, not the collection monoid. ## Repro (green by execution) @@ -19,7 +20,7 @@ Two discriminating failure shapes: CTRL_BUILD_MODE=local RUSTC_WRAPPER= cargo test -p v1-compiler-tests class_b_trim_specimen -- --nocapture ``` -Eight tests: coincidence narrow-pool trim unbound (no `dag/std`), coincidence pool-coincidence trim binding when algebra is in closure, explicit-import narrow-pool refusal without `dag/std`, explicit-import narrow-pool compile with `dag/std`, coincidence compile success with `dag/std` present, direct-import census with coincidence pool, perturbation overlay stability, FreeMonoid receiver refusal. +Six tests: bare trim refuses without algebra in pool; bare trim refuses with algebra in pool but no listed import; explicit-import refuses without algebra authority; explicit-import compiles in narrow pool with ListedImport binding; perturbation overlay stability; FreeMonoid receiver refusal. ## Relation to rust_test_fixtures Class B gate diff --git a/fixtures/class_b_trim/coincidence_specimen.dag b/fixtures/class_b_trim/coincidence_specimen.dag index 2a1a1709d38..1c798e030d8 100644 --- a/fixtures/class_b_trim/coincidence_specimen.dag +++ b/fixtures/class_b_trim/coincidence_specimen.dag @@ -2,7 +2,7 @@ module test.claim.class_b_trim_coincidence_specimen import std.types { String, Bool } -data class_b_trim_coincidence_specimen_note: String = "Regression control for Class B trim (#6985): trim free-call WITHOUT explicit std.algebra import. Compiles only when std.algebra is already in the compilation pool via unrelated transitive import (pool-membership coincidence). Explicit-import specimen is class_b_trim_specimen.dag." +data class_b_trim_coincidence_specimen_note: String = "Negative control for Class B trim (#6985 / #8062): trim free-call WITHOUT explicit std.algebra import. Must REFUSE even when std.algebra is already in the compilation pool via unrelated transitive import — pool coincidence binding for trim is the defect this specimen guards against. Explicit-import specimen is class_b_trim_specimen.dag." fn free_trim(s: String) -> String { trim(s) diff --git a/src/v1/04_env.dag b/src/v1/04_env.dag index 47dfce2bda6..453c4b3d856 100644 --- a/src/v1/04_env.dag +++ b/src/v1/04_env.dag @@ -473,15 +473,42 @@ fn lookup_binding_by_name_local(env: TypeEnv, name: String) -> TypeBinding? { fn lookup_binding_by_name(env: TypeEnv, name: String) -> TypeBinding? { match lookup_binding_by_name_local(env: env, name: name) { - Present { value: binding } => Present { value: binding } + Present { value: binding } => + if listed_import_required_bare_call_blocked(env: env, name: name) { none } + else { Present { value: binding } } Absent => lookup_binding_after_global_bare(env: env, name: name) } } +data closure_independent_bare_free_call_note: String = "Closure-independent bare free-call binding (#6985 / #8062): symbols registered here refuse global_bare / pool-coincidence resolution unless the name is in source_visible_names (selective import or kernel surface). Dissolve-on: PrimitiveDefinition identity-join closes the denominator so every importable free fn is registered once with its definer module — not a hand-maintained trim-only list." + +fn bare_free_call_requires_listed_import(name: String) -> Bool { + name == "trim" +} + +fn import_visible_name(env: TypeEnv, name: String) -> Bool { + match map_get(env.source_visible_names, name) { + Present { value: vis } => vis + Absent => false + } +} + +fn global_bare_blocked_by_listed_import_requirement(env: TypeEnv, name: String) -> Bool { + bare_free_call_requires_listed_import(name: name) && !import_visible_name(env: env, name: name) +} + +fn listed_import_required_bare_call_blocked(env: TypeEnv, name: String) -> Bool { + global_bare_blocked_by_listed_import_requirement(env: env, name: name) +} + fn lookup_binding_after_global_bare(env: TypeEnv, name: String) -> TypeBinding? { - match global_bare_lookup(env: env, name: name) { - Present { value: binding } => Present { value: binding } - Absent => lookup_qualified_module_projection(env: env, name: name) + if global_bare_blocked_by_listed_import_requirement(env: env, name: name) { + none + } else { + match global_bare_lookup(env: env, name: name) { + Present { value: binding } => Present { value: binding } + Absent => lookup_qualified_module_projection(env: env, name: name) + } } } diff --git a/src/v1/04_infer.dag b/src/v1/04_infer.dag index 99cdd61d837..116aac2d655 100644 --- a/src/v1/04_infer.dag +++ b/src/v1/04_infer.dag @@ -103,7 +103,8 @@ import v1.compiler.infer_env { qualify_borrowed_type_names, qualify_borrowed_inferred, qualified_all_but_last, env_with_type_variable_bindings, GlobalBareCandidate, node_with_inferred, node_with_children, ServiceCensusEntry, qualify_decl_reference_positions, - lookup_binding_by_name, binding_declares_name + lookup_binding_by_name, binding_declares_name, + listed_import_required_bare_call_blocked } import std.node { compiler_inductive_fields, compiler_recursive_types } import std.content_hash { content_hash_atom, content_hash_tagged_structural, content_hash_combine_structural } @@ -3150,14 +3151,18 @@ fn infer_expr_body(texpr: Node, scope: InferScope, expected: Node?) -> InferResu Absent => call_fold_acc_type } } else { call_fold_acc_type } - let method_resolution = resolve_known_method_node( - receiver: method_receiver, - receiver_type: resolve_method_receiver_type(receiver_type: first_arg_type, env: scope.type_env), - method_name: func_name, - fold_accumulator_type: if call_fold_info != none { Present { value: refined_call_fold_acc_type } } else { none }, - service_registry: scope.service_registry, - source_indices: scope.type_env.source_indices - ) + let method_resolution = if listed_import_required_bare_call_blocked(env: scope.type_env, name: func_name) { + KnownMethodResolution { semantics: none, result_type: none, diagnostics: [] } + } else { + resolve_known_method_node( + receiver: method_receiver, + receiver_type: resolve_method_receiver_type(receiver_type: first_arg_type, env: scope.type_env), + method_name: func_name, + fold_accumulator_type: if call_fold_info != none { Present { value: refined_call_fold_acc_type } } else { none }, + service_registry: scope.service_registry, + source_indices: scope.type_env.source_indices + ) + } let is_known_method = method_resolution.result_type != none if is_known_method && typed_args |> count > 0 { let receiver = method_receiver diff --git a/src/v1/04_lookup.dag b/src/v1/04_lookup.dag index 5d912a75301..5dfd35f1900 100644 --- a/src/v1/04_lookup.dag +++ b/src/v1/04_lookup.dag @@ -39,7 +39,8 @@ import v1.compiler.infer_types { import v1.compiler.infer_env { TypeEnv, TypeBinding, authored_name, is_recursive_type, lookup_type, lookup_type_for, - GlobalBareLookupState, borrowed_generic_param_names, global_bare_policy_candidate, lookup_binding_by_name, lookup_binding_by_name_local, qualified_all_but_last, qualify_borrowed_type_names, symbol_index_lookup + GlobalBareLookupState, borrowed_generic_param_names, global_bare_policy_candidate, lookup_binding_by_name, lookup_binding_by_name_local, qualified_all_but_last, qualify_borrowed_type_names, symbol_index_lookup, + listed_import_required_bare_call_blocked } import v1.compiler.infer_emit_info { @@ -79,10 +80,14 @@ fn lookup_in_scope(locals: Map, name: String) -> Node? { } fn lookup_func_sig(func_env: ResolvedFuncEnv, type_env: TypeEnv, name: String) -> FuncSigLookup { - match lookup_resolved_sig(env: func_env, name: name) { - FuncSigResolved { sig: sig } => FuncSigResolved { sig: sig } - FuncSigAmbiguous { candidates: cands } => FuncSigAmbiguous { candidates: cands } - FuncSigUnresolved => func_sig_from_global_bare(type_env: type_env, name: name) + if listed_import_required_bare_call_blocked(env: type_env, name: name) { + FuncSigUnresolved + } else { + match lookup_resolved_sig(env: func_env, name: name) { + FuncSigResolved { sig: sig } => FuncSigResolved { sig: sig } + FuncSigAmbiguous { candidates: cands } => FuncSigAmbiguous { candidates: cands } + FuncSigUnresolved => func_sig_from_global_bare(type_env: type_env, name: name) + } } } @@ -107,16 +112,20 @@ fn borrowed_census_decl(type_env: TypeEnv, name: String) -> BorrowedCensusDecl? Absent => none } } else { - match map_get(type_env.symbol_index.global_bare, name) { - Present { value: GlobalBareUniqueBinding { module_path: mp, binding: b } } => - Present { value: BorrowedCensusDecl { owner_module_path: mp, node: b.resolved } } - Present { value: GlobalBareAmbiguousBinding { candidates: cands } } => - match global_bare_policy_candidate(env_module_path: type_env.module_path, candidates: cands) { - Present { value: cand } => - Present { value: BorrowedCensusDecl { owner_module_path: cand.module_path, node: cand.binding.resolved } } - Absent => none - } - Absent => none + if listed_import_required_bare_call_blocked(env: type_env, name: name) { + none + } else { + match map_get(type_env.symbol_index.global_bare, name) { + Present { value: GlobalBareUniqueBinding { module_path: mp, binding: b } } => + Present { value: BorrowedCensusDecl { owner_module_path: mp, node: b.resolved } } + Present { value: GlobalBareAmbiguousBinding { candidates: cands } } => + match global_bare_policy_candidate(env_module_path: type_env.module_path, candidates: cands) { + Present { value: cand } => + Present { value: BorrowedCensusDecl { owner_module_path: cand.module_path, node: cand.binding.resolved } } + Absent => none + } + Absent => none + } } } } diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index 341d3d89305..a61d0ae158f 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -1,5 +1,6 @@ module v1.compiler.emit_rust +import std.algebra { trim } import std.syntax { LiteralValue, BinOp, AlgebraFieldKind } import v1.std.core { Node, InferredNode, Resolved, CompilerError, TypeVariable, is_compiler_error, diff --git a/src/v1/stage0/src/std_algebra.rs b/src/v1/stage0/src/std_algebra.rs index f8f7fb873fa..eb435125b46 100644 --- a/src/v1/stage0/src/std_algebra.rs +++ b/src/v1/stage0/src/std_algebra.rs @@ -1681,7 +1681,7 @@ pub fn all_algebra_template_names() -> Rc> { pub fn trim_free_function_authority_note() -> String { thread_local! { static CACHED: String = { - "trim is the importable free-function authority for whitespace trim on String. The declared substrate body is a pure-dag seam (1 / 0); semantic execution routes through free_call.trim (v1_rt::trim). Host realization authority: std.primitives trim_contract. Emitted host-native std.algebra::trim delegates to v1_rt::trim via rust_host_string_op_fn_emit. The scalar template row trim on FreeMonoid is the method spelling on the same carrier. Free-call trim(s) requires explicit import std.algebra { trim } — binding via std.types transitive algebra pool membership alone is Class B pool coincidence (#6985). Dissolve-on: bare trim free-call without listed import refuses once closure-independent binding lands repo-wide.".to_string() + "trim is the importable free-function authority for whitespace trim on String. The declared substrate body is a pure-dag seam (1 / 0); semantic execution routes through free_call.trim (v1_rt::trim). Host realization authority: std.primitives trim_contract. Emitted host-native std.algebra::trim delegates to v1_rt::trim via rust_host_string_op_fn_emit. The scalar template row trim on FreeMonoid is the method spelling on the same carrier. Free-call trim(s) requires explicit import std.algebra { trim } — bare trim refuses via closure_independent_bare_free_call registry in v1.compiler.infer_env even when std.algebra is already in the compilation pool (#6985 Class B pool coincidence closed on trim by #8062).".to_string() }; } CACHED.with(|c: &String| c.clone()) diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index dbc04e7fe42..c66b7386784 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -11,6 +11,7 @@ pub use crate::extdeps_languages_rust_emit::{ }; pub use crate::gunbc_rust_decl_type_overlay::rust_decl_type_container_overlay_is_admitted; pub use crate::gunbc_stage0_crate_layout_generated::generated_pub_mod_block; +pub use crate::std_algebra::trim; use crate::std_induction::SubValueRelation::SubValueUnknown; pub use crate::std_induction::{InductiveField, SubValueRelation}; use crate::std_serialization::VariantEncoding::*; diff --git a/src/v1/stage0/src/v1_compiler_infer.rs b/src/v1/stage0/src/v1_compiler_infer.rs index d1a4dd6c96a..afbf327424d 100644 --- a/src/v1/stage0/src/v1_compiler_infer.rs +++ b/src/v1/stage0/src/v1_compiler_infer.rs @@ -64,13 +64,13 @@ pub use crate::v1_compiler_infer_env::{ bare_name_miss_diagnostic, binding_declares_name, empty_symbol_index, empty_type_env_cache, env_with_type_variable_bindings, global_bare_is_ambiguous, global_bare_strict_ambiguity_candidates, inductive_fields_for, inductive_fields_list_to_map, - is_recursive_type, is_recursive_type_by_name, lookup_binding_by_name, lookup_type, - lookup_type_by_name, lookup_type_for, merge_inductive_fields, merge_type_env_cache, - merge_type_env_cache_guarded, node_with_children, node_with_inferred, put_inductive_field, - put_inductive_field_cross, qualified_all_but_last, qualify_borrowed_inferred, - qualify_borrowed_type_names, qualify_decl_reference_positions, str_bindings_from_bindings, - symbol_index_insert, symbol_index_insert_decl, symbol_index_insert_service, - symbol_index_lookup, + is_recursive_type, is_recursive_type_by_name, listed_import_required_bare_call_blocked, + lookup_binding_by_name, lookup_type, lookup_type_by_name, lookup_type_for, + merge_inductive_fields, merge_type_env_cache, merge_type_env_cache_guarded, node_with_children, + node_with_inferred, put_inductive_field, put_inductive_field_cross, qualified_all_but_last, + qualify_borrowed_inferred, qualify_borrowed_type_names, qualify_decl_reference_positions, + str_bindings_from_bindings, symbol_index_insert, symbol_index_insert_decl, + symbol_index_insert_service, symbol_index_lookup, }; pub use crate::v1_compiler_infer_env::{ GlobalBareCandidate, GlobalBareLookupState, GuardedTypeEnvCacheMerge, ServiceCensusEntry, @@ -5885,21 +5885,33 @@ pub fn infer_expr_body( } else { call_fold_acc_type.clone() }; - let method_resolution = resolve_known_method_node( - method_receiver.clone(), - resolve_method_receiver_type( - first_arg_type.clone(), + let method_resolution = + if listed_import_required_bare_call_blocked( scope.type_env.clone(), - ), - func_name.clone(), - if (call_fold_info.clone() != None) { - Some(refined_call_fold_acc_type.clone()) + func_name.clone(), + ) { + Rc::new(KnownMethodResolution { + semantics: None, + result_type: None, + diagnostics: Rc::new(vec![]), + }) } else { - None - }, - scope.service_registry.clone(), - scope.type_env.clone().source_indices.clone(), - ); + resolve_known_method_node( + method_receiver.clone(), + resolve_method_receiver_type( + first_arg_type.clone(), + scope.type_env.clone(), + ), + func_name.clone(), + if (call_fold_info.clone() != None) { + Some(refined_call_fold_acc_type.clone()) + } else { + None + }, + scope.service_registry.clone(), + scope.type_env.clone().source_indices.clone(), + ) + }; let is_known_method = (method_resolution.result_type.clone() != None); if (is_known_method.clone() diff --git a/src/v1/stage0/src/v1_compiler_infer_env.rs b/src/v1/stage0/src/v1_compiler_infer_env.rs index 34e2fa3ce78..295c9884e8d 100644 --- a/src/v1/stage0/src/v1_compiler_infer_env.rs +++ b/src/v1/stage0/src/v1_compiler_infer_env.rs @@ -817,15 +817,54 @@ pub fn lookup_binding_by_name_local(env: Rc, name: String) -> Option, name: String) -> Option> { match lookup_binding_by_name_local(env.clone(), name.clone()) { - Some(binding) => Some(binding.clone()), + Some(binding) => { + if listed_import_required_bare_call_blocked(env.clone(), name.clone()) { + None + } else { + Some(binding.clone()) + } + } None => lookup_binding_after_global_bare(env.clone(), name.clone()), } } +pub fn closure_independent_bare_free_call_note() -> String { + thread_local! { + static CACHED: String = { + "Closure-independent bare free-call binding (#6985 / #8062): symbols registered here refuse global_bare / pool-coincidence resolution unless the name is in source_visible_names (selective import or kernel surface). Dissolve-on: PrimitiveDefinition identity-join closes the denominator so every importable free fn is registered once with its definer module — not a hand-maintained trim-only list.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn bare_free_call_requires_listed_import(name: String) -> bool { + (name.clone() == "trim".to_string()) +} + +pub fn import_visible_name(env: Rc, name: String) -> bool { + match v1_rt::map_get(&env.source_visible_names.clone(), name.clone()) { + Some(vis) => vis.clone(), + None => false, + } +} + +pub fn global_bare_blocked_by_listed_import_requirement(env: Rc, name: String) -> bool { + (bare_free_call_requires_listed_import(name.clone()) + && !import_visible_name(env.clone(), name.clone())) +} + +pub fn listed_import_required_bare_call_blocked(env: Rc, name: String) -> bool { + global_bare_blocked_by_listed_import_requirement(env.clone(), name.clone()) +} + pub fn lookup_binding_after_global_bare(env: Rc, name: String) -> Option> { - match global_bare_lookup(env.clone(), name.clone()) { - Some(binding) => Some(binding.clone()), - None => lookup_qualified_module_projection(env.clone(), name.clone()), + if global_bare_blocked_by_listed_import_requirement(env.clone(), name.clone()) { + None + } else { + match global_bare_lookup(env.clone(), name.clone()) { + Some(binding) => Some(binding.clone()), + None => lookup_qualified_module_projection(env.clone(), name.clone()), + } } } diff --git a/src/v1/stage0/src/v1_compiler_infer_lookup.rs b/src/v1/stage0/src/v1_compiler_infer_lookup.rs index 1fefe0d3822..a33ca5cc05b 100644 --- a/src/v1/stage0/src/v1_compiler_infer_lookup.rs +++ b/src/v1/stage0/src/v1_compiler_infer_lookup.rs @@ -21,8 +21,9 @@ pub use crate::v1_compiler_infer_emit_info::{ use crate::v1_compiler_infer_env::GlobalBareLookupState::*; pub use crate::v1_compiler_infer_env::{ authored_name, borrowed_generic_param_names, global_bare_policy_candidate, is_recursive_type, - lookup_binding_by_name, lookup_binding_by_name_local, lookup_type, lookup_type_for, - qualified_all_but_last, qualify_borrowed_type_names, symbol_index_lookup, + listed_import_required_bare_call_blocked, lookup_binding_by_name, lookup_binding_by_name_local, + lookup_type, lookup_type_for, qualified_all_but_last, qualify_borrowed_type_names, + symbol_index_lookup, }; pub use crate::v1_compiler_infer_env::{GlobalBareLookupState, TypeBinding, TypeEnv}; pub use crate::v1_compiler_infer_method::infer_builtin_call_type; @@ -92,17 +93,21 @@ pub fn lookup_func_sig( type_env: Rc, name: String, ) -> Rc { - match (*lookup_resolved_sig(func_env.clone(), name.clone())).clone() { - FuncSigLookup::FuncSigResolved { sig: sig, .. } => { - Rc::new(FuncSigLookup::FuncSigResolved { sig: sig.clone() }) - } - FuncSigLookup::FuncSigAmbiguous { - candidates: cands, .. - } => Rc::new(FuncSigLookup::FuncSigAmbiguous { - candidates: cands.clone(), - }), - FuncSigLookup::FuncSigUnresolved => { - func_sig_from_global_bare(type_env.clone(), name.clone()) + if listed_import_required_bare_call_blocked(type_env.clone(), name.clone()) { + Rc::new(FuncSigLookup::FuncSigUnresolved) + } else { + match (*lookup_resolved_sig(func_env.clone(), name.clone())).clone() { + FuncSigLookup::FuncSigResolved { sig: sig, .. } => { + Rc::new(FuncSigLookup::FuncSigResolved { sig: sig.clone() }) + } + FuncSigLookup::FuncSigAmbiguous { + candidates: cands, .. + } => Rc::new(FuncSigLookup::FuncSigAmbiguous { + candidates: cands.clone(), + }), + FuncSigLookup::FuncSigUnresolved => { + func_sig_from_global_bare(type_env.clone(), name.clone()) + } } } } @@ -131,31 +136,39 @@ pub fn borrowed_census_decl(type_env: Rc, name: String) -> Option None, } } else { - match v1_rt::map_get( - &type_env.symbol_index.clone().global_bare.clone(), - name.clone(), - ) - .as_deref() - .cloned() - { - Some(GlobalBareLookupState::GlobalBareUniqueBinding { - module_path: mp, - binding: b, - .. - }) => Some(Rc::new(BorrowedCensusDecl { - owner_module_path: mp.clone(), - node: b.resolved.clone(), - })), - Some(GlobalBareLookupState::GlobalBareAmbiguousBinding { - candidates: cands, .. - }) => match global_bare_policy_candidate(type_env.module_path.clone(), cands.clone()) { - Some(cand) => Some(Rc::new(BorrowedCensusDecl { - owner_module_path: cand.module_path.clone(), - node: cand.binding.clone().resolved.clone(), + if listed_import_required_bare_call_blocked(type_env.clone(), name.clone()) { + None + } else { + match v1_rt::map_get( + &type_env.symbol_index.clone().global_bare.clone(), + name.clone(), + ) + .as_deref() + .cloned() + { + Some(GlobalBareLookupState::GlobalBareUniqueBinding { + module_path: mp, + binding: b, + .. + }) => Some(Rc::new(BorrowedCensusDecl { + owner_module_path: mp.clone(), + node: b.resolved.clone(), })), + Some(GlobalBareLookupState::GlobalBareAmbiguousBinding { + candidates: cands, + .. + }) => { + match global_bare_policy_candidate(type_env.module_path.clone(), cands.clone()) + { + Some(cand) => Some(Rc::new(BorrowedCensusDecl { + owner_module_path: cand.module_path.clone(), + node: cand.binding.clone().resolved.clone(), + })), + None => None, + } + } None => None, - }, - None => None, + } } } } diff --git a/src/v1/tests/src/class_b_trim_specimen_test.rs b/src/v1/tests/src/class_b_trim_specimen_test.rs index 506a8510852..fd8d97e08c6 100644 --- a/src/v1/tests/src/class_b_trim_specimen_test.rs +++ b/src/v1/tests/src/class_b_trim_specimen_test.rs @@ -1,5 +1,5 @@ //! Class B trim: explicit `import std.algebra { trim }` binds in narrow pools; -//! coincidence binding remains a regression control only. +//! bare trim refuses even when std.algebra is already in the compilation pool. //! //! SCAFFOLD (DESIGN §7 HAND-RUST GATE — explicit deferral): this module exercises //! the v1 compiler-test harness because Class B trim discrimination needs @@ -9,11 +9,9 @@ //! `item_registry` symbols (`run_class_b_import_closure_gate` / `rust_selection_policy_node`) //! and `trim` has no such registry row. Lane: import-strip witness-discovery cascade //! (#6985 Class B pool-membership coincidence; `import-strip-witness-discovery-cascade-diagnosis.md` -//! §9, §12). Sole dissolution: the change landing closure-independent binding for bare -//! free-call `trim` (dissolve trigger on `trim_free_function_authority_note` in -//! `std.algebra` — bare trim without listed import refuses repo-wide) must migrate -//! `fixtures/class_b_trim/*.dag` into enrolled `dag/test/claim/class_b_trim_*` witness -//! rows on the floor roster and delete this Rust module in the same change. +//! §9, §12). Sole dissolution: migrate `fixtures/class_b_trim/*.dag` into enrolled +//! `dag/test/claim/class_b_trim_*` witness rows on the floor roster and delete this +//! Rust module when trim is enrolled on the Class B observation surface. use v1_compiler::cli_run::{ compile_declared_import_closure_only_with_pool, @@ -83,6 +81,12 @@ fn trim_not_found_diagnostic_count( .count() } +fn trim_compile_refused( + compiled: &v1_compiler::v1_compiler_compile::ResolvedPipelineResult, +) -> bool { + trim_not_found_diagnostic_count(compiled) > 0 || hard_diagnostic_count(compiled) > 0 +} + fn algebra_module_in_graph( compiled: &v1_compiler::v1_compiler_compile::ResolvedPipelineResult, ) -> bool { @@ -109,7 +113,7 @@ fn consumer_lists_algebra_directly( } #[test] -fn trim_free_call_fails_in_narrow_pool_without_algebra_coincidence() { +fn trim_free_call_refuses_in_narrow_pool_without_algebra_in_closure() { std::env::set_current_dir(workspace_root()).expect("cwd"); let compiled = compile_declared_import_closure_only_with_pool( &trim_narrow_pool_without_algebra_roots(), @@ -122,34 +126,18 @@ fn trim_free_call_fails_in_narrow_pool_without_algebra_coincidence() { "std.algebra must be absent from narrow pool without dag/std" ); assert!( - !consumer_lists_algebra_directly(compiled.as_ref(), COINCIDENCE_CONSUMER), - "coincidence specimen must not directly import std.algebra" - ); - let trim_binding = declared_import_closure_binding_observation_from_resolved( - compiled.as_ref(), - COINCIDENCE_CONSUMER, - "trim", + trim_compile_refused(compiled.as_ref()), + "bare trim must refuse when std.algebra is not in pool: {:?}", + compiled + .diagnostics + .iter() + .map(|d| format!("{:?}", d.diagnostic)) + .collect::>() ); - match trim_binding { - DeclaredImportClosureBindingObservation::Observed( - DeclaredImportClosureBindingObserved { - symbol_resolves: false, - .. - }, - ) => {} - DeclaredImportClosureBindingObservation::Observed(observed) => { - panic!( - "trim must not resolve via pool coincidence when std.algebra is absent from narrow pool: {observed:?}" - ); - } - DeclaredImportClosureBindingObservation::NotRunnable(reason) => { - panic!("trim binding observation must be runnable: {reason}"); - } - } } #[test] -fn trim_coincidence_free_call_binds_via_pool_when_algebra_in_closure() { +fn trim_free_call_refuses_when_algebra_in_pool_without_listed_import() { std::env::set_current_dir(workspace_root()).expect("cwd"); let compiled = compile_declared_import_closure_only_with_pool( &trim_declared_import_pool_roots(), @@ -161,24 +149,34 @@ fn trim_coincidence_free_call_binds_via_pool_when_algebra_in_closure() { algebra_module_in_graph(compiled.as_ref()), "std.algebra must be in declared-import closure via std.types transitive import" ); + assert!( + !consumer_lists_algebra_directly(compiled.as_ref(), COINCIDENCE_CONSUMER), + "coincidence specimen must not directly import std.algebra" + ); + assert!( + trim_compile_refused(compiled.as_ref()), + "bare trim must refuse even when std.algebra is in pool without listed import: {:?}", + compiled + .diagnostics + .iter() + .map(|d| format!("{:?}", d.diagnostic)) + .collect::>() + ); let trim_binding = declared_import_closure_binding_observation_from_resolved( compiled.as_ref(), COINCIDENCE_CONSUMER, "trim", ); match trim_binding { + DeclaredImportClosureBindingObservation::NotRunnable(_) => {} DeclaredImportClosureBindingObservation::Observed( DeclaredImportClosureBindingObserved { - symbol_resolves: true, - binding_source: Some(UnlistedImportBindingSource::PoolCoincidence), - definer_module: Some(definer), + symbol_resolves: false, .. }, - ) => { - assert_eq!(definer, "std.algebra"); - } + ) => {} other => panic!( - "trim must resolve via pool coincidence when std.algebra is in closure without direct import: {other:?}" + "trim must not resolve via pool coincidence when std.algebra is in closure without listed import: {other:?}" ), } } @@ -197,8 +195,7 @@ fn trim_free_call_refuses_in_narrow_pool_without_algebra_authority() { "std.algebra must be absent from narrow pool without dag/std" ); assert!( - trim_not_found_diagnostic_count(compiled.as_ref()) > 0 - || hard_diagnostic_count(compiled.as_ref()) > 0, + trim_compile_refused(compiled.as_ref()), "explicit import std.algebra {{ trim }} must refuse when std.algebra is not in pool: {:?}", compiled .diagnostics @@ -224,42 +221,26 @@ fn trim_free_call_compiles_via_explicit_import_in_narrow_pool() { consumer_lists_algebra_directly(compiled.as_ref(), BOUND_CONSUMER), "bound specimen must list std.algebra via explicit trim import" ); -} - -#[test] -fn trim_coincidence_binding_still_works_without_explicit_import() { - std::env::set_current_dir(workspace_root()).expect("cwd"); - let pool = trim_declared_import_pool_roots(); - let compiled = compile_declared_import_closure_only_with_pool(&pool, COINCIDENCE_ENTRY, None) - .expect("compile"); - assert!(compiled.graph.is_some(), "graph required"); - assert_eq!(hard_diagnostic_count(compiled.as_ref()), 0); - assert_eq!(trim_not_found_diagnostic_count(compiled.as_ref()), 0); - assert!( - algebra_module_in_graph(compiled.as_ref()), - "std.algebra must be in pool for coincidence binding" - ); - assert!( - !consumer_lists_algebra_directly(compiled.as_ref(), COINCIDENCE_CONSUMER), - "coincidence specimen must not directly import std.algebra" - ); -} - -#[test] -fn trim_explicit_import_not_required_when_pool_coincidence_present() { - std::env::set_current_dir(workspace_root()).expect("cwd"); - let compiled = compile_declared_import_closure_only_with_pool( - &trim_declared_import_pool_roots(), - SPECIMEN_ENTRY, - None, - ) - .expect("compile"); - assert_eq!(hard_diagnostic_count(compiled.as_ref()), 0); - assert_eq!(trim_not_found_diagnostic_count(compiled.as_ref()), 0); - assert!( - consumer_lists_algebra_directly(compiled.as_ref(), BOUND_CONSUMER), - "bound specimen lists trim import even when coincidence pool is present" + let trim_binding = declared_import_closure_binding_observation_from_resolved( + compiled.as_ref(), + BOUND_CONSUMER, + "trim", ); + match trim_binding { + DeclaredImportClosureBindingObservation::Observed( + DeclaredImportClosureBindingObserved { + symbol_resolves: true, + binding_source: Some(UnlistedImportBindingSource::ListedImport), + definer_module: Some(definer), + .. + }, + ) => { + assert_eq!(definer, "std.algebra"); + } + other => { + panic!("trim with explicit import must bind via ListedImport in narrow pool: {other:?}") + } + } } #[test] @@ -290,8 +271,7 @@ fn trim_method_form_fails_on_freemonoid_receiver() { let compiled = compile_declared_import_closure_only_with_pool(&pool, MONOID_ENTRY, None).expect("compile"); assert!( - trim_not_found_diagnostic_count(compiled.as_ref()) > 0 - || hard_diagnostic_count(compiled.as_ref()) > 0, + trim_compile_refused(compiled.as_ref()), "method trim on FreeMonoid receiver must refuse: {:?}", compiled .diagnostics From 0ee2d85de2acaa688bcec44cf342218d0a7ad327 Mon Sep 17 00:00:00 2001 From: "gunbai-bot[bot]" <289086189+gunbai-bot[bot]@users.noreply.github.com> Date: Sat, 8 Aug 2026 22:15:23 -0400 Subject: [PATCH 13/15] Enroll both Sparks; replace the matrix rectangle counts with identity joins (#8056) Three witnesses asserted the derived host/phase matrix against enrollments.length() * host_standup_spine.length(). That is a count equality over a rectangle: it cannot tell a correct matrix from one that hands every host every spine step, which is exactly the shape a participation-scoped program is supposed to make impossible. DESIGN.md section 5 rules that completeness is an identity join, not a count. They are now joins derived from each enrollment's participation-selected program: - per-enrollment cell count == program_step_count(assimilation_program_for) - duplicate-free over host_phase_cell_key - matrix hosts are exactly the enrolled roster, no strays - unmodeled count cross-checked against a disposition filter over the same matrix, rather than against a rectangle - runner enrollments still contribute exactly the whole spine srv5 and srv6 are enrolled as InferenceServingParticipation, which selects exactly four shared obligations each and no runner-only one. ENROLLED IS NOT CONVERGED: all eight Spark cells are honestly unobserved, and a witness asserts that so a future producer reds here instead of silently upgrading the claim. Positive controls keep both sides non-vacuous -- runner-only obligations do exist on runner hosts, and the spine does carry gap phases. Unblocking root fix, not scope creep: gunbc.roadmap_instrument_sandbox defined fn cell(s:) and fn row(cells:) alongside gunbc.plans.md_helpers' fn cell(text:) and fn row(cells:) -- a section 3 homonym pair. Editing this witness widened its closure enough to pull both definers into the pool, after which the plan modules' explicit `cell` imports lost to pool coincidence (#6985 Class B) and every witness in the file failed with "calling 'cell': no parameter named 'text'". The HTML-fragment builders are renamed fragment_cell / fragment_row; md_helpers keeps the plain names. The sandbox keystone still passes. Green by execution, eight witnesses, one at a time on the live tree. Co-authored-by: gunbc-ci-auto-heal Co-authored-by: Claude Opus 5 (1M context) --- dag/gunbc/host_phase_status.dag | 5 +- dag/gunbc/roadmap_instrument_sandbox.dag | 84 +++++------ dag/gunbc/roadmap_instrument_tuner.dag | 50 +++---- .../claim/host_phase_status_witness_test.dag | 130 ++++++++++++++++-- 4 files changed, 190 insertions(+), 79 deletions(-) diff --git a/dag/gunbc/host_phase_status.dag b/dag/gunbc/host_phase_status.dag index 0853b86a885..5a1eac296a1 100644 --- a/dag/gunbc/host_phase_status.dag +++ b/dag/gunbc/host_phase_status.dag @@ -16,6 +16,7 @@ import gunbc.host_assimilation_program { HostEnrollmentIntent, assimilation_program_for, ci_runner_enrollment_participation, + inference_serving_enrollment_participation, } import gunbc.host_standup_assimilation_deduction { srv3_post_install_actuation_verdict } import gunbc.membership_reconcile { @@ -29,7 +30,7 @@ import gunbc.membership_reconcile { membership_write_count, } import gunbc.ownership { Ownership, Owned } -import gunbc.fleet_intent_network { operator_host_srv1, operator_host_srv2, operator_host_srv3, operator_host_srv4 } +import gunbc.fleet_intent_network { operator_host_srv1, operator_host_srv2, operator_host_srv3, operator_host_srv4, operator_host_srv5, operator_host_srv6 } import product.placement_supply { HostIdentity } import std.measure { millisecond } @@ -144,6 +145,8 @@ data fleet_subsumption_enrollments: List = [ HostEnrollmentIntent { host: operator_host_srv2, participation: ci_runner_enrollment_participation }, HostEnrollmentIntent { host: operator_host_srv3, participation: ci_runner_enrollment_participation }, HostEnrollmentIntent { host: operator_host_srv4, participation: ci_runner_enrollment_participation }, + HostEnrollmentIntent { host: operator_host_srv5, participation: inference_serving_enrollment_participation }, + HostEnrollmentIntent { host: operator_host_srv6, participation: inference_serving_enrollment_participation }, ] data fleet_subsumption_hosts_projection_note: String = "The bare host list is DERIVED from the enrollments, never authored beside them. Consumers that legitimately care only about WHICH hosts exist -- toolchain components, network diagnosis, swap backing -- keep reading a List of HostIdentity and are untouched by program scoping, while the enrollment rows stay the single authority for who is in the fleet and what they are in it for. Authoring both lists would rebuild the parallel representation this refactor removes." diff --git a/dag/gunbc/roadmap_instrument_sandbox.dag b/dag/gunbc/roadmap_instrument_sandbox.dag index 4cd0560c87d..dea484f6060 100644 --- a/dag/gunbc/roadmap_instrument_sandbox.dag +++ b/dag/gunbc/roadmap_instrument_sandbox.dag @@ -235,11 +235,11 @@ fn instrument_css_at_role(role: SalienceRole) -> String { join([instrument_emission_root_css_for(role: role), serialize_rules(rules: instrument_rules())], "\n") } -fn cell(s: String) -> Fragment { +fn fragment_cell(s: String) -> Fragment { el_class("span", instrument_cell_class, [ txt(s) ]) } -fn row(cells: List) -> Fragment { +fn fragment_row(cells: List) -> Fragment { el_class("div", instrument_row_class, cells) } @@ -280,11 +280,11 @@ fn live_program_view_result() -> LiveProgramView { } fn depth_cell(d: ProgramDepth) -> Fragment { - cell(s: join([to_string(program_depth_index(d: d)), " · ", program_depth_key(d: d)], "")) + fragment_cell(s: join([to_string(program_depth_index(d: d)), " · ", program_depth_key(d: d)], "")) } fn owner_cell(o: StateOwner) -> Fragment { - cell(s: join([ + fragment_cell(s: join([ state_owner_key(o: o), " — ", if owner_may_write_domain_fact(o: o) { "may write domain facts" } else { "presentation only" }, ], "")) @@ -295,10 +295,10 @@ fn camera_section() -> Fragment { [ el("h2", [ txt("semantic camera") ]), note(s: "Five depths on one axis, three modes across it. A depth change crosses exactly one detent, and every move carries the selected subject unchanged."), - row(cells: map(program_depths(), d => depth_cell(d: d))), + fragment_row(cells: map(program_depths(), d => depth_cell(d: d))), el("h3", [ txt("state ownership") ]), note(s: "Only one owner may write a domain fact, and it is not reachable from the instrument."), - row(cells: map( + fragment_row(cells: map( [AuthoritativeProgramView, LocalInstrumentState, InstrumentObservation, RendererEphemera], o => owner_cell(o: o))), ], @@ -320,32 +320,32 @@ fn remaining_text(r: RemainingShape) -> String { fn render_reading(label: String, r: ProgramReading) -> Fragment { match r { - ReadingNoSubject => row(cells: [ cell(s: label), cell(s: "no subject selected") ]) + ReadingNoSubject => fragment_row(cells: [ fragment_cell(s: label), fragment_cell(s: "no subject selected") ]) ReadingProgramShape { remaining } => - row(cells: [ cell(s: label), cell(s: "whole program"), cell(s: remaining_text(r: remaining)) ]) - ReadingOutcomeStatus { name, node, accepted, open_fronts } => row(cells: [ - cell(s: label), - cell(s: name), - cell(s: node), - cell(s: if accepted { "accepted" } else { "not accepted" }), - cell(s: join([to_string(open_fronts), " open fronts"], "")), + fragment_row(cells: [ fragment_cell(s: label), fragment_cell(s: "whole program"), fragment_cell(s: remaining_text(r: remaining)) ]) + ReadingOutcomeStatus { name, node, accepted, open_fronts } => fragment_row(cells: [ + fragment_cell(s: label), + fragment_cell(s: name), + fragment_cell(s: node), + fragment_cell(s: if accepted { "accepted" } else { "not accepted" }), + fragment_cell(s: join([to_string(open_fronts), " open fronts"], "")), ]) ReadingOutcomeRemaining { name, remaining } => - row(cells: [ cell(s: label), cell(s: name), cell(s: remaining_text(r: remaining)) ]) - ReadingConstraint { subject, kind, headline, in_flight, depth } => row(cells: [ - cell(s: label), - cell(s: join([kind, " constraint"], "")), - cell(s: subject), - cell(s: headline), - cell(s: if in_flight { "work observed" } else { "no work observed" }), + fragment_row(cells: [ fragment_cell(s: label), fragment_cell(s: name), fragment_cell(s: remaining_text(r: remaining)) ]) + ReadingConstraint { subject, kind, headline, in_flight, depth } => fragment_row(cells: [ + fragment_cell(s: label), + fragment_cell(s: join([kind, " constraint"], "")), + fragment_cell(s: subject), + fragment_cell(s: headline), + fragment_cell(s: if in_flight { "work observed" } else { "no work observed" }), ]) ReadingNoConstraint { subject_key, reason } => - row(cells: [ cell(s: label), cell(s: subject_key), cell(s: reason) ]) - ReadingFront { key, name, closed, gates_accepted, gates_total } => row(cells: [ - cell(s: label), - cell(s: name), - cell(s: join([to_string(gates_accepted), " of ", to_string(gates_total), " gates accepted"], "")), - cell(s: if closed { "closed" } else { "open" }), + fragment_row(cells: [ fragment_cell(s: label), fragment_cell(s: subject_key), fragment_cell(s: reason) ]) + ReadingFront { key, name, closed, gates_accepted, gates_total } => fragment_row(cells: [ + fragment_cell(s: label), + fragment_cell(s: name), + fragment_cell(s: join([to_string(gates_accepted), " of ", to_string(gates_total), " gates accepted"], "")), + fragment_cell(s: if closed { "closed" } else { "open" }), ]) ReadingUnavailable { mode, subject_key, cause } => el_class("div", instrument_refusal_class, [ @@ -399,10 +399,10 @@ fn reading_rows() -> Fragment { fn salience_row(assignments: List, spec: V1FinishLineSpec) -> Fragment { let key = v1_finish_line_key(l: spec.line) - row(cells: [ - cell(s: salience_key(role: assignment_role_for(assignments: assignments, subject: key))), - cell(s: spec.name), - cell(s: key), + fragment_row(cells: [ + fragment_cell(s: salience_key(role: assignment_role_for(assignments: assignments, subject: key))), + fragment_cell(s: spec.name), + fragment_cell(s: key), ]) } @@ -412,9 +412,9 @@ fn salience_block(label: String, camera: ProgramCamera, refusal: RefusalAggregat concat( [ el("h3", [ txt(label) ]) ], map(v1_exit_finish_lines(), s => salience_row(assignments: assignments, spec: s))), - [ row(cells: [ - cell(s: salience_key(role: assignment_role_for(assignments: assignments, subject: refusal_subject_key))), - cell(s: refusal_subject_key), + [ fragment_row(cells: [ + fragment_cell(s: salience_key(role: assignment_role_for(assignments: assignments, subject: refusal_subject_key))), + fragment_cell(s: refusal_subject_key), ]) ])) } @@ -447,7 +447,7 @@ fn audition_card(i: AuditionIntensity) -> Fragment { let verdict = admit_audition(i: i, role: audition_subject_role(), subject_key: audition_subject_key()) el_class("div", audition_card_class(i: i), [ el("h3", [ txt(audition_intensity_key(i: i)) ]), - cell(s: "Compiler fixed point"), + fragment_cell(s: "Compiler fixed point"), note(s: audition_verdict_text(a: verdict)), ]) } @@ -461,10 +461,10 @@ fn audition_section() -> Fragment { } fn channel_row(p: InstrumentPreference, c: InstrumentChannel) -> Fragment { - row(cells: [ - cell(s: channel_key(c: c)), - cell(s: join([to_string(amplitude_px(a: channel_amplitude(p: p, c: c))), "px"], "")), - cell(s: "carries no domain fact"), + fragment_row(cells: [ + fragment_cell(s: channel_key(c: c)), + fragment_cell(s: join([to_string(amplitude_px(a: channel_amplitude(p: p, c: c))), "px"], "")), + fragment_cell(s: "carries no domain fact"), ]) } @@ -486,7 +486,7 @@ fn displacement_text(p: InstrumentPreference) -> String { } fn impulse_cell(s: PhysicalImpulseSource, a: SensorAvailability) -> Fragment { - cell(s: join([ + fragment_cell(s: join([ sensor_availability_key(a: a), " → ", match admit_impulse(p: preference_default, s: s, sensor: a) { ImpulseAdmitted { source, settle } => join(["admitted (", settle.name, ")"], "") @@ -501,8 +501,8 @@ fn sensor_states() -> List { } fn impulse_row(s: PhysicalImpulseSource) -> Fragment { - row(cells: concat( - [ cell(s: impulse_source_key(s: s)) ], + fragment_row(cells: concat( + [ fragment_cell(s: impulse_source_key(s: s)) ], map(sensor_states(), a => impulse_cell(s: s, a: a)))) } diff --git a/dag/gunbc/roadmap_instrument_tuner.dag b/dag/gunbc/roadmap_instrument_tuner.dag index e39a5f0777a..eeabccb7b10 100644 --- a/dag/gunbc/roadmap_instrument_tuner.dag +++ b/dag/gunbc/roadmap_instrument_tuner.dag @@ -32,7 +32,7 @@ import gunbc.design.instrument_audition { import gunbc.roadmap_instrument_sandbox { instrument_css, instrument_refusal_class, instrument_row_class, live_program_view_result, LiveViewDerived, LiveViewRefused, - cell, row, note, + fragment_cell, fragment_row, note, } import gunbc.roadmap_program_view { V1DeletionProgramView, RemainingShape } import gunbc.v1_deletion_plan { v1_exit_finish_lines, v1_finish_line_key, V1FinishLineSpec } @@ -139,32 +139,32 @@ fn remaining_text(r: RemainingShape) -> String { fn render_reading(label: String, r: ProgramReading) -> Fragment { match r { - ReadingNoSubject => row(cells: [ cell(s: label), cell(s: "no subject selected") ]) + ReadingNoSubject => fragment_row(cells: [ fragment_cell(s: label), fragment_cell(s: "no subject selected") ]) ReadingProgramShape { remaining } => - row(cells: [ cell(s: label), cell(s: "whole program"), cell(s: remaining_text(r: remaining)) ]) - ReadingOutcomeStatus { name, node, accepted, open_fronts } => row(cells: [ - cell(s: label), - cell(s: name), - cell(s: node), - cell(s: if accepted { "accepted" } else { "not accepted" }), - cell(s: join([to_string(open_fronts), " open fronts"], "")), + fragment_row(cells: [ fragment_cell(s: label), fragment_cell(s: "whole program"), fragment_cell(s: remaining_text(r: remaining)) ]) + ReadingOutcomeStatus { name, node, accepted, open_fronts } => fragment_row(cells: [ + fragment_cell(s: label), + fragment_cell(s: name), + fragment_cell(s: node), + fragment_cell(s: if accepted { "accepted" } else { "not accepted" }), + fragment_cell(s: join([to_string(open_fronts), " open fronts"], "")), ]) ReadingOutcomeRemaining { name, remaining } => - row(cells: [ cell(s: label), cell(s: name), cell(s: remaining_text(r: remaining)) ]) - ReadingConstraint { subject, kind, headline, in_flight, depth } => row(cells: [ - cell(s: label), - cell(s: join([kind, " constraint"], "")), - cell(s: subject), - cell(s: headline), - cell(s: if in_flight { "work observed" } else { "no work observed" }), + fragment_row(cells: [ fragment_cell(s: label), fragment_cell(s: name), fragment_cell(s: remaining_text(r: remaining)) ]) + ReadingConstraint { subject, kind, headline, in_flight, depth } => fragment_row(cells: [ + fragment_cell(s: label), + fragment_cell(s: join([kind, " constraint"], "")), + fragment_cell(s: subject), + fragment_cell(s: headline), + fragment_cell(s: if in_flight { "work observed" } else { "no work observed" }), ]) ReadingNoConstraint { subject_key, reason } => - row(cells: [ cell(s: label), cell(s: subject_key), cell(s: reason) ]) - ReadingFront { key, name, closed, gates_accepted, gates_total } => row(cells: [ - cell(s: label), - cell(s: name), - cell(s: join([to_string(gates_accepted), " of ", to_string(gates_total), " gates accepted"], "")), - cell(s: if closed { "closed" } else { "open" }), + fragment_row(cells: [ fragment_cell(s: label), fragment_cell(s: subject_key), fragment_cell(s: reason) ]) + ReadingFront { key, name, closed, gates_accepted, gates_total } => fragment_row(cells: [ + fragment_cell(s: label), + fragment_cell(s: name), + fragment_cell(s: join([to_string(gates_accepted), " of ", to_string(gates_total), " gates accepted"], "")), + fragment_cell(s: if closed { "closed" } else { "open" }), ]) ReadingUnavailable { mode, subject_key, cause } => el_class("div", instrument_refusal_class, [ @@ -214,9 +214,9 @@ fn subject_row(spec: V1FinishLineSpec) -> Fragment { attr(name: "tabindex", value: "0"), attr(name: "data-subject-key", value: key), ], [ - cell(s: spec.name), - cell(s: key), - cell(s: spec.claim), + fragment_cell(s: spec.name), + fragment_cell(s: key), + fragment_cell(s: spec.claim), ]) } diff --git a/dag/test/claim/host_phase_status_witness_test.dag b/dag/test/claim/host_phase_status_witness_test.dag index bb84a605fdc..b2b936ed2a2 100644 --- a/dag/test/claim/host_phase_status_witness_test.dag +++ b/dag/test/claim/host_phase_status_witness_test.dag @@ -138,22 +138,44 @@ test fn witness_reconcile_covers_upsert_teardown_and_noop() -> Bool { && membership_refusal_count(plan: plan) == 0 } -data witness_matrix_shape_note: String = "Shape assertions over the derived matrix, pinned to the spine and the enrolled fleet rather than to hand-copied numbers: the matrix is exactly (hosts x spine steps), so it cannot silently drop a phase or a host. The coverage count is the honest measure of the live-observation frontier — one cell has a real producer today, (srv3, prefix:os-install-actuated) — and that number is what items 2 and 3 of the lane exist to raise. Pinning it means adding a producer is a VISIBLE, counted event that must update this witness, never a silent improvement nobody can point at." - -test fn witness_matrix_is_hosts_times_spine() -> Bool { - let cells = host_fleet_phase_matrix(enrollments: fleet_subsumption_enrollments).length() - let expected = fleet_subsumption_enrollments.length() * host_standup_spine.length() - cells == expected +data witness_matrix_shape_note: String = "Shape assertions over the derived matrix, pinned to the spine and the enrolled fleet rather than to hand-copied numbers: the matrix is an exact identity join over (host x the obligations that host's participation selects), duplicate-free and covering exactly the enrolled roster, so it cannot silently drop a phase or a host — nor hand a host an obligation its participation does not own, which the former rectangle count could not see. The coverage count is the honest measure of the live-observation frontier — one cell has a real producer today, (srv3, prefix:os-install-actuated) — and that number is what items 2 and 3 of the lane exist to raise. Pinning it means adding a producer is a VISIBLE, counted event that must update this witness, never a silent improvement nobody can point at." + +// An exact identity join, not a product. Every enrollment contributes exactly the obligations its +// own participation selects — program_step_count over assimilation_program_for — so a host whose +// participation claims four of the spine's steps contributes four cells, never the full spine. The +// former assertion was enrollments.length() * host_standup_spine.length(), which is a count +// equality over a rectangle: it could not distinguish a correct matrix from one that gave every +// host every step, and DESIGN.md §5 rules that completeness is an identity join, not a count. +test fn witness_matrix_is_exact_join_over_participation_programs() -> Bool { + let matrix = host_fleet_phase_matrix(enrollments: fleet_subsumption_enrollments) + let matched = filter( + fleet_subsumption_enrollments, + e => { + let owed = program_step_count(program: assimilation_program_for(intent: e)) + let got = filter(matrix, c => c.cell.host == e.host).length() + got == owed + }, + ) + matched.length() == fleet_subsumption_enrollments.length() } test fn witness_live_observation_frontier_is_one_cell() -> Bool { host_phase_observation_coverage_count(enrollments: fleet_subsumption_enrollments) == 1 } +// Cross-check between two independent producers over the same cells rather than a rectangle: the +// counter walks the enrollments, the join filters the derived matrix by disposition. A rectangle +// assertion here would go green even if the gap steps landed on the wrong hosts. test fn witness_unmodeled_cells_track_spine_gaps() -> Bool { - let unmodeled = host_phase_unmodeled_count(enrollments: fleet_subsumption_enrollments) - let expected = fleet_subsumption_enrollments.length() * host_standup_gap_phase_count() - unmodeled == expected + let matrix = host_fleet_phase_matrix(enrollments: fleet_subsumption_enrollments) + let gap_cells = filter( + matrix, + c => match c.disposition { + DeclaredGap { interim_realization: _, refusal: _ } => true + ModeledCompose { authorities: _ } => false + }, + ) + host_phase_unmodeled_count(enrollments: fleet_subsumption_enrollments) == gap_cells.length() } data witness_remaining_work_note: String = "Remaining work is the reconcile's own upsert count, and it must equal every cell that is not already at the FULL desired state — modeled AND converged. Asserting it against an independently computed count is what stops the reconcile from silently retiring cells: if desired ever weakened to 'converged, disposition ignored', srv3's installed-but-unmodeled OS cell would drop out of the count and the lane would look one item closer to done than it is." @@ -276,7 +298,93 @@ test fn witness_every_spine_step_is_claimed_by_some_participation() -> Bool { unclaimed.length() == 0 } +// The runner control the scope requires: enrolling the Sparks must not change what a CI-runner +// enrollment owes. Each runner enrollment still contributes exactly its own program, and that +// program is still the whole spine — asserted per enrollment, so it stays true no matter how many +// non-runner hosts join the roster. test fn witness_program_scoping_preserves_the_runner_fleet_matrix() -> Bool { - let cells = host_fleet_phase_matrix(enrollments: fleet_subsumption_enrollments).length() - cells == fleet_subsumption_hosts.length() * host_standup_spine.length() + let matrix = host_fleet_phase_matrix(enrollments: fleet_subsumption_enrollments) + let runners = filter( + fleet_subsumption_enrollments, + e => { + let owed = program_step_count(program: assimilation_program_for(intent: e)) + owed == host_standup_spine.length() + }, + ) + let intact = filter( + runners, + e => filter(matrix, c => c.cell.host == e.host).length() == host_standup_spine.length(), + ) + runners.length() == 4 && intact.length() == runners.length() +} + +test fn witness_fleet_matrix_has_no_duplicated_cells() -> Bool { + let matrix = host_fleet_phase_matrix(enrollments: fleet_subsumption_enrollments) + let duplicated = filter( + matrix, + c => filter(matrix, d => host_phase_cell_key(c: d) == host_phase_cell_key(c: c)).length() > 1, + ) + duplicated.length() == 0 +} + +test fn witness_matrix_hosts_are_exactly_the_enrolled_roster() -> Bool { + let matrix = host_fleet_phase_matrix(enrollments: fleet_subsumption_enrollments) + let covered = filter( + fleet_subsumption_hosts, + h => filter(matrix, c => c.cell.host == h).length() > 0, + ) + let stray = filter( + matrix, + c => filter(fleet_subsumption_hosts, h => h == c.cell.host).length() == 0, + ) + covered.length() == fleet_subsumption_hosts.length() && stray.length() == 0 +} + +// The Spark enrollment itself, stated at the grain it is actually known: srv5 and srv6 are +// ENROLLED as InferenceServingParticipation, which selects exactly four shared obligations and no +// runner-only one. Enrolled is not converged — every one of these eight cells is honestly +// unobserved, and nothing here claims identity convergence, reach, compute enrolment, or serving. +test fn witness_enrolled_sparks_carry_exactly_the_shared_obligations() -> Bool { + let matrix = host_fleet_phase_matrix(enrollments: fleet_subsumption_enrollments) + let sparks = filter( + matrix, + c => c.cell.host == operator_host_srv5 || c.cell.host == operator_host_srv6, + ) + let shared = filter(sparks, c => cell_label_is_shared(c: c)) + let runner_only = filter(sparks, c => cell_label_is_runner_only(c: c)) + sparks.length() == 8 && shared.length() == 8 && runner_only.length() == 0 +} + +// The unobserved half of the same claim, kept separate so a future producer landing on a Spark +// cell reds this witness instead of silently upgrading the sentence above. +test fn witness_enrolled_spark_cells_are_all_unobserved() -> Bool { + let matrix = host_fleet_phase_matrix(enrollments: fleet_subsumption_enrollments) + let observed = filter( + matrix, + c => (c.cell.host == operator_host_srv5 || c.cell.host == operator_host_srv6) + && match c.observation { + Present { value: _ } => true + Absent => false + }, + ) + observed.length() == 0 +} + +// Non-vacuity guard for the gap join above: if the spine carried no DeclaredGap step at all, that +// witness would compare zero against zero and pass while proving nothing. +test fn witness_spine_actually_carries_gap_phases() -> Bool { + host_standup_gap_phase_count() > 0 +} + +// Positive control for the Spark witness above: cell_label_is_runner_only must actually match +// something, or "the Sparks carry no runner-only obligation" is satisfied by a predicate that +// matches nothing. The runner hosts are where those obligations live. +test fn witness_runner_only_obligations_exist_on_runner_hosts() -> Bool { + let matrix = host_fleet_phase_matrix(enrollments: fleet_subsumption_enrollments) + let runner_only = filter(matrix, c => cell_label_is_runner_only(c: c)) + let on_sparks = filter( + runner_only, + c => c.cell.host == operator_host_srv5 || c.cell.host == operator_host_srv6, + ) + runner_only.length() > 0 && on_sparks.length() == 0 } From 748e319c52d49df8890b8f6c12918bd91e1b00ae Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sun, 9 Aug 2026 02:42:09 +0000 Subject: [PATCH 14/15] Register service-op String wire projection method fork class (#8062). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Names the infer-layer FaithfulFreeMonoid vs String receiver split, pins trim_method_form_fails_on_freemonoid_receiver, and records dissolve-on as type-node unification — separate from the trim binding-bridge repair. Co-authored-by: Cursor --- dag/extdeps/shell/exec.dag | 2 ++ dag/gunbc/plans/model_realization_fork.dag | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/dag/extdeps/shell/exec.dag b/dag/extdeps/shell/exec.dag index 6bc9e9f1c10..80a37f64db1 100644 --- a/dag/extdeps/shell/exec.dag +++ b/dag/extdeps/shell/exec.dag @@ -46,6 +46,8 @@ fn transport_script_seal(body: String) -> TransportScript data shell_exec_run_stdin_route_note: String = "shell.Exec.Run transport: argv is fixed [bash, -s]; the script input is routed through the transport stdin field. Large payloads belong here — they never become a single argv token, so they do not encounter the Linux per-argument exec ceiling modeled in extdeps.exec.exec_arg_limit.host_exec_arg_max_strlen (deploy Part A / #6711)." +data service_op_string_wire_projection_method_fork_note: String = "🟡 KNOWN CLASS (infer-layer, not runtime Value): extdeps service-operation output fields declared `T from \"wire_key\"` (e.g. stdout: String from \"stdout\") infer as FaithfulFreeMonoid / Coproduct carriers when the closure lacks v1-seed sources (v1.compiler.infer rust_corpus_repr), while ordinary String parameters and algebra ReceiverSelf method templates (trim, length, …) attach to the declared String type node. Method syntax on the wire-minted receiver therefore refuses (method trim not found on Coproduct(FreeMonoid)) even though the extdeps contract names String. Free-call trim(s: x) can still compile when explicit import lists std.algebra { trim } and the argument coerces to String — a binding bridge, not type-node unification (#8062 trim registry). Pinning witness: fixtures/class_b_trim/monoid_specimen.dag + v1-compiler-tests trim_method_form_fails_on_freemonoid_receiver; bold-eagle effect_plan wire projection confirmed same class. Every other String method on any service-op result field shares this latent fork until dissolved. DISSOLVE-ON: service-op wire projection unifies with the declared output type authority so ReceiverSelf algebra methods attach to the same node the extdeps contract names (FaithfulFreeMonoid mint ≡ String for String-declared wire fields)." + data shell_exec_check_argv_embed_note: String = "shell.Exec.Check transport: argv is [sh, -c, command] — the entire command string is ONE argv element. On Linux hosts this is the surface that hits execve(2) MAX_ARG_STRLEN (extdeps.exec.exec_arg_limit.host_exec_arg_max_strlen): an oversized embed fails E2BIG (opaque os error 7) unless the interpreter refuses first at dispatch_shell (InterpError::ArgvExceedsHostArgMax). Prefer Run for payloads approaching the per-argument ceiling." data shell_exec_transport_ceiling_authority: ByteSize = host_exec_arg_max_strlen diff --git a/dag/gunbc/plans/model_realization_fork.dag b/dag/gunbc/plans/model_realization_fork.dag index 8a65470816f..53114851b65 100644 --- a/dag/gunbc/plans/model_realization_fork.dag +++ b/dag/gunbc/plans/model_realization_fork.dag @@ -46,7 +46,9 @@ fn model_realization_fork_body() -> List { li(text: "**`Bool`** — currently safe (no nominal-arm match in the corpus); if one lands, apply the numeric pattern (~5 lines). Low priority."), ]), p(text: "**Net:** grounding the numeric tower kills the *visible* straddle guards; splitting `Value::Null` is what actually closes the fail-open *class*. Until #2 lands, the cache/eq guards are per-site patches, not a dissolved root."), - h2(text: "4. Relationship to the lock-down"), + h2(text: "5. Infer-layer service-op String wire projection fork (algebra method attachment)"), + p(text: "**Registered class (2026-08-09, #8062 trim + bold-eagle-107):** extdeps service-operation outputs declared `String from \"stdout\"` (and sibling wire fields) infer as **FaithfulFreeMonoid / Coproduct** receivers in pure-v2 closures (`rust_corpus_repr` without v1 seed), while algebra **ReceiverSelf** method templates (trim, and every other String method) attach only to the **declared String** type node. Method syntax on the wire-minted receiver refuses (`method trim not found on Coproduct(FreeMonoid)`) even though the service contract names String. Free-call `trim(s: x)` with `import std.algebra { trim }` is a **binding bridge** only — it does not unify the type nodes (#8062 scope). **Pinning witness:** `fixtures/class_b_trim/monoid_specimen.dag` + `v1-compiler-tests::trim_method_form_fails_on_freemonoid_receiver`; carrier note `extdeps.shell.exec` `service_op_string_wire_projection_method_fork_note`. **Dissolve-on:** wire projection mints the same type node the extdeps output declaration names so ReceiverSelf methods attach without a per-method binding workaround."), + h2(text: "6. Relationship to the lock-down"), p(text: "This is the root the lock-down lane (`fail-closed-lockdown.md` §3a) was hunting. The §4 lock-down fixes (content-key caches, purity oracles, realizer-key lens) are *per-site patches* that stay necessary until this seam is grounded; grounding (esp. #2) makes whole classes of them dead code. Sequence the lock-down fixes to ship now (stop the bleeding), and the grounding (numeric tower → Null split) to dissolve the root behind them."), ] } From 698cc74a01569a0cec7eed64ea73912e2bd58203 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Sun, 9 Aug 2026 02:51:30 +0000 Subject: [PATCH 15/15] chore: regenerate drifted generated artifacts (ci auto-heal) --- docs/plans/model-realization-fork.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/plans/model-realization-fork.md b/docs/plans/model-realization-fork.md index 005f9053b6e..a597c58aa93 100644 --- a/docs/plans/model-realization-fork.md +++ b/docs/plans/model-realization-fork.md @@ -44,7 +44,11 @@ Every primitive is **modeled** as a `.dag` coproduct and **realized** as a nativ **Net:** grounding the numeric tower kills the *visible* straddle guards; splitting `Value::Null` is what actually closes the fail-open *class*. Until #2 lands, the cache/eq guards are per-site patches, not a dissolved root. -## 4. Relationship to the lock-down +## 5. Infer-layer service-op String wire projection fork (algebra method attachment) + +**Registered class (2026-08-09, #8062 trim + bold-eagle-107):** extdeps service-operation outputs declared `String from "stdout"` (and sibling wire fields) infer as **FaithfulFreeMonoid / Coproduct** receivers in pure-v2 closures (`rust_corpus_repr` without v1 seed), while algebra **ReceiverSelf** method templates (trim, and every other String method) attach only to the **declared String** type node. Method syntax on the wire-minted receiver refuses (`method trim not found on Coproduct(FreeMonoid)`) even though the service contract names String. Free-call `trim(s: x)` with `import std.algebra { trim }` is a **binding bridge** only — it does not unify the type nodes (#8062 scope). **Pinning witness:** `fixtures/class_b_trim/monoid_specimen.dag` + `v1-compiler-tests::trim_method_form_fails_on_freemonoid_receiver`; carrier note `extdeps.shell.exec` `service_op_string_wire_projection_method_fork_note`. **Dissolve-on:** wire projection mints the same type node the extdeps output declaration names so ReceiverSelf methods attach without a per-method binding workaround. + +## 6. Relationship to the lock-down This is the root the lock-down lane (`fail-closed-lockdown.md` §3a) was hunting. The §4 lock-down fixes (content-key caches, purity oracles, realizer-key lens) are *per-site patches* that stay necessary until this seam is grounded; grounding (esp. #2) makes whole classes of them dead code. Sequence the lock-down fixes to ship now (stop the bleeding), and the grounding (numeric tower → Null split) to dissolve the root behind them.