From 911abc43409dfe574b0db11bc872cc35476e1437 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 28 Sep 2026 08:41:14 +0000 Subject: [PATCH 01/38] Modeled operation realization: file transport arm + gunbc.filesystem_model Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/extdeps/transports/file.dag | 14 +- dag/gunbc/filesystem_model.dag | 202 ++++++++++++++++++ .../claim/modeled_filesystem_witness_test.dag | 191 +++++++++++++++++ src/v1/stage0/src/v1_interpreter.rs | 87 +++++++- src/v2/std/operation_realization.dag | 2 + 5 files changed, 491 insertions(+), 5 deletions(-) create mode 100644 dag/gunbc/filesystem_model.dag create mode 100644 dag/test/claim/modeled_filesystem_witness_test.dag diff --git a/dag/extdeps/transports/file.dag b/dag/extdeps/transports/file.dag index 830f6d17267..ceeb0934b04 100644 --- a/dag/extdeps/transports/file.dag +++ b/dag/extdeps/transports/file.dag @@ -1,6 +1,7 @@ module extdeps.transports.file -import std.types { FermiDepth } +import std.types { FermiDepth, Int, String } +import extdeps.filesystem.filesystem_io { FilesystemFailureKind } import std.fidelity { TransportFidelity } import extdeps.external_authority { ExternalAuthority } import extdeps.uri { Uri, Https } @@ -17,3 +18,14 @@ data file_transport_fidelity: TransportFidelity = TransportFidelity { depth: S, type FileTransportConfig { base_path: String } + +// WHAT THE FILE TRANSPORT OBSERVES, as a value: an operation that succeeded with its byte count and +// any content it read or listed, or one that failed with the host's CLOSED failure kind and its +// message. This is the observation a modeled operation realization (v2.std.operation_realization) +// supplies in place of a real filesystem call; the interpreter feeds it to the same declared-output +// projection a real file result reaches, so success, error, error_kind, content and byte_count are +// derived exactly as from a real call. A listing's content follows the realization's own contract: +// the entry names, sorted, one per line. +type FileExchangeObservation + = FileOperationSucceeded { byte_count: Int, content: String } + | FileOperationFailed { kind: FilesystemFailureKind, error: String } diff --git a/dag/gunbc/filesystem_model.dag b/dag/gunbc/filesystem_model.dag new file mode 100644 index 00000000000..daca446e524 --- /dev/null +++ b/dag/gunbc/filesystem_model.dag @@ -0,0 +1,202 @@ +module gunbc.filesystem_model + +import std.types { Bool, List, String } +import std.measure { second } +import v2.std.operation_argv { OperationRef, BoundOperationInvocation } +import v2.std.operation_realization { + OperationBinding, OperationCall, OperationStep, OperationObserved, OperationHarnessFault, + FileObserved, operation_input_text, +} +import extdeps.transports.file { FileExchangeObservation, FileOperationSucceeded, FileOperationFailed } +import extdeps.filesystem.filesystem_io { + FilesystemNotFound, FilesystemAlreadyExists, FilesystemNotDirectory, FilesystemOtherFailure, +} + +// A MODELED FILESYSTEM: the subset of POSIX file behaviour the file transport realizes, as a pure +// value a modeled operation realization embeds in its scenario state. It is generic -- a hold store, +// a SOL pid file and a capture file are all just paths in it -- and it reproduces the realization's +// own contract (v1 interpreter dispatch_file), not an idealised filesystem: +// - a write needs its parent directory to exist, and answers not_found otherwise; +// - write_create_new answers already_exists when the path exists, and never overwrites; +// - a listing is the immediate children's names, sorted, one per line; listing a file answers +// not_a_directory and listing an absent path answers not_found; +// - reading or writing a directory as a file answers `other`, as an EISDIR does. +// WHAT IT DOES NOT MODEL, stated so a green over it is not over-read: file modes (the declared mode of +// write_create_new_with_mode is accepted and not stored), ownership, symlinks, partial writes, and +// the atomicity of a real create-exclusive under concurrent processes. Those are properties of the real +// filesystem, and their evidence is the file-store tests that run against a real directory. +// byte_count is the content's string length, which equals its byte length for the ASCII content the +// consumers here write. +type ModeledFile { + path: String + content: String +} + +type ModeledFilesystem { + directories: List + files: List +} + +fn fs_is_directory(fs: ModeledFilesystem, path: String) -> Bool { + any_string(xs: fs.directories, x: path) +} + +fn any_string(xs: List, x: String) -> Bool { + !all(xs, y => y != x) +} + +fn fs_file(fs: ModeledFilesystem, path: String) -> ModeledFile? { + fold(fs.files, init: none, f: fn(acc, file) { + match acc { + Present { value: v } => Present { value: v } + Absent => if file.path == path { Present { value: file } } else { none } + } + }) +} + +// The parent of an absolute path: everything before its last separator. "/a/b" -> "/a", "/a" -> "/". +fn fs_parent(path: String) -> String { + let segments = split(s: path, delimiter: "/") + let kept = segments.take(n: count(segments) - 1) + let joined = join(kept, "/") + if joined == "" { "/" } else { joined } +} + +fn fs_leaf(path: String) -> String { + match split(s: path, delimiter: "/").last() { + Present { value: leaf } => leaf + Absent => path + } +} + +fn failed_with(kind: extdeps.filesystem.filesystem_io.FilesystemFailureKind, message: String) -> FileExchangeObservation { + FileOperationFailed { kind: kind, error: message } +} + +fn fs_read(fs: ModeledFilesystem, path: String) -> FileExchangeObservation { + match fs_file(fs: fs, path: path) { + Present { value: file } => FileOperationSucceeded { byte_count: string_length(s: file.content), content: file.content } + Absent => + if fs_is_directory(fs: fs, path: path) { failed_with(kind: FilesystemOtherFailure, message: concat("Is a directory: ", path)) } + else { failed_with(kind: FilesystemNotFound, message: concat("No such file or directory: ", path)) } + } +} + +type ModeledFileWrite { + fs: ModeledFilesystem + observation: FileExchangeObservation +} + +fn fs_write(fs: ModeledFilesystem, path: String, content: String, create_new: Bool) -> ModeledFileWrite { + if !fs_is_directory(fs: fs, path: fs_parent(path: path)) { + ModeledFileWrite { fs: fs, observation: failed_with(kind: FilesystemNotFound, message: concat("No such file or directory: ", path)) } + } else if fs_is_directory(fs: fs, path: path) { + ModeledFileWrite { fs: fs, observation: failed_with(kind: FilesystemOtherFailure, message: concat("Is a directory: ", path)) } + } else { + match fs_file(fs: fs, path: path) { + Present { value: _ } => + if create_new { + ModeledFileWrite { fs: fs, observation: failed_with(kind: FilesystemAlreadyExists, message: concat("File exists: ", path)) } + } else { + ModeledFileWrite { fs: fs_with_file(fs: fs, path: path, content: content), observation: FileOperationSucceeded { byte_count: string_length(s: content), content: "" } } + } + Absent => + ModeledFileWrite { fs: fs_with_file(fs: fs, path: path, content: content), observation: FileOperationSucceeded { byte_count: string_length(s: content), content: "" } } + } + } +} + +fn fs_with_file(fs: ModeledFilesystem, path: String, content: String) -> ModeledFilesystem { + ModeledFilesystem { + directories: fs.directories, + files: list_append(filter(fs.files, f => f.path != path), ModeledFile { path: path, content: content }), + } +} + +fn fs_delete(fs: ModeledFilesystem, path: String) -> ModeledFileWrite { + match fs_file(fs: fs, path: path) { + Present { value: _ } => + ModeledFileWrite { fs: ModeledFilesystem { directories: fs.directories, files: filter(fs.files, f => f.path != path) }, observation: FileOperationSucceeded { byte_count: 0, content: "" } } + Absent => + if fs_is_directory(fs: fs, path: path) { ModeledFileWrite { fs: fs, observation: failed_with(kind: FilesystemOtherFailure, message: concat("Is a directory: ", path)) } } + else { ModeledFileWrite { fs: fs, observation: failed_with(kind: FilesystemNotFound, message: concat("No such file or directory: ", path)) } } + } +} + +fn fs_list(fs: ModeledFilesystem, path: String) -> FileExchangeObservation { + if fs_is_directory(fs: fs, path: path) { + let file_names = map(filter(fs.files, f => fs_parent(path: f.path) == path), f => fs_leaf(path: f.path)) + let dir_names = map(filter(fs.directories, d => d != path && fs_parent(path: d) == path), d => fs_leaf(path: d)) + let listing = join(sort_by(concat_lists(a: file_names, b: dir_names), n => n), "\n") + FileOperationSucceeded { byte_count: string_length(s: listing), content: listing } + } else { + match fs_file(fs: fs, path: path) { + Present { value: _ } => failed_with(kind: FilesystemNotDirectory, message: concat("Not a directory: ", path)) + Absent => failed_with(kind: FilesystemNotFound, message: concat("No such file or directory: ", path)) + } + } +} + +fn concat_lists(a: List, b: List) -> List { + fold(b, init: a, f: fn(acc, x) { list_append(acc, x) }) +} + +// THE BINDINGS, through a lens into the scenario state, so any scenario world can embed a filesystem +// without this module knowing its shape. Filesystem operations take no virtual time. +data filesystem_io_operation_path: String = "dag/extdeps/filesystem/filesystem_io.dag" + +fn filesystem_operation(operation: String) -> OperationRef { + OperationRef { path: filesystem_io_operation_path, service: "Filesystem", operation: operation } +} + +fn file_step(state: S, observation: FileExchangeObservation) -> OperationStep { + OperationObserved { observation: FileObserved { observation: observation }, state: state, elapsed: second(count: 0) } +} + +fn filesystem_bindings(get: fn(S) -> ModeledFilesystem, put: fn(S, ModeledFilesystem) -> S) -> List> { + let reading = fn(state, call) { + match operation_input_text(invocation: call.invocation, name: "path") { + Absent => OperationHarnessFault { reason: "a filesystem read was dispatched without a path" as NonEmptyStr } + Present { value: path } => file_step(state: state, observation: fs_read(fs: get(state), path: path)) + } + } + let listing = fn(state, call) { + match operation_input_text(invocation: call.invocation, name: "path") { + Absent => OperationHarnessFault { reason: "a filesystem list was dispatched without a path" as NonEmptyStr } + Present { value: path } => file_step(state: state, observation: fs_list(fs: get(state), path: path)) + } + } + let deleting = fn(state, call) { + match operation_input_text(invocation: call.invocation, name: "path") { + Absent => OperationHarnessFault { reason: "a filesystem delete was dispatched without a path" as NonEmptyStr } + Present { value: path } => { + let w = fs_delete(fs: get(state), path: path) + file_step(state: put(state, w.fs), observation: w.observation) + } + } + } + [ + OperationBinding { at: filesystem_operation(operation: "Read"), handler: reading }, + OperationBinding { at: filesystem_operation(operation: "List"), handler: listing }, + OperationBinding { at: filesystem_operation(operation: "Delete"), handler: deleting }, + OperationBinding { at: filesystem_operation(operation: "Write"), handler: writing(get: get, put: put, create_new: false) }, + OperationBinding { at: filesystem_operation(operation: "WriteOwnerOnly"), handler: writing(get: get, put: put, create_new: false) }, + OperationBinding { at: filesystem_operation(operation: "WriteCreateNew"), handler: writing(get: get, put: put, create_new: true) }, + OperationBinding { at: filesystem_operation(operation: "WriteCreateNewWithMode"), handler: writing(get: get, put: put, create_new: true) }, + ] +} + +fn writing(get: fn(S) -> ModeledFilesystem, put: fn(S, ModeledFilesystem) -> S, create_new: Bool) -> fn(S, OperationCall) -> OperationStep { + fn(state, call) { + match operation_input_text(invocation: call.invocation, name: "path") { + Absent => OperationHarnessFault { reason: "a filesystem write was dispatched without a path" as NonEmptyStr } + Present { value: path } => match operation_input_text(invocation: call.invocation, name: "content") { + Absent => OperationHarnessFault { reason: "a filesystem write was dispatched without content" as NonEmptyStr } + Present { value: content } => { + let w = fs_write(fs: get(state), path: path, content: content, create_new: create_new) + file_step(state: put(state, w.fs), observation: w.observation) + } + } + } + } +} diff --git a/dag/test/claim/modeled_filesystem_witness_test.dag b/dag/test/claim/modeled_filesystem_witness_test.dag new file mode 100644 index 00000000000..346e733645a --- /dev/null +++ b/dag/test/claim/modeled_filesystem_witness_test.dag @@ -0,0 +1,191 @@ +module test.claim.modeled_filesystem_witness_test + +import std.types { Bool, List, NonEmptyStr, String } +import std.materialization_ladder { Frame, SharedStateFrame } +import std.effect_grant { + Read, Write, ServiceOpTree, NamespacePosition, + ModeledRealization, LifecycleByConstruction, Grant, Envelope, +} +import v2.std.operation_realization { + OperationRealization, OperationBinding, OperationCall, OperationStep, DispatchRecord, + OperationObserved, ShellObserved, DispatchObserved, + virtual_clock_origin, +} +import v2.std.witness_evaluation { + WitnessEvaluationFrame, WitnessReturned, WitnessRefused, WitnessInterrupted, + evaluate_in_witness_frame, witness_diagnostic_rendered_reason, +} +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } +import std.measure { second } +import extdeps.transports.shell { ShellProcessExited } +import extdeps.filesystem.filesystem_io { + Filesystem, FilesystemFailureKindAdmitted, FilesystemAlreadyExists, admit_filesystem_failure_kind, +} +import gunbc.durable_exclusive_hold_file_store { + file_hold_acquire, FileHoldAcquired, FileHoldOccupied, +} +import gunbc.machine_intake_mtcollins1_maintenance_hold { boot_run_owner } +import gunbc.filesystem_model { + ModeledFilesystem, ModeledFile, filesystem_bindings, filesystem_operation, +} + +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +// THE FILE ARM OF THE MODELED OPERATION REALIZATION, exercised through the real dispatcher and the +// production durable-hold store. Every subject calls production code -- file_hold_acquire runs the +// real compare-and-set fold (gunbc.durable_cas_file_store), which lists, reads and create-exclusively +// writes its slot through extdeps.filesystem.filesystem_io -- and the model answers only the file +// operations. The hold root is an ordinary path inside the modeled filesystem, so nothing touches +// /var/lib on the host running the witness. +data realization_identity: NonEmptyStr = "modeled-filesystem-witness" as NonEmptyStr + +data hold_root: NonEmptyStr = "/var/lib/gunbc/unit-holds" as NonEmptyStr + +data hold_key: NonEmptyStr = "operator-host-mtcollins1" as NonEmptyStr + +fn filesystem_grant(verb: std.effect_grant.Verb) -> Grant { + Grant { + verb: verb + root: NamespacePosition { tree: ServiceOpTree { service: "Filesystem" }, path: [] } + binding: ModeledRealization { realization: realization_identity as String } + lifecycle: LifecycleByConstruction + } +} + +fn filesystem_frame(initial: ModeledFilesystem) -> WitnessEvaluationFrame { + WitnessEvaluationFrame { + envelope: Envelope { + frame: Frame { name: "modeled-filesystem-witness", kind: SharedStateFrame } + grants: [filesystem_grant(verb: Read), filesystem_grant(verb: Write)] + } + rest_fixtures: [] + realization: Present { value: OperationRealization { + identity: realization_identity, + initial: initial, + epoch: virtual_clock_origin(), + bindings: filesystem_bindings(get: fn(fs) { fs }, put: fn(fs, next) { next }), + advance: fn(fs, now) { fs }, + } } + } +} + +fn empty_hold_store() -> ModeledFilesystem { + ModeledFilesystem { directories: ["/", "/var", "/var/lib", "/var/lib/gunbc", hold_root as String], files: [] } +} + +fn route_operations(route: List) -> List { + map(route, r => r.invocation.at.operation) +} + +fn acquired(o: gunbc.durable_exclusive_hold_file_store.FileHoldAcquireOutcome) -> Bool { + match o { + FileHoldAcquired { slot_key: _, owner: _, generation: _ } => true + _ => false + } +} + +fn occupied_by(o: gunbc.durable_exclusive_hold_file_store.FileHoldAcquireOutcome, owner: String) -> Bool { + match o { + FileHoldOccupied { slot_key: _, holder: h, generation: _ } => (h as String) == owner + _ => false + } +} + +// THE REAL COMPARE-AND-SET, TWO CONTENDERS FOR ONE UNIT. The first acquire commits into the empty +// store; the second, a different run for the SAME key in the SAME store, finds the committed slot and +// is refused as occupied by the first run. Both go through file_hold_acquire unchanged, so the verdict +// is the production fold's reading of what the model's filesystem holds, and the route shows the +// second contender never wrote. +test fn two_contenders_for_one_unit_get_one_hold() -> Bool { + let first_owner = boot_run_owner(run_id: "run-a" as NonEmptyStr) + let second_owner = boot_run_owner(run_id: "run-b" as NonEmptyStr) + let result = evaluate_in_witness_frame( + frame: filesystem_frame(initial: empty_hold_store()), + subject: fn(_scope) { + let first = file_hold_acquire(root: hold_root, slot_key: hold_key, requested_owner: first_owner) + let second = file_hold_acquire(root: hold_root, slot_key: hold_key, requested_owner: second_owner) + acquired(o: first) && occupied_by(o: second, owner: first_owner as String) + } + ) + match result { + WitnessReturned { value, route, state } => + value + && count(filter(route, r => r.invocation.at.service == "Filesystem")) == count(route) + && count(filter(route, r => starts_with(s: r.invocation.at.operation, prefix: "WriteCreateNew"))) == 1 + && match state { Present { value: fs } => store_holds_one_slot(fs: fs) Absent => false } + _ => false + } +} + +fn store_holds_one_slot(fs: ModeledFilesystem) -> Bool { + count(filter(fs.files, f => starts_with(s: f.path, prefix: hold_root as String))) == 1 +} + +// THE FAILURE KIND TRAVELS THE REAL CHANNEL. A create-exclusive write to an existing path answers +// already_exists, and the operation's declared error_kind field, read by the production admission +// admit_filesystem_failure_kind, decodes it to FilesystemAlreadyExists -- the same projection a host +// io::Error reaches. +test fn a_create_exclusive_write_over_an_existing_file_is_already_exists() -> Bool { + let seeded = ModeledFilesystem { directories: ["/", "/tmp"], files: [ModeledFile { path: "/tmp/slot", content: "held" }] } + match evaluate_in_witness_frame( + frame: filesystem_frame(initial: seeded), + subject: fn(_scope) { + let w = Filesystem.WriteCreateNew(path: "/tmp/slot", content: "mine") + !w.success && match admit_filesystem_failure_kind(observed: w.error_kind) { + FilesystemFailureKindAdmitted { kind: k } => match k { FilesystemAlreadyExists => true _ => false } + _ => false + } + } + ) { + WitnessReturned { value, state } => value && match state { Present { value: fs } => untouched(fs: fs) Absent => false } + _ => false + } +} + +fn untouched(fs: ModeledFilesystem) -> Bool { + all(fs.files, f => f.content == "held") +} + +// A LISTING IS THE REALIZATION'S OWN CONTRACT: immediate children, sorted, one per line, files and +// directories alike; a file listed as a directory is not_a_directory. +test fn a_listing_is_sorted_immediate_children() -> Bool { + let fs = ModeledFilesystem { + directories: ["/", "/d", "/d/sub", "/d/sub/deeper"], + files: [ModeledFile { path: "/d/b", content: "" }, ModeledFile { path: "/d/a", content: "" }, ModeledFile { path: "/d/sub/c", content: "" }], + } + match evaluate_in_witness_frame( + frame: filesystem_frame(initial: fs), + subject: fn(_scope) { + let listed = Filesystem.List(path: "/d") + let as_dir = Filesystem.List(path: "/d/a") + listed.success && listed.entries == "a\nb\nsub" && !as_dir.success && as_dir.error_kind == "not_a_directory" + } + ) { + WitnessReturned { value } => value + _ => false + } +} + +// AN OBSERVATION OF THE WRONG TRANSPORT IS A HARNESS FAULT: a file operation answered with a shell +// observation refuses rather than being read through either projection. +test fn a_file_operation_answered_as_a_shell_run_refuses() -> Bool { + let realization = OperationRealization { + identity: realization_identity, + initial: empty_hold_store(), + epoch: virtual_clock_origin(), + bindings: [OperationBinding { + at: filesystem_operation(operation: "Read"), + handler: fn(s, c) { OperationObserved { observation: ShellObserved { observation: ShellProcessExited { exit_code: 0, stdout: "x", stderr: "" } }, state: s, elapsed: second(count: 0) } }, + }], + advance: fn(s, now) { s }, + } + let frame = filesystem_frame(initial: empty_hold_store()) + match evaluate_in_witness_frame( + frame: WitnessEvaluationFrame { envelope: frame.envelope, rest_fixtures: [], realization: Present { value: realization } }, + subject: fn(_scope) { Filesystem.Read(path: "/x").success } + ) { + WitnessRefused { diagnostic } => + string_contains(s: witness_diagnostic_rendered_reason(diagnostic: diagnostic), pattern: "needs FileObserved") + _ => false + } +} diff --git a/src/v1/stage0/src/v1_interpreter.rs b/src/v1/stage0/src/v1_interpreter.rs index 1bcd4cdf3de..914e6bd6550 100644 --- a/src/v1/stage0/src/v1_interpreter.rs +++ b/src/v1/stage0/src/v1_interpreter.rs @@ -8277,9 +8277,10 @@ fn dispatch_modeled_operation( ); modeled_refused(&key, format!("harness fault: {reason}")) }; - if !is_shell_transport(transport.clone()) { + let shell_operation = is_shell_transport(transport.clone()); + if !shell_operation && !is_file_transport(transport.clone(), ctx.si()) { return Err(harness_fault( - "a modeled realization supplies shell transport observations only; this operation's transport is not shell".to_string(), + "a modeled realization supplies shell and file transport observations only; this operation's transport is neither".to_string(), )); } let handler = record_field(ctx, &binding, "handler") @@ -8357,7 +8358,20 @@ fn dispatch_modeled_operation( return Err(error); } }; - let shell = shell_result_of_observation(&observation, ctx).map_err(&harness_fault)?; + let projected = if shell_operation { + shell_result_of_observation(&observation, ctx) + .map_err(&harness_fault) + .map(|shell| shell_result_projection(shell, op_node, ctx)) + } else { + let path = match param_env.lookup(ctx.sym("path")) { + Some(Value::Str(p)) => p.to_string(), + _ => String::new(), + }; + file_result_of_observation(&observation, &path, ctx) + .map_err(&harness_fault) + .map(|file| map_file_outputs(&file, op_node, ctx)) + }; + let projected = projected?; log( variant_value( ctx, @@ -8369,7 +8383,7 @@ fn dispatch_modeled_operation( Some(advanced), false, ); - shell_result_projection(shell, op_node, ctx).map(Some) + projected.map(Some) } "OperationWorkerKilled" => { let committed = ctx @@ -8404,6 +8418,71 @@ fn dispatch_modeled_operation( } } +/// A modeled FileExchangeObservation as the file transport result the real dispatcher produces. +/// The failure kind is named by its closed .dag authority (`filesystem_failure_kind_name`), the same +/// channel a host `io::Error` is projected onto, so a consumer's kind admission reads it unchanged. +fn file_result_of_observation( + observation: &Value, + path: &str, + ctx: &InterpContext, +) -> Result { + let (arm, fields) = variant_parts(ctx, observation).ok_or("the observation is malformed")?; + if arm != "FileObserved" { + return Err(format!( + "a file operation was answered with a {arm} observation; a file operation needs FileObserved" + )); + } + let file = ctx + .field(&fields, "observation") + .ok_or("FileObserved carries no observation")?; + let (file_arm, file_fields) = + variant_parts(ctx, file).ok_or("the file observation is malformed")?; + let text = |name: &str| match ctx.field(&file_fields, name) { + Some(Value::Str(s)) => Ok(s.to_string()), + _ => Err(format!("the file observation carries no {name}")), + }; + match file_arm.as_str() { + "FileOperationSucceeded" => { + let byte_count = match ctx.field(&file_fields, "byte_count") { + Some(Value::Int(n)) => *n, + _ => return Err("the file observation carries no byte_count".to_string()), + }; + Ok(FileResult { + success: true, + byte_count, + path: path.to_string(), + error: String::new(), + error_kind: String::new(), + content: text("content")?, + }) + } + "FileOperationFailed" => { + let kind = ctx + .field(&file_fields, "kind") + .cloned() + .ok_or("the file observation carries no kind")?; + let kind_name = match run_in_context_with_args( + ctx, + "filesystem_failure_kind_name", + &[(Some("kind".to_string()), kind)], + false, + ) { + Ok(Value::Str(s)) => s.to_string(), + _ => return Err("the file observation's kind has no name".to_string()), + }; + Ok(FileResult { + success: false, + byte_count: 0, + path: path.to_string(), + error: text("error")?, + error_kind: kind_name, + content: String::new(), + }) + } + other => Err(format!("unrecognized file observation {other}")), + } +} + /// A modeled ShellExchangeObservation as the shell transport result the real dispatcher produces. fn shell_result_of_observation( observation: &Value, diff --git a/src/v2/std/operation_realization.dag b/src/v2/std/operation_realization.dag index 97e82210962..57ea3d906f9 100644 --- a/src/v2/std/operation_realization.dag +++ b/src/v2/std/operation_realization.dag @@ -11,6 +11,7 @@ import std.measure { Second, second, second_count } import std.checked_arithmetic { checked_int_to_nat } import std.execution_mode { ExecutionMode, Hermetic, Wet, Record } import extdeps.transports.shell { ShellExchangeObservation, ShellProcessExited } +import extdeps.transports.file { FileExchangeObservation } // A MODELED OPERATION REALIZATION: the dry arm of an operation's realization, bound per grant. // @@ -43,6 +44,7 @@ import extdeps.transports.shell { ShellExchangeObservation, ShellProcessExited } // and the dispatch log for the dynamic extent of one witness frame. type TransportObservation = ShellObserved { observation: ShellExchangeObservation } + | FileObserved { observation: FileExchangeObservation } // A step either answers with an observation, or reports that the worker died after the effect was // (or was not) committed and before any reply, or reports that the SCENARIO is malformed. The last From cd5bdd5119c07e51e5072e1217475ba1921f2dd5 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 28 Sep 2026 09:58:07 +0000 Subject: [PATCH 02/38] Boot dry realization: environment, local/remote exact-invocation, remote host, wall clock models Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/gunbc/bmc_dry_realization.dag | 58 +++++--- dag/gunbc/host_command_model.dag | 98 ++++++++++++++ .../mtcollins1_boot_dry_realization.dag | 74 ++++++++++ dag/gunbc/process_environment_model.dag | 58 ++++++++ dag/gunbc/remote_host_model.dag | 127 ++++++++++++++++++ dag/gunbc/wall_clock_model.dag | 89 ++++++++++++ src/v1/stage0/src/v1_interpreter.rs | 18 +++ 7 files changed, 505 insertions(+), 17 deletions(-) create mode 100644 dag/gunbc/host_command_model.dag create mode 100644 dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag create mode 100644 dag/gunbc/process_environment_model.dag create mode 100644 dag/gunbc/remote_host_model.dag create mode 100644 dag/gunbc/wall_clock_model.dag diff --git a/dag/gunbc/bmc_dry_realization.dag b/dag/gunbc/bmc_dry_realization.dag index 0fff5d963a8..f5b0e17eec9 100644 --- a/dag/gunbc/bmc_dry_realization.dag +++ b/dag/gunbc/bmc_dry_realization.dag @@ -38,38 +38,62 @@ fn bmc_ipmi_operation(operation: String) -> OperationRef { OperationRef { path: bmc_ipmi_operation_path, service: bmc_ipmi_service, operation: operation } } -fn exited(stdout: String, world: BmcWorld) -> OperationStep { - OperationObserved { - observation: ShellObserved { observation: ShellProcessExited { exit_code: 0, stdout: stdout, stderr: "" } }, - state: world, - elapsed: second(count: 1), - } -} +// ONE BMC COMMAND'S RESULT BEFORE IT IS PLACED IN A SCENARIO: the stdout the client would print and the +// world after it, or a harness fault. Handlers are written over the BMC world alone; bmc_bindings lifts +// them through a lens into whatever scenario state embeds the world. +type BmcReply + = BmcReplied { stdout: String, world: BmcWorld } + | BmcReplyFault { reason: NonEmptyStr } -fn bmc_chassis_status_handler(world: BmcWorld, call: OperationCall) -> OperationStep { - exited(stdout: ipmitool_chassis_status_power_line(power_on: bmc_power_is_on(world: world)), world: world) +fn bmc_chassis_status_reply(world: BmcWorld, call: OperationCall) -> BmcReply { + BmcReplied { stdout: ipmitool_chassis_status_power_line(power_on: bmc_power_is_on(world: world)), world: world } } -fn bmc_chassis_power_control_handler(world: BmcWorld, call: OperationCall) -> OperationStep { +fn bmc_chassis_power_control_reply(world: BmcWorld, call: OperationCall) -> BmcReply { match operation_input_text(invocation: call.invocation, name: "action") { - Absent => OperationHarnessFault { reason: "ChassisPowerControl was dispatched without an action input" as NonEmptyStr } + Absent => BmcReplyFault { reason: "ChassisPowerControl was dispatched without an action input" as NonEmptyStr } Present { value: verb } => match ipmi_chassis_control_action_of_verb(verb: verb) { - Absent => OperationHarnessFault { reason: join(["ChassisPowerControl action `", verb, "` names no IPMI chassis control action the model carries"], "") as NonEmptyStr } - Present { value: action } => exited(stdout: ipmitool_chassis_power_control_reply(action: action), world: bmc_chassis_control(world: world, action: action).world) + Absent => BmcReplyFault { reason: join(["ChassisPowerControl action `", verb, "` names no IPMI chassis control action the model carries"], "") as NonEmptyStr } + Present { value: action } => BmcReplied { stdout: ipmitool_chassis_power_control_reply(action: action), world: bmc_chassis_control(world: world, action: action).world } + } + } +} + +// One IPMI round trip takes one virtual second. +fn bmc_lift(get: fn(S) -> BmcWorld, put: fn(S, BmcWorld) -> S, reply: fn(BmcWorld, OperationCall) -> BmcReply) -> fn(S, OperationCall) -> OperationStep { + fn(state, call) { + match reply(get(state), call) { + BmcReplyFault { reason: r } => OperationHarnessFault { reason: r } + BmcReplied { stdout: out, world: w } => OperationObserved { + observation: ShellObserved { observation: ShellProcessExited { exit_code: 0, stdout: out, stderr: "" } }, + state: put(state, w), + elapsed: second(count: 1), + } } } } +fn bmc_bindings(get: fn(S) -> BmcWorld, put: fn(S, BmcWorld) -> S) -> List> { + [ + OperationBinding { at: bmc_ipmi_operation(operation: "ChassisStatus"), handler: bmc_lift(get: get, put: put, reply: bmc_chassis_status_reply) }, + OperationBinding { at: bmc_ipmi_operation(operation: "ChassisPowerControl"), handler: bmc_lift(get: get, put: put, reply: bmc_chassis_power_control_reply) }, + ] +} + +fn bmc_chassis_power_control_handler(world: BmcWorld, call: OperationCall) -> OperationStep { + let handler = bmc_lift(get: fn(w) { w }, put: fn(w, next) { next }, reply: bmc_chassis_power_control_reply) + handler(world, call) +} + fn bmc_dry_realization(identity: NonEmptyStr, initial: BmcWorld, epoch: Second) -> OperationRealization { OperationRealization { identity: identity, initial: initial, epoch: epoch, - bindings: [ - OperationBinding { at: bmc_ipmi_operation(operation: "ChassisStatus"), handler: bmc_chassis_status_handler }, - OperationBinding { at: bmc_ipmi_operation(operation: "ChassisPowerControl"), handler: bmc_chassis_power_control_handler }, + bindings: list_append( + bmc_bindings(get: fn(w) { w }, put: fn(w, next) { next }), virtual_delay_binding(at: sleep_delay_seconds_operation, seconds_input: "seconds"), - ], + ), advance: bmc_advance, } } diff --git a/dag/gunbc/host_command_model.dag b/dag/gunbc/host_command_model.dag new file mode 100644 index 00000000000..dc5fdd29373 --- /dev/null +++ b/dag/gunbc/host_command_model.dag @@ -0,0 +1,98 @@ +module gunbc.host_command_model + +import std.types { Bool, List, NonEmptyStr, String } +import std.measure { second } +import v2.std.operation_argv { OperationRef, InputText, InputTextList } +import v2.std.operation_realization { + OperationBinding, OperationCall, OperationStep, OperationObserved, OperationHarnessFault, + ShellObserved, +} +import extdeps.transports.shell { ShellExchangeObservation } +import extdeps.exec.command { ArgvCommand } + +// AN OPERATION THAT CARRIES A WHOLE COMMAND AS ITS INPUT, AND WHAT THE FAR SIDE ANSWERS. Some +// operations are one identity for every command they run: extdeps.shell.exec shell.Exec.RunArgv +// starts any local program, and extdeps.ssh.session ssh.Session.ExecPortableWords runs any remote +// command. Binding such an operation by identity alone would answer every command the same way, and +// answering by the program's NAME would be the executable-name binding the #12423 design decision +// refuses. So the binding is an EXACT-INVOCATION table, the keying REST replay already uses for its +// fixtures: a scenario lists the complete word sequences it models -- OBTAINED FROM THE PRODUCTION +// BUILDERS the code under test calls, never hand-spelled -- and a dispatched invocation is answered +// only when the concatenation of its named inputs equals one listed sequence. An unlisted invocation, +// or one matching two entries, is a harness fault, never a guessed answer. The answer is a function +// of the scenario state, so the far side's reply can depend on the world. +type ModeledInvocation { + words: List + answer: fn(S) -> ShellExchangeObservation +} + +data shell_exec_run_argv_operation: OperationRef = OperationRef { + path: "dag/extdeps/shell/exec.dag", + service: "shell.Exec", + operation: "RunArgv", +} + +data ssh_session_exec_portable_words_operation: OperationRef = OperationRef { + path: "dag/extdeps/ssh/session.dag", + service: "ssh.Session", + operation: "ExecPortableWords", +} + +// The words a LOCAL ArgvCommand dispatches as: its program then its arguments, exactly what +// command_over_transport builds for LocalExec and RunArgv receives. +fn local_command_words(command: ArgvCommand) -> List { + concat([command.program as String], command.arguments) +} + +fn dispatched_words(bindings: List, name: String) -> List? { + fold(bindings, init: none, f: fn(acc, b) { + match acc { + Present { value: v } => Present { value: v } + Absent => if b.name == name { + match b.value { + InputTextList { items: xs } => Present { value: xs } + InputText { text: t } => Present { value: [t] } + } + } else { none } + } + }) +} + +fn invocation_words(bindings: List, inputs: List) -> List? { + fold(inputs, init: Present { value: [] }, f: fn(acc, name) { + match acc { + Absent => none + Present { value: so_far } => match dispatched_words(bindings: bindings, name: name) { + Absent => none + Present { value: ws } => Present { value: concat(so_far, ws) } + } + } + }) +} + +fn exact_invocation_binding(at: OperationRef, inputs: List, table: List>) -> OperationBinding { + OperationBinding { + at: at, + handler: fn(state, call) { + match invocation_words(bindings: call.invocation.bindings, inputs: inputs) { + Absent => OperationHarnessFault { reason: join(["the invocation lacks one of its declared inputs: ", join(inputs, ", ")], "") as NonEmptyStr } + Present { value: words } => { + let matching = filter(table, m => m.words == words) + if count(matching) == 1 { + match matching.first() { + Present { value: m } => { + let answer = m.answer + OperationObserved { observation: ShellObserved { observation: answer(state) }, state: state, elapsed: second(count: 0) } + } + Absent => OperationHarnessFault { reason: "a matched invocation vanished" as NonEmptyStr } + } + } else if count(matching) == 0 { + OperationHarnessFault { reason: join(["no modeled invocation is exactly `", join(words, " "), "`"], "") as NonEmptyStr } + } else { + OperationHarnessFault { reason: join(["two modeled invocations are exactly `", join(words, " "), "`"], "") as NonEmptyStr } + } + } + } + }, + } +} diff --git a/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag b/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag new file mode 100644 index 00000000000..fe043da3884 --- /dev/null +++ b/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag @@ -0,0 +1,74 @@ +module gunbc.machine_intake_mtcollins1_boot_dry_realization + +import std.types { List, NonEmptyStr, String } +import std.measure { Second } +import v2.std.operation_realization { OperationRealization, virtual_delay_binding } +import gunbc.bmc_model { BmcWorld, bmc_advance } +import gunbc.bmc_dry_realization { bmc_bindings, sleep_delay_seconds_operation } +import gunbc.filesystem_model { ModeledFilesystem, filesystem_bindings } +import gunbc.process_environment_model { ModeledVariable, environment_binding } +import gunbc.host_command_model { ModeledInvocation, exact_invocation_binding, local_command_words, shell_exec_run_argv_operation } +import extdeps.transports.shell { ShellExchangeObservation, ShellProcessExited } +import extdeps.exec.command { env_prefixed_command } +import extdeps.tools.env { EnvSet } +import extdeps.ssh.openssh_client_commands { ssh_add_list_identities_command } +import gunbc.fleet_ssh_access { fleet_automation_ssh_key_fingerprint } +import gunbc.remote_host_model { ModeledRemoteHost, remote_host_binding } +import gunbc.wall_clock_model { ModeledWallClock, wall_clock_bindings } + +// THE DRY REALIZATION OF ONE mtcollins1 BOOT ATTEMPT'S WHOLE EFFECT DEMAND. The scenario world +// composes the one BMC model with the worker's filesystem (credential file, unit-hold store, SOL pid +// and capture files) and its process environment (credential path, SOL paths, run and revision +// identity). Every transition belongs to its own model; this module only places them side by side and +// lifts their bindings into the composite state. The real entry -- +// gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 -- runs unchanged over it. +type MtCollins1BootWorld { + bmc: BmcWorld + fs: ModeledFilesystem + environment: List + agent: ModeledSshAgent + remote_hosts: List + clock: ModeledWallClock +} + +// THE WORKER'S SSH AGENT: whether it holds the fleet automation key. Its listing follows +// `ssh-add -l`: one ` ()` line per identity, exit 0; and with no +// identities, "The agent has no identities." on stdout with exit 1 (ssh-add(1)). +type ModeledSshAgent { + socket: String + holds_fleet_key: Bool +} + +fn agent_listing(agent: ModeledSshAgent) -> ShellExchangeObservation { + if agent.holds_fleet_key { + ShellProcessExited { exit_code: 0, stdout: join(["256 ", fleet_automation_ssh_key_fingerprint, " fleet-automation@gunbc (ED25519)\n"], ""), stderr: "" } + } else { + ShellProcessExited { exit_code: 1, stdout: "The agent has no identities.\n", stderr: "" } + } +} + +fn boot_world_with_bmc(w: MtCollins1BootWorld, bmc: BmcWorld) -> MtCollins1BootWorld { + MtCollins1BootWorld { bmc: bmc, fs: w.fs, environment: w.environment, agent: w.agent, remote_hosts: w.remote_hosts, clock: w.clock } +} + +fn boot_world_with_fs(w: MtCollins1BootWorld, fs: ModeledFilesystem) -> MtCollins1BootWorld { + MtCollins1BootWorld { bmc: w.bmc, fs: fs, environment: w.environment, agent: w.agent, remote_hosts: w.remote_hosts, clock: w.clock } +} + +fn mtcollins1_boot_dry_realization(identity: NonEmptyStr, initial: MtCollins1BootWorld, epoch: Second) -> OperationRealization { + let bmc = bmc_bindings(get: fn(w) { w.bmc }, put: boot_world_with_bmc) + let files = filesystem_bindings(get: fn(w) { w.fs }, put: boot_world_with_fs) + let local_commands = exact_invocation_binding(at: shell_exec_run_argv_operation, inputs: ["program", "arguments"], table: [ + ModeledInvocation { + words: local_command_words(command: env_prefixed_command(bindings: [EnvSet { name: "SSH_AUTH_SOCK", value: initial.agent.socket }], command: ssh_add_list_identities_command())), + answer: fn(w) { agent_listing(agent: w.agent) }, + }, + ]) + OperationRealization { + identity: identity, + initial: initial, + epoch: epoch, + bindings: concat(concat(concat(bmc, files), wall_clock_bindings(get: fn(w) { w.clock })), [environment_binding(get: fn(w) { w.environment }), virtual_delay_binding(at: sleep_delay_seconds_operation, seconds_input: "seconds"), local_commands, remote_host_binding(get: fn(w) { w.remote_hosts })]), + advance: fn(w, now) { boot_world_with_bmc(w: w, bmc: bmc_advance(world: w.bmc, now: now)) }, + } +} diff --git a/dag/gunbc/process_environment_model.dag b/dag/gunbc/process_environment_model.dag new file mode 100644 index 00000000000..7c659345545 --- /dev/null +++ b/dag/gunbc/process_environment_model.dag @@ -0,0 +1,58 @@ +module gunbc.process_environment_model + +import std.types { List, NonEmptyStr, String } +import std.measure { second } +import v2.std.operation_argv { OperationRef } +import v2.std.operation_realization { + OperationBinding, OperationCall, OperationStep, OperationObserved, OperationHarnessFault, + ShellObserved, operation_input_text, +} +import extdeps.transports.shell { ShellProcessExited } + +// A MODELED PROCESS ENVIRONMENT: the variables a scenario's worker was started with, answered through +// the realization's own contract for extdeps.shell shell.Env.Get (printenv): a set variable prints its +// value and exits 0; an unset one prints nothing and exits 1, which the operation's optional output +// projects to Absent -- exactly as a real printenv does. A variable set to the empty string is SET, and +// prints an empty line, so the consumer's own set-but-empty refusal is exercised rather than bypassed. +type ModeledVariable { + name: String + value: String +} + +data shell_env_get_operation: OperationRef = OperationRef { + path: "dag/extdeps/shell.dag", + service: "shell.Env", + operation: "Get", +} + +fn modeled_variable(variables: List, name: String) -> ModeledVariable? { + fold(variables, init: none, f: fn(acc, v) { + match acc { + Present { value: found } => Present { value: found } + Absent => if v.name == name { Present { value: v } } else { none } + } + }) +} + +fn environment_binding(get: fn(S) -> List) -> OperationBinding { + OperationBinding { + at: shell_env_get_operation, + handler: fn(state, call) { + match operation_input_text(invocation: call.invocation, name: "name") { + Absent => OperationHarnessFault { reason: "an environment read was dispatched without a name" as NonEmptyStr } + Present { value: name } => match modeled_variable(variables: get(state), name: name) { + Present { value: v } => OperationObserved { + observation: ShellObserved { observation: ShellProcessExited { exit_code: 0, stdout: concat(v.value, "\n"), stderr: "" } }, + state: state, + elapsed: second(count: 0), + } + Absent => OperationObserved { + observation: ShellObserved { observation: ShellProcessExited { exit_code: 1, stdout: "", stderr: "" } }, + state: state, + elapsed: second(count: 0), + } + } + } + }, + } +} diff --git a/dag/gunbc/remote_host_model.dag b/dag/gunbc/remote_host_model.dag new file mode 100644 index 00000000000..df1aa23cc36 --- /dev/null +++ b/dag/gunbc/remote_host_model.dag @@ -0,0 +1,127 @@ +module gunbc.remote_host_model + +import std.types { Bool, List, NonEmptyStr, String } +import std.measure { second } +import v2.std.operation_realization { + OperationBinding, OperationCall, OperationStep, OperationObserved, OperationHarnessFault, ShellObserved, +} +import extdeps.transports.shell { ShellExchangeObservation, ShellProcessExited } +import extdeps.exec.command { argv_words } +import extdeps.tools.gnu_coreutils { cat_command } +import extdeps.crypto.hash { sha256sum_file_command } +import gunbc.host_command_model { dispatched_words, ssh_session_exec_portable_words_operation } + +// A MODELED REMOTE HOST reached over extdeps.ssh.session ssh.Session.ExecPortableWords. The SSH client +// arguments are shaped as ` -- ` (gunbc.fleet_known_hosts_anchor +// shape_fleet_ssh_exec), and the far side sees exactly the endpoint and the remote words; the model is +// that far side. It answers ONLY the remote words the production command builders produce for the +// paths it holds -- extdeps.tools.gnu_coreutils cat_command and extdeps.crypto.hash +// sha256sum_file_command -- compared exactly, so nothing is dispatched on a program's name. Anything +// else, including a request for another endpoint, is a harness fault. +// +// NOT JUDGED HERE, and stated so a green over it is not over-read: the client-side options before the +// endpoint (host-key trust, credential, config suppression). They are the real OpenSSH client's to +// honour; their evidence is the SSH transport's own controls, not this model. +// +// A FILE IS ITS CONTENT AND ITS DECLARED DIGEST. An image is not hashed in .dag; the scenario declares +// the SHA-256 its bytes have, and the model reports it in sha256sum's own format. A path with no +// content is absent, and both commands answer as coreutils does for a missing file. +type ModeledRemoteFile { + path: String + content: String? + sha256: String? +} + +type ModeledRemoteHost { + endpoint: String + files: List +} + +fn remote_missing(tool: String, path: String) -> ShellExchangeObservation { + ShellProcessExited { exit_code: 1, stdout: "", stderr: join([tool, ": ", path, ": No such file or directory\n"], "") } +} + +fn remote_cat(file: ModeledRemoteFile) -> ShellExchangeObservation { + match file.content { + Absent => remote_missing(tool: "cat", path: file.path) + Present { value: c } => ShellProcessExited { exit_code: 0, stdout: c, stderr: "" } + } +} + +fn remote_sha256sum(file: ModeledRemoteFile) -> ShellExchangeObservation { + match file.content { + Absent => remote_missing(tool: "sha256sum", path: file.path) + Present { value: _ } => match file.sha256 { + Absent => ShellProcessExited { exit_code: 1, stdout: "", stderr: join(["sha256sum: ", file.path, ": Input/output error\n"], "") } + Present { value: d } => ShellProcessExited { exit_code: 0, stdout: join([d, " ", file.path, "\n"], ""), stderr: "" } + } + } +} + +type RemoteRequest { + endpoint: String + words: List +} + +// The far side of the client args: the word before the FIRST `--` is the endpoint, everything after +// it is the remote command -- which may itself carry a `--` (sha256sum -- ). No separator at all +// means the request is not one this shape produces. +fn remote_request(client_args: List) -> RemoteRequest? { + let separators = filter(client_args, a => a == "--") + if count(separators) == 0 { none } else { + let before = take_while(xs: client_args, keep: fn(a) { a != "--" }) + let after = client_args.skip(n: count(before) + 1) + match before.last() { + Absent => none + Present { value: endpoint } => Present { value: RemoteRequest { endpoint: endpoint, words: after } } + } + } +} + +fn take_while(xs: List, keep: fn(String) -> Bool) -> List { + fold(xs, init: TakeWhileState { kept: [], open: true }, f: fn(acc, x) { + if acc.open && keep(x) { TakeWhileState { kept: list_append(acc.kept, x), open: true } } + else { TakeWhileState { kept: acc.kept, open: false } } + }).kept +} + +type TakeWhileState { + kept: List + open: Bool +} + +fn remote_answer(host: ModeledRemoteHost, words: List) -> ShellExchangeObservation? { + fold(host.files, init: none, f: fn(acc, file) { + match acc { + Present { value: v } => Present { value: v } + Absent => + if words == argv_words(command: cat_command(path: file.path)) { Present { value: remote_cat(file: file) } } + else if words == argv_words(command: sha256sum_file_command(path: file.path)) { Present { value: remote_sha256sum(file: file) } } + else { none } + } + }) +} + +fn remote_host_binding(get: fn(S) -> List) -> OperationBinding { + OperationBinding { + at: ssh_session_exec_portable_words_operation, + handler: fn(state, call) { + match dispatched_words(bindings: call.invocation.bindings, name: "client_args") { + Absent => OperationHarnessFault { reason: "a remote exec was dispatched without client args" as NonEmptyStr } + Present { value: args } => match remote_request(client_args: args) { + Absent => OperationHarnessFault { reason: "the client args do not end in ` -- `" as NonEmptyStr } + Present { value: request } => { + let hosts = filter(get(state), h => h.endpoint == request.endpoint) + match hosts.first() { + Absent => OperationHarnessFault { reason: join(["no modeled host answers at ", request.endpoint], "") as NonEmptyStr } + Present { value: host } => match remote_answer(host: host, words: request.words) { + Absent => OperationHarnessFault { reason: join(["the modeled host at ", request.endpoint, " answers no command `", join(request.words, " "), "`"], "") as NonEmptyStr } + Present { value: observation } => OperationObserved { observation: ShellObserved { observation: observation }, state: state, elapsed: second(count: 1) } + } + } + } + } + } + }, + } +} diff --git a/dag/gunbc/wall_clock_model.dag b/dag/gunbc/wall_clock_model.dag new file mode 100644 index 00000000000..199470400bb --- /dev/null +++ b/dag/gunbc/wall_clock_model.dag @@ -0,0 +1,89 @@ +module gunbc.wall_clock_model + +import std.types { Int, List, String } +import std.measure { Second, second, second_count } +import v2.std.operation_argv { OperationRef } +import v2.std.operation_realization { + OperationBinding, OperationCall, OperationStep, OperationObserved, ShellObserved, +} +import extdeps.transports.shell { ShellProcessExited } + +// A MODELED WALL CLOCK: what the worker's `date` prints, as a function of the realization's virtual +// clock. The virtual clock (v2.std.operation_realization, a Nat-counted Second) is monotonic by +// construction; the WALL clock is not, and a real host's can step backwards (NTP, a VM restored from +// a snapshot). So the model keeps them apart: a reading is the Unix time the scenario assigns to the +// virtual origin, plus the virtual seconds elapsed, plus a signed step the scenario may set -- a +// negative step is a backward clock the consumer must survive, while virtual time, deadlines and +// scheduled events keep moving forward. +// +// It answers extdeps.clock Clock.Now in `date -u +%Y-%m-%dT%H:%M:%SZ` form, and Clock.UnixSecs and +// Clock.UnixMillis in `date +%s` and `date +%s%3N` form, over the proleptic Gregorian calendar. +type ModeledWallClock { + unix_at_origin: Int + step_seconds: Int +} + +fn wall_clock_unix(clock: ModeledWallClock, now: Second) -> Int { + clock.unix_at_origin + (second_count(s: now) as Int) + clock.step_seconds +} + +fn two_digits(n: Int) -> String { + if n < 10 { concat("0", to_string(n)) } else { to_string(n) } +} + +// Days since 1970-01-01 to a civil date: H. Hinnant, "chrono-Compatible Low-Level Date Algorithms", +// civil_from_days -- exact for the whole proleptic Gregorian range, no table. +type CivilDate { + year: Int + month: Int + day: Int +} + +fn floor_div(a: Int, b: Int) -> Int { + if a >= 0 { a / b } else { 0 - ((0 - a + b - 1) / b) } +} + +fn civil_from_days(days: Int) -> CivilDate { + let z = days + 719468 + let era = floor_div(a: z, b: 146097) + let doe = z - era * 146097 + let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365 + let y = yoe + era * 400 + let doy = doe - (365 * yoe + yoe / 4 - yoe / 100) + let mp = (5 * doy + 2) / 153 + let d = doy - (153 * mp + 2) / 5 + 1 + let m = if mp < 10 { mp + 3 } else { mp - 9 } + CivilDate { year: if m <= 2 { y + 1 } else { y }, month: m, day: d } +} + +fn iso8601_utc(unix: Int) -> String { + let days = floor_div(a: unix, b: 86400) + let rem = unix - days * 86400 + let date = civil_from_days(days: days) + join([ + to_string(date.year), "-", two_digits(n: date.month), "-", two_digits(n: date.day), + "T", two_digits(n: rem / 3600), ":", two_digits(n: (rem - (rem / 3600) * 3600) / 60), ":", two_digits(n: rem - (rem / 60) * 60), "Z", + ], "") +} + +data clock_operation_path: String = "dag/extdeps/clock/clock.dag" + +fn clock_operation(operation: String) -> OperationRef { + OperationRef { path: clock_operation_path, service: "Clock", operation: operation } +} + +fn printed(state: S, text: String) -> OperationStep { + OperationObserved { + observation: ShellObserved { observation: ShellProcessExited { exit_code: 0, stdout: concat(text, "\n"), stderr: "" } }, + state: state, + elapsed: second(count: 0), + } +} + +fn wall_clock_bindings(get: fn(S) -> ModeledWallClock) -> List> { + [ + OperationBinding { at: clock_operation(operation: "Now"), handler: fn(state, call) { printed(state: state, text: iso8601_utc(unix: wall_clock_unix(clock: get(state), now: call.now))) } }, + OperationBinding { at: clock_operation(operation: "UnixSecs"), handler: fn(state, call) { printed(state: state, text: to_string(wall_clock_unix(clock: get(state), now: call.now))) } }, + OperationBinding { at: clock_operation(operation: "UnixMillis"), handler: fn(state, call) { printed(state: state, text: to_string(wall_clock_unix(clock: get(state), now: call.now) * 1000)) } }, + ] +} diff --git a/src/v1/stage0/src/v1_interpreter.rs b/src/v1/stage0/src/v1_interpreter.rs index 914e6bd6550..8b2ef4139db 100644 --- a/src/v1/stage0/src/v1_interpreter.rs +++ b/src/v1/stage0/src/v1_interpreter.rs @@ -8121,6 +8121,24 @@ fn bound_operation_invocation_value( continue; }; let bound = match value { + // An argv expansion binds as the words a real spawn receives, expanded by the same + // seed realization of v2.std.compilers.cli_surface ProcessArgvExpansion the shell + // dispatcher uses, never as a rendering of the carrier. + Value::Record { type_name, fields } + if resolve_sym(*type_name).rsplit('.').next() == Some("ProcessArgvExpansion") => + { + let mut words = Vec::new(); + push_process_argv_expansion(&mut words, &fields)?; + variant_value( + ctx, + "OperationInputValue", + "InputTextList", + vec![( + "items", + list_value(words.into_iter().map(str_value).collect::>()), + )], + ) + } Value::List(items) => { let texts: Vec = items.iter().map(|v| str_value(render_input(v))).collect(); variant_value( From 3967705250d10724b8c05b0afb5ea671d7ef2cae Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 28 Sep 2026 10:02:12 +0000 Subject: [PATCH 03/38] Witness the boot-world models' own semantics (calendar, backward step, remote request, coreutils formats, parent rule) Co-Authored-By: Claude Opus 5.5 (1M context) --- .../claim/boot_world_models_witness_test.dag | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 dag/test/claim/boot_world_models_witness_test.dag diff --git a/dag/test/claim/boot_world_models_witness_test.dag b/dag/test/claim/boot_world_models_witness_test.dag new file mode 100644 index 00000000000..756eec54443 --- /dev/null +++ b/dag/test/claim/boot_world_models_witness_test.dag @@ -0,0 +1,74 @@ +module test.claim.boot_world_models_witness_test + +import std.types { Bool, List, String } +import std.measure { second } +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } +import extdeps.transports.shell { ShellProcessExited } +import extdeps.exec.command { argv_words } +import extdeps.tools.gnu_coreutils { cat_command } +import extdeps.crypto.hash { sha256sum_file_command } +import gunbc.wall_clock_model { ModeledWallClock, iso8601_utc, wall_clock_unix } +import gunbc.remote_host_model { ModeledRemoteHost, ModeledRemoteFile, remote_request, remote_answer } +import gunbc.filesystem_model { ModeledFilesystem, ModeledFile, fs_parent, fs_write, fs_list } + +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +// THE MODELS' OWN SEMANTICS, checked apart from any orchestrator run, so a matrix case cannot pass +// against a model that is wrong in the same direction as the code under test. Reference values for the +// calendar are independent: Python's datetime.fromtimestamp(t, timezone.utc) for each instant. +test fn the_wall_clock_renders_utc_like_date() -> Bool { + iso8601_utc(unix: 0) == "1970-01-01T00:00:00Z" + && iso8601_utc(unix: 951782400) == "2000-02-29T00:00:00Z" + && iso8601_utc(unix: 1790000000) == "2026-09-21T14:13:20Z" + && iso8601_utc(unix: 4102444799) == "2099-12-31T23:59:59Z" + && iso8601_utc(unix: 0 - 1) == "1969-12-31T23:59:59Z" +} + +// A NEGATIVE STEP IS A WALL CLOCK THAT WENT BACKWARDS while virtual time moved forward. +test fn a_backward_step_reads_earlier_while_virtual_time_advances() -> Bool { + let clock = ModeledWallClock { unix_at_origin: 1790000000, step_seconds: 0 - 120 } + wall_clock_unix(clock: clock, now: second(count: 60)) == 1789999940 +} + +// THE FAR SIDE OF AN SSH REQUEST is the word before the first `--` and everything after it, so a +// remote command that itself carries `--` keeps it. +test fn a_remote_request_splits_at_the_first_separator() -> Bool { + match remote_request(client_args: ["-o", "BatchMode=yes", "srv2", "--", "sha256sum", "--", "/srv/x"]) { + Present { value: r } => r.endpoint == "srv2" && r.words == ["sha256sum", "--", "/srv/x"] + Absent => false + } + && match remote_request(client_args: ["srv2", "cat", "/srv/x"]) { Present { value: _ } => false Absent => true } +} + +// THE REMOTE HOST ANSWERS ONLY THE PRODUCTION BUILDERS' WORDS, in coreutils' formats. +test fn the_remote_host_answers_production_commands_in_coreutils_format() -> Bool { + let host = ModeledRemoteHost { endpoint: "srv2", files: [ + ModeledRemoteFile { path: "/srv/rec", content: Present { value: "abc\n" }, sha256: none }, + ModeledRemoteFile { path: "/srv/img", content: Present { value: "bytes" }, sha256: Present { value: "ff00" } }, + ModeledRemoteFile { path: "/srv/gone", content: none, sha256: none }, + ] } + let cat_ok = match remote_answer(host: host, words: argv_words(command: cat_command(path: "/srv/rec"))) { + Present { value: ShellProcessExited { exit_code: 0, stdout: "abc\n", stderr: _ } } => true + _ => false + } + let sum_ok = match remote_answer(host: host, words: argv_words(command: sha256sum_file_command(path: "/srv/img"))) { + Present { value: ShellProcessExited { exit_code: 0, stdout: "ff00 /srv/img\n", stderr: _ } } => true + _ => false + } + let gone = match remote_answer(host: host, words: argv_words(command: cat_command(path: "/srv/gone"))) { + Present { value: ShellProcessExited { exit_code: 1, stdout: _, stderr: "cat: /srv/gone: No such file or directory\n" } } => true + _ => false + } + let unlisted = match remote_answer(host: host, words: ["rm", "-rf", "/srv"]) { Present { value: _ } => false Absent => true } + cat_ok && sum_ok && gone && unlisted +} + +// THE FILESYSTEM'S PARENT RULE: a write under a directory the model does not hold is not_found. +test fn a_write_needs_its_parent_directory() -> Bool { + let fs = ModeledFilesystem { directories: ["/", "/run"], files: [] } + fs_parent(path: "/run/sol.pid") == "/run" && fs_parent(path: "/x") == "/" && fs_parent(path: "target/a.pub") == "target" + && match fs_write(fs: fs, path: "/var/x", content: "1", create_new: false).observation { + extdeps.transports.file.FileOperationFailed { kind: extdeps.filesystem.filesystem_io.FilesystemNotFound, error: _ } => true + _ => false + } +} From 96ba551277e83c08118e74c78aecb85357793d50 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 28 Sep 2026 10:12:05 +0000 Subject: [PATCH 04/38] Boot world: expanded BMC model (override, SEL, SOL session, cycle restore), ipmitool observed-output rows, SOL collector/process table, SDR dump, SMpro, uptime, host console Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/extdeps/bmc/ipmitool_observed_output.dag | 56 +++++ dag/gunbc/bmc_dry_realization.dag | 50 +++- dag/gunbc/bmc_model.dag | 140 ++++++++--- .../mtcollins1_boot_dry_realization.dag | 228 ++++++++++++++++-- .../operation_realization_witness_test.dag | 9 +- 5 files changed, 430 insertions(+), 53 deletions(-) create mode 100644 dag/extdeps/bmc/ipmitool_observed_output.dag diff --git a/dag/extdeps/bmc/ipmitool_observed_output.dag b/dag/extdeps/bmc/ipmitool_observed_output.dag new file mode 100644 index 00000000000..5b3831c55be --- /dev/null +++ b/dag/extdeps/bmc/ipmitool_observed_output.dag @@ -0,0 +1,56 @@ +module extdeps.bmc.ipmitool_observed_output + +import std.types { Int, NonEmptyStr, String } +import extdeps.external_authority { ExternalAuthority, CitedFigureStanding, TranscribedUncited } +import extdeps.uri { Uri, Https } + +data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { + uri: Uri { + scheme: Https + locator: "github.com/ipmitool/ipmitool" + } +} + +// WHAT ipmitool PRINTS, AS OBSERVED ON THE MT. COLLINS AMI MEGARAC BUILD. Each row is the client's +// output format for one operation, taken verbatim from a retained capture and cited by that capture's +// path and SHA-256, so a modeled realization renders exactly what the production decoder was built to +// read. These are client-format facts about the upstream tool on one controller build, not +// facts about any scenario; a row whose value is not in a retained capture says so in its standing. + +// `ipmitool chassis bootparam get 5` with no override pending. Source: run 36335369059 +// mtcollins1-boot-diagnostics.txt (sha256 eda19cde2e60d90d3d04946273e615eb5bf774496b9907e03d3bc15dbedc2c1c), +// "boot parameter 5 before", verbatim including the trailing space after "legacy) boot". +data ipmitool_bootparam5_no_override: String = "Boot parameter version: 1\nBoot parameter 5 is valid/unlocked\nBoot parameter data: 0000000000\n Boot Flags :\n - Boot Flag Invalid\n - Options apply to only next boot\n - BIOS PC Compatible (legacy) boot \n - Boot Device Selector : No override\n - BIOS verbosity : System Default\n - Console Redirection control : Console redirection occurs per BIOS configuration setting (default)\n - BIOS Mux Control Override : BIOS uses recommended setting of the mux at the end of POST\n" + +// The same read after `chassis bootdev cdrom options=efiboot`. Source: artifacts/bmc/mtcollins1-32dimm/30-bootdev.txt +// (sha256 6488554239e12f033203c9589285bd6ce27dfb5716658be931e2422ea238c4ba), the readback block. +data ipmitool_bootparam5_cdrom_efi_next_boot: String = "Boot parameter version: 1\nBoot parameter 5 is valid/unlocked\nBoot parameter data: a014000000\n Boot Flags :\n - Boot Flag Valid\n - Options apply to only next boot\n - BIOS EFI boot \n - Boot Device Selector : Force Boot from CD/DVD\n - BIOS verbosity : System Default\n - Console Redirection control : Console redirection occurs per BIOS configuration setting (default)\n - BIOS Mux Control Override : BIOS uses recommended setting of the mux at the end of POST\n" + +// `ipmitool chassis bootdev cdrom options=efiboot`. Same capture, line 2. +data ipmitool_bootdev_cdrom_reply: String = "Set Boot Device to cdrom\n" + +// What `ipmitool sol activate` prints first when its stdin is not a terminal. Source: +// artifacts/bmc/mtcollins1-32dimm/22-sol-probe.log (sha256 e24f9ed8d9cedff87e6d11327c602c31fd7c3d9e2ba16e7e1ab0f8a91cdfe41e), +// lines 1-2. +data ipmitool_sol_activate_preamble: String = "tcgetattr: Inappropriate ioctl for device\n[SOL Session operational. Use ~? for help]\n" + +// One `ipmitool sel elist` line: the record id in lowercase hex right-aligned in four columns, then +// date, time, sensor, event and state separated by ` | `. Source: artifacts/bmc/mtcollins1-32dimm/50-sel-final.txt +// (sha256 14e84cd9cbd2d89d6be46caff42c3d5ab0a19079fa4546d6e6ec449f759c7ba5) and the diagnostics capture above +// (ids ` 1` and ` b5a`). +fn ipmitool_sel_elist_line(record_id_hex: String, date: String, time: String, sensor: String, event: String, state: String) -> String { + let pad = if string_length(s: record_id_hex) >= 4 { "" } else if string_length(s: record_id_hex) == 3 { " " } else if string_length(s: record_id_hex) == 2 { " " } else { " " } + join([pad, record_id_hex, " | ", date, " | ", time, " | ", sensor, " | ", event, " | ", state, "\n"], "") +} + +// `ipmitool raw 0x06 0x52 ...` (Master Write-Read) answering two bytes. NO SMpro exchange with this +// controller is retained in the corpus (gunbc.machine_intake_mtcollins1_smpro_observation witness +// notes), so the spacing is ipmitool's raw-response print as the production decoder's own supplied +// samples spell it, and is carried with that standing. +data ipmitool_raw_two_bytes_standing: CitedFigureStanding = TranscribedUncited { + read_obligation: "a retained stdout of `ipmitool raw 0x06 0x52 2 ` against the Mt. Collins BMC, cited by digest" as NonEmptyStr +} + +fn ipmitool_raw_two_bytes(first_hex: String, second_hex: String) -> String { + join([" ", first_hex, " ", second_hex, "\n"], "") +} diff --git a/dag/gunbc/bmc_dry_realization.dag b/dag/gunbc/bmc_dry_realization.dag index f5b0e17eec9..ffafc952744 100644 --- a/dag/gunbc/bmc_dry_realization.dag +++ b/dag/gunbc/bmc_dry_realization.dag @@ -12,7 +12,14 @@ import extdeps.transports.shell { ShellProcessExited } import extdeps.bmc.ipmi_chassis_control { ipmi_chassis_control_action_of_verb, ipmitool_chassis_status_power_line, ipmitool_chassis_power_control_reply, } -import gunbc.bmc_model { BmcWorld, bmc_chassis_control, bmc_power_is_on, bmc_advance } +import gunbc.bmc_model { + BmcWorld, bmc_chassis_control, bmc_power_is_on, bmc_advance, bmc_with_override, + BootOverrideNone, BootOverrideCdromEfiNextBoot, +} +import extdeps.bmc.ipmitool_observed_output { + ipmitool_bootparam5_no_override, ipmitool_bootparam5_cdrom_efi_next_boot, ipmitool_bootdev_cdrom_reply, + ipmitool_sel_elist_line, +} // THE DRY ADAPTER BETWEEN THE extdeps.bmc INTERFACE AND THE ONE BMC MODEL. For each bound operation // it reads the actual inputs the dispatcher bound, asks gunbc.bmc_model for the transition, and @@ -54,11 +61,46 @@ fn bmc_chassis_power_control_reply(world: BmcWorld, call: OperationCall) -> BmcR Absent => BmcReplyFault { reason: "ChassisPowerControl was dispatched without an action input" as NonEmptyStr } Present { value: verb } => match ipmi_chassis_control_action_of_verb(verb: verb) { Absent => BmcReplyFault { reason: join(["ChassisPowerControl action `", verb, "` names no IPMI chassis control action the model carries"], "") as NonEmptyStr } - Present { value: action } => BmcReplied { stdout: ipmitool_chassis_power_control_reply(action: action), world: bmc_chassis_control(world: world, action: action).world } + Present { value: action } => BmcReplied { stdout: ipmitool_chassis_power_control_reply(action: action), world: bmc_chassis_control(world: world, action: action, now: call.now).world } + } + } +} + +fn input_is(call: OperationCall, name: String, expected: String) -> Bool { + match operation_input_text(invocation: call.invocation, name: name) { + Present { value: v } => v == expected + Absent => false + } +} + +fn bmc_bootparam_get_reply(world: BmcWorld, call: OperationCall) -> BmcReply { + if input_is(call: call, name: "parameter", expected: "5") { + BmcReplied { + stdout: match world.boot_override { + BootOverrideNone => ipmitool_bootparam5_no_override + BootOverrideCdromEfiNextBoot => ipmitool_bootparam5_cdrom_efi_next_boot + }, + world: world, } + } else { + BmcReplyFault { reason: "the model answers boot parameter 5 only" as NonEmptyStr } } } +// The override this controller has been observed to take: cdrom, EFI, next boot only. Any other +// device or option set is a request the model does not carry, and it refuses rather than guessing. +fn bmc_bootdev_reply(world: BmcWorld, call: OperationCall) -> BmcReply { + if input_is(call: call, name: "device", expected: "cdrom") && input_is(call: call, name: "options", expected: "efiboot") { + BmcReplied { stdout: ipmitool_bootdev_cdrom_reply, world: bmc_with_override(world: world, over: BootOverrideCdromEfiNextBoot) } + } else { + BmcReplyFault { reason: "the model carries the cdrom boot override with options=efiboot only" as NonEmptyStr } + } +} + +fn bmc_sel_elist_reply(world: BmcWorld, call: OperationCall) -> BmcReply { + BmcReplied { stdout: join(map(world.sel, r => ipmitool_sel_elist_line(record_id_hex: r.id_hex, date: r.date, time: r.time, sensor: r.sensor, event: r.event, state: r.state)), ""), world: world } +} + // One IPMI round trip takes one virtual second. fn bmc_lift(get: fn(S) -> BmcWorld, put: fn(S, BmcWorld) -> S, reply: fn(BmcWorld, OperationCall) -> BmcReply) -> fn(S, OperationCall) -> OperationStep { fn(state, call) { @@ -77,6 +119,10 @@ fn bmc_bindings(get: fn(S) -> BmcWorld, put: fn(S, BmcWorld) -> S) -> List fired: List + boot_override: BmcBootOverride + sel: List + sol_session_open: Bool + cycle_off_interval: Second + host_booted_at: Second? + booted_via_override: Bool +} + +fn bmc_world(power: BmcPower) -> BmcWorld { + BmcWorld { + power: power, pending: [], fired: [], boot_override: BootOverrideNone, sel: [], + sol_session_open: false, cycle_off_interval: second(count: 5), host_booted_at: none, booted_via_override: false, + } } // A COMMAND'S EFFECT AND ITS REPLY ARE SEPARATE FACTS. effect_taken says whether the world changed; -// the reply is the adapter's to render from the new world. §28.3 recommends a controller refuse a -// power cycle at an OFF host with completion code D5h; the Mt. Collins MegaRAC does not, and answers -// success doing nothing (extdeps.bmc.ipmi_chassis_control, run 36023602469). The model follows the -// observed unit, and that departure from the recommendation is the reason the arm exists. +// the reply is the adapter's to render. §28.3 recommends a controller refuse a power cycle at an OFF +// host with completion code D5h; the Mt. Collins MegaRAC does not, and answers success doing nothing +// (extdeps.bmc.ipmi_chassis_control, run 36023602469). The model follows the observed unit, and that +// departure from the recommendation is the reason the arm exists. type BmcChassisControlStep { world: BmcWorld effect_taken: Bool @@ -56,31 +88,81 @@ fn bmc_power_is_on(world: BmcWorld) -> Bool { } fn bmc_with_power(world: BmcWorld, power: BmcPower) -> BmcWorld { - BmcWorld { power: power, pending: world.pending, fired: world.fired } + BmcWorld { + power: power, pending: world.pending, fired: world.fired, boot_override: world.boot_override, sel: world.sel, + sol_session_open: world.sol_session_open, cycle_off_interval: world.cycle_off_interval, + host_booted_at: world.host_booted_at, booted_via_override: world.booted_via_override, + } +} + +fn bmc_with_override(world: BmcWorld, over: BmcBootOverride) -> BmcWorld { + BmcWorld { + power: world.power, pending: world.pending, fired: world.fired, boot_override: over, sel: world.sel, + sol_session_open: world.sol_session_open, cycle_off_interval: world.cycle_off_interval, + host_booted_at: world.host_booted_at, booted_via_override: world.booted_via_override, + } +} + +fn bmc_with_sol_session(world: BmcWorld, open: Bool) -> BmcWorld { + BmcWorld { + power: world.power, pending: world.pending, fired: world.fired, boot_override: world.boot_override, sel: world.sel, + sol_session_open: open, cycle_off_interval: world.cycle_off_interval, + host_booted_at: world.host_booted_at, booted_via_override: world.booted_via_override, + } +} + +fn bmc_with_pending(world: BmcWorld, pending: List, fired: List) -> BmcWorld { + BmcWorld { + power: world.power, pending: pending, fired: fired, boot_override: world.boot_override, sel: world.sel, + sol_session_open: world.sol_session_open, cycle_off_interval: world.cycle_off_interval, + host_booted_at: world.host_booted_at, booted_via_override: world.booted_via_override, + } +} + +// The host starts booting now: power is on, the boot is timed from here, and a pending next-boot +// override is consumed by it. +fn bmc_host_boots(world: BmcWorld, now: Second) -> BmcWorld { + let via = match world.boot_override { BootOverrideCdromEfiNextBoot => true BootOverrideNone => false } + BmcWorld { + power: BmcPowerOn, pending: world.pending, fired: world.fired, boot_override: BootOverrideNone, sel: world.sel, + sol_session_open: world.sol_session_open, cycle_off_interval: world.cycle_off_interval, + host_booted_at: Present { value: now }, booted_via_override: via, + } } -fn bmc_chassis_control(world: BmcWorld, action: IpmiChassisControlAction) -> BmcChassisControlStep { +fn bmc_chassis_control(world: BmcWorld, action: IpmiChassisControlAction, now: Second) -> BmcChassisControlStep { match action { - IpmiChassisPowerUp => BmcChassisControlStep { world: bmc_with_power(world: world, power: BmcPowerOn), effect_taken: !bmc_power_is_on(world: world) } + IpmiChassisPowerUp => + if bmc_power_is_on(world: world) { BmcChassisControlStep { world: world, effect_taken: false } } + else { BmcChassisControlStep { world: bmc_host_boots(world: world, now: now), effect_taken: true } } IpmiChassisPowerDown => BmcChassisControlStep { world: bmc_with_power(world: world, power: BmcPowerOff), effect_taken: bmc_power_is_on(world: world) } - IpmiChassisPowerCycle => BmcChassisControlStep { world: world, effect_taken: bmc_power_is_on(world: world) } - IpmiChassisHardReset => BmcChassisControlStep { world: world, effect_taken: bmc_power_is_on(world: world) } + IpmiChassisPowerCycle => + if bmc_power_is_on(world: world) { + let off = bmc_with_power(world: world, power: BmcPowerOff) + let restore = BmcScheduledEvent { at: second(count: second_count(s: now) + second_count(s: world.cycle_off_interval)), event: BmcPowerRestored } + BmcChassisControlStep { world: bmc_with_pending(world: off, pending: list_append(off.pending, restore), fired: off.fired), effect_taken: true } + } else { BmcChassisControlStep { world: world, effect_taken: false } } + IpmiChassisHardReset => + if bmc_power_is_on(world: world) { BmcChassisControlStep { world: bmc_host_boots(world: world, now: now), effect_taken: true } } + else { BmcChassisControlStep { world: world, effect_taken: false } } } } -fn bmc_apply_event(world: BmcWorld, event: BmcEvent) -> BmcWorld { +fn bmc_apply_event(world: BmcWorld, event: BmcEvent, at: Second) -> BmcWorld { match event { - BmcAcPowerLost {} => bmc_with_power(world: world, power: BmcPowerOff) + BmcAcPowerLost => bmc_with_power(world: world, power: BmcPowerOff) + BmcPowerRestored => bmc_host_boots(world: world, now: at) } } // Every event due at or before `now` fires, in schedule order, and moves from pending to fired. fn bmc_advance(world: BmcWorld, now: Second) -> BmcWorld { - fold(world.pending, init: BmcWorld { power: world.power, pending: [], fired: world.fired }, f: fn(acc, e) { + fold(world.pending, init: bmc_with_pending(world: world, pending: [], fired: world.fired), f: fn(acc, e) { if second_count(s: e.at) <= second_count(s: now) { - BmcWorld { power: bmc_apply_event(world: acc, event: e.event).power, pending: acc.pending, fired: list_append(acc.fired, e) } + let applied = bmc_apply_event(world: acc, event: e.event, at: e.at) + bmc_with_pending(world: applied, pending: acc.pending, fired: list_append(acc.fired, e)) } else { - BmcWorld { power: acc.power, pending: list_append(acc.pending, e), fired: acc.fired } + bmc_with_pending(world: acc, pending: list_append(acc.pending, e), fired: acc.fired) } }) } diff --git a/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag b/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag index fe043da3884..8ca9bf3fed5 100644 --- a/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag +++ b/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag @@ -1,26 +1,34 @@ module gunbc.machine_intake_mtcollins1_boot_dry_realization -import std.types { List, NonEmptyStr, String } -import std.measure { Second } -import v2.std.operation_realization { OperationRealization, virtual_delay_binding } -import gunbc.bmc_model { BmcWorld, bmc_advance } -import gunbc.bmc_dry_realization { bmc_bindings, sleep_delay_seconds_operation } -import gunbc.filesystem_model { ModeledFilesystem, filesystem_bindings } +import std.types { Bool, Int, List, NonEmptyStr, String } +import std.measure { Second, second, second_count } +import v2.std.operation_argv { OperationRef } +import v2.std.operation_realization { + OperationRealization, OperationBinding, OperationCall, OperationStep, + OperationObserved, OperationHarnessFault, ShellObserved, + virtual_delay_binding, operation_input_text, +} +import gunbc.bmc_model { BmcWorld, bmc_advance, bmc_with_sol_session, bmc_power_is_on } +import gunbc.bmc_dry_realization { bmc_bindings, bmc_ipmi_operation, sleep_delay_seconds_operation } +import gunbc.filesystem_model { ModeledFilesystem, ModeledFile, filesystem_bindings, fs_with_file, fs_file, fs_write } import gunbc.process_environment_model { ModeledVariable, environment_binding } import gunbc.host_command_model { ModeledInvocation, exact_invocation_binding, local_command_words, shell_exec_run_argv_operation } import extdeps.transports.shell { ShellExchangeObservation, ShellProcessExited } import extdeps.exec.command { env_prefixed_command } import extdeps.tools.env { EnvSet } import extdeps.ssh.openssh_client_commands { ssh_add_list_identities_command } +import extdeps.bmc.ipmitool_observed_output { ipmitool_sol_activate_preamble } import gunbc.fleet_ssh_access { fleet_automation_ssh_key_fingerprint } import gunbc.remote_host_model { ModeledRemoteHost, remote_host_binding } import gunbc.wall_clock_model { ModeledWallClock, wall_clock_bindings } // THE DRY REALIZATION OF ONE mtcollins1 BOOT ATTEMPT'S WHOLE EFFECT DEMAND. The scenario world -// composes the one BMC model with the worker's filesystem (credential file, unit-hold store, SOL pid -// and capture files) and its process environment (credential path, SOL paths, run and revision -// identity). Every transition belongs to its own model; this module only places them side by side and -// lifts their bindings into the composite state. The real entry -- +// composes the one BMC model with what the worker running the attempt has around it: its filesystem +// (credential file, unit-hold store, SOL pid and capture files, /proc), its processes, its process +// environment, its SSH agent, the hosts it reaches over SSH, its clocks, and the host's console. Every +// transition belongs to its own model; this module places them side by side, lifts their bindings into +// the composite state, and owns only the handlers whose effect spans two of them (a collector the BMC +// session feeds and the worker's filesystem records). The real entry -- // gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 -- runs unchanged over it. type MtCollins1BootWorld { bmc: BmcWorld @@ -29,6 +37,8 @@ type MtCollins1BootWorld { agent: ModeledSshAgent remote_hosts: List clock: ModeledWallClock + worker: ModeledWorker + console: ModeledHostConsole } // THE WORKER'S SSH AGENT: whether it holds the fleet automation key. Its listing follows @@ -47,28 +57,210 @@ fn agent_listing(agent: ModeledSshAgent) -> ShellExchangeObservation { } } -fn boot_world_with_bmc(w: MtCollins1BootWorld, bmc: BmcWorld) -> MtCollins1BootWorld { - MtCollins1BootWorld { bmc: bmc, fs: w.fs, environment: w.environment, agent: w.agent, remote_hosts: w.remote_hosts, clock: w.clock } +// THE WORKER'S PROCESSES that the attempt starts: today, the SOL collector. A live process is visible +// the way the production liveness check reads it -- /proc//cmdline -- and a dead one is not. The +// model writes cmdline with spaces between arguments where Linux writes NULs; the production reader +// asks only whether it contains `ipmitool`, which both spellings answer the same way. +type ModeledProcess { + pid: Int + cmdline: String + capture_path: String + alive: Bool +} + +type ModeledWorker { + next_pid: Int + processes: List + uptime_at_origin: Int +} + +// THE HOST'S CONSOLE: what the machine prints on its serial line, each line at an offset from the +// start of the boot. A line reaches a capture only while the BMC's SOL session is open and a live +// collector appends it; a line printed while nobody is listening is lost, as it is on the real unit. +// emitted counts the lines of the current boot already printed. +type TimedConsoleLine { + after: Second + text: String +} + +type ModeledHostConsole { + lines: List + emitted: Int + boot: Second? +} + +fn with_bmc(w: MtCollins1BootWorld, bmc: BmcWorld) -> MtCollins1BootWorld { + MtCollins1BootWorld { bmc: bmc, fs: w.fs, environment: w.environment, agent: w.agent, remote_hosts: w.remote_hosts, clock: w.clock, worker: w.worker, console: w.console } +} + +fn with_fs(w: MtCollins1BootWorld, fs: ModeledFilesystem) -> MtCollins1BootWorld { + MtCollins1BootWorld { bmc: w.bmc, fs: fs, environment: w.environment, agent: w.agent, remote_hosts: w.remote_hosts, clock: w.clock, worker: w.worker, console: w.console } +} + +fn with_worker(w: MtCollins1BootWorld, worker: ModeledWorker) -> MtCollins1BootWorld { + MtCollins1BootWorld { bmc: w.bmc, fs: w.fs, environment: w.environment, agent: w.agent, remote_hosts: w.remote_hosts, clock: w.clock, worker: worker, console: w.console } +} + +fn with_console(w: MtCollins1BootWorld, console: ModeledHostConsole) -> MtCollins1BootWorld { + MtCollins1BootWorld { bmc: w.bmc, fs: w.fs, environment: w.environment, agent: w.agent, remote_hosts: w.remote_hosts, clock: w.clock, worker: w.worker, console: console } +} + +fn appended(fs: ModeledFilesystem, path: String, text: String) -> ModeledFilesystem { + match fs_file(fs: fs, path: path) { + Present { value: f } => fs_with_file(fs: fs, path: path, content: concat(f.content, text)) + Absent => fs_with_file(fs: fs, path: path, content: text) + } +} + +fn proc_cmdline_path(pid: Int) -> String { + join(["/proc/", to_string(pid), "/cmdline"], "") +} + +fn live_collectors(w: MtCollins1BootWorld) -> List { + filter(w.worker.processes, p => p.alive) +} + +fn exited_step(state: S, stdout: String, exit_code: Int) -> OperationStep { + OperationObserved { observation: ShellObserved { observation: ShellProcessExited { exit_code: exit_code, stdout: stdout, stderr: "" } }, state: state, elapsed: second(count: 1) } +} + +// `ipmitool sdr dump ` writes the controller's SDR repository to the named local file; the +// production code checks only that it succeeded and later reads through the file. The model writes a +// placeholder of the dump into the worker's filesystem, so a later read through it finds a file. +fn sdr_dump_handler(w: MtCollins1BootWorld, call: OperationCall) -> OperationStep { + match operation_input_text(invocation: call.invocation, name: "sdr_cache_file") { + Absent => OperationHarnessFault { reason: "sdr dump was dispatched without a cache file" as NonEmptyStr } + Present { value: path } => { + let written = fs_write(fs: w.fs, path: path, content: "", create_new: false) + exited_step(state: with_fs(w: w, fs: written.fs), stdout: join(["Dumping Sensor Data Repository to '", path, "'\n"], ""), exit_code: 0) + } + } +} + +// gunbc.machine_intake.sol_hold ActivateHeld: a shell starts `ipmitool ... sol activate` in the +// background with its output appended to the capture, records its pid in the pid file, and exits 0 once +// the background job exists. The collector then opens the BMC's SOL session. If the session is already +// held by another client, the real client prints the refusal and exits at once -- but the shell has +// already succeeded and written the pid, exactly as here. +fn sol_activate_held_handler(w: MtCollins1BootWorld, call: OperationCall) -> OperationStep { + match operation_input_text(invocation: call.invocation, name: "capture_path") { + Absent => OperationHarnessFault { reason: "ActivateHeld was dispatched without a capture path" as NonEmptyStr } + Present { value: capture } => match operation_input_text(invocation: call.invocation, name: "pid_path") { + Absent => OperationHarnessFault { reason: "ActivateHeld was dispatched without a pid path" as NonEmptyStr } + Present { value: pid_path } => { + let pid = w.worker.next_pid + let program = match operation_input_text(invocation: call.invocation, name: "ipmitool") { Present { value: p } => p Absent => "ipmitool" } + let already = w.bmc.sol_session_open + let process = ModeledProcess { pid: pid, cmdline: concat(program, " -H bmc -I lanplus sol activate"), capture_path: capture, alive: !already } + let fs1 = fs_write(fs: w.fs, path: pid_path, content: concat(to_string(pid), "\n"), create_new: false).fs + let fs2 = if already { appended(fs: fs1, path: capture, text: "Info: SOL payload already active on another session\n") } + else { fs_with_file(fs: appended(fs: fs1, path: capture, text: ipmitool_sol_activate_preamble), path: proc_cmdline_path(pid: pid), content: process.cmdline) } + let worker = ModeledWorker { next_pid: pid + 1, processes: list_append(w.worker.processes, process), uptime_at_origin: w.worker.uptime_at_origin } + let bmc = if already { w.bmc } else { bmc_with_sol_session(world: w.bmc, open: true) } + exited_step(state: with_bmc(w: with_worker(w: with_fs(w: w, fs: fs2), worker: worker), bmc: bmc), stdout: "", exit_code: 0) + } + } + } +} + +// `ipmitool sol deactivate` ends the BMC's SOL session; a collector attached to it loses its session +// and exits, so its /proc entry disappears. Its pid file is NOT removed -- nothing in the realization +// removes it -- which is exactly what the production teardown observes afterwards. +fn sol_deactivate_handler(w: MtCollins1BootWorld, call: OperationCall) -> OperationStep { + let fs = fold(live_collectors(w: w), init: w.fs, f: fn(acc, p) { + ModeledFilesystem { directories: acc.directories, files: filter(acc.files, f => f.path != proc_cmdline_path(pid: p.pid)) } + }) + let worker = ModeledWorker { next_pid: w.worker.next_pid, processes: map(w.worker.processes, p => ModeledProcess { pid: p.pid, cmdline: p.cmdline, capture_path: p.capture_path, alive: false }), uptime_at_origin: w.worker.uptime_at_origin } + exited_step(state: with_bmc(w: with_worker(w: with_fs(w: w, fs: fs), worker: worker), bmc: bmc_with_sol_session(world: w.bmc, open: false)), stdout: "", exit_code: 0) +} + +// Master Write-Read toward the SMpro. The retained attempt of 2026-09-27 (run 36335369059, +// mtcollins1-boot-diagnostics.txt sha256 eda19cde2e60d90d3d04946273e615eb5bf774496b9907e03d3bc15dbedc2c1c) recorded +// every probe refused with completion code 0xff, and no successful exchange is retained, so that is the +// grounded answer the model gives. The production code records SMpro passes and never gates on them. +fn smpro_refused_handler(w: MtCollins1BootWorld, call: OperationCall) -> OperationStep { + OperationObserved { + observation: ShellObserved { observation: ShellProcessExited { exit_code: 1, stdout: "", stderr: "Unable to send RAW command (channel=0x0 netfn=0x6 lun=0x0 cmd=0x52 rsp=0xff): Unspecified error\n" } }, + state: w, + elapsed: second(count: 1), + } +} + +// /proc/uptime: the worker's seconds since its own boot, `. ` (proc(5)). +data linux_procfs_read_uptime_operation: OperationRef = OperationRef { + path: "dag/extdeps/linux/procfs.dag", + service: "linux.Procfs", + operation: "ReadUptime", +} + +fn uptime_handler(w: MtCollins1BootWorld, call: OperationCall) -> OperationStep { + let up = w.worker.uptime_at_origin + (second_count(s: call.now) as Int) + OperationObserved { + observation: ShellObserved { observation: ShellProcessExited { exit_code: 0, stdout: join([to_string(up), ".00 ", to_string(up), ".00\n"], ""), stderr: "" } }, + state: w, + elapsed: second(count: 0), + } +} + +// THE CONSOLE ADVANCES WITH TIME. After the BMC's own events fire, every console line of the current +// boot whose offset has passed is printed; it reaches each live collector's capture only if the SOL +// session is open. A new boot restarts the console from its first line. +fn console_advance(w: MtCollins1BootWorld, now: Second) -> MtCollins1BootWorld { + match w.bmc.host_booted_at { + Absent => w + Present { value: booted } => { + let fresh = match w.console.boot { Present { value: b } => second_count(s: b) != second_count(s: booted) Absent => true } + let console = if fresh { ModeledHostConsole { lines: w.console.lines, emitted: 0, boot: Present { value: booted } } } else { w.console } + let elapsed = (second_count(s: now) as Int) - (second_count(s: booted) as Int) + let due = filter(console.lines.skip(n: console.emitted), l => (second_count(s: l.after) as Int) <= elapsed) + let printed = concat_text(lines: due) + let listening = w.bmc.sol_session_open && bmc_power_is_on(world: w.bmc) + let fs = if listening { fold(live_collectors(w: w), init: w.fs, f: fn(acc, p) { appended(fs: acc, path: p.capture_path, text: printed) }) } else { w.fs } + with_console(w: with_fs(w: w, fs: fs), console: ModeledHostConsole { lines: console.lines, emitted: console.emitted + count(due), boot: console.boot }) + } + } +} + +fn concat_text(lines: List) -> String { + join(map(lines, l => concat(l.text, "\n")), "") +} + +fn boot_advance(w: MtCollins1BootWorld, now: Second) -> MtCollins1BootWorld { + console_advance(w: with_bmc(w: w, bmc: bmc_advance(world: w.bmc, now: now)), now: now) } -fn boot_world_with_fs(w: MtCollins1BootWorld, fs: ModeledFilesystem) -> MtCollins1BootWorld { - MtCollins1BootWorld { bmc: w.bmc, fs: fs, environment: w.environment, agent: w.agent, remote_hosts: w.remote_hosts, clock: w.clock } +data sol_hold_activate_held_operation: OperationRef = OperationRef { + path: "dag/gunbc/machine_intake/sol_hold.dag", + service: "gunbc.machine_intake.sol_hold", + operation: "ActivateHeld", } fn mtcollins1_boot_dry_realization(identity: NonEmptyStr, initial: MtCollins1BootWorld, epoch: Second) -> OperationRealization { - let bmc = bmc_bindings(get: fn(w) { w.bmc }, put: boot_world_with_bmc) - let files = filesystem_bindings(get: fn(w) { w.fs }, put: boot_world_with_fs) + let bmc = bmc_bindings(get: fn(w) { w.bmc }, put: with_bmc) + let files = filesystem_bindings(get: fn(w) { w.fs }, put: with_fs) let local_commands = exact_invocation_binding(at: shell_exec_run_argv_operation, inputs: ["program", "arguments"], table: [ ModeledInvocation { words: local_command_words(command: env_prefixed_command(bindings: [EnvSet { name: "SSH_AUTH_SOCK", value: initial.agent.socket }], command: ssh_add_list_identities_command())), answer: fn(w) { agent_listing(agent: w.agent) }, }, ]) + let spanning = [ + OperationBinding { at: bmc_ipmi_operation(operation: "SdrDumpAuthenticated"), handler: sdr_dump_handler }, + OperationBinding { at: bmc_ipmi_operation(operation: "SolDeactivate"), handler: sol_deactivate_handler }, + OperationBinding { at: bmc_ipmi_operation(operation: "MasterWriteReadAuthenticated"), handler: smpro_refused_handler }, + OperationBinding { at: sol_hold_activate_held_operation, handler: sol_activate_held_handler }, + OperationBinding { at: linux_procfs_read_uptime_operation, handler: uptime_handler }, + ] OperationRealization { identity: identity, initial: initial, epoch: epoch, - bindings: concat(concat(concat(bmc, files), wall_clock_bindings(get: fn(w) { w.clock })), [environment_binding(get: fn(w) { w.environment }), virtual_delay_binding(at: sleep_delay_seconds_operation, seconds_input: "seconds"), local_commands, remote_host_binding(get: fn(w) { w.remote_hosts })]), - advance: fn(w, now) { boot_world_with_bmc(w: w, bmc: bmc_advance(world: w.bmc, now: now)) }, + bindings: concat(concat(concat(concat(bmc, files), wall_clock_bindings(get: fn(w) { w.clock })), spanning), [ + environment_binding(get: fn(w) { w.environment }), + virtual_delay_binding(at: sleep_delay_seconds_operation, seconds_input: "seconds"), + local_commands, + remote_host_binding(get: fn(w) { w.remote_hosts }), + ]), + advance: boot_advance, } } diff --git a/dag/test/claim/operation_realization_witness_test.dag b/dag/test/claim/operation_realization_witness_test.dag index 74a36efc63c..52237a01a27 100644 --- a/dag/test/claim/operation_realization_witness_test.dag +++ b/dag/test/claim/operation_realization_witness_test.dag @@ -31,6 +31,7 @@ import extdeps.bmc.ipmi_chassis_control { import gunbc.machine_intake_oob_boot_handoff { read_chassis_power } import gunbc.bmc_model { BmcWorld, BmcPowerOn, BmcPowerOff, BmcScheduledEvent, BmcAcPowerLost, bmc_power_is_on, + bmc_world, bmc_with_pending, bmc_with_power, } import gunbc.bmc_dry_realization { bmc_dry_realization, bmc_ipmi_operation, bmc_chassis_power_control_handler, @@ -71,7 +72,7 @@ fn modeled_frame(realization: OperationRealization) -> WitnessEvaluati } fn world(power_on: Bool) -> BmcWorld { - BmcWorld { power: if power_on { BmcPowerOn } else { BmcPowerOff }, pending: [], fired: [] } + bmc_world(power: if power_on { BmcPowerOn } else { BmcPowerOff }) } fn read_power() -> ChassisPowerObservation { @@ -202,7 +203,7 @@ fn loss_fired_and_none_pending(w: BmcWorld) -> Bool { // (t 0 -> 1), sleeps five seconds through the production sleep helper (t 1 -> 6), and reads again. The // loss fired during the sleep with no operation issued at t=3, and the second read observes it. test fn a_scheduled_event_fires_during_a_quiet_wait() -> Bool { - let initial = BmcWorld { power: BmcPowerOn, pending: [BmcScheduledEvent { at: second(count: 3), event: BmcAcPowerLost {} }], fired: [] } + let initial = bmc_with_pending(world: bmc_world(power: BmcPowerOn), pending: [BmcScheduledEvent { at: second(count: 3), event: BmcAcPowerLost }], fired: []) let result = evaluate_in_witness_frame( frame: modeled_frame(realization: bmc_dry_realization(identity: realization_identity, initial: initial, epoch: virtual_clock_origin())), subject: fn(_scope) { @@ -307,7 +308,7 @@ test fn a_binding_to_an_undeclared_identity_refuses_before_the_subject_runs() -> // witness is the supervisor: it resumes a SECOND attempt from that world in a new frame, and the // resumed attempt reads before it writes and finds the host already on. fn killed_after_power_on(state: BmcWorld, call: OperationCall) -> OperationStep { - OperationWorkerKilled { committed: true, state: BmcWorld { power: BmcPowerOn, pending: state.pending, fired: state.fired } } + OperationWorkerKilled { committed: true, state: bmc_with_power(world: state, power: BmcPowerOn) } } test fn a_killed_worker_is_interrupted_and_a_resumed_attempt_reconciles() -> Bool { @@ -523,7 +524,7 @@ test fn frames_are_independent_after_the_outer_scope_ends() -> Bool { // fires) and reads off, exactly as the uninterrupted history does. Had the resume restarted at the // origin its reads would complete at 1 and the loss would not yet have fired. fn loss_at_five() -> BmcWorld { - BmcWorld { power: BmcPowerOn, pending: [BmcScheduledEvent { at: second(count: 5), event: BmcAcPowerLost {} }], fired: [] } + bmc_with_pending(world: bmc_world(power: BmcPowerOn), pending: [BmcScheduledEvent { at: second(count: 5), event: BmcAcPowerLost }], fired: []) } fn killed_without_commit(state: BmcWorld, call: OperationCall) -> OperationStep { From a712a75f0f228e0ff67743206d1d1b46f6c78e89 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 28 Sep 2026 10:30:17 +0000 Subject: [PATCH 05/38] MegaRAC media model + adapter; mtcollins1 boot acceptance matrix over the real entry (deadline refusal, pinned SOL-teardown defect, held-unit contender) Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/extdeps/bmc/megarac_observed_output.dag | 73 +++++ dag/gunbc/bmc_dry_realization.dag | 110 ++++++- .../mtcollins1_boot_dry_realization.dag | 20 +- dag/gunbc/megarac_media_model.dag | 138 +++++++++ ...mtcollins1_boot_acceptance_matrix_test.dag | 275 ++++++++++++++++++ 5 files changed, 608 insertions(+), 8 deletions(-) create mode 100644 dag/extdeps/bmc/megarac_observed_output.dag create mode 100644 dag/gunbc/megarac_media_model.dag create mode 100644 dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag diff --git a/dag/extdeps/bmc/megarac_observed_output.dag b/dag/extdeps/bmc/megarac_observed_output.dag new file mode 100644 index 00000000000..6633306a62b --- /dev/null +++ b/dag/extdeps/bmc/megarac_observed_output.dag @@ -0,0 +1,73 @@ +module extdeps.bmc.megarac_observed_output + +import std.types { Int, NonEmptyStr, String } +import extdeps.external_authority { ExternalAuthority, CitedFigureStanding, TranscribedUncited } +import extdeps.uri { Uri, Https } + +data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { + uri: Uri { + scheme: Https + locator: "www.ami.com/megarac/" + } +} + +// WHAT THE Mt. Collins MegaRAC (firmware 0.32) ANSWERS ON ITS MEDIA REST ROUTES, as observed. Each +// renderer reproduces a retained body's key order and spelling, with the fields a scenario varies +// substituted; its source is cited beside it. A body no retained exchange carries says so in its +// standing -- the production decoder reads only the members named there, and the rest of the body is +// the firmware's form as far as it has been read. + +// GET /api/settings/media/general. Source: test.claim.machine_intake.megarac_media_convergence_witness_test +// mtcollins1_media_general_2026_09_27, a read-only GET at 2026-09-27T18:37:18Z after boot run 36330382023, +// verbatim, with the share, mount and CD error code as parameters. The firmware escapes `/` as `\/`. +fn megarac_media_general_body(server: String, source_path: String, share_type: String, mount_cd: Int, cd_error_code: Int) -> String { + join([ + "{ \"id\": 1, \"local_media_support\": 0, \"remote_media_support\": 1, \"same_settings\": 0, \"cd_remote_server_address\": \"", server, + "\", \"cd_remote_source_path\": \"", json_escaped_slashes(s: source_path), + "\", \"cd_remote_share_type\": \"", share_type, + "\", \"cd_remote_domain_name\": \"\", \"cd_remote_user_name\": \"\", \"mount_cd\": ", to_string(mount_cd), + ", \"cd_image_name\": \"\", \"cd_error_code\": ", to_string(cd_error_code), + ", \"mount_hd\": 0, \"hd_remote_server_address\": \"\", \"hd_remote_source_path\": \"\", \"hd_remote_share_type\": \"\", \"hd_remote_domain_name\": \"\", \"hd_remote_user_name\": \"\", \"hd_image_name\": \"\", \"hd_error_code\": 0, \"rmedia_retry_count\": 3, \"rmedia_retry_interval\": 15 }", + ], "") +} + +fn json_escaped_slashes(s: String) -> String { + join(split(s: s, delimiter: "/"), "\\/") +} + +// One row of GET /api/settings/media/remote/configurations. Source: the cleared row retained in +// test.claim.machine_intake.megarac_media_convergence_witness_test +// (`[{"media_type":1,"image_name":"","redirection_status":0,"media_index":0,"session_index":255}]`), +// with the image, status and indices as parameters. +fn megarac_configuration_row(image_name: String, redirection_status: Int, media_index: Int, session_index: Int) -> String { + join(["{\"media_type\":1,\"image_name\":\"", image_name, "\",\"redirection_status\":", to_string(redirection_status), ",\"media_index\":", to_string(media_index), ",\"session_index\":", to_string(session_index), "}"], "") +} + +// One row of GET /api/settings/media/remote/images. Source: the listing row in the same witness, +// `{"image_name":"","image_index":5}`, with the index the real attach logs report. +fn megarac_image_row(image_name: String, image_index: Int) -> String { + join(["{\"image_name\":\"", image_name, "\",\"image_index\":", to_string(image_index), "}"], "") +} + +// The rejection a route gives a request without a valid session. Source: +// test.claim.machine_intake.megarac_session_release_witness_test, `{"error":"invalid session"}` with 401. +data megarac_invalid_session_body: String = "{\"error\":\"invalid session\"}" + +// POST /api/session. NO SESSION REPLY IS RETAINED VERBATIM. The member names are those the firmware's +// own web UI reads (source.min.js, probed 2026-09-27 on branch session/eager-koi-811), and the production +// decoder reads only CSRFToken. +data megarac_session_body_standing: CitedFigureStanding = TranscribedUncited { + read_obligation: "a retained POST /api/session reply from the Mt. Collins MegaRAC, cited by digest" as NonEmptyStr +} + +fn megarac_session_body(racsession_id: Int, csrf_token: String) -> String { + join(["{ \"ok\": 0, \"privilege\": 4, \"extendedpriv\": 259, \"racsession_id\": ", to_string(racsession_id), ", \"CSRFToken\": \"", csrf_token, "\" }"], "") +} + +// The replies to start-media, stop-media and DELETE /api/session on success are not decoded by the +// production code (readiness and re-observation decide), and none is retained verbatim. +data megarac_write_ack_body_standing: CitedFigureStanding = TranscribedUncited { + read_obligation: "retained 200 bodies of start-media, stop-media and DELETE /api/session from the Mt. Collins MegaRAC, cited by digest" as NonEmptyStr +} + +data megarac_write_ack_body: String = "{}" diff --git a/dag/gunbc/bmc_dry_realization.dag b/dag/gunbc/bmc_dry_realization.dag index ffafc952744..914b446b6ef 100644 --- a/dag/gunbc/bmc_dry_realization.dag +++ b/dag/gunbc/bmc_dry_realization.dag @@ -1,6 +1,6 @@ module gunbc.bmc_dry_realization -import std.types { Bool, List, NonEmptyStr, String } +import std.types { Bool, Int, List, NonEmptyStr, String } import std.measure { Second, second } import v2.std.operation_argv { OperationRef } import v2.std.operation_realization { @@ -16,6 +16,16 @@ import gunbc.bmc_model { BmcWorld, bmc_chassis_control, bmc_power_is_on, bmc_advance, bmc_with_override, BootOverrideNone, BootOverrideCdromEfiNextBoot, } +import gunbc.megarac_media_model { + MegaRacMediaWorld, megarac_session_valid, megarac_open_session, megarac_close_session, + megarac_start_media, megarac_stop_media, +} +import extdeps.bmc.megarac_observed_output { + megarac_media_general_body, megarac_configuration_row, megarac_image_row, megarac_invalid_session_body, + megarac_session_body, megarac_write_ack_body, +} +import extdeps.languages.json.parse { parse_json_document, JsonDocumentParsed, JsonDocumentUnreadable } +import gunbc.machine_intake_megarac_media_attach { json_string_member, json_number_member } import extdeps.bmc.ipmitool_observed_output { ipmitool_bootparam5_no_override, ipmitool_bootparam5_cdrom_efi_next_boot, ipmitool_bootdev_cdrom_reply, ipmitool_sel_elist_line, @@ -143,3 +153,101 @@ fn bmc_dry_realization(identity: NonEmptyStr, initial: BmcWorld, epoch: Second) advance: bmc_advance, } } + +// THE MEGARAC MEDIA ROUTES, answered over the media model as curl --fail-with-body sees them: a +// request with a valid session gets the firmware's body and exit 0; one without gets the 401 body on +// stdout, curl's own error line on stderr, and exit 22 (curl(1), --fail-with-body). The session probe's +// `-w "\n%{http_code}"` appends the status line. One request takes one virtual second. +data megarac_operation_path: String = "dag/extdeps/bmc/megarac.dag" + +fn megarac_operation(operation: String) -> OperationRef { + OperationRef { path: megarac_operation_path, service: "megarac.Media", operation: operation } +} + +fn curl_answer(state: S, exit_code: Int, stdout: String, stderr: String) -> OperationStep { + OperationObserved { observation: ShellObserved { observation: ShellProcessExited { exit_code: exit_code, stdout: stdout, stderr: stderr } }, state: state, elapsed: second(count: 1) } +} + +fn curl_refused_401(state: S, with_status_line: Bool) -> OperationStep { + curl_answer(state: state, exit_code: 22, stdout: if with_status_line { concat(megarac_invalid_session_body, "\n401") } else { megarac_invalid_session_body }, stderr: "curl: (22) The requested URL returned error: 401\n") +} + +fn call_text(call: OperationCall, name: String) -> String { + match operation_input_text(invocation: call.invocation, name: name) { Present { value: v } => v Absent => "" } +} + +fn megarac_configurations_body(media: MegaRacMediaWorld) -> String { + concat("[", concat(megarac_configuration_row(image_name: media.cd.image_name, redirection_status: media.cd.redirection_status, media_index: media.cd.media_index, session_index: media.cd.session_index), "]")) +} + +fn megarac_images_body(media: MegaRacMediaWorld) -> String { + concat("[", concat(join(map(media.images, i => megarac_image_row(image_name: i.image_name, image_index: i.image_index)), ","), "]")) +} + +fn megarac_authenticated(get: fn(S) -> MegaRacMediaWorld, render: fn(S, OperationCall) -> OperationStep) -> fn(S, OperationCall) -> OperationStep { + fn(state, call) { + if megarac_session_valid(world: get(state), cookie_jar: call_text(call: call, name: "cookie_jar"), csrf_token: call_text(call: call, name: "csrf_token")) { + render(state, call) + } else { + curl_refused_401(state: state, with_status_line: false) + } + } +} + +// The start and stop bodies are the production builders' JSON; the model reads them with the same +// member readers the production decoders use (gunbc.machine_intake_megarac_media_attach). +fn body_member_text(call: OperationCall, key: String) -> String? { + match parse_json_document(s: call_text(call: call, name: "json_body")) { + JsonDocumentParsed { value: doc } => json_string_member(doc: doc, key: key) + JsonDocumentUnreadable { gap: _ } => none + } +} + +fn body_member_int(call: OperationCall, key: String) -> Int? { + match parse_json_document(s: call_text(call: call, name: "json_body")) { + JsonDocumentParsed { value: doc } => match json_number_member(doc: doc, key: key) { + Present { value: n } => parse_int(s: n) + Absent => none + } + JsonDocumentUnreadable { gap: _ } => none + } +} + +fn megarac_bindings(get: fn(S) -> MegaRacMediaWorld, put: fn(S, MegaRacMediaWorld) -> S) -> List> { + [ + OperationBinding { at: megarac_operation(operation: "OpenSession"), handler: fn(state, call) { + let opened = megarac_open_session(world: get(state), cookie_jar: call_text(call: call, name: "cookie_jar")) + curl_answer(state: put(state, opened.world), exit_code: 0, stdout: megarac_session_body(racsession_id: opened.racsession_id, csrf_token: opened.csrf_token), stderr: "") + } }, + OperationBinding { at: megarac_operation(operation: "ProbeSessionOnMediaRoute"), handler: fn(state, call) { + if megarac_session_valid(world: get(state), cookie_jar: call_text(call: call, name: "cookie_jar"), csrf_token: call_text(call: call, name: "csrf_token")) { + curl_answer(state: state, exit_code: 0, stdout: concat(megarac_configurations_body(media: get(state)), "\n200"), stderr: "") + } else { curl_refused_401(state: state, with_status_line: true) } + } }, + OperationBinding { at: megarac_operation(operation: "GetMediaGeneral"), handler: megarac_authenticated(get: get, render: fn(state, call) { + let m = get(state) + curl_answer(state: state, exit_code: 0, stdout: megarac_media_general_body(server: m.share.server, source_path: m.share.source_path, share_type: m.share.share_type, mount_cd: m.mount_cd, cd_error_code: m.cd_error_code), stderr: "") + }) }, + OperationBinding { at: megarac_operation(operation: "GetRemoteConfigurations"), handler: megarac_authenticated(get: get, render: fn(state, call) { + curl_answer(state: state, exit_code: 0, stdout: megarac_configurations_body(media: get(state)), stderr: "") + }) }, + OperationBinding { at: megarac_operation(operation: "GetRemoteImages"), handler: megarac_authenticated(get: get, render: fn(state, call) { + curl_answer(state: state, exit_code: 0, stdout: megarac_images_body(media: get(state)), stderr: "") + }) }, + OperationBinding { at: megarac_operation(operation: "StartMedia"), handler: megarac_authenticated(get: get, render: fn(state, call) { + match body_member_text(call: call, key: "image_name") { + Absent => OperationHarnessFault { reason: "start-media was dispatched without a readable image_name" as NonEmptyStr } + Present { value: name } => match body_member_int(call: call, key: "image_index") { + Absent => OperationHarnessFault { reason: "start-media was dispatched without a readable image_index" as NonEmptyStr } + Present { value: idx } => curl_answer(state: put(state, megarac_start_media(world: get(state), image_name: name, image_index: idx, now: call.now)), exit_code: 0, stdout: megarac_write_ack_body, stderr: "") + } + } + }) }, + OperationBinding { at: megarac_operation(operation: "StopMedia"), handler: megarac_authenticated(get: get, render: fn(state, call) { + curl_answer(state: put(state, megarac_stop_media(world: get(state))), exit_code: 0, stdout: megarac_write_ack_body, stderr: "") + }) }, + OperationBinding { at: megarac_operation(operation: "CloseSession"), handler: megarac_authenticated(get: get, render: fn(state, call) { + curl_answer(state: put(state, megarac_close_session(world: get(state), cookie_jar: call_text(call: call, name: "cookie_jar"), csrf_token: call_text(call: call, name: "csrf_token"))), exit_code: 0, stdout: megarac_write_ack_body, stderr: "") + }) }, + ] +} diff --git a/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag b/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag index 8ca9bf3fed5..bcaeb2e703f 100644 --- a/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag +++ b/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag @@ -9,7 +9,8 @@ import v2.std.operation_realization { virtual_delay_binding, operation_input_text, } import gunbc.bmc_model { BmcWorld, bmc_advance, bmc_with_sol_session, bmc_power_is_on } -import gunbc.bmc_dry_realization { bmc_bindings, bmc_ipmi_operation, sleep_delay_seconds_operation } +import gunbc.bmc_dry_realization { bmc_bindings, megarac_bindings, bmc_ipmi_operation, sleep_delay_seconds_operation } +import gunbc.megarac_media_model { MegaRacMediaWorld, megarac_media_advance } import gunbc.filesystem_model { ModeledFilesystem, ModeledFile, filesystem_bindings, fs_with_file, fs_file, fs_write } import gunbc.process_environment_model { ModeledVariable, environment_binding } import gunbc.host_command_model { ModeledInvocation, exact_invocation_binding, local_command_words, shell_exec_run_argv_operation } @@ -39,6 +40,7 @@ type MtCollins1BootWorld { clock: ModeledWallClock worker: ModeledWorker console: ModeledHostConsole + media: MegaRacMediaWorld } // THE WORKER'S SSH AGENT: whether it holds the fleet automation key. Its listing follows @@ -90,19 +92,23 @@ type ModeledHostConsole { } fn with_bmc(w: MtCollins1BootWorld, bmc: BmcWorld) -> MtCollins1BootWorld { - MtCollins1BootWorld { bmc: bmc, fs: w.fs, environment: w.environment, agent: w.agent, remote_hosts: w.remote_hosts, clock: w.clock, worker: w.worker, console: w.console } + MtCollins1BootWorld { bmc: bmc, fs: w.fs, environment: w.environment, agent: w.agent, remote_hosts: w.remote_hosts, clock: w.clock, worker: w.worker, console: w.console, media: w.media } } fn with_fs(w: MtCollins1BootWorld, fs: ModeledFilesystem) -> MtCollins1BootWorld { - MtCollins1BootWorld { bmc: w.bmc, fs: fs, environment: w.environment, agent: w.agent, remote_hosts: w.remote_hosts, clock: w.clock, worker: w.worker, console: w.console } + MtCollins1BootWorld { bmc: w.bmc, fs: fs, environment: w.environment, agent: w.agent, remote_hosts: w.remote_hosts, clock: w.clock, worker: w.worker, console: w.console, media: w.media } } fn with_worker(w: MtCollins1BootWorld, worker: ModeledWorker) -> MtCollins1BootWorld { - MtCollins1BootWorld { bmc: w.bmc, fs: w.fs, environment: w.environment, agent: w.agent, remote_hosts: w.remote_hosts, clock: w.clock, worker: worker, console: w.console } + MtCollins1BootWorld { bmc: w.bmc, fs: w.fs, environment: w.environment, agent: w.agent, remote_hosts: w.remote_hosts, clock: w.clock, worker: worker, console: w.console, media: w.media } } fn with_console(w: MtCollins1BootWorld, console: ModeledHostConsole) -> MtCollins1BootWorld { - MtCollins1BootWorld { bmc: w.bmc, fs: w.fs, environment: w.environment, agent: w.agent, remote_hosts: w.remote_hosts, clock: w.clock, worker: w.worker, console: console } + MtCollins1BootWorld { bmc: w.bmc, fs: w.fs, environment: w.environment, agent: w.agent, remote_hosts: w.remote_hosts, clock: w.clock, worker: w.worker, console: console, media: w.media } +} + +fn with_media(w: MtCollins1BootWorld, media: MegaRacMediaWorld) -> MtCollins1BootWorld { + MtCollins1BootWorld { bmc: w.bmc, fs: w.fs, environment: w.environment, agent: w.agent, remote_hosts: w.remote_hosts, clock: w.clock, worker: w.worker, console: w.console, media: media } } fn appended(fs: ModeledFilesystem, path: String, text: String) -> ModeledFilesystem { @@ -226,7 +232,7 @@ fn concat_text(lines: List) -> String { } fn boot_advance(w: MtCollins1BootWorld, now: Second) -> MtCollins1BootWorld { - console_advance(w: with_bmc(w: w, bmc: bmc_advance(world: w.bmc, now: now)), now: now) + console_advance(w: with_media(w: with_bmc(w: w, bmc: bmc_advance(world: w.bmc, now: now)), media: megarac_media_advance(world: w.media, now: now)), now: now) } data sol_hold_activate_held_operation: OperationRef = OperationRef { @@ -236,7 +242,7 @@ data sol_hold_activate_held_operation: OperationRef = OperationRef { } fn mtcollins1_boot_dry_realization(identity: NonEmptyStr, initial: MtCollins1BootWorld, epoch: Second) -> OperationRealization { - let bmc = bmc_bindings(get: fn(w) { w.bmc }, put: with_bmc) + let bmc = concat(bmc_bindings(get: fn(w) { w.bmc }, put: with_bmc), megarac_bindings(get: fn(w) { w.media }, put: with_media)) let files = filesystem_bindings(get: fn(w) { w.fs }, put: with_fs) let local_commands = exact_invocation_binding(at: shell_exec_run_argv_operation, inputs: ["program", "arguments"], table: [ ModeledInvocation { diff --git a/dag/gunbc/megarac_media_model.dag b/dag/gunbc/megarac_media_model.dag new file mode 100644 index 00000000000..9a777506c8f --- /dev/null +++ b/dag/gunbc/megarac_media_model.dag @@ -0,0 +1,138 @@ +module gunbc.megarac_media_model + +import std.types { Bool, Int, List, String } +import std.measure { Second, second, second_count } + +// THE BMC's VIRTUAL-MEDIA SUBSYSTEM (AMI MegaRAC SP-X, firmware 0.32): the same controller as +// gunbc.bmc_model, held as its own record because its state -- web sessions, the NFS share binding, +// the image listing and the presented CD -- moves independently of power and IPMI. Pure transitions +// with time as an input; the wire shapes are extdeps.bmc.megarac and extdeps.bmc.megarac_observed_output. +// +// SESSIONS. POST /api/session opens a session the client holds through its cookie jar and a CSRF +// token; every other route needs both. DELETE /api/session closes it, after which the same cookie and +// token are refused with 401 -- the property the production release check re-observes. +type MegaRacSession { + cookie_jar: String + csrf_token: String + open: Bool +} + +type MegaRacShare { + server: String + source_path: String + share_type: String +} + +// THE PRESENTED CD, as one configurations row. redirection_status on this firmware: 0 stopped, 100 +// connecting, 1 started (extdeps.bmc.megarac). A start moves the row to connecting and, after the +// readiness delay, to started with a bound session index; session index 255 means no session. +// ready_at is when a connecting row becomes started. +type MegaRacCdRow { + image_name: String + redirection_status: Int + media_index: Int + session_index: Int + ready_at: Second? +} + +type MegaRacImage { + image_name: String + image_index: Int +} + +// ready_after is the controller's connecting interval. Observed 2026-09-27 (gunbc.machine_intake_megarac_media_attach +// readiness notes): status 100 at +6s, then 1 with session index 0 from +12s. +type MegaRacMediaWorld { + sessions: List + next_session: Int + share: MegaRacShare + mount_cd: Int + cd_error_code: Int + images: List + cd: MegaRacCdRow + ready_after: Second +} + +fn megarac_cleared_cd() -> MegaRacCdRow { + MegaRacCdRow { image_name: "", redirection_status: 0, media_index: 0, session_index: 255, ready_at: none } +} + +fn megarac_session_valid(world: MegaRacMediaWorld, cookie_jar: String, csrf_token: String) -> Bool { + !all(world.sessions, s => !(s.open && s.cookie_jar == cookie_jar && s.csrf_token == csrf_token)) +} + +fn megarac_session_by_jar(world: MegaRacMediaWorld, cookie_jar: String) -> Bool { + !all(world.sessions, s => !(s.open && s.cookie_jar == cookie_jar)) +} + +fn megarac_with_sessions(world: MegaRacMediaWorld, sessions: List, next_session: Int) -> MegaRacMediaWorld { + MegaRacMediaWorld { sessions: sessions, next_session: next_session, share: world.share, mount_cd: world.mount_cd, cd_error_code: world.cd_error_code, images: world.images, cd: world.cd, ready_after: world.ready_after } +} + +fn megarac_with_cd(world: MegaRacMediaWorld, cd: MegaRacCdRow) -> MegaRacMediaWorld { + MegaRacMediaWorld { sessions: world.sessions, next_session: world.next_session, share: world.share, mount_cd: world.mount_cd, cd_error_code: world.cd_error_code, images: world.images, cd: cd, ready_after: world.ready_after } +} + +type MegaRacOpened { + world: MegaRacMediaWorld + racsession_id: Int + csrf_token: String +} + +fn megarac_open_session(world: MegaRacMediaWorld, cookie_jar: String) -> MegaRacOpened { + let id = world.next_session + let token = concat("modeled-csrf-", to_string(id)) + MegaRacOpened { + world: megarac_with_sessions(world: world, sessions: list_append(world.sessions, MegaRacSession { cookie_jar: cookie_jar, csrf_token: token, open: true }), next_session: id + 1), + racsession_id: id, + csrf_token: token, + } +} + +fn megarac_close_session(world: MegaRacMediaWorld, cookie_jar: String, csrf_token: String) -> MegaRacMediaWorld { + megarac_with_sessions( + world: world, + sessions: map(world.sessions, s => if s.cookie_jar == cookie_jar && s.csrf_token == csrf_token { MegaRacSession { cookie_jar: s.cookie_jar, csrf_token: s.csrf_token, open: false } } else { s }), + next_session: world.next_session, + ) +} + +fn megarac_image_index(world: MegaRacMediaWorld, image_name: String) -> Int? { + fold(world.images, init: none, f: fn(acc, i) { + match acc { + Present { value: v } => Present { value: v } + Absent => if i.image_name == image_name { Present { value: i.image_index } } else { none } + } + }) +} + +// A start names an image and its listing index. The row connects, and becomes started after the +// connecting interval. A start naming an image the listing does not hold changes nothing; whether the +// firmware answers such a start with an error body is not observed, so the adapter reports the write as +// accepted and readiness -- which the production code waits on -- never arrives. +fn megarac_start_media(world: MegaRacMediaWorld, image_name: String, image_index: Int, now: Second) -> MegaRacMediaWorld { + match megarac_image_index(world: world, image_name: image_name) { + Present { value: idx } => + if idx == image_index { + megarac_with_cd(world: world, cd: MegaRacCdRow { + image_name: image_name, redirection_status: 100, media_index: 0, session_index: 255, + ready_at: Present { value: second(count: second_count(s: now) + second_count(s: world.ready_after)) }, + }) + } else { world } + Absent => world + } +} + +fn megarac_stop_media(world: MegaRacMediaWorld) -> MegaRacMediaWorld { + megarac_with_cd(world: world, cd: megarac_cleared_cd()) +} + +fn megarac_media_advance(world: MegaRacMediaWorld, now: Second) -> MegaRacMediaWorld { + match world.cd.ready_at { + Absent => world + Present { value: at } => + if second_count(s: at) <= second_count(s: now) { + megarac_with_cd(world: world, cd: MegaRacCdRow { image_name: world.cd.image_name, redirection_status: 1, media_index: world.cd.media_index, session_index: 0, ready_at: none }) + } else { world } + } +} diff --git a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag new file mode 100644 index 00000000000..23d5c5c7db6 --- /dev/null +++ b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag @@ -0,0 +1,275 @@ +module test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test + +import std.types { Bool, Int, List, NonEmptyStr, String } +import std.measure { Second, second } +import std.materialization_ladder { Frame, SharedStateFrame } +import std.effect_grant { Read, Write, ServiceOpTree, NamespacePosition, ModeledRealization, LifecycleByConstruction, Grant, Envelope } +import v2.std.operation_argv { InputText, InputTextList } +import v2.std.operation_realization { DispatchRecord, virtual_clock_origin } +import v2.std.witness_evaluation { + WitnessEvaluation, WitnessEvaluationFrame, WitnessReturned, WitnessRefused, WitnessInterrupted, + evaluate_in_witness_frame, witness_diagnostic_rendered_reason, +} +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } +import gunbc.bmc_model { BmcWorld, BmcPowerOff, bmc_world } +import gunbc.filesystem_model { ModeledFilesystem, ModeledFile } +import gunbc.process_environment_model { ModeledVariable } +import gunbc.remote_host_model { ModeledRemoteHost, ModeledRemoteFile } +import gunbc.wall_clock_model { ModeledWallClock } +import gunbc.megarac_media_model { MegaRacMediaWorld, MegaRacShare, MegaRacImage, megarac_cleared_cd } +import gunbc.machine_intake_mtcollins1_boot_dry_realization { + MtCollins1BootWorld, ModeledSshAgent, ModeledWorker, ModeledHostConsole, TimedConsoleLine, + mtcollins1_boot_dry_realization, +} +import gunbc.machine_intake_mtcollins1_boot_run { + MtCollins1BootAttempt, mtcollins1_boot_wet_on_srv1, mtcollins1_boot_exit_reason, + ActuationNotPoweredOn, ActuationPoweredOn, ActuationCensusEnded, +} +import gunbc.machine_intake_mtcollins1_boot_authorization { mtcollins1_boot_medium, mtcollins1_boot_medium_image_name, MtCollins1CensusMedium } +import gunbc.machine_intake_mtcollins1_census_image { mtcollins1_census_image, Mtcollins1CensusImageDerived } +import gunbc.machine_intake_mtcollins1_census_medium_readback { mtcollins1_census_record_path, mtcollins1_census_medium_served_path } +import gunbc.durable_exclusive_hold_file_store { file_hold_acquire } +import gunbc.machine_intake_mtcollins1_maintenance_hold { boot_run_owner, unit_hold_store_root, mtcollins1_unit_hold_key } + +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +// THE mtcollins1 BOOT ACCEPTANCE MATRIX (gunbc#12423, operator ruling 2026-09-27). Every case runs THE +// REAL ENTRY -- gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1, unchanged -- over +// a dry realization of its whole effect demand (gunbc.machine_intake_mtcollins1_boot_dry_realization): +// the one BMC model, its virtual-media subsystem, the worker's filesystem, processes, environment and +// clocks, the SSH agent and srv2. Nothing in the orchestrator is replaced. Each case asserts the ROUTE +// the dispatcher logged -- which effects, in what order, under which hold -- and the typed outcome. +// +// EVIDENCE BOUNDARY, for every case here: the deepest real layer is the orchestration and every +// production decoder above the transport observation; the first substituted layer is the transport +// observation. None of these cases is evidence about what the physical controller, the ipmitool or +// curl clients, the SOL collector process or the host firmware do -- those are the companion transport +// controls and live acceptance -- and every modeled answer is grounded where the corpus retains one +// (extdeps.bmc.ipmitool_observed_output, extdeps.bmc.megarac_observed_output). +data realization_identity: NonEmptyStr = "mtcollins1-boot-matrix" as NonEmptyStr + +data credential_path: String = "/run/bmc-credential" + +data sol_capture_path: String = "/run/sol.capture" + +data sol_pid_path: String = "/run/sol.pid" + +fn grant(service: String, verb: std.effect_grant.Verb) -> Grant { + Grant { verb: verb, root: NamespacePosition { tree: ServiceOpTree { service: service }, path: [] }, binding: ModeledRealization { realization: realization_identity as String }, lifecycle: LifecycleByConstruction } +} + +// The services the entry's effect demand reaches, each granted to the dry realization for reading and +// writing. An operation of any other service refuses as uncovered. +data matrix_services: List = [ + "diagnostic.ipmi.Tool", "megarac.Media", "Filesystem", "shell.Env", "sleep.Delay", + "gunbc.machine_intake.sol_hold", "shell.Exec", "ssh.Session", "Clock", "linux.Procfs", +] + +fn matrix_frame(world: MtCollins1BootWorld) -> WitnessEvaluationFrame { + WitnessEvaluationFrame { + envelope: Envelope { + frame: Frame { name: "mtcollins1-boot-matrix", kind: SharedStateFrame } + grants: concat(map(matrix_services, s => grant(service: s, verb: Read)), map(matrix_services, s => grant(service: s, verb: Write))) + } + rest_fixtures: [] + realization: Present { value: mtcollins1_boot_dry_realization(identity: realization_identity, initial: world, epoch: virtual_clock_origin()) } + } +} + +fn run_attempt(world: MtCollins1BootWorld) -> WitnessEvaluation { + evaluate_in_witness_frame(frame: matrix_frame(world: world), subject: fn(_scope) { mtcollins1_boot_wet_on_srv1() }) +} + +// ─── THE HEALTHY WORLD, and the facts it is built from ─────────────────────────────────────────── +fn selected_digest() -> String { + match mtcollins1_boot_medium { + MtCollins1CensusMedium { output_digest: d } => d as String + _ => "" + } +} + +fn healthy_srv2() -> ModeledRemoteHost { + ModeledRemoteHost { endpoint: "srv2", files: match mtcollins1_census_image { + Mtcollins1CensusImageDerived { input: input } => [ + ModeledRemoteFile { path: mtcollins1_census_record_path(input: input) as String, content: Present { value: concat(selected_digest(), "\n") }, sha256: none }, + ModeledRemoteFile { path: mtcollins1_census_medium_served_path(medium: mtcollins1_boot_medium) as String, content: Present { value: "" }, sha256: Present { value: selected_digest() } }, + ] + _ => [] + } } +} + +fn healthy_media() -> MegaRacMediaWorld { + MegaRacMediaWorld { + sessions: [], next_session: 1, + share: MegaRacShare { server: "192.168.1.188", source_path: "/srv/bmc", share_type: "nfs" }, + mount_cd: 1, cd_error_code: 0, + images: [MegaRacImage { image_name: mtcollins1_boot_medium_image_name(medium: mtcollins1_boot_medium) as String, image_index: 5 }], + cd: megarac_cleared_cd(), + ready_after: second(count: 12), + } +} + +fn worker_filesystem() -> ModeledFilesystem { + ModeledFilesystem { + directories: ["target", "/", "/run", "/proc", "/var", "/var/lib", "/var/lib/gunbc", unit_hold_store_root as String], + files: [ModeledFile { path: credential_path, content: "secret" }], + } +} + +fn world_with_console(lines: List) -> MtCollins1BootWorld { + MtCollins1BootWorld { + bmc: bmc_world(power: BmcPowerOff), + fs: worker_filesystem(), + environment: [ + ModeledVariable { name: "GUNBC_HOST_RESET_BMC_CREDENTIAL_FILE", value: credential_path }, + ModeledVariable { name: "GUNBC_MTCOLLINS1_SOL_CAPTURE", value: sol_capture_path }, + ModeledVariable { name: "GUNBC_MTCOLLINS1_SOL_PID_FILE", value: sol_pid_path }, + ModeledVariable { name: "GITHUB_RUN_ID", value: "4242" }, + ModeledVariable { name: "SSH_AUTH_SOCK", value: "/run/ssh-agent.sock" }, + ModeledVariable { name: "GITHUB_SHA", value: "0123456789abcdef0123456789abcdef01234567" }, + ], + agent: ModeledSshAgent { socket: "/run/ssh-agent.sock", holds_fleet_key: true }, + remote_hosts: [healthy_srv2()], + clock: ModeledWallClock { unix_at_origin: 1790000000, step_seconds: 0 }, + worker: ModeledWorker { next_pid: 4000, processes: [], uptime_at_origin: 86400 }, + console: ModeledHostConsole { lines: lines, emitted: 0, boot: none }, + media: healthy_media(), + } +} + +// A census image that boots one socket and completes: the capture test.claim.machine_intake.mtcollins1_boot_run_witness_test +// `clean` shows reaching TerminalAccepted, printed a minute into the boot and closed at four minutes. +data one_socket_census_capture: String = "=====GUNBC-HOST-CAPTURE mtcollins1 attempt=1 begin 2026-09-18T00:00:00Z=====\n----SECTION boot-media----\n----ARGV boot-media: ls -l /dev/disk/by-label; blkid\nLABEL=\"GUNBC_MTC1_CENSUS_2404_3\"\n----EXIT boot-media: 0\n----SECTION nproc----\n----ARGV nproc: nproc\n80\n----EXIT nproc: 0\n----SECTION numa----\n----ARGV numa: cat /sys/devices/system/node/online; numactl -H\n0\navailable: 1 nodes (0)\nnode 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79\nnode 0 size: 255937 MB\nnode 0 free: 250112 MB\n----EXIT numa: 0\n----SECTION edac-before----\n----ARGV edac-before: (counters)\n== /sys/devices/system/edac/mc/mc0/ce_count == 0\n== /sys/devices/system/edac/mc/mc0/ue_count == 0\n----EXIT edac-before: 0\n----SECTION workload----\n----ARGV workload: (the rendered workload command)\nworkload-bytes=4294967296\nworkload-shm-avail-bytes=8373932032\nworkload-mem-available-bytes=15032385536\nworkload-preflight=fits\nworkload-write-rc=0\nworkload-digest-stable=yes\n----EXIT workload: 0\n----SECTION edac-after----\n----ARGV edac-after: (counters)\n== /sys/devices/system/edac/mc/mc0/ce_count == 0\n== /sys/devices/system/edac/mc/mc0/ue_count == 0\n----EXIT edac-after: 0\n=====GUNBC-HOST-CAPTURE mtcollins1 attempt=1 end 2026-09-18T00:04:00Z=====" + +fn console_of(capture: String, begin_after: Int, end_after: Int) -> List { + map(filter(split(s: capture, delimiter: "\n"), l => l != ""), l => TimedConsoleLine { + after: second(count: if starts_with(s: l, prefix: "=====GUNBC-HOST-CAPTURE mtcollins1 attempt=1 end") { end_after } else { begin_after }), + text: l, + }) +} + +// ─── ROUTE READING ───────────────────────────────────────────────────────────────────────────── +fn operation_name(r: DispatchRecord) -> String { + join([r.invocation.at.service, ".", r.invocation.at.operation], "") +} + +fn record_path(r: DispatchRecord) -> String { + fold(r.invocation.bindings, init: "", f: fn(acc, b) { + if acc != "" { acc } else if b.name == "path" { match b.value { InputText { text: t } => t InputTextList { items: _ } => "" } } else { "" } + }) +} + +fn is_hold_write(r: DispatchRecord) -> Bool { + r.invocation.at.service == "Filesystem" && starts_with(s: r.invocation.at.operation, prefix: "WriteCreateNew") + && starts_with(s: record_path(r: r), prefix: unit_hold_store_root as String) +} + +// The effects that change the controller or start a process against it. +data controller_writes: List = [ + "diagnostic.ipmi.Tool.ChassisPowerControl", "diagnostic.ipmi.Tool.ChassisBootDevWithOptions", + "megarac.Media.StartMedia", "megarac.Media.StopMedia", "diagnostic.ipmi.Tool.SolDeactivate", + "gunbc.machine_intake.sol_hold.ActivateHeld", +] + +fn is_controller_write(r: DispatchRecord) -> Bool { + !all(controller_writes, w => w != operation_name(r: r)) +} + +fn ordinals_where(route: List, keep: fn(DispatchRecord) -> Bool) -> List { + map(filter(route, r => keep(r)), r => r.ordinal) +} + +fn count_named(route: List, name: String) -> Int { + count(filter(route, r => operation_name(r: r) == name)) +} + +// EVERY CONTROLLER WRITE HAPPENS UNDER THE UNIT HOLD: after the hold's first commit and before its +// last (the release). The hold writes are the only WriteCreateNew calls under the hold root. +fn controller_writes_are_under_the_hold(route: List) -> Bool { + let holds = ordinals_where(route: route, keep: is_hold_write) + let writes = ordinals_where(route: route, keep: is_controller_write) + match holds.first() { + Absent => count(writes) == 0 + Present { value: acquired } => match holds.last() { + Absent => false + Present { value: released } => count(holds) >= 2 && all(writes, o => o > acquired && o < released) + } + } +} + +fn first_ordinal(route: List, name: String) -> Int { + match ordinals_where(route: route, keep: fn(r) { operation_name(r: r) == name }).first() { Present { value: o } => o Absent => 0 - 1 } +} + +fn outcome_reason(a: MtCollins1BootAttempt) -> String { + mtcollins1_boot_exit_reason(outcome: a.outcome) +} + +fn census_ended(a: MtCollins1BootAttempt) -> Bool { + match a.stage { + ActuationCensusEnded { selection: _, smpro_power_on: _, smpro_census: _ } => true + _ => false + } +} + +// ─── CASES ─────────────────────────────────────────────────────────────────────────────────────── + +// THE HOST NEVER PRINTS A CENSUS. The attempt goes all the way through -- media converged and ready, +// cdrom override set, power on -- and refuses at the terminal deadline with exactly the outcome the +// real attempt of 2026-09-27 recorded (run 36335369059). Route: the collector is started before any +// other controller write; the media start precedes the boot override, which precedes the one power +// action; every controller write is under the hold; the collector is torn down last. +test fn a_host_that_never_prints_a_census_refuses_at_the_deadline() -> Bool { + match run_attempt(world: world_with_console(lines: [])) { + WitnessReturned { value, route } => + string_contains(s: outcome_reason(a: value), pattern: "deadline reached without a host-capture terminal") + && count_named(route: route, name: "diagnostic.ipmi.Tool.ChassisPowerControl") == 1 + && count_named(route: route, name: "gunbc.machine_intake.sol_hold.ActivateHeld") == 1 + && first_ordinal(route: route, name: "gunbc.machine_intake.sol_hold.ActivateHeld") < first_ordinal(route: route, name: "megarac.Media.StartMedia") + && first_ordinal(route: route, name: "megarac.Media.StartMedia") < first_ordinal(route: route, name: "diagnostic.ipmi.Tool.ChassisBootDevWithOptions") + && first_ordinal(route: route, name: "diagnostic.ipmi.Tool.ChassisBootDevWithOptions") < first_ordinal(route: route, name: "diagnostic.ipmi.Tool.ChassisPowerControl") + && controller_writes_are_under_the_hold(route: route) + _ => false + } +} + +// A HEALTHY HOST CANNOT CURRENTLY REPORT SUCCESS. PINNED DEFECT, found by this matrix: the census +// closes and is accepted (the stage is ActuationCensusEnded), and the attempt still refuses at the SOL +// teardown, because after `sol deactivate` the collector exits and nothing removes its pid file, which +// the re-observation then reads as stale (gunbc.machine_intake_mtcollins1_boot_run +// mtcollins1_boot_release_sol). This case asserts TODAY'S behaviour so the defect stays visible; when +// the teardown is repaired it must FLIP to ExitSuccess, and that flip is the repair landing. +test fn pinned_a_healthy_census_is_refused_at_the_sol_teardown() -> Bool { + match run_attempt(world: world_with_console(lines: console_of(capture: one_socket_census_capture, begin_after: 60, end_after: 240))) { + WitnessReturned { value, route } => + census_ended(a: value) + && outcome_reason(a: value) == "refused: mtcollins1 boot: SOL pid file stale after deactivate" + && count_named(route: route, name: "diagnostic.ipmi.Tool.SolDeactivate") == 1 + && controller_writes_are_under_the_hold(route: route) + _ => false + } +} + +// THE SAME UNIT HELD BY ANOTHER RUN. Before this attempt starts, a different run's boot holds the +// unit (acquired through the production file_hold_acquire, into the same store). This attempt is +// refused at the hold with the controller untouched: no controller write of any kind appears in its +// route, and it never starts a SOL collector. +test fn a_second_run_for_a_held_unit_writes_nothing_to_the_controller() -> Bool { + let prior = evaluate_in_witness_frame( + frame: matrix_frame(world: world_with_console(lines: [])), + subject: fn(_scope) { file_hold_acquire(root: unit_hold_store_root, slot_key: mtcollins1_unit_hold_key, requested_owner: boot_run_owner(run_id: "4141" as NonEmptyStr)) } + ) + match prior { + WitnessReturned { state } => match state { + Absent => false + Present { value: held } => match run_attempt(world: held) { + WitnessReturned { value, route } => + starts_with(s: outcome_reason(a: value), prefix: "refused:") + && count(filter(route, r => is_controller_write(r: r))) == 0 + && count_named(route: route, name: "gunbc.machine_intake.sol_hold.ActivateHeld") == 0 + _ => false + } + } + _ => false + } +} From cb2f3b1dc7d92c0ef722e9ae6c734d687baaf57c Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 28 Sep 2026 11:44:04 +0000 Subject: [PATCH 06/38] Matrix: media, observation, resource and interruption cases asserting each case's own cause; media withdrawal and SOL-drop events Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/gunbc/bmc_model.dag | 4 + .../mtcollins1_boot_dry_realization.dag | 15 +- dag/gunbc/megarac_media_model.dag | 33 ++- ...mtcollins1_boot_acceptance_matrix_test.dag | 251 +++++++++++++++++- 4 files changed, 294 insertions(+), 9 deletions(-) diff --git a/dag/gunbc/bmc_model.dag b/dag/gunbc/bmc_model.dag index 1d52544a61a..0d36446aa05 100644 --- a/dag/gunbc/bmc_model.dag +++ b/dag/gunbc/bmc_model.dag @@ -26,11 +26,14 @@ type BmcBootOverride // An event the world undergoes on its own, independent of any request. AC loss is grounded in the // Mt. Collins SEL (run 36335369059, mtcollins1-boot-diagnostics.txt sha256 // eda19cde2e60d90d3d04946273e615eb5bf774496b9907e03d3bc15dbedc2c1c: `Power Unit ChassisPwrStatus | AC lost`). +// A dropped SOL session is the controller ending the serial session on its own, which the 2026-09-27 +// incident (gunbc#12423) recorded as a loss mid-boot while the management interface kept answering. // A power restore is the second half of a power cycle: the controller drops power, and after the // scenario's off interval brings it back, so a read taken inside the interval sees it off. type BmcEvent = BmcAcPowerLost | BmcPowerRestored + | BmcSolSessionDropped type BmcScheduledEvent { at: Second @@ -152,6 +155,7 @@ fn bmc_apply_event(world: BmcWorld, event: BmcEvent, at: Second) -> BmcWorld { match event { BmcAcPowerLost => bmc_with_power(world: world, power: BmcPowerOff) BmcPowerRestored => bmc_host_boots(world: world, now: at) + BmcSolSessionDropped => bmc_with_sol_session(world: world, open: false) } } diff --git a/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag b/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag index bcaeb2e703f..1fbd1389723 100644 --- a/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag +++ b/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag @@ -231,8 +231,21 @@ fn concat_text(lines: List) -> String { join(map(lines, l => concat(l.text, "\n")), "") } +// A COLLECTOR LIVES ONLY WHILE ITS SESSION DOES. When the controller's SOL session ends -- on its own +// or by a deactivate -- the `ipmitool sol activate` client loses its session and exits, so its /proc +// entry disappears; its pid file stays, since nothing in the realization removes it. +fn collectors_follow_session(w: MtCollins1BootWorld) -> MtCollins1BootWorld { + if w.bmc.sol_session_open { w } else { + let fs = fold(live_collectors(w: w), init: w.fs, f: fn(acc, p) { + ModeledFilesystem { directories: acc.directories, files: filter(acc.files, f => f.path != proc_cmdline_path(pid: p.pid)) } + }) + with_worker(w: with_fs(w: w, fs: fs), worker: ModeledWorker { next_pid: w.worker.next_pid, processes: map(w.worker.processes, p => ModeledProcess { pid: p.pid, cmdline: p.cmdline, capture_path: p.capture_path, alive: false }), uptime_at_origin: w.worker.uptime_at_origin }) + } +} + fn boot_advance(w: MtCollins1BootWorld, now: Second) -> MtCollins1BootWorld { - console_advance(w: with_media(w: with_bmc(w: w, bmc: bmc_advance(world: w.bmc, now: now)), media: megarac_media_advance(world: w.media, now: now)), now: now) + let stepped = with_media(w: with_bmc(w: w, bmc: bmc_advance(world: w.bmc, now: now)), media: megarac_media_advance(world: w.media, now: now)) + console_advance(w: collectors_follow_session(w: stepped), now: now) } data sol_hold_activate_held_operation: OperationRef = OperationRef { diff --git a/dag/gunbc/megarac_media_model.dag b/dag/gunbc/megarac_media_model.dag index 9a777506c8f..3fb72c3a749 100644 --- a/dag/gunbc/megarac_media_model.dag +++ b/dag/gunbc/megarac_media_model.dag @@ -51,6 +51,8 @@ type MegaRacMediaWorld { images: List cd: MegaRacCdRow ready_after: Second + withdraw_at: Second? + withdraw_after_ready: Second? } fn megarac_cleared_cd() -> MegaRacCdRow { @@ -66,11 +68,11 @@ fn megarac_session_by_jar(world: MegaRacMediaWorld, cookie_jar: String) -> Bool } fn megarac_with_sessions(world: MegaRacMediaWorld, sessions: List, next_session: Int) -> MegaRacMediaWorld { - MegaRacMediaWorld { sessions: sessions, next_session: next_session, share: world.share, mount_cd: world.mount_cd, cd_error_code: world.cd_error_code, images: world.images, cd: world.cd, ready_after: world.ready_after } + MegaRacMediaWorld { sessions: sessions, next_session: next_session, share: world.share, mount_cd: world.mount_cd, cd_error_code: world.cd_error_code, images: world.images, cd: world.cd, ready_after: world.ready_after, withdraw_at: world.withdraw_at, withdraw_after_ready: world.withdraw_after_ready } } fn megarac_with_cd(world: MegaRacMediaWorld, cd: MegaRacCdRow) -> MegaRacMediaWorld { - MegaRacMediaWorld { sessions: world.sessions, next_session: world.next_session, share: world.share, mount_cd: world.mount_cd, cd_error_code: world.cd_error_code, images: world.images, cd: cd, ready_after: world.ready_after } + MegaRacMediaWorld { sessions: world.sessions, next_session: world.next_session, share: world.share, mount_cd: world.mount_cd, cd_error_code: world.cd_error_code, images: world.images, cd: cd, ready_after: world.ready_after, withdraw_at: world.withdraw_at, withdraw_after_ready: world.withdraw_after_ready } } type MegaRacOpened { @@ -127,12 +129,37 @@ fn megarac_stop_media(world: MegaRacMediaWorld) -> MegaRacMediaWorld { megarac_with_cd(world: world, cd: megarac_cleared_cd()) } +// A PRESENTATION CAN BE LOST AFTER IT WAS READY: withdraw_at, when set, is the instant the controller +// drops the presented CD on its own (the NFS export going away, the firmware's retry giving up), after +// which the row reads cleared. The production code re-observes the presentation before the handoff for +// exactly this. fn megarac_media_advance(world: MegaRacMediaWorld, now: Second) -> MegaRacMediaWorld { + let readied = megarac_media_ready(world: world, now: now) + match readied.withdraw_at { + Absent => readied + Present { value: at } => + if second_count(s: at) <= second_count(s: now) { + MegaRacMediaWorld { sessions: readied.sessions, next_session: readied.next_session, share: readied.share, mount_cd: readied.mount_cd, cd_error_code: readied.cd_error_code, images: readied.images, cd: megarac_cleared_cd(), ready_after: readied.ready_after, withdraw_at: none, withdraw_after_ready: readied.withdraw_after_ready } + } else { readied } + } +} + +fn megarac_media_ready(world: MegaRacMediaWorld, now: Second) -> MegaRacMediaWorld { match world.cd.ready_at { Absent => world Present { value: at } => if second_count(s: at) <= second_count(s: now) { - megarac_with_cd(world: world, cd: MegaRacCdRow { image_name: world.cd.image_name, redirection_status: 1, media_index: world.cd.media_index, session_index: 0, ready_at: none }) + megarac_withdrawal_armed(world: megarac_with_cd(world: world, cd: MegaRacCdRow { image_name: world.cd.image_name, redirection_status: 1, media_index: world.cd.media_index, session_index: 0, ready_at: none }), ready: at) } else { world } } } + +// A scenario may ask for the presentation to be lost a fixed interval after it became ready; the +// instant is armed when readiness arrives, so the loss follows the route rather than a guessed clock. +fn megarac_withdrawal_armed(world: MegaRacMediaWorld, ready: Second) -> MegaRacMediaWorld { + match world.withdraw_after_ready { + Absent => world + Present { value: after } => + MegaRacMediaWorld { sessions: world.sessions, next_session: world.next_session, share: world.share, mount_cd: world.mount_cd, cd_error_code: world.cd_error_code, images: world.images, cd: world.cd, ready_after: world.ready_after, withdraw_at: Present { value: second(count: second_count(s: ready) + second_count(s: after)) }, withdraw_after_ready: none } + } +} diff --git a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag index 23d5c5c7db6..a87691a4ae4 100644 --- a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag +++ b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag @@ -11,15 +11,19 @@ import v2.std.witness_evaluation { evaluate_in_witness_frame, witness_diagnostic_rendered_reason, } import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } -import gunbc.bmc_model { BmcWorld, BmcPowerOff, bmc_world } +import gunbc.bmc_model { BmcWorld, BmcPowerOff, BmcScheduledEvent, BmcSolSessionDropped, bmc_world, bmc_with_sol_session, bmc_with_pending } import gunbc.filesystem_model { ModeledFilesystem, ModeledFile } import gunbc.process_environment_model { ModeledVariable } import gunbc.remote_host_model { ModeledRemoteHost, ModeledRemoteFile } import gunbc.wall_clock_model { ModeledWallClock } -import gunbc.megarac_media_model { MegaRacMediaWorld, MegaRacShare, MegaRacImage, megarac_cleared_cd } +import gunbc.megarac_media_model { MegaRacMediaWorld, MegaRacShare, MegaRacImage, MegaRacCdRow, megarac_cleared_cd } import gunbc.machine_intake_mtcollins1_boot_dry_realization { MtCollins1BootWorld, ModeledSshAgent, ModeledWorker, ModeledHostConsole, TimedConsoleLine, - mtcollins1_boot_dry_realization, + mtcollins1_boot_dry_realization, with_media, with_bmc, +} +import v2.std.operation_realization { + OperationRealization, OperationBinding, OperationCall, OperationStep, OperationObserved, OperationWorkerKilled, + DispatchWorkerKilled, } import gunbc.machine_intake_mtcollins1_boot_run { MtCollins1BootAttempt, mtcollins1_boot_wet_on_srv1, mtcollins1_boot_exit_reason, @@ -66,13 +70,17 @@ data matrix_services: List = [ ] fn matrix_frame(world: MtCollins1BootWorld) -> WitnessEvaluationFrame { + matrix_frame_over(realization: mtcollins1_boot_dry_realization(identity: realization_identity, initial: world, epoch: virtual_clock_origin())) +} + +fn matrix_frame_over(realization: OperationRealization) -> WitnessEvaluationFrame { WitnessEvaluationFrame { envelope: Envelope { frame: Frame { name: "mtcollins1-boot-matrix", kind: SharedStateFrame } grants: concat(map(matrix_services, s => grant(service: s, verb: Read)), map(matrix_services, s => grant(service: s, verb: Write))) } rest_fixtures: [] - realization: Present { value: mtcollins1_boot_dry_realization(identity: realization_identity, initial: world, epoch: virtual_clock_origin()) } + realization: Present { value: realization } } } @@ -106,6 +114,8 @@ fn healthy_media() -> MegaRacMediaWorld { images: [MegaRacImage { image_name: mtcollins1_boot_medium_image_name(medium: mtcollins1_boot_medium) as String, image_index: 5 }], cd: megarac_cleared_cd(), ready_after: second(count: 12), + withdraw_at: none, + withdraw_after_ready: none, } } @@ -264,7 +274,7 @@ test fn a_second_run_for_a_held_unit_writes_nothing_to_the_controller() -> Bool Absent => false Present { value: held } => match run_attempt(world: held) { WitnessReturned { value, route } => - starts_with(s: outcome_reason(a: value), prefix: "refused:") + string_contains(s: outcome_reason(a: value), pattern: "is held by 'mtcollins1-boot:4141'") && count(filter(route, r => is_controller_write(r: r))) == 0 && count_named(route: route, name: "gunbc.machine_intake.sol_hold.ActivateHeld") == 0 _ => false @@ -273,3 +283,234 @@ test fn a_second_run_for_a_held_unit_writes_nothing_to_the_controller() -> Bool _ => false } } + +// ─── MEDIA VARIATION ─────────────────────────────────────────────────────────────────────────────── +fn media_varied(media: MegaRacMediaWorld) -> MtCollins1BootWorld { + with_media(w: world_with_console(lines: []), media: media) +} + +fn media_with(images: List, share: MegaRacShare, cd: MegaRacCdRow, ready_after: Int, withdraw_after_ready: Second?) -> MegaRacMediaWorld { + MegaRacMediaWorld { + sessions: [], next_session: 1, share: share, mount_cd: 1, cd_error_code: 0, images: images, cd: cd, + ready_after: second(count: ready_after), withdraw_at: none, withdraw_after_ready: withdraw_after_ready, + } +} + +fn no_withdrawal() -> Second? { + none +} + +fn desired_image() -> String { + mtcollins1_boot_medium_image_name(medium: mtcollins1_boot_medium) as String +} + +data real_share: MegaRacShare = MegaRacShare { server: "192.168.1.188", source_path: "/srv/bmc", share_type: "nfs" } + +fn reached_no_power_action(route: List) -> Bool { + count_named(route: route, name: "diagnostic.ipmi.Tool.ChassisPowerControl") == 0 + && count_named(route: route, name: "diagnostic.ipmi.Tool.ChassisBootDevWithOptions") == 0 +} + +// THE IMAGE IS ALREADY PRESENTED AND READY. Convergence has nothing to do: no stop, no start, and the +// attempt proceeds to the boot exactly as from a fresh attach. +test fn an_already_presented_image_is_not_attached_again() -> Bool { + let presented = MegaRacCdRow { image_name: desired_image(), redirection_status: 1, media_index: 0, session_index: 0, ready_at: none } + match run_attempt(world: media_varied(media: media_with(images: [MegaRacImage { image_name: desired_image(), image_index: 5 }], share: real_share, cd: presented, ready_after: 12, withdraw_after_ready: no_withdrawal()))) { + WitnessReturned { value, route } => + count_named(route: route, name: "megarac.Media.StartMedia") == 0 + && count_named(route: route, name: "megarac.Media.StopMedia") == 0 + && count_named(route: route, name: "diagnostic.ipmi.Tool.ChassisPowerControl") == 1 + && controller_writes_are_under_the_hold(route: route) + _ => false + } +} + +// READINESS SLOWER THAN THE WAIT. The controller keeps the row connecting past the readiness budget; +// the attempt refuses without setting the boot override or touching power. +test fn media_that_never_becomes_ready_refuses_before_any_power_action() -> Bool { + match run_attempt(world: media_varied(media: media_with(images: [MegaRacImage { image_name: desired_image(), image_index: 5 }], share: real_share, cd: megarac_cleared_cd(), ready_after: 600, withdraw_after_ready: no_withdrawal()))) { + WitnessReturned { value, route } => + string_contains(s: outcome_reason(a: value), pattern: "did not reach Started with a bound session") + && string_contains(s: outcome_reason(a: value), pattern: "No boot-device or power handoff was made") + && count_named(route: route, name: "megarac.Media.StartMedia") == 1 + && reached_no_power_action(route: route) + _ => false + } +} + +// THE SAME FILENAME ON THE WRONG SHARE. The listing offers an image with the desired name, but the +// controller's CD share is bound to another server. It refuses before any power action; a start, if +// one is issued, must not be followed by the boot. +test fn the_right_filename_on_the_wrong_share_refuses_before_any_power_action() -> Bool { + let wrong = MegaRacShare { server: "192.168.1.99", source_path: "/srv/bmc", share_type: "nfs" } + match run_attempt(world: media_varied(media: media_with(images: [MegaRacImage { image_name: desired_image(), image_index: 5 }], share: wrong, cd: megarac_cleared_cd(), ready_after: 12, withdraw_after_ready: no_withdrawal()))) { + WitnessReturned { value, route } => + string_contains(s: outcome_reason(a: value), pattern: "remote-media share is not this unit's boot share") + && string_contains(s: outcome_reason(a: value), pattern: "observed 192.168.1.99:/srv/bmc") + && count_named(route: route, name: "megarac.Media.StartMedia") == 0 + && reached_no_power_action(route: route) + _ => false + } +} + +// THE LISTING NAMES THE IMAGE TWICE. Taking either row would let the controller's order choose which +// file is attached, so nothing is started and nothing is powered. +test fn a_listing_that_names_the_image_twice_starts_nothing() -> Bool { + let twice = [MegaRacImage { image_name: desired_image(), image_index: 5 }, MegaRacImage { image_name: desired_image(), image_index: 6 }] + match run_attempt(world: media_varied(media: media_with(images: twice, share: real_share, cd: megarac_cleared_cd(), ready_after: 12, withdraw_after_ready: no_withdrawal()))) { + WitnessReturned { value, route } => + string_contains(s: outcome_reason(a: value), pattern: "names the image more than once, so its index is ambiguous") + && count_named(route: route, name: "megarac.Media.StartMedia") == 0 + && reached_no_power_action(route: route) + _ => false + } +} + +// THE PRESENTATION IS LOST FIVE SECONDS AFTER IT BECAME READY: after the attach saw it ready and before +// the handoff. Measured window: a loss 3 to 8 seconds after readiness falls here; 1 second falls between +// two readiness looks (the attach never sees it ready), and 12 seconds or more falls after the handoff. +// The pre-handoff re-observation must see it gone and refuse without the boot override or power. +test fn a_presentation_lost_after_readiness_stops_the_handoff() -> Bool { + match run_attempt(world: media_varied(media: media_with(images: [MegaRacImage { image_name: desired_image(), image_index: 5 }], share: real_share, cd: megarac_cleared_cd(), ready_after: 12, withdraw_after_ready: Present { value: second(count: 5) }))) { + WitnessReturned { value, route } => + string_contains(s: outcome_reason(a: value), pattern: "pre-handoff recheck did not admit it") + && count_named(route: route, name: "megarac.Media.StartMedia") == 1 + && reached_no_power_action(route: route) + _ => false + } +} + +// PINNED: A PRESENTATION LOST AFTER THE HANDOFF IS NOT NAMED AS A CAUSE. Lost twenty seconds after +// readiness -- after the boot override and the power action -- the attempt goes on to the terminal +// deadline and refuses there, and the loss appears only in the recorded after-handoff look. The +// outcome does not say the medium disappeared under the boot (#12423 media: presentation withdrawal). +// When a loss after the handoff becomes a typed cause, this case flips. +test fn pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause() -> Bool { + match run_attempt(world: media_varied(media: media_with(images: [MegaRacImage { image_name: desired_image(), image_index: 5 }], share: real_share, cd: megarac_cleared_cd(), ready_after: 12, withdraw_after_ready: Present { value: second(count: 20) }))) { + WitnessReturned { value, route } => + string_contains(s: outcome_reason(a: value), pattern: "deadline reached without a host-capture terminal") + && count_named(route: route, name: "diagnostic.ipmi.Tool.ChassisPowerControl") == 1 + _ => false + } +} + +// ─── OBSERVATION FAILURES ───────────────────────────────────────────────────────────────────────── + +// ANOTHER CLIENT ALREADY HOLDS THE SOL SESSION. The collector starts and exits at once, so it is not +// held after the settle; the attempt refuses before any other controller write. +test fn a_sol_session_held_elsewhere_refuses_before_the_attach() -> Bool { + let w = world_with_console(lines: []) + match run_attempt(world: with_bmc(w: w, bmc: bmc_with_sol_session(world: w.bmc, open: true))) { + WitnessReturned { value, route } => + string_contains(s: outcome_reason(a: value), pattern: "SOL collector is not held after acquire") + && count_named(route: route, name: "megarac.Media.StartMedia") == 0 + && reached_no_power_action(route: route) + _ => false + } +} + +fn power_on_at(route: List) -> Int { + match filter(route, r => operation_name(r: r) == "diagnostic.ipmi.Tool.ChassisPowerControl").first() { + Present { value: r } => std.measure.second_count(s: r.completed_at) as Int + Absent => 0 - 1 + } +} + +// SOL IS LOST THIRTY SECONDS AFTER POWER-ON WHILE MANAGEMENT KEEPS ANSWERING, and the census would have +// printed at sixty. The drop is scheduled from the baseline route's own power-on instant. PINNED: the +// attempt learns of the loss only at the terminal deadline -- the capture stops growing and the watch +// polls to its bound -- so the first loss is not reported before the deadline (#12423 asks that it is). +// When loss detection lands this case must flip to an early, typed refusal. +test fn pinned_a_sol_loss_mid_boot_is_reported_only_at_the_deadline() -> Bool { + let console = console_of(capture: one_socket_census_capture, begin_after: 60, end_after: 240) + match run_attempt(world: world_with_console(lines: console)) { + WitnessReturned { route: baseline } => { + let drop_at = power_on_at(route: baseline) + 30 + let w = world_with_console(lines: console) + match std.checked_arithmetic.checked_int_to_nat(n: drop_at) { + Absent => false + Present { value: drop_count } => { + let dropping = with_bmc(w: w, bmc: bmc_with_pending(world: w.bmc, pending: [BmcScheduledEvent { at: second(count: drop_count), event: BmcSolSessionDropped }], fired: [])) + match run_attempt(world: dropping) { + WitnessReturned { value, route } => + drop_at > 30 + && string_contains(s: outcome_reason(a: value), pattern: "deadline reached without a host-capture terminal") + && count_named(route: route, name: "diagnostic.ipmi.Tool.ChassisPowerControl") == 1 + _ => false + } + } + } + } + _ => false + } +} + +// ─── RESOURCE VARIATION ─────────────────────────────────────────────────────────────────────────── + +// TWO SOCKETS ANSWER. The census reports 160 CPUs over two NUMA nodes; the declared expectation for +// this unit is one socket and 80 CPUs, so the terminal verdict refuses on topology and reports what it +// observed -- the thread count and the per-node placement -- rather than a transport failure. The +// refused terminal is reported at the powered-on stage, not as a census that ended. +data two_socket_census_capture: String = "=====GUNBC-HOST-CAPTURE mtcollins1 attempt=1 begin 2026-09-18T00:00:00Z=====\n----SECTION boot-media----\n----ARGV boot-media: ls -l /dev/disk/by-label; blkid\nLABEL=\"GUNBC_MTC1_CENSUS_2404_3\"\n----EXIT boot-media: 0\n----SECTION nproc----\n----ARGV nproc: nproc\n160\n----EXIT nproc: 0\n----SECTION numa----\n----ARGV numa: cat /sys/devices/system/node/online; numactl -H\n0-1\navailable: 2 nodes (0-1)\nnode 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79\nnode 1 cpus: 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159\n----EXIT numa: 0\n----SECTION edac-before----\n----ARGV edac-before: (counters)\n== /sys/devices/system/edac/mc/mc0/ce_count == 0\n== /sys/devices/system/edac/mc/mc0/ue_count == 0\n----EXIT edac-before: 0\n----SECTION workload----\n----ARGV workload: (the rendered workload command)\nworkload-bytes=4294967296\nworkload-shm-avail-bytes=8373932032\nworkload-mem-available-bytes=15032385536\nworkload-preflight=fits\nworkload-write-rc=0\nworkload-digest-stable=yes\n----EXIT workload: 0\n----SECTION edac-after----\n----ARGV edac-after: (counters)\n== /sys/devices/system/edac/mc/mc0/ce_count == 0\n== /sys/devices/system/edac/mc/mc0/ue_count == 0\n----EXIT edac-after: 0\n=====GUNBC-HOST-CAPTURE mtcollins1 attempt=1 end 2026-09-18T00:04:00Z=====" + +test fn a_two_socket_answer_is_a_truthful_topology_refusal() -> Bool { + match run_attempt(world: world_with_console(lines: console_of(capture: two_socket_census_capture, begin_after: 60, end_after: 240))) { + WitnessReturned { value, route } => + !census_ended(a: value) + && string_contains(s: outcome_reason(a: value), pattern: "nproc observed 160, qualification expects 80") + && string_contains(s: outcome_reason(a: value), pattern: "node 0=80, node 1=80") + && count_named(route: route, name: "diagnostic.ipmi.Tool.ChassisPowerControl") == 1 + _ => false + } +} + +// ─── INTERRUPTION ───────────────────────────────────────────────────────────────────────────────── + +// THE WORKER DIES RIGHT AFTER THE START-MEDIA WRITE COMMITS, before its reply. The realization is the +// matrix's own with that one handler wrapped: the controller takes the start, and the worker is gone. +fn killed_after_start_media(realization: OperationRealization) -> OperationRealization { + OperationRealization { + identity: realization.identity, initial: realization.initial, epoch: realization.epoch, advance: realization.advance, + bindings: map(realization.bindings, b => if b.at.operation == "StartMedia" { + OperationBinding { at: b.at, handler: fn(state, call) { + let inner = b.handler + match inner(state, call) { + OperationObserved { observation: _, state: after, elapsed: _ } => OperationWorkerKilled { committed: true, state: after } + other => other + } + } } + } else { b }), + } +} + +fn as_another_run(w: MtCollins1BootWorld, run_id: String) -> MtCollins1BootWorld { + let env = map(w.environment, v => if v.name == "GITHUB_RUN_ID" { ModeledVariable { name: v.name, value: run_id } } else { v }) + MtCollins1BootWorld { bmc: w.bmc, fs: w.fs, environment: env, agent: w.agent, remote_hosts: w.remote_hosts, clock: w.clock, worker: w.worker, console: w.console, media: w.media } +} + +// PINNED: THE NEXT ATTEMPT IS LOCKED OUT BY THE DEAD RUN'S HOLD. The interrupted run never released the +// unit, so a new run finds it held by the dead one and refuses with the controller untouched -- safe, +// but it cannot reconcile: nothing retires a hold whose owner is gone, so every later attempt refuses +// until an operator intervenes (#12423: interrupted operations must be reconciled without undocumented +// preparation). When dead-owner reconciliation lands this case flips. +test fn pinned_an_interrupted_attempt_locks_out_the_next_one() -> Bool { + let realization = killed_after_start_media(realization: mtcollins1_boot_dry_realization(identity: realization_identity, initial: world_with_console(lines: []), epoch: virtual_clock_origin())) + match evaluate_in_witness_frame(frame: matrix_frame_over(realization: realization), subject: fn(_scope) { mtcollins1_boot_wet_on_srv1() }) { + WitnessInterrupted { at, state, now: interrupted_clock } => { + let next = as_another_run(w: state, run_id: "4243") + let resumed = evaluate_in_witness_frame( + frame: matrix_frame_over(realization: mtcollins1_boot_dry_realization(identity: realization_identity, initial: next, epoch: interrupted_clock)), + subject: fn(_scope) { mtcollins1_boot_wet_on_srv1() } + ) + at.invocation.at.operation == "StartMedia" + && match at.outcome { DispatchWorkerKilled { committed: c } => c _ => false } + && match resumed { + WitnessReturned { value, route } => + string_contains(s: outcome_reason(a: value), pattern: "is held by 'mtcollins1-boot:4242'") + && count(filter(route, r => is_controller_write(r: r))) == 0 + _ => false + } + } + _ => false + } +} From 1574a97ef3b911942ff9023af02b799f3c3bb12c Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 28 Sep 2026 12:03:23 +0000 Subject: [PATCH 07/38] Floor: rename the covering-grant binder and the matrix grant helper (UnimportedBareProvider on 'grant') Co-Authored-By: Claude Opus 5.5 (1M context) --- .../machine_intake/mtcollins1_boot_acceptance_matrix_test.dag | 4 ++-- src/v2/std/operation_realization.dag | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag index a87691a4ae4..72881769836 100644 --- a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag +++ b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag @@ -58,7 +58,7 @@ data sol_capture_path: String = "/run/sol.capture" data sol_pid_path: String = "/run/sol.pid" -fn grant(service: String, verb: std.effect_grant.Verb) -> Grant { +fn matrix_grant(service: String, verb: std.effect_grant.Verb) -> Grant { Grant { verb: verb, root: NamespacePosition { tree: ServiceOpTree { service: service }, path: [] }, binding: ModeledRealization { realization: realization_identity as String }, lifecycle: LifecycleByConstruction } } @@ -77,7 +77,7 @@ fn matrix_frame_over(realization: OperationRealization) -> WitnessEvaluationFrame { envelope: Envelope { frame: Frame { name: "mtcollins1-boot-matrix", kind: SharedStateFrame } - grants: concat(map(matrix_services, s => grant(service: s, verb: Read)), map(matrix_services, s => grant(service: s, verb: Write))) + grants: concat(map(matrix_services, s => matrix_grant(service: s, verb: Read)), map(matrix_services, s => matrix_grant(service: s, verb: Write))) } rest_fixtures: [] realization: Present { value: realization } diff --git a/src/v2/std/operation_realization.dag b/src/v2/std/operation_realization.dag index 57ea3d906f9..d3b5b37f8b9 100644 --- a/src/v2/std/operation_realization.dag +++ b/src/v2/std/operation_realization.dag @@ -158,7 +158,7 @@ fn operation_handler_selection( let at = invocation.at match covering_grant(env: env, verb: operation_verb(readonly: readonly), target: operation_position(at: at)) { NoCoveringGrant { verb: _, target: _, frame: _ } => OperationHandlerRefused { cause: OperationUncovered { at: at } } - CoveredBy { grant } => match grant.binding { + CoveredBy { grant: covering } => match covering.binding { ModeledRealization { realization: named } => if named != (realization.identity as String) { OperationHandlerRefused { cause: OperationRealizationMismatch { at: at, named: named, active: realization.identity } } From 41b5730cb1c86b023e186d0fa97fd86bd0d79201 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 28 Sep 2026 13:16:42 +0000 Subject: [PATCH 08/38] Matrix floor pricing (cost-debt admissions + declared eval-step drop); wall clock on std.measure carriers; seed-growth row covers the file arm - Per eager-owl-205's ruling (msg_83af891b): the eleven matrix cases over the new-witness eval-step budget are typed cost-debt admissions (floor_cost_debt_admission mtcollins1_boot_matrix_typed_admissions, reason not reading) and members of a declared 4b(3) drop (gunbc.rung_drop mtcollins1_boot_matrix_new_witness_eval_step_cost, list floor_eval_step_cost_drop_boot_matrix_rows) whose restoration trigger is the natively emitted evaluation frame on the merge path. The two cases under the per-subject line are neither (a row there is stale). - Review 72230: ModeledWallClock carries EpochSecs and a signed std.measure SecondDisplacement (new, beside CelsiusDelta/ArcsecondDisplacement). - Seed-growth row names file_result_of_observation and the file/argv boundary. Co-Authored-By: Claude Opus 5.5 (1M context) --- ...eled_operation_realization_seed_growth.dag | 3 +- ...boot_matrix_new_witness_eval_step_cost.dag | 47 +++++++++++++++ dag/gunbc/rung_drop/roster.dag | 2 + dag/gunbc/wall_clock_model.dag | 10 ++-- dag/std/measure.dag | 13 +++++ .../claim/boot_world_models_witness_test.dag | 2 +- ...mtcollins1_boot_acceptance_matrix_test.dag | 2 +- src/v2/workflow/floor_cost_debt_admission.dag | 54 ++++++++++++++++- src/v2/workflow/floor_eval_step_cost_drop.dag | 58 ++++++++++++++++++- 9 files changed, 180 insertions(+), 11 deletions(-) create mode 100644 dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag diff --git a/dag/gunbc/modeled_operation_realization_seed_growth.dag b/dag/gunbc/modeled_operation_realization_seed_growth.dag index 4fbc25de101..eac23b41049 100644 --- a/dag/gunbc/modeled_operation_realization_seed_growth.dag +++ b/dag/gunbc/modeled_operation_realization_seed_growth.dag @@ -51,9 +51,10 @@ data modeled_operation_realization_seed_growth_justification: SeedGrowthJustific DeclarationRef { module_path: "v1_compiler.v1_interpreter", decl_name: "dispatch_modeled_operation", field: WholeDeclaration }, DeclarationRef { module_path: "v1_compiler.v1_interpreter", decl_name: "shell_result_of_observation", field: WholeDeclaration }, DeclarationRef { module_path: "v1_compiler.v1_interpreter", decl_name: "shell_result_projection", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.v1_interpreter", decl_name: "file_result_of_observation", field: WholeDeclaration }, ], reason: "The seed interpreter is the only evaluator that runs the boot orchestrator, and the acceptance matrix (#12423, operator ruling 2026-09-27) must execute that orchestrator against controlled device histories. These items are the seed realization of a .dag contract: selection is v2.std.operation_realization operation_handler_selection over std.effect_grant covering_grant, admission is modeled_realization_admitted_in and operation_realization_duplicate, the virtual clock is virtual_clock_origin and virtual_clock_after over std.measure Second, and every scenario transition is gunbc.bmc_model. The Rust holds a state value, a clock value and the dispatch log, and dispatches; shell_result_projection is the existing wet shell projection extracted so the modeled observation reaches the same decoder path.", owning_dissolution_lane: "v1-materialization-kernel" as RoadmapNodeId, trigger: "Witnesses emit to native code and the emitted runtime realizes the evaluation frame and its modeled operation realization; these items then delete with the WITNESS_EVALUATION_FRAMES stack while test.claim.operation_realization_witness_test stays green without them. That witness is the deletion's regression control.", - current_boundary: "Shell transport observations only: a REST or file operation under a modeled frame refuses as a harness fault. Admitted only under Hermetic execution. No device logic in Rust. Nested frames inside an active realization are refused.", + current_boundary: "Shell and file transport observations (the file arm added by gunbc#12533 feeds the existing map_file_outputs; bound_operation_invocation_value records a ProcessArgvExpansion input as the words push_process_argv_expansion expands for a real spawn): a REST operation under a modeled frame refuses as a harness fault. Admitted only under Hermetic execution. No device logic in Rust. Nested frames inside an active realization are refused.", } diff --git a/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag b/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag new file mode 100644 index 00000000000..b7f32f5e384 --- /dev/null +++ b/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag @@ -0,0 +1,47 @@ +module gunbc.rung_drop.mtcollins1_boot_matrix_new_witness_eval_step_cost + +import std.types { String, List, NonEmptyStr } +import gunbc.rung_drop { RungDrop, Standing, TypedDeclaration, ReplacementStaged } +import gunbc.guarantee_rung { Mitigatable, MechanicallyPreventable } +import v2.workflow.floor_eval_step_cost_drop { floor_eval_step_cost_drop_boot_matrix_rows } + +// DECLARED 4b(3) DROP (gunbc#12533, 2026-09-28; the cost-debt ruling of eager-owl-205 on the #12423 +// matrix, msg_83af891b). The DECLARATION lives here; the POPULATION's authority is +// `v2.workflow.floor_eval_step_cost_drop` `floor_eval_step_cost_drop_boot_matrix_rows`. +// +// WHAT IS LOST IS ONE WALL, AND THE CLAIMS ARE UNTOUCHED. The eleven identities are planned, executed +// and measured on every pull request that edits them; a semantic red and a wall-clock crossing still +// block. Only the eval-step overrun is reported as `EvalStepsOverBudgetUnderDeclaredDrop` rather than +// refusing. The budget is not raised, and each identity is also a typed cost-debt admission +// (`v2.workflow.floor_cost_debt_admission` `mtcollins1_boot_matrix_typed_admissions`), which keeps it +// executing while its CPU is reported. +// +// WHY THE OVERRUN IS THE SUBJECT'S AND NOT DUPLICATED WORK. Each case asserts the ROUTE the real boot +// entry takes -- which effects, in which order, under which hold -- and its typed outcome, which #12423 +// requires be executed through the real production boundary. The decision folds each case reaches are +// already witnessed at their narrower interfaces with supplied values (the media convergence folds, +// the power selection, the host-capture verdict, the hold store); what only the entry can establish is +// that it reaches them with the state its own earlier steps built. The work computed identically +// across cases -- world construction and the route prefix up to the hold -- is named in gunbc#12533 as +// a follow-up to hoist; hoisting it lowers the bill and does not remove the subject. +data mtcollins1_boot_matrix_new_witness_eval_step_cost_population: List = floor_eval_step_cost_drop_boot_matrix_rows |> map(m => concat(concat(m.identity, ": over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by "), m.measured_by)) + +data mtcollins1_boot_matrix_new_witness_eval_step_cost: RungDrop = RungDrop { + identity: "mtcollins1_boot_matrix_new_witness_eval_step_cost" as NonEmptyStr, + + subject: "new-witness eval-step cost gate over the eleven mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered", + + declared: "2026-09-28", + + standing: Standing, + + declaration: TypedDeclaration { + previous: MechanicallyPreventable, + temporary: Mitigatable, + reason: ReplacementStaged { + replacement: "the evaluation frame and its modeled operation realization realized by the natively emitted runtime, so the boot entry runs as emitted code rather than in the seed interpreter", + }, + population: mtcollins1_boot_matrix_new_witness_eval_step_cost_population, + restoration_trigger: "THE CAPABILITY: witnesses emitted to native code with the emitted runtime realizing v2.std.witness_evaluation evaluate_in_witness_frame and its v2.std.operation_realization modeled realization, EXECUTING ON THE MERGE PATH as a phase of a required lane whose red blocks a merge, running these eleven identities with the real mtcollins1_boot_wet_on_srv1 entry; WHAT THAT MUST BE SUFFICIENT FOR: each case measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither.", + } +} diff --git a/dag/gunbc/rung_drop/roster.dag b/dag/gunbc/rung_drop/roster.dag index be983db411d..596af1379d7 100644 --- a/dag/gunbc/rung_drop/roster.dag +++ b/dag/gunbc/rung_drop/roster.dag @@ -91,6 +91,7 @@ import gunbc.rung_drop.live_deploy_apply_render_new_witness_eval_step_cost { liv import gunbc.rung_drop.roadmap_live_forecast_new_witness_eval_step_cost { roadmap_live_forecast_new_witness_eval_step_cost } import gunbc.rung_drop.roadmap_page_style_new_witness_eval_step_cost { roadmap_page_style_new_witness_eval_step_cost } import gunbc.rung_drop.app_attest_interpreted_crypto_new_witness_eval_step_cost { app_attest_interpreted_crypto_new_witness_eval_step_cost } +import gunbc.rung_drop.mtcollins1_boot_matrix_new_witness_eval_step_cost { mtcollins1_boot_matrix_new_witness_eval_step_cost } import gunbc.rung_drop.app_attest_verifier_new_witness_eval_step_cost { app_attest_verifier_new_witness_eval_step_cost } import gunbc.rung_drop.app_attest_verifier_enrolment_dead_band_observed_only { app_attest_verifier_enrolment_dead_band_observed_only } import gunbc.rung_drop.sha256_span_program_serialize_new_witness_eval_step_cost { sha256_span_program_serialize_new_witness_eval_step_cost } @@ -183,6 +184,7 @@ data rung_drop_roster: List = [ roadmap_live_forecast_new_witness_eval_step_cost, roadmap_page_style_new_witness_eval_step_cost, app_attest_interpreted_crypto_new_witness_eval_step_cost, + mtcollins1_boot_matrix_new_witness_eval_step_cost, app_attest_verifier_new_witness_eval_step_cost, app_attest_verifier_enrolment_dead_band_observed_only, sha256_span_program_serialize_new_witness_eval_step_cost, diff --git a/dag/gunbc/wall_clock_model.dag b/dag/gunbc/wall_clock_model.dag index 199470400bb..cec79144ca2 100644 --- a/dag/gunbc/wall_clock_model.dag +++ b/dag/gunbc/wall_clock_model.dag @@ -1,7 +1,7 @@ module gunbc.wall_clock_model -import std.types { Int, List, String } -import std.measure { Second, second, second_count } +import std.types { EpochSecs, Int, List, String } +import std.measure { Second, second, second_count, SecondDisplacement, second_displacement_count } import v2.std.operation_argv { OperationRef } import v2.std.operation_realization { OperationBinding, OperationCall, OperationStep, OperationObserved, ShellObserved, @@ -19,12 +19,12 @@ import extdeps.transports.shell { ShellProcessExited } // It answers extdeps.clock Clock.Now in `date -u +%Y-%m-%dT%H:%M:%SZ` form, and Clock.UnixSecs and // Clock.UnixMillis in `date +%s` and `date +%s%3N` form, over the proleptic Gregorian calendar. type ModeledWallClock { - unix_at_origin: Int - step_seconds: Int + unix_at_origin: EpochSecs + step: SecondDisplacement } fn wall_clock_unix(clock: ModeledWallClock, now: Second) -> Int { - clock.unix_at_origin + (second_count(s: now) as Int) + clock.step_seconds + (clock.unix_at_origin as Int) + (second_count(s: now) as Int) + second_displacement_count(d: clock.step) } fn two_digits(n: Int) -> String { diff --git a/dag/std/measure.dag b/dag/std/measure.dag index 9386690dd35..d4ef4990025 100644 --- a/dag/std/measure.dag +++ b/dag/std/measure.dag @@ -1609,6 +1609,19 @@ fn second_count(s: Second) -> Nat { measure_count(s) } +// A SIGNED DURATION: a displacement in time that may be negative, as a wall clock stepping backwards +// is. Second is Nat-counted and so cannot carry it; this is the same instant-versus-displacement split +// CelsiusDelta and ArcsecondDisplacement make for their quantities. +type SecondDisplacement = Measure + +fn second_displacement(count: Int) -> SecondDisplacement { + Measure { count: count } +} + +fn second_displacement_count(d: SecondDisplacement) -> Int { + measure_count(d) +} + fn energy_from_power_and_time(power: Watt, time: Second) -> Joule { joule(watt_count(w: power) * second_count(s: time)) } diff --git a/dag/test/claim/boot_world_models_witness_test.dag b/dag/test/claim/boot_world_models_witness_test.dag index 756eec54443..51e2696b9b4 100644 --- a/dag/test/claim/boot_world_models_witness_test.dag +++ b/dag/test/claim/boot_world_models_witness_test.dag @@ -26,7 +26,7 @@ test fn the_wall_clock_renders_utc_like_date() -> Bool { // A NEGATIVE STEP IS A WALL CLOCK THAT WENT BACKWARDS while virtual time moved forward. test fn a_backward_step_reads_earlier_while_virtual_time_advances() -> Bool { - let clock = ModeledWallClock { unix_at_origin: 1790000000, step_seconds: 0 - 120 } + let clock = ModeledWallClock { unix_at_origin: 1790000000, step: std.measure.second_displacement(count: 0 - 120) } wall_clock_unix(clock: clock, now: second(count: 60)) == 1789999940 } diff --git a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag index 72881769836..31fa7710e62 100644 --- a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag +++ b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag @@ -140,7 +140,7 @@ fn world_with_console(lines: List) -> MtCollins1BootWorld { ], agent: ModeledSshAgent { socket: "/run/ssh-agent.sock", holds_fleet_key: true }, remote_hosts: [healthy_srv2()], - clock: ModeledWallClock { unix_at_origin: 1790000000, step_seconds: 0 }, + clock: ModeledWallClock { unix_at_origin: 1790000000, step: std.measure.second_displacement(count: 0) }, worker: ModeledWorker { next_pid: 4000, processes: [], uptime_at_origin: 86400 }, console: ModeledHostConsole { lines: lines, emitted: 0, boot: none }, media: healthy_media(), diff --git a/src/v2/workflow/floor_cost_debt_admission.dag b/src/v2/workflow/floor_cost_debt_admission.dag index ad0029f8426..3eddf41b4fb 100644 --- a/src/v2/workflow/floor_cost_debt_admission.dag +++ b/src/v2/workflow/floor_cost_debt_admission.dag @@ -103,8 +103,60 @@ data app_attest_interpreted_crypto_typed_admissions: List = [ + FloorCostDebtTypedAdmission { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_host_that_never_prints_a_census_refuses_at_the_deadline", + reason: "the subject is the real mtcollins1 boot entry (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1) evaluated end to end in the seed interpreter over the dry operation realization, as #12423 requires of the acceptance matrix; a narrower supplied value would drop the route under test; owner: the mtcollins1 boot acceptance matrix lane, discharged by the natively emitted evaluation frame" as NonEmptyStr, + }, + FloorCostDebtTypedAdmission { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_healthy_census_is_refused_at_the_sol_teardown", + reason: "the subject is the real mtcollins1 boot entry (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1) evaluated end to end in the seed interpreter over the dry operation realization, as #12423 requires of the acceptance matrix; a narrower supplied value would drop the route under test; owner: the mtcollins1 boot acceptance matrix lane, discharged by the natively emitted evaluation frame" as NonEmptyStr, + }, + FloorCostDebtTypedAdmission { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.an_already_presented_image_is_not_attached_again", + reason: "the subject is the real mtcollins1 boot entry (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1) evaluated end to end in the seed interpreter over the dry operation realization, as #12423 requires of the acceptance matrix; a narrower supplied value would drop the route under test; owner: the mtcollins1 boot acceptance matrix lane, discharged by the natively emitted evaluation frame" as NonEmptyStr, + }, + FloorCostDebtTypedAdmission { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.media_that_never_becomes_ready_refuses_before_any_power_action", + reason: "the subject is the real mtcollins1 boot entry (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1) evaluated end to end in the seed interpreter over the dry operation realization, as #12423 requires of the acceptance matrix; a narrower supplied value would drop the route under test; owner: the mtcollins1 boot acceptance matrix lane, discharged by the natively emitted evaluation frame" as NonEmptyStr, + }, + FloorCostDebtTypedAdmission { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.the_right_filename_on_the_wrong_share_refuses_before_any_power_action", + reason: "the subject is the real mtcollins1 boot entry (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1) evaluated end to end in the seed interpreter over the dry operation realization, as #12423 requires of the acceptance matrix; a narrower supplied value would drop the route under test; owner: the mtcollins1 boot acceptance matrix lane, discharged by the natively emitted evaluation frame" as NonEmptyStr, + }, + FloorCostDebtTypedAdmission { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_listing_that_names_the_image_twice_starts_nothing", + reason: "the subject is the real mtcollins1 boot entry (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1) evaluated end to end in the seed interpreter over the dry operation realization, as #12423 requires of the acceptance matrix; a narrower supplied value would drop the route under test; owner: the mtcollins1 boot acceptance matrix lane, discharged by the natively emitted evaluation frame" as NonEmptyStr, + }, + FloorCostDebtTypedAdmission { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_presentation_lost_after_readiness_stops_the_handoff", + reason: "the subject is the real mtcollins1 boot entry (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1) evaluated end to end in the seed interpreter over the dry operation realization, as #12423 requires of the acceptance matrix; a narrower supplied value would drop the route under test; owner: the mtcollins1 boot acceptance matrix lane, discharged by the natively emitted evaluation frame" as NonEmptyStr, + }, + FloorCostDebtTypedAdmission { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause", + reason: "the subject is the real mtcollins1 boot entry (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1) evaluated end to end in the seed interpreter over the dry operation realization, as #12423 requires of the acceptance matrix; a narrower supplied value would drop the route under test; owner: the mtcollins1 boot acceptance matrix lane, discharged by the natively emitted evaluation frame" as NonEmptyStr, + }, + FloorCostDebtTypedAdmission { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_sol_loss_mid_boot_is_reported_only_at_the_deadline", + reason: "the subject is the real mtcollins1 boot entry (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1) evaluated end to end in the seed interpreter over the dry operation realization, as #12423 requires of the acceptance matrix; a narrower supplied value would drop the route under test; owner: the mtcollins1 boot acceptance matrix lane, discharged by the natively emitted evaluation frame" as NonEmptyStr, + }, + FloorCostDebtTypedAdmission { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_two_socket_answer_is_a_truthful_topology_refusal", + reason: "the subject is the real mtcollins1 boot entry (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1) evaluated end to end in the seed interpreter over the dry operation realization, as #12423 requires of the acceptance matrix; a narrower supplied value would drop the route under test; owner: the mtcollins1 boot acceptance matrix lane, discharged by the natively emitted evaluation frame" as NonEmptyStr, + }, + FloorCostDebtTypedAdmission { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_an_interrupted_attempt_locks_out_the_next_one", + reason: "the subject is the real mtcollins1 boot entry (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1) evaluated end to end in the seed interpreter over the dry operation realization, as #12423 requires of the acceptance matrix; a narrower supplied value would drop the route under test; owner: the mtcollins1 boot acceptance matrix lane, discharged by the natively emitted evaluation frame" as NonEmptyStr, + }, +] + fn floor_cost_debt_typed_admissions() -> List { - app_attest_interpreted_crypto_typed_admissions + concat(app_attest_interpreted_crypto_typed_admissions, mtcollins1_boot_matrix_typed_admissions) } fn floor_cost_debt_typed_admitted_identities() -> List { diff --git a/src/v2/workflow/floor_eval_step_cost_drop.dag b/src/v2/workflow/floor_eval_step_cost_drop.dag index c1b9ee7711a..76aa9c2b69c 100644 --- a/src/v2/workflow/floor_eval_step_cost_drop.dag +++ b/src/v2/workflow/floor_eval_step_cost_drop.dag @@ -25,7 +25,9 @@ import std.types { NonEmptyStr } // - `floor_eval_step_cost_drop_span_program_rows`: the byte-span program inhabitance claim // (`sha256_span_program_serialize_new_witness_eval_step_cost`); // - `floor_eval_step_cost_drop_python_to_typescript_inhabitance_rows`: the python->typescript -// compile inhabitance claim (`python_to_typescript_compile_inhabitance_off_the_required_gate`). +// compile inhabitance claim (`python_to_typescript_compile_inhabitance_off_the_required_gate`); +// - `floor_eval_step_cost_drop_boot_matrix_rows`: the mtcollins1 boot acceptance matrix +// (`mtcollins1_boot_matrix_new_witness_eval_step_cost`). // The loss is a WORKFLOW fact -- which // identities the eval-step ceiling does not refuse -- so it lives beside the other two rosters the // ceiling consults (`v2.workflow.floor_grandfathered_roster`, `v2.workflow.floor_cost_debt`) and @@ -232,8 +234,60 @@ data floor_eval_step_cost_drop_python_to_typescript_inhabitance_rows: List = [ + EvalStepCostDropMeasurement { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_host_that_never_prints_a_census_refuses_at_the_deadline" as NonEmptyStr, + measured_by: "PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization" as NonEmptyStr, + }, + EvalStepCostDropMeasurement { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_healthy_census_is_refused_at_the_sol_teardown" as NonEmptyStr, + measured_by: "PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization" as NonEmptyStr, + }, + EvalStepCostDropMeasurement { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.an_already_presented_image_is_not_attached_again" as NonEmptyStr, + measured_by: "PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization" as NonEmptyStr, + }, + EvalStepCostDropMeasurement { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.media_that_never_becomes_ready_refuses_before_any_power_action" as NonEmptyStr, + measured_by: "PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization" as NonEmptyStr, + }, + EvalStepCostDropMeasurement { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.the_right_filename_on_the_wrong_share_refuses_before_any_power_action" as NonEmptyStr, + measured_by: "PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization" as NonEmptyStr, + }, + EvalStepCostDropMeasurement { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_listing_that_names_the_image_twice_starts_nothing" as NonEmptyStr, + measured_by: "PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization" as NonEmptyStr, + }, + EvalStepCostDropMeasurement { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_presentation_lost_after_readiness_stops_the_handoff" as NonEmptyStr, + measured_by: "PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization" as NonEmptyStr, + }, + EvalStepCostDropMeasurement { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause" as NonEmptyStr, + measured_by: "PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization" as NonEmptyStr, + }, + EvalStepCostDropMeasurement { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_sol_loss_mid_boot_is_reported_only_at_the_deadline" as NonEmptyStr, + measured_by: "PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization" as NonEmptyStr, + }, + EvalStepCostDropMeasurement { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_two_socket_answer_is_a_truthful_topology_refusal" as NonEmptyStr, + measured_by: "PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization" as NonEmptyStr, + }, + EvalStepCostDropMeasurement { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_an_interrupted_attempt_locks_out_the_next_one" as NonEmptyStr, + measured_by: "PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization" as NonEmptyStr, + }, +] + fn floor_eval_step_cost_drop_all_rows() -> List { - concat(concat(concat(concat(concat(concat(concat(floor_eval_step_cost_drop_rows, floor_eval_step_cost_drop_apply_render_rows), floor_eval_step_cost_drop_live_forecast_rows), floor_eval_step_cost_drop_page_style_rows), floor_eval_step_cost_drop_interpreted_crypto_rows), floor_eval_step_cost_drop_app_attest_verifier_rows), floor_eval_step_cost_drop_span_program_rows), floor_eval_step_cost_drop_python_to_typescript_inhabitance_rows) + concat(concat(concat(concat(concat(concat(concat(concat(floor_eval_step_cost_drop_rows, floor_eval_step_cost_drop_apply_render_rows), floor_eval_step_cost_drop_live_forecast_rows), floor_eval_step_cost_drop_page_style_rows), floor_eval_step_cost_drop_interpreted_crypto_rows), floor_eval_step_cost_drop_app_attest_verifier_rows), floor_eval_step_cost_drop_span_program_rows), floor_eval_step_cost_drop_python_to_typescript_inhabitance_rows), floor_eval_step_cost_drop_boot_matrix_rows) } // THE PROJECTION THE SEED RUNNER READS AND THE MODEL TESTS AGAINST. The runner refuses an empty or From c11634606da8601c381aa2973f0590f24b4b90f2 Mon Sep 17 00:00:00 2001 From: "gunbai-bot[bot]" <289086189+gunbai-bot[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 14:03:10 +0000 Subject: [PATCH 09/38] chore: regenerate drifted generated artifacts (ci auto-heal) Ledger-Repair-Judged: docs/design-rung-drops.md Ledger-Rows-Repaired: docs/design-rung-drops.md mtcollins1_boot_matrix_new_witness_eval_step_cost Heal-Candidate-Run: 36427507942 --- docs/design-rung-drops.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/design-rung-drops.md b/docs/design-rung-drops.md index 97328cf2c18..0e81e536ad0 100644 --- a/docs/design-rung-drops.md +++ b/docs/design-rung-drops.md @@ -338,6 +338,10 @@ new-witness eval-step cost gate over the one roadmap page claim that derives the new-witness eval-step cost gate over the ten interpreted P-256 and P-384 point and group-law checks and SHA-384 FIPS vectors of the App Attest path: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the P-384 and SHA-384 primitives executing as natively emitted code rather than as interpreted bignat and Word64 folds). Population: test.claim.p384_dag_ecdsa_witness_test.the_p384_base_point_is_on_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.p384_dag_ecdsa_witness_test.a_point_beside_the_base_point_is_off_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.p384_dag_ecdsa_witness_test.a_point_with_a_coordinate_at_p_is_not_admitted: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.p384_dag_ecdsa_witness_test.apples_ca_and_root_keys_are_on_p384: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.sha384_fips_witness_test.sha384_of_the_empty_message_is_the_published_value_and_one_octet_discriminates: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35703555837 (gunbc#11981, head 0b0a2dffe), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, over the new-witness eval-step budget and above the per-subject line. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field. The billed work is two single-block SHA-384 compressions, the published empty message and a one-octet message on an interpreter that reifies no machine width; the published positive and its discriminating mutation are one subject, so the cost is caused by the claim's own boundary and not by neighbouring vectors, test.claim.sha384_fips_witness_test.sha384_of_abc_is_the_published_value_and_one_octet_discriminates: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35703555837 (gunbc#11981, head 0b0a2dffe), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, over the new-witness eval-step budget and above the per-subject line. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field. The billed work is two single-block SHA-384 compressions, the published abc vector and abd on an interpreter that reifies no machine width; the published positive and its discriminating mutation are one subject, so the cost is caused by the claim's own boundary and not by neighbouring vectors, test.claim.sha384_fips_witness_test.sha384_of_the_896_bit_message_is_the_published_two_block_value_and_one_octet_discriminates: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35703555837 (gunbc#11981, head 0b0a2dffe), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, over the new-witness eval-step budget and above the per-subject line. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field. The billed work is four SHA-384 block compressions, the published 896-bit two-block vector and a last-block mutation on an interpreter that reifies no machine width; the published positive and its discriminating mutation are one subject, so the cost is caused by the claim's own boundary and not by neighbouring vectors, test.claim.p256_dag_ecdsa_witness_test.the_base_point_is_on_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35708011844 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, well inside the wall deadline and over the new-witness eval-step budget. The billed work is one P-256 curve equation over the published base point as 11-limb bignat folds on an interpreter that reifies no machine width. It is a MEMBER BECAUSE IT WAS SPLIT OUT of a claim that also asserted n*G = infinity: that conjunction crossed the 8000ms wall deadline and was INTERRUPTED BEFORE ANY VERDICT, so both facts were being lost; the scalar multiplication left the floor for the native row the frontier names, and this cheap half now reaches a verdict. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field, test.claim.p384_dag_ecdsa_witness_test.doubling_and_adding_the_base_point_stay_on_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by claim_batch --hermetic [witness] receipt on session/nimble-eagle-216-step3 (added for review 69917 after run 35678328407): verdict pass, wall under the 8 s deadline, over the new-witness eval-step budget; the billed work is one Jacobian doubling, one addition and two projective curve-equation checks at 16 limbs, test.claim.p384_dag_ecdsa_witness_test.doubling_an_off_curve_point_stays_off_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by claim_batch --hermetic [witness] receipt on session/nimble-eagle-216-step3 (added for review 69917 after run 35678328407): verdict pass, wall under the 8 s deadline, over the new-witness eval-step budget; the billed work is one Jacobian doubling and one projective curve-equation check at 16 limbs. Restored when: THE CAPABILITY: gunbc test //gunbc/instruments:native-crypto-vectors EXECUTING ON THE MERGE PATH, i.e. as a phase of a required lane whose red blocks a merge, running these ten identities natively (its cases p256_the_base_point_is_on_the_curve, p384_the_base_point_is_on_the_curve, p384_red_a_point_beside_the_base_point_is_off_the_curve, p384_red_a_point_with_a_coordinate_at_p_is_not_admitted, p384_doubling_and_adding_the_base_point_stay_on_the_curve, p384_red_doubling_an_off_curve_point_stays_off_the_curve, p384_apples_ca_and_root_keys_are_on_the_curve and the sha384_* FIPS cases with their reds) while each still evaluates the real curve equation or the real hash; WHAT THAT MUST BE SUFFICIENT FOR: the P-384 parameters and SHA-384 are exercised on the acceptance path with no interpreted 16-limb fold inside a claim frame, so the interpreted identities can leave the floor for that row, or they measure under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives. The same row run BY NAME (as it exists since gunbc#12306) executes the facts natively but is NOT the acceptance path and retires nothing. Supplying the curve check's answer or the digest as a fixture, or deleting the identities, satisfies neither. +### new-witness eval-step cost gate over the eleven mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered — declared 2026-09-28 + +new-witness eval-step cost gate over the eleven mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the evaluation frame and its modeled operation realization realized by the natively emitted runtime, so the boot entry runs as emitted code rather than in the seed interpreter). Population: test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_host_that_never_prints_a_census_refuses_at_the_deadline: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_healthy_census_is_refused_at_the_sol_teardown: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.an_already_presented_image_is_not_attached_again: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.media_that_never_becomes_ready_refuses_before_any_power_action: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.the_right_filename_on_the_wrong_share_refuses_before_any_power_action: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_listing_that_names_the_image_twice_starts_nothing: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_presentation_lost_after_readiness_stops_the_handoff: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_sol_loss_mid_boot_is_reported_only_at_the_deadline: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_two_socket_answer_is_a_truthful_topology_refusal: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_an_interrupted_attempt_locks_out_the_next_one: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization. Restored when: THE CAPABILITY: witnesses emitted to native code with the emitted runtime realizing v2.std.witness_evaluation evaluate_in_witness_frame and its v2.std.operation_realization modeled realization, EXECUTING ON THE MERGE PATH as a phase of a required lane whose red blocks a merge, running these eleven identities with the real mtcollins1_boot_wet_on_srv1 entry; WHAT THAT MUST BE SUFFICIENT FOR: each case measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither. + ### new-witness eval-step cost gate over six App Attest verifier claims -- the two real-path inhabitance claims over Apple's objects, three single structural steps priced by their own SHA-256s, and the wire-carrier join: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered — declared 2026-09-22 new-witness eval-step cost gate over six App Attest verifier claims -- the two real-path inhabitance claims over Apple's objects, three single structural steps priced by their own SHA-256s, and the wire-carrier join: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the CBOR, DER and SHA-256 readers executing as natively emitted code over the sample objects rather than as interpreted octet-list folds). Population: test.claim.app_attest_verifier_witness_test.the_sample_passes_every_step_before_the_extension_steps: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_app_attest_verifier_rows; measured by claim_batch --hermetic [witness] receipt at session/nimble-eagle-216-step3b 4d089af1cb9 (gunbc#11989): verdict pass, wall under the 8 s deadline, eval_steps read from that receipt's own line and over the new-witness budget; the billed work is the real path end to end: Apple's attestation object through decode_attestation (CBOR, two DER certificates, the PEM-pinned root, authenticator data) and the whole structural fold with its SHA-256s. It is the attestation path's one inhabitance claim, so nothing here is supplied, test.claim.app_attest_verifier_witness_test.the_nonce_step_reds_on_other_client_data: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_app_attest_verifier_rows; measured by claim_batch --hermetic [witness] receipt at session/nimble-eagle-216-step3b 4d089af1cb9 (gunbc#11989): verdict pass, wall under the 8 s deadline, eval_steps read from that receipt's own line and over the new-witness budget; the billed work is nonce_step alone over supplied inputs: SHA-256 of the client data and SHA-256 of authData concatenated with it, two interpreted hashes, which is the step and nothing before it, test.claim.app_attest_verifier_witness_test.the_app_id_step_reds_on_another_app_id: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_app_attest_verifier_rows; measured by claim_batch --hermetic [witness] receipt at session/nimble-eagle-216-step3b 4d089af1cb9 (gunbc#11989): verdict pass, wall under the 8 s deadline, eval_steps read from that receipt's own line and over the new-witness budget; the billed work is app_id_step alone over supplied inputs: one interpreted SHA-256 of the App ID, test.claim.app_attest_verifier_witness_test.the_key_id_step_admits_the_real_key_and_reds_on_another_key_id: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_app_attest_verifier_rows; measured by claim_batch --hermetic [witness] receipt at session/nimble-eagle-216-step3b (gunbc#11989), and the required floor's required-ci-measurement-receipt: verdict pass, wall under the 8 s deadline, eval_steps read from that receipt's own line and over the new-witness budget; the billed work is key_id_step alone over supplied inputs, paired over its own subject: one interpreted SHA-256 of the credential certificate's 65-octet public key (the second comparison reuses it), test.claim.app_attest_verifier_witness_test.the_sample_assertion_passes_rp_id_and_refuses_on_absent_extensions: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_app_attest_verifier_rows; measured by claim_batch --hermetic [witness] receipt at session/nimble-eagle-216-step3b (gunbc#11989), and the required floor's required-ci-measurement-receipt: verdict pass, wall under the 8 s deadline, eval_steps read from that receipt's own line and over the new-witness budget; the billed work is the assertion's real path: the assertion object through assertion_parts and the fold's RP ID SHA-256, plus the attestation's leaf certificate read for the credential key (the attestation-to-assertion join). It is the assertion path's one inhabitance claim, paired with a wrong-App-ID refusal over the same subject, test.claim.signature_verify_join_witness_test.malformed_carriers_refuse_before_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_app_attest_verifier_rows; measured by claim_batch --hermetic [witness] receipt at session/nimble-eagle-216-step3b 4d089af1cb9 (gunbc#11989): verdict pass, wall under the 8 s deadline, eval_steps read from that receipt's own line and over the new-witness budget. THIS ROW'S BILLED WORK IS NOT THE VERIFIER ROWS': it touches no Apple object, no certificate and no hash. It base64url-decodes two carriers of the RFC 6979 A.2.5 P-256 vectors through extdeps.crypto.signature verify_signature, which asks signature_carrier_refusal FIRST and refuses on size before the implementation is consulted. What the budget prices is the interpreter walking those octet lists byte by byte to decode and size them. Restored when: THE CAPABILITY. MachineWidth reification (gunbc#11819) lets the extdeps.apple.app_attest closure emit, and these six identities execute on the natively emitted route -- the gunbc test instrument row the ecdsa_verification_realization_frontier names -- while each still exercises its own production code over its own inputs -- Apple's objects end to end for the two inhabitance claims, its one named step for the three step claims, the RFC 6979 carriers for the carrier-join claim; WHAT THAT MUST BE SUFFICIENT FOR: the verifier folds are exercised over the real sample on the acceptance path, and the carrier join over its real carriers, with no interpreted octet walk inside a claim frame, and the identities then measure under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, or leave the floor for that native row. Supplying the two inhabitance claims' inputs as fixtures, precomputing a step's hashes, or deleting the identities satisfies neither. From a0e46d8b6d4708350a09707632532682535719cc Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 28 Sep 2026 14:33:15 +0000 Subject: [PATCH 10/38] Matrix cost: bindings indexed once at admission, SOL drop armed by the host's boot (one attempt), stale cost-debt rows removed, drop population = the 8 over-budget cases, rung-drop projection regenerated The first floor run showed every typed cost-debt row stale: the matrix cases sit under the 500ms per-subject line, where such a row blocks. The honest fix is cost, not a different exemption: operation_realization_index maps bindings by identity once per frame (each of ~190 dispatches no longer scans the list), and the SOL-loss case no longer pays a baseline attempt. Measured locally the dearest case is now 220ms CPU (was 307ms on CI), under the 302ms enrolment margin. OperationBoundTwice/BindingMatch deleted (unreachable: duplicates refuse at admission). Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/gunbc/bmc_model.dag | 34 +++++++++--- ...boot_matrix_new_witness_eval_step_cost.dag | 11 ++-- ...mtcollins1_boot_acceptance_matrix_test.dag | 47 ++++++---------- docs/design-rung-drops.md | 4 ++ src/v1/stage0/src/v1_interpreter.rs | 14 ++++- src/v2/std/operation_realization.dag | 38 +++++-------- src/v2/workflow/floor_cost_debt_admission.dag | 54 +------------------ src/v2/workflow/floor_eval_step_cost_drop.dag | 21 ++------ 8 files changed, 86 insertions(+), 137 deletions(-) diff --git a/dag/gunbc/bmc_model.dag b/dag/gunbc/bmc_model.dag index 0d36446aa05..a82ce78dfc8 100644 --- a/dag/gunbc/bmc_model.dag +++ b/dag/gunbc/bmc_model.dag @@ -51,6 +51,9 @@ type BmcSelRecord { state: String } +// sol_drop_after_boot, when a scenario sets it, schedules the controller dropping its SOL session that +// long after the host starts booting, so the loss follows the route's own power-on rather than a clock +// guessed in advance. // host_booted_at is the virtual instant the host last started booting (power on, or the restore of a // cycle); the host's console is timed from it. booted_via_override records whether that boot consumed // the one-shot override. @@ -64,12 +67,13 @@ type BmcWorld { cycle_off_interval: Second host_booted_at: Second? booted_via_override: Bool + sol_drop_after_boot: Second? } fn bmc_world(power: BmcPower) -> BmcWorld { BmcWorld { power: power, pending: [], fired: [], boot_override: BootOverrideNone, sel: [], - sol_session_open: false, cycle_off_interval: second(count: 5), host_booted_at: none, booted_via_override: false, + sol_session_open: false, cycle_off_interval: second(count: 5), host_booted_at: none, booted_via_override: false, sol_drop_after_boot: none, } } @@ -94,7 +98,7 @@ fn bmc_with_power(world: BmcWorld, power: BmcPower) -> BmcWorld { BmcWorld { power: power, pending: world.pending, fired: world.fired, boot_override: world.boot_override, sel: world.sel, sol_session_open: world.sol_session_open, cycle_off_interval: world.cycle_off_interval, - host_booted_at: world.host_booted_at, booted_via_override: world.booted_via_override, + host_booted_at: world.host_booted_at, booted_via_override: world.booted_via_override, sol_drop_after_boot: world.sol_drop_after_boot, } } @@ -102,7 +106,7 @@ fn bmc_with_override(world: BmcWorld, over: BmcBootOverride) -> BmcWorld { BmcWorld { power: world.power, pending: world.pending, fired: world.fired, boot_override: over, sel: world.sel, sol_session_open: world.sol_session_open, cycle_off_interval: world.cycle_off_interval, - host_booted_at: world.host_booted_at, booted_via_override: world.booted_via_override, + host_booted_at: world.host_booted_at, booted_via_override: world.booted_via_override, sol_drop_after_boot: world.sol_drop_after_boot, } } @@ -110,7 +114,7 @@ fn bmc_with_sol_session(world: BmcWorld, open: Bool) -> BmcWorld { BmcWorld { power: world.power, pending: world.pending, fired: world.fired, boot_override: world.boot_override, sel: world.sel, sol_session_open: open, cycle_off_interval: world.cycle_off_interval, - host_booted_at: world.host_booted_at, booted_via_override: world.booted_via_override, + host_booted_at: world.host_booted_at, booted_via_override: world.booted_via_override, sol_drop_after_boot: world.sol_drop_after_boot, } } @@ -118,7 +122,7 @@ fn bmc_with_pending(world: BmcWorld, pending: List, fired: Li BmcWorld { power: world.power, pending: pending, fired: fired, boot_override: world.boot_override, sel: world.sel, sol_session_open: world.sol_session_open, cycle_off_interval: world.cycle_off_interval, - host_booted_at: world.host_booted_at, booted_via_override: world.booted_via_override, + host_booted_at: world.host_booted_at, booted_via_override: world.booted_via_override, sol_drop_after_boot: world.sol_drop_after_boot, } } @@ -126,10 +130,14 @@ fn bmc_with_pending(world: BmcWorld, pending: List, fired: Li // override is consumed by it. fn bmc_host_boots(world: BmcWorld, now: Second) -> BmcWorld { let via = match world.boot_override { BootOverrideCdromEfiNextBoot => true BootOverrideNone => false } + let pending = match world.sol_drop_after_boot { + Absent => world.pending + Present { value: after } => list_append(world.pending, BmcScheduledEvent { at: second(count: second_count(s: now) + second_count(s: after)), event: BmcSolSessionDropped }) + } BmcWorld { - power: BmcPowerOn, pending: world.pending, fired: world.fired, boot_override: BootOverrideNone, sel: world.sel, + power: BmcPowerOn, pending: pending, fired: world.fired, boot_override: BootOverrideNone, sel: world.sel, sol_session_open: world.sol_session_open, cycle_off_interval: world.cycle_off_interval, - host_booted_at: Present { value: now }, booted_via_override: via, + host_booted_at: Present { value: now }, booted_via_override: via, sol_drop_after_boot: world.sol_drop_after_boot, } } @@ -170,3 +178,15 @@ fn bmc_advance(world: BmcWorld, now: Second) -> BmcWorld { } }) } + +fn bmc_with_sol_drop_after_boot(world: BmcWorld, after: Second) -> BmcWorld { + BmcWorld { + power: world.power, pending: world.pending, fired: world.fired, boot_override: world.boot_override, sel: world.sel, + sol_session_open: world.sol_session_open, cycle_off_interval: world.cycle_off_interval, + host_booted_at: world.host_booted_at, booted_via_override: world.booted_via_override, sol_drop_after_boot: Present { value: after }, + } +} + +fn bmc_sol_drop_fired(world: BmcWorld) -> Bool { + !all(world.fired, e => match e.event { BmcSolSessionDropped => false _ => true }) +} diff --git a/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag b/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag index b7f32f5e384..9a841cf09e1 100644 --- a/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag +++ b/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag @@ -9,12 +9,11 @@ import v2.workflow.floor_eval_step_cost_drop { floor_eval_step_cost_drop_boot_ma // matrix, msg_83af891b). The DECLARATION lives here; the POPULATION's authority is // `v2.workflow.floor_eval_step_cost_drop` `floor_eval_step_cost_drop_boot_matrix_rows`. // -// WHAT IS LOST IS ONE WALL, AND THE CLAIMS ARE UNTOUCHED. The eleven identities are planned, executed +// WHAT IS LOST IS ONE WALL, AND THE CLAIMS ARE UNTOUCHED. The eight identities are planned, executed // and measured on every pull request that edits them; a semantic red and a wall-clock crossing still // block. Only the eval-step overrun is reported as `EvalStepsOverBudgetUnderDeclaredDrop` rather than -// refusing. The budget is not raised, and each identity is also a typed cost-debt admission -// (`v2.workflow.floor_cost_debt_admission` `mtcollins1_boot_matrix_typed_admissions`), which keeps it -// executing while its CPU is reported. +// refusing. The budget is not raised. Their CPU stays under the enrolment margin, so they enrol +// without a cost-debt admission (one would be stale under the per-subject line and block). // // WHY THE OVERRUN IS THE SUBJECT'S AND NOT DUPLICATED WORK. Each case asserts the ROUTE the real boot // entry takes -- which effects, in which order, under which hold -- and its typed outcome, which #12423 @@ -29,7 +28,7 @@ data mtcollins1_boot_matrix_new_witness_eval_step_cost_population: List data mtcollins1_boot_matrix_new_witness_eval_step_cost: RungDrop = RungDrop { identity: "mtcollins1_boot_matrix_new_witness_eval_step_cost" as NonEmptyStr, - subject: "new-witness eval-step cost gate over the eleven mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered", + subject: "new-witness eval-step cost gate over the eight mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered", declared: "2026-09-28", @@ -42,6 +41,6 @@ data mtcollins1_boot_matrix_new_witness_eval_step_cost: RungDrop = RungDrop { replacement: "the evaluation frame and its modeled operation realization realized by the natively emitted runtime, so the boot entry runs as emitted code rather than in the seed interpreter", }, population: mtcollins1_boot_matrix_new_witness_eval_step_cost_population, - restoration_trigger: "THE CAPABILITY: witnesses emitted to native code with the emitted runtime realizing v2.std.witness_evaluation evaluate_in_witness_frame and its v2.std.operation_realization modeled realization, EXECUTING ON THE MERGE PATH as a phase of a required lane whose red blocks a merge, running these eleven identities with the real mtcollins1_boot_wet_on_srv1 entry; WHAT THAT MUST BE SUFFICIENT FOR: each case measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither.", + restoration_trigger: "THE CAPABILITY: witnesses emitted to native code with the emitted runtime realizing v2.std.witness_evaluation evaluate_in_witness_frame and its v2.std.operation_realization modeled realization, EXECUTING ON THE MERGE PATH as a phase of a required lane whose red blocks a merge, running these eight identities with the real mtcollins1_boot_wet_on_srv1 entry; WHAT THAT MUST BE SUFFICIENT FOR: each case measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither.", } } diff --git a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag index 31fa7710e62..31cc33ca5e0 100644 --- a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag +++ b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag @@ -11,7 +11,7 @@ import v2.std.witness_evaluation { evaluate_in_witness_frame, witness_diagnostic_rendered_reason, } import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } -import gunbc.bmc_model { BmcWorld, BmcPowerOff, BmcScheduledEvent, BmcSolSessionDropped, bmc_world, bmc_with_sol_session, bmc_with_pending } +import gunbc.bmc_model { BmcWorld, BmcPowerOff, bmc_world, bmc_with_sol_session, bmc_with_sol_drop_after_boot, bmc_sol_drop_fired } import gunbc.filesystem_model { ModeledFilesystem, ModeledFile } import gunbc.process_environment_model { ModeledVariable } import gunbc.remote_host_model { ModeledRemoteHost, ModeledRemoteFile } @@ -409,38 +409,25 @@ test fn a_sol_session_held_elsewhere_refuses_before_the_attach() -> Bool { } } -fn power_on_at(route: List) -> Int { - match filter(route, r => operation_name(r: r) == "diagnostic.ipmi.Tool.ChassisPowerControl").first() { - Present { value: r } => std.measure.second_count(s: r.completed_at) as Int - Absent => 0 - 1 - } +// SOL IS LOST THIRTY SECONDS AFTER THE HOST STARTS BOOTING WHILE MANAGEMENT KEEPS ANSWERING, and the +// census would have printed at sixty. The drop is armed by the host's own boot in the model, so it +// follows the route's real power-on. PINNED: the attempt learns of the loss only at the terminal +// deadline -- the capture stops growing and the watch polls to its bound -- so the first loss is not +// reported before the deadline (#12423 asks that it is). The final world shows the drop fired, so the +// case is not green on a run where the loss never happened. When loss detection lands it must flip to +// an early, typed refusal. +fn sol_drop_fired_in(w: MtCollins1BootWorld) -> Bool { + bmc_sol_drop_fired(world: w.bmc) } -// SOL IS LOST THIRTY SECONDS AFTER POWER-ON WHILE MANAGEMENT KEEPS ANSWERING, and the census would have -// printed at sixty. The drop is scheduled from the baseline route's own power-on instant. PINNED: the -// attempt learns of the loss only at the terminal deadline -- the capture stops growing and the watch -// polls to its bound -- so the first loss is not reported before the deadline (#12423 asks that it is). -// When loss detection lands this case must flip to an early, typed refusal. test fn pinned_a_sol_loss_mid_boot_is_reported_only_at_the_deadline() -> Bool { - let console = console_of(capture: one_socket_census_capture, begin_after: 60, end_after: 240) - match run_attempt(world: world_with_console(lines: console)) { - WitnessReturned { route: baseline } => { - let drop_at = power_on_at(route: baseline) + 30 - let w = world_with_console(lines: console) - match std.checked_arithmetic.checked_int_to_nat(n: drop_at) { - Absent => false - Present { value: drop_count } => { - let dropping = with_bmc(w: w, bmc: bmc_with_pending(world: w.bmc, pending: [BmcScheduledEvent { at: second(count: drop_count), event: BmcSolSessionDropped }], fired: [])) - match run_attempt(world: dropping) { - WitnessReturned { value, route } => - drop_at > 30 - && string_contains(s: outcome_reason(a: value), pattern: "deadline reached without a host-capture terminal") - && count_named(route: route, name: "diagnostic.ipmi.Tool.ChassisPowerControl") == 1 - _ => false - } - } - } - } + let w = world_with_console(lines: console_of(capture: one_socket_census_capture, begin_after: 60, end_after: 240)) + let dropping = with_bmc(w: w, bmc: bmc_with_sol_drop_after_boot(world: w.bmc, after: second(count: 30))) + match evaluate_in_witness_frame(frame: matrix_frame(world: dropping), subject: fn(_scope) { mtcollins1_boot_wet_on_srv1() }) { + WitnessReturned { value, route, state } => + string_contains(s: outcome_reason(a: value), pattern: "deadline reached without a host-capture terminal") + && count_named(route: route, name: "diagnostic.ipmi.Tool.ChassisPowerControl") == 1 + && match state { Present { value: end } => sol_drop_fired_in(w: end) Absent => false } _ => false } } diff --git a/docs/design-rung-drops.md b/docs/design-rung-drops.md index 97328cf2c18..fe7c3561532 100644 --- a/docs/design-rung-drops.md +++ b/docs/design-rung-drops.md @@ -338,6 +338,10 @@ new-witness eval-step cost gate over the one roadmap page claim that derives the new-witness eval-step cost gate over the ten interpreted P-256 and P-384 point and group-law checks and SHA-384 FIPS vectors of the App Attest path: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the P-384 and SHA-384 primitives executing as natively emitted code rather than as interpreted bignat and Word64 folds). Population: test.claim.p384_dag_ecdsa_witness_test.the_p384_base_point_is_on_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.p384_dag_ecdsa_witness_test.a_point_beside_the_base_point_is_off_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.p384_dag_ecdsa_witness_test.a_point_with_a_coordinate_at_p_is_not_admitted: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.p384_dag_ecdsa_witness_test.apples_ca_and_root_keys_are_on_p384: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.sha384_fips_witness_test.sha384_of_the_empty_message_is_the_published_value_and_one_octet_discriminates: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35703555837 (gunbc#11981, head 0b0a2dffe), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, over the new-witness eval-step budget and above the per-subject line. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field. The billed work is two single-block SHA-384 compressions, the published empty message and a one-octet message on an interpreter that reifies no machine width; the published positive and its discriminating mutation are one subject, so the cost is caused by the claim's own boundary and not by neighbouring vectors, test.claim.sha384_fips_witness_test.sha384_of_abc_is_the_published_value_and_one_octet_discriminates: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35703555837 (gunbc#11981, head 0b0a2dffe), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, over the new-witness eval-step budget and above the per-subject line. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field. The billed work is two single-block SHA-384 compressions, the published abc vector and abd on an interpreter that reifies no machine width; the published positive and its discriminating mutation are one subject, so the cost is caused by the claim's own boundary and not by neighbouring vectors, test.claim.sha384_fips_witness_test.sha384_of_the_896_bit_message_is_the_published_two_block_value_and_one_octet_discriminates: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35703555837 (gunbc#11981, head 0b0a2dffe), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, over the new-witness eval-step budget and above the per-subject line. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field. The billed work is four SHA-384 block compressions, the published 896-bit two-block vector and a last-block mutation on an interpreter that reifies no machine width; the published positive and its discriminating mutation are one subject, so the cost is caused by the claim's own boundary and not by neighbouring vectors, test.claim.p256_dag_ecdsa_witness_test.the_base_point_is_on_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35708011844 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, well inside the wall deadline and over the new-witness eval-step budget. The billed work is one P-256 curve equation over the published base point as 11-limb bignat folds on an interpreter that reifies no machine width. It is a MEMBER BECAUSE IT WAS SPLIT OUT of a claim that also asserted n*G = infinity: that conjunction crossed the 8000ms wall deadline and was INTERRUPTED BEFORE ANY VERDICT, so both facts were being lost; the scalar multiplication left the floor for the native row the frontier names, and this cheap half now reaches a verdict. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field, test.claim.p384_dag_ecdsa_witness_test.doubling_and_adding_the_base_point_stay_on_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by claim_batch --hermetic [witness] receipt on session/nimble-eagle-216-step3 (added for review 69917 after run 35678328407): verdict pass, wall under the 8 s deadline, over the new-witness eval-step budget; the billed work is one Jacobian doubling, one addition and two projective curve-equation checks at 16 limbs, test.claim.p384_dag_ecdsa_witness_test.doubling_an_off_curve_point_stays_off_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by claim_batch --hermetic [witness] receipt on session/nimble-eagle-216-step3 (added for review 69917 after run 35678328407): verdict pass, wall under the 8 s deadline, over the new-witness eval-step budget; the billed work is one Jacobian doubling and one projective curve-equation check at 16 limbs. Restored when: THE CAPABILITY: gunbc test //gunbc/instruments:native-crypto-vectors EXECUTING ON THE MERGE PATH, i.e. as a phase of a required lane whose red blocks a merge, running these ten identities natively (its cases p256_the_base_point_is_on_the_curve, p384_the_base_point_is_on_the_curve, p384_red_a_point_beside_the_base_point_is_off_the_curve, p384_red_a_point_with_a_coordinate_at_p_is_not_admitted, p384_doubling_and_adding_the_base_point_stay_on_the_curve, p384_red_doubling_an_off_curve_point_stays_off_the_curve, p384_apples_ca_and_root_keys_are_on_the_curve and the sha384_* FIPS cases with their reds) while each still evaluates the real curve equation or the real hash; WHAT THAT MUST BE SUFFICIENT FOR: the P-384 parameters and SHA-384 are exercised on the acceptance path with no interpreted 16-limb fold inside a claim frame, so the interpreted identities can leave the floor for that row, or they measure under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives. The same row run BY NAME (as it exists since gunbc#12306) executes the facts natively but is NOT the acceptance path and retires nothing. Supplying the curve check's answer or the digest as a fixture, or deleting the identities, satisfies neither. +### new-witness eval-step cost gate over the eight mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered — declared 2026-09-28 + +new-witness eval-step cost gate over the eight mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the evaluation frame and its modeled operation realization realized by the natively emitted runtime, so the boot entry runs as emitted code rather than in the seed interpreter). Population: test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_host_that_never_prints_a_census_refuses_at_the_deadline: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_healthy_census_is_refused_at_the_sol_teardown: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.an_already_presented_image_is_not_attached_again: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.media_that_never_becomes_ready_refuses_before_any_power_action: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_presentation_lost_after_readiness_stops_the_handoff: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_sol_loss_mid_boot_is_reported_only_at_the_deadline: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_two_socket_answer_is_a_truthful_topology_refusal: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization. Restored when: THE CAPABILITY: witnesses emitted to native code with the emitted runtime realizing v2.std.witness_evaluation evaluate_in_witness_frame and its v2.std.operation_realization modeled realization, EXECUTING ON THE MERGE PATH as a phase of a required lane whose red blocks a merge, running these eight identities with the real mtcollins1_boot_wet_on_srv1 entry; WHAT THAT MUST BE SUFFICIENT FOR: each case measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither. + ### new-witness eval-step cost gate over six App Attest verifier claims -- the two real-path inhabitance claims over Apple's objects, three single structural steps priced by their own SHA-256s, and the wire-carrier join: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered — declared 2026-09-22 new-witness eval-step cost gate over six App Attest verifier claims -- the two real-path inhabitance claims over Apple's objects, three single structural steps priced by their own SHA-256s, and the wire-carrier join: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the CBOR, DER and SHA-256 readers executing as natively emitted code over the sample objects rather than as interpreted octet-list folds). Population: test.claim.app_attest_verifier_witness_test.the_sample_passes_every_step_before_the_extension_steps: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_app_attest_verifier_rows; measured by claim_batch --hermetic [witness] receipt at session/nimble-eagle-216-step3b 4d089af1cb9 (gunbc#11989): verdict pass, wall under the 8 s deadline, eval_steps read from that receipt's own line and over the new-witness budget; the billed work is the real path end to end: Apple's attestation object through decode_attestation (CBOR, two DER certificates, the PEM-pinned root, authenticator data) and the whole structural fold with its SHA-256s. It is the attestation path's one inhabitance claim, so nothing here is supplied, test.claim.app_attest_verifier_witness_test.the_nonce_step_reds_on_other_client_data: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_app_attest_verifier_rows; measured by claim_batch --hermetic [witness] receipt at session/nimble-eagle-216-step3b 4d089af1cb9 (gunbc#11989): verdict pass, wall under the 8 s deadline, eval_steps read from that receipt's own line and over the new-witness budget; the billed work is nonce_step alone over supplied inputs: SHA-256 of the client data and SHA-256 of authData concatenated with it, two interpreted hashes, which is the step and nothing before it, test.claim.app_attest_verifier_witness_test.the_app_id_step_reds_on_another_app_id: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_app_attest_verifier_rows; measured by claim_batch --hermetic [witness] receipt at session/nimble-eagle-216-step3b 4d089af1cb9 (gunbc#11989): verdict pass, wall under the 8 s deadline, eval_steps read from that receipt's own line and over the new-witness budget; the billed work is app_id_step alone over supplied inputs: one interpreted SHA-256 of the App ID, test.claim.app_attest_verifier_witness_test.the_key_id_step_admits_the_real_key_and_reds_on_another_key_id: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_app_attest_verifier_rows; measured by claim_batch --hermetic [witness] receipt at session/nimble-eagle-216-step3b (gunbc#11989), and the required floor's required-ci-measurement-receipt: verdict pass, wall under the 8 s deadline, eval_steps read from that receipt's own line and over the new-witness budget; the billed work is key_id_step alone over supplied inputs, paired over its own subject: one interpreted SHA-256 of the credential certificate's 65-octet public key (the second comparison reuses it), test.claim.app_attest_verifier_witness_test.the_sample_assertion_passes_rp_id_and_refuses_on_absent_extensions: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_app_attest_verifier_rows; measured by claim_batch --hermetic [witness] receipt at session/nimble-eagle-216-step3b (gunbc#11989), and the required floor's required-ci-measurement-receipt: verdict pass, wall under the 8 s deadline, eval_steps read from that receipt's own line and over the new-witness budget; the billed work is the assertion's real path: the assertion object through assertion_parts and the fold's RP ID SHA-256, plus the attestation's leaf certificate read for the credential key (the attestation-to-assertion join). It is the assertion path's one inhabitance claim, paired with a wrong-App-ID refusal over the same subject, test.claim.signature_verify_join_witness_test.malformed_carriers_refuse_before_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_app_attest_verifier_rows; measured by claim_batch --hermetic [witness] receipt at session/nimble-eagle-216-step3b 4d089af1cb9 (gunbc#11989): verdict pass, wall under the 8 s deadline, eval_steps read from that receipt's own line and over the new-witness budget. THIS ROW'S BILLED WORK IS NOT THE VERIFIER ROWS': it touches no Apple object, no certificate and no hash. It base64url-decodes two carriers of the RFC 6979 A.2.5 P-256 vectors through extdeps.crypto.signature verify_signature, which asks signature_carrier_refusal FIRST and refuses on size before the implementation is consulted. What the budget prices is the interpreter walking those octet lists byte by byte to decode and size them. Restored when: THE CAPABILITY. MachineWidth reification (gunbc#11819) lets the extdeps.apple.app_attest closure emit, and these six identities execute on the natively emitted route -- the gunbc test instrument row the ecdsa_verification_realization_frontier names -- while each still exercises its own production code over its own inputs -- Apple's objects end to end for the two inhabitance claims, its one named step for the three step claims, the RFC 6979 carriers for the carrier-join claim; WHAT THAT MUST BE SUFFICIENT FOR: the verifier folds are exercised over the real sample on the acceptance path, and the carrier join over its real carriers, with no interpreted octet walk inside a claim frame, and the identities then measure under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, or leave the floor for that native row. Supplying the two inhabitance claims' inputs as fixtures, precomputing a step's hashes, or deleting the identities satisfies neither. diff --git a/src/v1/stage0/src/v1_interpreter.rs b/src/v1/stage0/src/v1_interpreter.rs index 8b2ef4139db..15e2acc0f48 100644 --- a/src/v1/stage0/src/v1_interpreter.rs +++ b/src/v1/stage0/src/v1_interpreter.rs @@ -7858,6 +7858,9 @@ fn current_witness_evaluation_frame() -> Option { struct ModeledRealizationSlot { envelope: Value, realization: Value, + /// The realization's bindings by operation identity (`operation_realization_index`), built + /// once at admission so no dispatch rescans the binding list. + index: Value, identity: String, state: Value, /// The virtual clock, an opaque `std.measure` `Second`: the dispatcher never reads its @@ -8084,9 +8087,16 @@ fn admit_modeled_realization( let advance = record_field(ctx, &realization, "advance") .ok_or_else(|| modeled_refused("(frame)", "the realization carries no advance function"))?; let state = apply_modeled_handler(&advance, &[initial, now.clone()], env, ctx)?; + let index = run_in_context_with_args( + ctx, + "operation_realization_index", + &[(Some("realization".to_string()), realization.clone())], + false, + )?; Ok(Some(ModeledRealizationSlot { envelope, realization, + index, identity, state, now, @@ -8195,6 +8205,7 @@ fn dispatch_modeled_operation( ( s.envelope.clone(), s.realization.clone(), + s.index.clone(), s.identity.clone(), s.state.clone(), s.now.clone(), @@ -8203,7 +8214,7 @@ fn dispatch_modeled_operation( }) }) }); - let Some((envelope, realization, identity, state, now, ordinal)) = snapshot else { + let Some((envelope, realization, index, identity, state, now, ordinal)) = snapshot else { return Ok(None); }; let key = format!("{service_name}.{op_name}"); @@ -8243,6 +8254,7 @@ fn dispatch_modeled_operation( &[ (Some("env".to_string()), envelope), (Some("realization".to_string()), realization.clone()), + (Some("index".to_string()), index), (Some("invocation".to_string()), invocation.clone()), ( Some("readonly".to_string()), diff --git a/src/v2/std/operation_realization.dag b/src/v2/std/operation_realization.dag index d3b5b37f8b9..054eaa00866 100644 --- a/src/v2/std/operation_realization.dag +++ b/src/v2/std/operation_realization.dag @@ -1,6 +1,6 @@ module v2.std.operation_realization -import std.types { Bool, Int, List, NonEmptyStr, String } +import std.types { Bool, Int, List, Map, NonEmptyStr, String } import std.effect_grant { Envelope, HandlerBinding, CoveredBy, NoCoveringGrant, covering_grant, NamespacePosition, ServiceOpTree, Read, Write, Verb, @@ -111,7 +111,6 @@ type OperationBindingRefusal | OperationBoundElsewhere { at: OperationRef, binding: HandlerBinding } | OperationRealizationMismatch { at: OperationRef, named: String, active: NonEmptyStr } | OperationNotBound { at: OperationRef } - | OperationBoundTwice { at: OperationRef, count: Int } type OperationHandlerSelection = OperationHandlerSelected { binding: OperationBinding } @@ -129,29 +128,13 @@ fn operation_verb(readonly: Bool) -> Verb { if readonly { Read } else { Write } } -type BindingMatch - = BindingMatchNone - | BindingMatchOne { binding: OperationBinding } - | BindingMatchMany { count: Int } - -fn binding_match(bindings: List>, at: OperationRef) -> BindingMatch { - fold(bindings, init: BindingMatchNone, f: fn(acc, b) { - if operation_ref_eq(a: b.at, b: at) { - match acc { - BindingMatchNone => BindingMatchOne { binding: b } - BindingMatchOne { binding: _ } => BindingMatchMany { count: 2 } - BindingMatchMany { count: n } => BindingMatchMany { count: n + 1 } - } - } else { acc } - }) -} - // THE SINGLE SELECTION DECISION, called by the dispatcher for every operation issued while a modeled // realization is active. Grant coverage first (the same covering_grant REST replay uses), then the -// named realization, then exactly one binding for the resolved identity. +// named realization, then the one binding the admitted index holds for the resolved identity. fn operation_handler_selection( env: Envelope, realization: OperationRealization, + index: Map>, invocation: BoundOperationInvocation, readonly: Bool, ) -> OperationHandlerSelection { @@ -163,10 +146,9 @@ fn operation_handler_selection( if named != (realization.identity as String) { OperationHandlerRefused { cause: OperationRealizationMismatch { at: at, named: named, active: realization.identity } } } else { - match binding_match(bindings: realization.bindings, at: at) { - BindingMatchNone => OperationHandlerRefused { cause: OperationNotBound { at: at } } - BindingMatchMany { count: n } => OperationHandlerRefused { cause: OperationBoundTwice { at: at, count: n } } - BindingMatchOne { binding: b } => OperationHandlerSelected { binding: b } + match map_get(index, operation_ref_key(at: at)) { + Absent => OperationHandlerRefused { cause: OperationNotBound { at: at } } + Present { value: b } => OperationHandlerSelected { binding: b } } } other => OperationHandlerRefused { cause: OperationBoundElsewhere { at: at, binding: other } } @@ -174,6 +156,14 @@ fn operation_handler_selection( } } +// THE BINDINGS BY IDENTITY, BUILT ONCE WHEN THE FRAME IS ADMITTED. Every dispatch then looks its +// operation up instead of scanning the binding list: the list is fixed for the frame's extent, so a +// per-dispatch scan re-derived the same fact on every call (DESIGN section 2). Admission has already +// refused a realization that binds one identity twice, so each key holds exactly one binding. +fn operation_realization_index(realization: OperationRealization) -> Map> { + fold(realization.bindings, init: empty_map(), f: fn(acc, b) { map_insert(acc, operation_ref_key(at: b.at), b) }) +} + // ADMISSION BEFORE THE SUBJECT RUNS: a realization binding one identity twice is malformed whatever // the subject later issues. Resolution of each binding against the operation registry is the // dispatcher's (it owns the registry); this is the part decidable from the value alone. diff --git a/src/v2/workflow/floor_cost_debt_admission.dag b/src/v2/workflow/floor_cost_debt_admission.dag index 3eddf41b4fb..ad0029f8426 100644 --- a/src/v2/workflow/floor_cost_debt_admission.dag +++ b/src/v2/workflow/floor_cost_debt_admission.dag @@ -103,60 +103,8 @@ data app_attest_interpreted_crypto_typed_admissions: List = [ - FloorCostDebtTypedAdmission { - identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_host_that_never_prints_a_census_refuses_at_the_deadline", - reason: "the subject is the real mtcollins1 boot entry (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1) evaluated end to end in the seed interpreter over the dry operation realization, as #12423 requires of the acceptance matrix; a narrower supplied value would drop the route under test; owner: the mtcollins1 boot acceptance matrix lane, discharged by the natively emitted evaluation frame" as NonEmptyStr, - }, - FloorCostDebtTypedAdmission { - identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_healthy_census_is_refused_at_the_sol_teardown", - reason: "the subject is the real mtcollins1 boot entry (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1) evaluated end to end in the seed interpreter over the dry operation realization, as #12423 requires of the acceptance matrix; a narrower supplied value would drop the route under test; owner: the mtcollins1 boot acceptance matrix lane, discharged by the natively emitted evaluation frame" as NonEmptyStr, - }, - FloorCostDebtTypedAdmission { - identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.an_already_presented_image_is_not_attached_again", - reason: "the subject is the real mtcollins1 boot entry (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1) evaluated end to end in the seed interpreter over the dry operation realization, as #12423 requires of the acceptance matrix; a narrower supplied value would drop the route under test; owner: the mtcollins1 boot acceptance matrix lane, discharged by the natively emitted evaluation frame" as NonEmptyStr, - }, - FloorCostDebtTypedAdmission { - identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.media_that_never_becomes_ready_refuses_before_any_power_action", - reason: "the subject is the real mtcollins1 boot entry (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1) evaluated end to end in the seed interpreter over the dry operation realization, as #12423 requires of the acceptance matrix; a narrower supplied value would drop the route under test; owner: the mtcollins1 boot acceptance matrix lane, discharged by the natively emitted evaluation frame" as NonEmptyStr, - }, - FloorCostDebtTypedAdmission { - identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.the_right_filename_on_the_wrong_share_refuses_before_any_power_action", - reason: "the subject is the real mtcollins1 boot entry (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1) evaluated end to end in the seed interpreter over the dry operation realization, as #12423 requires of the acceptance matrix; a narrower supplied value would drop the route under test; owner: the mtcollins1 boot acceptance matrix lane, discharged by the natively emitted evaluation frame" as NonEmptyStr, - }, - FloorCostDebtTypedAdmission { - identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_listing_that_names_the_image_twice_starts_nothing", - reason: "the subject is the real mtcollins1 boot entry (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1) evaluated end to end in the seed interpreter over the dry operation realization, as #12423 requires of the acceptance matrix; a narrower supplied value would drop the route under test; owner: the mtcollins1 boot acceptance matrix lane, discharged by the natively emitted evaluation frame" as NonEmptyStr, - }, - FloorCostDebtTypedAdmission { - identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_presentation_lost_after_readiness_stops_the_handoff", - reason: "the subject is the real mtcollins1 boot entry (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1) evaluated end to end in the seed interpreter over the dry operation realization, as #12423 requires of the acceptance matrix; a narrower supplied value would drop the route under test; owner: the mtcollins1 boot acceptance matrix lane, discharged by the natively emitted evaluation frame" as NonEmptyStr, - }, - FloorCostDebtTypedAdmission { - identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause", - reason: "the subject is the real mtcollins1 boot entry (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1) evaluated end to end in the seed interpreter over the dry operation realization, as #12423 requires of the acceptance matrix; a narrower supplied value would drop the route under test; owner: the mtcollins1 boot acceptance matrix lane, discharged by the natively emitted evaluation frame" as NonEmptyStr, - }, - FloorCostDebtTypedAdmission { - identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_sol_loss_mid_boot_is_reported_only_at_the_deadline", - reason: "the subject is the real mtcollins1 boot entry (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1) evaluated end to end in the seed interpreter over the dry operation realization, as #12423 requires of the acceptance matrix; a narrower supplied value would drop the route under test; owner: the mtcollins1 boot acceptance matrix lane, discharged by the natively emitted evaluation frame" as NonEmptyStr, - }, - FloorCostDebtTypedAdmission { - identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_two_socket_answer_is_a_truthful_topology_refusal", - reason: "the subject is the real mtcollins1 boot entry (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1) evaluated end to end in the seed interpreter over the dry operation realization, as #12423 requires of the acceptance matrix; a narrower supplied value would drop the route under test; owner: the mtcollins1 boot acceptance matrix lane, discharged by the natively emitted evaluation frame" as NonEmptyStr, - }, - FloorCostDebtTypedAdmission { - identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_an_interrupted_attempt_locks_out_the_next_one", - reason: "the subject is the real mtcollins1 boot entry (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1) evaluated end to end in the seed interpreter over the dry operation realization, as #12423 requires of the acceptance matrix; a narrower supplied value would drop the route under test; owner: the mtcollins1 boot acceptance matrix lane, discharged by the natively emitted evaluation frame" as NonEmptyStr, - }, -] - fn floor_cost_debt_typed_admissions() -> List { - concat(app_attest_interpreted_crypto_typed_admissions, mtcollins1_boot_matrix_typed_admissions) + app_attest_interpreted_crypto_typed_admissions } fn floor_cost_debt_typed_admitted_identities() -> List { diff --git a/src/v2/workflow/floor_eval_step_cost_drop.dag b/src/v2/workflow/floor_eval_step_cost_drop.dag index 76aa9c2b69c..e9e32abcabd 100644 --- a/src/v2/workflow/floor_eval_step_cost_drop.dag +++ b/src/v2/workflow/floor_eval_step_cost_drop.dag @@ -235,10 +235,11 @@ data floor_eval_step_cost_drop_python_to_typescript_inhabitance_rows: List = [ EvalStepCostDropMeasurement { identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_host_that_never_prints_a_census_refuses_at_the_deadline" as NonEmptyStr, @@ -256,14 +257,6 @@ data floor_eval_step_cost_drop_boot_matrix_rows: List List { From 52b285027a613a28d6c4eed47c0e90c81d165c7e Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 28 Sep 2026 14:57:22 +0000 Subject: [PATCH 11/38] Floor: import the map operations from v2.std.collection (map_lookup, the total form) Co-Authored-By: Claude Opus 5.5 (1M context) --- src/v2/std/operation_realization.dag | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/v2/std/operation_realization.dag b/src/v2/std/operation_realization.dag index 054eaa00866..225cc431f40 100644 --- a/src/v2/std/operation_realization.dag +++ b/src/v2/std/operation_realization.dag @@ -7,6 +7,7 @@ import std.effect_grant { } import v2.std.operation_argv { OperationRef, BoundOperationInvocation, InputText, InputTextList } import std.list { first_duplicate_by_key } +import v2.std.collection { empty_map, map_insert, map_lookup } import std.measure { Second, second, second_count } import std.checked_arithmetic { checked_int_to_nat } import std.execution_mode { ExecutionMode, Hermetic, Wet, Record } @@ -146,7 +147,7 @@ fn operation_handler_selection( if named != (realization.identity as String) { OperationHandlerRefused { cause: OperationRealizationMismatch { at: at, named: named, active: realization.identity } } } else { - match map_get(index, operation_ref_key(at: at)) { + match map_lookup(m: index, key: operation_ref_key(at: at)) { Absent => OperationHandlerRefused { cause: OperationNotBound { at: at } } Present { value: b } => OperationHandlerSelected { binding: b } } @@ -161,7 +162,7 @@ fn operation_handler_selection( // per-dispatch scan re-derived the same fact on every call (DESIGN section 2). Admission has already // refused a realization that binds one identity twice, so each key holds exactly one binding. fn operation_realization_index(realization: OperationRealization) -> Map> { - fold(realization.bindings, init: empty_map(), f: fn(acc, b) { map_insert(acc, operation_ref_key(at: b.at), b) }) + fold(realization.bindings, init: empty_map(), f: fn(acc, b) { map_insert(m: acc, key: operation_ref_key(at: b.at), value: b) }) } // ADMISSION BEFORE THE SUBJECT RUNS: a realization binding one identity twice is malformed whatever From 53c1ce69d62dff6ad4b78ee1faeb5ed0ab17d0b8 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 28 Sep 2026 16:53:39 +0000 Subject: [PATCH 12/38] Matrix cost: remember each frame's handler selections; advance does nothing when nothing is due Measured on the deadline case (temporary instrumentation, reverted): the modeled dispatch was ~110ms of ~243ms CPU, and handler selection ~60ms of that -- the covering_grant fold re-derived per dispatch for ~15 distinct operations. The selection reads only the operation identity and readonly flag besides frame-fixed inputs, so the slot keeps each decided selection keyed by that complete identity. The world advance short-circuits when no BMC event, media transition or console line is due. Deadline case now 214ms local (was 307/284ms on CI runs). Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/gunbc/bmc_model.dag | 7 ++- .../mtcollins1_boot_dry_realization.dag | 19 ++++++- src/v1/stage0/src/v1_interpreter.rs | 57 ++++++++++++++----- 3 files changed, 65 insertions(+), 18 deletions(-) diff --git a/dag/gunbc/bmc_model.dag b/dag/gunbc/bmc_model.dag index a82ce78dfc8..7fd22b6d3df 100644 --- a/dag/gunbc/bmc_model.dag +++ b/dag/gunbc/bmc_model.dag @@ -167,8 +167,13 @@ fn bmc_apply_event(world: BmcWorld, event: BmcEvent, at: Second) -> BmcWorld { } } -// Every event due at or before `now` fires, in schedule order, and moves from pending to fired. +// Every event due at or before `now` fires, in schedule order, and moves from pending to fired. With +// nothing pending there is nothing to fire, and the world is returned as it is rather than rebuilt. fn bmc_advance(world: BmcWorld, now: Second) -> BmcWorld { + if count(world.pending) == 0 { world } else { bmc_advance_pending(world: world, now: now) } +} + +fn bmc_advance_pending(world: BmcWorld, now: Second) -> BmcWorld { fold(world.pending, init: bmc_with_pending(world: world, pending: [], fired: world.fired), f: fn(acc, e) { if second_count(s: e.at) <= second_count(s: now) { let applied = bmc_apply_event(world: acc, event: e.event, at: e.at) diff --git a/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag b/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag index 1fbd1389723..28c827c2301 100644 --- a/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag +++ b/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag @@ -212,17 +212,23 @@ fn uptime_handler(w: MtCollins1BootWorld, call: OperationCall) -> OperationStep< // boot whose offset has passed is printed; it reaches each live collector's capture only if the SOL // session is open. A new boot restarts the console from its first line. fn console_advance(w: MtCollins1BootWorld, now: Second) -> MtCollins1BootWorld { + if count(w.console.lines) == 0 { w } else { console_advance_booted(w: w, now: now) } +} + +fn console_advance_booted(w: MtCollins1BootWorld, now: Second) -> MtCollins1BootWorld { match w.bmc.host_booted_at { Absent => w Present { value: booted } => { let fresh = match w.console.boot { Present { value: b } => second_count(s: b) != second_count(s: booted) Absent => true } let console = if fresh { ModeledHostConsole { lines: w.console.lines, emitted: 0, boot: Present { value: booted } } } else { w.console } + if !fresh && console.emitted >= count(console.lines) { w } else { let elapsed = (second_count(s: now) as Int) - (second_count(s: booted) as Int) let due = filter(console.lines.skip(n: console.emitted), l => (second_count(s: l.after) as Int) <= elapsed) let printed = concat_text(lines: due) let listening = w.bmc.sol_session_open && bmc_power_is_on(world: w.bmc) let fs = if listening { fold(live_collectors(w: w), init: w.fs, f: fn(acc, p) { appended(fs: acc, path: p.capture_path, text: printed) }) } else { w.fs } with_console(w: with_fs(w: w, fs: fs), console: ModeledHostConsole { lines: console.lines, emitted: console.emitted + count(due), boot: console.boot }) + } } } } @@ -235,7 +241,7 @@ fn concat_text(lines: List) -> String { // or by a deactivate -- the `ipmitool sol activate` client loses its session and exits, so its /proc // entry disappears; its pid file stays, since nothing in the realization removes it. fn collectors_follow_session(w: MtCollins1BootWorld) -> MtCollins1BootWorld { - if w.bmc.sol_session_open { w } else { + if w.bmc.sol_session_open || count(live_collectors(w: w)) == 0 { w } else { let fs = fold(live_collectors(w: w), init: w.fs, f: fn(acc, p) { ModeledFilesystem { directories: acc.directories, files: filter(acc.files, f => f.path != proc_cmdline_path(pid: p.pid)) } }) @@ -243,8 +249,17 @@ fn collectors_follow_session(w: MtCollins1BootWorld) -> MtCollins1BootWorld { } } +// Time only changes the world when something is scheduled -- a BMC event, a connecting or withdrawing +// presentation -- or when the console has lines left to print; otherwise the world is returned as it +// is, rather than rebuilt on every dispatch. +fn boot_world_idle(w: MtCollins1BootWorld) -> Bool { + count(w.bmc.pending) == 0 + && (match w.media.cd.ready_at { Absent => true Present { value: _ } => false }) + && (match w.media.withdraw_at { Absent => true Present { value: _ } => false }) +} + fn boot_advance(w: MtCollins1BootWorld, now: Second) -> MtCollins1BootWorld { - let stepped = with_media(w: with_bmc(w: w, bmc: bmc_advance(world: w.bmc, now: now)), media: megarac_media_advance(world: w.media, now: now)) + let stepped = if boot_world_idle(w: w) { w } else { with_media(w: with_bmc(w: w, bmc: bmc_advance(world: w.bmc, now: now)), media: megarac_media_advance(world: w.media, now: now)) } console_advance(w: collectors_follow_session(w: stepped), now: now) } diff --git a/src/v1/stage0/src/v1_interpreter.rs b/src/v1/stage0/src/v1_interpreter.rs index 15e2acc0f48..1c07976a545 100644 --- a/src/v1/stage0/src/v1_interpreter.rs +++ b/src/v1/stage0/src/v1_interpreter.rs @@ -7861,6 +7861,12 @@ struct ModeledRealizationSlot { /// The realization's bindings by operation identity (`operation_realization_index`), built /// once at admission so no dispatch rescans the binding list. index: Value, + /// Handler selections already decided in this frame, keyed by the COMPLETE input of + /// `operation_handler_selection` that varies: the operation's declaring file, service, + /// operation and whether it is readonly. The envelope, the realization and its index are fixed + /// for the frame's extent and the selection reads nothing else of the invocation, so a hit is + /// the same fact recomputed, never a different one. + selections: HashMap, identity: String, state: Value, /// The virtual clock, an opaque `std.measure` `Second`: the dispatcher never reads its @@ -8097,6 +8103,7 @@ fn admit_modeled_realization( envelope, realization, index, + selections: HashMap::new(), identity, state, now, @@ -8248,21 +8255,41 @@ fn dispatch_modeled_operation( }); record }; - let selection = run_in_context_with_args( - ctx, - "operation_handler_selection", - &[ - (Some("env".to_string()), envelope), - (Some("realization".to_string()), realization.clone()), - (Some("index".to_string()), index), - (Some("invocation".to_string()), invocation.clone()), - ( - Some("readonly".to_string()), - Value::Bool(op_declared_readonly(op_node, ctx)), - ), - ], - false, - )?; + let readonly = op_declared_readonly(op_node, ctx); + let selection_key = format!( + "{}#{}.{}#{}", + op_node.span.file, service_name, op_name, readonly + ); + let remembered = MODELED_REALIZATION_SLOTS.with(|slots| { + slots.borrow().last().and_then(|slot| { + slot.as_ref() + .and_then(|s| s.selections.get(&selection_key).cloned()) + }) + }); + let selection = match remembered { + Some(v) => v, + None => { + let decided = run_in_context_with_args( + ctx, + "operation_handler_selection", + &[ + (Some("env".to_string()), envelope), + (Some("realization".to_string()), realization.clone()), + (Some("index".to_string()), index), + (Some("invocation".to_string()), invocation.clone()), + (Some("readonly".to_string()), Value::Bool(readonly)), + ], + false, + )?; + MODELED_REALIZATION_SLOTS.with(|slots| { + if let Some(Some(slot)) = slots.borrow_mut().last_mut() { + slot.selections + .insert(selection_key.clone(), decided.clone()); + } + }); + decided + } + }; let (arm, fields) = variant_parts(ctx, &selection) .ok_or_else(|| modeled_refused(&key, "handler selection returned a malformed value"))?; let binding = match arm.as_str() { From 60dbd2b81cec64013691fed1acf36b13c4b8939b Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 28 Sep 2026 18:10:40 +0000 Subject: [PATCH 13/38] mtcollins1 unit hold: a boot's hold names its process, and a successor recovers it only once that process is observed dead (#12533 finding 2) Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/extdeps/linux/proc_pid_stat.dag | 17 + .../durable_exclusive_hold_file_store.dag | 44 +++ .../mtcollins1_maintenance_hold.dag | 296 +++++++++++++++++- dag/std/durable_exclusive_hold.dag | 69 ++++ .../durable_exclusive_hold_witness_test.dag | 55 +++- ...collins1_maintenance_hold_witness_test.dag | 31 +- 6 files changed, 491 insertions(+), 21 deletions(-) diff --git a/dag/extdeps/linux/proc_pid_stat.dag b/dag/extdeps/linux/proc_pid_stat.dag index bcb5e0ab8bd..ba216f91da5 100644 --- a/dag/extdeps/linux/proc_pid_stat.dag +++ b/dag/extdeps/linux/proc_pid_stat.dag @@ -112,3 +112,20 @@ fn proc_pid_stat_start_time(content: String) -> ProcPidStatStartTime { fn process_start_epoch_seconds(boot_epoch_seconds: EpochSecs, ticks_per_second: Hertz, start_ticks: Int) -> Int { boot_epoch_seconds + (start_ticks / hertz_count(h: ticks_per_second)) } + +// FIELD 1, pid (proc_pid_stat(5)): "The process ID." It precedes comm, so it is the text before the +// FIRST " (" -- comm is parenthesized and may contain anything, but nothing precedes it except the pid +// and one space. Read through /proc/self/stat this is how a process learns its own pid from the same +// file that carries its starttime, so the pair is one reading of one process, not two. +type ProcPidStatPid + = PidObserved { pid: Int } + | PidUnparseable { field: String } + +fn proc_pid_stat_pid(content: String) -> ProcPidStatPid { + let line = trim(s: content) + let field = match split(s: line, delimiter: " ").first() { Present { value: w } => w Absent => "" } + match parse_int(s: field) { + Present { value: pid } => if pid > 0 { PidObserved { pid: pid } } else { PidUnparseable { field: field } } + Absent => PidUnparseable { field: field } + } +} diff --git a/dag/gunbc/durable_exclusive_hold_file_store.dag b/dag/gunbc/durable_exclusive_hold_file_store.dag index 5f6e68cef07..7063c99497d 100644 --- a/dag/gunbc/durable_exclusive_hold_file_store.dag +++ b/dag/gunbc/durable_exclusive_hold_file_store.dag @@ -23,6 +23,7 @@ module gunbc.durable_exclusive_hold_file_store // pins the specimens byte-for-byte. import std.types { NonEmptyStr, String } +import std.decl_ref { decl_ref } import std.durable_compare_and_set { CasExpectation, ExpectSlotGeneration, CasCommitted, CasPreconditionFailed, CasStoreRefused, CasStoreFailure, CasSlotObservationRefused, @@ -38,6 +39,8 @@ import std.durable_exclusive_hold { HoldAcquireOccupied, HoldAcquireSlotUndecodable, HoldAcquireObservationUnavailable, HoldReleaseEligible, HoldReleaseRefused, DurableHoldReleaseRefusal, durable_hold_acquire_assess, durable_hold_release_assess, + DurableHoldHolderReport, DurableHoldRecoveryRefusal, HoldRecoveryEligible, HoldRecoveryRefused, + durable_hold_recovery_assess, } import gunbc.durable_cas_file_store { DefaultAccessCreateOnly, DerivedPayloadAdmitted, DerivedPayloadKeyNotSlotAddressable, @@ -323,3 +326,44 @@ fn file_hold_release_commit(plan: FileHoldReleasePlan) -> FileHoldReleaseOutcome } } } + +// RECOVERING A DEAD HOLDER'S HOLD FREES IT THROUGH THE SAME PLAN AND COMMIT AS A RELEASE: the write +// is identical -- held at N becomes free at N+1 -- and only the eligibility differs (the holder was +// observed dead rather than presenting itself). So the plan is minted here from THIS live read, bound +// to the report's holder and generation, and committed by file_hold_release_commit, which still tells +// a slot that moved after this read (Lost) from a store fault. The released_by carries the dead holder +// and the evidence, so the freed slot says why it was freed. +// +// The report is a verdict about the world that only the observing consumer can take, so this mint +// admits its callers by name: a caller that could hand it an authored HolderObservedDead would be the +// forged-release path the release plan's sole_constructor exists to close. +type FileHoldRecoveryAssessment + = FileHoldRecoveryPlanned { plan: FileHoldReleasePlan, dead_holder: DurableHoldOwnerRef, evidence: NonEmptyStr } + | FileHoldRecoveryNotEligible { slot_key: NonEmptyStr, refusal: DurableHoldRecoveryRefusal } + +fn file_hold_recovery_assess( + root: NonEmptyStr, + slot_key: NonEmptyStr, + report: DurableHoldHolderReport, + released_by: DurableHoldReleaseRef, +) -> FileHoldRecoveryAssessment + admit_callers: [ + decl_ref(module_path: "gunbc.machine_intake_mtcollins1_maintenance_hold", decl_name: "unit_hold_recover_dead_holder"), + ] +{ + match durable_hold_recovery_assess(observed: observe_file_hold(root: root, key: slot_key), report: report) { + HoldRecoveryRefused { refusal: r } => FileHoldRecoveryNotEligible { slot_key: slot_key, refusal: r } + HoldRecoveryEligible { generation: g, dead_holder: h, evidence: e } => + FileHoldRecoveryPlanned { + plan: FileHoldReleasePlan { + root: root, + slot_key: slot_key, + owner: h, + generation: g, + free_payload: hold_slot_payload(state: DurableHoldFree { released_by: released_by }), + }, + dead_holder: h, + evidence: e, + } + } +} diff --git a/dag/gunbc/machine_intake/mtcollins1_maintenance_hold.dag b/dag/gunbc/machine_intake/mtcollins1_maintenance_hold.dag index 99d0b48bc47..1c4220eb2e3 100644 --- a/dag/gunbc/machine_intake/mtcollins1_maintenance_hold.dag +++ b/dag/gunbc/machine_intake/mtcollins1_maintenance_hold.dag @@ -7,6 +7,22 @@ import v2.std.optional { Present, Absent } import std.durable_exclusive_hold { DurableHoldOwnerRef, DurableHoldReleaseRef, HoldSlotAbsent, HoldSlotFree, HoldSlotHeld, HoldSlotUndecodable, HoldObservationUnavailable, + DurableHoldHolderLiveness, HolderObservedLive, HolderObservedDead, HolderLivenessUnobservable, + DurableHoldHolderReport, DurableHoldRecoveryRefusal, + HoldRecoveryNothingHeld, HoldRecoveryHolderLive, HoldRecoveryHolderUnobservable, HoldRecoverySlotMoved, + HoldRecoveryReportForAnotherHolder, HoldRecoverySlotUndecodable, HoldRecoveryObservationUnavailable, +} +import std.nat { Nat } +import std.checked_arithmetic { checked_int_to_nat } +import std.algebra { trim } +import gunbc.build_cache_instance { ProcessIdentity, process_identity_eq } +import extdeps.linux.proc_pid_stat { + proc_pid_stat_start_time, StartTimeObserved, StartTimeCommUnterminated, StartTimeFieldAbsent, StartTimeUnparseable, + proc_pid_stat_pid, PidObserved, PidUnparseable, +} +import extdeps.filesystem.filesystem_io { + Filesystem, filesystem_exact_read, FilesystemExactRead, FilesystemExactPathRead, FilesystemExactPathAbsent, + FilesystemExactPathUnreadable, FilesystemExactPathKindUnrecognized, } import gunbc.durable_exclusive_hold_file_store { FileHoldAcquireOutcome, FileHoldAcquired, FileHoldOccupied, FileHoldAcquireLost, @@ -14,6 +30,7 @@ import gunbc.durable_exclusive_hold_file_store { FileHoldReleasePlanned, FileHoldReleaseNotEligible, FileHoldReleased, FileHoldReleaseLost, FileHoldReleaseStoreUnavailable, FileHoldReleaseKeyNotSlotAddressable, file_hold_acquire, file_hold_release_assess, file_hold_release_commit, observe_file_hold, + FileHoldRecoveryPlanned, FileHoldRecoveryNotEligible, file_hold_recovery_assess, } import gunbc.actions_run_binding { actions_variable_trimmed } import std.decl_ref { decl_ref } @@ -52,23 +69,66 @@ data mtcollins1_unit_hold_store_host: NonEmptyStr = operator_host_srv1 as NonEmp // WHO HOLDS THE UNIT IS A DECLARED SUM (review 70459), each kind carrying its detail. The durable // owner string is RENDERED from it and DECODED back to it here, in one place: nothing else builds an // owner string or reads a kind out of one. +// +// A BOOT RUN IS A PROCESS, SO ITS OWNER NAMES THE PROCESS (gunbc#12533 finding 2). A run id alone +// says which attempt took the slot and nothing about whether that attempt is still running, so a +// worker killed while holding the unit left a hold nobody could ever show to be dead, and every later +// attempt refused on it. The boot's owner therefore carries the holding process's identity -- boot id, +// pid and start time, gunbc.build_cache_instance ProcessIdentity, the triple that survives pid reuse +// and reboots -- and a successor recovers the slot only after OBSERVING that process gone. An operator +// maintenance hold is deliberately not a process (it outlives the session that took it), so it is +// never recovered; a host reset-return hold names no process yet, so it is never recovered either. type UnitHoldOwner = OperatorMaintenance { reason: NonEmptyStr } - | BootRun { run_id: NonEmptyStr } + | BootRun { run_id: NonEmptyStr, process: ProcessIdentity } | HostResetReturn { attempt: NonEmptyStr } fn unit_hold_owner_tag(o: UnitHoldOwner) -> String { match o { OperatorMaintenance { reason: _ } => "operator-maintenance:" - BootRun { run_id: _ } => "mtcollins1-boot:" + BootRun { run_id: _, process: _ } => "mtcollins1-boot:" HostResetReturn { attempt: _ } => "host-reset-return:" } } +// THE PROCESS PART OF A BOOT OWNER, rendered after the run id and decoded back here and nowhere else: +// `@boot=,pid=,start=`. +data unit_hold_process_marker: String = "@boot=" + +fn unit_hold_process_text(p: ProcessIdentity) -> String { + join([unit_hold_process_marker, p.boot_id as String, ",pid=", to_string(p.pid), ",start=", p.start_time as String], "") +} + +fn unit_hold_process_decode(text: String) -> ProcessIdentity? { + let parts = split(s: text, delimiter: ",") + if count(parts) != 3 { + none + } else { + let b = parts[0] + let pid_text = parts[1] + let start_text = parts[2] + if !starts_with(s: b, prefix: "boot=") || !starts_with(s: pid_text, prefix: "pid=") || !starts_with(s: start_text, prefix: "start=") { + none + } else { + let boot = substring(s: b, start: 5, end: string_length(b)) + let start = substring(s: start_text, start: 6, end: string_length(start_text)) + match parse_int(s: substring(s: pid_text, start: 4, end: string_length(pid_text))) { + Absent => none + Present { value: n } => + match checked_int_to_nat(n: n) { + Absent => none + Present { value: pid } => + if boot == "" || start == "" { none } else { Present { value: ProcessIdentity { boot_id: boot as NonEmptyStr, pid: pid, start_time: start as NonEmptyStr } } } + } + } + } + } +} + fn unit_hold_owner_detail(o: UnitHoldOwner) -> String { match o { OperatorMaintenance { reason: r } => r as String - BootRun { run_id: r } => r as String + BootRun { run_id: r, process: p } => concat(r as String, unit_hold_process_text(p: p)) HostResetReturn { attempt: a } => a as String } } @@ -80,7 +140,7 @@ fn unit_hold_owner_ref(o: UnitHoldOwner) -> DurableHoldOwnerRef { fn unit_hold_owner_label(o: UnitHoldOwner) -> String { match o { OperatorMaintenance { reason: r } => concat("operator maintenance: ", r as String) - BootRun { run_id: r } => concat("the mtcollins1 boot run ", r as String) + BootRun { run_id: r, process: p } => join(["the mtcollins1 boot run ", r as String, " (process ", to_string(p.pid), " on boot ", p.boot_id as String, ")"], "") HostResetReturn { attempt: a } => concat("host reset-return attempt ", a as String) } } @@ -104,7 +164,20 @@ fn decode_unit_hold_owner(owner: DurableHoldOwnerRef) -> UnitHoldOwnerDecode { if starts_with(s: raw, prefix: op) { match unit_hold_owner_detail_after(raw: raw, tag: op) { Present { value: d } => UnitHoldOwnerDecoded { owner: OperatorMaintenance { reason: d } } Absent => UnitHoldOwnerUnrecognized { raw: raw } } } else if starts_with(s: raw, prefix: boot) { - match unit_hold_owner_detail_after(raw: raw, tag: boot) { Present { value: d } => UnitHoldOwnerDecoded { owner: BootRun { run_id: d } } Absent => UnitHoldOwnerUnrecognized { raw: raw } } + match unit_hold_owner_detail_after(raw: raw, tag: boot) { + Absent => UnitHoldOwnerUnrecognized { raw: raw } + Present { value: d } => { + let halves = split(s: d as String, delimiter: unit_hold_process_marker) + if count(halves) != 2 || halves[0] == "" { + UnitHoldOwnerUnrecognized { raw: raw } + } else { + match unit_hold_process_decode(text: concat("boot=", halves[1])) { + Present { value: p } => UnitHoldOwnerDecoded { owner: BootRun { run_id: halves[0] as NonEmptyStr, process: p } } + Absent => UnitHoldOwnerUnrecognized { raw: raw } + } + } + } + } } else if starts_with(s: raw, prefix: reset) { match unit_hold_owner_detail_after(raw: raw, tag: reset) { Present { value: d } => UnitHoldOwnerDecoded { owner: HostResetReturn { attempt: d } } Absent => UnitHoldOwnerUnrecognized { raw: raw } } } else { @@ -123,15 +196,16 @@ fn operator_maintenance_owner(reason: NonEmptyStr) -> DurableHoldOwnerRef { unit_hold_owner_ref(o: OperatorMaintenance { reason: reason }) } -fn boot_run_owner(run_id: NonEmptyStr) -> DurableHoldOwnerRef { - unit_hold_owner_ref(o: BootRun { run_id: run_id }) +fn boot_run_owner(run_id: NonEmptyStr, process: ProcessIdentity) -> DurableHoldOwnerRef { + unit_hold_owner_ref(o: BootRun { run_id: run_id, process: process }) } // ── THE PROOF: MINTED ONLY INSIDE THE LIVE ACQUIRE'S FileHoldAcquired ARM ────────────────────── // Every function that performs a BMC write on a hold-gated unit takes a UnitHoldProof, and the // proof is sole_constructor: the compiler refuses a UnitHoldProof literal anywhere but this module // (SoleConstructorViolation), and this module mints one in exactly one place -- the FileHoldAcquired -// arm of unit_hold_acquire, right after the live compare-and-set landed. A supplied or authored +// arm of unit_hold_minted, admitted only to unit_hold_acquire and fed only that call's own live +// compare-and-set outcomes. A supplied or authored // acquire outcome therefore cannot become a proof: the pure refusal fold below reads outcomes, and // it returns text, never a proof. test.claim.machine_intake.mtcollins1_maintenance_hold_witness_test // enrolls test.probe.unit_hold_proof_forged_probe, where a forged proof is refused at the literal. @@ -164,15 +238,175 @@ fn unit_hold_refusal(outcome: FileHoldAcquireOutcome) -> NonEmptyStr? { } } -// THE ONE MINT. The live acquire and the proof are one boundary: the proof exists only when the -// compare-and-set this call performed committed a hold for this owner. -fn unit_hold_acquire(root: NonEmptyStr, key: NonEmptyStr, owner: DurableHoldOwnerRef) -> UnitHoldAcquisition +// ── WHETHER A HOLDING PROCESS STILL RUNS, OBSERVED ───────────────────────────────────────────── +// The store and every holder that can be recovered run on one host (mtcollins1_unit_hold_store_host; +// the boot admission refuses any other), so the holder's process is observable through this host's +// procfs. Three reads decide it, each typed by the host's own error kind: +// - /proc/sys/kernel/random/boot_id differs from the holder's boot -> the host rebooted since, so +// every process of that boot is gone: OBSERVED DEAD; +// - same boot, /proc//stat is ABSENT (the host answered NotFound) -> no such process: DEAD; +// - same boot, the stat reads and its starttime differs -> the pid was reused: DEAD; +// - same boot, same starttime -> the holder runs: LIVE. +// Any read the host refused for another reason, and any content that does not parse, is +// UNOBSERVABLE: the holder may well be alive, and nothing here concludes otherwise from a failed look. +data proc_boot_id_path: String = "/proc/sys/kernel/random/boot_id" + +data proc_self_stat_path: String = "/proc/self/stat" + +fn exact_read_of(path: String) -> FilesystemExactRead { + let read = Filesystem.Read(path: path) + filesystem_exact_read(path: path, content: read.content, success: read.success, error: read.error, error_kind: read.error_kind) +} + +type ExactText + = ExactTextRead { text: String } + | ExactTextAbsent + | ExactTextUnread { cause: NonEmptyStr } + +fn exact_text(path: String) -> ExactText { + match exact_read_of(path: path) { + FilesystemExactPathRead { path: _, content: c } => ExactTextRead { text: c } + FilesystemExactPathAbsent { path: _ } => ExactTextAbsent + FilesystemExactPathUnreadable { path: _, kind: _, error: e } => ExactTextUnread { cause: join([path, " could not be read: ", e], "") as NonEmptyStr } + FilesystemExactPathKindUnrecognized { path: _, observed: o, error: e } => ExactTextUnread { cause: join([path, " failed with an unrecognized error kind ", o, ": ", e], "") as NonEmptyStr } + } +} + +fn start_time_text(stat: String) -> String? { + match proc_pid_stat_start_time(content: stat) { + StartTimeObserved { ticks_since_boot: t } => Present { value: to_string(t) } + _ => none + } +} + +fn holder_process_liveness(p: ProcessIdentity) -> DurableHoldHolderLiveness { + match exact_text(path: proc_boot_id_path) { + ExactTextAbsent => HolderLivenessUnobservable { cause: concat(proc_boot_id_path, " is absent on this host") as NonEmptyStr } + ExactTextUnread { cause: c } => HolderLivenessUnobservable { cause: c } + ExactTextRead { text: t } => { + let now = trim(s: t) + if now == "" { + HolderLivenessUnobservable { cause: concat(proc_boot_id_path, " was empty") as NonEmptyStr } + } else if now != (p.boot_id as String) { + HolderObservedDead { evidence: join(["the host has booted since the holder started (boot ", p.boot_id as String, ", now ", now, ")"], "") as NonEmptyStr } + } else { + let stat_path = join(["/proc/", to_string(p.pid), "/stat"], "") + match exact_text(path: stat_path) { + ExactTextAbsent => HolderObservedDead { evidence: join(["no process ", to_string(p.pid), " on the holder's boot (", stat_path, " not found)"], "") as NonEmptyStr } + ExactTextUnread { cause: c } => HolderLivenessUnobservable { cause: c } + ExactTextRead { text: stat } => + match start_time_text(stat: stat) { + Absent => HolderLivenessUnobservable { cause: concat(stat_path, " carried no readable starttime") as NonEmptyStr } + Present { value: st } => + if st == (p.start_time as String) { + HolderObservedLive { evidence: join(["process ", to_string(p.pid), " started at tick ", st, " still runs"], "") as NonEmptyStr } + } else { + HolderObservedDead { evidence: join(["pid ", to_string(p.pid), " now names another process (start tick ", st, ", holder's ", p.start_time as String, ")"], "") as NonEmptyStr } + } + } + } + } + } + } +} + +// THIS PROCESS'S OWN IDENTITY, read from the same procfs a successor will read it back through: the +// boot id, and pid and starttime from ONE read of /proc/self/stat. A boot that cannot name itself does +// not take the unit -- a hold it could never be shown dead in is the lockout this replaces. +type SelfProcessIdentity + = SelfIdentified { process: ProcessIdentity } + | SelfUnidentified { cause: NonEmptyStr } + +fn self_process_identity() -> SelfProcessIdentity { + match exact_text(path: proc_boot_id_path) { + ExactTextAbsent => SelfUnidentified { cause: concat(proc_boot_id_path, " is absent on this host") as NonEmptyStr } + ExactTextUnread { cause: c } => SelfUnidentified { cause: c } + ExactTextRead { text: b } => { + let boot = trim(s: b) + match exact_text(path: proc_self_stat_path) { + ExactTextAbsent => SelfUnidentified { cause: concat(proc_self_stat_path, " is absent on this host") as NonEmptyStr } + ExactTextUnread { cause: c } => SelfUnidentified { cause: c } + ExactTextRead { text: stat } => + match proc_pid_stat_pid(content: stat) { + PidUnparseable { field: f } => SelfUnidentified { cause: concat("/proc/self/stat carried no pid: ", f) as NonEmptyStr } + PidObserved { pid: n } => + match start_time_text(stat: stat) { + Absent => SelfUnidentified { cause: "/proc/self/stat carried no readable starttime" as NonEmptyStr } + Present { value: st } => + match checked_int_to_nat(n: n) { + Absent => SelfUnidentified { cause: "/proc/self/stat pid is not a natural number" as NonEmptyStr } + Present { value: pid } => + if boot == "" { SelfUnidentified { cause: concat(proc_boot_id_path, " was empty") as NonEmptyStr } } + else { SelfIdentified { process: ProcessIdentity { boot_id: boot as NonEmptyStr, pid: pid, start_time: st as NonEmptyStr } } } + } + } + } + } + } + } +} + +// ── RECOVERING A HOLD WHOSE HOLDER WAS OBSERVED DEAD ─────────────────────────────────────────── +// Only a holder whose owner names a process can be observed, so only a decoded BootRun is looked at; +// every other owner is reported as not recoverable. The liveness verdict is bound to the holder and +// generation the refused acquire saw, and the recovery re-reads the slot, so a hold that changed hands +// in between is not the one freed. Age is never an input. +fn holder_report(holder: DurableHoldOwnerRef, generation: CasGeneration) -> DurableHoldHolderReport { + let liveness = match decode_unit_hold_owner(owner: holder) { + UnitHoldOwnerDecoded { owner: BootRun { run_id: _, process: p } } => holder_process_liveness(p: p) + UnitHoldOwnerDecoded { owner: OperatorMaintenance { reason: _ } } => + HolderLivenessUnobservable { cause: "an operator maintenance hold is not bound to a process; only the operator releases it" as NonEmptyStr } + UnitHoldOwnerDecoded { owner: HostResetReturn { attempt: _ } } => + HolderLivenessUnobservable { cause: "a host reset-return hold names no process, so its holder cannot be observed" as NonEmptyStr } + UnitHoldOwnerUnrecognized { raw: _ } => + HolderLivenessUnobservable { cause: "the holder's owner names no process this module can observe (it was written without one, or not by this module)" as NonEmptyStr } + } + DurableHoldHolderReport { holder: holder, generation: generation, liveness: liveness } +} + +type DeadHolderRecovery + = DeadHolderRecovered { dead_holder: DurableHoldOwnerRef, evidence: NonEmptyStr } + | DeadHolderNotRecovered { reason: NonEmptyStr } + +fn recovery_refusal_text(key: NonEmptyStr, r: DurableHoldRecoveryRefusal) -> NonEmptyStr { + let why = match r { + HoldRecoveryNothingHeld => "the slot was released before recovery" + HoldRecoveryHolderLive { generation: _, holder: h, evidence: e } => join(["is held by '", h as String, "', which is alive (", e as String, "); no BMC actuation until it is released"], "") + HoldRecoveryHolderUnobservable { generation: _, holder: h, cause: c } => join(["is held by '", h as String, "', whose liveness could not be observed (", c as String, "); an unobserved holder is not a dead one, so the hold stands"], "") + HoldRecoverySlotMoved { reported: _, observed: _ } => "moved while its holder was being observed; re-run" + HoldRecoveryReportForAnotherHolder { holder: h, reported: _ } => join(["changed hands to '", h as String, "' while its holder was being observed; re-run"], "") + HoldRecoverySlotUndecodable { generation: _, detail: d } => concat("became undecodable: ", d as String) + HoldRecoveryObservationUnavailable { cause: _ } => "could not be re-read for recovery; an unreadable interlock is not a released one" + } + join(["unit ", key as String, " ", why, "; controller untouched"], "") as NonEmptyStr +} + +fn unit_hold_recover_dead_holder(root: NonEmptyStr, key: NonEmptyStr, holder: DurableHoldOwnerRef, generation: CasGeneration) -> DeadHolderRecovery admit_callers: [ - decl_ref(module_path: "gunbc.machine_intake_mtcollins1_maintenance_hold", decl_name: "mtcollins1_boot_acquire_unit_hold"), - decl_ref(module_path: "gunbc.host_reset_return_run", decl_name: "host_reset_return_wet"), + decl_ref(module_path: "gunbc.machine_intake_mtcollins1_maintenance_hold", decl_name: "unit_hold_acquire"), + ] +{ + let report = holder_report(holder: holder, generation: generation) + let released_by = join(["recovered-dead-holder:", holder as String], "") as DurableHoldReleaseRef + match file_hold_recovery_assess(root: root, slot_key: key, report: report, released_by: released_by) { + FileHoldRecoveryNotEligible { slot_key: _, refusal: r } => DeadHolderNotRecovered { reason: recovery_refusal_text(key: key, r: r) } + FileHoldRecoveryPlanned { plan: p, dead_holder: h, evidence: e } => + match file_hold_release_commit(plan: p) { + FileHoldReleased { slot_key: _, generation: _ } => DeadHolderRecovered { dead_holder: h, evidence: e } + FileHoldReleaseLost { slot_key: _ } => DeadHolderNotRecovered { reason: join(["unit ", key as String, " moved before the dead holder's hold was freed; re-run; controller untouched"], "") as NonEmptyStr } + FileHoldReleaseStoreUnavailable { slot_key: _, cause: _ } => DeadHolderNotRecovered { reason: join(["unit hold store refused freeing the dead holder's hold on ", key as String, "; controller untouched"], "") as NonEmptyStr } + FileHoldReleaseKeyNotSlotAddressable { slot_key: _ } => DeadHolderNotRecovered { reason: join(["unit hold key ", key as String, " is not slot-addressable; controller untouched"], "") as NonEmptyStr } + } + } +} + +// The proof is built here, from an outcome only unit_hold_acquire's own live acquires produce: the +// admission is what keeps a supplied FileHoldAcquired from becoming a proof. +fn unit_hold_minted(root: NonEmptyStr, key: NonEmptyStr, outcome: FileHoldAcquireOutcome) -> UnitHoldAcquisition + admit_callers: [ + decl_ref(module_path: "gunbc.machine_intake_mtcollins1_maintenance_hold", decl_name: "unit_hold_acquire"), ] { - let outcome = file_hold_acquire(root: root, slot_key: key, requested_owner: owner) match outcome { FileHoldAcquired { slot_key: _, owner: o, generation: g } => UnitHeld { proof: UnitHoldProof { root: root, key: key, owner: o, generation: g } } @@ -184,6 +418,29 @@ fn unit_hold_acquire(root: NonEmptyStr, key: NonEmptyStr, owner: DurableHoldOwne } } +// THE ONE MINT. The live acquire and the proof are one boundary: the proof exists only when the +// compare-and-set this call performed committed a hold for this owner. An OCCUPIED slot is asked one +// further question -- is its holder observed dead? -- and only an observed-dead holder's hold is freed, +// after which this caller acquires once more like any acquirer. There is no loop: a slot taken by +// someone else in between refuses as occupied, naming them. +fn unit_hold_acquire(root: NonEmptyStr, key: NonEmptyStr, owner: DurableHoldOwnerRef) -> UnitHoldAcquisition + admit_callers: [ + decl_ref(module_path: "gunbc.machine_intake_mtcollins1_maintenance_hold", decl_name: "mtcollins1_boot_acquire_unit_hold"), + decl_ref(module_path: "gunbc.host_reset_return_run", decl_name: "host_reset_return_wet"), + ] +{ + let outcome = file_hold_acquire(root: root, slot_key: key, requested_owner: owner) + match outcome { + FileHoldOccupied { slot_key: _, holder: h, generation: g } => + match unit_hold_recover_dead_holder(root: root, key: key, holder: h, generation: g) { + DeadHolderNotRecovered { reason: r } => UnitHoldRefused { reason: r } + DeadHolderRecovered { dead_holder: _, evidence: _ } => + unit_hold_minted(root: root, key: key, outcome: file_hold_acquire(root: root, slot_key: key, requested_owner: owner)) + } + _ => unit_hold_minted(root: root, key: key, outcome: outcome) + } +} + fn unit_hold_proof_key(proof: UnitHoldProof) -> NonEmptyStr { proof.key } @@ -193,7 +450,12 @@ fn mtcollins1_boot_acquire_unit_hold(run_id: NonEmptyStr) -> UnitHoldAcquisition decl_ref(module_path: "gunbc.machine_intake_mtcollins1_boot_run", decl_name: "mtcollins1_boot_under_live_unit_hold"), ] { - unit_hold_acquire(root: unit_hold_store_root, key: mtcollins1_unit_hold_key, owner: boot_run_owner(run_id: run_id)) + match self_process_identity() { + SelfUnidentified { cause: c } => + UnitHoldRefused { reason: join(["this boot cannot name its own process (", c as String, "), and a hold it could never be shown dead in would lock the unit out if it died; controller untouched"], "") as NonEmptyStr } + SelfIdentified { process: p } => + unit_hold_acquire(root: unit_hold_store_root, key: mtcollins1_unit_hold_key, owner: boot_run_owner(run_id: run_id, process: p)) + } } // THE RELEASE CONSUMES THE PROOF AND KEEPS THE STEP'S OWN OUTCOME, saying when the hold was left: a @@ -266,8 +528,8 @@ fn mtcollins1_maintenance_hold_release() -> ProcessExit { match decode_unit_hold_owner(owner: o) { UnitHoldOwnerUnrecognized { raw: r } => exit_failure(reason: concat("mtcollins1 maintenance hold: held by an owner this module did not write, so it is not released from here: ", r)) - UnitHoldOwnerDecoded { owner: BootRun { run_id: r } } => - exit_failure(reason: concat("mtcollins1 maintenance hold: held by ", concat(unit_hold_owner_label(o: BootRun { run_id: r }), ", not by maintenance; that run frees its own hold"))) + UnitHoldOwnerDecoded { owner: BootRun { run_id: r, process: p } } => + exit_failure(reason: concat("mtcollins1 maintenance hold: held by ", concat(unit_hold_owner_label(o: BootRun { run_id: r, process: p }), ", not by maintenance; that run frees its own hold, and the next boot recovers it if that run is observed dead"))) UnitHoldOwnerDecoded { owner: HostResetReturn { attempt: a } } => exit_failure(reason: concat("mtcollins1 maintenance hold: held by ", concat(unit_hold_owner_label(o: HostResetReturn { attempt: a }), ", not by maintenance; that run frees its own hold"))) UnitHoldOwnerDecoded { owner: OperatorMaintenance { reason: _ } } => diff --git a/dag/std/durable_exclusive_hold.dag b/dag/std/durable_exclusive_hold.dag index e0bacffa70f..b6a1cb34e0a 100644 --- a/dag/std/durable_exclusive_hold.dag +++ b/dag/std/durable_exclusive_hold.dag @@ -169,3 +169,72 @@ fn durable_hold_release_assess( } } } + +// RECOVERY OF A HOLD WHOSE HOLDER WAS OBSERVED DEAD -- THE THIRD AMENDMENT (gunbc#12533 finding 2). +// +// Without it a holder that dies holding the slot holds it forever: acquire refuses while held, and +// only the holder may release. That is not a safe default but a lockout the protocol manufactured, +// because the holder's death is an observable fact about the world and nothing here could consume it. +// +// RECOVERY IS DECIDED BY AN OBSERVATION OF THE HOLDER, NEVER BY AGE. There is deliberately no clock, +// timestamp or duration anywhere in this assessment: a hold is never broken because it is old, only +// because its holder was OBSERVED gone. How a holder is observed is the consumer's (a process by boot, +// pid and start time; a lease by its declared deadline), so the protocol takes the verdict, not the +// method -- and takes it BOUND to the holder and generation it is about, so a verdict taken of one +// hold cannot recover another. Three verdicts, and the third is the reason there are three: a holder +// that could not be observed is not dead, and a recovery over it would be ⊤-as-ignorance. +type DurableHoldHolderLiveness + = HolderObservedLive { evidence: NonEmptyStr } + | HolderObservedDead { evidence: NonEmptyStr } + | HolderLivenessUnobservable { cause: NonEmptyStr } + +type DurableHoldHolderReport { + holder: DurableHoldOwnerRef + generation: CasGeneration + liveness: DurableHoldHolderLiveness +} + +// Each refusal names its own remedy: nothing held means acquire directly; a live holder means wait; +// an unobservable holder sends the caller to whatever prevented the look; a slot that moved since the +// report means the report is about a hold that no longer stands, whoever holds the slot now. +type DurableHoldRecoveryRefusal + = HoldRecoveryNothingHeld + | HoldRecoveryHolderLive { generation: CasGeneration, holder: DurableHoldOwnerRef, evidence: NonEmptyStr } + | HoldRecoveryHolderUnobservable { generation: CasGeneration, holder: DurableHoldOwnerRef, cause: NonEmptyStr } + | HoldRecoverySlotMoved { reported: CasGeneration, observed: CasGeneration } + | HoldRecoveryReportForAnotherHolder { holder: DurableHoldOwnerRef, reported: DurableHoldOwnerRef } + | HoldRecoverySlotUndecodable { generation: CasGeneration, detail: NonEmptyStr } + | HoldRecoveryObservationUnavailable { cause: CasUnreadableSlot } + +// The eligible arm frees the slot at the generation both the live read and the report name; it does +// not hand the slot to anyone. The successor then acquires through durable_hold_acquire_assess like +// every other acquirer, so there is still exactly one way a slot becomes held. +type DurableHoldRecoveryAssessment + = HoldRecoveryEligible { generation: CasGeneration, dead_holder: DurableHoldOwnerRef, evidence: NonEmptyStr } + | HoldRecoveryRefused { refusal: DurableHoldRecoveryRefusal } + +fn durable_hold_recovery_assess(observed: DurableHoldObservation, report: DurableHoldHolderReport) -> DurableHoldRecoveryAssessment { + match observed { + HoldSlotAbsent => HoldRecoveryRefused { refusal: HoldRecoveryNothingHeld } + HoldSlotFree { generation: _, released_by: _ } => HoldRecoveryRefused { refusal: HoldRecoveryNothingHeld } + HoldSlotUndecodable { generation: g, detail: d } => + HoldRecoveryRefused { refusal: HoldRecoverySlotUndecodable { generation: g, detail: d } } + HoldObservationUnavailable { cause: c } => + HoldRecoveryRefused { refusal: HoldRecoveryObservationUnavailable { cause: c } } + HoldSlotHeld { generation: g, owner: holder } => + if !durable_hold_owner_equal(left: holder, right: report.holder) { + HoldRecoveryRefused { refusal: HoldRecoveryReportForAnotherHolder { holder: holder, reported: report.holder } } + } else if cas_generation_count(g: g) != cas_generation_count(g: report.generation) { + HoldRecoveryRefused { refusal: HoldRecoverySlotMoved { reported: report.generation, observed: g } } + } else { + match report.liveness { + HolderObservedLive { evidence: e } => + HoldRecoveryRefused { refusal: HoldRecoveryHolderLive { generation: g, holder: holder, evidence: e } } + HolderLivenessUnobservable { cause: c } => + HoldRecoveryRefused { refusal: HoldRecoveryHolderUnobservable { generation: g, holder: holder, cause: c } } + HolderObservedDead { evidence: e } => + HoldRecoveryEligible { generation: g, dead_holder: holder, evidence: e } + } + } + } +} diff --git a/dag/test/claim/durable_exclusive_hold_witness_test.dag b/dag/test/claim/durable_exclusive_hold_witness_test.dag index 27b3af57801..8d696eaa972 100644 --- a/dag/test/claim/durable_exclusive_hold_witness_test.dag +++ b/dag/test/claim/durable_exclusive_hold_witness_test.dag @@ -7,7 +7,7 @@ import std.durable_compare_and_set { CasExpectation, ExpectSlotAbsent, ExpectSlotGeneration, CasSlotObservation, CasObservedReadable, CasObservedUnreadable, CasReadableAbsent, CasReadablePresent, CasSlotVersion, - CasUnreadableReadRefused, cas_generation_count, + CasUnreadableReadRefused, cas_generation_count, CasGeneration, } import std.durable_exclusive_hold { DurableHoldOwnerRef, DurableHoldReleaseRef, @@ -22,6 +22,10 @@ import std.durable_exclusive_hold { HoldReleaseHeldByAnotherOwner, HoldReleaseAgainstAnotherGeneration, HoldReleaseSlotUndecodable, HoldReleaseObservationUnavailable, durable_hold_acquire_assess, durable_hold_release_assess, durable_hold_owner_equal, + DurableHoldHolderReport, HolderObservedLive, HolderObservedDead, HolderLivenessUnobservable, + DurableHoldRecoveryAssessment, HoldRecoveryEligible, HoldRecoveryRefused, + HoldRecoveryNothingHeld, HoldRecoveryHolderLive, HoldRecoveryHolderUnobservable, HoldRecoverySlotMoved, + HoldRecoveryReportForAnotherHolder, durable_hold_recovery_assess, } import gunbc.durable_exclusive_hold_file_store { HoldPayloadDecode, HoldPayloadDecoded, HoldPayloadUndecodable, @@ -305,3 +309,52 @@ test fn holder_equality_is_exact() -> Bool { && !durable_hold_owner_equal(left: eh_owner(text: "demand-7|offer-1"), right: eh_owner(text: "Demand-7|offer-1")) && !durable_hold_owner_equal(left: eh_owner(text: "demand-7|offer-1"), right: eh_owner(text: "demand-7|offer-1 ")) } + +// ── RECOVERY OF A DEAD HOLDER'S HOLD (gunbc#12533 finding 2) ───────────────────────────────── +// The three controls the brief names, over one slot held by one owner at one generation, differing +// ONLY in the liveness verdict: a live holder blocks, an observed-dead holder is recovered at the +// generation both the read and the report name, and an unobservable holder refuses -- never +// recovered. There is no age input to vary: the assessment takes none. +fn eh_report(holder: String, generation: Int, dead: Int) -> DurableHoldHolderReport { + DurableHoldHolderReport { + holder: eh_owner(text: holder), + generation: generation as CasGeneration, + liveness: if dead == 0 { HolderObservedLive { evidence: "process 7 started at tick 90 still runs" } } + else if dead == 1 { HolderObservedDead { evidence: "no process 7 on the holder's boot" } } + else { HolderLivenessUnobservable { cause: "/proc/7/stat could not be read" } }, + } +} + +test fn a_live_holder_blocks_an_observed_dead_holder_is_recovered_and_an_unobservable_one_refuses() -> Bool { + let held = eh_held_at(owner: "mtcollins1-boot:4242", generation: 3) + (match durable_hold_recovery_assess(observed: held, report: eh_report(holder: "mtcollins1-boot:4242", generation: 3, dead: 0)) { + HoldRecoveryRefused { refusal: HoldRecoveryHolderLive { generation: _, holder: _, evidence: _ } } => true + _ => false + }) + && (match durable_hold_recovery_assess(observed: held, report: eh_report(holder: "mtcollins1-boot:4242", generation: 3, dead: 1)) { + HoldRecoveryEligible { generation: g, dead_holder: h, evidence: _ } => cas_generation_count(g: g) == 3 && (h as String) == "mtcollins1-boot:4242" + _ => false + }) + && (match durable_hold_recovery_assess(observed: held, report: eh_report(holder: "mtcollins1-boot:4242", generation: 3, dead: 2)) { + HoldRecoveryRefused { refusal: HoldRecoveryHolderUnobservable { generation: _, holder: _, cause: _ } } => true + _ => false + }) +} + +// A DEAD VERDICT RECOVERS ONLY THE HOLD IT WAS TAKEN OF: the same verdict against a slot that moved +// on, that another owner now holds, or that is already free recovers nothing. +test fn a_dead_verdict_recovers_only_the_hold_it_was_taken_of() -> Bool { + let dead = eh_report(holder: "mtcollins1-boot:4242", generation: 3, dead: 1) + (match durable_hold_recovery_assess(observed: eh_held_at(owner: "mtcollins1-boot:4242", generation: 5), report: dead) { + HoldRecoveryRefused { refusal: HoldRecoverySlotMoved { reported: _, observed: _ } } => true + _ => false + }) + && (match durable_hold_recovery_assess(observed: eh_held_at(owner: "operator-maintenance:reseat", generation: 3), report: dead) { + HoldRecoveryRefused { refusal: HoldRecoveryReportForAnotherHolder { holder: _, reported: _ } } => true + _ => false + }) + && (match durable_hold_recovery_assess(observed: eh_free_at(generation: 4), report: dead) { + HoldRecoveryRefused { refusal: HoldRecoveryNothingHeld } => true + _ => false + }) +} diff --git a/dag/test/claim/machine_intake/mtcollins1_maintenance_hold_witness_test.dag b/dag/test/claim/machine_intake/mtcollins1_maintenance_hold_witness_test.dag index baa0cf0c6c3..b28d952e40e 100644 --- a/dag/test/claim/machine_intake/mtcollins1_maintenance_hold_witness_test.dag +++ b/dag/test/claim/machine_intake/mtcollins1_maintenance_hold_witness_test.dag @@ -19,9 +19,12 @@ import gunbc.auth.mtcollins1_boot_federation { } import gunbc.auth.gcp_secret_access { SecretAccessGrant } import v2.std.optional { Present, Absent } +import gunbc.build_cache_instance { ProcessIdentity } data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly +data holder_process: ProcessIdentity = ProcessIdentity { boot_id: "73c8153e-0e0e-4480-a36c-bc7560c50e8b" as NonEmptyStr, pid: 3975, start_time: "81234" as NonEmptyStr } + // ── The interlock: the boot refuses under an active maintenance hold ────────────────────────── // Supplied at the interface (DESIGN §3, a witness discriminates at one interface), and only into the // PURE refusal fold: a supplied outcome yields text and never a UnitHoldProof, whose one mint is the @@ -55,12 +58,12 @@ test fn the_boot_refuses_under_an_active_maintenance_hold_and_names_the_holder() generation: first_generation(), } refused_naming(r: unit_hold_refusal(outcome: held), pattern: "operator-maintenance:reseating DIMMs") - && not_refused(r: unit_hold_refusal(outcome: FileHoldAcquired { slot_key: mtcollins1_unit_hold_key, owner: boot_run_owner(run_id: "42"), generation: first_generation() })) + && not_refused(r: unit_hold_refusal(outcome: FileHoldAcquired { slot_key: mtcollins1_unit_hold_key, owner: boot_run_owner(run_id: "42", process: holder_process), generation: first_generation() })) } // ANOTHER HOLDER IS A HOLDER: a running boot, or a host reset-return, excludes the next writer. test fn a_second_writer_refuses_while_another_run_holds_the_unit() -> Bool { - refused_naming(r: unit_hold_refusal(outcome: FileHoldOccupied { slot_key: mtcollins1_unit_hold_key, holder: boot_run_owner(run_id: "41"), generation: first_generation() }), pattern: "mtcollins1-boot:41") + refused_naming(r: unit_hold_refusal(outcome: FileHoldOccupied { slot_key: mtcollins1_unit_hold_key, holder: boot_run_owner(run_id: "41", process: holder_process), generation: first_generation() }), pattern: "mtcollins1-boot:41") && refused_naming(r: unit_hold_refusal(outcome: FileHoldOccupied { slot_key: mtcollins1_unit_hold_key, holder: host_reset_owner(attempt: "srv1-mtcollins1"), generation: first_generation() }), pattern: "host-reset-return:srv1-mtcollins1") } @@ -138,7 +141,7 @@ fn round_trips(o: UnitHoldOwner, label: String) -> Bool { test fn every_holder_kind_round_trips_and_a_foreign_owner_is_refused() -> Bool { round_trips(o: OperatorMaintenance { reason: "reseating DIMMs" as NonEmptyStr }, label: "operator maintenance: reseating DIMMs") - && round_trips(o: BootRun { run_id: "41" as NonEmptyStr }, label: "the mtcollins1 boot run 41") + && round_trips(o: BootRun { run_id: "41" as NonEmptyStr, process: holder_process }, label: "the mtcollins1 boot run 41 (process 3975 on boot 73c8153e-0e0e-4480-a36c-bc7560c50e8b)") && round_trips(o: HostResetReturn { attempt: "srv1-mtcollins1" as NonEmptyStr }, label: "host reset-return attempt srv1-mtcollins1") && match decode_unit_hold_owner(owner: "someone-else:x" as DurableHoldOwnerRef) { UnitHoldOwnerUnrecognized { raw: _ } => true @@ -149,3 +152,25 @@ test fn every_holder_kind_round_trips_and_a_foreign_owner_is_refused() -> Bool { UnitHoldOwnerDecoded { owner: _ } => false } } + +// A BOOT OWNER NAMES ITS PROCESS AND DECODES BACK TO IT (gunbc#12533 finding 2): the rendered owner +// carries boot id, pid and start time, and decodes to the same identity -- the identity a successor +// observes the holder by. A boot owner written WITHOUT a process (the spelling before this change, +// still possibly on disk) is not guessed into one: it decodes as unrecognized, so its holder is +// unobservable and its hold is refused rather than broken. +test fn a_boot_owner_carries_its_process_and_a_processless_one_is_not_recoverable() -> Bool { + let rendered = boot_run_owner(run_id: "4242", process: holder_process) as String + rendered == "mtcollins1-boot:4242@boot=73c8153e-0e0e-4480-a36c-bc7560c50e8b,pid=3975,start=81234" + && (match decode_unit_hold_owner(owner: rendered as DurableHoldOwnerRef) { + UnitHoldOwnerDecoded { owner: BootRun { run_id: r, process: p } } => (r as String) == "4242" && p.pid == 3975 && (p.start_time as String) == "81234" + _ => false + }) + && (match decode_unit_hold_owner(owner: "mtcollins1-boot:4242" as DurableHoldOwnerRef) { + UnitHoldOwnerUnrecognized { raw: _ } => true + _ => false + }) + && (match decode_unit_hold_owner(owner: "mtcollins1-boot:4242@boot=x,pid=-1,start=5" as DurableHoldOwnerRef) { + UnitHoldOwnerUnrecognized { raw: _ } => true + _ => false + }) +} From e25a17d930703159a018ee324c018dfc2c1cf695 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 28 Sep 2026 18:11:25 +0000 Subject: [PATCH 14/38] BMC model: an event a transition schedules is kept (power restore keeps its SOL drop) #12423 side-chat review 5342387382: bmc_advance_pending rebuilt pending from its own accumulator after each applied event, discarding events the transition had just scheduled -- a power cycle's restore arming the after-boot SOL drop lost that drop. The advance now fires the earliest due event from the world's own pending list, applies it, and repeats on the world that transition produced. New model control a_power_restore_keeps_the_sol_drop_it_schedules (ON host, cycle at 0, restore at 5, drop at 35; quiet advances to 10 and to 40); it fails on the previous fold. Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/gunbc/bmc_model.dag | 61 ++++++++++++++++--- .../claim/boot_world_models_witness_test.dag | 20 ++++++ 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/dag/gunbc/bmc_model.dag b/dag/gunbc/bmc_model.dag index 7fd22b6d3df..ef2554835a2 100644 --- a/dag/gunbc/bmc_model.dag +++ b/dag/gunbc/bmc_model.dag @@ -167,23 +167,64 @@ fn bmc_apply_event(world: BmcWorld, event: BmcEvent, at: Second) -> BmcWorld { } } -// Every event due at or before `now` fires, in schedule order, and moves from pending to fired. With -// nothing pending there is nothing to fire, and the world is returned as it is rather than rebuilt. +// EVERY EVENT DUE AT OR BEFORE `now` FIRES, EARLIEST FIRST, AND AN EVENT A TRANSITION SCHEDULES IS +// KEPT. One due event is taken off the world's own pending list, applied, and recorded as fired; the +// advance then repeats on the world THAT TRANSITION PRODUCED, so an event it scheduled -- a power +// restore arming its SOL drop -- stays pending and fires in turn if it too is due. (An earlier fold +// rebuilt pending from its own accumulator after each transition and so discarded exactly those +// events: #12423 side-chat review 5342387382.) Each step fires one due event, so the repetition ends. +// With nothing due the world is returned as it is rather than rebuilt. fn bmc_advance(world: BmcWorld, now: Second) -> BmcWorld { - if count(world.pending) == 0 { world } else { bmc_advance_pending(world: world, now: now) } + match bmc_first_due(pending: world.pending, now: now) { + Absent => world + Present { value: e } => { + let remaining = bmc_without_first(pending: world.pending, event: e) + let applied = bmc_apply_event(world: bmc_with_pending(world: world, pending: remaining, fired: world.fired), event: e.event, at: e.at) + bmc_advance(world: bmc_with_pending(world: applied, pending: applied.pending, fired: list_append(applied.fired, e)), now: now) + } + } } -fn bmc_advance_pending(world: BmcWorld, now: Second) -> BmcWorld { - fold(world.pending, init: bmc_with_pending(world: world, pending: [], fired: world.fired), f: fn(acc, e) { - if second_count(s: e.at) <= second_count(s: now) { - let applied = bmc_apply_event(world: acc, event: e.event, at: e.at) - bmc_with_pending(world: applied, pending: acc.pending, fired: list_append(acc.fired, e)) - } else { - bmc_with_pending(world: acc, pending: list_append(acc.pending, e), fired: acc.fired) +// The due event with the earliest instant; among equal instants, the first in schedule order. +fn bmc_no_event() -> BmcScheduledEvent? { + none +} + +fn bmc_first_due(pending: List, now: Second) -> BmcScheduledEvent? { + fold(pending, init: bmc_no_event(), f: fn(acc, e) { + if second_count(s: e.at) > second_count(s: now) { acc } else { + match acc { + Absent => Present { value: e } + Present { value: best } => if second_count(s: e.at) < second_count(s: best.at) { Present { value: e } } else { acc } + } } }) } +type BmcPendingRemoval { + kept: List + removed: Bool +} + +// Removes ONE occurrence -- the first equal to `event` -- so two identical scheduled events fire twice. +fn bmc_without_first(pending: List, event: BmcScheduledEvent) -> List { + fold(pending, init: BmcPendingRemoval { kept: [], removed: false }, f: fn(acc, e) { + if !acc.removed && second_count(s: e.at) == second_count(s: event.at) && bmc_event_eq(a: e.event, b: event.event) { + BmcPendingRemoval { kept: acc.kept, removed: true } + } else { + BmcPendingRemoval { kept: list_append(acc.kept, e), removed: acc.removed } + } + }).kept +} + +fn bmc_event_eq(a: BmcEvent, b: BmcEvent) -> Bool { + match a { + BmcAcPowerLost => match b { BmcAcPowerLost => true _ => false } + BmcPowerRestored => match b { BmcPowerRestored => true _ => false } + BmcSolSessionDropped => match b { BmcSolSessionDropped => true _ => false } + } +} + fn bmc_with_sol_drop_after_boot(world: BmcWorld, after: Second) -> BmcWorld { BmcWorld { power: world.power, pending: world.pending, fired: world.fired, boot_override: world.boot_override, sel: world.sel, diff --git a/dag/test/claim/boot_world_models_witness_test.dag b/dag/test/claim/boot_world_models_witness_test.dag index 51e2696b9b4..60a37223697 100644 --- a/dag/test/claim/boot_world_models_witness_test.dag +++ b/dag/test/claim/boot_world_models_witness_test.dag @@ -2,6 +2,8 @@ module test.claim.boot_world_models_witness_test import std.types { Bool, List, String } import std.measure { second } +import gunbc.bmc_model { BmcWorld, BmcPowerOn, bmc_world, bmc_with_sol_session, bmc_with_sol_drop_after_boot, bmc_chassis_control, bmc_advance, bmc_power_is_on, bmc_sol_drop_fired } +import extdeps.bmc.ipmi_chassis_control { IpmiChassisPowerCycle } import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } import extdeps.transports.shell { ShellProcessExited } import extdeps.exec.command { argv_words } @@ -72,3 +74,21 @@ test fn a_write_needs_its_parent_directory() -> Bool { _ => false } } + +// A POWER RESTORE KEEPS THE SOL DROP IT SCHEDULES (#12423 side-chat review 5342387382). An ON host with +// an open SOL session and a 30-second after-boot drop is power-cycled at t=0: the controller drops power +// and schedules the restore at t=5; the restore boots the host and schedules the drop at t=35. A quiet +// advance to t=10 fires only the restore; ONE quiet advance to t=40, spanning both due times, fires both +// -- the drop the restore scheduled is not discarded -- and leaves nothing pending. +fn cycled_on_host() -> BmcWorld { + let on = bmc_with_sol_drop_after_boot(world: bmc_with_sol_session(world: bmc_world(power: BmcPowerOn), open: true), after: second(count: 30)) + bmc_chassis_control(world: on, action: IpmiChassisPowerCycle, now: second(count: 0)).world +} + +test fn a_power_restore_keeps_the_sol_drop_it_schedules() -> Bool { + let at_ten = bmc_advance(world: cycled_on_host(), now: second(count: 10)) + let at_forty = bmc_advance(world: cycled_on_host(), now: second(count: 40)) + bmc_power_is_on(world: at_ten) && at_ten.sol_session_open && count(at_ten.fired) == 1 && count(at_ten.pending) == 1 + && bmc_power_is_on(world: at_forty) && !at_forty.sol_session_open && bmc_sol_drop_fired(world: at_forty) + && count(at_forty.fired) == 2 && count(at_forty.pending) == 0 +} From c41d5e460da36cad896fe3192877710572f1f812 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 28 Sep 2026 18:16:58 +0000 Subject: [PATCH 15/38] Dry world models the boot worker's procfs identity and its death; the interrupted case flips to recovery, with live and unobservable holder controls (finding 2) Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/gunbc/filesystem_model.dag | 18 ++- .../mtcollins1_boot_dry_realization.dag | 32 ++++- .../claim/boot_world_models_witness_test.dag | 2 +- ...mtcollins1_boot_acceptance_matrix_test.dag | 110 +++++++++++++----- .../claim/modeled_filesystem_witness_test.dag | 5 +- 5 files changed, 130 insertions(+), 37 deletions(-) diff --git a/dag/gunbc/filesystem_model.dag b/dag/gunbc/filesystem_model.dag index daca446e524..e21efaafd1a 100644 --- a/dag/gunbc/filesystem_model.dag +++ b/dag/gunbc/filesystem_model.dag @@ -9,7 +9,7 @@ import v2.std.operation_realization { } import extdeps.transports.file { FileExchangeObservation, FileOperationSucceeded, FileOperationFailed } import extdeps.filesystem.filesystem_io { - FilesystemNotFound, FilesystemAlreadyExists, FilesystemNotDirectory, FilesystemOtherFailure, + FilesystemNotFound, FilesystemAlreadyExists, FilesystemNotDirectory, FilesystemOtherFailure, FilesystemPermissionDenied, } // A MODELED FILESYSTEM: the subset of POSIX file behaviour the file transport realizes, as a pure @@ -21,6 +21,8 @@ import extdeps.filesystem.filesystem_io { // - a listing is the immediate children's names, sorted, one per line; listing a file answers // not_a_directory and listing an absent path answers not_found; // - reading or writing a directory as a file answers `other`, as an EISDIR does. +// - a path in refused_reads answers permission_denied to a read, whether or not it exists: the one +// permission fact modeled, so a consumer's "could not look" arm is reachable over this model. // WHAT IT DOES NOT MODEL, stated so a green over it is not over-read: file modes (the declared mode of // write_create_new_with_mode is accepted and not stored), ownership, symlinks, partial writes, and // the atomicity of a real create-exclusive under concurrent processes. Those are properties of the real @@ -35,6 +37,7 @@ type ModeledFile { type ModeledFilesystem { directories: List files: List + refused_reads: List } fn fs_is_directory(fs: ModeledFilesystem, path: String) -> Bool { @@ -74,12 +77,22 @@ fn failed_with(kind: extdeps.filesystem.filesystem_io.FilesystemFailureKind, mes } fn fs_read(fs: ModeledFilesystem, path: String) -> FileExchangeObservation { + if any_string(xs: fs.refused_reads, x: path) { failed_with(kind: FilesystemPermissionDenied, message: concat("Permission denied: ", path)) } else { match fs_file(fs: fs, path: path) { Present { value: file } => FileOperationSucceeded { byte_count: string_length(s: file.content), content: file.content } Absent => if fs_is_directory(fs: fs, path: path) { failed_with(kind: FilesystemOtherFailure, message: concat("Is a directory: ", path)) } else { failed_with(kind: FilesystemNotFound, message: concat("No such file or directory: ", path)) } } + } +} + +fn fs_without_file(fs: ModeledFilesystem, path: String) -> ModeledFilesystem { + ModeledFilesystem { directories: fs.directories, files: filter(fs.files, f => f.path != path), refused_reads: fs.refused_reads } +} + +fn fs_refusing_reads_of(fs: ModeledFilesystem, path: String) -> ModeledFilesystem { + ModeledFilesystem { directories: fs.directories, files: fs.files, refused_reads: list_append(fs.refused_reads, path) } } type ModeledFileWrite { @@ -110,13 +123,14 @@ fn fs_with_file(fs: ModeledFilesystem, path: String, content: String) -> Modeled ModeledFilesystem { directories: fs.directories, files: list_append(filter(fs.files, f => f.path != path), ModeledFile { path: path, content: content }), + refused_reads: fs.refused_reads, } } fn fs_delete(fs: ModeledFilesystem, path: String) -> ModeledFileWrite { match fs_file(fs: fs, path: path) { Present { value: _ } => - ModeledFileWrite { fs: ModeledFilesystem { directories: fs.directories, files: filter(fs.files, f => f.path != path) }, observation: FileOperationSucceeded { byte_count: 0, content: "" } } + ModeledFileWrite { fs: fs_without_file(fs: fs, path: path), observation: FileOperationSucceeded { byte_count: 0, content: "" } } Absent => if fs_is_directory(fs: fs, path: path) { ModeledFileWrite { fs: fs, observation: failed_with(kind: FilesystemOtherFailure, message: concat("Is a directory: ", path)) } } else { ModeledFileWrite { fs: fs, observation: failed_with(kind: FilesystemNotFound, message: concat("No such file or directory: ", path)) } } diff --git a/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag b/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag index 28c827c2301..12a20378ff4 100644 --- a/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag +++ b/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag @@ -11,7 +11,7 @@ import v2.std.operation_realization { import gunbc.bmc_model { BmcWorld, bmc_advance, bmc_with_sol_session, bmc_power_is_on } import gunbc.bmc_dry_realization { bmc_bindings, megarac_bindings, bmc_ipmi_operation, sleep_delay_seconds_operation } import gunbc.megarac_media_model { MegaRacMediaWorld, megarac_media_advance } -import gunbc.filesystem_model { ModeledFilesystem, ModeledFile, filesystem_bindings, fs_with_file, fs_file, fs_write } +import gunbc.filesystem_model { ModeledFilesystem, ModeledFile, filesystem_bindings, fs_with_file, fs_file, fs_write, fs_without_file } import gunbc.process_environment_model { ModeledVariable, environment_binding } import gunbc.host_command_model { ModeledInvocation, exact_invocation_binding, local_command_words, shell_exec_run_argv_operation } import extdeps.transports.shell { ShellExchangeObservation, ShellProcessExited } @@ -122,6 +122,32 @@ fn proc_cmdline_path(pid: Int) -> String { join(["/proc/", to_string(pid), "/cmdline"], "") } +// THE WORKER'S OWN PROCESS, as procfs shows it (proc(5), proc_pid_stat(5)): the host's boot id, and +// one stat line per process -- pid, a parenthesized comm, then fields 3.. with starttime at field 22 -- +// under /proc//stat and, for the reader itself, /proc/self/stat. The boot's unit-hold owner is +// read from these (gunbc.machine_intake_mtcollins1_maintenance_hold self_process_identity) and a +// successor observes the holder through them (holder_process_liveness), so a worker that starts writes +// both, and a worker that is killed loses its /proc//stat: the kernel removes a dead process's +// directory. Only the fields a reader uses are meaningful; the others are zeros. +data proc_boot_id_file: String = "/proc/sys/kernel/random/boot_id" + +fn proc_stat_path(pid: Int) -> String { + join(["/proc/", to_string(pid), "/stat"], "") +} + +fn proc_stat_line(pid: Int, start_ticks: Int) -> String { + join([to_string(pid), " (gunbc) S 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ", to_string(start_ticks), " 0 0\n"], "") +} + +fn worker_process_started(fs: ModeledFilesystem, boot_id: String, pid: Int, start_ticks: Int) -> ModeledFilesystem { + let line = proc_stat_line(pid: pid, start_ticks: start_ticks) + fs_with_file(fs: fs_with_file(fs: fs_with_file(fs: fs, path: proc_boot_id_file, content: concat(boot_id, "\n")), path: "/proc/self/stat", content: line), path: proc_stat_path(pid: pid), content: line) +} + +fn worker_process_killed(fs: ModeledFilesystem, pid: Int) -> ModeledFilesystem { + fs_without_file(fs: fs_without_file(fs: fs, path: proc_stat_path(pid: pid)), path: "/proc/self/stat") +} + fn live_collectors(w: MtCollins1BootWorld) -> List { filter(w.worker.processes, p => p.alive) } @@ -174,7 +200,7 @@ fn sol_activate_held_handler(w: MtCollins1BootWorld, call: OperationCall) -> Ope // removes it -- which is exactly what the production teardown observes afterwards. fn sol_deactivate_handler(w: MtCollins1BootWorld, call: OperationCall) -> OperationStep { let fs = fold(live_collectors(w: w), init: w.fs, f: fn(acc, p) { - ModeledFilesystem { directories: acc.directories, files: filter(acc.files, f => f.path != proc_cmdline_path(pid: p.pid)) } + fs_without_file(fs: acc, path: proc_cmdline_path(pid: p.pid)) }) let worker = ModeledWorker { next_pid: w.worker.next_pid, processes: map(w.worker.processes, p => ModeledProcess { pid: p.pid, cmdline: p.cmdline, capture_path: p.capture_path, alive: false }), uptime_at_origin: w.worker.uptime_at_origin } exited_step(state: with_bmc(w: with_worker(w: with_fs(w: w, fs: fs), worker: worker), bmc: bmc_with_sol_session(world: w.bmc, open: false)), stdout: "", exit_code: 0) @@ -243,7 +269,7 @@ fn concat_text(lines: List) -> String { fn collectors_follow_session(w: MtCollins1BootWorld) -> MtCollins1BootWorld { if w.bmc.sol_session_open || count(live_collectors(w: w)) == 0 { w } else { let fs = fold(live_collectors(w: w), init: w.fs, f: fn(acc, p) { - ModeledFilesystem { directories: acc.directories, files: filter(acc.files, f => f.path != proc_cmdline_path(pid: p.pid)) } + fs_without_file(fs: acc, path: proc_cmdline_path(pid: p.pid)) }) with_worker(w: with_fs(w: w, fs: fs), worker: ModeledWorker { next_pid: w.worker.next_pid, processes: map(w.worker.processes, p => ModeledProcess { pid: p.pid, cmdline: p.cmdline, capture_path: p.capture_path, alive: false }), uptime_at_origin: w.worker.uptime_at_origin }) } diff --git a/dag/test/claim/boot_world_models_witness_test.dag b/dag/test/claim/boot_world_models_witness_test.dag index 60a37223697..318ee92c1f3 100644 --- a/dag/test/claim/boot_world_models_witness_test.dag +++ b/dag/test/claim/boot_world_models_witness_test.dag @@ -67,7 +67,7 @@ test fn the_remote_host_answers_production_commands_in_coreutils_format() -> Boo // THE FILESYSTEM'S PARENT RULE: a write under a directory the model does not hold is not_found. test fn a_write_needs_its_parent_directory() -> Bool { - let fs = ModeledFilesystem { directories: ["/", "/run"], files: [] } + let fs = ModeledFilesystem { directories: ["/", "/run"], files: [], refused_reads: [] } fs_parent(path: "/run/sol.pid") == "/run" && fs_parent(path: "/x") == "/" && fs_parent(path: "target/a.pub") == "target" && match fs_write(fs: fs, path: "/var/x", content: "1", create_new: false).observation { extdeps.transports.file.FileOperationFailed { kind: extdeps.filesystem.filesystem_io.FilesystemNotFound, error: _ } => true diff --git a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag index 31cc33ca5e0..5a140540513 100644 --- a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag +++ b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag @@ -12,14 +12,15 @@ import v2.std.witness_evaluation { } import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } import gunbc.bmc_model { BmcWorld, BmcPowerOff, bmc_world, bmc_with_sol_session, bmc_with_sol_drop_after_boot, bmc_sol_drop_fired } -import gunbc.filesystem_model { ModeledFilesystem, ModeledFile } +import gunbc.filesystem_model { ModeledFilesystem, ModeledFile, fs_refusing_reads_of } import gunbc.process_environment_model { ModeledVariable } import gunbc.remote_host_model { ModeledRemoteHost, ModeledRemoteFile } import gunbc.wall_clock_model { ModeledWallClock } import gunbc.megarac_media_model { MegaRacMediaWorld, MegaRacShare, MegaRacImage, MegaRacCdRow, megarac_cleared_cd } import gunbc.machine_intake_mtcollins1_boot_dry_realization { MtCollins1BootWorld, ModeledSshAgent, ModeledWorker, ModeledHostConsole, TimedConsoleLine, - mtcollins1_boot_dry_realization, with_media, with_bmc, + mtcollins1_boot_dry_realization, with_media, with_bmc, with_fs, + worker_process_started, worker_process_killed, proc_stat_path, } import v2.std.operation_realization { OperationRealization, OperationBinding, OperationCall, OperationStep, OperationObserved, OperationWorkerKilled, @@ -119,11 +120,20 @@ fn healthy_media() -> MegaRacMediaWorld { } } +// The worker host's boot, and the boot worker's own process (pid, start tick) as procfs shows it. +data worker_boot_id: String = "73c8153e-0e0e-4480-a36c-bc7560c50e8b" +data first_worker_pid: Int = 3100 +data first_worker_start: Int = 5000 + fn worker_filesystem() -> ModeledFilesystem { - ModeledFilesystem { - directories: ["target", "/", "/run", "/proc", "/var", "/var/lib", "/var/lib/gunbc", unit_hold_store_root as String], - files: [ModeledFile { path: credential_path, content: "secret" }], - } + worker_process_started( + fs: ModeledFilesystem { + directories: ["target", "/", "/run", "/proc", "/var", "/var/lib", "/var/lib/gunbc", unit_hold_store_root as String], + files: [ModeledFile { path: credential_path, content: "secret" }], + refused_reads: [], + }, + boot_id: worker_boot_id, pid: first_worker_pid, start_ticks: first_worker_start, + ) } fn world_with_console(lines: List) -> MtCollins1BootWorld { @@ -455,14 +465,19 @@ test fn a_two_socket_answer_is_a_truthful_topology_refusal() -> Bool { // THE WORKER DIES RIGHT AFTER THE START-MEDIA WRITE COMMITS, before its reply. The realization is the // matrix's own with that one handler wrapped: the controller takes the start, and the worker is gone. -fn killed_after_start_media(realization: OperationRealization) -> OperationRealization { +// THE WORKER DIES RIGHT AFTER StartMedia COMMITS: the dispatch reports it killed, and the kernel +// removes the dead process's /proc entry. stopped_after_start_media is the same interruption with the +// worker's process STILL RUNNING (the dispatch stopped being observed; the holder did not exit) -- the +// live-holder control. +fn interrupted_after_start_media(realization: OperationRealization, process_exits: Bool) -> OperationRealization { OperationRealization { identity: realization.identity, initial: realization.initial, epoch: realization.epoch, advance: realization.advance, bindings: map(realization.bindings, b => if b.at.operation == "StartMedia" { OperationBinding { at: b.at, handler: fn(state, call) { let inner = b.handler match inner(state, call) { - OperationObserved { observation: _, state: after, elapsed: _ } => OperationWorkerKilled { committed: true, state: after } + OperationObserved { observation: _, state: after, elapsed: _ } => + OperationWorkerKilled { committed: true, state: if process_exits { with_fs(w: after, fs: worker_process_killed(fs: after.fs, pid: first_worker_pid)) } else { after } } other => other } } } @@ -470,34 +485,71 @@ fn killed_after_start_media(realization: OperationRealization) -> OperationRealization { + interrupted_after_start_media(realization: realization, process_exits: true) +} + +fn stopped_after_start_media(realization: OperationRealization) -> OperationRealization { + interrupted_after_start_media(realization: realization, process_exits: false) +} + +// THE NEXT RUN IS A NEW WORKER PROCESS on the same host: a new run id, and its own pid and start tick +// in procfs. The first worker's /proc entry is left as the interruption left it. fn as_another_run(w: MtCollins1BootWorld, run_id: String) -> MtCollins1BootWorld { let env = map(w.environment, v => if v.name == "GITHUB_RUN_ID" { ModeledVariable { name: v.name, value: run_id } } else { v }) - MtCollins1BootWorld { bmc: w.bmc, fs: w.fs, environment: env, agent: w.agent, remote_hosts: w.remote_hosts, clock: w.clock, worker: w.worker, console: w.console, media: w.media } + let fs = worker_process_started(fs: w.fs, boot_id: worker_boot_id, pid: first_worker_pid + 1, start_ticks: first_worker_start + 9000) + MtCollins1BootWorld { bmc: w.bmc, fs: fs, environment: env, agent: w.agent, remote_hosts: w.remote_hosts, clock: w.clock, worker: w.worker, console: w.console, media: w.media } } -// PINNED: THE NEXT ATTEMPT IS LOCKED OUT BY THE DEAD RUN'S HOLD. The interrupted run never released the -// unit, so a new run finds it held by the dead one and refuses with the controller untouched -- safe, -// but it cannot reconcile: nothing retires a hold whose owner is gone, so every later attempt refuses -// until an operator intervenes (#12423: interrupted operations must be reconciled without undocumented -// preparation). When dead-owner reconciliation lands this case flips. -test fn pinned_an_interrupted_attempt_locks_out_the_next_one() -> Bool { - let realization = killed_after_start_media(realization: mtcollins1_boot_dry_realization(identity: realization_identity, initial: world_with_console(lines: []), epoch: virtual_clock_origin())) +// THE NEXT ATTEMPT RECOVERS THE DEAD RUN'S HOLD (finding 2, repaired in #12555). The interrupted run +// never released the unit; its process is gone from procfs, so the next run observes the holder dead, +// frees the hold and proceeds to the controller -- no operator step (#12423: interrupted operations are +// reconciled without undocumented preparation). The red is the old lockout: "is held by" with no write. +// The two controls beside it differ only in what procfs says of the holder: still running, and a stat +// read the host refuses. Neither is ever recovered, and neither writes to the controller. +fn resumed_after(realization: OperationRealization, refuse_holder_stat: Bool) -> WitnessEvaluation? { match evaluate_in_witness_frame(frame: matrix_frame_over(realization: realization), subject: fn(_scope) { mtcollins1_boot_wet_on_srv1() }) { WitnessInterrupted { at, state, now: interrupted_clock } => { - let next = as_another_run(w: state, run_id: "4243") - let resumed = evaluate_in_witness_frame( - frame: matrix_frame_over(realization: mtcollins1_boot_dry_realization(identity: realization_identity, initial: next, epoch: interrupted_clock)), - subject: fn(_scope) { mtcollins1_boot_wet_on_srv1() } - ) - at.invocation.at.operation == "StartMedia" - && match at.outcome { DispatchWorkerKilled { committed: c } => c _ => false } - && match resumed { - WitnessReturned { value, route } => - string_contains(s: outcome_reason(a: value), pattern: "is held by 'mtcollins1-boot:4242'") - && count(filter(route, r => is_controller_write(r: r))) == 0 - _ => false - } + let another = as_another_run(w: state, run_id: "4243") + let next = if refuse_holder_stat { with_fs(w: another, fs: fs_refusing_reads_of(fs: another.fs, path: proc_stat_path(pid: first_worker_pid))) } else { another } + if at.invocation.at.operation != "StartMedia" { none } else { + Present { value: evaluate_in_witness_frame( + frame: matrix_frame_over(realization: mtcollins1_boot_dry_realization(identity: realization_identity, initial: next, epoch: interrupted_clock)), + subject: fn(_scope) { mtcollins1_boot_wet_on_srv1() } + ) } + } } + _ => none + } +} + +fn fresh_realization() -> OperationRealization { + mtcollins1_boot_dry_realization(identity: realization_identity, initial: world_with_console(lines: []), epoch: virtual_clock_origin()) +} + +test fn an_interrupted_attempts_dead_hold_is_recovered_by_the_next_one() -> Bool { + match resumed_after(realization: killed_after_start_media(realization: fresh_realization()), refuse_holder_stat: false) { + Present { value: WitnessReturned { value, route } } => + !string_contains(s: outcome_reason(a: value), pattern: "is held by") + && count(filter(route, r => is_controller_write(r: r))) > 0 + _ => false + } +} + +test fn a_live_holder_still_blocks_the_next_attempt() -> Bool { + match resumed_after(realization: stopped_after_start_media(realization: fresh_realization()), refuse_holder_stat: false) { + Present { value: WitnessReturned { value, route } } => + string_contains(s: outcome_reason(a: value), pattern: "which is alive") + && count(filter(route, r => is_controller_write(r: r))) == 0 + _ => false + } +} + +test fn an_unobservable_holder_is_refused_not_recovered() -> Bool { + match resumed_after(realization: killed_after_start_media(realization: fresh_realization()), refuse_holder_stat: true) { + Present { value: WitnessReturned { value, route } } => + string_contains(s: outcome_reason(a: value), pattern: "whose liveness could not be observed") + && count(filter(route, r => is_controller_write(r: r))) == 0 _ => false } } diff --git a/dag/test/claim/modeled_filesystem_witness_test.dag b/dag/test/claim/modeled_filesystem_witness_test.dag index 346e733645a..ba8111f3839 100644 --- a/dag/test/claim/modeled_filesystem_witness_test.dag +++ b/dag/test/claim/modeled_filesystem_witness_test.dag @@ -70,7 +70,7 @@ fn filesystem_frame(initial: ModeledFilesystem) -> WitnessEvaluationFrame ModeledFilesystem { - ModeledFilesystem { directories: ["/", "/var", "/var/lib", "/var/lib/gunbc", hold_root as String], files: [] } + ModeledFilesystem { directories: ["/", "/var", "/var/lib", "/var/lib/gunbc", hold_root as String], files: [], refused_reads: [] } } fn route_operations(route: List) -> List { @@ -126,7 +126,7 @@ fn store_holds_one_slot(fs: ModeledFilesystem) -> Bool { // admit_filesystem_failure_kind, decodes it to FilesystemAlreadyExists -- the same projection a host // io::Error reaches. test fn a_create_exclusive_write_over_an_existing_file_is_already_exists() -> Bool { - let seeded = ModeledFilesystem { directories: ["/", "/tmp"], files: [ModeledFile { path: "/tmp/slot", content: "held" }] } + let seeded = ModeledFilesystem { directories: ["/", "/tmp"], files: [ModeledFile { path: "/tmp/slot", content: "held" }], refused_reads: [] } match evaluate_in_witness_frame( frame: filesystem_frame(initial: seeded), subject: fn(_scope) { @@ -152,6 +152,7 @@ test fn a_listing_is_sorted_immediate_children() -> Bool { let fs = ModeledFilesystem { directories: ["/", "/d", "/d/sub", "/d/sub/deeper"], files: [ModeledFile { path: "/d/b", content: "" }, ModeledFile { path: "/d/a", content: "" }, ModeledFile { path: "/d/sub/c", content: "" }], + refused_reads: [], } match evaluate_in_witness_frame( frame: filesystem_frame(initial: fs), From 2dbba796f4084395ec3117c58a9fa18f98256f97 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 28 Sep 2026 18:51:52 +0000 Subject: [PATCH 16/38] Fix the floor's refusals: optional list reads, the tag literal's process, callers of boot_run_owner; the held-unit case's other run is live in procfs Co-Authored-By: Claude Opus 5.5 (1M context) --- .../mtcollins1_maintenance_hold.dag | 23 ++++++++++++------- ...mtcollins1_boot_acceptance_matrix_test.dag | 12 ++++++---- .../claim/modeled_filesystem_witness_test.dag | 5 ++-- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/dag/gunbc/machine_intake/mtcollins1_maintenance_hold.dag b/dag/gunbc/machine_intake/mtcollins1_maintenance_hold.dag index 1c4220eb2e3..d1aef9b1d83 100644 --- a/dag/gunbc/machine_intake/mtcollins1_maintenance_hold.dag +++ b/dag/gunbc/machine_intake/mtcollins1_maintenance_hold.dag @@ -1,6 +1,6 @@ module gunbc.machine_intake_mtcollins1_maintenance_hold -import std.types { Bool, NonEmptyStr, String } +import std.types { Bool, Int, List, NonEmptyStr, String } import std.process { ProcessExit, ExitSuccess, ExitFailure, exit_failure } import std.durable_compare_and_set { CasGeneration } import v2.std.optional { Present, Absent } @@ -99,14 +99,21 @@ fn unit_hold_process_text(p: ProcessIdentity) -> String { join([unit_hold_process_marker, p.boot_id as String, ",pid=", to_string(p.pid), ",start=", p.start_time as String], "") } +fn text_at(xs: List, i: Int) -> String { + match xs.skip(n: i).first() { + Present { value: v } => v + Absent => "" + } +} + fn unit_hold_process_decode(text: String) -> ProcessIdentity? { let parts = split(s: text, delimiter: ",") if count(parts) != 3 { none } else { - let b = parts[0] - let pid_text = parts[1] - let start_text = parts[2] + let b = text_at(xs: parts, i: 0) + let pid_text = text_at(xs: parts, i: 1) + let start_text = text_at(xs: parts, i: 2) if !starts_with(s: b, prefix: "boot=") || !starts_with(s: pid_text, prefix: "pid=") || !starts_with(s: start_text, prefix: "start=") { none } else { @@ -159,7 +166,7 @@ fn unit_hold_owner_detail_after(raw: String, tag: String) -> NonEmptyStr? { fn decode_unit_hold_owner(owner: DurableHoldOwnerRef) -> UnitHoldOwnerDecode { let raw = owner as String let op = unit_hold_owner_tag(o: OperatorMaintenance { reason: "x" as NonEmptyStr }) - let boot = unit_hold_owner_tag(o: BootRun { run_id: "x" as NonEmptyStr }) + let boot = unit_hold_owner_tag(o: BootRun { run_id: "x" as NonEmptyStr, process: ProcessIdentity { boot_id: "x" as NonEmptyStr, pid: 1, start_time: "x" as NonEmptyStr } }) let reset = unit_hold_owner_tag(o: HostResetReturn { attempt: "x" as NonEmptyStr }) if starts_with(s: raw, prefix: op) { match unit_hold_owner_detail_after(raw: raw, tag: op) { Present { value: d } => UnitHoldOwnerDecoded { owner: OperatorMaintenance { reason: d } } Absent => UnitHoldOwnerUnrecognized { raw: raw } } @@ -168,11 +175,11 @@ fn decode_unit_hold_owner(owner: DurableHoldOwnerRef) -> UnitHoldOwnerDecode { Absent => UnitHoldOwnerUnrecognized { raw: raw } Present { value: d } => { let halves = split(s: d as String, delimiter: unit_hold_process_marker) - if count(halves) != 2 || halves[0] == "" { + if count(halves) != 2 || text_at(xs: halves, i: 0) == "" { UnitHoldOwnerUnrecognized { raw: raw } } else { - match unit_hold_process_decode(text: concat("boot=", halves[1])) { - Present { value: p } => UnitHoldOwnerDecoded { owner: BootRun { run_id: halves[0] as NonEmptyStr, process: p } } + match unit_hold_process_decode(text: concat("boot=", text_at(xs: halves, i: 1))) { + Present { value: p } => UnitHoldOwnerDecoded { owner: BootRun { run_id: text_at(xs: halves, i: 0) as NonEmptyStr, process: p } } Absent => UnitHoldOwnerUnrecognized { raw: raw } } } diff --git a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag index 5a140540513..07783cc4a13 100644 --- a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag +++ b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag @@ -1,5 +1,6 @@ module test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test +import gunbc.build_cache_instance { ProcessIdentity } import std.types { Bool, Int, List, NonEmptyStr, String } import std.measure { Second, second } import std.materialization_ladder { Frame, SharedStateFrame } @@ -12,7 +13,7 @@ import v2.std.witness_evaluation { } import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } import gunbc.bmc_model { BmcWorld, BmcPowerOff, bmc_world, bmc_with_sol_session, bmc_with_sol_drop_after_boot, bmc_sol_drop_fired } -import gunbc.filesystem_model { ModeledFilesystem, ModeledFile, fs_refusing_reads_of } +import gunbc.filesystem_model { ModeledFilesystem, ModeledFile, fs_refusing_reads_of, fs_with_file } import gunbc.process_environment_model { ModeledVariable } import gunbc.remote_host_model { ModeledRemoteHost, ModeledRemoteFile } import gunbc.wall_clock_model { ModeledWallClock } @@ -20,7 +21,7 @@ import gunbc.megarac_media_model { MegaRacMediaWorld, MegaRacShare, MegaRacImage import gunbc.machine_intake_mtcollins1_boot_dry_realization { MtCollins1BootWorld, ModeledSshAgent, ModeledWorker, ModeledHostConsole, TimedConsoleLine, mtcollins1_boot_dry_realization, with_media, with_bmc, with_fs, - worker_process_started, worker_process_killed, proc_stat_path, + worker_process_started, worker_process_killed, proc_stat_path, proc_stat_line, } import v2.std.operation_realization { OperationRealization, OperationBinding, OperationCall, OperationStep, OperationObserved, OperationWorkerKilled, @@ -275,9 +276,12 @@ test fn pinned_a_healthy_census_is_refused_at_the_sol_teardown() -> Bool { // refused at the hold with the controller untouched: no controller write of any kind appears in its // route, and it never starts a SOL collector. test fn a_second_run_for_a_held_unit_writes_nothing_to_the_controller() -> Bool { + // The other run is still running: its process is in procfs, so it is a live holder, not a dead one. + let base = world_with_console(lines: []) + let other_running = with_fs(w: base, fs: fs_with_file(fs: base.fs, path: proc_stat_path(pid: 2900), content: proc_stat_line(pid: 2900, start_ticks: 100))) let prior = evaluate_in_witness_frame( - frame: matrix_frame(world: world_with_console(lines: [])), - subject: fn(_scope) { file_hold_acquire(root: unit_hold_store_root, slot_key: mtcollins1_unit_hold_key, requested_owner: boot_run_owner(run_id: "4141" as NonEmptyStr)) } + frame: matrix_frame(world: other_running), + subject: fn(_scope) { file_hold_acquire(root: unit_hold_store_root, slot_key: mtcollins1_unit_hold_key, requested_owner: boot_run_owner(run_id: "4141" as NonEmptyStr, process: ProcessIdentity { boot_id: worker_boot_id as NonEmptyStr, pid: 2900, start_time: "100" as NonEmptyStr })) } ) match prior { WitnessReturned { state } => match state { diff --git a/dag/test/claim/modeled_filesystem_witness_test.dag b/dag/test/claim/modeled_filesystem_witness_test.dag index ba8111f3839..ca802309a74 100644 --- a/dag/test/claim/modeled_filesystem_witness_test.dag +++ b/dag/test/claim/modeled_filesystem_witness_test.dag @@ -1,5 +1,6 @@ module test.claim.modeled_filesystem_witness_test +import gunbc.build_cache_instance { ProcessIdentity } import std.types { Bool, List, NonEmptyStr, String } import std.materialization_ladder { Frame, SharedStateFrame } import std.effect_grant { @@ -97,8 +98,8 @@ fn occupied_by(o: gunbc.durable_exclusive_hold_file_store.FileHoldAcquireOutcome // is the production fold's reading of what the model's filesystem holds, and the route shows the // second contender never wrote. test fn two_contenders_for_one_unit_get_one_hold() -> Bool { - let first_owner = boot_run_owner(run_id: "run-a" as NonEmptyStr) - let second_owner = boot_run_owner(run_id: "run-b" as NonEmptyStr) + let first_owner = boot_run_owner(run_id: "run-a" as NonEmptyStr, process: ProcessIdentity { boot_id: "b" as NonEmptyStr, pid: 10, start_time: "1" as NonEmptyStr }) + let second_owner = boot_run_owner(run_id: "run-b" as NonEmptyStr, process: ProcessIdentity { boot_id: "b" as NonEmptyStr, pid: 11, start_time: "2" as NonEmptyStr }) let result = evaluate_in_witness_frame( frame: filesystem_frame(initial: empty_hold_store()), subject: fn(_scope) { From e597579516ffa5d6ba13fcc3cc3ab40556c9bb92 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Mon, 28 Sep 2026 19:04:46 +0000 Subject: [PATCH 17/38] File arm refuses a pathless dispatch; modeled byte_count is a ByteSize of UTF-8 bytes - Review 72311: a file operation dispatched without a string `path` is a harness fault, not an empty path fed to map_file_outputs. - Review 72309 (relayed from #12554): FileOperationSucceeded.byte_count is a std.measure ByteSize, and gunbc.filesystem_model fills it with the UTF-8 byte length (std.bytes utf8_encode_bytes, as std.materialization_object does), matching the realization's content.len(), not the code-point count. The dispatcher reads it through extdeps.transports.file file_observation_byte_count. Control: a_modeled_read_counts_utf8_bytes. Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/extdeps/transports/file.dag | 9 +++++- dag/gunbc/filesystem_model.dag | 21 ++++++++----- .../claim/boot_world_models_witness_test.dag | 16 +++++++++- src/v1/stage0/src/v1_interpreter.rs | 31 +++++++++++++------ 4 files changed, 57 insertions(+), 20 deletions(-) diff --git a/dag/extdeps/transports/file.dag b/dag/extdeps/transports/file.dag index ceeb0934b04..cddd7a67933 100644 --- a/dag/extdeps/transports/file.dag +++ b/dag/extdeps/transports/file.dag @@ -1,6 +1,7 @@ module extdeps.transports.file import std.types { FermiDepth, Int, String } +import std.measure { ByteSize, byte_size_count } import extdeps.filesystem.filesystem_io { FilesystemFailureKind } import std.fidelity { TransportFidelity } import extdeps.external_authority { ExternalAuthority } @@ -27,5 +28,11 @@ type FileTransportConfig { // derived exactly as from a real call. A listing's content follows the realization's own contract: // the entry names, sorted, one per line. type FileExchangeObservation - = FileOperationSucceeded { byte_count: Int, content: String } + = FileOperationSucceeded { byte_count: ByteSize, content: String } | FileOperationFailed { kind: FilesystemFailureKind, error: String } + +// The byte count as the host integer the file transport's `byte_count` output field carries; the +// dispatcher reads a modeled observation's count through this rather than decoding the measure. +fn file_observation_byte_count(bytes: ByteSize) -> Int { + byte_size_count(b: bytes) as Int +} diff --git a/dag/gunbc/filesystem_model.dag b/dag/gunbc/filesystem_model.dag index daca446e524..008fdbc0401 100644 --- a/dag/gunbc/filesystem_model.dag +++ b/dag/gunbc/filesystem_model.dag @@ -1,7 +1,8 @@ module gunbc.filesystem_model import std.types { Bool, List, String } -import std.measure { second } +import std.measure { second, ByteSize, byte_size } +import std.bytes { bytes_octets, utf8_encode_bytes } import v2.std.operation_argv { OperationRef, BoundOperationInvocation } import v2.std.operation_realization { OperationBinding, OperationCall, OperationStep, OperationObserved, OperationHarnessFault, @@ -25,8 +26,12 @@ import extdeps.filesystem.filesystem_io { // write_create_new_with_mode is accepted and not stored), ownership, symlinks, partial writes, and // the atomicity of a real create-exclusive under concurrent processes. Those are properties of the real // filesystem, and their evidence is the file-store tests that run against a real directory. -// byte_count is the content's string length, which equals its byte length for the ASCII content the -// consumers here write. +// byte_count is the content's UTF-8 byte length, as the realization's own `content.len()` reports it -- +// not its code-point count -- computed as std.materialization_object does. +fn content_bytes(s: String) -> ByteSize { + byte_size(count: bytes_octets(b: utf8_encode_bytes(s: s)) |> count()) +} + type ModeledFile { path: String content: String @@ -75,7 +80,7 @@ fn failed_with(kind: extdeps.filesystem.filesystem_io.FilesystemFailureKind, mes fn fs_read(fs: ModeledFilesystem, path: String) -> FileExchangeObservation { match fs_file(fs: fs, path: path) { - Present { value: file } => FileOperationSucceeded { byte_count: string_length(s: file.content), content: file.content } + Present { value: file } => FileOperationSucceeded { byte_count: content_bytes(s: file.content), content: file.content } Absent => if fs_is_directory(fs: fs, path: path) { failed_with(kind: FilesystemOtherFailure, message: concat("Is a directory: ", path)) } else { failed_with(kind: FilesystemNotFound, message: concat("No such file or directory: ", path)) } @@ -98,10 +103,10 @@ fn fs_write(fs: ModeledFilesystem, path: String, content: String, create_new: Bo if create_new { ModeledFileWrite { fs: fs, observation: failed_with(kind: FilesystemAlreadyExists, message: concat("File exists: ", path)) } } else { - ModeledFileWrite { fs: fs_with_file(fs: fs, path: path, content: content), observation: FileOperationSucceeded { byte_count: string_length(s: content), content: "" } } + ModeledFileWrite { fs: fs_with_file(fs: fs, path: path, content: content), observation: FileOperationSucceeded { byte_count: content_bytes(s: content), content: "" } } } Absent => - ModeledFileWrite { fs: fs_with_file(fs: fs, path: path, content: content), observation: FileOperationSucceeded { byte_count: string_length(s: content), content: "" } } + ModeledFileWrite { fs: fs_with_file(fs: fs, path: path, content: content), observation: FileOperationSucceeded { byte_count: content_bytes(s: content), content: "" } } } } } @@ -116,7 +121,7 @@ fn fs_with_file(fs: ModeledFilesystem, path: String, content: String) -> Modeled fn fs_delete(fs: ModeledFilesystem, path: String) -> ModeledFileWrite { match fs_file(fs: fs, path: path) { Present { value: _ } => - ModeledFileWrite { fs: ModeledFilesystem { directories: fs.directories, files: filter(fs.files, f => f.path != path) }, observation: FileOperationSucceeded { byte_count: 0, content: "" } } + ModeledFileWrite { fs: ModeledFilesystem { directories: fs.directories, files: filter(fs.files, f => f.path != path) }, observation: FileOperationSucceeded { byte_count: byte_size(count: 0), content: "" } } Absent => if fs_is_directory(fs: fs, path: path) { ModeledFileWrite { fs: fs, observation: failed_with(kind: FilesystemOtherFailure, message: concat("Is a directory: ", path)) } } else { ModeledFileWrite { fs: fs, observation: failed_with(kind: FilesystemNotFound, message: concat("No such file or directory: ", path)) } } @@ -128,7 +133,7 @@ fn fs_list(fs: ModeledFilesystem, path: String) -> FileExchangeObservation { let file_names = map(filter(fs.files, f => fs_parent(path: f.path) == path), f => fs_leaf(path: f.path)) let dir_names = map(filter(fs.directories, d => d != path && fs_parent(path: d) == path), d => fs_leaf(path: d)) let listing = join(sort_by(concat_lists(a: file_names, b: dir_names), n => n), "\n") - FileOperationSucceeded { byte_count: string_length(s: listing), content: listing } + FileOperationSucceeded { byte_count: content_bytes(s: listing), content: listing } } else { match fs_file(fs: fs, path: path) { Present { value: _ } => failed_with(kind: FilesystemNotDirectory, message: concat("Not a directory: ", path)) diff --git a/dag/test/claim/boot_world_models_witness_test.dag b/dag/test/claim/boot_world_models_witness_test.dag index 60a37223697..ce1e736918a 100644 --- a/dag/test/claim/boot_world_models_witness_test.dag +++ b/dag/test/claim/boot_world_models_witness_test.dag @@ -11,7 +11,7 @@ import extdeps.tools.gnu_coreutils { cat_command } import extdeps.crypto.hash { sha256sum_file_command } import gunbc.wall_clock_model { ModeledWallClock, iso8601_utc, wall_clock_unix } import gunbc.remote_host_model { ModeledRemoteHost, ModeledRemoteFile, remote_request, remote_answer } -import gunbc.filesystem_model { ModeledFilesystem, ModeledFile, fs_parent, fs_write, fs_list } +import gunbc.filesystem_model { ModeledFilesystem, ModeledFile, fs_parent, fs_write, fs_list, fs_read } data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly @@ -92,3 +92,17 @@ test fn a_power_restore_keeps_the_sol_drop_it_schedules() -> Bool { && bmc_power_is_on(world: at_forty) && !at_forty.sol_session_open && bmc_sol_drop_fired(world: at_forty) && count(at_forty.fired) == 2 && count(at_forty.pending) == 0 } + +// A READ REPORTS UTF-8 BYTES, NOT CODE POINTS (review 72309): the realization's own file read reports +// content.len(), so the model must too. `é` is one code point and two bytes. +fn read_byte_count(fs: ModeledFilesystem, path: String) -> Int { + match fs_read(fs: fs, path: path) { + extdeps.transports.file.FileOperationSucceeded { byte_count: b, content: _ } => extdeps.transports.file.file_observation_byte_count(bytes: b) + _ => 0 - 1 + } +} + +test fn a_modeled_read_counts_utf8_bytes() -> Bool { + let fs = ModeledFilesystem { directories: ["/", "/t"], files: [ModeledFile { path: "/t/a", content: "é" }, ModeledFile { path: "/t/b", content: "abc" }] } + read_byte_count(fs: fs, path: "/t/a") == 2 && read_byte_count(fs: fs, path: "/t/b") == 3 +} diff --git a/src/v1/stage0/src/v1_interpreter.rs b/src/v1/stage0/src/v1_interpreter.rs index 1c07976a545..aa2bd7f3e7a 100644 --- a/src/v1/stage0/src/v1_interpreter.rs +++ b/src/v1/stage0/src/v1_interpreter.rs @@ -8420,13 +8420,15 @@ fn dispatch_modeled_operation( .map_err(&harness_fault) .map(|shell| shell_result_projection(shell, op_node, ctx)) } else { - let path = match param_env.lookup(ctx.sym("path")) { - Some(Value::Str(p)) => p.to_string(), - _ => String::new(), - }; - file_result_of_observation(&observation, &path, ctx) - .map_err(&harness_fault) - .map(|file| map_file_outputs(&file, op_node, ctx)) + // Every file-transport operation declares its path; one dispatched without a string + // path is a malformed dispatch and refuses, never an empty path the projection would + // report as if the operation had named one. + match param_env.lookup(ctx.sym("path")) { + Some(Value::Str(p)) => file_result_of_observation(&observation, &p, ctx), + _ => Err("a file operation was dispatched without a string path".to_string()), + } + .map_err(&harness_fault) + .map(|file| map_file_outputs(&file, op_node, ctx)) }; let projected = projected?; log( @@ -8500,9 +8502,18 @@ fn file_result_of_observation( }; match file_arm.as_str() { "FileOperationSucceeded" => { - let byte_count = match ctx.field(&file_fields, "byte_count") { - Some(Value::Int(n)) => *n, - _ => return Err("the file observation carries no byte_count".to_string()), + let bytes = ctx + .field(&file_fields, "byte_count") + .cloned() + .ok_or("the file observation carries no byte_count")?; + let byte_count = match run_in_context_with_args( + ctx, + "file_observation_byte_count", + &[(Some("bytes".to_string()), bytes)], + false, + ) { + Ok(Value::Int(n)) => n, + _ => return Err("the file observation's byte_count is not a byte size".to_string()), }; Ok(FileResult { success: true, From 355cd9d06e0de1af92a78fb2b59b00aefb24bd43 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 28 Sep 2026 19:08:54 +0000 Subject: [PATCH 18/38] Matrix: move the live-holder note above its declaration (annotations are module-item grain) Co-Authored-By: Claude Opus 5.5 (1M context) --- .../machine_intake/mtcollins1_boot_acceptance_matrix_test.dag | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag index 07783cc4a13..fe571d15a1e 100644 --- a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag +++ b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag @@ -274,9 +274,9 @@ test fn pinned_a_healthy_census_is_refused_at_the_sol_teardown() -> Bool { // THE SAME UNIT HELD BY ANOTHER RUN. Before this attempt starts, a different run's boot holds the // unit (acquired through the production file_hold_acquire, into the same store). This attempt is // refused at the hold with the controller untouched: no controller write of any kind appears in its -// route, and it never starts a SOL collector. +// route, and it never starts a SOL collector. The other run is still running -- its process is in +// procfs -- so it is a live holder, not a dead one to recover. test fn a_second_run_for_a_held_unit_writes_nothing_to_the_controller() -> Bool { - // The other run is still running: its process is in procfs, so it is a live holder, not a dead one. let base = world_with_console(lines: []) let other_running = with_fs(w: base, fs: fs_with_file(fs: base.fs, path: proc_stat_path(pid: 2900), content: proc_stat_line(pid: 2900, start_ticks: 100))) let prior = evaluate_in_witness_frame( From 441d65356fa3bfeb8a25d71f1535cf91bc94dd0f Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 28 Sep 2026 19:49:25 +0000 Subject: [PATCH 19/38] Unit hold: the holder's identity carries its pid namespace; a missing pid is death only when read from that namespace, otherwise unobservable (review 72326) Co-Authored-By: Claude Opus 5.5 (1M context) --- .../mtcollins1_boot_dry_realization.dag | 26 ++++- .../mtcollins1_maintenance_hold.dag | 100 +++++++++++++----- ...mtcollins1_boot_acceptance_matrix_test.dag | 30 +++++- ...collins1_maintenance_hold_witness_test.dag | 17 +-- .../claim/modeled_filesystem_witness_test.dag | 4 +- 5 files changed, 136 insertions(+), 41 deletions(-) diff --git a/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag b/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag index 12a20378ff4..c818dd919e3 100644 --- a/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag +++ b/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag @@ -1,5 +1,6 @@ module gunbc.machine_intake_mtcollins1_boot_dry_realization +import extdeps.tools.gnu_coreutils { readlink_command } import std.types { Bool, Int, List, NonEmptyStr, String } import std.measure { Second, second, second_count } import v2.std.operation_argv { OperationRef } @@ -128,7 +129,10 @@ fn proc_cmdline_path(pid: Int) -> String { // read from these (gunbc.machine_intake_mtcollins1_maintenance_hold self_process_identity) and a // successor observes the holder through them (holder_process_liveness), so a worker that starts writes // both, and a worker that is killed loses its /proc//stat: the kernel removes a dead process's -// directory. Only the fields a reader uses are meaningful; the others are zeros. +// directory. Only the fields a reader uses are meaningful; the others are zeros. The worker's pid +// namespace is the target of the /proc/self/ns/pid link, which the model stores as that path's text +// and answers to `readlink /proc/self/ns/pid` (readlink_answer); the kernel refuses to read the link +// itself, and production only ever readlinks it. data proc_boot_id_file: String = "/proc/sys/kernel/random/boot_id" fn proc_stat_path(pid: Int) -> String { @@ -139,9 +143,21 @@ fn proc_stat_line(pid: Int, start_ticks: Int) -> String { join([to_string(pid), " (gunbc) S 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ", to_string(start_ticks), " 0 0\n"], "") } -fn worker_process_started(fs: ModeledFilesystem, boot_id: String, pid: Int, start_ticks: Int) -> ModeledFilesystem { +data proc_self_pid_namespace_link: String = "/proc/self/ns/pid" + +fn worker_process_started(fs: ModeledFilesystem, boot_id: String, pid_namespace: String, pid: Int, start_ticks: Int) -> ModeledFilesystem { let line = proc_stat_line(pid: pid, start_ticks: start_ticks) - fs_with_file(fs: fs_with_file(fs: fs_with_file(fs: fs, path: proc_boot_id_file, content: concat(boot_id, "\n")), path: "/proc/self/stat", content: line), path: proc_stat_path(pid: pid), content: line) + let with_ns = fs_with_file(fs: fs, path: proc_self_pid_namespace_link, content: pid_namespace) + fs_with_file(fs: fs_with_file(fs: fs_with_file(fs: with_ns, path: proc_boot_id_file, content: concat(boot_id, "\n")), path: "/proc/self/stat", content: line), path: proc_stat_path(pid: pid), content: line) +} + +// readlink(1): the link's target and a newline on stdout, exit 0; for a missing link, a diagnostic +// on stderr and exit 1. +fn readlink_answer(w: MtCollins1BootWorld, path: String) -> ShellExchangeObservation { + match fs_file(fs: w.fs, path: path) { + Present { value: f } => ShellProcessExited { exit_code: 0, stdout: concat(f.content, "\n"), stderr: "" } + Absent => ShellProcessExited { exit_code: 1, stdout: "", stderr: "" } + } } fn worker_process_killed(fs: ModeledFilesystem, pid: Int) -> ModeledFilesystem { @@ -303,6 +319,10 @@ fn mtcollins1_boot_dry_realization(identity: NonEmptyStr, initial: MtCollins1Boo words: local_command_words(command: env_prefixed_command(bindings: [EnvSet { name: "SSH_AUTH_SOCK", value: initial.agent.socket }], command: ssh_add_list_identities_command())), answer: fn(w) { agent_listing(agent: w.agent) }, }, + ModeledInvocation { + words: local_command_words(command: readlink_command(path: proc_self_pid_namespace_link)), + answer: fn(w) { readlink_answer(w: w, path: proc_self_pid_namespace_link) }, + }, ]) let spanning = [ OperationBinding { at: bmc_ipmi_operation(operation: "SdrDumpAuthenticated"), handler: sdr_dump_handler }, diff --git a/dag/gunbc/machine_intake/mtcollins1_maintenance_hold.dag b/dag/gunbc/machine_intake/mtcollins1_maintenance_hold.dag index d1aef9b1d83..ea74599d698 100644 --- a/dag/gunbc/machine_intake/mtcollins1_maintenance_hold.dag +++ b/dag/gunbc/machine_intake/mtcollins1_maintenance_hold.dag @@ -16,6 +16,10 @@ import std.nat { Nat } import std.checked_arithmetic { checked_int_to_nat } import std.algebra { trim } import gunbc.build_cache_instance { ProcessIdentity, process_identity_eq } +import gunbc.command_runner { run_shell_command_capture } +import extdeps.exec.command { LocalExec } +import extdeps.shell.exec { ProcessOutcome, ProcessOutputPresent, ProcessOutputAbsent, ProcessRefused } +import extdeps.tools.gnu_coreutils { readlink_command } import extdeps.linux.proc_pid_stat { proc_pid_stat_start_time, StartTimeObserved, StartTimeCommUnterminated, StartTimeFieldAbsent, StartTimeUnparseable, proc_pid_stat_pid, PidObserved, PidUnparseable, @@ -75,28 +79,36 @@ data mtcollins1_unit_hold_store_host: NonEmptyStr = operator_host_srv1 as NonEmp // worker killed while holding the unit left a hold nobody could ever show to be dead, and every later // attempt refused on it. The boot's owner therefore carries the holding process's identity -- boot id, // pid and start time, gunbc.build_cache_instance ProcessIdentity, the triple that survives pid reuse -// and reboots -- and a successor recovers the slot only after OBSERVING that process gone. An operator +// and reboots -- and the PID NAMESPACE it was read in (review 72326): a pid means something only in +// its namespace, and boot_id is shared by every namespace on a host, so a successor in another +// namespace would find NotFound for a holder that is alive. A successor recovers the slot only after +// OBSERVING that process gone FROM THE HOLDER'S OWN NAMESPACE; from any other it cannot look. An operator // maintenance hold is deliberately not a process (it outlives the session that took it), so it is // never recovered; a host reset-return hold names no process yet, so it is never recovered either. type UnitHoldOwner = OperatorMaintenance { reason: NonEmptyStr } - | BootRun { run_id: NonEmptyStr, process: ProcessIdentity } + | BootRun { run_id: NonEmptyStr, process: ProcessIdentity, pid_namespace: NonEmptyStr } | HostResetReturn { attempt: NonEmptyStr } fn unit_hold_owner_tag(o: UnitHoldOwner) -> String { match o { OperatorMaintenance { reason: _ } => "operator-maintenance:" - BootRun { run_id: _, process: _ } => "mtcollins1-boot:" + BootRun { run_id: _, process: _, pid_namespace: _ } => "mtcollins1-boot:" HostResetReturn { attempt: _ } => "host-reset-return:" } } // THE PROCESS PART OF A BOOT OWNER, rendered after the run id and decoded back here and nowhere else: -// `@boot=,pid=,start=`. +// `@boot=,pid=,start=,pidns=`. data unit_hold_process_marker: String = "@boot=" -fn unit_hold_process_text(p: ProcessIdentity) -> String { - join([unit_hold_process_marker, p.boot_id as String, ",pid=", to_string(p.pid), ",start=", p.start_time as String], "") +fn unit_hold_process_text(p: ProcessIdentity, pid_namespace: NonEmptyStr) -> String { + join([unit_hold_process_marker, p.boot_id as String, ",pid=", to_string(p.pid), ",start=", p.start_time as String, ",pidns=", pid_namespace as String], "") +} + +type UnitHoldHolderProcess { + process: ProcessIdentity + pid_namespace: NonEmptyStr } fn text_at(xs: List, i: Int) -> String { @@ -106,26 +118,28 @@ fn text_at(xs: List, i: Int) -> String { } } -fn unit_hold_process_decode(text: String) -> ProcessIdentity? { +fn unit_hold_process_decode(text: String) -> UnitHoldHolderProcess? { let parts = split(s: text, delimiter: ",") - if count(parts) != 3 { + if count(parts) != 4 { none } else { let b = text_at(xs: parts, i: 0) let pid_text = text_at(xs: parts, i: 1) let start_text = text_at(xs: parts, i: 2) - if !starts_with(s: b, prefix: "boot=") || !starts_with(s: pid_text, prefix: "pid=") || !starts_with(s: start_text, prefix: "start=") { + let ns_text = text_at(xs: parts, i: 3) + if !starts_with(s: b, prefix: "boot=") || !starts_with(s: pid_text, prefix: "pid=") || !starts_with(s: start_text, prefix: "start=") || !starts_with(s: ns_text, prefix: "pidns=") { none } else { let boot = substring(s: b, start: 5, end: string_length(b)) let start = substring(s: start_text, start: 6, end: string_length(start_text)) + let ns = substring(s: ns_text, start: 6, end: string_length(ns_text)) match parse_int(s: substring(s: pid_text, start: 4, end: string_length(pid_text))) { Absent => none Present { value: n } => match checked_int_to_nat(n: n) { Absent => none Present { value: pid } => - if boot == "" || start == "" { none } else { Present { value: ProcessIdentity { boot_id: boot as NonEmptyStr, pid: pid, start_time: start as NonEmptyStr } } } + if boot == "" || start == "" || ns == "" { none } else { Present { value: UnitHoldHolderProcess { process: ProcessIdentity { boot_id: boot as NonEmptyStr, pid: pid, start_time: start as NonEmptyStr }, pid_namespace: ns as NonEmptyStr } } } } } } @@ -135,7 +149,7 @@ fn unit_hold_process_decode(text: String) -> ProcessIdentity? { fn unit_hold_owner_detail(o: UnitHoldOwner) -> String { match o { OperatorMaintenance { reason: r } => r as String - BootRun { run_id: r, process: p } => concat(r as String, unit_hold_process_text(p: p)) + BootRun { run_id: r, process: p, pid_namespace: n } => concat(r as String, unit_hold_process_text(p: p, pid_namespace: n)) HostResetReturn { attempt: a } => a as String } } @@ -147,7 +161,7 @@ fn unit_hold_owner_ref(o: UnitHoldOwner) -> DurableHoldOwnerRef { fn unit_hold_owner_label(o: UnitHoldOwner) -> String { match o { OperatorMaintenance { reason: r } => concat("operator maintenance: ", r as String) - BootRun { run_id: r, process: p } => join(["the mtcollins1 boot run ", r as String, " (process ", to_string(p.pid), " on boot ", p.boot_id as String, ")"], "") + BootRun { run_id: r, process: p, pid_namespace: _ } => join(["the mtcollins1 boot run ", r as String, " (process ", to_string(p.pid), " on boot ", p.boot_id as String, ")"], "") HostResetReturn { attempt: a } => concat("host reset-return attempt ", a as String) } } @@ -166,7 +180,7 @@ fn unit_hold_owner_detail_after(raw: String, tag: String) -> NonEmptyStr? { fn decode_unit_hold_owner(owner: DurableHoldOwnerRef) -> UnitHoldOwnerDecode { let raw = owner as String let op = unit_hold_owner_tag(o: OperatorMaintenance { reason: "x" as NonEmptyStr }) - let boot = unit_hold_owner_tag(o: BootRun { run_id: "x" as NonEmptyStr, process: ProcessIdentity { boot_id: "x" as NonEmptyStr, pid: 1, start_time: "x" as NonEmptyStr } }) + let boot = unit_hold_owner_tag(o: BootRun { run_id: "x" as NonEmptyStr, process: ProcessIdentity { boot_id: "x" as NonEmptyStr, pid: 1, start_time: "x" as NonEmptyStr }, pid_namespace: "x" as NonEmptyStr }) let reset = unit_hold_owner_tag(o: HostResetReturn { attempt: "x" as NonEmptyStr }) if starts_with(s: raw, prefix: op) { match unit_hold_owner_detail_after(raw: raw, tag: op) { Present { value: d } => UnitHoldOwnerDecoded { owner: OperatorMaintenance { reason: d } } Absent => UnitHoldOwnerUnrecognized { raw: raw } } @@ -179,7 +193,7 @@ fn decode_unit_hold_owner(owner: DurableHoldOwnerRef) -> UnitHoldOwnerDecode { UnitHoldOwnerUnrecognized { raw: raw } } else { match unit_hold_process_decode(text: concat("boot=", text_at(xs: halves, i: 1))) { - Present { value: p } => UnitHoldOwnerDecoded { owner: BootRun { run_id: text_at(xs: halves, i: 0) as NonEmptyStr, process: p } } + Present { value: h } => UnitHoldOwnerDecoded { owner: BootRun { run_id: text_at(xs: halves, i: 0) as NonEmptyStr, process: h.process, pid_namespace: h.pid_namespace } } Absent => UnitHoldOwnerUnrecognized { raw: raw } } } @@ -203,8 +217,8 @@ fn operator_maintenance_owner(reason: NonEmptyStr) -> DurableHoldOwnerRef { unit_hold_owner_ref(o: OperatorMaintenance { reason: reason }) } -fn boot_run_owner(run_id: NonEmptyStr, process: ProcessIdentity) -> DurableHoldOwnerRef { - unit_hold_owner_ref(o: BootRun { run_id: run_id, process: process }) +fn boot_run_owner(run_id: NonEmptyStr, process: ProcessIdentity, pid_namespace: NonEmptyStr) -> DurableHoldOwnerRef { + unit_hold_owner_ref(o: BootRun { run_id: run_id, process: process, pid_namespace: pid_namespace }) } // ── THE PROOF: MINTED ONLY INSIDE THE LIVE ACQUIRE'S FileHoldAcquired ARM ────────────────────── @@ -260,6 +274,25 @@ data proc_boot_id_path: String = "/proc/sys/kernel/random/boot_id" data proc_self_stat_path: String = "/proc/self/stat" +// THE READER'S PID NAMESPACE (namespaces(7), pid_namespaces(7)): /proc/self/ns/pid is a symbolic link +// whose target, `pid:[]`, identifies the namespace; two processes share a pid namespace exactly +// when the targets are equal. It is read with readlink(1), because the link is not a readable file. +data proc_self_pid_namespace_path: String = "/proc/self/ns/pid" + +type PidNamespaceReading + = PidNamespaceRead { namespace: NonEmptyStr } + | PidNamespaceUnread { cause: NonEmptyStr } + +fn own_pid_namespace() -> PidNamespaceReading { + match run_shell_command_capture(command: readlink_command(path: proc_self_pid_namespace_path), transport: LocalExec) { + ProcessOutputPresent { text: t } => + if trim(s: t) == "" { PidNamespaceUnread { cause: "readlink /proc/self/ns/pid printed nothing" as NonEmptyStr } } + else { PidNamespaceRead { namespace: trim(s: t) as NonEmptyStr } } + ProcessOutputAbsent => PidNamespaceUnread { cause: "readlink /proc/self/ns/pid printed nothing" as NonEmptyStr } + ProcessRefused { exit_code: c, stderr: e } => PidNamespaceUnread { cause: join(["readlink /proc/self/ns/pid exited ", to_string(c), ": ", e], "") as NonEmptyStr } + } +} + fn exact_read_of(path: String) -> FilesystemExactRead { let read = Filesystem.Read(path: path) filesystem_exact_read(path: path, content: read.content, success: read.success, error: read.error, error_kind: read.error_kind) @@ -286,7 +319,21 @@ fn start_time_text(stat: String) -> String? { } } -fn holder_process_liveness(p: ProcessIdentity) -> DurableHoldHolderLiveness { +// THE NAMESPACE GATE COMES FIRST: only a reader in the holder's own pid namespace can conclude +// anything from a pid, so a reader elsewhere -- or one that cannot name its namespace -- cannot look. +fn holder_process_liveness(p: ProcessIdentity, pid_namespace: NonEmptyStr) -> DurableHoldHolderLiveness { + match own_pid_namespace() { + PidNamespaceUnread { cause: c } => HolderLivenessUnobservable { cause: c } + PidNamespaceRead { namespace: mine } => + if (mine as String) != (pid_namespace as String) { + HolderLivenessUnobservable { cause: join(["the holder's pid namespace is ", pid_namespace as String, " and this reader's is ", mine as String, "; its pid cannot be looked up from here"], "") as NonEmptyStr } + } else { + holder_process_liveness_in_namespace(p: p) + } + } +} + +fn holder_process_liveness_in_namespace(p: ProcessIdentity) -> DurableHoldHolderLiveness { match exact_text(path: proc_boot_id_path) { ExactTextAbsent => HolderLivenessUnobservable { cause: concat(proc_boot_id_path, " is absent on this host") as NonEmptyStr } ExactTextUnread { cause: c } => HolderLivenessUnobservable { cause: c } @@ -321,7 +368,7 @@ fn holder_process_liveness(p: ProcessIdentity) -> DurableHoldHolderLiveness { // boot id, and pid and starttime from ONE read of /proc/self/stat. A boot that cannot name itself does // not take the unit -- a hold it could never be shown dead in is the lockout this replaces. type SelfProcessIdentity - = SelfIdentified { process: ProcessIdentity } + = SelfIdentified { process: ProcessIdentity, pid_namespace: NonEmptyStr } | SelfUnidentified { cause: NonEmptyStr } fn self_process_identity() -> SelfProcessIdentity { @@ -344,7 +391,12 @@ fn self_process_identity() -> SelfProcessIdentity { Absent => SelfUnidentified { cause: "/proc/self/stat pid is not a natural number" as NonEmptyStr } Present { value: pid } => if boot == "" { SelfUnidentified { cause: concat(proc_boot_id_path, " was empty") as NonEmptyStr } } - else { SelfIdentified { process: ProcessIdentity { boot_id: boot as NonEmptyStr, pid: pid, start_time: st as NonEmptyStr } } } + else { + match own_pid_namespace() { + PidNamespaceUnread { cause: c } => SelfUnidentified { cause: c } + PidNamespaceRead { namespace: n } => SelfIdentified { process: ProcessIdentity { boot_id: boot as NonEmptyStr, pid: pid, start_time: st as NonEmptyStr }, pid_namespace: n } + } + } } } } @@ -360,7 +412,7 @@ fn self_process_identity() -> SelfProcessIdentity { // in between is not the one freed. Age is never an input. fn holder_report(holder: DurableHoldOwnerRef, generation: CasGeneration) -> DurableHoldHolderReport { let liveness = match decode_unit_hold_owner(owner: holder) { - UnitHoldOwnerDecoded { owner: BootRun { run_id: _, process: p } } => holder_process_liveness(p: p) + UnitHoldOwnerDecoded { owner: BootRun { run_id: _, process: p, pid_namespace: n } } => holder_process_liveness(p: p, pid_namespace: n) UnitHoldOwnerDecoded { owner: OperatorMaintenance { reason: _ } } => HolderLivenessUnobservable { cause: "an operator maintenance hold is not bound to a process; only the operator releases it" as NonEmptyStr } UnitHoldOwnerDecoded { owner: HostResetReturn { attempt: _ } } => @@ -460,8 +512,8 @@ fn mtcollins1_boot_acquire_unit_hold(run_id: NonEmptyStr) -> UnitHoldAcquisition match self_process_identity() { SelfUnidentified { cause: c } => UnitHoldRefused { reason: join(["this boot cannot name its own process (", c as String, "), and a hold it could never be shown dead in would lock the unit out if it died; controller untouched"], "") as NonEmptyStr } - SelfIdentified { process: p } => - unit_hold_acquire(root: unit_hold_store_root, key: mtcollins1_unit_hold_key, owner: boot_run_owner(run_id: run_id, process: p)) + SelfIdentified { process: p, pid_namespace: n } => + unit_hold_acquire(root: unit_hold_store_root, key: mtcollins1_unit_hold_key, owner: boot_run_owner(run_id: run_id, process: p, pid_namespace: n)) } } @@ -535,8 +587,8 @@ fn mtcollins1_maintenance_hold_release() -> ProcessExit { match decode_unit_hold_owner(owner: o) { UnitHoldOwnerUnrecognized { raw: r } => exit_failure(reason: concat("mtcollins1 maintenance hold: held by an owner this module did not write, so it is not released from here: ", r)) - UnitHoldOwnerDecoded { owner: BootRun { run_id: r, process: p } } => - exit_failure(reason: concat("mtcollins1 maintenance hold: held by ", concat(unit_hold_owner_label(o: BootRun { run_id: r, process: p }), ", not by maintenance; that run frees its own hold, and the next boot recovers it if that run is observed dead"))) + UnitHoldOwnerDecoded { owner: BootRun { run_id: r, process: p, pid_namespace: n } } => + exit_failure(reason: concat("mtcollins1 maintenance hold: held by ", concat(unit_hold_owner_label(o: BootRun { run_id: r, process: p, pid_namespace: n }), ", not by maintenance; that run frees its own hold, and the next boot recovers it if that run is observed dead"))) UnitHoldOwnerDecoded { owner: HostResetReturn { attempt: a } } => exit_failure(reason: concat("mtcollins1 maintenance hold: held by ", concat(unit_hold_owner_label(o: HostResetReturn { attempt: a }), ", not by maintenance; that run frees its own hold"))) UnitHoldOwnerDecoded { owner: OperatorMaintenance { reason: _ } } => diff --git a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag index fe571d15a1e..28898b519b7 100644 --- a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag +++ b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag @@ -123,6 +123,7 @@ fn healthy_media() -> MegaRacMediaWorld { // The worker host's boot, and the boot worker's own process (pid, start tick) as procfs shows it. data worker_boot_id: String = "73c8153e-0e0e-4480-a36c-bc7560c50e8b" +data worker_pid_namespace: String = "pid:[4026531836]" data first_worker_pid: Int = 3100 data first_worker_start: Int = 5000 @@ -133,7 +134,7 @@ fn worker_filesystem() -> ModeledFilesystem { files: [ModeledFile { path: credential_path, content: "secret" }], refused_reads: [], }, - boot_id: worker_boot_id, pid: first_worker_pid, start_ticks: first_worker_start, + boot_id: worker_boot_id, pid_namespace: worker_pid_namespace, pid: first_worker_pid, start_ticks: first_worker_start, ) } @@ -281,7 +282,7 @@ test fn a_second_run_for_a_held_unit_writes_nothing_to_the_controller() -> Bool let other_running = with_fs(w: base, fs: fs_with_file(fs: base.fs, path: proc_stat_path(pid: 2900), content: proc_stat_line(pid: 2900, start_ticks: 100))) let prior = evaluate_in_witness_frame( frame: matrix_frame(world: other_running), - subject: fn(_scope) { file_hold_acquire(root: unit_hold_store_root, slot_key: mtcollins1_unit_hold_key, requested_owner: boot_run_owner(run_id: "4141" as NonEmptyStr, process: ProcessIdentity { boot_id: worker_boot_id as NonEmptyStr, pid: 2900, start_time: "100" as NonEmptyStr })) } + subject: fn(_scope) { file_hold_acquire(root: unit_hold_store_root, slot_key: mtcollins1_unit_hold_key, requested_owner: boot_run_owner(run_id: "4141" as NonEmptyStr, process: ProcessIdentity { boot_id: worker_boot_id as NonEmptyStr, pid: 2900, start_time: "100" as NonEmptyStr }, pid_namespace: worker_pid_namespace as NonEmptyStr)) } ) match prior { WitnessReturned { state } => match state { @@ -500,8 +501,12 @@ fn stopped_after_start_media(realization: OperationRealization MtCollins1BootWorld { + as_another_run_in(w: w, run_id: run_id, pid_namespace: worker_pid_namespace) +} + +fn as_another_run_in(w: MtCollins1BootWorld, run_id: String, pid_namespace: String) -> MtCollins1BootWorld { let env = map(w.environment, v => if v.name == "GITHUB_RUN_ID" { ModeledVariable { name: v.name, value: run_id } } else { v }) - let fs = worker_process_started(fs: w.fs, boot_id: worker_boot_id, pid: first_worker_pid + 1, start_ticks: first_worker_start + 9000) + let fs = worker_process_started(fs: w.fs, boot_id: worker_boot_id, pid_namespace: pid_namespace, pid: first_worker_pid + 1, start_ticks: first_worker_start + 9000) MtCollins1BootWorld { bmc: w.bmc, fs: fs, environment: env, agent: w.agent, remote_hosts: w.remote_hosts, clock: w.clock, worker: w.worker, console: w.console, media: w.media } } @@ -512,9 +517,13 @@ fn as_another_run(w: MtCollins1BootWorld, run_id: String) -> MtCollins1BootWorld // The two controls beside it differ only in what procfs says of the holder: still running, and a stat // read the host refuses. Neither is ever recovered, and neither writes to the controller. fn resumed_after(realization: OperationRealization, refuse_holder_stat: Bool) -> WitnessEvaluation? { + resumed_in(realization: realization, refuse_holder_stat: refuse_holder_stat, pid_namespace: worker_pid_namespace) +} + +fn resumed_in(realization: OperationRealization, refuse_holder_stat: Bool, pid_namespace: String) -> WitnessEvaluation? { match evaluate_in_witness_frame(frame: matrix_frame_over(realization: realization), subject: fn(_scope) { mtcollins1_boot_wet_on_srv1() }) { WitnessInterrupted { at, state, now: interrupted_clock } => { - let another = as_another_run(w: state, run_id: "4243") + let another = as_another_run_in(w: state, run_id: "4243", pid_namespace: pid_namespace) let next = if refuse_holder_stat { with_fs(w: another, fs: fs_refusing_reads_of(fs: another.fs, path: proc_stat_path(pid: first_worker_pid))) } else { another } if at.invocation.at.operation != "StartMedia" { none } else { Present { value: evaluate_in_witness_frame( @@ -557,3 +566,16 @@ test fn an_unobservable_holder_is_refused_not_recovered() -> Bool { _ => false } } + +// A SUCCESSOR IN ANOTHER PID NAMESPACE CANNOT LOOK (review 72326). The holder is dead and its +// /proc entry gone, exactly as in the recovery case above; the only difference is that the next run +// reads a different /proc/self/ns/pid. From there a missing pid proves nothing, so the hold is +// refused, never recovered, and nothing is written. The red is recovery across namespaces. +test fn a_successor_in_another_pid_namespace_refuses_rather_than_recovering() -> Bool { + match resumed_in(realization: killed_after_start_media(realization: fresh_realization()), refuse_holder_stat: false, pid_namespace: "pid:[4026532999]") { + Present { value: WitnessReturned { value, route } } => + string_contains(s: outcome_reason(a: value), pattern: "its pid cannot be looked up from here") + && count(filter(route, r => is_controller_write(r: r))) == 0 + _ => false + } +} diff --git a/dag/test/claim/machine_intake/mtcollins1_maintenance_hold_witness_test.dag b/dag/test/claim/machine_intake/mtcollins1_maintenance_hold_witness_test.dag index b28d952e40e..3144de4c820 100644 --- a/dag/test/claim/machine_intake/mtcollins1_maintenance_hold_witness_test.dag +++ b/dag/test/claim/machine_intake/mtcollins1_maintenance_hold_witness_test.dag @@ -58,12 +58,12 @@ test fn the_boot_refuses_under_an_active_maintenance_hold_and_names_the_holder() generation: first_generation(), } refused_naming(r: unit_hold_refusal(outcome: held), pattern: "operator-maintenance:reseating DIMMs") - && not_refused(r: unit_hold_refusal(outcome: FileHoldAcquired { slot_key: mtcollins1_unit_hold_key, owner: boot_run_owner(run_id: "42", process: holder_process), generation: first_generation() })) + && not_refused(r: unit_hold_refusal(outcome: FileHoldAcquired { slot_key: mtcollins1_unit_hold_key, owner: boot_run_owner(run_id: "42", process: holder_process, pid_namespace: "pid:[4026531836]"), generation: first_generation() })) } // ANOTHER HOLDER IS A HOLDER: a running boot, or a host reset-return, excludes the next writer. test fn a_second_writer_refuses_while_another_run_holds_the_unit() -> Bool { - refused_naming(r: unit_hold_refusal(outcome: FileHoldOccupied { slot_key: mtcollins1_unit_hold_key, holder: boot_run_owner(run_id: "41", process: holder_process), generation: first_generation() }), pattern: "mtcollins1-boot:41") + refused_naming(r: unit_hold_refusal(outcome: FileHoldOccupied { slot_key: mtcollins1_unit_hold_key, holder: boot_run_owner(run_id: "41", process: holder_process, pid_namespace: "pid:[4026531836]"), generation: first_generation() }), pattern: "mtcollins1-boot:41") && refused_naming(r: unit_hold_refusal(outcome: FileHoldOccupied { slot_key: mtcollins1_unit_hold_key, holder: host_reset_owner(attempt: "srv1-mtcollins1"), generation: first_generation() }), pattern: "host-reset-return:srv1-mtcollins1") } @@ -141,7 +141,7 @@ fn round_trips(o: UnitHoldOwner, label: String) -> Bool { test fn every_holder_kind_round_trips_and_a_foreign_owner_is_refused() -> Bool { round_trips(o: OperatorMaintenance { reason: "reseating DIMMs" as NonEmptyStr }, label: "operator maintenance: reseating DIMMs") - && round_trips(o: BootRun { run_id: "41" as NonEmptyStr, process: holder_process }, label: "the mtcollins1 boot run 41 (process 3975 on boot 73c8153e-0e0e-4480-a36c-bc7560c50e8b)") + && round_trips(o: BootRun { run_id: "41" as NonEmptyStr, process: holder_process, pid_namespace: "pid:[4026531836]" as NonEmptyStr }, label: "the mtcollins1 boot run 41 (process 3975 on boot 73c8153e-0e0e-4480-a36c-bc7560c50e8b)") && round_trips(o: HostResetReturn { attempt: "srv1-mtcollins1" as NonEmptyStr }, label: "host reset-return attempt srv1-mtcollins1") && match decode_unit_hold_owner(owner: "someone-else:x" as DurableHoldOwnerRef) { UnitHoldOwnerUnrecognized { raw: _ } => true @@ -157,19 +157,20 @@ test fn every_holder_kind_round_trips_and_a_foreign_owner_is_refused() -> Bool { // carries boot id, pid and start time, and decodes to the same identity -- the identity a successor // observes the holder by. A boot owner written WITHOUT a process (the spelling before this change, // still possibly on disk) is not guessed into one: it decodes as unrecognized, so its holder is -// unobservable and its hold is refused rather than broken. +// unobservable and its hold is refused rather than broken. So is one naming a process but not the +// pid namespace it was read in (review 72326): its pid has no meaning without it. test fn a_boot_owner_carries_its_process_and_a_processless_one_is_not_recoverable() -> Bool { - let rendered = boot_run_owner(run_id: "4242", process: holder_process) as String - rendered == "mtcollins1-boot:4242@boot=73c8153e-0e0e-4480-a36c-bc7560c50e8b,pid=3975,start=81234" + let rendered = boot_run_owner(run_id: "4242", process: holder_process, pid_namespace: "pid:[4026531836]") as String + rendered == "mtcollins1-boot:4242@boot=73c8153e-0e0e-4480-a36c-bc7560c50e8b,pid=3975,start=81234,pidns=pid:[4026531836]" && (match decode_unit_hold_owner(owner: rendered as DurableHoldOwnerRef) { - UnitHoldOwnerDecoded { owner: BootRun { run_id: r, process: p } } => (r as String) == "4242" && p.pid == 3975 && (p.start_time as String) == "81234" + UnitHoldOwnerDecoded { owner: BootRun { run_id: r, process: p, pid_namespace: n } } => (n as String) == "pid:[4026531836]" && (r as String) == "4242" && p.pid == 3975 && (p.start_time as String) == "81234" _ => false }) && (match decode_unit_hold_owner(owner: "mtcollins1-boot:4242" as DurableHoldOwnerRef) { UnitHoldOwnerUnrecognized { raw: _ } => true _ => false }) - && (match decode_unit_hold_owner(owner: "mtcollins1-boot:4242@boot=x,pid=-1,start=5" as DurableHoldOwnerRef) { + && (match decode_unit_hold_owner(owner: "mtcollins1-boot:4242@boot=x,pid=3975,start=5" as DurableHoldOwnerRef) { UnitHoldOwnerUnrecognized { raw: _ } => true _ => false }) diff --git a/dag/test/claim/modeled_filesystem_witness_test.dag b/dag/test/claim/modeled_filesystem_witness_test.dag index ca802309a74..00b77c6366f 100644 --- a/dag/test/claim/modeled_filesystem_witness_test.dag +++ b/dag/test/claim/modeled_filesystem_witness_test.dag @@ -98,8 +98,8 @@ fn occupied_by(o: gunbc.durable_exclusive_hold_file_store.FileHoldAcquireOutcome // is the production fold's reading of what the model's filesystem holds, and the route shows the // second contender never wrote. test fn two_contenders_for_one_unit_get_one_hold() -> Bool { - let first_owner = boot_run_owner(run_id: "run-a" as NonEmptyStr, process: ProcessIdentity { boot_id: "b" as NonEmptyStr, pid: 10, start_time: "1" as NonEmptyStr }) - let second_owner = boot_run_owner(run_id: "run-b" as NonEmptyStr, process: ProcessIdentity { boot_id: "b" as NonEmptyStr, pid: 11, start_time: "2" as NonEmptyStr }) + let first_owner = boot_run_owner(run_id: "run-a" as NonEmptyStr, process: ProcessIdentity { boot_id: "b" as NonEmptyStr, pid: 10, start_time: "1" as NonEmptyStr }, pid_namespace: "pid:[1]" as NonEmptyStr) + let second_owner = boot_run_owner(run_id: "run-b" as NonEmptyStr, process: ProcessIdentity { boot_id: "b" as NonEmptyStr, pid: 11, start_time: "2" as NonEmptyStr }, pid_namespace: "pid:[1]" as NonEmptyStr) let result = evaluate_in_witness_frame( frame: filesystem_frame(initial: empty_hold_store()), subject: fn(_scope) { From b4cc96d3da8bd3797676a1ca22fc14613c9a9b57 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 28 Sep 2026 20:45:17 +0000 Subject: [PATCH 20/38] Hold witness takes its report generation from an observation; the recovery cases (and the duplicate-listing case, now over by procfs reads) join the boot-matrix cost-drop roster, measured by floor run 36474912729 Co-Authored-By: Claude Opus 5.5 (1M context) --- ...boot_matrix_new_witness_eval_step_cost.dag | 4 ++-- .../durable_exclusive_hold_witness_test.dag | 13 ++++++++++-- docs/design-rung-drops.md | 4 ++-- src/v2/workflow/floor_eval_step_cost_drop.dag | 20 +++++++++++++++++++ 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag b/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag index 9a841cf09e1..2a5adbda1ec 100644 --- a/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag +++ b/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag @@ -28,7 +28,7 @@ data mtcollins1_boot_matrix_new_witness_eval_step_cost_population: List data mtcollins1_boot_matrix_new_witness_eval_step_cost: RungDrop = RungDrop { identity: "mtcollins1_boot_matrix_new_witness_eval_step_cost" as NonEmptyStr, - subject: "new-witness eval-step cost gate over the eight mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered", + subject: "new-witness eval-step cost gate over the mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered", declared: "2026-09-28", @@ -41,6 +41,6 @@ data mtcollins1_boot_matrix_new_witness_eval_step_cost: RungDrop = RungDrop { replacement: "the evaluation frame and its modeled operation realization realized by the natively emitted runtime, so the boot entry runs as emitted code rather than in the seed interpreter", }, population: mtcollins1_boot_matrix_new_witness_eval_step_cost_population, - restoration_trigger: "THE CAPABILITY: witnesses emitted to native code with the emitted runtime realizing v2.std.witness_evaluation evaluate_in_witness_frame and its v2.std.operation_realization modeled realization, EXECUTING ON THE MERGE PATH as a phase of a required lane whose red blocks a merge, running these eight identities with the real mtcollins1_boot_wet_on_srv1 entry; WHAT THAT MUST BE SUFFICIENT FOR: each case measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither.", + restoration_trigger: "THE CAPABILITY: witnesses emitted to native code with the emitted runtime realizing v2.std.witness_evaluation evaluate_in_witness_frame and its v2.std.operation_realization modeled realization, EXECUTING ON THE MERGE PATH as a phase of a required lane whose red blocks a merge, running these identities with the real mtcollins1_boot_wet_on_srv1 entry; WHAT THAT MUST BE SUFFICIENT FOR: each case measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither.", } } diff --git a/dag/test/claim/durable_exclusive_hold_witness_test.dag b/dag/test/claim/durable_exclusive_hold_witness_test.dag index 8d696eaa972..49c7118e0ee 100644 --- a/dag/test/claim/durable_exclusive_hold_witness_test.dag +++ b/dag/test/claim/durable_exclusive_hold_witness_test.dag @@ -7,7 +7,7 @@ import std.durable_compare_and_set { CasExpectation, ExpectSlotAbsent, ExpectSlotGeneration, CasSlotObservation, CasObservedReadable, CasObservedUnreadable, CasReadableAbsent, CasReadablePresent, CasSlotVersion, - CasUnreadableReadRefused, cas_generation_count, CasGeneration, + CasUnreadableReadRefused, cas_generation_count, CasGeneration, cas_generation_first, } import std.durable_exclusive_hold { DurableHoldOwnerRef, DurableHoldReleaseRef, @@ -315,10 +315,19 @@ test fn holder_equality_is_exact() -> Bool { // ONLY in the liveness verdict: a live holder blocks, an observed-dead holder is recovered at the // generation both the read and the report name, and an unobservable holder refuses -- never // recovered. There is no age input to vary: the assessment takes none. +// The report's generation is taken from an observation, the only place a CasGeneration comes from. +fn eh_generation_of(o: DurableHoldObservation) -> CasGeneration { + match o { + HoldSlotHeld { generation: g, owner: _ } => g + HoldSlotFree { generation: g, released_by: _ } => g + _ => cas_generation_first() + } +} + fn eh_report(holder: String, generation: Int, dead: Int) -> DurableHoldHolderReport { DurableHoldHolderReport { holder: eh_owner(text: holder), - generation: generation as CasGeneration, + generation: eh_generation_of(o: eh_held_at(owner: holder, generation: generation)), liveness: if dead == 0 { HolderObservedLive { evidence: "process 7 started at tick 90 still runs" } } else if dead == 1 { HolderObservedDead { evidence: "no process 7 on the holder's boot" } } else { HolderLivenessUnobservable { cause: "/proc/7/stat could not be read" } }, diff --git a/docs/design-rung-drops.md b/docs/design-rung-drops.md index e5c2079977c..fefb39666d8 100644 --- a/docs/design-rung-drops.md +++ b/docs/design-rung-drops.md @@ -338,9 +338,9 @@ new-witness eval-step cost gate over the one roadmap page claim that derives the new-witness eval-step cost gate over the ten interpreted P-256 and P-384 point and group-law checks and SHA-384 FIPS vectors of the App Attest path: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the P-384 and SHA-384 primitives executing as natively emitted code rather than as interpreted bignat and Word64 folds). Population: test.claim.p384_dag_ecdsa_witness_test.the_p384_base_point_is_on_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.p384_dag_ecdsa_witness_test.a_point_beside_the_base_point_is_off_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.p384_dag_ecdsa_witness_test.a_point_with_a_coordinate_at_p_is_not_admitted: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.p384_dag_ecdsa_witness_test.apples_ca_and_root_keys_are_on_p384: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.sha384_fips_witness_test.sha384_of_the_empty_message_is_the_published_value_and_one_octet_discriminates: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35703555837 (gunbc#11981, head 0b0a2dffe), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, over the new-witness eval-step budget and above the per-subject line. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field. The billed work is two single-block SHA-384 compressions, the published empty message and a one-octet message on an interpreter that reifies no machine width; the published positive and its discriminating mutation are one subject, so the cost is caused by the claim's own boundary and not by neighbouring vectors, test.claim.sha384_fips_witness_test.sha384_of_abc_is_the_published_value_and_one_octet_discriminates: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35703555837 (gunbc#11981, head 0b0a2dffe), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, over the new-witness eval-step budget and above the per-subject line. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field. The billed work is two single-block SHA-384 compressions, the published abc vector and abd on an interpreter that reifies no machine width; the published positive and its discriminating mutation are one subject, so the cost is caused by the claim's own boundary and not by neighbouring vectors, test.claim.sha384_fips_witness_test.sha384_of_the_896_bit_message_is_the_published_two_block_value_and_one_octet_discriminates: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35703555837 (gunbc#11981, head 0b0a2dffe), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, over the new-witness eval-step budget and above the per-subject line. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field. The billed work is four SHA-384 block compressions, the published 896-bit two-block vector and a last-block mutation on an interpreter that reifies no machine width; the published positive and its discriminating mutation are one subject, so the cost is caused by the claim's own boundary and not by neighbouring vectors, test.claim.p256_dag_ecdsa_witness_test.the_base_point_is_on_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35708011844 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, well inside the wall deadline and over the new-witness eval-step budget. The billed work is one P-256 curve equation over the published base point as 11-limb bignat folds on an interpreter that reifies no machine width. It is a MEMBER BECAUSE IT WAS SPLIT OUT of a claim that also asserted n*G = infinity: that conjunction crossed the 8000ms wall deadline and was INTERRUPTED BEFORE ANY VERDICT, so both facts were being lost; the scalar multiplication left the floor for the native row the frontier names, and this cheap half now reaches a verdict. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field, test.claim.p384_dag_ecdsa_witness_test.doubling_and_adding_the_base_point_stay_on_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by claim_batch --hermetic [witness] receipt on session/nimble-eagle-216-step3 (added for review 69917 after run 35678328407): verdict pass, wall under the 8 s deadline, over the new-witness eval-step budget; the billed work is one Jacobian doubling, one addition and two projective curve-equation checks at 16 limbs, test.claim.p384_dag_ecdsa_witness_test.doubling_an_off_curve_point_stays_off_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by claim_batch --hermetic [witness] receipt on session/nimble-eagle-216-step3 (added for review 69917 after run 35678328407): verdict pass, wall under the 8 s deadline, over the new-witness eval-step budget; the billed work is one Jacobian doubling and one projective curve-equation check at 16 limbs. Restored when: THE CAPABILITY: gunbc test //gunbc/instruments:native-crypto-vectors EXECUTING ON THE MERGE PATH, i.e. as a phase of a required lane whose red blocks a merge, running these ten identities natively (its cases p256_the_base_point_is_on_the_curve, p384_the_base_point_is_on_the_curve, p384_red_a_point_beside_the_base_point_is_off_the_curve, p384_red_a_point_with_a_coordinate_at_p_is_not_admitted, p384_doubling_and_adding_the_base_point_stay_on_the_curve, p384_red_doubling_an_off_curve_point_stays_off_the_curve, p384_apples_ca_and_root_keys_are_on_the_curve and the sha384_* FIPS cases with their reds) while each still evaluates the real curve equation or the real hash; WHAT THAT MUST BE SUFFICIENT FOR: the P-384 parameters and SHA-384 are exercised on the acceptance path with no interpreted 16-limb fold inside a claim frame, so the interpreted identities can leave the floor for that row, or they measure under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives. The same row run BY NAME (as it exists since gunbc#12306) executes the facts natively but is NOT the acceptance path and retires nothing. Supplying the curve check's answer or the digest as a fixture, or deleting the identities, satisfies neither. -### new-witness eval-step cost gate over the eight mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered — declared 2026-09-28 +### new-witness eval-step cost gate over the mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered — declared 2026-09-28 -new-witness eval-step cost gate over the eight mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the evaluation frame and its modeled operation realization realized by the natively emitted runtime, so the boot entry runs as emitted code rather than in the seed interpreter). Population: test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_host_that_never_prints_a_census_refuses_at_the_deadline: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_healthy_census_is_refused_at_the_sol_teardown: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.an_already_presented_image_is_not_attached_again: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.media_that_never_becomes_ready_refuses_before_any_power_action: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_presentation_lost_after_readiness_stops_the_handoff: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_sol_loss_mid_boot_is_reported_only_at_the_deadline: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_two_socket_answer_is_a_truthful_topology_refusal: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization. Restored when: THE CAPABILITY: witnesses emitted to native code with the emitted runtime realizing v2.std.witness_evaluation evaluate_in_witness_frame and its v2.std.operation_realization modeled realization, EXECUTING ON THE MERGE PATH as a phase of a required lane whose red blocks a merge, running these eight identities with the real mtcollins1_boot_wet_on_srv1 entry; WHAT THAT MUST BE SUFFICIENT FOR: each case measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither. +new-witness eval-step cost gate over the mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the evaluation frame and its modeled operation realization realized by the natively emitted runtime, so the boot entry runs as emitted code rather than in the seed interpreter). Population: test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_host_that_never_prints_a_census_refuses_at_the_deadline: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_healthy_census_is_refused_at_the_sol_teardown: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.an_already_presented_image_is_not_attached_again: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.media_that_never_becomes_ready_refuses_before_any_power_action: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_presentation_lost_after_readiness_stops_the_handoff: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_sol_loss_mid_boot_is_reported_only_at_the_deadline: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_two_socket_answer_is_a_truthful_topology_refusal: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.an_interrupted_attempts_dead_hold_is_recovered_by_the_next_one: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36474912729 of gunbc#12555, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, the interrupted-attempt cases twice (the killed run, then the next run over the world it left), test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_live_holder_still_blocks_the_next_attempt: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36474912729 of gunbc#12555, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, the interrupted-attempt cases twice (the killed run, then the next run over the world it left), test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.an_unobservable_holder_is_refused_not_recovered: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36474912729 of gunbc#12555, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, the interrupted-attempt cases twice (the killed run, then the next run over the world it left), test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_successor_in_another_pid_namespace_refuses_rather_than_recovering: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36474912729 of gunbc#12555, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, the interrupted-attempt cases twice (the killed run, then the next run over the world it left), test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_listing_that_names_the_image_twice_starts_nothing: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36474912729 of gunbc#12555, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization, the interrupted-attempt cases twice (the killed run, then the next run over the world it left). Restored when: THE CAPABILITY: witnesses emitted to native code with the emitted runtime realizing v2.std.witness_evaluation evaluate_in_witness_frame and its v2.std.operation_realization modeled realization, EXECUTING ON THE MERGE PATH as a phase of a required lane whose red blocks a merge, running these identities with the real mtcollins1_boot_wet_on_srv1 entry; WHAT THAT MUST BE SUFFICIENT FOR: each case measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither. ### new-witness eval-step cost gate over six App Attest verifier claims -- the two real-path inhabitance claims over Apple's objects, three single structural steps priced by their own SHA-256s, and the wire-carrier join: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered — declared 2026-09-22 diff --git a/src/v2/workflow/floor_eval_step_cost_drop.dag b/src/v2/workflow/floor_eval_step_cost_drop.dag index e9e32abcabd..bdca35cb223 100644 --- a/src/v2/workflow/floor_eval_step_cost_drop.dag +++ b/src/v2/workflow/floor_eval_step_cost_drop.dag @@ -273,6 +273,26 @@ data floor_eval_step_cost_drop_boot_matrix_rows: List List { From 1720c6571a04f7c99f6fde879446f7b17baaedb1 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 28 Sep 2026 20:58:26 +0000 Subject: [PATCH 21/38] Matrix: the held-unit case matches the owner's run id followed by its process identity, and asserts the holder was observed alive Co-Authored-By: Claude Opus 5.5 (1M context) --- .../machine_intake/mtcollins1_boot_acceptance_matrix_test.dag | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag index 28898b519b7..3eee8025bcf 100644 --- a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag +++ b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag @@ -289,7 +289,8 @@ test fn a_second_run_for_a_held_unit_writes_nothing_to_the_controller() -> Bool Absent => false Present { value: held } => match run_attempt(world: held) { WitnessReturned { value, route } => - string_contains(s: outcome_reason(a: value), pattern: "is held by 'mtcollins1-boot:4141'") + string_contains(s: outcome_reason(a: value), pattern: "is held by 'mtcollins1-boot:4141@") + && string_contains(s: outcome_reason(a: value), pattern: "which is alive") && count(filter(route, r => is_controller_write(r: r))) == 0 && count_named(route: route, name: "gunbc.machine_intake.sol_hold.ActivateHeld") == 0 _ => false From 72b84eb155a5e44ea0688b3265bf8170ce188538 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 29 Sep 2026 03:07:44 +0000 Subject: [PATCH 22/38] Regenerate the std.measure stage0 mirror for SecondDisplacement Co-Authored-By: Claude Opus 5.5 (1M context) --- src/v1/stage0/src/std_measure.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/v1/stage0/src/std_measure.rs b/src/v1/stage0/src/std_measure.rs index d11b18a5588..36e02a18888 100644 --- a/src/v1/stage0/src/std_measure.rs +++ b/src/v1/stage0/src/std_measure.rs @@ -1645,6 +1645,19 @@ pub fn second_count(s: Second) -> Nat { measure_count(s.clone()) } +pub type SecondDisplacement = Rc>; + +pub fn second_displacement(count: i64) -> SecondDisplacement { + Rc::new(Measure { + count: count.clone(), + _phantom: std::marker::PhantomData, + }) +} + +pub fn second_displacement_count(d: SecondDisplacement) -> i64 { + measure_count(d.clone()) +} + pub fn energy_from_power_and_time(power: Watt, time: Second) -> Joule { joule(v1_rt::int_mul( watt_count(power.clone()), From b12eb7c4511060c9d011c8689beb84dde6ce7e1c Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 29 Sep 2026 04:22:04 +0000 Subject: [PATCH 23/38] Cost-drop rationale matches its rows: the five #12555 members and why each exceeds the budget (review 72465) Co-Authored-By: Claude Opus 5.5 (1M context) --- ...ins1_boot_matrix_new_witness_eval_step_cost.dag | 3 ++- src/v2/workflow/floor_eval_step_cost_drop.dag | 14 +++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag b/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag index 2a5adbda1ec..ac915b2f6ec 100644 --- a/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag +++ b/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag @@ -9,7 +9,8 @@ import v2.workflow.floor_eval_step_cost_drop { floor_eval_step_cost_drop_boot_ma // matrix, msg_83af891b). The DECLARATION lives here; the POPULATION's authority is // `v2.workflow.floor_eval_step_cost_drop` `floor_eval_step_cost_drop_boot_matrix_rows`. // -// WHAT IS LOST IS ONE WALL, AND THE CLAIMS ARE UNTOUCHED. The eight identities are planned, executed +// WHAT IS LOST IS ONE WALL, AND THE CLAIMS ARE UNTOUCHED. The member identities (thirteen: eight from +// gunbc#12533, five from gunbc#12555, enumerated in the population authority above) are planned, executed // and measured on every pull request that edits them; a semantic red and a wall-clock crossing still // block. Only the eval-step overrun is reported as `EvalStepsOverBudgetUnderDeclaredDrop` rather than // refusing. The budget is not raised. Their CPU stays under the enrolment margin, so they enrol diff --git a/src/v2/workflow/floor_eval_step_cost_drop.dag b/src/v2/workflow/floor_eval_step_cost_drop.dag index bdca35cb223..a640b7cd3b1 100644 --- a/src/v2/workflow/floor_eval_step_cost_drop.dag +++ b/src/v2/workflow/floor_eval_step_cost_drop.dag @@ -235,11 +235,15 @@ data floor_eval_step_cost_drop_python_to_typescript_inhabitance_rows: List = [ EvalStepCostDropMeasurement { identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_host_that_never_prints_a_census_refuses_at_the_deadline" as NonEmptyStr, From c2ab8ad35f435c3ac05464f634b57f1482c8adea Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 29 Sep 2026 05:30:18 +0000 Subject: [PATCH 24/38] One Gregorian calendar: extdeps.units.iso8601 owns both directions (review 72483) The wall-clock model re-derived civil-from-days and hard-coded 86400/3600/60 beside extdeps.units.iso8601's own constants, while the roadmap reader carried a private days_from_civil with the same literals. Both directions and the month-length rule now live in extdeps.units.iso8601 over its constants; the reader and the wall-clock model import them. The reader's witness gains a round trip (format then parse), so the two directions cannot drift apart without a red. Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/extdeps/units/iso8601.dag | 73 ++++++++++++++++++- .../roadmap_launch_deployment_observe.dag | 26 ++----- dag/gunbc/wall_clock_model.dag | 44 +---------- .../claim/boot_world_models_witness_test.dag | 13 ++-- ...launch_deployment_observe_witness_test.dag | 1 + 5 files changed, 88 insertions(+), 69 deletions(-) diff --git a/dag/extdeps/units/iso8601.dag b/dag/extdeps/units/iso8601.dag index 7a7fbb29951..75b32a48f9f 100644 --- a/dag/extdeps/units/iso8601.dag +++ b/dag/extdeps/units/iso8601.dag @@ -1,6 +1,6 @@ module extdeps.units.iso8601 -import std.types { String } +import std.types { Int, String } import std.nat { Nat } import extdeps.external_authority { ExternalAuthority } import extdeps.uri { Uri, Https } @@ -26,3 +26,74 @@ fn iso8601_minutes_per_hour() -> Nat { fn iso8601_hours_per_day() -> Nat { 24 } + +fn iso8601_seconds_per_hour() -> Int { + (iso8601_minutes_per_hour() as Int) * (iso8601_seconds_per_minute() as Int) +} + +fn iso8601_seconds_per_day() -> Int { + (iso8601_hours_per_day() as Int) * iso8601_seconds_per_hour() +} + +// THE PROLEPTIC GREGORIAN CALENDAR THE DATE FORMAT SPELLS, in both directions over one day count +// (days since 1970-01-01): H. Hinnant, "chrono-Compatible Low-Level Date Algorithms", +// days_from_civil and civil_from_days -- exact over the whole range, no table. +type Iso8601CivilDate { + year: Int + month: Int + day: Int +} + +fn iso8601_floor_div(a: Int, b: Int) -> Int { + if a >= 0 { a / b } else { 0 - ((0 - a + b - 1) / b) } +} + +fn iso8601_days_from_civil(y: Int, m: Int, d: Int) -> Int { + let yy = if m <= 2 { y - 1 } else { y } + let era = iso8601_floor_div(a: yy, b: 400) + let yoe = yy - era * 400 + let mp = if m > 2 { m - 3 } else { m + 9 } + let doy = (153 * mp + 2) / 5 + d - 1 + let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy + era * 146097 + doe - 719468 +} + +fn iso8601_civil_from_days(days: Int) -> Iso8601CivilDate { + let z = days + 719468 + let era = iso8601_floor_div(a: z, b: 146097) + let doe = z - era * 146097 + let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365 + let y = yoe + era * 400 + let doy = doe - (365 * yoe + yoe / 4 - yoe / 100) + let mp = (5 * doy + 2) / 153 + let d = doy - (153 * mp + 2) / 5 + 1 + let m = if mp < 10 { mp + 3 } else { mp - 9 } + Iso8601CivilDate { year: if m <= 2 { y + 1 } else { y }, month: m, day: d } +} + +// The day-in-month bound with the leap rule, so a calendar-impossible day (Feb 30, Apr 31) is refused +// by a reader rather than normalized through the day count. +fn iso8601_gregorian_days_in_month(y: Int, m: Int) -> Int { + if m == 2 { + if y - (y / 4) * 4 == 0 && (y - (y / 100) * 100 != 0 || y - (y / 400) * 400 == 0) { 29 } else { 28 } + } else if m == 4 || m == 6 || m == 9 || m == 11 { 30 } else { 31 } +} + +fn iso8601_two_digits(n: Int) -> String { + if n < 10 { concat("0", to_string(n)) } else { to_string(n) } +} + +// Seconds since 1970-01-01T00:00:00Z to the UTC basic-extended form `YYYY-MM-DDThh:mm:ssZ`, the form +// `date -u +%Y-%m-%dT%H:%M:%SZ` prints; the inverse of a reader of that form over the same day count. +fn iso8601_utc_text(unix: Int) -> String { + let days = iso8601_floor_div(a: unix, b: iso8601_seconds_per_day()) + let rem = unix - days * iso8601_seconds_per_day() + let hour = rem / iso8601_seconds_per_hour() + let within_hour = rem - hour * iso8601_seconds_per_hour() + let minute = within_hour / (iso8601_seconds_per_minute() as Int) + let date = iso8601_civil_from_days(days: days) + join([ + to_string(date.year), "-", iso8601_two_digits(n: date.month), "-", iso8601_two_digits(n: date.day), + "T", iso8601_two_digits(n: hour), ":", iso8601_two_digits(n: minute), ":", iso8601_two_digits(n: within_hour - minute * (iso8601_seconds_per_minute() as Int)), "Z", + ], "") +} diff --git a/dag/gunbc/roadmap/roadmap_launch_deployment_observe.dag b/dag/gunbc/roadmap/roadmap_launch_deployment_observe.dag index 894ef87f0fb..868da4c6892 100644 --- a/dag/gunbc/roadmap/roadmap_launch_deployment_observe.dag +++ b/dag/gunbc/roadmap/roadmap_launch_deployment_observe.dag @@ -7,6 +7,7 @@ import gunbc.live_deploy.emit { belt_tick_cadence } import std.types { String, Bool, NonEmptyStr, Int, List } import std.algebra { trim } +import extdeps.units.iso8601 { iso8601_days_from_civil, iso8601_gregorian_days_in_month, iso8601_seconds_per_day, iso8601_seconds_per_hour, iso8601_seconds_per_minute } import extdeps.http.client import extdeps.languages.json.emit { JsonValue, JsonString, JsonBool, serialize_json } import extdeps.languages.json.parse { @@ -266,29 +267,12 @@ fn observe_workflow_document_routed(base_url: String) -> RoadmapWorkflowObservat // ---------------------------------------------------------------- instants // `%Y-%m-%dT%H:%M:%SZ` to seconds since 1970-01-01T00:00:00Z. Days-from-civil is the proleptic // Gregorian algorithm (Howard Hinnant, "chrono-Compatible Low-Level Date Algorithms"), exact for -// every date the format can spell. Anything that is not exactly that shape refuses. +// every date the format can spell; the calendar is extdeps.units.iso8601's, which also formats the +// same instants. Anything that is not exactly that shape refuses. fn two_digits_at(s: String, i: Int) -> Int? { parse_int(s: substring(s: s, start: i, end: i + 2)) } -fn days_from_civil(y: Int, m: Int, d: Int) -> Int { - let yy = if m <= 2 { y - 1 } else { y } - let era = (if yy >= 0 { yy } else { yy - 399 }) / 400 - let yoe = yy - era * 400 - let mp = if m > 2 { m - 3 } else { m + 9 } - let doy = (153 * mp + 2) / 5 + d - 1 - let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy - era * 146097 + doe - 719468 -} - -// systemd-independent Gregorian day-in-month bound with the leap rule; a calendar-impossible day -// (Feb 30, Apr 31) must refuse rather than normalize silently through days_from_civil (review 57773). -fn gregorian_days_in_month(y: Int, m: Int) -> Int { - if m == 2 { - if y - (y / 4) * 4 == 0 && (y - (y / 100) * 100 != 0 || y - (y / 400) * 400 == 0) { 29 } else { 28 } - } else if m == 4 || m == 6 || m == 9 || m == 11 { 30 } else { 31 } -} - fn iso8601_utc_epoch_seconds(text: String) -> Second? { let t = trim(s: text) if string_length(s: t) != 20 { @@ -316,11 +300,11 @@ fn iso8601_utc_epoch_seconds(text: String) -> Second? { match two_digits_at(s: t, i: 17) { Absent => none Present { value: ss } => - if m < 1 || m > 12 || d < 1 || d > gregorian_days_in_month(y: y, m: m) || hh > 23 || mm > 59 || ss > 60 { + if m < 1 || m > 12 || d < 1 || d > iso8601_gregorian_days_in_month(y: y, m: m) || hh > 23 || mm > 59 || ss > 60 { none } else { { - let epoch = days_from_civil(y: y, m: m, d: d) * 86400 + hh * 3600 + mm * 60 + ss + let epoch = iso8601_days_from_civil(y: y, m: m, d: d) * iso8601_seconds_per_day() + hh * iso8601_seconds_per_hour() + mm * (iso8601_seconds_per_minute() as Int) + ss if epoch < 0 { none } else { diff --git a/dag/gunbc/wall_clock_model.dag b/dag/gunbc/wall_clock_model.dag index cec79144ca2..9e0e5033c31 100644 --- a/dag/gunbc/wall_clock_model.dag +++ b/dag/gunbc/wall_clock_model.dag @@ -7,6 +7,7 @@ import v2.std.operation_realization { OperationBinding, OperationCall, OperationStep, OperationObserved, ShellObserved, } import extdeps.transports.shell { ShellProcessExited } +import extdeps.units.iso8601 { iso8601_utc_text } // A MODELED WALL CLOCK: what the worker's `date` prints, as a function of the realization's virtual // clock. The virtual clock (v2.std.operation_realization, a Nat-counted Second) is monotonic by @@ -17,7 +18,7 @@ import extdeps.transports.shell { ShellProcessExited } // scheduled events keep moving forward. // // It answers extdeps.clock Clock.Now in `date -u +%Y-%m-%dT%H:%M:%SZ` form, and Clock.UnixSecs and -// Clock.UnixMillis in `date +%s` and `date +%s%3N` form, over the proleptic Gregorian calendar. +// Clock.UnixMillis in `date +%s` and `date +%s%3N` form; the calendar is extdeps.units.iso8601's. type ModeledWallClock { unix_at_origin: EpochSecs step: SecondDisplacement @@ -27,45 +28,6 @@ fn wall_clock_unix(clock: ModeledWallClock, now: Second) -> Int { (clock.unix_at_origin as Int) + (second_count(s: now) as Int) + second_displacement_count(d: clock.step) } -fn two_digits(n: Int) -> String { - if n < 10 { concat("0", to_string(n)) } else { to_string(n) } -} - -// Days since 1970-01-01 to a civil date: H. Hinnant, "chrono-Compatible Low-Level Date Algorithms", -// civil_from_days -- exact for the whole proleptic Gregorian range, no table. -type CivilDate { - year: Int - month: Int - day: Int -} - -fn floor_div(a: Int, b: Int) -> Int { - if a >= 0 { a / b } else { 0 - ((0 - a + b - 1) / b) } -} - -fn civil_from_days(days: Int) -> CivilDate { - let z = days + 719468 - let era = floor_div(a: z, b: 146097) - let doe = z - era * 146097 - let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365 - let y = yoe + era * 400 - let doy = doe - (365 * yoe + yoe / 4 - yoe / 100) - let mp = (5 * doy + 2) / 153 - let d = doy - (153 * mp + 2) / 5 + 1 - let m = if mp < 10 { mp + 3 } else { mp - 9 } - CivilDate { year: if m <= 2 { y + 1 } else { y }, month: m, day: d } -} - -fn iso8601_utc(unix: Int) -> String { - let days = floor_div(a: unix, b: 86400) - let rem = unix - days * 86400 - let date = civil_from_days(days: days) - join([ - to_string(date.year), "-", two_digits(n: date.month), "-", two_digits(n: date.day), - "T", two_digits(n: rem / 3600), ":", two_digits(n: (rem - (rem / 3600) * 3600) / 60), ":", two_digits(n: rem - (rem / 60) * 60), "Z", - ], "") -} - data clock_operation_path: String = "dag/extdeps/clock/clock.dag" fn clock_operation(operation: String) -> OperationRef { @@ -82,7 +44,7 @@ fn printed(state: S, text: String) -> OperationStep { fn wall_clock_bindings(get: fn(S) -> ModeledWallClock) -> List> { [ - OperationBinding { at: clock_operation(operation: "Now"), handler: fn(state, call) { printed(state: state, text: iso8601_utc(unix: wall_clock_unix(clock: get(state), now: call.now))) } }, + OperationBinding { at: clock_operation(operation: "Now"), handler: fn(state, call) { printed(state: state, text: iso8601_utc_text(unix: wall_clock_unix(clock: get(state), now: call.now))) } }, OperationBinding { at: clock_operation(operation: "UnixSecs"), handler: fn(state, call) { printed(state: state, text: to_string(wall_clock_unix(clock: get(state), now: call.now))) } }, OperationBinding { at: clock_operation(operation: "UnixMillis"), handler: fn(state, call) { printed(state: state, text: to_string(wall_clock_unix(clock: get(state), now: call.now) * 1000)) } }, ] diff --git a/dag/test/claim/boot_world_models_witness_test.dag b/dag/test/claim/boot_world_models_witness_test.dag index ce1e736918a..4b98636bfaa 100644 --- a/dag/test/claim/boot_world_models_witness_test.dag +++ b/dag/test/claim/boot_world_models_witness_test.dag @@ -9,7 +9,8 @@ import extdeps.transports.shell { ShellProcessExited } import extdeps.exec.command { argv_words } import extdeps.tools.gnu_coreutils { cat_command } import extdeps.crypto.hash { sha256sum_file_command } -import gunbc.wall_clock_model { ModeledWallClock, iso8601_utc, wall_clock_unix } +import gunbc.wall_clock_model { ModeledWallClock, wall_clock_unix } +import extdeps.units.iso8601 { iso8601_utc_text } import gunbc.remote_host_model { ModeledRemoteHost, ModeledRemoteFile, remote_request, remote_answer } import gunbc.filesystem_model { ModeledFilesystem, ModeledFile, fs_parent, fs_write, fs_list, fs_read } @@ -19,11 +20,11 @@ data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly // against a model that is wrong in the same direction as the code under test. Reference values for the // calendar are independent: Python's datetime.fromtimestamp(t, timezone.utc) for each instant. test fn the_wall_clock_renders_utc_like_date() -> Bool { - iso8601_utc(unix: 0) == "1970-01-01T00:00:00Z" - && iso8601_utc(unix: 951782400) == "2000-02-29T00:00:00Z" - && iso8601_utc(unix: 1790000000) == "2026-09-21T14:13:20Z" - && iso8601_utc(unix: 4102444799) == "2099-12-31T23:59:59Z" - && iso8601_utc(unix: 0 - 1) == "1969-12-31T23:59:59Z" + iso8601_utc_text(unix: 0) == "1970-01-01T00:00:00Z" + && iso8601_utc_text(unix: 951782400) == "2000-02-29T00:00:00Z" + && iso8601_utc_text(unix: 1790000000) == "2026-09-21T14:13:20Z" + && iso8601_utc_text(unix: 4102444799) == "2099-12-31T23:59:59Z" + && iso8601_utc_text(unix: 0 - 1) == "1969-12-31T23:59:59Z" } // A NEGATIVE STEP IS A WALL CLOCK THAT WENT BACKWARDS while virtual time moved forward. diff --git a/dag/test/claim/roadmap/roadmap_launch_deployment_observe_witness_test.dag b/dag/test/claim/roadmap/roadmap_launch_deployment_observe_witness_test.dag index 667ba51f198..5ceaf860afe 100644 --- a/dag/test/claim/roadmap/roadmap_launch_deployment_observe_witness_test.dag +++ b/dag/test/claim/roadmap/roadmap_launch_deployment_observe_witness_test.dag @@ -29,6 +29,7 @@ test fn witness_iso8601_utc_epoch_seconds_matches_known_instants() -> Bool { && (match iso8601_utc_epoch_seconds(text: "2026-02-30T00:00:00Z") { Present { value: _ } => false Absent => true }) && (match iso8601_utc_epoch_seconds(text: "2027-04-31T00:00:00Z") { Present { value: _ } => false Absent => true }) && (match iso8601_utc_epoch_seconds(text: "2024-02-29T00:00:00Z") { Present { value: s } => second_count(s) == 1709164800 Absent => false }) + && (match iso8601_utc_epoch_seconds(text: iso8601_utc_text(unix: 4102444799)) { Present { value: s } => second_count(s) == 4102444799 Absent => false }) } // (2) Freshness: fresh within the cadence bound, stale beyond it, refused before the deploy, and From 7c60c3d6b226b0ad0ab8605ac167b1b58beba8b7 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 29 Sep 2026 05:58:49 +0000 Subject: [PATCH 25/38] wall_clock_model: name the reading helper wall_clock_printed (the floor's AmbiguousBareNameRead) Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/gunbc/wall_clock_model.dag | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dag/gunbc/wall_clock_model.dag b/dag/gunbc/wall_clock_model.dag index 9e0e5033c31..e231427cae3 100644 --- a/dag/gunbc/wall_clock_model.dag +++ b/dag/gunbc/wall_clock_model.dag @@ -34,7 +34,7 @@ fn clock_operation(operation: String) -> OperationRef { OperationRef { path: clock_operation_path, service: "Clock", operation: operation } } -fn printed(state: S, text: String) -> OperationStep { +fn wall_clock_printed(state: S, text: String) -> OperationStep { OperationObserved { observation: ShellObserved { observation: ShellProcessExited { exit_code: 0, stdout: concat(text, "\n"), stderr: "" } }, state: state, @@ -44,8 +44,8 @@ fn printed(state: S, text: String) -> OperationStep { fn wall_clock_bindings(get: fn(S) -> ModeledWallClock) -> List> { [ - OperationBinding { at: clock_operation(operation: "Now"), handler: fn(state, call) { printed(state: state, text: iso8601_utc_text(unix: wall_clock_unix(clock: get(state), now: call.now))) } }, - OperationBinding { at: clock_operation(operation: "UnixSecs"), handler: fn(state, call) { printed(state: state, text: to_string(wall_clock_unix(clock: get(state), now: call.now))) } }, - OperationBinding { at: clock_operation(operation: "UnixMillis"), handler: fn(state, call) { printed(state: state, text: to_string(wall_clock_unix(clock: get(state), now: call.now) * 1000)) } }, + OperationBinding { at: clock_operation(operation: "Now"), handler: fn(state, call) { wall_clock_printed(state: state, text: iso8601_utc_text(unix: wall_clock_unix(clock: get(state), now: call.now))) } }, + OperationBinding { at: clock_operation(operation: "UnixSecs"), handler: fn(state, call) { wall_clock_printed(state: state, text: to_string(wall_clock_unix(clock: get(state), now: call.now))) } }, + OperationBinding { at: clock_operation(operation: "UnixMillis"), handler: fn(state, call) { wall_clock_printed(state: state, text: to_string(wall_clock_unix(clock: get(state), now: call.now) * 1000)) } }, ] } From f96ba04ff8875dc5636ad904114d21485c4e2906 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 29 Sep 2026 06:24:41 +0000 Subject: [PATCH 26/38] Name String's declarer: extdeps.systemd.journalctl and extdeps.units.iso8601 import std.string_type The floor refused AmbiguousBareNameRead in gunbc.output_policy's scope: journalctl read String through std.types, which declares nothing by that name, while std.string_type and v2.std.text both declare it. The import now names the declarer, as the refusal's remedy says; iso8601, which this PR grew, gets the same import. Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/extdeps/systemd/journalctl.dag | 3 ++- dag/extdeps/units/iso8601.dag | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dag/extdeps/systemd/journalctl.dag b/dag/extdeps/systemd/journalctl.dag index 8ff9b302c4b..11ae274b776 100644 --- a/dag/extdeps/systemd/journalctl.dag +++ b/dag/extdeps/systemd/journalctl.dag @@ -1,6 +1,7 @@ module extdeps.systemd.journalctl -import std.types { NonEmptyStr, String, Bool } +import std.types { NonEmptyStr, Bool } +import std.string_type { String } import extdeps.external_authority { ExternalAuthority, ExternalModelScope, ExternalSubjectRef } import std.decl_ref { DeclarationRef, WholeDeclaration } import extdeps.uri { Uri, Https } diff --git a/dag/extdeps/units/iso8601.dag b/dag/extdeps/units/iso8601.dag index 75b32a48f9f..6ea3ecb5a7b 100644 --- a/dag/extdeps/units/iso8601.dag +++ b/dag/extdeps/units/iso8601.dag @@ -1,6 +1,7 @@ module extdeps.units.iso8601 -import std.types { Int, String } +import std.types { Int } +import std.string_type { String } import std.nat { Nat } import extdeps.external_authority { ExternalAuthority } import extdeps.uri { Uri, Https } From 0227a71ebd8bd246aacef1d43c047b6fd04746e8 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 29 Sep 2026 06:52:56 +0000 Subject: [PATCH 27/38] iso8601 keeps String through std.types: it is in the stage0 closure (Stage0EmittedEdgesNotCovered) extdeps.units.iso8601 is reached by std.measure, so it emits into the stage0 crate, which has no partition row for std.string_type; importing it there refused the regeneration. The floor's one ambiguous site was journalctl, which keeps its std.string_type import. Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/extdeps/units/iso8601.dag | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dag/extdeps/units/iso8601.dag b/dag/extdeps/units/iso8601.dag index 6ea3ecb5a7b..75b32a48f9f 100644 --- a/dag/extdeps/units/iso8601.dag +++ b/dag/extdeps/units/iso8601.dag @@ -1,7 +1,6 @@ module extdeps.units.iso8601 -import std.types { Int } -import std.string_type { String } +import std.types { Int, String } import std.nat { Nat } import extdeps.external_authority { ExternalAuthority } import extdeps.uri { Uri, Https } From 2c5d045bd0047b619c39bfa5e4867a72c5b2ee2d Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 29 Sep 2026 07:45:48 +0000 Subject: [PATCH 28/38] Calendar in extdeps.units.iso8601_calendar, off the compiler seed's closure extdeps.units.iso8601 is imported by std.measure, so the calendar added to it (review 72483) landed in the stage0 seed and the emitted v2 compiler: a stage0 mirror drift, Nat-as-Int casts the native emitter cannot realize, and every std.measure consumer's claim scope widened into bare-name ambiguities (String, then Unit, in journalctl). The calendar now lives in a sibling module over the same constants, imported only by its two consumers; iso8601 and journalctl are back to main's bytes. Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/extdeps/systemd/journalctl.dag | 3 +- dag/extdeps/units/iso8601.dag | 73 +---------------- dag/extdeps/units/iso8601_calendar.dag | 80 +++++++++++++++++++ .../roadmap_launch_deployment_observe.dag | 5 +- dag/gunbc/wall_clock_model.dag | 4 +- .../claim/boot_world_models_witness_test.dag | 2 +- 6 files changed, 88 insertions(+), 79 deletions(-) create mode 100644 dag/extdeps/units/iso8601_calendar.dag diff --git a/dag/extdeps/systemd/journalctl.dag b/dag/extdeps/systemd/journalctl.dag index 11ae274b776..8ff9b302c4b 100644 --- a/dag/extdeps/systemd/journalctl.dag +++ b/dag/extdeps/systemd/journalctl.dag @@ -1,7 +1,6 @@ module extdeps.systemd.journalctl -import std.types { NonEmptyStr, Bool } -import std.string_type { String } +import std.types { NonEmptyStr, String, Bool } import extdeps.external_authority { ExternalAuthority, ExternalModelScope, ExternalSubjectRef } import std.decl_ref { DeclarationRef, WholeDeclaration } import extdeps.uri { Uri, Https } diff --git a/dag/extdeps/units/iso8601.dag b/dag/extdeps/units/iso8601.dag index 75b32a48f9f..7a7fbb29951 100644 --- a/dag/extdeps/units/iso8601.dag +++ b/dag/extdeps/units/iso8601.dag @@ -1,6 +1,6 @@ module extdeps.units.iso8601 -import std.types { Int, String } +import std.types { String } import std.nat { Nat } import extdeps.external_authority { ExternalAuthority } import extdeps.uri { Uri, Https } @@ -26,74 +26,3 @@ fn iso8601_minutes_per_hour() -> Nat { fn iso8601_hours_per_day() -> Nat { 24 } - -fn iso8601_seconds_per_hour() -> Int { - (iso8601_minutes_per_hour() as Int) * (iso8601_seconds_per_minute() as Int) -} - -fn iso8601_seconds_per_day() -> Int { - (iso8601_hours_per_day() as Int) * iso8601_seconds_per_hour() -} - -// THE PROLEPTIC GREGORIAN CALENDAR THE DATE FORMAT SPELLS, in both directions over one day count -// (days since 1970-01-01): H. Hinnant, "chrono-Compatible Low-Level Date Algorithms", -// days_from_civil and civil_from_days -- exact over the whole range, no table. -type Iso8601CivilDate { - year: Int - month: Int - day: Int -} - -fn iso8601_floor_div(a: Int, b: Int) -> Int { - if a >= 0 { a / b } else { 0 - ((0 - a + b - 1) / b) } -} - -fn iso8601_days_from_civil(y: Int, m: Int, d: Int) -> Int { - let yy = if m <= 2 { y - 1 } else { y } - let era = iso8601_floor_div(a: yy, b: 400) - let yoe = yy - era * 400 - let mp = if m > 2 { m - 3 } else { m + 9 } - let doy = (153 * mp + 2) / 5 + d - 1 - let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy - era * 146097 + doe - 719468 -} - -fn iso8601_civil_from_days(days: Int) -> Iso8601CivilDate { - let z = days + 719468 - let era = iso8601_floor_div(a: z, b: 146097) - let doe = z - era * 146097 - let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365 - let y = yoe + era * 400 - let doy = doe - (365 * yoe + yoe / 4 - yoe / 100) - let mp = (5 * doy + 2) / 153 - let d = doy - (153 * mp + 2) / 5 + 1 - let m = if mp < 10 { mp + 3 } else { mp - 9 } - Iso8601CivilDate { year: if m <= 2 { y + 1 } else { y }, month: m, day: d } -} - -// The day-in-month bound with the leap rule, so a calendar-impossible day (Feb 30, Apr 31) is refused -// by a reader rather than normalized through the day count. -fn iso8601_gregorian_days_in_month(y: Int, m: Int) -> Int { - if m == 2 { - if y - (y / 4) * 4 == 0 && (y - (y / 100) * 100 != 0 || y - (y / 400) * 400 == 0) { 29 } else { 28 } - } else if m == 4 || m == 6 || m == 9 || m == 11 { 30 } else { 31 } -} - -fn iso8601_two_digits(n: Int) -> String { - if n < 10 { concat("0", to_string(n)) } else { to_string(n) } -} - -// Seconds since 1970-01-01T00:00:00Z to the UTC basic-extended form `YYYY-MM-DDThh:mm:ssZ`, the form -// `date -u +%Y-%m-%dT%H:%M:%SZ` prints; the inverse of a reader of that form over the same day count. -fn iso8601_utc_text(unix: Int) -> String { - let days = iso8601_floor_div(a: unix, b: iso8601_seconds_per_day()) - let rem = unix - days * iso8601_seconds_per_day() - let hour = rem / iso8601_seconds_per_hour() - let within_hour = rem - hour * iso8601_seconds_per_hour() - let minute = within_hour / (iso8601_seconds_per_minute() as Int) - let date = iso8601_civil_from_days(days: days) - join([ - to_string(date.year), "-", iso8601_two_digits(n: date.month), "-", iso8601_two_digits(n: date.day), - "T", iso8601_two_digits(n: hour), ":", iso8601_two_digits(n: minute), ":", iso8601_two_digits(n: within_hour - minute * (iso8601_seconds_per_minute() as Int)), "Z", - ], "") -} diff --git a/dag/extdeps/units/iso8601_calendar.dag b/dag/extdeps/units/iso8601_calendar.dag new file mode 100644 index 00000000000..c145d6b3853 --- /dev/null +++ b/dag/extdeps/units/iso8601_calendar.dag @@ -0,0 +1,80 @@ +module extdeps.units.iso8601_calendar + +import std.types { Int, String } +import extdeps.units.iso8601 { iso8601_seconds_per_minute, iso8601_minutes_per_hour, iso8601_hours_per_day } + +// THE ISO 8601 CALENDAR over extdeps.units.iso8601's time-unit constants: the proleptic Gregorian day +// count in both directions, the month-length rule, and the UTC text form. It is a module of its own, +// beside the constants rather than inside them, because std.measure imports the constants and so puts +// that module in the compiler seed's closure; the calendar has no compiler consumer, and adding it +// there would grow the seed for nothing. +fn iso8601_seconds_per_hour() -> Int { + (iso8601_minutes_per_hour() as Int) * (iso8601_seconds_per_minute() as Int) +} + +fn iso8601_seconds_per_day() -> Int { + (iso8601_hours_per_day() as Int) * iso8601_seconds_per_hour() +} + +// THE PROLEPTIC GREGORIAN CALENDAR THE DATE FORMAT SPELLS, in both directions over one day count +// (days since 1970-01-01): H. Hinnant, "chrono-Compatible Low-Level Date Algorithms", +// days_from_civil and civil_from_days -- exact over the whole range, no table. +type Iso8601CivilDate { + year: Int + month: Int + day: Int +} + +fn iso8601_floor_div(a: Int, b: Int) -> Int { + if a >= 0 { a / b } else { 0 - ((0 - a + b - 1) / b) } +} + +fn iso8601_days_from_civil(y: Int, m: Int, d: Int) -> Int { + let yy = if m <= 2 { y - 1 } else { y } + let era = iso8601_floor_div(a: yy, b: 400) + let yoe = yy - era * 400 + let mp = if m > 2 { m - 3 } else { m + 9 } + let doy = (153 * mp + 2) / 5 + d - 1 + let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy + era * 146097 + doe - 719468 +} + +fn iso8601_civil_from_days(days: Int) -> Iso8601CivilDate { + let z = days + 719468 + let era = iso8601_floor_div(a: z, b: 146097) + let doe = z - era * 146097 + let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365 + let y = yoe + era * 400 + let doy = doe - (365 * yoe + yoe / 4 - yoe / 100) + let mp = (5 * doy + 2) / 153 + let d = doy - (153 * mp + 2) / 5 + 1 + let m = if mp < 10 { mp + 3 } else { mp - 9 } + Iso8601CivilDate { year: if m <= 2 { y + 1 } else { y }, month: m, day: d } +} + +// The day-in-month bound with the leap rule, so a calendar-impossible day (Feb 30, Apr 31) is refused +// by a reader rather than normalized through the day count. +fn iso8601_gregorian_days_in_month(y: Int, m: Int) -> Int { + if m == 2 { + if y - (y / 4) * 4 == 0 && (y - (y / 100) * 100 != 0 || y - (y / 400) * 400 == 0) { 29 } else { 28 } + } else if m == 4 || m == 6 || m == 9 || m == 11 { 30 } else { 31 } +} + +fn iso8601_two_digits(n: Int) -> String { + if n < 10 { concat("0", to_string(n)) } else { to_string(n) } +} + +// Seconds since 1970-01-01T00:00:00Z to the UTC basic-extended form `YYYY-MM-DDThh:mm:ssZ`, the form +// `date -u +%Y-%m-%dT%H:%M:%SZ` prints; the inverse of a reader of that form over the same day count. +fn iso8601_utc_text(unix: Int) -> String { + let days = iso8601_floor_div(a: unix, b: iso8601_seconds_per_day()) + let rem = unix - days * iso8601_seconds_per_day() + let hour = rem / iso8601_seconds_per_hour() + let within_hour = rem - hour * iso8601_seconds_per_hour() + let minute = within_hour / (iso8601_seconds_per_minute() as Int) + let date = iso8601_civil_from_days(days: days) + join([ + to_string(date.year), "-", iso8601_two_digits(n: date.month), "-", iso8601_two_digits(n: date.day), + "T", iso8601_two_digits(n: hour), ":", iso8601_two_digits(n: minute), ":", iso8601_two_digits(n: within_hour - minute * (iso8601_seconds_per_minute() as Int)), "Z", + ], "") +} diff --git a/dag/gunbc/roadmap/roadmap_launch_deployment_observe.dag b/dag/gunbc/roadmap/roadmap_launch_deployment_observe.dag index 868da4c6892..c0e6f86f07d 100644 --- a/dag/gunbc/roadmap/roadmap_launch_deployment_observe.dag +++ b/dag/gunbc/roadmap/roadmap_launch_deployment_observe.dag @@ -7,7 +7,8 @@ import gunbc.live_deploy.emit { belt_tick_cadence } import std.types { String, Bool, NonEmptyStr, Int, List } import std.algebra { trim } -import extdeps.units.iso8601 { iso8601_days_from_civil, iso8601_gregorian_days_in_month, iso8601_seconds_per_day, iso8601_seconds_per_hour, iso8601_seconds_per_minute } +import extdeps.units.iso8601 { iso8601_seconds_per_minute } +import extdeps.units.iso8601_calendar { iso8601_days_from_civil, iso8601_gregorian_days_in_month, iso8601_seconds_per_day, iso8601_seconds_per_hour } import extdeps.http.client import extdeps.languages.json.emit { JsonValue, JsonString, JsonBool, serialize_json } import extdeps.languages.json.parse { @@ -267,7 +268,7 @@ fn observe_workflow_document_routed(base_url: String) -> RoadmapWorkflowObservat // ---------------------------------------------------------------- instants // `%Y-%m-%dT%H:%M:%SZ` to seconds since 1970-01-01T00:00:00Z. Days-from-civil is the proleptic // Gregorian algorithm (Howard Hinnant, "chrono-Compatible Low-Level Date Algorithms"), exact for -// every date the format can spell; the calendar is extdeps.units.iso8601's, which also formats the +// every date the format can spell; the calendar is extdeps.units.iso8601_calendar's, which also formats the // same instants. Anything that is not exactly that shape refuses. fn two_digits_at(s: String, i: Int) -> Int? { parse_int(s: substring(s: s, start: i, end: i + 2)) diff --git a/dag/gunbc/wall_clock_model.dag b/dag/gunbc/wall_clock_model.dag index e231427cae3..4180a8b828f 100644 --- a/dag/gunbc/wall_clock_model.dag +++ b/dag/gunbc/wall_clock_model.dag @@ -7,7 +7,7 @@ import v2.std.operation_realization { OperationBinding, OperationCall, OperationStep, OperationObserved, ShellObserved, } import extdeps.transports.shell { ShellProcessExited } -import extdeps.units.iso8601 { iso8601_utc_text } +import extdeps.units.iso8601_calendar { iso8601_utc_text } // A MODELED WALL CLOCK: what the worker's `date` prints, as a function of the realization's virtual // clock. The virtual clock (v2.std.operation_realization, a Nat-counted Second) is monotonic by @@ -18,7 +18,7 @@ import extdeps.units.iso8601 { iso8601_utc_text } // scheduled events keep moving forward. // // It answers extdeps.clock Clock.Now in `date -u +%Y-%m-%dT%H:%M:%SZ` form, and Clock.UnixSecs and -// Clock.UnixMillis in `date +%s` and `date +%s%3N` form; the calendar is extdeps.units.iso8601's. +// Clock.UnixMillis in `date +%s` and `date +%s%3N` form; the calendar is extdeps.units.iso8601_calendar's. type ModeledWallClock { unix_at_origin: EpochSecs step: SecondDisplacement diff --git a/dag/test/claim/boot_world_models_witness_test.dag b/dag/test/claim/boot_world_models_witness_test.dag index 4b98636bfaa..b1c5b5357bb 100644 --- a/dag/test/claim/boot_world_models_witness_test.dag +++ b/dag/test/claim/boot_world_models_witness_test.dag @@ -10,7 +10,7 @@ import extdeps.exec.command { argv_words } import extdeps.tools.gnu_coreutils { cat_command } import extdeps.crypto.hash { sha256sum_file_command } import gunbc.wall_clock_model { ModeledWallClock, wall_clock_unix } -import extdeps.units.iso8601 { iso8601_utc_text } +import extdeps.units.iso8601_calendar { iso8601_utc_text } import gunbc.remote_host_model { ModeledRemoteHost, ModeledRemoteFile, remote_request, remote_answer } import gunbc.filesystem_model { ModeledFilesystem, ModeledFile, fs_parent, fs_write, fs_list, fs_read } From 6e9568a1cee8557522c47811e2b396ae8b0cc3c5 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 29 Sep 2026 08:26:04 +0000 Subject: [PATCH 29/38] journalctl names the declarers of String and Unit (floor AmbiguousBareNameRead) The refusal is main's latent defect in extdeps.systemd.journalctl, which read String through std.types (a re-export) and Unit bare; it surfaces in the claim scope this PR's touched modules select. journalctl is outside the stage0 closure, so std.string_type is admissible there (the earlier stage0 edge refusal was the calendar in iso8601, since moved). Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/extdeps/systemd/journalctl.dag | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dag/extdeps/systemd/journalctl.dag b/dag/extdeps/systemd/journalctl.dag index 8ff9b302c4b..4af4cd7ad4b 100644 --- a/dag/extdeps/systemd/journalctl.dag +++ b/dag/extdeps/systemd/journalctl.dag @@ -1,6 +1,7 @@ module extdeps.systemd.journalctl -import std.types { NonEmptyStr, String, Bool } +import std.types { NonEmptyStr, Bool, Unit } +import std.string_type { String } import extdeps.external_authority { ExternalAuthority, ExternalModelScope, ExternalSubjectRef } import std.decl_ref { DeclarationRef, WholeDeclaration } import extdeps.uri { Uri, Https } From 826011d53b49f61a3ab807144952630412e2e9f0 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 29 Sep 2026 08:27:21 +0000 Subject: [PATCH 30/38] Rung-drop row: the route prefix is per-world, not a repeated computation (review 72549) Co-Authored-By: Claude Opus 5.5 (1M context) --- ...mtcollins1_boot_matrix_new_witness_eval_step_cost.dag | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag b/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag index 9a841cf09e1..4f3bdf819f3 100644 --- a/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag +++ b/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag @@ -20,9 +20,12 @@ import v2.workflow.floor_eval_step_cost_drop { floor_eval_step_cost_drop_boot_ma // requires be executed through the real production boundary. The decision folds each case reaches are // already witnessed at their narrower interfaces with supplied values (the media convergence folds, // the power selection, the host-capture verdict, the hold store); what only the entry can establish is -// that it reaches them with the state its own earlier steps built. The work computed identically -// across cases -- world construction and the route prefix up to the hold -- is named in gunbc#12533 as -// a follow-up to hoist; hoisting it lowers the bill and does not remove the subject. +// that it reaches them with the state its own earlier steps built. The route prefix up to the hold is +// NOT one computation repeated: each case evaluates the entry over its own world, so the same code runs +// on different inputs, and sharing it would mean resuming every case from one snapshot -- replacing the +// route from the entry that each case exists to assert. What IS shared is the pure construction of the +// healthy world the cases vary; hoisting it lowers the bill, is owed as a follow-up with a re-measure +// (gunbc#12533, review 72549), and retires nothing on its own. data mtcollins1_boot_matrix_new_witness_eval_step_cost_population: List = floor_eval_step_cost_drop_boot_matrix_rows |> map(m => concat(concat(m.identity, ": over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by "), m.measured_by)) data mtcollins1_boot_matrix_new_witness_eval_step_cost: RungDrop = RungDrop { From d75894e646cf13518c4babc0d44effbf6ef35991 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 29 Sep 2026 09:12:04 +0000 Subject: [PATCH 31/38] roadmap_served_observation names Filesystem's declarer (floor AmbiguousBareNameRead) Its Filesystem.Read/Write are the extdeps.filesystem.filesystem_io service; std.resources also declares the name. Main's latent defect, surfaced in the roadmap witness scope this PR edits. Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/gunbc/roadmap/roadmap_served_observation.dag | 1 + 1 file changed, 1 insertion(+) diff --git a/dag/gunbc/roadmap/roadmap_served_observation.dag b/dag/gunbc/roadmap/roadmap_served_observation.dag index 51d6e080e84..84b7780020d 100644 --- a/dag/gunbc/roadmap/roadmap_served_observation.dag +++ b/dag/gunbc/roadmap/roadmap_served_observation.dag @@ -4,6 +4,7 @@ import gunbc.site.markup { page_render_refusal_body } import gunbc.roadmap.roadmap_event_carrier { roadmap_event_carrier_layout_for_instance, roadmap_standings_read } import std.types { String, Int, Bool, List, NonEmptyStr } +import extdeps.filesystem.filesystem_io { Filesystem } import std.algebra { trim } import std.content_hash { content_hash_tagged_structural, content_hash_atom } import extdeps.http.server { MediaType, text_html_utf8, text_plain_utf8, application_json_utf8, ServeHttpResponse } From b7c671c620873f561c1584345fac2cde9b141483 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Tue, 29 Sep 2026 12:42:07 +0000 Subject: [PATCH 32/38] Rung-drop row: the shared world construction is measured, and owes no hoist (review 72606) claim_batch: building the healthy world, its census console and the frame costs 1,687-3,793 eval steps against 78,317-119,267 per member; with it removed every member stays over 72,300. The world is already a supplied value, which is the witness rule's remedy, not a derivation. Co-Authored-By: Claude Opus 5.5 (1M context) --- ...mtcollins1_boot_matrix_new_witness_eval_step_cost.dag | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag b/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag index 4f3bdf819f3..85519017afa 100644 --- a/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag +++ b/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag @@ -23,9 +23,12 @@ import v2.workflow.floor_eval_step_cost_drop { floor_eval_step_cost_drop_boot_ma // that it reaches them with the state its own earlier steps built. The route prefix up to the hold is // NOT one computation repeated: each case evaluates the entry over its own world, so the same code runs // on different inputs, and sharing it would mean resuming every case from one snapshot -- replacing the -// route from the entry that each case exists to assert. What IS shared is the pure construction of the -// healthy world the cases vary; hoisting it lowers the bill, is owed as a follow-up with a re-measure -// (gunbc#12533, review 72549), and retires nothing on its own. +// route from the entry that each case exists to assert. What IS shared is the construction of the +// healthy world the cases vary, and it is already the section 3 remedy: a SUPPLIED value, constructed +// directly rather than derived by executing production. MEASURED (claim_batch, 2026-09-29, review +// 72606): building that world with its census console and the frame costs 1,687 to 3,793 eval steps, +// against 78,317 to 119,267 for the member cases; with it removed even the cheapest member stays over +// the 72,300 budget. So hoisting it neither retires this drop nor removes a member, and none is owed. data mtcollins1_boot_matrix_new_witness_eval_step_cost_population: List = floor_eval_step_cost_drop_boot_matrix_rows |> map(m => concat(concat(m.identity, ": over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by "), m.measured_by)) data mtcollins1_boot_matrix_new_witness_eval_step_cost: RungDrop = RungDrop { From bd99cbb48998bcf1d95a9337700ae39d42d1dad9 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 30 Sep 2026 00:08:23 +0000 Subject: [PATCH 33/38] Matrix: bind #12434's SOL route; the two pinned SOL cases flip to controls The dry realization now answers the route #12434 landed: - processes carry a start time, visible as /proc//stat (field 22) beside cmdline; ActivateHeld publishes " ", sends the grounded preamble's banner to the capture and its stderr to the client diagnostics, and a foreign session's refusal to the diagnostics; - one exit transition (deactivate, session drop, ReleaseHeld) removes the /proc entry and records the supervisor's exit line; ReleaseHeld stops only the recorded instance; - the notice watcher the step starts before the entry is scenario state (with_notice_watcher); - shell.Move File is a rename in gunbc.filesystem_model; ipmitool mc info answers with the two fields the corpus read from this controller, its layout typed TranscribedUncited. Flips: pinned_a_healthy_census_is_refused_at_the_sol_teardown -> a_healthy_census_completes_and_releases_its_collector (ok; deactivate, ReleaseHeld, two retiring deletes, all under the hold). pinned_a_sol_loss_mid_boot_is_reported_only_at_the_deadline -> a_sol_loss_mid_boot_is_reported_before_the_deadline (typed ObservationChannelLost, incident frozen, BMC answering, teardown within 60 s of power-on). The held-elsewhere case asserts the typed cause. claim_batch, the merged tree: matrix, realization, model and filesystem witnesses 42/42 PASS. The eval-step drop now covers nine members (the listing case crossed the budget on the longer route), each row re-measured; docs/design-rung-drops.md regenerated. Co-Authored-By: Claude Opus 5.5 (1M context) --- dag/extdeps/bmc/ipmitool_observed_output.dag | 11 ++ dag/gunbc/bmc_dry_realization.dag | 7 + dag/gunbc/filesystem_model.dag | 41 ++++- .../mtcollins1_boot_dry_realization.dag | 162 ++++++++++++++---- ...boot_matrix_new_witness_eval_step_cost.dag | 8 +- ...mtcollins1_boot_acceptance_matrix_test.dag | 57 +++--- docs/design-rung-drops.md | 4 + src/v2/workflow/floor_eval_step_cost_drop.dag | 28 +-- 8 files changed, 244 insertions(+), 74 deletions(-) diff --git a/dag/extdeps/bmc/ipmitool_observed_output.dag b/dag/extdeps/bmc/ipmitool_observed_output.dag index 5b3831c55be..8d6583ae89b 100644 --- a/dag/extdeps/bmc/ipmitool_observed_output.dag +++ b/dag/extdeps/bmc/ipmitool_observed_output.dag @@ -54,3 +54,14 @@ data ipmitool_raw_two_bytes_standing: CitedFigureStanding = TranscribedUncited { fn ipmitool_raw_two_bytes(first_hex: String, second_hex: String) -> String { join([" ", first_hex, " ", second_hex, "\n"], "") } + +// `ipmitool mc info` against the Mt. Collins BMC: the two fields the corpus has read from this +// controller -- Firmware Revision 0.32 and IPMI Version 2.0 (gunbc.machine_intake_mtcollins1_access_observation) +// in ipmitool's ` : ` field layout. No verbatim stdout is retained, so the layout, and +// the absence of the other fields ipmitool prints, carry this standing. The boot's reachability read +// consumes only the exit and stderr. +data ipmitool_mc_info_standing: CitedFigureStanding = TranscribedUncited { + read_obligation: "a retained stdout of `ipmitool mc info` against the Mt. Collins BMC, cited by digest" as NonEmptyStr +} + +data ipmitool_mc_info_mt_collins: String = "Firmware Revision : 0.32\nIPMI Version : 2.0\n" diff --git a/dag/gunbc/bmc_dry_realization.dag b/dag/gunbc/bmc_dry_realization.dag index 914b446b6ef..7f7fa113851 100644 --- a/dag/gunbc/bmc_dry_realization.dag +++ b/dag/gunbc/bmc_dry_realization.dag @@ -27,6 +27,7 @@ import extdeps.bmc.megarac_observed_output { import extdeps.languages.json.parse { parse_json_document, JsonDocumentParsed, JsonDocumentUnreadable } import gunbc.machine_intake_megarac_media_attach { json_string_member, json_number_member } import extdeps.bmc.ipmitool_observed_output { + ipmitool_mc_info_mt_collins, ipmitool_bootparam5_no_override, ipmitool_bootparam5_cdrom_efi_next_boot, ipmitool_bootdev_cdrom_reply, ipmitool_sel_elist_line, } @@ -111,6 +112,11 @@ fn bmc_sel_elist_reply(world: BmcWorld, call: OperationCall) -> BmcReply { BmcReplied { stdout: join(map(world.sel, r => ipmitool_sel_elist_line(record_id_hex: r.id_hex, date: r.date, time: r.time, sensor: r.sensor, event: r.event, state: r.state)), ""), world: world } } +// `mc info` answers whenever the management controller does, whatever the host's power or SOL state. +fn bmc_mc_info_reply(world: BmcWorld, call: OperationCall) -> BmcReply { + BmcReplied { stdout: ipmitool_mc_info_mt_collins, world: world } +} + // One IPMI round trip takes one virtual second. fn bmc_lift(get: fn(S) -> BmcWorld, put: fn(S, BmcWorld) -> S, reply: fn(BmcWorld, OperationCall) -> BmcReply) -> fn(S, OperationCall) -> OperationStep { fn(state, call) { @@ -133,6 +139,7 @@ fn bmc_bindings(get: fn(S) -> BmcWorld, put: fn(S, BmcWorld) -> S) -> List(get: fn(S) -> ModeledFilesystem, put: fn(S, ModeledFil OperationBinding { at: filesystem_operation(operation: "WriteOwnerOnly"), handler: writing(get: get, put: put, create_new: false) }, OperationBinding { at: filesystem_operation(operation: "WriteCreateNew"), handler: writing(get: get, put: put, create_new: true) }, OperationBinding { at: filesystem_operation(operation: "WriteCreateNewWithMode"), handler: writing(get: get, put: put, create_new: true) }, + OperationBinding { at: shell_move_file_operation, handler: moving(get: get, put: put) }, ] } @@ -205,3 +207,40 @@ fn writing(get: fn(S) -> ModeledFilesystem, put: fn(S, ModeledFilesystem) -> } } } + +// extdeps.shell shell.Move File: `mv `, a rename within the one modeled +// filesystem. It replaces an existing destination file, as mv does, and fails -- exit 1 with mv's +// "cannot stat" -- when the source does not exist or the destination's directory does not. +data shell_move_file_operation: OperationRef = OperationRef { path: "dag/extdeps/shell.dag", service: "shell.Move", operation: "File" } + +fn fs_move(fs: ModeledFilesystem, source: String, destination: String) -> ModeledFileWrite { + match fs_file(fs: fs, path: source) { + Absent => ModeledFileWrite { fs: fs, observation: failed_with(kind: FilesystemNotFound, message: concat("No such file or directory: ", source)) } + Present { value: f } => + if !fs_is_directory(fs: fs, path: fs_parent(path: destination)) { + ModeledFileWrite { fs: fs, observation: failed_with(kind: FilesystemNotFound, message: concat("No such file or directory: ", destination)) } + } else { + let removed = ModeledFilesystem { directories: fs.directories, files: filter(fs.files, x => x.path != source) } + ModeledFileWrite { fs: fs_with_file(fs: removed, path: destination, content: f.content), observation: FileOperationSucceeded { byte_count: byte_size(count: 0), content: "" } } + } + } +} + +fn moving(get: fn(S) -> ModeledFilesystem, put: fn(S, ModeledFilesystem) -> S) -> fn(S, OperationCall) -> OperationStep { + fn(state, call) { + match operation_input_text(invocation: call.invocation, name: "source") { + Absent => OperationHarnessFault { reason: "a move was dispatched without a source" as NonEmptyStr } + Present { value: source } => match operation_input_text(invocation: call.invocation, name: "destination") { + Absent => OperationHarnessFault { reason: "a move was dispatched without a destination" as NonEmptyStr } + Present { value: destination } => { + let m = fs_move(fs: get(state), source: source, destination: destination) + let exited = match m.observation { + FileOperationSucceeded { byte_count: _, content: _ } => ShellProcessExited { exit_code: 0, stdout: "", stderr: "" } + FileOperationFailed { kind: _, error: _ } => ShellProcessExited { exit_code: 1, stdout: "", stderr: join(["mv: cannot stat '", source, "': No such file or directory\n"], "") } + } + OperationObserved { observation: ShellObserved { observation: exited }, state: put(state, m.fs), elapsed: second(count: 0) } + } + } + } + } +} diff --git a/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag b/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag index 28c827c2301..40d603ef75a 100644 --- a/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag +++ b/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag @@ -2,6 +2,7 @@ module gunbc.machine_intake_mtcollins1_boot_dry_realization import std.types { Bool, Int, List, NonEmptyStr, String } import std.measure { Second, second, second_count } +import std.algebra { trim } import v2.std.operation_argv { OperationRef } import v2.std.operation_realization { OperationRealization, OperationBinding, OperationCall, OperationStep, @@ -19,6 +20,9 @@ import extdeps.exec.command { env_prefixed_command } import extdeps.tools.env { EnvSet } import extdeps.ssh.openssh_client_commands { ssh_add_list_identities_command } import extdeps.bmc.ipmitool_observed_output { ipmitool_sol_activate_preamble } +import extdeps.bmc.ipmi { ipmitool_sol_operational_banner } +import gunbc.machine_intake_sol_hold { sol_hold_exit_record_prefix } +import gunbc.machine_intake_mtcollins1_boot_run { mtcollins1_sol_notice_ready_suffix, mtcollins1_sol_notice_watcher_path, mtcollins1_sol_notice_token_env } import gunbc.fleet_ssh_access { fleet_automation_ssh_key_fingerprint } import gunbc.remote_host_model { ModeledRemoteHost, remote_host_binding } import gunbc.wall_clock_model { ModeledWallClock, wall_clock_bindings } @@ -59,14 +63,20 @@ fn agent_listing(agent: ModeledSshAgent) -> ShellExchangeObservation { } } -// THE WORKER'S PROCESSES that the attempt starts: today, the SOL collector. A live process is visible -// the way the production liveness check reads it -- /proc//cmdline -- and a dead one is not. The -// model writes cmdline with spaces between arguments where Linux writes NULs; the production reader -// asks only whether it contains `ipmitool`, which both spellings answer the same way. +// THE WORKER'S PROCESSES: the SOL collector the attempt starts, and the notice watcher the boot step +// starts before the entry runs. A live process is visible the way the production observers read it +// (gunbc.machine_intake_mtcollins1_boot_run mtcollins1_sol_collector_observe, sol_observer_refusal_of): +// /proc//stat, whose field 22 is the start time the hold publishes as " ", and +// /proc//cmdline. An exited process has neither. The model writes cmdline with spaces where Linux +// writes NULs; the production reader asks only whether it contains `ipmitool`, which both answer alike. +// diagnostic_path is where the hold sends the process's stderr and its exit record (empty: none). type ModeledProcess { pid: Int + start_time: Int + comm: String cmdline: String capture_path: String + diagnostic_path: String alive: Bool } @@ -122,8 +132,70 @@ fn proc_cmdline_path(pid: Int) -> String { join(["/proc/", to_string(pid), "/cmdline"], "") } +fn proc_stat_path(pid: Int) -> String { + join(["/proc/", to_string(pid), "/stat"], "") +} + +// proc(5) stat: " () ..." with the start time at field 22, which is word 19 of what +// follows the comm -- the position the production reader (proc_stat_after_comm) takes it from. The +// fields the readers do not consult are zero. +fn proc_stat_line(p: ModeledProcess) -> String { + join([to_string(p.pid), " (", p.comm, ") S ", join(map([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], _i => "0"), " "), " ", to_string(p.start_time), " 0\n"], "") +} + +// The published identity, as gunbc.machine_intake.sol_hold's supervisor writes it: " ". +fn process_identity(p: ModeledProcess) -> String { + join([to_string(p.pid), " ", to_string(p.start_time), "\n"], "") +} + +// Start times in clock ticks since the worker booted (USER_HZ 100), so a later process has a later one. +fn start_ticks(w: MtCollins1BootWorld, now: Second) -> Int { + (w.worker.uptime_at_origin + (second_count(s: now) as Int)) * 100 +} + +fn process_visible(fs: ModeledFilesystem, p: ModeledProcess) -> ModeledFilesystem { + fs_with_file(fs: fs_with_file(fs: fs, path: proc_stat_path(pid: p.pid), content: proc_stat_line(p: p)), path: proc_cmdline_path(pid: p.pid), content: p.cmdline) +} + +// A PROCESS EXITS: its /proc entry goes, and the hold's supervisor records the exit in the process's +// diagnostic file (sol_hold_exit_record_prefix). The one transition every way a process ends uses -- +// a deactivate, the controller dropping the session, a release. +fn process_gone(fs: ModeledFilesystem, p: ModeledProcess) -> ModeledFilesystem { + let without = ModeledFilesystem { directories: fs.directories, files: filter(fs.files, f => f.path != proc_stat_path(pid: p.pid) && f.path != proc_cmdline_path(pid: p.pid)) } + if p.diagnostic_path == "" { without } else { appended(fs: without, path: p.diagnostic_path, text: concat(sol_hold_exit_record_prefix, "0\n")) } +} + +fn process_dead(p: ModeledProcess) -> ModeledProcess { + ModeledProcess { pid: p.pid, start_time: p.start_time, comm: p.comm, cmdline: p.cmdline, capture_path: p.capture_path, diagnostic_path: p.diagnostic_path, alive: false } +} + +fn exit_processes(w: MtCollins1BootWorld, ending: fn(ModeledProcess) -> Bool) -> MtCollins1BootWorld { + let fs = fold(filter(w.worker.processes, p => p.alive && ending(p)), init: w.fs, f: fn(acc, p) { process_gone(fs: acc, p: p) }) + let processes = map(w.worker.processes, p => if p.alive && ending(p) { process_dead(p: p) } else { p }) + with_worker(w: with_fs(w: w, fs: fs), worker: ModeledWorker { next_pid: w.worker.next_pid, processes: processes, uptime_at_origin: w.worker.uptime_at_origin }) +} + +fn is_collector(p: ModeledProcess) -> Bool { + p.comm == "ipmitool" +} + fn live_collectors(w: MtCollins1BootWorld) -> List { - filter(w.worker.processes, p => p.alive) + filter(w.worker.processes, p => p.alive && is_collector(p: p)) +} + +fn started(w: MtCollins1BootWorld, p: ModeledProcess, fs: ModeledFilesystem) -> MtCollins1BootWorld { + with_worker(w: with_fs(w: w, fs: if p.alive { process_visible(fs: fs, p: p) } else { fs }), worker: ModeledWorker { next_pid: p.pid + 1, processes: list_append(w.worker.processes, p), uptime_at_origin: w.worker.uptime_at_origin }) +} + +// THE NOTICE WATCHER the boot step starts before the entry (gunbc.ci_spec): its token in the +// environment, its readiness file holding that token, and its record naming a live process -- what +// gunbc.machine_intake_mtcollins1_boot_run mtcollins1_sol_await_observer requires before any BMC +// contact. It is scenario state because the entry does not start it. +fn with_notice_watcher(w: MtCollins1BootWorld, capture_path: String, token: String) -> MtCollins1BootWorld { + let p = ModeledProcess { pid: w.worker.next_pid, start_time: start_ticks(w: w, now: second(count: 0)), comm: "bash", cmdline: "bash -c gunbc-sol-notice-watch", capture_path: capture_path, diagnostic_path: "", alive: true } + let fs = fs_with_file(fs: fs_with_file(fs: w.fs, path: concat(capture_path, mtcollins1_sol_notice_ready_suffix), content: concat(token, "\n")), path: mtcollins1_sol_notice_watcher_path(capture_path: capture_path), content: process_identity(p: p)) + let env = list_append(w.environment, ModeledVariable { name: mtcollins1_sol_notice_token_env as String, value: token }) + started(w: MtCollins1BootWorld { bmc: w.bmc, fs: w.fs, environment: env, agent: w.agent, remote_hosts: w.remote_hosts, clock: w.clock, worker: w.worker, console: w.console, media: w.media }, p: p, fs: fs) } fn exited_step(state: S, stdout: String, exit_code: Int) -> OperationStep { @@ -143,41 +215,59 @@ fn sdr_dump_handler(w: MtCollins1BootWorld, call: OperationCall) -> OperationSte } } -// gunbc.machine_intake.sol_hold ActivateHeld: a shell starts `ipmitool ... sol activate` in the -// background with its output appended to the capture, records its pid in the pid file, and exits 0 once -// the background job exists. The collector then opens the BMC's SOL session. If the session is already -// held by another client, the real client prints the refusal and exits at once -- but the shell has -// already succeeded and written the pid, exactly as here. +// gunbc.machine_intake.sol_hold ActivateHeld: the hold starts `ipmitool ... sol activate` in the +// background with its stdout appended to the capture and its stderr to the client diagnostic file, +// publishes " " to the pid file, and exits 0 once the job exists. On activation the +// client prints the grounded preamble (extdeps.bmc.ipmitool_observed_output +// ipmitool_sol_activate_preamble): its banner line on stdout, the rest on stderr. If the controller's +// session is already held, the client prints the refusal on stderr and exits at once, and the +// supervisor records that exit -- but the hold has already succeeded and published the pid. fn sol_activate_held_handler(w: MtCollins1BootWorld, call: OperationCall) -> OperationStep { match operation_input_text(invocation: call.invocation, name: "capture_path") { Absent => OperationHarnessFault { reason: "ActivateHeld was dispatched without a capture path" as NonEmptyStr } Present { value: capture } => match operation_input_text(invocation: call.invocation, name: "pid_path") { Absent => OperationHarnessFault { reason: "ActivateHeld was dispatched without a pid path" as NonEmptyStr } - Present { value: pid_path } => { - let pid = w.worker.next_pid - let program = match operation_input_text(invocation: call.invocation, name: "ipmitool") { Present { value: p } => p Absent => "ipmitool" } - let already = w.bmc.sol_session_open - let process = ModeledProcess { pid: pid, cmdline: concat(program, " -H bmc -I lanplus sol activate"), capture_path: capture, alive: !already } - let fs1 = fs_write(fs: w.fs, path: pid_path, content: concat(to_string(pid), "\n"), create_new: false).fs - let fs2 = if already { appended(fs: fs1, path: capture, text: "Info: SOL payload already active on another session\n") } - else { fs_with_file(fs: appended(fs: fs1, path: capture, text: ipmitool_sol_activate_preamble), path: proc_cmdline_path(pid: pid), content: process.cmdline) } - let worker = ModeledWorker { next_pid: pid + 1, processes: list_append(w.worker.processes, process), uptime_at_origin: w.worker.uptime_at_origin } - let bmc = if already { w.bmc } else { bmc_with_sol_session(world: w.bmc, open: true) } - exited_step(state: with_bmc(w: with_worker(w: with_fs(w: w, fs: fs2), worker: worker), bmc: bmc), stdout: "", exit_code: 0) + Present { value: pid_path } => match operation_input_text(invocation: call.invocation, name: "client_diagnostic_path") { + Absent => OperationHarnessFault { reason: "ActivateHeld was dispatched without a client diagnostic path" as NonEmptyStr } + Present { value: diag } => { + let program = match operation_input_text(invocation: call.invocation, name: "ipmitool") { Present { value: p } => p Absent => "ipmitool" } + let already = w.bmc.sol_session_open + let p = ModeledProcess { pid: w.worker.next_pid, start_time: start_ticks(w: w, now: call.now), comm: "ipmitool", cmdline: concat(program, " -H bmc -I lanplus sol activate"), capture_path: capture, diagnostic_path: diag, alive: !already } + let published = fs_write(fs: w.fs, path: pid_path, content: process_identity(p: p), create_new: false).fs + let preamble = filter(split(s: ipmitool_sol_activate_preamble, delimiter: "\n"), l => l != "") + let banner = join(map(filter(preamble, l => string_contains(s: l, pattern: ipmitool_sol_operational_banner)), l => concat(l, "\n")), "") + let stderr = join(map(filter(preamble, l => !string_contains(s: l, pattern: ipmitool_sol_operational_banner)), l => concat(l, "\n")), "") + let fs = if already { appended(fs: published, path: diag, text: concat("Info: SOL payload already active on another session\n", concat(sol_hold_exit_record_prefix, "1\n"))) } + else { appended(fs: appended(fs: published, path: capture, text: banner), path: diag, text: stderr) } + let bmc = if already { w.bmc } else { bmc_with_sol_session(world: w.bmc, open: true) } + exited_step(state: with_bmc(w: started(w: w, p: p, fs: fs), bmc: bmc), stdout: "", exit_code: 0) + } } } } } -// `ipmitool sol deactivate` ends the BMC's SOL session; a collector attached to it loses its session -// and exits, so its /proc entry disappears. Its pid file is NOT removed -- nothing in the realization -// removes it -- which is exactly what the production teardown observes afterwards. +// `ipmitool sol deactivate` ends the controller's SOL session, and a collector attached to it loses +// its session and exits. Its pid file and activation receipt stay: removing them is the production +// release's retirement, not the controller's. fn sol_deactivate_handler(w: MtCollins1BootWorld, call: OperationCall) -> OperationStep { - let fs = fold(live_collectors(w: w), init: w.fs, f: fn(acc, p) { - ModeledFilesystem { directories: acc.directories, files: filter(acc.files, f => f.path != proc_cmdline_path(pid: p.pid)) } - }) - let worker = ModeledWorker { next_pid: w.worker.next_pid, processes: map(w.worker.processes, p => ModeledProcess { pid: p.pid, cmdline: p.cmdline, capture_path: p.capture_path, alive: false }), uptime_at_origin: w.worker.uptime_at_origin } - exited_step(state: with_bmc(w: with_worker(w: with_fs(w: w, fs: fs), worker: worker), bmc: bmc_with_sol_session(world: w.bmc, open: false)), stdout: "", exit_code: 0) + exited_step(state: with_bmc(w: exit_processes(w: w, ending: is_collector), bmc: bmc_with_sol_session(world: w.bmc, open: false)), stdout: "", exit_code: 0) +} + +// gunbc.machine_intake.sol_hold ReleaseHeld: stops the process the pid file names only while that +// " " is a live instance with that start time (sol_hold_owned_condition), and succeeds +// once it is gone; a record naming no live instance has nothing to stop and also succeeds. +fn sol_release_held_handler(w: MtCollins1BootWorld, call: OperationCall) -> OperationStep { + match operation_input_text(invocation: call.invocation, name: "pid_path") { + Absent => OperationHarnessFault { reason: "ReleaseHeld was dispatched without a pid path" as NonEmptyStr } + Present { value: pid_path } => match fs_file(fs: w.fs, path: pid_path) { + Absent => exited_step(state: w, stdout: "", exit_code: 0) + Present { value: record } => { + let named = trim(s: record.content) + exited_step(state: exit_processes(w: w, ending: fn(p) { trim(s: process_identity(p: p)) == named }), stdout: "", exit_code: 0) + } + } + } } // Master Write-Read toward the SMpro. The retained attempt of 2026-09-27 (run 36335369059, @@ -241,12 +331,7 @@ fn concat_text(lines: List) -> String { // or by a deactivate -- the `ipmitool sol activate` client loses its session and exits, so its /proc // entry disappears; its pid file stays, since nothing in the realization removes it. fn collectors_follow_session(w: MtCollins1BootWorld) -> MtCollins1BootWorld { - if w.bmc.sol_session_open || count(live_collectors(w: w)) == 0 { w } else { - let fs = fold(live_collectors(w: w), init: w.fs, f: fn(acc, p) { - ModeledFilesystem { directories: acc.directories, files: filter(acc.files, f => f.path != proc_cmdline_path(pid: p.pid)) } - }) - with_worker(w: with_fs(w: w, fs: fs), worker: ModeledWorker { next_pid: w.worker.next_pid, processes: map(w.worker.processes, p => ModeledProcess { pid: p.pid, cmdline: p.cmdline, capture_path: p.capture_path, alive: false }), uptime_at_origin: w.worker.uptime_at_origin }) - } + if w.bmc.sol_session_open || count(live_collectors(w: w)) == 0 { w } else { exit_processes(w: w, ending: is_collector) } } // Time only changes the world when something is scheduled -- a BMC event, a connecting or withdrawing @@ -269,6 +354,12 @@ data sol_hold_activate_held_operation: OperationRef = OperationRef { operation: "ActivateHeld", } +data sol_hold_release_held_operation: OperationRef = OperationRef { + path: "dag/gunbc/machine_intake/sol_hold.dag", + service: "gunbc.machine_intake.sol_hold", + operation: "ReleaseHeld", +} + fn mtcollins1_boot_dry_realization(identity: NonEmptyStr, initial: MtCollins1BootWorld, epoch: Second) -> OperationRealization { let bmc = concat(bmc_bindings(get: fn(w) { w.bmc }, put: with_bmc), megarac_bindings(get: fn(w) { w.media }, put: with_media)) let files = filesystem_bindings(get: fn(w) { w.fs }, put: with_fs) @@ -283,6 +374,7 @@ fn mtcollins1_boot_dry_realization(identity: NonEmptyStr, initial: MtCollins1Boo OperationBinding { at: bmc_ipmi_operation(operation: "SolDeactivate"), handler: sol_deactivate_handler }, OperationBinding { at: bmc_ipmi_operation(operation: "MasterWriteReadAuthenticated"), handler: smpro_refused_handler }, OperationBinding { at: sol_hold_activate_held_operation, handler: sol_activate_held_handler }, + OperationBinding { at: sol_hold_release_held_operation, handler: sol_release_held_handler }, OperationBinding { at: linux_procfs_read_uptime_operation, handler: uptime_handler }, ] OperationRealization { diff --git a/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag b/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag index 85519017afa..2ab9f02ae62 100644 --- a/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag +++ b/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag @@ -9,7 +9,7 @@ import v2.workflow.floor_eval_step_cost_drop { floor_eval_step_cost_drop_boot_ma // matrix, msg_83af891b). The DECLARATION lives here; the POPULATION's authority is // `v2.workflow.floor_eval_step_cost_drop` `floor_eval_step_cost_drop_boot_matrix_rows`. // -// WHAT IS LOST IS ONE WALL, AND THE CLAIMS ARE UNTOUCHED. The eight identities are planned, executed +// WHAT IS LOST IS ONE WALL, AND THE CLAIMS ARE UNTOUCHED. The nine identities are planned, executed // and measured on every pull request that edits them; a semantic red and a wall-clock crossing still // block. Only the eval-step overrun is reported as `EvalStepsOverBudgetUnderDeclaredDrop` rather than // refusing. The budget is not raised. Their CPU stays under the enrolment margin, so they enrol @@ -27,14 +27,14 @@ import v2.workflow.floor_eval_step_cost_drop { floor_eval_step_cost_drop_boot_ma // healthy world the cases vary, and it is already the section 3 remedy: a SUPPLIED value, constructed // directly rather than derived by executing production. MEASURED (claim_batch, 2026-09-29, review // 72606): building that world with its census console and the frame costs 1,687 to 3,793 eval steps, -// against 78,317 to 119,267 for the member cases; with it removed even the cheapest member stays over +// against 77,801 to 167,236 for the member cases (re-measured after #12434's route was bound); with it removed even the cheapest member stays over // the 72,300 budget. So hoisting it neither retires this drop nor removes a member, and none is owed. data mtcollins1_boot_matrix_new_witness_eval_step_cost_population: List = floor_eval_step_cost_drop_boot_matrix_rows |> map(m => concat(concat(m.identity, ": over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by "), m.measured_by)) data mtcollins1_boot_matrix_new_witness_eval_step_cost: RungDrop = RungDrop { identity: "mtcollins1_boot_matrix_new_witness_eval_step_cost" as NonEmptyStr, - subject: "new-witness eval-step cost gate over the eight mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered", + subject: "new-witness eval-step cost gate over the nine mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered", declared: "2026-09-28", @@ -47,6 +47,6 @@ data mtcollins1_boot_matrix_new_witness_eval_step_cost: RungDrop = RungDrop { replacement: "the evaluation frame and its modeled operation realization realized by the natively emitted runtime, so the boot entry runs as emitted code rather than in the seed interpreter", }, population: mtcollins1_boot_matrix_new_witness_eval_step_cost_population, - restoration_trigger: "THE CAPABILITY: witnesses emitted to native code with the emitted runtime realizing v2.std.witness_evaluation evaluate_in_witness_frame and its v2.std.operation_realization modeled realization, EXECUTING ON THE MERGE PATH as a phase of a required lane whose red blocks a merge, running these eight identities with the real mtcollins1_boot_wet_on_srv1 entry; WHAT THAT MUST BE SUFFICIENT FOR: each case measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither.", + restoration_trigger: "THE CAPABILITY: witnesses emitted to native code with the emitted runtime realizing v2.std.witness_evaluation evaluate_in_witness_frame and its v2.std.operation_realization modeled realization, EXECUTING ON THE MERGE PATH as a phase of a required lane whose red blocks a merge, running these nine identities with the real mtcollins1_boot_wet_on_srv1 entry; WHAT THAT MUST BE SUFFICIENT FOR: each case measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither.", } } diff --git a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag index 31cc33ca5e0..c3caf077966 100644 --- a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag +++ b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag @@ -19,7 +19,7 @@ import gunbc.wall_clock_model { ModeledWallClock } import gunbc.megarac_media_model { MegaRacMediaWorld, MegaRacShare, MegaRacImage, MegaRacCdRow, megarac_cleared_cd } import gunbc.machine_intake_mtcollins1_boot_dry_realization { MtCollins1BootWorld, ModeledSshAgent, ModeledWorker, ModeledHostConsole, TimedConsoleLine, - mtcollins1_boot_dry_realization, with_media, with_bmc, + mtcollins1_boot_dry_realization, with_media, with_bmc, with_notice_watcher, } import v2.std.operation_realization { OperationRealization, OperationBinding, OperationCall, OperationStep, OperationObserved, OperationWorkerKilled, @@ -66,7 +66,7 @@ fn matrix_grant(service: String, verb: std.effect_grant.Verb) -> Grant { // writing. An operation of any other service refuses as uncovered. data matrix_services: List = [ "diagnostic.ipmi.Tool", "megarac.Media", "Filesystem", "shell.Env", "sleep.Delay", - "gunbc.machine_intake.sol_hold", "shell.Exec", "ssh.Session", "Clock", "linux.Procfs", + "gunbc.machine_intake.sol_hold", "shell.Exec", "ssh.Session", "Clock", "linux.Procfs", "shell.Move", ] fn matrix_frame(world: MtCollins1BootWorld) -> WitnessEvaluationFrame { @@ -127,7 +127,7 @@ fn worker_filesystem() -> ModeledFilesystem { } fn world_with_console(lines: List) -> MtCollins1BootWorld { - MtCollins1BootWorld { + with_notice_watcher(capture_path: sol_capture_path, token: "matrix-notice-token", w: MtCollins1BootWorld { bmc: bmc_world(power: BmcPowerOff), fs: worker_filesystem(), environment: [ @@ -144,7 +144,7 @@ fn world_with_console(lines: List) -> MtCollins1BootWorld { worker: ModeledWorker { next_pid: 4000, processes: [], uptime_at_origin: 86400 }, console: ModeledHostConsole { lines: lines, emitted: 0, boot: none }, media: healthy_media(), - } + }) } // A census image that boots one socket and completes: the capture test.claim.machine_intake.mtcollins1_boot_run_witness_test @@ -243,18 +243,21 @@ test fn a_host_that_never_prints_a_census_refuses_at_the_deadline() -> Bool { } } -// A HEALTHY HOST CANNOT CURRENTLY REPORT SUCCESS. PINNED DEFECT, found by this matrix: the census -// closes and is accepted (the stage is ActuationCensusEnded), and the attempt still refuses at the SOL -// teardown, because after `sol deactivate` the collector exits and nothing removes its pid file, which -// the re-observation then reads as stale (gunbc.machine_intake_mtcollins1_boot_run -// mtcollins1_boot_release_sol). This case asserts TODAY'S behaviour so the defect stays visible; when -// the teardown is repaired it must FLIP to ExitSuccess, and that flip is the repair landing. -test fn pinned_a_healthy_census_is_refused_at_the_sol_teardown() -> Bool { +// A HEALTHY HOST REPORTS SUCCESS, AND THE TEARDOWN RELEASES ONLY WHAT IT OWNS. The census closes and +// is accepted, and the attempt completes. This case was pinned as a defect -- the collector's pid file +// read as stale after the deactivate -- until #12434 made the release identity-checked and retiring; it +// is now the control that the repair holds. Route: the SOL deactivate, then the hold's ReleaseHeld +// of the recorded instance, then the retirement of its pid file and activation receipt; every +// controller write under the unit hold. +test fn a_healthy_census_completes_and_releases_its_collector() -> Bool { match run_attempt(world: world_with_console(lines: console_of(capture: one_socket_census_capture, begin_after: 60, end_after: 240))) { WitnessReturned { value, route } => census_ended(a: value) - && outcome_reason(a: value) == "refused: mtcollins1 boot: SOL pid file stale after deactivate" + && outcome_reason(a: value) == "ok" && count_named(route: route, name: "diagnostic.ipmi.Tool.SolDeactivate") == 1 + && first_ordinal(route: route, name: "diagnostic.ipmi.Tool.SolDeactivate") < first_ordinal(route: route, name: "gunbc.machine_intake.sol_hold.ReleaseHeld") + && first_ordinal(route: route, name: "gunbc.machine_intake.sol_hold.ReleaseHeld") < first_ordinal(route: route, name: "Filesystem.Delete") + && count_named(route: route, name: "Filesystem.Delete") == 2 && controller_writes_are_under_the_hold(route: route) _ => false } @@ -396,14 +399,17 @@ test fn pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause() // ─── OBSERVATION FAILURES ───────────────────────────────────────────────────────────────────────── -// ANOTHER CLIENT ALREADY HOLDS THE SOL SESSION. The collector starts and exits at once, so it is not -// held after the settle; the attempt refuses before any other controller write. +// ANOTHER CLIENT ALREADY HOLDS THE SOL SESSION. The collector starts and exits at once with the +// controller's refusal on its stderr, and the attempt refuses with that typed cause -- read from the +// client diagnostics, never the host capture -- before any other controller write, and without +// deactivating a session this hold did not establish. test fn a_sol_session_held_elsewhere_refuses_before_the_attach() -> Bool { let w = world_with_console(lines: []) match run_attempt(world: with_bmc(w: w, bmc: bmc_with_sol_session(world: w.bmc, open: true))) { WitnessReturned { value, route } => - string_contains(s: outcome_reason(a: value), pattern: "SOL collector is not held after acquire") + string_contains(s: outcome_reason(a: value), pattern: "SOL collector not established: the SOL payload is already active on another session") && count_named(route: route, name: "megarac.Media.StartMedia") == 0 + && count_named(route: route, name: "diagnostic.ipmi.Tool.SolDeactivate") == 0 && reached_no_power_action(route: route) _ => false } @@ -411,22 +417,29 @@ test fn a_sol_session_held_elsewhere_refuses_before_the_attach() -> Bool { // SOL IS LOST THIRTY SECONDS AFTER THE HOST STARTS BOOTING WHILE MANAGEMENT KEEPS ANSWERING, and the // census would have printed at sixty. The drop is armed by the host's own boot in the model, so it -// follows the route's real power-on. PINNED: the attempt learns of the loss only at the terminal -// deadline -- the capture stops growing and the watch polls to its bound -- so the first loss is not -// reported before the deadline (#12423 asks that it is). The final world shows the drop fired, so the -// case is not green on a run where the loss never happened. When loss detection lands it must flip to -// an early, typed refusal. +// follows the route's real power-on. The loss is reported when it is observed, not at the terminal +// deadline (#12423): a typed ObservationChannelLost, the incident frozen, the BMC shown answering, and +// the teardown within sixty seconds of the power action -- before the census could have printed. This +// case was pinned as reporting the loss only at the deadline until #12434's watch detected it. The final +// world shows the drop fired, so the case is not green on a run where the loss never happened. fn sol_drop_fired_in(w: MtCollins1BootWorld) -> Bool { bmc_sol_drop_fired(world: w.bmc) } -test fn pinned_a_sol_loss_mid_boot_is_reported_only_at_the_deadline() -> Bool { +fn first_dispatch_second(route: List, name: String) -> Int { + match filter(route, r => operation_name(r: r) == name).first() { Present { value: r } => std.measure.second_count(s: r.dispatched_at) as Int Absent => 0 - 1 } +} + +test fn a_sol_loss_mid_boot_is_reported_before_the_deadline() -> Bool { let w = world_with_console(lines: console_of(capture: one_socket_census_capture, begin_after: 60, end_after: 240)) let dropping = with_bmc(w: w, bmc: bmc_with_sol_drop_after_boot(world: w.bmc, after: second(count: 30))) match evaluate_in_witness_frame(frame: matrix_frame(world: dropping), subject: fn(_scope) { mtcollins1_boot_wet_on_srv1() }) { WitnessReturned { value, route, state } => - string_contains(s: outcome_reason(a: value), pattern: "deadline reached without a host-capture terminal") + string_contains(s: outcome_reason(a: value), pattern: "ObservationChannelLost while the boot was being watched") + && string_contains(s: outcome_reason(a: value), pattern: "incident frozen to /run/sol.capture.loss") + && string_contains(s: outcome_reason(a: value), pattern: "BMC answered mc info") && count_named(route: route, name: "diagnostic.ipmi.Tool.ChassisPowerControl") == 1 + && first_dispatch_second(route: route, name: "diagnostic.ipmi.Tool.SolDeactivate") - first_dispatch_second(route: route, name: "diagnostic.ipmi.Tool.ChassisPowerControl") <= 60 && match state { Present { value: end } => sol_drop_fired_in(w: end) Absent => false } _ => false } diff --git a/docs/design-rung-drops.md b/docs/design-rung-drops.md index 22a709e6079..1a3e041ff35 100644 --- a/docs/design-rung-drops.md +++ b/docs/design-rung-drops.md @@ -342,6 +342,10 @@ new-witness eval-step cost gate over the one roadmap page claim that derives the new-witness eval-step cost gate over the ten interpreted P-256 and P-384 point and group-law checks and SHA-384 FIPS vectors of the App Attest path: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the P-384 and SHA-384 primitives executing as natively emitted code rather than as interpreted bignat and Word64 folds). Population: test.claim.p384_dag_ecdsa_witness_test.the_p384_base_point_is_on_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.p384_dag_ecdsa_witness_test.a_point_beside_the_base_point_is_off_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.p384_dag_ecdsa_witness_test.a_point_with_a_coordinate_at_p_is_not_admitted: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.p384_dag_ecdsa_witness_test.apples_ca_and_root_keys_are_on_p384: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.sha384_fips_witness_test.sha384_of_the_empty_message_is_the_published_value_and_one_octet_discriminates: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35703555837 (gunbc#11981, head 0b0a2dffe), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, over the new-witness eval-step budget and above the per-subject line. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field. The billed work is two single-block SHA-384 compressions, the published empty message and a one-octet message on an interpreter that reifies no machine width; the published positive and its discriminating mutation are one subject, so the cost is caused by the claim's own boundary and not by neighbouring vectors, test.claim.sha384_fips_witness_test.sha384_of_abc_is_the_published_value_and_one_octet_discriminates: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35703555837 (gunbc#11981, head 0b0a2dffe), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, over the new-witness eval-step budget and above the per-subject line. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field. The billed work is two single-block SHA-384 compressions, the published abc vector and abd on an interpreter that reifies no machine width; the published positive and its discriminating mutation are one subject, so the cost is caused by the claim's own boundary and not by neighbouring vectors, test.claim.sha384_fips_witness_test.sha384_of_the_896_bit_message_is_the_published_two_block_value_and_one_octet_discriminates: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35703555837 (gunbc#11981, head 0b0a2dffe), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, over the new-witness eval-step budget and above the per-subject line. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field. The billed work is four SHA-384 block compressions, the published 896-bit two-block vector and a last-block mutation on an interpreter that reifies no machine width; the published positive and its discriminating mutation are one subject, so the cost is caused by the claim's own boundary and not by neighbouring vectors, test.claim.p256_dag_ecdsa_witness_test.the_base_point_is_on_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35708011844 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, well inside the wall deadline and over the new-witness eval-step budget. The billed work is one P-256 curve equation over the published base point as 11-limb bignat folds on an interpreter that reifies no machine width. It is a MEMBER BECAUSE IT WAS SPLIT OUT of a claim that also asserted n*G = infinity: that conjunction crossed the 8000ms wall deadline and was INTERRUPTED BEFORE ANY VERDICT, so both facts were being lost; the scalar multiplication left the floor for the native row the frontier names, and this cheap half now reaches a verdict. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field, test.claim.p384_dag_ecdsa_witness_test.doubling_and_adding_the_base_point_stay_on_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by claim_batch --hermetic [witness] receipt on session/nimble-eagle-216-step3 (added for review 69917 after run 35678328407): verdict pass, wall under the 8 s deadline, over the new-witness eval-step budget; the billed work is one Jacobian doubling, one addition and two projective curve-equation checks at 16 limbs, test.claim.p384_dag_ecdsa_witness_test.doubling_an_off_curve_point_stays_off_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by claim_batch --hermetic [witness] receipt on session/nimble-eagle-216-step3 (added for review 69917 after run 35678328407): verdict pass, wall under the 8 s deadline, over the new-witness eval-step budget; the billed work is one Jacobian doubling and one projective curve-equation check at 16 limbs. Restored when: THE CAPABILITY: gunbc test //gunbc/instruments:native-crypto-vectors EXECUTING ON THE MERGE PATH, i.e. as a phase of a required lane whose red blocks a merge, running these ten identities natively (its cases p256_the_base_point_is_on_the_curve, p384_the_base_point_is_on_the_curve, p384_red_a_point_beside_the_base_point_is_off_the_curve, p384_red_a_point_with_a_coordinate_at_p_is_not_admitted, p384_doubling_and_adding_the_base_point_stay_on_the_curve, p384_red_doubling_an_off_curve_point_stays_off_the_curve, p384_apples_ca_and_root_keys_are_on_the_curve and the sha384_* FIPS cases with their reds) while each still evaluates the real curve equation or the real hash; WHAT THAT MUST BE SUFFICIENT FOR: the P-384 parameters and SHA-384 are exercised on the acceptance path with no interpreted 16-limb fold inside a claim frame, so the interpreted identities can leave the floor for that row, or they measure under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives. The same row run BY NAME (as it exists since gunbc#12306) executes the facts natively but is NOT the acceptance path and retires nothing. Supplying the curve check's answer or the digest as a fixture, or deleting the identities, satisfies neither. +### new-witness eval-step cost gate over the nine mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered — declared 2026-09-28 + +new-witness eval-step cost gate over the nine mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the evaluation frame and its modeled operation realization realized by the natively emitted runtime, so the boot entry runs as emitted code rather than in the seed interpreter). Population: test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_host_that_never_prints_a_census_refuses_at_the_deadline: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_host_that_never_prints_a_census_refuses_at_the_deadline, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=167236 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_healthy_census_completes_and_releases_its_collector: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_healthy_census_completes_and_releases_its_collector, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=158055 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.an_already_presented_image_is_not_attached_again: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions an_already_presented_image_is_not_attached_again, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=134675 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.media_that_never_becomes_ready_refuses_before_any_power_action: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions media_that_never_becomes_ready_refuses_before_any_power_action, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=91254 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_presentation_lost_after_readiness_stops_the_handoff: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_presentation_lost_after_readiness_stops_the_handoff, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=97089 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=129921 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_sol_loss_mid_boot_is_reported_before_the_deadline: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_sol_loss_mid_boot_is_reported_before_the_deadline, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=124297 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_two_socket_answer_is_a_truthful_topology_refusal: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_two_socket_answer_is_a_truthful_topology_refusal, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=124495 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_listing_that_names_the_image_twice_starts_nothing: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_listing_that_names_the_image_twice_starts_nothing, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=77801 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization. Restored when: THE CAPABILITY: witnesses emitted to native code with the emitted runtime realizing v2.std.witness_evaluation evaluate_in_witness_frame and its v2.std.operation_realization modeled realization, EXECUTING ON THE MERGE PATH as a phase of a required lane whose red blocks a merge, running these nine identities with the real mtcollins1_boot_wet_on_srv1 entry; WHAT THAT MUST BE SUFFICIENT FOR: each case measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither. + ### new-witness eval-step cost gate over six App Attest verifier claims -- the two real-path inhabitance claims over Apple's objects, three single structural steps priced by their own SHA-256s, and the wire-carrier join: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered — declared 2026-09-22 new-witness eval-step cost gate over six App Attest verifier claims -- the two real-path inhabitance claims over Apple's objects, three single structural steps priced by their own SHA-256s, and the wire-carrier join: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the CBOR, DER and SHA-256 readers executing as natively emitted code over the sample objects rather than as interpreted octet-list folds). Population: test.claim.app_attest_verifier_witness_test.the_sample_passes_every_step_before_the_extension_steps: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_app_attest_verifier_rows; measured by claim_batch --hermetic [witness] receipt at session/nimble-eagle-216-step3b 4d089af1cb9 (gunbc#11989): verdict pass, wall under the 8 s deadline, eval_steps read from that receipt's own line and over the new-witness budget; the billed work is the real path end to end: Apple's attestation object through decode_attestation (CBOR, two DER certificates, the PEM-pinned root, authenticator data) and the whole structural fold with its SHA-256s. It is the attestation path's one inhabitance claim, so nothing here is supplied, test.claim.app_attest_verifier_witness_test.the_nonce_step_reds_on_other_client_data: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_app_attest_verifier_rows; measured by claim_batch --hermetic [witness] receipt at session/nimble-eagle-216-step3b 4d089af1cb9 (gunbc#11989): verdict pass, wall under the 8 s deadline, eval_steps read from that receipt's own line and over the new-witness budget; the billed work is nonce_step alone over supplied inputs: SHA-256 of the client data and SHA-256 of authData concatenated with it, two interpreted hashes, which is the step and nothing before it, test.claim.app_attest_verifier_witness_test.the_app_id_step_reds_on_another_app_id: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_app_attest_verifier_rows; measured by claim_batch --hermetic [witness] receipt at session/nimble-eagle-216-step3b 4d089af1cb9 (gunbc#11989): verdict pass, wall under the 8 s deadline, eval_steps read from that receipt's own line and over the new-witness budget; the billed work is app_id_step alone over supplied inputs: one interpreted SHA-256 of the App ID, test.claim.app_attest_verifier_witness_test.the_key_id_step_admits_the_real_key_and_reds_on_another_key_id: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_app_attest_verifier_rows; measured by claim_batch --hermetic [witness] receipt at session/nimble-eagle-216-step3b (gunbc#11989), and the required floor's required-ci-measurement-receipt: verdict pass, wall under the 8 s deadline, eval_steps read from that receipt's own line and over the new-witness budget; the billed work is key_id_step alone over supplied inputs, paired over its own subject: one interpreted SHA-256 of the credential certificate's 65-octet public key (the second comparison reuses it), test.claim.app_attest_verifier_witness_test.the_sample_assertion_passes_rp_id_and_refuses_on_absent_extensions: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_app_attest_verifier_rows; measured by claim_batch --hermetic [witness] receipt at session/nimble-eagle-216-step3b (gunbc#11989), and the required floor's required-ci-measurement-receipt: verdict pass, wall under the 8 s deadline, eval_steps read from that receipt's own line and over the new-witness budget; the billed work is the assertion's real path: the assertion object through assertion_parts and the fold's RP ID SHA-256, plus the attestation's leaf certificate read for the credential key (the attestation-to-assertion join). It is the assertion path's one inhabitance claim, paired with a wrong-App-ID refusal over the same subject, test.claim.signature_verify_join_witness_test.malformed_carriers_refuse_before_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_app_attest_verifier_rows; measured by claim_batch --hermetic [witness] receipt at session/nimble-eagle-216-step3b 4d089af1cb9 (gunbc#11989): verdict pass, wall under the 8 s deadline, eval_steps read from that receipt's own line and over the new-witness budget. THIS ROW'S BILLED WORK IS NOT THE VERIFIER ROWS': it touches no Apple object, no certificate and no hash. It base64url-decodes two carriers of the RFC 6979 A.2.5 P-256 vectors through extdeps.crypto.signature verify_signature, which asks signature_carrier_refusal FIRST and refuses on size before the implementation is consulted. What the budget prices is the interpreter walking those octet lists byte by byte to decode and size them. Restored when: THE CAPABILITY. MachineWidth reification (gunbc#11819) lets the extdeps.apple.app_attest closure emit, and these six identities execute on the natively emitted route -- the gunbc test instrument row the ecdsa_verification_realization_frontier names -- while each still exercises its own production code over its own inputs -- Apple's objects end to end for the two inhabitance claims, its one named step for the three step claims, the RFC 6979 carriers for the carrier-join claim; WHAT THAT MUST BE SUFFICIENT FOR: the verifier folds are exercised over the real sample on the acceptance path, and the carrier join over its real carriers, with no interpreted octet walk inside a claim frame, and the identities then measure under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, or leave the floor for that native row. Supplying the two inhabitance claims' inputs as fixtures, precomputing a step's hashes, or deleting the identities satisfies neither. diff --git a/src/v2/workflow/floor_eval_step_cost_drop.dag b/src/v2/workflow/floor_eval_step_cost_drop.dag index 967c218d0d8..e521780e260 100644 --- a/src/v2/workflow/floor_eval_step_cost_drop.dag +++ b/src/v2/workflow/floor_eval_step_cost_drop.dag @@ -237,43 +237,47 @@ data floor_eval_step_cost_drop_python_to_typescript_inhabitance_rows: List = [ EvalStepCostDropMeasurement { identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_host_that_never_prints_a_census_refuses_at_the_deadline" as NonEmptyStr, - measured_by: "PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization" as NonEmptyStr, + measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_host_that_never_prints_a_census_refuses_at_the_deadline, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=167236 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, }, EvalStepCostDropMeasurement { - identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_healthy_census_is_refused_at_the_sol_teardown" as NonEmptyStr, - measured_by: "PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization" as NonEmptyStr, + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_healthy_census_completes_and_releases_its_collector" as NonEmptyStr, + measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_healthy_census_completes_and_releases_its_collector, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=158055 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, }, EvalStepCostDropMeasurement { identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.an_already_presented_image_is_not_attached_again" as NonEmptyStr, - measured_by: "PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization" as NonEmptyStr, + measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions an_already_presented_image_is_not_attached_again, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=134675 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, }, EvalStepCostDropMeasurement { identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.media_that_never_becomes_ready_refuses_before_any_power_action" as NonEmptyStr, - measured_by: "PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization" as NonEmptyStr, + measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions media_that_never_becomes_ready_refuses_before_any_power_action, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=91254 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, }, EvalStepCostDropMeasurement { identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_presentation_lost_after_readiness_stops_the_handoff" as NonEmptyStr, - measured_by: "PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization" as NonEmptyStr, + measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_presentation_lost_after_readiness_stops_the_handoff, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=97089 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, }, EvalStepCostDropMeasurement { identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause" as NonEmptyStr, - measured_by: "PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization" as NonEmptyStr, + measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=129921 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, }, EvalStepCostDropMeasurement { - identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_sol_loss_mid_boot_is_reported_only_at_the_deadline" as NonEmptyStr, - measured_by: "PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization" as NonEmptyStr, + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_sol_loss_mid_boot_is_reported_before_the_deadline" as NonEmptyStr, + measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_sol_loss_mid_boot_is_reported_before_the_deadline, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=124297 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, }, EvalStepCostDropMeasurement { identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_two_socket_answer_is_a_truthful_topology_refusal" as NonEmptyStr, - measured_by: "PR required floor run 36419407813 of gunbc#12533, the [over-cost] and changed-witness lines for this identity (planned_as_changed_witness, verdict pass, wall under 500ms, over the new-witness eval-step budget); the billed work is the real gunbc.machine_intake_mtcollins1_boot_run mtcollins1_boot_wet_on_srv1 evaluated end to end in the seed interpreter over the dry realization" as NonEmptyStr, + measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_two_socket_answer_is_a_truthful_topology_refusal, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=124495 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, + }, + EvalStepCostDropMeasurement { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_listing_that_names_the_image_twice_starts_nothing" as NonEmptyStr, + measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_listing_that_names_the_image_twice_starts_nothing, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=77801 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, }, ] From 45d967da2bdc3246b3e7997d7fa78ecb9c47ac26 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 30 Sep 2026 00:48:08 +0000 Subject: [PATCH 34/38] Matrix: the SOL-loss case requires exactly one teardown, after the power action and within 60 s At bd99cbb489 first_dispatch_second returned -1 when a dispatch was absent, so an absent teardown, or one before the power action, satisfied the delta bound (side-chat hold on #12533). Both instants are now Optional, the delay must exist and be nonnegative, and the route must carry exactly one SolDeactivate after the power action. Co-Authored-By: Claude Opus 5.5 (1M context) --- ...mtcollins1_boot_acceptance_matrix_test.dag | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag index c3caf077966..13db7c2ddf0 100644 --- a/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag +++ b/dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag @@ -426,8 +426,21 @@ fn sol_drop_fired_in(w: MtCollins1BootWorld) -> Bool { bmc_sol_drop_fired(world: w.bmc) } -fn first_dispatch_second(route: List, name: String) -> Int { - match filter(route, r => operation_name(r: r) == name).first() { Present { value: r } => std.measure.second_count(s: r.dispatched_at) as Int Absent => 0 - 1 } +fn first_dispatch_second(route: List, name: String) -> Int? { + match filter(route, r => operation_name(r: r) == name).first() { Present { value: r } => Present { value: std.measure.second_count(s: r.dispatched_at) as Int } Absent => none } +} + +// The teardown's delay after the power action, required to exist on both ends -- an absent deactivate +// or power action is not a zero -- and to be nonnegative and at most `bound` seconds (review of #12533 +// at bd99cbb489: a -1 sentinel let an absent or early teardown pass). +fn teardown_within(route: List, bound: Int) -> Bool { + match first_dispatch_second(route: route, name: "diagnostic.ipmi.Tool.ChassisPowerControl") { + Absent => false + Present { value: power } => match first_dispatch_second(route: route, name: "diagnostic.ipmi.Tool.SolDeactivate") { + Absent => false + Present { value: torn } => torn - power >= 0 && torn - power <= bound + } + } } test fn a_sol_loss_mid_boot_is_reported_before_the_deadline() -> Bool { @@ -439,7 +452,9 @@ test fn a_sol_loss_mid_boot_is_reported_before_the_deadline() -> Bool { && string_contains(s: outcome_reason(a: value), pattern: "incident frozen to /run/sol.capture.loss") && string_contains(s: outcome_reason(a: value), pattern: "BMC answered mc info") && count_named(route: route, name: "diagnostic.ipmi.Tool.ChassisPowerControl") == 1 - && first_dispatch_second(route: route, name: "diagnostic.ipmi.Tool.SolDeactivate") - first_dispatch_second(route: route, name: "diagnostic.ipmi.Tool.ChassisPowerControl") <= 60 + && count_named(route: route, name: "diagnostic.ipmi.Tool.SolDeactivate") == 1 + && first_ordinal(route: route, name: "diagnostic.ipmi.Tool.ChassisPowerControl") < first_ordinal(route: route, name: "diagnostic.ipmi.Tool.SolDeactivate") + && teardown_within(route: route, bound: 60) && match state { Present { value: end } => sol_drop_fired_in(w: end) Absent => false } _ => false } From a67a3dcf220b9771951b67eb3036bcca9fc393b9 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 30 Sep 2026 01:35:57 +0000 Subject: [PATCH 35/38] Matrix: six cases admitted to the enrolment dead band under one declared drop; two eval-step rows Side-chat ruling (eager-owl-205, 2026-09-30): the six cases CI measured strictly above the 302 ms enrolment margin and under the 500 ms line (396, 394, 378, 361, 339, 332 ms, run 36648847499) are rostered in v2.workflow.floor_enrolment_dead_band, self-staling both ways, under the declared drop gunbc.rung_drop.mtcollins1_boot_matrix_enrolment_dead_band_observed_only. Its population derives from those rows, and its trigger names the one capability both matrix drops wait on, now a single row (mtcollins1_boot_matrix_native_witness_capability) that the eval-step drop also reads. The CPU is #12434's own polling route run faithfully; ablation found no model hotspot. The eval-step drop gains the interrupted-attempt and wrong-share cases (74,419 and 77,423 on CI), eleven rows. Rung-drop and enrolment witnesses 19/19 PASS; docs/design-rung-drops.md regenerated. Co-Authored-By: Claude Opus 5.5 (1M context) --- ...trix_enrolment_dead_band_observed_only.dag | 44 +++++++++++++++++++ ...boot_matrix_new_witness_eval_step_cost.dag | 11 +++-- dag/gunbc/rung_drop/roster.dag | 2 + docs/design-rung-drops.md | 8 +++- src/v2/workflow/floor_enrolment_dead_band.dag | 34 +++++++++++++- src/v2/workflow/floor_eval_step_cost_drop.dag | 14 ++++-- 6 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 dag/gunbc/rung_drop/mtcollins1_boot_matrix_enrolment_dead_band_observed_only.dag diff --git a/dag/gunbc/rung_drop/mtcollins1_boot_matrix_enrolment_dead_band_observed_only.dag b/dag/gunbc/rung_drop/mtcollins1_boot_matrix_enrolment_dead_band_observed_only.dag new file mode 100644 index 00000000000..934575ee04b --- /dev/null +++ b/dag/gunbc/rung_drop/mtcollins1_boot_matrix_enrolment_dead_band_observed_only.dag @@ -0,0 +1,44 @@ +module gunbc.rung_drop.mtcollins1_boot_matrix_enrolment_dead_band_observed_only + +import std.types { NonEmptyStr, List, String } +import gunbc.rung_drop { RungDrop, Standing, TypedDeclaration, ReplacementStaged } +import gunbc.guarantee_rung { Mitigatable, MechanicallyPreventable } +import gunbc.rung_drop.mtcollins1_boot_matrix_new_witness_eval_step_cost { mtcollins1_boot_matrix_native_witness_capability } +import v2.workflow.floor_enrolment_dead_band { mtcollins1_boot_matrix_enrolment_dead_band_observed_only } + +// THE DECLARED RUNG DROP for the enrolment margin over the mtcollins1 boot matrix cases whose honest +// CPU lies in the gate's dead band (margin, per-subject line] (DESIGN 4b(3); side-chat ruling of +// eager-owl-205 on gunbc#12533, 2026-09-30; precedent `app_attest_verifier_enrolment_dead_band_observed_only`). +// The POPULATION's authority is `v2.workflow.floor_enrolment_dead_band` +// `mtcollins1_boot_matrix_enrolment_dead_band_observed_only`, each row carrying its CI-observed CPU. +// +// WHAT IS LOST IS THE ENROLMENT-MARGIN DECISION, AND ONLY THAT. The cases stay test fns and execute on +// every required floor that plans them; a semantic red, a runtime error, a route gap, a wall +// interruption and a missing terminal stay armed, and the eval-step overrun is the sibling drop's. The +// authority is self-staling on both sides: at or under the margin a row blocks as +// enrolment_dead_band_stale, above the line as enrolment_dead_band_wrong_ground. +// +// WHY THE COST IS NOT CUT: it is the subject. Each case runs the real entry, and the CPU above the +// margin is gunbc#12434's own SOL polling route -- watch ticks reading the pid file, /proc and the +// capture -- answered faithfully by the dry realization; an ablation on the merged tree found no model +// hotspot (a stubbed advance only shortened the route). The restoration is the same capability as the +// eval-step drop's, named once in `mtcollins1_boot_matrix_native_witness_capability`. +data mtcollins1_boot_matrix_enrolment_dead_band_observed_only_population: List = mtcollins1_boot_matrix_enrolment_dead_band_observed_only() |> map(o => concat(concat(o.identity, ": "), o.reason as String)) + +data mtcollins1_boot_matrix_enrolment_dead_band_observed_only_drop: RungDrop = RungDrop { + identity: "mtcollins1_boot_matrix_enrolment_dead_band_observed_only" as NonEmptyStr, + + subject: "the enrolment-margin decision over the mtcollins1 boot acceptance matrix cases whose honest CPU lies in (margin, per-subject line]: they execute, and an exact reading inside the band is reported rather than decided", + + declared: "2026-09-30", + + standing: Standing, + + declaration: TypedDeclaration { + previous: MechanicallyPreventable, + temporary: Mitigatable, + reason: ReplacementStaged { replacement: "the matrix cases executing as natively emitted witnesses, the same replacement the eval-step drop mtcollins1_boot_matrix_new_witness_eval_step_cost waits on" }, + population: mtcollins1_boot_matrix_enrolment_dead_band_observed_only_population, + restoration_trigger: concat(concat("THE CAPABILITY: ", mtcollins1_boot_matrix_native_witness_capability), "; WHAT THAT MUST BE SUFFICIENT FOR: each dead-band identity reaches its verdict with stable headroom under the enrolment margin v2.workflow.floor_enrolment_margin derives, with its route and outcome assertions unchanged -- at which point every row stales and deletes. Cutting the polling route the cases assert, or supplying a decided value in place of the entry, satisfies neither."), + } +} diff --git a/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag b/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag index 2ab9f02ae62..3dc77e7bf87 100644 --- a/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag +++ b/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag @@ -9,7 +9,7 @@ import v2.workflow.floor_eval_step_cost_drop { floor_eval_step_cost_drop_boot_ma // matrix, msg_83af891b). The DECLARATION lives here; the POPULATION's authority is // `v2.workflow.floor_eval_step_cost_drop` `floor_eval_step_cost_drop_boot_matrix_rows`. // -// WHAT IS LOST IS ONE WALL, AND THE CLAIMS ARE UNTOUCHED. The nine identities are planned, executed +// WHAT IS LOST IS ONE WALL, AND THE CLAIMS ARE UNTOUCHED. The eleven identities are planned, executed // and measured on every pull request that edits them; a semantic red and a wall-clock crossing still // block. Only the eval-step overrun is reported as `EvalStepsOverBudgetUnderDeclaredDrop` rather than // refusing. The budget is not raised. Their CPU stays under the enrolment margin, so they enrol @@ -31,10 +31,15 @@ import v2.workflow.floor_eval_step_cost_drop { floor_eval_step_cost_drop_boot_ma // the 72,300 budget. So hoisting it neither retires this drop nor removes a member, and none is owed. data mtcollins1_boot_matrix_new_witness_eval_step_cost_population: List = floor_eval_step_cost_drop_boot_matrix_rows |> map(m => concat(concat(m.identity, ": over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by "), m.measured_by)) +// THE ONE CAPABILITY that retires both matrix cost drops -- this one and +// `gunbc.rung_drop.mtcollins1_boot_matrix_enrolment_dead_band_observed_only` -- named once so the two +// cannot drift apart: the cost of both is the seed interpreter running the real entry. +data mtcollins1_boot_matrix_native_witness_capability: String = "witnesses emitted to native code with the emitted runtime realizing v2.std.witness_evaluation evaluate_in_witness_frame and its v2.std.operation_realization modeled realization, EXECUTING ON THE MERGE PATH as a phase of a required lane whose red blocks a merge, running the mtcollins1 boot acceptance matrix with the real mtcollins1_boot_wet_on_srv1 entry" + data mtcollins1_boot_matrix_new_witness_eval_step_cost: RungDrop = RungDrop { identity: "mtcollins1_boot_matrix_new_witness_eval_step_cost" as NonEmptyStr, - subject: "new-witness eval-step cost gate over the nine mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered", + subject: "new-witness eval-step cost gate over the eleven mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered", declared: "2026-09-28", @@ -47,6 +52,6 @@ data mtcollins1_boot_matrix_new_witness_eval_step_cost: RungDrop = RungDrop { replacement: "the evaluation frame and its modeled operation realization realized by the natively emitted runtime, so the boot entry runs as emitted code rather than in the seed interpreter", }, population: mtcollins1_boot_matrix_new_witness_eval_step_cost_population, - restoration_trigger: "THE CAPABILITY: witnesses emitted to native code with the emitted runtime realizing v2.std.witness_evaluation evaluate_in_witness_frame and its v2.std.operation_realization modeled realization, EXECUTING ON THE MERGE PATH as a phase of a required lane whose red blocks a merge, running these nine identities with the real mtcollins1_boot_wet_on_srv1 entry; WHAT THAT MUST BE SUFFICIENT FOR: each case measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither.", + restoration_trigger: concat(concat("THE CAPABILITY: ", mtcollins1_boot_matrix_native_witness_capability), "; WHAT THAT MUST BE SUFFICIENT FOR: each of these eleven identities measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither."), } } diff --git a/dag/gunbc/rung_drop/roster.dag b/dag/gunbc/rung_drop/roster.dag index e371113dfe5..1cf21185ecd 100644 --- a/dag/gunbc/rung_drop/roster.dag +++ b/dag/gunbc/rung_drop/roster.dag @@ -95,6 +95,7 @@ import gunbc.rung_drop.app_attest_interpreted_crypto_new_witness_eval_step_cost import gunbc.rung_drop.mtcollins1_boot_matrix_new_witness_eval_step_cost { mtcollins1_boot_matrix_new_witness_eval_step_cost } import gunbc.rung_drop.app_attest_verifier_new_witness_eval_step_cost { app_attest_verifier_new_witness_eval_step_cost } import gunbc.rung_drop.app_attest_verifier_enrolment_dead_band_observed_only { app_attest_verifier_enrolment_dead_band_observed_only } +import gunbc.rung_drop.mtcollins1_boot_matrix_enrolment_dead_band_observed_only { mtcollins1_boot_matrix_enrolment_dead_band_observed_only_drop } import gunbc.rung_drop.sha256_span_program_serialize_new_witness_eval_step_cost { sha256_span_program_serialize_new_witness_eval_step_cost } import gunbc.rung_drop.v41_row_store_host_realized_by_sample { v41_row_store_host_realized_by_sample } import gunbc.rung_drop.seam_monolith_control_unmeasured_derived_root { seam_monolith_control_unmeasured_derived_root } @@ -189,6 +190,7 @@ data rung_drop_roster: List = [ mtcollins1_boot_matrix_new_witness_eval_step_cost, app_attest_verifier_new_witness_eval_step_cost, app_attest_verifier_enrolment_dead_band_observed_only, + mtcollins1_boot_matrix_enrolment_dead_band_observed_only_drop, sha256_span_program_serialize_new_witness_eval_step_cost, v41_row_store_host_realized_by_sample, seam_monolith_control_unmeasured_derived_root, diff --git a/docs/design-rung-drops.md b/docs/design-rung-drops.md index 1a3e041ff35..025cf621baa 100644 --- a/docs/design-rung-drops.md +++ b/docs/design-rung-drops.md @@ -342,9 +342,9 @@ new-witness eval-step cost gate over the one roadmap page claim that derives the new-witness eval-step cost gate over the ten interpreted P-256 and P-384 point and group-law checks and SHA-384 FIPS vectors of the App Attest path: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the P-384 and SHA-384 primitives executing as natively emitted code rather than as interpreted bignat and Word64 folds). Population: test.claim.p384_dag_ecdsa_witness_test.the_p384_base_point_is_on_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.p384_dag_ecdsa_witness_test.a_point_beside_the_base_point_is_off_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.p384_dag_ecdsa_witness_test.a_point_with_a_coordinate_at_p_is_not_admitted: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.p384_dag_ecdsa_witness_test.apples_ca_and_root_keys_are_on_p384: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.sha384_fips_witness_test.sha384_of_the_empty_message_is_the_published_value_and_one_octet_discriminates: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35703555837 (gunbc#11981, head 0b0a2dffe), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, over the new-witness eval-step budget and above the per-subject line. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field. The billed work is two single-block SHA-384 compressions, the published empty message and a one-octet message on an interpreter that reifies no machine width; the published positive and its discriminating mutation are one subject, so the cost is caused by the claim's own boundary and not by neighbouring vectors, test.claim.sha384_fips_witness_test.sha384_of_abc_is_the_published_value_and_one_octet_discriminates: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35703555837 (gunbc#11981, head 0b0a2dffe), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, over the new-witness eval-step budget and above the per-subject line. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field. The billed work is two single-block SHA-384 compressions, the published abc vector and abd on an interpreter that reifies no machine width; the published positive and its discriminating mutation are one subject, so the cost is caused by the claim's own boundary and not by neighbouring vectors, test.claim.sha384_fips_witness_test.sha384_of_the_896_bit_message_is_the_published_two_block_value_and_one_octet_discriminates: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35703555837 (gunbc#11981, head 0b0a2dffe), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, over the new-witness eval-step budget and above the per-subject line. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field. The billed work is four SHA-384 block compressions, the published 896-bit two-block vector and a last-block mutation on an interpreter that reifies no machine width; the published positive and its discriminating mutation are one subject, so the cost is caused by the claim's own boundary and not by neighbouring vectors, test.claim.p256_dag_ecdsa_witness_test.the_base_point_is_on_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35708011844 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, well inside the wall deadline and over the new-witness eval-step budget. The billed work is one P-256 curve equation over the published base point as 11-limb bignat folds on an interpreter that reifies no machine width. It is a MEMBER BECAUSE IT WAS SPLIT OUT of a claim that also asserted n*G = infinity: that conjunction crossed the 8000ms wall deadline and was INTERRUPTED BEFORE ANY VERDICT, so both facts were being lost; the scalar multiplication left the floor for the native row the frontier names, and this cheap half now reaches a verdict. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field, test.claim.p384_dag_ecdsa_witness_test.doubling_and_adding_the_base_point_stay_on_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by claim_batch --hermetic [witness] receipt on session/nimble-eagle-216-step3 (added for review 69917 after run 35678328407): verdict pass, wall under the 8 s deadline, over the new-witness eval-step budget; the billed work is one Jacobian doubling, one addition and two projective curve-equation checks at 16 limbs, test.claim.p384_dag_ecdsa_witness_test.doubling_an_off_curve_point_stays_off_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by claim_batch --hermetic [witness] receipt on session/nimble-eagle-216-step3 (added for review 69917 after run 35678328407): verdict pass, wall under the 8 s deadline, over the new-witness eval-step budget; the billed work is one Jacobian doubling and one projective curve-equation check at 16 limbs. Restored when: THE CAPABILITY: gunbc test //gunbc/instruments:native-crypto-vectors EXECUTING ON THE MERGE PATH, i.e. as a phase of a required lane whose red blocks a merge, running these ten identities natively (its cases p256_the_base_point_is_on_the_curve, p384_the_base_point_is_on_the_curve, p384_red_a_point_beside_the_base_point_is_off_the_curve, p384_red_a_point_with_a_coordinate_at_p_is_not_admitted, p384_doubling_and_adding_the_base_point_stay_on_the_curve, p384_red_doubling_an_off_curve_point_stays_off_the_curve, p384_apples_ca_and_root_keys_are_on_the_curve and the sha384_* FIPS cases with their reds) while each still evaluates the real curve equation or the real hash; WHAT THAT MUST BE SUFFICIENT FOR: the P-384 parameters and SHA-384 are exercised on the acceptance path with no interpreted 16-limb fold inside a claim frame, so the interpreted identities can leave the floor for that row, or they measure under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives. The same row run BY NAME (as it exists since gunbc#12306) executes the facts natively but is NOT the acceptance path and retires nothing. Supplying the curve check's answer or the digest as a fixture, or deleting the identities, satisfies neither. -### new-witness eval-step cost gate over the nine mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered — declared 2026-09-28 +### new-witness eval-step cost gate over the eleven mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered — declared 2026-09-28 -new-witness eval-step cost gate over the nine mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the evaluation frame and its modeled operation realization realized by the natively emitted runtime, so the boot entry runs as emitted code rather than in the seed interpreter). Population: test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_host_that_never_prints_a_census_refuses_at_the_deadline: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_host_that_never_prints_a_census_refuses_at_the_deadline, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=167236 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_healthy_census_completes_and_releases_its_collector: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_healthy_census_completes_and_releases_its_collector, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=158055 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.an_already_presented_image_is_not_attached_again: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions an_already_presented_image_is_not_attached_again, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=134675 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.media_that_never_becomes_ready_refuses_before_any_power_action: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions media_that_never_becomes_ready_refuses_before_any_power_action, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=91254 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_presentation_lost_after_readiness_stops_the_handoff: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_presentation_lost_after_readiness_stops_the_handoff, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=97089 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=129921 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_sol_loss_mid_boot_is_reported_before_the_deadline: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_sol_loss_mid_boot_is_reported_before_the_deadline, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=124297 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_two_socket_answer_is_a_truthful_topology_refusal: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_two_socket_answer_is_a_truthful_topology_refusal, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=124495 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_listing_that_names_the_image_twice_starts_nothing: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_listing_that_names_the_image_twice_starts_nothing, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=77801 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization. Restored when: THE CAPABILITY: witnesses emitted to native code with the emitted runtime realizing v2.std.witness_evaluation evaluate_in_witness_frame and its v2.std.operation_realization modeled realization, EXECUTING ON THE MERGE PATH as a phase of a required lane whose red blocks a merge, running these nine identities with the real mtcollins1_boot_wet_on_srv1 entry; WHAT THAT MUST BE SUFFICIENT FOR: each case measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither. +new-witness eval-step cost gate over the eleven mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the evaluation frame and its modeled operation realization realized by the natively emitted runtime, so the boot entry runs as emitted code rather than in the seed interpreter). Population: test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_host_that_never_prints_a_census_refuses_at_the_deadline: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_host_that_never_prints_a_census_refuses_at_the_deadline, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=167236 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_healthy_census_completes_and_releases_its_collector: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_healthy_census_completes_and_releases_its_collector, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=158055 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.an_already_presented_image_is_not_attached_again: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions an_already_presented_image_is_not_attached_again, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=134675 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.media_that_never_becomes_ready_refuses_before_any_power_action: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions media_that_never_becomes_ready_refuses_before_any_power_action, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=91254 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_presentation_lost_after_readiness_stops_the_handoff: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_presentation_lost_after_readiness_stops_the_handoff, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=97089 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=129921 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_sol_loss_mid_boot_is_reported_before_the_deadline: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_sol_loss_mid_boot_is_reported_before_the_deadline, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=124297 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_two_socket_answer_is_a_truthful_topology_refusal: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_two_socket_answer_is_a_truthful_topology_refusal, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=124495 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_listing_that_names_the_image_twice_starts_nothing: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_listing_that_names_the_image_twice_starts_nothing, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=77801 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_an_interrupted_attempt_locks_out_the_next_one: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36648847499 of gunbc#12533 at bd99cbb4899, the COMPLETED-OVER-COST-REQUIREMENT line for this identity: verdict pass, eval_steps=74419 against the 72,300 new-witness budget, crossed once #12434's route was bound; the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.the_right_filename_on_the_wrong_share_refuses_before_any_power_action: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36648847499 of gunbc#12533 at bd99cbb4899, the COMPLETED-OVER-COST-REQUIREMENT line for this identity: verdict pass, eval_steps=77423 against the 72,300 new-witness budget, crossed once #12434's route was bound; the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization. Restored when: THE CAPABILITY: witnesses emitted to native code with the emitted runtime realizing v2.std.witness_evaluation evaluate_in_witness_frame and its v2.std.operation_realization modeled realization, EXECUTING ON THE MERGE PATH as a phase of a required lane whose red blocks a merge, running the mtcollins1 boot acceptance matrix with the real mtcollins1_boot_wet_on_srv1 entry; WHAT THAT MUST BE SUFFICIENT FOR: each of these eleven identities measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither. ### new-witness eval-step cost gate over six App Attest verifier claims -- the two real-path inhabitance claims over Apple's objects, three single structural steps priced by their own SHA-256s, and the wire-carrier join: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered — declared 2026-09-22 @@ -354,6 +354,10 @@ new-witness eval-step cost gate over six App Attest verifier claims -- the two r the enrolment-margin decision over exactly two App Attest verifier claims whose honest CPU lies in (margin, per-subject line]: they execute, and an exact reading inside the band is reported rather than decided: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: a general dead-band standing in v2.workflow.floor_enrolment_margin, or these two identities executing with stable headroom below the margin). Population: test.claim.app_attest_verifier_witness_test.the_key_id_step_admits_the_real_key_and_reds_on_another_key_id, test.claim.app_attest_verifier_witness_test.the_sample_assertion_passes_rp_id_and_refuses_on_absent_extensions. Restored when: THE CAPABILITY, EITHER OF TWO: (a) these EXACT two identities execute on a named native route (the gunbc test instrument row gunbc.auth.approval_device_redemption ecdsa_verification_realization_frontier names) with stable headroom below the enrolment margin across the envelope floor_enrolment_margin derives; or (b) a general repair of the dead-band policy in v2.workflow.floor_enrolment_margin gives every newly enrolled identity in the band a representable standing (gunbc.recurring_failure_mode enrolment_dead_band_has_no_representable_standing). MachineWidth reification or the native frontier ALONE does not retire this row: what must hold is that the two readings are under the margin with headroom, or that the policy no longer needs an exact-identity authority. v2.workflow.floor_enrolment_dead_band and this row then delete together. +### the enrolment-margin decision over the mtcollins1 boot acceptance matrix cases whose honest CPU lies in (margin, per-subject line]: they execute, and an exact reading inside the band is reported rather than decided — declared 2026-09-30 + +the enrolment-margin decision over the mtcollins1 boot acceptance matrix cases whose honest CPU lies in (margin, per-subject line]: they execute, and an exact reading inside the band is reported rather than decided: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the matrix cases executing as natively emitted witnesses, the same replacement the eval-step drop mtcollins1_boot_matrix_new_witness_eval_step_cost waits on). Population: test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_host_that_never_prints_a_census_refuses_at_the_deadline: the real boot entry end to end over the dry realization, whose cost is gunbc#12434's own SOL polling route run faithfully (the subject gunbc#12423 requires); PR required floor run 36648847499 of gunbc#12533 at bd99cbb4899 observed 396 ms CPU, strictly above the 302 ms margin and under the 500 ms line, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_healthy_census_completes_and_releases_its_collector: the real boot entry end to end over the dry realization, whose cost is gunbc#12434's own SOL polling route run faithfully (the subject gunbc#12423 requires); PR required floor run 36648847499 of gunbc#12533 at bd99cbb4899 observed 394 ms CPU, strictly above the 302 ms margin and under the 500 ms line, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause: the real boot entry end to end over the dry realization, whose cost is gunbc#12434's own SOL polling route run faithfully (the subject gunbc#12423 requires); PR required floor run 36648847499 of gunbc#12533 at bd99cbb4899 observed 378 ms CPU, strictly above the 302 ms margin and under the 500 ms line, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.an_already_presented_image_is_not_attached_again: the real boot entry end to end over the dry realization, whose cost is gunbc#12434's own SOL polling route run faithfully (the subject gunbc#12423 requires); PR required floor run 36648847499 of gunbc#12533 at bd99cbb4899 observed 361 ms CPU, strictly above the 302 ms margin and under the 500 ms line, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_two_socket_answer_is_a_truthful_topology_refusal: the real boot entry end to end over the dry realization, whose cost is gunbc#12434's own SOL polling route run faithfully (the subject gunbc#12423 requires); PR required floor run 36648847499 of gunbc#12533 at bd99cbb4899 observed 339 ms CPU, strictly above the 302 ms margin and under the 500 ms line, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_sol_loss_mid_boot_is_reported_before_the_deadline: the real boot entry end to end over the dry realization, whose cost is gunbc#12434's own SOL polling route run faithfully (the subject gunbc#12423 requires); PR required floor run 36648847499 of gunbc#12533 at bd99cbb4899 observed 332 ms CPU, strictly above the 302 ms margin and under the 500 ms line. Restored when: THE CAPABILITY: witnesses emitted to native code with the emitted runtime realizing v2.std.witness_evaluation evaluate_in_witness_frame and its v2.std.operation_realization modeled realization, EXECUTING ON THE MERGE PATH as a phase of a required lane whose red blocks a merge, running the mtcollins1 boot acceptance matrix with the real mtcollins1_boot_wet_on_srv1 entry; WHAT THAT MUST BE SUFFICIENT FOR: each dead-band identity reaches its verdict with stable headroom under the enrolment margin v2.workflow.floor_enrolment_margin derives, with its route and outcome assertions unchanged -- at which point every row stales and deletes. Cutting the polling route the cases assert, or supplying a decided value in place of the entry, satisfies neither. + ### new-witness eval-step cost gate over the one claim that inhabits the real byte-span argv with its really-serialized program: it still executes, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered — declared 2026-09-22 new-witness eval-step cost gate over the one claim that inhabits the real byte-span argv with its really-serialized program: it still executes, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the serialized byte-span program reaching a claim frame as a served value rather than being re-rendered inside it, or a serialization that fits the budget). Population: test.claim.spark.v41_checkpoint_materialize_witness.the_span_read_reads_exactly_its_region: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_span_program_rows; measured by gunbc#12010, claim_batch over dag/test/claim/spark/v41_checkpoint_materialize_witness_test.dag; planned_as_changed_witness, verdict pass, over the new-witness budget. The billed work is the one real serialization of gunbc.sha256sum_byte_span sha256sum_byte_span_program through gunbc.shell_command_text, which this claim is the argv's only inhabitance route for. Restored when: THE CAPABILITY, EITHER CLAUSE SATISFIES IT, AND BOTH ARE STATED BECAUSE THE CAUSE IS NOT YET ESTABLISHED. (i) gunbc.sha256sum_byte_span sha256sum_byte_span_program is served across claim frames by v2.workflow.floor_pure_producer_share on the acceptance path; WHAT THAT MUST BE SUFFICIENT FOR: a required-floor claim asserting the real byte-span argv carries the really-serialized program does not re-render it inside its own frame. A share row not covering this identity's demand does not satisfy this clause. OR (ii) v2.workflow.bash_command_fold_serialize serializes a fourteen-word statement list inside the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives -- whether by repair of a cost-shape defect there or by any other means. IN EITHER CASE this identity must measure under that budget WHILE STILL EXECUTING the real serialization route. Deleting the identity, supplying the program as a fixture, asserting only length(argv), or relocating it to a home that does not execute it satisfies neither clause. diff --git a/src/v2/workflow/floor_enrolment_dead_band.dag b/src/v2/workflow/floor_enrolment_dead_band.dag index b5c7fe1e6e7..592bebfa025 100644 --- a/src/v2/workflow/floor_enrolment_dead_band.dag +++ b/src/v2/workflow/floor_enrolment_dead_band.dag @@ -40,6 +40,38 @@ fn app_attest_verifier_enrolment_dead_band_observed_only() -> List List { + [ + EnrolmentDeadBandObservation { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_host_that_never_prints_a_census_refuses_at_the_deadline", + reason: "the real boot entry end to end over the dry realization, whose cost is gunbc#12434's own SOL polling route run faithfully (the subject gunbc#12423 requires); PR required floor run 36648847499 of gunbc#12533 at bd99cbb4899 observed 396 ms CPU, strictly above the 302 ms margin and under the 500 ms line" as NonEmptyStr, + }, + EnrolmentDeadBandObservation { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_healthy_census_completes_and_releases_its_collector", + reason: "the real boot entry end to end over the dry realization, whose cost is gunbc#12434's own SOL polling route run faithfully (the subject gunbc#12423 requires); PR required floor run 36648847499 of gunbc#12533 at bd99cbb4899 observed 394 ms CPU, strictly above the 302 ms margin and under the 500 ms line" as NonEmptyStr, + }, + EnrolmentDeadBandObservation { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause", + reason: "the real boot entry end to end over the dry realization, whose cost is gunbc#12434's own SOL polling route run faithfully (the subject gunbc#12423 requires); PR required floor run 36648847499 of gunbc#12533 at bd99cbb4899 observed 378 ms CPU, strictly above the 302 ms margin and under the 500 ms line" as NonEmptyStr, + }, + EnrolmentDeadBandObservation { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.an_already_presented_image_is_not_attached_again", + reason: "the real boot entry end to end over the dry realization, whose cost is gunbc#12434's own SOL polling route run faithfully (the subject gunbc#12423 requires); PR required floor run 36648847499 of gunbc#12533 at bd99cbb4899 observed 361 ms CPU, strictly above the 302 ms margin and under the 500 ms line" as NonEmptyStr, + }, + EnrolmentDeadBandObservation { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_two_socket_answer_is_a_truthful_topology_refusal", + reason: "the real boot entry end to end over the dry realization, whose cost is gunbc#12434's own SOL polling route run faithfully (the subject gunbc#12423 requires); PR required floor run 36648847499 of gunbc#12533 at bd99cbb4899 observed 339 ms CPU, strictly above the 302 ms margin and under the 500 ms line" as NonEmptyStr, + }, + EnrolmentDeadBandObservation { + identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_sol_loss_mid_boot_is_reported_before_the_deadline", + reason: "the real boot entry end to end over the dry realization, whose cost is gunbc#12434's own SOL polling route run faithfully (the subject gunbc#12423 requires); PR required floor run 36648847499 of gunbc#12533 at bd99cbb4899 observed 332 ms CPU, strictly above the 302 ms margin and under the 500 ms line" as NonEmptyStr, + }, + ] +} + fn enrolment_dead_band_identities() -> List { - app_attest_verifier_enrolment_dead_band_observed_only() |> map(o => o.identity) + concat(app_attest_verifier_enrolment_dead_band_observed_only(), mtcollins1_boot_matrix_enrolment_dead_band_observed_only()) |> map(o => o.identity) } diff --git a/src/v2/workflow/floor_eval_step_cost_drop.dag b/src/v2/workflow/floor_eval_step_cost_drop.dag index e521780e260..2c782630853 100644 --- a/src/v2/workflow/floor_eval_step_cost_drop.dag +++ b/src/v2/workflow/floor_eval_step_cost_drop.dag @@ -237,10 +237,10 @@ data floor_eval_step_cost_drop_python_to_typescript_inhabitance_rows: List = [ EvalStepCostDropMeasurement { @@ -279,6 +279,14 @@ data floor_eval_step_cost_drop_boot_matrix_rows: List Date: Wed, 30 Sep 2026 02:13:14 +0000 Subject: [PATCH 36/38] Integrate #12636: uptime answers over the file transport; one path resolution for wet and modeled #12636 binds linux.Procfs ReadUptime to the file transport with its path as a literal in the transport, not an input. The modeled file arm read the path from the inputs, a second and narrower route to the fact the wet dispatch resolves from the transport, and refused every matrix case at the uptime read. file_transport_path is now the one resolution both arms use (rostered in gunbc.modeled_operation_realization_seed_growth), and the dry uptime handler answers FileObserved with the record's final newline, the byte #12636 exists to keep. Re-measured on the merged tree: 51/51 PASS. The interrupted-attempt case is back under 72,300 (70,000) and leaves the eval-step drop; ten rows, each at this tree. Rung-drop and enrolment witnesses 19/19; docs/design-rung-drops.md regenerated. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../mtcollins1_boot_dry_realization.dag | 11 +++-- ...eled_operation_realization_seed_growth.dag | 1 + ...boot_matrix_new_witness_eval_step_cost.dag | 8 ++-- docs/design-rung-drops.md | 4 +- src/v1/stage0/src/v1_interpreter.rs | 40 +++++++++++++------ src/v2/workflow/floor_eval_step_cost_drop.dag | 30 ++++++-------- 6 files changed, 55 insertions(+), 39 deletions(-) diff --git a/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag b/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag index 40d603ef75a..328db81e665 100644 --- a/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag +++ b/dag/gunbc/machine_intake/mtcollins1_boot_dry_realization.dag @@ -6,13 +6,14 @@ import std.algebra { trim } import v2.std.operation_argv { OperationRef } import v2.std.operation_realization { OperationRealization, OperationBinding, OperationCall, OperationStep, - OperationObserved, OperationHarnessFault, ShellObserved, + OperationObserved, OperationHarnessFault, ShellObserved, FileObserved, virtual_delay_binding, operation_input_text, } import gunbc.bmc_model { BmcWorld, bmc_advance, bmc_with_sol_session, bmc_power_is_on } import gunbc.bmc_dry_realization { bmc_bindings, megarac_bindings, bmc_ipmi_operation, sleep_delay_seconds_operation } import gunbc.megarac_media_model { MegaRacMediaWorld, megarac_media_advance } -import gunbc.filesystem_model { ModeledFilesystem, ModeledFile, filesystem_bindings, fs_with_file, fs_file, fs_write } +import gunbc.filesystem_model { ModeledFilesystem, ModeledFile, filesystem_bindings, fs_with_file, fs_file, fs_write, content_bytes } +import extdeps.transports.file { FileOperationSucceeded } import gunbc.process_environment_model { ModeledVariable, environment_binding } import gunbc.host_command_model { ModeledInvocation, exact_invocation_binding, local_command_words, shell_exec_run_argv_operation } import extdeps.transports.shell { ShellExchangeObservation, ShellProcessExited } @@ -282,7 +283,8 @@ fn smpro_refused_handler(w: MtCollins1BootWorld, call: OperationCall) -> Operati } } -// /proc/uptime: the worker's seconds since its own boot, `. ` (proc(5)). +// /proc/uptime: the worker's seconds since its own boot, `. ` and one newline +// (proc(5)), read over the file transport since gunbc#12636, so the record keeps its final newline. data linux_procfs_read_uptime_operation: OperationRef = OperationRef { path: "dag/extdeps/linux/procfs.dag", service: "linux.Procfs", @@ -291,8 +293,9 @@ data linux_procfs_read_uptime_operation: OperationRef = OperationRef { fn uptime_handler(w: MtCollins1BootWorld, call: OperationCall) -> OperationStep { let up = w.worker.uptime_at_origin + (second_count(s: call.now) as Int) + let record = join([to_string(up), ".00 ", to_string(up), ".00\n"], "") OperationObserved { - observation: ShellObserved { observation: ShellProcessExited { exit_code: 0, stdout: join([to_string(up), ".00 ", to_string(up), ".00\n"], ""), stderr: "" } }, + observation: FileObserved { observation: FileOperationSucceeded { byte_count: content_bytes(s: record), content: record } }, state: w, elapsed: second(count: 0), } diff --git a/dag/gunbc/modeled_operation_realization_seed_growth.dag b/dag/gunbc/modeled_operation_realization_seed_growth.dag index eac23b41049..ce1fa8a5438 100644 --- a/dag/gunbc/modeled_operation_realization_seed_growth.dag +++ b/dag/gunbc/modeled_operation_realization_seed_growth.dag @@ -52,6 +52,7 @@ data modeled_operation_realization_seed_growth_justification: SeedGrowthJustific DeclarationRef { module_path: "v1_compiler.v1_interpreter", decl_name: "shell_result_of_observation", field: WholeDeclaration }, DeclarationRef { module_path: "v1_compiler.v1_interpreter", decl_name: "shell_result_projection", field: WholeDeclaration }, DeclarationRef { module_path: "v1_compiler.v1_interpreter", decl_name: "file_result_of_observation", field: WholeDeclaration }, + DeclarationRef { module_path: "v1_compiler.v1_interpreter", decl_name: "file_transport_path", field: WholeDeclaration }, ], reason: "The seed interpreter is the only evaluator that runs the boot orchestrator, and the acceptance matrix (#12423, operator ruling 2026-09-27) must execute that orchestrator against controlled device histories. These items are the seed realization of a .dag contract: selection is v2.std.operation_realization operation_handler_selection over std.effect_grant covering_grant, admission is modeled_realization_admitted_in and operation_realization_duplicate, the virtual clock is virtual_clock_origin and virtual_clock_after over std.measure Second, and every scenario transition is gunbc.bmc_model. The Rust holds a state value, a clock value and the dispatch log, and dispatches; shell_result_projection is the existing wet shell projection extracted so the modeled observation reaches the same decoder path.", owning_dissolution_lane: "v1-materialization-kernel" as RoadmapNodeId, diff --git a/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag b/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag index 3dc77e7bf87..e7c4c2f595d 100644 --- a/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag +++ b/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag @@ -9,7 +9,7 @@ import v2.workflow.floor_eval_step_cost_drop { floor_eval_step_cost_drop_boot_ma // matrix, msg_83af891b). The DECLARATION lives here; the POPULATION's authority is // `v2.workflow.floor_eval_step_cost_drop` `floor_eval_step_cost_drop_boot_matrix_rows`. // -// WHAT IS LOST IS ONE WALL, AND THE CLAIMS ARE UNTOUCHED. The eleven identities are planned, executed +// WHAT IS LOST IS ONE WALL, AND THE CLAIMS ARE UNTOUCHED. The ten identities are planned, executed // and measured on every pull request that edits them; a semantic red and a wall-clock crossing still // block. Only the eval-step overrun is reported as `EvalStepsOverBudgetUnderDeclaredDrop` rather than // refusing. The budget is not raised. Their CPU stays under the enrolment margin, so they enrol @@ -27,7 +27,7 @@ import v2.workflow.floor_eval_step_cost_drop { floor_eval_step_cost_drop_boot_ma // healthy world the cases vary, and it is already the section 3 remedy: a SUPPLIED value, constructed // directly rather than derived by executing production. MEASURED (claim_batch, 2026-09-29, review // 72606): building that world with its census console and the frame costs 1,687 to 3,793 eval steps, -// against 77,801 to 167,236 for the member cases (re-measured after #12434's route was bound); with it removed even the cheapest member stays over +// against 73,872 to 170,048 for the member cases (re-measured after #12434's route was bound); with it removed even the cheapest member stays over // the 72,300 budget. So hoisting it neither retires this drop nor removes a member, and none is owed. data mtcollins1_boot_matrix_new_witness_eval_step_cost_population: List = floor_eval_step_cost_drop_boot_matrix_rows |> map(m => concat(concat(m.identity, ": over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by "), m.measured_by)) @@ -39,7 +39,7 @@ data mtcollins1_boot_matrix_native_witness_capability: String = "witnesses emitt data mtcollins1_boot_matrix_new_witness_eval_step_cost: RungDrop = RungDrop { identity: "mtcollins1_boot_matrix_new_witness_eval_step_cost" as NonEmptyStr, - subject: "new-witness eval-step cost gate over the eleven mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered", + subject: "new-witness eval-step cost gate over the ten mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered", declared: "2026-09-28", @@ -52,6 +52,6 @@ data mtcollins1_boot_matrix_new_witness_eval_step_cost: RungDrop = RungDrop { replacement: "the evaluation frame and its modeled operation realization realized by the natively emitted runtime, so the boot entry runs as emitted code rather than in the seed interpreter", }, population: mtcollins1_boot_matrix_new_witness_eval_step_cost_population, - restoration_trigger: concat(concat("THE CAPABILITY: ", mtcollins1_boot_matrix_native_witness_capability), "; WHAT THAT MUST BE SUFFICIENT FOR: each of these eleven identities measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither."), + restoration_trigger: concat(concat("THE CAPABILITY: ", mtcollins1_boot_matrix_native_witness_capability), "; WHAT THAT MUST BE SUFFICIENT FOR: each of these ten identities measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither."), } } diff --git a/docs/design-rung-drops.md b/docs/design-rung-drops.md index 025cf621baa..397b8141473 100644 --- a/docs/design-rung-drops.md +++ b/docs/design-rung-drops.md @@ -342,9 +342,9 @@ new-witness eval-step cost gate over the one roadmap page claim that derives the new-witness eval-step cost gate over the ten interpreted P-256 and P-384 point and group-law checks and SHA-384 FIPS vectors of the App Attest path: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the P-384 and SHA-384 primitives executing as natively emitted code rather than as interpreted bignat and Word64 folds). Population: test.claim.p384_dag_ecdsa_witness_test.the_p384_base_point_is_on_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.p384_dag_ecdsa_witness_test.a_point_beside_the_base_point_is_off_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.p384_dag_ecdsa_witness_test.a_point_with_a_coordinate_at_p_is_not_admitted: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.p384_dag_ecdsa_witness_test.apples_ca_and_root_keys_are_on_p384: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.sha384_fips_witness_test.sha384_of_the_empty_message_is_the_published_value_and_one_octet_discriminates: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35703555837 (gunbc#11981, head 0b0a2dffe), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, over the new-witness eval-step budget and above the per-subject line. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field. The billed work is two single-block SHA-384 compressions, the published empty message and a one-octet message on an interpreter that reifies no machine width; the published positive and its discriminating mutation are one subject, so the cost is caused by the claim's own boundary and not by neighbouring vectors, test.claim.sha384_fips_witness_test.sha384_of_abc_is_the_published_value_and_one_octet_discriminates: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35703555837 (gunbc#11981, head 0b0a2dffe), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, over the new-witness eval-step budget and above the per-subject line. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field. The billed work is two single-block SHA-384 compressions, the published abc vector and abd on an interpreter that reifies no machine width; the published positive and its discriminating mutation are one subject, so the cost is caused by the claim's own boundary and not by neighbouring vectors, test.claim.sha384_fips_witness_test.sha384_of_the_896_bit_message_is_the_published_two_block_value_and_one_octet_discriminates: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35703555837 (gunbc#11981, head 0b0a2dffe), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, over the new-witness eval-step budget and above the per-subject line. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field. The billed work is four SHA-384 block compressions, the published 896-bit two-block vector and a last-block mutation on an interpreter that reifies no machine width; the published positive and its discriminating mutation are one subject, so the cost is caused by the claim's own boundary and not by neighbouring vectors, test.claim.p256_dag_ecdsa_witness_test.the_base_point_is_on_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35708011844 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, well inside the wall deadline and over the new-witness eval-step budget. The billed work is one P-256 curve equation over the published base point as 11-limb bignat folds on an interpreter that reifies no machine width. It is a MEMBER BECAUSE IT WAS SPLIT OUT of a claim that also asserted n*G = infinity: that conjunction crossed the 8000ms wall deadline and was INTERRUPTED BEFORE ANY VERDICT, so both facts were being lost; the scalar multiplication left the floor for the native row the frontier names, and this cheap half now reaches a verdict. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field, test.claim.p384_dag_ecdsa_witness_test.doubling_and_adding_the_base_point_stay_on_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by claim_batch --hermetic [witness] receipt on session/nimble-eagle-216-step3 (added for review 69917 after run 35678328407): verdict pass, wall under the 8 s deadline, over the new-witness eval-step budget; the billed work is one Jacobian doubling, one addition and two projective curve-equation checks at 16 limbs, test.claim.p384_dag_ecdsa_witness_test.doubling_an_off_curve_point_stays_off_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by claim_batch --hermetic [witness] receipt on session/nimble-eagle-216-step3 (added for review 69917 after run 35678328407): verdict pass, wall under the 8 s deadline, over the new-witness eval-step budget; the billed work is one Jacobian doubling and one projective curve-equation check at 16 limbs. Restored when: THE CAPABILITY: gunbc test //gunbc/instruments:native-crypto-vectors EXECUTING ON THE MERGE PATH, i.e. as a phase of a required lane whose red blocks a merge, running these ten identities natively (its cases p256_the_base_point_is_on_the_curve, p384_the_base_point_is_on_the_curve, p384_red_a_point_beside_the_base_point_is_off_the_curve, p384_red_a_point_with_a_coordinate_at_p_is_not_admitted, p384_doubling_and_adding_the_base_point_stay_on_the_curve, p384_red_doubling_an_off_curve_point_stays_off_the_curve, p384_apples_ca_and_root_keys_are_on_the_curve and the sha384_* FIPS cases with their reds) while each still evaluates the real curve equation or the real hash; WHAT THAT MUST BE SUFFICIENT FOR: the P-384 parameters and SHA-384 are exercised on the acceptance path with no interpreted 16-limb fold inside a claim frame, so the interpreted identities can leave the floor for that row, or they measure under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives. The same row run BY NAME (as it exists since gunbc#12306) executes the facts natively but is NOT the acceptance path and retires nothing. Supplying the curve check's answer or the digest as a fixture, or deleting the identities, satisfies neither. -### new-witness eval-step cost gate over the eleven mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered — declared 2026-09-28 +### new-witness eval-step cost gate over the ten mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered — declared 2026-09-28 -new-witness eval-step cost gate over the eleven mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the evaluation frame and its modeled operation realization realized by the natively emitted runtime, so the boot entry runs as emitted code rather than in the seed interpreter). Population: test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_host_that_never_prints_a_census_refuses_at_the_deadline: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_host_that_never_prints_a_census_refuses_at_the_deadline, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=167236 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_healthy_census_completes_and_releases_its_collector: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_healthy_census_completes_and_releases_its_collector, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=158055 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.an_already_presented_image_is_not_attached_again: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions an_already_presented_image_is_not_attached_again, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=134675 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.media_that_never_becomes_ready_refuses_before_any_power_action: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions media_that_never_becomes_ready_refuses_before_any_power_action, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=91254 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_presentation_lost_after_readiness_stops_the_handoff: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_presentation_lost_after_readiness_stops_the_handoff, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=97089 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=129921 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_sol_loss_mid_boot_is_reported_before_the_deadline: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_sol_loss_mid_boot_is_reported_before_the_deadline, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=124297 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_two_socket_answer_is_a_truthful_topology_refusal: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_two_socket_answer_is_a_truthful_topology_refusal, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=124495 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_listing_that_names_the_image_twice_starts_nothing: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_listing_that_names_the_image_twice_starts_nothing, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=77801 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_an_interrupted_attempt_locks_out_the_next_one: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36648847499 of gunbc#12533 at bd99cbb4899, the COMPLETED-OVER-COST-REQUIREMENT line for this identity: verdict pass, eval_steps=74419 against the 72,300 new-witness budget, crossed once #12434's route was bound; the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.the_right_filename_on_the_wrong_share_refuses_before_any_power_action: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36648847499 of gunbc#12533 at bd99cbb4899, the COMPLETED-OVER-COST-REQUIREMENT line for this identity: verdict pass, eval_steps=77423 against the 72,300 new-witness budget, crossed once #12434's route was bound; the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization. Restored when: THE CAPABILITY: witnesses emitted to native code with the emitted runtime realizing v2.std.witness_evaluation evaluate_in_witness_frame and its v2.std.operation_realization modeled realization, EXECUTING ON THE MERGE PATH as a phase of a required lane whose red blocks a merge, running the mtcollins1 boot acceptance matrix with the real mtcollins1_boot_wet_on_srv1 entry; WHAT THAT MUST BE SUFFICIENT FOR: each of these eleven identities measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither. +new-witness eval-step cost gate over the ten mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the evaluation frame and its modeled operation realization realized by the natively emitted runtime, so the boot entry runs as emitted code rather than in the seed interpreter). Population: test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_host_that_never_prints_a_census_refuses_at_the_deadline: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_host_that_never_prints_a_census_refuses_at_the_deadline, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=170048 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_healthy_census_completes_and_releases_its_collector: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_healthy_census_completes_and_releases_its_collector, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=160637 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.an_already_presented_image_is_not_attached_again: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions an_already_presented_image_is_not_attached_again, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=137485 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.media_that_never_becomes_ready_refuses_before_any_power_action: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions media_that_never_becomes_ready_refuses_before_any_power_action, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=92947 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_presentation_lost_after_readiness_stops_the_handoff: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_presentation_lost_after_readiness_stops_the_handoff, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=98794 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=132731 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_sol_loss_mid_boot_is_reported_before_the_deadline: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_sol_loss_mid_boot_is_reported_before_the_deadline, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=129808 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_two_socket_answer_is_a_truthful_topology_refusal: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_two_socket_answer_is_a_truthful_topology_refusal, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=126897 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_listing_that_names_the_image_twice_starts_nothing: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_listing_that_names_the_image_twice_starts_nothing, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=79511 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.the_right_filename_on_the_wrong_share_refuses_before_any_power_action: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions the_right_filename_on_the_wrong_share_refuses_before_any_power_action, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=73872 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization. Restored when: THE CAPABILITY: witnesses emitted to native code with the emitted runtime realizing v2.std.witness_evaluation evaluate_in_witness_frame and its v2.std.operation_realization modeled realization, EXECUTING ON THE MERGE PATH as a phase of a required lane whose red blocks a merge, running the mtcollins1 boot acceptance matrix with the real mtcollins1_boot_wet_on_srv1 entry; WHAT THAT MUST BE SUFFICIENT FOR: each of these ten identities measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither. ### new-witness eval-step cost gate over six App Attest verifier claims -- the two real-path inhabitance claims over Apple's objects, three single structural steps priced by their own SHA-256s, and the wire-carrier join: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered — declared 2026-09-22 diff --git a/src/v1/stage0/src/v1_interpreter.rs b/src/v1/stage0/src/v1_interpreter.rs index 161334b2f58..4d8a0cae656 100644 --- a/src/v1/stage0/src/v1_interpreter.rs +++ b/src/v1/stage0/src/v1_interpreter.rs @@ -8420,12 +8420,14 @@ fn dispatch_modeled_operation( .map_err(&harness_fault) .map(|shell| shell_result_projection(shell, op_node, ctx)) } else { - // Every file-transport operation declares its path; one dispatched without a string - // path is a malformed dispatch and refuses, never an empty path the projection would - // report as if the operation had named one. - match param_env.lookup(ctx.sym("path")) { - Some(Value::Str(p)) => file_result_of_observation(&observation, &p, ctx), - _ => Err("a file operation was dispatched without a string path".to_string()), + // The path is the transport's own (file_transport_path), resolved exactly as the wet + // dispatch resolves it; one that is missing or empty refuses, never an empty path the + // projection would report as if the operation had named one. + match file_transport_path(transport, param_env, ctx) { + Ok(p) => file_result_of_observation(&observation, &p, ctx), + Err(e) => Err(format!( + "a file operation's transport path did not resolve: {e}" + )), } .map_err(&harness_fault) .map(|file| map_file_outputs(&file, op_node, ctx)) @@ -17314,18 +17316,20 @@ fn io_error_kind_name(e: &std::io::Error) -> String { .to_string() } -fn dispatch_file( - op_node: &Rc, +/// THE ONE RESOLUTION OF A FILE OPERATION'S PATH: the transport's own `path` property, evaluated and +/// template-substituted over the operation's inputs. The wet dispatch and the modeled realization both +/// read it here, so a modeled answer is recorded against exactly the path the real transport would +/// touch -- including an operation whose path is a literal in its transport and not an input +/// (linux.Procfs ReadUptime). A missing or empty path refuses. +fn file_transport_path( transport: &Rc, param_env: &Rc, ctx: &InterpContext, -) -> InterpResult { - let si = ctx.si(); - +) -> InterpResult { let path = match find_property( transport.properties.clone(), "base_path".to_string(), - si.clone(), + ctx.si(), ) { Some(path_node) => { let path_val = eval_expr(&path_node, param_env, ctx)?; @@ -17342,6 +17346,18 @@ fn dispatch_file( msg: "file transport resolved to an empty path".to_string(), }); } + Ok(path) +} + +fn dispatch_file( + op_node: &Rc, + transport: &Rc, + param_env: &Rc, + ctx: &InterpContext, +) -> InterpResult { + let si = ctx.si(); + + let path = file_transport_path(transport, param_env, ctx)?; // Optional explicit verb on the transport row (`transport file { path: ..., verb: "delete" }`). // Delete/List are structurally indistinguishable from Read (path-only inputs), so the diff --git a/src/v2/workflow/floor_eval_step_cost_drop.dag b/src/v2/workflow/floor_eval_step_cost_drop.dag index 2c782630853..98019739583 100644 --- a/src/v2/workflow/floor_eval_step_cost_drop.dag +++ b/src/v2/workflow/floor_eval_step_cost_drop.dag @@ -237,55 +237,51 @@ data floor_eval_step_cost_drop_python_to_typescript_inhabitance_rows: List = [ EvalStepCostDropMeasurement { identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_host_that_never_prints_a_census_refuses_at_the_deadline" as NonEmptyStr, - measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_host_that_never_prints_a_census_refuses_at_the_deadline, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=167236 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, + measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_host_that_never_prints_a_census_refuses_at_the_deadline, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=170048 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, }, EvalStepCostDropMeasurement { identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_healthy_census_completes_and_releases_its_collector" as NonEmptyStr, - measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_healthy_census_completes_and_releases_its_collector, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=158055 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, + measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_healthy_census_completes_and_releases_its_collector, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=160637 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, }, EvalStepCostDropMeasurement { identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.an_already_presented_image_is_not_attached_again" as NonEmptyStr, - measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions an_already_presented_image_is_not_attached_again, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=134675 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, + measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions an_already_presented_image_is_not_attached_again, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=137485 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, }, EvalStepCostDropMeasurement { identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.media_that_never_becomes_ready_refuses_before_any_power_action" as NonEmptyStr, - measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions media_that_never_becomes_ready_refuses_before_any_power_action, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=91254 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, + measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions media_that_never_becomes_ready_refuses_before_any_power_action, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=92947 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, }, EvalStepCostDropMeasurement { identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_presentation_lost_after_readiness_stops_the_handoff" as NonEmptyStr, - measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_presentation_lost_after_readiness_stops_the_handoff, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=97089 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, + measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_presentation_lost_after_readiness_stops_the_handoff, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=98794 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, }, EvalStepCostDropMeasurement { identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause" as NonEmptyStr, - measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=129921 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, + measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=132731 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, }, EvalStepCostDropMeasurement { identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_sol_loss_mid_boot_is_reported_before_the_deadline" as NonEmptyStr, - measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_sol_loss_mid_boot_is_reported_before_the_deadline, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=124297 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, + measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_sol_loss_mid_boot_is_reported_before_the_deadline, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=129808 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, }, EvalStepCostDropMeasurement { identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_two_socket_answer_is_a_truthful_topology_refusal" as NonEmptyStr, - measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_two_socket_answer_is_a_truthful_topology_refusal, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=124495 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, + measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_two_socket_answer_is_a_truthful_topology_refusal, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=126897 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, }, EvalStepCostDropMeasurement { identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_listing_that_names_the_image_twice_starts_nothing" as NonEmptyStr, - measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_listing_that_names_the_image_twice_starts_nothing, over this branch merged with main after #12434 (tree at 66f58d5cb6b plus the #12434 route binding), 2026-09-29: PASS, eval_steps=77801 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, - }, - EvalStepCostDropMeasurement { - identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_an_interrupted_attempt_locks_out_the_next_one" as NonEmptyStr, - measured_by: "PR required floor run 36648847499 of gunbc#12533 at bd99cbb4899, the COMPLETED-OVER-COST-REQUIREMENT line for this identity: verdict pass, eval_steps=74419 against the 72,300 new-witness budget, crossed once #12434's route was bound; the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, + measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_listing_that_names_the_image_twice_starts_nothing, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=79511 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, }, EvalStepCostDropMeasurement { identity: "test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.the_right_filename_on_the_wrong_share_refuses_before_any_power_action" as NonEmptyStr, - measured_by: "PR required floor run 36648847499 of gunbc#12533 at bd99cbb4899, the COMPLETED-OVER-COST-REQUIREMENT line for this identity: verdict pass, eval_steps=77423 against the 72,300 new-witness budget, crossed once #12434's route was bound; the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, + measured_by: "claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions the_right_filename_on_the_wrong_share_refuses_before_any_power_action, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=73872 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization" as NonEmptyStr, }, ] From c308d7f3dd549246783cb75e64cba731e54e6cc3 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 30 Sep 2026 02:46:42 +0000 Subject: [PATCH 37/38] Retire dag/std/measure.dag#Time from the unimported-bare-provider roster as NotAReference The floor refused RosterStale: the file no longer carries the pair. measure.dag has imported Time from extdeps.units.iso_80000_3 since 2026-09-05, before the roster was seeded on 2026-09-25, so the file did not change; the seeding reader derived an imported name as unimported, and the parsed reader (#12609) does not. That is NotAReference -- the READER changed -- not ImportsFixed, which would claim a file change that did not happen. This PR surfaced it by touching measure.dag. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/v2/workflow/floor_unimported_bare_provider_debt_roster.dag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v2/workflow/floor_unimported_bare_provider_debt_roster.dag b/src/v2/workflow/floor_unimported_bare_provider_debt_roster.dag index d4ade40bea3..05dd7118331 100644 --- a/src/v2/workflow/floor_unimported_bare_provider_debt_roster.dag +++ b/src/v2/workflow/floor_unimported_bare_provider_debt_roster.dag @@ -906,7 +906,7 @@ data unimported_bare_provider_dispositions: List Date: Wed, 30 Sep 2026 04:04:30 +0000 Subject: [PATCH 38/38] Eval-step drop: the interrupted-attempt case is back, at CI's 75,263 The floor's run 36661418914 measured it over 72,300 where a local claim_batch read 70,000; the floor's figure decides membership. Eleven rows; docs/design-rung-drops.md regenerated. Co-Authored-By: Claude Opus 5.5 (1M context) --- ...collins1_boot_matrix_new_witness_eval_step_cost.dag | 6 +++--- docs/design-rung-drops.md | 4 ++-- src/v2/workflow/floor_eval_step_cost_drop.dag | 10 +++++++--- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag b/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag index e7c4c2f595d..9154e0dcae7 100644 --- a/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag +++ b/dag/gunbc/rung_drop/mtcollins1_boot_matrix_new_witness_eval_step_cost.dag @@ -9,7 +9,7 @@ import v2.workflow.floor_eval_step_cost_drop { floor_eval_step_cost_drop_boot_ma // matrix, msg_83af891b). The DECLARATION lives here; the POPULATION's authority is // `v2.workflow.floor_eval_step_cost_drop` `floor_eval_step_cost_drop_boot_matrix_rows`. // -// WHAT IS LOST IS ONE WALL, AND THE CLAIMS ARE UNTOUCHED. The ten identities are planned, executed +// WHAT IS LOST IS ONE WALL, AND THE CLAIMS ARE UNTOUCHED. The eleven identities are planned, executed // and measured on every pull request that edits them; a semantic red and a wall-clock crossing still // block. Only the eval-step overrun is reported as `EvalStepsOverBudgetUnderDeclaredDrop` rather than // refusing. The budget is not raised. Their CPU stays under the enrolment margin, so they enrol @@ -39,7 +39,7 @@ data mtcollins1_boot_matrix_native_witness_capability: String = "witnesses emitt data mtcollins1_boot_matrix_new_witness_eval_step_cost: RungDrop = RungDrop { identity: "mtcollins1_boot_matrix_new_witness_eval_step_cost" as NonEmptyStr, - subject: "new-witness eval-step cost gate over the ten mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered", + subject: "new-witness eval-step cost gate over the eleven mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered", declared: "2026-09-28", @@ -52,6 +52,6 @@ data mtcollins1_boot_matrix_new_witness_eval_step_cost: RungDrop = RungDrop { replacement: "the evaluation frame and its modeled operation realization realized by the natively emitted runtime, so the boot entry runs as emitted code rather than in the seed interpreter", }, population: mtcollins1_boot_matrix_new_witness_eval_step_cost_population, - restoration_trigger: concat(concat("THE CAPABILITY: ", mtcollins1_boot_matrix_native_witness_capability), "; WHAT THAT MUST BE SUFFICIENT FOR: each of these ten identities measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither."), + restoration_trigger: concat(concat("THE CAPABILITY: ", mtcollins1_boot_matrix_native_witness_capability), "; WHAT THAT MUST BE SUFFICIENT FOR: each of these eleven identities measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither."), } } diff --git a/docs/design-rung-drops.md b/docs/design-rung-drops.md index 397b8141473..b284c78d225 100644 --- a/docs/design-rung-drops.md +++ b/docs/design-rung-drops.md @@ -342,9 +342,9 @@ new-witness eval-step cost gate over the one roadmap page claim that derives the new-witness eval-step cost gate over the ten interpreted P-256 and P-384 point and group-law checks and SHA-384 FIPS vectors of the App Attest path: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the P-384 and SHA-384 primitives executing as natively emitted code rather than as interpreted bignat and Word64 folds). Population: test.claim.p384_dag_ecdsa_witness_test.the_p384_base_point_is_on_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.p384_dag_ecdsa_witness_test.a_point_beside_the_base_point_is_off_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.p384_dag_ecdsa_witness_test.a_point_with_a_coordinate_at_p_is_not_admitted: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.p384_dag_ecdsa_witness_test.apples_ca_and_root_keys_are_on_p384: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by merge-queue required floor run 35678328407 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, wall under 2000ms, over the new-witness eval-step budget: the billed work is interpreted 384-bit field arithmetic (16 limbs) or a SHA-384 block over Word64 pairs, test.claim.sha384_fips_witness_test.sha384_of_the_empty_message_is_the_published_value_and_one_octet_discriminates: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35703555837 (gunbc#11981, head 0b0a2dffe), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, over the new-witness eval-step budget and above the per-subject line. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field. The billed work is two single-block SHA-384 compressions, the published empty message and a one-octet message on an interpreter that reifies no machine width; the published positive and its discriminating mutation are one subject, so the cost is caused by the claim's own boundary and not by neighbouring vectors, test.claim.sha384_fips_witness_test.sha384_of_abc_is_the_published_value_and_one_octet_discriminates: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35703555837 (gunbc#11981, head 0b0a2dffe), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, over the new-witness eval-step budget and above the per-subject line. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field. The billed work is two single-block SHA-384 compressions, the published abc vector and abd on an interpreter that reifies no machine width; the published positive and its discriminating mutation are one subject, so the cost is caused by the claim's own boundary and not by neighbouring vectors, test.claim.sha384_fips_witness_test.sha384_of_the_896_bit_message_is_the_published_two_block_value_and_one_octet_discriminates: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35703555837 (gunbc#11981, head 0b0a2dffe), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, over the new-witness eval-step budget and above the per-subject line. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field. The billed work is four SHA-384 block compressions, the published 896-bit two-block vector and a last-block mutation on an interpreter that reifies no machine width; the published positive and its discriminating mutation are one subject, so the cost is caused by the claim's own boundary and not by neighbouring vectors, test.claim.p256_dag_ecdsa_witness_test.the_base_point_is_on_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by required floor run 35708011844 (gunbc#11981), artifact required-ci-measurement-receipt; planned_as_changed_witness, verdict pass, well inside the wall deadline and over the new-witness eval-step budget. The billed work is one P-256 curve equation over the published base point as 11-limb bignat folds on an interpreter that reifies no machine width. It is a MEMBER BECAUSE IT WAS SPLIT OUT of a claim that also asserted n*G = infinity: that conjunction crossed the 8000ms wall deadline and was INTERRUPTED BEFORE ANY VERDICT, so both facts were being lost; the scalar multiplication left the floor for the native row the frontier names, and this cheap half now reaches a verdict. The standing figure is whatever this identity's row reads in the artifact named above, never a number copied into this field, test.claim.p384_dag_ecdsa_witness_test.doubling_and_adding_the_base_point_stay_on_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by claim_batch --hermetic [witness] receipt on session/nimble-eagle-216-step3 (added for review 69917 after run 35678328407): verdict pass, wall under the 8 s deadline, over the new-witness eval-step budget; the billed work is one Jacobian doubling, one addition and two projective curve-equation checks at 16 limbs, test.claim.p384_dag_ecdsa_witness_test.doubling_an_off_curve_point_stays_off_the_curve: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_interpreted_crypto_rows; measured by claim_batch --hermetic [witness] receipt on session/nimble-eagle-216-step3 (added for review 69917 after run 35678328407): verdict pass, wall under the 8 s deadline, over the new-witness eval-step budget; the billed work is one Jacobian doubling and one projective curve-equation check at 16 limbs. Restored when: THE CAPABILITY: gunbc test //gunbc/instruments:native-crypto-vectors EXECUTING ON THE MERGE PATH, i.e. as a phase of a required lane whose red blocks a merge, running these ten identities natively (its cases p256_the_base_point_is_on_the_curve, p384_the_base_point_is_on_the_curve, p384_red_a_point_beside_the_base_point_is_off_the_curve, p384_red_a_point_with_a_coordinate_at_p_is_not_admitted, p384_doubling_and_adding_the_base_point_stay_on_the_curve, p384_red_doubling_an_off_curve_point_stays_off_the_curve, p384_apples_ca_and_root_keys_are_on_the_curve and the sha384_* FIPS cases with their reds) while each still evaluates the real curve equation or the real hash; WHAT THAT MUST BE SUFFICIENT FOR: the P-384 parameters and SHA-384 are exercised on the acceptance path with no interpreted 16-limb fold inside a claim frame, so the interpreted identities can leave the floor for that row, or they measure under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives. The same row run BY NAME (as it exists since gunbc#12306) executes the facts natively but is NOT the acceptance path and retires nothing. Supplying the curve check's answer or the digest as a fixture, or deleting the identities, satisfies neither. -### new-witness eval-step cost gate over the ten mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered — declared 2026-09-28 +### new-witness eval-step cost gate over the eleven mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered — declared 2026-09-28 -new-witness eval-step cost gate over the ten mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the evaluation frame and its modeled operation realization realized by the natively emitted runtime, so the boot entry runs as emitted code rather than in the seed interpreter). Population: test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_host_that_never_prints_a_census_refuses_at_the_deadline: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_host_that_never_prints_a_census_refuses_at_the_deadline, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=170048 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_healthy_census_completes_and_releases_its_collector: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_healthy_census_completes_and_releases_its_collector, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=160637 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.an_already_presented_image_is_not_attached_again: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions an_already_presented_image_is_not_attached_again, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=137485 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.media_that_never_becomes_ready_refuses_before_any_power_action: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions media_that_never_becomes_ready_refuses_before_any_power_action, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=92947 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_presentation_lost_after_readiness_stops_the_handoff: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_presentation_lost_after_readiness_stops_the_handoff, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=98794 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=132731 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_sol_loss_mid_boot_is_reported_before_the_deadline: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_sol_loss_mid_boot_is_reported_before_the_deadline, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=129808 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_two_socket_answer_is_a_truthful_topology_refusal: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_two_socket_answer_is_a_truthful_topology_refusal, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=126897 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_listing_that_names_the_image_twice_starts_nothing: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_listing_that_names_the_image_twice_starts_nothing, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=79511 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.the_right_filename_on_the_wrong_share_refuses_before_any_power_action: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions the_right_filename_on_the_wrong_share_refuses_before_any_power_action, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=73872 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization. Restored when: THE CAPABILITY: witnesses emitted to native code with the emitted runtime realizing v2.std.witness_evaluation evaluate_in_witness_frame and its v2.std.operation_realization modeled realization, EXECUTING ON THE MERGE PATH as a phase of a required lane whose red blocks a merge, running the mtcollins1 boot acceptance matrix with the real mtcollins1_boot_wet_on_srv1 entry; WHAT THAT MUST BE SUFFICIENT FOR: each of these ten identities measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither. +new-witness eval-step cost gate over the eleven mtcollins1 boot acceptance matrix cases that run the real boot entry end to end over the dry operation realization: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered: RUNG DROP, mechanically preventable -> mitigatable (replacement staged: the evaluation frame and its modeled operation realization realized by the natively emitted runtime, so the boot entry runs as emitted code rather than in the seed interpreter). Population: test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_host_that_never_prints_a_census_refuses_at_the_deadline: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_host_that_never_prints_a_census_refuses_at_the_deadline, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=170048 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_healthy_census_completes_and_releases_its_collector: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_healthy_census_completes_and_releases_its_collector, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=160637 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.an_already_presented_image_is_not_attached_again: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions an_already_presented_image_is_not_attached_again, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=137485 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.media_that_never_becomes_ready_refuses_before_any_power_action: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions media_that_never_becomes_ready_refuses_before_any_power_action, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=92947 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_presentation_lost_after_readiness_stops_the_handoff: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_presentation_lost_after_readiness_stops_the_handoff, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=98794 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions pinned_a_presentation_lost_after_the_handoff_is_not_the_reported_cause, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=132731 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_sol_loss_mid_boot_is_reported_before_the_deadline: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_sol_loss_mid_boot_is_reported_before_the_deadline, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=129808 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_two_socket_answer_is_a_truthful_topology_refusal: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_two_socket_answer_is_a_truthful_topology_refusal, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=126897 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.a_listing_that_names_the_image_twice_starts_nothing: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions a_listing_that_names_the_image_twice_starts_nothing, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=79511 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.the_right_filename_on_the_wrong_share_refuses_before_any_power_action: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by claim_batch --entry dag/test/claim/machine_intake/mtcollins1_boot_acceptance_matrix_test.dag --functions the_right_filename_on_the_wrong_share_refuses_before_any_power_action, over gunbc#12533 merged with main after gunbc#12636 (tree d6c5146bce6 plus the file-transport path resolution), 2026-09-30: PASS, eval_steps=73872 against the 72,300 new-witness budget (eval_steps are host-independent); the billed work is the real boot entry executed end to end in the seed interpreter over the dry realization, test.claim.machine_intake.mtcollins1_boot_acceptance_matrix_test.pinned_an_interrupted_attempt_locks_out_the_next_one: over the new-witness eval-step budget, rostered in v2.workflow.floor_eval_step_cost_drop floor_eval_step_cost_drop_boot_matrix_rows; measured by PR required floor run 36661418914 of gunbc#12533 at c308d7f3dd5, the COMPLETED-OVER-COST-REQUIREMENT line for this identity: verdict pass, eval_steps=75263 against the 72,300 new-witness budget (a local claim_batch over the same tree read 70,000; the floor's own figure decides membership); the billed work is the real boot entry executed twice in the seed interpreter over the dry realization. Restored when: THE CAPABILITY: witnesses emitted to native code with the emitted runtime realizing v2.std.witness_evaluation evaluate_in_witness_frame and its v2.std.operation_realization modeled realization, EXECUTING ON THE MERGE PATH as a phase of a required lane whose red blocks a merge, running the mtcollins1 boot acceptance matrix with the real mtcollins1_boot_wet_on_srv1 entry; WHAT THAT MUST BE SUFFICIENT FOR: each of these eleven identities measures under the NewWitnessTier budget that v2.workflow.required_floor claim_ceiling_eval_step_budget derives, with its route and outcome assertions unchanged. Hoisting the shared world construction or the route prefix lowers the bill and retires nothing on its own; supplying a decided value in place of the entry, or deleting the identities, satisfies neither. ### new-witness eval-step cost gate over six App Attest verifier claims -- the two real-path inhabitance claims over Apple's objects, three single structural steps priced by their own SHA-256s, and the wire-carrier join: they still execute, eval_steps stay recorded, a semantic red and a wall-clock crossing still block; only the eval-step cost-gate rung is lowered — declared 2026-09-22 diff --git a/src/v2/workflow/floor_eval_step_cost_drop.dag b/src/v2/workflow/floor_eval_step_cost_drop.dag index 98019739583..2db798bc442 100644 --- a/src/v2/workflow/floor_eval_step_cost_drop.dag +++ b/src/v2/workflow/floor_eval_step_cost_drop.dag @@ -237,10 +237,10 @@ data floor_eval_step_cost_drop_python_to_typescript_inhabitance_rows: List = [ EvalStepCostDropMeasurement { @@ -283,6 +283,10 @@ data floor_eval_step_cost_drop_boot_matrix_rows: List