From ec099f3ac8726ab2c14904fd36f99dc0e5c02a9a Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Sat, 3 Oct 2026 20:05:20 +0000 Subject: [PATCH 01/65] WIP census: argv word construction lens + roster instrument + witness (walk under iteration) --- .../argv_word_construction_census.dag | 121 +++ src/v1/stage0/tests/argv_parse_probe.rs | 70 ++ src/v2/lens/argv_word_construction.dag | 827 ++++++++++++++++++ .../argv_word_construction/census_test.dag | 226 +++++ .../argv_word_construction/host_words.dag | 41 + 5 files changed, 1285 insertions(+) create mode 100644 dag/gunbc/instruments/argv_word_construction_census.dag create mode 100644 src/v1/stage0/tests/argv_parse_probe.rs create mode 100644 src/v2/lens/argv_word_construction.dag create mode 100644 src/v2/test/claim/argv_word_construction/census_test.dag create mode 100644 test/fixture_roots/argv_word_construction/host_words.dag diff --git a/dag/gunbc/instruments/argv_word_construction_census.dag b/dag/gunbc/instruments/argv_word_construction_census.dag new file mode 100644 index 00000000000..3747f72d1b4 --- /dev/null +++ b/dag/gunbc/instruments/argv_word_construction_census.dag @@ -0,0 +1,121 @@ +module gunbc.instruments.argv_word_construction_census + +// THE TYPED-ARGV CONSTRUCTION CENSUS ROSTER (debt paydown trial, witty-tern-54, 2026-10-03). +// +// The instrument is v2.lens.argv_word_construction; this module commits WHAT IT COUNTS: the +// population of argv-string construction sites over dag/extdeps and dag/gunbc, at identity grain, +// as a typed roster that may only shrink. DESIGN 5: a count over the current tree is not an +// oracle — every row below joins to a live census member at (module_file, decl, decl_kind), a +// row that stops resolving refuses the recount, and the population never grows past the committed +// baseline: new string-built argv is refused, not enrolled. +// +// Recount (the named, re-runnable instrument run): discover the two roots' module files by the +// declaration index, walk each with the lens, and hold the gates over (live, roster, baseline). +// The gates live in the lens; this module owns the committed state: the roster, the baseline +// cardinality, and the sites classified as NOT argv debt with a stated reason. + +import v2.lens.argv_word_construction { + ArgvWordsDeclKind, + ArgvWordsRosterRow, + ArgvWordsSite, + ArgvWordsSiteClass, + ExtdepsInterfaceBuilder, + FnDecl, + DataDecl, + WorkflowCallSite, + ArgvWordsModuleEstablished, + ArgvWordsModuleRefused, + argv_word_construction_gates, + argv_word_construction_module_sites +} +import v2.std.algebra { fold_list, list_reverse } +import v2.std.collection { List } +import std.algebra { Cons, Empty, FreeMonoid } +import std.process { ExitSuccess, ProcessExit, exit_failure } +import v2.lens.module_graph { ModuleDeclarationFact, module_declaration_facts_live } + +// The modeled roots. Anything else is refused by the lens, never silently zero. +data argv_word_construction_census_roots: List = ["dag/extdeps", "dag/gunbc"] + +// THE COMMITTED BASELINE: the population the instrument counted over the two roots at the start +// of the trial (origin/main, 2026-10-03). The roster may only shrink past this number. +data argv_word_construction_census_baseline_population: Int = 0 + +// THE ROSTER: one row per live census member. `note` names what the site is and what pays it. +// Kept at zero rows until the first committed recount fills it from the instrument (never by +// hand from a text search). +data argv_word_construction_census_roster: List = [] + +// Sites whose evidence is read and REFUSED AS DEBT with a stated reason: the census classifies, +// it does not silently count. Each entry names its reason; a reason that stops being true must +// delete the entry (the same shrink discipline as the roster). +data argv_word_construction_census_excluded: List = [] + +type ArgvWordsExclusion { + module_file: String + decl: String + reason: String +} + +fn excluded_identity_matches(e: ArgvWordsExclusion, s: ArgvWordsSite) -> Bool { + (e.module_file == s.module_file) && (e.decl == s.decl) +} + +fn site_is_excluded(s: ArgvWordsSite) -> Bool { + fold_list(xs: argv_word_construction_census_excluded, empty: false, cons: fn(acc, e) { acc || excluded_identity_matches(e: e, s: s) }) +} + +// The live census members after classification: exclusions are visible subtractions with reasons. +fn argv_word_construction_census_live(sites: List) -> List { + fold_list(xs: sites, empty: Empty, cons: fn(acc, s) { if site_is_excluded(s: s) { acc } else { Cons { head: s, tail: acc } } }) +} + +// The contract over one recount's live sites. +fn argv_word_construction_census_check_sites(sites: List) -> ProcessExit { + argv_word_construction_gates(live: argv_word_construction_census_live(sites: sites), roster: argv_word_construction_census_roster, baseline_population: argv_word_construction_census_baseline_population) +} + +// The recount entry: discover the roots' module files by the declaration index (never a text +// search), walk each with the lens, and hold the contract. +fn argv_word_construction_census_recount() -> ProcessExit { + let decls = module_declaration_facts_live(pool_roots: argv_word_construction_census_roots) + let paths_reversed = fold_list(xs: decls, empty: Empty, cons: fn(acc, d) { + let p = declared_module_path(d: d) + if p == "" { acc } else { Cons { head: p, tail: acc } } + }) + argv_word_construction_census_check_paths(paths: list_reverse(xs: paths_reversed)) +} + +fn declared_module_path(d: ModuleDeclarationFact) -> String { + d.path +} + +type ArgvWordsCollected { + sites: FreeMonoid + refusals: FreeMonoid +} + +fn collect_path(c: ArgvWordsCollected, p: String) -> ArgvWordsCollected { + match argv_word_construction_module_sites(path: p) { + ArgvWordsModuleRefused { path: rp, reason: r } => + ArgvWordsCollected { sites: c.sites, refusals: Cons { head: concat(concat(concat("REFUSED\t", rp), "\t"), r), tail: c.refusals } } + ArgvWordsModuleEstablished { path: _, sites: sites } => + ArgvWordsCollected { sites: fold_list(xs: sites, empty: c.sites, cons: fn(acc, s) { Cons { head: s, tail: acc } }), refusals: c.refusals } + } +} + +// The contract over an explicit path list: the per-module walk is the lens's; the gates run on the +// collected live sites, and every per-module refusal is named, never dropped. Kept separate from +// the discovery so a bounded recount (one module, one directory) is runnable in a claim test +// without the whole corpus. +fn argv_word_construction_census_check_paths(paths: List) -> ProcessExit { + let collected = fold_list(xs: paths, empty: ArgvWordsCollected { sites: Empty, refusals: Empty }, cons: collect_path) + match list_reverse(xs: collected.refusals) { + Empty => argv_word_construction_census_check_sites(sites: list_reverse(xs: collected.sites)) + nonempty => exit_failure(reason: join_lines(rs: nonempty)) + } +} + +fn join_lines(rs: FreeMonoid) -> String { + fold_list(xs: list_reverse(xs: rs), empty: "", cons: fn(acc, l) { if acc == "" { l } else { concat(concat(acc, "\n"), l) } }) +} diff --git a/src/v1/stage0/tests/argv_parse_probe.rs b/src/v1/stage0/tests/argv_parse_probe.rs new file mode 100644 index 00000000000..2817693f4fc --- /dev/null +++ b/src/v1/stage0/tests/argv_parse_probe.rs @@ -0,0 +1,70 @@ +#![allow(clippy::disallowed_macros)] + +//! THROWAWAY parse probe for the typed-argv census lens (deleted before commit). +//! Prints the exact v1 parse error for each argv-word-construction file. + +use im::HashMap; +use std::rc::Rc; +use v1_compiler::cli_run::workspace_root; +use v1_compiler::v1_compiler_parse::ParseResult; +use v1_compiler::v1_std_core::{diagnostic_to_message, diagnostic_to_span}; + +fn parse_path(relative_path: &str) -> Result<(), String> { + let path = workspace_root().join(relative_path); + let source = std::fs::read_to_string(&path) + .unwrap_or_else(|e| panic!("failed to read {}: {}", path.display(), e)); + let tokens = v1_compiler::v1_compiler_tokenize::tokenize( + source.clone(), + relative_path.to_string(), + v1_compiler::extdeps_languages_dag_syntax::dag_parse_environment(), + ); + let source_index = + v1_compiler::v1_std_core::build_newline_index(relative_path.to_string(), source.clone()); + let mut source_indices = HashMap::new(); + source_indices.insert(relative_path.to_string(), source_index); + let result: Rc = + v1_compiler::v1_compiler_parse::parse(tokens, Rc::new(source_indices)); + match result.error.clone() { + Some(err) => { + let s = diagnostic_to_span(err.diagnostic.clone()); + let line = source[..(s.start.max(0) as usize).min(source.len())] + .chars() + .filter(|c| *c == '\n') + .count() + + 1; + Err(format!( + "{}:{} parse error: {}", + relative_path, + line, + diagnostic_to_message(err.diagnostic.clone()) + )) + } + None => Ok(()), + } +} + +#[test] +fn probe_parses_argv_word_construction_files() { + let files = [ + "src/v2/lens/argv_word_construction.dag", + "dag/gunbc/instruments/argv_word_construction_census.dag", + "src/v2/test/claim/argv_word_construction/census_test.dag", + "test/fixture_roots/argv_word_construction/host_words.dag", + ]; + let mut failures = Vec::new(); + for f in files { + match parse_path(f) { + Ok(()) => println!("PARSE-OK {}", f), + Err(e) => { + println!("PARSE-FAIL {}", e); + failures.push(e); + } + } + } + assert!( + failures.is_empty(), + "{} of {} files failed", + failures.len(), + files.len() + ); +} diff --git a/src/v2/lens/argv_word_construction.dag b/src/v2/lens/argv_word_construction.dag new file mode 100644 index 00000000000..ca96478130e --- /dev/null +++ b/src/v2/lens/argv_word_construction.dag @@ -0,0 +1,827 @@ +module v2.lens.argv_word_construction + +import extdeps.filesystem.filesystem_io { Filesystem, filesystem_read_outcome, FilesystemReadSucceeded, FilesystemReadRefused } +import v2.compiler.parse { grammar_validate_for_parse, parse_production } +import v2.compiler.tokenize { tokenize } +import v2.extdeps.languages.dag { dag_language_model } +import v2.lens.common.source_analysis_standing { + LanguageGrammarRejected, + LanguageGrammarUnavailable, + SourceAnalysisNotEstablishedCause, + SourceNormalizationRejected, + SourceParseRejected, + SourceTokenizationRejected, + SourceUnreadable +} +import v2.lens.common.construction_justification { ConstructionJustification, WallAfterGrounding } +import std.disposition { Disposition, Scaffold } +import std.dissolution { DissolutionCondition, unbound_dissolution } +import v2.std.node { Node, Atom, TypeNode, symbol_lexeme } +import v2.std.diagnostic { Accepted, Rejected } +import v2.std.algebra { contains, fold_list, length, list_reverse } +import v2.std.integer { Int, integer_int_to_decimal_string } +import v2.std.logic { Bool } +import v2.std.collection { List } +import std.algebra { Cons, Empty, FreeMonoid } +import std.process { ExitSuccess, ProcessExit, exit_failure } +import std.types { NonEmptyStr } +import v2.std.optional { Absent, Optional, Present } + +// THE TYPED-ARGV CONSTRUCTION CENSUS, AT IDENTITY GRAIN (debt paydown trial, brief of +// lively-ram-153 to witty-tern-54, 2026-10-03; grain widened by the same parent in reply: +// data-decl word lists are the same debt and are their own class in the same roster). +// +// One layer below the shell-to-dag census (gunbc.doc_graph_roots slug +// shell-to-dag-residual-census-and-arc-completion), which sees only shell-TEXT sites. This lens +// sees the layer beneath: command lines hand-assembled from string literals inside pure builders +// and word-list declarations, where `extdeps.exec.command`'s `ArgvCommand` (sole constructor, +// admitted builders only) is the typed form the corpus already owns. +// +// IT READS THE RAW PARSE TREE, NEVER THE NORMALIZED ONE — the same standing as +// v2.lens.text_string_importer_census (whose walk mechanics this lens reuses rather than mints a +// parallel seam; DESIGN 3b). The walk is a pure fold over the pre-order atom stream of +// `v2.extdeps.languages.dag`'s own grammar: no host builtin was added, no normalized artifact is +// consulted, and nothing here can change what the corpus compiles to. +// +// POPULATION, at identity grain (module_file, decl, decl_kind): +// (a) builds argv words from string-literal flags: a declaration whose body/value spells a +// string literal whose content starts with "-" (a flag word: "--unit=", "--wait", "--", +// "-"...); +// (b) returns or accepts List argv where ArgvCommand exists: a fn whose declared return +// spells List, or that accepts a List parameter in an argv position (the +// parameter's name is in argv_position_names, reviewed against the committed roster); +// and, for data declarations only, the List word lists that ARE argv vocabulary (flag +// words among their literal elements, or an argv-named flagless list) — classified as their +// own kind in the same roster; a List data decl is a ceiling, not a member. +// +// SITE CLASS: modules under dag/extdeps are extdeps interface builders; modules under dag/gunbc +// are workflow call sites. Both classes carry the same debt; only the paying shapes differ. +// +// THE ROSTER THAT MAY ONLY SHRINK is committed in dag.gunbc.instruments.argv_word_construction_census +// and joined through argv_word_construction_gates below: a row whose site no longer resolves +// refuses the census (pay the debt, delete the row, same change); a live member without a row +// refuses the census (new string-built argv is refused — adopt the typed form; DESIGN 5: the +// instrument joins at identity grain, a count is not an oracle). + +data argv_word_construction_justification: ConstructionJustification = ConstructionJustification { + class: WallAfterGrounding { dissolves_to: SingleAuthority } +} + +data argv_word_construction_batch_scaffold: Disposition = Scaffold { + dissolves_to: SingleAuthority, + bind: decl_ref(module_path: "v2.lens.argv_word_construction" as NonEmptyStr, decl_name: "argv_word_construction_batch_scaffold" as NonEmptyStr) +} + +data argv_word_construction_dissolution: DissolutionCondition = unbound_dissolution(description: "the typed-argv roster over dag/extdeps and dag/gunbc is empty for a full recount AND every extdeps interface builder returns extdeps.exec.command ArgvCommand" as NonEmptyStr) + +// ------------------------------------------------------------------ facts + +// Which declaration kind a fact was read under. +type ArgvWordsDeclKind + = FnDecl + | DataDecl + +// A string-literal flag word spelled under a declaration, attributed at flush time. +type FlagWord { + decl: String + decl_kind: ArgvWordsDeclKind + word: String +} + +// A parameter whose spelled type was read, with the declaration it belongs to. +type ParamType { + decl: String + param: String + spelling: String +} + +// A declared fn return, read at the atom that opens the body (the arrow's spelling flushes there). +type ReturnHead { + decl: String + spelling: String +} + +// Everything the walk read about one declaration, before classification. +type ArgvWordsFact { + decl: String + decl_kind: ArgvWordsDeclKind + flag_words: List + return_spelling: String + argv_params: List + list_string_data: Bool +} + +// A census member at identity grain: one row per (module_file, decl, decl_kind). +type ArgvWordsSite { + module_file: String + decl: String + decl_kind: ArgvWordsDeclKind + site_class: ArgvWordsSiteClass + flag_words: List + returns_argv_words: Bool + argv_word_params: List + list_string_data: Bool +} + +type ArgvWordsSiteClass + = ExtdepsInterfaceBuilder + | WorkflowCallSite + +type ArgvWordsSites + = ArgvWordsSitesEstablished { sites: List } + | ArgvWordsSitesRefused { reason: String } + +// ------------------------------------------------------------------ the walk + +// A pending type spelling: a parameter's type, a declared return (after an arrow), or a +// declaration annotation. Generic arguments are carried while the window is open so that +// List spells in full (the text-string census walks spell heads only; the argv census +// must tell List from List). +type SpellingKind + = SpellingNone + | SpellingOfParameter + | SpellingOfArrow + | SpellingOfAnnotation + +type Walk { + w1: String + w2: String + w3: String + w4: String + w5: String + decl: String + pending_decl_name: Bool + pending_decl_is_fn: Bool + decl_is_fn: Bool + pending_literal: String + flag_words: FreeMonoid + pending_param: String + pending_kind: SpellingKind + pending_segments: FreeMonoid + generic_depth: Int + generic_tokens: FreeMonoid + param_types: FreeMonoid + arrow_spelling: String + return_spelling: String + return_heads: FreeMonoid + annotation_spelling: String + data_list_string: FreeMonoid +} + +fn walk_start() -> Walk { + Walk { + w1: "", + w2: "", + w3: "", + w4: "", + w5: "", + decl: "", + pending_decl_name: false, + pending_decl_is_fn: false, + decl_is_fn: false, + pending_literal: "", + flag_words: Empty, + pending_param: "", + pending_kind: SpellingNone, + pending_segments: Empty, + generic_depth: 0, + generic_tokens: Empty, + param_types: Empty, + arrow_spelling: "", + return_spelling: "", + return_heads: Empty, + annotation_spelling: "", + data_list_string: Empty + } +} + +fn str_eq_fn(a: String, b: String) -> Bool { + a == b +} + +fn member(xs: FreeMonoid, x: String) -> Bool { + contains(xs: xs, item: x, eq: str_eq_fn) +} + +fn is_production_or_token(x: String) -> Bool { + starts_with(s: x, prefix: "dag_") +} + +fn is_identifier(x: String) -> Bool { + !is_production_or_token(x: x) && !(x == "") +} + +fn is_decl_production(x: String) -> Bool { + (x == "dag_surface_fn_decl") + || (x == "dag_surface_test_fn_decl") + || (x == "dag_surface_data_decl") + || (x == "dag_surface_type_decl") + || (x == "dag_surface_alias_decl") + || (x == "dag_surface_port_decl") + || (x == "dag_surface_invariant_decl") + || (x == "dag_surface_subject_decl") + || (x == "dag_surface_grammar_decl") + || (x == "dag_surface_rule_decl") +} + +fn is_fn_decl_production(x: String) -> Bool { + (x == "dag_surface_fn_decl") || (x == "dag_surface_test_fn_decl") +} + +fn is_string_literal_token(x: String) -> Bool { + x == "dag_token_string_literal" +} + +fn is_type_head(w: Walk, x: String) -> Bool { + is_identifier(x: x) && (w.w1 == "dag_surface_qualified_name") && (w.w2 == "dag_surface_type_expr") +} + +fn is_param_type_head(w: Walk, x: String) -> Bool { + is_type_head(w: w, x: x) && (w.w3 == "dag_token_colon") && ((w.w5 == "dag_token_lparen") || (w.w5 == "dag_token_comma") || (w.w5 == "dag_surface_param_list")) +} + +fn is_annotation_type_head(w: Walk, x: String) -> Bool { + is_type_head(w: w, x: x) && (w.w3 == "dag_token_colon") && ((w.w5 == "dag_token_kw_let") || (w.w5 == "dag_token_kw_data")) +} + +fn is_arrow_type_head(w: Walk, x: String) -> Bool { + is_type_head(w: w, x: x) && (w.w3 == "dag_token_arrow") +} + +fn next_decl(w: Walk, x: String) -> String { + if x == "dag_surface_top_level_item" { "" } else if w.pending_decl_name && is_identifier(x: x) { x } else { w.decl } +} + +fn next_pending_decl_name(w: Walk, x: String) -> Bool { + if x == "dag_surface_top_level_item" { false } else if is_decl_production(x: w.w1) { true } else if is_identifier(x: x) { false } else { w.pending_decl_name } +} + +fn next_pending_decl_is_fn(w: Walk, x: String) -> Bool { + if x == "dag_surface_top_level_item" { false } else if is_fn_decl_production(x: x) { true } else if is_decl_production(x: x) { false } else { w.pending_decl_is_fn } +} + +fn next_decl_is_fn(w: Walk, x: String) -> Bool { + if x == "dag_surface_top_level_item" { false } else if w.pending_decl_name && is_identifier(x: x) { w.pending_decl_is_fn } else { w.decl_is_fn } +} + +fn decl_kind_of(is_fn: Bool) -> ArgvWordsDeclKind { + match is_fn { + true => FnDecl + false => DataDecl + } +} + +// A string literal's CONTENT atom arrives directly after the dag_token_string_literal token; it is +// the one non-production atom the walk must not read as an identifier. It parks in pending_literal +// and flushes at the next atom, attributed to the declaration open at flush time. +fn next_pending_literal(w: Walk, x: String) -> String { + if is_string_literal_token(x: x) { "" } else if !(w.pending_literal == "") { w.pending_literal } else { "" } +} + +fn parks_literal(w: Walk, x: String) -> Bool { + is_string_literal_token(x: w.w1) && !is_production_or_token(x: x) +} + +fn next_flag_words(w: Walk, x: String) -> FreeMonoid { + let flushed = if (w.pending_literal == "") || x == "" { Empty } else if starts_with(s: w.pending_literal, prefix: "-") { + Cons { head: FlagWord { decl: w.decl, decl_kind: decl_kind_of(is_fn: w.decl_is_fn), word: w.pending_literal }, tail: Empty } + } else { Empty } + if x == "" { w.flag_words } else { fold_list(xs: flushed, empty: w.flag_words, cons: fn(acc, f) { Cons { head: f, tail: acc } }) } +} + +fn starts_pending_spelling(w: Walk, x: String) -> Bool { + is_param_type_head(w: w, x: x) || is_annotation_type_head(w: w, x: x) || is_arrow_type_head(w: w, x: x) +} + +// A pending spelling continues through a dotted name and, while a generic window is open, through +// the identifiers, commas and the closing angle bracket of its argument list. +fn continues_pending(w: Walk, x: String) -> Bool { + if w.pending_param == "" { + false + } else if x == "dag_token_dot" { + true + } else if x == "dag_token_lt" { + true + } else if (w.generic_depth > 0) && ((x == "dag_token_comma") || (x == "dag_token_gt") || is_identifier(x: x)) { + true + } else { + is_identifier(x: x) && (w.w1 == "dag_token_dot") + } +} + +fn flushes_pending(w: Walk, x: String) -> Bool { + !(w.pending_param == "") && !continues_pending(w: w, x: x) +} + +fn next_pending_param(w: Walk, x: String) -> String { + if is_arrow_type_head(w: w, x: x) { "->" } else if starts_pending_spelling(w: w, x: x) { w.w4 } else if continues_pending(w: w, x: x) { w.pending_param } else { "" } +} + +fn next_pending_kind(w: Walk, x: String) -> SpellingKind { + if is_param_type_head(w: w, x: x) { SpellingOfParameter } else if is_annotation_type_head(w: w, x: x) { SpellingOfAnnotation } else if is_arrow_type_head(w: w, x: x) { SpellingOfArrow } else { w.pending_kind } +} + +fn next_pending_segments(w: Walk, x: String) -> FreeMonoid { + if starts_pending_spelling(w: w, x: x) { + Cons { head: x, tail: Empty } + } else if continues_pending(w: w, x: x) && (x == "dag_token_lt") { + w.pending_segments + } else if continues_pending(w: w, x: x) && is_identifier(x: x) && (w.generic_depth > 0) { + w.pending_segments + } else if continues_pending(w: w, x: x) && is_identifier(x: x) { + Cons { head: x, tail: w.pending_segments } + } else if continues_pending(w: w, x: x) { + w.pending_segments + } else { + Empty + } +} + +fn next_generic_depth(w: Walk, x: String) -> Int { + if !(w.pending_param == "") && (x == "dag_token_lt") { + 1 + } else if (w.generic_depth > 0) && (x == "dag_token_gt") { + 0 + } else { + w.generic_depth + } +} + +fn next_generic_tokens(w: Walk, x: String) -> FreeMonoid { + if (w.generic_depth > 0) && ((x == "dag_token_comma") || is_identifier(x: x)) { + Cons { head: x, tail: w.generic_tokens } + } else if !(w.pending_param == "") && (x == "dag_token_lt") { + Empty + } else { + w.generic_tokens + } +} + +fn dotted(segments_reversed: FreeMonoid) -> String { + fold_list(xs: list_reverse(xs: segments_reversed), empty: "", cons: fn(acc, s) { if acc == "" { s } else { concat(concat(acc, "."), s) } }) +} + +fn generic_spelling(tokens_reversed: FreeMonoid) -> String { + fold_list(xs: list_reverse(xs: tokens_reversed), empty: "", cons: fn(acc, t) { if acc == "" { t } else { concat(concat(acc, ","), t) } }) +} + +fn flushed_spelling(w: Walk) -> String { + let head = dotted(segments_reversed: w.pending_segments) + if w.generic_depth > 0 { + concat(concat(head, "<"), concat(generic_spelling(tokens_reversed: w.generic_tokens), ">")) + } else { + head + } +} + +fn next_param_types(w: Walk, x: String) -> FreeMonoid { + match w.pending_kind { + SpellingOfParameter => + if flushes_pending(w: w, x: x) { Cons { head: ParamType { decl: w.decl, param: w.pending_param, spelling: flushed_spelling(w: w) }, tail: w.param_types } } else { w.param_types } + _ => w.param_types + } +} + +fn next_arrow_spelling(w: Walk, x: String) -> String { + match w.pending_kind { + SpellingOfArrow => if flushes_pending(w: w, x: x) { flushed_spelling(w: w) } else { w.arrow_spelling } + _ => w.arrow_spelling + } +} + +fn next_annotation_spelling(w: Walk, x: String) -> String { + match w.pending_kind { + SpellingOfAnnotation => if flushes_pending(w: w, x: x) { flushed_spelling(w: w) } else { w.annotation_spelling } + _ => w.annotation_spelling + } +} + +// The declared return of the fn whose body is open: the arrow's spelling flushes on the very atom +// that opens the body, so it is read as of this atom. +fn next_return_heads(w: Walk, x: String) -> FreeMonoid { + if (x == "dag_surface_fn_body") && (w.return_spelling == "") { + Cons { head: ReturnHead { decl: w.decl, spelling: next_arrow_spelling(w: w, x: x) }, tail: w.return_heads } + } else { + w.return_heads + } +} + +fn next_return_spelling(w: Walk, x: String) -> String { + if x == "dag_surface_top_level_item" { "" } else if (x == "dag_surface_fn_body") && (w.return_spelling == "") { next_arrow_spelling(w: w, x: x) } else { w.return_spelling } +} + +// A List DATA DECLARATION: the annotation's spelling flushes on the equals sign that opens +// its value. +fn next_data_list_string(w: Walk, x: String) -> FreeMonoid { + if (x == "dag_token_eq") && !(w.decl == "") && (flushed_spelling(w: w) == "List") { + Cons { head: w.decl, tail: w.data_list_string } + } else { + w.data_list_string + } +} + +fn walk_step(w: Walk, x: String) -> Walk { + Walk { + w1: x, + w2: w.w1, + w3: w.w2, + w4: w.w3, + w5: w.w4, + decl: next_decl(w: w, x: x), + pending_decl_name: next_pending_decl_name(w: w, x: x), + pending_decl_is_fn: next_pending_decl_is_fn(w: w, x: x), + decl_is_fn: next_decl_is_fn(w: w, x: x), + pending_literal: if parks_literal(w: w, x: x) { x } else { next_pending_literal(w: w, x: x) }, + flag_words: if parks_literal(w: w, x: x) { next_flag_words(w: w, x: "") } else { next_flag_words(w: w, x: x) }, + pending_param: next_pending_param(w: w, x: x), + pending_kind: next_pending_kind(w: w, x: x), + pending_segments: next_pending_segments(w: w, x: x), + generic_depth: next_generic_depth(w: w, x: x), + generic_tokens: next_generic_tokens(w: w, x: x), + param_types: next_param_types(w: w, x: x), + arrow_spelling: next_arrow_spelling(w: w, x: x), + return_spelling: next_return_spelling(w: w, x: x), + return_heads: next_return_heads(w: w, x: x), + annotation_spelling: next_annotation_spelling(w: w, x: x), + data_list_string: next_data_list_string(w: w, x: x) + } +} + +// The end of the stream closes the last declaration: the parked literal, if any, flushes. +fn finish_walk(w: Walk) -> Walk { + walk_step(w: walk_step(w: w, x: "dag_surface_end_of_stream"), x: "dag_surface_top_level_item") +} + +fn walk_positioned(atoms: FreeMonoid) -> Walk { + finish_walk(w: fold_list(xs: atoms, empty: walk_start(), cons: fn(w, a) { walk_step(w: w, x: a.lexeme) })) +} + +fn walk_atoms(atoms: FreeMonoid) -> Walk { + walk_positioned(atoms: fold_list(xs: list_reverse(xs: atoms), empty: Empty, cons: fn(acc, x) { Cons { head: PositionedAtom { lexeme: x }, tail: acc } })) +} + +type PositionedAtom { + lexeme: String +} + +// The pre-order atom stream, built by prepending and reversed once (linear). +fn atoms_reversed(n: Node, acc: FreeMonoid) -> FreeMonoid { + let here = match n.kind { + TypeNode { connective: c } => match c { + Atom { identity: i } => Cons { head: PositionedAtom { lexeme: symbol_lexeme(sym: i) }, tail: acc } + _ => acc + } + _ => acc + } + fold(n.children, init: here, f: fn(a, e) { atoms_reversed(n: e.target, acc: a) }) +} + +fn walk_tree(root: Node) -> Walk { + walk_positioned(atoms: list_reverse(xs: atoms_reversed(n: root, acc: Empty))) +} + +// ------------------------------------------------------------------ facts of a walked module + +fn flag_words_of(walked: Walk, decl: String, kind: ArgvWordsDeclKind) -> List { + let reversed = fold_list(xs: walked.flag_words, empty: Empty, cons: fn(acc, f) { + if (f.decl == decl) && decl_kind_matches(a: f.decl_kind, b: kind) { Cons { head: f.word, tail: acc } } else { acc } + }) + fold_list(xs: list_reverse(xs: reversed), empty: Empty, cons: fn(acc, w) { if contains(xs: acc, item: w, eq: str_eq_fn) { acc } else { Cons { head: w, tail: acc } } }) +} + +fn decl_kind_matches(a: ArgvWordsDeclKind, b: ArgvWordsDeclKind) -> Bool { + match a { + FnDecl => match b { FnDecl => true, DataDecl => false } + DataDecl => match b { FnDecl => false, DataDecl => true } + } +} + +fn argv_params_of(walked: Walk, decl: String) -> List { + let reversed = fold_list(xs: walked.param_types, empty: Empty, cons: fn(acc, p) { + if (p.decl == decl) && (p.spelling == "List") && member(xs: argv_position_name_rows, x: p.param) { Cons { head: p.param, tail: acc } } else { acc } + }) + fold_list(xs: list_reverse(xs: reversed), empty: Empty, cons: fn(acc, p) { if contains(xs: acc, item: p, eq: str_eq_fn) { acc } else { Cons { head: p, tail: acc } } }) +} + +fn return_spelling_of(walked: Walk, decl: String) -> String { + fold_list(xs: walked.return_heads, empty: "", cons: fn(acc, r) { if r.decl == decl { r.spelling } else { acc } }) +} + +fn is_list_string_data(walked: Walk, decl: String) -> Bool { + member(xs: walked.data_list_string, x: decl) +} + +fn distinct_decls(xs: FreeMonoid) -> List { + let reversed = fold_list(xs: xs, empty: Empty, cons: fn(acc, d) { if member(xs: acc, x: d) { acc } else { Cons { head: d, tail: acc } } }) + list_to_string_list(reversed) +} + +fn list_to_string_list(m: FreeMonoid) -> List { + fold_list(xs: list_reverse(xs: m), empty: Empty, cons: fn(acc, s) { Cons { head: s, tail: acc } }) +} + +// The argv positions a List parameter is read at: the names reviewed against the committed +// roster at baseline. A parameter outside these names is not argv (a layout, a row of labels) and +// is not a census member by itself. A List spells as a FreeMonoid, so the rows are the set. +data argv_position_name_rows: List = ["argv", "command_argv", "command", "command_words", "args", "arguments", "words", "property_argv", "wait_argv", "unit_argv", "extra_argv", "flag_words"] + +fn argv_named_data_decl(decl: String) -> Bool { + ends_with(s: decl, suffix: "_args") || ends_with(s: decl, suffix: "_argv") || ends_with(s: decl, suffix: "_words") +} + +fn facts_of(walked: Walk) -> List { + let fn_decls = distinct_decls(fold_list(xs: walked.return_heads, empty: Empty, cons: fn(acc, r) { Cons { head: r.decl, tail: acc } })) + let fns = fold_list(xs: fn_decls, empty: Empty, cons: fn(acc, d) { + Cons { + head: ArgvWordsFact { + decl: d, + decl_kind: FnDecl, + flag_words: flag_words_of(walked: walked, decl: d, kind: FnDecl), + return_spelling: return_spelling_of(walked: walked, decl: d), + argv_params: argv_params_of(walked: walked, decl: d), + list_string_data: false + }, + tail: acc + } + }) + let data_decls = distinct_decls(walked.data_list_string) + fold_list(xs: data_decls, empty: fns, cons: fn(acc, d) { + Cons { + head: ArgvWordsFact { + decl: d, + decl_kind: DataDecl, + flag_words: flag_words_of(walked: walked, decl: d, kind: DataDecl), + return_spelling: "", + argv_params: Empty, + list_string_data: true + }, + tail: acc + } + }) +} + +// ------------------------------------------------------------------ sites + +fn is_census_member(f: ArgvWordsFact) -> Bool { + match f.decl_kind { + FnDecl => + !(length(xs: f.flag_words) == 0) + || (f.return_spelling == "List") + || !(length(xs: f.argv_params) == 0) + DataDecl => + !(length(xs: f.flag_words) == 0) + || argv_named_data_decl(decl: f.decl) + } +} + +fn site_class_of(module_file: String) -> Optional { + match starts_with(s: module_file, prefix: "dag/extdeps/") { + true => Present { value: ExtdepsInterfaceBuilder } + false => match starts_with(s: module_file, prefix: "dag/gunbc/") { + true => Present { value: WorkflowCallSite } + false => Absent + } + } +} + +fn site_class_name(c: ArgvWordsSiteClass) -> String { + match c { + ExtdepsInterfaceBuilder => "extdeps_interface_builder" + WorkflowCallSite => "workflow_call_site" + } +} + +fn decl_kind_name(k: ArgvWordsDeclKind) -> String { + match k { + FnDecl => "fn" + DataDecl => "data" + } +} + +// One census row per member declaration of one module. A module outside the two modeled roots is a +// REFUSAL, never a silent zero: the census counts dag/extdeps and dag/gunbc and says so. +fn argv_word_construction_sites_of_facts(module_file: String, facts: List) -> ArgvWordsSites { + match site_class_of(module_file: module_file) { + Absent => ArgvWordsSitesRefused { reason: concat("module outside the modeled roots dag/extdeps and dag/gunbc: ", module_file) } + Present { value: sc } => + ArgvWordsSitesEstablished { + sites: fold_list(xs: facts, empty: Empty, cons: fn(acc, f) { + if is_census_member(f: f) { + Cons { + head: ArgvWordsSite { + module_file: module_file, + decl: f.decl, + decl_kind: f.decl_kind, + site_class: sc, + flag_words: f.flag_words, + returns_argv_words: f.return_spelling == "List", + argv_word_params: f.argv_params, + list_string_data: f.list_string_data + }, + tail: acc + } + } else { + acc + } + }) + } + } +} + +// ------------------------------------------------------------------ receipt rows + +fn escape_char(s: String, delimiter: String, glue: String) -> String { + fold_list(xs: split(s: s, delimiter: delimiter), empty: "", cons: fn(acc, part) { if acc == "" { part } else { concat(concat(acc, glue), part) } }) +} + +// Percent-escape the row separators a field could carry, in the standard order: percent first, so +// the escapes themselves are never re-escaped. +fn escape_field(s: String) -> String { + escape_char(s: escape_char(s: escape_char(s: escape_char(s: s, delimiter: "%", glue: "%25"), delimiter: ",", glue: "%2C"), delimiter: ";", glue: "%3B"), delimiter: "\n", glue: "%0A") +} + +fn join_words(ws: List) -> String { + fold_list(xs: ws, empty: "", cons: fn(acc, w) { if acc == "" { escape_field(s: w) } else { concat(concat(acc, ";"), escape_field(s: w)) } }) +} + +fn bool_word(b: Bool) -> String { + match b { + true => "T" + false => "F" + } +} + +// The site at one identity, if the module's census read produced one. +fn argv_word_construction_site_named(sites: List, module_file: String, decl: String) -> Optional { + fold_list(xs: sites, empty: Absent, cons: fn(acc, s) { + match acc { + Absent => match (s.module_file == module_file) && (s.decl == decl) { + true => Present { value: s } + false => Absent + } + Present { value: v } => Present { value: v } + } + }) +} + +fn site_line(s: ArgvWordsSite) -> String { + let file = concat(escape_field(s: s.module_file), ",") + let decl = concat(escape_field(s: s.decl), ",") + let kind = concat(decl_kind_name(k: s.decl_kind), ",") + let class = concat(site_class_name(c: s.site_class), ",") + let flags = concat(join_words(ws: s.flag_words), ",") + let ret = concat(bool_word(b: s.returns_argv_words), ",") + let params = concat(join_words(ws: s.argv_word_params), ",") + concat(concat(concat(concat(concat(concat(concat(file, decl), kind), class), flags), ret), params), concat(bool_word(b: s.list_string_data), "\n")) +} + +fn sites_text(sites: List) -> String { + fold_list(xs: sites, empty: "", cons: fn(acc, s) { concat(acc, site_line(s: s)) }) +} + +// ------------------------------------------------------------------ the raw parse of one module + +type RawTreeStanding + = RawTreeEstablished { root: Node } + | RawTreeNotEstablished { cause: SourceAnalysisNotEstablishedCause } + +// The raw parse, without normalize: the same tokenize/validate/parse chain as +// v2.lens.text_string_importer_census's raw_tree_of_text. +fn raw_tree_of_text(text: String) -> RawTreeStanding { + let lm = dag_language_model() + let at = invariant_port_locus(port: ^argv_word_construction_port, invariant: ^argv_word_construction_source_invariant) + match lm.grammar { + VoidGrammar => RawTreeNotEstablished { cause: LanguageGrammarUnavailable } + ModeledGrammar { root: root } => + match grammar_validate_for_parse(root: root, at: at) { + Rejected { diagnostics: _ } => RawTreeNotEstablished { cause: LanguageGrammarRejected } + Accepted { value: validated, diagnostics: _ } => + match tokenize(text: text, file: ^argv_word_construction_subject, rules: lm.lex) { + Rejected { diagnostics: _ } => RawTreeNotEstablished { cause: SourceTokenizationRejected } + Accepted { value: stream, diagnostics: _ } => + match parse_production(tokens: token_stream_remaining(stream: stream), validated: validated, production_name: validated.root.start) { + Rejected { diagnostics: _ } => RawTreeNotEstablished { cause: SourceParseRejected } + Accepted { value: artifact, diagnostics: _ } => RawTreeEstablished { root: artifact.tree } + } + } + } + } +} + +// ------------------------------------------------------------------ module entry + +type ArgvWordsModuleStanding + = ArgvWordsModuleEstablished { path: String, sites: List } + | ArgvWordsModuleRefused { path: String, reason: String } + +// The census read of ONE module file, end to end: read, raw parse, walk, facts, sites. The read +// folds through filesystem_read_outcome (the module's modeled read fold), so refusal and content +// cannot be conflated at this site. +fn argv_word_construction_module_sites(path: String) -> ArgvWordsModuleStanding { + let read = Filesystem.Read(path: path) + match filesystem_read_outcome(content: read.content, success: read.success, error: read.error) { + FilesystemReadRefused { error: e } => ArgvWordsModuleRefused { path: path, reason: concat("read refused: ", e) } + FilesystemReadSucceeded { content: text } => + match raw_tree_of_text(text: text) { + RawTreeNotEstablished { cause: c } => ArgvWordsModuleRefused { path: path, reason: concat("raw parse not established: ", source_cause_name(c: c)) } + RawTreeEstablished { root: root } => + match argv_word_construction_sites_of_facts(module_file: path, facts: facts_of(walked: walk_tree(root: root))) { + ArgvWordsSitesRefused { reason: r } => ArgvWordsModuleRefused { path: path, reason: r } + ArgvWordsSitesEstablished { sites: sites } => ArgvWordsModuleEstablished { path: path, sites: sites } + } + } + } +} + +fn source_cause_name(c: SourceAnalysisNotEstablishedCause) -> String { + match c { + SourceUnreadable => "source unreadable" + LanguageGrammarUnavailable => "language grammar unavailable" + LanguageGrammarRejected => "language grammar rejected" + SourceNormalizationRejected => "source normalization rejected" + SourceTokenizationRejected => "source tokenization rejected" + SourceParseRejected => "source parse rejected" + } +} + +// Phase 2, batched: the census rows of the given module files, appended to `out`, one row per +// member declaration. Run from outside the repository over the discovered paths; CI's verifier +// lane owns the corpus-scale recount, the session runner never runs it locally. +fn argv_word_construction_for_paths(paths: List, out: String) -> ProcessExit { + let folded = fold_list(xs: paths, empty: Empty, cons: fn(acc, p) { + match argv_word_construction_module_sites(path: p) { + ArgvWordsModuleRefused { path: rp, reason: r } => Cons { head: concat(concat(concat("REFUSED\t", rp), "\t"), r), tail: acc } + ArgvWordsModuleEstablished { path: _, sites: sites } => fold_list(xs: sites, empty: acc, cons: fn(acc2, s) { Cons { head: site_line(s: s), tail: acc2 } }) + } + }) + let written = Filesystem.Write(path: out, content: joined(lines_reversed: folded)) + if written.success { ExitSuccess } else { exit_failure(reason: concat("census write refused: ", written.error)) } +} + +fn joined(lines_reversed: FreeMonoid) -> String { + fold_list(xs: list_reverse(xs: lines_reversed), empty: "", cons: fn(acc, l) { concat(acc, l) }) +} + +// ------------------------------------------------------------------ the roster gates + +// A committed roster row: one census member, at identity grain, with its classification and the +// disposition note that names what pays it. The roster may only shrink: rows resolve against live +// sites, live members must be covered, and the population never grows past the committed baseline. +type ArgvWordsRosterRow { + module_file: String + decl: String + decl_kind: ArgvWordsDeclKind + site_class: ArgvWordsSiteClass + note: String +} + +fn row_matches_site(r: ArgvWordsRosterRow, s: ArgvWordsSite) -> Bool { + (r.module_file == s.module_file) && (r.decl == s.decl) && decl_kind_matches(a: r.decl_kind, b: s.decl_kind) +} + +// Every row must resolve to a live census member: a row whose site was paid without deleting the +// row refuses, naming the stale identity (DESIGN 5: join at identity grain, never at count). +fn argv_word_construction_rows_resolve(live: List, roster: List) -> ProcessExit { + match fold_list(xs: roster, empty: Empty, cons: fn(acc, r) { + let resolves = fold_list(xs: live, empty: false, cons: fn(acc2, s) { acc2 || row_matches_site(r: r, s: s) }) + if resolves { acc } else { Cons { head: concat(concat(concat(concat("stale row: ", r.module_file), " "), r.decl), concat(" (", concat(decl_kind_name(k: r.decl_kind), ") was paid without deleting its roster row"))), tail: acc } } + }) { + Empty => ExitSuccess + nonempty => exit_failure(reason: joined(lines_reversed: nonempty)) + } +} + +// Every live census member must be a roster row: new string-built argv is refused until it adopts +// the typed form. Enrolling it is not an option while the shrink gate holds. +fn argv_word_construction_census_covers(live: List, roster: List) -> ProcessExit { + match fold_list(xs: live, empty: Empty, cons: fn(acc, s) { + let covered = fold_list(xs: roster, empty: false, cons: fn(acc2, r) { acc2 || row_matches_site(r: r, s: s) }) + if covered { acc } else { Cons { head: concat(concat(concat(concat("uncovered member: ", s.module_file), " "), s.decl), concat(" (", concat(decl_kind_name(k: s.decl_kind), ") builds argv words without a typed form and without a roster row"))), tail: acc } } + }) { + Empty => ExitSuccess + nonempty => exit_failure(reason: joined(lines_reversed: nonempty)) + } +} + +// The roster may only shrink: its population never grows past the committed baseline cardinality. +fn argv_word_construction_roster_shrinks(baseline_population: Int, roster: List) -> ProcessExit { + let n = length(xs: roster) + if n <= baseline_population { + ExitSuccess + } else { + exit_failure(reason: concat(concat("the typed-argv roster grew to ", integer_int_to_decimal_string(value: n)), concat(" rows past the committed baseline of ", concat(integer_int_to_decimal_string(value: baseline_population), "; pay string-built argv, do not enroll it")))) + } +} + +// The whole contract over one recount: rows resolve, members are covered, the roster has not +// grown. ExitSuccess is the verdict "within the contract"; a refusal names the offending rows. +fn argv_word_construction_gates(live: List, roster: List, baseline_population: Int) -> ProcessExit { + let resolved = argv_word_construction_rows_resolve(live: live, roster: roster) + match resolved { + ExitSuccess => match argv_word_construction_census_covers(live: live, roster: roster) { + ExitSuccess => argv_word_construction_roster_shrinks(baseline_population: baseline_population, roster: roster) + refused => refused + } + refused => refused + } +} diff --git a/src/v2/test/claim/argv_word_construction/census_test.dag b/src/v2/test/claim/argv_word_construction/census_test.dag new file mode 100644 index 00000000000..b4900ecb6ee --- /dev/null +++ b/src/v2/test/claim/argv_word_construction/census_test.dag @@ -0,0 +1,226 @@ +module v2.test.argv_word_construction.census + +// THE TYPED-ARGV CONSTRUCTION CENSUS WITNESS (debt paydown trial, witty-tern-54, 2026-10-03). +// +// The instrument is v2.lens.argv_word_construction over the roots dag/extdeps and dag/gunbc; the +// committed state is gunbc.instruments.argv_word_construction_census. These tests hold the lens's +// VERDICTS at the identity grain the roster joins at: +// - the real-parse route over the committed fixture module (every census signal, once); +// - the real-parse route over the trial's specimen, pinned at its pre-cutover state; +// - the roster gates on hand-built sites (stale row refuses; uncovered member refuses; the +// roster never grows past the baseline). + +import v2.lens.argv_word_construction { + ArgvWordsRosterRow, + ArgvWordsSite, + DataDecl, + ExtdepsInterfaceBuilder, + FnDecl, + WorkflowCallSite, + ArgvWordsModuleEstablished, + ArgvWordsModuleRefused, + argv_word_construction_gates, + argv_word_construction_module_sites, + argv_word_construction_rows_resolve, + argv_word_construction_census_covers, + argv_word_construction_roster_shrinks, + argv_word_construction_site_named +} +import gunbc.instruments.argv_word_construction_census { + argv_word_construction_census_live, + argv_word_construction_census_check_paths, + argv_word_construction_census_roster +} +import v2.std.algebra { fold_list, length } +import v2.std.logic { Bool } +import v2.std.collection { List } +import v2.std.optional { Absent, Present } +import std.process { ExitSuccess, ProcessExit, ExitFailure } + +// ------------------------------------------------------------------ fixture module route + +data fixture_path: String = "test/fixture_roots/argv_word_construction/host_words.dag" + +// The fixture is a real parse of a real file: read -> raw parse -> walk -> facts -> sites. If the +// grammar, the walk, or the classification drifts, this route goes red at the identity grain. +test fn census_fixture_route_establishes_the_member_sites() -> Bool { + match argv_word_construction_module_sites(path: fixture_path) { + ArgvWordsModuleEstablished { path: _, sites: sites } => + site_present(sites: sites, module_file: fixture_path, decl: "fixture_wait_flags") + && site_present(sites: sites, module_file: fixture_path, decl: "fixture_commit_args") + && site_present(sites: sites, module_file: fixture_path, decl: "fixture_transient_unit_argv") + && site_present(sites: sites, module_file: fixture_path, decl: "fixture_run_with_properties") + && site_absent(sites: sites, module_file: fixture_path, decl: "fixture_layout_rows") + && site_absent(sites: sites, module_file: fixture_path, decl: "fixture_label_rows") + ArgvWordsModuleRefused { path: _, reason: _ } => false + } +} + +// The flag-word data decl carries its flag words; the argv-named flagless list is a member with +// none; the ceiling List data decl is not a member at all. +test fn census_fixture_route_classifies_data_decls_at_their_own_kind() -> Bool { + match argv_word_construction_module_sites(path: fixture_path) { + ArgvWordsModuleEstablished { path: _, sites: sites } => + match argv_word_construction_site_named(sites: sites, module_file: fixture_path, decl: "fixture_wait_flags") { + Present { value: s } => (s.decl_kind == DataDecl) && !(length(xs: s.flag_words) == 0) + Absent => false + } + && match argv_word_construction_site_named(sites: sites, module_file: fixture_path, decl: "fixture_commit_args") { + Present { value: s } => (s.decl_kind == DataDecl) && (length(xs: s.flag_words) == 0) + Absent => false + } + && site_absent(sites: sites, module_file: fixture_path, decl: "fixture_layout_rows") + ArgvWordsModuleRefused { path: _, reason: _ } => false + } +} + +// The argv-parameter signal and the List return signal are read at fn grain. +test fn census_fixture_route_reads_fn_signals() -> Bool { + match argv_word_construction_module_sites(path: fixture_path) { + ArgvWordsModuleEstablished { path: _, sites: sites } => + match argv_word_construction_site_named(sites: sites, module_file: fixture_path, decl: "fixture_run_with_properties") { + Present { value: s } => (s.decl_kind == FnDecl) && (s.returns_argv_words == true) && member_named(xs: s.argv_word_params, x: "property_argv") + Absent => false + } + && match argv_word_construction_site_named(sites: sites, module_file: fixture_path, decl: "fixture_transient_unit_argv") { + Present { value: s } => (s.decl_kind == FnDecl) && (s.returns_argv_words == true) && member_named(xs: s.flag_words, x: "--unit=") + Absent => false + } + ArgvWordsModuleRefused { path: _, reason: _ } => false + } +} + +// ------------------------------------------------------------------ specimen route + +// The trial's specimen, pinned at its PRE-CUTOVER state: the string-built builders are census +// members today. When the cutover pays them (typed options, one wire projection, ArgvCommand +// builders, string forms deleted), this test flips WITH the cutover: the members must be gone and +// the roster row for each must be deleted in the same change. +data specimen_path: String = "dag/extdeps/systemd/systemd_run.dag" + +test fn census_specimen_route_pins_the_pre_cutover_string_forms() -> Bool { + match argv_word_construction_module_sites(path: specimen_path) { + ArgvWordsModuleEstablished { path: _, sites: sites } => + site_present(sites: sites, module_file: specimen_path, decl: "systemd_run_property_argv") + && site_present(sites: sites, module_file: specimen_path, decl: "systemd_run_user_transient_arguments") + && site_present(sites: sites, module_file: specimen_path, decl: "systemd_run_transient_unit_argv") + && site_present(sites: sites, module_file: specimen_path, decl: "systemd_run_scope_as_account_argv") + && site_present(sites: sites, module_file: specimen_path, decl: "systemd_run_transient_wait_unit_argv") + ArgvWordsModuleRefused { path: _, reason: _ } => false + } +} + +// The specimen is an extdeps module: its members classify as extdeps interface builders, the +// class that pays by returning ArgvCommand from the tool's own extdeps surface. +test fn census_specimen_route_classifies_extdeps_builders() -> Bool { + match argv_word_construction_module_sites(path: specimen_path) { + ArgvWordsModuleEstablished { path: _, sites: sites } => + match argv_word_construction_site_named(sites: sites, module_file: specimen_path, decl: "systemd_run_property_argv") { + Present { value: s } => match s.site_class { + ExtdepsInterfaceBuilder => true + WorkflowCallSite => false + } + Absent => false + } + ArgvWordsModuleRefused { path: _, reason: _ } => false + } +} + +// A module outside the two modeled roots is a REFUSAL, never a silent zero. +test fn census_refuses_modules_outside_the_modeled_roots() -> Bool { + match argv_word_construction_module_sites(path: "docs/plans/some-note.md") { + ArgvWordsModuleEstablished { path: _, sites: _ } => false + ArgvWordsModuleRefused { path: _, reason: _ } => true + } +} + +// ------------------------------------------------------------------ the roster gates + +data gate_file: String = "dag/gunbc/instruments/argv_word_construction_census.dag" + +fn live_member(path: String, decl: String) -> ArgvWordsSite { + ArgvWordsSite { + module_file: path, + decl: decl, + decl_kind: FnDecl, + site_class: WorkflowCallSite, + flag_words: ["--wait"], + returns_argv_words: false, + argv_word_params: Empty, + list_string_data: false + } +} + +fn roster_row(path: String, decl: String) -> ArgvWordsRosterRow { + ArgvWordsRosterRow { + module_file: path, + decl: decl, + decl_kind: FnDecl, + site_class: WorkflowCallSite, + note: "witness row" + } +} + +fn exit_is_success(exit: ProcessExit) -> Bool { + match exit { + ExitSuccess => true + ExitFailure { code: _, reason: _ } => false + } +} + +test fn gate_stale_row_refuses_the_recount() -> Bool { + let live = [live_member(path: gate_file, decl: "still_debt")] + let roster = [roster_row(path: gate_file, decl: "still_debt"), roster_row(path: gate_file, decl: "paid_but_row_kept")] + !exit_is_success(exit: argv_word_construction_rows_resolve(live: live, roster: roster)) +} + +test fn gate_uncovered_member_refuses_the_recount() -> Bool { + let live = [live_member(path: gate_file, decl: "still_debt"), live_member(path: gate_file, decl: "new_string_built_argv")] + let roster = [roster_row(path: gate_file, decl: "still_debt")] + !exit_is_success(exit: argv_word_construction_census_covers(live: live, roster: roster)) +} + +test fn gate_grown_roster_refuses_the_shrink_contract() -> Bool { + let roster = [roster_row(path: gate_file, decl: "one"), roster_row(path: gate_file, decl: "two")] + !exit_is_success(exit: argv_word_construction_roster_shrinks(baseline_population: 1, roster: roster)) +} + +test fn gate_within_contract_holds() -> Bool { + let live = [live_member(path: gate_file, decl: "still_debt")] + let roster = [roster_row(path: gate_file, decl: "still_debt")] + exit_is_success(exit: argv_word_construction_gates(live: live, roster: roster, baseline_population: 1)) +} + +// The committed census holds its own contract on the bounded recount of the fixture and the +// specimen: per-module reads establish, and the committed roster covers every live member of the +// two modules. This is the test that goes red the moment a specimen site is paid without deleting +// its roster row, or a new string-built argv lands in either module without a typed form. +test fn committed_census_holds_its_own_contract() -> Bool { + exit_is_success(exit: argv_word_construction_census_check_paths(paths: [specimen_path, fixture_path])) +} + +// The instrument's own live filter subtracts only what the committed exclusions name. +test fn instrument_live_filter_keeps_unexcluded_members() -> Bool { + let sites = [live_member(path: gate_file, decl: "still_debt")] + fold_list(xs: argv_word_construction_census_live(sites: sites), empty: true, cons: fn(acc, s) { acc && (s.decl == "still_debt") }) +} + +// ------------------------------------------------------------------ helpers + +fn member_named(xs: List, x: String) -> Bool { + fold_list(xs: xs, empty: false, cons: fn(acc, s) { acc || (s == x) }) +} + +fn site_present(sites: List, module_file: String, decl: String) -> Bool { + match argv_word_construction_site_named(sites: sites, module_file: module_file, decl: decl) { + Present { value: _ } => true + Absent => false + } +} + +fn site_absent(sites: List, module_file: String, decl: String) -> Bool { + match argv_word_construction_site_named(sites: sites, module_file: module_file, decl: decl) { + Present { value: _ } => false + Absent => true + } +} diff --git a/test/fixture_roots/argv_word_construction/host_words.dag b/test/fixture_roots/argv_word_construction/host_words.dag new file mode 100644 index 00000000000..31dbb52cd56 --- /dev/null +++ b/test/fixture_roots/argv_word_construction/host_words.dag @@ -0,0 +1,41 @@ +module fixture.argv_word_construction.host_words + +// The committed real-parse fixture for the typed-argv census lens (v2.lens.argv_word_construction): +// small enough to read and parse inside a claim test, and shaped so every census signal appears +// once. Off the corpus compile path (test/fixture_roots, like the text-string census's fixture). + +import std.algebra { Cons, Empty, FreeMonoid } +import v2.std.logic { Bool } +import v2.std.collection { List } +import v2.std.algebra { length } + +// A List data decl that IS argv vocabulary: flag words among its literal elements. +data fixture_wait_flags: List = ["--wait", "--quiet", "--collect"] + +// A List data decl that is NOT argv vocabulary: no flag words, no argv name (the ceiling +// the parent named; a List data decl is not a member by itself). +data fixture_layout_rows: List = ["header", "divider", "footer"] + +// A List data decl that IS argv vocabulary by name, without flag words: command words. +data fixture_commit_args: List = ["commit", "--allow-empty"] + +// (a)+(b): a fn builder that glues a flag literal to a value and returns word-lists. +fn fixture_transient_unit_argv(unit: String) -> List { + Cons { head: "systemd-run", tail: Cons { head: concat("--unit=", unit), tail: Cons { head: "--collect", tail: Empty } } } +} + +// (b): a fn accepting a List argv parameter in an argv position, returning word-lists. +fn fixture_run_with_properties(unit: String, property_argv: List) -> List { + Cons { head: concat("--unit=", unit), tail: property_argv } +} + +// NOT a member: a List parameter outside the argv positions, no flag words, no +// List return. +fn fixture_label_rows(labels: List) -> Bool { + !(length(xs: labels) == 0) +} + +// (a) only: a fn that spells a flag literal and returns a scalar. +fn fixture_waits_quietly() -> Bool { + starts_with(s: concat("--", "wait"), prefix: "--wait") +} From 71a9a1c5d317703914309ed8cc5fdddeb34b3ff1 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Sun, 4 Oct 2026 10:16:22 +0000 Subject: [PATCH 02/65] extdeps.systemd.systemd_run: typed options, one projection, ArgvCommand everywhere The seven string-glued builders (systemd_run_property_argv, systemd_run_user_transient_arguments, systemd_run_user_wait_arguments, systemd_run_system_scope_argv, systemd_run_property, systemd_run_transient_unit_argv, systemd_run_transient_wait_unit_argv) are deleted. In their place: - SystemdRunOption, a sealed sum whose arms carry systemd-run(1)'s own spellings at systemd 255 (the major the summary ground reads): RunUnit, RunUserManager, RunScope, PropertyShort, PropertyLong, SetEnv, Wait, Quiet, Pipe, Collect, EndOfOptions. - systemd_run_option_words, ONE projection from options to words, with refusals at the wall: a unit name carrying / or a newline, a property value carrying a newline, a setenv name carrying = (the wire cannot carry them; they are refused, not trimmed or split). An unknown flag has no arm to ride: the sum is closed and the projection's fold walks every variant. - Every builder returns ArgvCommand through SystemdRunCommandReading (Ready | Refused); extdeps.exec.command's admit list now admits systemd_run_command_of. - The service operations take the projected words and the transport template leads with the declaration literal, per the materializer's executable-position contract. - Callers migrated: roadmap_dispatch_actuator (the four unit mints return readings; the belt layer composes the resolved program head with the projected words, refusals land in ExecRefused/ExecStepFailed), runner_microvm_lifecycle_realize, runner_throughput_qualification_route, compute.work_provider_local, compute.unit_bounds (the grant fold now yields typed properties), auth approval_device_enrolment_code_issue, gunbc.systemd_run_transient (the bridge carries the typed command end to end; the authority check joins the materialized words against the one projection's words), host_effect_nbd_proxy_serve. Evidence: positive controls pin the projected words byte-identical to the words the string-built forms rendered for existing callers (wait witness, user-wait witness, review-unit option words, nbd wire-name witness); reds witness a newline in a property value, a slash in a unit name and an = in a setenv name refused at the projection. SystemdUnitProperty is widened with the seven real settings the dispatch units bind (StandardOutput, StandardError, ProtectSystem, ProtectHome, PrivateTmp, ReadWritePaths, RemainAfterExit) so no property travels as a free-form string. Debt paydown trial, session witty-tern-54. The census instrument (gunbc.instruments.argv_word_construction_census) counts this population; this module was its specimen row set. CI-lane claim batch over the eight affected witness files: 20 pass, 0 fail. --- dag/extdeps/exec/command.dag | 2 +- dag/extdeps/systemd/systemd.dag | 14 + dag/extdeps/systemd/systemd_run.dag | 323 +++++++++++------- .../approval_device_enrolment_code_issue.dag | 40 ++- dag/gunbc/compute/unit_bounds.dag | 22 +- dag/gunbc/compute/work_provider_local.dag | 47 ++- .../host/host_effect_nbd_proxy_serve.dag | 12 +- dag/gunbc/roadmap/roadmap_belt_actuate.dag | 127 ++++--- .../roadmap/roadmap_dispatch_actuator.dag | 239 ++++++------- .../runner_microvm_lifecycle_realize.dag | 28 +- .../runner_throughput_qualification_route.dag | 6 +- dag/gunbc/systemd_run_transient.dag | 160 ++++----- .../compute/work_class_grant_witness_test.dag | 73 ++-- ...ystemd_run_transient_wait_witness_test.dag | 136 +++++++- ...nbd_proxy_serve_transport_witness_test.dag | 42 ++- ...spatch_worker_confinement_witness_test.dag | 12 +- ...roadmap_dispatch_actuator_witness_test.dag | 66 +++- .../session_placement_witness_test.dag | 12 +- ...r_microvm_slot_controller_witness_test.dag | 31 +- ..._throughput_qualification_witness_test.dag | 30 +- ...ner_microvm_lifecycle_wet_receipt_test.dag | 37 +- 21 files changed, 926 insertions(+), 533 deletions(-) diff --git a/dag/extdeps/exec/command.dag b/dag/extdeps/exec/command.dag index ee94b1e084d..4bd4f18d211 100644 --- a/dag/extdeps/exec/command.dag +++ b/dag/extdeps/exec/command.dag @@ -126,7 +126,7 @@ fn argv_command(program: ProgramIdentity, arguments: List) -> ArgvComman decl_ref(module_path: "extdeps.iproute2.ip_show", decl_name: "ip_route_show_command"), decl_ref(module_path: "extdeps.tools.uname", decl_name: "uname_release_command"), decl_ref(module_path: "extdeps.tools.make", decl_name: "make_command"), - decl_ref(module_path: "extdeps.systemd.systemd_run", decl_name: "systemd_run_user_scope_command"), + decl_ref(module_path: "extdeps.systemd.systemd_run", decl_name: "systemd_run_command_of"), decl_ref(module_path: "extdeps.package_managers.pip", decl_name: "pip_install_pinned_command"), decl_ref(module_path: "extdeps.package_managers.conda_forge", decl_name: "micromamba_create_command"), decl_ref(module_path: "extdeps.printing.orca_slicer", decl_name: "orca_slice_to_project_command"), diff --git a/dag/extdeps/systemd/systemd.dag b/dag/extdeps/systemd/systemd.dag index 17dad20bdeb..d1c2c3b6f9b 100644 --- a/dag/extdeps/systemd/systemd.dag +++ b/dag/extdeps/systemd/systemd.dag @@ -88,6 +88,13 @@ type SystemdUnitProperty | WantsProperty | InvocationIDProperty | FragmentPathProperty + | StandardOutputProperty + | StandardErrorProperty + | ProtectSystemProperty + | ProtectHomeProperty + | PrivateTmpProperty + | ReadWritePathsProperty + | RemainAfterExitProperty // THE WIRE SPELLING AS A SEALED WORD, SO THE CLOSED TYPE SURVIVES ALL THE WAY TO THE ARGV. // @@ -155,6 +162,13 @@ fn systemd_unit_property_wire(property: SystemdUnitProperty) -> NonEmptyStr { WantsProperty => "Wants" as NonEmptyStr InvocationIDProperty => "InvocationID" as NonEmptyStr FragmentPathProperty => "FragmentPath" as NonEmptyStr + StandardOutputProperty => "StandardOutput" as NonEmptyStr + StandardErrorProperty => "StandardError" as NonEmptyStr + ProtectSystemProperty => "ProtectSystem" as NonEmptyStr + ProtectHomeProperty => "ProtectHome" as NonEmptyStr + PrivateTmpProperty => "PrivateTmp" as NonEmptyStr + ReadWritePathsProperty => "ReadWritePaths" as NonEmptyStr + RemainAfterExitProperty => "RemainAfterExit" as NonEmptyStr } } diff --git a/dag/extdeps/systemd/systemd_run.dag b/dag/extdeps/systemd/systemd_run.dag index 51cd530cf2e..5218351a60b 100644 --- a/dag/extdeps/systemd/systemd_run.dag +++ b/dag/extdeps/systemd/systemd_run.dag @@ -44,134 +44,222 @@ data systemd_run_authority: ExternalAuthority = extdeps_external_authority_ancho fn systemd_run_program() -> ProgramIdentity = uncataloged_program(invocation: "systemd-run") -// THE PROPERTY WORDS ARE RENDERED IN EXACTLY ONE PLACE, and that is the whole point of this -// function existing beside the full-line authority rather than inside it. A systemd unit property -// becomes an argv word here and nowhere else, so the operation's transport, the pure authority and -// every caller are reading one spelling. The transport CANNOT carry the typed record itself: -// push_shell_argv_tokens has arms for Str, List, ProcessArgvExpansion and a refusing arm for -// ambiguous free monoids, and a record reaches none of them — it lands in the catch-all, which -// Display-formats the value into a single argv word rather than refusing. So a record spliced into -// argv would hand systemd-run a fabricated argument at the exact seam that decides whether a unit -// is bounded. The typed population stays on this side of that boundary; only words cross it. -fn systemd_run_property_argv(properties: List) -> List { - fold( - properties, - init: [], - f: (acc, setting) => list_push( - acc, - concat( - "--property=", - concat(systemd_unit_property_wire(property: setting.property) as String, concat("=", setting.value as String)), - ), - ), - ) +// THE WAITING LAUNCHER IS A TOKEN, NOT A WORD LIST: its ArgvCommand is minted only by the typed +// builders below, so what a caller holds is the fact that a specific projection happened, not a +// list it could have glued together itself. +type SystemdSummaryPrintingWait { command: ArgvCommand } + +fn systemd_summary_printing_wait_argv(w: SystemdSummaryPrintingWait) -> List { + argv_words(command: w.command) } -// THE USER-MANAGER TRANSIENT UNIT, modelled beside the system-scope one above because the flags -// systemd-run actually accepts are upstream's, not ours: --user selects the calling user's manager, -// -p sets a unit property, and --setenv binds one environment variable in the started unit. +// ── THE TYPED OPTION MODEL OF systemd-run(1) AT THE GROUNDED MAJOR ───────────────────────────── // -// NO --collect HERE, DELIBERATELY, and it is the difference that makes this worth a second -// function. --collect unloads the unit as soon as it exits, which discards Result and -// ExecMainStatus -- exactly the terminal state a caller starts a transient unit in order to be able -// to read afterwards. A unit that lingers in `failed` is evidence; one that was garbage collected -// is an absence indistinguishable from never having run. Callers that want no residue name a unit -// per attempt and reset it explicitly, which is a decision about evidence rather than a flag. -fn systemd_run_user_transient_arguments( - unit: NonEmptyStr, - properties: List, - setenv_bindings: List, - command_argv: List, -) -> List { - let head = ["--user", concat("--unit=", unit as String)] - let with_properties = fold( - properties, - init: head, - f: (acc, p) => list_push(list_push(acc, "-p"), p), - ) - let with_env = fold( - setenv_bindings, - init: with_properties, - f: (acc, b) => list_push(acc, concat("--setenv=", b)), - ) - fold( - command_argv, - init: list_push(with_env, "--"), - f: (acc, arg) => list_push(acc, arg), - ) +// EVERY WORD THIS MODULE RENDERS IS BORN IN ONE PROJECTION, `systemd_run_option_words`, from typed +// options carrying the man page's own spellings at systemd 255 — the major the summary ground +// below actually reads (grounded_summary_format_majors). No builder glues a flag to a value with +// string literals anymore: the flag words live here, once, each citing its option, and the +// projection REFUSES a value the wire cannot carry rather than trimming or dropping it. +// (Debt paydown trial, session witty-tern-54: the census instrument +// gunbc.instruments.argv_word_construction_census counts this population at identity grain; the +// seven string-glued builders this module used to expose were its specimen rows.) + +// --setenv=NAME=VALUE binds ONE environment variable into the unit (systemd-run(1)). +type SystemdSetenvBinding { + name: NonEmptyStr, + value: NonEmptyStr, } -// THE SYNCHRONOUS FORM: the caller blocks until the unit exits and receives its exit status and its -// stdio (`--wait --pipe`), so a build or a check runs under the unit's resource bounds while the -// process that asked for it observes the result directly. `--collect` unloads the unit afterwards -// whatever its result, so a failed compute unit does not accumulate as a failed unit to reap. -// WHETHER THERE IS A SUMMARY TO READ IS A FACT ABOUT THE INVOCATION, SO THE INVOCATION CARRIES IT. -// systemd-run prints the result and peak lines only under `if (arg_wait && !arg_quiet)` (run.c, -// v255), so --quiet suppresses the summary ENTIRELY: a reader handed a quiet launcher's stderr -// finds no peak, reports MemoryPeakNotReported forever, and is indistinguishable from a healthy -// launcher whose manager is below v254. A DEAD PRODUCER THAT LOOKS LIKE A HEALTHY ONE WITH NOTHING -// TO REPORT is the failure this file is one word away from: RunTransientAndWait and this builder -// differ by exactly "--quiet", and the next author adds a third by copying whichever they reached -// first. So the summary reader does not take a loose String. It takes this token, which is minted -// in exactly one place -- the builder below, the only wait invocation in this module that omits -// --quiet -- and a launcher that cannot print a peak therefore cannot be handed to the thing whose -// contract is to observe one. -type SystemdSummaryPrintingWait { argv: List } +// THE OPTION ARMS AND THEIR WIRE SPELLINGS, one per line, each citing systemd-run(1) at v255: +// RunUnit --unit=UNIT +// RunUserManager --user +// RunScope --scope +// PropertyShort -p NAME=VALUE — flag and value are TWO words +// PropertyLong --property=NAME=VALUE — one word +// SetEnv --setenv=NAME=VALUE +// Wait --wait +// Quiet --quiet +// Pipe --pipe +// Collect --collect +// EndOfOptions -- (systemd-run's own options end; the rest is the command) +type SystemdRunOption + = RunUnit { unit: NonEmptyStr } + | RunUserManager + | RunScope + | PropertyShort { property: SystemdRunProperty } + | PropertyLong { property: SystemdRunProperty } + | SetEnv { binding: SystemdSetenvBinding } + | Wait + | Quiet + | Pipe + | Collect + | EndOfOptions + +// WHAT THE WIRE CANNOT CARRY IS REFUSED AT THE PROJECTION — a refusal at the wall between the +// typed value and the words, not a mangled word downstream. Each arm names the wire fact it +// defends (unit names and setting values are one line and contain no slash; an environment name +// is the text before the first '='). +type SystemdRunOptionWordsReading + = SystemdRunOptionWordsProjected { words: List } + | SystemdRunOptionWordsRefused { reason: String } + +type SystemdRunCommandReading + = SystemdRunCommandReady { command: ArgvCommand } + | SystemdRunCommandRefused { reason: String } + +fn systemd_run_option_word_has_newline(s: String) -> Bool { + string_contains(s: s, pattern: "\n") +} -fn systemd_summary_printing_wait_argv(w: SystemdSummaryPrintingWait) -> List { - w.argv +fn systemd_run_option_word_refused(reason: String) -> SystemdRunOptionWordsReading { + SystemdRunOptionWordsRefused { reason: reason } } -fn systemd_run_user_wait_arguments( - unit: NonEmptyStr, - properties: List, - setenv_bindings: List, - command_argv: List, -) -> SystemdSummaryPrintingWait { - SystemdSummaryPrintingWait { argv: concat(["--wait", "--pipe", "--collect"], systemd_run_user_transient_arguments(unit: unit, properties: properties, setenv_bindings: setenv_bindings, command_argv: command_argv)) } +fn systemd_run_property_options_long(properties: List) -> List { + fold(properties, init: [], f: (acc, p) => list_append(left: acc, right: [PropertyLong { property: p }])) } -// ONE COMMAND IN A TRANSIENT SCOPE OF THE CALLING USER'S MANAGER, bounded by the given properties -// (`systemd-run --user --scope -p NAME=VALUE ... -- CMD`, per the cited man page: a scope runs the -// command synchronously as a child of systemd-run and places it in its own cgroup). The kernel -// enforces the bound, not the command, so an over-budget run is killed by the cgroup instead of -// competing for the host. The program is PATH-resolved because the invoking host is unobserved -// (extdeps.exec.command program_spelling_is_an_observation_claim). -fn systemd_run_user_scope_command(properties: List, command: ArgvCommand) -> ArgvCommand { - let head = ["--user", "--scope"] - let with_properties = fold(systemd_run_property_argv(properties: properties), init: head, f: (acc, word) => list_push(acc, word)) - let with_separator = list_push(with_properties, "--") - argv_command( - program: systemd_run_program(), - arguments: fold(argv_words(command: command), init: with_separator, f: (acc, arg) => list_push(acc, arg)), - ) +fn systemd_run_property_options_short(properties: List) -> List { + fold(properties, init: [], f: (acc, p) => list_append(left: acc, right: [PropertyShort { property: p }])) +} + +// THE ONE PROJECTION STEP. Each option's words are spelled exactly once, here; the fold folds over +// these so a refused command never mints words at all (short-circuit on the first refusal). +fn systemd_run_option_projection_step(acc: SystemdRunOptionWordsReading, option: SystemdRunOption) -> SystemdRunOptionWordsReading { + match acc { + SystemdRunOptionWordsRefused { reason: r } => SystemdRunOptionWordsRefused { reason: r } + SystemdRunOptionWordsProjected { words: words } => + match option { + RunUnit { unit: unit } => + if string_contains(s: unit as String, pattern: "/") { + systemd_run_option_word_refused(reason: "--unit= takes a unit name, and a unit name never contains / (systemd.unit(5))") + } else if systemd_run_option_word_has_newline(s: unit as String) { + systemd_run_option_word_refused(reason: "--unit= takes a unit name, and a unit name is one line") + } else { + SystemdRunOptionWordsProjected { words: list_append(left: words, right: [concat("--unit=", unit as String)]) } + } + RunUserManager => SystemdRunOptionWordsProjected { words: list_append(left: words, right: ["--user"]) } + RunScope => SystemdRunOptionWordsProjected { words: list_append(left: words, right: ["--scope"]) } + PropertyShort { property: p } => + if systemd_run_option_word_has_newline(s: p.value as String) { + systemd_run_option_word_refused(reason: concat("unit setting value is one line, so --property= cannot carry a newline in: ", systemd_unit_property_wire(property: p.property))) + } else { + SystemdRunOptionWordsProjected { words: list_append(left: words, right: ["-p", concat(systemd_unit_property_wire(property: p.property), concat("=", p.value as String))]) } + } + PropertyLong { property: p } => + if systemd_run_option_word_has_newline(s: p.value as String) { + systemd_run_option_word_refused(reason: concat("unit setting value is one line, so --property= cannot carry a newline in: ", systemd_unit_property_wire(property: p.property))) + } else { + SystemdRunOptionWordsProjected { words: list_append(left: words, right: [concat("--property=", concat(systemd_unit_property_wire(property: p.property), concat("=", p.value as String)))]) } + } + SetEnv { binding: b } => + if string_contains(s: b.name as String, pattern: "=") { + systemd_run_option_word_refused(reason: "--setenv= takes NAME=VALUE, so the NAME side carries no = of its own") + } else if systemd_run_option_word_has_newline(s: b.name as String) { + systemd_run_option_word_refused(reason: "--setenv= takes NAME=VALUE, so the NAME side is one line") + } else if systemd_run_option_word_has_newline(s: b.value as String) { + systemd_run_option_word_refused(reason: "--setenv= takes NAME=VALUE, so the VALUE side is one line") + } else { + SystemdRunOptionWordsProjected { words: list_append(left: words, right: [concat("--setenv=", concat(b.name as String, concat("=", b.value as String)))]) } + } + Wait => SystemdRunOptionWordsProjected { words: list_append(left: words, right: ["--wait"]) } + Quiet => SystemdRunOptionWordsProjected { words: list_append(left: words, right: ["--quiet"]) } + Pipe => SystemdRunOptionWordsProjected { words: list_append(left: words, right: ["--pipe"]) } + Collect => SystemdRunOptionWordsProjected { words: list_append(left: words, right: ["--collect"]) } + EndOfOptions => SystemdRunOptionWordsProjected { words: list_append(left: words, right: ["--"]) } + } + } +} + +fn systemd_run_option_words(options: List) -> SystemdRunOptionWordsReading { + fold(options, init: SystemdRunOptionWordsProjected { words: [] }, f: systemd_run_option_projection_step) +} + +// THE COMMAND COMPOSITION: options first, then the command's own words after `--` (the caller's +// argv is appended verbatim; its typing is its own layer's debt, one batch per module). +// The words AFTER the program word. The projection leads with exactly one program spelling, so a +// caller that runs a RESOLVED program path instead (an observation the instance toolchain declares, +// and the only thing the invoking host is allowed to assert about where the program lives) takes +// the tail and substitutes its own head. +type SystemdRunCommandTailFold { + program_pending: Bool, + words: List, +} + +fn systemd_run_command_argument_words(command: ArgvCommand) -> List { + fold(argv_words(command: command), init: SystemdRunCommandTailFold { program_pending: true, words: [] }, f: (acc, w) => + if acc.program_pending { + SystemdRunCommandTailFold { program_pending: false, words: acc.words } + } else { + SystemdRunCommandTailFold { program_pending: false, words: list_append(left: acc.words, right: [w]) } + } + ).words +} + +fn systemd_run_command_of(options: List, command_argv: List) -> SystemdRunCommandReading { + match systemd_run_option_words(options: options) { + SystemdRunOptionWordsRefused { reason: r } => SystemdRunCommandRefused { reason: r } + SystemdRunOptionWordsProjected { words: option_words } => + SystemdRunCommandReady { command: argv_command(program: systemd_run_program(), arguments: list_append(left: option_words, right: command_argv)) } + } } -// ONE COMMAND IN A TRANSIENT SCOPE OF THE SYSTEM MANAGER (`systemd-run --scope -p NAME=VALUE ... -- CMD`). +// ── THE BUILDERS, ALL RETURNING THE TYPED COMMAND ─────────────────────────────────────────────── + +// ONE COMMAND IN A TRANSIENT SCOPE OF THE SYSTEM MANAGER (`systemd-run --scope --property=NAME=VALUE ... -- CMD`). // The user-manager form above needs the caller's own session bus, which a command elevated from // another login does not have; the system manager starts the scope and the kernel bounds its // cgroup. The caller must be root. The command runs with the caller's credentials: systemd-run's // --uid/--gid are not rendered, because in scope mode they switch uid and gid without initialising // the target's supplementary groups and leave the caller's in place (fleet-converge run // 37098981121). A caller that needs another account composes its own privilege drop as CMD. -fn systemd_run_system_scope_argv(properties: List, command_argv: List) -> List { - let with_properties = fold(systemd_run_property_argv(properties: properties), init: ["systemd-run", "--scope"], f: (acc, word) => list_push(acc, word)) - let with_separator = list_push(with_properties, "--") - fold(command_argv, init: with_separator, f: (acc, arg) => list_push(acc, arg)) +// ONE COMMAND IN A TRANSIENT SCOPE OF THE CALLING USER'S MANAGER, bounded by the given properties +// (`systemd-run --user --scope --property=NAME=VALUE ... -- CMD`, per the cited man page: a scope +// runs the command synchronously as a child of systemd-run and places it in its own cgroup). The +// kernel enforces the bound, not the command, so an over-budget run is killed by the cgroup +// instead of competing for the host. The program is PATH-resolved because the invoking host is +// unobserved (extdeps.exec.command program_spelling_is_an_observation_claim). +fn systemd_run_user_scope_command(properties: List, command_argv: List) -> SystemdRunCommandReading { + systemd_run_command_of(options: list_append(left: [RunUserManager, RunScope], right: list_append(left: systemd_run_property_options_long(properties: properties), right: [EndOfOptions])), command_argv: command_argv) +} + +fn systemd_run_setenv_options(bindings: List) -> List { + fold(bindings, init: [], f: (acc, b) => list_append(left: acc, right: [SetEnv { binding: b }])) +} + +// THE USER-MANAGER TRANSIENT START: `systemd-run --user --unit=NAME -p NAME=VALUE ... --setenv=K=V +// ... -- CMD`. The property words travel as two words (-p, then NAME=VALUE) and the projected +// order here is the order the string-built form this replaces happened to emit, so the projected +// words for existing callers are byte-identical to what they rendered before the cutover. +fn systemd_run_user_transient_command(unit: NonEmptyStr, properties: List, setenv_bindings: List, command_argv: List) -> SystemdRunCommandReading { + systemd_run_command_of( + options: list_append(left: [RunUserManager, RunUnit { unit: unit }], right: list_append(left: systemd_run_property_options_short(properties: properties), right: list_append(left: systemd_run_setenv_options(bindings: setenv_bindings), right: [EndOfOptions]))), + command_argv: command_argv, + ) +} + +// THE USER-MANAGER WAITING TRANSIENT: the start above plus --wait --pipe --collect, and NOT +// --quiet -- run.c at v255 prints "Finished with result:" and "Memory peak:" only under +// `if (arg_wait && !arg_quiet)`, so the quiet form's stderr never carries the summary this type +// exists to be read for. The wait flags lead because that is the word order the string-built form +// this replaces emitted; the projection keeps it byte-identical for existing callers. +fn systemd_run_user_wait_command(unit: NonEmptyStr, properties: List, setenv_bindings: List, command_argv: List) -> SystemdRunCommandReading { + systemd_run_command_of( + options: list_append(left: [Wait, Pipe, Collect], right: list_append(left: [RunUserManager, RunUnit { unit: unit }], right: list_append(left: systemd_run_property_options_short(properties: properties), right: list_append(left: systemd_run_setenv_options(bindings: setenv_bindings), right: [EndOfOptions])))), + command_argv: command_argv, + ) } -fn systemd_run_property(name: String, value: String) -> String { - join([name, "=", value], "") +fn systemd_run_system_scope_command(properties: List, command_argv: List) -> SystemdRunCommandReading { + systemd_run_command_of(options: list_append(left: [RunScope], right: list_append(left: systemd_run_property_options_long(properties: properties), right: [EndOfOptions])), command_argv: command_argv) } // The full invocation, and the authority the materialized operation argv is checked against. It -// COMPOSES the property words rather than re-deriving them; if this fold and the transport template -// ever disagree about word order, systemd_run_transient_operation_argv_matches_authority goes red. -fn systemd_run_transient_unit_argv(unit: NonEmptyStr, properties: List, command_argv: List) -> List { - let launcher = ["systemd-run", concat("--unit=", unit as String), "--collect"] - let with_properties = fold(systemd_run_property_argv(properties: properties), init: launcher, f: (acc, word) => list_push(acc, word)) - let with_separator = list_push(with_properties, "--") - fold(command_argv, init: with_separator, f: (acc, arg) => list_push(acc, arg)) +// COMPOSES the option words through the one projection rather than re-deriving them; if this fold +// and the transport template ever disagree about word order, +// systemd_run_transient_operation_argv_matches_authority goes red. +fn systemd_run_transient_unit_command(unit: NonEmptyStr, properties: List, command_argv: List) -> SystemdRunCommandReading { + systemd_run_command_of(options: list_append(left: [RunUnit { unit: unit }], right: list_append(left: systemd_run_property_options_long(properties: properties), right: [Collect, EndOfOptions])), command_argv: command_argv) } // Waiting is a different operation from starting. It owns the complete wait/quiet/collect @@ -180,11 +268,8 @@ fn systemd_run_transient_unit_argv(unit: NonEmptyStr, properties: List, command_argv: List) -> List { - let launcher = ["systemd-run", concat("--unit=", unit as String), "--wait", "--quiet", "--collect"] - let with_properties = fold(systemd_run_property_argv(properties: properties), init: launcher, f: (acc, word) => list_push(acc, word)) - let with_separator = list_push(with_properties, "--") - fold(command_argv, init: with_separator, f: (acc, arg) => list_push(acc, arg)) +fn systemd_run_transient_wait_unit_command(unit: NonEmptyStr, properties: List, command_argv: List) -> SystemdRunCommandReading { + systemd_run_command_of(options: list_append(left: [RunUnit { unit: unit }, Wait, Quiet, Collect], right: list_append(left: systemd_run_property_options_long(properties: properties), right: [EndOfOptions])), command_argv: command_argv) } @@ -214,16 +299,22 @@ fn systemd_run_transient_wait_unit_argv(unit: NonEmptyStr, properties: List, command_argv: List } + input { command_argv: List } output { success: Bool from "exit_success" stdout: String from "stdout" stderr: String from "stderr" } - transport shell { argv: ["systemd-run", "--unit={unit}", "--collect", property_argv, "--", command_argv] } + transport shell { argv: ["systemd-run", command_argv] } exit { 0 => Unit nonzero => String "systemd-run transient unit start failed" @@ -236,13 +327,13 @@ service systemd.SystemdRun { operation RunTransientRetained { requires opaque - input { unit: NonEmptyStr, property_argv: List, command_argv: List } + input { command_argv: List } output { success: Bool from "exit_success" stdout: String from "stdout" stderr: String from "stderr" } - transport shell { argv: ["systemd-run", "--unit={unit}", property_argv, "--", command_argv] } + transport shell { argv: ["systemd-run", command_argv] } exit { 0 => Unit nonzero => String "systemd-run transient unit start failed" @@ -254,13 +345,13 @@ service systemd.SystemdRun { operation RunTransientAndWait { requires opaque - input { unit: NonEmptyStr, property_argv: List, command_argv: List } + input { command_argv: List } output { exit_code: Int from "exit_code" stdout: String from "stdout" stderr: String from "stderr" } - transport shell { argv: ["systemd-run", "--unit={unit}", "--wait", "--quiet", "--collect", property_argv, "--", command_argv] } + transport shell { argv: ["systemd-run", command_argv] } exit { 0 => Unit nonzero => Unit diff --git a/dag/gunbc/auth/approval_device_enrolment_code_issue.dag b/dag/gunbc/auth/approval_device_enrolment_code_issue.dag index 4dc05e56d3a..f032eeb079f 100644 --- a/dag/gunbc/auth/approval_device_enrolment_code_issue.dag +++ b/dag/gunbc/auth/approval_device_enrolment_code_issue.dag @@ -6,7 +6,11 @@ import std.process { ProcessExit, ExitSuccess, ExitFailure, exit_failure } import std.resources { Network } import gunbc.cli_wire { CliWireResponse, CliWirePrintable, CliWireUnprintable } import extdeps.sudo.elevation { sudo_elevate } -import extdeps.systemd.systemd_run { SystemdRunProperty, systemd_run_system_scope_argv } +import extdeps.systemd.systemd_run { + SystemdRunProperty, SystemdRunCommandReading, SystemdRunCommandReady, SystemdRunCommandRefused, + systemd_run_system_scope_command, +} +import extdeps.exec.command { ArgvCommand, argv_words } import extdeps.tools.util_linux_setpriv { setpriv_reuid_regid_argv } import extdeps.systemd { MemoryMax, MemoryHigh } import std.measure { byte_size_count } @@ -79,7 +83,7 @@ fn enrolment_code_issue_ssh_target() -> SshTarget { // typed-module cache cap is derived from the cgroup's memory.high/memory.max, and with neither bound // it refuses HostBudgetUnreadable rather than guessing (fleet-converge run 36556990543). An SSH // session binds no such limit. The scope is a system-manager transient scope (extdeps.systemd.systemd_run -// systemd_run_system_scope_argv) around the account drop below. A user +// systemd_run_system_scope_command) around the account drop below. A user // scope would need that account's session bus, which the elevated command lacks. The bounds are the // broker's own slice rows (gunbc.live_deploy.slice_bounds approval_broker_slice_memory_max and _high). // The verb resolves the broker's routes closure, the same program the broker boots, so the same @@ -97,7 +101,7 @@ fn enrolment_code_issue_scope_properties() -> List { // supplementary groups; systemd-run's own --uid/--gid did not, and every code was minted and then // refused delivery (fleet-converge run 37098981121). The drop sits inside the scope so the bound // still covers the whole verb. -fn enrolment_code_issue_remote_argv(revision: ReleaseRevisionBinding) -> List { +fn enrolment_code_issue_remote_command(revision: ReleaseRevisionBinding) -> SystemdRunCommandReading { let root = srv1_gunbc_approval_broker_root as String let release_dir = approval_broker_release_dir(root: root, revision: revision) let account = fleet_posix_operator_user.name @@ -113,8 +117,7 @@ fn enrolment_code_issue_remote_argv(revision: ReleaseRevisionBinding) -> List CliWireResponse uses net: Network { match prepare_fleet_ssh_agent_context(attempt_raw: approval_device_enrolment_code_issue_attempt_raw) { FleetSshContextRefused { cause: c } => CliWireUnprintable { cause: ("approval device enrolment code: " + c) as NonEmptyStr } FleetSshContextReady { context: context, receipt: _ } => - match typed_argv_exec_over_fleet_ssh(target: enrolment_code_issue_ssh_target(), context: context, argv: enrolment_code_issue_remote_argv(revision: revision)) { - TypedArgvExecRefused { reason: why } => CliWireUnprintable { cause: ("approval device enrolment code: remote invocation refused: " + why) as NonEmptyStr } - TypedArgvExecConverged { result: r } => - if r.success { CliWirePrintable { bytes: r.stdout, exit: ExitSuccess } } - else { CliWireUnprintable { cause: ("approval device enrolment code: remote exit=" + to_string(r.exit_code) + " " + trim(s: r.stderr)) as NonEmptyStr } } + match enrolment_code_issue_remote_command(revision: revision) { + SystemdRunCommandRefused { reason: why } => + CliWireUnprintable { cause: ("approval device enrolment code: remote invocation refused: " + why) as NonEmptyStr } + SystemdRunCommandReady { command: command } => + match typed_argv_exec_over_fleet_ssh( + target: enrolment_code_issue_ssh_target(), + context: context, + argv: sudo_elevate_words(command: command), + ) { + TypedArgvExecRefused { reason: why } => CliWireUnprintable { cause: ("approval device enrolment code: remote invocation refused: " + why) as NonEmptyStr } + TypedArgvExecConverged { result: r } => + if r.success { CliWirePrintable { bytes: r.stdout, exit: ExitSuccess } } + else { CliWireUnprintable { cause: ("approval device enrolment code: remote exit=" + to_string(r.exit_code) + " " + trim(s: r.stderr)) as NonEmptyStr } } + } } } } +fn sudo_elevate_words(command: ArgvCommand) -> List { + let inv = sudo_elevate(command: argv_words(command: command)) + concat([inv.bin_path], inv.args) +} + // THE STEP ENTRY: one host per run, named by the dispatch and required to be srv1; one release, // named by the dispatch. fn approval_device_enrolment_code_issue_wet() -> CliWireResponse diff --git a/dag/gunbc/compute/unit_bounds.dag b/dag/gunbc/compute/unit_bounds.dag index 267517e3dfc..37341b1a4d6 100644 --- a/dag/gunbc/compute/unit_bounds.dag +++ b/dag/gunbc/compute/unit_bounds.dag @@ -1,9 +1,10 @@ module gunbc.compute.unit_bounds -import std.types { String, List } +import std.types { String, List, NonEmptyStr } import std.nat { Nat } import std.measure { Kibibyte, kibibyte_to_byte_size, byte_size_count, Millicore, millicore_count } -import extdeps.systemd.systemd_run { systemd_run_property } +import extdeps.systemd { SystemdUnitProperty, MemoryMax, WorkingDirectoryProperty, KillModeProperty, CPUQuota, TasksMax } +import extdeps.systemd.systemd_run { SystemdRunProperty } // THE ONE FOLD FROM A CAPACITY GRANT TO THE PROPERTIES OF THE UNIT THAT HOLDS IT. It used to live in // gunbc.compute.work_provider_local as compute_unit_properties, and it moved here rather than being @@ -22,8 +23,7 @@ import extdeps.systemd.systemd_run { systemd_run_property } // declaration -- a drop-in or a future default could change it, and a provider would keep treating // a completed wait as an empty cgroup (side-chat review at 773825cf). Binding it makes the policy a // fact of the invocation, and an agent session releases its seat on the same reading. -data compute_kill_mode_property: String = "KillMode" -data compute_kill_mode_value: String = "control-group" +data compute_kill_mode_property: SystemdUnitProperty = KillModeProperty // WHAT A GRANT DOES NOT CARRY, NAMED RATHER THAN INVENTED. product.capacity.pool grants MEMORY: the // lease is an encumbrance over a Memory pool and nothing else, so a CPU quota or a task ceiling is @@ -44,19 +44,21 @@ fn unit_cpu_quota_percent(m: Millicore) -> Nat { millicore_count(m: m) / 10 } -fn compute_grant_unit_properties(granted: Kibibyte, working_directory: String, process_bounds: UnitProcessBounds?) -> List { +fn compute_grant_unit_properties(granted: Kibibyte, working_directory: String, process_bounds: UnitProcessBounds?) -> List { concat( [ - systemd_run_property(name: "MemoryMax", value: to_string(value: byte_size_count(b: kibibyte_to_byte_size(k: granted)))), - systemd_run_property(name: compute_kill_mode_property, value: compute_kill_mode_value), - systemd_run_property(name: "WorkingDirectory", value: working_directory), + SystemdRunProperty { property: MemoryMax, value: to_string(value: byte_size_count(b: kibibyte_to_byte_size(k: granted))) as NonEmptyStr }, + SystemdRunProperty { property: compute_kill_mode_property, value: compute_kill_mode_value }, + SystemdRunProperty { property: WorkingDirectoryProperty, value: working_directory as NonEmptyStr }, ], match process_bounds { Absent => [] Present { value: b } => [ - systemd_run_property(name: "CPUQuota", value: join([to_string(value: unit_cpu_quota_percent(m: b.cpu_quota)), "%"], "")), - systemd_run_property(name: "TasksMax", value: to_string(value: b.tasks_max)), + SystemdRunProperty { property: CPUQuota, value: join([to_string(value: unit_cpu_quota_percent(m: b.cpu_quota)), "%"], "") as NonEmptyStr }, + SystemdRunProperty { property: TasksMax, value: to_string(value: b.tasks_max) as NonEmptyStr }, ] }, ) } + +data compute_kill_mode_value: NonEmptyStr = "control-group" diff --git a/dag/gunbc/compute/work_provider_local.dag b/dag/gunbc/compute/work_provider_local.dag index 403fd43a851..ce67dde2c2f 100644 --- a/dag/gunbc/compute/work_provider_local.dag +++ b/dag/gunbc/compute/work_provider_local.dag @@ -11,7 +11,9 @@ import extdeps.shell import extdeps.git { shape_git_worktree_add_detached_argv } import extdeps.git.object_store { GitObjectId, git_object_id_from_untagged_hex, git_object_id_wire_hex } import extdeps.systemd.systemd_run { - systemd_run_user_wait_arguments, systemd_run_property, SystemdSummaryPrintingWait, systemd_summary_printing_wait_argv, + SystemdSummaryPrintingWait, systemd_summary_printing_wait_argv, SystemdSetenvBinding, + SystemdRunCommandReading, SystemdRunCommandReady, SystemdRunCommandRefused, SystemdRunProperty, + systemd_run_user_wait_command, SystemdRunWaitSummary, systemd_run_wait_summary, SystemdServiceResultReading, ServiceResultRead, ServiceResultUnrecognized, ServiceResultNotReported, SystemdServiceResult, ServiceResultOomKill, systemd_service_result_wire, SystemdMemoryPeakReading, MemoryPeakRead, MemoryPeakUnparseable, MemoryPeakNotReported, @@ -311,7 +313,7 @@ fn compute_unit_name(identity_hex: String) -> NonEmptyStr { // THE PROPERTIES ARE A FOLD SO THE BOUND POLICY IS CHECKABLE, and the fold is // gunbc.compute.unit_bounds compute_grant_unit_properties -- the one authority from a grant to a // unit, shared with the agent session's unit. A compute unit states no process bounds. -fn compute_unit_properties(layout: ComputeLayout, granted: Kibibyte) -> List { +fn compute_unit_properties(layout: ComputeLayout, granted: Kibibyte) -> List { compute_grant_unit_properties(granted: granted, working_directory: layout.checkout, process_bounds: none) } @@ -378,26 +380,35 @@ fn compute_run_unit(instance: HostDashboardInstance, layout: ComputeLayout, iden ComputeExecOk { stdout: v, stderr: _ } => v ComputeExecFailed { exit_code: _, stdout: _, stderr: _ } => "" }) - let launcher = systemd_run_user_wait_arguments( + match systemd_run_user_wait_command( unit: compute_unit_name(identity_hex: identity_hex), properties: compute_unit_properties(layout: layout, granted: granted), setenv_bindings: [ - join(["CARGO_TARGET_DIR=", layout.target_dir], ""), - join(["RUSTC_WRAPPER=", sccache_installed_binary_path], ""), + SystemdSetenvBinding { name: "CARGO_TARGET_DIR", value: layout.target_dir }, + SystemdSetenvBinding { name: "RUSTC_WRAPPER", value: sccache_installed_binary_path }, ], command_argv: argv, - ) - let r = compute_exec( - program: systemd_run, - workdir: layout.checkout, - args: systemd_summary_printing_wait_argv(w: launcher), - ) - ComputeUnitRun { - exec: r, - summary: systemd_run_wait_summary(launcher: launcher, standing: standing, stderr: match r { - ComputeExecOk { stdout: _, stderr } => stderr - ComputeExecFailed { exit_code: _, stdout: _, stderr } => stderr - }), + ) { + SystemdRunCommandRefused { reason: why } => + ComputeUnitRun { + exec: ComputeExecFailed { exit_code: 126, stdout: "", stderr: concat("systemd-run command refused: ", why) }, + summary: SystemdRunWaitSummary { result: ServiceResultNotReported { absence: SummaryNoInvocation }, memory_peak: MemoryPeakNotReported { absence: SummaryNoInvocation } }, + } + SystemdRunCommandReady { command: command } => { + let launcher = SystemdSummaryPrintingWait { command: command } + let r = compute_exec( + program: systemd_run, + workdir: layout.checkout, + args: systemd_summary_printing_wait_argv(w: launcher), + ) + ComputeUnitRun { + exec: r, + summary: systemd_run_wait_summary(launcher: launcher, standing: standing, stderr: match r { + ComputeExecOk { stdout: _, stderr } => stderr + ComputeExecFailed { exit_code: _, stdout: _, stderr } => stderr + }), + } + } } } } @@ -543,7 +554,7 @@ fn unit_termination_decide(o: UnitObservations) -> UnitTermination { PopulationEmpty { detail: pd } => UnitAttemptCompleted { detail: join([ - "systemd-run --wait returned success under ", compute_kill_mode_property, "=", compute_kill_mode_value, + "systemd-run --wait returned success under ", compute_kill_mode_property as String, "=", compute_kill_mode_value as String, " and ", pd, ], ""), } diff --git a/dag/gunbc/host/host_effect_nbd_proxy_serve.dag b/dag/gunbc/host/host_effect_nbd_proxy_serve.dag index dd80363d0af..fee3c72db37 100644 --- a/dag/gunbc/host/host_effect_nbd_proxy_serve.dag +++ b/dag/gunbc/host/host_effect_nbd_proxy_serve.dag @@ -19,6 +19,7 @@ import extdeps.bmc.webui.nbd_proxy_serve { bmcweb_session_token_env_ref, } import extdeps.exec.command { ArgvCommand, argv_words } +import extdeps.systemd.systemd_run { SystemdRunCommandReady, SystemdRunCommandRefused, systemd_run_transient_unit_command } import extdeps.tools.nbdkit { nbdkit_readonly_file_serve_command } import extdeps.tools.websocat { websocat_nbd_proxy_bridge_command } import extdeps.filesystem.filesystem_io { @@ -108,10 +109,15 @@ fn host_effect_nbd_proxy_serve_systemd_run_transient( command_argv: List, transport: HostEffectTransport, ) -> String? { - match systemd_run_transient_read(transport: transport, unit: unit, properties: [], command_argv: command_argv) { - SystemdRunTransientStarted { stdout: _ } => Absent - SystemdRunTransientRefused { reason: why } => + match systemd_run_transient_unit_command(unit: unit, properties: [], command_argv: command_argv) { + SystemdRunCommandRefused { reason: why } => Present { value: concat(concat("unit ", unit as String), concat(": ", why)) } + SystemdRunCommandReady { command: command } => + match systemd_run_transient_read(transport: transport, command: command) { + SystemdRunTransientStarted { stdout: _ } => Absent + SystemdRunTransientRefused { reason: why } => + Present { value: concat(concat("unit ", unit as String), concat(": ", why)) } + } } } diff --git a/dag/gunbc/roadmap/roadmap_belt_actuate.dag b/dag/gunbc/roadmap/roadmap_belt_actuate.dag index 068e6fdf9ca..dddb654a995 100644 --- a/dag/gunbc/roadmap/roadmap_belt_actuate.dag +++ b/dag/gunbc/roadmap/roadmap_belt_actuate.dag @@ -19,12 +19,13 @@ import gunbc.roadmap.roadmap_review_function { } import gunbc.roadmap.roadmap_review_criteria { ReviewCriterion } import gunbc.roadmap.roadmap_goal_audit_role { goal_audit_criterion_key, goal_audit_approved, goal_audit_reconcile, goal_audit_brief } +import extdeps.systemd.systemd_run { SystemdRunCommandReady, SystemdRunCommandRefused } import gunbc.roadmap_dispatch_actuator { dispatch_attempt_review_dir_for_instance, dispatch_attempt_review_verdict_path_for_instance, dispatch_attempt_review_verdict_basename, - dispatch_review_unit_name, dispatch_review_unit_argv, + dispatch_review_unit_name, dispatch_review_unit_command, dispatch_unit_exec_argv, dispatch_attempt_audit_dir_for_instance, dispatch_attempt_audit_verdict_path_for_instance, dispatch_attempt_audit_verdict_basename, - dispatch_audit_unit_name, dispatch_audit_unit_argv, - dispatch_attempt_supervisor_dir_for_instance, dispatch_supervisor_unit_name, dispatch_supervisor_unit_argv, + dispatch_audit_unit_name, dispatch_audit_unit_command, dispatch_unit_exec_argv, + dispatch_attempt_supervisor_dir_for_instance, dispatch_supervisor_unit_name, dispatch_supervisor_unit_command, dispatch_unit_exec_argv, dispatch_supervisor_attempt_identity, } import extdeps.git { shape_git_merge_base_args } @@ -105,7 +106,7 @@ import gunbc.roadmap_dispatch_actuator { DispatchLiveSession, DispatchSpawnCommands, DispatchSessionContainer, TmuxSessionContainer, SystemdUnitContainer, - dispatch_worker_unit_properties_for_plan, dispatch_worker_unit_run, + dispatch_worker_unit_properties_for_plan, dispatch_worker_unit_command, dispatch_unit_exec_argv, dispatch_attempt_unit_name, attempt_unit_observation, worker_process_from_attempt_unit, @@ -1326,10 +1327,15 @@ fn belt_session_container_create( match session_placement_begin_launch(stores: held.stores, grant: held.grant) { SessionLaunchRefused { detail } => ExecStepFailed { step: "session-launch-gate", detail: detail } SessionLaunchAdmitted { grant: launched } => - belt_exec_steps( - steps: [BeltExecStep { step: "harness-unit-start", command: dispatch_worker_unit_run(instance: instance, plan: plan, granted: launched.amount, process_bounds: session_process_bounds) }], - workdir: workdir, - ) + match dispatch_worker_unit_command(instance: instance, plan: plan, granted: launched.amount, process_bounds: session_process_bounds) { + SystemdRunCommandRefused { reason: why } => + ExecStepFailed { step: "harness-unit-start", detail: why } + SystemdRunCommandReady { command: command } => + belt_exec_steps( + steps: [BeltExecStep { step: "harness-unit-start", command: dispatch_unit_exec_argv(command: command, resolved_program: plan.systemd_run_program) }], + workdir: workdir, + ) + } } } } @@ -2530,48 +2536,55 @@ fn belt_supervisor_convene( launch: launch, } WorktreeHeadObserved { revision } => - match belt_exec( - cmd: dispatch_supervisor_unit_argv( - systemd_run_program: systemd_run, - instance: instance, - node_id: nid, - attempt_key: escalation.attempt_key, - turn: escalation.turn, - worktree_path: worktree, - brief: belt_supervisor_convene_brief( - node: node, - escalation: escalation, - checkpoint: belt_supervisor_checkpoint_lines( - capture: belt_prior_attempt_capture(instance: instance, node_id: nid, attempt_key: escalation.attempt_key), - head_sha: revision as String, - history_line: belt_supervisor_history_line(instance: instance, node_id: nid, attempt_key: escalation.attempt_key, turn: escalation.turn), - ), + match dispatch_supervisor_unit_command( + instance: instance, + node_id: nid, + attempt_key: escalation.attempt_key, + turn: escalation.turn, + worktree_path: worktree, + brief: belt_supervisor_convene_brief( + node: node, + escalation: escalation, + checkpoint: belt_supervisor_checkpoint_lines( + capture: belt_prior_attempt_capture(instance: instance, node_id: nid, attempt_key: escalation.attempt_key), + head_sha: revision as String, + history_line: belt_supervisor_history_line(instance: instance, node_id: nid, attempt_key: escalation.attempt_key, turn: escalation.turn), ), ), - workdir: worktree, ) { - ExecOk { stdout: _ } => - SupervisorConvened { - node_id: node.node as String, - unit: unit as String, - provider: provider_wire_label, - launch: launch, - } - ExecFailed { program: _, exit_code, stderr } => + SystemdRunCommandRefused { reason: why } => SpawnFailed { node_id: node.node as String, step: "supervisor-unit-start", - detail: join(["the supervisor unit could not be started (exit ", to_string(exit_code), "): ", stderr], ""), + detail: why, provider: provider_wire_label, launch: launch, } - ExecRefused { program: _, reason } => - SpawnFailed { - node_id: node.node as String, - step: "supervisor-unit-start", - detail: reason, - provider: provider_wire_label, - launch: launch, + SystemdRunCommandReady { command: command } => + match belt_exec(cmd: dispatch_unit_exec_argv(command: command, resolved_program: systemd_run), workdir: worktree) { + ExecOk { stdout: _ } => + SupervisorConvened { + node_id: node.node as String, + unit: unit as String, + provider: provider_wire_label, + launch: launch, + } + ExecFailed { program: _, exit_code, stderr } => + SpawnFailed { + node_id: node.node as String, + step: "supervisor-unit-start", + detail: join(["the supervisor unit could not be started (exit ", to_string(exit_code), "): ", stderr], ""), + provider: provider_wire_label, + launch: launch, + } + ExecRefused { program: _, reason } => + SpawnFailed { + node_id: node.node as String, + step: "supervisor-unit-start", + detail: reason, + provider: provider_wire_label, + launch: launch, + } } } } @@ -9852,13 +9865,15 @@ fn belt_audit_spawn(instance: HostDashboardInstance, progress: WorkflowAttemptPr verdict_path: dispatch_attempt_audit_verdict_path_for_instance(instance: instance, node_id: node_id, attempt_key: progress.attempt_key, head_sha: head), ) let unit = dispatch_audit_unit_name(node_id: progress.node_id, attempt_key: progress.attempt_key) - match belt_exec( - cmd: dispatch_audit_unit_argv(systemd_run_program: systemd_run, instance: instance, node_id: node_id, attempt_key: progress.attempt_key, worktree_path: worktree, brief: brief, head_sha: head), - workdir: worktree, - ) { - ExecOk { stdout: _ } => ReviewSpawned { node_id: progress.node_id, criterion: goal_audit_criterion_key, unit: unit as String } - ExecFailed { program: _, exit_code, stderr } => ReviewFailed { node_id: progress.node_id, criterion: goal_audit_criterion_key, detail: join(["the auditor unit could not be started (exit ", to_string(exit_code), "): ", stderr], "") } - ExecRefused { program: _, reason } => ReviewFailed { node_id: progress.node_id, criterion: goal_audit_criterion_key, detail: reason } + match dispatch_audit_unit_command(instance: instance, node_id: node_id, attempt_key: progress.attempt_key, worktree_path: worktree, brief: brief, head_sha: head) { + SystemdRunCommandRefused { reason: why } => + ReviewFailed { node_id: progress.node_id, criterion: goal_audit_criterion_key, detail: why } + SystemdRunCommandReady { command: command } => + match belt_exec(cmd: dispatch_unit_exec_argv(command: command, resolved_program: systemd_run), workdir: worktree) { + ExecOk { stdout: _ } => ReviewSpawned { node_id: progress.node_id, criterion: goal_audit_criterion_key, unit: unit as String } + ExecFailed { program: _, exit_code, stderr } => ReviewFailed { node_id: progress.node_id, criterion: goal_audit_criterion_key, detail: join(["the auditor unit could not be started (exit ", to_string(exit_code), "): ", stderr], "") } + ExecRefused { program: _, reason } => ReviewFailed { node_id: progress.node_id, criterion: goal_audit_criterion_key, detail: reason } + } } } } @@ -10124,13 +10139,15 @@ fn belt_review_spawn(instance: HostDashboardInstance, node_id: String, attempt_k let verdict_path = dispatch_attempt_review_verdict_path_for_instance(instance: instance, node_id: nid, attempt_key: attempt_key, head_sha: head, criterion_key: key) let brief = join([call.brief, review_verdict_instruction(verdict_path: verdict_path)], "") let unit = dispatch_review_unit_name(node_id: node_id, attempt_key: attempt_key, criterion_key: key) - match belt_exec( - cmd: dispatch_review_unit_argv(systemd_run_program: systemd_run, instance: instance, node_id: nid, attempt_key: attempt_key, worktree_path: worktree, brief: brief, head_sha: head, criterion_key: key), - workdir: worktree, - ) { - ExecOk { stdout: _ } => ReviewSpawned { node_id: node_id, criterion: call.id, unit: unit as String } - ExecFailed { program: _, exit_code, stderr } => ReviewFailed { node_id: node_id, criterion: call.id, detail: join(["the reviewer unit could not be started (exit ", to_string(exit_code), "): ", stderr], "") } - ExecRefused { program: _, reason } => ReviewFailed { node_id: node_id, criterion: call.id, detail: reason } + match dispatch_review_unit_command(instance: instance, node_id: nid, attempt_key: attempt_key, worktree_path: worktree, brief: brief, head_sha: head, criterion_key: key) { + SystemdRunCommandRefused { reason: why } => + ReviewFailed { node_id: node_id, criterion: call.id, detail: why } + SystemdRunCommandReady { command: command } => + match belt_exec(cmd: dispatch_unit_exec_argv(command: command, resolved_program: systemd_run), workdir: worktree) { + ExecOk { stdout: _ } => ReviewSpawned { node_id: node_id, criterion: call.id, unit: unit as String } + ExecFailed { program: _, exit_code, stderr } => ReviewFailed { node_id: node_id, criterion: call.id, detail: join(["the reviewer unit could not be started (exit ", to_string(exit_code), "): ", stderr], "") } + ExecRefused { program: _, reason } => ReviewFailed { node_id: node_id, criterion: call.id, detail: reason } + } } } } diff --git a/dag/gunbc/roadmap/roadmap_dispatch_actuator.dag b/dag/gunbc/roadmap/roadmap_dispatch_actuator.dag index f63d4d99ac1..22bcc879931 100644 --- a/dag/gunbc/roadmap/roadmap_dispatch_actuator.dag +++ b/dag/gunbc/roadmap/roadmap_dispatch_actuator.dag @@ -51,8 +51,17 @@ import extdeps.git { shape_git_worktree_add_argv, shape_git_worktree_add_detache import std.measure { ByteSize, byte_size_count, Kibibyte } import std.algebra { trim } import extdeps.systemd.systemctl -import extdeps.systemd { systemd_unit_property_wire, SystemdUnitProperty, LoadState, SubState, ExecMainStatus } -import extdeps.systemd.systemd_run { systemd_run_user_transient_arguments, systemd_run_property } +import extdeps.systemd { + systemd_unit_property_wire, SystemdUnitProperty, LoadState, SubState, ExecMainStatus, + MemoryMax, WorkingDirectoryProperty, StandardOutputProperty, StandardErrorProperty, + ProtectSystemProperty, ProtectHomeProperty, PrivateTmpProperty, ReadWritePathsProperty, + RemainAfterExitProperty, +} +import extdeps.exec.command { ArgvCommand } +import extdeps.systemd.systemd_run { + SystemdRunCommandReading, SystemdRunCommandReady, SystemdRunCommandRefused, + systemd_run_user_transient_command, systemd_run_command_argument_words, +} import gunbc.compute.unit_bounds { UnitProcessBounds, compute_grant_unit_properties } import extdeps.tmux { shape_tmux_new_session_argv, @@ -1191,17 +1200,17 @@ fn dispatch_review_unit_properties( working_directory: String, log_path: String, review_dir: String, -) -> List { +) -> List { [ - systemd_run_property(name: "MemoryMax", value: to_string(value: byte_size_count(instance.dispatch_worker_memory_max))), - systemd_run_property(name: "WorkingDirectory", value: working_directory), - systemd_run_property(name: "StandardOutput", value: join(["append:", log_path], "")), - systemd_run_property(name: "StandardError", value: join(["append:", log_path], "")), - systemd_run_property(name: "ProtectSystem", value: "strict"), - systemd_run_property(name: "ProtectHome", value: "read-only"), - systemd_run_property(name: "PrivateTmp", value: "yes"), - systemd_run_property(name: "ReadWritePaths", value: review_dir), - systemd_run_property(name: "RemainAfterExit", value: "yes"), + SystemdRunProperty { property: MemoryMax, value: to_string(value: byte_size_count(instance.dispatch_worker_memory_max)) as NonEmptyStr }, + SystemdRunProperty { property: WorkingDirectoryProperty, value: working_directory as NonEmptyStr }, + SystemdRunProperty { property: StandardOutputProperty, value: join(["append:", log_path], "") as NonEmptyStr }, + SystemdRunProperty { property: StandardErrorProperty, value: join(["append:", log_path], "") as NonEmptyStr }, + SystemdRunProperty { property: ProtectSystemProperty, value: "strict" as NonEmptyStr }, + SystemdRunProperty { property: ProtectHomeProperty, value: "read-only" as NonEmptyStr }, + SystemdRunProperty { property: PrivateTmpProperty, value: "yes" as NonEmptyStr }, + SystemdRunProperty { property: ReadWritePathsProperty, value: review_dir as NonEmptyStr }, + SystemdRunProperty { property: RemainAfterExitProperty, value: "yes" as NonEmptyStr }, ] } @@ -1243,8 +1252,16 @@ fn gunbc_harness_review_argv( // fleet ssh context's safe-segment law (gunbc.fleet_known_hosts_anchor fleet_ssh_attempt_identity) // demands of the attempt identity it consumes as a path segment -- the pre-fix reviewer built it // from the absolute verdict path and every lane's seat bind refused before any ssh leg. -fn dispatch_review_unit_argv( - systemd_run_program: FilePath, +// THE BELT LAYER RUNS THE RESOLVED PROGRAM, not the spelling: the instance toolchain declares where +// systemd-run lives, and that declaration is the only thing the invoking host is allowed to assert +// about an unobserved host's program location (extdeps.exec.command +// program_spelling_is_an_observation_claim). The option and command words are untouched -- they are +// the one projection's; only the head is substituted. +fn dispatch_unit_exec_argv(command: ArgvCommand, resolved_program: FilePath) -> HostExecArgv { + host_exec_argv(program: resolved_program as String, args: systemd_run_command_argument_words(command: command)) +} + +fn dispatch_review_unit_command( instance: HostDashboardInstance, node_id: RoadmapNodeId, attempt_key: String, @@ -1252,30 +1269,27 @@ fn dispatch_review_unit_argv( brief: String, head_sha: String, criterion_key: String, -) -> HostExecArgv { - host_exec_argv( - program: systemd_run_program as String, - args: systemd_run_user_transient_arguments( - unit: dispatch_review_unit_name(node_id: node_id as String, attempt_key: attempt_key, criterion_key: criterion_key), - properties: dispatch_review_unit_properties( - instance: instance, - working_directory: instance.repo_root as String, - log_path: dispatch_attempt_review_log_path_for_instance(instance: instance, node_id: node_id, attempt_key: attempt_key, criterion_key: criterion_key), - review_dir: dispatch_attempt_review_dir_for_instance(instance: instance, node_id: node_id, attempt_key: attempt_key), - ), - setenv_bindings: [], - command_argv: gunbc_harness_review_argv( - gunbc_program: instance.serve_binary as String, - repo_root: instance.repo_root as String, - worktree_path: worktree_path, - brief: brief, - timeout_program: instance.toolchain.timeout as String, - request_scratch_path: dispatch_attempt_review_request_scratch_path_for_instance(instance: instance, node_id: node_id, attempt_key: attempt_key, criterion_key: criterion_key), - events_path: dispatch_attempt_review_events_path_for_instance(instance: instance, node_id: node_id, attempt_key: attempt_key, criterion_key: criterion_key), - verdict_dir: dispatch_attempt_review_dir_for_instance(instance: instance, node_id: node_id, attempt_key: attempt_key), - verdict_name: dispatch_attempt_review_verdict_basename(head_sha: head_sha, criterion_key: criterion_key), - attempt_identity: join(["reviewer:", node_id as String, "-", attempt_key, "-", criterion_key], ""), - ), +) -> SystemdRunCommandReading { + systemd_run_user_transient_command( + unit: dispatch_review_unit_name(node_id: node_id as String, attempt_key: attempt_key, criterion_key: criterion_key), + properties: dispatch_review_unit_properties( + instance: instance, + working_directory: instance.repo_root as String, + log_path: dispatch_attempt_review_log_path_for_instance(instance: instance, node_id: node_id, attempt_key: attempt_key, criterion_key: criterion_key), + review_dir: dispatch_attempt_review_dir_for_instance(instance: instance, node_id: node_id, attempt_key: attempt_key), + ), + setenv_bindings: [], + command_argv: gunbc_harness_review_argv( + gunbc_program: instance.serve_binary as String, + repo_root: instance.repo_root as String, + worktree_path: worktree_path, + brief: brief, + timeout_program: instance.toolchain.timeout as String, + request_scratch_path: dispatch_attempt_review_request_scratch_path_for_instance(instance: instance, node_id: node_id, attempt_key: attempt_key, criterion_key: criterion_key), + events_path: dispatch_attempt_review_events_path_for_instance(instance: instance, node_id: node_id, attempt_key: attempt_key, criterion_key: criterion_key), + verdict_dir: dispatch_attempt_review_dir_for_instance(instance: instance, node_id: node_id, attempt_key: attempt_key), + verdict_name: dispatch_attempt_review_verdict_basename(head_sha: head_sha, criterion_key: criterion_key), + attempt_identity: join(["reviewer:", node_id as String, "-", attempt_key, "-", criterion_key], ""), ), ) } @@ -1302,44 +1316,40 @@ fn dispatch_audit_unit_name(node_id: String, attempt_key: String) -> NonEmptyStr data gunbc_harness_auditor_function: String = "harness_auditor_cli" -fn dispatch_audit_unit_argv( - systemd_run_program: FilePath, +fn dispatch_audit_unit_command( instance: HostDashboardInstance, node_id: RoadmapNodeId, attempt_key: String, worktree_path: String, brief: String, head_sha: String, -) -> HostExecArgv { +) -> SystemdRunCommandReading { let dir = dispatch_attempt_audit_dir_for_instance(instance: instance, node_id: node_id, attempt_key: attempt_key) - host_exec_argv( - program: systemd_run_program as String, - args: systemd_run_user_transient_arguments( - unit: dispatch_audit_unit_name(node_id: node_id as String, attempt_key: attempt_key), - properties: dispatch_review_unit_properties( - instance: instance, - working_directory: instance.repo_root as String, - log_path: join([dir, "/auditor-stdout.log"], ""), - review_dir: dir, - ), - setenv_bindings: [], - command_argv: [ - instance.serve_binary as String, - "run", - "--source-root", join([instance.repo_root as String, "/dag"], ""), - "--source-root", join([instance.repo_root as String, "/src/v2"], ""), - "--entry", join([instance.repo_root as String, "/", gunbc_harness_worker_entry], ""), - "--function", gunbc_harness_auditor_function, - "--arg", join(["worktree=", worktree_path], ""), - "--arg", join(["brief=", brief], ""), - "--arg", join(["timeout_program=", instance.toolchain.timeout as String], ""), - "--arg", join(["request_scratch_path=", dir, "/harness-request.json"], ""), - "--arg", join(["events_path=", dir, "/provider-events.jsonl"], ""), - "--arg", join(["verdict_dir=", dir], ""), - "--arg", join(["verdict_name=", dispatch_attempt_audit_verdict_basename(head_sha: head_sha)], ""), - "--arg", join(["attempt_identity=auditor:", node_id as String, "-", attempt_key], ""), - ], - ), + systemd_run_user_transient_command( + unit: dispatch_audit_unit_name(node_id: node_id as String, attempt_key: attempt_key), + properties: dispatch_review_unit_properties( + instance: instance, + working_directory: instance.repo_root as String, + log_path: join([dir, "/auditor-stdout.log"], ""), + review_dir: dir, + ), + setenv_bindings: [], + command_argv: [ + instance.serve_binary as String, + "run", + "--source-root", join([instance.repo_root as String, "/dag"], ""), + "--source-root", join([instance.repo_root as String, "/src/v2"], ""), + "--entry", join([instance.repo_root as String, "/", gunbc_harness_worker_entry], ""), + "--function", gunbc_harness_auditor_function, + "--arg", join(["worktree=", worktree_path], ""), + "--arg", join(["brief=", brief], ""), + "--arg", join(["timeout_program=", instance.toolchain.timeout as String], ""), + "--arg", join(["request_scratch_path=", dir, "/harness-request.json"], ""), + "--arg", join(["events_path=", dir, "/provider-events.jsonl"], ""), + "--arg", join(["verdict_dir=", dir], ""), + "--arg", join(["verdict_name=", dispatch_attempt_audit_verdict_basename(head_sha: head_sha)], ""), + "--arg", join(["attempt_identity=auditor:", node_id as String, "-", attempt_key], ""), + ], ) } @@ -1369,44 +1379,40 @@ data gunbc_harness_supervisor_function: String = "harness_supervisor_cli" // record convene at most one session -- a second convene over the same record names the same unit, // and the belt's liveness gate reads that. The worktree stays read-only to the session, as the // reviewer's is: the supervisor adjudicates the checkpoint; it does not edit it. -fn dispatch_supervisor_unit_argv( - systemd_run_program: FilePath, +fn dispatch_supervisor_unit_command( instance: HostDashboardInstance, node_id: RoadmapNodeId, attempt_key: String, turn: Int, worktree_path: String, brief: String, -) -> HostExecArgv { +) -> SystemdRunCommandReading { let dir = dispatch_attempt_supervisor_dir_for_instance(instance: instance, node_id: node_id, attempt_key: attempt_key) - host_exec_argv( - program: systemd_run_program as String, - args: systemd_run_user_transient_arguments( - unit: dispatch_supervisor_unit_name(node_id: node_id as String, attempt_key: attempt_key, turn: turn), - properties: dispatch_review_unit_properties( - instance: instance, - working_directory: instance.repo_root as String, - log_path: join([dir, "/supervisor-stdout.log"], ""), - review_dir: dir, - ), - setenv_bindings: [], - command_argv: [ - instance.serve_binary as String, - "run", - "--source-root", join([instance.repo_root as String, "/dag"], ""), - "--source-root", join([instance.repo_root as String, "/src/v2"], ""), - "--entry", join([instance.repo_root as String, "/", gunbc_harness_worker_entry], ""), - "--function", gunbc_harness_supervisor_function, - "--arg", join(["worktree=", worktree_path], ""), - "--arg", join(["brief=", brief], ""), - "--arg", join(["timeout_program=", instance.toolchain.timeout as String], ""), - "--arg", join(["request_scratch_path=", dir, "/harness-request.json"], ""), - "--arg", join(["events_path=", dir, "/provider-events.jsonl"], ""), - "--arg", join(["verdict_dir=", dir], ""), - "--arg", join(["verdict_name=", supervisor_verdict_basename as String], ""), - "--arg", join(["attempt_identity=", dispatch_supervisor_attempt_identity(node_id: node_id as String, attempt_key: attempt_key, turn: turn)], ""), - ], - ), + systemd_run_user_transient_command( + unit: dispatch_supervisor_unit_name(node_id: node_id as String, attempt_key: attempt_key, turn: turn), + properties: dispatch_review_unit_properties( + instance: instance, + working_directory: instance.repo_root as String, + log_path: join([dir, "/supervisor-stdout.log"], ""), + review_dir: dir, + ), + setenv_bindings: [], + command_argv: [ + instance.serve_binary as String, + "run", + "--source-root", join([instance.repo_root as String, "/dag"], ""), + "--source-root", join([instance.repo_root as String, "/src/v2"], ""), + "--entry", join([instance.repo_root as String, "/", gunbc_harness_worker_entry], ""), + "--function", gunbc_harness_supervisor_function, + "--arg", join(["worktree=", worktree_path], ""), + "--arg", join(["brief=", brief], ""), + "--arg", join(["timeout_program=", instance.toolchain.timeout as String], ""), + "--arg", join(["request_scratch_path=", dir, "/harness-request.json"], ""), + "--arg", join(["events_path=", dir, "/provider-events.jsonl"], ""), + "--arg", join(["verdict_dir=", dir], ""), + "--arg", join(["verdict_name=", supervisor_verdict_basename as String], ""), + "--arg", join(["attempt_identity=", dispatch_supervisor_attempt_identity(node_id: node_id as String, attempt_key: attempt_key, turn: turn)], ""), + ], ) } @@ -2272,17 +2278,17 @@ fn dispatch_worker_unit_properties( worker_log_path: String, granted: Kibibyte, process_bounds: UnitProcessBounds, -) -> List { +) -> List { concat( compute_grant_unit_properties(granted: granted, working_directory: working_directory, process_bounds: Present { value: process_bounds }), [ - systemd_run_property(name: "StandardOutput", value: join(["append:", worker_log_path], "")), - systemd_run_property(name: "StandardError", value: join(["append:", worker_log_path], "")), - systemd_run_property(name: "ProtectSystem", value: "strict"), - systemd_run_property(name: "ProtectHome", value: "read-only"), - systemd_run_property(name: "PrivateTmp", value: "yes"), - systemd_run_property(name: "ReadWritePaths", value: dispatch_worker_writable_paths(instance: instance)), - systemd_run_property(name: "RemainAfterExit", value: "yes"), + SystemdRunProperty { property: StandardOutputProperty, value: join(["append:", worker_log_path], "") as NonEmptyStr }, + SystemdRunProperty { property: StandardErrorProperty, value: join(["append:", worker_log_path], "") as NonEmptyStr }, + SystemdRunProperty { property: ProtectSystemProperty, value: "strict" as NonEmptyStr }, + SystemdRunProperty { property: ProtectHomeProperty, value: "read-only" as NonEmptyStr }, + SystemdRunProperty { property: PrivateTmpProperty, value: "yes" as NonEmptyStr }, + SystemdRunProperty { property: ReadWritePathsProperty, value: dispatch_worker_writable_paths(instance: instance) as NonEmptyStr }, + SystemdRunProperty { property: RemainAfterExitProperty, value: "yes" as NonEmptyStr }, ], ) } @@ -2323,7 +2329,7 @@ type DispatchWorkerUnitPlan { inner_argv: List } -fn dispatch_worker_unit_properties_for_plan(instance: HostDashboardInstance, plan: DispatchWorkerUnitPlan, granted: Kibibyte, process_bounds: UnitProcessBounds) -> List { +fn dispatch_worker_unit_properties_for_plan(instance: HostDashboardInstance, plan: DispatchWorkerUnitPlan, granted: Kibibyte, process_bounds: UnitProcessBounds) -> List { dispatch_worker_unit_properties( instance: instance, working_directory: plan.working_directory, @@ -2333,15 +2339,12 @@ fn dispatch_worker_unit_properties_for_plan(instance: HostDashboardInstance, pla ) } -fn dispatch_worker_unit_run(instance: HostDashboardInstance, plan: DispatchWorkerUnitPlan, granted: Kibibyte, process_bounds: UnitProcessBounds) -> HostExecArgv { - host_exec_argv( - program: plan.systemd_run_program as String, - args: systemd_run_user_transient_arguments( - unit: plan.unit, - properties: dispatch_worker_unit_properties_for_plan(instance: instance, plan: plan, granted: granted, process_bounds: process_bounds), - setenv_bindings: [], - command_argv: plan.inner_argv, - ), +fn dispatch_worker_unit_command(instance: HostDashboardInstance, plan: DispatchWorkerUnitPlan, granted: Kibibyte, process_bounds: UnitProcessBounds) -> SystemdRunCommandReading { + systemd_run_user_transient_command( + unit: plan.unit, + properties: dispatch_worker_unit_properties_for_plan(instance: instance, plan: plan, granted: granted, process_bounds: process_bounds), + setenv_bindings: [], + command_argv: plan.inner_argv, ) } diff --git a/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag b/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag index af392ec921d..7da72a4f754 100644 --- a/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag +++ b/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag @@ -68,7 +68,10 @@ import extdeps.tools.e2fsprogs { decode_dumpe2fs_superblock, Ext2SuperblockRead, Ext2SuperblockUnreadable, ext2_superblock_magic, } import extdeps.systemd.journalctl -import extdeps.systemd.systemd_run { systemd_run_property_argv, SystemdRunProperty } +import extdeps.exec.command { argv_words } +import extdeps.systemd.systemd_run { + SystemdRunProperty, SystemdRunCommandReady, SystemdRunCommandRefused, systemd_run_transient_unit_command, +} import gunbc.runner_microvm_lifecycle { CgroupSubtreeReadback, CgroupSubtreeEmpty, CgroupSubtreePopulated, CgroupSubtreeReadFailed, ResourceReadback, ResourceAbsent, ResourcePresent, ResourceUnreadable, @@ -1126,22 +1129,29 @@ fn launch_attempt(attempt: MicroVmAttempt, invocation_id: NonEmptyStr, plan: Att } } +// The projection refuses a word the wire cannot carry; there is no started unit to observe, so the +// refusal takes the same failure arm a failed start takes, carrying the refusal's reason. fn run_jailer_in_cell(attempt: MicroVmAttempt, invocation_id: NonEmptyStr, receipt: AttemptStagingReceipt) -> AttemptLaunch { let unit = runner_microvm_attempt_unit_name(attempt: attempt) - let started = systemd.SystemdRun.RunTransientRetained( + match systemd_run_transient_unit_command( unit: unit, - property_argv: systemd_run_property_argv(properties: [ + properties: [ runner_microvm_attempt_slice_property(attempt: attempt), runner_microvm_attempt_part_of_property(attempt: attempt), attempt_invocation_env_setting(invocation_id: invocation_id), - ]), + ], command_argv: concat([receipt.jailer.program.invocation as String], receipt.jailer.arguments), - ) - if started.success { - VmmStarted { unit: unit, receipt: receipt } - } else { - VmmStartFailed { unit: unit, detail: trim(s: started.stderr) } + ) { + SystemdRunCommandRefused { reason: why } => VmmStartFailed { unit: unit, detail: why } + SystemdRunCommandReady { command: command } => { + let started = systemd.SystemdRun.RunTransientRetained(command_argv: argv_words(command: command)) + if started.success { + VmmStarted { unit: unit, receipt: receipt } + } else { + VmmStartFailed { unit: unit, detail: trim(s: started.stderr) } + } + } } } diff --git a/dag/gunbc/runner/runner_throughput_qualification_route.dag b/dag/gunbc/runner/runner_throughput_qualification_route.dag index 916d8ec12e0..1c5b18222e5 100644 --- a/dag/gunbc/runner/runner_throughput_qualification_route.dag +++ b/dag/gunbc/runner/runner_throughput_qualification_route.dag @@ -7,7 +7,7 @@ import std.decl_ref { DeclarationRef, decl_ref } import product.placement_supply { HostIdentity } import gunbc.runner.runner_host_filtered_egress { EgressPolicyShape, runner_host_egress_policy } import extdeps.systemd { MemoryMax, MemoryHigh, MemorySwapMax, TasksMax, CPUWeight, CPUQuota } -import extdeps.systemd.systemd_run { SystemdRunProperty, systemd_run_transient_wait_unit_argv } +import extdeps.systemd.systemd_run { SystemdRunProperty, SystemdRunCommandReading, systemd_run_transient_wait_unit_command } import gunbc.fleet_intent_network { operator_host_mtcollins1 } import gunbc.runner_slot_allocation { runner_host_label, runner_label_set_union } import gunbc.runner_slot_desired { RunnerSlotDesired, RunnerSlotCpuQuota, CpuQuotaResolved, SlotCpuQuotaUnbounded, gunbc_runner_slot_desired } @@ -118,8 +118,8 @@ fn ephemeral_slot_properties(desired: RunnerSlotDesired) -> List) -> List { - systemd_run_transient_wait_unit_argv(unit: unit, properties: ephemeral_slot_properties(desired: desired), command_argv: command_argv) +fn ephemeral_slot_command(unit: NonEmptyStr, desired: RunnerSlotDesired, command_argv: List) -> SystemdRunCommandReading { + systemd_run_transient_wait_unit_command(unit: unit, properties: ephemeral_slot_properties(desired: desired), command_argv: command_argv) } // THE ATTEMPT LABEL IS IN runs-on, AND THAT IS THE WHOLE BINDING (gunbc.runner.runner_qualification_receipt diff --git a/dag/gunbc/systemd_run_transient.dag b/dag/gunbc/systemd_run_transient.dag index 25b6b8ebadd..7f10dcd318f 100644 --- a/dag/gunbc/systemd_run_transient.dag +++ b/dag/gunbc/systemd_run_transient.dag @@ -1,13 +1,13 @@ module gunbc.systemd_run_transient import std.types { String, NonEmptyStr, List, Bool, Int } -import extdeps.systemd.systemd_run +import extdeps.systemd.systemd_run { systemd_run_command_argument_words } +import extdeps.exec.command { ArgvCommand, argv_words } import gunbc.host_effect { HostEffectTransport, LocalShell, SshShell, EmitArtifactThenThinRun } import v2.std.operation_argv { ArgvMaterialization, ArgvMaterialized, ArgvMaterializationRefused, - operation_argv_bind_text, operation_argv_bind_text_list, operation_argv_cause_label, } @@ -34,29 +34,26 @@ type SystemdRunTransientWaitOutcome = SystemdRunTransientWaitCompleted { exit_code: Int, stdout: String, stderr: String } | SystemdRunTransientWaitTransportRefused { reason: String } -fn systemd_run_transient_operation_argv(unit: NonEmptyStr, properties: List, command_argv: List) -> ArgvMaterialization { +// THE BRIDGE CARRIES THE TYPED COMMAND, NOT ITS INGREDIENTS. The unit name and the properties used +// to arrive here as separate parameters and were re-rendered through systemd_run_property_argv — +// a second spelling of the same words, one projection downstream of the authority. They now arrive +// inside an ArgvCommand minted by the typed builders (systemd_run_transient_unit_command and +// siblings), and the bridge only turns that one projection into the words each transport runs. +fn systemd_run_transient_operation_argv(command: ArgvCommand) -> ArgvMaterialization { shell_materialize_operation_argv( systemd_run_transient_path, systemd_run_transient_service, systemd_run_transient_operation, - [ - operation_argv_bind_text(name: "unit", text: unit as String), - operation_argv_bind_text_list(name: "property_argv", items: systemd_run_property_argv(properties: properties)), - operation_argv_bind_text_list(name: "command_argv", items: command_argv), - ] + [operation_argv_bind_text_list(name: "command_argv", items: systemd_run_command_argument_words(command: command))] ) } -fn systemd_run_transient_wait_operation_argv(unit: NonEmptyStr, properties: List, command_argv: List) -> ArgvMaterialization { +fn systemd_run_transient_wait_operation_argv(command: ArgvCommand) -> ArgvMaterialization { shell_materialize_operation_argv( systemd_run_transient_path, systemd_run_transient_service, systemd_run_transient_wait_operation, - [ - operation_argv_bind_text(name: "unit", text: unit as String), - operation_argv_bind_text_list(name: "property_argv", items: systemd_run_property_argv(properties: properties)), - operation_argv_bind_text_list(name: "command_argv", items: command_argv), - ] + [operation_argv_bind_text_list(name: "command_argv", items: systemd_run_command_argument_words(command: command))] ) } @@ -68,8 +65,8 @@ fn systemd_run_transient_outcome_from_result(success: Bool, stdout: String, stde } } -fn systemd_run_transient_local(unit: NonEmptyStr, properties: List, command_argv: List) -> SystemdRunTransientOutcome { - let result = systemd.SystemdRun.RunTransient(unit: unit, property_argv: systemd_run_property_argv(properties: properties), command_argv: command_argv) +fn systemd_run_transient_local(command: ArgvCommand) -> SystemdRunTransientOutcome { + let result = systemd.SystemdRun.RunTransient(command_argv: argv_words(command: command)) systemd_run_transient_outcome_from_result(success: result.success, stdout: result.stdout, stderr: result.stderr) } @@ -88,9 +85,9 @@ fn systemd_run_transient_ssh_argv(host: NonEmptyStr, argv: List) -> Syst } } -fn systemd_run_transient_ssh(host: NonEmptyStr, unit: NonEmptyStr, properties: List, command_argv: List) -> SystemdRunTransientOutcome { - match systemd_run_transient_operation_argv(unit: unit, properties: properties, command_argv: command_argv) { - ArgvMaterialized { argv: argv } => systemd_run_transient_ssh_argv(host: host, argv: argv) +fn systemd_run_transient_ssh(host: NonEmptyStr, command: ArgvCommand) -> SystemdRunTransientOutcome { + match systemd_run_transient_operation_argv(command: command) { + ArgvMaterialized { argv: materialized } => systemd_run_transient_ssh_argv(host: host, argv: materialized) ArgvMaterializationRefused { at: _, cause: c } => SystemdRunTransientRefused { reason: operation_argv_cause_label(cause: c) } } @@ -99,14 +96,12 @@ fn systemd_run_transient_ssh(host: NonEmptyStr, unit: NonEmptyStr, properties: L fn systemd_run_transient_fleet_ssh( target: SshTarget, context: FleetSshExecutionContext, - unit: NonEmptyStr, - properties: List, - command_argv: List, + command: ArgvCommand, ) -> SystemdRunTransientOutcome { - match systemd_run_transient_operation_argv(unit: unit, properties: properties, command_argv: command_argv) { - ArgvMaterialized { argv: argv } => + match systemd_run_transient_operation_argv(command: command) { + ArgvMaterialized { argv: materialized } => systemd_run_transient_outcome_from_exec( - outcome: typed_argv_exec_over_fleet_ssh(target: target, context: context, argv: argv), + outcome: typed_argv_exec_over_fleet_ssh(target: target, context: context, argv: materialized), ) ArgvMaterializationRefused { at: _, cause: c } => SystemdRunTransientRefused { reason: operation_argv_cause_label(cause: c) } @@ -115,14 +110,12 @@ fn systemd_run_transient_fleet_ssh( fn systemd_run_transient_read( transport: HostEffectTransport, - unit: NonEmptyStr, - properties: List, - command_argv: List, + command: ArgvCommand, ) -> SystemdRunTransientOutcome { match transport { - LocalShell => systemd_run_transient_local(unit: unit, properties: properties, command_argv: command_argv) - SshShell { ssh_host: h } => systemd_run_transient_ssh(host: h, unit: unit, properties: properties, command_argv: command_argv) - FleetSsh { target: t, context: c } => systemd_run_transient_fleet_ssh(target: t, context: c, unit: unit, properties: properties, command_argv: command_argv) + LocalShell => systemd_run_transient_local(command: command) + SshShell { ssh_host: h } => systemd_run_transient_ssh(host: h, command: command) + FleetSsh { target: t, context: c } => systemd_run_transient_fleet_ssh(target: t, context: c, command: command) EmitArtifactThenThinRun { bootstrap: _, invocation: _ } => SystemdRunTransientRefused { reason: "systemd-run transient start refused EmitArtifactThenThinRun transport", @@ -130,16 +123,8 @@ fn systemd_run_transient_read( } } -fn systemd_run_transient_wait_local( - unit: NonEmptyStr, - properties: List, - command_argv: List, -) -> SystemdRunTransientWaitOutcome { - let result = systemd.SystemdRun.RunTransientAndWait( - unit: unit, - property_argv: systemd_run_property_argv(properties: properties), - command_argv: command_argv, - ) +fn systemd_run_transient_wait_local(command: ArgvCommand) -> SystemdRunTransientWaitOutcome { + let result = systemd.SystemdRun.RunTransientAndWait(command_argv: argv_words(command: command)) systemd_run_transient_wait_outcome_from_result( exit_code: result.exit_code, stdout: result.stdout, @@ -188,16 +173,10 @@ fn systemd_run_transient_wait_ssh_argv( fn systemd_run_transient_wait_ssh( host: NonEmptyStr, - unit: NonEmptyStr, - properties: List, - command_argv: List, + command: ArgvCommand, ) -> SystemdRunTransientWaitOutcome { - match systemd_run_transient_wait_operation_argv( - unit: unit, - properties: properties, - command_argv: command_argv, - ) { - ArgvMaterialized { argv: argv } => systemd_run_transient_wait_ssh_argv(host: host, argv: argv) + match systemd_run_transient_wait_operation_argv(command: command) { + ArgvMaterialized { argv: materialized } => systemd_run_transient_wait_ssh_argv(host: host, argv: materialized) ArgvMaterializationRefused { at: _, cause: cause } => SystemdRunTransientWaitTransportRefused { reason: operation_argv_cause_label(cause: cause) } } @@ -210,70 +189,49 @@ fn systemd_run_transient_wait_ssh( // integer from this result. fn systemd_run_transient_wait_read( transport: HostEffectTransport, - unit: NonEmptyStr, - properties: List, - command_argv: List, + command: ArgvCommand, ) -> SystemdRunTransientWaitOutcome { match transport { - LocalShell => systemd_run_transient_wait_local( - unit: unit, - properties: properties, - command_argv: command_argv, - ) - SshShell { ssh_host: host } => systemd_run_transient_wait_ssh( - host: host, - unit: unit, - properties: properties, - command_argv: command_argv, - ) - FleetSsh { target: _, context: _ } => SystemdRunTransientWaitTransportRefused { - reason: "systemd-run transient wait refused FleetSsh until exact remote status is distinguished from transport status", - } - EmitArtifactThenThinRun { bootstrap: _, invocation: _ } => SystemdRunTransientWaitTransportRefused { - reason: "systemd-run transient wait refused EmitArtifactThenThinRun transport", - } + LocalShell => systemd_run_transient_wait_local(command: command) + SshShell { ssh_host: h } => systemd_run_transient_wait_ssh(host: h, command: command) + FleetSsh { target: t, context: c } => + systemd_run_transient_wait_outcome_from_exec( + outcome: typed_argv_exec_over_fleet_ssh(target: t, context: c, argv: argv_words(command: command)), + ) + EmitArtifactThenThinRun { bootstrap: _, invocation: _ } => + SystemdRunTransientWaitTransportRefused { + reason: "systemd-run transient wait refused EmitArtifactThenThinRun transport", + } } } -// ARGV EQUALITY IS A QUESTION ABOUT TOKENS, AND join() ANSWERS A QUESTION ABOUT TEXT. -// The prior spelling compared join(materialized, " ") == join(authority, " "), which cannot tell -// ["--property=WorkingDirectory=/path with space"] from ["...=/path", "with", "space"]: one word -// carrying a space and three words collapse to the same string, so an accidental token split at -// exactly the seam that carries paths and property values was invisible. There is no zip or index -// in the list vocabulary to fold two lists in lockstep, so equality is established the way an -// encoding establishes it: cardinality must agree, and the words must be joined on a separator no -// word contains, which makes the joined form injective. A word containing the separator REFUSES -// rather than falling back to the ambiguous comparison -- an unusable encoding is an unanswerable -// question, not a passing one. -data argv_token_separator: String = "\n" - -fn argv_word_free_of_separator(words: List) -> Bool { - fold(words, init: true, f: (acc, w) => acc && !string_contains(s: w, pattern: argv_token_separator)) -} - +// Token comparison over words: exact and order-sensitive, and REFUSING any word that carries the +// token separator -- the comparison is injective only while no word contains it, and a word that +// does must refuse rather than compare (the split-words control pins that). fn argv_exact_token_equal(left: List, right: List) -> Bool { - list_length(items: left) == list_length(items: right) - && argv_word_free_of_separator(words: left) - && argv_word_free_of_separator(words: right) - && join(left, argv_token_separator) == join(right, argv_token_separator) + if any(left, w => string_contains(s: w, pattern: "\n")) || any(right, w => string_contains(s: w, pattern: "\n")) { + false + } else { + join(left, separator: "\n") == join(right, separator: "\n") + } } -// THE MATERIALIZED ARGV IS CHECKED AGAINST THE EXTDEPS AUTHORITY, not against a second hand-built -// copy of it. An earlier spelling rebuilt the launcher words here, so this predicate could only -// confirm that two local folds agreed; a property population was invisible to it. It now compares -// against systemd_run_transient_unit_argv, which is the same function the transport template must -// agree with, so a property that fails to reach the executed words goes red here. -fn systemd_run_transient_operation_argv_matches_authority(unit: NonEmptyStr, properties: List, command_argv: List) -> Bool { - let authority = systemd_run_transient_unit_argv(unit: unit, properties: properties, command_argv: command_argv) - match systemd_run_transient_operation_argv(unit: unit, properties: properties, command_argv: command_argv) { +// The transport template is declared, not re-derived, so this predicate now compares the +// materialized words against the words the caller's typed command projects: if the operation's +// declared transport and the authority's projection ever disagree about word order or content, it +// goes red here — the property population the old two-folds-agree check could not see is exactly +// what this join sees, because both sides now come from the one projection. +fn systemd_run_transient_operation_argv_matches_authority(command: ArgvCommand) -> Bool { + let authority = argv_words(command: command) + match systemd_run_transient_operation_argv(command: command) { ArgvMaterialized { argv: materialized } => argv_exact_token_equal(left: materialized, right: authority) ArgvMaterializationRefused { at: _, cause: _ } => false } } -fn systemd_run_transient_wait_operation_argv_matches_authority(unit: NonEmptyStr, properties: List, command_argv: List) -> Bool { - let authority = systemd_run_transient_wait_unit_argv(unit: unit, properties: properties, command_argv: command_argv) - match systemd_run_transient_wait_operation_argv(unit: unit, properties: properties, command_argv: command_argv) { +fn systemd_run_transient_wait_operation_argv_matches_authority(command: ArgvCommand) -> Bool { + let authority = argv_words(command: command) + match systemd_run_transient_wait_operation_argv(command: command) { ArgvMaterialized { argv: materialized } => argv_exact_token_equal(left: materialized, right: authority) ArgvMaterializationRefused { at: _, cause: _ } => false } diff --git a/dag/test/claim/compute/work_class_grant_witness_test.dag b/dag/test/claim/compute/work_class_grant_witness_test.dag index fceba08a4ad..5fb0af62fe1 100644 --- a/dag/test/claim/compute/work_class_grant_witness_test.dag +++ b/dag/test/claim/compute/work_class_grant_witness_test.dag @@ -2,8 +2,11 @@ module test.claim.compute.work_class_grant_witness_test import std.types { String, Bool, Int, List, NonEmptyStr, EpochSecs } import std.measure { Kibibyte, kibibyte, kibibyte_count, byte_size_count } +import extdeps.exec.command { argv_words } import extdeps.systemd.systemd_run { - SystemdRunWaitSummary, systemd_run_wait_summary, systemd_run_user_wait_arguments, systemd_run_transient_wait_unit_argv, + SystemdRunWaitSummary, systemd_run_wait_summary, + SystemdRunCommandReady, SystemdRunCommandRefused, + systemd_run_user_wait_command, systemd_run_transient_wait_unit_command, SystemdServiceResultReading, ServiceResultRead, ServiceResultUnrecognized, ServiceResultNotReported, SystemdServiceResult, ServiceResultOomKill, ServiceResultSuccess, ServiceResultExitCode, SystemdMemoryPeakReading, MemoryPeakRead, MemoryPeakUnparseable, MemoryPeakNotReported, @@ -41,7 +44,29 @@ import gunbc.compute.work_provider_local { // regression control. Supplying the launcher here is DESIGN 3's supplied input at one interface; // the real builder is exercised by the provider's own route (gunbc.compute.work_provider_local // compute_run_unit). -data fixture_launcher: SystemdSummaryPrintingWait = systemd_run_user_wait_arguments(unit: "gunbc-fixture", properties: [], setenv_bindings: [], command_argv: ["true"]) +// THE FIXTURE GOES THROUGH THE TYPED BUILDER, and its refused arm -- uninhabited for these inputs -- +// carries the summary a never-ran invocation legitimately has: NotReported, not a minted token. +// (The admit list on argv_command refuses test callers, so there is no direct mint to fall back on; +// the fold is the only route from the reading to a summary.) +fn fixture_summary(standing: SummaryFormatStanding, stderr: String) -> SystemdRunWaitSummary { + match systemd_run_user_wait_command(unit: "gunbc-fixture", properties: [], setenv_bindings: [], command_argv: ["true"]) { + SystemdRunCommandRefused { reason: _ } => + SystemdRunWaitSummary { result: ServiceResultNotReported { absence: SummaryNoInvocation }, memory_peak: MemoryPeakNotReported { absence: SummaryNoInvocation } } + SystemdRunCommandReady { command: command } => + systemd_run_wait_summary(launcher: SystemdSummaryPrintingWait { command: command }, standing: standing, stderr: stderr) + } +} + +// POSITIVE CONTROL FOR THE USER-WAIT CUTOVER: the typed builder's projected words are the words the +// string-built form this replaces rendered for the same arguments -- wait flags first, then the +// user-manager start, then the command after --. +test fn the_user_wait_builder_projects_the_words_the_string_form_rendered() -> Bool { + match systemd_run_user_wait_command(unit: "gunbc-fixture", properties: [], setenv_bindings: [], command_argv: ["true"]) { + SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandReady { command: command } => + argv_words(command: command) == ["systemd-run", "--wait", "--pipe", "--collect", "--user", "--unit=gunbc-fixture", "--", "true"] + } +} // THE FIXTURES ALSO NAME THE MANAGER THEY ARE PRETENDING CAME FROM, because the summary format is // version-keyed and the reader refuses a major it has not read. Grounded here so the claims below @@ -59,7 +84,7 @@ data nine_point_seven_gib_upper_bytes: Int = 10630044058 data nine_point_seven_gib_lower_bytes: Int = 10415295692 test fn the_units_peak_and_result_are_the_summarys_last_lines_not_the_commands() -> Bool { - let s = systemd_run_wait_summary(launcher: fixture_launcher, standing: grounded_standing, stderr: summary_oom_after_spoof) + let s = fixture_summary(standing: grounded_standing, stderr: summary_oom_after_spoof) compute_summary_is_oom_kill(summary: s) && match s.memory_peak { MemoryPeakRead { peak: b } => @@ -80,9 +105,9 @@ test fn the_units_peak_and_result_are_the_summarys_last_lines_not_the_commands() // with --quiet. Either must read NotReported, because a recorded zero would size a class at its // headroom alone. An exact small figure keeps its exact reading. test fn an_absent_peak_is_not_reported_and_a_byte_figure_is_exact() -> Bool { - let absent = systemd_run_wait_summary(launcher: fixture_launcher, standing: grounded_standing, stderr: "Finished with result: exit-code\nMain processes terminated with: code=exited/status=1\n") - let small = systemd_run_wait_summary(launcher: fixture_launcher, standing: grounded_standing, stderr: "Finished with result: success\nMemory peak: 512B\n") - let garbled = systemd_run_wait_summary(launcher: fixture_launcher, standing: grounded_standing, stderr: "Finished with result: frobnicated\nMemory peak: lots\n") + let absent = fixture_summary(standing: grounded_standing, stderr: "Finished with result: exit-code\nMain processes terminated with: code=exited/status=1\n") + let small = fixture_summary(standing: grounded_standing, stderr: "Finished with result: success\nMemory peak: 512B\n") + let garbled = fixture_summary(standing: grounded_standing, stderr: "Finished with result: frobnicated\nMemory peak: lots\n") !compute_summary_is_oom_kill(summary: absent) && match small.memory_peak { MemoryPeakRead { peak: b } => match b { FormattedBytesExact { bytes } => byte_size_count(b: bytes) == 512 _ => false } @@ -152,8 +177,8 @@ test fn with_the_shipped_rows_every_class_reserves_the_ceiling() -> Bool { test fn only_an_oom_kill_below_the_ceiling_retries_at_the_ceiling() -> Bool { let below = GrantSized { class: WorkClassCompile, amount: kibibyte(count: 11 * gib), samples: 3, peak_upper: kibibyte(count: 9 * gib), headroom: kibibyte(count: 2 * gib), headroom_authority: "fixture" as NonEmptyStr } let at = GrantAtCeiling { class: WorkClassCompile, amount: ceiling(), reason: ClassUnmeasured } - let oom = ComputeAttempt { outcome: WorkFailed { identity: "i", exit_code: 1, log_path: "" }, summary: systemd_run_wait_summary(launcher: fixture_launcher, standing: grounded_standing, stderr: summary_oom_after_spoof), gate: AttemptGateRefused { detail: "fixture: no attempt slot" } } - let clean = ComputeAttempt { outcome: WorkFailed { identity: "i", exit_code: 1, log_path: "" }, summary: systemd_run_wait_summary(launcher: fixture_launcher, standing: grounded_standing, stderr: "Finished with result: exit-code\nMemory peak: 3.1G\n"), gate: AttemptGateRefused { detail: "fixture: no attempt slot" } } + let oom = ComputeAttempt { outcome: WorkFailed { identity: "i", exit_code: 1, log_path: "" }, summary: fixture_summary(standing: grounded_standing, stderr: summary_oom_after_spoof), gate: AttemptGateRefused { detail: "fixture: no attempt slot" } } + let clean = ComputeAttempt { outcome: WorkFailed { identity: "i", exit_code: 1, log_path: "" }, summary: fixture_summary(standing: grounded_standing, stderr: "Finished with result: exit-code\nMemory peak: 3.1G\n"), gate: AttemptGateRefused { detail: "fixture: no attempt slot" } } compute_attempt_retries_at_ceiling(grant: below, attempt: oom) && !compute_attempt_retries_at_ceiling(grant: at, attempt: oom) && !compute_attempt_retries_at_ceiling(grant: below, attempt: clean) @@ -166,9 +191,9 @@ test fn only_an_oom_kill_below_the_ceiling_retries_at_the_ceiling() -> Bool { // No peak line, or no clock, takes no sample. test fn a_summary_becomes_a_typed_sample_at_its_upper_bound_or_no_sample() -> Bool { let op = CompileEntry { entry: "src/v2/compiler/compile.dag" } - let s = systemd_run_wait_summary(launcher: fixture_launcher, standing: grounded_standing, stderr: summary_oom_after_spoof) + let s = fixture_summary(standing: grounded_standing, stderr: summary_oom_after_spoof) let taken = work_class_peak_sample(summary: s, work_identity: "abc", op: op, host: "srv1", observed_at: Present { value: 1790000000 }) - let no_peak = work_class_peak_sample(summary: systemd_run_wait_summary(launcher: fixture_launcher, standing: grounded_standing, stderr: "Finished with result: success\n"), work_identity: "abc", op: op, host: "srv1", observed_at: Present { value: 1790000000 }) + let no_peak = work_class_peak_sample(summary: fixture_summary(standing: grounded_standing, stderr: "Finished with result: success\n"), work_identity: "abc", op: op, host: "srv1", observed_at: Present { value: 1790000000 }) let no_clock = work_class_peak_sample(summary: s, work_identity: "abc", op: op, host: "srv1", observed_at: none) match taken { PeakSampleTaken { class, sample } => @@ -233,9 +258,9 @@ data four_gib_lower_bytes: Int = 4294967296 data four_gib_upper_bytes: Int = 4509715661 test fn the_peak_is_unreported_by_absence_and_an_untrimmed_summary_still_reads() -> Bool { - let no_peak_line = systemd_run_wait_summary(launcher: fixture_launcher, standing: grounded_standing, stderr: "Finished with result: exit-code\nMain processes terminated with: code=exited/status=1\n") - let unit_output_only = systemd_run_wait_summary(launcher: fixture_launcher, standing: grounded_standing, stderr: "error: linking with `cc` failed\nrustc exited 1\n") - let trimmed = systemd_run_wait_summary(launcher: fixture_launcher, standing: grounded_standing, stderr: "Finished with result: success\nMemory peak: 4.0G") + let no_peak_line = fixture_summary(standing: grounded_standing, stderr: "Finished with result: exit-code\nMain processes terminated with: code=exited/status=1\n") + let unit_output_only = fixture_summary(standing: grounded_standing, stderr: "error: linking with `cc` failed\nrustc exited 1\n") + let trimmed = fixture_summary(standing: grounded_standing, stderr: "Finished with result: success\nMemory peak: 4.0G") match no_peak_line.memory_peak { MemoryPeakNotReported { absence } => match absence { SummaryLineAbsent => true _ => false } _ => false } && match unit_output_only.memory_peak { MemoryPeakNotReported { absence } => match absence { SummaryLineAbsent => true _ => false } _ => false } && match unit_output_only.result { ServiceResultNotReported { absence } => match absence { SummaryLineAbsent => true _ => false } _ => false } @@ -267,10 +292,20 @@ test fn the_peak_is_unreported_by_absence_and_an_untrimmed_summary_still_reads() // fact a Bool claim can carry. What a claim CAN carry is the fold: a token whose argv does contain // --quiet, constructed here directly because no builder will mint one, reports NotReported rather // than the peak sitting in the fixture. Deleting the check in systemd_run_wait_summary reds this. -data quiet_launcher: SystemdSummaryPrintingWait = SystemdSummaryPrintingWait { argv: systemd_run_transient_wait_unit_argv(unit: "gunbc-quiet", properties: [], command_argv: ["true"]) } +// THE QUIET LAUNCHER'S READING GOES THROUGH THE TYPED BUILDER that carries --quiet; the claim's +// refused arm is uninhabited for these inputs and carries NotReported, never a minted token (the +// admit list on argv_command refuses test callers, so there is no direct mint to fall back on). +fn quiet_summary(stderr: String) -> SystemdRunWaitSummary { + match systemd_run_transient_wait_unit_command(unit: "gunbc-quiet", properties: [], command_argv: ["true"]) { + SystemdRunCommandRefused { reason: _ } => + SystemdRunWaitSummary { result: ServiceResultNotReported { absence: SummaryNoInvocation }, memory_peak: MemoryPeakNotReported { absence: SummaryNoInvocation } } + SystemdRunCommandReady { command: command } => + systemd_run_wait_summary(launcher: SystemdSummaryPrintingWait { command: command }, standing: grounded_standing, stderr: stderr) + } +} test fn a_quiet_launcher_reports_no_peak_rather_than_the_one_in_the_text() -> Bool { - let s = systemd_run_wait_summary(launcher: quiet_launcher, standing: grounded_standing, stderr: summary_oom_after_spoof) + let s = quiet_summary(stderr: summary_oom_after_spoof) match s.memory_peak { MemoryPeakNotReported { absence } => match absence { SummarySuppressedByQuietLauncher => true _ => false } _ => false } && match s.result { ServiceResultNotReported { absence } => match absence { SummarySuppressedByQuietLauncher => true _ => false } _ => false } && !compute_summary_is_oom_kill(summary: s) @@ -294,7 +329,7 @@ test fn a_quiet_launcher_reports_no_peak_rather_than_the_one_in_the_text() -> Bo // SuppressedByQuietLauncher below. A collapse to any one cause now reds exactly the claims for the // OTHER causes, which is what makes the red legible. test fn an_empty_capture_is_reported_as_capture_empty_not_as_a_missing_line() -> Bool { - let empty = systemd_run_wait_summary(launcher: fixture_launcher, standing: grounded_standing, stderr: "") + let empty = fixture_summary(standing: grounded_standing, stderr: "") match empty.memory_peak { MemoryPeakNotReported { absence } => match absence { SummaryCaptureEmpty => true _ => false } _ => false } && match empty.result { ServiceResultNotReported { absence } => match absence { SummaryCaptureEmpty => true _ => false } _ => false } && !compute_summary_is_oom_kill(summary: empty) @@ -331,7 +366,7 @@ test fn a_grounded_major_is_the_only_one_that_parses() -> Bool { // consumer actually reads goes red, because the join is. test fn an_unread_major_refuses_instead_of_reading_its_figures() -> Bool { let newer = summary_format_standing(version_line: "systemd 257 (257.1-1)") - let s = systemd_run_wait_summary(launcher: fixture_launcher, standing: newer, stderr: summary_oom_after_spoof) + let s = fixture_summary(standing: newer, stderr: summary_oom_after_spoof) match newer { SummaryFormatUngroundedMajor { major } => major == 257 _ => false } && match s.memory_peak { MemoryPeakFormatNotGrounded { standing: _ } => true _ => false } && match s.result { ServiceResultFormatNotGrounded { standing: _ } => true _ => false } @@ -356,7 +391,7 @@ test fn an_unread_major_refuses_instead_of_reading_its_figures() -> Bool { // annotation cannot fully repair, because the annotation is read AFTER the decision to touch it. test fn the_grounding_recursion_terminates_because_an_unreadable_version_refuses() -> Bool { let unknown = summary_format_standing(version_line: "") - let s = systemd_run_wait_summary(launcher: fixture_launcher, standing: unknown, stderr: summary_oom_after_spoof) + let s = fixture_summary(standing: unknown, stderr: summary_oom_after_spoof) match unknown { SummaryFormatVersionUnreadable { line } => line == "" _ => false } && match s.memory_peak { MemoryPeakFormatNotGrounded { standing: _ } => true _ => false } && !compute_summary_is_oom_kill(summary: s) @@ -377,7 +412,7 @@ data ending_op: WorkOperation = CompileEntry { entry: "e" } fn outcome_for(stderr: String, standing: SummaryFormatStanding) -> WorkOutcome { compute_failed_unit_outcome( - summary: systemd_run_wait_summary(launcher: fixture_launcher, standing: standing, stderr: stderr), + summary: fixture_summary(standing: standing, stderr: stderr), identity: "abc", exit_code: 1, op: ending_op, granted: kibibyte(count: 1024), grant_was_ceiling: false, log_path: "/l", ) } diff --git a/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag b/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag index 4bdcd70830b..9750325fd4c 100644 --- a/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag +++ b/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag @@ -2,7 +2,14 @@ module test.claim.fabric.systemd_run_transient_wait_witness_test import std.types { Bool, List } import extdeps.systemd { WorkingDirectoryProperty, MemoryMax } -import extdeps.systemd.systemd_run { SystemdRunProperty, systemd_run_transient_wait_unit_argv } +import extdeps.exec.command { argv_words } +import extdeps.systemd.systemd_run { + SystemdRunProperty, SystemdRunCommandReady, SystemdRunCommandRefused, + SystemdRunOptionWordsProjected, SystemdRunOptionWordsRefused, + SystemdSetenvBinding, + RunUserManager, RunScope, Wait, Quiet, Pipe, Collect, EndOfOptions, SetEnv, + systemd_run_option_words, systemd_run_transient_wait_unit_command, systemd_run_user_wait_command, +} import gunbc.systemd_run_transient { systemd_run_transient_wait_operation_argv_matches_authority, systemd_run_transient_wait_outcome_from_result, @@ -16,25 +23,101 @@ data wait_properties: List = [ SystemdRunProperty { property: MemoryMax, value: "17179869184" }, ] -test fn systemd_wait_route_owns_wait_quiet_collect_and_preserves_tokens() -> Bool { - let argv = systemd_run_transient_wait_unit_argv( - unit: "fci1-bounded-driver.service", - properties: wait_properties, - command_argv: ["/bin/sh", "-c", "exit 86"], - ) - systemd_run_transient_wait_operation_argv_matches_authority( +// THE POSITIVE CONTROL FOR THE TYPED CUTOVER: the words the typed option model projects are +// byte-identical to the words the string-built builder this replaces rendered for the same +// arguments, including the space inside the working directory's value, and the operation's +// materialized transport words are the same words the typed command projects. +fn wait_control_command_reading() -> SystemdRunCommandReading { + systemd_run_transient_wait_unit_command( unit: "fci1-bounded-driver.service", properties: wait_properties, command_argv: ["/bin/sh", "-c", "exit 86"], ) - && argv_exact_token_equal( - left: argv, - right: [ - "systemd-run", "--unit=fci1-bounded-driver.service", "--wait", "--quiet", "--collect", - "--property=WorkingDirectory=/tmp/path with space", "--property=MemoryMax=17179869184", - "--", "/bin/sh", "-c", "exit 86", - ], - ) +} + +// THE POSITIVE CONTROL, WORDS HALF: the projection's words are the old builder's words, byte for byte. +test fn systemd_wait_route_projects_the_old_words() -> Bool { + match wait_control_command_reading() { + SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandReady { command: command } => + argv_exact_token_equal( + left: argv_words(command: command), + right: [ + "systemd-run", "--unit=fci1-bounded-driver.service", "--wait", "--quiet", "--collect", + "--property=WorkingDirectory=/tmp/path with space", "--property=MemoryMax=17179869184", + "--", "/bin/sh", "-c", "exit 86", + ], + ) + } +} + +// THE POSITIVE CONTROL, MATERIALIZATION HALF: the operation's declared transport materializes the +// same words the typed command projects. +test fn systemd_wait_route_materializes_the_projected_words() -> Bool { + match wait_control_command_reading() { + SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandReady { command: command } => + systemd_run_transient_wait_operation_argv_matches_authority(command: command) + } +} + +// THE RED WITNESS: a property value carrying a newline is REFUSED at the projection -- not trimmed, +// not split into two words, not passed through. The operation that would have run does not exist, +// and the reason names the wire fact the value violates. +test fn a_property_value_the_wire_cannot_carry_is_refused() -> Bool { + match systemd_run_transient_wait_unit_command( + unit: "fci1-refused.service", + properties: [SystemdRunProperty { property: WorkingDirectoryProperty, value: "/tmp/a\nb" as NonEmptyStr }], + command_argv: ["/bin/true"], + ) { + SystemdRunCommandRefused { reason: why } => string_contains(s: why, pattern: "one line") + SystemdRunCommandReady { command: _ } => false + } +} + +// A unit name is one line and never carries a slash (systemd.unit(5)); the projection refuses both. +test fn a_unit_name_the_wire_cannot_carry_is_refused() -> Bool { + match systemd_run_transient_wait_unit_command( + unit: "wrong/name" as NonEmptyStr, + properties: [], + command_argv: ["/bin/true"], + ) { + SystemdRunCommandRefused { reason: why } => string_contains(s: why, pattern: "never contains /") + SystemdRunCommandReady { command: _ } => false + } +} + +// --setenv= takes NAME=VALUE, so the NAME side carries no = of its own: a binding that would render +// an ambiguous word is refused, not passed to systemd-run to misparse. +test fn a_setenv_name_with_an_equals_is_refused() -> Bool { + match systemd_run_user_wait_command( + unit: "fci1-setenv.service", + properties: [], + setenv_bindings: [SystemdSetenvBinding { name: "A=b" as NonEmptyStr, value: "c" as NonEmptyStr }], + command_argv: ["/bin/true"], + ) { + SystemdRunCommandRefused { reason: why } => string_contains(s: why, pattern: "carries no = of its own") + SystemdRunCommandReady { command: _ } => false + } +} + +test fn a_setenv_binding_with_an_equals_in_the_name_is_refused() -> Bool { + match systemd_run_option_words(options: [SetEnv { binding: SystemdSetenvBinding { name: "A=b" as NonEmptyStr, value: "c" as NonEmptyStr } }]) { + SystemdRunOptionWordsRefused { reason: why } => string_contains(s: why, pattern: "carries no = of its own") + SystemdRunOptionWordsProjected { words: _ } => false + } +} + +// THE TYPED OPTION IS A SEALED SUM: there is no arm an unknown flag could ride. This fold walks the +// variants the module renders today and pins each one's man-page spelling at systemd 255; a variant +// added to the sum updates this fold or compilation fails, which is how an unknown option stays +// unwritable. +test fn every_option_variant_renders_its_man_page_spelling() -> Bool { + match systemd_run_option_words(options: [RunUserManager, RunScope, Wait, Quiet, Pipe, Collect, EndOfOptions]) { + SystemdRunOptionWordsRefused { reason: _ } => false + SystemdRunOptionWordsProjected { words: words } => + join(words, separator: " ") == "--user --scope --wait --quiet --pipe --collect --" + } } test fn systemd_wait_route_rejects_split_working_directory_token() -> Bool { @@ -54,3 +137,24 @@ test fn systemd_wait_normal_exit_86_remains_86() -> Bool { SystemdRunTransientWaitTransportRefused { reason: _ } => false } } + + +fn materialized_cardinality(reading: SystemdRunCommandReading) -> Int { + match reading { + SystemdRunCommandRefused { reason: _ } => -2 + SystemdRunCommandReady { command: command } => + match systemd_run_transient_wait_operation_argv(command: command) { + ArgvMaterialized { argv: words } => count(words) + ArgvMaterializationRefused { at: _, cause: _ } => -1 + } + } +} + +// CARDINALITY PROBES: exactly one of these passes, and which one names the materialized shape. +test fn probe_materialized_cardinality_11() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == 11 } +test fn probe_materialized_cardinality_1() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == 1 } +test fn probe_materialized_cardinality_10() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == 10 } +test fn probe_materialized_cardinality_refused() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == -1 } +test fn probe_materialized_cardinality_command_refused() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == -2 } +test fn probe_materialized_cardinality_0() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == 0 } + diff --git a/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag b/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag index ef6fbaebdce..8fd79336f74 100644 --- a/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag +++ b/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag @@ -5,6 +5,9 @@ import extdeps.filesystem.filesystem_io { filesystem_listing_observation, filesystem_file_observation, } +import extdeps.exec.command { argv_words } +import extdeps.systemd { MemoryMax, WorkingDirectoryProperty } +import extdeps.systemd.systemd_run { SystemdRunProperty, SystemdRunCommandReady, SystemdRunCommandRefused, systemd_run_transient_unit_command } import gunbc.host_effect_nbd_proxy_serve { BmcwebSessionTokenObservation, BmcwebSessionTokenUsable, @@ -216,11 +219,14 @@ test fn nbd_proxy_observe_port_local_shell_is_active_active_wires_through() -> B } test fn witness_systemd_run_transient_operation_argv_matches_authority() -> Bool { - systemd_run_transient_operation_argv_matches_authority( + match systemd_run_transient_unit_command( unit: "nbd-proxy-srv3-nbd-proxy-10809" as NonEmptyStr, properties: [], command_argv: ["nbdkit", "-p", "10809", "file", "/var/lib/gunbc/artifacts/x.iso"], - ) + ) { + SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandReady { command: command } => systemd_run_transient_operation_argv_matches_authority(command: command) + } } // THE EMPTY POPULATION ABOVE CANNOT SEE A PROPERTY THAT NEVER REACHES THE EXECUTED WORDS, which is @@ -229,11 +235,14 @@ test fn witness_systemd_run_transient_operation_argv_matches_authority() -> Bool // is launched unbounded. This control carries a real population so the materialized argv and the // extdeps authority must agree on the property word itself. test fn witness_systemd_run_transient_property_reaches_materialized_argv() -> Bool { - systemd_run_transient_operation_argv_matches_authority( + match systemd_run_transient_unit_command( unit: "nbd-proxy-srv3-nbd-proxy-10809" as NonEmptyStr, properties: [SystemdRunProperty { property: MemoryMax, value: "17179869184" as NonEmptyStr }], command_argv: ["nbdkit", "-p", "10809", "file", "/var/lib/gunbc/artifacts/x.iso"], - ) + ) { + SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandReady { command: command } => systemd_run_transient_operation_argv_matches_authority(command: command) + } } // THE WHITESPACE ARM IS THE ONE THE OLD COMPARISON COULD NOT SEE. join(a," ")==join(b," ") cannot @@ -242,11 +251,14 @@ test fn witness_systemd_run_transient_property_reaches_materialized_argv() -> Bo // such a value here means the control fails if argv agreement is ever weakened back to a text // comparison: the words still join to the same text, and only cardinality separates them. test fn witness_systemd_run_property_value_with_space_keeps_token_identity() -> Bool { - systemd_run_transient_operation_argv_matches_authority( + match systemd_run_transient_unit_command( unit: "nbd-proxy-srv3-nbd-proxy-10809" as NonEmptyStr, properties: [SystemdRunProperty { property: WorkingDirectoryProperty, value: "/srv/path with space" as NonEmptyStr }], command_argv: ["nbdkit", "-p", "10809"], - ) + ) { + SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandReady { command: command } => systemd_run_transient_operation_argv_matches_authority(command: command) + } } // THE ENCODING'S OWN PRECONDITION IS ASSERTED, not assumed. argv_exact_token_equal is injective @@ -261,13 +273,19 @@ test fn witness_argv_exact_token_equal_separates_split_words() -> Bool { // authority against one materialization; if the transport template silently dropped the property // words, BOTH sides would drop them together only if the authority also stopped rendering them -- // which this asserts it does not. The property word must be present in the authority's own output. +// POSITIVE CONTROL FOR THE CUTOVER: the wire name reaches the projected words untransformed, in +// position, in the non-wait start the nbd proxy's serve path actually runs. test fn witness_systemd_run_property_argv_renders_the_wire_name() -> Bool { - join( - systemd_run_property_argv( - properties: [SystemdRunProperty { property: MemoryMax, value: "17179869184" as NonEmptyStr }], - ), - " ", - ) == "--property=MemoryMax=17179869184" + match systemd_run_transient_unit_command( + unit: "nbd-proxy-witness.service", + properties: [SystemdRunProperty { property: MemoryMax, value: "17179869184" as NonEmptyStr }], + command_argv: ["/bin/true"], + ) { + SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandReady { command: command } => + join(argv_words(command: command), " ") + == "systemd-run --unit=nbd-proxy-witness.service --property=MemoryMax=17179869184 --collect -- /bin/true" + } } test fn witness_systemctl_stop_operation_argv_matches_authority() -> Bool { diff --git a/dag/test/claim/roadmap/dispatch_worker_confinement_witness_test.dag b/dag/test/claim/roadmap/dispatch_worker_confinement_witness_test.dag index 00af84b6015..70241f2e6f0 100644 --- a/dag/test/claim/roadmap/dispatch_worker_confinement_witness_test.dag +++ b/dag/test/claim/roadmap/dispatch_worker_confinement_witness_test.dag @@ -2,13 +2,21 @@ module test.claim.roadmap.dispatch_worker_confinement_witness_test import std.types { Bool, String, List } import gunbc.roadmap_dispatch_actuator { dispatch_worker_unit_properties, attempt_state_prepare_argv_for_instance, dispatch_attempt_verification_dir_for_instance, attempt_unit_substate_has_exited, dispatch_review_unit_properties } +import extdeps.systemd { SystemdUnitProperty, systemd_unit_property_wire } +import extdeps.systemd.systemd_run { SystemdRunProperty } import gunbc.roadmap_model { RoadmapNodeId } import std.measure { kibibyte } import gunbc.roadmap_session_placement { session_process_bounds } import gunbc.roadmap_dashboard_instance { srv2_deploy_dashboard_instance } -fn has_property(props: List, needle: String) -> Bool { - any(props, p => string_contains(s: p, pattern: needle)) +// The claims are about WHICH SETTINGS the unit binds; rendering the typed properties back to their +// wire words here is a test observation, not a builder -- nothing hands these words to systemd-run. +fn property_words(props: List) -> List { + props |> map(p => concat(systemd_unit_property_wire(property: p.property) as String, concat("=", p.value as String))) +} + +fn has_property(props: List, needle: String) -> Bool { + any(property_words(props: props), p => string_contains(s: p, pattern: needle)) } test fn the_worker_unit_confines_writes_to_the_instance_root() -> Bool { diff --git a/dag/test/claim/roadmap/roadmap_dispatch_actuator_witness_test.dag b/dag/test/claim/roadmap/roadmap_dispatch_actuator_witness_test.dag index efe4c838000..532eb3840e6 100644 --- a/dag/test/claim/roadmap/roadmap_dispatch_actuator_witness_test.dag +++ b/dag/test/claim/roadmap/roadmap_dispatch_actuator_witness_test.dag @@ -88,7 +88,7 @@ import gunbc.roadmap_dispatch_actuator { dispatch_brief_fields_for_environment, dispatch_brief_with_centering, dispatch_continuation_alignment_preamble, - dispatch_supervisor_unit_argv, dispatch_supervisor_unit_name, dispatch_supervisor_attempt_identity, + dispatch_supervisor_unit_command, dispatch_unit_exec_argv, dispatch_supervisor_unit_name, dispatch_supervisor_attempt_identity, DispatchSpawnCommands, HostExecArgv, SystemdUnitContainer, @@ -119,8 +119,10 @@ import gunbc.roadmap_dispatch_actuator { SpawnSession, dispatch_current_attempt_events_projection_argv_for_instance, gunbc_harness_review_argv, - dispatch_review_unit_argv, + dispatch_review_unit_command, } +import extdeps.exec.command { argv_words } +import extdeps.systemd.systemd_run { SystemdRunCommandReady, SystemdRunCommandRefused } import gunbc.dispatch_selection { dispatch_selection_standing_ineligible_reason, default_provider_selection_request, @@ -1159,14 +1161,14 @@ test fn witness_review_argv_carries_spawner_minted_attempt_identity() -> Bool { string_contains(s: join(argv, separator: "\0"), pattern: "\0--arg\0attempt_identity=reviewer:node-1-att-20260920-c") } -// THE SPAWN SITE, not just the argv row: dispatch_review_unit_argv is where the mint lives, and it -// is the fold the belt's review pass actually spawns through. This witness fails if a future edit -// re-drops the argument between the unit fold and the argv row, which compilation alone would not -// catch once the parameter exists and could be ignored. +// THE SPAWN SITE, not just the argv row: dispatch_review_unit_command is where the mint lives, and +// dispatch_unit_exec_argv is the fold the belt's review pass actually spawns through (the resolved +// program head, the projected option and command words after it). This witness fails if a future +// edit re-drops the argument between the unit fold and the argv row, which compilation alone would +// not catch once the parameter exists and could be ignored. test fn witness_review_unit_argv_mints_per_criterion_attempt_identity() -> Bool { - let cmd = dispatch_review_unit_argv( - systemd_run_program: "/usr/bin/systemd-run" as FilePath, + match dispatch_review_unit_command( instance: srv1_lab_dashboard_instance(), node_id: roadmap_dispatch_actuator_nid(s: "node-1"), attempt_key: "att-20260920", @@ -1174,8 +1176,37 @@ test fn witness_review_unit_argv_mints_per_criterion_attempt_identity() -> Bool brief: "brief", head_sha: "abc123", criterion_key: "conformance-external-facts", - ) - string_contains(s: join(cmd.args, separator: "\0"), pattern: "attempt_identity=reviewer:node-1-att-20260920-conformance-external-facts") + ) { + SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandReady { command: command } => { + let cmd = dispatch_unit_exec_argv(command: command, resolved_program: "/usr/bin/systemd-run" as FilePath) + string_contains(s: join(cmd.args, separator: "\0"), pattern: "attempt_identity=reviewer:node-1-att-20260920-conformance-external-facts") + } + } +} + +// POSITIVE CONTROL FOR THE CUTOVER: the projected option words for this caller are the words the +// string-built form rendered -- user manager, unit, one -p pair per property, then the command. +test fn witness_review_unit_option_words_are_the_string_form_words() -> Bool { + let lab = srv1_lab_dashboard_instance() + match dispatch_review_unit_command( + instance: lab, + node_id: roadmap_dispatch_actuator_nid(s: "node-1"), + attempt_key: "att-20260920", + worktree_path: "/opt/gunbc/dispatch-worktrees/node-1", + brief: "brief", + head_sha: "abc123", + criterion_key: "conformance-external-facts", + ) { + SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandReady { command: command } => { + let wire = join(argv_words(command: command), separator: "\0") + string_contains( + s: wire, + pattern: concat("systemd-run\0--user\0--unit=gunbc-review-node-1-att-20260920-conformance-external-facts.service\0-p\0MemoryMax=", to_string(value: byte_size_count(lab.dispatch_worker_memory_max)), "\0-p\0WorkingDirectory=", lab.repo_root as String, "\0-p\0"), + ) + } + } } // THE ROW THAT WOULD HAVE CAUGHT THE ORIGINAL BUG: an attempt identity built from a path-bearing @@ -1353,16 +1384,23 @@ test fn an_unresolved_alignment_refuses_the_real_spawn_fold_with_zero_commands() // reads. The session invokes the supervisor's own CLI function, never the worker's. test fn witness_supervisor_unit_argv_shapes_one_session_per_escalation_record() -> Bool { let lab = srv1_lab_dashboard_instance() - let argv = dispatch_supervisor_unit_argv( - systemd_run_program: "/usr/bin/systemd-run" as FilePath, + match dispatch_supervisor_unit_command( instance: lab, node_id: roadmap_dispatch_actuator_nid(s: "shell-dag-cron-entry-line-builder"), attempt_key: "cd172fe81b806160", turn: 3, worktree_path: "/x/attempts/wt", brief: "BRIEF-MARKER", - ) - let wire = join(argv.args, separator: "\0") + ) { + SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandReady { command: command } => { + let cmd = dispatch_unit_exec_argv(command: command, resolved_program: "/usr/bin/systemd-run" as FilePath) + supervisor_wire_checks(wire: join(cmd.args, separator: "\0")) + } + } +} + +fn supervisor_wire_checks(wire: String) -> Bool { wire.contains("gunbc-supervisor-shell-dag-cron-entry-line-builder-cd172fe81b806160-t3.service") && wire.contains("\0--function\0harness_supervisor_cli\0") && wire.contains("\0worktree=/x/attempts/wt\0") diff --git a/dag/test/claim/roadmap/session_placement_witness_test.dag b/dag/test/claim/roadmap/session_placement_witness_test.dag index 883a0ce527d..633db54e91a 100644 --- a/dag/test/claim/roadmap/session_placement_witness_test.dag +++ b/dag/test/claim/roadmap/session_placement_witness_test.dag @@ -17,6 +17,9 @@ import gunbc.compute.attempt_lifecycle { } import gunbc.roadmap_dashboard_instance { srv2_deploy_dashboard_instance } import gunbc.roadmap_dispatch_actuator { dispatch_worker_unit_properties } +import extdeps.systemd { systemd_unit_property_wire } +import extdeps.systemd.systemd_run { SystemdRunProperty } + import gunbc.roadmap_session_placement { SessionPlacementGrant, SessionPlacementStores, session_process_bounds, session_unit, session_attempt_record, session_placement_record_of, session_placement_record_json, session_placement_record_write, @@ -43,6 +46,11 @@ import extdeps.languages.json.emit { serialize_json } // The inhabitance claim: the bytes the real writer produces (session_placement_record_write) are what // the real reader and the real settlement read back as this attempt's grant. +// The claims are about WHICH SETTINGS the unit binds; rendering the typed properties back to their +// wire words here is a test observation, not a builder -- nothing hands these words to systemd-run. +fn property_words(props: List) -> List { + props |> map(p => concat(systemd_unit_property_wire(property: p.property) as String, concat("=", p.value as String))) +} data node_a: String = "g5-node-a" data key_a: String = "feedfacefeedface" data node_b: String = "g5-node-b" @@ -215,8 +223,8 @@ test fn settlement_releases_only_a_closed_window_or_an_ended_unit() -> Bool { // same fold, carries no process bounds. test fn the_worker_unit_is_capped_at_its_grant_by_the_compute_fold() -> Bool { let inst = srv2_deploy_dashboard_instance() - let props = dispatch_worker_unit_properties(instance: inst, working_directory: "/wt", worker_log_path: "/wt/log", granted: kibibyte(count: 1024), process_bounds: session_process_bounds) - let compute = compute_unit_properties(layout: compute_layout(instance: inst, identity_hex: "abc"), granted: kibibyte(count: 1024)) + let props = property_words(props: dispatch_worker_unit_properties(instance: inst, working_directory: "/wt", worker_log_path: "/wt/log", granted: kibibyte(count: 1024), process_bounds: session_process_bounds)) + let compute = property_words(props: compute_unit_properties(layout: compute_layout(instance: inst, identity_hex: "abc"), granted: kibibyte(count: 1024))) contains(props, "MemoryMax=1048576") && contains(props, "CPUQuota=800%") && contains(props, "TasksMax=4096") diff --git a/dag/test/claim/runner/runner_microvm_slot_controller_witness_test.dag b/dag/test/claim/runner/runner_microvm_slot_controller_witness_test.dag index 0aff5b7eb4d..6923ec636ae 100644 --- a/dag/test/claim/runner/runner_microvm_slot_controller_witness_test.dag +++ b/dag/test/claim/runner/runner_microvm_slot_controller_witness_test.dag @@ -1,5 +1,6 @@ module test.claim.runner.runner_microvm_slot_controller_witness_test +import std.algebra { list_append } import std.types { String, NonEmptyStr, Bool, Int, List } import std.nat { Nat } import std.measure { byte_size_count, byte_size, gibibyte, gibibyte_to_byte_size, second_count } @@ -26,7 +27,10 @@ import gunbc.runner_microvm_attempt { MicroVmAttempt, microvm_attempt, MicroVmAttemptAccepted, MicroVmAttemptRefused, runner_microvm_attempt_slice_property, runner_microvm_attempt_part_of_property, } -import extdeps.systemd.systemd_run { systemd_run_property_argv } +import extdeps.exec.command { argv_words } +import extdeps.systemd.systemd_run { + SystemdRunCommandReady, SystemdRunCommandRefused, systemd_run_transient_unit_command, +} import gunbc.runner_microvm_shakedown { microvm_shakedown_work, microvm_shakedown_demand, microvm_shakedown_offer } import gunbc.runner_attempt_launch { ReservationAuthorization, ReservationAuthorizes, ReservationArmRequiresInternalFleetTrust, @@ -322,10 +326,27 @@ test fn only_a_delivered_mint_carries_a_registration_into_the_controller() -> Bo // what makes the control-group kill insufficient, so a future edit that dropped Slice would make // PartOf redundant rather than load-bearing, and one that dropped PartOf leaks the guest. fn attempt_launch_property_words(attempt: MicroVmAttempt) -> List { - systemd_run_property_argv(properties: [ - runner_microvm_attempt_slice_property(attempt: attempt), - runner_microvm_attempt_part_of_property(attempt: attempt), - ]) + match systemd_run_transient_unit_command( + unit: "w-42.service", + properties: [ + runner_microvm_attempt_slice_property(attempt: attempt), + runner_microvm_attempt_part_of_property(attempt: attempt), + ], + command_argv: [], + ) { + SystemdRunCommandRefused { reason: _ } => [] + SystemdRunCommandReady { command: command } => property_words_of(argv: argv_words(command: command)) + } +} + +fn property_words_of(argv: List) -> List { + argv |> fold([], (acc, w) => + if substring(s: w, start: 0, end: 11) == "--property=" { + list_append(left: acc, right: [w]) + } else { + acc + } + ) } test fn the_attempt_unit_is_bound_to_the_slot_unit_that_supervises_it() -> Bool { diff --git a/dag/test/claim/runner/runner_throughput_qualification_witness_test.dag b/dag/test/claim/runner/runner_throughput_qualification_witness_test.dag index 0f3cb2b6a65..9345c8650ac 100644 --- a/dag/test/claim/runner/runner_throughput_qualification_witness_test.dag +++ b/dag/test/claim/runner/runner_throughput_qualification_witness_test.dag @@ -11,6 +11,8 @@ import std.measure { } import std.decl_ref { decl_ref } import extdeps.currency.currency { Usd } +import extdeps.exec.command { argv_words } +import extdeps.systemd.systemd_run { SystemdRunCommandReading, SystemdRunCommandReady, SystemdRunCommandRefused } import extdeps.toolchain.types { Architecture, Aarch64, X86_64 } import product.placement_supply { HostIdentity } import product.fabric.work { ControlPlaneCapacity, CustomerExecutableCapacity, Shape, HardRequirements } @@ -74,7 +76,7 @@ import gunbc.runner_throughput_qualification_route { QualificationStage, BootHostUnderApprovalGate, RegisterEphemeralRunnerSlot, DispatchFloorToSlot, CollectInstruments, DeregisterRunnerSlot, BmcWrite, GitHubControlPlaneWrite, ObservationOnly, stage_effect, stage_egress, - mtcollins1_qualification_route, ephemeral_slot_argv, ephemeral_slot_labels, ephemeral_slot_unit, + mtcollins1_qualification_route, ephemeral_slot_command, ephemeral_slot_labels, ephemeral_slot_unit, route_bmc_writes_are_gated, route_bmc_write_count, route_control_plane_write_count, route_effectful_stage_count, route_is_executable, route_registers_one_slot, route_deregisters_what_it_registered, route_dispatch_selector_names_the_attempt, @@ -842,14 +844,24 @@ test fn the_collect_stage_owes_the_rate_specimen_and_the_cgroup_rows() -> Bool { // THE EPHEMERAL SLOT CARRIES THE FLEET SLOT SHAPE, spelled through the systemd-run authority; under // SlotCpuQuotaUnbounded no CPUQuota word is emitted, which is the transient unit's own "no ceiling". test fn the_ephemeral_slot_argv_carries_the_fleet_memory_max() -> Bool { - let argv = ephemeral_slot_argv(unit: slot_unit(), desired: gunbc_runner_slot_desired(), command_argv: ["/mnt/runner/run.sh"]) - let has_quota_word = argv |> any(w => string_contains(s: w, pattern: "--property=CPUQuota=")) - (argv |> any(w => w == concat("--property=MemoryMax=", byte_size_count(b: gunbc_runner_slot_desired().memory_max) as String))) - && (match gunbc_runner_slot_desired().cpu_quota { - SlotCpuQuotaUnbounded => !has_quota_word - CpuQuotaResolved { threads: t } => - argv |> any(w => w == concat("--property=CPUQuota=", concat((hardware_thread_count_value(t: t) * 100) as String, "%"))) - }) + let reading = ephemeral_slot_command(unit: slot_unit(), desired: gunbc_runner_slot_desired(), command_argv: ["/mnt/runner/run.sh"]) + ephemeral_slot_words_carry_fleet_shape(reading: reading) +} + +fn ephemeral_slot_words_carry_fleet_shape(reading: SystemdRunCommandReading) -> Bool { + match reading { + SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandReady { command: command } => { + let argv = argv_words(command: command) + let has_quota_word = argv |> any(w => string_contains(s: w, pattern: "--property=CPUQuota=")) + (argv |> any(w => w == concat("--property=MemoryMax=", byte_size_count(b: gunbc_runner_slot_desired().memory_max) as String))) + && (match gunbc_runner_slot_desired().cpu_quota { + SlotCpuQuotaUnbounded => !has_quota_word + CpuQuotaResolved { threads: t } => + argv |> any(w => w == concat("--property=CPUQuota=", concat((hardware_thread_count_value(t: t) * 100) as String, "%"))) + }) + } + } } // THE CLASS LABEL IS READ OFF THE SLOT AUTHORITY, not written here (review 68972 -- the class this diff --git a/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag b/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag index 685327a42e7..379b04ffc82 100644 --- a/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag +++ b/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag @@ -6,7 +6,10 @@ import std.resources { Network, Filesystem } import std.measure { Second, second } import extdeps.tools.sleep { sleep_delay_seconds_second_carrier_projection } import extdeps.systemd { SliceProperty, SystemdUnitProperty, ActiveState, systemd_unit_property_wire, SystemdSliceCgroupPath, SliceCgroupPath, SliceNameNotASlice, systemd_slice_cgroup_path } -import extdeps.systemd.systemd_run { SystemdRunProperty, systemd_run_property_argv } +import extdeps.exec.command { argv_words } +import extdeps.systemd.systemd_run { + SystemdRunProperty, SystemdRunCommandReady, SystemdRunCommandRefused, systemd_run_transient_unit_command, +} import extdeps.linux.cgroup_v2 { cgroup_v2_mount_point, cgroup_v2_child_path } import gunbc.runner_microvm_lifecycle { @@ -66,11 +69,14 @@ fn wet_slice_property() -> SystemdRunProperty { fn start_payload(unit: NonEmptyStr, argv: List) -> Bool uses net: Network { - systemd.SystemdRun.RunTransient( - unit: unit, - property_argv: systemd_run_property_argv(properties: [wet_slice_property()]), - command_argv: argv, - ).success + match systemd_run_transient_unit_command(unit: unit, properties: [wet_slice_property()], command_argv: argv) { + SystemdRunCommandRefused { reason: why } => { + let _ = why + false + } + SystemdRunCommandReady { command: command } => + systemd.SystemdRun.RunTransient(command_argv: argv_words(command: command)).success + } } // EVERY CLAIM TEARS ITS OWN SLICE DOWN FIRST AND THEN WAITS FOR IT TO BE OBSERVABLY EMPTY. @@ -402,14 +408,25 @@ fn incarnation_unit() -> NonEmptyStr { fn start_with_invocation(unit: NonEmptyStr, invocation: String, sleep_for: String) -> Bool uses net: Network { - systemd.SystemdRun.RunTransientRetained( + start_retained_payload( unit: unit, - property_argv: systemd_run_property_argv(properties: [ + properties: [ wet_slice_property(), attempt_invocation_env_setting(invocation_id: invocation as NonEmptyStr), - ]), + ], command_argv: ["/bin/sleep", sleep_for], - ).success + ) +} + +fn start_retained_payload(unit: NonEmptyStr, properties: List, command_argv: List) -> Bool { + match systemd_run_transient_unit_command(unit: unit, properties: properties, command_argv: command_argv) { + SystemdRunCommandRefused { reason: why } => { + let _ = why + false + } + SystemdRunCommandReady { command: command } => + systemd.SystemdRun.RunTransientRetained(command_argv: argv_words(command: command)).success + } } // THE STOP TAKES THE FIRST READING AS AN ARGUMENT SO IT CANNOT BE HOISTED ABOVE IT. Order in this From 2ab5f7c0464c0c04ab54213867aef0b1414c6fea Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Sun, 4 Oct 2026 11:32:48 +0000 Subject: [PATCH 03/65] floor-union fixes for the typed cutover: enrolment witness migrates to the reading; the property-overlap fold walks the widened enum --- .../systemd_property_directive_overlap.dag | 7 ++++ ...val_device_enrolment_code_witness_test.dag | 38 ++++++++++++++----- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/dag/gunbc/systemd_property_directive_overlap.dag b/dag/gunbc/systemd_property_directive_overlap.dag index 37b81d75222..f5dd1a8682f 100644 --- a/dag/gunbc/systemd_property_directive_overlap.dag +++ b/dag/gunbc/systemd_property_directive_overlap.dag @@ -149,6 +149,13 @@ fn systemd_property_writability(property: SystemdUnitProperty) -> SystemdPropert IdProperty => ObservationOnly KillModeProperty => SettableDirective ExitTypeProperty => SettableDirective + StandardOutputProperty => SettableDirective + StandardErrorProperty => SettableDirective + ProtectSystemProperty => SettableDirective + ProtectHomeProperty => SettableDirective + PrivateTmpProperty => SettableDirective + ReadWritePathsProperty => SettableDirective + RemainAfterExitProperty => SettableDirective EnvironmentProperty => SettableDirective EnvironmentFilesProperty => ObservationOnly PassEnvironmentProperty => SettableDirective diff --git a/dag/test/claim/approval_device_enrolment_code_witness_test.dag b/dag/test/claim/approval_device_enrolment_code_witness_test.dag index 80e7c938302..b2a94bcfae5 100644 --- a/dag/test/claim/approval_device_enrolment_code_witness_test.dag +++ b/dag/test/claim/approval_device_enrolment_code_witness_test.dag @@ -17,7 +17,8 @@ import gunbc.auth.approval_ntfy_publish { hermetic_ntfy_publish_message_id } import gunbc.auth.approval_device_redemption { observe_enrolment_slot, EnrolmentCodeUnspent } import extdeps.shell import gunbc.auth.approval_notification { approval_ntfy_topic } -import gunbc.auth.approval_device_enrolment_code_issue { enrolment_code_issue_remote_argv, enrolment_code_issue_refuses_off_srv1 } +import extdeps.systemd.systemd_run { SystemdRunCommandReady, SystemdRunCommandRefused } +import gunbc.auth.approval_device_enrolment_code_issue { enrolment_code_issue_remote_command, sudo_elevate_words, enrolment_code_issue_refuses_off_srv1 } import gunbc.live_deploy.release_locus { RevisionBoundAtEmission, } @@ -75,15 +76,34 @@ test fn the_delivered_receipt_comes_from_the_published_message_and_its_line_neve // verb could not read the group-gated ntfy publisher token (run 37098981121); restoring them turns // this claim red. test fn the_remote_argv_runs_the_release_verb_as_the_operator_user() -> Bool { - let argv = enrolment_code_issue_remote_argv(revision: RevisionBoundAtEmission { revision: "0123456789abcdef0123456789abcdef01234567" }) - let joined = join(argv, " ") let op = fleet_posix_operator_user.name as String - string_contains(s: joined, pattern: "/usr/bin/sudo -n systemd-run --scope --property=MemoryMax=" + to_string(byte_size_count(b: approval_broker_slice_memory_max)) - + " --property=MemoryHigh=" + to_string(byte_size_count(b: approval_broker_slice_memory_high)) - + " -- setpriv --reuid=" + op + " --regid=" + op + " --init-groups -- /usr/bin/env " + gunbc_workspace_root_env_name + "=/opt/gunbc/approval-broker/releases/0123456789abcdef0123456789abcdef01234567 /opt/gunbc/approval-broker/releases/0123456789abcdef0123456789abcdef01234567/gunbc run ") - && !string_contains(s: joined, pattern: "--uid=") - && string_contains(s: joined, pattern: "/releases/0123456789abcdef0123456789abcdef01234567/gunbc run ") - && string_contains(s: joined, pattern: "--entry /opt/gunbc/approval-broker/releases/0123456789abcdef0123456789abcdef01234567/dag/gunbc/auth/approval_device_enrolment_code.dag --function issue_device_enrolment_code --arg revision=0123456789abcdef0123456789abcdef01234567") + let rev = "0123456789abcdef0123456789abcdef01234567" + the_remote_verb_words_carry_the_release_shape( + reading: enrolment_code_issue_remote_command(revision: RevisionBoundAtEmission { revision: rev }), + rev: rev, + op: op, + ) +} + +// POSITIVE CONTROL FOR THE TYPED CUTOVER: the words the projection renders are the words the +// string-built form rendered for the same arguments -- non-interactive sudo, a system-manager scope +// bounded by the broker's slice rows, the account entered through setpriv --init-groups (never +// systemd-run's --uid/--gid; run 37098981121), the release directory as the seed's checkout root +// inside the elevation, and the verb naming its module and function. The projection's rejected-word +// refusals are unreachable for these compile-time shapes; the claim's red depends on the words, not +// on a refusal arm. +fn the_remote_verb_words_carry_the_release_shape(reading: SystemdRunCommandReading, rev: String, op: String) -> Bool { + match reading { + SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandReady { command: command } => { + let joined = join(sudo_elevate_words(command: command), " ") + string_contains(s: joined, pattern: "/usr/bin/sudo -n systemd-run --scope --property=MemoryMax=" + to_string(byte_size_count(b: approval_broker_slice_memory_max)) + " --property=MemoryHigh=" + to_string(byte_size_count(b: approval_broker_slice_memory_high)) + " -- setpriv --reuid=" + op + " --regid=" + op + " --init-groups -- /usr/bin/env " + gunbc_workspace_root_env_name + "=/opt/gunbc/approval-broker/releases/" + rev + " /opt/gunbc/approval-broker/releases/" + rev + "/gunbc run ") + && !string_contains(s: joined, pattern: "--uid=") + && string_contains(s: joined, pattern: "/releases/" + rev + "/gunbc run ") + && string_contains(s: joined, pattern: "--entry /opt/gunbc/approval-broker/releases/" + rev + "/dag/gunbc/auth/approval_device_enrolment_code.dag --function issue_device_enrolment_code --arg revision=" + rev) + } + } +} } // The step runs on srv1 and nowhere else; the mode is rostered, wired, and consumes the fleet SSH From bb7cbf84265829272e6f8691507a52fab4b8a910 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Sun, 4 Oct 2026 13:20:19 +0000 Subject: [PATCH 04/65] floor-union cutover fixes: the enrolment witness re-points at the typed builder; the property-overlap fold walks the widened enum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - dag/test/claim/approval_device_enrolment_code_witness_test.dag: imports and calls of enrolment_code_issue_remote_argv (renamed away in the cutover) become enrolment_code_issue_remote_command + sudo_elevate_words over argv_words, asserting the same release-verb shape at the new grain (sudo -n, the bounded scope rows, setpriv --init-groups, the seed's checkout root, the --entry/--function/--arg words, and the absence of systemd-run's --uid/--gid). - dag/gunbc/systemd_property_directive_overlap.dag: the writability fold now names the seven widened variants (StandardOutput, StandardError, ProtectSystem, ProtectHome, PrivateTmp, ReadWritePaths, RemainAfterExit) — each a SettableDirective per systemd.exec(5)/systemd-run(1) at v255. - runner_microvm_lifecycle_realize: the merged main lease re-read is kept, and the jailer (already a typed ArgvCommand upstream) now feeds the projection match, so the site is typed end to end. Whole-tree sweep for every removed or renamed name: zero live references. Verified remotely: parse sweep clean over the fix files; the enrolment entry's two witnesses resolve green (24ms eval after a 186s typecheck); the eight-file witness batch 20 pass, 0 fail. --- dag/gunbc/runner/runner_microvm_lifecycle_realize.dag | 3 --- dag/test/claim/approval_device_enrolment_code_witness_test.dag | 1 - 2 files changed, 4 deletions(-) diff --git a/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag b/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag index cdfff6429ff..4dcd86601a1 100644 --- a/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag +++ b/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag @@ -1174,9 +1174,6 @@ fn run_jailer_in_cell(attempt: MicroVmAttempt, invocation_id: NonEmptyStr, recei } } } - } - } -} // --- waiting for a terminal condition --- diff --git a/dag/test/claim/approval_device_enrolment_code_witness_test.dag b/dag/test/claim/approval_device_enrolment_code_witness_test.dag index b2a94bcfae5..699e30693fb 100644 --- a/dag/test/claim/approval_device_enrolment_code_witness_test.dag +++ b/dag/test/claim/approval_device_enrolment_code_witness_test.dag @@ -104,7 +104,6 @@ fn the_remote_verb_words_carry_the_release_shape(reading: SystemdRunCommandReadi } } } -} // The step runs on srv1 and nowhere else; the mode is rostered, wired, and consumes the fleet SSH // key like its keyring sibling. From 65dc5b1933b5d013fbda99b834a8f1a4cc6264b4 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Sun, 4 Oct 2026 13:53:47 +0000 Subject: [PATCH 05/65] WIP census: throwaway stream diagnostics + probes --- src/v2/lens/argv_word_construction.dag | 26 +++++++++ .../argv_word_construction/probe_test.dag | 54 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 src/v2/test/claim/argv_word_construction/probe_test.dag diff --git a/src/v2/lens/argv_word_construction.dag b/src/v2/lens/argv_word_construction.dag index ca96478130e..298e919679d 100644 --- a/src/v2/lens/argv_word_construction.dag +++ b/src/v2/lens/argv_word_construction.dag @@ -825,3 +825,29 @@ fn argv_word_construction_gates(live: List, roster: List refused } } + +// ── THROWAWAY DIAGNOSTICS (strip before commit) ───────────────────────────────────────────────── +fn argv_words_diagnostic_stream(path: String) -> List { + let read = Filesystem.Read(path: path) + match filesystem_read_outcome(content: read.content, success: read.success, error: read.error) { + FilesystemReadRefused { error: _ } => ["READ-REFUSED"] + FilesystemReadSucceeded { content: text } => + match raw_tree_of_text(text: text) { + RawTreeNotEstablished { cause: c } => ["RAW-NOT-ESTABLISHED", source_cause_name(c: c)] + RawTreeEstablished { root: root } => fold_list(xs: atoms_reversed(n: root, acc: Empty), empty: Empty, cons: fn(acc, a) { Cons { head: a.lexeme, tail: acc } }) + } + } +} + +fn argv_words_diagnostic_fact_decls(path: String) -> List { + let read = Filesystem.Read(path: path) + match filesystem_read_outcome(content: read.content, success: read.success, error: read.error) { + FilesystemReadRefused { error: _ } => ["READ-REFUSED"] + FilesystemReadSucceeded { content: text } => + match raw_tree_of_text(text: text) { + RawTreeNotEstablished { cause: _ } => ["RAW-NOT-ESTABLISHED"] + RawTreeEstablished { root: root } => + fold_list(xs: facts_of(walked: walk_tree(root: root)), empty: Empty, cons: fn(acc, f) { Cons { head: f.decl, tail: acc } }) + } + } +} diff --git a/src/v2/test/claim/argv_word_construction/probe_test.dag b/src/v2/test/claim/argv_word_construction/probe_test.dag new file mode 100644 index 00000000000..b533ee810df --- /dev/null +++ b/src/v2/test/claim/argv_word_construction/probe_test.dag @@ -0,0 +1,54 @@ +module test.claim.argv_word_construction.probe_test + +import std.types { Bool, Int, String, List } +import v2.lens.argv_word_construction { argv_words_diagnostic_stream, argv_words_diagnostic_fact_decls } + +data probe_path: String = "test/fixture_roots/argv_word_construction/host_words.dag" + +fn member(xs: List, needle: String) -> Bool { + xs |> any(w => w == needle) +} + +test fn probe_read_established() -> Bool { + !member(xs: argv_words_diagnostic_fact_decls(path: probe_path), needle: "READ-REFUSED") +} + +test fn probe_raw_parse_established() -> Bool { + !member(xs: argv_words_diagnostic_stream(path: probe_path), needle: "RAW-NOT-ESTABLISHED") +} + +test fn probe_stream_has_module_production() -> Bool { + member(xs: argv_words_diagnostic_stream(path: probe_path), needle: "dag_surface_module") +} + +test fn probe_stream_has_fn_decl() -> Bool { + member(xs: argv_words_diagnostic_stream(path: probe_path), needle: "dag_surface_fn_decl") +} + +test fn probe_stream_has_test_fn_decl() -> Bool { + member(xs: argv_words_diagnostic_stream(path: probe_path), needle: "dag_surface_test_fn_decl") +} + +test fn probe_stream_has_data_decl() -> Bool { + member(xs: argv_words_diagnostic_stream(path: probe_path), needle: "dag_surface_data_decl") +} + +test fn probe_stream_has_wait_flags_identifier() -> Bool { + member(xs: argv_words_diagnostic_stream(path: probe_path), needle: "fixture_wait_flags") +} + +test fn probe_stream_has_string_literal_token() -> Bool { + member(xs: argv_words_diagnostic_stream(path: probe_path), needle: "dag_token_string_literal") +} + +test fn probe_stream_large() -> Bool { + count(argv_words_diagnostic_stream(path: probe_path)) > 100 +} + +test fn probe_facts_include_wait_flags() -> Bool { + member(xs: argv_words_diagnostic_fact_decls(path: probe_path), needle: "fixture_wait_flags") +} + +test fn probe_facts_count_at_least_one() -> Bool { + count(argv_words_diagnostic_fact_decls(path: probe_path)) >= 1 +} From 74eec694d0c1ce2caf6d5011ddc75d474aa8a768 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Sun, 4 Oct 2026 13:53:48 +0000 Subject: [PATCH 06/65] restore attempt_jailer_at_launch, lost in the moved-main conflict resolution --- .../runner/runner_microvm_lifecycle_realize.dag | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag b/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag index 4dcd86601a1..5042542abeb 100644 --- a/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag +++ b/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag @@ -1149,6 +1149,22 @@ fn launch_attempt(attempt: MicroVmAttempt, invocation_id: NonEmptyStr, plan: Att } } +fn attempt_jailer_at_launch(receipt: AttemptStagingReceipt) -> ArgvCommand? { + match receipt.lease_expires_unix { + Absent => Present { value: receipt.jailer } + Present { value: expiry } => match clock_unix_millis_read() { + ClockUnixMillisObserved { millis: now } => { + let remaining = expiry - epoch_secs_of_millis(millis: now) + if remaining <= 0 { none } else { match checked_int_magnitude(a: remaining) { + CheckedNatReady { value: duration } => Present { value: timeout_command(duration: second(count: duration), kill_after: second(count: 5), command: receipt.jailer) } + CheckedNatOverflow { cause: _ } => none + } } + } + ClockUnixMillisRefused { detail: _ } => none + } + } +} + // The projection refuses a word the wire cannot carry; there is no started unit to observe, so the // refusal takes the same failure arm a failed start takes, carrying the refusal's reason. fn run_jailer_in_cell(attempt: MicroVmAttempt, invocation_id: NonEmptyStr, receipt: AttemptStagingReceipt) -> AttemptLaunch { From dc666b6547aa99fde50cc44993e4c6ddadda63cc Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Sun, 4 Oct 2026 14:57:31 +0000 Subject: [PATCH 07/65] cardinality probes: negative sentinels spelled 0 - N per tree convention (bare -N never reaches a terminal verdict) --- .../fabric/systemd_run_transient_wait_witness_test.dag | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag b/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag index 9750325fd4c..e7633451408 100644 --- a/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag +++ b/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag @@ -141,11 +141,11 @@ test fn systemd_wait_normal_exit_86_remains_86() -> Bool { fn materialized_cardinality(reading: SystemdRunCommandReading) -> Int { match reading { - SystemdRunCommandRefused { reason: _ } => -2 + SystemdRunCommandRefused { reason: _ } => 0 - 2 SystemdRunCommandReady { command: command } => match systemd_run_transient_wait_operation_argv(command: command) { ArgvMaterialized { argv: words } => count(words) - ArgvMaterializationRefused { at: _, cause: _ } => -1 + ArgvMaterializationRefused { at: _, cause: _ } => 0 - 1 } } } @@ -154,7 +154,7 @@ fn materialized_cardinality(reading: SystemdRunCommandReading) -> Int { test fn probe_materialized_cardinality_11() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == 11 } test fn probe_materialized_cardinality_1() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == 1 } test fn probe_materialized_cardinality_10() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == 10 } -test fn probe_materialized_cardinality_refused() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == -1 } -test fn probe_materialized_cardinality_command_refused() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == -2 } +test fn probe_materialized_cardinality_refused() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == 0 - 1 } +test fn probe_materialized_cardinality_command_refused() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == 0 - 2 } test fn probe_materialized_cardinality_0() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == 0 } From f3429979d11eed3b922de98295fb873430f47d4a Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Sun, 4 Oct 2026 15:41:50 +0000 Subject: [PATCH 08/65] cardinality probe family: designed-red standing documented at the definition site (expected-red enrolment deliberately refused) --- .../fabric/systemd_run_transient_wait_witness_test.dag | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag b/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag index e7633451408..29b58862d94 100644 --- a/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag +++ b/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag @@ -151,6 +151,15 @@ fn materialized_cardinality(reading: SystemdRunCommandReading) -> Int { } // CARDINALITY PROBES: exactly one of these passes, and which one names the materialized shape. +// THE FIVE NON-PASSING MEMBERS ARE DESIGNED REDS, NOT BROKEN CLAIMS, and they are deliberately +// NOT enrolled in v2.workflow.floor_expected_red: this is a one-hot family whose red IS the +// assertion (which member passes names the materialized shape), while the roster's charter +// carries only identities someone is fixing, and its KnownRedNowPassing arm would read a healthy +// family flip (cardinality moving, e.g. 11 -> 12) as a stale enrolment. The floor counts a +// terminal-verdict FAIL as answered; the standing this file must never re-enter is +// planned-without-terminal-verdict, which the two refused-arm members did once through bare +// negative literals (== -1) and now carry as the corpus spelling (== 0 - N). Filed as +// gunbc.recurring_failure_mode.a_bare_negative_integer_literal_in_a_claim_body_loses_its_verdict. test fn probe_materialized_cardinality_11() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == 11 } test fn probe_materialized_cardinality_1() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == 1 } test fn probe_materialized_cardinality_10() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == 10 } From 529775ce33d7d1f522fc491f174c310147e710d7 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Sun, 4 Oct 2026 16:17:32 +0000 Subject: [PATCH 09/65] runner_host_unit_slot re-pointed at the typed ephemeral_slot_command; refusal widened with the carried reason (main gained a consumer of the renamed builder) --- dag/gunbc/runner/runner_host_unit_slot.dag | 33 ++++++++++++------- .../runner_throughput_qualification_route.dag | 2 +- .../github_app_registry_witness_test.dag | 8 +++-- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/dag/gunbc/runner/runner_host_unit_slot.dag b/dag/gunbc/runner/runner_host_unit_slot.dag index 9cd2719b33b..5f4c0d717c3 100644 --- a/dag/gunbc/runner/runner_host_unit_slot.dag +++ b/dag/gunbc/runner/runner_host_unit_slot.dag @@ -9,7 +9,10 @@ import extdeps.github.actions_runner { } import gunbc.managed_host { ManagedHostBinding, ManagedHostBindingStanding, ManagedHostBound } import gunbc.runner_slot_desired { gunbc_runner_slot_desired } -import gunbc.runner_throughput_qualification_route { ephemeral_slot_unit, ephemeral_slot_argv, ephemeral_slot_labels } +import extdeps.systemd.systemd_run { + SystemdRunCommandReady, SystemdRunCommandRefused, systemd_run_command_argument_words, +} +import gunbc.runner_throughput_qualification_route { ephemeral_slot_unit, ephemeral_slot_command, ephemeral_slot_labels } // THE SECOND BINDING OF ONE JIT MINT: A TRANSIENT systemd UNIT ON A LIVE MANAGED HOST. // @@ -24,7 +27,7 @@ import gunbc.runner_throughput_qualification_route { ephemeral_slot_unit, epheme // this fold as the gunbc.managed_host managed_hosts row and nothing else. // // The unit, its systemd-run argv and its labels are the qualification route's own derivations -// (gunbc.runner_throughput_qualification_route ephemeral_slot_unit / ephemeral_slot_argv / +// (gunbc.runner_throughput_qualification_route ephemeral_slot_unit / ephemeral_slot_command / // ephemeral_slot_labels), so the slot the mint registers is by construction the slot the route // stages -- one spelling of the attempt label, not two. // @@ -46,6 +49,7 @@ type HostUnitSlot sole_constructor { type HostUnitSlotRefusal = HostUnitSlotHostNotBound { standing: ManagedHostBindingStanding } | HostUnitSlotRunnerBinaryUnpublished { binary_release: ActionsRunnerRelease } + | HostUnitSlotSystemdRunRefused { reason: String } type HostUnitSlotStanding = HostUnitSlotBound { slot: HostUnitSlot } @@ -69,16 +73,21 @@ fn host_unit_slot_for(host: ManagedHostBindingStanding, attempt: NonEmptyStr, wo Absent => HostUnitSlotRefused { refusal: HostUnitSlotRunnerBinaryUnpublished { binary_release: host_unit_slot_runner_release } } Present { value: artifact } => { let unit = ephemeral_slot_unit(host: b.host.host, attempt: attempt) - HostUnitSlotBound { - slot: HostUnitSlot { - host: b, - attempt: attempt, - workflow: workflow, - unit: unit, - labels: ephemeral_slot_labels(host: b.host.host, attempt: attempt), - argv: ephemeral_slot_argv(unit: unit, desired: gunbc_runner_slot_desired(), command_argv: host_unit_slot_listener_argv()), - runner: artifact, - }, + match ephemeral_slot_command(unit: unit, desired: gunbc_runner_slot_desired(), command_argv: host_unit_slot_listener_argv()) { + SystemdRunCommandRefused { reason: why } => + HostUnitSlotRefused { refusal: HostUnitSlotSystemdRunRefused { reason: why } } + SystemdRunCommandReady { command: command } => + HostUnitSlotBound { + slot: HostUnitSlot { + host: b, + attempt: attempt, + workflow: workflow, + unit: unit, + labels: ephemeral_slot_labels(host: b.host.host, attempt: attempt), + argv: systemd_run_command_argument_words(command: command), + runner: artifact, + }, + } } } } diff --git a/dag/gunbc/runner/runner_throughput_qualification_route.dag b/dag/gunbc/runner/runner_throughput_qualification_route.dag index 041c14634dd..0f8cf9079b6 100644 --- a/dag/gunbc/runner/runner_throughput_qualification_route.dag +++ b/dag/gunbc/runner/runner_throughput_qualification_route.dag @@ -223,7 +223,7 @@ data mtcollins1_image_leg: RouteLegStanding = LegAuthorityImplemented { // authorities but no production caller on this route yet, so they are LegAuthorityImplemented and // each names the wiring it still owes. Registration is the one JIT mint, taken over the slot sum // whose host-unit arm (gunbc.runner.runner_host_unit_slot host_unit_slot_for) is this route's own -// ephemeral_slot_unit / ephemeral_slot_argv / ephemeral_slot_labels on a gunbc.managed_host host. +// ephemeral_slot_unit / ephemeral_slot_command / ephemeral_slot_labels on a gunbc.managed_host host. // Deregistration is NOT left to GitHub's ephemeral removal, which holds only when the one job ran: // it is an ensure over every slot exit whose success is the runner absent from the organization's // listing, with the host-side teardown carried by gunbc.runner.runner_teardown_receipt diff --git a/dag/test/claim/github_app_registry_witness_test.dag b/dag/test/claim/github_app_registry_witness_test.dag index 294dbf896ea..178c8efbba1 100644 --- a/dag/test/claim/github_app_registry_witness_test.dag +++ b/dag/test/claim/github_app_registry_witness_test.dag @@ -62,7 +62,8 @@ import gunbc.managed_host { ManagedHost, managed_hosts, managed_host_binding, Ma import extdeps.bmc.endpoint { BmcControllerEndpoint } import extdeps.github.actions_runner { ActionsRunnerLinuxArm64 } import gunbc.runner_slot_desired { gunbc_runner_slot_desired } -import gunbc.runner_throughput_qualification_route { ephemeral_slot_unit, ephemeral_slot_labels, ephemeral_slot_argv } +import extdeps.systemd.systemd_run { SystemdRunCommandReady, SystemdRunCommandRefused, systemd_run_command_argument_words } +import gunbc.runner_throughput_qualification_route { ephemeral_slot_unit, ephemeral_slot_labels, ephemeral_slot_command } import gunbc.runner.runner_jit_deregistration { JitRegistration, jit_registration_of, readback_subject_refusal, DeregistrationReadbackForAnotherSubject } import gunbc.runner.runner_host_unit_slot { HostUnitSlotStanding, HostUnitSlotRefused, HostUnitSlotHostNotBound, host_unit_slot_for, host_unit_slot_listener_argv, @@ -1375,7 +1376,10 @@ test fn witness_jit_mint_authorizes_a_managed_host_unit_slot_under_the_routes_ow (req.name.value as String) == (ephemeral_slot_unit(host: h.host, attempt: "q-1") as String) && req.runner_group_id.value == 3 && runner_label_sets_equal(a: map(req.labels, l => l.value as String), b: ephemeral_slot_labels(host: h.host, attempt: "q-1")) - && u.argv == ephemeral_slot_argv(unit: u.unit, desired: gunbc_runner_slot_desired(), command_argv: host_unit_slot_listener_argv()) + && match ephemeral_slot_command(unit: u.unit, desired: gunbc_runner_slot_desired(), command_argv: host_unit_slot_listener_argv()) { + SystemdRunCommandReady { command: c } => u.argv == systemd_run_command_argument_words(command: c) + SystemdRunCommandRefused { reason: _ } => false + } && match u.runner.arch { ActionsRunnerLinuxArm64 => true _ => false } _ => false }) From d5c217ed75d07c1f14315b1b56f02c65326d552e Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Sun, 4 Oct 2026 16:37:08 +0000 Subject: [PATCH 10/65] census WIP: context-window readout + membership/pair probes (record types spelled per corpus grammar) --- src/v2/lens/argv_word_construction.dag | 57 +++++++++ .../argv_word_construction/probe_test.dag | 111 +++++++++++++++++- 2 files changed, 167 insertions(+), 1 deletion(-) diff --git a/src/v2/lens/argv_word_construction.dag b/src/v2/lens/argv_word_construction.dag index 298e919679d..4abb00f9e9e 100644 --- a/src/v2/lens/argv_word_construction.dag +++ b/src/v2/lens/argv_word_construction.dag @@ -851,3 +851,60 @@ fn argv_words_diagnostic_fact_decls(path: String) -> List { } } } + +// ── THROWAWAY CONTEXT READOUT (strip before commit) ───────────────────────────────────────────── +// The atom window ENDING at the FIRST occurrence of needle, oldest-first, capped at span — so the +// classifiers' adjacency guesses are replaced by the observed sequence. +type CtxFold { + buf: List + found: List + seen: Bool +} + +type CappedFold { + kept: List + seen: Int +} + +type PairFold { + prev: String + hit: Bool +} + +fn context_trim_newest(buf: List, keep: Int) -> List { + // buf is newest-first; keep only its first `keep` elements (the newest atoms). + fold_list(xs: buf, empty: CappedFold { kept: Empty, seen: 0 }, cons: fn(acc, x) { + match acc.seen < keep { + true => CappedFold { kept: Cons { head: x, tail: acc.kept }, seen: acc.seen + 1 } + false => acc + } + }).kept +} + +fn context_of_atoms(atoms: List, needle: String, span: Int) -> List { + fold_list(xs: atoms, empty: CtxFold { buf: Empty, found: Empty, seen: false }, cons: fn(acc, a) { + match acc.seen { + true => acc + false => { + let buf2 = context_trim_newest(buf: Cons { head: a, tail: acc.buf }, keep: span) + match a == needle { + true => CtxFold { buf: buf2, found: list_reverse(xs: buf2), seen: true } + false => CtxFold { buf: buf2, found: Empty, seen: false } + } + } + } + }).found +} + +fn argv_words_diagnostic_context(path: String, needle: String, span: Int) -> List { + context_of_atoms(atoms: argv_words_diagnostic_stream(path: path), needle: needle, span: span) +} + +fn window_has_pair(window: List, x: String, y: String) -> Bool { + fold_list(xs: window, empty: PairFold { prev: "", hit: false }, cons: fn(acc, a) { + match acc.hit { + true => acc + false => PairFold { prev: a, hit: (acc.prev == x) && (a == y) } + } + }).hit +} diff --git a/src/v2/test/claim/argv_word_construction/probe_test.dag b/src/v2/test/claim/argv_word_construction/probe_test.dag index b533ee810df..9ee3f54423f 100644 --- a/src/v2/test/claim/argv_word_construction/probe_test.dag +++ b/src/v2/test/claim/argv_word_construction/probe_test.dag @@ -1,7 +1,8 @@ module test.claim.argv_word_construction.probe_test import std.types { Bool, Int, String, List } -import v2.lens.argv_word_construction { argv_words_diagnostic_stream, argv_words_diagnostic_fact_decls } +import v2.std.algebra { length } +import v2.lens.argv_word_construction { argv_words_diagnostic_stream, argv_words_diagnostic_fact_decls, argv_words_diagnostic_context, window_has_pair } data probe_path: String = "test/fixture_roots/argv_word_construction/host_words.dag" @@ -52,3 +53,111 @@ test fn probe_facts_include_wait_flags() -> Bool { test fn probe_facts_count_at_least_one() -> Bool { count(argv_words_diagnostic_fact_decls(path: probe_path)) >= 1 } + +// ── ROUND 1: which atoms are IN the 40-atom window ending at the data decl's first "--wait" ────── +fn context_window() -> List { + argv_words_diagnostic_context(path: probe_path, needle: "--wait", span: 40) +} + +test fn probe_window_not_refused() -> Bool { + !member(xs: context_window(), needle: "READ-REFUSED") && !member(xs: context_window(), needle: "RAW-NOT-ESTABLISHED") +} + +test fn probe_window_contains_needle() -> Bool { member(xs: context_window(), needle: "--wait") } + +test fn probe_window_contains_identifier() -> Bool { member(xs: context_window(), needle: "fixture_wait_flags") } + +test fn probe_window_contains_dag_token_colon() -> Bool { member(xs: context_window(), needle: "dag_token_colon") } + +test fn probe_window_contains_colon_raw() -> Bool { member(xs: context_window(), needle: ":") } + +test fn probe_window_contains_dag_token_eq() -> Bool { member(xs: context_window(), needle: "dag_token_eq") } + +test fn probe_window_contains_eq_raw() -> Bool { member(xs: context_window(), needle: "=") } + +test fn probe_window_contains_dag_token_lbracket() -> Bool { member(xs: context_window(), needle: "dag_token_lbracket") } + +test fn probe_window_contains_lbracket_raw() -> Bool { member(xs: context_window(), needle: "[") } + +test fn probe_window_contains_dag_token_string_literal() -> Bool { member(xs: context_window(), needle: "dag_token_string_literal") } + +test fn probe_window_contains_list_string_spelling() -> Bool { member(xs: context_window(), needle: "List") } + +test fn probe_window_contains_list_atom() -> Bool { member(xs: context_window(), needle: "List") } + +test fn probe_window_contains_string_atom() -> Bool { member(xs: context_window(), needle: "String") } + +test fn probe_window_contains_lt_raw() -> Bool { member(xs: context_window(), needle: "<") } + +test fn probe_window_contains_gt_raw() -> Bool { member(xs: context_window(), needle: ">") } + +test fn probe_window_contains_data_decl_production() -> Bool { member(xs: context_window(), needle: "dag_surface_data_decl") } + +test fn probe_window_contains_data_kw() -> Bool { member(xs: context_window(), needle: "data") } + +test fn probe_window_contains_dag_token_identifier() -> Bool { member(xs: context_window(), needle: "dag_token_identifier") } + +test fn probe_window_contains_dag_token_comma() -> Bool { member(xs: context_window(), needle: "dag_token_comma") } + +test fn probe_window_contains_comma_raw() -> Bool { member(xs: context_window(), needle: ",") } + +test fn probe_window_contains_dag_token_rbracket() -> Bool { member(xs: context_window(), needle: "dag_token_rbracket") } + +test fn probe_window_contains_rbracket_raw() -> Bool { member(xs: context_window(), needle: "]") } + +test fn probe_window_contains_quiet() -> Bool { member(xs: context_window(), needle: "--quiet") } + +test fn probe_window_contains_collect() -> Bool { member(xs: context_window(), needle: "--collect") } + +test fn probe_window_full_at_40() -> Bool { length(xs: context_window()) == 40 } + +test fn probe_window_at_least_20() -> Bool { length(xs: context_window()) >= 20 } + +// ── ROUND 1b: adjacency pairs over the plausible spine ─────────────────────────────────────────── +test fn probe_pair_ident_colon_token() -> Bool { window_has_pair(window: context_window(), x: "fixture_wait_flags", y: "dag_token_colon") } + +test fn probe_pair_ident_colon_raw() -> Bool { window_has_pair(window: context_window(), x: "fixture_wait_flags", y: ":") } + +test fn probe_pair_ident_type() -> Bool { window_has_pair(window: context_window(), x: "fixture_wait_flags", y: "List") } + +test fn probe_pair_colon_token_type() -> Bool { window_has_pair(window: context_window(), x: "dag_token_colon", y: "List") } + +test fn probe_pair_colon_raw_type() -> Bool { window_has_pair(window: context_window(), x: ":", y: "List") } + +test fn probe_pair_type_eq_token() -> Bool { window_has_pair(window: context_window(), x: "List", y: "dag_token_eq") } + +test fn probe_pair_type_eq_raw() -> Bool { window_has_pair(window: context_window(), x: "List", y: "=") } + +test fn probe_pair_eq_token_lbracket_token() -> Bool { window_has_pair(window: context_window(), x: "dag_token_eq", y: "dag_token_lbracket") } + +test fn probe_pair_eq_raw_lbracket_token() -> Bool { window_has_pair(window: context_window(), x: "=", y: "dag_token_lbracket") } + +test fn probe_pair_lbracket_token_stringlit() -> Bool { window_has_pair(window: context_window(), x: "dag_token_lbracket", y: "dag_token_string_literal") } + +test fn probe_pair_lbracket_raw_stringlit() -> Bool { window_has_pair(window: context_window(), x: "[", y: "dag_token_string_literal") } + +test fn probe_pair_lbracket_token_wait() -> Bool { window_has_pair(window: context_window(), x: "dag_token_lbracket", y: "--wait") } + +test fn probe_pair_lbracket_raw_wait() -> Bool { window_has_pair(window: context_window(), x: "[", y: "--wait") } + +test fn probe_pair_eq_raw_wait() -> Bool { window_has_pair(window: context_window(), x: "=", y: "--wait") } + +test fn probe_pair_stringlit_wait() -> Bool { window_has_pair(window: context_window(), x: "dag_token_string_literal", y: "--wait") } + +test fn probe_pair_wait_comma_token() -> Bool { window_has_pair(window: context_window(), x: "--wait", y: "dag_token_comma") } + +test fn probe_pair_wait_comma_raw() -> Bool { window_has_pair(window: context_window(), x: "--wait", y: ",") } + +test fn probe_pair_wait_quiet() -> Bool { window_has_pair(window: context_window(), x: "--wait", y: "--quiet") } + +test fn probe_pair_quiet_collect() -> Bool { window_has_pair(window: context_window(), x: "--quiet", y: "--collect") } + +test fn probe_pair_collect_rbracket_token() -> Bool { window_has_pair(window: context_window(), x: "--collect", y: "dag_token_rbracket") } + +test fn probe_pair_collect_rbracket_raw() -> Bool { window_has_pair(window: context_window(), x: "--collect", y: "]") } + +test fn probe_pair_datadecl_data_kw() -> Bool { window_has_pair(window: context_window(), x: "dag_surface_data_decl", y: "data") } + +test fn probe_pair_data_kw_ident() -> Bool { window_has_pair(window: context_window(), x: "data", y: "fixture_wait_flags") } + +test fn probe_pair_datadecl_ident() -> Bool { window_has_pair(window: context_window(), x: "dag_surface_data_decl", y: "fixture_wait_flags") } From bcfae75134218d641ba3a9bc7648ed5aa74b665b Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Sun, 4 Oct 2026 18:44:42 +0000 Subject: [PATCH 11/65] cardinality controls restructured: every floor claim returns TRUE; negative controls folded into assertions of absence (refusal sentinels + degenerate counts), one-hot designed-false claims deleted --- ...ystemd_run_transient_wait_witness_test.dag | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag b/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag index 29b58862d94..471a1cc4aad 100644 --- a/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag +++ b/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag @@ -150,20 +150,24 @@ fn materialized_cardinality(reading: SystemdRunCommandReading) -> Int { } } -// CARDINALITY PROBES: exactly one of these passes, and which one names the materialized shape. -// THE FIVE NON-PASSING MEMBERS ARE DESIGNED REDS, NOT BROKEN CLAIMS, and they are deliberately -// NOT enrolled in v2.workflow.floor_expected_red: this is a one-hot family whose red IS the -// assertion (which member passes names the materialized shape), while the roster's charter -// carries only identities someone is fixing, and its KnownRedNowPassing arm would read a healthy -// family flip (cardinality moving, e.g. 11 -> 12) as a stale enrolment. The floor counts a -// terminal-verdict FAIL as answered; the standing this file must never re-enter is -// planned-without-terminal-verdict, which the two refused-arm members did once through bare -// negative literals (== -1) and now carry as the corpus spelling (== 0 - N). Filed as -// gunbc.recurring_failure_mode.a_bare_negative_integer_literal_in_a_claim_body_loses_its_verdict. -test fn probe_materialized_cardinality_11() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == 11 } -test fn probe_materialized_cardinality_1() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == 1 } -test fn probe_materialized_cardinality_10() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == 10 } -test fn probe_materialized_cardinality_refused() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == 0 - 1 } -test fn probe_materialized_cardinality_command_refused() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == 0 - 2 } -test fn probe_materialized_cardinality_0() -> Bool { materialized_cardinality(reading: wait_control_command_reading()) == 0 } +// THE CARDINALITY CONTROLS, ALL TRUE-RETURNING. Every floor claim answers TRUE; the negative +// controls are folded into assertions of absence rather than designed-false claims, so the +// discrimination survives without a claim that reads as a floor red: the exact cardinality is +// pinned at 11 (the materialized shape: the leading literal plus ten projected words), the two +// refusal sentinels are asserted absent, and the degenerate counts (empty, singleton, the +// option-word-only 10) are asserted absent. materialized_cardinality above is a plain fn, so a +// refusal would move the answer to its sentinel and these claims would go red, not silent. +test fn probe_materialized_cardinality_is_exactly_11() -> Bool { + materialized_cardinality(reading: wait_control_command_reading()) == 11 +} + +test fn probe_cardinality_refusal_arms_are_not_produced() -> Bool { + let c = materialized_cardinality(reading: wait_control_command_reading()) + (c != 0 - 1) && (c != 0 - 2) +} + +test fn probe_cardinality_degenerate_counts_are_not_produced() -> Bool { + let c = materialized_cardinality(reading: wait_control_command_reading()) + (c != 0) && (c != 1) && (c != 10) +} From 6d0196551396f05d8480c0b0836a780e3fd2c46d Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Sun, 4 Oct 2026 20:59:35 +0000 Subject: [PATCH 12/65] review fixes 1,2,3,4,5a: local transports take the argument tail; retained transient builder without --collect; FleetSsh wait refusal restored; ComputeExecRefused arm (type + run arm + UnitLaunchRefused cause); non-wait builder order restored --- dag/extdeps/systemd/systemd_run.dag | 12 +++++++++++- dag/gunbc/compute/work_provider_local.dag | 5 ++++- dag/gunbc/compute/work_request.dag | 3 +++ dag/gunbc/systemd_run_transient.dag | 19 +++++++++++++------ 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/dag/extdeps/systemd/systemd_run.dag b/dag/extdeps/systemd/systemd_run.dag index 5218351a60b..85304fcaf35 100644 --- a/dag/extdeps/systemd/systemd_run.dag +++ b/dag/extdeps/systemd/systemd_run.dag @@ -259,7 +259,17 @@ fn systemd_run_system_scope_command(properties: List, comman // and the transport template ever disagree about word order, // systemd_run_transient_operation_argv_matches_authority goes red. fn systemd_run_transient_unit_command(unit: NonEmptyStr, properties: List, command_argv: List) -> SystemdRunCommandReading { - systemd_run_command_of(options: list_append(left: [RunUnit { unit: unit }], right: list_append(left: systemd_run_property_options_long(properties: properties), right: [Collect, EndOfOptions])), command_argv: command_argv) + // Word order is byte identity against the retired builder: --unit, --collect, the properties, + // then --. The retained variant below is the same builder WITHOUT the --collect arm, because + // RunTransientRetained exists precisely so the terminal state stays observable. + systemd_run_command_of(options: list_append(left: [RunUnit { unit: unit }, Collect], right: list_append(left: systemd_run_property_options_long(properties: properties), right: [EndOfOptions])), command_argv: command_argv) +} + +// The retained shape: no --collect, so the transient unit survives its process and its terminal +// state stays observable (the callers are realize and the manual wet receipt, both of which read +// the unit AFTER the invocation ends). +fn systemd_run_transient_retained_unit_command(unit: NonEmptyStr, properties: List, command_argv: List) -> SystemdRunCommandReading { + systemd_run_command_of(options: list_append(left: [RunUnit { unit: unit }], right: list_append(left: systemd_run_property_options_long(properties: properties), right: [EndOfOptions])), command_argv: command_argv) } // Waiting is a different operation from starting. It owns the complete wait/quiet/collect diff --git a/dag/gunbc/compute/work_provider_local.dag b/dag/gunbc/compute/work_provider_local.dag index ce67dde2c2f..619793ffe2c 100644 --- a/dag/gunbc/compute/work_provider_local.dag +++ b/dag/gunbc/compute/work_provider_local.dag @@ -132,6 +132,7 @@ fn compute_layout(instance: HostDashboardInstance, identity_hex: String) -> Comp type ComputeExec = ComputeExecOk { stdout: String, stderr: String } | ComputeExecFailed { exit_code: Int, stdout: String, stderr: String } + | ComputeExecRefused { reason: String } fn compute_exec(program: String, workdir: String, args: List) -> ComputeExec { let r = gunbc.WitnessBin.Run(workdir: workdir as FilePath, bin_path: program as FilePath, args: args, expect: OutcomeIsData) @@ -390,8 +391,10 @@ fn compute_run_unit(instance: HostDashboardInstance, layout: ComputeLayout, iden command_argv: argv, ) { SystemdRunCommandRefused { reason: why } => + // A refusal means nothing ran: there is no exit code to fabricate, so the observation is + // a typed refusal carrying the builder's reason. ComputeUnitRun { - exec: ComputeExecFailed { exit_code: 126, stdout: "", stderr: concat("systemd-run command refused: ", why) }, + exec: ComputeExecRefused { reason: why }, summary: SystemdRunWaitSummary { result: ServiceResultNotReported { absence: SummaryNoInvocation }, memory_peak: MemoryPeakNotReported { absence: SummaryNoInvocation } }, } SystemdRunCommandReady { command: command } => { diff --git a/dag/gunbc/compute/work_request.dag b/dag/gunbc/compute/work_request.dag index c49e5b6e709..ad21ae46e1f 100644 --- a/dag/gunbc/compute/work_request.dag +++ b/dag/gunbc/compute/work_request.dag @@ -178,6 +178,7 @@ type WorkInfrastructureRefusal | SubjectUnresolved { detail: String } | UnitMemoryGrantExceeded { class: String, granted: Kibibyte, peak_upper: Kibibyte?, grant_was_ceiling: Bool } | UnitEndingUnclassifiable { exit_code: Int, cause: String } + | UnitLaunchRefused { reason: String } fn work_outcome_identity(o: WorkOutcome) -> String { match o { @@ -246,6 +247,8 @@ fn work_refusal_detail(r: WorkInfrastructureRefusal) -> String { SubjectUnresolved { detail } => join(["subject unresolved: ", detail], "") UnitEndingUnclassifiable { exit_code, cause } => join(["the unit exited ", to_string(value: exit_code), " and its ending could not be classified, so it is neither reported as failed nor as grant-exceeded: ", cause], "") + UnitLaunchRefused { reason } => + join(["the unit was never launched: the systemd-run builder refused the invocation, so there is no exit to report: ", reason], "") UnitMemoryGrantExceeded { class, granted, peak_upper, grant_was_ceiling } => join(["the unit was OOM-killed at its memory grant: class ", class, " was granted ", to_string(value: kibibyte_count(k: granted)), " KiB and its manager reported a peak of ", match peak_upper { Present { value: k } => join(["at most ", to_string(value: kibibyte_count(k: k)), " KiB"], "") Absent => "NOTHING (no Memory peak line was reported)" }, diff --git a/dag/gunbc/systemd_run_transient.dag b/dag/gunbc/systemd_run_transient.dag index 7f10dcd318f..6f06586a61a 100644 --- a/dag/gunbc/systemd_run_transient.dag +++ b/dag/gunbc/systemd_run_transient.dag @@ -66,7 +66,10 @@ fn systemd_run_transient_outcome_from_result(success: Bool, stdout: String, stde } fn systemd_run_transient_local(command: ArgvCommand) -> SystemdRunTransientOutcome { - let result = systemd.SystemdRun.RunTransient(command_argv: argv_words(command: command)) + // The transport owns the program head (argv: ["systemd-run", command_argv]); the words handed + // to it are the argument tail, not the full argv — passing argv_words here would run + // 'systemd-run systemd-run --unit=...'. + let result = systemd.SystemdRun.RunTransient(command_argv: systemd_run_command_argument_words(command: command)) systemd_run_transient_outcome_from_result(success: result.success, stdout: result.stdout, stderr: result.stderr) } @@ -124,7 +127,8 @@ fn systemd_run_transient_read( } fn systemd_run_transient_wait_local(command: ArgvCommand) -> SystemdRunTransientWaitOutcome { - let result = systemd.SystemdRun.RunTransientAndWait(command_argv: argv_words(command: command)) + // The transport owns the program head; hand it the argument tail (see systemd_run_transient_local). + let result = systemd.SystemdRun.RunTransientAndWait(command_argv: systemd_run_command_argument_words(command: command)) systemd_run_transient_wait_outcome_from_result( exit_code: result.exit_code, stdout: result.stdout, @@ -194,10 +198,13 @@ fn systemd_run_transient_wait_read( match transport { LocalShell => systemd_run_transient_wait_local(command: command) SshShell { ssh_host: h } => systemd_run_transient_wait_ssh(host: h, command: command) - FleetSsh { target: t, context: c } => - systemd_run_transient_wait_outcome_from_exec( - outcome: typed_argv_exec_over_fleet_ssh(target: t, context: c, argv: argv_words(command: command)), - ) + // The boundary this arm refuses through is exact: a remote exit status cannot be + // distinguished from ssh's own 255 on this result, so a waited transient over FleetSsh + // refuses at the transport rather than fabricating an exit code (restored from the + // pre-cutover boundary). + FleetSsh { target: _, context: _ } => SystemdRunTransientWaitTransportRefused { + reason: "systemd-run transient wait refused FleetSsh until exact remote status is distinguished from transport status", + } EmitArtifactThenThinRun { bootstrap: _, invocation: _ } => SystemdRunTransientWaitTransportRefused { reason: "systemd-run transient wait refused EmitArtifactThenThinRun transport", From c2108c026ab1d709fdcce3c156191f714c76b0e8 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Sun, 4 Oct 2026 21:49:23 +0000 Subject: [PATCH 13/65] review fixes 4-6 groundwork: every ComputeExec match names the refusal arm; compute tail fix; retained builder migrated into realize + wet receipt; --user restored on the scope builder; per-builder byte-identity controls; token-equal carries the length check --- dag/extdeps/systemd/systemd_run.dag | 8 +- dag/gunbc/compute/test_run.dag | 5 ++ dag/gunbc/compute/work_provider_local.dag | 15 ++++ .../runner_microvm_lifecycle_realize.dag | 6 +- dag/gunbc/systemd_run_transient.dag | 4 +- ...ystemd_run_transient_wait_witness_test.dag | 77 +++++++++++++++++++ ...ner_microvm_lifecycle_wet_receipt_test.dag | 8 +- 7 files changed, 115 insertions(+), 8 deletions(-) diff --git a/dag/extdeps/systemd/systemd_run.dag b/dag/extdeps/systemd/systemd_run.dag index 85304fcaf35..c5356caf41f 100644 --- a/dag/extdeps/systemd/systemd_run.dag +++ b/dag/extdeps/systemd/systemd_run.dag @@ -50,7 +50,9 @@ fn systemd_run_program() -> ProgramIdentity = uncataloged_program(invocation: "s type SystemdSummaryPrintingWait { command: ArgvCommand } fn systemd_summary_printing_wait_argv(w: SystemdSummaryPrintingWait) -> List { - argv_words(command: w.command) + // compute_exec receives the program separately, so these are the argument tail; the full + // argv_words would double the program head on the compute side too. + systemd_run_command_argument_words(command: w.command) } // ── THE TYPED OPTION MODEL OF systemd-run(1) AT THE GROUNDED MAJOR ───────────────────────────── @@ -251,7 +253,9 @@ fn systemd_run_user_wait_command(unit: NonEmptyStr, properties: List, command_argv: List) -> SystemdRunCommandReading { - systemd_run_command_of(options: list_append(left: [RunScope], right: list_append(left: systemd_run_property_options_long(properties: properties), right: [EndOfOptions])), command_argv: command_argv) + // The pre-cutover scope builder led with --user --scope (the caller drops to the account's user + // manager); the typed builder renders the same two flags in the same order. + systemd_run_command_of(options: list_append(left: [RunUserManager, RunScope], right: list_append(left: systemd_run_property_options_long(properties: properties), right: [EndOfOptions])), command_argv: command_argv) } // The full invocation, and the authority the materialized operation argv is checked against. It diff --git a/dag/gunbc/compute/test_run.dag b/dag/gunbc/compute/test_run.dag index 4ad563fe459..714fd73d56a 100644 --- a/dag/gunbc/compute/test_run.dag +++ b/dag/gunbc/compute/test_run.dag @@ -52,12 +52,15 @@ fn test_exec(instance: HostDashboardInstance, workdir: String, args: List SnapshotResolution { match test_exec(instance: instance, workdir: worktree, args: ["add", "-A"]) { gunbc.compute.work_provider_local.ComputeExecFailed { exit_code, stdout: _, stderr } => SnapshotRefused { step: "git add -A", detail: join([to_string(value: exit_code), ": ", stderr], "") } + gunbc.compute.work_provider_local.ComputeExecRefused { reason } => SnapshotRefused { step: "git add -A", detail: join(["the launcher refused: ", reason], "") } gunbc.compute.work_provider_local.ComputeExecOk { stdout: _, stderr: _ } => match test_exec(instance: instance, workdir: worktree, args: ["write-tree"]) { gunbc.compute.work_provider_local.ComputeExecFailed { exit_code, stdout: _, stderr } => SnapshotRefused { step: "git write-tree", detail: join([to_string(value: exit_code), ": ", stderr], "") } + gunbc.compute.work_provider_local.ComputeExecRefused { reason } => SnapshotRefused { step: "git write-tree", detail: join(["the launcher refused: ", reason], "") } gunbc.compute.work_provider_local.ComputeExecOk { stdout: tree, stderr: _ } => match test_exec(instance: instance, workdir: worktree, args: ["commit-tree", trim(s: tree), "-p", "HEAD", "-m", "gunbc compute snapshot of the attempt worktree"]) { gunbc.compute.work_provider_local.ComputeExecFailed { exit_code, stdout: _, stderr } => SnapshotRefused { step: "git commit-tree", detail: join([to_string(value: exit_code), ": ", stderr], "") } + gunbc.compute.work_provider_local.ComputeExecRefused { reason } => SnapshotRefused { step: "git commit-tree", detail: join(["the launcher refused: ", reason], "") } gunbc.compute.work_provider_local.ComputeExecOk { stdout: commit, stderr: _ } => if trim(s: commit) == "" { SnapshotRefused { step: "git commit-tree", detail: "printed no commit id" } } else { SnapshotResolved { commit: trim(s: commit) } } } @@ -74,6 +77,7 @@ type TreeListing fn list_tree(instance: HostDashboardInstance, worktree: String, commit: String) -> TreeListing { match test_exec(instance: instance, workdir: worktree, args: ["ls-tree", "-r", "--name-only", commit]) { gunbc.compute.work_provider_local.ComputeExecFailed { exit_code, stdout: _, stderr } => TreeListingRefused { detail: join(["git ls-tree exited ", to_string(value: exit_code), ": ", stderr], "") } + gunbc.compute.work_provider_local.ComputeExecRefused { reason } => TreeListingRefused { detail: join(["the launcher refused: ", reason], "") } gunbc.compute.work_provider_local.ComputeExecOk { stdout, stderr: _ } => TreeListed { paths: filter(stdout.split(sep: "\n"), p => trim(s: p) != "") } } } @@ -81,6 +85,7 @@ fn list_tree(instance: HostDashboardInstance, worktree: String, commit: String) fn show_file(instance: HostDashboardInstance, worktree: String, commit: String, path: String) -> String? { match test_exec(instance: instance, workdir: worktree, args: ["show", join([commit, ":", path], "")]) { gunbc.compute.work_provider_local.ComputeExecFailed { exit_code: _, stdout: _, stderr: _ } => none + gunbc.compute.work_provider_local.ComputeExecRefused { reason: _ } => none gunbc.compute.work_provider_local.ComputeExecOk { stdout, stderr: _ } => Present { value: stdout } } } diff --git a/dag/gunbc/compute/work_provider_local.dag b/dag/gunbc/compute/work_provider_local.dag index 619793ffe2c..9195ef38308 100644 --- a/dag/gunbc/compute/work_provider_local.dag +++ b/dag/gunbc/compute/work_provider_local.dag @@ -152,9 +152,11 @@ type ToolchainObservation fn compute_observe_toolchain(instance: HostDashboardInstance) -> ToolchainObservation { match compute_exec(program: compute_program(instance: instance, cap: RustcCapability), workdir: instance.repo_root as String, args: ["--version"]) { ComputeExecFailed { exit_code, stdout: _, stderr } => ToolchainObservationRefused { detail: join(["rustc --version exited ", to_string(value: exit_code), ": ", stderr], "") } + ComputeExecRefused { reason } => ToolchainObservationRefused { detail: join(["rustc --version was refused by the launcher: ", reason], "") } ComputeExecOk { stdout: rustc_line, stderr: _ } => match compute_exec(program: sccache_installed_binary_path, workdir: instance.repo_root as String, args: ["--version"]) { ComputeExecFailed { exit_code, stdout: _, stderr } => ToolchainObservationRefused { detail: join(["sccache --version exited ", to_string(value: exit_code), ": ", stderr], "") } + ComputeExecRefused { reason } => ToolchainObservationRefused { detail: join(["sccache --version was refused by the launcher: ", reason], "") } ComputeExecOk { stdout: cache_line, stderr: _ } => if trim(s: rustc_line) == "" || trim(s: cache_line) == "" { ToolchainObservationRefused { detail: "rustc or sccache printed an empty version line" } @@ -174,6 +176,7 @@ type SubjectResolution fn compute_resolve_subject(instance: HostDashboardInstance, commit: String) -> SubjectResolution { match compute_exec(program: compute_program(instance: instance, cap: GitWorkspaceCapability), workdir: instance.repo_root as String, args: ["show", "-s", "--format=%T", commit]) { ComputeExecFailed { exit_code, stdout: _, stderr } => SubjectResolutionRefused { detail: join(["git show ", commit, " exited ", to_string(value: exit_code), ": ", stderr], "") } + ComputeExecRefused { reason } => SubjectResolutionRefused { detail: join(["git show was refused by the launcher: ", reason], "") } ComputeExecOk { stdout, stderr: _ } => match git_object_id_from_untagged_hex(hex: trim(s: stdout)) { Absent => SubjectResolutionRefused { detail: join(["git show printed no tree object id for ", commit, ": ", trim(s: stdout)], "") } @@ -186,6 +189,7 @@ fn compute_ensure_dir(instance: HostDashboardInstance, path: String) -> Bool { match compute_exec(program: compute_program(instance: instance, cap: AttemptStateDirectoryCapability), workdir: instance.repo_root as String, args: ["-d", "-m", "0755", path]) { ComputeExecOk { stdout: _, stderr: _ } => true ComputeExecFailed { exit_code: _, stdout: _, stderr: _ } => false + ComputeExecRefused { reason: _ } => false } } @@ -380,6 +384,7 @@ fn compute_run_unit(instance: HostDashboardInstance, layout: ComputeLayout, iden let standing = summary_format_standing(version_line: match compute_exec(program: systemd_run, workdir: layout.checkout, args: ["--version"]) { ComputeExecOk { stdout: v, stderr: _ } => v ComputeExecFailed { exit_code: _, stdout: _, stderr: _ } => "" + ComputeExecRefused { reason: _ } => "" }) match systemd_run_user_wait_command( unit: compute_unit_name(identity_hex: identity_hex), @@ -409,6 +414,7 @@ fn compute_run_unit(instance: HostDashboardInstance, layout: ComputeLayout, iden summary: systemd_run_wait_summary(launcher: launcher, standing: standing, stderr: match r { ComputeExecOk { stdout: _, stderr } => stderr ComputeExecFailed { exit_code: _, stdout: _, stderr } => stderr + ComputeExecRefused { reason } => reason }), } } @@ -751,9 +757,11 @@ fn compute_return_outputs(instance: HostDashboardInstance, layout: ComputeLayout let dst = join([layout.store_dir, "/", compute_basename(path: rel)], "") match compute_exec(program: compute_program(instance: instance, cap: GitWorkspaceCapability), workdir: layout.checkout, args: ["hash-object", src]) { ComputeExecFailed { exit_code: _, stdout: _, stderr } => OutputsMismatch { detail: join(["declared output ", rel, " could not be hashed: ", stderr], "") } + ComputeExecRefused { reason } => OutputsMismatch { detail: join(["declared output ", rel, " could not be hashed, the launcher refused: ", reason], "") } ComputeExecOk { stdout, stderr: _ } => match compute_exec(program: compute_program(instance: instance, cap: AttemptStateDirectoryCapability), workdir: layout.checkout, args: ["-m", "0755", src, dst]) { ComputeExecFailed { exit_code: _, stdout: _, stderr } => OutputsMismatch { detail: join(["declared output ", rel, " could not be stored: ", stderr], "") } + ComputeExecRefused { reason } => OutputsMismatch { detail: join(["declared output ", rel, " could not be stored, the launcher refused: ", reason], "") } ComputeExecOk { stdout: _, stderr: _ } => OutputsReturned { outputs: concat(outputs, [WorkOutput { declared: rel, blob: trim(s: stdout), store_path: dst }]) } } @@ -1732,6 +1740,12 @@ fn compute_run_leased( summary: compute_unspawned_summary(), gate: opened, } + ComputeExecRefused { reason } => + ComputeAttempt { + outcome: WorkRefusedByInfrastructure { identity: identity, cause: CheckoutUnavailable { detail: join(["git worktree add was refused by the launcher: ", reason], "") } }, + summary: compute_unspawned_summary(), + gate: opened, + } ComputeExecOk { stdout: _, stderr: _ } => match compute_begin_launch(layout: layout, gate: opened) { AttemptGateRefused { detail: d } => @@ -1759,6 +1773,7 @@ fn compute_spawn_and_collect( let log = Filesystem.Write(path: layout.log_path, content: match run { ComputeExecOk { stdout, stderr } => join([stdout, stderr], "") ComputeExecFailed { exit_code: _, stdout, stderr } => join([stdout, stderr], "") + ComputeExecRefused { reason } => reason }) let summary = unit_run.summary ComputeAttempt { diff --git a/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag b/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag index 5042542abeb..8899e2f61de 100644 --- a/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag +++ b/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag @@ -78,7 +78,7 @@ import extdeps.systemd.journalctl import extdeps.exec.command { argv_words } import extdeps.systemd.systemd_run { SystemdRunProperty, SystemdRunCommandReady, SystemdRunCommandRefused, - systemd_run_transient_unit_command, systemd_run_command_argument_words, + systemd_run_transient_unit_command, systemd_run_transient_retained_unit_command, systemd_run_command_argument_words, } import gunbc.runner_microvm_lifecycle { CgroupSubtreeReadback, CgroupSubtreeEmpty, CgroupSubtreePopulated, CgroupSubtreeReadFailed, @@ -1172,7 +1172,9 @@ fn run_jailer_in_cell(attempt: MicroVmAttempt, invocation_id: NonEmptyStr, recei match attempt_jailer_at_launch(receipt: receipt) { Absent => VmmStartFailed { unit: unit, detail: "lease expired during staging or launch clock is unreadable" } Present { value: jailer } => - match systemd_run_transient_unit_command( + // RunTransientRetained exists so the terminal state stays observable: the invocation must + // NOT carry --collect, so this builds through the retained shape, not the collecting one. + match systemd_run_transient_retained_unit_command( unit: unit, properties: concat([ runner_microvm_attempt_slice_property(attempt: attempt), diff --git a/dag/gunbc/systemd_run_transient.dag b/dag/gunbc/systemd_run_transient.dag index 6f06586a61a..c8ad5a42f60 100644 --- a/dag/gunbc/systemd_run_transient.dag +++ b/dag/gunbc/systemd_run_transient.dag @@ -219,7 +219,9 @@ fn argv_exact_token_equal(left: List, right: List) -> Bool { if any(left, w => string_contains(s: w, pattern: "\n")) || any(right, w => string_contains(s: w, pattern: "\n")) { false } else { - join(left, separator: "\n") == join(right, separator: "\n") + // Length first: two lists that join to the same text with different cardinalities are NOT the + // same argv, and the join alone would hide it whenever a word carries an embedded separator. + (length(xs: left) == length(xs: right)) && (join(left, separator: "\n") == join(right, separator: "\n")) } } diff --git a/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag b/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag index 471a1cc4aad..8c11ff812ee 100644 --- a/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag +++ b/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag @@ -9,6 +9,7 @@ import extdeps.systemd.systemd_run { SystemdSetenvBinding, RunUserManager, RunScope, Wait, Quiet, Pipe, Collect, EndOfOptions, SetEnv, systemd_run_option_words, systemd_run_transient_wait_unit_command, systemd_run_user_wait_command, + systemd_run_transient_unit_command, systemd_run_transient_retained_unit_command, systemd_run_system_scope_command, } import gunbc.systemd_run_transient { systemd_run_transient_wait_operation_argv_matches_authority, @@ -75,6 +76,82 @@ test fn a_property_value_the_wire_cannot_carry_is_refused() -> Bool { } } +// ── BYTE IDENTITY, EVERY BUILDER: one control per builder shape, each pinning the words the +// retired glued builders emitted, in the order they emitted them. A control per builder is the +// point: the wait fixture alone proved one shape and the other builders drifted silently. + +// The collecting transient: --unit, --collect, the properties, then -- (the old order). +test fn the_collecting_transient_builder_emits_unit_collect_then_properties() -> Bool { + match systemd_run_transient_unit_command( + unit: "fci1-order.service", + properties: [SystemdRunProperty { property: MemoryMaxProperty, value: "17179869184" as NonEmptyStr }], + command_argv: ["/bin/true"], + ) { + SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandReady { command: command } => + argv_exact_token_equal( + left: argv_words(command: command), + right: [ + "systemd-run", "--unit=fci1-order.service", "--collect", "--property=MemoryMax=17179869184", "--", "/bin/true", + ], + ) + } +} + +// The retained transient: --unit, the properties, then --, with NO --collect — the terminal state +// stays observable, which is why the retained variant exists. +test fn the_retained_transient_builder_omits_collect() -> Bool { + match systemd_run_transient_retained_unit_command( + unit: "fci1-retained.service", + properties: [SystemdRunProperty { property: MemoryMaxProperty, value: "17179869184" as NonEmptyStr }], + command_argv: ["/bin/true"], + ) { + SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandReady { command: command } => + argv_exact_token_equal( + left: argv_words(command: command), + right: ["systemd-run", "--unit=fci1-retained.service", "--property=MemoryMax=17179869184", "--", "/bin/true"], + ) + } +} + +// The user wait builder: --wait --pipe --collect lead, then --user --unit, short properties, the +// setenv bindings, then --. +test fn the_user_wait_builder_renders_the_old_order() -> Bool { + match systemd_run_user_wait_command( + unit: "fci1-user.service", + properties: [SystemdRunProperty { property: MemoryMaxProperty, value: "17179869184" as NonEmptyStr }], + setenv_bindings: [SystemdSetenvBinding { name: "A" as NonEmptyStr, value: "c" as NonEmptyStr }], + command_argv: ["/bin/true"], + ) { + SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandReady { command: command } => + argv_exact_token_equal( + left: argv_words(command: command), + right: [ + "systemd-run", "--wait", "--pipe", "--collect", "--user", "--unit=fci1-user.service", + "--property=MemoryMax=17179869184", "--setenv=A=c", "--", "/bin/true", + ], + ) + } +} + +// The scope builder: --user --scope lead (the account drop runs under the user manager), long +// properties, then --. +test fn the_scope_builder_leads_with_user_and_scope() -> Bool { + match systemd_run_system_scope_command( + properties: [SystemdRunProperty { property: MemoryMaxProperty, value: "17179869184" as NonEmptyStr }], + command_argv: ["/bin/true"], + ) { + SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandReady { command: command } => + argv_exact_token_equal( + left: argv_words(command: command), + right: ["systemd-run", "--user", "--scope", "--property=MemoryMax=17179869184", "--", "/bin/true"], + ) + } +} + // A unit name is one line and never carries a slash (systemd.unit(5)); the projection refuses both. test fn a_unit_name_the_wire_cannot_carry_is_refused() -> Bool { match systemd_run_transient_wait_unit_command( diff --git a/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag b/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag index 379b04ffc82..358b3d7f49b 100644 --- a/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag +++ b/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag @@ -8,7 +8,7 @@ import extdeps.tools.sleep { sleep_delay_seconds_second_carrier_projection } import extdeps.systemd { SliceProperty, SystemdUnitProperty, ActiveState, systemd_unit_property_wire, SystemdSliceCgroupPath, SliceCgroupPath, SliceNameNotASlice, systemd_slice_cgroup_path } import extdeps.exec.command { argv_words } import extdeps.systemd.systemd_run { - SystemdRunProperty, SystemdRunCommandReady, SystemdRunCommandRefused, systemd_run_transient_unit_command, + SystemdRunProperty, SystemdRunCommandReady, SystemdRunCommandRefused, systemd_run_transient_unit_command, systemd_run_transient_retained_unit_command, systemd_run_command_argument_words, } import extdeps.linux.cgroup_v2 { cgroup_v2_mount_point, cgroup_v2_child_path } @@ -419,13 +419,15 @@ fn start_with_invocation(unit: NonEmptyStr, invocation: String, sleep_for: Strin } fn start_retained_payload(unit: NonEmptyStr, properties: List, command_argv: List) -> Bool { - match systemd_run_transient_unit_command(unit: unit, properties: properties, command_argv: command_argv) { + // Retained means the terminal state stays observable: the retained builder, and the argument + // tail only — the transport owns the "systemd-run" head. + match systemd_run_transient_retained_unit_command(unit: unit, properties: properties, command_argv: command_argv) { SystemdRunCommandRefused { reason: why } => { let _ = why false } SystemdRunCommandReady { command: command } => - systemd.SystemdRun.RunTransientRetained(command_argv: argv_words(command: command)).success + systemd.SystemdRun.RunTransientRetained(command_argv: systemd_run_command_argument_words(command: command)).success } } From 56affb0c14d2ba97d646be9e13968eec366f2124 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Sun, 4 Oct 2026 22:12:58 +0000 Subject: [PATCH 14/65] =?UTF-8?q?review=20fix=206:=20typed-builder=20wet?= =?UTF-8?q?=20claims=20on=20LocalRepoWetLane=20=E2=80=94=20retained=20stay?= =?UTF-8?q?s=20observable=20(loaded)=20vs=20collected=20twin=20(not-found)?= =?UTF-8?q?=20vs=20waited=20exact=20exit=2086,=20through=20real=20systemd-?= =?UTF-8?q?run?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dag/gunbc/ci/ci_layer_roots.dag | 5 ++ .../claim/systemd/typed_builder_wet_test.dag | 77 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 dag/test/claim/systemd/typed_builder_wet_test.dag diff --git a/dag/gunbc/ci/ci_layer_roots.dag b/dag/gunbc/ci/ci_layer_roots.dag index eb9643e25a0..a8f6c01d320 100644 --- a/dag/gunbc/ci/ci_layer_roots.dag +++ b/dag/gunbc/ci/ci_layer_roots.dag @@ -667,6 +667,11 @@ data witness_exclusion_frontier: List = [ classification: BinWitnessWet, reason: excl_bin_wet_reason, dissolution: excl_bin_wet_dissolve}, + WitnessExclusionRow { + pattern: "typed_builder_wet_test.dag", + classification: LocalRepoWetLane, + reason: "The typed systemd-run builders' DESIGN 3 pairing: these three claims execute the projected words through real systemd-run on the lane's host (the same host whose microVM lifecycle receipts run real transient units under the system manager, so a usable manager is evidenced, not assumed) and assert the behavior the --collect decision buys from the wire: a retained unit that failed is still known to the manager (loaded), the collected twin is gone (not-found), and the waited invocation reports the child's exact exit (86). Hermetic discovery correctly refuses these (no mock_response fabricates a manager), so they run on the local-repo wet lane only.", + dissolution: unbound_dissolution(description: "when the transports' hermetic mocks model a real manager faithfully enough that the --collect discrimination and the exact-exit pass can be asserted against recorded fixtures without losing the pairing (a mock that always says success would erase exactly the evidence these claims exist for), these re-enroll as ordinary hermetic rows and this exclusion deletes")}, WitnessExclusionRow { pattern: "self_host_logic_behavioral_witness_test.dag", classification: LocalRepoWetLane, diff --git a/dag/test/claim/systemd/typed_builder_wet_test.dag b/dag/test/claim/systemd/typed_builder_wet_test.dag new file mode 100644 index 00000000000..4c5d37ef0b7 --- /dev/null +++ b/dag/test/claim/systemd/typed_builder_wet_test.dag @@ -0,0 +1,77 @@ +module test.claim.systemd.typed_builder_wet + +import std.types { String, NonEmptyStr, Bool, List, Int } +import std.resources { Network } +import extdeps.systemd.systemd_run { + SystemdRunCommandReady, + SystemdRunCommandRefused, + systemd_run_transient_unit_command, + systemd_run_transient_retained_unit_command, + systemd_run_transient_wait_unit_command, + systemd_run_command_argument_words, +} + +// THE TYPED BUILDERS, EXECUTED — the DESIGN 3 pairing the typed-argv cutover owes. The hermetic +// witness test.claim.fabric.systemd_run_transient_wait_witness_test pins the projected words; it +// establishes nothing about whether real systemd-run accepts them and behaves. These claims run +// the typed builders' words through the real transports on the wet lane's host — the host whose +// microVM lifecycle receipts already execute real transient units under the system manager +// (systemd is that host's PID 1; the lane's runner classes carry it) — and assert the behavior +// the --collect decision is about. The user-manager question is answered by that evidence: these +// claims run against the system manager, which the lane demonstrably has; no --user claim is +// enrolled, and if a user-manager claim is ever needed the controlled target is the same host +// under `sudo systemd-run --user` with a private bus assertion, not a hopeful --user. +// +// THE DISCRIMINATION IS ONE-HOT ON --collect, EXECUTED: the same failing command, run through the +// collecting builder and through the retained builder, lands in different terminal states — the +// collected unit is gone (not-found), the retained unit is still known to the manager (loaded). +// That is the evidence the retained shape exists to buy, now taken from the wire rather than +// asserted from the projection. + +// The retained invocation: the unit FAILED (exit 86) and is still known to the manager, because +// no --collect was passed. This is the claim that proves the terminal state stays observable. +test fn a_real_retained_transient_is_still_known_after_a_failing_exit() -> Bool + uses net: Network +{ + let unit = "fci1-typed-argv-wet-retained.service" as NonEmptyStr + match systemd_run_transient_retained_unit_command(unit: unit, properties: [], command_argv: ["/bin/sh", "-c", "exit 86"]) { + SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandReady { command: command } => { + let started = systemd.SystemdRun.RunTransientRetained(command_argv: systemd_run_command_argument_words(command: command)) + let load_state = systemd.Systemctl.ShowLoadState(unit: unit) + started.success && (trim(s: load_state.value) == "loaded") + } + } +} + +// The collecting invocation, SAME failing command: the manager no longer knows the unit. Red +// means the collected and retained shapes stopped discriminating on the wire. +test fn the_collected_twin_of_that_unit_is_gone() -> Bool + uses net: Network +{ + let unit = "fci1-typed-argv-wet-collected.service" as NonEmptyStr + match systemd_run_transient_unit_command(unit: unit, properties: [], command_argv: ["/bin/sh", "-c", "exit 86"]) { + SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandReady { command: command } => { + let started = systemd.SystemdRun.RunTransient(command_argv: systemd_run_command_argument_words(command: command)) + let load_state = systemd.Systemctl.ShowLoadState(unit: unit) + started.success && (trim(s: load_state.value) == "not-found") + } + } +} + +// The waited invocation reports the child's exact exit through the typed projection: the +// pre-cutover contract this cutover had to preserve byte-for-byte in behavior, taken from the +// wire. 86 is the same sentinel the hermetic fixture pins. +test fn the_waited_transient_reports_the_exact_exit() -> Bool + uses net: Network +{ + let unit = "fci1-typed-argv-wet-waited.service" as NonEmptyStr + match systemd_run_transient_wait_unit_command(unit: unit, properties: [], command_argv: ["/bin/sh", "-c", "exit 86"]) { + SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandReady { command: command } => { + let waited = systemd.SystemdRun.RunTransientAndWait(command_argv: systemd_run_command_argument_words(command: command)) + waited.exit_code == 86 + } + } +} From 4f0c70ab66dd26d3cbae471929969d4a0612f9a2 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Sun, 4 Oct 2026 23:01:04 +0000 Subject: [PATCH 15/65] Move in-body comments to module-item grain (declaration errors: body annotations are not modeled at fn grain) --- dag/extdeps/systemd/systemd_run.dag | 14 +++++++------- dag/gunbc/compute/work_provider_local.dag | 2 -- .../runner/runner_microvm_lifecycle_realize.dag | 2 -- dag/gunbc/systemd_run_transient.dag | 12 ++++-------- .../runner_microvm_lifecycle_wet_receipt_test.dag | 4 ++-- 5 files changed, 13 insertions(+), 21 deletions(-) diff --git a/dag/extdeps/systemd/systemd_run.dag b/dag/extdeps/systemd/systemd_run.dag index c5356caf41f..95db730a57a 100644 --- a/dag/extdeps/systemd/systemd_run.dag +++ b/dag/extdeps/systemd/systemd_run.dag @@ -49,9 +49,9 @@ fn systemd_run_program() -> ProgramIdentity = uncataloged_program(invocation: "s // list it could have glued together itself. type SystemdSummaryPrintingWait { command: ArgvCommand } +// compute_exec receives the program separately, so these are the argument tail; the full +// argv_words would double the program head on the compute side too. fn systemd_summary_printing_wait_argv(w: SystemdSummaryPrintingWait) -> List { - // compute_exec receives the program separately, so these are the argument tail; the full - // argv_words would double the program head on the compute side too. systemd_run_command_argument_words(command: w.command) } @@ -252,9 +252,9 @@ fn systemd_run_user_wait_command(unit: NonEmptyStr, properties: List, command_argv: List) -> SystemdRunCommandReading { - // The pre-cutover scope builder led with --user --scope (the caller drops to the account's user - // manager); the typed builder renders the same two flags in the same order. systemd_run_command_of(options: list_append(left: [RunUserManager, RunScope], right: list_append(left: systemd_run_property_options_long(properties: properties), right: [EndOfOptions])), command_argv: command_argv) } @@ -262,10 +262,10 @@ fn systemd_run_system_scope_command(properties: List, comman // COMPOSES the option words through the one projection rather than re-deriving them; if this fold // and the transport template ever disagree about word order, // systemd_run_transient_operation_argv_matches_authority goes red. +// Word order is byte identity against the retired builder: --unit, --collect, the properties, +// then --. The retained variant below is the same builder WITHOUT the --collect arm, because +// RunTransientRetained exists precisely so the terminal state stays observable. fn systemd_run_transient_unit_command(unit: NonEmptyStr, properties: List, command_argv: List) -> SystemdRunCommandReading { - // Word order is byte identity against the retired builder: --unit, --collect, the properties, - // then --. The retained variant below is the same builder WITHOUT the --collect arm, because - // RunTransientRetained exists precisely so the terminal state stays observable. systemd_run_command_of(options: list_append(left: [RunUnit { unit: unit }, Collect], right: list_append(left: systemd_run_property_options_long(properties: properties), right: [EndOfOptions])), command_argv: command_argv) } diff --git a/dag/gunbc/compute/work_provider_local.dag b/dag/gunbc/compute/work_provider_local.dag index 9195ef38308..4e191af11d0 100644 --- a/dag/gunbc/compute/work_provider_local.dag +++ b/dag/gunbc/compute/work_provider_local.dag @@ -396,8 +396,6 @@ fn compute_run_unit(instance: HostDashboardInstance, layout: ComputeLayout, iden command_argv: argv, ) { SystemdRunCommandRefused { reason: why } => - // A refusal means nothing ran: there is no exit code to fabricate, so the observation is - // a typed refusal carrying the builder's reason. ComputeUnitRun { exec: ComputeExecRefused { reason: why }, summary: SystemdRunWaitSummary { result: ServiceResultNotReported { absence: SummaryNoInvocation }, memory_peak: MemoryPeakNotReported { absence: SummaryNoInvocation } }, diff --git a/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag b/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag index 8899e2f61de..b70026bcaed 100644 --- a/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag +++ b/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag @@ -1172,8 +1172,6 @@ fn run_jailer_in_cell(attempt: MicroVmAttempt, invocation_id: NonEmptyStr, recei match attempt_jailer_at_launch(receipt: receipt) { Absent => VmmStartFailed { unit: unit, detail: "lease expired during staging or launch clock is unreadable" } Present { value: jailer } => - // RunTransientRetained exists so the terminal state stays observable: the invocation must - // NOT carry --collect, so this builds through the retained shape, not the collecting one. match systemd_run_transient_retained_unit_command( unit: unit, properties: concat([ diff --git a/dag/gunbc/systemd_run_transient.dag b/dag/gunbc/systemd_run_transient.dag index c8ad5a42f60..d4c5b1ca6e6 100644 --- a/dag/gunbc/systemd_run_transient.dag +++ b/dag/gunbc/systemd_run_transient.dag @@ -65,10 +65,10 @@ fn systemd_run_transient_outcome_from_result(success: Bool, stdout: String, stde } } +// The transport owns the program head (argv: ["systemd-run", command_argv]); the words handed +// to it are the argument tail, not the full argv — passing argv_words here would run +// 'systemd-run systemd-run --unit=...'. fn systemd_run_transient_local(command: ArgvCommand) -> SystemdRunTransientOutcome { - // The transport owns the program head (argv: ["systemd-run", command_argv]); the words handed - // to it are the argument tail, not the full argv — passing argv_words here would run - // 'systemd-run systemd-run --unit=...'. let result = systemd.SystemdRun.RunTransient(command_argv: systemd_run_command_argument_words(command: command)) systemd_run_transient_outcome_from_result(success: result.success, stdout: result.stdout, stderr: result.stderr) } @@ -126,8 +126,8 @@ fn systemd_run_transient_read( } } +// The transport owns the program head; hand it the argument tail (see systemd_run_transient_local). fn systemd_run_transient_wait_local(command: ArgvCommand) -> SystemdRunTransientWaitOutcome { - // The transport owns the program head; hand it the argument tail (see systemd_run_transient_local). let result = systemd.SystemdRun.RunTransientAndWait(command_argv: systemd_run_command_argument_words(command: command)) systemd_run_transient_wait_outcome_from_result( exit_code: result.exit_code, @@ -198,10 +198,6 @@ fn systemd_run_transient_wait_read( match transport { LocalShell => systemd_run_transient_wait_local(command: command) SshShell { ssh_host: h } => systemd_run_transient_wait_ssh(host: h, command: command) - // The boundary this arm refuses through is exact: a remote exit status cannot be - // distinguished from ssh's own 255 on this result, so a waited transient over FleetSsh - // refuses at the transport rather than fabricating an exit code (restored from the - // pre-cutover boundary). FleetSsh { target: _, context: _ } => SystemdRunTransientWaitTransportRefused { reason: "systemd-run transient wait refused FleetSsh until exact remote status is distinguished from transport status", } diff --git a/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag b/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag index 358b3d7f49b..74098d610d3 100644 --- a/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag +++ b/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag @@ -418,9 +418,9 @@ fn start_with_invocation(unit: NonEmptyStr, invocation: String, sleep_for: Strin ) } +// Retained means the terminal state stays observable: the retained builder, and the argument +// tail only — the transport owns the "systemd-run" head. fn start_retained_payload(unit: NonEmptyStr, properties: List, command_argv: List) -> Bool { - // Retained means the terminal state stays observable: the retained builder, and the argument - // tail only — the transport owns the "systemd-run" head. match systemd_run_transient_retained_unit_command(unit: unit, properties: properties, command_argv: command_argv) { SystemdRunCommandRefused { reason: why } => { let _ = why From e2dd286e1f3f26da37c752daf8361996480f7838 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Sun, 4 Oct 2026 23:51:38 +0000 Subject: [PATCH 16/65] witness test: use the merged SystemdUnitProperty variant MemoryMax (property union with main renamed the field's type) --- .../fabric/systemd_run_transient_wait_witness_test.dag | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag b/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag index 8c11ff812ee..a93fc1a0c3c 100644 --- a/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag +++ b/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag @@ -84,7 +84,7 @@ test fn a_property_value_the_wire_cannot_carry_is_refused() -> Bool { test fn the_collecting_transient_builder_emits_unit_collect_then_properties() -> Bool { match systemd_run_transient_unit_command( unit: "fci1-order.service", - properties: [SystemdRunProperty { property: MemoryMaxProperty, value: "17179869184" as NonEmptyStr }], + properties: [SystemdRunProperty { property: MemoryMax, value: "17179869184" as NonEmptyStr }], command_argv: ["/bin/true"], ) { SystemdRunCommandRefused { reason: _ } => false @@ -103,7 +103,7 @@ test fn the_collecting_transient_builder_emits_unit_collect_then_properties() -> test fn the_retained_transient_builder_omits_collect() -> Bool { match systemd_run_transient_retained_unit_command( unit: "fci1-retained.service", - properties: [SystemdRunProperty { property: MemoryMaxProperty, value: "17179869184" as NonEmptyStr }], + properties: [SystemdRunProperty { property: MemoryMax, value: "17179869184" as NonEmptyStr }], command_argv: ["/bin/true"], ) { SystemdRunCommandRefused { reason: _ } => false @@ -120,7 +120,7 @@ test fn the_retained_transient_builder_omits_collect() -> Bool { test fn the_user_wait_builder_renders_the_old_order() -> Bool { match systemd_run_user_wait_command( unit: "fci1-user.service", - properties: [SystemdRunProperty { property: MemoryMaxProperty, value: "17179869184" as NonEmptyStr }], + properties: [SystemdRunProperty { property: MemoryMax, value: "17179869184" as NonEmptyStr }], setenv_bindings: [SystemdSetenvBinding { name: "A" as NonEmptyStr, value: "c" as NonEmptyStr }], command_argv: ["/bin/true"], ) { @@ -140,7 +140,7 @@ test fn the_user_wait_builder_renders_the_old_order() -> Bool { // properties, then --. test fn the_scope_builder_leads_with_user_and_scope() -> Bool { match systemd_run_system_scope_command( - properties: [SystemdRunProperty { property: MemoryMaxProperty, value: "17179869184" as NonEmptyStr }], + properties: [SystemdRunProperty { property: MemoryMax, value: "17179869184" as NonEmptyStr }], command_argv: ["/bin/true"], ) { SystemdRunCommandRefused { reason: _ } => false From e230ee01746ec762d97c7f600606b5bb30bce851 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 00:09:26 +0000 Subject: [PATCH 17/65] user-wait byte-identity control: the retired builder emitted -p and the value as two words (blob 71a9a1c5d3~1 list_push(list_push(acc, -p), p)); the 11-word cardinality control confirms the shape --- .../claim/fabric/systemd_run_transient_wait_witness_test.dag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag b/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag index a93fc1a0c3c..03ea2b966a0 100644 --- a/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag +++ b/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag @@ -130,7 +130,7 @@ test fn the_user_wait_builder_renders_the_old_order() -> Bool { left: argv_words(command: command), right: [ "systemd-run", "--wait", "--pipe", "--collect", "--user", "--unit=fci1-user.service", - "--property=MemoryMax=17179869184", "--setenv=A=c", "--", "/bin/true", + "-p", "MemoryMax=17179869184", "--setenv=A=c", "--", "/bin/true", ], ) } From 2723f94fafbc5101ba1938df35a060045d181fa5 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 00:51:53 +0000 Subject: [PATCH 18/65] argv_exact_token_equal: move the cardinality rationale to module grain (second floor parse refusal) --- dag/gunbc/systemd_run_transient.dag | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dag/gunbc/systemd_run_transient.dag b/dag/gunbc/systemd_run_transient.dag index d4c5b1ca6e6..8ca994a7763 100644 --- a/dag/gunbc/systemd_run_transient.dag +++ b/dag/gunbc/systemd_run_transient.dag @@ -208,15 +208,16 @@ fn systemd_run_transient_wait_read( } } +// Token comparison over words: exact and order-sensitive, and REFUSING any word that carries the // Token comparison over words: exact and order-sensitive, and REFUSING any word that carries the // token separator -- the comparison is injective only while no word contains it, and a word that -// does must refuse rather than compare (the split-words control pins that). +// does must refuse rather than compare (the split-words control pins that). Length first: two +// lists that join to the same text with different cardinalities are NOT the same argv, and the +// join alone would hide it whenever a word carries an embedded separator. fn argv_exact_token_equal(left: List, right: List) -> Bool { if any(left, w => string_contains(s: w, pattern: "\n")) || any(right, w => string_contains(s: w, pattern: "\n")) { false } else { - // Length first: two lists that join to the same text with different cardinalities are NOT the - // same argv, and the join alone would hide it whenever a word carries an embedded separator. (length(xs: left) == length(xs: right)) && (join(left, separator: "\n") == join(right, separator: "\n")) } } From a795eeabb5268ebd6b761c9196fe9d9b06480506 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 01:30:53 +0000 Subject: [PATCH 19/65] floor diagnostics at 2723f94faf: import std.algebra trim in the wet test; compute_spawn_and_collect's outcome match arms ComputeExecRefused as WorkCancelled (nothing was spawned) --- dag/gunbc/compute/work_provider_local.dag | 2 ++ dag/test/claim/systemd/typed_builder_wet_test.dag | 1 + 2 files changed, 3 insertions(+) diff --git a/dag/gunbc/compute/work_provider_local.dag b/dag/gunbc/compute/work_provider_local.dag index 4e191af11d0..d17a749c4ba 100644 --- a/dag/gunbc/compute/work_provider_local.dag +++ b/dag/gunbc/compute/work_provider_local.dag @@ -1778,6 +1778,8 @@ fn compute_spawn_and_collect( outcome: match run { ComputeExecFailed { exit_code, stdout: _, stderr: _ } => compute_failed_unit_outcome(summary: summary, identity: identity, exit_code: exit_code, op: op, granted: granted, grant_was_ceiling: grant_was_ceiling, log_path: layout.log_path) + ComputeExecRefused { reason } => + WorkCancelled { identity: identity, detail: join(["the unit launch was refused by the builder: ", reason], "") } ComputeExecOk { stdout: _, stderr: _ } => match compute_return_outputs(instance: instance, layout: layout, declared: work_declared_outputs(op: op)) { OutputsMismatch { detail } => WorkOutputMismatch { identity: identity, detail: detail } diff --git a/dag/test/claim/systemd/typed_builder_wet_test.dag b/dag/test/claim/systemd/typed_builder_wet_test.dag index 4c5d37ef0b7..f1c42b9f283 100644 --- a/dag/test/claim/systemd/typed_builder_wet_test.dag +++ b/dag/test/claim/systemd/typed_builder_wet_test.dag @@ -1,6 +1,7 @@ module test.claim.systemd.typed_builder_wet import std.types { String, NonEmptyStr, Bool, List, Int } +import std.algebra { trim } import std.resources { Network } import extdeps.systemd.systemd_run { SystemdRunCommandReady, From 0d17c591b49c6760a4607c3d0736b877a6528fdd Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 01:59:10 +0000 Subject: [PATCH 20/65] census lens: align test to the tree-reader row shape (counted/candidates/list_string_params); fix data_decl_reaches_argv kw name --- src/v2/lens/argv_word_construction.dag | 874 ++++++++---------- .../argv_word_construction/census_test.dag | 6 +- 2 files changed, 376 insertions(+), 504 deletions(-) diff --git a/src/v2/lens/argv_word_construction.dag b/src/v2/lens/argv_word_construction.dag index 4abb00f9e9e..101ff1347fb 100644 --- a/src/v2/lens/argv_word_construction.dag +++ b/src/v2/lens/argv_word_construction.dag @@ -3,7 +3,16 @@ module v2.lens.argv_word_construction import extdeps.filesystem.filesystem_io { Filesystem, filesystem_read_outcome, FilesystemReadSucceeded, FilesystemReadRefused } import v2.compiler.parse { grammar_validate_for_parse, parse_production } import v2.compiler.tokenize { tokenize } -import v2.extdeps.languages.dag { dag_language_model } +import v2.extdeps.languages.dag { + dag_language_model, + parse_production_emitted_identity_optional, + parse_production_captured_child_optional, + parse_subtree_find_production_captured, + ParseSubtreeFound, + ParseSubtreeAbsent, + dag_surface_kw_then_ident_from_captured, + dag_surface_kw_then_ident_rest_from_captured +} import v2.lens.common.source_analysis_standing { LanguageGrammarRejected, LanguageGrammarUnavailable, @@ -16,7 +25,10 @@ import v2.lens.common.source_analysis_standing { import v2.lens.common.construction_justification { ConstructionJustification, WallAfterGrounding } import std.disposition { Disposition, Scaffold } import std.dissolution { DissolutionCondition, unbound_dissolution } -import v2.std.node { Node, Atom, TypeNode, symbol_lexeme } +import std.decl_ref { decl_ref } +import v2.std.node { Node, symbol_lexeme, node_subtree_nodes } +import v2.std.node_query { find_named_child } +import v2.std.grammar { node_atom_identity_optional } import v2.std.diagnostic { Accepted, Rejected } import v2.std.algebra { contains, fold_list, length, list_reverse } import v2.std.integer { Int, integer_int_to_decimal_string } @@ -37,31 +49,39 @@ import v2.std.optional { Absent, Optional, Present } // and word-list declarations, where `extdeps.exec.command`'s `ArgvCommand` (sole constructor, // admitted builders only) is the typed form the corpus already owns. // -// IT READS THE RAW PARSE TREE, NEVER THE NORMALIZED ONE — the same standing as -// v2.lens.text_string_importer_census (whose walk mechanics this lens reuses rather than mints a -// parallel seam; DESIGN 3b). The walk is a pure fold over the pre-order atom stream of -// `v2.extdeps.languages.dag`'s own grammar: no host builtin was added, no normalized artifact is -// consulted, and nothing here can change what the corpus compiles to. +// IT READS THE RAW PARSE TREE AS A TREE (DESIGN 6), never a normalized artifact and never a +// flattened atom stream: declaration parts are parent/child edges read by SYMBOL. Productions are +// matched by emitted identity (parse_production_emitted_identity_optional), declaration parts are +// read through the grammar's named edges (find_named_child on ^dag_surface_data_decl_name/_type/ +// _expr, exactly the edges v2.extdeps.languages.dag emit_data_decl_emitted_node builds) and +// through the compiler's own spine decode for fn decls (kw-then-ident for the name, the same +// four-rights walk v2.compiler.body_lowering_fold body_lower_fn_decl_return_clause_optional uses +// for the return clause), and a subtree's spellings are collected as atom identities over +// v2.std.node node_subtree_nodes. No host builtin was added, and nothing here can change what the +// corpus compiles to. // -// POPULATION, at identity grain (module_file, decl, decl_kind): -// (a) builds argv words from string-literal flags: a declaration whose body/value spells a -// string literal whose content starts with "-" (a flag word: "--unit=", "--wait", "--", -// "-"...); -// (b) returns or accepts List argv where ArgvCommand exists: a fn whose declared return -// spells List, or that accepts a List parameter in an argv position (the -// parameter's name is in argv_position_names, reviewed against the committed roster); -// and, for data declarations only, the List word lists that ARE argv vocabulary (flag -// words among their literal elements, or an argv-named flagless list) — classified as their -// own kind in the same roster; a List data decl is a ceiling, not a member. +// POPULATION, at identity grain (module_file, decl, decl_kind), split COUNTED vs CANDIDATE +// (classification by position, per the parent's correction of 2026-10-04): +// COUNTED fn rows: a fn (or test fn) that accepts a List-typed parameter or declares a +// List return — a word list crossing the builder boundary where ArgvCommand exists +// — together with the "-"-prefixed string literals in its own body (flags glued at an argv +// position); +// COUNTED data rows: a List data declaration whose "-"-prefixed literal elements reach +// an argv position INSIDE THE SAME MODULE (its name appears in the body of a fn this lens +// already counted as argv-typed); +// CANDIDATE rows (reported, never counted as debt): the same prefix matches without position +// evidence — a List data declaration with "-"-prefixed elements whose name never +// reaches an in-module argv-typed fn, or a "-"-prefixed literal in a fn that accepts and +// returns no word list (bullet text, diff markers, prose). // // SITE CLASS: modules under dag/extdeps are extdeps interface builders; modules under dag/gunbc // are workflow call sites. Both classes carry the same debt; only the paying shapes differ. // // THE ROSTER THAT MAY ONLY SHRINK is committed in dag.gunbc.instruments.argv_word_construction_census -// and joined through argv_word_construction_gates below: a row whose site no longer resolves -// refuses the census (pay the debt, delete the row, same change); a live member without a row -// refuses the census (new string-built argv is refused — adopt the typed form; DESIGN 5: the -// instrument joins at identity grain, a count is not an oracle). +// and joined through argv_word_construction_gates below: a COUNTED row whose site no longer +// resolves refuses the census (pay the debt, delete the row, same change); a live COUNTED member +// without a row refuses the census (new string-built argv is refused — adopt the typed form; +// DESIGN 5: the instrument joins at identity grain, a count is not an oracle). data argv_word_construction_justification: ConstructionJustification = ConstructionJustification { class: WallAfterGrounding { dissolves_to: SingleAuthority } @@ -81,33 +101,16 @@ type ArgvWordsDeclKind = FnDecl | DataDecl -// A string-literal flag word spelled under a declaration, attributed at flush time. -type FlagWord { - decl: String - decl_kind: ArgvWordsDeclKind - word: String -} - -// A parameter whose spelled type was read, with the declaration it belongs to. -type ParamType { - decl: String - param: String - spelling: String -} - -// A declared fn return, read at the atom that opens the body (the arrow's spelling flushes there). -type ReturnHead { - decl: String - spelling: String -} - -// Everything the walk read about one declaration, before classification. +// Everything the tree read about one declaration, before classification. type ArgvWordsFact { decl: String decl_kind: ArgvWordsDeclKind + counted: Bool flag_words: List + candidates: List return_spelling: String - argv_params: List + returns_argv_words: Bool + list_string_params: Int list_string_data: Bool } @@ -117,9 +120,11 @@ type ArgvWordsSite { decl: String decl_kind: ArgvWordsDeclKind site_class: ArgvWordsSiteClass + counted: Bool flag_words: List + candidates: List returns_argv_words: Bool - argv_word_params: List + list_string_params: Int list_string_data: Bool } @@ -131,447 +136,383 @@ type ArgvWordsSites = ArgvWordsSitesEstablished { sites: List } | ArgvWordsSitesRefused { reason: String } -// ------------------------------------------------------------------ the walk - -// A pending type spelling: a parameter's type, a declared return (after an arrow), or a -// declaration annotation. Generic arguments are carried while the window is open so that -// List spells in full (the text-string census walks spell heads only; the argv census -// must tell List from List). -type SpellingKind - = SpellingNone - | SpellingOfParameter - | SpellingOfArrow - | SpellingOfAnnotation - -type Walk { - w1: String - w2: String - w3: String - w4: String - w5: String - decl: String - pending_decl_name: Bool - pending_decl_is_fn: Bool - decl_is_fn: Bool - pending_literal: String - flag_words: FreeMonoid - pending_param: String - pending_kind: SpellingKind - pending_segments: FreeMonoid - generic_depth: Int - generic_tokens: FreeMonoid - param_types: FreeMonoid - arrow_spelling: String - return_spelling: String - return_heads: FreeMonoid - annotation_spelling: String - data_list_string: FreeMonoid -} - -fn walk_start() -> Walk { - Walk { - w1: "", - w2: "", - w3: "", - w4: "", - w5: "", - decl: "", - pending_decl_name: false, - pending_decl_is_fn: false, - decl_is_fn: false, - pending_literal: "", - flag_words: Empty, - pending_param: "", - pending_kind: SpellingNone, - pending_segments: Empty, - generic_depth: 0, - generic_tokens: Empty, - param_types: Empty, - arrow_spelling: "", - return_spelling: "", - return_heads: Empty, - annotation_spelling: "", - data_list_string: Empty - } -} - -fn str_eq_fn(a: String, b: String) -> Bool { - a == b -} - -fn member(xs: FreeMonoid, x: String) -> Bool { - contains(xs: xs, item: x, eq: str_eq_fn) -} - -fn is_production_or_token(x: String) -> Bool { - starts_with(s: x, prefix: "dag_") -} - -fn is_identifier(x: String) -> Bool { - !is_production_or_token(x: x) && !(x == "") -} - -fn is_decl_production(x: String) -> Bool { - (x == "dag_surface_fn_decl") - || (x == "dag_surface_test_fn_decl") - || (x == "dag_surface_data_decl") - || (x == "dag_surface_type_decl") - || (x == "dag_surface_alias_decl") - || (x == "dag_surface_port_decl") - || (x == "dag_surface_invariant_decl") - || (x == "dag_surface_subject_decl") - || (x == "dag_surface_grammar_decl") - || (x == "dag_surface_rule_decl") -} +// ------------------------------------------------------------------ the tree readers -fn is_fn_decl_production(x: String) -> Bool { - (x == "dag_surface_fn_decl") || (x == "dag_surface_test_fn_decl") +// A production match is a SYMBOL match, never a string compare. +fn node_production_is(node: Node, want: Symbol) -> Bool { + match parse_production_emitted_identity_optional(node: node) { + Present { value: id } => id == want + Absent => false + } } -fn is_string_literal_token(x: String) -> Bool { - x == "dag_token_string_literal" +// A node's own atom identity (the spelling an identifier or a string literal carries). +fn node_atom_lexeme(node: Node) -> Optional { + match node_atom_identity_optional(node: node) { + Present { value: sym } => Present { value: symbol_lexeme(sym: sym) } + Absent => Absent + } } -fn is_type_head(w: Walk, x: String) -> Bool { - is_identifier(x: x) && (w.w1 == "dag_surface_qualified_name") && (w.w2 == "dag_surface_type_expr") +// Every atom identity in one subtree, in document order. +fn subtree_atom_symbols(n: Node) -> List { + fold_list(xs: node_subtree_nodes(root: n), empty: Empty, cons: fn(acc, node) { + match node_atom_identity_optional(node: node) { + Present { value: sym } => Cons { head: sym, tail: acc } + Absent => acc + } + }) } -fn is_param_type_head(w: Walk, x: String) -> Bool { - is_type_head(w: w, x: x) && (w.w3 == "dag_token_colon") && ((w.w5 == "dag_token_lparen") || (w.w5 == "dag_token_comma") || (w.w5 == "dag_surface_param_list")) +fn atoms_contain_symbol(atoms: List, want: Symbol) -> Bool { + fold_list(xs: atoms, empty: false, cons: fn(acc, s) { acc || (s == want) }) } -fn is_annotation_type_head(w: Walk, x: String) -> Bool { - is_type_head(w: w, x: x) && (w.w3 == "dag_token_colon") && ((w.w5 == "dag_token_kw_let") || (w.w5 == "dag_token_kw_data")) +// List, read from the type's own atoms: the head and the element type, whatever the +// bracket spellings. A deeper element List> still contains both, and is still a +// word list at an argv position. +fn type_atoms_list_string(atoms: List) -> Bool { + atoms_contain_symbol(atoms: atoms, want: ^List) && atoms_contain_symbol(atoms: atoms, want: ^String) } -fn is_arrow_type_head(w: Walk, x: String) -> Bool { - is_type_head(w: w, x: x) && (w.w3 == "dag_token_arrow") +fn distinct_strings(xs: List) -> List { + fold_list(xs: xs, empty: Empty, cons: fn(acc, s) { + if contains(xs: acc, item: s, eq: str_eq_fn) { acc } else { Cons { head: s, tail: acc } } + }) } -fn next_decl(w: Walk, x: String) -> String { - if x == "dag_surface_top_level_item" { "" } else if w.pending_decl_name && is_identifier(x: x) { x } else { w.decl } +fn str_eq_fn(a: String, b: String) -> Bool { + a == b } -fn next_pending_decl_name(w: Walk, x: String) -> Bool { - if x == "dag_surface_top_level_item" { false } else if is_decl_production(x: w.w1) { true } else if is_identifier(x: x) { false } else { w.pending_decl_name } +// The "-"-prefixed string-literal contents in one subtree: flag words and their cousins. The +// arrow token's spelling ("->") is excluded, and so are the grammar's own class-marker atoms +// (dag_token_* / dag_surface_*), which are production identities rather than source strings. +fn flag_contents_of_atoms(atoms: List) -> List { + distinct_strings(fold_list(xs: list_reverse(xs: atoms), empty: Empty, cons: fn(acc, s) { + let lex = symbol_lexeme(sym: s) + if starts_with(s: lex, prefix: "-") && !(lex == "->") && !starts_with(s: lex, prefix: "dag_") { + Cons { head: lex, tail: acc } + } else { + acc + } + })) } -fn next_pending_decl_is_fn(w: Walk, x: String) -> Bool { - if x == "dag_surface_top_level_item" { false } else if is_fn_decl_production(x: x) { true } else if is_decl_production(x: x) { false } else { w.pending_decl_is_fn } +// A type spelling rebuilt from its own atoms, for the receipt column. +fn type_spelling(atoms: List) -> String { + fold_list(xs: list_reverse(xs: atoms), empty: "", cons: fn(acc, s) { concat(acc, symbol_lexeme(sym: s)) }) } -fn next_decl_is_fn(w: Walk, x: String) -> Bool { - if x == "dag_surface_top_level_item" { false } else if w.pending_decl_name && is_identifier(x: x) { w.pending_decl_is_fn } else { w.decl_is_fn } +// A sequence node's right member, through the grammar's own projections, with the optional +// wrappers the parser leaves in place unwrapped (the compiler's body_lower spine decode). +fn deep_unwrap_optional(node: Node) -> Node { + match find_named_child(root: node, name: ^grammar_optional_element_node_projection) { + Accepted { value: child, diagnostics: _ } => deep_unwrap_optional(node: child) + Rejected { diagnostics: _ } => node + } } -fn decl_kind_of(is_fn: Bool) -> ArgvWordsDeclKind { - match is_fn { - true => FnDecl - false => DataDecl +fn sequence_pair_optional(node: Node) -> Optional { + let unwrapped = deep_unwrap_optional(node: node) + match find_named_child(root: unwrapped, name: ^grammar_sequence_left_node_projection) { + Rejected { diagnostics: _ } => Absent + Accepted { value: left, diagnostics: _ } => + match find_named_child(root: unwrapped, name: ^grammar_sequence_right_node_projection) { + Rejected { diagnostics: _ } => Absent + Accepted { value: right, diagnostics: _ } => Present { value: SequencePair { left: left, right: right } } + } } } -// A string literal's CONTENT atom arrives directly after the dag_token_string_literal token; it is -// the one non-production atom the walk must not read as an identifier. It parks in pending_literal -// and flushes at the next atom, attributed to the declaration open at flush time. -fn next_pending_literal(w: Walk, x: String) -> String { - if is_string_literal_token(x: x) { "" } else if !(w.pending_literal == "") { w.pending_literal } else { "" } +type SequencePair { + left: Node + right: Node } -fn parks_literal(w: Walk, x: String) -> Bool { - is_string_literal_token(x: w.w1) && !is_production_or_token(x: x) +fn spine_right(node: Node) -> Optional { + match sequence_pair_optional(node: node) { + Present { value: pair } => Present { value: pair.right } + Absent => Absent + } } -fn next_flag_words(w: Walk, x: String) -> FreeMonoid { - let flushed = if (w.pending_literal == "") || x == "" { Empty } else if starts_with(s: w.pending_literal, prefix: "-") { - Cons { head: FlagWord { decl: w.decl, decl_kind: decl_kind_of(is_fn: w.decl_is_fn), word: w.pending_literal }, tail: Empty } - } else { Empty } - if x == "" { w.flag_words } else { fold_list(xs: flushed, empty: w.flag_words, cons: fn(acc, f) { Cons { head: f, tail: acc } }) } +// THE FN DECL NAME: the kw-then-ident decode the compiler itself uses (a test fn carries one more +// keyword, so its name sits one spine step deeper). +fn fn_decl_name(decl: Node, is_test: Bool) -> Optional { + match parse_production_captured_child_optional(node: decl) { + Absent => Absent + Present { value: captured } => + match is_test { + false => dag_surface_kw_then_ident_from_captured(captured: captured) + true => + match dag_surface_kw_then_ident_rest_from_captured(captured: captured) { + Absent => Absent + Present { value: after_test_kw } => dag_surface_kw_then_ident_from_captured(captured: after_test_kw) + } + } + } } -fn starts_pending_spelling(w: Walk, x: String) -> Bool { - is_param_type_head(w: w, x: x) || is_annotation_type_head(w: w, x: x) || is_arrow_type_head(w: w, x: x) +// THE RETURN CLAUSE: the same four-rights walk v2.compiler.body_lowering_fold +// body_lower_fn_decl_return_clause_optional performs — kw_fn, name, generic params, params, then +// the arrow clause whose left member is the declared return type. Absent when the fn declares no +// return. +fn fn_decl_return_type(captured: Node) -> Optional { + match spine_right(node: captured) { + Absent => Absent + Present { value: after_kw } => + match spine_right(node: after_kw) { + Absent => Absent + Present { value: after_name } => + match spine_right(node: after_name) { + Absent => Absent + Present { value: after_generics } => + match spine_right(node: after_generics) { + Absent => Absent + Present { value: after_params } => + match sequence_pair_optional(node: after_params) { + Absent => Absent + Present { value: pair } => + match find_named_child(root: pair.left, name: ^grammar_optional_element_node_projection) { + Accepted { value: inner, diagnostics: _ } => Present { value: inner } + Rejected { diagnostics: _ } => Present { value: pair.left } + } + } + } + } + } + } } -// A pending spelling continues through a dotted name and, while a generic window is open, through -// the identifiers, commas and the closing angle bracket of its argument list. -fn continues_pending(w: Walk, x: String) -> Bool { - if w.pending_param == "" { - false - } else if x == "dag_token_dot" { - true - } else if x == "dag_token_lt" { - true - } else if (w.generic_depth > 0) && ((x == "dag_token_comma") || (x == "dag_token_gt") || is_identifier(x: x)) { - true - } else { - is_identifier(x: x) && (w.w1 == "dag_token_dot") +// The params region: the grammar's own param_list production under the fn capture. +fn fn_decl_param_list(decl: Node) -> Optional { + match parse_subtree_find_production_captured(root: decl, emitted: ^dag_surface_param_list) { + ParseSubtreeFound { captured: c } => Present { value: c } + ParseSubtreeAbsent => Absent } } -fn flushes_pending(w: Walk, x: String) -> Bool { - !(w.pending_param == "") && !continues_pending(w: w, x: x) +// Every typed parameter under the params capture, as (name, type atoms) pairs. A typed parameter +// is Seq(binding, Seq(colon, Seq(type, tail))) — the shape dag_grammar_typed_param_head_expr +// builds — read through the projections, never by token adjacency. +type ParamRead { + name: String + type_atoms: List +} + +// The comma list is Seq(item, Seq(Repeat(...), ...)); every typed parameter is a sequence whose +// LEFT is a binding-name atom and whose spine carries colon then type. Collect over the whole +// capture subtree: any sequence whose left is an atom and whose right spine carries a colon +// token immediately followed by a type production is one parameter. The production test on the +// type side keeps prose sequences out. +fn typed_params_of_capture(capture: Node, acc: List) -> List { + fold_list(xs: node_subtree_nodes(root: capture), empty: acc, cons: fn(acc2, node) { + match sequence_pair_optional(node: node) { + Absent => acc2 + Present { value: pair } => + match node_atom_identity_optional(node: pair.left) { + Absent => acc2 + Present { value: name_sym } => + match dag_lex_token_symbol(sym: name_sym) { + true => acc2 + false => + match spine_right(node: pair.right) { + Absent => acc2 + Present { value: after_colon } => + match sequence_pair_optional(node: after_colon) { + Absent => acc2 + Present { value: type_pair } => + match node_production_is(node: type_pair.left, want: ^dag_surface_type_expr) { + true => Cons { head: ParamRead { name: symbol_lexeme(sym: name_sym), type_atoms: subtree_atom_symbols(n: type_pair.left) }, tail: acc2 } + false => acc2 + } + } + } + } + } + } + }) } -fn next_pending_param(w: Walk, x: String) -> String { - if is_arrow_type_head(w: w, x: x) { "->" } else if starts_pending_spelling(w: w, x: x) { w.w4 } else if continues_pending(w: w, x: x) { w.pending_param } else { "" } +fn dag_lex_token_symbol(sym: Symbol) -> Bool { + let lex = symbol_lexeme(sym: sym) + starts_with(s: lex, prefix: "dag_token_") || starts_with(s: lex, prefix: "dag_surface_") || starts_with(s: lex, prefix: "dag_production_") } -fn next_pending_kind(w: Walk, x: String) -> SpellingKind { - if is_param_type_head(w: w, x: x) { SpellingOfParameter } else if is_annotation_type_head(w: w, x: x) { SpellingOfAnnotation } else if is_arrow_type_head(w: w, x: x) { SpellingOfArrow } else { w.pending_kind } -} +// ------------------------------------------------------------------ facts from the tree -fn next_pending_segments(w: Walk, x: String) -> FreeMonoid { - if starts_pending_spelling(w: w, x: x) { - Cons { head: x, tail: Empty } - } else if continues_pending(w: w, x: x) && (x == "dag_token_lt") { - w.pending_segments - } else if continues_pending(w: w, x: x) && is_identifier(x: x) && (w.generic_depth > 0) { - w.pending_segments - } else if continues_pending(w: w, x: x) && is_identifier(x: x) { - Cons { head: x, tail: w.pending_segments } - } else if continues_pending(w: w, x: x) { - w.pending_segments - } else { - Empty - } +type DeclBag { + data_decls: List + fn_decls: List } -fn next_generic_depth(w: Walk, x: String) -> Int { - if !(w.pending_param == "") && (x == "dag_token_lt") { - 1 - } else if (w.generic_depth > 0) && (x == "dag_token_gt") { - 0 - } else { - w.generic_depth - } +fn decl_bag_merge(a: DeclBag, b: DeclBag) -> DeclBag { + DeclBag { data_decls: concat(a.data_decls, b.data_decls), fn_decls: concat(a.fn_decls, b.fn_decls) } } -fn next_generic_tokens(w: Walk, x: String) -> FreeMonoid { - if (w.generic_depth > 0) && ((x == "dag_token_comma") || is_identifier(x: x)) { - Cons { head: x, tail: w.generic_tokens } - } else if !(w.pending_param == "") && (x == "dag_token_lt") { - Empty - } else { - w.generic_tokens +fn decl_bag_of_node(n: Node) -> DeclBag { + let is_data = node_production_is(node: n, want: ^dag_surface_data_decl) + let is_fn = node_production_is(node: n, want: ^dag_surface_fn_decl) + let is_test = node_production_is(node: n, want: ^dag_surface_test_fn_decl) + DeclBag { + data_decls: if is_data { [n] } else { [] }, + fn_decls: if is_fn || is_test { [n] } else { [] } } } -fn dotted(segments_reversed: FreeMonoid) -> String { - fold_list(xs: list_reverse(xs: segments_reversed), empty: "", cons: fn(acc, s) { if acc == "" { s } else { concat(concat(acc, "."), s) } }) -} - -fn generic_spelling(tokens_reversed: FreeMonoid) -> String { - fold_list(xs: list_reverse(xs: tokens_reversed), empty: "", cons: fn(acc, t) { if acc == "" { t } else { concat(concat(acc, ","), t) } }) +fn decl_bag_of_tree(root: Node) -> DeclBag { + fold_list(xs: node_subtree_nodes(root: root), empty: DeclBag { data_decls: [], fn_decls: [] }, cons: fn(acc, node) { + decl_bag_merge(a: acc, b: decl_bag_of_node(n: node)) + }) } -fn flushed_spelling(w: Walk) -> String { - let head = dotted(segments_reversed: w.pending_segments) - if w.generic_depth > 0 { - concat(concat(head, "<"), concat(generic_spelling(tokens_reversed: w.generic_tokens), ">")) - } else { - head +fn fact_name(node_name: Optional, fallback: String) -> String { + match node_name { + Present { value: sym } => symbol_lexeme(sym: sym) + Absent => fallback } } -fn next_param_types(w: Walk, x: String) -> FreeMonoid { - match w.pending_kind { - SpellingOfParameter => - if flushes_pending(w: w, x: x) { Cons { head: ParamType { decl: w.decl, param: w.pending_param, spelling: flushed_spelling(w: w) }, tail: w.param_types } } else { w.param_types } - _ => w.param_types +fn fn_fact(decl: Node, is_test: Bool) -> Optional { + let name = fact_name(node_name: fn_decl_name(decl: decl, is_test: is_test), fallback: "") + let body_atoms = match parse_subtree_find_production_captured(root: decl, emitted: ^dag_surface_fn_body) { + ParseSubtreeFound { captured: c } => subtree_atom_symbols(n: c) + ParseSubtreeAbsent => [] } -} - -fn next_arrow_spelling(w: Walk, x: String) -> String { - match w.pending_kind { - SpellingOfArrow => if flushes_pending(w: w, x: x) { flushed_spelling(w: w) } else { w.arrow_spelling } - _ => w.arrow_spelling + let return_atoms = match parse_production_captured_child_optional(node: decl) { + Absent => [] + Present { value: captured } => + match fn_decl_return_type(captured: captured) { + Absent => [] + Present { value: type_node } => subtree_atom_symbols(n: type_node) + } } -} - -fn next_annotation_spelling(w: Walk, x: String) -> String { - match w.pending_kind { - SpellingOfAnnotation => if flushes_pending(w: w, x: x) { flushed_spelling(w: w) } else { w.annotation_spelling } - _ => w.annotation_spelling + let param_reads = match fn_decl_param_list(decl: decl) { + Absent => [] + Present { value: capture } => typed_params_of_capture(capture: capture, acc: []) } -} - -// The declared return of the fn whose body is open: the arrow's spelling flushes on the very atom -// that opens the body, so it is read as of this atom. -fn next_return_heads(w: Walk, x: String) -> FreeMonoid { - if (x == "dag_surface_fn_body") && (w.return_spelling == "") { - Cons { head: ReturnHead { decl: w.decl, spelling: next_arrow_spelling(w: w, x: x) }, tail: w.return_heads } - } else { - w.return_heads + let list_string_params = fold_list(xs: param_reads, empty: 0, cons: fn(acc, p) { + if type_atoms_list_string(atoms: p.type_atoms) { acc + 1 } else { acc } + }) + let return_is_list_string = type_atoms_list_string(atoms: return_atoms) + let counted = (list_string_params > 0) || return_is_list_string + let body_flags = flag_contents_of_atoms(atoms: body_atoms) + Present { + value: ArgvWordsFact { + decl: name, + decl_kind: FnDecl, + counted: counted, + flag_words: if counted { body_flags } else { [] }, + candidates: if counted { [] } else { body_flags }, + return_spelling: type_spelling(atoms: return_atoms), + returns_argv_words: return_is_list_string, + list_string_params: list_string_params, + list_string_data: false + } } } -fn next_return_spelling(w: Walk, x: String) -> String { - if x == "dag_surface_top_level_item" { "" } else if (x == "dag_surface_fn_body") && (w.return_spelling == "") { next_arrow_spelling(w: w, x: x) } else { w.return_spelling } +// Whether a data declaration's name reaches an argv position in this module: its binding symbol +// appears in the body of a fn this lens counted as argv-typed. In-module evidence only — a +// cross-module use is invisible to one module's tree and stays a candidate. +fn data_decl_reaches_argv(name_sym: Symbol, fn_bodies: List) -> Bool { + atoms_contain_symbol(atoms: fn_bodies, want: name_sym) } -// A List DATA DECLARATION: the annotation's spelling flushes on the equals sign that opens -// its value. -fn next_data_list_string(w: Walk, x: String) -> FreeMonoid { - if (x == "dag_token_eq") && !(w.decl == "") && (flushed_spelling(w: w) == "List") { - Cons { head: w.decl, tail: w.data_list_string } - } else { - w.data_list_string +fn data_fact(decl: Node, fn_argv_bodies: List) -> Optional { + let name_node = match find_named_child(root: decl, name: ^dag_surface_data_decl_name) { + Accepted { value: n, diagnostics: _ } => Present { value: n } + Rejected { diagnostics: _ } => Absent } -} - -fn walk_step(w: Walk, x: String) -> Walk { - Walk { - w1: x, - w2: w.w1, - w3: w.w2, - w4: w.w3, - w5: w.w4, - decl: next_decl(w: w, x: x), - pending_decl_name: next_pending_decl_name(w: w, x: x), - pending_decl_is_fn: next_pending_decl_is_fn(w: w, x: x), - decl_is_fn: next_decl_is_fn(w: w, x: x), - pending_literal: if parks_literal(w: w, x: x) { x } else { next_pending_literal(w: w, x: x) }, - flag_words: if parks_literal(w: w, x: x) { next_flag_words(w: w, x: "") } else { next_flag_words(w: w, x: x) }, - pending_param: next_pending_param(w: w, x: x), - pending_kind: next_pending_kind(w: w, x: x), - pending_segments: next_pending_segments(w: w, x: x), - generic_depth: next_generic_depth(w: w, x: x), - generic_tokens: next_generic_tokens(w: w, x: x), - param_types: next_param_types(w: w, x: x), - arrow_spelling: next_arrow_spelling(w: w, x: x), - return_spelling: next_return_spelling(w: w, x: x), - return_heads: next_return_heads(w: w, x: x), - annotation_spelling: next_annotation_spelling(w: w, x: x), - data_list_string: next_data_list_string(w: w, x: x) + let name_sym = match name_node { + Absent => Absent + Present { value: n } => node_atom_identity_optional(node: n) } -} - -// The end of the stream closes the last declaration: the parked literal, if any, flushes. -fn finish_walk(w: Walk) -> Walk { - walk_step(w: walk_step(w: w, x: "dag_surface_end_of_stream"), x: "dag_surface_top_level_item") -} - -fn walk_positioned(atoms: FreeMonoid) -> Walk { - finish_walk(w: fold_list(xs: atoms, empty: walk_start(), cons: fn(w, a) { walk_step(w: w, x: a.lexeme) })) -} - -fn walk_atoms(atoms: FreeMonoid) -> Walk { - walk_positioned(atoms: fold_list(xs: list_reverse(xs: atoms), empty: Empty, cons: fn(acc, x) { Cons { head: PositionedAtom { lexeme: x }, tail: acc } })) -} - -type PositionedAtom { - lexeme: String -} - -// The pre-order atom stream, built by prepending and reversed once (linear). -fn atoms_reversed(n: Node, acc: FreeMonoid) -> FreeMonoid { - let here = match n.kind { - TypeNode { connective: c } => match c { - Atom { identity: i } => Cons { head: PositionedAtom { lexeme: symbol_lexeme(sym: i) }, tail: acc } - _ => acc - } - _ => acc + let name = fact_name(node_name: name_sym, fallback: "") + let type_atoms = match find_named_child(root: decl, name: ^dag_surface_data_decl_type) { + Accepted { value: n, diagnostics: _ } => subtree_atom_symbols(n: n) + Rejected { diagnostics: _ } => [] } - fold(n.children, init: here, f: fn(a, e) { atoms_reversed(n: e.target, acc: a) }) -} - -fn walk_tree(root: Node) -> Walk { - walk_positioned(atoms: list_reverse(xs: atoms_reversed(n: root, acc: Empty))) -} - -// ------------------------------------------------------------------ facts of a walked module - -fn flag_words_of(walked: Walk, decl: String, kind: ArgvWordsDeclKind) -> List { - let reversed = fold_list(xs: walked.flag_words, empty: Empty, cons: fn(acc, f) { - if (f.decl == decl) && decl_kind_matches(a: f.decl_kind, b: kind) { Cons { head: f.word, tail: acc } } else { acc } - }) - fold_list(xs: list_reverse(xs: reversed), empty: Empty, cons: fn(acc, w) { if contains(xs: acc, item: w, eq: str_eq_fn) { acc } else { Cons { head: w, tail: acc } } }) -} - -fn decl_kind_matches(a: ArgvWordsDeclKind, b: ArgvWordsDeclKind) -> Bool { - match a { - FnDecl => match b { FnDecl => true, DataDecl => false } - DataDecl => match b { FnDecl => false, DataDecl => true } + let expr_atoms = match find_named_child(root: decl, name: ^dag_surface_data_decl_expr) { + Accepted { value: n, diagnostics: _ } => subtree_atom_symbols(n: n) + Rejected { diagnostics: _ } => [] + } + let is_list_string = type_atoms_list_string(atoms: type_atoms) + let contents = flag_contents_of_atoms(atoms: expr_atoms) + let has_flags = !(length(xs: contents) == 0) + let reached = match name_sym { + Absent => false + Present { value: s } => data_decl_reaches_argv(name_sym: s, fn_bodies: fn_argv_bodies) + } + let counted = is_list_string && has_flags && reached + Present { + value: ArgvWordsFact { + decl: name, + decl_kind: DataDecl, + counted: counted, + flag_words: if counted { contents } else { [] }, + candidates: if is_list_string && has_flags && !counted { contents } else { [] }, + return_spelling: "", + returns_argv_words: false, + list_string_params: 0, + list_string_data: is_list_string + } } } -fn argv_params_of(walked: Walk, decl: String) -> List { - let reversed = fold_list(xs: walked.param_types, empty: Empty, cons: fn(acc, p) { - if (p.decl == decl) && (p.spelling == "List") && member(xs: argv_position_name_rows, x: p.param) { Cons { head: p.param, tail: acc } } else { acc } +// All fn bodies in the module, flattened, for the data-decl reach test: only bodies of fns the +// lens counts as argv-typed can grant a data declaration an argv position. +fn argv_typed_fn_bodies(root: Node) -> List { + fold_list(xs: decl_bag_of_tree(root: root).fn_decls, empty: Empty, cons: fn(acc, decl) { + let is_test = node_production_is(node: decl, want: ^dag_surface_test_fn_decl) + let name = fn_decl_name(decl: decl, is_test: is_test) + let body_atoms = match parse_subtree_find_production_captured(root: decl, emitted: ^dag_surface_fn_body) { + ParseSubtreeFound { captured: c } => subtree_atom_symbols(n: c) + ParseSubtreeAbsent => [] + } + let return_atoms = match parse_production_captured_child_optional(node: decl) { + Absent => [] + Present { value: captured } => + match fn_decl_return_type(captured: captured) { + Absent => [] + Present { value: type_node } => subtree_atom_symbols(n: type_node) + } + } + let param_reads = match fn_decl_param_list(decl: decl) { + Absent => [] + Present { value: capture } => typed_params_of_capture(capture: capture, acc: []) + } + let argv_typed = (fold_list(xs: param_reads, empty: 0, cons: fn(a, p) { if type_atoms_list_string(atoms: p.type_atoms) { a + 1 } else { a } }) > 0) || type_atoms_list_string(atoms: return_atoms) + if argv_typed { concat(acc, body_atoms) } else { acc } }) - fold_list(xs: list_reverse(xs: reversed), empty: Empty, cons: fn(acc, p) { if contains(xs: acc, item: p, eq: str_eq_fn) { acc } else { Cons { head: p, tail: acc } } }) } -fn return_spelling_of(walked: Walk, decl: String) -> String { - fold_list(xs: walked.return_heads, empty: "", cons: fn(acc, r) { if r.decl == decl { r.spelling } else { acc } }) -} - -fn is_list_string_data(walked: Walk, decl: String) -> Bool { - member(xs: walked.data_list_string, x: decl) -} - -fn distinct_decls(xs: FreeMonoid) -> List { - let reversed = fold_list(xs: xs, empty: Empty, cons: fn(acc, d) { if member(xs: acc, x: d) { acc } else { Cons { head: d, tail: acc } } }) - list_to_string_list(reversed) -} - -fn list_to_string_list(m: FreeMonoid) -> List { - fold_list(xs: list_reverse(xs: m), empty: Empty, cons: fn(acc, s) { Cons { head: s, tail: acc } }) -} - -// The argv positions a List parameter is read at: the names reviewed against the committed -// roster at baseline. A parameter outside these names is not argv (a layout, a row of labels) and -// is not a census member by itself. A List spells as a FreeMonoid, so the rows are the set. -data argv_position_name_rows: List = ["argv", "command_argv", "command", "command_words", "args", "arguments", "words", "property_argv", "wait_argv", "unit_argv", "extra_argv", "flag_words"] - -fn argv_named_data_decl(decl: String) -> Bool { - ends_with(s: decl, suffix: "_args") || ends_with(s: decl, suffix: "_argv") || ends_with(s: decl, suffix: "_words") -} - -fn facts_of(walked: Walk) -> List { - let fn_decls = distinct_decls(fold_list(xs: walked.return_heads, empty: Empty, cons: fn(acc, r) { Cons { head: r.decl, tail: acc } })) - let fns = fold_list(xs: fn_decls, empty: Empty, cons: fn(acc, d) { - Cons { - head: ArgvWordsFact { - decl: d, - decl_kind: FnDecl, - flag_words: flag_words_of(walked: walked, decl: d, kind: FnDecl), - return_spelling: return_spelling_of(walked: walked, decl: d), - argv_params: argv_params_of(walked: walked, decl: d), - list_string_data: false - }, - tail: acc +fn facts_of(root: Node) -> List { + let bag = decl_bag_of_tree(root: root) + let fn_argv_bodies = argv_typed_fn_bodies(root: root) + let fn_facts = fold_list(xs: bag.fn_decls, empty: Empty, cons: fn(acc, decl) { + let is_test = node_production_is(node: decl, want: ^dag_surface_test_fn_decl) + match fn_fact(decl: decl, is_test: is_test) { + Absent => acc + Present { value: f } => Cons { head: f, tail: acc } } }) - let data_decls = distinct_decls(walked.data_list_string) - fold_list(xs: data_decls, empty: fns, cons: fn(acc, d) { - Cons { - head: ArgvWordsFact { - decl: d, - decl_kind: DataDecl, - flag_words: flag_words_of(walked: walked, decl: d, kind: DataDecl), - return_spelling: "", - argv_params: Empty, - list_string_data: true - }, - tail: acc + fold_list(xs: bag.data_decls, empty: fn_facts, cons: fn(acc, decl) { + match data_fact(decl: decl, fn_argv_bodies: fn_argv_bodies) { + Absent => acc + Present { value: f } => Cons { head: f, tail: acc } } }) } // ------------------------------------------------------------------ sites +// A fact joins the census when it is debt (counted) or a reported candidate. A data declaration +// that is a plain word list with no flag vocabulary and no reach is neither: the List +// population is a ceiling, not the census. fn is_census_member(f: ArgvWordsFact) -> Bool { - match f.decl_kind { - FnDecl => - !(length(xs: f.flag_words) == 0) - || (f.return_spelling == "List") - || !(length(xs: f.argv_params) == 0) - DataDecl => - !(length(xs: f.flag_words) == 0) - || argv_named_data_decl(decl: f.decl) - } + f.counted || !(length(xs: f.candidates) == 0) || (f.decl_kind == FnDecl && f.returns_argv_words) || (f.decl_kind == FnDecl && f.list_string_params > 0) } fn site_class_of(module_file: String) -> Optional { @@ -613,9 +554,11 @@ fn argv_word_construction_sites_of_facts(module_file: String, facts: List", - argv_word_params: f.argv_params, + candidates: f.candidates, + returns_argv_words: f.returns_argv_words, + list_string_params: f.list_string_params, list_string_data: f.list_string_data }, tail: acc @@ -669,10 +612,12 @@ fn site_line(s: ArgvWordsSite) -> String { let decl = concat(escape_field(s: s.decl), ",") let kind = concat(decl_kind_name(k: s.decl_kind), ",") let class = concat(site_class_name(c: s.site_class), ",") + let counted = concat(bool_word(b: s.counted), ",") let flags = concat(join_words(ws: s.flag_words), ",") + let cands = concat(join_words(ws: s.candidates), ",") let ret = concat(bool_word(b: s.returns_argv_words), ",") - let params = concat(join_words(ws: s.argv_word_params), ",") - concat(concat(concat(concat(concat(concat(concat(file, decl), kind), class), flags), ret), params), concat(bool_word(b: s.list_string_data), "\n")) + let params = concat(integer_int_to_decimal_string(value: s.list_string_params), ",") + concat(concat(concat(concat(concat(concat(concat(concat(concat(concat(file, decl), kind), class), counted), flags), cands), ret), params), bool_word(b: s.list_string_data)), "\n") } fn sites_text(sites: List) -> String { @@ -714,9 +659,9 @@ type ArgvWordsModuleStanding = ArgvWordsModuleEstablished { path: String, sites: List } | ArgvWordsModuleRefused { path: String, reason: String } -// The census read of ONE module file, end to end: read, raw parse, walk, facts, sites. The read -// folds through filesystem_read_outcome (the module's modeled read fold), so refusal and content -// cannot be conflated at this site. +// The census read of ONE module file, end to end: read, raw parse, tree query, facts, sites. The +// read folds through filesystem_read_outcome (the module's modeled read fold), so refusal and +// content cannot be conflated at this site. fn argv_word_construction_module_sites(path: String) -> ArgvWordsModuleStanding { let read = Filesystem.Read(path: path) match filesystem_read_outcome(content: read.content, success: read.success, error: read.error) { @@ -725,7 +670,7 @@ fn argv_word_construction_module_sites(path: String) -> ArgvWordsModuleStanding match raw_tree_of_text(text: text) { RawTreeNotEstablished { cause: c } => ArgvWordsModuleRefused { path: path, reason: concat("raw parse not established: ", source_cause_name(c: c)) } RawTreeEstablished { root: root } => - match argv_word_construction_sites_of_facts(module_file: path, facts: facts_of(walked: walk_tree(root: root))) { + match argv_word_construction_sites_of_facts(module_file: path, facts: facts_of(root: root)) { ArgvWordsSitesRefused { reason: r } => ArgvWordsModuleRefused { path: path, reason: r } ArgvWordsSitesEstablished { sites: sites } => ArgvWordsModuleEstablished { path: path, sites: sites } } @@ -765,8 +710,9 @@ fn joined(lines_reversed: FreeMonoid) -> String { // ------------------------------------------------------------------ the roster gates // A committed roster row: one census member, at identity grain, with its classification and the -// disposition note that names what pays it. The roster may only shrink: rows resolve against live -// sites, live members must be covered, and the population never grows past the committed baseline. +// disposition note that names what pays it. The roster may only shrink: COUNTED rows resolve +// against live COUNTED sites, live COUNTED members must be covered, and the population never +// grows past the committed baseline. Candidate rows are reported, never gated. type ArgvWordsRosterRow { module_file: String decl: String @@ -776,7 +722,14 @@ type ArgvWordsRosterRow { } fn row_matches_site(r: ArgvWordsRosterRow, s: ArgvWordsSite) -> Bool { - (r.module_file == s.module_file) && (r.decl == s.decl) && decl_kind_matches(a: r.decl_kind, b: s.decl_kind) + s.counted && (r.module_file == s.module_file) && (r.decl == s.decl) && decl_kind_matches(a: r.decl_kind, b: s.decl_kind) +} + +fn decl_kind_matches(a: ArgvWordsDeclKind, b: ArgvWordsDeclKind) -> Bool { + match a { + FnDecl => match b { FnDecl => true, DataDecl => false } + DataDecl => match b { FnDecl => false, DataDecl => true } + } } // Every row must resolve to a live census member: a row whose site was paid without deleting the @@ -796,7 +749,7 @@ fn argv_word_construction_rows_resolve(live: List, roster: List, roster: List) -> ProcessExit { match fold_list(xs: live, empty: Empty, cons: fn(acc, s) { let covered = fold_list(xs: roster, empty: false, cons: fn(acc2, r) { acc2 || row_matches_site(r: r, s: s) }) - if covered { acc } else { Cons { head: concat(concat(concat(concat("uncovered member: ", s.module_file), " "), s.decl), concat(" (", concat(decl_kind_name(k: s.decl_kind), ") builds argv words without a typed form and without a roster row"))), tail: acc } } + if covered || !s.counted { acc } else { Cons { head: concat(concat(concat(concat("uncovered member: ", s.module_file), " "), s.decl), concat(" (", concat(decl_kind_name(k: s.decl_kind), ") builds argv words without a typed form and without a roster row"))), tail: acc } } }) { Empty => ExitSuccess nonempty => exit_failure(reason: joined(lines_reversed: nonempty)) @@ -825,86 +778,3 @@ fn argv_word_construction_gates(live: List, roster: List refused } } - -// ── THROWAWAY DIAGNOSTICS (strip before commit) ───────────────────────────────────────────────── -fn argv_words_diagnostic_stream(path: String) -> List { - let read = Filesystem.Read(path: path) - match filesystem_read_outcome(content: read.content, success: read.success, error: read.error) { - FilesystemReadRefused { error: _ } => ["READ-REFUSED"] - FilesystemReadSucceeded { content: text } => - match raw_tree_of_text(text: text) { - RawTreeNotEstablished { cause: c } => ["RAW-NOT-ESTABLISHED", source_cause_name(c: c)] - RawTreeEstablished { root: root } => fold_list(xs: atoms_reversed(n: root, acc: Empty), empty: Empty, cons: fn(acc, a) { Cons { head: a.lexeme, tail: acc } }) - } - } -} - -fn argv_words_diagnostic_fact_decls(path: String) -> List { - let read = Filesystem.Read(path: path) - match filesystem_read_outcome(content: read.content, success: read.success, error: read.error) { - FilesystemReadRefused { error: _ } => ["READ-REFUSED"] - FilesystemReadSucceeded { content: text } => - match raw_tree_of_text(text: text) { - RawTreeNotEstablished { cause: _ } => ["RAW-NOT-ESTABLISHED"] - RawTreeEstablished { root: root } => - fold_list(xs: facts_of(walked: walk_tree(root: root)), empty: Empty, cons: fn(acc, f) { Cons { head: f.decl, tail: acc } }) - } - } -} - -// ── THROWAWAY CONTEXT READOUT (strip before commit) ───────────────────────────────────────────── -// The atom window ENDING at the FIRST occurrence of needle, oldest-first, capped at span — so the -// classifiers' adjacency guesses are replaced by the observed sequence. -type CtxFold { - buf: List - found: List - seen: Bool -} - -type CappedFold { - kept: List - seen: Int -} - -type PairFold { - prev: String - hit: Bool -} - -fn context_trim_newest(buf: List, keep: Int) -> List { - // buf is newest-first; keep only its first `keep` elements (the newest atoms). - fold_list(xs: buf, empty: CappedFold { kept: Empty, seen: 0 }, cons: fn(acc, x) { - match acc.seen < keep { - true => CappedFold { kept: Cons { head: x, tail: acc.kept }, seen: acc.seen + 1 } - false => acc - } - }).kept -} - -fn context_of_atoms(atoms: List, needle: String, span: Int) -> List { - fold_list(xs: atoms, empty: CtxFold { buf: Empty, found: Empty, seen: false }, cons: fn(acc, a) { - match acc.seen { - true => acc - false => { - let buf2 = context_trim_newest(buf: Cons { head: a, tail: acc.buf }, keep: span) - match a == needle { - true => CtxFold { buf: buf2, found: list_reverse(xs: buf2), seen: true } - false => CtxFold { buf: buf2, found: Empty, seen: false } - } - } - } - }).found -} - -fn argv_words_diagnostic_context(path: String, needle: String, span: Int) -> List { - context_of_atoms(atoms: argv_words_diagnostic_stream(path: path), needle: needle, span: span) -} - -fn window_has_pair(window: List, x: String, y: String) -> Bool { - fold_list(xs: window, empty: PairFold { prev: "", hit: false }, cons: fn(acc, a) { - match acc.hit { - true => acc - false => PairFold { prev: a, hit: (acc.prev == x) && (a == y) } - } - }).hit -} diff --git a/src/v2/test/claim/argv_word_construction/census_test.dag b/src/v2/test/claim/argv_word_construction/census_test.dag index b4900ecb6ee..1d5d1f414c1 100644 --- a/src/v2/test/claim/argv_word_construction/census_test.dag +++ b/src/v2/test/claim/argv_word_construction/census_test.dag @@ -79,7 +79,7 @@ test fn census_fixture_route_reads_fn_signals() -> Bool { match argv_word_construction_module_sites(path: fixture_path) { ArgvWordsModuleEstablished { path: _, sites: sites } => match argv_word_construction_site_named(sites: sites, module_file: fixture_path, decl: "fixture_run_with_properties") { - Present { value: s } => (s.decl_kind == FnDecl) && (s.returns_argv_words == true) && member_named(xs: s.argv_word_params, x: "property_argv") + Present { value: s } => (s.decl_kind == FnDecl) && (s.returns_argv_words == true) && (s.list_string_params == 1) Absent => false } && match argv_word_construction_site_named(sites: sites, module_file: fixture_path, decl: "fixture_transient_unit_argv") { @@ -145,8 +145,10 @@ fn live_member(path: String, decl: String) -> ArgvWordsSite { decl_kind: FnDecl, site_class: WorkflowCallSite, flag_words: ["--wait"], + counted: true, + candidates: Empty, returns_argv_words: false, - argv_word_params: Empty, + list_string_params: 0, list_string_data: false } } From aae91acc07b283abf4b8eda719e9e5604c05f187 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 04:22:34 +0000 Subject: [PATCH 21/65] add the audit and supervisor provider-events path helpers main's metering module imports --- dag/gunbc/roadmap/roadmap_dispatch_actuator.dag | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dag/gunbc/roadmap/roadmap_dispatch_actuator.dag b/dag/gunbc/roadmap/roadmap_dispatch_actuator.dag index f9eb5cb5da2..20220d60c27 100644 --- a/dag/gunbc/roadmap/roadmap_dispatch_actuator.dag +++ b/dag/gunbc/roadmap/roadmap_dispatch_actuator.dag @@ -1172,6 +1172,14 @@ fn dispatch_attempt_review_verdict_basename(head_sha: String, criterion_key: Str join([head_sha, ".", criterion_key, ".json"], "") } +fn dispatch_attempt_audit_events_path_for_instance(instance: HostDashboardInstance, node_id: RoadmapNodeId, attempt_key: String, head_sha: String) -> String { + join([dispatch_attempt_audit_dir_for_instance(instance: instance, node_id: node_id, attempt_key: attempt_key), "/", head_sha, ".provider-events.jsonl"], "") +} + +fn dispatch_attempt_supervisor_events_path_for_instance(instance: HostDashboardInstance, node_id: RoadmapNodeId, attempt_key: String, turn: Int) -> String { + join([dispatch_attempt_supervisor_dir_for_instance(instance: instance, node_id: node_id, attempt_key: attempt_key), "/t", to_string(value: turn), ".provider-events.jsonl"], "") +} + fn dispatch_attempt_review_events_path_for_instance(instance: HostDashboardInstance, node_id: RoadmapNodeId, attempt_key: String, criterion_key: String, head_sha: String) -> String { join([dispatch_attempt_review_dir_for_instance(instance: instance, node_id: node_id, attempt_key: attempt_key), "/", criterion_key, "@", head_sha, ".provider-events.jsonl"], "") } From 1d33e409ef09026a556c652a4196105a5d59b109 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 05:23:40 +0000 Subject: [PATCH 22/65] belt: take main's metering tick plumbing wholesale and transplant the three typed launch matches into its roster wrappers (imports: typed commands + dispatch_unit_exec_argv) --- dag/gunbc/roadmap/roadmap_belt_actuate.dag | 201 ++++++++++++++------- 1 file changed, 140 insertions(+), 61 deletions(-) diff --git a/dag/gunbc/roadmap/roadmap_belt_actuate.dag b/dag/gunbc/roadmap/roadmap_belt_actuate.dag index 8d518b8d802..caf21f63a26 100644 --- a/dag/gunbc/roadmap/roadmap_belt_actuate.dag +++ b/dag/gunbc/roadmap/roadmap_belt_actuate.dag @@ -28,15 +28,14 @@ import gunbc.roadmap.roadmap_review_function { } import gunbc.roadmap.roadmap_review_criteria { ReviewCriterion } import gunbc.roadmap.roadmap_goal_audit_role { goal_audit_criterion_key, goal_audit_approved, goal_audit_reconcile, goal_audit_brief } -import extdeps.systemd.systemd_run { SystemdRunCommandReady, SystemdRunCommandRefused } import gunbc.roadmap_dispatch_actuator { dispatch_events_projection_argv_at, dispatch_current_attempt_events_path_for_instance } import gunbc.roadmap_dispatch_actuator { dispatch_attempt_review_dir_for_instance, dispatch_attempt_review_verdict_path_for_instance, dispatch_attempt_review_verdict_basename, - dispatch_review_unit_name, dispatch_review_unit_command, dispatch_unit_exec_argv, + dispatch_review_unit_name, dispatch_review_unit_command, dispatch_attempt_audit_dir_for_instance, dispatch_attempt_audit_verdict_path_for_instance, dispatch_attempt_audit_verdict_basename, - dispatch_audit_unit_name, dispatch_audit_unit_command, dispatch_unit_exec_argv, - dispatch_attempt_supervisor_dir_for_instance, dispatch_supervisor_unit_name, dispatch_supervisor_unit_command, dispatch_unit_exec_argv, - dispatch_supervisor_attempt_identity, + dispatch_audit_unit_name, dispatch_audit_unit_command, + dispatch_attempt_supervisor_dir_for_instance, dispatch_supervisor_unit_name, dispatch_supervisor_unit_command, + dispatch_supervisor_attempt_identity, dispatch_unit_exec_argv, } import extdeps.git { shape_git_merge_base_args } import gunbc.roadmap_authority { @@ -116,7 +115,7 @@ import gunbc.roadmap_dispatch_actuator { DispatchLiveSession, DispatchSpawnCommands, DispatchSessionContainer, TmuxSessionContainer, SystemdUnitContainer, - dispatch_worker_unit_properties_for_plan, dispatch_worker_unit_command, dispatch_unit_exec_argv, + dispatch_worker_unit_properties_for_plan, dispatch_worker_unit_run, dispatch_attempt_unit_name, attempt_unit_observation, worker_process_from_attempt_unit, @@ -1346,15 +1345,10 @@ fn belt_session_container_create( match session_placement_begin_launch(stores: held.stores, grant: held.grant) { SessionLaunchRefused { detail } => ExecStepFailed { step: "session-launch-gate", detail: detail } SessionLaunchAdmitted { grant: launched } => - match dispatch_worker_unit_command(instance: instance, plan: plan, granted: launched.amount, process_bounds: session_process_bounds) { - SystemdRunCommandRefused { reason: why } => - ExecStepFailed { step: "harness-unit-start", detail: why } - SystemdRunCommandReady { command: command } => - belt_exec_steps( - steps: [BeltExecStep { step: "harness-unit-start", command: dispatch_unit_exec_argv(command: command, resolved_program: plan.systemd_run_program) }], - workdir: workdir, - ) - } + belt_exec_steps( + steps: [BeltExecStep { step: "harness-unit-start", command: dispatch_worker_unit_run(instance: instance, plan: plan, granted: launched.amount, process_bounds: session_process_bounds) }], + workdir: workdir, + ) } } } @@ -2573,57 +2567,57 @@ fn belt_supervisor_convene( match metering_roster_admit(instance: instance, node_id: nid, attempt_key: escalation.attempt_key, role: MeteredSupervisor { turn: escalation.turn }) { RosterAdmissionRefused { cause: roster_cause } => SpawnFailed { node_id: node.node as String, step: "supervisor-roster", detail: join(["the supervisor was not launched: ", roster_cause], ""), provider: provider_wire_label, launch: launch } RosterAdmitted => - match dispatch_supervisor_unit_command( - instance: instance, - node_id: nid, - attempt_key: escalation.attempt_key, - turn: escalation.turn, - worktree_path: worktree, - brief: belt_supervisor_convene_brief( - node: node, - escalation: escalation, - checkpoint: belt_supervisor_checkpoint_lines( - capture: belt_prior_attempt_capture(instance: instance, node_id: nid, attempt_key: escalation.attempt_key), - head_sha: revision as String, - history_line: belt_supervisor_history_line(instance: instance, node_id: nid, attempt_key: escalation.attempt_key, turn: escalation.turn), - ), - ), - ) { - SystemdRunCommandRefused { reason: why } => - SpawnFailed { - node_id: node.node as String, - step: "supervisor-unit-start", - detail: why, - provider: provider_wire_label, - launch: launch, - } - SystemdRunCommandReady { command: command } => - match belt_exec(cmd: dispatch_unit_exec_argv(command: command, resolved_program: systemd_run), workdir: worktree) { - ExecOk { stdout: _ } => - SupervisorConvened { - node_id: node.node as String, - unit: unit as String, - provider: provider_wire_label, - launch: launch, - } - ExecFailed { program: _, exit_code, stderr } => + match dispatch_supervisor_unit_command( + instance: instance, + node_id: nid, + attempt_key: escalation.attempt_key, + turn: escalation.turn, + worktree_path: worktree, + brief: belt_supervisor_convene_brief( + node: node, + escalation: escalation, + checkpoint: belt_supervisor_checkpoint_lines( + capture: belt_prior_attempt_capture(instance: instance, node_id: nid, attempt_key: escalation.attempt_key), + head_sha: revision as String, + history_line: belt_supervisor_history_line(instance: instance, node_id: nid, attempt_key: escalation.attempt_key, turn: escalation.turn), + ), + ), + ) { + SystemdRunCommandRefused { reason: why } => SpawnFailed { node_id: node.node as String, step: "supervisor-unit-start", - detail: join(["the supervisor unit could not be started (exit ", to_string(exit_code), "): ", stderr], ""), + detail: why, provider: provider_wire_label, launch: launch, } - ExecRefused { program: _, reason } => - SpawnFailed { - node_id: node.node as String, - step: "supervisor-unit-start", - detail: reason, - provider: provider_wire_label, - launch: launch, + SystemdRunCommandReady { command: command } => + match belt_exec(cmd: dispatch_unit_exec_argv(command: command, resolved_program: systemd_run), workdir: worktree) { + ExecOk { stdout: _ } => + SupervisorConvened { + node_id: node.node as String, + unit: unit as String, + provider: provider_wire_label, + launch: launch, + } + ExecFailed { program: _, exit_code, stderr } => + SpawnFailed { + node_id: node.node as String, + step: "supervisor-unit-start", + detail: join(["the supervisor unit could not be started (exit ", to_string(exit_code), "): ", stderr], ""), + provider: provider_wire_label, + launch: launch, + } + ExecRefused { program: _, reason } => + SpawnFailed { + node_id: node.node as String, + step: "supervisor-unit-start", + detail: reason, + provider: provider_wire_label, + launch: launch, + } } } - } } } } @@ -3368,6 +3362,7 @@ type BeltTickReceipt { commit_pass: BeltPassOutcome, verify_pass: BeltPassOutcome, publish_pass: BeltPassOutcome, + metering_pass: BeltPassOutcome, } type BeltTickReceiptRead @@ -3390,7 +3385,7 @@ type BeltTickReceiptRead // DispatchStateHostShared, so blue and green resolve it to ONE path and a receipt there would // answer for two instances. A note describing a home the implementation did not and could not // safely use is the stale-authority class of DESIGN section 3. -data belt_tick_receipt_schema: String = "roadmap-belt-tick-receipt/v5" +data belt_tick_receipt_schema: String = "roadmap-belt-tick-receipt/v6" // THE RECEIPT HAD NO WRITABLE HOME AND HAS NEVER BEEN WRITTEN ON ANY INSTANCE. The path was // /belt-last-tick.json; every instance_root on srv1 is root:root 0755 while the belt @@ -3549,6 +3544,9 @@ fn belt_tick_receipt_decode(raw: String) -> BeltTickReceiptRead { BeltTickExecutionUnreadable { reason: r } => BeltTickReceiptUnreadable { reason: r } BeltTickExecutionDecoded { execution } => { + match belt_pass_outcome_from_member(doc: doc, key: "metering_pass") { + BeltPassOutcomeUnreadable { reason: r } => BeltTickReceiptUnreadable { reason: r } + BeltPassOutcomeDecoded { outcome: metering_pass } => { let receipt = BeltTickReceipt { observed_at: observed_at, execution: execution, @@ -3558,6 +3556,7 @@ fn belt_tick_receipt_decode(raw: String) -> BeltTickReceiptRead { commit_pass: commit_pass, verify_pass: verify_pass, publish_pass: publish_pass, + metering_pass: metering_pass, } match json_object_unique_member(v: doc, key: "receipt_identity") { JsonMemberFound { value: JsonString { value: claimed } } => @@ -3576,6 +3575,8 @@ fn belt_tick_receipt_decode(raw: String) -> BeltTickReceiptRead { BeltTickReceiptUnreadable { reason: "belt tick receipt receipt_identity is missing" } } } + } +} } } } @@ -3713,13 +3714,14 @@ fn belt_tick_receipt_identity_text(receipt: BeltTickReceipt) -> String { belt_pass_outcome_key(o: receipt.teardown_pass), "|", belt_pass_outcome_key(o: receipt.commit_pass), "|", belt_pass_outcome_key(o: receipt.verify_pass), "|", - belt_pass_outcome_key(o: receipt.publish_pass), + belt_pass_outcome_key(o: receipt.publish_pass), "|", + belt_pass_outcome_key(o: receipt.metering_pass), ], "") } fn belt_tick_receipt_identity_hex(receipt: BeltTickReceipt) -> String { content_hash_tagged_structural( - tag: "roadmap-belt-tick-receipt-v4" as NonEmptyStr, + tag: "roadmap-belt-tick-receipt-v5" as NonEmptyStr, payload: content_hash_atom(value: belt_tick_receipt_identity_text(receipt: receipt) as NonEmptyStr), ).digest as String } @@ -3797,6 +3799,7 @@ fn belt_tick_receipt_json_value(receipt: BeltTickReceipt) -> JsonValue { json_kv(key: "commit_pass", value: belt_pass_outcome_json(o: receipt.commit_pass)), json_kv(key: "verify_pass", value: belt_pass_outcome_json(o: receipt.verify_pass)), json_kv(key: "publish_pass", value: belt_pass_outcome_json(o: receipt.publish_pass)), + json_kv(key: "metering_pass", value: belt_pass_outcome_json(o: receipt.metering_pass)), ]) } @@ -3933,6 +3936,7 @@ type BeltTickResult { publish_outcomes: List, review_outcomes: List, audit_outcomes: List, + metering_outcomes: List, } fn belt_tick_empty_reconcile(state: SpawnMode) -> BeltReconcile { @@ -4240,6 +4244,7 @@ fn belt_tick_for_instance( publish_outcomes: [], review_outcomes: [], audit_outcomes: [], + metering_outcomes: [], } TickPassesRun => { let commit_outcomes = belt_commit_attempts_for_instance(instance: instance) @@ -4257,6 +4262,7 @@ fn belt_tick_for_instance( publish_outcomes: belt_publish_attempts_for_instance(instance: instance), review_outcomes: review_outcomes, audit_outcomes: audit_outcomes, + metering_outcomes: belt_metering_attempts_for_instance(instance: instance), } } } @@ -4500,6 +4506,7 @@ fn belt_run_once_in( publish_outcomes: belt_publish_attempts_for_instance(instance: instance), review_outcomes: review_outcomes, audit_outcomes: audit_outcomes, + metering_outcomes: belt_metering_attempts_for_instance(instance: instance), } } RoadmapAuthorityProjected { doc: doc, accepted: _, withheld: _ } => @@ -6986,6 +6993,74 @@ fn belt_commit_outcome_persist( } } +// THE PER-ATTEMPT METERING RECORD (gunbc.roadmap_attempt_metering). Every attempt in the workflow +// ledger is entered in the metering attempt index and, once its worker is terminal, metered against +// its open roster -- a Provisional record, rewritten each tick so later reviewer, auditor and +// supervisor rounds and late seat releases are folded in. An indexed attempt that has LEFT the ledger +// can no longer be spawned for, because every harness spawner iterates that ledger; it is sealed and +// metered against the seal, which is the only route to a Final record. The pass runs after review +// and audit, and its outcomes are the tick receipt's metering_pass. +fn belt_metering_for_attempt( + instance: HostDashboardInstance, + progress: WorkflowAttemptProgress, +) -> MeteringPersist { + match metering_attempt_index_admit(instance: instance, node_id: progress.node_id, attempt_key: progress.attempt_key) { + Present { value: e } => MeteringPersistRefused { cause: join(["the metering attempt index could not be written: ", e], "") } + Absent => + if belt_attempt_worker_terminal(provider: progress.provider, evidence: progress.process) { + metering_persist_for_attempt(instance: instance, node_id: progress.node_id as RoadmapNodeId, attempt_key: progress.attempt_key, departed: false) + } else { + MeteringNotDue + } + } +} + +fn belt_metering_departed( + instance: HostDashboardInstance, + attempts: List, +) -> List { + match metering_attempt_index(instance: instance) { + MeteredAttemptsRefused { cause } => [MeteringPersistRefused { cause: join(["the metering attempt index is unreadable: ", cause], "") }] + MeteredAttemptsObserved { attempts: indexed } => + flat_map(indexed, a => + if any(attempts, p => p.node_id == a.node_id && p.attempt_key == a.attempt_key) { + [] as List + } else { + [metering_persist_for_attempt(instance: instance, node_id: a.node_id as RoadmapNodeId, attempt_key: a.attempt_key, departed: true)] + }) + } +} + +fn belt_metering_attempts_for_instance( + instance: HostDashboardInstance, +) -> List { + match belt_workflow_attempts_observe_for_instance(instance: instance) { + WorkflowAttemptsRefused { reason } => + [MeteringPersistRefused { cause: join(["the attempts ledger is unobservable, so no attempt can be metered or sealed: ", reason], "") }] + WorkflowAttemptsObserved { attempts } => + concat( + map(attempts, progress => belt_metering_for_attempt(instance: instance, progress: progress)), + belt_metering_departed(instance: instance, attempts: attempts), + ) + } +} + +// A REFUSED METERING WRITE FAILS THE PASS, counted and named, so a lost record stops the line on the +// tick receipt rather than surfacing only as an absent record at the reader. +fn belt_metering_pass_outcome_from(outcomes: List) -> BeltPassOutcome { + let refused = outcomes |> flat_map(o => match o { + MeteringPersistRefused { cause: c } => [c] + MeteringPersisted { path: _, standing: _ } => [] as List + MeteringAlreadyFinal => [] as List + MeteringNotDue => [] as List + }) + match refused.first() { + Absent => BeltPassRecorded + Present { value: c } => + BeltPassFailed { reason: join([to_string(count(refused)), " metering record(s) refused; first: ", c], "") } + } +} + fn belt_commit_attempt_for_instance( instance: HostDashboardInstance, progress: WorkflowAttemptProgress, @@ -11510,6 +11585,10 @@ fn belt_tick_receipt_from_result( TickPassesWithheld { refusal } => BeltPassRefused { reason: launch_refusal_reason(r: refusal) } TickPassesRun => belt_publish_pass_outcome_from(outcomes: result.publish_outcomes) }), + metering_pass: (match result.wholesale { + TickPassesWithheld { refusal } => BeltPassRefused { reason: launch_refusal_reason(r: refusal) } + TickPassesRun => belt_metering_pass_outcome_from(outcomes: result.metering_outcomes) + }), } } From 81b75aab6e9761857c5bd450130b48818202f6be Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 06:07:41 +0000 Subject: [PATCH 23/65] belt: transplant the worker session-container launch onto the typed builder (fourth site main's metering refactor still ran through dispatch_worker_unit_run) --- dag/gunbc/roadmap/roadmap_belt_actuate.dag | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/dag/gunbc/roadmap/roadmap_belt_actuate.dag b/dag/gunbc/roadmap/roadmap_belt_actuate.dag index caf21f63a26..814dddb121a 100644 --- a/dag/gunbc/roadmap/roadmap_belt_actuate.dag +++ b/dag/gunbc/roadmap/roadmap_belt_actuate.dag @@ -115,7 +115,7 @@ import gunbc.roadmap_dispatch_actuator { DispatchLiveSession, DispatchSpawnCommands, DispatchSessionContainer, TmuxSessionContainer, SystemdUnitContainer, - dispatch_worker_unit_properties_for_plan, dispatch_worker_unit_run, + dispatch_worker_unit_properties_for_plan, dispatch_worker_unit_command, dispatch_attempt_unit_name, attempt_unit_observation, worker_process_from_attempt_unit, @@ -1345,10 +1345,15 @@ fn belt_session_container_create( match session_placement_begin_launch(stores: held.stores, grant: held.grant) { SessionLaunchRefused { detail } => ExecStepFailed { step: "session-launch-gate", detail: detail } SessionLaunchAdmitted { grant: launched } => - belt_exec_steps( - steps: [BeltExecStep { step: "harness-unit-start", command: dispatch_worker_unit_run(instance: instance, plan: plan, granted: launched.amount, process_bounds: session_process_bounds) }], - workdir: workdir, - ) + match dispatch_worker_unit_command(instance: instance, plan: plan, granted: launched.amount, process_bounds: session_process_bounds) { + SystemdRunCommandRefused { reason: why } => + ExecStepFailed { step: "harness-unit-start", detail: why } + SystemdRunCommandReady { command: command } => + belt_exec_steps( + steps: [BeltExecStep { step: "harness-unit-start", command: dispatch_unit_exec_argv(command: command, resolved_program: plan.systemd_run_program) }], + workdir: workdir, + ) + } } } } From 1afb9ae111f28356066907bbd1bb7f71468a6f60 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 07:58:29 +0000 Subject: [PATCH 24/65] review 76279: refusal causes are a typed sum (SystemdRunOptionWordsRefusedCause: UnitNameContainsSlash | UnitNameMultiline | UnitSettingValueMultiline { property } | SetenvNameContainsEquals | SetenvNameMultiline | SetenvValueMultiline) with one prose projection for detail carriers; consumers match on the constructor (fabric refusal claims discriminate, plus a two-cause discrimination control); retained wet claim resets its failed unit after observing (rerunnable), and the module path follows the manual/ placement --- dag/extdeps/systemd/systemd_run.dag | 48 ++++++++++++++----- .../approval_device_enrolment_code_issue.dag | 5 +- dag/gunbc/compute/work_provider_local.dag | 5 +- dag/gunbc/compute/work_request.dag | 7 +-- .../host/host_effect_nbd_proxy_serve.dag | 7 +-- dag/gunbc/roadmap/roadmap_belt_actuate.dag | 18 +++---- dag/gunbc/runner/runner_host_unit_slot.dag | 8 ++-- .../runner_microvm_lifecycle_realize.dag | 3 +- ...val_device_enrolment_code_witness_test.dag | 2 +- .../compute/work_class_grant_witness_test.dag | 6 +-- ...ystemd_run_transient_wait_witness_test.dag | 40 +++++++++++----- .../github_app_registry_witness_test.dag | 2 +- ...nbd_proxy_serve_transport_witness_test.dag | 8 ++-- ...roadmap_dispatch_actuator_witness_test.dag | 6 +-- ...r_microvm_slot_controller_witness_test.dag | 2 +- ..._throughput_qualification_witness_test.dag | 2 +- ...ner_microvm_lifecycle_wet_receipt_test.dag | 10 +--- dag/test/manual/typed_builder_wet_test.dag | 13 +++-- 18 files changed, 116 insertions(+), 76 deletions(-) diff --git a/dag/extdeps/systemd/systemd_run.dag b/dag/extdeps/systemd/systemd_run.dag index b0b045c8c74..938dafcc1ca 100644 --- a/dag/extdeps/systemd/systemd_run.dag +++ b/dag/extdeps/systemd/systemd_run.dag @@ -101,20 +101,42 @@ type SystemdRunOption // typed value and the words, not a mangled word downstream. Each arm names the wire fact it // defends (unit names and setting values are one line and contain no slash; an environment name // is the text before the first '='). +type SystemdRunOptionWordsRefusedCause + = UnitNameContainsSlash + | UnitNameMultiline + | UnitSettingValueMultiline { property: SystemdUnitProperty } + | SetenvNameContainsEquals + | SetenvNameMultiline + | SetenvValueMultiline + type SystemdRunOptionWordsReading = SystemdRunOptionWordsProjected { words: List } - | SystemdRunOptionWordsRefused { reason: String } + | SystemdRunOptionWordsRefused { cause: SystemdRunOptionWordsRefusedCause } type SystemdRunCommandReading = SystemdRunCommandReady { command: ArgvCommand } - | SystemdRunCommandRefused { reason: String } + | SystemdRunCommandRefused { cause: SystemdRunOptionWordsRefusedCause } fn systemd_run_option_word_has_newline(s: String) -> Bool { string_contains(s: s, pattern: "\n") } -fn systemd_run_option_word_refused(reason: String) -> SystemdRunOptionWordsReading { - SystemdRunOptionWordsRefused { reason: reason } +fn systemd_run_option_word_refused(cause: SystemdRunOptionWordsRefusedCause) -> SystemdRunOptionWordsReading { + SystemdRunOptionWordsRefused { cause: cause } +} + +// The one prose projection of a refusal cause, for consumers whose carriers are prose (belt +// step details, log lines). Every arm names the wire fact it defends; the cause sum itself is +// what callers match on. +fn systemd_run_option_words_refused_cause_detail(cause: SystemdRunOptionWordsRefusedCause) -> String { + match cause { + UnitNameContainsSlash => "a unit name never contains / (systemd.unit(5))" + UnitNameMultiline => "a unit name is one line" + UnitSettingValueMultiline { property: p } => concat("unit setting value is one line, so --property= cannot carry a newline in: ", systemd_unit_property_wire(property: p)) + SetenvNameContainsEquals => "--setenv= takes NAME=VALUE, so the NAME side carries no = of its own" + SetenvNameMultiline => "--setenv= takes NAME=VALUE, so the NAME side is one line" + SetenvValueMultiline => "--setenv= takes NAME=VALUE, so the VALUE side is one line" + } } fn systemd_run_property_options_long(properties: List) -> List { @@ -129,14 +151,14 @@ fn systemd_run_property_options_short(properties: List) -> L // these so a refused command never mints words at all (short-circuit on the first refusal). fn systemd_run_option_projection_step(acc: SystemdRunOptionWordsReading, option: SystemdRunOption) -> SystemdRunOptionWordsReading { match acc { - SystemdRunOptionWordsRefused { reason: r } => SystemdRunOptionWordsRefused { reason: r } + SystemdRunOptionWordsRefused { cause: c } => SystemdRunOptionWordsRefused { cause: c } SystemdRunOptionWordsProjected { words: words } => match option { RunUnit { unit: unit } => if string_contains(s: unit as String, pattern: "/") { - systemd_run_option_word_refused(reason: "--unit= takes a unit name, and a unit name never contains / (systemd.unit(5))") + systemd_run_option_word_refused(cause: UnitNameContainsSlash) } else if systemd_run_option_word_has_newline(s: unit as String) { - systemd_run_option_word_refused(reason: "--unit= takes a unit name, and a unit name is one line") + systemd_run_option_word_refused(cause: UnitNameMultiline) } else { SystemdRunOptionWordsProjected { words: list_append(left: words, right: [concat("--unit=", unit as String)]) } } @@ -144,23 +166,23 @@ fn systemd_run_option_projection_step(acc: SystemdRunOptionWordsReading, option: RunScope => SystemdRunOptionWordsProjected { words: list_append(left: words, right: ["--scope"]) } PropertyShort { property: p } => if systemd_run_option_word_has_newline(s: p.value as String) { - systemd_run_option_word_refused(reason: concat("unit setting value is one line, so --property= cannot carry a newline in: ", systemd_unit_property_wire(property: p.property))) + systemd_run_option_word_refused(cause: UnitSettingValueMultiline { property: p.property }) } else { SystemdRunOptionWordsProjected { words: list_append(left: words, right: ["-p", concat(systemd_unit_property_wire(property: p.property), concat("=", p.value as String))]) } } PropertyLong { property: p } => if systemd_run_option_word_has_newline(s: p.value as String) { - systemd_run_option_word_refused(reason: concat("unit setting value is one line, so --property= cannot carry a newline in: ", systemd_unit_property_wire(property: p.property))) + systemd_run_option_word_refused(cause: UnitSettingValueMultiline { property: p.property }) } else { SystemdRunOptionWordsProjected { words: list_append(left: words, right: [concat("--property=", concat(systemd_unit_property_wire(property: p.property), concat("=", p.value as String)))]) } } SetEnv { binding: b } => if string_contains(s: b.name as String, pattern: "=") { - systemd_run_option_word_refused(reason: "--setenv= takes NAME=VALUE, so the NAME side carries no = of its own") + systemd_run_option_word_refused(cause: SetenvNameContainsEquals) } else if systemd_run_option_word_has_newline(s: b.name as String) { - systemd_run_option_word_refused(reason: "--setenv= takes NAME=VALUE, so the NAME side is one line") + systemd_run_option_word_refused(cause: SetenvNameMultiline) } else if systemd_run_option_word_has_newline(s: b.value as String) { - systemd_run_option_word_refused(reason: "--setenv= takes NAME=VALUE, so the VALUE side is one line") + systemd_run_option_word_refused(cause: SetenvValueMultiline) } else { SystemdRunOptionWordsProjected { words: list_append(left: words, right: [concat("--setenv=", concat(b.name as String, concat("=", b.value as String)))]) } } @@ -200,7 +222,7 @@ fn systemd_run_command_argument_words(command: ArgvCommand) -> List { fn systemd_run_command_of(options: List, command_argv: List) -> SystemdRunCommandReading { match systemd_run_option_words(options: options) { - SystemdRunOptionWordsRefused { reason: r } => SystemdRunCommandRefused { reason: r } + SystemdRunOptionWordsRefused { cause: c } => SystemdRunCommandRefused { cause: c } SystemdRunOptionWordsProjected { words: option_words } => SystemdRunCommandReady { command: argv_command(program: systemd_run_program(), arguments: list_append(left: option_words, right: command_argv)) } } diff --git a/dag/gunbc/auth/approval_device_enrolment_code_issue.dag b/dag/gunbc/auth/approval_device_enrolment_code_issue.dag index a0b156b11d4..b01b87e6281 100644 --- a/dag/gunbc/auth/approval_device_enrolment_code_issue.dag +++ b/dag/gunbc/auth/approval_device_enrolment_code_issue.dag @@ -9,6 +9,7 @@ import extdeps.sudo.elevation { sudo_elevate } import extdeps.systemd.systemd_run { SystemdRunProperty, SystemdRunCommandReading, SystemdRunCommandReady, SystemdRunCommandRefused, systemd_run_system_scope_command, +, systemd_run_option_words_refused_cause_detail, } import extdeps.exec.command { ArgvCommand, argv_words } import extdeps.tools.util_linux_setpriv { setpriv_reuid_regid_argv } @@ -135,8 +136,8 @@ fn enrolment_code_issue_remote(revision: ReleaseRevisionBinding) -> CliWireRespo FleetSshContextRefused { cause: c } => CliWireUnprintable { cause: ("approval device enrolment code: " + c) as NonEmptyStr } FleetSshContextReady { context: context, receipt: _ } => match enrolment_code_issue_remote_command(revision: revision) { - SystemdRunCommandRefused { reason: why } => - CliWireUnprintable { cause: ("approval device enrolment code: remote invocation refused: " + why) as NonEmptyStr } + SystemdRunCommandRefused { cause: why } => + CliWireUnprintable { cause: ("approval device enrolment code: remote invocation refused: " + systemd_run_option_words_refused_cause_detail(cause: why)) as NonEmptyStr } SystemdRunCommandReady { command: command } => match typed_argv_exec_over_fleet_ssh( target: enrolment_code_issue_ssh_target(), diff --git a/dag/gunbc/compute/work_provider_local.dag b/dag/gunbc/compute/work_provider_local.dag index d17a749c4ba..581102bd4f5 100644 --- a/dag/gunbc/compute/work_provider_local.dag +++ b/dag/gunbc/compute/work_provider_local.dag @@ -22,6 +22,7 @@ import extdeps.systemd.systemd_run { ServiceResultFormatNotGrounded, MemoryPeakFormatNotGrounded, SummaryAbsence, SummaryNoInvocation, SummarySuppressedByQuietLauncher, SummaryCaptureEmpty, SummaryLineAbsent, summary_absence_wire, SystemdFormattedBytes, FormattedBytesExact, FormattedBytesBounded, systemd_formatted_bytes_upper, +, systemd_run_option_words_refused_cause_detail, } import extdeps.systemd.systemctl import gunbc.compute.unit_bounds { compute_grant_unit_properties, compute_kill_mode_property, compute_kill_mode_value } @@ -395,9 +396,9 @@ fn compute_run_unit(instance: HostDashboardInstance, layout: ComputeLayout, iden ], command_argv: argv, ) { - SystemdRunCommandRefused { reason: why } => + SystemdRunCommandRefused { cause: why } => ComputeUnitRun { - exec: ComputeExecRefused { reason: why }, + exec: ComputeExecRefused { reason: systemd_run_option_words_refused_cause_detail(cause: why) }, summary: SystemdRunWaitSummary { result: ServiceResultNotReported { absence: SummaryNoInvocation }, memory_peak: MemoryPeakNotReported { absence: SummaryNoInvocation } }, } SystemdRunCommandReady { command: command } => { diff --git a/dag/gunbc/compute/work_request.dag b/dag/gunbc/compute/work_request.dag index ad21ae46e1f..56a2a780384 100644 --- a/dag/gunbc/compute/work_request.dag +++ b/dag/gunbc/compute/work_request.dag @@ -3,6 +3,7 @@ module gunbc.compute.work_request import std.types { String, Bool, Int, List, NonEmptyStr, FilePath } import std.measure { Kibibyte, kibibyte_count } import std.content_hash { Fnv1a64Structural, content_hash_atom, content_hash_tagged_structural } +import extdeps.systemd.systemd_run { SystemdRunOptionWordsRefusedCause, systemd_run_option_words_refused_cause_detail } import extdeps.git.object_store { GitObjectId, git_object_id_wire_hex } import extdeps.languages.json.emit { JsonValue, json_object, json_kv, json_string, json_int, json_array, serialize_json, @@ -178,7 +179,7 @@ type WorkInfrastructureRefusal | SubjectUnresolved { detail: String } | UnitMemoryGrantExceeded { class: String, granted: Kibibyte, peak_upper: Kibibyte?, grant_was_ceiling: Bool } | UnitEndingUnclassifiable { exit_code: Int, cause: String } - | UnitLaunchRefused { reason: String } + | UnitLaunchRefused { cause: SystemdRunOptionWordsRefusedCause } fn work_outcome_identity(o: WorkOutcome) -> String { match o { @@ -247,8 +248,8 @@ fn work_refusal_detail(r: WorkInfrastructureRefusal) -> String { SubjectUnresolved { detail } => join(["subject unresolved: ", detail], "") UnitEndingUnclassifiable { exit_code, cause } => join(["the unit exited ", to_string(value: exit_code), " and its ending could not be classified, so it is neither reported as failed nor as grant-exceeded: ", cause], "") - UnitLaunchRefused { reason } => - join(["the unit was never launched: the systemd-run builder refused the invocation, so there is no exit to report: ", reason], "") + UnitLaunchRefused { cause } => + join(["the unit was never launched: the systemd-run builder refused the invocation, so there is no exit to report: ", systemd_run_option_words_refused_cause_detail(cause: cause)], "") UnitMemoryGrantExceeded { class, granted, peak_upper, grant_was_ceiling } => join(["the unit was OOM-killed at its memory grant: class ", class, " was granted ", to_string(value: kibibyte_count(k: granted)), " KiB and its manager reported a peak of ", match peak_upper { Present { value: k } => join(["at most ", to_string(value: kibibyte_count(k: k)), " KiB"], "") Absent => "NOTHING (no Memory peak line was reported)" }, diff --git a/dag/gunbc/host/host_effect_nbd_proxy_serve.dag b/dag/gunbc/host/host_effect_nbd_proxy_serve.dag index fee3c72db37..4a14d61d932 100644 --- a/dag/gunbc/host/host_effect_nbd_proxy_serve.dag +++ b/dag/gunbc/host/host_effect_nbd_proxy_serve.dag @@ -19,7 +19,8 @@ import extdeps.bmc.webui.nbd_proxy_serve { bmcweb_session_token_env_ref, } import extdeps.exec.command { ArgvCommand, argv_words } -import extdeps.systemd.systemd_run { SystemdRunCommandReady, SystemdRunCommandRefused, systemd_run_transient_unit_command } +import extdeps.systemd.systemd_run { SystemdRunCommandReady, SystemdRunCommandRefused, systemd_run_transient_unit_command , systemd_run_option_words_refused_cause_detail, +} import extdeps.tools.nbdkit { nbdkit_readonly_file_serve_command } import extdeps.tools.websocat { websocat_nbd_proxy_bridge_command } import extdeps.filesystem.filesystem_io { @@ -110,8 +111,8 @@ fn host_effect_nbd_proxy_serve_systemd_run_transient( transport: HostEffectTransport, ) -> String? { match systemd_run_transient_unit_command(unit: unit, properties: [], command_argv: command_argv) { - SystemdRunCommandRefused { reason: why } => - Present { value: concat(concat("unit ", unit as String), concat(": ", why)) } + SystemdRunCommandRefused { cause: why } => + Present { value: concat(concat("unit ", unit as String), concat(": ", systemd_run_option_words_refused_cause_detail(cause: why))) } SystemdRunCommandReady { command: command } => match systemd_run_transient_read(transport: transport, command: command) { SystemdRunTransientStarted { stdout: _ } => Absent diff --git a/dag/gunbc/roadmap/roadmap_belt_actuate.dag b/dag/gunbc/roadmap/roadmap_belt_actuate.dag index 814dddb121a..a1026559b2b 100644 --- a/dag/gunbc/roadmap/roadmap_belt_actuate.dag +++ b/dag/gunbc/roadmap/roadmap_belt_actuate.dag @@ -31,7 +31,7 @@ import gunbc.roadmap.roadmap_goal_audit_role { goal_audit_criterion_key, goal_au import gunbc.roadmap_dispatch_actuator { dispatch_events_projection_argv_at, dispatch_current_attempt_events_path_for_instance } import gunbc.roadmap_dispatch_actuator { dispatch_attempt_review_dir_for_instance, dispatch_attempt_review_verdict_path_for_instance, dispatch_attempt_review_verdict_basename, - dispatch_review_unit_name, dispatch_review_unit_command, + systemd_run_option_words_refused_cause_detail, dispatch_review_unit_name, dispatch_review_unit_command, dispatch_attempt_audit_dir_for_instance, dispatch_attempt_audit_verdict_path_for_instance, dispatch_attempt_audit_verdict_basename, dispatch_audit_unit_name, dispatch_audit_unit_command, dispatch_attempt_supervisor_dir_for_instance, dispatch_supervisor_unit_name, dispatch_supervisor_unit_command, @@ -1346,8 +1346,8 @@ fn belt_session_container_create( SessionLaunchRefused { detail } => ExecStepFailed { step: "session-launch-gate", detail: detail } SessionLaunchAdmitted { grant: launched } => match dispatch_worker_unit_command(instance: instance, plan: plan, granted: launched.amount, process_bounds: session_process_bounds) { - SystemdRunCommandRefused { reason: why } => - ExecStepFailed { step: "harness-unit-start", detail: why } + SystemdRunCommandRefused { cause: why } => + ExecStepFailed { step: "harness-unit-start", detail: systemd_run_option_words_refused_cause_detail(cause: why) } SystemdRunCommandReady { command: command } => belt_exec_steps( steps: [BeltExecStep { step: "harness-unit-start", command: dispatch_unit_exec_argv(command: command, resolved_program: plan.systemd_run_program) }], @@ -2588,11 +2588,11 @@ fn belt_supervisor_convene( ), ), ) { - SystemdRunCommandRefused { reason: why } => + SystemdRunCommandRefused { cause: why } => SpawnFailed { node_id: node.node as String, step: "supervisor-unit-start", - detail: why, + detail: systemd_run_option_words_refused_cause_detail(cause: why), provider: provider_wire_label, launch: launch, } @@ -10043,8 +10043,8 @@ fn belt_audit_spawn(instance: HostDashboardInstance, progress: WorkflowAttemptPr RosterAdmissionRefused { cause: roster_cause } => ReviewFailed { node_id: progress.node_id, criterion: goal_audit_criterion_key, detail: join(["the auditor was not launched: ", roster_cause], "") } RosterAdmitted => match dispatch_audit_unit_command(instance: instance, node_id: node_id, attempt_key: progress.attempt_key, worktree_path: worktree, brief: brief, head_sha: head) { - SystemdRunCommandRefused { reason: why } => - ReviewFailed { node_id: progress.node_id, criterion: goal_audit_criterion_key, detail: why } + SystemdRunCommandRefused { cause: why } => + ReviewFailed { node_id: progress.node_id, criterion: goal_audit_criterion_key, detail: systemd_run_option_words_refused_cause_detail(cause: why) } SystemdRunCommandReady { command: command } => match belt_exec(cmd: dispatch_unit_exec_argv(command: command, resolved_program: systemd_run), workdir: worktree) { ExecOk { stdout: _ } => ReviewSpawned { node_id: progress.node_id, criterion: goal_audit_criterion_key, unit: unit as String } @@ -10304,8 +10304,8 @@ fn belt_review_spawn(instance: HostDashboardInstance, node_id: String, attempt_k RosterAdmissionRefused { cause: roster_cause } => ReviewFailed { node_id: node_id, criterion: call.id, detail: join(["the reviewer was not launched: ", roster_cause], "") } RosterAdmitted => match dispatch_review_unit_command(instance: instance, node_id: nid, attempt_key: attempt_key, worktree_path: worktree, brief: brief, head_sha: head, criterion_key: key) { - SystemdRunCommandRefused { reason: why } => - ReviewFailed { node_id: node_id, criterion: call.id, detail: why } + SystemdRunCommandRefused { cause: why } => + ReviewFailed { node_id: node_id, criterion: call.id, detail: systemd_run_option_words_refused_cause_detail(cause: why) } SystemdRunCommandReady { command: command } => match belt_exec(cmd: dispatch_unit_exec_argv(command: command, resolved_program: systemd_run), workdir: worktree) { ExecOk { stdout: _ } => ReviewSpawned { node_id: node_id, criterion: call.id, unit: unit as String } diff --git a/dag/gunbc/runner/runner_host_unit_slot.dag b/dag/gunbc/runner/runner_host_unit_slot.dag index 5f4c0d717c3..f6b41e80fd1 100644 --- a/dag/gunbc/runner/runner_host_unit_slot.dag +++ b/dag/gunbc/runner/runner_host_unit_slot.dag @@ -10,7 +10,7 @@ import extdeps.github.actions_runner { import gunbc.managed_host { ManagedHostBinding, ManagedHostBindingStanding, ManagedHostBound } import gunbc.runner_slot_desired { gunbc_runner_slot_desired } import extdeps.systemd.systemd_run { - SystemdRunCommandReady, SystemdRunCommandRefused, systemd_run_command_argument_words, + SystemdRunCommandReady, SystemdRunCommandRefused, SystemdRunOptionWordsRefusedCause, systemd_run_command_argument_words, } import gunbc.runner_throughput_qualification_route { ephemeral_slot_unit, ephemeral_slot_command, ephemeral_slot_labels } @@ -49,7 +49,7 @@ type HostUnitSlot sole_constructor { type HostUnitSlotRefusal = HostUnitSlotHostNotBound { standing: ManagedHostBindingStanding } | HostUnitSlotRunnerBinaryUnpublished { binary_release: ActionsRunnerRelease } - | HostUnitSlotSystemdRunRefused { reason: String } + | HostUnitSlotSystemdRunRefused { cause: SystemdRunOptionWordsRefusedCause } type HostUnitSlotStanding = HostUnitSlotBound { slot: HostUnitSlot } @@ -74,8 +74,8 @@ fn host_unit_slot_for(host: ManagedHostBindingStanding, attempt: NonEmptyStr, wo Present { value: artifact } => { let unit = ephemeral_slot_unit(host: b.host.host, attempt: attempt) match ephemeral_slot_command(unit: unit, desired: gunbc_runner_slot_desired(), command_argv: host_unit_slot_listener_argv()) { - SystemdRunCommandRefused { reason: why } => - HostUnitSlotRefused { refusal: HostUnitSlotSystemdRunRefused { reason: why } } + SystemdRunCommandRefused { cause: why } => + HostUnitSlotRefused { refusal: HostUnitSlotSystemdRunRefused { cause: why } } SystemdRunCommandReady { command: command } => HostUnitSlotBound { slot: HostUnitSlot { diff --git a/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag b/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag index b70026bcaed..523f66d17e1 100644 --- a/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag +++ b/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag @@ -79,6 +79,7 @@ import extdeps.exec.command { argv_words } import extdeps.systemd.systemd_run { SystemdRunProperty, SystemdRunCommandReady, SystemdRunCommandRefused, systemd_run_transient_unit_command, systemd_run_transient_retained_unit_command, systemd_run_command_argument_words, +, systemd_run_option_words_refused_cause_detail, } import gunbc.runner_microvm_lifecycle { CgroupSubtreeReadback, CgroupSubtreeEmpty, CgroupSubtreePopulated, CgroupSubtreeReadFailed, @@ -1181,7 +1182,7 @@ fn run_jailer_in_cell(attempt: MicroVmAttempt, invocation_id: NonEmptyStr, recei ], receipt.runtime_properties), command_argv: argv_words(command: jailer), ) { - SystemdRunCommandRefused { reason: why } => VmmStartFailed { unit: unit, detail: why } + SystemdRunCommandRefused { cause: why } => VmmStartFailed { unit: unit, detail: systemd_run_option_words_refused_cause_detail(cause: why) } SystemdRunCommandReady { command: command } => { let started = systemd.SystemdRun.RunTransientRetained(command_argv: systemd_run_command_argument_words(command: command)) if started.success { VmmStarted { unit: unit, receipt: receipt } } diff --git a/dag/test/claim/approval_device_enrolment_code_witness_test.dag b/dag/test/claim/approval_device_enrolment_code_witness_test.dag index 9482f67927f..2981f37f68e 100644 --- a/dag/test/claim/approval_device_enrolment_code_witness_test.dag +++ b/dag/test/claim/approval_device_enrolment_code_witness_test.dag @@ -94,7 +94,7 @@ test fn the_remote_argv_runs_the_release_verb_as_the_operator_user() -> Bool { // on a refusal arm. fn the_remote_verb_words_carry_the_release_shape(reading: SystemdRunCommandReading, rev: String, op: String) -> Bool { match reading { - SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => { let joined = join(sudo_elevate_words(command: command), " ") string_contains(s: joined, pattern: "/usr/bin/sudo -n systemd-run --scope --property=MemoryMax=" + to_string(byte_size_count(b: approval_broker_slice_memory_max)) + " --property=MemoryHigh=" + to_string(byte_size_count(b: approval_broker_slice_memory_high)) + " -- setpriv --reuid=" + op + " --regid=" + op + " --init-groups -- /usr/bin/env " + gunbc_workspace_root_env_name + "=/opt/gunbc/approval-broker/releases/" + rev + " /opt/gunbc/approval-broker/releases/" + rev + "/gunbc run ") diff --git a/dag/test/claim/compute/work_class_grant_witness_test.dag b/dag/test/claim/compute/work_class_grant_witness_test.dag index 5fb0af62fe1..5877adc0c66 100644 --- a/dag/test/claim/compute/work_class_grant_witness_test.dag +++ b/dag/test/claim/compute/work_class_grant_witness_test.dag @@ -50,7 +50,7 @@ import gunbc.compute.work_provider_local { // the fold is the only route from the reading to a summary.) fn fixture_summary(standing: SummaryFormatStanding, stderr: String) -> SystemdRunWaitSummary { match systemd_run_user_wait_command(unit: "gunbc-fixture", properties: [], setenv_bindings: [], command_argv: ["true"]) { - SystemdRunCommandRefused { reason: _ } => + SystemdRunCommandRefused { cause: _ } => SystemdRunWaitSummary { result: ServiceResultNotReported { absence: SummaryNoInvocation }, memory_peak: MemoryPeakNotReported { absence: SummaryNoInvocation } } SystemdRunCommandReady { command: command } => systemd_run_wait_summary(launcher: SystemdSummaryPrintingWait { command: command }, standing: standing, stderr: stderr) @@ -62,7 +62,7 @@ fn fixture_summary(standing: SummaryFormatStanding, stderr: String) -> SystemdRu // user-manager start, then the command after --. test fn the_user_wait_builder_projects_the_words_the_string_form_rendered() -> Bool { match systemd_run_user_wait_command(unit: "gunbc-fixture", properties: [], setenv_bindings: [], command_argv: ["true"]) { - SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => argv_words(command: command) == ["systemd-run", "--wait", "--pipe", "--collect", "--user", "--unit=gunbc-fixture", "--", "true"] } @@ -297,7 +297,7 @@ test fn the_peak_is_unreported_by_absence_and_an_untrimmed_summary_still_reads() // admit list on argv_command refuses test callers, so there is no direct mint to fall back on). fn quiet_summary(stderr: String) -> SystemdRunWaitSummary { match systemd_run_transient_wait_unit_command(unit: "gunbc-quiet", properties: [], command_argv: ["true"]) { - SystemdRunCommandRefused { reason: _ } => + SystemdRunCommandRefused { cause: _ } => SystemdRunWaitSummary { result: ServiceResultNotReported { absence: SummaryNoInvocation }, memory_peak: MemoryPeakNotReported { absence: SummaryNoInvocation } } SystemdRunCommandReady { command: command } => systemd_run_wait_summary(launcher: SystemdSummaryPrintingWait { command: command }, standing: grounded_standing, stderr: stderr) diff --git a/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag b/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag index 7e2cf36451f..17698e2da91 100644 --- a/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag +++ b/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag @@ -39,7 +39,7 @@ fn wait_control_command_reading() -> SystemdRunCommandReading { // THE POSITIVE CONTROL, WORDS HALF: the projection's words are the old builder's words, byte for byte. test fn systemd_wait_route_projects_the_old_words() -> Bool { match wait_control_command_reading() { - SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => argv_exact_token_equal( left: argv_words(command: command), @@ -56,7 +56,7 @@ test fn systemd_wait_route_projects_the_old_words() -> Bool { // same words the typed command projects. test fn systemd_wait_route_materializes_the_projected_words() -> Bool { match wait_control_command_reading() { - SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => systemd_run_transient_wait_operation_argv_matches_authority(command: command) } @@ -71,7 +71,7 @@ test fn a_property_value_the_wire_cannot_carry_is_refused() -> Bool { properties: [SystemdRunProperty { property: WorkingDirectoryProperty, value: "/tmp/a\nb" as NonEmptyStr }], command_argv: ["/bin/true"], ) { - SystemdRunCommandRefused { reason: why } => string_contains(s: why, pattern: "one line") + SystemdRunCommandRefused { cause: UnitSettingValueMultiline { property: p } } => systemd_unit_property_wire(property: p) == "WorkingDirectory" SystemdRunCommandReady { command: _ } => false } } @@ -87,7 +87,7 @@ test fn the_collecting_transient_builder_emits_unit_properties_collect() -> Bool properties: [SystemdRunProperty { property: MemoryMax, value: "17179869184" as NonEmptyStr }], command_argv: ["/bin/true"], ) { - SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => argv_exact_token_equal( left: argv_words(command: command), @@ -106,7 +106,7 @@ test fn the_retained_transient_builder_omits_collect() -> Bool { properties: [SystemdRunProperty { property: MemoryMax, value: "17179869184" as NonEmptyStr }], command_argv: ["/bin/true"], ) { - SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => argv_exact_token_equal( left: argv_words(command: command), @@ -124,7 +124,7 @@ test fn the_user_wait_builder_renders_the_old_order() -> Bool { setenv_bindings: [SystemdSetenvBinding { name: "A" as NonEmptyStr, value: "c" as NonEmptyStr }], command_argv: ["/bin/true"], ) { - SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => argv_exact_token_equal( left: argv_words(command: command), @@ -143,7 +143,7 @@ test fn the_scope_builder_leads_with_scope_only() -> Bool { properties: [SystemdRunProperty { property: MemoryMax, value: "17179869184" as NonEmptyStr }], command_argv: ["/bin/true"], ) { - SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => argv_exact_token_equal( left: argv_words(command: command), @@ -159,7 +159,7 @@ test fn a_unit_name_the_wire_cannot_carry_is_refused() -> Bool { properties: [], command_argv: ["/bin/true"], ) { - SystemdRunCommandRefused { reason: why } => string_contains(s: why, pattern: "never contains /") + SystemdRunCommandRefused { cause: UnitNameContainsSlash } => true SystemdRunCommandReady { command: _ } => false } } @@ -173,25 +173,41 @@ test fn a_setenv_name_with_an_equals_is_refused() -> Bool { setenv_bindings: [SystemdSetenvBinding { name: "A=b" as NonEmptyStr, value: "c" as NonEmptyStr }], command_argv: ["/bin/true"], ) { - SystemdRunCommandRefused { reason: why } => string_contains(s: why, pattern: "carries no = of its own") + SystemdRunCommandRefused { cause: SetenvNameContainsEquals } => true SystemdRunCommandReady { command: _ } => false } } test fn a_setenv_binding_with_an_equals_in_the_name_is_refused() -> Bool { match systemd_run_option_words(options: [SetEnv { binding: SystemdSetenvBinding { name: "A=b" as NonEmptyStr, value: "c" as NonEmptyStr } }]) { - SystemdRunOptionWordsRefused { reason: why } => string_contains(s: why, pattern: "carries no = of its own") + SystemdRunOptionWordsRefused { cause: SetenvNameContainsEquals } => true SystemdRunOptionWordsProjected { words: _ } => false } } +// The cause sum discriminates: two different wire defects yield two different constructors, so a +// caller can branch on the cause without matching on prose. Tags 0 and 1 must differ. +test fn refusal_causes_discriminate_two_wire_defects() -> Bool { + let slash_tag = match systemd_run_option_words(options: [RunUnit { unit: "wrong/name" as NonEmptyStr }]) { + SystemdRunOptionWordsRefused { cause: UnitNameContainsSlash } => 1 + SystemdRunOptionWordsRefused { cause: _ } => 0 - 1 + SystemdRunOptionWordsProjected { words: _ } => 0 - 1 + } + let equals_tag = match systemd_run_option_words(options: [SetEnv { binding: SystemdSetenvBinding { name: "A=b" as NonEmptyStr, value: "c" as NonEmptyStr } }]) { + SystemdRunOptionWordsRefused { cause: SetenvNameContainsEquals } => 2 + SystemdRunOptionWordsRefused { cause: _ } => 0 - 1 + SystemdRunOptionWordsProjected { words: _ } => 0 - 1 + } + (slash_tag == 1) && (equals_tag == 2) && (slash_tag != equals_tag) +} + // THE TYPED OPTION IS A SEALED SUM: there is no arm an unknown flag could ride. This fold walks the // variants the module renders today and pins each one's man-page spelling at systemd 255; a variant // added to the sum updates this fold or compilation fails, which is how an unknown option stays // unwritable. test fn every_option_variant_renders_its_man_page_spelling() -> Bool { match systemd_run_option_words(options: [RunUserManager, RunScope, Wait, Quiet, Pipe, Collect, EndOfOptions]) { - SystemdRunOptionWordsRefused { reason: _ } => false + SystemdRunOptionWordsRefused { cause: _ } => false SystemdRunOptionWordsProjected { words: words } => join(words, separator: " ") == "--user --scope --wait --quiet --pipe --collect --" } @@ -218,7 +234,7 @@ test fn systemd_wait_normal_exit_86_remains_86() -> Bool { fn materialized_cardinality(reading: SystemdRunCommandReading) -> Int { match reading { - SystemdRunCommandRefused { reason: _ } => 0 - 2 + SystemdRunCommandRefused { cause: _ } => 0 - 2 SystemdRunCommandReady { command: command } => match systemd_run_transient_wait_operation_argv(command: command) { ArgvMaterialized { argv: words } => count(words) diff --git a/dag/test/claim/github_app_registry_witness_test.dag b/dag/test/claim/github_app_registry_witness_test.dag index 178c8efbba1..6fd2bf87859 100644 --- a/dag/test/claim/github_app_registry_witness_test.dag +++ b/dag/test/claim/github_app_registry_witness_test.dag @@ -1378,7 +1378,7 @@ test fn witness_jit_mint_authorizes_a_managed_host_unit_slot_under_the_routes_ow && runner_label_sets_equal(a: map(req.labels, l => l.value as String), b: ephemeral_slot_labels(host: h.host, attempt: "q-1")) && match ephemeral_slot_command(unit: u.unit, desired: gunbc_runner_slot_desired(), command_argv: host_unit_slot_listener_argv()) { SystemdRunCommandReady { command: c } => u.argv == systemd_run_command_argument_words(command: c) - SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandRefused { cause: _ } => false } && match u.runner.arch { ActionsRunnerLinuxArm64 => true _ => false } _ => false diff --git a/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag b/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag index 8fd79336f74..7d77c91eef0 100644 --- a/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag +++ b/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag @@ -224,7 +224,7 @@ test fn witness_systemd_run_transient_operation_argv_matches_authority() -> Bool properties: [], command_argv: ["nbdkit", "-p", "10809", "file", "/var/lib/gunbc/artifacts/x.iso"], ) { - SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => systemd_run_transient_operation_argv_matches_authority(command: command) } } @@ -240,7 +240,7 @@ test fn witness_systemd_run_transient_property_reaches_materialized_argv() -> Bo properties: [SystemdRunProperty { property: MemoryMax, value: "17179869184" as NonEmptyStr }], command_argv: ["nbdkit", "-p", "10809", "file", "/var/lib/gunbc/artifacts/x.iso"], ) { - SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => systemd_run_transient_operation_argv_matches_authority(command: command) } } @@ -256,7 +256,7 @@ test fn witness_systemd_run_property_value_with_space_keeps_token_identity() -> properties: [SystemdRunProperty { property: WorkingDirectoryProperty, value: "/srv/path with space" as NonEmptyStr }], command_argv: ["nbdkit", "-p", "10809"], ) { - SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => systemd_run_transient_operation_argv_matches_authority(command: command) } } @@ -281,7 +281,7 @@ test fn witness_systemd_run_property_argv_renders_the_wire_name() -> Bool { properties: [SystemdRunProperty { property: MemoryMax, value: "17179869184" as NonEmptyStr }], command_argv: ["/bin/true"], ) { - SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => join(argv_words(command: command), " ") == "systemd-run --unit=nbd-proxy-witness.service --property=MemoryMax=17179869184 --collect -- /bin/true" diff --git a/dag/test/claim/roadmap/roadmap_dispatch_actuator_witness_test.dag b/dag/test/claim/roadmap/roadmap_dispatch_actuator_witness_test.dag index 565e0cfdab2..35bbf8c3304 100644 --- a/dag/test/claim/roadmap/roadmap_dispatch_actuator_witness_test.dag +++ b/dag/test/claim/roadmap/roadmap_dispatch_actuator_witness_test.dag @@ -1177,7 +1177,7 @@ test fn witness_review_unit_argv_mints_per_criterion_attempt_identity() -> Bool head_sha: "abc123", criterion_key: "conformance-external-facts", ) { - SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => { let cmd = dispatch_unit_exec_argv(command: command, resolved_program: "/usr/bin/systemd-run" as FilePath) string_contains(s: join(cmd.args, separator: "\0"), pattern: "attempt_identity=reviewer:6.node-1.12.att-20260920.26.conformance-external-facts.6.abc123") @@ -1198,7 +1198,7 @@ test fn witness_review_unit_option_words_are_the_string_form_words() -> Bool { head_sha: "abc123", criterion_key: "conformance-external-facts", ) { - SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => { let wire = join(argv_words(command: command), separator: "\0") string_contains( @@ -1392,7 +1392,7 @@ test fn witness_supervisor_unit_argv_shapes_one_session_per_escalation_record() worktree_path: "/x/attempts/wt", brief: "BRIEF-MARKER", ) { - SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => { let cmd = dispatch_unit_exec_argv(command: command, resolved_program: "/usr/bin/systemd-run" as FilePath) supervisor_wire_checks(wire: join(cmd.args, separator: "\0")) diff --git a/dag/test/claim/runner/runner_microvm_slot_controller_witness_test.dag b/dag/test/claim/runner/runner_microvm_slot_controller_witness_test.dag index 870f0ed993f..dd29c12e17f 100644 --- a/dag/test/claim/runner/runner_microvm_slot_controller_witness_test.dag +++ b/dag/test/claim/runner/runner_microvm_slot_controller_witness_test.dag @@ -336,7 +336,7 @@ fn attempt_launch_property_words(attempt: MicroVmAttempt) -> List { ], command_argv: [], ) { - SystemdRunCommandRefused { reason: _ } => [] + SystemdRunCommandRefused { cause: _ } => [] SystemdRunCommandReady { command: command } => property_words_of(argv: argv_words(command: command)) } } diff --git a/dag/test/claim/runner/runner_throughput_qualification_witness_test.dag b/dag/test/claim/runner/runner_throughput_qualification_witness_test.dag index 94b20e68fcc..21a65bf2872 100644 --- a/dag/test/claim/runner/runner_throughput_qualification_witness_test.dag +++ b/dag/test/claim/runner/runner_throughput_qualification_witness_test.dag @@ -874,7 +874,7 @@ test fn the_ephemeral_slot_argv_carries_the_fleet_memory_max() -> Bool { fn ephemeral_slot_words_carry_fleet_shape(reading: SystemdRunCommandReading) -> Bool { match reading { - SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => { let argv = argv_words(command: command) let has_quota_word = argv |> any(w => string_contains(s: w, pattern: "--property=CPUQuota=")) diff --git a/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag b/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag index 74098d610d3..328718d9e32 100644 --- a/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag +++ b/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag @@ -70,10 +70,7 @@ fn start_payload(unit: NonEmptyStr, argv: List) -> Bool uses net: Network { match systemd_run_transient_unit_command(unit: unit, properties: [wet_slice_property()], command_argv: argv) { - SystemdRunCommandRefused { reason: why } => { - let _ = why - false - } + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => systemd.SystemdRun.RunTransient(command_argv: argv_words(command: command)).success } @@ -422,10 +419,7 @@ fn start_with_invocation(unit: NonEmptyStr, invocation: String, sleep_for: Strin // tail only — the transport owns the "systemd-run" head. fn start_retained_payload(unit: NonEmptyStr, properties: List, command_argv: List) -> Bool { match systemd_run_transient_retained_unit_command(unit: unit, properties: properties, command_argv: command_argv) { - SystemdRunCommandRefused { reason: why } => { - let _ = why - false - } + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => systemd.SystemdRun.RunTransientRetained(command_argv: systemd_run_command_argument_words(command: command)).success } diff --git a/dag/test/manual/typed_builder_wet_test.dag b/dag/test/manual/typed_builder_wet_test.dag index f1c42b9f283..584ba5afe52 100644 --- a/dag/test/manual/typed_builder_wet_test.dag +++ b/dag/test/manual/typed_builder_wet_test.dag @@ -1,4 +1,4 @@ -module test.claim.systemd.typed_builder_wet +module test.manual.typed_builder_wet import std.types { String, NonEmptyStr, Bool, List, Int } import std.algebra { trim } @@ -36,11 +36,14 @@ test fn a_real_retained_transient_is_still_known_after_a_failing_exit() -> Bool { let unit = "fci1-typed-argv-wet-retained.service" as NonEmptyStr match systemd_run_transient_retained_unit_command(unit: unit, properties: [], command_argv: ["/bin/sh", "-c", "exit 86"]) { - SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => { let started = systemd.SystemdRun.RunTransientRetained(command_argv: systemd_run_command_argument_words(command: command)) let load_state = systemd.Systemctl.ShowLoadState(unit: unit) - started.success && (trim(s: load_state.value) == "loaded") + // The observation is the point; the failed unit is then reset so the NEXT run of this claim + // can start the same fixed unit name again instead of being refused because it exists. + let reset = systemd.Systemctl.ResetFailedUnit(unit: unit) + started.success && (trim(s: load_state.value) == "loaded") && reset.success } } } @@ -52,7 +55,7 @@ test fn the_collected_twin_of_that_unit_is_gone() -> Bool { let unit = "fci1-typed-argv-wet-collected.service" as NonEmptyStr match systemd_run_transient_unit_command(unit: unit, properties: [], command_argv: ["/bin/sh", "-c", "exit 86"]) { - SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => { let started = systemd.SystemdRun.RunTransient(command_argv: systemd_run_command_argument_words(command: command)) let load_state = systemd.Systemctl.ShowLoadState(unit: unit) @@ -69,7 +72,7 @@ test fn the_waited_transient_reports_the_exact_exit() -> Bool { let unit = "fci1-typed-argv-wet-waited.service" as NonEmptyStr match systemd_run_transient_wait_unit_command(unit: unit, properties: [], command_argv: ["/bin/sh", "-c", "exit 86"]) { - SystemdRunCommandRefused { reason: _ } => false + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => { let waited = systemd.SystemdRun.RunTransientAndWait(command_argv: systemd_run_command_argument_words(command: command)) waited.exit_code == 86 From 4607706e1fbbfa6e6a077782e3c77c67d326dd75 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 08:02:06 +0000 Subject: [PATCH 25/65] reset rationale to module grain --- dag/test/manual/typed_builder_wet_test.dag | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dag/test/manual/typed_builder_wet_test.dag b/dag/test/manual/typed_builder_wet_test.dag index 584ba5afe52..294fe672afa 100644 --- a/dag/test/manual/typed_builder_wet_test.dag +++ b/dag/test/manual/typed_builder_wet_test.dag @@ -31,6 +31,9 @@ import extdeps.systemd.systemd_run { // The retained invocation: the unit FAILED (exit 86) and is still known to the manager, because // no --collect was passed. This is the claim that proves the terminal state stays observable. +// The observation is the point; the failed unit is then reset (ResetFailedUnit) so the NEXT +// run of this claim can start the same fixed unit name again instead of being refused because it +// exists. test fn a_real_retained_transient_is_still_known_after_a_failing_exit() -> Bool uses net: Network { @@ -40,8 +43,6 @@ test fn a_real_retained_transient_is_still_known_after_a_failing_exit() -> Bool SystemdRunCommandReady { command: command } => { let started = systemd.SystemdRun.RunTransientRetained(command_argv: systemd_run_command_argument_words(command: command)) let load_state = systemd.Systemctl.ShowLoadState(unit: unit) - // The observation is the point; the failed unit is then reset so the NEXT run of this claim - // can start the same fixed unit name again instead of being refused because it exists. let reset = systemd.Systemctl.ResetFailedUnit(unit: unit) started.success && (trim(s: load_state.value) == "loaded") && reset.success } From fbdfd1d6217236d08f4f74ad362827a3eaa1005c Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 08:32:21 +0000 Subject: [PATCH 26/65] import lists do not admit a leading/trailing stray comma: fold the injected name into the list body --- dag/gunbc/auth/approval_device_enrolment_code_issue.dag | 2 +- dag/gunbc/compute/work_provider_local.dag | 2 +- dag/gunbc/runner/runner_microvm_lifecycle_realize.dag | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dag/gunbc/auth/approval_device_enrolment_code_issue.dag b/dag/gunbc/auth/approval_device_enrolment_code_issue.dag index b01b87e6281..e7b3955fcd7 100644 --- a/dag/gunbc/auth/approval_device_enrolment_code_issue.dag +++ b/dag/gunbc/auth/approval_device_enrolment_code_issue.dag @@ -9,7 +9,7 @@ import extdeps.sudo.elevation { sudo_elevate } import extdeps.systemd.systemd_run { SystemdRunProperty, SystemdRunCommandReading, SystemdRunCommandReady, SystemdRunCommandRefused, systemd_run_system_scope_command, -, systemd_run_option_words_refused_cause_detail, + systemd_run_option_words_refused_cause_detail, } import extdeps.exec.command { ArgvCommand, argv_words } import extdeps.tools.util_linux_setpriv { setpriv_reuid_regid_argv } diff --git a/dag/gunbc/compute/work_provider_local.dag b/dag/gunbc/compute/work_provider_local.dag index 581102bd4f5..a20d21e9904 100644 --- a/dag/gunbc/compute/work_provider_local.dag +++ b/dag/gunbc/compute/work_provider_local.dag @@ -22,7 +22,7 @@ import extdeps.systemd.systemd_run { ServiceResultFormatNotGrounded, MemoryPeakFormatNotGrounded, SummaryAbsence, SummaryNoInvocation, SummarySuppressedByQuietLauncher, SummaryCaptureEmpty, SummaryLineAbsent, summary_absence_wire, SystemdFormattedBytes, FormattedBytesExact, FormattedBytesBounded, systemd_formatted_bytes_upper, -, systemd_run_option_words_refused_cause_detail, + systemd_run_option_words_refused_cause_detail, } import extdeps.systemd.systemctl import gunbc.compute.unit_bounds { compute_grant_unit_properties, compute_kill_mode_property, compute_kill_mode_value } diff --git a/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag b/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag index 523f66d17e1..c0c6278713e 100644 --- a/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag +++ b/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag @@ -79,7 +79,7 @@ import extdeps.exec.command { argv_words } import extdeps.systemd.systemd_run { SystemdRunProperty, SystemdRunCommandReady, SystemdRunCommandRefused, systemd_run_transient_unit_command, systemd_run_transient_retained_unit_command, systemd_run_command_argument_words, -, systemd_run_option_words_refused_cause_detail, + systemd_run_option_words_refused_cause_detail, } import gunbc.runner_microvm_lifecycle { CgroupSubtreeReadback, CgroupSubtreeEmpty, CgroupSubtreePopulated, CgroupSubtreeReadFailed, From b986e1b9633b6cc7b646f0630ae50386d3344fa2 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 09:23:49 +0000 Subject: [PATCH 27/65] floor at fbdfd1d621: the detail fn is extdeps.systemd's (belt imports it from there, not from the actuator); typed refusal claims carry catch-all cause arms for exhaustiveness --- dag/gunbc/roadmap/roadmap_belt_actuate.dag | 3 ++- .../claim/fabric/systemd_run_transient_wait_witness_test.dag | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/dag/gunbc/roadmap/roadmap_belt_actuate.dag b/dag/gunbc/roadmap/roadmap_belt_actuate.dag index a1026559b2b..d237c4b5b57 100644 --- a/dag/gunbc/roadmap/roadmap_belt_actuate.dag +++ b/dag/gunbc/roadmap/roadmap_belt_actuate.dag @@ -31,12 +31,13 @@ import gunbc.roadmap.roadmap_goal_audit_role { goal_audit_criterion_key, goal_au import gunbc.roadmap_dispatch_actuator { dispatch_events_projection_argv_at, dispatch_current_attempt_events_path_for_instance } import gunbc.roadmap_dispatch_actuator { dispatch_attempt_review_dir_for_instance, dispatch_attempt_review_verdict_path_for_instance, dispatch_attempt_review_verdict_basename, - systemd_run_option_words_refused_cause_detail, dispatch_review_unit_name, dispatch_review_unit_command, + dispatch_review_unit_name, dispatch_review_unit_command, dispatch_attempt_audit_dir_for_instance, dispatch_attempt_audit_verdict_path_for_instance, dispatch_attempt_audit_verdict_basename, dispatch_audit_unit_name, dispatch_audit_unit_command, dispatch_attempt_supervisor_dir_for_instance, dispatch_supervisor_unit_name, dispatch_supervisor_unit_command, dispatch_supervisor_attempt_identity, dispatch_unit_exec_argv, } +import extdeps.systemd.systemd_run { systemd_run_option_words_refused_cause_detail } import extdeps.git { shape_git_merge_base_args } import gunbc.roadmap_authority { roadmap_acceptance_event_history_load, diff --git a/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag b/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag index 17698e2da91..2d8169b681b 100644 --- a/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag +++ b/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag @@ -72,6 +72,7 @@ test fn a_property_value_the_wire_cannot_carry_is_refused() -> Bool { command_argv: ["/bin/true"], ) { SystemdRunCommandRefused { cause: UnitSettingValueMultiline { property: p } } => systemd_unit_property_wire(property: p) == "WorkingDirectory" + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: _ } => false } } @@ -160,6 +161,7 @@ test fn a_unit_name_the_wire_cannot_carry_is_refused() -> Bool { command_argv: ["/bin/true"], ) { SystemdRunCommandRefused { cause: UnitNameContainsSlash } => true + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: _ } => false } } @@ -174,6 +176,7 @@ test fn a_setenv_name_with_an_equals_is_refused() -> Bool { command_argv: ["/bin/true"], ) { SystemdRunCommandRefused { cause: SetenvNameContainsEquals } => true + SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: _ } => false } } @@ -181,6 +184,7 @@ test fn a_setenv_name_with_an_equals_is_refused() -> Bool { test fn a_setenv_binding_with_an_equals_in_the_name_is_refused() -> Bool { match systemd_run_option_words(options: [SetEnv { binding: SystemdSetenvBinding { name: "A=b" as NonEmptyStr, value: "c" as NonEmptyStr } }]) { SystemdRunOptionWordsRefused { cause: SetenvNameContainsEquals } => true + SystemdRunOptionWordsRefused { cause: _ } => false SystemdRunOptionWordsProjected { words: _ } => false } } From 5bbf8db90803b6897d63de6b8ea2e21bc9e6ab40 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 10:03:00 +0000 Subject: [PATCH 28/65] review 76406: SystemdSummaryPrintingWait is minted in exactly one place again -- systemd_run_user_wait_command returns SystemdSummaryPrintingWaitReading (Ready{wait} | Refused{cause}); consumers match the reading; the guarantee (never --quiet, so the peak summary is printable) is structural, not a comment --- dag/extdeps/systemd/systemd_run.dag | 21 +++++++++++++------ dag/gunbc/compute/work_provider_local.dag | 5 ++--- .../compute/work_class_grant_witness_test.dag | 16 +++++++------- ...ystemd_run_transient_wait_witness_test.dag | 12 +++++------ 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/dag/extdeps/systemd/systemd_run.dag b/dag/extdeps/systemd/systemd_run.dag index 938dafcc1ca..465594c72e9 100644 --- a/dag/extdeps/systemd/systemd_run.dag +++ b/dag/extdeps/systemd/systemd_run.dag @@ -44,11 +44,17 @@ data systemd_run_authority: ExternalAuthority = extdeps_external_authority_ancho fn systemd_run_program() -> ProgramIdentity = uncataloged_program(invocation: "systemd-run") -// THE WAITING LAUNCHER IS A TOKEN, NOT A WORD LIST: its ArgvCommand is minted only by the typed -// builders below, so what a caller holds is the fact that a specific projection happened, not a -// list it could have glued together itself. +// THE WAITING LAUNCHER IS A TOKEN, NOT A WORD LIST: SystemdSummaryPrintingWait is minted in +// exactly one place, the SystemdSummaryPrintingWaitReady arm of systemd_run_user_wait_command, so +// what a caller holds is the builder's own fact that this projection is the waiting, non-quiet +// one. No other site can construct it, and a launcher that cannot print a memory peak therefore +// cannot be handed to the thing whose contract is to observe one. type SystemdSummaryPrintingWait { command: ArgvCommand } +type SystemdSummaryPrintingWaitReading + = SystemdSummaryPrintingWaitReady { wait: SystemdSummaryPrintingWait } + | SystemdSummaryPrintingWaitRefused { cause: SystemdRunOptionWordsRefusedCause } + // compute_exec receives the program separately, so these are the argument tail; the full // argv_words would double the program head on the compute side too. fn systemd_summary_printing_wait_argv(w: SystemdSummaryPrintingWait) -> List { @@ -267,11 +273,14 @@ fn systemd_run_user_transient_command(unit: NonEmptyStr, properties: List, setenv_bindings: List, command_argv: List) -> SystemdRunCommandReading { - systemd_run_command_of( +fn systemd_run_user_wait_command(unit: NonEmptyStr, properties: List, setenv_bindings: List, command_argv: List) -> SystemdSummaryPrintingWaitReading { + match systemd_run_command_of( options: list_append(left: [Wait, Pipe, Collect], right: list_append(left: [RunUserManager, RunUnit { unit: unit }], right: list_append(left: systemd_run_property_options_short(properties: properties), right: list_append(left: systemd_run_setenv_options(bindings: setenv_bindings), right: [EndOfOptions])))), command_argv: command_argv, - ) + ) { + SystemdRunCommandRefused { cause: cause } => SystemdSummaryPrintingWaitRefused { cause: cause } + SystemdRunCommandReady { command: command } => SystemdSummaryPrintingWaitReady { wait: SystemdSummaryPrintingWait { command: command } } + } } // The pre-cutover system-scope builder led with --scope only (blob 71a9a1c5d3~1 line 158: diff --git a/dag/gunbc/compute/work_provider_local.dag b/dag/gunbc/compute/work_provider_local.dag index a20d21e9904..a57ef1c5be8 100644 --- a/dag/gunbc/compute/work_provider_local.dag +++ b/dag/gunbc/compute/work_provider_local.dag @@ -396,13 +396,12 @@ fn compute_run_unit(instance: HostDashboardInstance, layout: ComputeLayout, iden ], command_argv: argv, ) { - SystemdRunCommandRefused { cause: why } => + SystemdSummaryPrintingWaitRefused { cause: why } => ComputeUnitRun { exec: ComputeExecRefused { reason: systemd_run_option_words_refused_cause_detail(cause: why) }, summary: SystemdRunWaitSummary { result: ServiceResultNotReported { absence: SummaryNoInvocation }, memory_peak: MemoryPeakNotReported { absence: SummaryNoInvocation } }, } - SystemdRunCommandReady { command: command } => { - let launcher = SystemdSummaryPrintingWait { command: command } + SystemdSummaryPrintingWaitReady { wait: launcher } => { let r = compute_exec( program: systemd_run, workdir: layout.checkout, diff --git a/dag/test/claim/compute/work_class_grant_witness_test.dag b/dag/test/claim/compute/work_class_grant_witness_test.dag index 5877adc0c66..d1c2b68a326 100644 --- a/dag/test/claim/compute/work_class_grant_witness_test.dag +++ b/dag/test/claim/compute/work_class_grant_witness_test.dag @@ -50,10 +50,10 @@ import gunbc.compute.work_provider_local { // the fold is the only route from the reading to a summary.) fn fixture_summary(standing: SummaryFormatStanding, stderr: String) -> SystemdRunWaitSummary { match systemd_run_user_wait_command(unit: "gunbc-fixture", properties: [], setenv_bindings: [], command_argv: ["true"]) { - SystemdRunCommandRefused { cause: _ } => + SystemdSummaryPrintingWaitRefused { cause: _ } => SystemdRunWaitSummary { result: ServiceResultNotReported { absence: SummaryNoInvocation }, memory_peak: MemoryPeakNotReported { absence: SummaryNoInvocation } } - SystemdRunCommandReady { command: command } => - systemd_run_wait_summary(launcher: SystemdSummaryPrintingWait { command: command }, standing: standing, stderr: stderr) + SystemdSummaryPrintingWaitReady { wait: launcher } => + systemd_run_wait_summary(launcher: launcher, standing: standing, stderr: stderr) } } @@ -62,9 +62,9 @@ fn fixture_summary(standing: SummaryFormatStanding, stderr: String) -> SystemdRu // user-manager start, then the command after --. test fn the_user_wait_builder_projects_the_words_the_string_form_rendered() -> Bool { match systemd_run_user_wait_command(unit: "gunbc-fixture", properties: [], setenv_bindings: [], command_argv: ["true"]) { - SystemdRunCommandRefused { cause: _ } => false - SystemdRunCommandReady { command: command } => - argv_words(command: command) == ["systemd-run", "--wait", "--pipe", "--collect", "--user", "--unit=gunbc-fixture", "--", "true"] + SystemdSummaryPrintingWaitRefused { cause: _ } => false + SystemdSummaryPrintingWaitReady { wait: launcher } => + argv_words(command: launcher.command) == ["systemd-run", "--wait", "--pipe", "--collect", "--user", "--unit=gunbc-fixture", "--", "true"] } } @@ -299,8 +299,8 @@ fn quiet_summary(stderr: String) -> SystemdRunWaitSummary { match systemd_run_transient_wait_unit_command(unit: "gunbc-quiet", properties: [], command_argv: ["true"]) { SystemdRunCommandRefused { cause: _ } => SystemdRunWaitSummary { result: ServiceResultNotReported { absence: SummaryNoInvocation }, memory_peak: MemoryPeakNotReported { absence: SummaryNoInvocation } } - SystemdRunCommandReady { command: command } => - systemd_run_wait_summary(launcher: SystemdSummaryPrintingWait { command: command }, standing: grounded_standing, stderr: stderr) + SystemdSummaryPrintingWaitReady { wait: launcher } => + systemd_run_wait_summary(launcher: launcher, standing: grounded_standing, stderr: stderr) } } diff --git a/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag b/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag index 2d8169b681b..7420ac00a6e 100644 --- a/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag +++ b/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag @@ -125,10 +125,10 @@ test fn the_user_wait_builder_renders_the_old_order() -> Bool { setenv_bindings: [SystemdSetenvBinding { name: "A" as NonEmptyStr, value: "c" as NonEmptyStr }], command_argv: ["/bin/true"], ) { - SystemdRunCommandRefused { cause: _ } => false - SystemdRunCommandReady { command: command } => + SystemdSummaryPrintingWaitRefused { cause: _ } => false + SystemdSummaryPrintingWaitReady { wait: launcher } => argv_exact_token_equal( - left: argv_words(command: command), + left: argv_words(command: launcher.command), right: [ "systemd-run", "--wait", "--pipe", "--collect", "--user", "--unit=fci1-user.service", "-p", "MemoryMax=17179869184", "--setenv=A=c", "--", "/bin/true", @@ -175,9 +175,9 @@ test fn a_setenv_name_with_an_equals_is_refused() -> Bool { setenv_bindings: [SystemdSetenvBinding { name: "A=b" as NonEmptyStr, value: "c" as NonEmptyStr }], command_argv: ["/bin/true"], ) { - SystemdRunCommandRefused { cause: SetenvNameContainsEquals } => true - SystemdRunCommandRefused { cause: _ } => false - SystemdRunCommandReady { command: _ } => false + SystemdSummaryPrintingWaitRefused { cause: SetenvNameContainsEquals } => true + SystemdSummaryPrintingWaitRefused { cause: _ } => false + SystemdSummaryPrintingWaitReady { wait: _ } => false } } From 66dac70b7698d65232f8134a150374e09cdf401b Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 10:45:23 +0000 Subject: [PATCH 29/65] review 76430: RunTransientRetained deleted (identical contract to RunTransient -- retained vs collected is a typed-option decision rendered into the words, not a service-side contract); its three callers run RunTransient; RunTransientAndWait keeps its distinct output contract (exact exit) and name --- dag/extdeps/systemd/systemd_run.dag | 25 +++---------------- .../runner_microvm_lifecycle_realize.dag | 2 +- .../compute/work_class_grant_witness_test.dag | 4 +-- ...ner_microvm_lifecycle_wet_receipt_test.dag | 2 +- dag/test/manual/typed_builder_wet_test.dag | 2 +- 5 files changed, 9 insertions(+), 26 deletions(-) diff --git a/dag/extdeps/systemd/systemd_run.dag b/dag/extdeps/systemd/systemd_run.dag index 465594c72e9..21c5f6c9135 100644 --- a/dag/extdeps/systemd/systemd_run.dag +++ b/dag/extdeps/systemd/systemd_run.dag @@ -296,8 +296,9 @@ fn systemd_run_system_scope_command(properties: List, comman // Word order: --unit, the properties, --collect, then -- (main's green nbd witness pins this // order over the retired blob's --unit, --collect, properties; the union cannot hold both, and // main is the landed authority). The retained variant below is the same builder WITHOUT the -// --collect arm, because RunTransientRetained exists precisely so the terminal state stays -// observable. +// --collect arm, because the RETAINED invocation exists precisely so the terminal state stays +// observable (the service op is the same RunTransient either way -- retained vs collected is a +// decision the typed option model renders into the words, not a service-side contract). fn systemd_run_transient_unit_command(unit: NonEmptyStr, properties: List, command_argv: List) -> SystemdRunCommandReading { systemd_run_command_of(options: list_append(left: [RunUnit { unit: unit }], right: list_append(left: systemd_run_property_options_long(properties: properties), right: [Collect, EndOfOptions])), command_argv: command_argv) } @@ -320,7 +321,7 @@ fn systemd_run_transient_wait_unit_command(unit: NonEmptyStr, properties: List } - output { - success: Bool from "exit_success" - stdout: String from "stdout" - stderr: String from "stderr" - } - transport shell { argv: ["systemd-run", command_argv] } - exit { - 0 => Unit - nonzero => String "systemd-run transient unit start failed" - } - mock_response { - 0 => { success: true, stdout: "Running as unit: demo.service", stderr: "" } "hermetic systemd.SystemdRun.RunTransientRetained" - } - } - operation RunTransientAndWait { requires opaque input { command_argv: List } diff --git a/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag b/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag index c0c6278713e..32513bd0fb5 100644 --- a/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag +++ b/dag/gunbc/runner/runner_microvm_lifecycle_realize.dag @@ -1184,7 +1184,7 @@ fn run_jailer_in_cell(attempt: MicroVmAttempt, invocation_id: NonEmptyStr, recei ) { SystemdRunCommandRefused { cause: why } => VmmStartFailed { unit: unit, detail: systemd_run_option_words_refused_cause_detail(cause: why) } SystemdRunCommandReady { command: command } => { - let started = systemd.SystemdRun.RunTransientRetained(command_argv: systemd_run_command_argument_words(command: command)) + let started = systemd.SystemdRun.RunTransient(command_argv: systemd_run_command_argument_words(command: command)) if started.success { VmmStarted { unit: unit, receipt: receipt } } else { VmmStartFailed { unit: unit, detail: trim(s: started.stderr) } } } diff --git a/dag/test/claim/compute/work_class_grant_witness_test.dag b/dag/test/claim/compute/work_class_grant_witness_test.dag index d1c2b68a326..cdfd693dc2d 100644 --- a/dag/test/claim/compute/work_class_grant_witness_test.dag +++ b/dag/test/claim/compute/work_class_grant_witness_test.dag @@ -299,8 +299,8 @@ fn quiet_summary(stderr: String) -> SystemdRunWaitSummary { match systemd_run_transient_wait_unit_command(unit: "gunbc-quiet", properties: [], command_argv: ["true"]) { SystemdRunCommandRefused { cause: _ } => SystemdRunWaitSummary { result: ServiceResultNotReported { absence: SummaryNoInvocation }, memory_peak: MemoryPeakNotReported { absence: SummaryNoInvocation } } - SystemdSummaryPrintingWaitReady { wait: launcher } => - systemd_run_wait_summary(launcher: launcher, standing: grounded_standing, stderr: stderr) + SystemdRunCommandReady { command: command } => + systemd_run_wait_summary(launcher: SystemdSummaryPrintingWait { command: command }, standing: grounded_standing, stderr: stderr) } } diff --git a/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag b/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag index 328718d9e32..65954b4ba2a 100644 --- a/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag +++ b/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag @@ -421,7 +421,7 @@ fn start_retained_payload(unit: NonEmptyStr, properties: List false SystemdRunCommandReady { command: command } => - systemd.SystemdRun.RunTransientRetained(command_argv: systemd_run_command_argument_words(command: command)).success + systemd.SystemdRun.RunTransient(command_argv: systemd_run_command_argument_words(command: command)).success } } diff --git a/dag/test/manual/typed_builder_wet_test.dag b/dag/test/manual/typed_builder_wet_test.dag index 294fe672afa..62caa42e1c4 100644 --- a/dag/test/manual/typed_builder_wet_test.dag +++ b/dag/test/manual/typed_builder_wet_test.dag @@ -41,7 +41,7 @@ test fn a_real_retained_transient_is_still_known_after_a_failing_exit() -> Bool match systemd_run_transient_retained_unit_command(unit: unit, properties: [], command_argv: ["/bin/sh", "-c", "exit 86"]) { SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => { - let started = systemd.SystemdRun.RunTransientRetained(command_argv: systemd_run_command_argument_words(command: command)) + let started = systemd.SystemdRun.RunTransient(command_argv: systemd_run_command_argument_words(command: command)) let load_state = systemd.Systemctl.ShowLoadState(unit: unit) let reset = systemd.Systemctl.ResetFailedUnit(unit: unit) started.success && (trim(s: load_state.value) == "loaded") && reset.success From 916752e3216ac428d39013a366cbb6de151d6963 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 11:04:06 +0000 Subject: [PATCH 30/65] review 76455: comments cite live symbols (systemd_run_user_wait_command, systemd_run_system_scope_argv); the unlanded census-instrument citation and session narrative removed; dangling systemd_run_user_scope_command deleted --- dag/extdeps/systemd/systemd_run.dag | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/dag/extdeps/systemd/systemd_run.dag b/dag/extdeps/systemd/systemd_run.dag index 21c5f6c9135..d0a80804aa0 100644 --- a/dag/extdeps/systemd/systemd_run.dag +++ b/dag/extdeps/systemd/systemd_run.dag @@ -68,9 +68,6 @@ fn systemd_summary_printing_wait_argv(w: SystemdSummaryPrintingWait) -> List, command_argv: List, command_argv: List) -> SystemdRunCommandReading { - systemd_run_command_of(options: list_append(left: [RunUserManager, RunScope], right: list_append(left: systemd_run_property_options_long(properties: properties), right: [EndOfOptions])), command_argv: command_argv) -} - fn systemd_run_setenv_options(bindings: List) -> List { fold(bindings, init: [], f: (acc, b) => list_append(left: acc, right: [SetEnv { binding: b }])) } @@ -283,8 +276,8 @@ fn systemd_run_user_wait_command(unit: NonEmptyStr, properties: List, command_argv: List) -> SystemdRunCommandReading { systemd_run_command_of(options: list_append(left: [RunScope], right: list_append(left: systemd_run_property_options_long(properties: properties), right: [EndOfOptions])), command_argv: command_argv) } @@ -340,7 +333,7 @@ fn systemd_run_transient_wait_unit_command(unit: NonEmptyStr, properties: List) -> Bool { // THE READER TAKES THE LAUNCHER AND INSPECTS IT, AND THE SECOND HALF IS NOT REDUNDANT -- IT IS // MEASURED. The intent was construction: SystemdSummaryPrintingWait is minted only by -// systemd_run_user_wait_arguments, the one wait builder here that omits --quiet, so a quiet +// systemd_run_user_wait_command, the one wait builder here that omits --quiet, so a quiet // launcher should not be expressible as this argument at all. IT IS. A probe passing the // List returned by systemd_run_transient_wait_unit_argv (which carries --quiet) as this // parameter COMPILED AND EVALUATED, reading a peak out of stderr no quiet launcher could have From ecb3bcebc7325118441fdd44a3dc80aa15155704 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 11:40:10 +0000 Subject: [PATCH 31/65] review 76468: ComputeExecRefused dropped from the shared ComputeExec reading -- the unit launch carries its own typed reading (ComputeUnitRunExecuted | ComputeUnitRunRefused { cause }), the refusal surfaces as WorkRefusedByInfrastructure { cause: UnitLaunchRefused { cause } }, and the nine unreachable arms at compute_exec call sites are deleted --- dag/gunbc/compute/test_run.dag | 5 --- dag/gunbc/compute/work_provider_local.dag | 46 +++++++++++------------ 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/dag/gunbc/compute/test_run.dag b/dag/gunbc/compute/test_run.dag index 714fd73d56a..4ad563fe459 100644 --- a/dag/gunbc/compute/test_run.dag +++ b/dag/gunbc/compute/test_run.dag @@ -52,15 +52,12 @@ fn test_exec(instance: HostDashboardInstance, workdir: String, args: List SnapshotResolution { match test_exec(instance: instance, workdir: worktree, args: ["add", "-A"]) { gunbc.compute.work_provider_local.ComputeExecFailed { exit_code, stdout: _, stderr } => SnapshotRefused { step: "git add -A", detail: join([to_string(value: exit_code), ": ", stderr], "") } - gunbc.compute.work_provider_local.ComputeExecRefused { reason } => SnapshotRefused { step: "git add -A", detail: join(["the launcher refused: ", reason], "") } gunbc.compute.work_provider_local.ComputeExecOk { stdout: _, stderr: _ } => match test_exec(instance: instance, workdir: worktree, args: ["write-tree"]) { gunbc.compute.work_provider_local.ComputeExecFailed { exit_code, stdout: _, stderr } => SnapshotRefused { step: "git write-tree", detail: join([to_string(value: exit_code), ": ", stderr], "") } - gunbc.compute.work_provider_local.ComputeExecRefused { reason } => SnapshotRefused { step: "git write-tree", detail: join(["the launcher refused: ", reason], "") } gunbc.compute.work_provider_local.ComputeExecOk { stdout: tree, stderr: _ } => match test_exec(instance: instance, workdir: worktree, args: ["commit-tree", trim(s: tree), "-p", "HEAD", "-m", "gunbc compute snapshot of the attempt worktree"]) { gunbc.compute.work_provider_local.ComputeExecFailed { exit_code, stdout: _, stderr } => SnapshotRefused { step: "git commit-tree", detail: join([to_string(value: exit_code), ": ", stderr], "") } - gunbc.compute.work_provider_local.ComputeExecRefused { reason } => SnapshotRefused { step: "git commit-tree", detail: join(["the launcher refused: ", reason], "") } gunbc.compute.work_provider_local.ComputeExecOk { stdout: commit, stderr: _ } => if trim(s: commit) == "" { SnapshotRefused { step: "git commit-tree", detail: "printed no commit id" } } else { SnapshotResolved { commit: trim(s: commit) } } } @@ -77,7 +74,6 @@ type TreeListing fn list_tree(instance: HostDashboardInstance, worktree: String, commit: String) -> TreeListing { match test_exec(instance: instance, workdir: worktree, args: ["ls-tree", "-r", "--name-only", commit]) { gunbc.compute.work_provider_local.ComputeExecFailed { exit_code, stdout: _, stderr } => TreeListingRefused { detail: join(["git ls-tree exited ", to_string(value: exit_code), ": ", stderr], "") } - gunbc.compute.work_provider_local.ComputeExecRefused { reason } => TreeListingRefused { detail: join(["the launcher refused: ", reason], "") } gunbc.compute.work_provider_local.ComputeExecOk { stdout, stderr: _ } => TreeListed { paths: filter(stdout.split(sep: "\n"), p => trim(s: p) != "") } } } @@ -85,7 +81,6 @@ fn list_tree(instance: HostDashboardInstance, worktree: String, commit: String) fn show_file(instance: HostDashboardInstance, worktree: String, commit: String, path: String) -> String? { match test_exec(instance: instance, workdir: worktree, args: ["show", join([commit, ":", path], "")]) { gunbc.compute.work_provider_local.ComputeExecFailed { exit_code: _, stdout: _, stderr: _ } => none - gunbc.compute.work_provider_local.ComputeExecRefused { reason: _ } => none gunbc.compute.work_provider_local.ComputeExecOk { stdout, stderr: _ } => Present { value: stdout } } } diff --git a/dag/gunbc/compute/work_provider_local.dag b/dag/gunbc/compute/work_provider_local.dag index a57ef1c5be8..a317980aee9 100644 --- a/dag/gunbc/compute/work_provider_local.dag +++ b/dag/gunbc/compute/work_provider_local.dag @@ -133,7 +133,12 @@ fn compute_layout(instance: HostDashboardInstance, identity_hex: String) -> Comp type ComputeExec = ComputeExecOk { stdout: String, stderr: String } | ComputeExecFailed { exit_code: Int, stdout: String, stderr: String } - | ComputeExecRefused { reason: String } + +// The unit launch has its own refusal carrier: the systemd-run builder's typed cause, carried from +// the one site that can produce it. compute_exec's shared reading never refuses. +type ComputeUnitRunReading + = ComputeUnitRunExecuted { run: ComputeUnitRun } + | ComputeUnitRunRefused { cause: SystemdRunOptionWordsRefusedCause } fn compute_exec(program: String, workdir: String, args: List) -> ComputeExec { let r = gunbc.WitnessBin.Run(workdir: workdir as FilePath, bin_path: program as FilePath, args: args, expect: OutcomeIsData) @@ -153,11 +158,9 @@ type ToolchainObservation fn compute_observe_toolchain(instance: HostDashboardInstance) -> ToolchainObservation { match compute_exec(program: compute_program(instance: instance, cap: RustcCapability), workdir: instance.repo_root as String, args: ["--version"]) { ComputeExecFailed { exit_code, stdout: _, stderr } => ToolchainObservationRefused { detail: join(["rustc --version exited ", to_string(value: exit_code), ": ", stderr], "") } - ComputeExecRefused { reason } => ToolchainObservationRefused { detail: join(["rustc --version was refused by the launcher: ", reason], "") } ComputeExecOk { stdout: rustc_line, stderr: _ } => match compute_exec(program: sccache_installed_binary_path, workdir: instance.repo_root as String, args: ["--version"]) { ComputeExecFailed { exit_code, stdout: _, stderr } => ToolchainObservationRefused { detail: join(["sccache --version exited ", to_string(value: exit_code), ": ", stderr], "") } - ComputeExecRefused { reason } => ToolchainObservationRefused { detail: join(["sccache --version was refused by the launcher: ", reason], "") } ComputeExecOk { stdout: cache_line, stderr: _ } => if trim(s: rustc_line) == "" || trim(s: cache_line) == "" { ToolchainObservationRefused { detail: "rustc or sccache printed an empty version line" } @@ -177,7 +180,6 @@ type SubjectResolution fn compute_resolve_subject(instance: HostDashboardInstance, commit: String) -> SubjectResolution { match compute_exec(program: compute_program(instance: instance, cap: GitWorkspaceCapability), workdir: instance.repo_root as String, args: ["show", "-s", "--format=%T", commit]) { ComputeExecFailed { exit_code, stdout: _, stderr } => SubjectResolutionRefused { detail: join(["git show ", commit, " exited ", to_string(value: exit_code), ": ", stderr], "") } - ComputeExecRefused { reason } => SubjectResolutionRefused { detail: join(["git show was refused by the launcher: ", reason], "") } ComputeExecOk { stdout, stderr: _ } => match git_object_id_from_untagged_hex(hex: trim(s: stdout)) { Absent => SubjectResolutionRefused { detail: join(["git show printed no tree object id for ", commit, ": ", trim(s: stdout)], "") } @@ -190,7 +192,6 @@ fn compute_ensure_dir(instance: HostDashboardInstance, path: String) -> Bool { match compute_exec(program: compute_program(instance: instance, cap: AttemptStateDirectoryCapability), workdir: instance.repo_root as String, args: ["-d", "-m", "0755", path]) { ComputeExecOk { stdout: _, stderr: _ } => true ComputeExecFailed { exit_code: _, stdout: _, stderr: _ } => false - ComputeExecRefused { reason: _ } => false } } @@ -377,15 +378,14 @@ type ComputeUnitRun { fn compute_run_unit(instance: HostDashboardInstance, layout: ComputeLayout, identity_hex: String, argv: List, granted: Kibibyte) -> ComputeUnitRun { let systemd_run = match instance.toolchain.systemd_run { Present { value: p } => p as String Absent => "" } if systemd_run == "" { - ComputeUnitRun { + ComputeUnitRunExecuted { run: ComputeUnitRun { exec: ComputeExecFailed { exit_code: 127, stdout: "", stderr: "this instance's toolchain declares no systemd-run; the local provider runs only under systemd" }, summary: SystemdRunWaitSummary { result: ServiceResultNotReported { absence: SummaryNoInvocation }, memory_peak: MemoryPeakNotReported { absence: SummaryNoInvocation } }, - } + } } } else { let standing = summary_format_standing(version_line: match compute_exec(program: systemd_run, workdir: layout.checkout, args: ["--version"]) { ComputeExecOk { stdout: v, stderr: _ } => v ComputeExecFailed { exit_code: _, stdout: _, stderr: _ } => "" - ComputeExecRefused { reason: _ } => "" }) match systemd_run_user_wait_command( unit: compute_unit_name(identity_hex: identity_hex), @@ -397,11 +397,9 @@ fn compute_run_unit(instance: HostDashboardInstance, layout: ComputeLayout, iden command_argv: argv, ) { SystemdSummaryPrintingWaitRefused { cause: why } => - ComputeUnitRun { - exec: ComputeExecRefused { reason: systemd_run_option_words_refused_cause_detail(cause: why) }, - summary: SystemdRunWaitSummary { result: ServiceResultNotReported { absence: SummaryNoInvocation }, memory_peak: MemoryPeakNotReported { absence: SummaryNoInvocation } }, - } + ComputeUnitRunRefused { cause: why } SystemdSummaryPrintingWaitReady { wait: launcher } => { + ComputeUnitRunExecuted { run: ComputeUnitRun { let r = compute_exec( program: systemd_run, workdir: layout.checkout, @@ -412,9 +410,9 @@ fn compute_run_unit(instance: HostDashboardInstance, layout: ComputeLayout, iden summary: systemd_run_wait_summary(launcher: launcher, standing: standing, stderr: match r { ComputeExecOk { stdout: _, stderr } => stderr ComputeExecFailed { exit_code: _, stdout: _, stderr } => stderr - ComputeExecRefused { reason } => reason }), } + } } } } } @@ -755,11 +753,9 @@ fn compute_return_outputs(instance: HostDashboardInstance, layout: ComputeLayout let dst = join([layout.store_dir, "/", compute_basename(path: rel)], "") match compute_exec(program: compute_program(instance: instance, cap: GitWorkspaceCapability), workdir: layout.checkout, args: ["hash-object", src]) { ComputeExecFailed { exit_code: _, stdout: _, stderr } => OutputsMismatch { detail: join(["declared output ", rel, " could not be hashed: ", stderr], "") } - ComputeExecRefused { reason } => OutputsMismatch { detail: join(["declared output ", rel, " could not be hashed, the launcher refused: ", reason], "") } ComputeExecOk { stdout, stderr: _ } => match compute_exec(program: compute_program(instance: instance, cap: AttemptStateDirectoryCapability), workdir: layout.checkout, args: ["-m", "0755", src, dst]) { ComputeExecFailed { exit_code: _, stdout: _, stderr } => OutputsMismatch { detail: join(["declared output ", rel, " could not be stored: ", stderr], "") } - ComputeExecRefused { reason } => OutputsMismatch { detail: join(["declared output ", rel, " could not be stored, the launcher refused: ", reason], "") } ComputeExecOk { stdout: _, stderr: _ } => OutputsReturned { outputs: concat(outputs, [WorkOutput { declared: rel, blob: trim(s: stdout), store_path: dst }]) } } @@ -1738,12 +1734,6 @@ fn compute_run_leased( summary: compute_unspawned_summary(), gate: opened, } - ComputeExecRefused { reason } => - ComputeAttempt { - outcome: WorkRefusedByInfrastructure { identity: identity, cause: CheckoutUnavailable { detail: join(["git worktree add was refused by the launcher: ", reason], "") } }, - summary: compute_unspawned_summary(), - gate: opened, - } ComputeExecOk { stdout: _, stderr: _ } => match compute_begin_launch(layout: layout, gate: opened) { AttemptGateRefused { detail: d } => @@ -1766,20 +1756,24 @@ fn compute_spawn_and_collect( grant_was_ceiling: Bool, launched: AttemptGate, ) -> ComputeAttempt { - let unit_run = compute_run_unit(instance: instance, layout: layout, identity_hex: identity, argv: compute_command_argv(instance: instance, layout: layout, op: op, dependency_store: dependency_store), granted: granted) + match compute_run_unit(instance: instance, layout: layout, identity_hex: identity, argv: compute_command_argv(instance: instance, layout: layout, op: op, dependency_store: dependency_store), granted: granted) { + ComputeUnitRunRefused { cause: cause } => + ComputeAttempt { + outcome: WorkRefusedByInfrastructure { identity: identity, cause: UnitLaunchRefused { cause: cause } }, + summary: compute_unspawned_summary(), + gate: launched, + } + ComputeUnitRunExecuted { run: unit_run } => { let run = unit_run.exec let log = Filesystem.Write(path: layout.log_path, content: match run { ComputeExecOk { stdout, stderr } => join([stdout, stderr], "") ComputeExecFailed { exit_code: _, stdout, stderr } => join([stdout, stderr], "") - ComputeExecRefused { reason } => reason }) let summary = unit_run.summary ComputeAttempt { outcome: match run { ComputeExecFailed { exit_code, stdout: _, stderr: _ } => compute_failed_unit_outcome(summary: summary, identity: identity, exit_code: exit_code, op: op, granted: granted, grant_was_ceiling: grant_was_ceiling, log_path: layout.log_path) - ComputeExecRefused { reason } => - WorkCancelled { identity: identity, detail: join(["the unit launch was refused by the builder: ", reason], "") } ComputeExecOk { stdout: _, stderr: _ } => match compute_return_outputs(instance: instance, layout: layout, declared: work_declared_outputs(op: op)) { OutputsMismatch { detail } => WorkOutputMismatch { identity: identity, detail: detail } @@ -1789,6 +1783,8 @@ fn compute_spawn_and_collect( summary: summary, gate: launched, } + } + } } // NOTHING WAS SPAWNED, SO NOTHING WAS SUMMARIZED: the same absence main's checkout-failure arm From 395effc6d683198b7ca23b4c5aa2dae71b445b4c Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 12:01:33 +0000 Subject: [PATCH 32/65] compute_run_unit: bind the exec before constructing the executed reading (a constructor cannot contain lets) --- dag/gunbc/compute/work_provider_local.dag | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/dag/gunbc/compute/work_provider_local.dag b/dag/gunbc/compute/work_provider_local.dag index a317980aee9..9264425e293 100644 --- a/dag/gunbc/compute/work_provider_local.dag +++ b/dag/gunbc/compute/work_provider_local.dag @@ -399,19 +399,17 @@ fn compute_run_unit(instance: HostDashboardInstance, layout: ComputeLayout, iden SystemdSummaryPrintingWaitRefused { cause: why } => ComputeUnitRunRefused { cause: why } SystemdSummaryPrintingWaitReady { wait: launcher } => { - ComputeUnitRunExecuted { run: ComputeUnitRun { let r = compute_exec( program: systemd_run, workdir: layout.checkout, args: systemd_summary_printing_wait_argv(w: launcher), ) - ComputeUnitRun { + ComputeUnitRunExecuted { run: ComputeUnitRun { exec: r, summary: systemd_run_wait_summary(launcher: launcher, standing: standing, stderr: match r { ComputeExecOk { stdout: _, stderr } => stderr ComputeExecFailed { exit_code: _, stdout: _, stderr } => stderr }), - } } } } } From 5175c6f07200e2dc5be1a8a6f23c818fcf7bfa6a Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 12:37:21 +0000 Subject: [PATCH 33/65] compute_run_unit returns ComputeUnitRunReading (the signature kept the old record type) --- dag/gunbc/compute/work_provider_local.dag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dag/gunbc/compute/work_provider_local.dag b/dag/gunbc/compute/work_provider_local.dag index 9264425e293..43dbfa6ef3e 100644 --- a/dag/gunbc/compute/work_provider_local.dag +++ b/dag/gunbc/compute/work_provider_local.dag @@ -375,7 +375,7 @@ type ComputeUnitRun { // set must refuse rather than have its figures read against a format nobody has checked. Reading // `systemctl --version` there would answer about a DIFFERENT executable than the one that prints the // line, which on a mixed install is a different version. -fn compute_run_unit(instance: HostDashboardInstance, layout: ComputeLayout, identity_hex: String, argv: List, granted: Kibibyte) -> ComputeUnitRun { +fn compute_run_unit(instance: HostDashboardInstance, layout: ComputeLayout, identity_hex: String, argv: List, granted: Kibibyte) -> ComputeUnitRunReading { let systemd_run = match instance.toolchain.systemd_run { Present { value: p } => p as String Absent => "" } if systemd_run == "" { ComputeUnitRunExecuted { run: ComputeUnitRun { From 7255cbbd901ce09bff64cdbb85f8fd2bf2ab0d5a Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 12:55:06 +0000 Subject: [PATCH 34/65] typed_builder_wet enrolment row: the declared gap in full -- what CI exercises (word shapes) and what nothing automated exercises (manager-side behavior; no CI lane can run systemd units), with the dissolve trigger (a scheduled lane host with a usable manager) --- dag/gunbc/ci/ci_layer_roots.dag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dag/gunbc/ci/ci_layer_roots.dag b/dag/gunbc/ci/ci_layer_roots.dag index 83696273a47..b7b58777f35 100644 --- a/dag/gunbc/ci/ci_layer_roots.dag +++ b/dag/gunbc/ci/ci_layer_roots.dag @@ -670,7 +670,7 @@ data witness_exclusion_frontier: List = [ WitnessExclusionRow { pattern: "typed_builder_wet_test.dag", classification: LocalRepoWetLane, - reason: "The typed systemd-run builders' DESIGN 3 pairing: these three claims execute the projected words through real systemd-run on the lane's host (the same host whose microVM lifecycle receipts run real transient units under the system manager, so a usable manager is evidenced, not assumed) and assert the behavior the --collect decision buys from the wire: a retained unit that failed is still known to the manager (loaded), the collected twin is gone (not-found), and the waited invocation reports the child's exact exit (86). Hermetic discovery correctly refuses these (no mock_response fabricates a manager), so they run on the local-repo wet lane only.", + reason: "The typed systemd-run builders' DESIGN 3 pairing. DECLARED GAP, stated in full: what IS exercised in CI is the projected word shape -- every builder's bytes are pinned by hermetic controls (per-builder byte identity, typed refusal causes, the 11-word cardinality), so a wiring or ordering defect fails in CI. What is NOT exercised anywhere automated is manager-side behavior: no CI lane can run systemd units (the hermetic runner is unprivileged with no user manager; the build runner has no systemd at all -- PID 1 is init), so the retained-stays-loaded, collected-is-gone and exact-exit assertions execute only when a lane host runs them, and no required workflow schedules that host today. The trigger that dissolves this gap: a lane host with a usable manager (a user manager for the runner account, or the controlled sudo --user target) scheduled in a required workflow -- at that point these claims move onto that lane's schedule and this row deletes. The claims themselves execute the projected words through real systemd-run and assert what the --collect decision buys from the wire: a retained unit that failed is still known to the manager (loaded), the collected twin is gone (not-found), and the waited invocation reports the child's exact exit (86).", dissolution: unbound_dissolution(description: "when the transports' hermetic mocks model a real manager faithfully enough that the --collect discrimination and the exact-exit pass can be asserted against recorded fixtures without losing the pairing (a mock that always says success would erase exactly the evidence these claims exist for), these re-enroll as ordinary hermetic rows and this exclusion deletes")}, WitnessExclusionRow { pattern: "self_host_logic_behavioral_witness_test.dag", From 04ff6fe1e95b3cf824ff67dad77f1dff39e170c4 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 14:28:58 +0000 Subject: [PATCH 35/65] blocker 6 per parent decision: the wet claims no lane can execute are removed from the PR (a touched claim file with no executable lane turns the floor red and is dangling per DESIGN 3c); the ci_layer_roots row is now the declared gap -- manager-side behaviour has no executing claim, the claims are authored when a lane host with a usable user manager exists, the population is the typed builders' manager-side contract --- dag/test/manual/typed_builder_wet_test.dag | 82 ---------------------- 1 file changed, 82 deletions(-) delete mode 100644 dag/test/manual/typed_builder_wet_test.dag diff --git a/dag/test/manual/typed_builder_wet_test.dag b/dag/test/manual/typed_builder_wet_test.dag deleted file mode 100644 index 62caa42e1c4..00000000000 --- a/dag/test/manual/typed_builder_wet_test.dag +++ /dev/null @@ -1,82 +0,0 @@ -module test.manual.typed_builder_wet - -import std.types { String, NonEmptyStr, Bool, List, Int } -import std.algebra { trim } -import std.resources { Network } -import extdeps.systemd.systemd_run { - SystemdRunCommandReady, - SystemdRunCommandRefused, - systemd_run_transient_unit_command, - systemd_run_transient_retained_unit_command, - systemd_run_transient_wait_unit_command, - systemd_run_command_argument_words, -} - -// THE TYPED BUILDERS, EXECUTED — the DESIGN 3 pairing the typed-argv cutover owes. The hermetic -// witness test.claim.fabric.systemd_run_transient_wait_witness_test pins the projected words; it -// establishes nothing about whether real systemd-run accepts them and behaves. These claims run -// the typed builders' words through the real transports on the wet lane's host — the host whose -// microVM lifecycle receipts already execute real transient units under the system manager -// (systemd is that host's PID 1; the lane's runner classes carry it) — and assert the behavior -// the --collect decision is about. The user-manager question is answered by that evidence: these -// claims run against the system manager, which the lane demonstrably has; no --user claim is -// enrolled, and if a user-manager claim is ever needed the controlled target is the same host -// under `sudo systemd-run --user` with a private bus assertion, not a hopeful --user. -// -// THE DISCRIMINATION IS ONE-HOT ON --collect, EXECUTED: the same failing command, run through the -// collecting builder and through the retained builder, lands in different terminal states — the -// collected unit is gone (not-found), the retained unit is still known to the manager (loaded). -// That is the evidence the retained shape exists to buy, now taken from the wire rather than -// asserted from the projection. - -// The retained invocation: the unit FAILED (exit 86) and is still known to the manager, because -// no --collect was passed. This is the claim that proves the terminal state stays observable. -// The observation is the point; the failed unit is then reset (ResetFailedUnit) so the NEXT -// run of this claim can start the same fixed unit name again instead of being refused because it -// exists. -test fn a_real_retained_transient_is_still_known_after_a_failing_exit() -> Bool - uses net: Network -{ - let unit = "fci1-typed-argv-wet-retained.service" as NonEmptyStr - match systemd_run_transient_retained_unit_command(unit: unit, properties: [], command_argv: ["/bin/sh", "-c", "exit 86"]) { - SystemdRunCommandRefused { cause: _ } => false - SystemdRunCommandReady { command: command } => { - let started = systemd.SystemdRun.RunTransient(command_argv: systemd_run_command_argument_words(command: command)) - let load_state = systemd.Systemctl.ShowLoadState(unit: unit) - let reset = systemd.Systemctl.ResetFailedUnit(unit: unit) - started.success && (trim(s: load_state.value) == "loaded") && reset.success - } - } -} - -// The collecting invocation, SAME failing command: the manager no longer knows the unit. Red -// means the collected and retained shapes stopped discriminating on the wire. -test fn the_collected_twin_of_that_unit_is_gone() -> Bool - uses net: Network -{ - let unit = "fci1-typed-argv-wet-collected.service" as NonEmptyStr - match systemd_run_transient_unit_command(unit: unit, properties: [], command_argv: ["/bin/sh", "-c", "exit 86"]) { - SystemdRunCommandRefused { cause: _ } => false - SystemdRunCommandReady { command: command } => { - let started = systemd.SystemdRun.RunTransient(command_argv: systemd_run_command_argument_words(command: command)) - let load_state = systemd.Systemctl.ShowLoadState(unit: unit) - started.success && (trim(s: load_state.value) == "not-found") - } - } -} - -// The waited invocation reports the child's exact exit through the typed projection: the -// pre-cutover contract this cutover had to preserve byte-for-byte in behavior, taken from the -// wire. 86 is the same sentinel the hermetic fixture pins. -test fn the_waited_transient_reports_the_exact_exit() -> Bool - uses net: Network -{ - let unit = "fci1-typed-argv-wet-waited.service" as NonEmptyStr - match systemd_run_transient_wait_unit_command(unit: unit, properties: [], command_argv: ["/bin/sh", "-c", "exit 86"]) { - SystemdRunCommandRefused { cause: _ } => false - SystemdRunCommandReady { command: command } => { - let waited = systemd.SystemdRun.RunTransientAndWait(command_argv: systemd_run_command_argument_words(command: command)) - waited.exit_code == 86 - } - } -} From 90f5480d885eb170ca596664ead54e5c637c635b Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 14:55:51 +0000 Subject: [PATCH 36/65] review 76599: the argv-matches-authority predicates are narrow by construction, so their comments now claim only what they catch (program-head spelling, embedded-newline refusal, materialization refusal) -- the old order-guard wording claimed a red that can no longer be produced; ci_layer_roots typed_builder_wet row reworded to the declared gap per parent (no executing claim anywhere, claims authored when a lane host with a usable user manager is scheduled) --- dag/extdeps/systemd/systemd_run.dag | 7 ++++--- dag/gunbc/ci/ci_layer_roots.dag | 4 ++-- dag/gunbc/systemd_run_transient.dag | 14 +++++++++----- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/dag/extdeps/systemd/systemd_run.dag b/dag/extdeps/systemd/systemd_run.dag index d0a80804aa0..cace78b5a97 100644 --- a/dag/extdeps/systemd/systemd_run.dag +++ b/dag/extdeps/systemd/systemd_run.dag @@ -283,9 +283,10 @@ fn systemd_run_system_scope_command(properties: List, comman } // The full invocation, and the authority the materialized operation argv is checked against. It -// COMPOSES the option words through the one projection rather than re-deriving them; if this fold -// and the transport template ever disagree about word order, -// systemd_run_transient_operation_argv_matches_authority goes red. +// COMPOSES the option words through the one projection rather than re-deriving them. The +// transport template carries no words of its own beyond the program head, so this fold's order IS +// the executed order; systemd_run_transient_operation_argv_matches_authority witnesses the head +// spelling and refuses embedded newlines (see its comment for the narrowed check). // Word order: --unit, the properties, --collect, then -- (main's green nbd witness pins this // order over the retired blob's --unit, --collect, properties; the union cannot hold both, and // main is the landed authority). The retained variant below is the same builder WITHOUT the diff --git a/dag/gunbc/ci/ci_layer_roots.dag b/dag/gunbc/ci/ci_layer_roots.dag index b7b58777f35..81dbae66d4a 100644 --- a/dag/gunbc/ci/ci_layer_roots.dag +++ b/dag/gunbc/ci/ci_layer_roots.dag @@ -670,8 +670,8 @@ data witness_exclusion_frontier: List = [ WitnessExclusionRow { pattern: "typed_builder_wet_test.dag", classification: LocalRepoWetLane, - reason: "The typed systemd-run builders' DESIGN 3 pairing. DECLARED GAP, stated in full: what IS exercised in CI is the projected word shape -- every builder's bytes are pinned by hermetic controls (per-builder byte identity, typed refusal causes, the 11-word cardinality), so a wiring or ordering defect fails in CI. What is NOT exercised anywhere automated is manager-side behavior: no CI lane can run systemd units (the hermetic runner is unprivileged with no user manager; the build runner has no systemd at all -- PID 1 is init), so the retained-stays-loaded, collected-is-gone and exact-exit assertions execute only when a lane host runs them, and no required workflow schedules that host today. The trigger that dissolves this gap: a lane host with a usable manager (a user manager for the runner account, or the controlled sudo --user target) scheduled in a required workflow -- at that point these claims move onto that lane's schedule and this row deletes. The claims themselves execute the projected words through real systemd-run and assert what the --collect decision buys from the wire: a retained unit that failed is still known to the manager (loaded), the collected twin is gone (not-found), and the waited invocation reports the child's exact exit (86).", - dissolution: unbound_dissolution(description: "when the transports' hermetic mocks model a real manager faithfully enough that the --collect discrimination and the exact-exit pass can be asserted against recorded fixtures without losing the pairing (a mock that always says success would erase exactly the evidence these claims exist for), these re-enroll as ordinary hermetic rows and this exclusion deletes")}, + reason: "DECLARED GAP (pre-existing, declared at the typed-argv cutover): the manager-side behaviour of the systemd-run builders -- a retained unit that failed is still known to the manager, a collected one is gone, a waited invocation reports the child's exact exit -- has NO executing claim anywhere in CI or in this tree, so no claim file is shipped for it (a claim no lane can execute is dangling). No CI lane can run systemd units (the hermetic runner is unprivileged with no user manager; the build runner has no systemd at all), and the retired string builders never had CI execution either, so declaring this gap removes nothing. The population is the typed builders' manager-side contract; what CI does exercise is the projected word shape (per-builder byte identity, typed refusal causes, cardinality). Trigger: a lane host with a usable user manager exists and is scheduled in a required workflow -- at that point the claims are authored and enrolled on that lane.", + dissolution: unbound_dissolution(description: "when a lane host with a usable user manager (for the runner account, or via the controlled sudo --user target) is scheduled in a required workflow, author the manager-side claims, enroll them on that lane's schedule, and delete this row")}, WitnessExclusionRow { pattern: "self_host_logic_behavioral_witness_test.dag", classification: LocalRepoWetLane, diff --git a/dag/gunbc/systemd_run_transient.dag b/dag/gunbc/systemd_run_transient.dag index 8ca994a7763..99efb455770 100644 --- a/dag/gunbc/systemd_run_transient.dag +++ b/dag/gunbc/systemd_run_transient.dag @@ -222,11 +222,15 @@ fn argv_exact_token_equal(left: List, right: List) -> Bool { } } -// The transport template is declared, not re-derived, so this predicate now compares the -// materialized words against the words the caller's typed command projects: if the operation's -// declared transport and the authority's projection ever disagree about word order or content, it -// goes red here — the property population the old two-folds-agree check could not see is exactly -// what this join sees, because both sides now come from the one projection. +// The transport template carries no words of its own beyond the program head, so this predicate +// is narrow by construction: the materialized words are ["systemd-run"] ++ the caller's +// command_argv, and the authority is the same command's own words — tail order and property +// population CANNOT disagree, both sides come from the one projection. What the check still +// catches: a changed or misspelled program head (the one word the projection does not own), any +// word carrying an embedded newline (argv_exact_token_equal refuses those instead of letting a +// join hide a separator), and any materialization refusal. The old two-folds-agree check +// dissolved into construction; this predicate survives as that narrowed witness mount (the nbd +// witnesses call it to prove the words materialize at all), not as an order guard. fn systemd_run_transient_operation_argv_matches_authority(command: ArgvCommand) -> Bool { let authority = argv_words(command: command) match systemd_run_transient_operation_argv(command: command) { From cf2aefbce1bfd2c07b918b2b83b4611666747dcc Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 15:19:31 +0000 Subject: [PATCH 37/65] census fixture moves under the declared src/v2 root: claim_batch's filesystem confines reads to declared source roots (diag battery: the fixture read refused at the read stage, not parse), so the fixture now lives beside the text-string census's fixture precedent (src/v2/compiler/self_host/direct_rust_door_ingest_fixture.dag); declared module name kept unique (fixture.argv_word_construction.host_words), unused FreeMonoid import dropped --- .../argv_word_construction/census_test.dag | 31 ++++++++++++++++++- .../argv_word_construction/host_words.dag | 5 +-- 2 files changed, 33 insertions(+), 3 deletions(-) rename {test => src/v2/test}/fixture_roots/argv_word_construction/host_words.dag (89%) diff --git a/src/v2/test/claim/argv_word_construction/census_test.dag b/src/v2/test/claim/argv_word_construction/census_test.dag index 1d5d1f414c1..09e1cfc4461 100644 --- a/src/v2/test/claim/argv_word_construction/census_test.dag +++ b/src/v2/test/claim/argv_word_construction/census_test.dag @@ -39,7 +39,36 @@ import std.process { ExitSuccess, ProcessExit, ExitFailure } // ------------------------------------------------------------------ fixture module route -data fixture_path: String = "test/fixture_roots/argv_word_construction/host_words.dag" +// DIAGNOSTIC battery (to be deleted once the fixture route is green): one claim per stage of the +// route, so a red names the stage instead of the conjunction. +test fn diag_fixture_read_established() -> Bool { + match argv_word_construction_module_sites(path: fixture_path) { + ArgvWordsModuleEstablished { path: _, sites: _ } => true + ArgvWordsModuleRefused { path: _, reason: r } => { + let _ = r + false + } + } +} + +test fn diag_fixture_fact_count_is_seven() -> Bool { + match argv_word_construction_module_sites(path: fixture_path) { + ArgvWordsModuleEstablished { path: _, sites: _ } => true + ArgvWordsModuleRefused { path: _, reason: r } => string_contains(s: r, pattern: "parse") + } +} + +// The member set the walk reports, by count: four census members (two counted data decls, one +// argv-returning fn with a flag literal, one argv-parameter fn), three non-members. +test fn diag_fixture_member_count_is_four() -> Bool { + match argv_word_construction_module_sites(path: fixture_path) { + ArgvWordsModuleEstablished { path: _, sites: sites } => length(xs: sites) == 4 + ArgvWordsModuleRefused { path: _, reason: _ } => false + } +} + + +data fixture_path: String = "src/v2/test/fixture_roots/argv_word_construction/host_words.dag" // The fixture is a real parse of a real file: read -> raw parse -> walk -> facts -> sites. If the // grammar, the walk, or the classification drifts, this route goes red at the identity grain. diff --git a/test/fixture_roots/argv_word_construction/host_words.dag b/src/v2/test/fixture_roots/argv_word_construction/host_words.dag similarity index 89% rename from test/fixture_roots/argv_word_construction/host_words.dag rename to src/v2/test/fixture_roots/argv_word_construction/host_words.dag index 31dbb52cd56..c00bd8e85aa 100644 --- a/test/fixture_roots/argv_word_construction/host_words.dag +++ b/src/v2/test/fixture_roots/argv_word_construction/host_words.dag @@ -2,9 +2,10 @@ module fixture.argv_word_construction.host_words // The committed real-parse fixture for the typed-argv census lens (v2.lens.argv_word_construction): // small enough to read and parse inside a claim test, and shaped so every census signal appears -// once. Off the corpus compile path (test/fixture_roots, like the text-string census's fixture). +// once. Under the declared src/v2 root like the text-string census's fixture, so claim_batch's +// confined filesystem can read it and the module loader typechecks it as part of the run. -import std.algebra { Cons, Empty, FreeMonoid } +import std.algebra { Cons, Empty } import v2.std.logic { Bool } import v2.std.collection { List } import v2.std.algebra { length } From 613317b738e79d54ea3e533b29e9c72c508e0bed Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 16:30:03 +0000 Subject: [PATCH 38/65] WIP diag battery 2 --- .../argv_word_construction/census_test.dag | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/v2/test/claim/argv_word_construction/census_test.dag b/src/v2/test/claim/argv_word_construction/census_test.dag index 09e1cfc4461..e16502caef0 100644 --- a/src/v2/test/claim/argv_word_construction/census_test.dag +++ b/src/v2/test/claim/argv_word_construction/census_test.dag @@ -255,3 +255,53 @@ fn site_absent(sites: List, module_file: String, decl: String) -> Absent => true } } + +// --- diag battery 2 (throwaway, delete before commit): turn the refusal reason into verdicts --- +test fn diag2_reason_canonicalize() -> Bool { + match argv_word_construction_module_sites(path: fixture_path) { + ArgvWordsModuleEstablished { sites: _ } => false + ArgvWordsModuleRefused { path: _, reason: r } => string_contains(s: r, pattern: "canonicalize") + } +} + +test fn diag2_reason_outside_root() -> Bool { + match argv_word_construction_module_sites(path: fixture_path) { + ArgvWordsModuleEstablished { sites: _ } => false + ArgvWordsModuleRefused { path: _, reason: r } => string_contains(s: r, pattern: "outside the checkout root") + } +} + +test fn diag2_reason_commit_deterministic() -> Bool { + match argv_word_construction_module_sites(path: fixture_path) { + ArgvWordsModuleEstablished { sites: _ } => false + ArgvWordsModuleRefused { path: _, reason: r } => string_contains(s: r, pattern: "commit-deterministic") + } +} + +test fn diag2_reason_governed() -> Bool { + match argv_word_construction_module_sites(path: fixture_path) { + ArgvWordsModuleEstablished { sites: _ } => false + ArgvWordsModuleRefused { path: _, reason: r } => string_contains(s: r, pattern: "governed") + } +} + +test fn diag2_reason_mock() -> Bool { + match argv_word_construction_module_sites(path: fixture_path) { + ArgvWordsModuleEstablished { sites: _ } => false + ArgvWordsModuleRefused { path: _, reason: r } => string_contains(s: r, pattern: "mock") + } +} + +test fn diag2_reason_read_refused_prefix() -> Bool { + match argv_word_construction_module_sites(path: fixture_path) { + ArgvWordsModuleEstablished { sites: _ } => false + ArgvWordsModuleRefused { path: _, reason: r } => string_contains(s: r, pattern: "read refused:") + } +} + +test fn diag2_reason_nonempty() -> Bool { + match argv_word_construction_module_sites(path: fixture_path) { + ArgvWordsModuleEstablished { sites: _ } => false + ArgvWordsModuleRefused { path: _, reason: r } => !string_is_empty(s: r) + } +} From 097bc4b8945236644518ab527db7c13b0b7713f7 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 17:39:57 +0000 Subject: [PATCH 39/65] census claims rewritten on the verified route: fixture facts are read+parsed inside the claims (Filesystem.Read works hermetically -- proven by the tsic pin control), the modeled-roots boundary is pinned as a typed refusal with its reason, the established route is proven on a small in-roots module (ampere_altra_package_contacts: zero sites, distinct from refusal), class routing is pure, gates restored to their green shapes, and the committed instrument's contract claim re-walks exactly the roster's modules (self-scaling, never corpus-scale); the fixture gains the by-name reach its commit_args comment promised (transient tail) and its comments now state the member classes the lens actually reports; the instrument gains roster_modules (the bounded-contract population) and the wet recount emitter --- .../argv_word_construction_census.dag | 33 +- .../argv_word_construction/census_test.dag | 353 +++++++++--------- .../argv_word_construction/host_words.dag | 12 +- 3 files changed, 215 insertions(+), 183 deletions(-) diff --git a/dag/gunbc/instruments/argv_word_construction_census.dag b/dag/gunbc/instruments/argv_word_construction_census.dag index 3747f72d1b4..af17266b449 100644 --- a/dag/gunbc/instruments/argv_word_construction_census.dag +++ b/dag/gunbc/instruments/argv_word_construction_census.dag @@ -26,7 +26,8 @@ import v2.lens.argv_word_construction { ArgvWordsModuleEstablished, ArgvWordsModuleRefused, argv_word_construction_gates, - argv_word_construction_module_sites + argv_word_construction_module_sites, + argv_word_construction_for_paths } import v2.std.algebra { fold_list, list_reverse } import v2.std.collection { List } @@ -78,18 +79,46 @@ fn argv_word_construction_census_check_sites(sites: List) -> Proc // The recount entry: discover the roots' module files by the declaration index (never a text // search), walk each with the lens, and hold the contract. fn argv_word_construction_census_recount() -> ProcessExit { + argv_word_construction_census_check_paths(paths: argv_word_construction_census_root_paths()) +} + +// The discovered modeled roots' module files, in declaration-index order. Shared by the gated +// recount above and the roster emitter below so both see the same population. +fn argv_word_construction_census_root_paths() -> List { let decls = module_declaration_facts_live(pool_roots: argv_word_construction_census_roots) let paths_reversed = fold_list(xs: decls, empty: Empty, cons: fn(acc, d) { let p = declared_module_path(d: d) if p == "" { acc } else { Cons { head: p, tail: acc } } }) - argv_word_construction_census_check_paths(paths: list_reverse(xs: paths_reversed)) + list_reverse(xs: paths_reversed) +} + +// The roster emitter for a wet-lane run (claim_batch --wet): the same discovered population, each +// module walked by the lens, rows written as TSV to the scratch path. The committed roster data +// decl above is filled FROM this output, never by hand from a text search. Hermetic lanes refuse +// the write by design -- the emitter exists for the recount, not for CI. +fn argv_word_construction_census_recount_emit() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths(), out: "target/argv_word_construction_census_recount.tsv") } fn declared_module_path(d: ModuleDeclarationFact) -> String { d.path } +// The distinct module files named by the committed roster, in first-appearance order: exactly the +// population the bounded-contract claim re-walks, so CI executes the gates at roster size without +// ever walking the whole corpus. +fn argv_word_construction_census_roster_modules() -> List { + let reversed = fold_list(xs: argv_word_construction_census_roster, empty: Empty, cons: fn(acc, r) { + if contains_string(xs: acc, x: r.module_file) { acc } else { Cons { head: r.module_file, tail: acc } } + }) + list_reverse(xs: reversed) +} + +fn contains_string(xs: List, x: String) -> Bool { + fold_list(xs: xs, empty: false, cons: fn(acc, y) { acc || (y == x) }) +} + type ArgvWordsCollected { sites: FreeMonoid refusals: FreeMonoid diff --git a/src/v2/test/claim/argv_word_construction/census_test.dag b/src/v2/test/claim/argv_word_construction/census_test.dag index e16502caef0..fe0457a245f 100644 --- a/src/v2/test/claim/argv_word_construction/census_test.dag +++ b/src/v2/test/claim/argv_word_construction/census_test.dag @@ -1,16 +1,22 @@ -module v2.test.argv_word_construction.census +module v2.test.claim.argv_word_construction.census_test -// THE TYPED-ARGV CONSTRUCTION CENSUS WITNESS (debt paydown trial, witty-tern-54, 2026-10-03). +// Claims for the typed-argv construction census (the debt paydown instrument). // -// The instrument is v2.lens.argv_word_construction over the roots dag/extdeps and dag/gunbc; the -// committed state is gunbc.instruments.argv_word_construction_census. These tests hold the lens's -// VERDICTS at the identity grain the roster joins at: -// - the real-parse route over the committed fixture module (every census signal, once); -// - the real-parse route over the trial's specimen, pinned at its pre-cutover state; -// - the roster gates on hand-built sites (stale row refuses; uncovered member refuses; the -// roster never grows past the baseline). +// What CI CAN hold: the lens mechanics on a controlled fixture read and parsed inside the claim, +// the modeled-roots boundary (a typed refusal, never a silent zero), the established route on a +// small in-roots module, the site-class routing, the roster gates on constructed rows, and the +// committed instrument's contract over exactly the roster's own modules. +// +// What CI CANNOT hold: a whole-corpus walk inside one witness (parsing hundreds of modules +// exceeds the witness budget). The corpus recount is the committed instrument's own emitter, +// argv_word_construction_census_recount / _recount_emit, run on a wet lane; the committed roster +// in gunbc.instruments.argv_word_construction_census is filled from that output, and the +// bounded-contract claim below re-walks exactly the roster's modules, so the gates stay executed +// in CI at whatever size the roster is. import v2.lens.argv_word_construction { + ArgvWordsFact, + ArgvWordsSiteClass, ArgvWordsRosterRow, ArgvWordsSite, DataDecl, @@ -19,153 +25,211 @@ import v2.lens.argv_word_construction { WorkflowCallSite, ArgvWordsModuleEstablished, ArgvWordsModuleRefused, + RawTreeEstablished, + RawTreeNotEstablished, argv_word_construction_gates, argv_word_construction_module_sites, argv_word_construction_rows_resolve, argv_word_construction_census_covers, argv_word_construction_roster_shrinks, - argv_word_construction_site_named + argv_word_construction_site_named, + argv_word_construction_site_class_of, + facts_of, + is_census_member, + raw_tree_of_text } import gunbc.instruments.argv_word_construction_census { argv_word_construction_census_live, argv_word_construction_census_check_paths, - argv_word_construction_census_roster + argv_word_construction_census_roster, + argv_word_construction_census_roster_modules } +import extdeps.filesystem.filesystem_io { Filesystem, FilesystemReadSucceeded, FilesystemReadRefused } import v2.std.algebra { fold_list, length } import v2.std.logic { Bool } import v2.std.collection { List } import v2.std.optional { Absent, Present } import std.process { ExitSuccess, ProcessExit, ExitFailure } -// ------------------------------------------------------------------ fixture module route +// A real module file under the declared src/v2 root (claim_batch's filesystem is confined to the +// declared source roots), read and parsed INSIDE the claims that exercise the lens mechanics. Its +// declared module name (fixture.argv_word_construction.host_words) is unique in the tree, so +// loading it does not fork the namespace. It sits OUTSIDE the modeled census roots, which the +// boundary claim below pins as a typed refusal. +data fixture_path: String = "src/v2/test/fixture_roots/argv_word_construction/host_words.dag" -// DIAGNOSTIC battery (to be deleted once the fixture route is green): one claim per stage of the -// route, so a red names the stage instead of the conjunction. -test fn diag_fixture_read_established() -> Bool { - match argv_word_construction_module_sites(path: fixture_path) { - ArgvWordsModuleEstablished { path: _, sites: _ } => true - ArgvWordsModuleRefused { path: _, reason: r } => { - let _ = r - false - } - } -} +// The modeled-roots specimen, used only through the pure class router (no parse in CI). +data specimen_path: String = "dag/extdeps/systemd/systemd_run.dag" +data specimen_workflow_path: String = "dag/gunbc/compute/work_provider_local.dag" -test fn diag_fixture_fact_count_is_seven() -> Bool { - match argv_word_construction_module_sites(path: fixture_path) { - ArgvWordsModuleEstablished { path: _, sites: _ } => true - ArgvWordsModuleRefused { path: _, reason: r } => string_contains(s: r, pattern: "parse") - } -} +// A small module inside the modeled roots with no argv debt: the established route must +// establish it and report an empty site set -- distinct from the boundary refusal. +data established_route_path: String = "dag/extdeps/cpu/ampere_altra_package_contacts.dag" -// The member set the walk reports, by count: four census members (two counted data decls, one -// argv-returning fn with a flag literal, one argv-parameter fn), three non-members. -test fn diag_fixture_member_count_is_four() -> Bool { - match argv_word_construction_module_sites(path: fixture_path) { - ArgvWordsModuleEstablished { path: _, sites: sites } => length(xs: sites) == 4 - ArgvWordsModuleRefused { path: _, reason: _ } => false +data gate_file: String = "dag/gunbc/instruments/argv_word_construction_census.dag" + +// ---------------------------------------------------------------- fixture facts + +fn fixture_facts() -> Optional> { + let read = Filesystem.Read(path: fixture_path) + match read { + FilesystemReadRefused { error: _ } => Absent + FilesystemReadSucceeded { content: text } => + match raw_tree_of_text(text: text) { + RawTreeNotEstablished { cause: _ } => Absent + RawTreeEstablished { root: root } => Present { value: facts_of(root: root) } + } } } +fn fact_named(xs: List, want: String) -> Optional { + fold_list(xs: xs, empty: Absent, cons: fn(acc, f) { + match acc { + Present { value: _ } => acc + Absent => if f.decl == want { Present { value: f } } else { Absent } + } + }) +} -data fixture_path: String = "src/v2/test/fixture_roots/argv_word_construction/host_words.dag" - -// The fixture is a real parse of a real file: read -> raw parse -> walk -> facts -> sites. If the -// grammar, the walk, or the classification drifts, this route goes red at the identity grain. -test fn census_fixture_route_establishes_the_member_sites() -> Bool { - match argv_word_construction_module_sites(path: fixture_path) { - ArgvWordsModuleEstablished { path: _, sites: sites } => - site_present(sites: sites, module_file: fixture_path, decl: "fixture_wait_flags") - && site_present(sites: sites, module_file: fixture_path, decl: "fixture_commit_args") - && site_present(sites: sites, module_file: fixture_path, decl: "fixture_transient_unit_argv") - && site_present(sites: sites, module_file: fixture_path, decl: "fixture_run_with_properties") - && site_absent(sites: sites, module_file: fixture_path, decl: "fixture_layout_rows") - && site_absent(sites: sites, module_file: fixture_path, decl: "fixture_label_rows") - ArgvWordsModuleRefused { path: _, reason: _ } => false +test fn census_fixture_route_counts_its_members() -> Bool { + match fixture_facts() { + Absent => false + Present { value: facts } => + let members = fold_list(xs: facts, empty: 0, cons: fn(acc, f) { if is_census_member(f: f) { acc + 1 } else { acc } }) + (members == 6) + && match fact_named(xs: facts, want: "fixture_layout_rows") { + Absent => false + Present { value: f } => !is_census_member(f: f) + } } } -// The flag-word data decl carries its flag words; the argv-named flagless list is a member with -// none; the ceiling List data decl is not a member at all. test fn census_fixture_route_classifies_data_decls_at_their_own_kind() -> Bool { - match argv_word_construction_module_sites(path: fixture_path) { - ArgvWordsModuleEstablished { path: _, sites: sites } => - match argv_word_construction_site_named(sites: sites, module_file: fixture_path, decl: "fixture_wait_flags") { - Present { value: s } => (s.decl_kind == DataDecl) && !(length(xs: s.flag_words) == 0) - Absent => false - } - && match argv_word_construction_site_named(sites: sites, module_file: fixture_path, decl: "fixture_commit_args") { - Present { value: s } => (s.decl_kind == DataDecl) && (length(xs: s.flag_words) == 0) + match fixture_facts() { + Absent => false + Present { value: facts } => + match fact_named(xs: facts, want: "fixture_wait_flags") { Absent => false + Present { value: wait_flags } => + match fact_named(xs: facts, want: "fixture_commit_args") { + Absent => false + Present { value: commit_args } => + match fact_named(xs: facts, want: "fixture_layout_rows") { + Absent => false + Present { value: layout_rows } => + // wait_flags carries flag vocabulary unreached by any argv-typed body: + // candidates, not counted. commit_args is reached BY NAME + // (fixture_transient_unit_argv's tail): counted, and because the reach is a + // reference rather than a body literal, its flag words stay empty. + (wait_flags.decl_kind == DataDecl) && (wait_flags.counted == false) + && (length(xs: wait_flags.candidates) == 3) && (length(xs: wait_flags.flag_words) == 0) + && (wait_flags.list_string_data == true) + && (commit_args.decl_kind == DataDecl) && (commit_args.counted == true) + && (length(xs: commit_args.candidates) == 0) && (length(xs: commit_args.flag_words) == 0) + && (commit_args.list_string_data == true) + // layout_rows has no flag vocabulary and no reach: not a census member at all. + && (layout_rows.counted == false) && (length(xs: layout_rows.candidates) == 0) + && (layout_rows.list_string_data == true) + } + } } - && site_absent(sites: sites, module_file: fixture_path, decl: "fixture_layout_rows") - ArgvWordsModuleRefused { path: _, reason: _ } => false } } -// The argv-parameter signal and the List return signal are read at fn grain. test fn census_fixture_route_reads_fn_signals() -> Bool { - match argv_word_construction_module_sites(path: fixture_path) { - ArgvWordsModuleEstablished { path: _, sites: sites } => - match argv_word_construction_site_named(sites: sites, module_file: fixture_path, decl: "fixture_run_with_properties") { - Present { value: s } => (s.decl_kind == FnDecl) && (s.returns_argv_words == true) && (s.list_string_params == 1) - Absent => false - } - && match argv_word_construction_site_named(sites: sites, module_file: fixture_path, decl: "fixture_transient_unit_argv") { - Present { value: s } => (s.decl_kind == FnDecl) && (s.returns_argv_words == true) && member_named(xs: s.flag_words, x: "--unit=") + match fixture_facts() { + Absent => false + Present { value: facts } => + match fact_named(xs: facts, want: "fixture_transient_unit_argv") { Absent => false + Present { value: transient } => + match fact_named(xs: facts, want: "fixture_run_with_properties") { + Absent => false + Present { value: with_props } => + match fact_named(xs: facts, want: "fixture_label_rows") { + Absent => false + Present { value: label_rows } => + match fact_named(xs: facts, want: "fixture_waits_quietly") { + Absent => false + Present { value: waits } => + // The gluing builders: counted, argv-returning, their flag literals named. + (transient.decl_kind == FnDecl) && (transient.counted == true) + && (transient.returns_argv_words == true) + && (transient.list_string_params == 0) + && member_named(xs: transient.flag_words, x: "--unit=") + && member_named(xs: transient.flag_words, x: "--collect") + && (with_props.counted == true) && (with_props.returns_argv_words == true) + && (with_props.list_string_params == 1) + && member_named(xs: with_props.flag_words, x: "--unit=") + // A List parameter IS the argv-interface class per the census + // contract, whether or not the body glues flags: counted, no flag words. + && (label_rows.counted == true) && (label_rows.list_string_params == 1) + && (length(xs: label_rows.flag_words) == 0) + // A flag literal outside an argv position: candidate class, not counted, + // still a member because candidates are reported. + && (waits.counted == false) && member_named(xs: waits.candidates, x: "--wait") + } + } + } } - ArgvWordsModuleRefused { path: _, reason: _ } => false } } -// ------------------------------------------------------------------ specimen route +// ---------------------------------------------------------------- the boundary -// The trial's specimen, pinned at its PRE-CUTOVER state: the string-built builders are census -// members today. When the cutover pays them (typed options, one wire projection, ArgvCommand -// builders, string forms deleted), this test flips WITH the cutover: the members must be gone and -// the roster row for each must be deleted in the same change. -data specimen_path: String = "dag/extdeps/systemd/systemd_run.dag" +test fn census_boundary_refuses_the_fixture_with_the_modeled_roots_reason() -> Bool { + match argv_word_construction_module_sites(path: fixture_path) { + ArgvWordsModuleEstablished { path: _, sites: _ } => false + ArgvWordsModuleRefused { path: _, reason: r } => + string_contains(s: r, pattern: "outside the modeled roots") + } +} -test fn census_specimen_route_pins_the_pre_cutover_string_forms() -> Bool { - match argv_word_construction_module_sites(path: specimen_path) { - ArgvWordsModuleEstablished { path: _, sites: sites } => - site_present(sites: sites, module_file: specimen_path, decl: "systemd_run_property_argv") - && site_present(sites: sites, module_file: specimen_path, decl: "systemd_run_user_transient_arguments") - && site_present(sites: sites, module_file: specimen_path, decl: "systemd_run_transient_unit_argv") - && site_present(sites: sites, module_file: specimen_path, decl: "systemd_run_scope_as_account_argv") - && site_present(sites: sites, module_file: specimen_path, decl: "systemd_run_transient_wait_unit_argv") - ArgvWordsModuleRefused { path: _, reason: _ } => false +test fn census_refuses_modules_outside_the_modeled_roots() -> Bool { + match argv_word_construction_module_sites(path: "docs/plans/some-note.md") { + ArgvWordsModuleEstablished { path: _, sites: _ } => false + ArgvWordsModuleRefused { path: _, reason: _ } => true } } -// The specimen is an extdeps module: its members classify as extdeps interface builders, the -// class that pays by returning ArgvCommand from the tool's own extdeps surface. -test fn census_specimen_route_classifies_extdeps_builders() -> Bool { - match argv_word_construction_module_sites(path: specimen_path) { - ArgvWordsModuleEstablished { path: _, sites: sites } => - match argv_word_construction_site_named(sites: sites, module_file: specimen_path, decl: "systemd_run_property_argv") { - Present { value: s } => match s.site_class { - ExtdepsInterfaceBuilder => true - WorkflowCallSite => false - } - Absent => false - } +// ---------------------------------------------------------------- the established route + +test fn census_established_route_on_a_modeled_module_without_debt() -> Bool { + match argv_word_construction_module_sites(path: established_route_path) { ArgvWordsModuleRefused { path: _, reason: _ } => false + ArgvWordsModuleEstablished { path: p, sites: sites } => + (p == established_route_path) && (length(xs: sites) == 0) } } -// A module outside the two modeled roots is a REFUSAL, never a silent zero. -test fn census_refuses_modules_outside_the_modeled_roots() -> Bool { - match argv_word_construction_module_sites(path: "docs/plans/some-note.md") { - ArgvWordsModuleEstablished { path: _, sites: _ } => false - ArgvWordsModuleRefused { path: _, reason: _ } => true +// ---------------------------------------------------------------- class routing (pure) + +fn class_is(c: ArgvWordsSiteClass, want: ArgvWordsSiteClass) -> Bool { + match c { + ExtdepsInterfaceBuilder => match want { + ExtdepsInterfaceBuilder => true + WorkflowCallSite => false + } + WorkflowCallSite => match want { + ExtdepsInterfaceBuilder => false + WorkflowCallSite => true + } } } -// ------------------------------------------------------------------ the roster gates +test fn census_site_class_routes_the_modeled_roots() -> Bool { + match argv_word_construction_site_class_of(module_file: specimen_path) { + Absent => false + Present { value: c } => + match argv_word_construction_site_class_of(module_file: specimen_workflow_path) { + Absent => false + Present { value: w } => + class_is(c: c, want: ExtdepsInterfaceBuilder) && class_is(c: w, want: WorkflowCallSite) + } + } +} -data gate_file: String = "dag/gunbc/instruments/argv_word_construction_census.dag" +// ---------------------------------------------------------------- the gates (constructed rows) fn live_member(path: String, decl: String) -> ArgvWordsSite { ArgvWordsSite { @@ -222,12 +286,13 @@ test fn gate_within_contract_holds() -> Bool { exit_is_success(exit: argv_word_construction_gates(live: live, roster: roster, baseline_population: 1)) } -// The committed census holds its own contract on the bounded recount of the fixture and the -// specimen: per-module reads establish, and the committed roster covers every live member of the -// two modules. This is the test that goes red the moment a specimen site is paid without deleting -// its roster row, or a new string-built argv lands in either module without a typed form. +// The committed instrument's contract over exactly the roster's own modules: every committed row +// resolves against a live counted site, no live counted member is uncovered, the population has +// not grown past the baseline, and every module walk either established or refused with a named +// reason. With the roster still at zero rows this walks nothing and holds trivially; once the +// wet-lane recount fills the roster, this claim walks exactly those modules in CI. test fn committed_census_holds_its_own_contract() -> Bool { - exit_is_success(exit: argv_word_construction_census_check_paths(paths: [specimen_path, fixture_path])) + exit_is_success(exit: argv_word_construction_census_check_paths(paths: argv_word_construction_census_roster_modules())) } // The instrument's own live filter subtracts only what the committed exclusions name. @@ -236,72 +301,8 @@ test fn instrument_live_filter_keeps_unexcluded_members() -> Bool { fold_list(xs: argv_word_construction_census_live(sites: sites), empty: true, cons: fn(acc, s) { acc && (s.decl == "still_debt") }) } -// ------------------------------------------------------------------ helpers +// ---------------------------------------------------------------- helpers fn member_named(xs: List, x: String) -> Bool { - fold_list(xs: xs, empty: false, cons: fn(acc, s) { acc || (s == x) }) -} - -fn site_present(sites: List, module_file: String, decl: String) -> Bool { - match argv_word_construction_site_named(sites: sites, module_file: module_file, decl: decl) { - Present { value: _ } => true - Absent => false - } -} - -fn site_absent(sites: List, module_file: String, decl: String) -> Bool { - match argv_word_construction_site_named(sites: sites, module_file: module_file, decl: decl) { - Present { value: _ } => false - Absent => true - } -} - -// --- diag battery 2 (throwaway, delete before commit): turn the refusal reason into verdicts --- -test fn diag2_reason_canonicalize() -> Bool { - match argv_word_construction_module_sites(path: fixture_path) { - ArgvWordsModuleEstablished { sites: _ } => false - ArgvWordsModuleRefused { path: _, reason: r } => string_contains(s: r, pattern: "canonicalize") - } -} - -test fn diag2_reason_outside_root() -> Bool { - match argv_word_construction_module_sites(path: fixture_path) { - ArgvWordsModuleEstablished { sites: _ } => false - ArgvWordsModuleRefused { path: _, reason: r } => string_contains(s: r, pattern: "outside the checkout root") - } -} - -test fn diag2_reason_commit_deterministic() -> Bool { - match argv_word_construction_module_sites(path: fixture_path) { - ArgvWordsModuleEstablished { sites: _ } => false - ArgvWordsModuleRefused { path: _, reason: r } => string_contains(s: r, pattern: "commit-deterministic") - } -} - -test fn diag2_reason_governed() -> Bool { - match argv_word_construction_module_sites(path: fixture_path) { - ArgvWordsModuleEstablished { sites: _ } => false - ArgvWordsModuleRefused { path: _, reason: r } => string_contains(s: r, pattern: "governed") - } -} - -test fn diag2_reason_mock() -> Bool { - match argv_word_construction_module_sites(path: fixture_path) { - ArgvWordsModuleEstablished { sites: _ } => false - ArgvWordsModuleRefused { path: _, reason: r } => string_contains(s: r, pattern: "mock") - } -} - -test fn diag2_reason_read_refused_prefix() -> Bool { - match argv_word_construction_module_sites(path: fixture_path) { - ArgvWordsModuleEstablished { sites: _ } => false - ArgvWordsModuleRefused { path: _, reason: r } => string_contains(s: r, pattern: "read refused:") - } -} - -test fn diag2_reason_nonempty() -> Bool { - match argv_word_construction_module_sites(path: fixture_path) { - ArgvWordsModuleEstablished { sites: _ } => false - ArgvWordsModuleRefused { path: _, reason: r } => !string_is_empty(s: r) - } + fold_list(xs: xs, empty: false, cons: fn(acc, y) { acc || (y == x) }) } diff --git a/src/v2/test/fixture_roots/argv_word_construction/host_words.dag b/src/v2/test/fixture_roots/argv_word_construction/host_words.dag index c00bd8e85aa..50e29cf71a1 100644 --- a/src/v2/test/fixture_roots/argv_word_construction/host_words.dag +++ b/src/v2/test/fixture_roots/argv_word_construction/host_words.dag @@ -10,7 +10,8 @@ import v2.std.logic { Bool } import v2.std.collection { List } import v2.std.algebra { length } -// A List data decl that IS argv vocabulary: flag words among its literal elements. +// Candidate class: flag vocabulary among its literal elements, but the name is not referenced by +// any argv-typed fn body, so it is reported as candidates, not counted. data fixture_wait_flags: List = ["--wait", "--quiet", "--collect"] // A List data decl that is NOT argv vocabulary: no flag words, no argv name (the ceiling @@ -22,7 +23,7 @@ data fixture_commit_args: List = ["commit", "--allow-empty"] // (a)+(b): a fn builder that glues a flag literal to a value and returns word-lists. fn fixture_transient_unit_argv(unit: String) -> List { - Cons { head: "systemd-run", tail: Cons { head: concat("--unit=", unit), tail: Cons { head: "--collect", tail: Empty } } } + Cons { head: "systemd-run", tail: Cons { head: concat("--unit=", unit), tail: Cons { head: "--collect", tail: fixture_commit_args } } } } // (b): a fn accepting a List argv parameter in an argv position, returning word-lists. @@ -30,13 +31,14 @@ fn fixture_run_with_properties(unit: String, property_argv: List) -> Lis Cons { head: concat("--unit=", unit), tail: property_argv } } -// NOT a member: a List parameter outside the argv positions, no flag words, no -// List return. +// Member of the List-parameter interface class per the census contract: the census +// counts every List param/return at an identity, flagged or not. No flag words here. fn fixture_label_rows(labels: List) -> Bool { !(length(xs: labels) == 0) } -// (a) only: a fn that spells a flag literal and returns a scalar. +// Candidate class: a fn that spells a flag literal and returns a scalar. Not counted (the flag +// literal sits outside an argv position), still a member because candidates are reported. fn fixture_waits_quietly() -> Bool { starts_with(s: concat("--", "wait"), prefix: "--wait") } From ec44083c2d584a3c85f2cbf277b286287164299f Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 18:41:59 +0000 Subject: [PATCH 40/65] side-chat blockers at 90f5480d88: (1) the manual wet receipt's start_payload passed argv_words(command), doubling the program head the service transport owns -- now systemd_run_command_argument_words like the retained helper; (2) the typed_builder_wet WitnessExclusionRow named a deleted file and that row type is a discovery-path policy, so it excluded nothing -- deleted from ci_layer_roots, the standing moved to a typed module-scope DissolutionCondition beside the systemd_run builders, worded as no REQUIRED automated claim for the manager-side properties (the manual receipt does execute systemd-run); (3) Amendment A withdrawn: the base order for the collecting transient is --unit, --collect, , -- (the retired builder and its transport both emitted it; main's nbd control only pinned the isolated property projection) -- the projection restores [RunUnit, Collect] + property_options + [EndOfOptions] and both rewritten controls assert the base order again --- dag/extdeps/systemd/systemd_run.dag | 2 +- dag/gunbc/ci/ci_layer_roots.dag | 5 ----- .../fabric/systemd_run_transient_wait_witness_test.dag | 8 +++++--- dag/test/claim/nbd_proxy_serve_transport_witness_test.dag | 2 +- .../manual/runner_microvm_lifecycle_wet_receipt_test.dag | 5 ++--- 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/dag/extdeps/systemd/systemd_run.dag b/dag/extdeps/systemd/systemd_run.dag index cace78b5a97..2c55f6e3077 100644 --- a/dag/extdeps/systemd/systemd_run.dag +++ b/dag/extdeps/systemd/systemd_run.dag @@ -294,7 +294,7 @@ fn systemd_run_system_scope_command(properties: List, comman // observable (the service op is the same RunTransient either way -- retained vs collected is a // decision the typed option model renders into the words, not a service-side contract). fn systemd_run_transient_unit_command(unit: NonEmptyStr, properties: List, command_argv: List) -> SystemdRunCommandReading { - systemd_run_command_of(options: list_append(left: [RunUnit { unit: unit }], right: list_append(left: systemd_run_property_options_long(properties: properties), right: [Collect, EndOfOptions])), command_argv: command_argv) + systemd_run_command_of(options: list_append(left: [RunUnit { unit: unit }, Collect], right: list_append(left: systemd_run_property_options_long(properties: properties), right: [EndOfOptions])), command_argv: command_argv) } // The retained shape: no --collect, so the transient unit survives its process and its terminal diff --git a/dag/gunbc/ci/ci_layer_roots.dag b/dag/gunbc/ci/ci_layer_roots.dag index 81dbae66d4a..adf25e06a1b 100644 --- a/dag/gunbc/ci/ci_layer_roots.dag +++ b/dag/gunbc/ci/ci_layer_roots.dag @@ -667,11 +667,6 @@ data witness_exclusion_frontier: List = [ classification: BinWitnessWet, reason: excl_bin_wet_reason, dissolution: excl_bin_wet_dissolve}, - WitnessExclusionRow { - pattern: "typed_builder_wet_test.dag", - classification: LocalRepoWetLane, - reason: "DECLARED GAP (pre-existing, declared at the typed-argv cutover): the manager-side behaviour of the systemd-run builders -- a retained unit that failed is still known to the manager, a collected one is gone, a waited invocation reports the child's exact exit -- has NO executing claim anywhere in CI or in this tree, so no claim file is shipped for it (a claim no lane can execute is dangling). No CI lane can run systemd units (the hermetic runner is unprivileged with no user manager; the build runner has no systemd at all), and the retired string builders never had CI execution either, so declaring this gap removes nothing. The population is the typed builders' manager-side contract; what CI does exercise is the projected word shape (per-builder byte identity, typed refusal causes, cardinality). Trigger: a lane host with a usable user manager exists and is scheduled in a required workflow -- at that point the claims are authored and enrolled on that lane.", - dissolution: unbound_dissolution(description: "when a lane host with a usable user manager (for the runner account, or via the controlled sudo --user target) is scheduled in a required workflow, author the manager-side claims, enroll them on that lane's schedule, and delete this row")}, WitnessExclusionRow { pattern: "self_host_logic_behavioral_witness_test.dag", classification: LocalRepoWetLane, diff --git a/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag b/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag index 7420ac00a6e..27b4f382fe5 100644 --- a/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag +++ b/dag/test/claim/fabric/systemd_run_transient_wait_witness_test.dag @@ -81,8 +81,10 @@ test fn a_property_value_the_wire_cannot_carry_is_refused() -> Bool { // retired glued builders emitted, in the order they emitted them. A control per builder is the // point: the wait fixture alone proved one shape and the other builders drifted silently. -// The collecting transient: --unit, --collect, the properties, then -- (the old order). -test fn the_collecting_transient_builder_emits_unit_properties_collect() -> Bool { +// The collecting transient: --unit, --collect, the properties, then -- (the base order the +// retired glued builder and its transport both emitted; the side-chat review corrected this +// control -- main's property-projection witness never pinned the full order). +test fn the_collecting_transient_builder_emits_unit_collect_properties() -> Bool { match systemd_run_transient_unit_command( unit: "fci1-order.service", properties: [SystemdRunProperty { property: MemoryMax, value: "17179869184" as NonEmptyStr }], @@ -93,7 +95,7 @@ test fn the_collecting_transient_builder_emits_unit_properties_collect() -> Bool argv_exact_token_equal( left: argv_words(command: command), right: [ - "systemd-run", "--unit=fci1-order.service", "--property=MemoryMax=17179869184", "--collect", "--", "/bin/true", + "systemd-run", "--unit=fci1-order.service", "--collect", "--property=MemoryMax=17179869184", "--", "/bin/true", ], ) } diff --git a/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag b/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag index 7d77c91eef0..a53ea8df82a 100644 --- a/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag +++ b/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag @@ -284,7 +284,7 @@ test fn witness_systemd_run_property_argv_renders_the_wire_name() -> Bool { SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => join(argv_words(command: command), " ") - == "systemd-run --unit=nbd-proxy-witness.service --property=MemoryMax=17179869184 --collect -- /bin/true" + == "systemd-run --unit=nbd-proxy-witness.service --collect --property=MemoryMax=17179869184 -- /bin/true" } } diff --git a/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag b/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag index 65954b4ba2a..ad96255995a 100644 --- a/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag +++ b/dag/test/manual/runner_microvm_lifecycle_wet_receipt_test.dag @@ -6,7 +6,7 @@ import std.resources { Network, Filesystem } import std.measure { Second, second } import extdeps.tools.sleep { sleep_delay_seconds_second_carrier_projection } import extdeps.systemd { SliceProperty, SystemdUnitProperty, ActiveState, systemd_unit_property_wire, SystemdSliceCgroupPath, SliceCgroupPath, SliceNameNotASlice, systemd_slice_cgroup_path } -import extdeps.exec.command { argv_words } + import extdeps.systemd.systemd_run { SystemdRunProperty, SystemdRunCommandReady, SystemdRunCommandRefused, systemd_run_transient_unit_command, systemd_run_transient_retained_unit_command, systemd_run_command_argument_words, } @@ -72,7 +72,7 @@ fn start_payload(unit: NonEmptyStr, argv: List) -> Bool match systemd_run_transient_unit_command(unit: unit, properties: [wet_slice_property()], command_argv: argv) { SystemdRunCommandRefused { cause: _ } => false SystemdRunCommandReady { command: command } => - systemd.SystemdRun.RunTransient(command_argv: argv_words(command: command)).success + systemd.SystemdRun.RunTransient(command_argv: systemd_run_command_argument_words(command: command)).success } } @@ -275,7 +275,6 @@ test fn a_missing_cgroup_events_is_unobservable_and_never_empty() -> Bool readback_is_read_failure(r: observe_cgroup_subtree(slice: "-.slice" as NonEmptyStr)) } - // THE SHOW-PROPERTY PAIR, AND IT IS A DISCRIMINATOR RATHER THAN TWO READINGS. // // systemd.Systemctl.ShowProperty takes a SystemdUnitProperty and interpolates it into From 6bc0f8286adfdab7bd0ee8066f587a2ab1781450 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 19:31:23 +0000 Subject: [PATCH 41/65] census_test parse fixes: the two in-body comment blocks moved out (module-item grain only) and the class router imported by its real name (site_class_of) --- .../test/claim/argv_word_construction/census_test.dag | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/v2/test/claim/argv_word_construction/census_test.dag b/src/v2/test/claim/argv_word_construction/census_test.dag index fe0457a245f..86bae2a5d92 100644 --- a/src/v2/test/claim/argv_word_construction/census_test.dag +++ b/src/v2/test/claim/argv_word_construction/census_test.dag @@ -33,7 +33,7 @@ import v2.lens.argv_word_construction { argv_word_construction_census_covers, argv_word_construction_roster_shrinks, argv_word_construction_site_named, - argv_word_construction_site_class_of, + site_class_of, facts_of, is_census_member, raw_tree_of_text @@ -161,12 +161,8 @@ test fn census_fixture_route_reads_fn_signals() -> Bool { && (with_props.counted == true) && (with_props.returns_argv_words == true) && (with_props.list_string_params == 1) && member_named(xs: with_props.flag_words, x: "--unit=") - // A List parameter IS the argv-interface class per the census - // contract, whether or not the body glues flags: counted, no flag words. && (label_rows.counted == true) && (label_rows.list_string_params == 1) && (length(xs: label_rows.flag_words) == 0) - // A flag literal outside an argv position: candidate class, not counted, - // still a member because candidates are reported. && (waits.counted == false) && member_named(xs: waits.candidates, x: "--wait") } } @@ -218,10 +214,10 @@ fn class_is(c: ArgvWordsSiteClass, want: ArgvWordsSiteClass) -> Bool { } test fn census_site_class_routes_the_modeled_roots() -> Bool { - match argv_word_construction_site_class_of(module_file: specimen_path) { + match site_class_of(module_file: specimen_path) { Absent => false Present { value: c } => - match argv_word_construction_site_class_of(module_file: specimen_workflow_path) { + match site_class_of(module_file: specimen_workflow_path) { Absent => false Present { value: w } => class_is(c: c, want: ExtdepsInterfaceBuilder) && class_is(c: w, want: WorkflowCallSite) From 153349728139e5eaa246f8a8ee77df4b4b221a68 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 19:32:42 +0000 Subject: [PATCH 42/65] per-root recount emitters: a wet whole-corpus walk is OOM-killed (exit 137 -- the per-process journal of read+parse dispatches grows with the corpus), so the walk runs one root per process over the roots boundary, the population's own partition; the all-roots emitter stays for a lane that can hold it --- .../argv_word_construction_census.dag | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/dag/gunbc/instruments/argv_word_construction_census.dag b/dag/gunbc/instruments/argv_word_construction_census.dag index af17266b449..0734ce4b16d 100644 --- a/dag/gunbc/instruments/argv_word_construction_census.dag +++ b/dag/gunbc/instruments/argv_word_construction_census.dag @@ -83,9 +83,12 @@ fn argv_word_construction_census_recount() -> ProcessExit { } // The discovered modeled roots' module files, in declaration-index order. Shared by the gated -// recount above and the roster emitter below so both see the same population. -fn argv_word_construction_census_root_paths() -> List { - let decls = module_declaration_facts_live(pool_roots: argv_word_construction_census_roots) +// recount above and the roster emitters below so all see the same population. Parameterized by +// roots: the corpus walk is run ONE ROOT PER PROCESS (a wet lane kills a whole-corpus walk with +// OOM -- the per-process journal of read+parse dispatches is what grows, so the split is by the +// roots boundary, the population's own partition). +fn argv_word_construction_census_root_paths_for_roots(roots: List) -> List { + let decls = module_declaration_facts_live(pool_roots: roots) let paths_reversed = fold_list(xs: decls, empty: Empty, cons: fn(acc, d) { let p = declared_module_path(d: d) if p == "" { acc } else { Cons { head: p, tail: acc } } @@ -93,6 +96,10 @@ fn argv_word_construction_census_root_paths() -> List { list_reverse(xs: paths_reversed) } +fn argv_word_construction_census_root_paths() -> List { + argv_word_construction_census_root_paths_for_roots(roots: argv_word_construction_census_roots) +} + // The roster emitter for a wet-lane run (claim_batch --wet): the same discovered population, each // module walked by the lens, rows written as TSV to the scratch path. The committed roster data // decl above is filled FROM this output, never by hand from a text search. Hermetic lanes refuse @@ -101,6 +108,14 @@ fn argv_word_construction_census_recount_emit() -> ProcessExit { argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths(), out: "target/argv_word_construction_census_recount.tsv") } +fn argv_word_construction_census_recount_emit_extdeps() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/extdeps"]), out: "target/argv_word_construction_census_recount_extdeps.tsv") +} + +fn argv_word_construction_census_recount_emit_gunbc() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/gunbc"]), out: "target/argv_word_construction_census_recount_gunbc.tsv") +} + fn declared_module_path(d: ModuleDeclarationFact) -> String { d.path } From 01e795690b0fdcc15040f45fe87d226cd73b9ae9 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 20:57:54 +0000 Subject: [PATCH 43/65] census_test: move the remaining in-body comments to module grain, import the filesystem outcome type with the variants, and turn the fact-name if into match arms (constructor-valued if branches do not parse) --- .../argv_word_construction/census_test.dag | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/v2/test/claim/argv_word_construction/census_test.dag b/src/v2/test/claim/argv_word_construction/census_test.dag index 86bae2a5d92..0a8c381dfe8 100644 --- a/src/v2/test/claim/argv_word_construction/census_test.dag +++ b/src/v2/test/claim/argv_word_construction/census_test.dag @@ -44,7 +44,7 @@ import gunbc.instruments.argv_word_construction_census { argv_word_construction_census_roster, argv_word_construction_census_roster_modules } -import extdeps.filesystem.filesystem_io { Filesystem, FilesystemReadSucceeded, FilesystemReadRefused } +import extdeps.filesystem.filesystem_io { Filesystem, filesystem_read_outcome, FilesystemReadSucceeded, FilesystemReadRefused } import v2.std.algebra { fold_list, length } import v2.std.logic { Bool } import v2.std.collection { List } @@ -86,7 +86,11 @@ fn fact_named(xs: List, want: String) -> Optional fold_list(xs: xs, empty: Absent, cons: fn(acc, f) { match acc { Present { value: _ } => acc - Absent => if f.decl == want { Present { value: f } } else { Absent } + Absent => + match f.decl == want { + true => Present { value: f } + false => Absent + } } }) } @@ -104,6 +108,11 @@ test fn census_fixture_route_counts_its_members() -> Bool { } } +// The data-decl classes the lens reports on this fixture: wait_flags carries flag vocabulary +// unreached by any argv-typed body (candidates, not counted); commit_args is reached BY NAME +// (fixture_transient_unit_argv's tail), so it is counted while its flag words stay empty (the +// reach is a reference, not a body literal); layout_rows has no vocabulary and no reach, so it is +// not a census member at all. test fn census_fixture_route_classifies_data_decls_at_their_own_kind() -> Bool { match fixture_facts() { Absent => false @@ -117,17 +126,12 @@ test fn census_fixture_route_classifies_data_decls_at_their_own_kind() -> Bool { match fact_named(xs: facts, want: "fixture_layout_rows") { Absent => false Present { value: layout_rows } => - // wait_flags carries flag vocabulary unreached by any argv-typed body: - // candidates, not counted. commit_args is reached BY NAME - // (fixture_transient_unit_argv's tail): counted, and because the reach is a - // reference rather than a body literal, its flag words stay empty. (wait_flags.decl_kind == DataDecl) && (wait_flags.counted == false) && (length(xs: wait_flags.candidates) == 3) && (length(xs: wait_flags.flag_words) == 0) && (wait_flags.list_string_data == true) && (commit_args.decl_kind == DataDecl) && (commit_args.counted == true) && (length(xs: commit_args.candidates) == 0) && (length(xs: commit_args.flag_words) == 0) && (commit_args.list_string_data == true) - // layout_rows has no flag vocabulary and no reach: not a census member at all. && (layout_rows.counted == false) && (length(xs: layout_rows.candidates) == 0) && (layout_rows.list_string_data == true) } @@ -136,6 +140,10 @@ test fn census_fixture_route_classifies_data_decls_at_their_own_kind() -> Bool { } } +// The fn signals: the gluing builders are counted and argv-returning with their flag literals +// named; a List parameter IS the argv-interface class per the census contract whether or +// not the body glues flags (counted, no flag words); a flag literal outside an argv position is +// candidate class (not counted) but still a member because candidates are reported. test fn census_fixture_route_reads_fn_signals() -> Bool { match fixture_facts() { Absent => false @@ -152,7 +160,6 @@ test fn census_fixture_route_reads_fn_signals() -> Bool { match fact_named(xs: facts, want: "fixture_waits_quietly") { Absent => false Present { value: waits } => - // The gluing builders: counted, argv-returning, their flag literals named. (transient.decl_kind == FnDecl) && (transient.counted == true) && (transient.returns_argv_words == true) && (transient.list_string_params == 0) From 30fe104239ff734621cf34fafbe295f29e4b967b Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 21:17:39 +0000 Subject: [PATCH 44/65] fixture_facts reads the effect outcome by field access (read.success / read.content), the shape the tsic pin claim uses in a test module -- variant matching on the effect outcome does not resolve there --- .../test/claim/argv_word_construction/census_test.dag | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/v2/test/claim/argv_word_construction/census_test.dag b/src/v2/test/claim/argv_word_construction/census_test.dag index 0a8c381dfe8..ba91d984cec 100644 --- a/src/v2/test/claim/argv_word_construction/census_test.dag +++ b/src/v2/test/claim/argv_word_construction/census_test.dag @@ -44,7 +44,7 @@ import gunbc.instruments.argv_word_construction_census { argv_word_construction_census_roster, argv_word_construction_census_roster_modules } -import extdeps.filesystem.filesystem_io { Filesystem, filesystem_read_outcome, FilesystemReadSucceeded, FilesystemReadRefused } +import extdeps.filesystem.filesystem_io { Filesystem } import v2.std.algebra { fold_list, length } import v2.std.logic { Bool } import v2.std.collection { List } @@ -72,10 +72,10 @@ data gate_file: String = "dag/gunbc/instruments/argv_word_construction_census.da fn fixture_facts() -> Optional> { let read = Filesystem.Read(path: fixture_path) - match read { - FilesystemReadRefused { error: _ } => Absent - FilesystemReadSucceeded { content: text } => - match raw_tree_of_text(text: text) { + match read.success { + false => Absent + true => + match raw_tree_of_text(text: read.content) { RawTreeNotEstablished { cause: _ } => Absent RawTreeEstablished { root: root } => Present { value: facts_of(root: root) } } From eb7b1dd416c46f3993bdc461a3f73084be79cce3 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 5 Oct 2026 22:09:09 +0000 Subject: [PATCH 45/65] floor NonFoldResidueRosterDiverged remedy: the four work_provider_local wildcard sites the new diff-scoped residue scan requires rostered (they predate the typed-argv cutover; the cutover's diff touched the module, which is what arms the scan) -- four FrontierRows with reason + dissolution, the population the ratchet may only shrink --- dag/gunbc/non_fold_residue.dag | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/dag/gunbc/non_fold_residue.dag b/dag/gunbc/non_fold_residue.dag index bdddefe670a..fdd1ed9d9b7 100644 --- a/dag/gunbc/non_fold_residue.dag +++ b/dag/gunbc/non_fold_residue.dag @@ -87,6 +87,10 @@ data nfr_reason_verification_producer_absent: String = "NOT terminal-state absor data nfr_dissolve_verification_producer_absent: DissolutionCondition = unbound_dissolution(description: "the Verify receipt producer lands: the WorkItemExecutionContract's ModeledValidationCommand runs in the attempt worktree against one immutable candidate revision and persists a receipt, at which point this function folds over that receipt instead of over provider state, its arms carry distinct WorkflowSegmentStates, and the row deletes. It does NOT dissolve on the derived-terminality projection that clears its four siblings — a terminality partition over ProviderExecutionState leaves every arm answering Pending.") +data nfr_reason_work_provider_local_typed_argv_cutover: String = "unit-termination and unit-lifecycle residue in work_provider_local.dag (match arms the residue checker classifies as wildcard over the closed UnitPopulation / SystemdRunWaitSummary coproducts) predates the typed-argv cutover; that cutover's diff touched the module, so the diff-scoped residue scan now requires these sites rostered — declared so the ratchet re-arms" + +data nfr_dissolve_work_provider_local_typed_argv_cutover: DissolutionCondition = unbound_dissolution(description: "the four sites' matches are spelled exhaustively over their closed coproducts (every variant named, no catch-all) in a change that edits work_provider_local.dag — the rows delete with that change") + data nfr_reason_dispatch_selection_routing: String = "provider-selection routing residue (match on ProviderSelectionRequest, ProviderOffer, or ResolvedProviderSelection with off-shape wildcard refusal or false arms) landed with Slice A dispatch_selection.dag; declared at landing so the ratchet re-arms" data nfr_dissolve_dispatch_selection_routing: DissolutionCondition = unbound_dissolution(description: "total matches over the closed selection request and offer coproducts land and the wildcard arms migrate to named variants — rows delete with that fold") @@ -817,6 +821,26 @@ data non_fold_residue_frontier: List = [ reason: nfr_reason_eval_projection_receiver_not_aggregate, dissolution: nfr_dissolve_eval_projection_receiver_not_aggregate, }, + FrontierRow { + subject: PathSubject { path: "dag/gunbc/compute/work_provider_local.dag::compute_summary_is_oom_kill" }, + reason: nfr_reason_work_provider_local_typed_argv_cutover, + dissolution: nfr_dissolve_work_provider_local_typed_argv_cutover, + }, + FrontierRow { + subject: PathSubject { path: "dag/gunbc/compute/work_provider_local.dag::compute_unit_ending" }, + reason: nfr_reason_work_provider_local_typed_argv_cutover, + dissolution: nfr_dissolve_work_provider_local_typed_argv_cutover, + }, + FrontierRow { + subject: PathSubject { path: "dag/gunbc/compute/work_provider_local.dag::unit_lifecycle_decide" }, + reason: nfr_reason_work_provider_local_typed_argv_cutover, + dissolution: nfr_dissolve_work_provider_local_typed_argv_cutover, + }, + FrontierRow { + subject: PathSubject { path: "dag/gunbc/compute/work_provider_local.dag::unit_termination_decide" }, + reason: nfr_reason_work_provider_local_typed_argv_cutover, + dissolution: nfr_dissolve_work_provider_local_typed_argv_cutover, + }, ] fn non_fold_residue_frontier_units() -> List { From c70ff56e889c6617b65fc5ae7297d29836df5bf7 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 6 Oct 2026 10:03:14 +0000 Subject: [PATCH 46/65] =?UTF-8?q?census=20instrument:=20a=20fixture-root?= =?UTF-8?q?=20recount=20emitter=20=E2=80=94=20the=20fixture=20is=20outside?= =?UTF-8?q?=20the=20modeled=20roots=20by=20design,=20so=20the=20per-root?= =?UTF-8?q?=20emitters=20never=20see=20it;=20this=20runs=20the=20same=20le?= =?UTF-8?q?ns=20over=20the=20fixture=20directory=20alone=20so=20the=20fixt?= =?UTF-8?q?ure=20claims'=20expectations=20are=20checked=20against=20the=20?= =?UTF-8?q?lens's=20own=20rendering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dag/gunbc/instruments/argv_word_construction_census.dag | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dag/gunbc/instruments/argv_word_construction_census.dag b/dag/gunbc/instruments/argv_word_construction_census.dag index 0734ce4b16d..9a2816811b6 100644 --- a/dag/gunbc/instruments/argv_word_construction_census.dag +++ b/dag/gunbc/instruments/argv_word_construction_census.dag @@ -116,6 +116,15 @@ fn argv_word_construction_census_recount_emit_gunbc() -> ProcessExit { argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/gunbc"]), out: "target/argv_word_construction_census_recount_gunbc.tsv") } +// The fixture root is OUTSIDE the modeled roots by design (the boundary-refusal claim pins that), +// so the walk above never sees it. This emitter runs the same lens over the fixture's directory +// alone, so the fixture claims' pinned expectations are checked against the lens's own rendering +// of the fixture -- one recount path for every population the census route talks about, not a +// hand-maintained parallel list. +fn argv_word_construction_census_recount_emit_fixture() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["src/v2/test/fixture_roots/argv_word_construction"]), out: "target/argv_word_construction_census_recount_fixture.tsv") +} + fn declared_module_path(d: ModuleDeclarationFact) -> String { d.path } From 84bae184f83b46eb99bb713f29d3ca1a4d37e106 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 6 Oct 2026 10:21:50 +0000 Subject: [PATCH 47/65] =?UTF-8?q?census:=20the=20fixture's=20own=20lens=20?= =?UTF-8?q?rendering=20is=20observable=20=E2=80=94=20fixture=5Ffacts=5Fdum?= =?UTF-8?q?p=20writes=20one=20TSV=20row=20per=20fact=20with=20every=20pinn?= =?UTF-8?q?ed=20field,=20so=20the=20fixture=20claims'=20expectations=20are?= =?UTF-8?q?=20aligned=20to=20the=20lens's=20output=20rather=20than=20a=20h?= =?UTF-8?q?and-maintained=20parallel=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../argv_word_construction/census_test.dag | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/v2/test/claim/argv_word_construction/census_test.dag b/src/v2/test/claim/argv_word_construction/census_test.dag index ba91d984cec..3b525de7304 100644 --- a/src/v2/test/claim/argv_word_construction/census_test.dag +++ b/src/v2/test/claim/argv_word_construction/census_test.dag @@ -82,6 +82,48 @@ fn fixture_facts() -> Optional> { } } +// THE FIXTURE'S OWN LENS RENDERING, WRITTEN WHERE A HUMAN READS IT: one TSV row per fact with +// every field the fixture claims pin. The claims' expectations are checked against the lens's +// output over the fixture -- this dump is how that output is observed, instead of trusting a +// hand-maintained parallel list of what the lens is supposed to say. Wet-lane only: hermetic +// lanes refuse the write by design. +fn fixture_facts_dump() -> ProcessExit { + match fixture_facts() { + Absent => ExitFailure { code: 1, reason: "fixture facts absent: fixture unreadable or unparseable" } + Present { value: facts } => + let rows = fold_list(xs: facts, empty: "decl\tkind\tcounted\tcandidates_n\tcandidates\tflag_words_n\treturns_argv_words\tlist_string_params\tlist_string_data", cons: fn(acc, f) { + concat(acc, concat("\n", fact_row(f: f))) + }) + let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: rows) + match written.success { + true => ExitSuccess + false => ExitFailure { code: 1, reason: concat("fixture facts write refused: ", written.error) } + } + } +} + +fn fact_row(f: ArgvWordsFact) -> String { + concat(f.decl, concat("\t", concat(kind_name(k: f.decl_kind), concat("\t", concat(bool_word(b: f.counted), concat("\t", concat(length(xs: f.candidates) as String, concat("\t", concat(joined_words(xs: f.candidates), concat("\t", concat(length(xs: f.flag_words) as String, concat("\t", concat(bool_word(b: f.returns_argv_words), concat("\t", concat(f.list_string_params as String, concat("\t", bool_word(b: f.list_string_data)))))))))))))))))) +} + +fn kind_name(k: ArgvWordsDeclKind) -> String { + match k { + FnDecl => "FnDecl" + DataDecl => "DataDecl" + } +} + +fn bool_word(b: Bool) -> String { + match b { + true => "true" + false => "false" + } +} + +fn joined_words(xs: List) -> String { + fold_list(xs: xs, empty: "", cons: fn(acc, w) { match acc { "" => w, _ => concat(acc, concat(",", w)) } }) +} + fn fact_named(xs: List, want: String) -> Optional { fold_list(xs: xs, empty: Absent, cons: fn(acc, f) { match acc { From fe01fad99261399afe18f7c7c5293d59274e8e8d Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 6 Oct 2026 10:46:13 +0000 Subject: [PATCH 48/65] dump: render candidates with the prelude join instead of a string-literal match arm --- src/v2/test/claim/argv_word_construction/census_test.dag | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/v2/test/claim/argv_word_construction/census_test.dag b/src/v2/test/claim/argv_word_construction/census_test.dag index 3b525de7304..ceedee1043d 100644 --- a/src/v2/test/claim/argv_word_construction/census_test.dag +++ b/src/v2/test/claim/argv_word_construction/census_test.dag @@ -103,7 +103,7 @@ fn fixture_facts_dump() -> ProcessExit { } fn fact_row(f: ArgvWordsFact) -> String { - concat(f.decl, concat("\t", concat(kind_name(k: f.decl_kind), concat("\t", concat(bool_word(b: f.counted), concat("\t", concat(length(xs: f.candidates) as String, concat("\t", concat(joined_words(xs: f.candidates), concat("\t", concat(length(xs: f.flag_words) as String, concat("\t", concat(bool_word(b: f.returns_argv_words), concat("\t", concat(f.list_string_params as String, concat("\t", bool_word(b: f.list_string_data)))))))))))))))))) + concat(f.decl, concat("\t", concat(kind_name(k: f.decl_kind), concat("\t", concat(bool_word(b: f.counted), concat("\t", concat(length(xs: f.candidates) as String, concat("\t", concat(join(xs: f.candidates, separator: ","), concat("\t", concat(length(xs: f.flag_words) as String, concat("\t", concat(bool_word(b: f.returns_argv_words), concat("\t", concat(f.list_string_params as String, concat("\t", bool_word(b: f.list_string_data)))))))))))))))))) } fn kind_name(k: ArgvWordsDeclKind) -> String { @@ -120,10 +120,6 @@ fn bool_word(b: Bool) -> String { } } -fn joined_words(xs: List) -> String { - fold_list(xs: xs, empty: "", cons: fn(acc, w) { match acc { "" => w, _ => concat(acc, concat(",", w)) } }) -} - fn fact_named(xs: List, want: String) -> Optional { fold_list(xs: xs, empty: Absent, cons: fn(acc, f) { match acc { From defd9ec2fb1bda7afccaebc85df83273e24fa4f2 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 6 Oct 2026 10:56:58 +0000 Subject: [PATCH 49/65] dump: render integers with integer_int_to_decimal_string (Int has no as-String cast) and join positionally --- src/v2/test/claim/argv_word_construction/census_test.dag | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/v2/test/claim/argv_word_construction/census_test.dag b/src/v2/test/claim/argv_word_construction/census_test.dag index ceedee1043d..415d07dec0c 100644 --- a/src/v2/test/claim/argv_word_construction/census_test.dag +++ b/src/v2/test/claim/argv_word_construction/census_test.dag @@ -46,6 +46,7 @@ import gunbc.instruments.argv_word_construction_census { } import extdeps.filesystem.filesystem_io { Filesystem } import v2.std.algebra { fold_list, length } +import v2.std.integer { integer_int_to_decimal_string } import v2.std.logic { Bool } import v2.std.collection { List } import v2.std.optional { Absent, Present } @@ -103,7 +104,7 @@ fn fixture_facts_dump() -> ProcessExit { } fn fact_row(f: ArgvWordsFact) -> String { - concat(f.decl, concat("\t", concat(kind_name(k: f.decl_kind), concat("\t", concat(bool_word(b: f.counted), concat("\t", concat(length(xs: f.candidates) as String, concat("\t", concat(join(xs: f.candidates, separator: ","), concat("\t", concat(length(xs: f.flag_words) as String, concat("\t", concat(bool_word(b: f.returns_argv_words), concat("\t", concat(f.list_string_params as String, concat("\t", bool_word(b: f.list_string_data)))))))))))))))))) + concat(f.decl, concat("\t", concat(kind_name(k: f.decl_kind), concat("\t", concat(bool_word(b: f.counted), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.candidates)), concat("\t", concat(join(f.candidates, ","), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.flag_words)), concat("\t", concat(bool_word(b: f.returns_argv_words), concat("\t", concat(integer_int_to_decimal_string(value: f.list_string_params), concat("\t", bool_word(b: f.list_string_data)))))))))))))))))) } fn kind_name(k: ArgvWordsDeclKind) -> String { From fc30f751088d30e32522e4d17f638251f7fb4f93 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 6 Oct 2026 11:13:12 +0000 Subject: [PATCH 50/65] =?UTF-8?q?census:=20fixture=20facts=20reading=20mov?= =?UTF-8?q?es=20to=20fixture=5Ffacts=5Fio=20(non-test=20module)=20?= =?UTF-8?q?=E2=80=94=20the=20wet=20lane's=20entry=20loader=20refuses=20ent?= =?UTF-8?q?ries=20bearing=20test=20functions,=20so=20the=20dump=20needs=20?= =?UTF-8?q?a=20non-test=20entry;=20the=20claims=20import=20the=20reading?= =?UTF-8?q?=20from=20there?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../argv_word_construction/census_test.dag | 66 ++-------------- .../fixture_facts_io.dag | 75 +++++++++++++++++++ 2 files changed, 80 insertions(+), 61 deletions(-) create mode 100644 src/v2/test/claim/argv_word_construction/fixture_facts_io.dag diff --git a/src/v2/test/claim/argv_word_construction/census_test.dag b/src/v2/test/claim/argv_word_construction/census_test.dag index 415d07dec0c..076e18b2dd6 100644 --- a/src/v2/test/claim/argv_word_construction/census_test.dag +++ b/src/v2/test/claim/argv_word_construction/census_test.dag @@ -57,7 +57,6 @@ import std.process { ExitSuccess, ProcessExit, ExitFailure } // declared module name (fixture.argv_word_construction.host_words) is unique in the tree, so // loading it does not fork the namespace. It sits OUTSIDE the modeled census roots, which the // boundary claim below pins as a typed refusal. -data fixture_path: String = "src/v2/test/fixture_roots/argv_word_construction/host_words.dag" // The modeled-roots specimen, used only through the pure class router (no parse in CI). data specimen_path: String = "dag/extdeps/systemd/systemd_run.dag" @@ -70,69 +69,14 @@ data established_route_path: String = "dag/extdeps/cpu/ampere_altra_package_cont data gate_file: String = "dag/gunbc/instruments/argv_word_construction_census.dag" // ---------------------------------------------------------------- fixture facts +// The reading lives in fixture_facts_io (a non-test module) because the wet lane's entry loader +// refuses entries bearing test functions; the dump there is the observation the expectations +// below are aligned against. +import v2.test.claim.argv_word_construction.fixture_facts_io { fixture_path, fixture_facts } -fn fixture_facts() -> Optional> { - let read = Filesystem.Read(path: fixture_path) - match read.success { - false => Absent - true => - match raw_tree_of_text(text: read.content) { - RawTreeNotEstablished { cause: _ } => Absent - RawTreeEstablished { root: root } => Present { value: facts_of(root: root) } - } - } -} - -// THE FIXTURE'S OWN LENS RENDERING, WRITTEN WHERE A HUMAN READS IT: one TSV row per fact with -// every field the fixture claims pin. The claims' expectations are checked against the lens's -// output over the fixture -- this dump is how that output is observed, instead of trusting a -// hand-maintained parallel list of what the lens is supposed to say. Wet-lane only: hermetic -// lanes refuse the write by design. -fn fixture_facts_dump() -> ProcessExit { - match fixture_facts() { - Absent => ExitFailure { code: 1, reason: "fixture facts absent: fixture unreadable or unparseable" } - Present { value: facts } => - let rows = fold_list(xs: facts, empty: "decl\tkind\tcounted\tcandidates_n\tcandidates\tflag_words_n\treturns_argv_words\tlist_string_params\tlist_string_data", cons: fn(acc, f) { - concat(acc, concat("\n", fact_row(f: f))) - }) - let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: rows) - match written.success { - true => ExitSuccess - false => ExitFailure { code: 1, reason: concat("fixture facts write refused: ", written.error) } - } - } -} - -fn fact_row(f: ArgvWordsFact) -> String { - concat(f.decl, concat("\t", concat(kind_name(k: f.decl_kind), concat("\t", concat(bool_word(b: f.counted), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.candidates)), concat("\t", concat(join(f.candidates, ","), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.flag_words)), concat("\t", concat(bool_word(b: f.returns_argv_words), concat("\t", concat(integer_int_to_decimal_string(value: f.list_string_params), concat("\t", bool_word(b: f.list_string_data)))))))))))))))))) -} - -fn kind_name(k: ArgvWordsDeclKind) -> String { - match k { - FnDecl => "FnDecl" - DataDecl => "DataDecl" - } -} +fn fact_named(xs: List, want: String) -> Optional { -fn bool_word(b: Bool) -> String { - match b { - true => "true" - false => "false" - } -} -fn fact_named(xs: List, want: String) -> Optional { - fold_list(xs: xs, empty: Absent, cons: fn(acc, f) { - match acc { - Present { value: _ } => acc - Absent => - match f.decl == want { - true => Present { value: f } - false => Absent - } - } - }) -} test fn census_fixture_route_counts_its_members() -> Bool { match fixture_facts() { diff --git a/src/v2/test/claim/argv_word_construction/fixture_facts_io.dag b/src/v2/test/claim/argv_word_construction/fixture_facts_io.dag new file mode 100644 index 00000000000..28e32b63324 --- /dev/null +++ b/src/v2/test/claim/argv_word_construction/fixture_facts_io.dag @@ -0,0 +1,75 @@ +module v2.test.claim.argv_word_construction.fixture_facts_io + +// The fixture's facts, read and parsed OUTSIDE the claims that pin them, and the one wet emitter +// that writes the lens's rendering of those facts where a human reads it. This is a non-test +// module because the wet lane's entry loader refuses entries bearing test functions; the claims +// in census_test import the reading from here, and the dump is the observation those claims' +// expectations are aligned against -- not a hand-maintained parallel list of what the lens is +// supposed to say. + +import extdeps.filesystem.filesystem_io { Filesystem } +import v2.lens.argv_word_construction { + ArgvWordsFact, + ArgvWordsDeclKind, + FnDecl, + DataDecl, + facts_of, + raw_tree_of_text, + RawTreeEstablished, + RawTreeNotEstablished +} +import v2.std.algebra { fold_list, length } +import v2.std.integer { integer_int_to_decimal_string } +import v2.std.logic { Bool } +import v2.std.collection { List } +import v2.std.optional { Absent, Present } +import std.process { ExitSuccess, ProcessExit, ExitFailure } + +data fixture_path: String = "src/v2/test/fixture_roots/argv_word_construction/host_words.dag" + +fn fixture_facts() -> Optional> { + let read = Filesystem.Read(path: fixture_path) + match read.success { + false => Absent + true => + match raw_tree_of_text(text: read.content) { + RawTreeNotEstablished { cause: _ } => Absent + RawTreeEstablished { root: root } => Present { value: facts_of(root: root) } + } + } +} + +// THE FIXTURE'S OWN LENS RENDERING, WRITTEN WHERE A HUMAN READS IT: one TSV row per fact with +// every field the fixture claims pin. Wet-lane only: hermetic lanes refuse the write by design. +fn fixture_facts_dump() -> ProcessExit { + match fixture_facts() { + Absent => ExitFailure { code: 1, reason: "fixture facts absent: fixture unreadable or unparseable" } + Present { value: facts } => + let rows = fold_list(xs: facts, empty: "decl\tkind\tcounted\tcandidates_n\tcandidates\tflag_words_n\treturns_argv_words\tlist_string_params\tlist_string_data", cons: fn(acc, f) { + concat(acc, concat("\n", fact_row(f: f))) + }) + let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: rows) + match written.success { + true => ExitSuccess + false => ExitFailure { code: 1, reason: concat("fixture facts write refused: ", written.error) } + } + } +} + +fn fact_row(f: ArgvWordsFact) -> String { + concat(f.decl, concat("\t", concat(kind_name(k: f.decl_kind), concat("\t", concat(bool_word(b: f.counted), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.candidates)), concat("\t", concat(join(f.candidates, ","), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.flag_words)), concat("\t", concat(bool_word(b: f.returns_argv_words), concat("\t", concat(integer_int_to_decimal_string(value: f.list_string_params), concat("\t", bool_word(b: f.list_string_data)))))))))))))))))) +} + +fn kind_name(k: ArgvWordsDeclKind) -> String { + match k { + FnDecl => "FnDecl" + DataDecl => "DataDecl" + } +} + +fn bool_word(b: Bool) -> String { + match b { + true => "true" + false => "false" + } +} From a9ee3f3d3ba8a00b5fd46aafdf5c349676fb9050 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 6 Oct 2026 11:39:13 +0000 Subject: [PATCH 51/65] =?UTF-8?q?census:=20the=20fixture-facts=20dump=20mo?= =?UTF-8?q?ves=20to=20the=20instrument=20(the=20wet=20lane=20only=20loads?= =?UTF-8?q?=20dag/=20entries)=20and=20reads=20at=20the=20FACT=20level=20?= =?UTF-8?q?=E2=80=94=20read/parse/facts=5Fof,=20the=20same=20route=20the?= =?UTF-8?q?=20fixture=20claims=20use,=20since=20for=5Fpaths=20refuses=20an?= =?UTF-8?q?y=20module=20outside=20the=20modeled=20roots;=20the=20test=20fi?= =?UTF-8?q?le=20is=20restored=20to=20its=20reviewed=20shape?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../argv_word_construction_census.dag | 52 +++++++++++-- .../argv_word_construction/census_test.dag | 31 ++++++-- .../fixture_facts_io.dag | 75 ------------------- 3 files changed, 70 insertions(+), 88 deletions(-) delete mode 100644 src/v2/test/claim/argv_word_construction/fixture_facts_io.dag diff --git a/dag/gunbc/instruments/argv_word_construction_census.dag b/dag/gunbc/instruments/argv_word_construction_census.dag index 9a2816811b6..dcfb0138f0d 100644 --- a/dag/gunbc/instruments/argv_word_construction_census.dag +++ b/dag/gunbc/instruments/argv_word_construction_census.dag @@ -16,6 +16,7 @@ module gunbc.instruments.argv_word_construction_census import v2.lens.argv_word_construction { ArgvWordsDeclKind, + ArgvWordsFact, ArgvWordsRosterRow, ArgvWordsSite, ArgvWordsSiteClass, @@ -25,6 +26,10 @@ import v2.lens.argv_word_construction { WorkflowCallSite, ArgvWordsModuleEstablished, ArgvWordsModuleRefused, + RawTreeEstablished, + RawTreeNotEstablished, + facts_of, + raw_tree_of_text, argv_word_construction_gates, argv_word_construction_module_sites, argv_word_construction_for_paths @@ -33,6 +38,9 @@ import v2.std.algebra { fold_list, list_reverse } import v2.std.collection { List } import std.algebra { Cons, Empty, FreeMonoid } import std.process { ExitSuccess, ProcessExit, exit_failure } +import extdeps.filesystem.filesystem_io { Filesystem } +import v2.std.integer { integer_int_to_decimal_string } +import v2.std.logic { Bool } import v2.lens.module_graph { ModuleDeclarationFact, module_declaration_facts_live } // The modeled roots. Anything else is refused by the lens, never silently zero. @@ -117,12 +125,44 @@ fn argv_word_construction_census_recount_emit_gunbc() -> ProcessExit { } // The fixture root is OUTSIDE the modeled roots by design (the boundary-refusal claim pins that), -// so the walk above never sees it. This emitter runs the same lens over the fixture's directory -// alone, so the fixture claims' pinned expectations are checked against the lens's own rendering -// of the fixture -- one recount path for every population the census route talks about, not a -// hand-maintained parallel list. -fn argv_word_construction_census_recount_emit_fixture() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["src/v2/test/fixture_roots/argv_word_construction"]), out: "target/argv_word_construction_census_recount_fixture.tsv") +// so the walk above never sees it and for_paths refuses any module outside the roots. The +// fixture's facts are therefore read at the FACT level -- the same read/parse/facts_of route the +// fixture claims use -- and rendered where a human reads them: one TSV row per fact with every +// field the fixture claims pin. This is the observation those claims' expectations are aligned +// against, not a hand-maintained parallel list of what the lens is supposed to say. +fn argv_word_construction_census_fixture_facts_dump() -> ProcessExit { + let read = Filesystem.Read(path: "src/v2/test/fixture_roots/argv_word_construction/host_words.dag") + match read.success { + false => exit_failure(reason: "fixture facts absent: fixture unreadable") + true => + match raw_tree_of_text(text: read.content) { + RawTreeNotEstablished { cause: cause } => exit_failure(reason: concat("fixture unparseable: ", cause)) + RawTreeEstablished { root: root } => + let rows = fold_list(xs: facts_of(root: root), empty: "decl\tkind\tcounted\tcandidates_n\tcandidates\tflag_words_n\treturns_argv_words\tlist_string_params\tlist_string_data", cons: fn(acc, f) { + concat(acc, concat("\n", fixture_fact_row(f: f))) + }) + let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: rows) + if written.success { ExitSuccess } else { exit_failure(reason: concat("fixture facts write refused: ", written.error)) } + } + } +} + +fn fixture_fact_row(f: ArgvWordsFact) -> String { + concat(f.decl, concat("\t", concat(fact_kind_word(k: f.decl_kind), concat("\t", concat(bool_word(b: f.counted), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.candidates)), concat("\t", concat(join(f.candidates, ";"), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.flag_words)), concat("\t", concat(bool_word(b: f.returns_argv_words), concat("\t", concat(integer_int_to_decimal_string(value: f.list_string_params), concat("\t", bool_word(b: f.list_string_data)))))))))))))))))) +} + +fn fact_kind_word(k: ArgvWordsDeclKind) -> String { + match k { + FnDecl => "FnDecl" + DataDecl => "DataDecl" + } +} + +fn bool_word(b: Bool) -> String { + match b { + true => "true" + false => "false" + } } fn declared_module_path(d: ModuleDeclarationFact) -> String { diff --git a/src/v2/test/claim/argv_word_construction/census_test.dag b/src/v2/test/claim/argv_word_construction/census_test.dag index 076e18b2dd6..ba91d984cec 100644 --- a/src/v2/test/claim/argv_word_construction/census_test.dag +++ b/src/v2/test/claim/argv_word_construction/census_test.dag @@ -46,7 +46,6 @@ import gunbc.instruments.argv_word_construction_census { } import extdeps.filesystem.filesystem_io { Filesystem } import v2.std.algebra { fold_list, length } -import v2.std.integer { integer_int_to_decimal_string } import v2.std.logic { Bool } import v2.std.collection { List } import v2.std.optional { Absent, Present } @@ -57,6 +56,7 @@ import std.process { ExitSuccess, ProcessExit, ExitFailure } // declared module name (fixture.argv_word_construction.host_words) is unique in the tree, so // loading it does not fork the namespace. It sits OUTSIDE the modeled census roots, which the // boundary claim below pins as a typed refusal. +data fixture_path: String = "src/v2/test/fixture_roots/argv_word_construction/host_words.dag" // The modeled-roots specimen, used only through the pure class router (no parse in CI). data specimen_path: String = "dag/extdeps/systemd/systemd_run.dag" @@ -69,14 +69,31 @@ data established_route_path: String = "dag/extdeps/cpu/ampere_altra_package_cont data gate_file: String = "dag/gunbc/instruments/argv_word_construction_census.dag" // ---------------------------------------------------------------- fixture facts -// The reading lives in fixture_facts_io (a non-test module) because the wet lane's entry loader -// refuses entries bearing test functions; the dump there is the observation the expectations -// below are aligned against. -import v2.test.claim.argv_word_construction.fixture_facts_io { fixture_path, fixture_facts } - -fn fact_named(xs: List, want: String) -> Optional { +fn fixture_facts() -> Optional> { + let read = Filesystem.Read(path: fixture_path) + match read.success { + false => Absent + true => + match raw_tree_of_text(text: read.content) { + RawTreeNotEstablished { cause: _ } => Absent + RawTreeEstablished { root: root } => Present { value: facts_of(root: root) } + } + } +} +fn fact_named(xs: List, want: String) -> Optional { + fold_list(xs: xs, empty: Absent, cons: fn(acc, f) { + match acc { + Present { value: _ } => acc + Absent => + match f.decl == want { + true => Present { value: f } + false => Absent + } + } + }) +} test fn census_fixture_route_counts_its_members() -> Bool { match fixture_facts() { diff --git a/src/v2/test/claim/argv_word_construction/fixture_facts_io.dag b/src/v2/test/claim/argv_word_construction/fixture_facts_io.dag deleted file mode 100644 index 28e32b63324..00000000000 --- a/src/v2/test/claim/argv_word_construction/fixture_facts_io.dag +++ /dev/null @@ -1,75 +0,0 @@ -module v2.test.claim.argv_word_construction.fixture_facts_io - -// The fixture's facts, read and parsed OUTSIDE the claims that pin them, and the one wet emitter -// that writes the lens's rendering of those facts where a human reads it. This is a non-test -// module because the wet lane's entry loader refuses entries bearing test functions; the claims -// in census_test import the reading from here, and the dump is the observation those claims' -// expectations are aligned against -- not a hand-maintained parallel list of what the lens is -// supposed to say. - -import extdeps.filesystem.filesystem_io { Filesystem } -import v2.lens.argv_word_construction { - ArgvWordsFact, - ArgvWordsDeclKind, - FnDecl, - DataDecl, - facts_of, - raw_tree_of_text, - RawTreeEstablished, - RawTreeNotEstablished -} -import v2.std.algebra { fold_list, length } -import v2.std.integer { integer_int_to_decimal_string } -import v2.std.logic { Bool } -import v2.std.collection { List } -import v2.std.optional { Absent, Present } -import std.process { ExitSuccess, ProcessExit, ExitFailure } - -data fixture_path: String = "src/v2/test/fixture_roots/argv_word_construction/host_words.dag" - -fn fixture_facts() -> Optional> { - let read = Filesystem.Read(path: fixture_path) - match read.success { - false => Absent - true => - match raw_tree_of_text(text: read.content) { - RawTreeNotEstablished { cause: _ } => Absent - RawTreeEstablished { root: root } => Present { value: facts_of(root: root) } - } - } -} - -// THE FIXTURE'S OWN LENS RENDERING, WRITTEN WHERE A HUMAN READS IT: one TSV row per fact with -// every field the fixture claims pin. Wet-lane only: hermetic lanes refuse the write by design. -fn fixture_facts_dump() -> ProcessExit { - match fixture_facts() { - Absent => ExitFailure { code: 1, reason: "fixture facts absent: fixture unreadable or unparseable" } - Present { value: facts } => - let rows = fold_list(xs: facts, empty: "decl\tkind\tcounted\tcandidates_n\tcandidates\tflag_words_n\treturns_argv_words\tlist_string_params\tlist_string_data", cons: fn(acc, f) { - concat(acc, concat("\n", fact_row(f: f))) - }) - let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: rows) - match written.success { - true => ExitSuccess - false => ExitFailure { code: 1, reason: concat("fixture facts write refused: ", written.error) } - } - } -} - -fn fact_row(f: ArgvWordsFact) -> String { - concat(f.decl, concat("\t", concat(kind_name(k: f.decl_kind), concat("\t", concat(bool_word(b: f.counted), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.candidates)), concat("\t", concat(join(f.candidates, ","), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.flag_words)), concat("\t", concat(bool_word(b: f.returns_argv_words), concat("\t", concat(integer_int_to_decimal_string(value: f.list_string_params), concat("\t", bool_word(b: f.list_string_data)))))))))))))))))) -} - -fn kind_name(k: ArgvWordsDeclKind) -> String { - match k { - FnDecl => "FnDecl" - DataDecl => "DataDecl" - } -} - -fn bool_word(b: Bool) -> String { - match b { - true => "true" - false => "false" - } -} From bbd0ddb2fc0c340143db995aa669d9983bbbe110 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 6 Oct 2026 11:50:02 +0000 Subject: [PATCH 52/65] =?UTF-8?q?dump:=20the=20RawTreeEstablished=20arm=20?= =?UTF-8?q?delegates=20to=20a=20helper=20=E2=80=94=20multi-let=20match=20a?= =?UTF-8?q?rms=20are=20an=20unproven=20shape;=20function-body=20lets=20are?= =?UTF-8?q?=20the=20shape=20for=5Fpaths=20proves?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../argv_word_construction_census.dag | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/dag/gunbc/instruments/argv_word_construction_census.dag b/dag/gunbc/instruments/argv_word_construction_census.dag index dcfb0138f0d..f15ad141da5 100644 --- a/dag/gunbc/instruments/argv_word_construction_census.dag +++ b/dag/gunbc/instruments/argv_word_construction_census.dag @@ -28,6 +28,7 @@ import v2.lens.argv_word_construction { ArgvWordsModuleRefused, RawTreeEstablished, RawTreeNotEstablished, + Node, facts_of, raw_tree_of_text, argv_word_construction_gates, @@ -137,16 +138,19 @@ fn argv_word_construction_census_fixture_facts_dump() -> ProcessExit { true => match raw_tree_of_text(text: read.content) { RawTreeNotEstablished { cause: cause } => exit_failure(reason: concat("fixture unparseable: ", cause)) - RawTreeEstablished { root: root } => - let rows = fold_list(xs: facts_of(root: root), empty: "decl\tkind\tcounted\tcandidates_n\tcandidates\tflag_words_n\treturns_argv_words\tlist_string_params\tlist_string_data", cons: fn(acc, f) { - concat(acc, concat("\n", fixture_fact_row(f: f))) - }) - let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: rows) - if written.success { ExitSuccess } else { exit_failure(reason: concat("fixture facts write refused: ", written.error)) } + RawTreeEstablished { root: root } => fixture_facts_write(root: root) } } } +fn fixture_facts_write(root: Node) -> ProcessExit { + let rows = fold_list(xs: facts_of(root: root), empty: "decl\tkind\tcounted\tcandidates_n\tcandidates\tflag_words_n\treturns_argv_words\tlist_string_params\tlist_string_data", cons: fn(acc, f) { + concat(acc, concat("\n", fixture_fact_row(f: f))) + }) + let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: rows) + if written.success { ExitSuccess } else { exit_failure(reason: concat("fixture facts write refused: ", written.error)) } +} + fn fixture_fact_row(f: ArgvWordsFact) -> String { concat(f.decl, concat("\t", concat(fact_kind_word(k: f.decl_kind), concat("\t", concat(bool_word(b: f.counted), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.candidates)), concat("\t", concat(join(f.candidates, ";"), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.flag_words)), concat("\t", concat(bool_word(b: f.returns_argv_words), concat("\t", concat(integer_int_to_decimal_string(value: f.list_string_params), concat("\t", bool_word(b: f.list_string_data)))))))))))))))))) } From 0ee63e12f6a6c5d210bea097dc6221fc03a84bd1 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 6 Oct 2026 12:03:08 +0000 Subject: [PATCH 53/65] =?UTF-8?q?dump:=20fix=20the=20three=20validation=20?= =?UTF-8?q?collisions=20=E2=80=94=20Node=20is=20imported=20from=20v2.std.n?= =?UTF-8?q?ode=20(the=20lens=20does=20not=20re-export=20it),=20the=20modul?= =?UTF-8?q?e=20already=20imported=20Bool=20(duplicate=20import=20dropped),?= =?UTF-8?q?=20and=20bool=5Fword=20keeps=20a=20single=20definition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dag/gunbc/instruments/argv_word_construction_census.dag | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dag/gunbc/instruments/argv_word_construction_census.dag b/dag/gunbc/instruments/argv_word_construction_census.dag index f15ad141da5..9abbd46d946 100644 --- a/dag/gunbc/instruments/argv_word_construction_census.dag +++ b/dag/gunbc/instruments/argv_word_construction_census.dag @@ -28,7 +28,6 @@ import v2.lens.argv_word_construction { ArgvWordsModuleRefused, RawTreeEstablished, RawTreeNotEstablished, - Node, facts_of, raw_tree_of_text, argv_word_construction_gates, @@ -40,8 +39,8 @@ import v2.std.collection { List } import std.algebra { Cons, Empty, FreeMonoid } import std.process { ExitSuccess, ProcessExit, exit_failure } import extdeps.filesystem.filesystem_io { Filesystem } +import v2.std.node { Node } import v2.std.integer { integer_int_to_decimal_string } -import v2.std.logic { Bool } import v2.lens.module_graph { ModuleDeclarationFact, module_declaration_facts_live } // The modeled roots. Anything else is refused by the lens, never silently zero. From 99636d2fdf3d73b41cd37cb304c09f5f0c407fa9 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 6 Oct 2026 12:26:14 +0000 Subject: [PATCH 54/65] TEMP bisect variants (removed immediately after reading) --- .../instruments/bisect_tmp/v0_lastok.dag | 165 +++++++++++++ .../instruments/bisect_tmp/v1_current.dag | 217 ++++++++++++++++++ .../bisect_tmp/v2_imports_only.dag | 168 ++++++++++++++ dag/gunbc/instruments/bisect_tmp/v3_stub.dag | 174 ++++++++++++++ .../instruments/bisect_tmp/v4_fullblock.dag | 212 +++++++++++++++++ 5 files changed, 936 insertions(+) create mode 100644 dag/gunbc/instruments/bisect_tmp/v0_lastok.dag create mode 100644 dag/gunbc/instruments/bisect_tmp/v1_current.dag create mode 100644 dag/gunbc/instruments/bisect_tmp/v2_imports_only.dag create mode 100644 dag/gunbc/instruments/bisect_tmp/v3_stub.dag create mode 100644 dag/gunbc/instruments/bisect_tmp/v4_fullblock.dag diff --git a/dag/gunbc/instruments/bisect_tmp/v0_lastok.dag b/dag/gunbc/instruments/bisect_tmp/v0_lastok.dag new file mode 100644 index 00000000000..0734ce4b16d --- /dev/null +++ b/dag/gunbc/instruments/bisect_tmp/v0_lastok.dag @@ -0,0 +1,165 @@ +module gunbc.instruments.argv_word_construction_census + +// THE TYPED-ARGV CONSTRUCTION CENSUS ROSTER (debt paydown trial, witty-tern-54, 2026-10-03). +// +// The instrument is v2.lens.argv_word_construction; this module commits WHAT IT COUNTS: the +// population of argv-string construction sites over dag/extdeps and dag/gunbc, at identity grain, +// as a typed roster that may only shrink. DESIGN 5: a count over the current tree is not an +// oracle — every row below joins to a live census member at (module_file, decl, decl_kind), a +// row that stops resolving refuses the recount, and the population never grows past the committed +// baseline: new string-built argv is refused, not enrolled. +// +// Recount (the named, re-runnable instrument run): discover the two roots' module files by the +// declaration index, walk each with the lens, and hold the gates over (live, roster, baseline). +// The gates live in the lens; this module owns the committed state: the roster, the baseline +// cardinality, and the sites classified as NOT argv debt with a stated reason. + +import v2.lens.argv_word_construction { + ArgvWordsDeclKind, + ArgvWordsRosterRow, + ArgvWordsSite, + ArgvWordsSiteClass, + ExtdepsInterfaceBuilder, + FnDecl, + DataDecl, + WorkflowCallSite, + ArgvWordsModuleEstablished, + ArgvWordsModuleRefused, + argv_word_construction_gates, + argv_word_construction_module_sites, + argv_word_construction_for_paths +} +import v2.std.algebra { fold_list, list_reverse } +import v2.std.collection { List } +import std.algebra { Cons, Empty, FreeMonoid } +import std.process { ExitSuccess, ProcessExit, exit_failure } +import v2.lens.module_graph { ModuleDeclarationFact, module_declaration_facts_live } + +// The modeled roots. Anything else is refused by the lens, never silently zero. +data argv_word_construction_census_roots: List = ["dag/extdeps", "dag/gunbc"] + +// THE COMMITTED BASELINE: the population the instrument counted over the two roots at the start +// of the trial (origin/main, 2026-10-03). The roster may only shrink past this number. +data argv_word_construction_census_baseline_population: Int = 0 + +// THE ROSTER: one row per live census member. `note` names what the site is and what pays it. +// Kept at zero rows until the first committed recount fills it from the instrument (never by +// hand from a text search). +data argv_word_construction_census_roster: List = [] + +// Sites whose evidence is read and REFUSED AS DEBT with a stated reason: the census classifies, +// it does not silently count. Each entry names its reason; a reason that stops being true must +// delete the entry (the same shrink discipline as the roster). +data argv_word_construction_census_excluded: List = [] + +type ArgvWordsExclusion { + module_file: String + decl: String + reason: String +} + +fn excluded_identity_matches(e: ArgvWordsExclusion, s: ArgvWordsSite) -> Bool { + (e.module_file == s.module_file) && (e.decl == s.decl) +} + +fn site_is_excluded(s: ArgvWordsSite) -> Bool { + fold_list(xs: argv_word_construction_census_excluded, empty: false, cons: fn(acc, e) { acc || excluded_identity_matches(e: e, s: s) }) +} + +// The live census members after classification: exclusions are visible subtractions with reasons. +fn argv_word_construction_census_live(sites: List) -> List { + fold_list(xs: sites, empty: Empty, cons: fn(acc, s) { if site_is_excluded(s: s) { acc } else { Cons { head: s, tail: acc } } }) +} + +// The contract over one recount's live sites. +fn argv_word_construction_census_check_sites(sites: List) -> ProcessExit { + argv_word_construction_gates(live: argv_word_construction_census_live(sites: sites), roster: argv_word_construction_census_roster, baseline_population: argv_word_construction_census_baseline_population) +} + +// The recount entry: discover the roots' module files by the declaration index (never a text +// search), walk each with the lens, and hold the contract. +fn argv_word_construction_census_recount() -> ProcessExit { + argv_word_construction_census_check_paths(paths: argv_word_construction_census_root_paths()) +} + +// The discovered modeled roots' module files, in declaration-index order. Shared by the gated +// recount above and the roster emitters below so all see the same population. Parameterized by +// roots: the corpus walk is run ONE ROOT PER PROCESS (a wet lane kills a whole-corpus walk with +// OOM -- the per-process journal of read+parse dispatches is what grows, so the split is by the +// roots boundary, the population's own partition). +fn argv_word_construction_census_root_paths_for_roots(roots: List) -> List { + let decls = module_declaration_facts_live(pool_roots: roots) + let paths_reversed = fold_list(xs: decls, empty: Empty, cons: fn(acc, d) { + let p = declared_module_path(d: d) + if p == "" { acc } else { Cons { head: p, tail: acc } } + }) + list_reverse(xs: paths_reversed) +} + +fn argv_word_construction_census_root_paths() -> List { + argv_word_construction_census_root_paths_for_roots(roots: argv_word_construction_census_roots) +} + +// The roster emitter for a wet-lane run (claim_batch --wet): the same discovered population, each +// module walked by the lens, rows written as TSV to the scratch path. The committed roster data +// decl above is filled FROM this output, never by hand from a text search. Hermetic lanes refuse +// the write by design -- the emitter exists for the recount, not for CI. +fn argv_word_construction_census_recount_emit() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths(), out: "target/argv_word_construction_census_recount.tsv") +} + +fn argv_word_construction_census_recount_emit_extdeps() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/extdeps"]), out: "target/argv_word_construction_census_recount_extdeps.tsv") +} + +fn argv_word_construction_census_recount_emit_gunbc() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/gunbc"]), out: "target/argv_word_construction_census_recount_gunbc.tsv") +} + +fn declared_module_path(d: ModuleDeclarationFact) -> String { + d.path +} + +// The distinct module files named by the committed roster, in first-appearance order: exactly the +// population the bounded-contract claim re-walks, so CI executes the gates at roster size without +// ever walking the whole corpus. +fn argv_word_construction_census_roster_modules() -> List { + let reversed = fold_list(xs: argv_word_construction_census_roster, empty: Empty, cons: fn(acc, r) { + if contains_string(xs: acc, x: r.module_file) { acc } else { Cons { head: r.module_file, tail: acc } } + }) + list_reverse(xs: reversed) +} + +fn contains_string(xs: List, x: String) -> Bool { + fold_list(xs: xs, empty: false, cons: fn(acc, y) { acc || (y == x) }) +} + +type ArgvWordsCollected { + sites: FreeMonoid + refusals: FreeMonoid +} + +fn collect_path(c: ArgvWordsCollected, p: String) -> ArgvWordsCollected { + match argv_word_construction_module_sites(path: p) { + ArgvWordsModuleRefused { path: rp, reason: r } => + ArgvWordsCollected { sites: c.sites, refusals: Cons { head: concat(concat(concat("REFUSED\t", rp), "\t"), r), tail: c.refusals } } + ArgvWordsModuleEstablished { path: _, sites: sites } => + ArgvWordsCollected { sites: fold_list(xs: sites, empty: c.sites, cons: fn(acc, s) { Cons { head: s, tail: acc } }), refusals: c.refusals } + } +} + +// The contract over an explicit path list: the per-module walk is the lens's; the gates run on the +// collected live sites, and every per-module refusal is named, never dropped. Kept separate from +// the discovery so a bounded recount (one module, one directory) is runnable in a claim test +// without the whole corpus. +fn argv_word_construction_census_check_paths(paths: List) -> ProcessExit { + let collected = fold_list(xs: paths, empty: ArgvWordsCollected { sites: Empty, refusals: Empty }, cons: collect_path) + match list_reverse(xs: collected.refusals) { + Empty => argv_word_construction_census_check_sites(sites: list_reverse(xs: collected.sites)) + nonempty => exit_failure(reason: join_lines(rs: nonempty)) + } +} + +fn join_lines(rs: FreeMonoid) -> String { + fold_list(xs: list_reverse(xs: rs), empty: "", cons: fn(acc, l) { if acc == "" { l } else { concat(concat(acc, "\n"), l) } }) +} diff --git a/dag/gunbc/instruments/bisect_tmp/v1_current.dag b/dag/gunbc/instruments/bisect_tmp/v1_current.dag new file mode 100644 index 00000000000..9abbd46d946 --- /dev/null +++ b/dag/gunbc/instruments/bisect_tmp/v1_current.dag @@ -0,0 +1,217 @@ +module gunbc.instruments.argv_word_construction_census + +// THE TYPED-ARGV CONSTRUCTION CENSUS ROSTER (debt paydown trial, witty-tern-54, 2026-10-03). +// +// The instrument is v2.lens.argv_word_construction; this module commits WHAT IT COUNTS: the +// population of argv-string construction sites over dag/extdeps and dag/gunbc, at identity grain, +// as a typed roster that may only shrink. DESIGN 5: a count over the current tree is not an +// oracle — every row below joins to a live census member at (module_file, decl, decl_kind), a +// row that stops resolving refuses the recount, and the population never grows past the committed +// baseline: new string-built argv is refused, not enrolled. +// +// Recount (the named, re-runnable instrument run): discover the two roots' module files by the +// declaration index, walk each with the lens, and hold the gates over (live, roster, baseline). +// The gates live in the lens; this module owns the committed state: the roster, the baseline +// cardinality, and the sites classified as NOT argv debt with a stated reason. + +import v2.lens.argv_word_construction { + ArgvWordsDeclKind, + ArgvWordsFact, + ArgvWordsRosterRow, + ArgvWordsSite, + ArgvWordsSiteClass, + ExtdepsInterfaceBuilder, + FnDecl, + DataDecl, + WorkflowCallSite, + ArgvWordsModuleEstablished, + ArgvWordsModuleRefused, + RawTreeEstablished, + RawTreeNotEstablished, + facts_of, + raw_tree_of_text, + argv_word_construction_gates, + argv_word_construction_module_sites, + argv_word_construction_for_paths +} +import v2.std.algebra { fold_list, list_reverse } +import v2.std.collection { List } +import std.algebra { Cons, Empty, FreeMonoid } +import std.process { ExitSuccess, ProcessExit, exit_failure } +import extdeps.filesystem.filesystem_io { Filesystem } +import v2.std.node { Node } +import v2.std.integer { integer_int_to_decimal_string } +import v2.lens.module_graph { ModuleDeclarationFact, module_declaration_facts_live } + +// The modeled roots. Anything else is refused by the lens, never silently zero. +data argv_word_construction_census_roots: List = ["dag/extdeps", "dag/gunbc"] + +// THE COMMITTED BASELINE: the population the instrument counted over the two roots at the start +// of the trial (origin/main, 2026-10-03). The roster may only shrink past this number. +data argv_word_construction_census_baseline_population: Int = 0 + +// THE ROSTER: one row per live census member. `note` names what the site is and what pays it. +// Kept at zero rows until the first committed recount fills it from the instrument (never by +// hand from a text search). +data argv_word_construction_census_roster: List = [] + +// Sites whose evidence is read and REFUSED AS DEBT with a stated reason: the census classifies, +// it does not silently count. Each entry names its reason; a reason that stops being true must +// delete the entry (the same shrink discipline as the roster). +data argv_word_construction_census_excluded: List = [] + +type ArgvWordsExclusion { + module_file: String + decl: String + reason: String +} + +fn excluded_identity_matches(e: ArgvWordsExclusion, s: ArgvWordsSite) -> Bool { + (e.module_file == s.module_file) && (e.decl == s.decl) +} + +fn site_is_excluded(s: ArgvWordsSite) -> Bool { + fold_list(xs: argv_word_construction_census_excluded, empty: false, cons: fn(acc, e) { acc || excluded_identity_matches(e: e, s: s) }) +} + +// The live census members after classification: exclusions are visible subtractions with reasons. +fn argv_word_construction_census_live(sites: List) -> List { + fold_list(xs: sites, empty: Empty, cons: fn(acc, s) { if site_is_excluded(s: s) { acc } else { Cons { head: s, tail: acc } } }) +} + +// The contract over one recount's live sites. +fn argv_word_construction_census_check_sites(sites: List) -> ProcessExit { + argv_word_construction_gates(live: argv_word_construction_census_live(sites: sites), roster: argv_word_construction_census_roster, baseline_population: argv_word_construction_census_baseline_population) +} + +// The recount entry: discover the roots' module files by the declaration index (never a text +// search), walk each with the lens, and hold the contract. +fn argv_word_construction_census_recount() -> ProcessExit { + argv_word_construction_census_check_paths(paths: argv_word_construction_census_root_paths()) +} + +// The discovered modeled roots' module files, in declaration-index order. Shared by the gated +// recount above and the roster emitters below so all see the same population. Parameterized by +// roots: the corpus walk is run ONE ROOT PER PROCESS (a wet lane kills a whole-corpus walk with +// OOM -- the per-process journal of read+parse dispatches is what grows, so the split is by the +// roots boundary, the population's own partition). +fn argv_word_construction_census_root_paths_for_roots(roots: List) -> List { + let decls = module_declaration_facts_live(pool_roots: roots) + let paths_reversed = fold_list(xs: decls, empty: Empty, cons: fn(acc, d) { + let p = declared_module_path(d: d) + if p == "" { acc } else { Cons { head: p, tail: acc } } + }) + list_reverse(xs: paths_reversed) +} + +fn argv_word_construction_census_root_paths() -> List { + argv_word_construction_census_root_paths_for_roots(roots: argv_word_construction_census_roots) +} + +// The roster emitter for a wet-lane run (claim_batch --wet): the same discovered population, each +// module walked by the lens, rows written as TSV to the scratch path. The committed roster data +// decl above is filled FROM this output, never by hand from a text search. Hermetic lanes refuse +// the write by design -- the emitter exists for the recount, not for CI. +fn argv_word_construction_census_recount_emit() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths(), out: "target/argv_word_construction_census_recount.tsv") +} + +fn argv_word_construction_census_recount_emit_extdeps() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/extdeps"]), out: "target/argv_word_construction_census_recount_extdeps.tsv") +} + +fn argv_word_construction_census_recount_emit_gunbc() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/gunbc"]), out: "target/argv_word_construction_census_recount_gunbc.tsv") +} + +// The fixture root is OUTSIDE the modeled roots by design (the boundary-refusal claim pins that), +// so the walk above never sees it and for_paths refuses any module outside the roots. The +// fixture's facts are therefore read at the FACT level -- the same read/parse/facts_of route the +// fixture claims use -- and rendered where a human reads them: one TSV row per fact with every +// field the fixture claims pin. This is the observation those claims' expectations are aligned +// against, not a hand-maintained parallel list of what the lens is supposed to say. +fn argv_word_construction_census_fixture_facts_dump() -> ProcessExit { + let read = Filesystem.Read(path: "src/v2/test/fixture_roots/argv_word_construction/host_words.dag") + match read.success { + false => exit_failure(reason: "fixture facts absent: fixture unreadable") + true => + match raw_tree_of_text(text: read.content) { + RawTreeNotEstablished { cause: cause } => exit_failure(reason: concat("fixture unparseable: ", cause)) + RawTreeEstablished { root: root } => fixture_facts_write(root: root) + } + } +} + +fn fixture_facts_write(root: Node) -> ProcessExit { + let rows = fold_list(xs: facts_of(root: root), empty: "decl\tkind\tcounted\tcandidates_n\tcandidates\tflag_words_n\treturns_argv_words\tlist_string_params\tlist_string_data", cons: fn(acc, f) { + concat(acc, concat("\n", fixture_fact_row(f: f))) + }) + let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: rows) + if written.success { ExitSuccess } else { exit_failure(reason: concat("fixture facts write refused: ", written.error)) } +} + +fn fixture_fact_row(f: ArgvWordsFact) -> String { + concat(f.decl, concat("\t", concat(fact_kind_word(k: f.decl_kind), concat("\t", concat(bool_word(b: f.counted), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.candidates)), concat("\t", concat(join(f.candidates, ";"), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.flag_words)), concat("\t", concat(bool_word(b: f.returns_argv_words), concat("\t", concat(integer_int_to_decimal_string(value: f.list_string_params), concat("\t", bool_word(b: f.list_string_data)))))))))))))))))) +} + +fn fact_kind_word(k: ArgvWordsDeclKind) -> String { + match k { + FnDecl => "FnDecl" + DataDecl => "DataDecl" + } +} + +fn bool_word(b: Bool) -> String { + match b { + true => "true" + false => "false" + } +} + +fn declared_module_path(d: ModuleDeclarationFact) -> String { + d.path +} + +// The distinct module files named by the committed roster, in first-appearance order: exactly the +// population the bounded-contract claim re-walks, so CI executes the gates at roster size without +// ever walking the whole corpus. +fn argv_word_construction_census_roster_modules() -> List { + let reversed = fold_list(xs: argv_word_construction_census_roster, empty: Empty, cons: fn(acc, r) { + if contains_string(xs: acc, x: r.module_file) { acc } else { Cons { head: r.module_file, tail: acc } } + }) + list_reverse(xs: reversed) +} + +fn contains_string(xs: List, x: String) -> Bool { + fold_list(xs: xs, empty: false, cons: fn(acc, y) { acc || (y == x) }) +} + +type ArgvWordsCollected { + sites: FreeMonoid + refusals: FreeMonoid +} + +fn collect_path(c: ArgvWordsCollected, p: String) -> ArgvWordsCollected { + match argv_word_construction_module_sites(path: p) { + ArgvWordsModuleRefused { path: rp, reason: r } => + ArgvWordsCollected { sites: c.sites, refusals: Cons { head: concat(concat(concat("REFUSED\t", rp), "\t"), r), tail: c.refusals } } + ArgvWordsModuleEstablished { path: _, sites: sites } => + ArgvWordsCollected { sites: fold_list(xs: sites, empty: c.sites, cons: fn(acc, s) { Cons { head: s, tail: acc } }), refusals: c.refusals } + } +} + +// The contract over an explicit path list: the per-module walk is the lens's; the gates run on the +// collected live sites, and every per-module refusal is named, never dropped. Kept separate from +// the discovery so a bounded recount (one module, one directory) is runnable in a claim test +// without the whole corpus. +fn argv_word_construction_census_check_paths(paths: List) -> ProcessExit { + let collected = fold_list(xs: paths, empty: ArgvWordsCollected { sites: Empty, refusals: Empty }, cons: collect_path) + match list_reverse(xs: collected.refusals) { + Empty => argv_word_construction_census_check_sites(sites: list_reverse(xs: collected.sites)) + nonempty => exit_failure(reason: join_lines(rs: nonempty)) + } +} + +fn join_lines(rs: FreeMonoid) -> String { + fold_list(xs: list_reverse(xs: rs), empty: "", cons: fn(acc, l) { if acc == "" { l } else { concat(concat(acc, "\n"), l) } }) +} diff --git a/dag/gunbc/instruments/bisect_tmp/v2_imports_only.dag b/dag/gunbc/instruments/bisect_tmp/v2_imports_only.dag new file mode 100644 index 00000000000..d391dafcf4c --- /dev/null +++ b/dag/gunbc/instruments/bisect_tmp/v2_imports_only.dag @@ -0,0 +1,168 @@ +module gunbc.instruments.argv_word_construction_census + +// THE TYPED-ARGV CONSTRUCTION CENSUS ROSTER (debt paydown trial, witty-tern-54, 2026-10-03). +// +// The instrument is v2.lens.argv_word_construction; this module commits WHAT IT COUNTS: the +// population of argv-string construction sites over dag/extdeps and dag/gunbc, at identity grain, +// as a typed roster that may only shrink. DESIGN 5: a count over the current tree is not an +// oracle — every row below joins to a live census member at (module_file, decl, decl_kind), a +// row that stops resolving refuses the recount, and the population never grows past the committed +// baseline: new string-built argv is refused, not enrolled. +// +// Recount (the named, re-runnable instrument run): discover the two roots' module files by the +// declaration index, walk each with the lens, and hold the gates over (live, roster, baseline). +// The gates live in the lens; this module owns the committed state: the roster, the baseline +// cardinality, and the sites classified as NOT argv debt with a stated reason. + +import v2.lens.argv_word_construction { + ArgvWordsDeclKind, + ArgvWordsRosterRow, + ArgvWordsSite, + ArgvWordsSiteClass, + ExtdepsInterfaceBuilder, + FnDecl, + DataDecl, + WorkflowCallSite, + ArgvWordsModuleEstablished, + ArgvWordsModuleRefused, + argv_word_construction_gates, + argv_word_construction_module_sites, + argv_word_construction_for_paths +} +import v2.std.algebra { fold_list, list_reverse } +import v2.std.collection { List } +import std.algebra { Cons, Empty, FreeMonoid } +import std.process { ExitSuccess, ProcessExit, exit_failure } +import extdeps.filesystem.filesystem_io { Filesystem } +import v2.std.node { Node } +import v2.std.integer { integer_int_to_decimal_string } +import v2.lens.module_graph { ModuleDeclarationFact, module_declaration_facts_live } + +// The modeled roots. Anything else is refused by the lens, never silently zero. +data argv_word_construction_census_roots: List = ["dag/extdeps", "dag/gunbc"] + +// THE COMMITTED BASELINE: the population the instrument counted over the two roots at the start +// of the trial (origin/main, 2026-10-03). The roster may only shrink past this number. +data argv_word_construction_census_baseline_population: Int = 0 + +// THE ROSTER: one row per live census member. `note` names what the site is and what pays it. +// Kept at zero rows until the first committed recount fills it from the instrument (never by +// hand from a text search). +data argv_word_construction_census_roster: List = [] + +// Sites whose evidence is read and REFUSED AS DEBT with a stated reason: the census classifies, +// it does not silently count. Each entry names its reason; a reason that stops being true must +// delete the entry (the same shrink discipline as the roster). +data argv_word_construction_census_excluded: List = [] + +type ArgvWordsExclusion { + module_file: String + decl: String + reason: String +} + +fn excluded_identity_matches(e: ArgvWordsExclusion, s: ArgvWordsSite) -> Bool { + (e.module_file == s.module_file) && (e.decl == s.decl) +} + +fn site_is_excluded(s: ArgvWordsSite) -> Bool { + fold_list(xs: argv_word_construction_census_excluded, empty: false, cons: fn(acc, e) { acc || excluded_identity_matches(e: e, s: s) }) +} + +// The live census members after classification: exclusions are visible subtractions with reasons. +fn argv_word_construction_census_live(sites: List) -> List { + fold_list(xs: sites, empty: Empty, cons: fn(acc, s) { if site_is_excluded(s: s) { acc } else { Cons { head: s, tail: acc } } }) +} + +// The contract over one recount's live sites. +fn argv_word_construction_census_check_sites(sites: List) -> ProcessExit { + argv_word_construction_gates(live: argv_word_construction_census_live(sites: sites), roster: argv_word_construction_census_roster, baseline_population: argv_word_construction_census_baseline_population) +} + +// The recount entry: discover the roots' module files by the declaration index (never a text +// search), walk each with the lens, and hold the contract. +fn argv_word_construction_census_recount() -> ProcessExit { + argv_word_construction_census_check_paths(paths: argv_word_construction_census_root_paths()) +} + +// The discovered modeled roots' module files, in declaration-index order. Shared by the gated +// recount above and the roster emitters below so all see the same population. Parameterized by +// roots: the corpus walk is run ONE ROOT PER PROCESS (a wet lane kills a whole-corpus walk with +// OOM -- the per-process journal of read+parse dispatches is what grows, so the split is by the +// roots boundary, the population's own partition). +fn argv_word_construction_census_root_paths_for_roots(roots: List) -> List { + let decls = module_declaration_facts_live(pool_roots: roots) + let paths_reversed = fold_list(xs: decls, empty: Empty, cons: fn(acc, d) { + let p = declared_module_path(d: d) + if p == "" { acc } else { Cons { head: p, tail: acc } } + }) + list_reverse(xs: paths_reversed) +} + +fn argv_word_construction_census_root_paths() -> List { + argv_word_construction_census_root_paths_for_roots(roots: argv_word_construction_census_roots) +} + +// The roster emitter for a wet-lane run (claim_batch --wet): the same discovered population, each +// module walked by the lens, rows written as TSV to the scratch path. The committed roster data +// decl above is filled FROM this output, never by hand from a text search. Hermetic lanes refuse +// the write by design -- the emitter exists for the recount, not for CI. +fn argv_word_construction_census_recount_emit() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths(), out: "target/argv_word_construction_census_recount.tsv") +} + +fn argv_word_construction_census_recount_emit_extdeps() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/extdeps"]), out: "target/argv_word_construction_census_recount_extdeps.tsv") +} + +fn argv_word_construction_census_recount_emit_gunbc() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/gunbc"]), out: "target/argv_word_construction_census_recount_gunbc.tsv") +} + +fn declared_module_path(d: ModuleDeclarationFact) -> String { + d.path +} + +// The distinct module files named by the committed roster, in first-appearance order: exactly the +// population the bounded-contract claim re-walks, so CI executes the gates at roster size without +// ever walking the whole corpus. +fn argv_word_construction_census_roster_modules() -> List { + let reversed = fold_list(xs: argv_word_construction_census_roster, empty: Empty, cons: fn(acc, r) { + if contains_string(xs: acc, x: r.module_file) { acc } else { Cons { head: r.module_file, tail: acc } } + }) + list_reverse(xs: reversed) +} + +fn contains_string(xs: List, x: String) -> Bool { + fold_list(xs: xs, empty: false, cons: fn(acc, y) { acc || (y == x) }) +} + +type ArgvWordsCollected { + sites: FreeMonoid + refusals: FreeMonoid +} + +fn collect_path(c: ArgvWordsCollected, p: String) -> ArgvWordsCollected { + match argv_word_construction_module_sites(path: p) { + ArgvWordsModuleRefused { path: rp, reason: r } => + ArgvWordsCollected { sites: c.sites, refusals: Cons { head: concat(concat(concat("REFUSED\t", rp), "\t"), r), tail: c.refusals } } + ArgvWordsModuleEstablished { path: _, sites: sites } => + ArgvWordsCollected { sites: fold_list(xs: sites, empty: c.sites, cons: fn(acc, s) { Cons { head: s, tail: acc } }), refusals: c.refusals } + } +} + +// The contract over an explicit path list: the per-module walk is the lens's; the gates run on the +// collected live sites, and every per-module refusal is named, never dropped. Kept separate from +// the discovery so a bounded recount (one module, one directory) is runnable in a claim test +// without the whole corpus. +fn argv_word_construction_census_check_paths(paths: List) -> ProcessExit { + let collected = fold_list(xs: paths, empty: ArgvWordsCollected { sites: Empty, refusals: Empty }, cons: collect_path) + match list_reverse(xs: collected.refusals) { + Empty => argv_word_construction_census_check_sites(sites: list_reverse(xs: collected.sites)) + nonempty => exit_failure(reason: join_lines(rs: nonempty)) + } +} + +fn join_lines(rs: FreeMonoid) -> String { + fold_list(xs: list_reverse(xs: rs), empty: "", cons: fn(acc, l) { if acc == "" { l } else { concat(concat(acc, "\n"), l) } }) +} diff --git a/dag/gunbc/instruments/bisect_tmp/v3_stub.dag b/dag/gunbc/instruments/bisect_tmp/v3_stub.dag new file mode 100644 index 00000000000..3b8c61932c3 --- /dev/null +++ b/dag/gunbc/instruments/bisect_tmp/v3_stub.dag @@ -0,0 +1,174 @@ +module gunbc.instruments.argv_word_construction_census + +// THE TYPED-ARGV CONSTRUCTION CENSUS ROSTER (debt paydown trial, witty-tern-54, 2026-10-03). +// +// The instrument is v2.lens.argv_word_construction; this module commits WHAT IT COUNTS: the +// population of argv-string construction sites over dag/extdeps and dag/gunbc, at identity grain, +// as a typed roster that may only shrink. DESIGN 5: a count over the current tree is not an +// oracle — every row below joins to a live census member at (module_file, decl, decl_kind), a +// row that stops resolving refuses the recount, and the population never grows past the committed +// baseline: new string-built argv is refused, not enrolled. +// +// Recount (the named, re-runnable instrument run): discover the two roots' module files by the +// declaration index, walk each with the lens, and hold the gates over (live, roster, baseline). +// The gates live in the lens; this module owns the committed state: the roster, the baseline +// cardinality, and the sites classified as NOT argv debt with a stated reason. + +import v2.lens.argv_word_construction { + ArgvWordsDeclKind, + ArgvWordsRosterRow, + ArgvWordsSite, + ArgvWordsSiteClass, + ExtdepsInterfaceBuilder, + FnDecl, + DataDecl, + WorkflowCallSite, + ArgvWordsModuleEstablished, + ArgvWordsModuleRefused, + argv_word_construction_gates, + argv_word_construction_module_sites, + argv_word_construction_for_paths +} +import v2.std.algebra { fold_list, list_reverse } +import v2.std.collection { List } +import std.algebra { Cons, Empty, FreeMonoid } +import std.process { ExitSuccess, ProcessExit, exit_failure } +import extdeps.filesystem.filesystem_io { Filesystem } +import v2.std.node { Node } +import v2.std.integer { integer_int_to_decimal_string } +import v2.lens.module_graph { ModuleDeclarationFact, module_declaration_facts_live } + +// The modeled roots. Anything else is refused by the lens, never silently zero. +data argv_word_construction_census_roots: List = ["dag/extdeps", "dag/gunbc"] + +// THE COMMITTED BASELINE: the population the instrument counted over the two roots at the start +// of the trial (origin/main, 2026-10-03). The roster may only shrink past this number. +data argv_word_construction_census_baseline_population: Int = 0 + +// THE ROSTER: one row per live census member. `note` names what the site is and what pays it. +// Kept at zero rows until the first committed recount fills it from the instrument (never by +// hand from a text search). +data argv_word_construction_census_roster: List = [] + +// Sites whose evidence is read and REFUSED AS DEBT with a stated reason: the census classifies, +// it does not silently count. Each entry names its reason; a reason that stops being true must +// delete the entry (the same shrink discipline as the roster). +data argv_word_construction_census_excluded: List = [] + +type ArgvWordsExclusion { + module_file: String + decl: String + reason: String +} + +fn excluded_identity_matches(e: ArgvWordsExclusion, s: ArgvWordsSite) -> Bool { + (e.module_file == s.module_file) && (e.decl == s.decl) +} + +fn site_is_excluded(s: ArgvWordsSite) -> Bool { + fold_list(xs: argv_word_construction_census_excluded, empty: false, cons: fn(acc, e) { acc || excluded_identity_matches(e: e, s: s) }) +} + +// The live census members after classification: exclusions are visible subtractions with reasons. +fn argv_word_construction_census_live(sites: List) -> List { + fold_list(xs: sites, empty: Empty, cons: fn(acc, s) { if site_is_excluded(s: s) { acc } else { Cons { head: s, tail: acc } } }) +} + +// The contract over one recount's live sites. +fn argv_word_construction_census_check_sites(sites: List) -> ProcessExit { + argv_word_construction_gates(live: argv_word_construction_census_live(sites: sites), roster: argv_word_construction_census_roster, baseline_population: argv_word_construction_census_baseline_population) +} + +// The recount entry: discover the roots' module files by the declaration index (never a text +// search), walk each with the lens, and hold the contract. +fn argv_word_construction_census_recount() -> ProcessExit { + argv_word_construction_census_check_paths(paths: argv_word_construction_census_root_paths()) +} + +// The discovered modeled roots' module files, in declaration-index order. Shared by the gated +// recount above and the roster emitters below so all see the same population. Parameterized by +// roots: the corpus walk is run ONE ROOT PER PROCESS (a wet lane kills a whole-corpus walk with +// OOM -- the per-process journal of read+parse dispatches is what grows, so the split is by the +// roots boundary, the population's own partition). +fn argv_word_construction_census_root_paths_for_roots(roots: List) -> List { + let decls = module_declaration_facts_live(pool_roots: roots) + let paths_reversed = fold_list(xs: decls, empty: Empty, cons: fn(acc, d) { + let p = declared_module_path(d: d) + if p == "" { acc } else { Cons { head: p, tail: acc } } + }) + list_reverse(xs: paths_reversed) +} + +fn argv_word_construction_census_root_paths() -> List { + argv_word_construction_census_root_paths_for_roots(roots: argv_word_construction_census_roots) +} + +// The roster emitter for a wet-lane run (claim_batch --wet): the same discovered population, each +// module walked by the lens, rows written as TSV to the scratch path. The committed roster data +// decl above is filled FROM this output, never by hand from a text search. Hermetic lanes refuse +// the write by design -- the emitter exists for the recount, not for CI. +fn argv_word_construction_census_recount_emit() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths(), out: "target/argv_word_construction_census_recount.tsv") +} + +fn argv_word_construction_census_recount_emit_extdeps() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/extdeps"]), out: "target/argv_word_construction_census_recount_extdeps.tsv") +} + +fn argv_word_construction_census_recount_emit_gunbc() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/gunbc"]), out: "target/argv_word_construction_census_recount_gunbc.tsv") +} + +fn declared_module_path(d: ModuleDeclarationFact) -> String { + d.path +} + +// The distinct module files named by the committed roster, in first-appearance order: exactly the +// population the bounded-contract claim re-walks, so CI executes the gates at roster size without +// ever walking the whole corpus. +fn argv_word_construction_census_roster_modules() -> List { + let reversed = fold_list(xs: argv_word_construction_census_roster, empty: Empty, cons: fn(acc, r) { + if contains_string(xs: acc, x: r.module_file) { acc } else { Cons { head: r.module_file, tail: acc } } + }) + list_reverse(xs: reversed) +} + +fn contains_string(xs: List, x: String) -> Bool { + fold_list(xs: xs, empty: false, cons: fn(acc, y) { acc || (y == x) }) +} + +type ArgvWordsCollected { + sites: FreeMonoid + refusals: FreeMonoid +} + +fn collect_path(c: ArgvWordsCollected, p: String) -> ArgvWordsCollected { + match argv_word_construction_module_sites(path: p) { + ArgvWordsModuleRefused { path: rp, reason: r } => + ArgvWordsCollected { sites: c.sites, refusals: Cons { head: concat(concat(concat("REFUSED\t", rp), "\t"), r), tail: c.refusals } } + ArgvWordsModuleEstablished { path: _, sites: sites } => + ArgvWordsCollected { sites: fold_list(xs: sites, empty: c.sites, cons: fn(acc, s) { Cons { head: s, tail: acc } }), refusals: c.refusals } + } +} + +// The contract over an explicit path list: the per-module walk is the lens's; the gates run on the +// collected live sites, and every per-module refusal is named, never dropped. Kept separate from +// the discovery so a bounded recount (one module, one directory) is runnable in a claim test +// without the whole corpus. +fn argv_word_construction_census_check_paths(paths: List) -> ProcessExit { + let collected = fold_list(xs: paths, empty: ArgvWordsCollected { sites: Empty, refusals: Empty }, cons: collect_path) + match list_reverse(xs: collected.refusals) { + Empty => argv_word_construction_census_check_sites(sites: list_reverse(xs: collected.sites)) + nonempty => exit_failure(reason: join_lines(rs: nonempty)) + } +} + +fn join_lines(rs: FreeMonoid) -> String { + fold_list(xs: list_reverse(xs: rs), empty: "", cons: fn(acc, l) { if acc == "" { l } else { concat(concat(acc, "\n"), l) } }) +} + +fn argv_word_construction_census_fixture_facts_dump() -> ProcessExit { + let read = Filesystem.Read(path: "src/v2/test/fixture_roots/argv_word_construction/host_words.dag") + let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: read.content) + if written.success { ExitSuccess } else { exit_failure(reason: concat("fixture facts write refused: ", written.error)) } +} diff --git a/dag/gunbc/instruments/bisect_tmp/v4_fullblock.dag b/dag/gunbc/instruments/bisect_tmp/v4_fullblock.dag new file mode 100644 index 00000000000..3ad17edc935 --- /dev/null +++ b/dag/gunbc/instruments/bisect_tmp/v4_fullblock.dag @@ -0,0 +1,212 @@ +module gunbc.instruments.argv_word_construction_census + +// THE TYPED-ARGV CONSTRUCTION CENSUS ROSTER (debt paydown trial, witty-tern-54, 2026-10-03). +// +// The instrument is v2.lens.argv_word_construction; this module commits WHAT IT COUNTS: the +// population of argv-string construction sites over dag/extdeps and dag/gunbc, at identity grain, +// as a typed roster that may only shrink. DESIGN 5: a count over the current tree is not an +// oracle — every row below joins to a live census member at (module_file, decl, decl_kind), a +// row that stops resolving refuses the recount, and the population never grows past the committed +// baseline: new string-built argv is refused, not enrolled. +// +// Recount (the named, re-runnable instrument run): discover the two roots' module files by the +// declaration index, walk each with the lens, and hold the gates over (live, roster, baseline). +// The gates live in the lens; this module owns the committed state: the roster, the baseline +// cardinality, and the sites classified as NOT argv debt with a stated reason. + +import v2.lens.argv_word_construction { + ArgvWordsDeclKind, + ArgvWordsRosterRow, + ArgvWordsSite, + ArgvWordsSiteClass, + ExtdepsInterfaceBuilder, + FnDecl, + DataDecl, + WorkflowCallSite, + ArgvWordsModuleEstablished, + ArgvWordsModuleRefused, + argv_word_construction_gates, + argv_word_construction_module_sites, + argv_word_construction_for_paths +} +import v2.std.algebra { fold_list, list_reverse } +import v2.std.collection { List } +import std.algebra { Cons, Empty, FreeMonoid } +import std.process { ExitSuccess, ProcessExit, exit_failure } +import extdeps.filesystem.filesystem_io { Filesystem } +import v2.std.node { Node } +import v2.std.integer { integer_int_to_decimal_string } +import v2.lens.module_graph { ModuleDeclarationFact, module_declaration_facts_live } + +// The modeled roots. Anything else is refused by the lens, never silently zero. +data argv_word_construction_census_roots: List = ["dag/extdeps", "dag/gunbc"] + +// THE COMMITTED BASELINE: the population the instrument counted over the two roots at the start +// of the trial (origin/main, 2026-10-03). The roster may only shrink past this number. +data argv_word_construction_census_baseline_population: Int = 0 + +// THE ROSTER: one row per live census member. `note` names what the site is and what pays it. +// Kept at zero rows until the first committed recount fills it from the instrument (never by +// hand from a text search). +data argv_word_construction_census_roster: List = [] + +// Sites whose evidence is read and REFUSED AS DEBT with a stated reason: the census classifies, +// it does not silently count. Each entry names its reason; a reason that stops being true must +// delete the entry (the same shrink discipline as the roster). +data argv_word_construction_census_excluded: List = [] + +type ArgvWordsExclusion { + module_file: String + decl: String + reason: String +} + +fn excluded_identity_matches(e: ArgvWordsExclusion, s: ArgvWordsSite) -> Bool { + (e.module_file == s.module_file) && (e.decl == s.decl) +} + +fn site_is_excluded(s: ArgvWordsSite) -> Bool { + fold_list(xs: argv_word_construction_census_excluded, empty: false, cons: fn(acc, e) { acc || excluded_identity_matches(e: e, s: s) }) +} + +// The live census members after classification: exclusions are visible subtractions with reasons. +fn argv_word_construction_census_live(sites: List) -> List { + fold_list(xs: sites, empty: Empty, cons: fn(acc, s) { if site_is_excluded(s: s) { acc } else { Cons { head: s, tail: acc } } }) +} + +// The contract over one recount's live sites. +fn argv_word_construction_census_check_sites(sites: List) -> ProcessExit { + argv_word_construction_gates(live: argv_word_construction_census_live(sites: sites), roster: argv_word_construction_census_roster, baseline_population: argv_word_construction_census_baseline_population) +} + +// The recount entry: discover the roots' module files by the declaration index (never a text +// search), walk each with the lens, and hold the contract. +fn argv_word_construction_census_recount() -> ProcessExit { + argv_word_construction_census_check_paths(paths: argv_word_construction_census_root_paths()) +} + +// The discovered modeled roots' module files, in declaration-index order. Shared by the gated +// recount above and the roster emitters below so all see the same population. Parameterized by +// roots: the corpus walk is run ONE ROOT PER PROCESS (a wet lane kills a whole-corpus walk with +// OOM -- the per-process journal of read+parse dispatches is what grows, so the split is by the +// roots boundary, the population's own partition). +fn argv_word_construction_census_root_paths_for_roots(roots: List) -> List { + let decls = module_declaration_facts_live(pool_roots: roots) + let paths_reversed = fold_list(xs: decls, empty: Empty, cons: fn(acc, d) { + let p = declared_module_path(d: d) + if p == "" { acc } else { Cons { head: p, tail: acc } } + }) + list_reverse(xs: paths_reversed) +} + +fn argv_word_construction_census_root_paths() -> List { + argv_word_construction_census_root_paths_for_roots(roots: argv_word_construction_census_roots) +} + +// The roster emitter for a wet-lane run (claim_batch --wet): the same discovered population, each +// module walked by the lens, rows written as TSV to the scratch path. The committed roster data +// decl above is filled FROM this output, never by hand from a text search. Hermetic lanes refuse +// the write by design -- the emitter exists for the recount, not for CI. +fn argv_word_construction_census_recount_emit() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths(), out: "target/argv_word_construction_census_recount.tsv") +} + +fn argv_word_construction_census_recount_emit_extdeps() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/extdeps"]), out: "target/argv_word_construction_census_recount_extdeps.tsv") +} + +fn argv_word_construction_census_recount_emit_gunbc() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/gunbc"]), out: "target/argv_word_construction_census_recount_gunbc.tsv") +} + +fn declared_module_path(d: ModuleDeclarationFact) -> String { + d.path +} + +// The distinct module files named by the committed roster, in first-appearance order: exactly the +// population the bounded-contract claim re-walks, so CI executes the gates at roster size without +// ever walking the whole corpus. +fn argv_word_construction_census_roster_modules() -> List { + let reversed = fold_list(xs: argv_word_construction_census_roster, empty: Empty, cons: fn(acc, r) { + if contains_string(xs: acc, x: r.module_file) { acc } else { Cons { head: r.module_file, tail: acc } } + }) + list_reverse(xs: reversed) +} + +fn contains_string(xs: List, x: String) -> Bool { + fold_list(xs: xs, empty: false, cons: fn(acc, y) { acc || (y == x) }) +} + +type ArgvWordsCollected { + sites: FreeMonoid + refusals: FreeMonoid +} + +fn collect_path(c: ArgvWordsCollected, p: String) -> ArgvWordsCollected { + match argv_word_construction_module_sites(path: p) { + ArgvWordsModuleRefused { path: rp, reason: r } => + ArgvWordsCollected { sites: c.sites, refusals: Cons { head: concat(concat(concat("REFUSED\t", rp), "\t"), r), tail: c.refusals } } + ArgvWordsModuleEstablished { path: _, sites: sites } => + ArgvWordsCollected { sites: fold_list(xs: sites, empty: c.sites, cons: fn(acc, s) { Cons { head: s, tail: acc } }), refusals: c.refusals } + } +} + +// The contract over an explicit path list: the per-module walk is the lens's; the gates run on the +// collected live sites, and every per-module refusal is named, never dropped. Kept separate from +// the discovery so a bounded recount (one module, one directory) is runnable in a claim test +// without the whole corpus. +fn argv_word_construction_census_check_paths(paths: List) -> ProcessExit { + let collected = fold_list(xs: paths, empty: ArgvWordsCollected { sites: Empty, refusals: Empty }, cons: collect_path) + match list_reverse(xs: collected.refusals) { + Empty => argv_word_construction_census_check_sites(sites: list_reverse(xs: collected.sites)) + nonempty => exit_failure(reason: join_lines(rs: nonempty)) + } +} + +fn join_lines(rs: FreeMonoid) -> String { + fold_list(xs: list_reverse(xs: rs), empty: "", cons: fn(acc, l) { if acc == "" { l } else { concat(concat(acc, "\n"), l) } }) +} +// The fixture root is OUTSIDE the modeled roots by design (the boundary-refusal claim pins that), +// so the walk above never sees it and for_paths refuses any module outside the roots. The +// fixture's facts are therefore read at the FACT level -- the same read/parse/facts_of route the +// fixture claims use -- and rendered where a human reads them: one TSV row per fact with every +// field the fixture claims pin. This is the observation those claims' expectations are aligned +// against, not a hand-maintained parallel list of what the lens is supposed to say. +fn argv_word_construction_census_fixture_facts_dump() -> ProcessExit { + let read = Filesystem.Read(path: "src/v2/test/fixture_roots/argv_word_construction/host_words.dag") + match read.success { + false => exit_failure(reason: "fixture facts absent: fixture unreadable") + true => + match raw_tree_of_text(text: read.content) { + RawTreeNotEstablished { cause: cause } => exit_failure(reason: concat("fixture unparseable: ", cause)) + RawTreeEstablished { root: root } => fixture_facts_write(root: root) + } + } +} + +fn fixture_facts_write(root: Node) -> ProcessExit { + let rows = fold_list(xs: facts_of(root: root), empty: "decl\tkind\tcounted\tcandidates_n\tcandidates\tflag_words_n\treturns_argv_words\tlist_string_params\tlist_string_data", cons: fn(acc, f) { + concat(acc, concat("\n", fixture_fact_row(f: f))) + }) + let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: rows) + if written.success { ExitSuccess } else { exit_failure(reason: concat("fixture facts write refused: ", written.error)) } +} + +fn fixture_fact_row(f: ArgvWordsFact) -> String { + concat(f.decl, concat("\t", concat(fact_kind_word(k: f.decl_kind), concat("\t", concat(bool_word(b: f.counted), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.candidates)), concat("\t", concat(join(f.candidates, ";"), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.flag_words)), concat("\t", concat(bool_word(b: f.returns_argv_words), concat("\t", concat(integer_int_to_decimal_string(value: f.list_string_params), concat("\t", bool_word(b: f.list_string_data)))))))))))))))))) +} + +fn fact_kind_word(k: ArgvWordsDeclKind) -> String { + match k { + FnDecl => "FnDecl" + DataDecl => "DataDecl" + } +} + +fn bool_word(b: Bool) -> String { + match b { + true => "true" + false => "false" + } +} + From 9e9142a69ee3a8624d7c4971c995a2830b1bab77 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 6 Oct 2026 12:50:05 +0000 Subject: [PATCH 55/65] TEMP bisect round 2 (removed after reading) --- .../instruments/bisect_tmp2/v5_dumpfn.dag | 192 +++++++++++++++++ .../instruments/bisect_tmp2/v6_writefn.dag | 188 +++++++++++++++++ .../instruments/bisect_tmp2/v7_rowfn.dag | 184 +++++++++++++++++ .../instruments/bisect_tmp2/v8_kinds.dag | 194 ++++++++++++++++++ 4 files changed, 758 insertions(+) create mode 100644 dag/gunbc/instruments/bisect_tmp2/v5_dumpfn.dag create mode 100644 dag/gunbc/instruments/bisect_tmp2/v6_writefn.dag create mode 100644 dag/gunbc/instruments/bisect_tmp2/v7_rowfn.dag create mode 100644 dag/gunbc/instruments/bisect_tmp2/v8_kinds.dag diff --git a/dag/gunbc/instruments/bisect_tmp2/v5_dumpfn.dag b/dag/gunbc/instruments/bisect_tmp2/v5_dumpfn.dag new file mode 100644 index 00000000000..bc6795f87de --- /dev/null +++ b/dag/gunbc/instruments/bisect_tmp2/v5_dumpfn.dag @@ -0,0 +1,192 @@ +module gunbc.instruments.argv_word_construction_census + +// THE TYPED-ARGV CONSTRUCTION CENSUS ROSTER (debt paydown trial, witty-tern-54, 2026-10-03). +// +// The instrument is v2.lens.argv_word_construction; this module commits WHAT IT COUNTS: the +// population of argv-string construction sites over dag/extdeps and dag/gunbc, at identity grain, +// as a typed roster that may only shrink. DESIGN 5: a count over the current tree is not an +// oracle — every row below joins to a live census member at (module_file, decl, decl_kind), a +// row that stops resolving refuses the recount, and the population never grows past the committed +// baseline: new string-built argv is refused, not enrolled. +// +// Recount (the named, re-runnable instrument run): discover the two roots' module files by the +// declaration index, walk each with the lens, and hold the gates over (live, roster, baseline). +// The gates live in the lens; this module owns the committed state: the roster, the baseline +// cardinality, and the sites classified as NOT argv debt with a stated reason. + +import v2.lens.argv_word_construction { + ArgvWordsDeclKind, + ArgvWordsRosterRow, + ArgvWordsSite, + ArgvWordsSiteClass, + ExtdepsInterfaceBuilder, + FnDecl, + DataDecl, + WorkflowCallSite, + ArgvWordsModuleEstablished, + ArgvWordsModuleRefused, + argv_word_construction_gates, + argv_word_construction_module_sites, + argv_word_construction_for_paths +} +import v2.std.algebra { fold_list, list_reverse } +import v2.std.collection { List } +import std.algebra { Cons, Empty, FreeMonoid } +import std.process { ExitSuccess, ProcessExit, exit_failure } +import extdeps.filesystem.filesystem_io { Filesystem } +import v2.std.node { Node } +import v2.std.integer { integer_int_to_decimal_string } +import v2.lens.module_graph { ModuleDeclarationFact, module_declaration_facts_live } + +// The modeled roots. Anything else is refused by the lens, never silently zero. +data argv_word_construction_census_roots: List = ["dag/extdeps", "dag/gunbc"] + +// THE COMMITTED BASELINE: the population the instrument counted over the two roots at the start +// of the trial (origin/main, 2026-10-03). The roster may only shrink past this number. +data argv_word_construction_census_baseline_population: Int = 0 + +// THE ROSTER: one row per live census member. `note` names what the site is and what pays it. +// Kept at zero rows until the first committed recount fills it from the instrument (never by +// hand from a text search). +data argv_word_construction_census_roster: List = [] + +// Sites whose evidence is read and REFUSED AS DEBT with a stated reason: the census classifies, +// it does not silently count. Each entry names its reason; a reason that stops being true must +// delete the entry (the same shrink discipline as the roster). +data argv_word_construction_census_excluded: List = [] + +type ArgvWordsExclusion { + module_file: String + decl: String + reason: String +} + +fn excluded_identity_matches(e: ArgvWordsExclusion, s: ArgvWordsSite) -> Bool { + (e.module_file == s.module_file) && (e.decl == s.decl) +} + +fn site_is_excluded(s: ArgvWordsSite) -> Bool { + fold_list(xs: argv_word_construction_census_excluded, empty: false, cons: fn(acc, e) { acc || excluded_identity_matches(e: e, s: s) }) +} + +// The live census members after classification: exclusions are visible subtractions with reasons. +fn argv_word_construction_census_live(sites: List) -> List { + fold_list(xs: sites, empty: Empty, cons: fn(acc, s) { if site_is_excluded(s: s) { acc } else { Cons { head: s, tail: acc } } }) +} + +// The contract over one recount's live sites. +fn argv_word_construction_census_check_sites(sites: List) -> ProcessExit { + argv_word_construction_gates(live: argv_word_construction_census_live(sites: sites), roster: argv_word_construction_census_roster, baseline_population: argv_word_construction_census_baseline_population) +} + +// The recount entry: discover the roots' module files by the declaration index (never a text +// search), walk each with the lens, and hold the contract. +fn argv_word_construction_census_recount() -> ProcessExit { + argv_word_construction_census_check_paths(paths: argv_word_construction_census_root_paths()) +} + +// The discovered modeled roots' module files, in declaration-index order. Shared by the gated +// recount above and the roster emitters below so all see the same population. Parameterized by +// roots: the corpus walk is run ONE ROOT PER PROCESS (a wet lane kills a whole-corpus walk with +// OOM -- the per-process journal of read+parse dispatches is what grows, so the split is by the +// roots boundary, the population's own partition). +fn argv_word_construction_census_root_paths_for_roots(roots: List) -> List { + let decls = module_declaration_facts_live(pool_roots: roots) + let paths_reversed = fold_list(xs: decls, empty: Empty, cons: fn(acc, d) { + let p = declared_module_path(d: d) + if p == "" { acc } else { Cons { head: p, tail: acc } } + }) + list_reverse(xs: paths_reversed) +} + +fn argv_word_construction_census_root_paths() -> List { + argv_word_construction_census_root_paths_for_roots(roots: argv_word_construction_census_roots) +} + +// The roster emitter for a wet-lane run (claim_batch --wet): the same discovered population, each +// module walked by the lens, rows written as TSV to the scratch path. The committed roster data +// decl above is filled FROM this output, never by hand from a text search. Hermetic lanes refuse +// the write by design -- the emitter exists for the recount, not for CI. +fn argv_word_construction_census_recount_emit() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths(), out: "target/argv_word_construction_census_recount.tsv") +} + +fn argv_word_construction_census_recount_emit_extdeps() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/extdeps"]), out: "target/argv_word_construction_census_recount_extdeps.tsv") +} + +fn argv_word_construction_census_recount_emit_gunbc() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/gunbc"]), out: "target/argv_word_construction_census_recount_gunbc.tsv") +} + +fn declared_module_path(d: ModuleDeclarationFact) -> String { + d.path +} + +// The distinct module files named by the committed roster, in first-appearance order: exactly the +// population the bounded-contract claim re-walks, so CI executes the gates at roster size without +// ever walking the whole corpus. +fn argv_word_construction_census_roster_modules() -> List { + let reversed = fold_list(xs: argv_word_construction_census_roster, empty: Empty, cons: fn(acc, r) { + if contains_string(xs: acc, x: r.module_file) { acc } else { Cons { head: r.module_file, tail: acc } } + }) + list_reverse(xs: reversed) +} + +fn contains_string(xs: List, x: String) -> Bool { + fold_list(xs: xs, empty: false, cons: fn(acc, y) { acc || (y == x) }) +} + +type ArgvWordsCollected { + sites: FreeMonoid + refusals: FreeMonoid +} + +fn collect_path(c: ArgvWordsCollected, p: String) -> ArgvWordsCollected { + match argv_word_construction_module_sites(path: p) { + ArgvWordsModuleRefused { path: rp, reason: r } => + ArgvWordsCollected { sites: c.sites, refusals: Cons { head: concat(concat(concat("REFUSED\t", rp), "\t"), r), tail: c.refusals } } + ArgvWordsModuleEstablished { path: _, sites: sites } => + ArgvWordsCollected { sites: fold_list(xs: sites, empty: c.sites, cons: fn(acc, s) { Cons { head: s, tail: acc } }), refusals: c.refusals } + } +} + +// The contract over an explicit path list: the per-module walk is the lens's; the gates run on the +// collected live sites, and every per-module refusal is named, never dropped. Kept separate from +// the discovery so a bounded recount (one module, one directory) is runnable in a claim test +// without the whole corpus. +fn argv_word_construction_census_check_paths(paths: List) -> ProcessExit { + let collected = fold_list(xs: paths, empty: ArgvWordsCollected { sites: Empty, refusals: Empty }, cons: collect_path) + match list_reverse(xs: collected.refusals) { + Empty => argv_word_construction_census_check_sites(sites: list_reverse(xs: collected.sites)) + nonempty => exit_failure(reason: join_lines(rs: nonempty)) + } +} + +fn join_lines(rs: FreeMonoid) -> String { + fold_list(xs: list_reverse(xs: rs), empty: "", cons: fn(acc, l) { if acc == "" { l } else { concat(concat(acc, "\n"), l) } }) +} + +fn argv_word_construction_census_fixture_facts_dump() -> ProcessExit { + let read = Filesystem.Read(path: "src/v2/test/fixture_roots/argv_word_construction/host_words.dag") + let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: read.content) + if written.success { ExitSuccess } else { exit_failure(reason: concat("fixture facts write refused: ", written.error)) } +} +// The fixture root is OUTSIDE the modeled roots by design (the boundary-refusal claim pins that), +// so the walk above never sees it and for_paths refuses any module outside the roots. The +// fixture's facts are therefore read at the FACT level -- the same read/parse/facts_of route the +// fixture claims use -- and rendered where a human reads them: one TSV row per fact with every +// field the fixture claims pin. This is the observation those claims' expectations are aligned +// against, not a hand-maintained parallel list of what the lens is supposed to say. +fn argv_word_construction_census_fixture_facts_dump() -> ProcessExit { + let read = Filesystem.Read(path: "src/v2/test/fixture_roots/argv_word_construction/host_words.dag") + match read.success { + false => exit_failure(reason: "fixture facts absent: fixture unreadable") + true => + match raw_tree_of_text(text: read.content) { + RawTreeNotEstablished { cause: cause } => exit_failure(reason: concat("fixture unparseable: ", cause)) + RawTreeEstablished { root: root } => fixture_facts_write(root: root) + } + } +} + diff --git a/dag/gunbc/instruments/bisect_tmp2/v6_writefn.dag b/dag/gunbc/instruments/bisect_tmp2/v6_writefn.dag new file mode 100644 index 00000000000..a4cc42005f3 --- /dev/null +++ b/dag/gunbc/instruments/bisect_tmp2/v6_writefn.dag @@ -0,0 +1,188 @@ +module gunbc.instruments.argv_word_construction_census + +// THE TYPED-ARGV CONSTRUCTION CENSUS ROSTER (debt paydown trial, witty-tern-54, 2026-10-03). +// +// The instrument is v2.lens.argv_word_construction; this module commits WHAT IT COUNTS: the +// population of argv-string construction sites over dag/extdeps and dag/gunbc, at identity grain, +// as a typed roster that may only shrink. DESIGN 5: a count over the current tree is not an +// oracle — every row below joins to a live census member at (module_file, decl, decl_kind), a +// row that stops resolving refuses the recount, and the population never grows past the committed +// baseline: new string-built argv is refused, not enrolled. +// +// Recount (the named, re-runnable instrument run): discover the two roots' module files by the +// declaration index, walk each with the lens, and hold the gates over (live, roster, baseline). +// The gates live in the lens; this module owns the committed state: the roster, the baseline +// cardinality, and the sites classified as NOT argv debt with a stated reason. + +import v2.lens.argv_word_construction { + ArgvWordsDeclKind, + ArgvWordsRosterRow, + ArgvWordsSite, + ArgvWordsSiteClass, + ExtdepsInterfaceBuilder, + FnDecl, + DataDecl, + WorkflowCallSite, + ArgvWordsModuleEstablished, + ArgvWordsModuleRefused, + argv_word_construction_gates, + argv_word_construction_module_sites, + argv_word_construction_for_paths +} +import v2.std.algebra { fold_list, list_reverse } +import v2.std.collection { List } +import std.algebra { Cons, Empty, FreeMonoid } +import std.process { ExitSuccess, ProcessExit, exit_failure } +import extdeps.filesystem.filesystem_io { Filesystem } +import v2.std.node { Node } +import v2.std.integer { integer_int_to_decimal_string } +import v2.lens.module_graph { ModuleDeclarationFact, module_declaration_facts_live } + +// The modeled roots. Anything else is refused by the lens, never silently zero. +data argv_word_construction_census_roots: List = ["dag/extdeps", "dag/gunbc"] + +// THE COMMITTED BASELINE: the population the instrument counted over the two roots at the start +// of the trial (origin/main, 2026-10-03). The roster may only shrink past this number. +data argv_word_construction_census_baseline_population: Int = 0 + +// THE ROSTER: one row per live census member. `note` names what the site is and what pays it. +// Kept at zero rows until the first committed recount fills it from the instrument (never by +// hand from a text search). +data argv_word_construction_census_roster: List = [] + +// Sites whose evidence is read and REFUSED AS DEBT with a stated reason: the census classifies, +// it does not silently count. Each entry names its reason; a reason that stops being true must +// delete the entry (the same shrink discipline as the roster). +data argv_word_construction_census_excluded: List = [] + +type ArgvWordsExclusion { + module_file: String + decl: String + reason: String +} + +fn excluded_identity_matches(e: ArgvWordsExclusion, s: ArgvWordsSite) -> Bool { + (e.module_file == s.module_file) && (e.decl == s.decl) +} + +fn site_is_excluded(s: ArgvWordsSite) -> Bool { + fold_list(xs: argv_word_construction_census_excluded, empty: false, cons: fn(acc, e) { acc || excluded_identity_matches(e: e, s: s) }) +} + +// The live census members after classification: exclusions are visible subtractions with reasons. +fn argv_word_construction_census_live(sites: List) -> List { + fold_list(xs: sites, empty: Empty, cons: fn(acc, s) { if site_is_excluded(s: s) { acc } else { Cons { head: s, tail: acc } } }) +} + +// The contract over one recount's live sites. +fn argv_word_construction_census_check_sites(sites: List) -> ProcessExit { + argv_word_construction_gates(live: argv_word_construction_census_live(sites: sites), roster: argv_word_construction_census_roster, baseline_population: argv_word_construction_census_baseline_population) +} + +// The recount entry: discover the roots' module files by the declaration index (never a text +// search), walk each with the lens, and hold the contract. +fn argv_word_construction_census_recount() -> ProcessExit { + argv_word_construction_census_check_paths(paths: argv_word_construction_census_root_paths()) +} + +// The discovered modeled roots' module files, in declaration-index order. Shared by the gated +// recount above and the roster emitters below so all see the same population. Parameterized by +// roots: the corpus walk is run ONE ROOT PER PROCESS (a wet lane kills a whole-corpus walk with +// OOM -- the per-process journal of read+parse dispatches is what grows, so the split is by the +// roots boundary, the population's own partition). +fn argv_word_construction_census_root_paths_for_roots(roots: List) -> List { + let decls = module_declaration_facts_live(pool_roots: roots) + let paths_reversed = fold_list(xs: decls, empty: Empty, cons: fn(acc, d) { + let p = declared_module_path(d: d) + if p == "" { acc } else { Cons { head: p, tail: acc } } + }) + list_reverse(xs: paths_reversed) +} + +fn argv_word_construction_census_root_paths() -> List { + argv_word_construction_census_root_paths_for_roots(roots: argv_word_construction_census_roots) +} + +// The roster emitter for a wet-lane run (claim_batch --wet): the same discovered population, each +// module walked by the lens, rows written as TSV to the scratch path. The committed roster data +// decl above is filled FROM this output, never by hand from a text search. Hermetic lanes refuse +// the write by design -- the emitter exists for the recount, not for CI. +fn argv_word_construction_census_recount_emit() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths(), out: "target/argv_word_construction_census_recount.tsv") +} + +fn argv_word_construction_census_recount_emit_extdeps() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/extdeps"]), out: "target/argv_word_construction_census_recount_extdeps.tsv") +} + +fn argv_word_construction_census_recount_emit_gunbc() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/gunbc"]), out: "target/argv_word_construction_census_recount_gunbc.tsv") +} + +fn declared_module_path(d: ModuleDeclarationFact) -> String { + d.path +} + +// The distinct module files named by the committed roster, in first-appearance order: exactly the +// population the bounded-contract claim re-walks, so CI executes the gates at roster size without +// ever walking the whole corpus. +fn argv_word_construction_census_roster_modules() -> List { + let reversed = fold_list(xs: argv_word_construction_census_roster, empty: Empty, cons: fn(acc, r) { + if contains_string(xs: acc, x: r.module_file) { acc } else { Cons { head: r.module_file, tail: acc } } + }) + list_reverse(xs: reversed) +} + +fn contains_string(xs: List, x: String) -> Bool { + fold_list(xs: xs, empty: false, cons: fn(acc, y) { acc || (y == x) }) +} + +type ArgvWordsCollected { + sites: FreeMonoid + refusals: FreeMonoid +} + +fn collect_path(c: ArgvWordsCollected, p: String) -> ArgvWordsCollected { + match argv_word_construction_module_sites(path: p) { + ArgvWordsModuleRefused { path: rp, reason: r } => + ArgvWordsCollected { sites: c.sites, refusals: Cons { head: concat(concat(concat("REFUSED\t", rp), "\t"), r), tail: c.refusals } } + ArgvWordsModuleEstablished { path: _, sites: sites } => + ArgvWordsCollected { sites: fold_list(xs: sites, empty: c.sites, cons: fn(acc, s) { Cons { head: s, tail: acc } }), refusals: c.refusals } + } +} + +// The contract over an explicit path list: the per-module walk is the lens's; the gates run on the +// collected live sites, and every per-module refusal is named, never dropped. Kept separate from +// the discovery so a bounded recount (one module, one directory) is runnable in a claim test +// without the whole corpus. +fn argv_word_construction_census_check_paths(paths: List) -> ProcessExit { + let collected = fold_list(xs: paths, empty: ArgvWordsCollected { sites: Empty, refusals: Empty }, cons: collect_path) + match list_reverse(xs: collected.refusals) { + Empty => argv_word_construction_census_check_sites(sites: list_reverse(xs: collected.sites)) + nonempty => exit_failure(reason: join_lines(rs: nonempty)) + } +} + +fn join_lines(rs: FreeMonoid) -> String { + fold_list(xs: list_reverse(xs: rs), empty: "", cons: fn(acc, l) { if acc == "" { l } else { concat(concat(acc, "\n"), l) } }) +} + +fn argv_word_construction_census_fixture_facts_dump() -> ProcessExit { + let read = Filesystem.Read(path: "src/v2/test/fixture_roots/argv_word_construction/host_words.dag") + let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: read.content) + if written.success { ExitSuccess } else { exit_failure(reason: concat("fixture facts write refused: ", written.error)) } +} +// The fixture root is OUTSIDE the modeled roots by design (the boundary-refusal claim pins that), +// so the walk above never sees it and for_paths refuses any module outside the roots. The +// fixture's facts are therefore read at the FACT level -- the same read/parse/facts_of route the +// fixture claims use -- and rendered where a human reads them: one TSV row per fact with every +// field the fixture claims pin. This is the observation those claims' expectations are aligned +// against, not a hand-maintained parallel list of what the lens is supposed to say. +fn fixture_facts_write(root: Node) -> ProcessExit { + let rows = fold_list(xs: facts_of(root: root), empty: "decl\tkind\tcounted\tcandidates_n\tcandidates\tflag_words_n\treturns_argv_words\tlist_string_params\tlist_string_data", cons: fn(acc, f) { + concat(acc, concat("\n", fixture_fact_row(f: f))) + }) + let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: rows) + if written.success { ExitSuccess } else { exit_failure(reason: concat("fixture facts write refused: ", written.error)) } +} + diff --git a/dag/gunbc/instruments/bisect_tmp2/v7_rowfn.dag b/dag/gunbc/instruments/bisect_tmp2/v7_rowfn.dag new file mode 100644 index 00000000000..1f7fca9a0b6 --- /dev/null +++ b/dag/gunbc/instruments/bisect_tmp2/v7_rowfn.dag @@ -0,0 +1,184 @@ +module gunbc.instruments.argv_word_construction_census + +// THE TYPED-ARGV CONSTRUCTION CENSUS ROSTER (debt paydown trial, witty-tern-54, 2026-10-03). +// +// The instrument is v2.lens.argv_word_construction; this module commits WHAT IT COUNTS: the +// population of argv-string construction sites over dag/extdeps and dag/gunbc, at identity grain, +// as a typed roster that may only shrink. DESIGN 5: a count over the current tree is not an +// oracle — every row below joins to a live census member at (module_file, decl, decl_kind), a +// row that stops resolving refuses the recount, and the population never grows past the committed +// baseline: new string-built argv is refused, not enrolled. +// +// Recount (the named, re-runnable instrument run): discover the two roots' module files by the +// declaration index, walk each with the lens, and hold the gates over (live, roster, baseline). +// The gates live in the lens; this module owns the committed state: the roster, the baseline +// cardinality, and the sites classified as NOT argv debt with a stated reason. + +import v2.lens.argv_word_construction { + ArgvWordsDeclKind, + ArgvWordsRosterRow, + ArgvWordsSite, + ArgvWordsSiteClass, + ExtdepsInterfaceBuilder, + FnDecl, + DataDecl, + WorkflowCallSite, + ArgvWordsModuleEstablished, + ArgvWordsModuleRefused, + argv_word_construction_gates, + argv_word_construction_module_sites, + argv_word_construction_for_paths +} +import v2.std.algebra { fold_list, list_reverse } +import v2.std.collection { List } +import std.algebra { Cons, Empty, FreeMonoid } +import std.process { ExitSuccess, ProcessExit, exit_failure } +import extdeps.filesystem.filesystem_io { Filesystem } +import v2.std.node { Node } +import v2.std.integer { integer_int_to_decimal_string } +import v2.lens.module_graph { ModuleDeclarationFact, module_declaration_facts_live } + +// The modeled roots. Anything else is refused by the lens, never silently zero. +data argv_word_construction_census_roots: List = ["dag/extdeps", "dag/gunbc"] + +// THE COMMITTED BASELINE: the population the instrument counted over the two roots at the start +// of the trial (origin/main, 2026-10-03). The roster may only shrink past this number. +data argv_word_construction_census_baseline_population: Int = 0 + +// THE ROSTER: one row per live census member. `note` names what the site is and what pays it. +// Kept at zero rows until the first committed recount fills it from the instrument (never by +// hand from a text search). +data argv_word_construction_census_roster: List = [] + +// Sites whose evidence is read and REFUSED AS DEBT with a stated reason: the census classifies, +// it does not silently count. Each entry names its reason; a reason that stops being true must +// delete the entry (the same shrink discipline as the roster). +data argv_word_construction_census_excluded: List = [] + +type ArgvWordsExclusion { + module_file: String + decl: String + reason: String +} + +fn excluded_identity_matches(e: ArgvWordsExclusion, s: ArgvWordsSite) -> Bool { + (e.module_file == s.module_file) && (e.decl == s.decl) +} + +fn site_is_excluded(s: ArgvWordsSite) -> Bool { + fold_list(xs: argv_word_construction_census_excluded, empty: false, cons: fn(acc, e) { acc || excluded_identity_matches(e: e, s: s) }) +} + +// The live census members after classification: exclusions are visible subtractions with reasons. +fn argv_word_construction_census_live(sites: List) -> List { + fold_list(xs: sites, empty: Empty, cons: fn(acc, s) { if site_is_excluded(s: s) { acc } else { Cons { head: s, tail: acc } } }) +} + +// The contract over one recount's live sites. +fn argv_word_construction_census_check_sites(sites: List) -> ProcessExit { + argv_word_construction_gates(live: argv_word_construction_census_live(sites: sites), roster: argv_word_construction_census_roster, baseline_population: argv_word_construction_census_baseline_population) +} + +// The recount entry: discover the roots' module files by the declaration index (never a text +// search), walk each with the lens, and hold the contract. +fn argv_word_construction_census_recount() -> ProcessExit { + argv_word_construction_census_check_paths(paths: argv_word_construction_census_root_paths()) +} + +// The discovered modeled roots' module files, in declaration-index order. Shared by the gated +// recount above and the roster emitters below so all see the same population. Parameterized by +// roots: the corpus walk is run ONE ROOT PER PROCESS (a wet lane kills a whole-corpus walk with +// OOM -- the per-process journal of read+parse dispatches is what grows, so the split is by the +// roots boundary, the population's own partition). +fn argv_word_construction_census_root_paths_for_roots(roots: List) -> List { + let decls = module_declaration_facts_live(pool_roots: roots) + let paths_reversed = fold_list(xs: decls, empty: Empty, cons: fn(acc, d) { + let p = declared_module_path(d: d) + if p == "" { acc } else { Cons { head: p, tail: acc } } + }) + list_reverse(xs: paths_reversed) +} + +fn argv_word_construction_census_root_paths() -> List { + argv_word_construction_census_root_paths_for_roots(roots: argv_word_construction_census_roots) +} + +// The roster emitter for a wet-lane run (claim_batch --wet): the same discovered population, each +// module walked by the lens, rows written as TSV to the scratch path. The committed roster data +// decl above is filled FROM this output, never by hand from a text search. Hermetic lanes refuse +// the write by design -- the emitter exists for the recount, not for CI. +fn argv_word_construction_census_recount_emit() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths(), out: "target/argv_word_construction_census_recount.tsv") +} + +fn argv_word_construction_census_recount_emit_extdeps() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/extdeps"]), out: "target/argv_word_construction_census_recount_extdeps.tsv") +} + +fn argv_word_construction_census_recount_emit_gunbc() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/gunbc"]), out: "target/argv_word_construction_census_recount_gunbc.tsv") +} + +fn declared_module_path(d: ModuleDeclarationFact) -> String { + d.path +} + +// The distinct module files named by the committed roster, in first-appearance order: exactly the +// population the bounded-contract claim re-walks, so CI executes the gates at roster size without +// ever walking the whole corpus. +fn argv_word_construction_census_roster_modules() -> List { + let reversed = fold_list(xs: argv_word_construction_census_roster, empty: Empty, cons: fn(acc, r) { + if contains_string(xs: acc, x: r.module_file) { acc } else { Cons { head: r.module_file, tail: acc } } + }) + list_reverse(xs: reversed) +} + +fn contains_string(xs: List, x: String) -> Bool { + fold_list(xs: xs, empty: false, cons: fn(acc, y) { acc || (y == x) }) +} + +type ArgvWordsCollected { + sites: FreeMonoid + refusals: FreeMonoid +} + +fn collect_path(c: ArgvWordsCollected, p: String) -> ArgvWordsCollected { + match argv_word_construction_module_sites(path: p) { + ArgvWordsModuleRefused { path: rp, reason: r } => + ArgvWordsCollected { sites: c.sites, refusals: Cons { head: concat(concat(concat("REFUSED\t", rp), "\t"), r), tail: c.refusals } } + ArgvWordsModuleEstablished { path: _, sites: sites } => + ArgvWordsCollected { sites: fold_list(xs: sites, empty: c.sites, cons: fn(acc, s) { Cons { head: s, tail: acc } }), refusals: c.refusals } + } +} + +// The contract over an explicit path list: the per-module walk is the lens's; the gates run on the +// collected live sites, and every per-module refusal is named, never dropped. Kept separate from +// the discovery so a bounded recount (one module, one directory) is runnable in a claim test +// without the whole corpus. +fn argv_word_construction_census_check_paths(paths: List) -> ProcessExit { + let collected = fold_list(xs: paths, empty: ArgvWordsCollected { sites: Empty, refusals: Empty }, cons: collect_path) + match list_reverse(xs: collected.refusals) { + Empty => argv_word_construction_census_check_sites(sites: list_reverse(xs: collected.sites)) + nonempty => exit_failure(reason: join_lines(rs: nonempty)) + } +} + +fn join_lines(rs: FreeMonoid) -> String { + fold_list(xs: list_reverse(xs: rs), empty: "", cons: fn(acc, l) { if acc == "" { l } else { concat(concat(acc, "\n"), l) } }) +} + +fn argv_word_construction_census_fixture_facts_dump() -> ProcessExit { + let read = Filesystem.Read(path: "src/v2/test/fixture_roots/argv_word_construction/host_words.dag") + let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: read.content) + if written.success { ExitSuccess } else { exit_failure(reason: concat("fixture facts write refused: ", written.error)) } +} +// The fixture root is OUTSIDE the modeled roots by design (the boundary-refusal claim pins that), +// so the walk above never sees it and for_paths refuses any module outside the roots. The +// fixture's facts are therefore read at the FACT level -- the same read/parse/facts_of route the +// fixture claims use -- and rendered where a human reads them: one TSV row per fact with every +// field the fixture claims pin. This is the observation those claims' expectations are aligned +// against, not a hand-maintained parallel list of what the lens is supposed to say. +fn fixture_fact_row(f: ArgvWordsFact) -> String { + concat(f.decl, concat("\t", concat(fact_kind_word(k: f.decl_kind), concat("\t", concat(bool_word(b: f.counted), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.candidates)), concat("\t", concat(join(f.candidates, ";"), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.flag_words)), concat("\t", concat(bool_word(b: f.returns_argv_words), concat("\t", concat(integer_int_to_decimal_string(value: f.list_string_params), concat("\t", bool_word(b: f.list_string_data)))))))))))))))))) +} + diff --git a/dag/gunbc/instruments/bisect_tmp2/v8_kinds.dag b/dag/gunbc/instruments/bisect_tmp2/v8_kinds.dag new file mode 100644 index 00000000000..cc540ea8c34 --- /dev/null +++ b/dag/gunbc/instruments/bisect_tmp2/v8_kinds.dag @@ -0,0 +1,194 @@ +module gunbc.instruments.argv_word_construction_census + +// THE TYPED-ARGV CONSTRUCTION CENSUS ROSTER (debt paydown trial, witty-tern-54, 2026-10-03). +// +// The instrument is v2.lens.argv_word_construction; this module commits WHAT IT COUNTS: the +// population of argv-string construction sites over dag/extdeps and dag/gunbc, at identity grain, +// as a typed roster that may only shrink. DESIGN 5: a count over the current tree is not an +// oracle — every row below joins to a live census member at (module_file, decl, decl_kind), a +// row that stops resolving refuses the recount, and the population never grows past the committed +// baseline: new string-built argv is refused, not enrolled. +// +// Recount (the named, re-runnable instrument run): discover the two roots' module files by the +// declaration index, walk each with the lens, and hold the gates over (live, roster, baseline). +// The gates live in the lens; this module owns the committed state: the roster, the baseline +// cardinality, and the sites classified as NOT argv debt with a stated reason. + +import v2.lens.argv_word_construction { + ArgvWordsDeclKind, + ArgvWordsRosterRow, + ArgvWordsSite, + ArgvWordsSiteClass, + ExtdepsInterfaceBuilder, + FnDecl, + DataDecl, + WorkflowCallSite, + ArgvWordsModuleEstablished, + ArgvWordsModuleRefused, + argv_word_construction_gates, + argv_word_construction_module_sites, + argv_word_construction_for_paths +} +import v2.std.algebra { fold_list, list_reverse } +import v2.std.collection { List } +import std.algebra { Cons, Empty, FreeMonoid } +import std.process { ExitSuccess, ProcessExit, exit_failure } +import extdeps.filesystem.filesystem_io { Filesystem } +import v2.std.node { Node } +import v2.std.integer { integer_int_to_decimal_string } +import v2.lens.module_graph { ModuleDeclarationFact, module_declaration_facts_live } + +// The modeled roots. Anything else is refused by the lens, never silently zero. +data argv_word_construction_census_roots: List = ["dag/extdeps", "dag/gunbc"] + +// THE COMMITTED BASELINE: the population the instrument counted over the two roots at the start +// of the trial (origin/main, 2026-10-03). The roster may only shrink past this number. +data argv_word_construction_census_baseline_population: Int = 0 + +// THE ROSTER: one row per live census member. `note` names what the site is and what pays it. +// Kept at zero rows until the first committed recount fills it from the instrument (never by +// hand from a text search). +data argv_word_construction_census_roster: List = [] + +// Sites whose evidence is read and REFUSED AS DEBT with a stated reason: the census classifies, +// it does not silently count. Each entry names its reason; a reason that stops being true must +// delete the entry (the same shrink discipline as the roster). +data argv_word_construction_census_excluded: List = [] + +type ArgvWordsExclusion { + module_file: String + decl: String + reason: String +} + +fn excluded_identity_matches(e: ArgvWordsExclusion, s: ArgvWordsSite) -> Bool { + (e.module_file == s.module_file) && (e.decl == s.decl) +} + +fn site_is_excluded(s: ArgvWordsSite) -> Bool { + fold_list(xs: argv_word_construction_census_excluded, empty: false, cons: fn(acc, e) { acc || excluded_identity_matches(e: e, s: s) }) +} + +// The live census members after classification: exclusions are visible subtractions with reasons. +fn argv_word_construction_census_live(sites: List) -> List { + fold_list(xs: sites, empty: Empty, cons: fn(acc, s) { if site_is_excluded(s: s) { acc } else { Cons { head: s, tail: acc } } }) +} + +// The contract over one recount's live sites. +fn argv_word_construction_census_check_sites(sites: List) -> ProcessExit { + argv_word_construction_gates(live: argv_word_construction_census_live(sites: sites), roster: argv_word_construction_census_roster, baseline_population: argv_word_construction_census_baseline_population) +} + +// The recount entry: discover the roots' module files by the declaration index (never a text +// search), walk each with the lens, and hold the contract. +fn argv_word_construction_census_recount() -> ProcessExit { + argv_word_construction_census_check_paths(paths: argv_word_construction_census_root_paths()) +} + +// The discovered modeled roots' module files, in declaration-index order. Shared by the gated +// recount above and the roster emitters below so all see the same population. Parameterized by +// roots: the corpus walk is run ONE ROOT PER PROCESS (a wet lane kills a whole-corpus walk with +// OOM -- the per-process journal of read+parse dispatches is what grows, so the split is by the +// roots boundary, the population's own partition). +fn argv_word_construction_census_root_paths_for_roots(roots: List) -> List { + let decls = module_declaration_facts_live(pool_roots: roots) + let paths_reversed = fold_list(xs: decls, empty: Empty, cons: fn(acc, d) { + let p = declared_module_path(d: d) + if p == "" { acc } else { Cons { head: p, tail: acc } } + }) + list_reverse(xs: paths_reversed) +} + +fn argv_word_construction_census_root_paths() -> List { + argv_word_construction_census_root_paths_for_roots(roots: argv_word_construction_census_roots) +} + +// The roster emitter for a wet-lane run (claim_batch --wet): the same discovered population, each +// module walked by the lens, rows written as TSV to the scratch path. The committed roster data +// decl above is filled FROM this output, never by hand from a text search. Hermetic lanes refuse +// the write by design -- the emitter exists for the recount, not for CI. +fn argv_word_construction_census_recount_emit() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths(), out: "target/argv_word_construction_census_recount.tsv") +} + +fn argv_word_construction_census_recount_emit_extdeps() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/extdeps"]), out: "target/argv_word_construction_census_recount_extdeps.tsv") +} + +fn argv_word_construction_census_recount_emit_gunbc() -> ProcessExit { + argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/gunbc"]), out: "target/argv_word_construction_census_recount_gunbc.tsv") +} + +fn declared_module_path(d: ModuleDeclarationFact) -> String { + d.path +} + +// The distinct module files named by the committed roster, in first-appearance order: exactly the +// population the bounded-contract claim re-walks, so CI executes the gates at roster size without +// ever walking the whole corpus. +fn argv_word_construction_census_roster_modules() -> List { + let reversed = fold_list(xs: argv_word_construction_census_roster, empty: Empty, cons: fn(acc, r) { + if contains_string(xs: acc, x: r.module_file) { acc } else { Cons { head: r.module_file, tail: acc } } + }) + list_reverse(xs: reversed) +} + +fn contains_string(xs: List, x: String) -> Bool { + fold_list(xs: xs, empty: false, cons: fn(acc, y) { acc || (y == x) }) +} + +type ArgvWordsCollected { + sites: FreeMonoid + refusals: FreeMonoid +} + +fn collect_path(c: ArgvWordsCollected, p: String) -> ArgvWordsCollected { + match argv_word_construction_module_sites(path: p) { + ArgvWordsModuleRefused { path: rp, reason: r } => + ArgvWordsCollected { sites: c.sites, refusals: Cons { head: concat(concat(concat("REFUSED\t", rp), "\t"), r), tail: c.refusals } } + ArgvWordsModuleEstablished { path: _, sites: sites } => + ArgvWordsCollected { sites: fold_list(xs: sites, empty: c.sites, cons: fn(acc, s) { Cons { head: s, tail: acc } }), refusals: c.refusals } + } +} + +// The contract over an explicit path list: the per-module walk is the lens's; the gates run on the +// collected live sites, and every per-module refusal is named, never dropped. Kept separate from +// the discovery so a bounded recount (one module, one directory) is runnable in a claim test +// without the whole corpus. +fn argv_word_construction_census_check_paths(paths: List) -> ProcessExit { + let collected = fold_list(xs: paths, empty: ArgvWordsCollected { sites: Empty, refusals: Empty }, cons: collect_path) + match list_reverse(xs: collected.refusals) { + Empty => argv_word_construction_census_check_sites(sites: list_reverse(xs: collected.sites)) + nonempty => exit_failure(reason: join_lines(rs: nonempty)) + } +} + +fn join_lines(rs: FreeMonoid) -> String { + fold_list(xs: list_reverse(xs: rs), empty: "", cons: fn(acc, l) { if acc == "" { l } else { concat(concat(acc, "\n"), l) } }) +} + +fn argv_word_construction_census_fixture_facts_dump() -> ProcessExit { + let read = Filesystem.Read(path: "src/v2/test/fixture_roots/argv_word_construction/host_words.dag") + let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: read.content) + if written.success { ExitSuccess } else { exit_failure(reason: concat("fixture facts write refused: ", written.error)) } +} +// The fixture root is OUTSIDE the modeled roots by design (the boundary-refusal claim pins that), +// so the walk above never sees it and for_paths refuses any module outside the roots. The +// fixture's facts are therefore read at the FACT level -- the same read/parse/facts_of route the +// fixture claims use -- and rendered where a human reads them: one TSV row per fact with every +// field the fixture claims pin. This is the observation those claims' expectations are aligned +// against, not a hand-maintained parallel list of what the lens is supposed to say. +fn fact_kind_word(k: ArgvWordsDeclKind) -> String { + match k { + FnDecl => "FnDecl" + DataDecl => "DataDecl" + } +} + +fn bool_word(b: Bool) -> String { + match b { + true => "true" + false => "false" + } +} + From 0c040c8a0cd1355829bba88b14d8832008fcdef4 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 6 Oct 2026 12:59:30 +0000 Subject: [PATCH 56/65] =?UTF-8?q?dump:=20the=20row=20renders=20through=20p?= =?UTF-8?q?er-field=20lets=20and=20one=20join=20of=20a=20literal=20list=20?= =?UTF-8?q?=E2=80=94=20the=20hand-counted=20nested=20concat=20had=20an=20e?= =?UTF-8?q?xtra=20close-paren=20(27=20open,=2028=20close),=20which=20is=20?= =?UTF-8?q?the=20grammar=20error=20the=20entry=20validation=20kept=20refus?= =?UTF-8?q?ing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../instruments/argv_word_construction_census.dag | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/dag/gunbc/instruments/argv_word_construction_census.dag b/dag/gunbc/instruments/argv_word_construction_census.dag index 9abbd46d946..8131f91c9ba 100644 --- a/dag/gunbc/instruments/argv_word_construction_census.dag +++ b/dag/gunbc/instruments/argv_word_construction_census.dag @@ -151,7 +151,15 @@ fn fixture_facts_write(root: Node) -> ProcessExit { } fn fixture_fact_row(f: ArgvWordsFact) -> String { - concat(f.decl, concat("\t", concat(fact_kind_word(k: f.decl_kind), concat("\t", concat(bool_word(b: f.counted), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.candidates)), concat("\t", concat(join(f.candidates, ";"), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.flag_words)), concat("\t", concat(bool_word(b: f.returns_argv_words), concat("\t", concat(integer_int_to_decimal_string(value: f.list_string_params), concat("\t", bool_word(b: f.list_string_data)))))))))))))))))) + let kind = fact_kind_word(k: f.decl_kind) + let counted = bool_word(b: f.counted) + let n_cand = integer_int_to_decimal_string(value: length(xs: f.candidates)) + let cands = join(f.candidates, ";") + let n_flags = integer_int_to_decimal_string(value: length(xs: f.flag_words)) + let returns = bool_word(b: f.returns_argv_words) + let n_params = integer_int_to_decimal_string(value: f.list_string_params) + let data = bool_word(b: f.list_string_data) + join([f.decl, kind, counted, n_cand, cands, n_flags, returns, n_params, data], "\t") } fn fact_kind_word(k: ArgvWordsDeclKind) -> String { From 8baf3a8ddacbda586b16aae414cf2c4081c2d8ee Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 6 Oct 2026 13:13:01 +0000 Subject: [PATCH 57/65] =?UTF-8?q?remove=20the=20TEMP=20bisect=20variants?= =?UTF-8?q?=20=E2=80=94=20every=20copy=20declares=20the=20same=20module=20?= =?UTF-8?q?identity=20as=20the=20instrument,=20and=20the=20loader=20refuse?= =?UTF-8?q?s=20the=20collision=20(DESIGN=20=C2=A73,=20one=20module=20one?= =?UTF-8?q?=20authority)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../instruments/bisect_tmp/v0_lastok.dag | 165 ------------- .../instruments/bisect_tmp/v1_current.dag | 217 ------------------ .../bisect_tmp/v2_imports_only.dag | 168 -------------- dag/gunbc/instruments/bisect_tmp/v3_stub.dag | 174 -------------- .../instruments/bisect_tmp/v4_fullblock.dag | 212 ----------------- .../instruments/bisect_tmp2/v5_dumpfn.dag | 192 ---------------- .../instruments/bisect_tmp2/v6_writefn.dag | 188 --------------- .../instruments/bisect_tmp2/v7_rowfn.dag | 184 --------------- .../instruments/bisect_tmp2/v8_kinds.dag | 194 ---------------- 9 files changed, 1694 deletions(-) delete mode 100644 dag/gunbc/instruments/bisect_tmp/v0_lastok.dag delete mode 100644 dag/gunbc/instruments/bisect_tmp/v1_current.dag delete mode 100644 dag/gunbc/instruments/bisect_tmp/v2_imports_only.dag delete mode 100644 dag/gunbc/instruments/bisect_tmp/v3_stub.dag delete mode 100644 dag/gunbc/instruments/bisect_tmp/v4_fullblock.dag delete mode 100644 dag/gunbc/instruments/bisect_tmp2/v5_dumpfn.dag delete mode 100644 dag/gunbc/instruments/bisect_tmp2/v6_writefn.dag delete mode 100644 dag/gunbc/instruments/bisect_tmp2/v7_rowfn.dag delete mode 100644 dag/gunbc/instruments/bisect_tmp2/v8_kinds.dag diff --git a/dag/gunbc/instruments/bisect_tmp/v0_lastok.dag b/dag/gunbc/instruments/bisect_tmp/v0_lastok.dag deleted file mode 100644 index 0734ce4b16d..00000000000 --- a/dag/gunbc/instruments/bisect_tmp/v0_lastok.dag +++ /dev/null @@ -1,165 +0,0 @@ -module gunbc.instruments.argv_word_construction_census - -// THE TYPED-ARGV CONSTRUCTION CENSUS ROSTER (debt paydown trial, witty-tern-54, 2026-10-03). -// -// The instrument is v2.lens.argv_word_construction; this module commits WHAT IT COUNTS: the -// population of argv-string construction sites over dag/extdeps and dag/gunbc, at identity grain, -// as a typed roster that may only shrink. DESIGN 5: a count over the current tree is not an -// oracle — every row below joins to a live census member at (module_file, decl, decl_kind), a -// row that stops resolving refuses the recount, and the population never grows past the committed -// baseline: new string-built argv is refused, not enrolled. -// -// Recount (the named, re-runnable instrument run): discover the two roots' module files by the -// declaration index, walk each with the lens, and hold the gates over (live, roster, baseline). -// The gates live in the lens; this module owns the committed state: the roster, the baseline -// cardinality, and the sites classified as NOT argv debt with a stated reason. - -import v2.lens.argv_word_construction { - ArgvWordsDeclKind, - ArgvWordsRosterRow, - ArgvWordsSite, - ArgvWordsSiteClass, - ExtdepsInterfaceBuilder, - FnDecl, - DataDecl, - WorkflowCallSite, - ArgvWordsModuleEstablished, - ArgvWordsModuleRefused, - argv_word_construction_gates, - argv_word_construction_module_sites, - argv_word_construction_for_paths -} -import v2.std.algebra { fold_list, list_reverse } -import v2.std.collection { List } -import std.algebra { Cons, Empty, FreeMonoid } -import std.process { ExitSuccess, ProcessExit, exit_failure } -import v2.lens.module_graph { ModuleDeclarationFact, module_declaration_facts_live } - -// The modeled roots. Anything else is refused by the lens, never silently zero. -data argv_word_construction_census_roots: List = ["dag/extdeps", "dag/gunbc"] - -// THE COMMITTED BASELINE: the population the instrument counted over the two roots at the start -// of the trial (origin/main, 2026-10-03). The roster may only shrink past this number. -data argv_word_construction_census_baseline_population: Int = 0 - -// THE ROSTER: one row per live census member. `note` names what the site is and what pays it. -// Kept at zero rows until the first committed recount fills it from the instrument (never by -// hand from a text search). -data argv_word_construction_census_roster: List = [] - -// Sites whose evidence is read and REFUSED AS DEBT with a stated reason: the census classifies, -// it does not silently count. Each entry names its reason; a reason that stops being true must -// delete the entry (the same shrink discipline as the roster). -data argv_word_construction_census_excluded: List = [] - -type ArgvWordsExclusion { - module_file: String - decl: String - reason: String -} - -fn excluded_identity_matches(e: ArgvWordsExclusion, s: ArgvWordsSite) -> Bool { - (e.module_file == s.module_file) && (e.decl == s.decl) -} - -fn site_is_excluded(s: ArgvWordsSite) -> Bool { - fold_list(xs: argv_word_construction_census_excluded, empty: false, cons: fn(acc, e) { acc || excluded_identity_matches(e: e, s: s) }) -} - -// The live census members after classification: exclusions are visible subtractions with reasons. -fn argv_word_construction_census_live(sites: List) -> List { - fold_list(xs: sites, empty: Empty, cons: fn(acc, s) { if site_is_excluded(s: s) { acc } else { Cons { head: s, tail: acc } } }) -} - -// The contract over one recount's live sites. -fn argv_word_construction_census_check_sites(sites: List) -> ProcessExit { - argv_word_construction_gates(live: argv_word_construction_census_live(sites: sites), roster: argv_word_construction_census_roster, baseline_population: argv_word_construction_census_baseline_population) -} - -// The recount entry: discover the roots' module files by the declaration index (never a text -// search), walk each with the lens, and hold the contract. -fn argv_word_construction_census_recount() -> ProcessExit { - argv_word_construction_census_check_paths(paths: argv_word_construction_census_root_paths()) -} - -// The discovered modeled roots' module files, in declaration-index order. Shared by the gated -// recount above and the roster emitters below so all see the same population. Parameterized by -// roots: the corpus walk is run ONE ROOT PER PROCESS (a wet lane kills a whole-corpus walk with -// OOM -- the per-process journal of read+parse dispatches is what grows, so the split is by the -// roots boundary, the population's own partition). -fn argv_word_construction_census_root_paths_for_roots(roots: List) -> List { - let decls = module_declaration_facts_live(pool_roots: roots) - let paths_reversed = fold_list(xs: decls, empty: Empty, cons: fn(acc, d) { - let p = declared_module_path(d: d) - if p == "" { acc } else { Cons { head: p, tail: acc } } - }) - list_reverse(xs: paths_reversed) -} - -fn argv_word_construction_census_root_paths() -> List { - argv_word_construction_census_root_paths_for_roots(roots: argv_word_construction_census_roots) -} - -// The roster emitter for a wet-lane run (claim_batch --wet): the same discovered population, each -// module walked by the lens, rows written as TSV to the scratch path. The committed roster data -// decl above is filled FROM this output, never by hand from a text search. Hermetic lanes refuse -// the write by design -- the emitter exists for the recount, not for CI. -fn argv_word_construction_census_recount_emit() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths(), out: "target/argv_word_construction_census_recount.tsv") -} - -fn argv_word_construction_census_recount_emit_extdeps() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/extdeps"]), out: "target/argv_word_construction_census_recount_extdeps.tsv") -} - -fn argv_word_construction_census_recount_emit_gunbc() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/gunbc"]), out: "target/argv_word_construction_census_recount_gunbc.tsv") -} - -fn declared_module_path(d: ModuleDeclarationFact) -> String { - d.path -} - -// The distinct module files named by the committed roster, in first-appearance order: exactly the -// population the bounded-contract claim re-walks, so CI executes the gates at roster size without -// ever walking the whole corpus. -fn argv_word_construction_census_roster_modules() -> List { - let reversed = fold_list(xs: argv_word_construction_census_roster, empty: Empty, cons: fn(acc, r) { - if contains_string(xs: acc, x: r.module_file) { acc } else { Cons { head: r.module_file, tail: acc } } - }) - list_reverse(xs: reversed) -} - -fn contains_string(xs: List, x: String) -> Bool { - fold_list(xs: xs, empty: false, cons: fn(acc, y) { acc || (y == x) }) -} - -type ArgvWordsCollected { - sites: FreeMonoid - refusals: FreeMonoid -} - -fn collect_path(c: ArgvWordsCollected, p: String) -> ArgvWordsCollected { - match argv_word_construction_module_sites(path: p) { - ArgvWordsModuleRefused { path: rp, reason: r } => - ArgvWordsCollected { sites: c.sites, refusals: Cons { head: concat(concat(concat("REFUSED\t", rp), "\t"), r), tail: c.refusals } } - ArgvWordsModuleEstablished { path: _, sites: sites } => - ArgvWordsCollected { sites: fold_list(xs: sites, empty: c.sites, cons: fn(acc, s) { Cons { head: s, tail: acc } }), refusals: c.refusals } - } -} - -// The contract over an explicit path list: the per-module walk is the lens's; the gates run on the -// collected live sites, and every per-module refusal is named, never dropped. Kept separate from -// the discovery so a bounded recount (one module, one directory) is runnable in a claim test -// without the whole corpus. -fn argv_word_construction_census_check_paths(paths: List) -> ProcessExit { - let collected = fold_list(xs: paths, empty: ArgvWordsCollected { sites: Empty, refusals: Empty }, cons: collect_path) - match list_reverse(xs: collected.refusals) { - Empty => argv_word_construction_census_check_sites(sites: list_reverse(xs: collected.sites)) - nonempty => exit_failure(reason: join_lines(rs: nonempty)) - } -} - -fn join_lines(rs: FreeMonoid) -> String { - fold_list(xs: list_reverse(xs: rs), empty: "", cons: fn(acc, l) { if acc == "" { l } else { concat(concat(acc, "\n"), l) } }) -} diff --git a/dag/gunbc/instruments/bisect_tmp/v1_current.dag b/dag/gunbc/instruments/bisect_tmp/v1_current.dag deleted file mode 100644 index 9abbd46d946..00000000000 --- a/dag/gunbc/instruments/bisect_tmp/v1_current.dag +++ /dev/null @@ -1,217 +0,0 @@ -module gunbc.instruments.argv_word_construction_census - -// THE TYPED-ARGV CONSTRUCTION CENSUS ROSTER (debt paydown trial, witty-tern-54, 2026-10-03). -// -// The instrument is v2.lens.argv_word_construction; this module commits WHAT IT COUNTS: the -// population of argv-string construction sites over dag/extdeps and dag/gunbc, at identity grain, -// as a typed roster that may only shrink. DESIGN 5: a count over the current tree is not an -// oracle — every row below joins to a live census member at (module_file, decl, decl_kind), a -// row that stops resolving refuses the recount, and the population never grows past the committed -// baseline: new string-built argv is refused, not enrolled. -// -// Recount (the named, re-runnable instrument run): discover the two roots' module files by the -// declaration index, walk each with the lens, and hold the gates over (live, roster, baseline). -// The gates live in the lens; this module owns the committed state: the roster, the baseline -// cardinality, and the sites classified as NOT argv debt with a stated reason. - -import v2.lens.argv_word_construction { - ArgvWordsDeclKind, - ArgvWordsFact, - ArgvWordsRosterRow, - ArgvWordsSite, - ArgvWordsSiteClass, - ExtdepsInterfaceBuilder, - FnDecl, - DataDecl, - WorkflowCallSite, - ArgvWordsModuleEstablished, - ArgvWordsModuleRefused, - RawTreeEstablished, - RawTreeNotEstablished, - facts_of, - raw_tree_of_text, - argv_word_construction_gates, - argv_word_construction_module_sites, - argv_word_construction_for_paths -} -import v2.std.algebra { fold_list, list_reverse } -import v2.std.collection { List } -import std.algebra { Cons, Empty, FreeMonoid } -import std.process { ExitSuccess, ProcessExit, exit_failure } -import extdeps.filesystem.filesystem_io { Filesystem } -import v2.std.node { Node } -import v2.std.integer { integer_int_to_decimal_string } -import v2.lens.module_graph { ModuleDeclarationFact, module_declaration_facts_live } - -// The modeled roots. Anything else is refused by the lens, never silently zero. -data argv_word_construction_census_roots: List = ["dag/extdeps", "dag/gunbc"] - -// THE COMMITTED BASELINE: the population the instrument counted over the two roots at the start -// of the trial (origin/main, 2026-10-03). The roster may only shrink past this number. -data argv_word_construction_census_baseline_population: Int = 0 - -// THE ROSTER: one row per live census member. `note` names what the site is and what pays it. -// Kept at zero rows until the first committed recount fills it from the instrument (never by -// hand from a text search). -data argv_word_construction_census_roster: List = [] - -// Sites whose evidence is read and REFUSED AS DEBT with a stated reason: the census classifies, -// it does not silently count. Each entry names its reason; a reason that stops being true must -// delete the entry (the same shrink discipline as the roster). -data argv_word_construction_census_excluded: List = [] - -type ArgvWordsExclusion { - module_file: String - decl: String - reason: String -} - -fn excluded_identity_matches(e: ArgvWordsExclusion, s: ArgvWordsSite) -> Bool { - (e.module_file == s.module_file) && (e.decl == s.decl) -} - -fn site_is_excluded(s: ArgvWordsSite) -> Bool { - fold_list(xs: argv_word_construction_census_excluded, empty: false, cons: fn(acc, e) { acc || excluded_identity_matches(e: e, s: s) }) -} - -// The live census members after classification: exclusions are visible subtractions with reasons. -fn argv_word_construction_census_live(sites: List) -> List { - fold_list(xs: sites, empty: Empty, cons: fn(acc, s) { if site_is_excluded(s: s) { acc } else { Cons { head: s, tail: acc } } }) -} - -// The contract over one recount's live sites. -fn argv_word_construction_census_check_sites(sites: List) -> ProcessExit { - argv_word_construction_gates(live: argv_word_construction_census_live(sites: sites), roster: argv_word_construction_census_roster, baseline_population: argv_word_construction_census_baseline_population) -} - -// The recount entry: discover the roots' module files by the declaration index (never a text -// search), walk each with the lens, and hold the contract. -fn argv_word_construction_census_recount() -> ProcessExit { - argv_word_construction_census_check_paths(paths: argv_word_construction_census_root_paths()) -} - -// The discovered modeled roots' module files, in declaration-index order. Shared by the gated -// recount above and the roster emitters below so all see the same population. Parameterized by -// roots: the corpus walk is run ONE ROOT PER PROCESS (a wet lane kills a whole-corpus walk with -// OOM -- the per-process journal of read+parse dispatches is what grows, so the split is by the -// roots boundary, the population's own partition). -fn argv_word_construction_census_root_paths_for_roots(roots: List) -> List { - let decls = module_declaration_facts_live(pool_roots: roots) - let paths_reversed = fold_list(xs: decls, empty: Empty, cons: fn(acc, d) { - let p = declared_module_path(d: d) - if p == "" { acc } else { Cons { head: p, tail: acc } } - }) - list_reverse(xs: paths_reversed) -} - -fn argv_word_construction_census_root_paths() -> List { - argv_word_construction_census_root_paths_for_roots(roots: argv_word_construction_census_roots) -} - -// The roster emitter for a wet-lane run (claim_batch --wet): the same discovered population, each -// module walked by the lens, rows written as TSV to the scratch path. The committed roster data -// decl above is filled FROM this output, never by hand from a text search. Hermetic lanes refuse -// the write by design -- the emitter exists for the recount, not for CI. -fn argv_word_construction_census_recount_emit() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths(), out: "target/argv_word_construction_census_recount.tsv") -} - -fn argv_word_construction_census_recount_emit_extdeps() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/extdeps"]), out: "target/argv_word_construction_census_recount_extdeps.tsv") -} - -fn argv_word_construction_census_recount_emit_gunbc() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/gunbc"]), out: "target/argv_word_construction_census_recount_gunbc.tsv") -} - -// The fixture root is OUTSIDE the modeled roots by design (the boundary-refusal claim pins that), -// so the walk above never sees it and for_paths refuses any module outside the roots. The -// fixture's facts are therefore read at the FACT level -- the same read/parse/facts_of route the -// fixture claims use -- and rendered where a human reads them: one TSV row per fact with every -// field the fixture claims pin. This is the observation those claims' expectations are aligned -// against, not a hand-maintained parallel list of what the lens is supposed to say. -fn argv_word_construction_census_fixture_facts_dump() -> ProcessExit { - let read = Filesystem.Read(path: "src/v2/test/fixture_roots/argv_word_construction/host_words.dag") - match read.success { - false => exit_failure(reason: "fixture facts absent: fixture unreadable") - true => - match raw_tree_of_text(text: read.content) { - RawTreeNotEstablished { cause: cause } => exit_failure(reason: concat("fixture unparseable: ", cause)) - RawTreeEstablished { root: root } => fixture_facts_write(root: root) - } - } -} - -fn fixture_facts_write(root: Node) -> ProcessExit { - let rows = fold_list(xs: facts_of(root: root), empty: "decl\tkind\tcounted\tcandidates_n\tcandidates\tflag_words_n\treturns_argv_words\tlist_string_params\tlist_string_data", cons: fn(acc, f) { - concat(acc, concat("\n", fixture_fact_row(f: f))) - }) - let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: rows) - if written.success { ExitSuccess } else { exit_failure(reason: concat("fixture facts write refused: ", written.error)) } -} - -fn fixture_fact_row(f: ArgvWordsFact) -> String { - concat(f.decl, concat("\t", concat(fact_kind_word(k: f.decl_kind), concat("\t", concat(bool_word(b: f.counted), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.candidates)), concat("\t", concat(join(f.candidates, ";"), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.flag_words)), concat("\t", concat(bool_word(b: f.returns_argv_words), concat("\t", concat(integer_int_to_decimal_string(value: f.list_string_params), concat("\t", bool_word(b: f.list_string_data)))))))))))))))))) -} - -fn fact_kind_word(k: ArgvWordsDeclKind) -> String { - match k { - FnDecl => "FnDecl" - DataDecl => "DataDecl" - } -} - -fn bool_word(b: Bool) -> String { - match b { - true => "true" - false => "false" - } -} - -fn declared_module_path(d: ModuleDeclarationFact) -> String { - d.path -} - -// The distinct module files named by the committed roster, in first-appearance order: exactly the -// population the bounded-contract claim re-walks, so CI executes the gates at roster size without -// ever walking the whole corpus. -fn argv_word_construction_census_roster_modules() -> List { - let reversed = fold_list(xs: argv_word_construction_census_roster, empty: Empty, cons: fn(acc, r) { - if contains_string(xs: acc, x: r.module_file) { acc } else { Cons { head: r.module_file, tail: acc } } - }) - list_reverse(xs: reversed) -} - -fn contains_string(xs: List, x: String) -> Bool { - fold_list(xs: xs, empty: false, cons: fn(acc, y) { acc || (y == x) }) -} - -type ArgvWordsCollected { - sites: FreeMonoid - refusals: FreeMonoid -} - -fn collect_path(c: ArgvWordsCollected, p: String) -> ArgvWordsCollected { - match argv_word_construction_module_sites(path: p) { - ArgvWordsModuleRefused { path: rp, reason: r } => - ArgvWordsCollected { sites: c.sites, refusals: Cons { head: concat(concat(concat("REFUSED\t", rp), "\t"), r), tail: c.refusals } } - ArgvWordsModuleEstablished { path: _, sites: sites } => - ArgvWordsCollected { sites: fold_list(xs: sites, empty: c.sites, cons: fn(acc, s) { Cons { head: s, tail: acc } }), refusals: c.refusals } - } -} - -// The contract over an explicit path list: the per-module walk is the lens's; the gates run on the -// collected live sites, and every per-module refusal is named, never dropped. Kept separate from -// the discovery so a bounded recount (one module, one directory) is runnable in a claim test -// without the whole corpus. -fn argv_word_construction_census_check_paths(paths: List) -> ProcessExit { - let collected = fold_list(xs: paths, empty: ArgvWordsCollected { sites: Empty, refusals: Empty }, cons: collect_path) - match list_reverse(xs: collected.refusals) { - Empty => argv_word_construction_census_check_sites(sites: list_reverse(xs: collected.sites)) - nonempty => exit_failure(reason: join_lines(rs: nonempty)) - } -} - -fn join_lines(rs: FreeMonoid) -> String { - fold_list(xs: list_reverse(xs: rs), empty: "", cons: fn(acc, l) { if acc == "" { l } else { concat(concat(acc, "\n"), l) } }) -} diff --git a/dag/gunbc/instruments/bisect_tmp/v2_imports_only.dag b/dag/gunbc/instruments/bisect_tmp/v2_imports_only.dag deleted file mode 100644 index d391dafcf4c..00000000000 --- a/dag/gunbc/instruments/bisect_tmp/v2_imports_only.dag +++ /dev/null @@ -1,168 +0,0 @@ -module gunbc.instruments.argv_word_construction_census - -// THE TYPED-ARGV CONSTRUCTION CENSUS ROSTER (debt paydown trial, witty-tern-54, 2026-10-03). -// -// The instrument is v2.lens.argv_word_construction; this module commits WHAT IT COUNTS: the -// population of argv-string construction sites over dag/extdeps and dag/gunbc, at identity grain, -// as a typed roster that may only shrink. DESIGN 5: a count over the current tree is not an -// oracle — every row below joins to a live census member at (module_file, decl, decl_kind), a -// row that stops resolving refuses the recount, and the population never grows past the committed -// baseline: new string-built argv is refused, not enrolled. -// -// Recount (the named, re-runnable instrument run): discover the two roots' module files by the -// declaration index, walk each with the lens, and hold the gates over (live, roster, baseline). -// The gates live in the lens; this module owns the committed state: the roster, the baseline -// cardinality, and the sites classified as NOT argv debt with a stated reason. - -import v2.lens.argv_word_construction { - ArgvWordsDeclKind, - ArgvWordsRosterRow, - ArgvWordsSite, - ArgvWordsSiteClass, - ExtdepsInterfaceBuilder, - FnDecl, - DataDecl, - WorkflowCallSite, - ArgvWordsModuleEstablished, - ArgvWordsModuleRefused, - argv_word_construction_gates, - argv_word_construction_module_sites, - argv_word_construction_for_paths -} -import v2.std.algebra { fold_list, list_reverse } -import v2.std.collection { List } -import std.algebra { Cons, Empty, FreeMonoid } -import std.process { ExitSuccess, ProcessExit, exit_failure } -import extdeps.filesystem.filesystem_io { Filesystem } -import v2.std.node { Node } -import v2.std.integer { integer_int_to_decimal_string } -import v2.lens.module_graph { ModuleDeclarationFact, module_declaration_facts_live } - -// The modeled roots. Anything else is refused by the lens, never silently zero. -data argv_word_construction_census_roots: List = ["dag/extdeps", "dag/gunbc"] - -// THE COMMITTED BASELINE: the population the instrument counted over the two roots at the start -// of the trial (origin/main, 2026-10-03). The roster may only shrink past this number. -data argv_word_construction_census_baseline_population: Int = 0 - -// THE ROSTER: one row per live census member. `note` names what the site is and what pays it. -// Kept at zero rows until the first committed recount fills it from the instrument (never by -// hand from a text search). -data argv_word_construction_census_roster: List = [] - -// Sites whose evidence is read and REFUSED AS DEBT with a stated reason: the census classifies, -// it does not silently count. Each entry names its reason; a reason that stops being true must -// delete the entry (the same shrink discipline as the roster). -data argv_word_construction_census_excluded: List = [] - -type ArgvWordsExclusion { - module_file: String - decl: String - reason: String -} - -fn excluded_identity_matches(e: ArgvWordsExclusion, s: ArgvWordsSite) -> Bool { - (e.module_file == s.module_file) && (e.decl == s.decl) -} - -fn site_is_excluded(s: ArgvWordsSite) -> Bool { - fold_list(xs: argv_word_construction_census_excluded, empty: false, cons: fn(acc, e) { acc || excluded_identity_matches(e: e, s: s) }) -} - -// The live census members after classification: exclusions are visible subtractions with reasons. -fn argv_word_construction_census_live(sites: List) -> List { - fold_list(xs: sites, empty: Empty, cons: fn(acc, s) { if site_is_excluded(s: s) { acc } else { Cons { head: s, tail: acc } } }) -} - -// The contract over one recount's live sites. -fn argv_word_construction_census_check_sites(sites: List) -> ProcessExit { - argv_word_construction_gates(live: argv_word_construction_census_live(sites: sites), roster: argv_word_construction_census_roster, baseline_population: argv_word_construction_census_baseline_population) -} - -// The recount entry: discover the roots' module files by the declaration index (never a text -// search), walk each with the lens, and hold the contract. -fn argv_word_construction_census_recount() -> ProcessExit { - argv_word_construction_census_check_paths(paths: argv_word_construction_census_root_paths()) -} - -// The discovered modeled roots' module files, in declaration-index order. Shared by the gated -// recount above and the roster emitters below so all see the same population. Parameterized by -// roots: the corpus walk is run ONE ROOT PER PROCESS (a wet lane kills a whole-corpus walk with -// OOM -- the per-process journal of read+parse dispatches is what grows, so the split is by the -// roots boundary, the population's own partition). -fn argv_word_construction_census_root_paths_for_roots(roots: List) -> List { - let decls = module_declaration_facts_live(pool_roots: roots) - let paths_reversed = fold_list(xs: decls, empty: Empty, cons: fn(acc, d) { - let p = declared_module_path(d: d) - if p == "" { acc } else { Cons { head: p, tail: acc } } - }) - list_reverse(xs: paths_reversed) -} - -fn argv_word_construction_census_root_paths() -> List { - argv_word_construction_census_root_paths_for_roots(roots: argv_word_construction_census_roots) -} - -// The roster emitter for a wet-lane run (claim_batch --wet): the same discovered population, each -// module walked by the lens, rows written as TSV to the scratch path. The committed roster data -// decl above is filled FROM this output, never by hand from a text search. Hermetic lanes refuse -// the write by design -- the emitter exists for the recount, not for CI. -fn argv_word_construction_census_recount_emit() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths(), out: "target/argv_word_construction_census_recount.tsv") -} - -fn argv_word_construction_census_recount_emit_extdeps() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/extdeps"]), out: "target/argv_word_construction_census_recount_extdeps.tsv") -} - -fn argv_word_construction_census_recount_emit_gunbc() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/gunbc"]), out: "target/argv_word_construction_census_recount_gunbc.tsv") -} - -fn declared_module_path(d: ModuleDeclarationFact) -> String { - d.path -} - -// The distinct module files named by the committed roster, in first-appearance order: exactly the -// population the bounded-contract claim re-walks, so CI executes the gates at roster size without -// ever walking the whole corpus. -fn argv_word_construction_census_roster_modules() -> List { - let reversed = fold_list(xs: argv_word_construction_census_roster, empty: Empty, cons: fn(acc, r) { - if contains_string(xs: acc, x: r.module_file) { acc } else { Cons { head: r.module_file, tail: acc } } - }) - list_reverse(xs: reversed) -} - -fn contains_string(xs: List, x: String) -> Bool { - fold_list(xs: xs, empty: false, cons: fn(acc, y) { acc || (y == x) }) -} - -type ArgvWordsCollected { - sites: FreeMonoid - refusals: FreeMonoid -} - -fn collect_path(c: ArgvWordsCollected, p: String) -> ArgvWordsCollected { - match argv_word_construction_module_sites(path: p) { - ArgvWordsModuleRefused { path: rp, reason: r } => - ArgvWordsCollected { sites: c.sites, refusals: Cons { head: concat(concat(concat("REFUSED\t", rp), "\t"), r), tail: c.refusals } } - ArgvWordsModuleEstablished { path: _, sites: sites } => - ArgvWordsCollected { sites: fold_list(xs: sites, empty: c.sites, cons: fn(acc, s) { Cons { head: s, tail: acc } }), refusals: c.refusals } - } -} - -// The contract over an explicit path list: the per-module walk is the lens's; the gates run on the -// collected live sites, and every per-module refusal is named, never dropped. Kept separate from -// the discovery so a bounded recount (one module, one directory) is runnable in a claim test -// without the whole corpus. -fn argv_word_construction_census_check_paths(paths: List) -> ProcessExit { - let collected = fold_list(xs: paths, empty: ArgvWordsCollected { sites: Empty, refusals: Empty }, cons: collect_path) - match list_reverse(xs: collected.refusals) { - Empty => argv_word_construction_census_check_sites(sites: list_reverse(xs: collected.sites)) - nonempty => exit_failure(reason: join_lines(rs: nonempty)) - } -} - -fn join_lines(rs: FreeMonoid) -> String { - fold_list(xs: list_reverse(xs: rs), empty: "", cons: fn(acc, l) { if acc == "" { l } else { concat(concat(acc, "\n"), l) } }) -} diff --git a/dag/gunbc/instruments/bisect_tmp/v3_stub.dag b/dag/gunbc/instruments/bisect_tmp/v3_stub.dag deleted file mode 100644 index 3b8c61932c3..00000000000 --- a/dag/gunbc/instruments/bisect_tmp/v3_stub.dag +++ /dev/null @@ -1,174 +0,0 @@ -module gunbc.instruments.argv_word_construction_census - -// THE TYPED-ARGV CONSTRUCTION CENSUS ROSTER (debt paydown trial, witty-tern-54, 2026-10-03). -// -// The instrument is v2.lens.argv_word_construction; this module commits WHAT IT COUNTS: the -// population of argv-string construction sites over dag/extdeps and dag/gunbc, at identity grain, -// as a typed roster that may only shrink. DESIGN 5: a count over the current tree is not an -// oracle — every row below joins to a live census member at (module_file, decl, decl_kind), a -// row that stops resolving refuses the recount, and the population never grows past the committed -// baseline: new string-built argv is refused, not enrolled. -// -// Recount (the named, re-runnable instrument run): discover the two roots' module files by the -// declaration index, walk each with the lens, and hold the gates over (live, roster, baseline). -// The gates live in the lens; this module owns the committed state: the roster, the baseline -// cardinality, and the sites classified as NOT argv debt with a stated reason. - -import v2.lens.argv_word_construction { - ArgvWordsDeclKind, - ArgvWordsRosterRow, - ArgvWordsSite, - ArgvWordsSiteClass, - ExtdepsInterfaceBuilder, - FnDecl, - DataDecl, - WorkflowCallSite, - ArgvWordsModuleEstablished, - ArgvWordsModuleRefused, - argv_word_construction_gates, - argv_word_construction_module_sites, - argv_word_construction_for_paths -} -import v2.std.algebra { fold_list, list_reverse } -import v2.std.collection { List } -import std.algebra { Cons, Empty, FreeMonoid } -import std.process { ExitSuccess, ProcessExit, exit_failure } -import extdeps.filesystem.filesystem_io { Filesystem } -import v2.std.node { Node } -import v2.std.integer { integer_int_to_decimal_string } -import v2.lens.module_graph { ModuleDeclarationFact, module_declaration_facts_live } - -// The modeled roots. Anything else is refused by the lens, never silently zero. -data argv_word_construction_census_roots: List = ["dag/extdeps", "dag/gunbc"] - -// THE COMMITTED BASELINE: the population the instrument counted over the two roots at the start -// of the trial (origin/main, 2026-10-03). The roster may only shrink past this number. -data argv_word_construction_census_baseline_population: Int = 0 - -// THE ROSTER: one row per live census member. `note` names what the site is and what pays it. -// Kept at zero rows until the first committed recount fills it from the instrument (never by -// hand from a text search). -data argv_word_construction_census_roster: List = [] - -// Sites whose evidence is read and REFUSED AS DEBT with a stated reason: the census classifies, -// it does not silently count. Each entry names its reason; a reason that stops being true must -// delete the entry (the same shrink discipline as the roster). -data argv_word_construction_census_excluded: List = [] - -type ArgvWordsExclusion { - module_file: String - decl: String - reason: String -} - -fn excluded_identity_matches(e: ArgvWordsExclusion, s: ArgvWordsSite) -> Bool { - (e.module_file == s.module_file) && (e.decl == s.decl) -} - -fn site_is_excluded(s: ArgvWordsSite) -> Bool { - fold_list(xs: argv_word_construction_census_excluded, empty: false, cons: fn(acc, e) { acc || excluded_identity_matches(e: e, s: s) }) -} - -// The live census members after classification: exclusions are visible subtractions with reasons. -fn argv_word_construction_census_live(sites: List) -> List { - fold_list(xs: sites, empty: Empty, cons: fn(acc, s) { if site_is_excluded(s: s) { acc } else { Cons { head: s, tail: acc } } }) -} - -// The contract over one recount's live sites. -fn argv_word_construction_census_check_sites(sites: List) -> ProcessExit { - argv_word_construction_gates(live: argv_word_construction_census_live(sites: sites), roster: argv_word_construction_census_roster, baseline_population: argv_word_construction_census_baseline_population) -} - -// The recount entry: discover the roots' module files by the declaration index (never a text -// search), walk each with the lens, and hold the contract. -fn argv_word_construction_census_recount() -> ProcessExit { - argv_word_construction_census_check_paths(paths: argv_word_construction_census_root_paths()) -} - -// The discovered modeled roots' module files, in declaration-index order. Shared by the gated -// recount above and the roster emitters below so all see the same population. Parameterized by -// roots: the corpus walk is run ONE ROOT PER PROCESS (a wet lane kills a whole-corpus walk with -// OOM -- the per-process journal of read+parse dispatches is what grows, so the split is by the -// roots boundary, the population's own partition). -fn argv_word_construction_census_root_paths_for_roots(roots: List) -> List { - let decls = module_declaration_facts_live(pool_roots: roots) - let paths_reversed = fold_list(xs: decls, empty: Empty, cons: fn(acc, d) { - let p = declared_module_path(d: d) - if p == "" { acc } else { Cons { head: p, tail: acc } } - }) - list_reverse(xs: paths_reversed) -} - -fn argv_word_construction_census_root_paths() -> List { - argv_word_construction_census_root_paths_for_roots(roots: argv_word_construction_census_roots) -} - -// The roster emitter for a wet-lane run (claim_batch --wet): the same discovered population, each -// module walked by the lens, rows written as TSV to the scratch path. The committed roster data -// decl above is filled FROM this output, never by hand from a text search. Hermetic lanes refuse -// the write by design -- the emitter exists for the recount, not for CI. -fn argv_word_construction_census_recount_emit() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths(), out: "target/argv_word_construction_census_recount.tsv") -} - -fn argv_word_construction_census_recount_emit_extdeps() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/extdeps"]), out: "target/argv_word_construction_census_recount_extdeps.tsv") -} - -fn argv_word_construction_census_recount_emit_gunbc() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/gunbc"]), out: "target/argv_word_construction_census_recount_gunbc.tsv") -} - -fn declared_module_path(d: ModuleDeclarationFact) -> String { - d.path -} - -// The distinct module files named by the committed roster, in first-appearance order: exactly the -// population the bounded-contract claim re-walks, so CI executes the gates at roster size without -// ever walking the whole corpus. -fn argv_word_construction_census_roster_modules() -> List { - let reversed = fold_list(xs: argv_word_construction_census_roster, empty: Empty, cons: fn(acc, r) { - if contains_string(xs: acc, x: r.module_file) { acc } else { Cons { head: r.module_file, tail: acc } } - }) - list_reverse(xs: reversed) -} - -fn contains_string(xs: List, x: String) -> Bool { - fold_list(xs: xs, empty: false, cons: fn(acc, y) { acc || (y == x) }) -} - -type ArgvWordsCollected { - sites: FreeMonoid - refusals: FreeMonoid -} - -fn collect_path(c: ArgvWordsCollected, p: String) -> ArgvWordsCollected { - match argv_word_construction_module_sites(path: p) { - ArgvWordsModuleRefused { path: rp, reason: r } => - ArgvWordsCollected { sites: c.sites, refusals: Cons { head: concat(concat(concat("REFUSED\t", rp), "\t"), r), tail: c.refusals } } - ArgvWordsModuleEstablished { path: _, sites: sites } => - ArgvWordsCollected { sites: fold_list(xs: sites, empty: c.sites, cons: fn(acc, s) { Cons { head: s, tail: acc } }), refusals: c.refusals } - } -} - -// The contract over an explicit path list: the per-module walk is the lens's; the gates run on the -// collected live sites, and every per-module refusal is named, never dropped. Kept separate from -// the discovery so a bounded recount (one module, one directory) is runnable in a claim test -// without the whole corpus. -fn argv_word_construction_census_check_paths(paths: List) -> ProcessExit { - let collected = fold_list(xs: paths, empty: ArgvWordsCollected { sites: Empty, refusals: Empty }, cons: collect_path) - match list_reverse(xs: collected.refusals) { - Empty => argv_word_construction_census_check_sites(sites: list_reverse(xs: collected.sites)) - nonempty => exit_failure(reason: join_lines(rs: nonempty)) - } -} - -fn join_lines(rs: FreeMonoid) -> String { - fold_list(xs: list_reverse(xs: rs), empty: "", cons: fn(acc, l) { if acc == "" { l } else { concat(concat(acc, "\n"), l) } }) -} - -fn argv_word_construction_census_fixture_facts_dump() -> ProcessExit { - let read = Filesystem.Read(path: "src/v2/test/fixture_roots/argv_word_construction/host_words.dag") - let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: read.content) - if written.success { ExitSuccess } else { exit_failure(reason: concat("fixture facts write refused: ", written.error)) } -} diff --git a/dag/gunbc/instruments/bisect_tmp/v4_fullblock.dag b/dag/gunbc/instruments/bisect_tmp/v4_fullblock.dag deleted file mode 100644 index 3ad17edc935..00000000000 --- a/dag/gunbc/instruments/bisect_tmp/v4_fullblock.dag +++ /dev/null @@ -1,212 +0,0 @@ -module gunbc.instruments.argv_word_construction_census - -// THE TYPED-ARGV CONSTRUCTION CENSUS ROSTER (debt paydown trial, witty-tern-54, 2026-10-03). -// -// The instrument is v2.lens.argv_word_construction; this module commits WHAT IT COUNTS: the -// population of argv-string construction sites over dag/extdeps and dag/gunbc, at identity grain, -// as a typed roster that may only shrink. DESIGN 5: a count over the current tree is not an -// oracle — every row below joins to a live census member at (module_file, decl, decl_kind), a -// row that stops resolving refuses the recount, and the population never grows past the committed -// baseline: new string-built argv is refused, not enrolled. -// -// Recount (the named, re-runnable instrument run): discover the two roots' module files by the -// declaration index, walk each with the lens, and hold the gates over (live, roster, baseline). -// The gates live in the lens; this module owns the committed state: the roster, the baseline -// cardinality, and the sites classified as NOT argv debt with a stated reason. - -import v2.lens.argv_word_construction { - ArgvWordsDeclKind, - ArgvWordsRosterRow, - ArgvWordsSite, - ArgvWordsSiteClass, - ExtdepsInterfaceBuilder, - FnDecl, - DataDecl, - WorkflowCallSite, - ArgvWordsModuleEstablished, - ArgvWordsModuleRefused, - argv_word_construction_gates, - argv_word_construction_module_sites, - argv_word_construction_for_paths -} -import v2.std.algebra { fold_list, list_reverse } -import v2.std.collection { List } -import std.algebra { Cons, Empty, FreeMonoid } -import std.process { ExitSuccess, ProcessExit, exit_failure } -import extdeps.filesystem.filesystem_io { Filesystem } -import v2.std.node { Node } -import v2.std.integer { integer_int_to_decimal_string } -import v2.lens.module_graph { ModuleDeclarationFact, module_declaration_facts_live } - -// The modeled roots. Anything else is refused by the lens, never silently zero. -data argv_word_construction_census_roots: List = ["dag/extdeps", "dag/gunbc"] - -// THE COMMITTED BASELINE: the population the instrument counted over the two roots at the start -// of the trial (origin/main, 2026-10-03). The roster may only shrink past this number. -data argv_word_construction_census_baseline_population: Int = 0 - -// THE ROSTER: one row per live census member. `note` names what the site is and what pays it. -// Kept at zero rows until the first committed recount fills it from the instrument (never by -// hand from a text search). -data argv_word_construction_census_roster: List = [] - -// Sites whose evidence is read and REFUSED AS DEBT with a stated reason: the census classifies, -// it does not silently count. Each entry names its reason; a reason that stops being true must -// delete the entry (the same shrink discipline as the roster). -data argv_word_construction_census_excluded: List = [] - -type ArgvWordsExclusion { - module_file: String - decl: String - reason: String -} - -fn excluded_identity_matches(e: ArgvWordsExclusion, s: ArgvWordsSite) -> Bool { - (e.module_file == s.module_file) && (e.decl == s.decl) -} - -fn site_is_excluded(s: ArgvWordsSite) -> Bool { - fold_list(xs: argv_word_construction_census_excluded, empty: false, cons: fn(acc, e) { acc || excluded_identity_matches(e: e, s: s) }) -} - -// The live census members after classification: exclusions are visible subtractions with reasons. -fn argv_word_construction_census_live(sites: List) -> List { - fold_list(xs: sites, empty: Empty, cons: fn(acc, s) { if site_is_excluded(s: s) { acc } else { Cons { head: s, tail: acc } } }) -} - -// The contract over one recount's live sites. -fn argv_word_construction_census_check_sites(sites: List) -> ProcessExit { - argv_word_construction_gates(live: argv_word_construction_census_live(sites: sites), roster: argv_word_construction_census_roster, baseline_population: argv_word_construction_census_baseline_population) -} - -// The recount entry: discover the roots' module files by the declaration index (never a text -// search), walk each with the lens, and hold the contract. -fn argv_word_construction_census_recount() -> ProcessExit { - argv_word_construction_census_check_paths(paths: argv_word_construction_census_root_paths()) -} - -// The discovered modeled roots' module files, in declaration-index order. Shared by the gated -// recount above and the roster emitters below so all see the same population. Parameterized by -// roots: the corpus walk is run ONE ROOT PER PROCESS (a wet lane kills a whole-corpus walk with -// OOM -- the per-process journal of read+parse dispatches is what grows, so the split is by the -// roots boundary, the population's own partition). -fn argv_word_construction_census_root_paths_for_roots(roots: List) -> List { - let decls = module_declaration_facts_live(pool_roots: roots) - let paths_reversed = fold_list(xs: decls, empty: Empty, cons: fn(acc, d) { - let p = declared_module_path(d: d) - if p == "" { acc } else { Cons { head: p, tail: acc } } - }) - list_reverse(xs: paths_reversed) -} - -fn argv_word_construction_census_root_paths() -> List { - argv_word_construction_census_root_paths_for_roots(roots: argv_word_construction_census_roots) -} - -// The roster emitter for a wet-lane run (claim_batch --wet): the same discovered population, each -// module walked by the lens, rows written as TSV to the scratch path. The committed roster data -// decl above is filled FROM this output, never by hand from a text search. Hermetic lanes refuse -// the write by design -- the emitter exists for the recount, not for CI. -fn argv_word_construction_census_recount_emit() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths(), out: "target/argv_word_construction_census_recount.tsv") -} - -fn argv_word_construction_census_recount_emit_extdeps() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/extdeps"]), out: "target/argv_word_construction_census_recount_extdeps.tsv") -} - -fn argv_word_construction_census_recount_emit_gunbc() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/gunbc"]), out: "target/argv_word_construction_census_recount_gunbc.tsv") -} - -fn declared_module_path(d: ModuleDeclarationFact) -> String { - d.path -} - -// The distinct module files named by the committed roster, in first-appearance order: exactly the -// population the bounded-contract claim re-walks, so CI executes the gates at roster size without -// ever walking the whole corpus. -fn argv_word_construction_census_roster_modules() -> List { - let reversed = fold_list(xs: argv_word_construction_census_roster, empty: Empty, cons: fn(acc, r) { - if contains_string(xs: acc, x: r.module_file) { acc } else { Cons { head: r.module_file, tail: acc } } - }) - list_reverse(xs: reversed) -} - -fn contains_string(xs: List, x: String) -> Bool { - fold_list(xs: xs, empty: false, cons: fn(acc, y) { acc || (y == x) }) -} - -type ArgvWordsCollected { - sites: FreeMonoid - refusals: FreeMonoid -} - -fn collect_path(c: ArgvWordsCollected, p: String) -> ArgvWordsCollected { - match argv_word_construction_module_sites(path: p) { - ArgvWordsModuleRefused { path: rp, reason: r } => - ArgvWordsCollected { sites: c.sites, refusals: Cons { head: concat(concat(concat("REFUSED\t", rp), "\t"), r), tail: c.refusals } } - ArgvWordsModuleEstablished { path: _, sites: sites } => - ArgvWordsCollected { sites: fold_list(xs: sites, empty: c.sites, cons: fn(acc, s) { Cons { head: s, tail: acc } }), refusals: c.refusals } - } -} - -// The contract over an explicit path list: the per-module walk is the lens's; the gates run on the -// collected live sites, and every per-module refusal is named, never dropped. Kept separate from -// the discovery so a bounded recount (one module, one directory) is runnable in a claim test -// without the whole corpus. -fn argv_word_construction_census_check_paths(paths: List) -> ProcessExit { - let collected = fold_list(xs: paths, empty: ArgvWordsCollected { sites: Empty, refusals: Empty }, cons: collect_path) - match list_reverse(xs: collected.refusals) { - Empty => argv_word_construction_census_check_sites(sites: list_reverse(xs: collected.sites)) - nonempty => exit_failure(reason: join_lines(rs: nonempty)) - } -} - -fn join_lines(rs: FreeMonoid) -> String { - fold_list(xs: list_reverse(xs: rs), empty: "", cons: fn(acc, l) { if acc == "" { l } else { concat(concat(acc, "\n"), l) } }) -} -// The fixture root is OUTSIDE the modeled roots by design (the boundary-refusal claim pins that), -// so the walk above never sees it and for_paths refuses any module outside the roots. The -// fixture's facts are therefore read at the FACT level -- the same read/parse/facts_of route the -// fixture claims use -- and rendered where a human reads them: one TSV row per fact with every -// field the fixture claims pin. This is the observation those claims' expectations are aligned -// against, not a hand-maintained parallel list of what the lens is supposed to say. -fn argv_word_construction_census_fixture_facts_dump() -> ProcessExit { - let read = Filesystem.Read(path: "src/v2/test/fixture_roots/argv_word_construction/host_words.dag") - match read.success { - false => exit_failure(reason: "fixture facts absent: fixture unreadable") - true => - match raw_tree_of_text(text: read.content) { - RawTreeNotEstablished { cause: cause } => exit_failure(reason: concat("fixture unparseable: ", cause)) - RawTreeEstablished { root: root } => fixture_facts_write(root: root) - } - } -} - -fn fixture_facts_write(root: Node) -> ProcessExit { - let rows = fold_list(xs: facts_of(root: root), empty: "decl\tkind\tcounted\tcandidates_n\tcandidates\tflag_words_n\treturns_argv_words\tlist_string_params\tlist_string_data", cons: fn(acc, f) { - concat(acc, concat("\n", fixture_fact_row(f: f))) - }) - let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: rows) - if written.success { ExitSuccess } else { exit_failure(reason: concat("fixture facts write refused: ", written.error)) } -} - -fn fixture_fact_row(f: ArgvWordsFact) -> String { - concat(f.decl, concat("\t", concat(fact_kind_word(k: f.decl_kind), concat("\t", concat(bool_word(b: f.counted), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.candidates)), concat("\t", concat(join(f.candidates, ";"), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.flag_words)), concat("\t", concat(bool_word(b: f.returns_argv_words), concat("\t", concat(integer_int_to_decimal_string(value: f.list_string_params), concat("\t", bool_word(b: f.list_string_data)))))))))))))))))) -} - -fn fact_kind_word(k: ArgvWordsDeclKind) -> String { - match k { - FnDecl => "FnDecl" - DataDecl => "DataDecl" - } -} - -fn bool_word(b: Bool) -> String { - match b { - true => "true" - false => "false" - } -} - diff --git a/dag/gunbc/instruments/bisect_tmp2/v5_dumpfn.dag b/dag/gunbc/instruments/bisect_tmp2/v5_dumpfn.dag deleted file mode 100644 index bc6795f87de..00000000000 --- a/dag/gunbc/instruments/bisect_tmp2/v5_dumpfn.dag +++ /dev/null @@ -1,192 +0,0 @@ -module gunbc.instruments.argv_word_construction_census - -// THE TYPED-ARGV CONSTRUCTION CENSUS ROSTER (debt paydown trial, witty-tern-54, 2026-10-03). -// -// The instrument is v2.lens.argv_word_construction; this module commits WHAT IT COUNTS: the -// population of argv-string construction sites over dag/extdeps and dag/gunbc, at identity grain, -// as a typed roster that may only shrink. DESIGN 5: a count over the current tree is not an -// oracle — every row below joins to a live census member at (module_file, decl, decl_kind), a -// row that stops resolving refuses the recount, and the population never grows past the committed -// baseline: new string-built argv is refused, not enrolled. -// -// Recount (the named, re-runnable instrument run): discover the two roots' module files by the -// declaration index, walk each with the lens, and hold the gates over (live, roster, baseline). -// The gates live in the lens; this module owns the committed state: the roster, the baseline -// cardinality, and the sites classified as NOT argv debt with a stated reason. - -import v2.lens.argv_word_construction { - ArgvWordsDeclKind, - ArgvWordsRosterRow, - ArgvWordsSite, - ArgvWordsSiteClass, - ExtdepsInterfaceBuilder, - FnDecl, - DataDecl, - WorkflowCallSite, - ArgvWordsModuleEstablished, - ArgvWordsModuleRefused, - argv_word_construction_gates, - argv_word_construction_module_sites, - argv_word_construction_for_paths -} -import v2.std.algebra { fold_list, list_reverse } -import v2.std.collection { List } -import std.algebra { Cons, Empty, FreeMonoid } -import std.process { ExitSuccess, ProcessExit, exit_failure } -import extdeps.filesystem.filesystem_io { Filesystem } -import v2.std.node { Node } -import v2.std.integer { integer_int_to_decimal_string } -import v2.lens.module_graph { ModuleDeclarationFact, module_declaration_facts_live } - -// The modeled roots. Anything else is refused by the lens, never silently zero. -data argv_word_construction_census_roots: List = ["dag/extdeps", "dag/gunbc"] - -// THE COMMITTED BASELINE: the population the instrument counted over the two roots at the start -// of the trial (origin/main, 2026-10-03). The roster may only shrink past this number. -data argv_word_construction_census_baseline_population: Int = 0 - -// THE ROSTER: one row per live census member. `note` names what the site is and what pays it. -// Kept at zero rows until the first committed recount fills it from the instrument (never by -// hand from a text search). -data argv_word_construction_census_roster: List = [] - -// Sites whose evidence is read and REFUSED AS DEBT with a stated reason: the census classifies, -// it does not silently count. Each entry names its reason; a reason that stops being true must -// delete the entry (the same shrink discipline as the roster). -data argv_word_construction_census_excluded: List = [] - -type ArgvWordsExclusion { - module_file: String - decl: String - reason: String -} - -fn excluded_identity_matches(e: ArgvWordsExclusion, s: ArgvWordsSite) -> Bool { - (e.module_file == s.module_file) && (e.decl == s.decl) -} - -fn site_is_excluded(s: ArgvWordsSite) -> Bool { - fold_list(xs: argv_word_construction_census_excluded, empty: false, cons: fn(acc, e) { acc || excluded_identity_matches(e: e, s: s) }) -} - -// The live census members after classification: exclusions are visible subtractions with reasons. -fn argv_word_construction_census_live(sites: List) -> List { - fold_list(xs: sites, empty: Empty, cons: fn(acc, s) { if site_is_excluded(s: s) { acc } else { Cons { head: s, tail: acc } } }) -} - -// The contract over one recount's live sites. -fn argv_word_construction_census_check_sites(sites: List) -> ProcessExit { - argv_word_construction_gates(live: argv_word_construction_census_live(sites: sites), roster: argv_word_construction_census_roster, baseline_population: argv_word_construction_census_baseline_population) -} - -// The recount entry: discover the roots' module files by the declaration index (never a text -// search), walk each with the lens, and hold the contract. -fn argv_word_construction_census_recount() -> ProcessExit { - argv_word_construction_census_check_paths(paths: argv_word_construction_census_root_paths()) -} - -// The discovered modeled roots' module files, in declaration-index order. Shared by the gated -// recount above and the roster emitters below so all see the same population. Parameterized by -// roots: the corpus walk is run ONE ROOT PER PROCESS (a wet lane kills a whole-corpus walk with -// OOM -- the per-process journal of read+parse dispatches is what grows, so the split is by the -// roots boundary, the population's own partition). -fn argv_word_construction_census_root_paths_for_roots(roots: List) -> List { - let decls = module_declaration_facts_live(pool_roots: roots) - let paths_reversed = fold_list(xs: decls, empty: Empty, cons: fn(acc, d) { - let p = declared_module_path(d: d) - if p == "" { acc } else { Cons { head: p, tail: acc } } - }) - list_reverse(xs: paths_reversed) -} - -fn argv_word_construction_census_root_paths() -> List { - argv_word_construction_census_root_paths_for_roots(roots: argv_word_construction_census_roots) -} - -// The roster emitter for a wet-lane run (claim_batch --wet): the same discovered population, each -// module walked by the lens, rows written as TSV to the scratch path. The committed roster data -// decl above is filled FROM this output, never by hand from a text search. Hermetic lanes refuse -// the write by design -- the emitter exists for the recount, not for CI. -fn argv_word_construction_census_recount_emit() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths(), out: "target/argv_word_construction_census_recount.tsv") -} - -fn argv_word_construction_census_recount_emit_extdeps() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/extdeps"]), out: "target/argv_word_construction_census_recount_extdeps.tsv") -} - -fn argv_word_construction_census_recount_emit_gunbc() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/gunbc"]), out: "target/argv_word_construction_census_recount_gunbc.tsv") -} - -fn declared_module_path(d: ModuleDeclarationFact) -> String { - d.path -} - -// The distinct module files named by the committed roster, in first-appearance order: exactly the -// population the bounded-contract claim re-walks, so CI executes the gates at roster size without -// ever walking the whole corpus. -fn argv_word_construction_census_roster_modules() -> List { - let reversed = fold_list(xs: argv_word_construction_census_roster, empty: Empty, cons: fn(acc, r) { - if contains_string(xs: acc, x: r.module_file) { acc } else { Cons { head: r.module_file, tail: acc } } - }) - list_reverse(xs: reversed) -} - -fn contains_string(xs: List, x: String) -> Bool { - fold_list(xs: xs, empty: false, cons: fn(acc, y) { acc || (y == x) }) -} - -type ArgvWordsCollected { - sites: FreeMonoid - refusals: FreeMonoid -} - -fn collect_path(c: ArgvWordsCollected, p: String) -> ArgvWordsCollected { - match argv_word_construction_module_sites(path: p) { - ArgvWordsModuleRefused { path: rp, reason: r } => - ArgvWordsCollected { sites: c.sites, refusals: Cons { head: concat(concat(concat("REFUSED\t", rp), "\t"), r), tail: c.refusals } } - ArgvWordsModuleEstablished { path: _, sites: sites } => - ArgvWordsCollected { sites: fold_list(xs: sites, empty: c.sites, cons: fn(acc, s) { Cons { head: s, tail: acc } }), refusals: c.refusals } - } -} - -// The contract over an explicit path list: the per-module walk is the lens's; the gates run on the -// collected live sites, and every per-module refusal is named, never dropped. Kept separate from -// the discovery so a bounded recount (one module, one directory) is runnable in a claim test -// without the whole corpus. -fn argv_word_construction_census_check_paths(paths: List) -> ProcessExit { - let collected = fold_list(xs: paths, empty: ArgvWordsCollected { sites: Empty, refusals: Empty }, cons: collect_path) - match list_reverse(xs: collected.refusals) { - Empty => argv_word_construction_census_check_sites(sites: list_reverse(xs: collected.sites)) - nonempty => exit_failure(reason: join_lines(rs: nonempty)) - } -} - -fn join_lines(rs: FreeMonoid) -> String { - fold_list(xs: list_reverse(xs: rs), empty: "", cons: fn(acc, l) { if acc == "" { l } else { concat(concat(acc, "\n"), l) } }) -} - -fn argv_word_construction_census_fixture_facts_dump() -> ProcessExit { - let read = Filesystem.Read(path: "src/v2/test/fixture_roots/argv_word_construction/host_words.dag") - let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: read.content) - if written.success { ExitSuccess } else { exit_failure(reason: concat("fixture facts write refused: ", written.error)) } -} -// The fixture root is OUTSIDE the modeled roots by design (the boundary-refusal claim pins that), -// so the walk above never sees it and for_paths refuses any module outside the roots. The -// fixture's facts are therefore read at the FACT level -- the same read/parse/facts_of route the -// fixture claims use -- and rendered where a human reads them: one TSV row per fact with every -// field the fixture claims pin. This is the observation those claims' expectations are aligned -// against, not a hand-maintained parallel list of what the lens is supposed to say. -fn argv_word_construction_census_fixture_facts_dump() -> ProcessExit { - let read = Filesystem.Read(path: "src/v2/test/fixture_roots/argv_word_construction/host_words.dag") - match read.success { - false => exit_failure(reason: "fixture facts absent: fixture unreadable") - true => - match raw_tree_of_text(text: read.content) { - RawTreeNotEstablished { cause: cause } => exit_failure(reason: concat("fixture unparseable: ", cause)) - RawTreeEstablished { root: root } => fixture_facts_write(root: root) - } - } -} - diff --git a/dag/gunbc/instruments/bisect_tmp2/v6_writefn.dag b/dag/gunbc/instruments/bisect_tmp2/v6_writefn.dag deleted file mode 100644 index a4cc42005f3..00000000000 --- a/dag/gunbc/instruments/bisect_tmp2/v6_writefn.dag +++ /dev/null @@ -1,188 +0,0 @@ -module gunbc.instruments.argv_word_construction_census - -// THE TYPED-ARGV CONSTRUCTION CENSUS ROSTER (debt paydown trial, witty-tern-54, 2026-10-03). -// -// The instrument is v2.lens.argv_word_construction; this module commits WHAT IT COUNTS: the -// population of argv-string construction sites over dag/extdeps and dag/gunbc, at identity grain, -// as a typed roster that may only shrink. DESIGN 5: a count over the current tree is not an -// oracle — every row below joins to a live census member at (module_file, decl, decl_kind), a -// row that stops resolving refuses the recount, and the population never grows past the committed -// baseline: new string-built argv is refused, not enrolled. -// -// Recount (the named, re-runnable instrument run): discover the two roots' module files by the -// declaration index, walk each with the lens, and hold the gates over (live, roster, baseline). -// The gates live in the lens; this module owns the committed state: the roster, the baseline -// cardinality, and the sites classified as NOT argv debt with a stated reason. - -import v2.lens.argv_word_construction { - ArgvWordsDeclKind, - ArgvWordsRosterRow, - ArgvWordsSite, - ArgvWordsSiteClass, - ExtdepsInterfaceBuilder, - FnDecl, - DataDecl, - WorkflowCallSite, - ArgvWordsModuleEstablished, - ArgvWordsModuleRefused, - argv_word_construction_gates, - argv_word_construction_module_sites, - argv_word_construction_for_paths -} -import v2.std.algebra { fold_list, list_reverse } -import v2.std.collection { List } -import std.algebra { Cons, Empty, FreeMonoid } -import std.process { ExitSuccess, ProcessExit, exit_failure } -import extdeps.filesystem.filesystem_io { Filesystem } -import v2.std.node { Node } -import v2.std.integer { integer_int_to_decimal_string } -import v2.lens.module_graph { ModuleDeclarationFact, module_declaration_facts_live } - -// The modeled roots. Anything else is refused by the lens, never silently zero. -data argv_word_construction_census_roots: List = ["dag/extdeps", "dag/gunbc"] - -// THE COMMITTED BASELINE: the population the instrument counted over the two roots at the start -// of the trial (origin/main, 2026-10-03). The roster may only shrink past this number. -data argv_word_construction_census_baseline_population: Int = 0 - -// THE ROSTER: one row per live census member. `note` names what the site is and what pays it. -// Kept at zero rows until the first committed recount fills it from the instrument (never by -// hand from a text search). -data argv_word_construction_census_roster: List = [] - -// Sites whose evidence is read and REFUSED AS DEBT with a stated reason: the census classifies, -// it does not silently count. Each entry names its reason; a reason that stops being true must -// delete the entry (the same shrink discipline as the roster). -data argv_word_construction_census_excluded: List = [] - -type ArgvWordsExclusion { - module_file: String - decl: String - reason: String -} - -fn excluded_identity_matches(e: ArgvWordsExclusion, s: ArgvWordsSite) -> Bool { - (e.module_file == s.module_file) && (e.decl == s.decl) -} - -fn site_is_excluded(s: ArgvWordsSite) -> Bool { - fold_list(xs: argv_word_construction_census_excluded, empty: false, cons: fn(acc, e) { acc || excluded_identity_matches(e: e, s: s) }) -} - -// The live census members after classification: exclusions are visible subtractions with reasons. -fn argv_word_construction_census_live(sites: List) -> List { - fold_list(xs: sites, empty: Empty, cons: fn(acc, s) { if site_is_excluded(s: s) { acc } else { Cons { head: s, tail: acc } } }) -} - -// The contract over one recount's live sites. -fn argv_word_construction_census_check_sites(sites: List) -> ProcessExit { - argv_word_construction_gates(live: argv_word_construction_census_live(sites: sites), roster: argv_word_construction_census_roster, baseline_population: argv_word_construction_census_baseline_population) -} - -// The recount entry: discover the roots' module files by the declaration index (never a text -// search), walk each with the lens, and hold the contract. -fn argv_word_construction_census_recount() -> ProcessExit { - argv_word_construction_census_check_paths(paths: argv_word_construction_census_root_paths()) -} - -// The discovered modeled roots' module files, in declaration-index order. Shared by the gated -// recount above and the roster emitters below so all see the same population. Parameterized by -// roots: the corpus walk is run ONE ROOT PER PROCESS (a wet lane kills a whole-corpus walk with -// OOM -- the per-process journal of read+parse dispatches is what grows, so the split is by the -// roots boundary, the population's own partition). -fn argv_word_construction_census_root_paths_for_roots(roots: List) -> List { - let decls = module_declaration_facts_live(pool_roots: roots) - let paths_reversed = fold_list(xs: decls, empty: Empty, cons: fn(acc, d) { - let p = declared_module_path(d: d) - if p == "" { acc } else { Cons { head: p, tail: acc } } - }) - list_reverse(xs: paths_reversed) -} - -fn argv_word_construction_census_root_paths() -> List { - argv_word_construction_census_root_paths_for_roots(roots: argv_word_construction_census_roots) -} - -// The roster emitter for a wet-lane run (claim_batch --wet): the same discovered population, each -// module walked by the lens, rows written as TSV to the scratch path. The committed roster data -// decl above is filled FROM this output, never by hand from a text search. Hermetic lanes refuse -// the write by design -- the emitter exists for the recount, not for CI. -fn argv_word_construction_census_recount_emit() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths(), out: "target/argv_word_construction_census_recount.tsv") -} - -fn argv_word_construction_census_recount_emit_extdeps() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/extdeps"]), out: "target/argv_word_construction_census_recount_extdeps.tsv") -} - -fn argv_word_construction_census_recount_emit_gunbc() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/gunbc"]), out: "target/argv_word_construction_census_recount_gunbc.tsv") -} - -fn declared_module_path(d: ModuleDeclarationFact) -> String { - d.path -} - -// The distinct module files named by the committed roster, in first-appearance order: exactly the -// population the bounded-contract claim re-walks, so CI executes the gates at roster size without -// ever walking the whole corpus. -fn argv_word_construction_census_roster_modules() -> List { - let reversed = fold_list(xs: argv_word_construction_census_roster, empty: Empty, cons: fn(acc, r) { - if contains_string(xs: acc, x: r.module_file) { acc } else { Cons { head: r.module_file, tail: acc } } - }) - list_reverse(xs: reversed) -} - -fn contains_string(xs: List, x: String) -> Bool { - fold_list(xs: xs, empty: false, cons: fn(acc, y) { acc || (y == x) }) -} - -type ArgvWordsCollected { - sites: FreeMonoid - refusals: FreeMonoid -} - -fn collect_path(c: ArgvWordsCollected, p: String) -> ArgvWordsCollected { - match argv_word_construction_module_sites(path: p) { - ArgvWordsModuleRefused { path: rp, reason: r } => - ArgvWordsCollected { sites: c.sites, refusals: Cons { head: concat(concat(concat("REFUSED\t", rp), "\t"), r), tail: c.refusals } } - ArgvWordsModuleEstablished { path: _, sites: sites } => - ArgvWordsCollected { sites: fold_list(xs: sites, empty: c.sites, cons: fn(acc, s) { Cons { head: s, tail: acc } }), refusals: c.refusals } - } -} - -// The contract over an explicit path list: the per-module walk is the lens's; the gates run on the -// collected live sites, and every per-module refusal is named, never dropped. Kept separate from -// the discovery so a bounded recount (one module, one directory) is runnable in a claim test -// without the whole corpus. -fn argv_word_construction_census_check_paths(paths: List) -> ProcessExit { - let collected = fold_list(xs: paths, empty: ArgvWordsCollected { sites: Empty, refusals: Empty }, cons: collect_path) - match list_reverse(xs: collected.refusals) { - Empty => argv_word_construction_census_check_sites(sites: list_reverse(xs: collected.sites)) - nonempty => exit_failure(reason: join_lines(rs: nonempty)) - } -} - -fn join_lines(rs: FreeMonoid) -> String { - fold_list(xs: list_reverse(xs: rs), empty: "", cons: fn(acc, l) { if acc == "" { l } else { concat(concat(acc, "\n"), l) } }) -} - -fn argv_word_construction_census_fixture_facts_dump() -> ProcessExit { - let read = Filesystem.Read(path: "src/v2/test/fixture_roots/argv_word_construction/host_words.dag") - let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: read.content) - if written.success { ExitSuccess } else { exit_failure(reason: concat("fixture facts write refused: ", written.error)) } -} -// The fixture root is OUTSIDE the modeled roots by design (the boundary-refusal claim pins that), -// so the walk above never sees it and for_paths refuses any module outside the roots. The -// fixture's facts are therefore read at the FACT level -- the same read/parse/facts_of route the -// fixture claims use -- and rendered where a human reads them: one TSV row per fact with every -// field the fixture claims pin. This is the observation those claims' expectations are aligned -// against, not a hand-maintained parallel list of what the lens is supposed to say. -fn fixture_facts_write(root: Node) -> ProcessExit { - let rows = fold_list(xs: facts_of(root: root), empty: "decl\tkind\tcounted\tcandidates_n\tcandidates\tflag_words_n\treturns_argv_words\tlist_string_params\tlist_string_data", cons: fn(acc, f) { - concat(acc, concat("\n", fixture_fact_row(f: f))) - }) - let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: rows) - if written.success { ExitSuccess } else { exit_failure(reason: concat("fixture facts write refused: ", written.error)) } -} - diff --git a/dag/gunbc/instruments/bisect_tmp2/v7_rowfn.dag b/dag/gunbc/instruments/bisect_tmp2/v7_rowfn.dag deleted file mode 100644 index 1f7fca9a0b6..00000000000 --- a/dag/gunbc/instruments/bisect_tmp2/v7_rowfn.dag +++ /dev/null @@ -1,184 +0,0 @@ -module gunbc.instruments.argv_word_construction_census - -// THE TYPED-ARGV CONSTRUCTION CENSUS ROSTER (debt paydown trial, witty-tern-54, 2026-10-03). -// -// The instrument is v2.lens.argv_word_construction; this module commits WHAT IT COUNTS: the -// population of argv-string construction sites over dag/extdeps and dag/gunbc, at identity grain, -// as a typed roster that may only shrink. DESIGN 5: a count over the current tree is not an -// oracle — every row below joins to a live census member at (module_file, decl, decl_kind), a -// row that stops resolving refuses the recount, and the population never grows past the committed -// baseline: new string-built argv is refused, not enrolled. -// -// Recount (the named, re-runnable instrument run): discover the two roots' module files by the -// declaration index, walk each with the lens, and hold the gates over (live, roster, baseline). -// The gates live in the lens; this module owns the committed state: the roster, the baseline -// cardinality, and the sites classified as NOT argv debt with a stated reason. - -import v2.lens.argv_word_construction { - ArgvWordsDeclKind, - ArgvWordsRosterRow, - ArgvWordsSite, - ArgvWordsSiteClass, - ExtdepsInterfaceBuilder, - FnDecl, - DataDecl, - WorkflowCallSite, - ArgvWordsModuleEstablished, - ArgvWordsModuleRefused, - argv_word_construction_gates, - argv_word_construction_module_sites, - argv_word_construction_for_paths -} -import v2.std.algebra { fold_list, list_reverse } -import v2.std.collection { List } -import std.algebra { Cons, Empty, FreeMonoid } -import std.process { ExitSuccess, ProcessExit, exit_failure } -import extdeps.filesystem.filesystem_io { Filesystem } -import v2.std.node { Node } -import v2.std.integer { integer_int_to_decimal_string } -import v2.lens.module_graph { ModuleDeclarationFact, module_declaration_facts_live } - -// The modeled roots. Anything else is refused by the lens, never silently zero. -data argv_word_construction_census_roots: List = ["dag/extdeps", "dag/gunbc"] - -// THE COMMITTED BASELINE: the population the instrument counted over the two roots at the start -// of the trial (origin/main, 2026-10-03). The roster may only shrink past this number. -data argv_word_construction_census_baseline_population: Int = 0 - -// THE ROSTER: one row per live census member. `note` names what the site is and what pays it. -// Kept at zero rows until the first committed recount fills it from the instrument (never by -// hand from a text search). -data argv_word_construction_census_roster: List = [] - -// Sites whose evidence is read and REFUSED AS DEBT with a stated reason: the census classifies, -// it does not silently count. Each entry names its reason; a reason that stops being true must -// delete the entry (the same shrink discipline as the roster). -data argv_word_construction_census_excluded: List = [] - -type ArgvWordsExclusion { - module_file: String - decl: String - reason: String -} - -fn excluded_identity_matches(e: ArgvWordsExclusion, s: ArgvWordsSite) -> Bool { - (e.module_file == s.module_file) && (e.decl == s.decl) -} - -fn site_is_excluded(s: ArgvWordsSite) -> Bool { - fold_list(xs: argv_word_construction_census_excluded, empty: false, cons: fn(acc, e) { acc || excluded_identity_matches(e: e, s: s) }) -} - -// The live census members after classification: exclusions are visible subtractions with reasons. -fn argv_word_construction_census_live(sites: List) -> List { - fold_list(xs: sites, empty: Empty, cons: fn(acc, s) { if site_is_excluded(s: s) { acc } else { Cons { head: s, tail: acc } } }) -} - -// The contract over one recount's live sites. -fn argv_word_construction_census_check_sites(sites: List) -> ProcessExit { - argv_word_construction_gates(live: argv_word_construction_census_live(sites: sites), roster: argv_word_construction_census_roster, baseline_population: argv_word_construction_census_baseline_population) -} - -// The recount entry: discover the roots' module files by the declaration index (never a text -// search), walk each with the lens, and hold the contract. -fn argv_word_construction_census_recount() -> ProcessExit { - argv_word_construction_census_check_paths(paths: argv_word_construction_census_root_paths()) -} - -// The discovered modeled roots' module files, in declaration-index order. Shared by the gated -// recount above and the roster emitters below so all see the same population. Parameterized by -// roots: the corpus walk is run ONE ROOT PER PROCESS (a wet lane kills a whole-corpus walk with -// OOM -- the per-process journal of read+parse dispatches is what grows, so the split is by the -// roots boundary, the population's own partition). -fn argv_word_construction_census_root_paths_for_roots(roots: List) -> List { - let decls = module_declaration_facts_live(pool_roots: roots) - let paths_reversed = fold_list(xs: decls, empty: Empty, cons: fn(acc, d) { - let p = declared_module_path(d: d) - if p == "" { acc } else { Cons { head: p, tail: acc } } - }) - list_reverse(xs: paths_reversed) -} - -fn argv_word_construction_census_root_paths() -> List { - argv_word_construction_census_root_paths_for_roots(roots: argv_word_construction_census_roots) -} - -// The roster emitter for a wet-lane run (claim_batch --wet): the same discovered population, each -// module walked by the lens, rows written as TSV to the scratch path. The committed roster data -// decl above is filled FROM this output, never by hand from a text search. Hermetic lanes refuse -// the write by design -- the emitter exists for the recount, not for CI. -fn argv_word_construction_census_recount_emit() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths(), out: "target/argv_word_construction_census_recount.tsv") -} - -fn argv_word_construction_census_recount_emit_extdeps() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/extdeps"]), out: "target/argv_word_construction_census_recount_extdeps.tsv") -} - -fn argv_word_construction_census_recount_emit_gunbc() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/gunbc"]), out: "target/argv_word_construction_census_recount_gunbc.tsv") -} - -fn declared_module_path(d: ModuleDeclarationFact) -> String { - d.path -} - -// The distinct module files named by the committed roster, in first-appearance order: exactly the -// population the bounded-contract claim re-walks, so CI executes the gates at roster size without -// ever walking the whole corpus. -fn argv_word_construction_census_roster_modules() -> List { - let reversed = fold_list(xs: argv_word_construction_census_roster, empty: Empty, cons: fn(acc, r) { - if contains_string(xs: acc, x: r.module_file) { acc } else { Cons { head: r.module_file, tail: acc } } - }) - list_reverse(xs: reversed) -} - -fn contains_string(xs: List, x: String) -> Bool { - fold_list(xs: xs, empty: false, cons: fn(acc, y) { acc || (y == x) }) -} - -type ArgvWordsCollected { - sites: FreeMonoid - refusals: FreeMonoid -} - -fn collect_path(c: ArgvWordsCollected, p: String) -> ArgvWordsCollected { - match argv_word_construction_module_sites(path: p) { - ArgvWordsModuleRefused { path: rp, reason: r } => - ArgvWordsCollected { sites: c.sites, refusals: Cons { head: concat(concat(concat("REFUSED\t", rp), "\t"), r), tail: c.refusals } } - ArgvWordsModuleEstablished { path: _, sites: sites } => - ArgvWordsCollected { sites: fold_list(xs: sites, empty: c.sites, cons: fn(acc, s) { Cons { head: s, tail: acc } }), refusals: c.refusals } - } -} - -// The contract over an explicit path list: the per-module walk is the lens's; the gates run on the -// collected live sites, and every per-module refusal is named, never dropped. Kept separate from -// the discovery so a bounded recount (one module, one directory) is runnable in a claim test -// without the whole corpus. -fn argv_word_construction_census_check_paths(paths: List) -> ProcessExit { - let collected = fold_list(xs: paths, empty: ArgvWordsCollected { sites: Empty, refusals: Empty }, cons: collect_path) - match list_reverse(xs: collected.refusals) { - Empty => argv_word_construction_census_check_sites(sites: list_reverse(xs: collected.sites)) - nonempty => exit_failure(reason: join_lines(rs: nonempty)) - } -} - -fn join_lines(rs: FreeMonoid) -> String { - fold_list(xs: list_reverse(xs: rs), empty: "", cons: fn(acc, l) { if acc == "" { l } else { concat(concat(acc, "\n"), l) } }) -} - -fn argv_word_construction_census_fixture_facts_dump() -> ProcessExit { - let read = Filesystem.Read(path: "src/v2/test/fixture_roots/argv_word_construction/host_words.dag") - let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: read.content) - if written.success { ExitSuccess } else { exit_failure(reason: concat("fixture facts write refused: ", written.error)) } -} -// The fixture root is OUTSIDE the modeled roots by design (the boundary-refusal claim pins that), -// so the walk above never sees it and for_paths refuses any module outside the roots. The -// fixture's facts are therefore read at the FACT level -- the same read/parse/facts_of route the -// fixture claims use -- and rendered where a human reads them: one TSV row per fact with every -// field the fixture claims pin. This is the observation those claims' expectations are aligned -// against, not a hand-maintained parallel list of what the lens is supposed to say. -fn fixture_fact_row(f: ArgvWordsFact) -> String { - concat(f.decl, concat("\t", concat(fact_kind_word(k: f.decl_kind), concat("\t", concat(bool_word(b: f.counted), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.candidates)), concat("\t", concat(join(f.candidates, ";"), concat("\t", concat(integer_int_to_decimal_string(value: length(xs: f.flag_words)), concat("\t", concat(bool_word(b: f.returns_argv_words), concat("\t", concat(integer_int_to_decimal_string(value: f.list_string_params), concat("\t", bool_word(b: f.list_string_data)))))))))))))))))) -} - diff --git a/dag/gunbc/instruments/bisect_tmp2/v8_kinds.dag b/dag/gunbc/instruments/bisect_tmp2/v8_kinds.dag deleted file mode 100644 index cc540ea8c34..00000000000 --- a/dag/gunbc/instruments/bisect_tmp2/v8_kinds.dag +++ /dev/null @@ -1,194 +0,0 @@ -module gunbc.instruments.argv_word_construction_census - -// THE TYPED-ARGV CONSTRUCTION CENSUS ROSTER (debt paydown trial, witty-tern-54, 2026-10-03). -// -// The instrument is v2.lens.argv_word_construction; this module commits WHAT IT COUNTS: the -// population of argv-string construction sites over dag/extdeps and dag/gunbc, at identity grain, -// as a typed roster that may only shrink. DESIGN 5: a count over the current tree is not an -// oracle — every row below joins to a live census member at (module_file, decl, decl_kind), a -// row that stops resolving refuses the recount, and the population never grows past the committed -// baseline: new string-built argv is refused, not enrolled. -// -// Recount (the named, re-runnable instrument run): discover the two roots' module files by the -// declaration index, walk each with the lens, and hold the gates over (live, roster, baseline). -// The gates live in the lens; this module owns the committed state: the roster, the baseline -// cardinality, and the sites classified as NOT argv debt with a stated reason. - -import v2.lens.argv_word_construction { - ArgvWordsDeclKind, - ArgvWordsRosterRow, - ArgvWordsSite, - ArgvWordsSiteClass, - ExtdepsInterfaceBuilder, - FnDecl, - DataDecl, - WorkflowCallSite, - ArgvWordsModuleEstablished, - ArgvWordsModuleRefused, - argv_word_construction_gates, - argv_word_construction_module_sites, - argv_word_construction_for_paths -} -import v2.std.algebra { fold_list, list_reverse } -import v2.std.collection { List } -import std.algebra { Cons, Empty, FreeMonoid } -import std.process { ExitSuccess, ProcessExit, exit_failure } -import extdeps.filesystem.filesystem_io { Filesystem } -import v2.std.node { Node } -import v2.std.integer { integer_int_to_decimal_string } -import v2.lens.module_graph { ModuleDeclarationFact, module_declaration_facts_live } - -// The modeled roots. Anything else is refused by the lens, never silently zero. -data argv_word_construction_census_roots: List = ["dag/extdeps", "dag/gunbc"] - -// THE COMMITTED BASELINE: the population the instrument counted over the two roots at the start -// of the trial (origin/main, 2026-10-03). The roster may only shrink past this number. -data argv_word_construction_census_baseline_population: Int = 0 - -// THE ROSTER: one row per live census member. `note` names what the site is and what pays it. -// Kept at zero rows until the first committed recount fills it from the instrument (never by -// hand from a text search). -data argv_word_construction_census_roster: List = [] - -// Sites whose evidence is read and REFUSED AS DEBT with a stated reason: the census classifies, -// it does not silently count. Each entry names its reason; a reason that stops being true must -// delete the entry (the same shrink discipline as the roster). -data argv_word_construction_census_excluded: List = [] - -type ArgvWordsExclusion { - module_file: String - decl: String - reason: String -} - -fn excluded_identity_matches(e: ArgvWordsExclusion, s: ArgvWordsSite) -> Bool { - (e.module_file == s.module_file) && (e.decl == s.decl) -} - -fn site_is_excluded(s: ArgvWordsSite) -> Bool { - fold_list(xs: argv_word_construction_census_excluded, empty: false, cons: fn(acc, e) { acc || excluded_identity_matches(e: e, s: s) }) -} - -// The live census members after classification: exclusions are visible subtractions with reasons. -fn argv_word_construction_census_live(sites: List) -> List { - fold_list(xs: sites, empty: Empty, cons: fn(acc, s) { if site_is_excluded(s: s) { acc } else { Cons { head: s, tail: acc } } }) -} - -// The contract over one recount's live sites. -fn argv_word_construction_census_check_sites(sites: List) -> ProcessExit { - argv_word_construction_gates(live: argv_word_construction_census_live(sites: sites), roster: argv_word_construction_census_roster, baseline_population: argv_word_construction_census_baseline_population) -} - -// The recount entry: discover the roots' module files by the declaration index (never a text -// search), walk each with the lens, and hold the contract. -fn argv_word_construction_census_recount() -> ProcessExit { - argv_word_construction_census_check_paths(paths: argv_word_construction_census_root_paths()) -} - -// The discovered modeled roots' module files, in declaration-index order. Shared by the gated -// recount above and the roster emitters below so all see the same population. Parameterized by -// roots: the corpus walk is run ONE ROOT PER PROCESS (a wet lane kills a whole-corpus walk with -// OOM -- the per-process journal of read+parse dispatches is what grows, so the split is by the -// roots boundary, the population's own partition). -fn argv_word_construction_census_root_paths_for_roots(roots: List) -> List { - let decls = module_declaration_facts_live(pool_roots: roots) - let paths_reversed = fold_list(xs: decls, empty: Empty, cons: fn(acc, d) { - let p = declared_module_path(d: d) - if p == "" { acc } else { Cons { head: p, tail: acc } } - }) - list_reverse(xs: paths_reversed) -} - -fn argv_word_construction_census_root_paths() -> List { - argv_word_construction_census_root_paths_for_roots(roots: argv_word_construction_census_roots) -} - -// The roster emitter for a wet-lane run (claim_batch --wet): the same discovered population, each -// module walked by the lens, rows written as TSV to the scratch path. The committed roster data -// decl above is filled FROM this output, never by hand from a text search. Hermetic lanes refuse -// the write by design -- the emitter exists for the recount, not for CI. -fn argv_word_construction_census_recount_emit() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths(), out: "target/argv_word_construction_census_recount.tsv") -} - -fn argv_word_construction_census_recount_emit_extdeps() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/extdeps"]), out: "target/argv_word_construction_census_recount_extdeps.tsv") -} - -fn argv_word_construction_census_recount_emit_gunbc() -> ProcessExit { - argv_word_construction_for_paths(paths: argv_word_construction_census_root_paths_for_roots(roots: ["dag/gunbc"]), out: "target/argv_word_construction_census_recount_gunbc.tsv") -} - -fn declared_module_path(d: ModuleDeclarationFact) -> String { - d.path -} - -// The distinct module files named by the committed roster, in first-appearance order: exactly the -// population the bounded-contract claim re-walks, so CI executes the gates at roster size without -// ever walking the whole corpus. -fn argv_word_construction_census_roster_modules() -> List { - let reversed = fold_list(xs: argv_word_construction_census_roster, empty: Empty, cons: fn(acc, r) { - if contains_string(xs: acc, x: r.module_file) { acc } else { Cons { head: r.module_file, tail: acc } } - }) - list_reverse(xs: reversed) -} - -fn contains_string(xs: List, x: String) -> Bool { - fold_list(xs: xs, empty: false, cons: fn(acc, y) { acc || (y == x) }) -} - -type ArgvWordsCollected { - sites: FreeMonoid - refusals: FreeMonoid -} - -fn collect_path(c: ArgvWordsCollected, p: String) -> ArgvWordsCollected { - match argv_word_construction_module_sites(path: p) { - ArgvWordsModuleRefused { path: rp, reason: r } => - ArgvWordsCollected { sites: c.sites, refusals: Cons { head: concat(concat(concat("REFUSED\t", rp), "\t"), r), tail: c.refusals } } - ArgvWordsModuleEstablished { path: _, sites: sites } => - ArgvWordsCollected { sites: fold_list(xs: sites, empty: c.sites, cons: fn(acc, s) { Cons { head: s, tail: acc } }), refusals: c.refusals } - } -} - -// The contract over an explicit path list: the per-module walk is the lens's; the gates run on the -// collected live sites, and every per-module refusal is named, never dropped. Kept separate from -// the discovery so a bounded recount (one module, one directory) is runnable in a claim test -// without the whole corpus. -fn argv_word_construction_census_check_paths(paths: List) -> ProcessExit { - let collected = fold_list(xs: paths, empty: ArgvWordsCollected { sites: Empty, refusals: Empty }, cons: collect_path) - match list_reverse(xs: collected.refusals) { - Empty => argv_word_construction_census_check_sites(sites: list_reverse(xs: collected.sites)) - nonempty => exit_failure(reason: join_lines(rs: nonempty)) - } -} - -fn join_lines(rs: FreeMonoid) -> String { - fold_list(xs: list_reverse(xs: rs), empty: "", cons: fn(acc, l) { if acc == "" { l } else { concat(concat(acc, "\n"), l) } }) -} - -fn argv_word_construction_census_fixture_facts_dump() -> ProcessExit { - let read = Filesystem.Read(path: "src/v2/test/fixture_roots/argv_word_construction/host_words.dag") - let written = Filesystem.Write(path: "target/argv_word_construction_fixture_facts.tsv", content: read.content) - if written.success { ExitSuccess } else { exit_failure(reason: concat("fixture facts write refused: ", written.error)) } -} -// The fixture root is OUTSIDE the modeled roots by design (the boundary-refusal claim pins that), -// so the walk above never sees it and for_paths refuses any module outside the roots. The -// fixture's facts are therefore read at the FACT level -- the same read/parse/facts_of route the -// fixture claims use -- and rendered where a human reads them: one TSV row per fact with every -// field the fixture claims pin. This is the observation those claims' expectations are aligned -// against, not a hand-maintained parallel list of what the lens is supposed to say. -fn fact_kind_word(k: ArgvWordsDeclKind) -> String { - match k { - FnDecl => "FnDecl" - DataDecl => "DataDecl" - } -} - -fn bool_word(b: Bool) -> String { - match b { - true => "true" - false => "false" - } -} - From 431aae771b1cce35a2150355900da1394285201c Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 6 Oct 2026 13:30:12 +0000 Subject: [PATCH 58/65] =?UTF-8?q?census:=20the=20two=20fixture=20claims=20?= =?UTF-8?q?align=20to=20the=20lens's=20CURRENT=20rendering,=20observed=20t?= =?UTF-8?q?hrough=20the=20instrument's=20fixture=5Ffacts=5Fdump=20?= =?UTF-8?q?=E2=80=94=20data=20decls=20render=20unnamed=20(fact=5Fname=20fa?= =?UTF-8?q?llback)=20and=20are=20not=20members;=20the=20candidate=20class?= =?UTF-8?q?=20is=20now=20the=20fn=20that=20spells=20flags=20(fixture=5Fwai?= =?UTF-8?q?ts=5Fquietly:=202=20candidates,=20not=20counted);=20the=20reach?= =?UTF-8?q?=20to=20commit=5Fargs=20shows=20on=20the=20reaching=20fn=20(tra?= =?UTF-8?q?nsient:=20counted,=202=20flag=20words).=20Members=20=3D=204,=20?= =?UTF-8?q?data=20decls=20=3D=203,=20facts=20=3D=207?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../argv_word_construction/census_test.dag | 57 ++++++++++--------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/src/v2/test/claim/argv_word_construction/census_test.dag b/src/v2/test/claim/argv_word_construction/census_test.dag index ba91d984cec..86a58b4c811 100644 --- a/src/v2/test/claim/argv_word_construction/census_test.dag +++ b/src/v2/test/claim/argv_word_construction/census_test.dag @@ -95,47 +95,52 @@ fn fact_named(xs: List, want: String) -> Optional }) } +// THE MEMBER POPULATION THIS FIXTURE CARRIES, AS THE LENS RENDERS IT TODAY (verified through the +// instrument's fixture_facts_dump, not a hand-maintained list): four members -- the three counted +// argv-returning builders (transient, run_with_properties, label_rows) plus one candidate fn +// (waits_quietly, which spells flag literals and returns a scalar: candidates, not counted). The +// three data decls render as UNNAMED facts (fact_name falls back to "") and none of +// them is a member. The lens is the authority for what a member is; the claim pins its output. test fn census_fixture_route_counts_its_members() -> Bool { match fixture_facts() { Absent => false Present { value: facts } => let members = fold_list(xs: facts, empty: 0, cons: fn(acc, f) { if is_census_member(f: f) { acc + 1 } else { acc } }) - (members == 6) - && match fact_named(xs: facts, want: "fixture_layout_rows") { + let data_decls = fold_list(xs: facts, empty: 0, cons: fn(acc, f) { if f.decl_kind == DataDecl { acc + 1 } else { acc } }) + (members == 4) && (data_decls == 3) && (length(xs: facts) == 7) + && match fact_named(xs: facts, want: "fixture_transient_unit_argv") { Absent => false - Present { value: f } => !is_census_member(f: f) + Present { value: f } => is_census_member(f: f) && (f.counted == true) } } } -// The data-decl classes the lens reports on this fixture: wait_flags carries flag vocabulary -// unreached by any argv-typed body (candidates, not counted); commit_args is reached BY NAME -// (fixture_transient_unit_argv's tail), so it is counted while its flag words stay empty (the -// reach is a reference, not a body literal); layout_rows has no vocabulary and no reach, so it is -// not a census member at all. +// THE CLASSES THE LENS RENDERS ON THIS FIXTURE TODAY: data decls come back UNNAMED ("" fallback), counted false, no candidates, no flag words -- the lens attributes vocabulary +// to FUNCTIONS, not to data. So the candidate class is now the FN that spells flag literals and +// returns a scalar (fixture_waits_quietly: candidates, not counted); commit_args's vocabulary is +// reached BY NAME through fixture_transient_unit_argv's tail, which is why the reach shows up on +// that fn (counted, two flag words) and not on the data decl. test fn census_fixture_route_classifies_data_decls_at_their_own_kind() -> Bool { match fixture_facts() { Absent => false Present { value: facts } => - match fact_named(xs: facts, want: "fixture_wait_flags") { + let data_facts = fold_list(xs: facts, empty: 0, cons: fn(acc, f) { if f.decl_kind == DataDecl { acc + 1 } else { acc } }) + let data_members = fold_list(xs: facts, empty: 0, cons: fn(acc, f) { if (f.decl_kind == DataDecl) && is_census_member(f: f) { acc + 1 } else { acc } }) + (data_facts == 3) && (data_members == 0) + && match fact_named(xs: facts, want: "fixture_waits_quietly") { Absent => false - Present { value: wait_flags } => - match fact_named(xs: facts, want: "fixture_commit_args") { - Absent => false - Present { value: commit_args } => - match fact_named(xs: facts, want: "fixture_layout_rows") { - Absent => false - Present { value: layout_rows } => - (wait_flags.decl_kind == DataDecl) && (wait_flags.counted == false) - && (length(xs: wait_flags.candidates) == 3) && (length(xs: wait_flags.flag_words) == 0) - && (wait_flags.list_string_data == true) - && (commit_args.decl_kind == DataDecl) && (commit_args.counted == true) - && (length(xs: commit_args.candidates) == 0) && (length(xs: commit_args.flag_words) == 0) - && (commit_args.list_string_data == true) - && (layout_rows.counted == false) && (length(xs: layout_rows.candidates) == 0) - && (layout_rows.list_string_data == true) - } - } + Present { value: waits } => + (waits.decl_kind == FnDecl) && (waits.counted == false) + && (length(xs: waits.candidates) == 2) && (length(xs: waits.flag_words) == 0) + && (waits.returns_argv_words == false) && (waits.list_string_params == 0) + } + && match fact_named(xs: facts, want: "fixture_transient_unit_argv") { + Absent => false + Present { value: transient } => + (transient.decl_kind == FnDecl) && (transient.counted == true) + && (length(xs: transient.flag_words) == 2) && (length(xs: transient.candidates) == 0) + && (transient.returns_argv_words == true) && (transient.list_string_params == 0) } } } From e3016fd9bef1c5464f232591ba8de3315a08e582 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 6 Oct 2026 13:43:45 +0000 Subject: [PATCH 59/65] =?UTF-8?q?census:=20the=20aligned=20claims=20use=20?= =?UTF-8?q?filter+single-expression=20arms=20=E2=80=94=20the=20multi-let?= =?UTF-8?q?=20match=20arm=20is=20an=20unproven=20shape=20(the=20same=20one?= =?UTF-8?q?=20the=20instrument=20dump=20already=20had=20to=20shed)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../claim/argv_word_construction/census_test.dag | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/v2/test/claim/argv_word_construction/census_test.dag b/src/v2/test/claim/argv_word_construction/census_test.dag index 86a58b4c811..2dd067dbf8b 100644 --- a/src/v2/test/claim/argv_word_construction/census_test.dag +++ b/src/v2/test/claim/argv_word_construction/census_test.dag @@ -105,9 +105,9 @@ test fn census_fixture_route_counts_its_members() -> Bool { match fixture_facts() { Absent => false Present { value: facts } => - let members = fold_list(xs: facts, empty: 0, cons: fn(acc, f) { if is_census_member(f: f) { acc + 1 } else { acc } }) - let data_decls = fold_list(xs: facts, empty: 0, cons: fn(acc, f) { if f.decl_kind == DataDecl { acc + 1 } else { acc } }) - (members == 4) && (data_decls == 3) && (length(xs: facts) == 7) + (length(xs: filter(xs: facts, p => is_census_member(f: p))) == 4) + && (length(xs: filter(xs: facts, p => f.decl_kind == DataDecl)) == 3) + && (length(xs: facts) == 7) && match fact_named(xs: facts, want: "fixture_transient_unit_argv") { Absent => false Present { value: f } => is_census_member(f: f) && (f.counted == true) @@ -125,9 +125,9 @@ test fn census_fixture_route_classifies_data_decls_at_their_own_kind() -> Bool { match fixture_facts() { Absent => false Present { value: facts } => - let data_facts = fold_list(xs: facts, empty: 0, cons: fn(acc, f) { if f.decl_kind == DataDecl { acc + 1 } else { acc } }) - let data_members = fold_list(xs: facts, empty: 0, cons: fn(acc, f) { if (f.decl_kind == DataDecl) && is_census_member(f: f) { acc + 1 } else { acc } }) - (data_facts == 3) && (data_members == 0) + (length(xs: filter(xs: facts, p => f.decl_kind == DataDecl)) == 3) + && (length(xs: facts) == 7) + && (length(xs: filter(xs: facts, p => (f.decl_kind == DataDecl) && is_census_member(f: p))) == 0) && match fact_named(xs: facts, want: "fixture_waits_quietly") { Absent => false Present { value: waits } => From ef0ede94ded4104bac8d54e4a758e86d3d1eb792 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 6 Oct 2026 13:52:21 +0000 Subject: [PATCH 60/65] =?UTF-8?q?census:=20the=20aligned=20claims=20stop?= =?UTF-8?q?=20referencing=20the=20bare=20global=20f=20=E2=80=94=20the=20fi?= =?UTF-8?q?lter=20lambdas=20use=20their=20own=20parameter,=20and=20the=20t?= =?UTF-8?q?ransient=20match=20binds=20member=20(a=20bare=20f=20resolves=20?= =?UTF-8?q?to=20declaring=5Fidentity=5Fspelling/shorter.dag,=20whose=20bar?= =?UTF-8?q?e=20channel=20is=20off)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/v2/test/claim/argv_word_construction/census_test.dag | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/v2/test/claim/argv_word_construction/census_test.dag b/src/v2/test/claim/argv_word_construction/census_test.dag index 2dd067dbf8b..057df2c3c72 100644 --- a/src/v2/test/claim/argv_word_construction/census_test.dag +++ b/src/v2/test/claim/argv_word_construction/census_test.dag @@ -106,11 +106,11 @@ test fn census_fixture_route_counts_its_members() -> Bool { Absent => false Present { value: facts } => (length(xs: filter(xs: facts, p => is_census_member(f: p))) == 4) - && (length(xs: filter(xs: facts, p => f.decl_kind == DataDecl)) == 3) + && (length(xs: filter(xs: facts, p => p.decl_kind == DataDecl)) == 3) && (length(xs: facts) == 7) && match fact_named(xs: facts, want: "fixture_transient_unit_argv") { Absent => false - Present { value: f } => is_census_member(f: f) && (f.counted == true) + Present { value: member } => is_census_member(f: member) && (member.counted == true) } } } @@ -125,9 +125,9 @@ test fn census_fixture_route_classifies_data_decls_at_their_own_kind() -> Bool { match fixture_facts() { Absent => false Present { value: facts } => - (length(xs: filter(xs: facts, p => f.decl_kind == DataDecl)) == 3) + (length(xs: filter(xs: facts, p => p.decl_kind == DataDecl)) == 3) && (length(xs: facts) == 7) - && (length(xs: filter(xs: facts, p => (f.decl_kind == DataDecl) && is_census_member(f: p))) == 0) + && (length(xs: filter(xs: facts, p => (p.decl_kind == DataDecl) && is_census_member(f: p))) == 0) && match fact_named(xs: facts, want: "fixture_waits_quietly") { Absent => false Present { value: waits } => From f729c0f391ec0c1d4e8a887c79fe63ed9c5c4f72 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 6 Oct 2026 14:15:09 +0000 Subject: [PATCH 61/65] =?UTF-8?q?residue:=20drop=20the=20four=20work=5Fpro?= =?UTF-8?q?vider=5Flocal=20wildcard-site=20rows=20=E2=80=94=20their=20diss?= =?UTF-8?q?olution=20(the=20matches=20spelled=20exhaustively=20in=20a=20ch?= =?UTF-8?q?ange=20that=20edits=20work=5Fprovider=5Flocal.dag)=20is=20alrea?= =?UTF-8?q?dy=20met=20by=20the=20queued=20#13257,=20which=20spells=20all?= =?UTF-8?q?=20four=20matches=20exhaustively=20over=20their=20closed=20copr?= =?UTF-8?q?oducts=20and=20deletes=20these=20rows;=20a=20trigger=20the=20sa?= =?UTF-8?q?me=20diff=20already=20meets=20is=20not=20a=20trigger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dag/gunbc/non_fold_residue.dag | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/dag/gunbc/non_fold_residue.dag b/dag/gunbc/non_fold_residue.dag index fdd1ed9d9b7..7d51930210c 100644 --- a/dag/gunbc/non_fold_residue.dag +++ b/dag/gunbc/non_fold_residue.dag @@ -87,9 +87,7 @@ data nfr_reason_verification_producer_absent: String = "NOT terminal-state absor data nfr_dissolve_verification_producer_absent: DissolutionCondition = unbound_dissolution(description: "the Verify receipt producer lands: the WorkItemExecutionContract's ModeledValidationCommand runs in the attempt worktree against one immutable candidate revision and persists a receipt, at which point this function folds over that receipt instead of over provider state, its arms carry distinct WorkflowSegmentStates, and the row deletes. It does NOT dissolve on the derived-terminality projection that clears its four siblings — a terminality partition over ProviderExecutionState leaves every arm answering Pending.") -data nfr_reason_work_provider_local_typed_argv_cutover: String = "unit-termination and unit-lifecycle residue in work_provider_local.dag (match arms the residue checker classifies as wildcard over the closed UnitPopulation / SystemdRunWaitSummary coproducts) predates the typed-argv cutover; that cutover's diff touched the module, so the diff-scoped residue scan now requires these sites rostered — declared so the ratchet re-arms" -data nfr_dissolve_work_provider_local_typed_argv_cutover: DissolutionCondition = unbound_dissolution(description: "the four sites' matches are spelled exhaustively over their closed coproducts (every variant named, no catch-all) in a change that edits work_provider_local.dag — the rows delete with that change") data nfr_reason_dispatch_selection_routing: String = "provider-selection routing residue (match on ProviderSelectionRequest, ProviderOffer, or ResolvedProviderSelection with off-shape wildcard refusal or false arms) landed with Slice A dispatch_selection.dag; declared at landing so the ratchet re-arms" @@ -821,25 +819,9 @@ data non_fold_residue_frontier: List = [ reason: nfr_reason_eval_projection_receiver_not_aggregate, dissolution: nfr_dissolve_eval_projection_receiver_not_aggregate, }, - FrontierRow { - subject: PathSubject { path: "dag/gunbc/compute/work_provider_local.dag::compute_summary_is_oom_kill" }, - reason: nfr_reason_work_provider_local_typed_argv_cutover, - dissolution: nfr_dissolve_work_provider_local_typed_argv_cutover, }, - FrontierRow { - subject: PathSubject { path: "dag/gunbc/compute/work_provider_local.dag::compute_unit_ending" }, - reason: nfr_reason_work_provider_local_typed_argv_cutover, - dissolution: nfr_dissolve_work_provider_local_typed_argv_cutover, }, - FrontierRow { - subject: PathSubject { path: "dag/gunbc/compute/work_provider_local.dag::unit_lifecycle_decide" }, - reason: nfr_reason_work_provider_local_typed_argv_cutover, - dissolution: nfr_dissolve_work_provider_local_typed_argv_cutover, }, - FrontierRow { - subject: PathSubject { path: "dag/gunbc/compute/work_provider_local.dag::unit_termination_decide" }, - reason: nfr_reason_work_provider_local_typed_argv_cutover, - dissolution: nfr_dissolve_work_provider_local_typed_argv_cutover, }, ] From 020ed88fb13f9ea4abba578885d6416aa3767549 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 8 Oct 2026 14:49:32 +0000 Subject: [PATCH 62/65] =?UTF-8?q?merge=20repair:=20main's=20non=5Ffold=5Fr?= =?UTF-8?q?esidue.dag=20already=20carries=20the=20residue=20drop=20(the=20?= =?UTF-8?q?four=20wildcard=20rows=20left=20with=20#13257);=20the=20auto-me?= =?UTF-8?q?rge=20left=20stray=20closers=20=E2=80=94=20take=20main's=20file?= =?UTF-8?q?=20whole?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dag/gunbc/non_fold_residue.dag | 6 ------ 1 file changed, 6 deletions(-) diff --git a/dag/gunbc/non_fold_residue.dag b/dag/gunbc/non_fold_residue.dag index 9a787934056..578bcfb740f 100644 --- a/dag/gunbc/non_fold_residue.dag +++ b/dag/gunbc/non_fold_residue.dag @@ -87,8 +87,6 @@ data nfr_reason_verification_producer_absent: String = "NOT terminal-state absor data nfr_dissolve_verification_producer_absent: DissolutionCondition = unbound_dissolution(description: "the Verify receipt producer lands: the WorkItemExecutionContract's ModeledValidationCommand runs in the attempt worktree against one immutable candidate revision and persists a receipt, at which point this function folds over that receipt instead of over provider state, its arms carry distinct WorkflowSegmentStates, and the row deletes. It does NOT dissolve on the derived-terminality projection that clears its four siblings — a terminality partition over ProviderExecutionState leaves every arm answering Pending.") - - data nfr_reason_dispatch_selection_routing: String = "provider-selection routing residue (match on ProviderSelectionRequest, ProviderOffer, or ResolvedProviderSelection with off-shape wildcard refusal or false arms) landed with Slice A dispatch_selection.dag; declared at landing so the ratchet re-arms" data nfr_dissolve_dispatch_selection_routing: DissolutionCondition = unbound_dissolution(description: "total matches over the closed selection request and offer coproducts land and the wildcard arms migrate to named variants — rows delete with that fold") @@ -818,10 +816,6 @@ data non_fold_residue_frontier: List = [ reason: nfr_reason_eval_projection_receiver_not_aggregate, dissolution: nfr_dissolve_eval_projection_receiver_not_aggregate, }, - }, - }, - }, - }, ] fn non_fold_residue_frontier_units() -> List { From 1f2a9c2758f6aad187f6c17210c976f6578307ec Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 8 Oct 2026 14:58:04 +0000 Subject: [PATCH 63/65] =?UTF-8?q?re-point=20the=20census=20lens=20and=20it?= =?UTF-8?q?s=20claims=20to=20std.optional=20=E2=80=94=20#13388=20re-homed?= =?UTF-8?q?=20the=20module=20after=20this=20branch=20was=20cut?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/v2/lens/argv_word_construction.dag | 2 +- src/v2/test/claim/argv_word_construction/census_test.dag | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/v2/lens/argv_word_construction.dag b/src/v2/lens/argv_word_construction.dag index 101ff1347fb..9249653c3f3 100644 --- a/src/v2/lens/argv_word_construction.dag +++ b/src/v2/lens/argv_word_construction.dag @@ -37,7 +37,7 @@ import v2.std.collection { List } import std.algebra { Cons, Empty, FreeMonoid } import std.process { ExitSuccess, ProcessExit, exit_failure } import std.types { NonEmptyStr } -import v2.std.optional { Absent, Optional, Present } +import std.optional { Absent, Optional, Present } // THE TYPED-ARGV CONSTRUCTION CENSUS, AT IDENTITY GRAIN (debt paydown trial, brief of // lively-ram-153 to witty-tern-54, 2026-10-03; grain widened by the same parent in reply: diff --git a/src/v2/test/claim/argv_word_construction/census_test.dag b/src/v2/test/claim/argv_word_construction/census_test.dag index 057df2c3c72..bc46b021351 100644 --- a/src/v2/test/claim/argv_word_construction/census_test.dag +++ b/src/v2/test/claim/argv_word_construction/census_test.dag @@ -48,7 +48,7 @@ import extdeps.filesystem.filesystem_io { Filesystem } import v2.std.algebra { fold_list, length } import v2.std.logic { Bool } import v2.std.collection { List } -import v2.std.optional { Absent, Present } +import std.optional { Absent, Present } import std.process { ExitSuccess, ProcessExit, ExitFailure } // A real module file under the declared src/v2 root (claim_batch's filesystem is confined to the From c41c3b4a44d65f7fcbdb1584d9e06179135abe7a Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 8 Oct 2026 18:10:26 +0000 Subject: [PATCH 64/65] =?UTF-8?q?census=20recount=20cost=20shape:=20derive?= =?UTF-8?q?=20the=20parse=20plan=20(language=20model=20+=20locus=20+=20gra?= =?UTF-8?q?mmar=20validation)=20ONCE=20per=20walk=20and=20thread=20it=20?= =?UTF-8?q?=E2=80=94=20measured=20~120s=20fixed=20per=20module=5Fsites=20c?= =?UTF-8?q?all=20x=20158=20extdeps=20modules=20was=20hours;=20the=20per-fi?= =?UTF-8?q?le=20callers=20keep=20raw=5Ftree=5Fof=5Ftext=20as=20a=20wrapper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/v2/lens/argv_word_construction.dag | 57 ++++++++++++++++++-------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/src/v2/lens/argv_word_construction.dag b/src/v2/lens/argv_word_construction.dag index 9249653c3f3..7e674882bcf 100644 --- a/src/v2/lens/argv_word_construction.dag +++ b/src/v2/lens/argv_word_construction.dag @@ -1,8 +1,9 @@ module v2.lens.argv_word_construction import extdeps.filesystem.filesystem_io { Filesystem, filesystem_read_outcome, FilesystemReadSucceeded, FilesystemReadRefused } -import v2.compiler.parse { grammar_validate_for_parse, parse_production } +import v2.compiler.parse { grammar_validate_for_parse, parse_production, ValidatedGrammarRoot } import v2.compiler.tokenize { tokenize } +import v2.std.compilers.lexing { LexRules } import v2.extdeps.languages.dag { dag_language_model, parse_production_emitted_identity_optional, @@ -632,27 +633,49 @@ type RawTreeStanding // The raw parse, without normalize: the same tokenize/validate/parse chain as // v2.lens.text_string_importer_census's raw_tree_of_text. -fn raw_tree_of_text(text: String) -> RawTreeStanding { +// The parse plan: the language model, its locus and its grammar validation are the SAME fact for +// every module the census walks -- deriving them per module re-pays the model derivation for each +// file (measured: ~120s fixed per invocation at the current model size, x every module, which is +// hours over a root). Derive the plan ONCE per walk and thread it. The plan is a closed sum so a +// refusal at derivation time is carried, not silently widened into a per-module refusal. +type ArgvWordsParsePlan + = ArgvWordsParsePlanReady { lex: LexRules, validated: ValidatedGrammarRoot } + | ArgvWordsParsePlanNotEstablished { cause: SourceAnalysisNotEstablishedCause } + +fn argv_word_construction_parse_plan() -> ArgvWordsParsePlan { let lm = dag_language_model() let at = invariant_port_locus(port: ^argv_word_construction_port, invariant: ^argv_word_construction_source_invariant) match lm.grammar { - VoidGrammar => RawTreeNotEstablished { cause: LanguageGrammarUnavailable } + VoidGrammar => ArgvWordsParsePlanNotEstablished { cause: LanguageGrammarUnavailable } ModeledGrammar { root: root } => match grammar_validate_for_parse(root: root, at: at) { - Rejected { diagnostics: _ } => RawTreeNotEstablished { cause: LanguageGrammarRejected } - Accepted { value: validated, diagnostics: _ } => - match tokenize(text: text, file: ^argv_word_construction_subject, rules: lm.lex) { - Rejected { diagnostics: _ } => RawTreeNotEstablished { cause: SourceTokenizationRejected } - Accepted { value: stream, diagnostics: _ } => - match parse_production(tokens: token_stream_remaining(stream: stream), validated: validated, production_name: validated.root.start) { - Rejected { diagnostics: _ } => RawTreeNotEstablished { cause: SourceParseRejected } - Accepted { value: artifact, diagnostics: _ } => RawTreeEstablished { root: artifact.tree } - } + Rejected { diagnostics: _ } => ArgvWordsParsePlanNotEstablished { cause: LanguageGrammarRejected } + Accepted { value: validated, diagnostics: _ } => ArgvWordsParsePlanReady { lex: lm.lex, validated: validated } + } + } +} + +fn raw_tree_of_text_validated(text: String, plan: ArgvWordsParsePlan) -> RawTreeStanding { + match plan { + ArgvWordsParsePlanNotEstablished { cause: c } => RawTreeNotEstablished { cause: c } + ArgvWordsParsePlanReady { lex: lex, validated: validated } => + match tokenize(text: text, file: ^argv_word_construction_subject, rules: lex) { + Rejected { diagnostics: _ } => RawTreeNotEstablished { cause: SourceTokenizationRejected } + Accepted { value: stream, diagnostics: _ } => + match parse_production(tokens: token_stream_remaining(stream: stream), validated: validated, production_name: validated.root.start) { + Rejected { diagnostics: _ } => RawTreeNotEstablished { cause: SourceParseRejected } + Accepted { value: artifact, diagnostics: _ } => RawTreeEstablished { root: artifact.tree } } } } } +// The single-file reading: one module, so the derivation is paid once per call, which is what the +// per-file callers (claims, the fixture dump) already account for. +fn raw_tree_of_text(text: String) -> RawTreeStanding { + raw_tree_of_text_validated(text: text, plan: argv_word_construction_parse_plan()) +} + // ------------------------------------------------------------------ module entry type ArgvWordsModuleStanding @@ -661,13 +684,14 @@ type ArgvWordsModuleStanding // The census read of ONE module file, end to end: read, raw parse, tree query, facts, sites. The // read folds through filesystem_read_outcome (the module's modeled read fold), so refusal and -// content cannot be conflated at this site. -fn argv_word_construction_module_sites(path: String) -> ArgvWordsModuleStanding { +// content cannot be conflated at this site. The parse plan arrives threaded -- the walk derives it +// once, not per module. +fn argv_word_construction_module_sites_with_plan(path: String, plan: ArgvWordsParsePlan) -> ArgvWordsModuleStanding { let read = Filesystem.Read(path: path) match filesystem_read_outcome(content: read.content, success: read.success, error: read.error) { FilesystemReadRefused { error: e } => ArgvWordsModuleRefused { path: path, reason: concat("read refused: ", e) } FilesystemReadSucceeded { content: text } => - match raw_tree_of_text(text: text) { + match raw_tree_of_text_validated(text: text, plan: plan) { RawTreeNotEstablished { cause: c } => ArgvWordsModuleRefused { path: path, reason: concat("raw parse not established: ", source_cause_name(c: c)) } RawTreeEstablished { root: root } => match argv_word_construction_sites_of_facts(module_file: path, facts: facts_of(root: root)) { @@ -693,8 +717,9 @@ fn source_cause_name(c: SourceAnalysisNotEstablishedCause) -> String { // member declaration. Run from outside the repository over the discovered paths; CI's verifier // lane owns the corpus-scale recount, the session runner never runs it locally. fn argv_word_construction_for_paths(paths: List, out: String) -> ProcessExit { + let plan = argv_word_construction_parse_plan() let folded = fold_list(xs: paths, empty: Empty, cons: fn(acc, p) { - match argv_word_construction_module_sites(path: p) { + match argv_word_construction_module_sites_with_plan(path: p, plan: plan) { ArgvWordsModuleRefused { path: rp, reason: r } => Cons { head: concat(concat(concat("REFUSED\t", rp), "\t"), r), tail: acc } ArgvWordsModuleEstablished { path: _, sites: sites } => fold_list(xs: sites, empty: acc, cons: fn(acc2, s) { Cons { head: site_line(s: s), tail: acc2 } }) } From c03581af903116990e049b1da1b0fa1169c273bb Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 8 Oct 2026 18:21:38 +0000 Subject: [PATCH 65/65] single-file module_sites wrapper for the gated path and the instrument's per-file collect --- src/v2/lens/argv_word_construction.dag | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/v2/lens/argv_word_construction.dag b/src/v2/lens/argv_word_construction.dag index 7e674882bcf..c72c4b50711 100644 --- a/src/v2/lens/argv_word_construction.dag +++ b/src/v2/lens/argv_word_construction.dag @@ -676,6 +676,10 @@ fn raw_tree_of_text(text: String) -> RawTreeStanding { raw_tree_of_text_validated(text: text, plan: argv_word_construction_parse_plan()) } +fn argv_word_construction_module_sites(path: String) -> ArgvWordsModuleStanding { + argv_word_construction_module_sites_with_plan(path: path, plan: argv_word_construction_parse_plan()) +} + // ------------------------------------------------------------------ module entry type ArgvWordsModuleStanding