diff --git a/dag/extdeps/linux/boot_console.dag b/dag/extdeps/linux/boot_console.dag new file mode 100644 index 00000000000..c80a56b831a --- /dev/null +++ b/dag/extdeps/linux/boot_console.dag @@ -0,0 +1,41 @@ +module extdeps.linux.boot_console + +import std.types { NonEmptyStr } +import extdeps.external_authority { ExternalAuthority, ExternalModelScope, ExternalSubjectRef } +import std.decl_ref { DeclarationRef, WholeDeclaration } +import extdeps.uri { Https, Uri } + +// THE KERNEL'S OWN CONSOLE LINES THE BOOT MODEL READS. Its panic line, Linux v6.8 kernel/panic.c: panic() prints +// pr_emerg("Kernel panic - not syncing: %s\n", buf) once, and its end marker +// "---[ end Kernel panic - not syncing: %s ]---". The reason after the prefix is the caller's text. +data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { + uri: Uri { + scheme: Https + locator: "github.com/torvalds/linux/blob/v6.8/kernel/panic.c" + } +} + +data extdeps_model_scope: ExternalModelScope = ExternalModelScope { + subject: ExternalSubjectRef { + declaration: DeclarationRef { + module_path: "extdeps.linux.boot_console", + decl_name: "linux_kernel_panic_prefix", + field: WholeDeclaration + } + }, + first_citation: extdeps_external_authority_anchor, + further_citations: [ + ExternalAuthority { + uri: Uri { scheme: Https, locator: "github.com/torvalds/linux/blob/v6.8/init/version-timestamp.c" }, + }, + ], +} + +// init/version-timestamp.c linux_banner: "Linux version " UTS_RELEASE ..., the first line the +// kernel proper prints, so its presence establishes that the stub handed off to the kernel. +data linux_banner_prefix: NonEmptyStr = "Linux version " + +data linux_kernel_panic_prefix: NonEmptyStr = "Kernel panic - not syncing: " + +// The end marker repeats the prefix; a reader that took it as a second panic would count one twice. +data linux_kernel_panic_end_marker: NonEmptyStr = "---[ end Kernel panic - not syncing: " diff --git a/dag/extdeps/linux/efi_stub.dag b/dag/extdeps/linux/efi_stub.dag new file mode 100644 index 00000000000..5d81a705551 --- /dev/null +++ b/dag/extdeps/linux/efi_stub.dag @@ -0,0 +1,50 @@ +module extdeps.linux.efi_stub + +import std.types { NonEmptyStr } +import extdeps.external_authority { ExternalAuthority, ExternalModelScope, ExternalSubjectRef } +import std.decl_ref { DeclarationRef, WholeDeclaration } +import extdeps.uri { Https, Uri } + +// THE LINUX EFI STUB'S CONSOLE LINES, Linux v6.8 drivers/firmware/efi/libstub. The stub runs as a +// UEFI application before the kernel proper: efi_printk (printk.c) writes "EFI stub: " before every +// message, efi_err (efistub.h) prefixes "ERROR: ", and efi_load_initrd (efi-stub-helper.c) prints +// efi_err("Failed to load initrd: 0x%lx\n", status) when no initrd could be loaded, status being the +// UEFI status it got. Only the lines the boot model reads are carried. +data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { + uri: Uri { + scheme: Https + locator: "github.com/torvalds/linux/blob/v6.8/drivers/firmware/efi/libstub/efi-stub-helper.c" + } +} + +data extdeps_model_scope: ExternalModelScope = ExternalModelScope { + subject: ExternalSubjectRef { + declaration: DeclarationRef { + module_path: "extdeps.linux.efi_stub", + decl_name: "linux_efi_stub_initrd_load_failed_prefix", + field: WholeDeclaration + } + }, + first_citation: extdeps_external_authority_anchor, + further_citations: [ + ExternalAuthority { + uri: Uri { scheme: Https, locator: "github.com/torvalds/linux/blob/v6.8/drivers/firmware/efi/libstub/printk.c" }, + }, + ExternalAuthority { + uri: Uri { scheme: Https, locator: "github.com/torvalds/linux/blob/v6.8/drivers/firmware/efi/libstub/efistub.h" }, + }, + ExternalAuthority { + uri: Uri { scheme: Https, locator: "github.com/torvalds/linux/blob/v6.8/include/linux/efi.h" }, + }, + ], +} + +// efi_printk: efi_puts("EFI stub: ") before the message. +data linux_efi_stub_line_prefix: NonEmptyStr = "EFI stub: " + +// efi_err("Failed to load initrd: 0x%lx\n", status), so the whole printed prefix of the line. +data linux_efi_stub_initrd_load_failed_prefix: NonEmptyStr = "EFI stub: ERROR: Failed to load initrd: 0x" + +// include/linux/efi.h: #define EFI_LOAD_ERROR ( 1 | (1UL << (BITS_PER_LONG-1))), printed by %lx on a +// 64-bit stub. Any other status is carried as its printed digits, never mapped onto this one. +data linux_efi_load_error_rendering: NonEmptyStr = "8000000000000001" diff --git a/dag/gunbc/extdeps_scope_frontier.dag b/dag/gunbc/extdeps_scope_frontier.dag index 56cf1c72486..d09bf274fce 100644 --- a/dag/gunbc/extdeps_scope_frontier.dag +++ b/dag/gunbc/extdeps_scope_frontier.dag @@ -374,6 +374,8 @@ data scope_carrier_paths: List = [ "dag/extdeps/instrument/fs_box_v3_v4_v5_faq.dag", "dag/extdeps/linux/edac.dag", "dag/extdeps/linux/kernel.dag", + "dag/extdeps/linux/efi_stub.dag", + "dag/extdeps/linux/boot_console.dag", "dag/extdeps/linux/mlx5_core.dag", "dag/extdeps/linux/proc_self_cgroup.dag", "dag/extdeps/os/debian.dag", diff --git a/dag/gunbc/fleet/mtcollins_firmware_converge.dag b/dag/gunbc/fleet/mtcollins_firmware_converge.dag index 2165384fd33..9154c2b3e82 100644 --- a/dag/gunbc/fleet/mtcollins_firmware_converge.dag +++ b/dag/gunbc/fleet/mtcollins_firmware_converge.dag @@ -43,9 +43,11 @@ import gunbc.auth.authorization_pattern_selection { import gunbc.machine_intake_bmc_secure { BmcSecureStanding, BmcSecureRefusalCause, BmcSecured, BmcSecureRefused, ManagedCredentialReference, } -import gunbc.machine_intake_boot_outcome { - HostBootObservation, HostReachedUefi, PostDdrProgressLost, HostBootUnobservable, ResetInitiator, +import gunbc.machine_intake_mtcollins1_power_on_account { + FirmwareSetBootReadback, BootReachedUefiAfterSet, BootProgressLostAfterSet, BootReadbackUnobservable, + HostEnding, RestartedRepeatedly, ReachedCensus, StoppedWithStatedFailure, StoppedWithoutRestart, HostEndingNotEstablished, } +import gunbc.machine_intake_ampere_checkpoint_console_observation { AmpereCheckpointRelation } import gunbc.machine_intake_mtcollins1_boot_diagnostic_bundle { SelSnapshotReading, SelSnapshotRead, SelSnapshotUnread, SelSnapshotNotTaken, } @@ -363,8 +365,9 @@ type MtCollinsFirmwareWorld { } // WHAT THE HOST DID WHEN IT FIRST BOOTED AFTER THE SET is the only observation that says the set worked, -// and it is gunbc.machine_intake_boot_outcome's fact, not this module's. Reaching UEFI grounds the set; -// PostDdrProgressLost refuses. +// and it is the power-on account's fact (gunbc.machine_intake_mtcollins1_power_on_account +// firmware_set_boot_readback), not this module's. A UEFI milestone grounds the set unless the session +// then restarted repeatedly; lost progress refuses (boot_verdict). // The witnessed AC power cycle. It is an operator-attested record: the substrate cannot see a power // cord, which is why selecting HumanOnlyStep was the honest answer. ac_off is what the @@ -443,7 +446,8 @@ type FirmwareConvergenceRefusal | AcPowerCycleTooShort { target: FirmwareTarget, observed: Second, minimum: Second } | ReadbackMismatch { target: FirmwareTarget, reason: String } | AcCycleNotAHumanStep { target: FirmwareTarget } - | BootProgressLostAfterFirmwareSet { last_checkpoint: Int, soc_silent: Second, initiator: ResetInitiator } + | BootProgressLostAfterFirmwareSet { ending: HostEnding, last_relation: AmpereCheckpointRelation? } + | BootReachedUefiThenRestartedRepeatedly { cycle: Int, ending: HostEnding } | CpuSiliconSupportUnstated { socket: Nat, bios_family: NonEmptyStr } | NoRollbackImageRetained { target: FirmwareTarget, reason: NonEmptyStr } | RollbackImageNotTheInstalledVersion { target: FirmwareTarget } @@ -1076,7 +1080,11 @@ fn uncited_readbacks(records: List) -> List { }) } -fn boot_verdict(records: List, boot: HostBootObservation?) -> FirmwareConvergenceVerdict { +// THE POST-SET READBACK IS A PROJECTION OF THE WHOLE POWER-ON ACCOUNT (gunbc.machine_intake_mtcollins1_power_on_account +// firmware_set_boot_readback; ruling R1 on #13041 and #13071), never an authored observation. A UEFI +// milestone in any cycle grounds the set unless the session then restarted repeatedly: reaching UEFI +// and looping afterwards is not a converged boot, so both facts are read together. +fn boot_verdict(records: List, boot: FirmwareSetBootReadback?) -> FirmwareConvergenceVerdict { match records.first() { Absent => FirmwareBaselineConverged { uncited_readbacks: [] } Present { value: _ } => @@ -1084,9 +1092,15 @@ fn boot_verdict(records: List, boot: HostBootObservation?) -> F Absent => FirmwareBootReadbackDue Present { value: o } => match o { - HostReachedUefi => FirmwareBaselineConverged { uncited_readbacks: uncited_readbacks(records: records) } - PostDdrProgressLost { last_checkpoint: c, soc_silent: q, chassis_power_on_while_silent: _, next_cycle_logged_s5_and_system_restart: _, watchdog: _, initiator: i } => refused(cause: BootProgressLostAfterFirmwareSet { last_checkpoint: c, soc_silent: q, initiator: i }) - HostBootUnobservable { reason: r } => refused(cause: BootOutcomeUnobservable { reason: r }) + BootReachedUefiAfterSet { cycle: c, milestone: _, ending: e, degradations: _, account: _ } => match e { + RestartedRepeatedly { cycles: _, last_cycle_ended_after: _, cadence: _, console_errors: _, sel_restarts: _, watchdog: _, reset_cause: _ } => refused(cause: BootReachedUefiThenRestartedRepeatedly { cycle: c, ending: e }) + ReachedCensus => FirmwareBaselineConverged { uncited_readbacks: uncited_readbacks(records: records) } + StoppedWithStatedFailure { subject: _, error: _ } => FirmwareBaselineConverged { uncited_readbacks: uncited_readbacks(records: records) } + StoppedWithoutRestart { last_milestone: _, ended_after: _, console_errors: _, restarts_before: _ } => FirmwareBaselineConverged { uncited_readbacks: uncited_readbacks(records: records) } + HostEndingNotEstablished => FirmwareBaselineConverged { uncited_readbacks: uncited_readbacks(records: records) } + } + BootProgressLostAfterSet { ending: e, last_relation: r, account: _ } => refused(cause: BootProgressLostAfterFirmwareSet { ending: e, last_relation: r }) + BootReadbackUnobservable { account: _ } => refused(cause: BootOutcomeUnobservable { reason: "the power-on account establishes neither a UEFI milestone nor a lost-progress ending" }) } } } @@ -1096,7 +1110,7 @@ fn converge_firmware( baseline: FirmwareBaseline, world: MtCollinsFirmwareWorld, records: List, - boot: HostBootObservation?, + boot: FirmwareSetBootReadback?, ) -> FirmwareConvergenceVerdict { match baseline_admission_refusal(b: baseline) { Present { value: cause } => refused(cause: BaselineNotAdmitted { cause: cause }) diff --git a/dag/gunbc/machine_intake/ampere_checkpoint_console_observation.dag b/dag/gunbc/machine_intake/ampere_checkpoint_console_observation.dag new file mode 100644 index 00000000000..8d3936238f1 --- /dev/null +++ b/dag/gunbc/machine_intake/ampere_checkpoint_console_observation.dag @@ -0,0 +1,262 @@ +module gunbc.machine_intake_ampere_checkpoint_console_observation + +import v2.std.algebra { any, filter } +import std.types { Bool, Int, List, NonEmptyStr, String } +import std.algebra { trim } +import v2.std.optional { Present } +import extdeps.tools.sha256sum { Sha256FileDigest } +import gunbc.machine_intake_pre_os_bringup_verdict { firmware_checkpoint_line_prefix } +import gunbc.machine_intake_ampere_dram_console_observation { ampere_dram_fw_version_marker, ampere_dram_console_line } + +// THE "CP: xxxxxxxx" CHECKPOINT TOKENS THE AMPERE FIRMWARE PRINTED ON THE SERIAL CONSOLE, as an +// observation receipt of this repository (docs/plans/power-on-sequence-model.md, ruling R2). +// +// A TOKEN IS OPAQUE, AND THAT IS THE RULING, NOT A SHORTCUT. No public source states the layout of a +// checkpoint word, so this module never decodes one and never attributes one to a firmware stage: +// a token is its eight printed hex digits, and the only facts it yields are ORDER (which token a +// span printed last) and EQUALITY against another capture of our own. Stage and sub-step attribution +// is the gunbc-private refinement slice's, which consumes this observation; nothing here imports it. +// +// SPANS, NOT CYCLES. Each "DRAM FW version" banner opens a console span; tokens printed before the +// first banner form a span of their own that no banner opened. A span is console evidence that the +// DRAM firmware printed within some boot cycle -- it does not make a cycle exist, and binding spans +// to cycles is the power-on account's (gunbc.machine_intake_mtcollins1_power_on_account), which also +// admits cycles that never reached a banner. +// +// A "CP:" LINE WHOSE WORD IS NOT EIGHT HEX DIGITS IS KEPT AS MALFORMED, never dropped (DESIGN +// section 5): a firmware that changes the word's width shows up in the receipt, not as a shorter +// sequence. + +// Eight hex digits, as every capture this corpus holds prints them; checkpoint_token_of indexes them. +data ampere_checkpoint_token_digits: Int = 8 + +data ampere_checkpoint_hex_digits: String = "0123456789abcdefABCDEF" + +type AmpereCheckpointToken { + digits: NonEmptyStr +} + +type AmpereCheckpointSpan { + index: Int + opened_by_banner: Bool + tokens: List + malformed: List +} + +type AmpereCheckpointConsoleObservation { + spans: List +} + +fn is_hex_digit(c: String) -> Bool { + c.length() == 1 && string_contains(s: ampere_checkpoint_hex_digits, pattern: c) +} + +fn checkpoint_token_of(text: String) -> AmpereCheckpointToken? { + let t = trim(text) + if t.length() != ampere_checkpoint_token_digits { none } + else if any(map([0, 1, 2, 3, 4, 5, 6, 7], i => char_at(s: t, pos: i)), c => !is_hex_digit(c: c)) { none } + else { Present { value: AmpereCheckpointToken { digits: t as NonEmptyStr } } } +} + +type AmpereCheckpointLine + = CheckpointSpanOpens + | CheckpointTokenLine { token: AmpereCheckpointToken } + | CheckpointMalformedLine { line: String } + | CheckpointUnrelatedLine + +fn ampere_checkpoint_line_of(raw: String) -> AmpereCheckpointLine { + let line = trim(ampere_dram_console_line(raw: raw)) + if string_contains(s: line, pattern: ampere_dram_fw_version_marker) { CheckpointSpanOpens } + else if !starts_with(s: line, prefix: firmware_checkpoint_line_prefix as String) { CheckpointUnrelatedLine } + else { + match checkpoint_token_of(text: substring(s: line, start: (firmware_checkpoint_line_prefix as String).length(), end: line.length())) { + Present { value: t } => CheckpointTokenLine { token: t } + Absent => CheckpointMalformedLine { line: line } + } + } +} + +type AmpereCheckpointFold { + closed: List + current: AmpereCheckpointSpan +} + +fn empty_span(index: Int, opened_by_banner: Bool) -> AmpereCheckpointSpan { + let ts: List = [] + let ms: List = [] + AmpereCheckpointSpan { index: index, opened_by_banner: opened_by_banner, tokens: ts, malformed: ms } +} + +fn span_printed_nothing(s: AmpereCheckpointSpan) -> Bool { + count(s.tokens) == 0 && count(s.malformed) == 0 +} + +// A banner closes the current span and opens the next. The pre-banner span is kept only when it +// printed something, so a capture that starts at a banner has no empty leading span; a span a banner +// opened is kept even when it printed no token yet, because the banner itself is console evidence. +fn checkpoint_fold_step(acc: AmpereCheckpointFold, raw: String) -> AmpereCheckpointFold { + match ampere_checkpoint_line_of(raw: raw) { + CheckpointSpanOpens => { + let keep = acc.current.opened_by_banner || !span_printed_nothing(s: acc.current) + let closed = if keep { concat(acc.closed, [acc.current]) } else { acc.closed } + AmpereCheckpointFold { closed: closed, current: empty_span(index: count(closed), opened_by_banner: true) } + } + CheckpointTokenLine { token: t } => + AmpereCheckpointFold { closed: acc.closed, current: AmpereCheckpointSpan { index: acc.current.index, opened_by_banner: acc.current.opened_by_banner, tokens: concat(acc.current.tokens, [t]), malformed: acc.current.malformed } } + CheckpointMalformedLine { line: l } => + AmpereCheckpointFold { closed: acc.closed, current: AmpereCheckpointSpan { index: acc.current.index, opened_by_banner: acc.current.opened_by_banner, tokens: acc.current.tokens, malformed: concat(acc.current.malformed, [l]) } } + CheckpointUnrelatedLine => acc + } +} + +fn ampere_checkpoint_console_observation(lines: List) -> AmpereCheckpointConsoleObservation { + let none_closed: List = [] + let done = fold(lines, init: AmpereCheckpointFold { closed: none_closed, current: empty_span(index: 0, opened_by_banner: false) }, f: (acc, l) => checkpoint_fold_step(acc: acc, raw: l)) + let keep = done.current.opened_by_banner || !span_printed_nothing(s: done.current) + AmpereCheckpointConsoleObservation { spans: if keep { concat(done.closed, [done.current]) } else { done.closed } } +} + +// THE CONSOLE'S LINES, GROUPED BY THE SPAN THEY FALL IN, by this module's own fold: an owning reader +// run over one span's lines reports what THAT span printed, so a statement printed in two spans is two +// statements, each in its own span (the power-on account binds spans to cycles). +type AmpereConsoleSpanLines { + span: Int + lines: List +} + +type SpanLinesFold { + spans: AmpereCheckpointFold + groups: List +} + +fn span_lines_step(acc: SpanLinesFold, raw: String) -> SpanLinesFold { + let spans = checkpoint_fold_step(acc: acc.spans, raw: raw) + let k = spans.current.index + let none_lines: List = [] + let groups = match acc.groups.last() { + Present { value: g } => if g.span == k { concat(acc.groups.take(n: count(acc.groups) - 1), [AmpereConsoleSpanLines { span: k, lines: concat(g.lines, [raw]) }]) } else { concat(acc.groups, [AmpereConsoleSpanLines { span: k, lines: concat(none_lines, [raw]) }]) } + Absent => [AmpereConsoleSpanLines { span: k, lines: concat(none_lines, [raw]) }] + } + SpanLinesFold { spans: spans, groups: groups } +} + +fn ampere_console_span_lines(lines: List) -> List { + let none_closed: List = [] + let none_groups: List = [] + fold(lines, init: SpanLinesFold { spans: AmpereCheckpointFold { closed: none_closed, current: empty_span(index: 0, opened_by_banner: false) }, groups: none_groups }, f: (acc, l) => span_lines_step(acc: acc, raw: l)).groups +} + +fn ampere_checkpoint_last_token(s: AmpereCheckpointSpan) -> AmpereCheckpointToken? { + s.tokens.last() +} + +fn ampere_checkpoint_banner_spans(o: AmpereCheckpointConsoleObservation) -> Int { + count(filter(o.spans, s => s.opened_by_banner)) +} + +// ── AGAINST A REFERENCE CAPTURE (R2: an opaque relation, nothing more) ─────────────────────────── +// The reference is another capture of our own, named by its digest and by the firmware and +// configuration it was taken under: a continuation observed under a different firmware set or +// population says only that THAT capture continued. The subject span's last token is looked up in +// the reference's spans, first occurrence first; the number of occurrences is carried so the choice +// is visible. The reference scope is the caller's row, never inferred here. +type AmpereCheckpointReference { + path: NonEmptyStr + digest: Sha256FileDigest + firmware_scope: NonEmptyStr + configuration_scope: NonEmptyStr + observation: AmpereCheckpointConsoleObservation +} + +type AmpereCheckpointContinuation + = ReferenceContinues { next: AmpereCheckpointToken, remaining: Int, occurrences: Int } + | ReferenceEndsThere + | NotInReference + | ReferenceUnread { detail: String } + +type AmpereCheckpointRelation { + span: Int + capture: Sha256FileDigest + last: AmpereCheckpointToken + reference_path: NonEmptyStr + reference_digest: Sha256FileDigest + reference_firmware: NonEmptyStr + reference_configuration: NonEmptyStr + continuation: AmpereCheckpointContinuation +} + +type TokenSearch { + at: Int + found: Int? +} + +fn first_index(tokens: List, t: AmpereCheckpointToken) -> Int? { + fold(tokens, init: TokenSearch { at: 0, found: none }, f: (acc, x) => TokenSearch { + at: acc.at + 1, + found: match acc.found { Present { value: i } => Present { value: i } Absent => if (x.digits as String) == (t.digits as String) { Present { value: acc.at } } else { none } }, + }).found +} + +fn continuation_in(tokens: List, last: AmpereCheckpointToken) -> AmpereCheckpointContinuation { + match first_index(tokens: tokens, t: last) { + Absent => NotInReference + Present { value: i } => { + let after = tokens.skip(n: i + 1) + let occurrences = count(filter(tokens, x => (x.digits as String) == (last.digits as String))) + match after.first() { + Absent => ReferenceEndsThere + Present { value: next } => ReferenceContinues { next: next, remaining: count(after), occurrences: occurrences } + } + } + } +} + +// None when the span printed no token: an opaque relation needs a token to relate. +fn ampere_checkpoint_relation(span: AmpereCheckpointSpan, capture: Sha256FileDigest, reference: AmpereCheckpointReference) -> AmpereCheckpointRelation? { + match ampere_checkpoint_last_token(s: span) { + Absent => none + Present { value: last } => Present { value: AmpereCheckpointRelation { + span: span.index, capture: capture, last: last, + reference_path: reference.path, reference_digest: reference.digest, + reference_firmware: reference.firmware_scope, reference_configuration: reference.configuration_scope, + continuation: continuation_in(tokens: flat_map(reference.observation.spans, s => s.tokens), last: last), + } } + } +} + +// The same relation when the reference could not be read: carried as such, never as "not in it". +fn ampere_checkpoint_relation_unread(span: AmpereCheckpointSpan, capture: Sha256FileDigest, path: NonEmptyStr, digest: Sha256FileDigest, firmware_scope: NonEmptyStr, configuration_scope: NonEmptyStr, detail: String) -> AmpereCheckpointRelation? { + match ampere_checkpoint_last_token(s: span) { + Absent => none + Present { value: last } => Present { value: AmpereCheckpointRelation { + span: span.index, capture: capture, last: last, + reference_path: path, reference_digest: digest, reference_firmware: firmware_scope, reference_configuration: configuration_scope, + continuation: ReferenceUnread { detail: detail }, + } } + } +} + +fn digest_text(d: Sha256FileDigest) -> String { + match d { Sha256FileDigest { digest: x } => concat("sha256 ", x.hex as String) Sha256FileDigestUnavailable { path: _, reason: r } => concat("digest unavailable: ", r) } +} + +fn ampere_checkpoint_span_text(s: AmpereCheckpointSpan) -> String { + concat("span ", to_string(s.index), if s.opened_by_banner { " (banner)" } else { " (before any banner)" }, ": ", to_string(count(s.tokens)), " token(s), last ", + match ampere_checkpoint_last_token(s: s) { Absent => "none" Present { value: t } => t.digits as String }, + if count(s.malformed) == 0 { "" } else { concat("; MALFORMED: ", join(s.malformed, " | ")) }) +} + +fn ampere_checkpoint_console_text(o: AmpereCheckpointConsoleObservation) -> String { + if count(o.spans) == 0 { "no CP: token and no DRAM firmware banner printed" } else { join(map(o.spans, s => ampere_checkpoint_span_text(s: s)), "\n") } +} + +fn ampere_checkpoint_relation_text(r: AmpereCheckpointRelation) -> String { + concat("span ", to_string(r.span), " of the capture (", digest_text(d: r.capture), ") ended after token ", r.last.digits as String, "; reference ", r.reference_path as String, + " (", digest_text(d: r.reference_digest), "; firmware: ", r.reference_firmware as String, "; configuration: ", r.reference_configuration as String, ") ", + match r.continuation { + ReferenceContinues { next: n, remaining: k, occurrences: o } => concat("printed it (", to_string(o), " time(s)) followed by ", n.digits as String, " and ", to_string(k - 1), " further token(s)") + ReferenceEndsThere => "also ended at it" + NotInReference => "never printed it" + ReferenceUnread { detail: d } => concat("is UNREAD: ", d) + }) +} diff --git a/dag/gunbc/machine_intake/ampere_dram_console_observation.dag b/dag/gunbc/machine_intake/ampere_dram_console_observation.dag index 85da253e46e..e1a19ab0fba 100644 --- a/dag/gunbc/machine_intake/ampere_dram_console_observation.dag +++ b/dag/gunbc/machine_intake/ampere_dram_console_observation.dag @@ -273,22 +273,21 @@ fn ampere_dram_socket_untrained(block: AmpereDramBlock, socket: Int) -> Bool { ampere_dram_dimms_on_socket(block: block, socket: socket) == 0 } -// THE DECISIVE FINDINGS THIS RECEIPT ESTABLISHES, given the sockets the caller expects: an expected -// socket with no trained DIMM row in a CLOSED roster, and a row inside a recognised subsection -// that does not parse. Params are never findings (see the note at the top). -fn ampere_dram_findings(observation: AmpereDramConsoleObservation, expected_sockets: List) -> List { +// THE EXPECTED SOCKETS THIS RECEIPT ESTABLISHES AS UNTRAINED: no trained DIMM row in a CLOSED +// roster. An open roster (the capture ended mid-roster) and an absent one establish nothing, so they +// name no socket (see AmpereDramRosterStanding). +fn ampere_dram_untrained_sockets(observation: AmpereDramConsoleObservation, expected_sockets: List) -> List { match observation { AmpereDramBlockNotPrinted => [] - AmpereDramBlockObserved { block: b, blocks_printed: _ } => { - let closed = match b.roster { RosterClosed => true RosterNotObserved => false RosterOpen => false } - let empty_sockets = map(filter(expected_sockets, k => closed && ampere_dram_socket_untrained(block: b, socket: k)), - k => concat("DRAM firmware ", b.firmware_version as String, " printed no trained DIMM for socket ", to_string(k), " (no SK", to_string(k), " row under 'DRAM populated DIMMs:')")) - let bad = map(b.malformed, m => concat("DRAM firmware row under '", m.subsection as String, "' not in the transcribed grammar: ", m.line)) - concat(empty_sockets, bad) + AmpereDramBlockObserved { block: b, blocks_printed: _ } => match b.roster { + RosterClosed => filter(expected_sockets, k => ampere_dram_socket_untrained(block: b, socket: k)) + RosterOpen => [] + RosterNotObserved => [] } } } + fn ampere_dram_console_text(observation: AmpereDramConsoleObservation, expected_sockets: List) -> String { match observation { AmpereDramBlockNotPrinted => "ampere dram firmware: NOT PRINTED in this capture (no 'DRAM FW version' line); no roster is observed" diff --git a/dag/gunbc/machine_intake/boot_outcome.dag b/dag/gunbc/machine_intake/boot_outcome.dag deleted file mode 100644 index cc0dad832ff..00000000000 --- a/dag/gunbc/machine_intake/boot_outcome.dag +++ /dev/null @@ -1,52 +0,0 @@ -module gunbc.machine_intake_boot_outcome - -import std.types { Bool, Int, NonEmptyStr } -import std.measure { Second, second } - -// WHAT ONE BOOT SHOWED, at the boot-observation layer beside gunbc.machine_intake_mtcollins1_boot_milestone. -// Its one consumer today is the firmware convergence's post-set readback (gunbc.fleet.mtcollins_firmware_converge -// boot_verdict). The boot run and the socket-1 investigation do NOT import it yet: that join is a declared -// later change, #13071 slice A, which replaces this vocabulary atomically (ruling R1 on #13041) rather than -// leaving a second boot-outcome spelling beside it. -// -// Reaching UEFI is success. The failure seen on mtcollins1 on 2026-10-02/03, after the whole vendor set -// (BMC 0.45.3, BIOS 0ACOD014, SCP 2.10, MB CPLD V06), is PostDdrProgressLost: the last checkpoint the -// host reported was 0x00001a0a, an opaque code -- no public source states its stage, so none is claimed -// here; then the whole SoC went silent for about 22 s with chassis power still -// ON; then the NEXT cycle's start logged CPU0_ACPI_State S5 and System Restart; nothing named the -// initiator. The BMC's IPMI watchdog was stopped with no action set, so it is not the initiator. -// 0x1a0a is NOT a normal warm-reset point: the 2026-09-13 two-socket boot went 0x1a0a -> 0x1a0b in one -// pass. So a loss of progress there is a typed failure, never `still booting`, and the unknown initiator -// stays unknown. Evidence: srv1 ~/mtc-fw/capture-20261003T003536Z (SHA256SUMS inside). -type ResetInitiator - = ResetInitiatorUnobserved - | ResetInitiatorObserved { initiator: NonEmptyStr } - -// What the BMC's IPMI watchdog was doing across the lost cycle: the watchdog is a candidate initiator -// only while it is armed with an action, so the observation carries its state rather than an opinion. -type BmcWatchdogDisposition - = WatchdogStoppedNoAction - | WatchdogArmed { action: NonEmptyStr } - | WatchdogUnobserved { reason: NonEmptyStr } - -type HostBootObservation - = HostReachedUefi - | PostDdrProgressLost { - last_checkpoint: Int, - soc_silent: Second, - chassis_power_on_while_silent: Bool, - next_cycle_logged_s5_and_system_restart: Bool, - watchdog: BmcWatchdogDisposition, - initiator: ResetInitiator, - } - | HostBootUnobservable { reason: NonEmptyStr } - -// The 2026-10-03 observation itself (last_checkpoint 6666 = 0x00001a0a; the substrate has no hex literal), so the convergence witness and any later reader cite one row. -data mtcollins1_post_firmware_set_boot_2026_10_03: HostBootObservation = PostDdrProgressLost { - last_checkpoint: 6666, - soc_silent: second(count: 22), - chassis_power_on_while_silent: true, - next_cycle_logged_s5_and_system_restart: true, - watchdog: WatchdogStoppedNoAction, - initiator: ResetInitiatorUnobserved, -} diff --git a/dag/gunbc/machine_intake/kernel_module_decompression_observation.dag b/dag/gunbc/machine_intake/kernel_module_decompression_observation.dag index 168874b0227..f5982032f73 100644 --- a/dag/gunbc/machine_intake/kernel_module_decompression_observation.dag +++ b/dag/gunbc/machine_intake/kernel_module_decompression_observation.dag @@ -253,9 +253,3 @@ fn kernel_module_decompression_text(o: KernelModuleDecompressionObservation) -> } } -fn kernel_module_decompression_findings(o: KernelModuleDecompressionObservation) -> List { - match o { - NoRefusalRecorded { insert_failures: _, unparsed_insert_records: _ } => [] - RefusalRecorded { refusals: _, unparsed_refusal_records: _, insert_failures: _, unparsed_insert_records: _, identity: _ } => [kernel_module_decompression_text(o: o)] - } -} diff --git a/dag/gunbc/machine_intake/linux_boot_console_observation.dag b/dag/gunbc/machine_intake/linux_boot_console_observation.dag new file mode 100644 index 00000000000..3da37c49724 --- /dev/null +++ b/dag/gunbc/machine_intake/linux_boot_console_observation.dag @@ -0,0 +1,112 @@ +module gunbc.machine_intake_linux_boot_console_observation + +import v2.std.algebra { any, filter } +import std.types { Bool, Int, List, NonEmptyStr, String } +import std.algebra { trim } +import v2.std.optional { Present } +import extdeps.linux.efi_stub { linux_efi_stub_line_prefix, linux_efi_stub_initrd_load_failed_prefix, linux_efi_load_error_rendering } +import extdeps.linux.boot_console { linux_banner_prefix, linux_kernel_panic_prefix, linux_kernel_panic_end_marker } + +// WHAT THE LINUX EFI STUB AND THE KERNEL PRINTED ON THE SERIAL CONSOLE DURING ONE BOOT, as an +// observation receipt of this repository. The lines are upstream's (extdeps.linux.efi_stub, +// extdeps.linux.boot_console); which of them a capture holds is ours. +// +// A LINE IS MATCHED BY THE UPSTREAM TEXT ANYWHERE IN IT, not at its start: the kernel prefixes its +// own lines with a "[ seconds ]" timestamp, and on mtcollins1 the stub's first line follows GRUB's +// screen-drawing escape sequences on the same serial line (run 37069907299). +// +// WHAT IS NOT CLAIMED (DESIGN section 4d). A capture with no stub line says nothing about whether +// the stub ran: the console may have been lost first. So every reading below that is not a stated +// line is "not printed", never "did not happen". + +// The status the stub printed after "0x". EFI_LOAD_ERROR is the one upstream row this corpus has +// observed (run 37069907299); any other status is kept as its digits. +type EfiStubStatus + = EfiLoadError + | EfiStatusOther { digits: NonEmptyStr } + +type EfiStubInitrdReading + = InitrdFailureNotPrinted + | InitrdLoadFailed { status: EfiStubStatus, line: NonEmptyStr } + +type KernelPanicReading + = KernelPanicNotPrinted + | KernelPanicked { reason: NonEmptyStr } + +type LinuxBootConsoleObservation { + stub_lines: Int + initrd: EfiStubInitrdReading + kernel_banner_printed: Bool + panic: KernelPanicReading +} + +fn text_after_marker(line: String, marker: String) -> String? { + let parts = split(s: line, delimiter: marker) + if count(parts) < 2 { none } else { Present { value: join(parts.skip(n: 1), marker) } } +} + +fn efi_stub_status_of(digits: String) -> EfiStubStatus? { + let d = trim(digits) + if d == "" { none } + else if d == (linux_efi_load_error_rendering as String) { Present { value: EfiLoadError } } + else { Present { value: EfiStatusOther { digits: d as NonEmptyStr } } } +} + +fn initrd_reading_of(line: String) -> EfiStubInitrdReading? { + match text_after_marker(line: line, marker: linux_efi_stub_initrd_load_failed_prefix as String) { + Absent => none + Present { value: rest } => match efi_stub_status_of(digits: rest) { + Absent => none + Present { value: st } => Present { value: InitrdLoadFailed { status: st, line: trim(line) as NonEmptyStr } } + } + } +} + +// The panic line, never its end marker: the marker repeats the prefix, and reading it as a second +// panic would report the reason with "]---" appended. +fn panic_reason_of(line: String) -> String? { + if string_contains(s: line, pattern: linux_kernel_panic_end_marker as String) { none } else { + match text_after_marker(line: line, marker: linux_kernel_panic_prefix as String) { + Absent => none + Present { value: r } => if trim(r) == "" { none } else { Present { value: trim(r) } } + } + } +} + +// THE FIRST stated initrd failure and the FIRST panic: each is printed once per boot, and the first +// is the one that led to what followed. +fn linux_boot_console_observation(lines: List) -> LinuxBootConsoleObservation { + let initrd = match flat_map(lines, l => match initrd_reading_of(line: l) { Absent => [] Present { value: r } => [r] }).first() { + Absent => InitrdFailureNotPrinted + Present { value: r } => r + } + let panic = match flat_map(lines, l => match panic_reason_of(line: l) { Absent => [] Present { value: r } => [r] }).first() { + Absent => KernelPanicNotPrinted + Present { value: r } => KernelPanicked { reason: r as NonEmptyStr } + } + LinuxBootConsoleObservation { + stub_lines: count(filter(lines, l => string_contains(s: l, pattern: linux_efi_stub_line_prefix as String))), + initrd: initrd, + kernel_banner_printed: any(lines, l => string_contains(s: l, pattern: linux_banner_prefix as String)), + panic: panic, + } +} + +fn efi_stub_status_text(s: EfiStubStatus) -> String { + match s { + EfiLoadError => concat("EFI_LOAD_ERROR (0x", linux_efi_load_error_rendering as String, ")") + EfiStatusOther { digits: d } => concat("0x", d as String) + } +} + +fn linux_boot_console_text(o: LinuxBootConsoleObservation) -> String { + join([ + concat("efi stub lines: ", to_string(o.stub_lines)), + concat("initrd: ", match o.initrd { + InitrdFailureNotPrinted => "no load failure printed" + InitrdLoadFailed { status: s, line: _ } => concat("load FAILED, status ", efi_stub_status_text(s: s)) + }), + concat("kernel banner: ", if o.kernel_banner_printed { "printed" } else { "not printed" }), + concat("panic: ", match o.panic { KernelPanicNotPrinted => "none printed" KernelPanicked { reason: r } => r as String }), + ], "\n") +} diff --git a/dag/gunbc/machine_intake/mtcollins1_bmc_sensor_observation.dag b/dag/gunbc/machine_intake/mtcollins1_bmc_sensor_observation.dag index 4c8194d400a..2de60f2daa6 100644 --- a/dag/gunbc/machine_intake/mtcollins1_bmc_sensor_observation.dag +++ b/dag/gunbc/machine_intake/mtcollins1_bmc_sensor_observation.dag @@ -1,6 +1,7 @@ module gunbc.machine_intake_mtcollins1_bmc_sensor_observation import std.types { Bool, Int, List, NonEmptyStr, String } +import gunbc.machine_intake_mtcollins1_power_on_account { BmcAnomaly, SensorNotNominal, SensorLineWithoutReadingNotNominal, SocketSensorsAbsent } import std.algebra { trim } import v2.std.optional { Present } import extdeps.bmc.ipmi_sdr { SdrLineResult, SdrLineDecoded, SdrLineUnstructured, SdrTypeLine } @@ -54,23 +55,24 @@ fn mtcollins1_socket_unstructured_lines(lines: List, socket: Int) }) } -// PER SOCKET, TWO KINDS OF FINDING. A socket sensor whose status is not "ok" is quoted as the tool -// printed it ("ns" is no reading), decoded or not; and a socket with NO sensor in the table at all is -// its own finding. Either on socket 1 beside a clean socket 0 is what the socket-1 investigation + +// PER SOCKET, TWO KINDS OF ANOMALY. A socket sensor whose status is not "ok" is carried as the tool +// printed it ("ns" is no reading), decoded or not, for every socket the board's sensor names cover; +// and a socket with NO sensor in the table at all is an anomaly ONLY when the attempt's receipt +// EXPECTS that socket -- with its CPU removed, an empty socket's missing sensors are the expected +// state, and with no recorded expectation absence judges nothing (law 6). Either on socket 1 beside a clean socket 0 is what the socket-1 investigation // asked the BMC for (gunbc.machine_intake_mtcollins1_socket1_investigation_observation -// mtcollins1_socket1_open_hypotheses, the VRM row). No status or reading is interpreted further. -fn mtcollins1_sensor_findings(lines: List, sockets: List) -> List { +// mtcollins1_socket1_open_hypotheses, the VRM row). No status or reading is interpreted further; the +// power-on fold (gunbc.machine_intake_mtcollins1_power_on_account) places them on the chassis power-on stage. +fn mtcollins1_sensor_anomalies(lines: List, sockets: List, expected: List?) -> List { flat_map(sockets, k => { let decoded = mtcollins1_socket_sensor_lines(lines: lines, socket: k) let discrete = mtcollins1_socket_unstructured_lines(lines: lines, socket: k) if count(decoded) == 0 && count(discrete) == 0 { - [concat("bmc sensors: no ", mtcollins1_sensor_socket_prefix, to_string(k), "_ sensor in the table")] + match expected { Present { value: ks } => if any(ks, j => j == k) { [SocketSensorsAbsent { socket: k }] } else { [] } Absent => [] } } else { - concat( - map(filter(decoded, l => (l.status as String) != "ok"), l => - concat("bmc sensor ", l.name as String, " status ", l.status as String, ": ", l.reading_text as String)), - map(filter(discrete, t => pipe_field(text: t, index: 2) != "ok"), t => concat("bmc sensor line (no reading field): ", t)), - ) + concat(map(filter(decoded, l => (l.status as String) != "ok"), l => SensorNotNominal { line: l }), + map(filter(discrete, t => pipe_field(text: t, index: 2) != "ok" && t != ""), t => SensorLineWithoutReadingNotNominal { text: t as NonEmptyStr })) } }) } diff --git a/dag/gunbc/machine_intake/mtcollins1_boot_diagnostic_bundle.dag b/dag/gunbc/machine_intake/mtcollins1_boot_diagnostic_bundle.dag index 6cfc5ba4084..f522519c535 100644 --- a/dag/gunbc/machine_intake/mtcollins1_boot_diagnostic_bundle.dag +++ b/dag/gunbc/machine_intake/mtcollins1_boot_diagnostic_bundle.dag @@ -21,9 +21,11 @@ import extdeps.bmc.ipmi_sel { FirmwareError, FirmwareHang, FirmwareProgress } import extdeps.bmc.ipmi_sdr { SdrLineResult, sdr_decode_lines } import gunbc.machine_intake_mtcollins1_smpro_observation { SmproProbeOutcome, SmproWordRead, SmproControllerRefused, SmproTransportFailed, SmproResponseMalformed, - mtcollins1_smpro_probe_count, mtcollins1_smpro_dossier_text, mtcollins1_smpro_findings, + mtcollins1_smpro_probe_count, mtcollins1_smpro_dossier_text, mtcollins1_smpro_targets, + mtcollins1_smpro_socket_records, smpro_socket_word, smpro_outcome_word, smpro_outcome_text, } -import gunbc.machine_intake_mtcollins1_bmc_sensor_observation { mtcollins1_sensor_findings, mtcollins1_sensor_table_text } +import extdeps.ampere.smpro_register { smpro_gpi_ras_err_reg } +import gunbc.machine_intake_mtcollins1_bmc_sensor_observation { mtcollins1_sensor_table_text, mtcollins1_sensor_anomalies } import extdeps.bmc.redfish_memory_inventory { RedfishMemoryMember, RedfishProcessorMember, RedfishResourceStatus, RedfishMemoryLocation, RedfishResourceState, StateAbsent, StateDisabled, StateUnavailableOffline, StateStandbyOffline, @@ -43,13 +45,31 @@ import gunbc.machine_intake_sel_stimulus_listener { SelDelta, sel_read, sel_afte import gunbc.machine_intake_pre_os_bringup_verdict { SelWindowEvent, BootRestart, FirmwareProgressRecord, OemTimestamped, OtherStandardEvent, FirmwareConsoleReport, NoTerminalStated, UndecodedErrorCodesOnly, FirmwareErrorStatementUnrecognized, TrainingRefusedByFirmware, - firmware_console_report, + firmware_console_report, is_firmware_error_line, is_firmware_err_code_line, } import gunbc.machine_intake_ampere_dram_console_observation { - AmpereDramConsoleObservation, ampere_dram_console_observation, ampere_dram_findings, ampere_dram_console_text, + AmpereDramConsoleObservation, ampere_dram_console_observation, ampere_dram_console_text, } -import gunbc.machine_intake_kernel_module_decompression_observation { KernelModuleDecompressionObservation, kernel_module_decompression_observation, kernel_module_decompression_text, kernel_module_decompression_findings } +import gunbc.machine_intake_kernel_module_decompression_observation { KernelModuleDecompressionObservation, kernel_module_decompression_observation, kernel_module_decompression_text } import gunbc.machine_intake_ampere_nvparam_console_observation { NvParamConsoleObservation, ampere_nvparam_console_observation, ampere_nvparam_console_text } +import gunbc.machine_intake_ampere_checkpoint_console_observation { + AmpereCheckpointConsoleObservation, AmpereCheckpointRelation, AmpereCheckpointReference, + ampere_checkpoint_console_observation, ampere_checkpoint_relation, ampere_checkpoint_relation_unread, ampere_checkpoint_console_text, ampere_checkpoint_relation_text, +} +import gunbc.machine_intake_mtcollins1_console_milestone_observation { ConsoleMilestoneObservation, mtcollins1_console_milestones, console_milestone_sighting_text } +import gunbc.machine_intake_linux_boot_console_observation { LinuxBootConsoleObservation, linux_boot_console_observation, linux_boot_console_text } +import extdeps.bootloader.grub { GrubConsoleReport, GrubNothingStated, GrubBannerOnly, GrubStillTrying, GrubBootFailed, grub_console_report, grub_error_text } +import extdeps.tools.sha256sum { Sha256FileDigest, Sha256FileDigestUnavailable, sha256sum_file_digest_via_shell } +import gunbc.machine_intake_mtcollins1_power_on_account { + PowerOnAccount, PowerOnEvidence, AttemptConfigurationReceipt, BmcAnomaly, ConsoleEvidence, ConsoleRead, ConsoleNotRead, SmproPassEvidence, + Carrier, SolConsole, KvmScreen, SmproRegisters, SmproErrorRegisters, SystemEventLog, SensorTable, ChassisPower, RedfishInventory, VirtualMedia, HostCaptureMarkers, + CarrierCoverage, CarrierStanding, CarrierRead, CarrierReadEmpty, CarrierPartial, CarrierRefused, CarrierNotTaken, + PowerOnHandoff, HandoffPoweredOn, HandoffNotConfirmed, SelEvidence, SelEventsRead, SelEventsNotRead, + WatchdogUnobserved, ExpectedSockets, ExpectedTopologyNotRecorded, StimulusRequestNotRecorded, + PopulationLiveObserved, PopulationOperatorAttested, PopulationConflicted, SlotRow, StimulusApplicationNotRecorded, PopulationNotRecorded, FirmwareReadbacksNotRecorded, + ProcessorMissingOnExpectedSocket, ProcessorSocketDuplicated, ProcessorSocketUnreadable, ProcessorNotNominal, ProcessorAbsentOnExpectedSocket, MemoryBelowRecordedPopulation, MemoryNotNominal, SelHardwareEvent, + mtcollins1_power_on_account, mtcollins1_power_on_account_text, mtcollins1_power_on_account_notes, SpanConsoleReading, span_console_readings, +} import gunbc.machine_intake_ampere_socket_console_observation { AmpereSocketConsoleObservation, AmpereSummaryObserved, AmpereSummaryNotPrinted, AmpereSocketReferenceComparison, ampere_socket_console_observation, ampere_socket_console_text, ampere_socket_reference_comparison, ampere_socket_reference_comparison_text, @@ -150,8 +170,11 @@ import gunbc.machine_intake_megarac_media_attach { // renders it. It does not decode anything those authorities do not. // // THE BUNDLE NEVER DECIDES THE VERDICT (brief, and DESIGN section 5's separation of observation -// from judgment). mtcollins1_boot_outcome_with_findings only appends findings to a refusal -// reason. It never turns a success into a failure or a failure into a success, and the witness +// from judgment). mtcollins1_boot_outcome_with_findings only appends what was found to a refusal +// reason. WHAT WAS FOUND IS TYPED, TWO WAYS: what the MACHINE did is the power-on fold's +// PowerOnAccount (gunbc.machine_intake_mtcollins1_power_on_account; docs/plans/power-on-sequence-model.md), +// and what OUR HARNESS did -- media, handoff, override, power after, the BMC's reachability, the +// credential -- is a HarnessFinding. Neither is a list of strings. It never turns a success into a failure or a failure into a success, and the witness // asserts both directions. // // A READ THAT FAILS IS A TYPED, LOCATED READING IN THE BUNDLE, NEVER A MISSING SECTION (DESIGN @@ -358,7 +381,7 @@ type InventoryReading { // those bytes would under-count multi-byte console output. The size is taken before the content is // read, so a capture over the bound is reported without being loaded. type ConsoleReading - = ConsoleRetained { path: NonEmptyStr, size: ByteSize, lines: Int, firmware: FirmwareConsoleReport, dram: AmpereDramConsoleObservation, sockets: AmpereSocketConsoleObservation, against_reference: AmpereSocketReferenceComparison, modules: KernelModuleDecompressionObservation, nvparam: NvParamConsoleObservation } + = ConsoleRetained { path: NonEmptyStr, size: ByteSize, lines: Int, firmware: FirmwareConsoleReport, firmware_error_lines: Int, capture_digest: Sha256FileDigest, dram: AmpereDramConsoleObservation, checkpoints: AmpereCheckpointConsoleObservation, checkpoint_relations: List, milestones: ConsoleMilestoneObservation, sockets: AmpereSocketConsoleObservation, against_reference: AmpereSocketReferenceComparison, loader: GrubConsoleReport, linux: LinuxBootConsoleObservation, modules: KernelModuleDecompressionObservation, nvparam: NvParamConsoleObservation, span_readings: List } | ConsoleExceedsBound { path: NonEmptyStr, size: ByteSize, bound: ByteSize } | ConsoleNotRetained { path: NonEmptyStr } | ConsoleUnread { path: NonEmptyStr, detail: String } @@ -638,16 +661,32 @@ fn media_attach_text(record: MtCollins1MediaAttachRecord) -> String { } } -fn media_attach_findings(record: MtCollins1MediaAttachRecord) -> List { +// WHAT OUR HARNESS DID THAT A READER OF THIS ATTEMPT MUST KNOW, as typed values carrying the record +// they were decided from. These are facts about the observation, not about the machine: the +// machine's side is the power-on account (gunbc.machine_intake_mtcollins1_power_on_account). Each is decided by +// the *_harness function beside its record and rendered once, by harness_finding_text. +type HarnessFinding + = EveryBmcReadFailed { failed: List } + | MediaAttachNotReady { record: MtCollins1MediaAttachRecord } + | MediaAttachSessionHeld { record: MtCollins1MediaAttachRecord } + | HandoffWithheldAtRecheck { handoff: MtCollins1HandoffMedia } + | HandoffUnconfirmed { handoff: MtCollins1HandoffMedia } + | PresentationAfterHandoff { look: MegaRacAttachResult } + | OverrideNotConsumed { consumption: BootOverrideConsumption } + | HostPoweredOffWithoutCensusEnd + | CredentialMayRemain + +fn media_attach_harness(record: MtCollins1MediaAttachRecord) -> List { match record { MediaAttachNotAttempted { reason: _ } => [] MediaAttachAttempted { attempt: _, medium_image_name: _, medium_image_sha256: _, power: _, stale_power_off: _, result: res } => match media_attach_outcome_reason(outcome: res.outcome) { - Present { value: why } => [concat("media attach: ", why)] + Present { value: _ } => [MediaAttachNotReady { record: record }] Absent => match res.session_release { SessionReleased => [] - other => [concat("media attach: the presentation is ready but the session was ", session_release_text(standing: other))] + SessionReleaseRefused { detail: _ } => [MediaAttachSessionHeld { record: record }] + SessionReleaseNotAttempted => [MediaAttachSessionHeld { record: record }] } } } @@ -705,20 +744,18 @@ fn recheck_text(v: PresentationStillServed) -> String { // AFFIRMED LOSS AND AN UNOBSERVED LOOK ARE DIFFERENT EVIDENCE (side-chat review 5331738657). Both // withhold before the handoff, but the bundle says which it was: "LOST" only when a look was taken // and showed the medium not served; a failed look is indeterminate and is reported as such. -fn handoff_media_findings(h: MtCollins1HandoffMedia) -> List { +fn handoff_media_harness(h: MtCollins1HandoffMedia) -> List { match h { HandoffMediaNotReached { reason: _ } => [] - HandoffMediaWithheldAtRecheck { before: b } => - [concat("media was ready at the gate but the pre-handoff recheck did not admit it, so no boot-device or power write was made: ", recheck_text(v: presentation_still_served(result: b)))] + HandoffMediaWithheldAtRecheck { before: _ } => [HandoffWithheldAtRecheck { handoff: h }] HandoffMediaAttempted { before: _, handoff: hc, after: a } => - concat(match hc { HandoffConfirmed => [] HandoffAttemptedUnconfirmed { reason: r } => [concat("the boot-device and power handoff was attempted and not confirmed: ", r)] }, + concat(match hc { HandoffConfirmed => [] HandoffAttemptedUnconfirmed { reason: _ } => [HandoffUnconfirmed { handoff: h }] }, match presentation_still_served(result: a) { PresentationStillReady => [] - PresentationNoLongerReady { reason: why } => [concat("the presentation was LOST at or after the boot-device and power handoff (", concat(why as String, concat("): ", observation_text(r: a))))] - PresentationUnobserved { reason: why } => [concat("the post-handoff look was UNOBSERVED, so whether the presentation survived the handoff is INDETERMINATE (", concat(why as String, concat("): ", observation_text(r: a))))] - PresentationReadyButSessionHeld { held: r } => [concat("after the handoff the presentation was served, but the look's own session was ", session_release_text(standing: r))] - } - ) + PresentationReadyButSessionHeld { held: _ } => [PresentationAfterHandoff { look: a }] + PresentationNoLongerReady { reason: _ } => [PresentationAfterHandoff { look: a }] + PresentationUnobserved { reason: _ } => [PresentationAfterHandoff { look: a }] + }) } } @@ -814,6 +851,7 @@ fn outcome_attributing_media_loss(outcome: ProcessExit, loss: MediaLossAfterHand } type MtCollins1BootRunRecord { + census_ended: Bool end_media: MtCollins1EndMedia handoff_media: MtCollins1HandoffMedia media: MtCollins1MediaAttachRecord @@ -828,6 +866,7 @@ type MtCollins1BootRunRecord { fn run_record_not_taken(reason: NonEmptyStr) -> MtCollins1BootRunRecord { MtCollins1BootRunRecord { + census_ended: false, end_media: EndMediaNotTaken { reason: reason }, handoff_media: HandoffMediaNotReached { reason: reason }, media: MediaAttachNotAttempted { reason: reason }, @@ -907,10 +946,10 @@ fn power_after_attempt(observation: ChassisPowerObservation, census_ended: Bool) } } -fn override_findings(c: BootOverrideConsumption) -> List { +fn override_harness(c: BootOverrideConsumption) -> List { match c { - OverrideStillPendingAfterAttempt => ["the one-shot boot override was still pending after the attempt: the host did not boot through it"] - OverridePendingBeforeAttempt => ["a boot override matching this attempt's was already pending before it was set, so its consumption cannot be attributed to this attempt"] + OverrideStillPendingAfterAttempt => [OverrideNotConsumed { consumption: c }] + OverridePendingBeforeAttempt => [OverrideNotConsumed { consumption: c }] OverrideConsumedByCensusBoot => [] OverrideNoLongerPendingAfterAttempt => [] OverrideNotHandedOff { reason: _ } => [] @@ -918,9 +957,9 @@ fn override_findings(c: BootOverrideConsumption) -> List { } } -fn power_after_findings(p: PowerAfterAttempt) -> List { +fn power_after_harness(p: PowerAfterAttempt) -> List { match p { - PoweredOffWithoutCensusEnd => ["the host was powered off after the attempt without the census END marker"] + PoweredOffWithoutCensusEnd => [HostPoweredOffWithoutCensusEnd] PoweredOffAfterCensusEnd => [] PoweredOnAfterAttempt => [] PowerAfterUnobserved { observation: _ } => [] @@ -1076,34 +1115,6 @@ fn mtcollins1_census_socket_count() -> Int { count(mtcollins1_census_sockets()) } -// THE FIRMWARE'S OWN SOCKET COUNT AGAINST THE CENSUS. A summary that prints fewer active sockets -// than the census holds is a finding; one that was not printed says nothing either way. -fn socket_summary_findings(o: AmpereSocketConsoleObservation) -> List { - match o.summary { - AmpereSummaryNotPrinted => [] - AmpereSummaryObserved { summary: s, summaries_printed: _ } => match s.active_sockets { - Absent => [] - Present { value: n } => if n >= mtcollins1_census_socket_count() { [] } else { - [concat("firmware boot summary: ", to_string(n), " active socket(s), the census holds ", to_string(mtcollins1_census_socket_count()))] - } - } - } -} - -fn console_findings(c: ConsoleReading) -> List { - match c { - ConsoleRetained { path: _, size: _, lines: _, firmware: _, dram: d, sockets: o, against_reference: _, modules: m, nvparam: _ } => concat(ampere_dram_findings(observation: d, expected_sockets: mtcollins1_census_sockets()), concat(socket_summary_findings(o: o), kernel_module_decompression_findings(o: m))) - ConsoleExceedsBound { path: _, size: _, bound: _ } => [] - ConsoleNotRetained { path: _ } => [] - ConsoleUnread { path: _, detail: _ } => [] - ConsoleNotConfigured => [] - } -} - -fn smpro_findings(s: SmproReading) -> List { - match s { SmproProbed { outcomes: os } => mtcollins1_smpro_findings(outcomes: os) SmproNotTaken { reason: _ } => [] } -} - fn smpro_text(s: SmproReading) -> String { match s { SmproProbed { outcomes: os } => mtcollins1_smpro_dossier_text(outcomes: os) @@ -1111,9 +1122,9 @@ fn smpro_text(s: SmproReading) -> String { } } -fn sensor_findings(s: SensorReading) -> List { +fn sensor_anomalies(s: SensorReading, receipt: AttemptConfigurationReceipt) -> List { match s { - SensorsRead { lines: ls, at: _ } => mtcollins1_sensor_findings(lines: ls, sockets: mtcollins1_census_sockets()) + SensorsRead { lines: ls, at: _ } => mtcollins1_sensor_anomalies(lines: ls, sockets: mtcollins1_census_sockets(), expected: expected_socket_list(receipt: receipt)) SensorsUnread { cause: _ } => [] SensorsNotTaken { reason: _ } => [] } @@ -1185,10 +1196,25 @@ fn state_is_absent(state: RedfishResourceState) -> Bool { } } -fn socket_absent_finding(ms: List, socket: Int) -> String { - let on = filter(ms, m => memory_socket_key(m: m) == socket) - let absent = count(filter(on, m => state_is_absent(state: m.status.state))) - if absent == 0 || socket < 0 { "" } else { concat("BMC reports ", to_string(absent), " of ", to_string(count(on)), " socket-", to_string(socket), " Memory member(s) State=Absent") } +fn recorded_slot_rows(receipt: AttemptConfigurationReceipt) -> List? { + match receipt.dimms { + PopulationLiveObserved { rows: rs, source: _ } => Present { value: rs } + PopulationOperatorAttested { rows: rs, receipt: _ } => Present { value: rs } + PopulationConflicted { readings: _ } => none + PopulationNotRecorded { reason: _ } => none + } +} + +fn expected_socket_list(receipt: AttemptConfigurationReceipt) -> List? { + match receipt.expected { ExpectedSockets { sockets: ks, source: _ } => Present { value: ks } ExpectedTopologyNotRecorded { reason: _ } => none } +} + +// "CPU 1", "Socket 1": the trailing number of the BMC's own socket rendering, or none. +fn processor_socket_number(p: RedfishProcessorMember) -> Int? { + match p.socket { + Absent => none + Present { value: t } => match filter(split(s: trim(t), delimiter: " "), x => x != "").last() { Absent => none Present { value: tok } => parse_int(s: tok) } + } } fn memory_label(m: RedfishMemoryMember) -> String { @@ -1202,45 +1228,61 @@ fn processor_socket_suffix(p: RedfishProcessorMember) -> String { match p.socket { Present { value: s } => concat(" (Socket ", s, ")") Absent => "" } } -fn processor_findings(processors: MembersReading) -> List { +// COMPLETENESS IS JUDGED PER EXPECTED SOCKET, never by a count: for each socket the receipt expects, +// exactly one Processor member names it (none is Missing, two or more is Duplicated); a member whose +// socket the BMC renders unreadably is its own anomaly. With no recorded topology none of these is +// judged (law 6). What the BMC states about a member's health stays unconditional. +fn processor_anomalies(processors: MembersReading, receipt: AttemptConfigurationReceipt) -> List { match processors { MembersRead { members: ps } => { - let expected = mtcollins1_census_socket_count() - let none_short: List = [] - let short = if count(ps) < expected { - [concat("BMC lists ", to_string(count(ps)), " Processor member(s); the 2026-09-10 census observed ", to_string(expected), " sockets")] - } else { none_short } - concat(short, map(filter(ps, p => state_is_down(state: p.status.state) || health_is_bad(health: p.status.health)), - p => concat("BMC Processor ", p.id as String, processor_socket_suffix(p: p), ": ", status_text(status: p.status)))) + let unhealthy = map(filter(ps, p => health_is_bad(health: p.status.health) || (state_is_down(state: p.status.state) && !state_is_absent(state: p.status.state))), p => ProcessorNotNominal { member: p }) + let per_socket = match expected_socket_list(receipt: receipt) { + Absent => [] + Present { value: ks } => concat( + flat_map(ks, k => { + let on = filter(ps, p => match processor_socket_number(p: p) { Present { value: n } => n == k Absent => false }) + if count(on) == 0 { [ProcessorMissingOnExpectedSocket { socket: k }] } + else if count(on) > 1 { [ProcessorSocketDuplicated { socket: k, members: on }] } + else { flat_map(on, p => if state_is_absent(state: p.status.state) && !health_is_bad(health: p.status.health) { [ProcessorAbsentOnExpectedSocket { member: p, socket: k }] } else { [] }) } + }), + map(filter(ps, p => match processor_socket_number(p: p) { Present { value: _ } => false Absent => true }), p => ProcessorSocketUnreadable { member: p })) + } + concat(unhealthy, per_socket) } MembersUnread { cause: _ } => [] MembersNotTaken { reason: _ } => [] } } -fn memory_findings(memory: MembersReading) -> List { +// PER SOCKET, OVER THE UNION OF THE SOCKETS THE RECEIPT RECORDS POPULATED AND THE SOCKETS THE BMC +// LISTS: a socket whose present members are fewer than the receipt's populated slots on it is an +// anomaly, including a socket the collection never mentions. With no recorded population nothing is +// judged (law 6). What the BMC states about a member's health stays unconditional. +fn memory_anomalies(memory: MembersReading, receipt: AttemptConfigurationReceipt) -> List { match memory { MembersRead { members: ms } => { - let slots = count(mtcollins1_slots) - let none_short: List = [] - let short = if count(ms) < slots { - [concat("BMC lists ", to_string(count(ms)), " Memory member(s); the census board has ", to_string(slots), " slots")] - } else { none_short } - let sockets = distinct_ints(xs: map(ms, m => memory_socket_key(m: m))) - let absent_by_socket = filter(map(sockets, k => socket_absent_finding(ms: ms, socket: k)), line => line != "") - let bad = map(filter(ms, m => (state_is_down(state: m.status.state) && !state_is_absent(state: m.status.state)) || health_is_bad(health: m.status.health)), - m => concat("BMC Memory ", memory_label(m: m), ": ", status_text(status: m.status))) - concat(short, concat(absent_by_socket, bad)) + let bad = map(filter(ms, m => (state_is_down(state: m.status.state) && !state_is_absent(state: m.status.state)) || health_is_bad(health: m.status.health)), m => MemoryNotNominal { member: m }) + let below = match recorded_slot_rows(receipt: receipt) { + Absent => [] + Present { value: rows } => { + let sockets = distinct_ints(xs: concat(map(filter(rows, r => r.populated), r => r.socket), filter(map(ms, m => memory_socket_key(m: m)), k => k >= 0))) + flat_map(sockets, k => { + let present = count(filter(ms, m => memory_socket_key(m: m) == k && !state_is_absent(state: m.status.state))) + let populated = count(filter(rows, r => r.socket == k && r.populated)) + if present < populated { [MemoryBelowRecordedPopulation { socket: k, present: present, populated: populated }] } else { [] } + }) + } + } + concat(below, bad) } MembersUnread { cause: _ } => [] MembersNotTaken { reason: _ } => [] } } -fn sel_findings(delta: SelDeltaReading) -> List { +fn sel_hardware_anomalies(delta: SelDeltaReading) -> List { match delta { - SelDeltaDecoded { delta: d } => - map(filter(d.events, e => sel_event_is_memory_or_processor(event: e)), e => concat("SEL ", sel_event_text(event: e))) + SelDeltaDecoded { delta: d } => map(filter(d.events, e => sel_event_is_memory_or_processor(event: e)), e => SelHardwareEvent { event: e }) SelDeltaRefused { cause: _ } => [] SelDeltaUnavailable { reason: _ } => [] } @@ -1336,24 +1378,278 @@ fn bmc_all_reads_failed(bundle: MtCollins1BootDiagnosticBundle) -> List List { - let nothing: List = [] +// ── WHAT THE BUNDLE ESTABLISHES ──────────────────────────────────────────────────────────────── +// THE EVIDENCE THE POWER-ON ACCOUNT READS, assembled from this bundle's own sections. Nothing is +// re-read: each field is a section already taken, or a standing derived from one. +fn smpro_pass(label: NonEmptyStr, s: SmproReading) -> List { + match s { + SmproProbed { outcomes: os } => [SmproPassEvidence { label: label, outcomes: os }] + SmproNotTaken { reason: _ } => [] + } +} + +fn handoff_standing(h: MtCollins1HandoffMedia) -> PowerOnHandoff { + match h { + HandoffMediaAttempted { before: _, handoff: hc, after: _ } => match hc { + HandoffConfirmed => HandoffPoweredOn + HandoffAttemptedUnconfirmed { reason: r } => HandoffNotConfirmed { reason: concat("the boot-device and power handoff was attempted and not confirmed: ", r) as NonEmptyStr } + } + HandoffMediaWithheldAtRecheck { before: _ } => HandoffNotConfirmed { reason: "the pre-handoff recheck withheld the power write" } + HandoffMediaNotReached { reason: r } => HandoffNotConfirmed { reason: r } + } +} + +fn console_evidence(c: ConsoleReading) -> ConsoleEvidence { + match c { + ConsoleRetained { path: _, size: _, lines: _, firmware: f, firmware_error_lines: e, capture_digest: dg, dram: d, checkpoints: cp, checkpoint_relations: rs, milestones: ms, sockets: o, against_reference: _, loader: g, linux: l, modules: m, nvparam: _, span_readings: sr } => + ConsoleRead { capture: dg, firmware: f, firmware_error_lines: e, dram: d, checkpoints: cp, relations: rs, milestones: ms, sockets: o, loader: g, linux: l, modules: m, span_readings: sr } + ConsoleExceedsBound { path: _, size: _, bound: _ } => ConsoleNotRead + ConsoleNotRetained { path: _ } => ConsoleNotRead + ConsoleUnread { path: _, detail: _ } => ConsoleNotRead + ConsoleNotConfigured => ConsoleNotRead + } +} + +fn sel_evidence(delta: SelDeltaReading) -> SelEvidence { + match delta { + SelDeltaDecoded { delta: d } => SelEventsRead { events: d.events } + SelDeltaRefused { cause: _ } => SelEventsNotRead + SelDeltaUnavailable { reason: _ } => SelEventsNotRead + } +} + +// ── COVERAGE: one standing per carrier, never a host statement ───────────────────────────────── +fn console_coverage(c: ConsoleReading) -> CarrierStanding { + match c { + ConsoleRetained { path: _, size: _, lines: n, firmware: _, firmware_error_lines: _, capture_digest: _, dram: _, checkpoints: _, checkpoint_relations: _, milestones: _, sockets: _, against_reference: _, loader: _, linux: _, modules: _, nvparam: _, span_readings: _ } => + if n == 0 { CarrierReadEmpty } else { CarrierRead } + ConsoleExceedsBound { path: p, size: _, bound: _ } => CarrierPartial { detail: concat("capture ", p as String, " is over the byte bound and was not read") as NonEmptyStr } + ConsoleNotRetained { path: p } => CarrierNotTaken { reason: concat("capture ", p as String, " is not retained with this artifact") as NonEmptyStr } + ConsoleUnread { path: p, detail: d } => CarrierRefused { cause: concat("capture ", p as String, " unread: ", d) as NonEmptyStr } + ConsoleNotConfigured => CarrierNotTaken { reason: "the SOL capture path was unset for this attempt" } + } +} + +fn kvm_coverage(rec: KvmObserverRecord) -> CarrierStanding { + match rec.start { + KvmObserverNotStarted { reason: r } => CarrierNotTaken { reason: r } + KvmObserverStarted { dir: _, subject: _, standing: _, checked_at: _ } => if count(rec.stills) == 0 { CarrierReadEmpty } else { CarrierRead } + } +} + +fn smpro_answered(s: SmproReading) -> Bool { + match s { SmproProbed { outcomes: os } => any(os, o => match smpro_outcome_word(o: o) { Present { value: _ } => true Absent => false }) SmproNotTaken { reason: _ } => false } +} + +fn smpro_coverage(readings: List) -> CarrierStanding { + if any(readings, r => smpro_answered(s: r)) { CarrierRead } + else if any(readings, r => match r { SmproProbed { outcomes: _ } => true SmproNotTaken { reason: _ } => false }) { CarrierRefused { cause: "no SMpro register word was read in any pass" } } + else { CarrierNotTaken { reason: "no SMpro pass was taken" } } +} + +// THE ERROR-RECORD REGISTERS, covered apart from the boot-stage pair (#13058): a pass whose CUR and +// BOOTSTAGE read while GPI_RAS_ERR or a record word refused is a gap in THIS carrier, not in progress. +fn record_gaps(os: List, socket: Int) -> List { + let r = mtcollins1_smpro_socket_records(outcomes: os, socket: socket) + let gpi = match smpro_socket_word(outcomes: os, socket: socket, register: smpro_gpi_ras_err_reg.name) { + Absent => [concat("socket ", to_string(socket), " GPI_RAS_ERR was not probed")] + Present { value: o } => match smpro_outcome_word(o: o) { Present { value: _ } => [] Absent => [concat("socket ", to_string(socket), " GPI_RAS_ERR ", smpro_outcome_text(o: o))] } + } + concat(gpi, concat(match r.smpro.missing { Present { value: m } => [concat("socket ", to_string(socket), " SMpro records: ", m)] Absent => [] }, + match r.pmpro.missing { Present { value: m } => [concat("socket ", to_string(socket), " PMpro records: ", m)] Absent => [] })) +} + +fn smpro_error_coverage(readings: List) -> CarrierStanding { + let passes = flat_map(readings, r => match r { SmproProbed { outcomes: os } => [os] SmproNotTaken { reason: _ } => [] }) + let gaps = flat_map(passes, os => flat_map(mtcollins1_smpro_targets, t => record_gaps(os: os, socket: t.socket))) + let wanted = count(passes) * count(mtcollins1_smpro_targets) * 3 + if count(passes) == 0 { CarrierNotTaken { reason: "no SMpro pass was taken" } } + else if count(gaps) == 0 { CarrierRead } + else if count(gaps) >= wanted { CarrierRefused { cause: (match gaps.first() { Present { value: g } => g Absent => "every error-record read failed" }) as NonEmptyStr } } + else { CarrierPartial { detail: join(gaps, "; ") as NonEmptyStr } } +} + +fn sel_coverage(delta: SelDeltaReading) -> CarrierStanding { + match delta { + SelDeltaDecoded { delta: _ } => CarrierRead + SelDeltaRefused { cause: c } => CarrierRefused { cause: c } + SelDeltaUnavailable { reason: r } => CarrierNotTaken { reason: (if r == "" { "the SEL delta is unavailable" } else { r }) as NonEmptyStr } + } +} + +fn sensor_coverage(s: SensorReading) -> CarrierStanding { + match s { + SensorsRead { lines: ls, at: _ } => if count(ls) == 0 { CarrierReadEmpty } else { CarrierRead } + SensorsUnread { cause: c } => CarrierRefused { cause: bmc_read_cause_text(cause: c) as NonEmptyStr } + SensorsNotTaken { reason: r } => CarrierNotTaken { reason: r } + } +} + +fn chassis_coverage(p: PowerAfterAttempt) -> CarrierStanding { + match p { + PowerAfterUnobserved { observation: _ } => CarrierRefused { cause: "the post-attempt chassis read did not answer" } + PowerAfterNotTaken { reason: r } => CarrierNotTaken { reason: r } + PoweredOffAfterCensusEnd => CarrierRead + PoweredOffWithoutCensusEnd => CarrierRead + PoweredOnAfterAttempt => CarrierRead + } +} + +fn members_coverage(m: MembersReading) -> CarrierStanding { + match m { + MembersRead { members: _ } => CarrierRead + MembersUnread { cause: c } => CarrierRefused { cause: bmc_read_cause_text(cause: c) as NonEmptyStr } + MembersNotTaken { reason: r } => CarrierNotTaken { reason: r } + } +} + +fn inventory_coverage(i: InventoryReading) -> CarrierStanding { + match members_coverage(m: i.memory) { + CarrierRead => members_coverage(m: i.processors) + other => other + } +} + +fn media_coverage(m: MtCollins1MediaAttachRecord) -> CarrierStanding { + match m { + MediaAttachNotAttempted { reason: r } => CarrierNotTaken { reason: r } + MediaAttachAttempted { attempt: _, medium_image_name: _, medium_image_sha256: _, power: _, stale_power_off: _, result: _ } => CarrierRead + } +} + +fn census_coverage(run: MtCollins1BootRunRecord) -> CarrierStanding { + if run.census_ended { CarrierRead } else { CarrierPartial { detail: "no census END marker was observed; the watch's own termination is the run's exit" } } +} + +fn mtcollins1_boot_coverage(bundle: MtCollins1BootDiagnosticBundle) -> List { + let delta = sel_delta_reading(before: bundle.sel_before, after: bundle.sel_after) + [ + CarrierCoverage { carrier: SolConsole, standing: console_coverage(c: bundle.console) }, + CarrierCoverage { carrier: KvmScreen, standing: kvm_coverage(rec: bundle.screen) }, + CarrierCoverage { carrier: SmproRegisters, standing: smpro_coverage(readings: [bundle.run.smpro_power_on, bundle.run.smpro_census, bundle.smpro]) }, + CarrierCoverage { carrier: SmproErrorRegisters, standing: smpro_error_coverage(readings: [bundle.run.smpro_power_on, bundle.run.smpro_census, bundle.smpro]) }, + CarrierCoverage { carrier: SystemEventLog, standing: sel_coverage(delta: delta) }, + CarrierCoverage { carrier: SensorTable, standing: sensor_coverage(s: bundle.sensors) }, + CarrierCoverage { carrier: ChassisPower, standing: chassis_coverage(p: bundle.run.power_after) }, + CarrierCoverage { carrier: RedfishInventory, standing: inventory_coverage(i: bundle.inventory) }, + CarrierCoverage { carrier: VirtualMedia, standing: media_coverage(m: bundle.run.media) }, + CarrierCoverage { carrier: HostCaptureMarkers, standing: census_coverage(run: bundle.run) }, + ] +} + +// THE ATTEMPT CONFIGURATION RECEIPT THIS RUN CAN FREEZE TODAY. The fleet-converge boot takes no +// stimulus, population, expected-topology or pre-actuation firmware receipt yet, so those fields are +// NotRecorded with that reason and every question needing them is open; the historical census and +// orientation rows are deliberately NOT used (they map topology, never current occupancy, and are no +// expectation of THIS attempt), so no topology degradation is minted from them. DECLARED FRONTIER: the receipt's live inputs land +// with the boot workflow's stimulus-receipt input (docs/plans/power-on-sequence-model.md section 10); +// until then the account says what it cannot judge rather than judging it. +data mtcollins1_receipt_input_frontier: NonEmptyStr = "the fleet-converge boot takes no stimulus, population or pre-actuation firmware receipt yet" + +fn mtcollins1_attempt_configuration_receipt(attempt: AttemptIdentityReading) -> AttemptConfigurationReceipt { + AttemptConfigurationReceipt { + subject: "mtcollins1", + attempt: attempt_identity_text(a: attempt) as NonEmptyStr, + expected: ExpectedTopologyNotRecorded { reason: mtcollins1_receipt_input_frontier }, + requested: StimulusRequestNotRecorded { reason: mtcollins1_receipt_input_frontier }, + applied: StimulusApplicationNotRecorded { reason: mtcollins1_receipt_input_frontier }, + cpus: PopulationNotRecorded { reason: mtcollins1_receipt_input_frontier }, + dimms: PopulationNotRecorded { reason: mtcollins1_receipt_input_frontier }, + firmware: FirmwareReadbacksNotRecorded { reason: mtcollins1_receipt_input_frontier }, + } +} + +fn mtcollins1_boot_evidence(bundle: MtCollins1BootDiagnosticBundle) -> PowerOnEvidence { + let delta = sel_delta_reading(before: bundle.sel_before, after: bundle.sel_after) + let receipt = mtcollins1_attempt_configuration_receipt(attempt: bundle.attempt) + PowerOnEvidence { + configuration: receipt, + handoff: handoff_standing(h: bundle.run.handoff_media), + smpro: concat(smpro_pass(label: "power-on", s: bundle.run.smpro_power_on), concat(smpro_pass(label: "census", s: bundle.run.smpro_census), smpro_pass(label: "after the attempt", s: bundle.smpro))), + console: console_evidence(c: bundle.console), + sel: sel_evidence(delta: delta), + watchdog: WatchdogUnobserved { reason: "the boot run does not read the BMC's IPMI watchdog" }, + bmc_anomalies: concat(processor_anomalies(processors: bundle.inventory.processors, receipt: receipt), concat(memory_anomalies(memory: bundle.inventory.memory, receipt: receipt), concat(sensor_anomalies(s: bundle.sensors, receipt: receipt), sel_hardware_anomalies(delta: delta)))), + census_ended: bundle.run.census_ended, + coverage: mtcollins1_boot_coverage(bundle: bundle), + } +} + +// WHAT THE BUNDLE FOUND: the power-on account (the host and our coverage of it) and, beside it, what +// our harness did. The two are composed here because the harness records are this module's. +type MtCollins1BootFindings { + account: PowerOnAccount + harness: List +} + +fn mtcollins1_boot_harness(bundle: MtCollins1BootDiagnosticBundle) -> List { let failed = bmc_all_reads_failed(bundle: bundle) - let unreachable = if count(failed) == 0 { nothing } else { [bmc_all_failed_text(failed: failed)] } - let shred = if bundle.inventory.credential_removed { nothing } else { ["the per-attempt Redfish netrc could not be removed; credential material may remain on the runner"] } - concat(unreachable, concat(processor_findings(processors: bundle.inventory.processors), - concat(memory_findings(memory: bundle.inventory.memory), - concat(sel_findings(delta: sel_delta_reading(before: bundle.sel_before, after: bundle.sel_after)), concat(sensor_findings(s: bundle.sensors), concat(smpro_findings(s: bundle.smpro), concat(smpro_findings(s: bundle.run.smpro_power_on), concat(smpro_findings(s: bundle.run.smpro_census), concat(media_attach_findings(record: bundle.run.media), concat(handoff_media_findings(h: bundle.run.handoff_media), concat(override_findings(c: bundle.run.override), concat(power_after_findings(p: bundle.run.power_after), concat(console_findings(c: bundle.console), shred))))))))))))) + concat(if count(failed) == 0 { [] } else { [EveryBmcReadFailed { failed: failed }] }, + concat(media_attach_harness(record: bundle.run.media), + concat(handoff_media_harness(h: bundle.run.handoff_media), + concat(override_harness(c: bundle.run.override), + concat(power_after_harness(p: bundle.run.power_after), + if bundle.inventory.credential_removed { [] } else { [CredentialMayRemain] }))))) +} + +fn mtcollins1_boot_findings(bundle: MtCollins1BootDiagnosticBundle) -> MtCollins1BootFindings { + MtCollins1BootFindings { account: mtcollins1_power_on_account(e: mtcollins1_boot_evidence(bundle: bundle)), harness: mtcollins1_boot_harness(bundle: bundle) } } -// THE ONE-LINE CONSEQUENCE ON THE RUN'S OUTCOME: findings are appended to a refusal reason, and a -// success stays a success. This is the whole of the bundle's reach into the verdict. -fn mtcollins1_boot_outcome_with_findings(outcome: ProcessExit, findings: List) -> ProcessExit { +fn harness_finding_text(f: HarnessFinding) -> String { + match f { + EveryBmcReadFailed { failed: xs } => bmc_all_failed_text(failed: xs) + MediaAttachNotReady { record: r } => match r { + MediaAttachAttempted { attempt: _, medium_image_name: _, medium_image_sha256: _, power: _, stale_power_off: _, result: res } => + concat("media attach: ", match media_attach_outcome_reason(outcome: res.outcome) { Present { value: why } => why Absent => "not ready" }) + MediaAttachNotAttempted { reason: why } => concat("media attach: not attempted: ", why as String) + } + MediaAttachSessionHeld { record: r } => match r { + MediaAttachAttempted { attempt: _, medium_image_name: _, medium_image_sha256: _, power: _, stale_power_off: _, result: res } => + concat("media attach: the presentation is ready but the session was ", session_release_text(standing: res.session_release)) + MediaAttachNotAttempted { reason: why } => concat("media attach: not attempted: ", why as String) + } + HandoffWithheldAtRecheck { handoff: h } => match h { + HandoffMediaWithheldAtRecheck { before: b } => concat("media was ready at the gate but the pre-handoff recheck did not admit it, so no boot-device or power write was made: ", recheck_text(v: presentation_still_served(result: b))) + HandoffMediaNotReached { reason: _ } => "the pre-handoff recheck withheld the handoff" + HandoffMediaAttempted { before: _, handoff: _, after: _ } => "the pre-handoff recheck withheld the handoff" + } + HandoffUnconfirmed { handoff: h } => match h { + HandoffMediaAttempted { before: _, handoff: hc, after: _ } => match hc { + HandoffAttemptedUnconfirmed { reason: r } => concat("the boot-device and power handoff was attempted and not confirmed: ", r) + HandoffConfirmed => "the boot-device and power handoff was confirmed" + } + HandoffMediaNotReached { reason: _ } => "the boot-device and power handoff was not attempted" + HandoffMediaWithheldAtRecheck { before: _ } => "the boot-device and power handoff was not attempted" + } + PresentationAfterHandoff { look: a } => match presentation_still_served(result: a) { + PresentationStillReady => "after the handoff the presentation was still served" + PresentationNoLongerReady { reason: why } => concat("the presentation was LOST at or after the boot-device and power handoff (", why as String, "): ", observation_text(r: a)) + PresentationUnobserved { reason: why } => concat("the post-handoff look was UNOBSERVED, so whether the presentation survived the handoff is INDETERMINATE (", why as String, "): ", observation_text(r: a)) + PresentationReadyButSessionHeld { held: r } => concat("after the handoff the presentation was served, but the look's own session was ", session_release_text(standing: r)) + } + OverrideNotConsumed { consumption: c } => match c { + OverrideStillPendingAfterAttempt => "the one-shot boot override was still pending after the attempt: the host did not boot through it" + OverridePendingBeforeAttempt => "a boot override matching this attempt's was already pending before it was set, so its consumption cannot be attributed to this attempt" + OverrideConsumedByCensusBoot => override_consumption_text(c: c) + OverrideNoLongerPendingAfterAttempt => override_consumption_text(c: c) + OverrideNotHandedOff { reason: _ } => override_consumption_text(c: c) + OverrideConsumptionUnread { reason: _ } => override_consumption_text(c: c) + } + HostPoweredOffWithoutCensusEnd => "the host was powered off after the attempt without the census END marker" + CredentialMayRemain => "the per-attempt Redfish netrc could not be removed; credential material may remain on the runner" + } +} + +// THE ONE-LINE CONSEQUENCE ON THE RUN'S OUTCOME: the account's notes (its ending, every stated error, +// degradation and anomaly) and every harness finding are appended to a refusal reason, and a success +// stays a success. This is the whole of the bundle's reach into the verdict. +fn mtcollins1_boot_outcome_with_findings(outcome: ProcessExit, findings: MtCollins1BootFindings) -> ProcessExit { match outcome { ExitSuccess => ExitSuccess ExitFailure { code: c, reason: r } => - if count(findings) == 0 { ExitFailure { code: c, reason: r } } - else { ExitFailure { code: c, reason: concat(r, " | bmc diagnostics: ", join(findings, "; ")) } } + ExitFailure { code: c, reason: concat(r, " | power-on account: ", join(mtcollins1_power_on_account_notes(a: findings.account), "; "), + if count(findings.harness) == 0 { "" } else { concat(" | harness: ", join(map(findings.harness, f => harness_finding_text(f: f)), "; ")) }) } } } @@ -1447,10 +1743,19 @@ fn firmware_report_text(report: FirmwareConsoleReport) -> String { } } +fn grub_report_text(report: GrubConsoleReport) -> String { + match report { + GrubNothingStated => "GRUB: nothing printed" + GrubBannerOnly => "GRUB: banner printed, no error" + GrubStillTrying { errors: es } => concat("GRUB: still trying after ", join(map(es, e => grub_error_text(error: e)), "; ")) + GrubBootFailed { errors: es } => concat("GRUB: boot FAILED: ", join(map(es, e => grub_error_text(error: e)), "; ")) + } +} + fn console_text(c: ConsoleReading) -> String { match c { - ConsoleRetained { path: p, size: b, lines: n, firmware: f, dram: d, sockets: o, against_reference: cmp, modules: m, nvparam: nv } => - concat("capture ", p as String, " retained in this artifact: ", to_string(byte_size_count(b: b)), " bytes (stat), ", to_string(n), " line(s), within the ", to_string(byte_size_count(b: mtcollins1_boot_console_bound)), "-byte bound; ", firmware_report_text(report: f), "\n", ampere_dram_console_text(observation: d, expected_sockets: mtcollins1_census_sockets()), "\n", ampere_socket_console_text(o: o), ampere_socket_reference_comparison_text(c: cmp), "\n", kernel_module_decompression_text(o: m), "\n", ampere_nvparam_console_text(o: nv)) + ConsoleRetained { path: p, size: b, lines: n, firmware: f, firmware_error_lines: e, capture_digest: _, dram: d, checkpoints: cp, checkpoint_relations: rs, milestones: ms, sockets: o, against_reference: cmp, loader: g, linux: l, modules: m, nvparam: nv, span_readings: _ } => + concat("capture ", p as String, " retained in this artifact: ", to_string(byte_size_count(b: b)), " bytes (stat), ", to_string(n), " line(s), within the ", to_string(byte_size_count(b: mtcollins1_boot_console_bound)), "-byte bound; ", firmware_report_text(report: f), "; ", to_string(e), " firmware error line(s)\n", ampere_dram_console_text(observation: d, expected_sockets: mtcollins1_census_sockets()), "\n-- checkpoints --\n", ampere_checkpoint_console_text(o: cp), "\n", if count(rs) == 0 { "no checkpoint relation" } else { join(map(rs, r => ampere_checkpoint_relation_text(r: r)), "\n") }, "\n-- milestones --\n", if count(ms.sightings) == 0 { "none" } else { join(map(ms.sightings, x => console_milestone_sighting_text(s: x)), "\n") }, "\n", ampere_socket_console_text(o: o), ampere_socket_reference_comparison_text(c: cmp), "\n-- loader and linux --\n", grub_report_text(report: g), "\n", linux_boot_console_text(o: l), "\n", kernel_module_decompression_text(o: m), "\n", ampere_nvparam_console_text(o: nv)) ConsoleExceedsBound { path: p, size: b, bound: bound } => concat("capture ", p as String, " is ", to_string(byte_size_count(b: b)), " bytes (stat), OVER the ", to_string(byte_size_count(b: bound)), "-byte bound; the collector does not truncate, so the artifact carries it whole") ConsoleNotRetained { path: p } => @@ -1469,13 +1774,15 @@ fn sensors_text(s: SensorReading) -> String { } fn mtcollins1_boot_bundle_text(bundle: MtCollins1BootDiagnosticBundle) -> String { - let findings = mtcollins1_boot_bundle_findings(bundle: bundle) + let findings = mtcollins1_boot_findings(bundle: bundle) join([ "mtcollins1 boot diagnostics", concat("attempt=", attempt_identity_text(a: bundle.attempt)), concat("outcome=", bundle.outcome), - "== decisive findings ==", - if count(findings) == 0 { "none" } else { join(findings, "\n") }, + "== our best understanding of what happened (the power-on account) ==", + mtcollins1_power_on_account_text(a: findings.account), + "== what our harness did ==", + if count(findings.harness) == 0 { "nothing to report" } else { join(map(findings.harness, f => harness_finding_text(f: f)), "\n") }, "== bmc system event log ==", sel_snapshot_text(label: "before", s: bundle.sel_before), sel_snapshot_text(label: "after", s: bundle.sel_after), @@ -2129,10 +2436,32 @@ type ReferenceCaptureRead { text: String ok: Bool error: String + digest: Sha256FileDigest +} + +// THE REFERENCE'S SCOPE, for the opaque checkpoint relation (ruling R2): a continuation the reference +// shows is evidence only under the firmware and population it was taken with. Both are this +// capture's own record (gunbc.machine_intake_mtcollins1_memory_census_observation, 2026-09-10/11): +// two sockets, sixteen DIMMs, the firmware set installed before the 2026-10-02 update. +data mtcollins1_census_reference_firmware_scope: NonEmptyStr = "the vendor firmware set installed before the 2026-10-02 update" + +data mtcollins1_census_reference_configuration_scope: NonEmptyStr = "two sockets, sixteen DIMMs (the 2026-09-10/11 census population)" + +fn checkpoint_relations(checkpoints: AmpereCheckpointConsoleObservation, capture: Sha256FileDigest, reference: ReferenceCaptureRead) -> List { + flat_map(checkpoints.spans, span => match if reference.ok { + ampere_checkpoint_relation(span: span, capture: capture, reference: AmpereCheckpointReference { + path: mtcollins1_census_sol_artifact_path, digest: reference.digest, + firmware_scope: mtcollins1_census_reference_firmware_scope, configuration_scope: mtcollins1_census_reference_configuration_scope, + observation: ampere_checkpoint_console_observation(lines: host_capture_lines(capture: HostCaptureLiveStream { text: reference.text })), + }) + } else { + ampere_checkpoint_relation_unread(span: span, capture: capture, path: mtcollins1_census_sol_artifact_path, digest: reference.digest, + firmware_scope: mtcollins1_census_reference_firmware_scope, configuration_scope: mtcollins1_census_reference_configuration_scope, detail: reference.error) + } { Absent => [] Present { value: r } => [r] }) } // THE PURE DECISION over the capture's stat size and, when within the bound, its content. -fn mtcollins1_boot_console_reading(path: String, size: ByteSize, capture: String, read_ok: Bool, read_error: String, reference: ReferenceCaptureRead) -> ConsoleReading { +fn mtcollins1_boot_console_reading(path: String, size: ByteSize, capture: String, read_ok: Bool, read_error: String, capture_digest: Sha256FileDigest, reference: ReferenceCaptureRead) -> ConsoleReading { if path == "" { ConsoleNotConfigured } else if !console_path_retained(path: path) { @@ -2144,12 +2473,22 @@ fn mtcollins1_boot_console_reading(path: String, size: ByteSize, capture: String } else { let lines = host_capture_lines(capture: HostCaptureLiveStream { text: capture }) let sockets = ampere_socket_console_observation(lines: lines) + let checkpoints = ampere_checkpoint_console_observation(lines: lines) ConsoleRetained { - path: path as NonEmptyStr, size: size, lines: count(lines), firmware: firmware_console_report(lines: lines), dram: ampere_dram_console_observation(lines: lines), + path: path as NonEmptyStr, size: size, lines: count(lines), firmware: firmware_console_report(lines: lines), + firmware_error_lines: count(filter(lines, l => is_firmware_error_line(line: l) || is_firmware_err_code_line(line: l))), + capture_digest: capture_digest, + dram: ampere_dram_console_observation(lines: lines), + checkpoints: checkpoints, + checkpoint_relations: checkpoint_relations(checkpoints: checkpoints, capture: capture_digest, reference: reference), + milestones: mtcollins1_console_milestones(lines: lines), sockets: sockets, against_reference: ampere_socket_reference_comparison(reference_path: mtcollins1_census_sol_artifact_path, reference_text: reference.text, read_ok: reference.ok, read_error: reference.error, subject: sockets), + loader: grub_console_report(lines: lines), + linux: linux_boot_console_observation(lines: lines), modules: kernel_module_decompression_observation(lines: lines), nvparam: ampere_nvparam_console_observation(lines: lines), + span_readings: span_console_readings(lines: lines), } } } @@ -2170,7 +2509,9 @@ fn mtcollins1_boot_console(path: String) -> ConsoleReading { let r = Filesystem.Read(path: path) let reference = Filesystem.Read(path: mtcollins1_census_sol_artifact_path as String) mtcollins1_boot_console_reading(path: path, size: size, capture: r.content, read_ok: r.success, read_error: r.error, - reference: ReferenceCaptureRead { text: reference.content, ok: reference.success, error: reference.error }) + capture_digest: sha256sum_file_digest_via_shell(path: path as NonEmptyStr), + reference: ReferenceCaptureRead { text: reference.content, ok: reference.success, error: reference.error, + digest: sha256sum_file_digest_via_shell(path: mtcollins1_census_sol_artifact_path) }) } } } @@ -2194,7 +2535,7 @@ fn mtcollins1_boot_write_bundle(bundle: MtCollins1BootDiagnosticBundle) -> Bundl } fn mtcollins1_boot_outcome_after_bundle(outcome: ProcessExit, bundle: MtCollins1BootDiagnosticBundle, write: BundleWrite) -> ProcessExit { - let with_findings = mtcollins1_boot_outcome_with_findings(outcome: outcome, findings: mtcollins1_boot_bundle_findings(bundle: bundle)) + let with_findings = mtcollins1_boot_outcome_with_findings(outcome: outcome, findings: mtcollins1_boot_findings(bundle: bundle)) match write { BundleWritten => with_findings BundleWriteFailed { error: e } => diff --git a/dag/gunbc/machine_intake/mtcollins1_boot_run.dag b/dag/gunbc/machine_intake/mtcollins1_boot_run.dag index fdced7e59b0..93c564e2a5a 100644 --- a/dag/gunbc/machine_intake/mtcollins1_boot_run.dag +++ b/dag/gunbc/machine_intake/mtcollins1_boot_run.dag @@ -77,12 +77,13 @@ import gunbc.machine_intake_sol_hold { import gunbc.machine_intake_mtcollins1_smpro_observation { mtcollins1_smpro_probes, mtcollins1_smpro_probe_count } import extdeps.bmc.ipmi_boot_selection { IpmiBootSelection } import extdeps.bmc.ipmi_chassis_control { ChassisPowerObservation, chassis_observation_text } +import gunbc.machine_intake_mtcollins1_power_on_account { CarrierRead, CarrierReadEmpty, CarrierPartial, CarrierRefused, CarrierNotTaken, mtcollins1_power_on_account_notes, carrier_text, carrier_standing_text } import gunbc.machine_intake_mtcollins1_boot_diagnostic_bundle { MtCollins1BootDiagnosticBundle, SelSnapshotReading, SelSnapshotNotTaken, inventory_not_taken, SdrCacheReading, SdrCacheNotTaken, mtcollins1_boot_sdr_cache, SensorsNotTaken, mtcollins1_boot_sensors, SmproProbed, SmproNotTaken, mtcollins1_boot_sel_snapshot, mtcollins1_boot_inventory, mtcollins1_boot_console, - mtcollins1_boot_write_bundle, mtcollins1_boot_outcome_after_bundle, mtcollins1_boot_bundle_findings, bundle_write_receipt_line, + mtcollins1_boot_write_bundle, mtcollins1_boot_outcome_after_bundle, mtcollins1_boot_findings, harness_finding_text, bundle_write_receipt_line, mtcollins1_boot_diagnostics_allowance, AttemptIdentityReading, AttemptRunId, AttemptIdentityUnavailable, attempt_identity_text, SmproReading, BootParam5Reading, BootParam5NotTaken, BootParam5Read, BootParam5Unread, PowerAfterUnobserved, MtCollins1BootRunRecord, run_record_not_taken, MtCollins1MediaAttachRecord, MediaAttachNotAttempted, MtCollins1HandoffMedia, recheck_text, MtCollins1EndMedia, EndMediaNotTaken, EndMediaObserved, HandoffMediaNotReached, HandoffMediaWithheldAtRecheck, HandoffMediaAttempted, HandoffConfirmed, HandoffAttemptedUnconfirmed, end_media_is_owed, media_loss_after_handoff, outcome_attributing_media_loss, mtcollins1_boot_param5, mtcollins1_boot_chassis_power, BmcReachability, bmc_reachability_text, mtcollins1_boot_bmc_reachability, @@ -2546,6 +2547,7 @@ fn mtcollins1_boot_run_record(media: MtCollins1MediaAttachRecord, handoff_media: match stage { ActuationNotPoweredOn { reason: why } => MtCollins1BootRunRecord { + census_ended: false, media: media, handoff_media: handoff_media, end_media: end_media, @@ -2559,6 +2561,7 @@ fn mtcollins1_boot_run_record(media: MtCollins1MediaAttachRecord, handoff_media: } ActuationPoweredOn { selection: sel, smpro_power_on: on, smpro_census: census } => MtCollins1BootRunRecord { + census_ended: false, media: media, handoff_media: handoff_media, end_media: end_media, @@ -2572,6 +2575,7 @@ fn mtcollins1_boot_run_record(media: MtCollins1MediaAttachRecord, handoff_media: } ActuationCensusEnded { selection: sel, smpro_power_on: on, smpro_census: census } => MtCollins1BootRunRecord { + census_ended: true, media: media, handoff_media: handoff_media, end_media: end_media, @@ -2612,7 +2616,7 @@ fn mtcollins1_boot_conclude(attempt: MtCollins1BootAttempt) -> ProcessExit let bundle = mtcollins1_boot_bundle_of_attempt(attempt: attempt, run: run, capture_path: actions_variable_trimmed(name: mtcollins1_boot_sol_capture_path_env)) let write = mtcollins1_boot_write_bundle(bundle: bundle) let concluded = mtcollins1_boot_outcome_after_bundle(outcome: attempt.outcome, bundle: bundle, write: write) - let findings = mtcollins1_boot_bundle_findings(bundle: bundle) + let findings = mtcollins1_boot_findings(bundle: bundle) let exit_word = match concluded { ExitSuccess => "ok" ExitFailure { code: _, reason: _ } => "refused" } let receipt_body = join([ "authorization=workload identity of the fleet-converge run on main (operator ruling 2026-09-25); no per-run or standing approval; the unit hold is the interlock", @@ -2620,7 +2624,9 @@ fn mtcollins1_boot_conclude(attempt: MtCollins1BootAttempt) -> ProcessExit concat("exit=", exit_word), bundle_write_receipt_line(w: write), kvm_observer_receipt_line(rec: attempt.screen), - concat("findings=", if count(findings) == 0 { "none" } else { join(findings, "; ") }), + concat("account=", join(mtcollins1_power_on_account_notes(a: findings.account), "; ")), + concat("coverage=", join(map(filter(findings.account.coverage, c => match c.standing { CarrierRead => false CarrierReadEmpty => true CarrierPartial { detail: _ } => true CarrierRefused { cause: _ } => true CarrierNotTaken { reason: _ } => true }), c => concat(carrier_text(c: c.carrier), ": ", carrier_standing_text(s: c.standing))), "; ")), + concat("harness=", if count(findings.harness) == 0 { "none" } else { join(map(findings.harness, f => harness_finding_text(f: f)), "; ") }), ], "\n") let kept = Filesystem.Write(path: mtcollins1_boot_receipt_path, content: receipt_body) if !kept.success { diff --git a/dag/gunbc/machine_intake/mtcollins1_console_milestone_observation.dag b/dag/gunbc/machine_intake/mtcollins1_console_milestone_observation.dag new file mode 100644 index 00000000000..e09cdf072ea --- /dev/null +++ b/dag/gunbc/machine_intake/mtcollins1_console_milestone_observation.dag @@ -0,0 +1,158 @@ +module gunbc.machine_intake_mtcollins1_console_milestone_observation + +import std.types { Bool, Int, List, NonEmptyStr, String } +import std.algebra { trim } +import v2.std.optional { Present } +import extdeps.bootloader.grub { grub_console_banner_prefix, grub_failed_default_and_fallback, grub_error_prefix } +import extdeps.linux.module_decompress { linux_module_zstd_decompress_failed_stem } +import gunbc.machine_intake_pre_os_bringup_verdict { is_firmware_error_line, is_firmware_err_code_line } +import extdeps.linux.efi_stub { linux_efi_stub_line_prefix, linux_efi_stub_initrd_load_failed_prefix } +import extdeps.linux.boot_console { linux_banner_prefix, linux_kernel_panic_prefix, linux_kernel_panic_end_marker } +import gunbc.machine_intake_ampere_dram_console_observation { ampere_dram_fw_version_marker, ampere_dram_console_line } +import gunbc.machine_intake_ampere_socket_console_observation { ampere_summary_rc_marker } +import gunbc.machine_intake_ampere_checkpoint_console_observation { AmpereCheckpointFold, AmpereCheckpointSpan, checkpoint_fold_step, empty_span } + +// THE CONSOLE MILESTONES ONE mtcollins1 CAPTURE SHOWS, as an observation receipt of this repository +// (docs/plans/power-on-sequence-model.md, ruling Q1). +// +// A MILESTONE IS AN EXACT, COMPONENT-OWNED LINE, AND IT ESTABLISHES ONLY THAT THAT COMPONENT'S CODE +// RAN FAR ENOUGH TO PRINT IT. It never establishes an extdeps.ampere.scp_diagnostic AmpereBootStage, +// a stage's completion, the component's health, or a successful handoff to the next one: those come +// from an SMpro register reading (gunbc.machine_intake_mtcollins1_power_on_account) or not at all. +// Each milestone carries the exact line, which parser recognised it (the marker it matched), its +// index in the capture and the console span it falls in, so a later reader can re-derive it. +// +// THE MARKERS ARE THE OWNING MODULES' OWN DATA, imported rather than restated: the DRAM firmware +// banner (gunbc.machine_intake_ampere_dram_console_observation), the firmware summary's first line +// (gunbc.machine_intake_ampere_socket_console_observation), the GRUB banner (extdeps.bootloader.grub), +// the EFI stub's prefix and the kernel banner (extdeps.linux.efi_stub, extdeps.linux.boot_console). +// The two UEFI lines below have no other home in this corpus: +// +// - The AMI Aptio setup prompt is transcribed from our own captures (it is AMI product output this +// repository observed, not a published specification): run 37069907299's SOL capture and the +// committed artifacts/bmc/mtcollins1-hw-census-sol.log both print it verbatim. +// - The UEFI shell banner is edk2's STR_VER_OUTPUT_MAIN_SHELL, "UEFI %s Shell v%d.%d" +// (github.com/tianocore/edk2 ShellPkg/Application/Shell/Shell.uni), which mtcollins1 printed as +// "UEFI Interactive Shell v2.2" in eager-gull-22's 2026-10-03 step-3 capture. The prompt is the +// weaker milestone (UEFI code ran far enough to offer setup); the shell is the stronger (UEFI +// reached its boot manager's shell); neither says anything about the next boot option. +data ami_aptio_setup_prompt_marker: NonEmptyStr = "Press or to Entering the Setup." + +data edk2_shell_banner_prefix: NonEmptyStr = "UEFI " + +data edk2_shell_banner_infix: NonEmptyStr = " Shell v" + +type ConsoleMilestone + = DramFirmwareBannerObserved + | FirmwareSummaryObserved + | UefiConsolePromptObserved + | UefiShellObserved + | GrubBannerObserved + | EfiStubLineObserved + | KernelBannerObserved + +type ConsoleMilestoneSighting { + milestone: ConsoleMilestone + line: NonEmptyStr + marker: NonEmptyStr + line_index: Int + span: Int +} + +// THE SPAN A MILESTONE FALLS IN IS THE CHECKPOINT READER'S, NOT A SECOND COUNT: the fold below runs +// gunbc.machine_intake_ampere_checkpoint_console_observation checkpoint_fold_step itself and reads +// the span index it is in after each line, so a milestone and a checkpoint token in one span are in +// one span by construction, whatever rule that reader uses for the span before the first banner. +// WHERE A STATED FAILURE WAS PRINTED. The whole-capture readers (pre_os firmware statements, GRUB, the +// EFI stub, the kernel, module decompression) decide WHAT a failure is; this reader records EVERY line +// of each kind, with its index and span, using those modules' own markers and predicates, so +// the power-on account can bind the failure to a cycle. A failure line is never a milestone. +type ConsoleFailureLine + = FirmwareErrorStatementLine + | GrubFailureLine + | InitrdLoadFailureLine + | KernelPanicLine + | ModuleDecompressionRefusalLine + +type ConsoleFailureSighting { + kind: ConsoleFailureLine + line: NonEmptyStr + line_index: Int + span: Int +} + + +type ConsoleMilestoneObservation { + sightings: List + failures: List +} + +fn failure_kind_of(line: String) -> ConsoleFailureLine? { + if is_firmware_error_line(line: line) || is_firmware_err_code_line(line: line) { Present { value: FirmwareErrorStatementLine } } + else if string_contains(s: line, pattern: grub_failed_default_and_fallback as String) || starts_with(s: line, prefix: grub_error_prefix as String) { Present { value: GrubFailureLine } } + else if string_contains(s: line, pattern: linux_efi_stub_initrd_load_failed_prefix as String) { Present { value: InitrdLoadFailureLine } } + else if string_contains(s: line, pattern: linux_kernel_panic_prefix as String) && !string_contains(s: line, pattern: linux_kernel_panic_end_marker as String) { Present { value: KernelPanicLine } } + else if string_contains(s: line, pattern: linux_module_zstd_decompress_failed_stem as String) { Present { value: ModuleDecompressionRefusalLine } } + else { none } +} + +fn milestone_marker_of(line: String) -> ConsoleMilestoneSighting? { + let banner_shell = starts_with(s: line, prefix: edk2_shell_banner_prefix as String) && string_contains(s: line, pattern: edk2_shell_banner_infix as String) + if string_contains(s: line, pattern: ampere_dram_fw_version_marker) { Present { value: sighting(m: DramFirmwareBannerObserved, line: line, marker: ampere_dram_fw_version_marker) } } + else if starts_with(s: line, prefix: ampere_summary_rc_marker) { Present { value: sighting(m: FirmwareSummaryObserved, line: line, marker: ampere_summary_rc_marker) } } + else if string_contains(s: line, pattern: ami_aptio_setup_prompt_marker as String) { Present { value: sighting(m: UefiConsolePromptObserved, line: line, marker: ami_aptio_setup_prompt_marker as String) } } + else if banner_shell { Present { value: sighting(m: UefiShellObserved, line: line, marker: concat(edk2_shell_banner_prefix as String, "%s", edk2_shell_banner_infix as String)) } } + else if string_contains(s: line, pattern: grub_console_banner_prefix as String) { Present { value: sighting(m: GrubBannerObserved, line: line, marker: grub_console_banner_prefix as String) } } + else if string_contains(s: line, pattern: linux_efi_stub_line_prefix as String) { Present { value: sighting(m: EfiStubLineObserved, line: line, marker: linux_efi_stub_line_prefix as String) } } + else if string_contains(s: line, pattern: linux_banner_prefix as String) { Present { value: sighting(m: KernelBannerObserved, line: line, marker: linux_banner_prefix as String) } } + else { none } +} + +fn sighting(m: ConsoleMilestone, line: String, marker: String) -> ConsoleMilestoneSighting { + ConsoleMilestoneSighting { milestone: m, line: line as NonEmptyStr, marker: marker as NonEmptyStr, line_index: 0, span: 0 } +} + +type MilestoneFold { + index: Int + spans: AmpereCheckpointFold + sightings: List + failures: List +} + +fn milestone_fold_step(acc: MilestoneFold, raw: String) -> MilestoneFold { + let spans = checkpoint_fold_step(acc: acc.spans, raw: raw) + let line = trim(ampere_dram_console_line(raw: raw)) + let seen = match milestone_marker_of(line: line) { + Absent => acc.sightings + Present { value: s } => concat(acc.sightings, [ConsoleMilestoneSighting { milestone: s.milestone, line: s.line, marker: s.marker, line_index: acc.index, span: spans.current.index }]) + } + let failed = match failure_kind_of(line: line) { + Absent => acc.failures + Present { value: k } => concat(acc.failures, [ConsoleFailureSighting { kind: k, line: line as NonEmptyStr, line_index: acc.index, span: spans.current.index }]) + } + MilestoneFold { index: acc.index + 1, spans: spans, sightings: seen, failures: failed } +} + +fn mtcollins1_console_milestones(lines: List) -> ConsoleMilestoneObservation { + let none_seen: List = [] + let none_failed: List = [] + let none_closed: List = [] + let done = fold(lines, init: MilestoneFold { index: 0, spans: AmpereCheckpointFold { closed: none_closed, current: empty_span(index: 0, opened_by_banner: false) }, sightings: none_seen, failures: none_failed }, f: (acc, l) => milestone_fold_step(acc: acc, raw: l)) + ConsoleMilestoneObservation { sightings: done.sightings, failures: done.failures } +} + +fn console_milestone_name(m: ConsoleMilestone) -> String { + match m { + DramFirmwareBannerObserved => "DRAM firmware banner" + FirmwareSummaryObserved => "firmware boot summary" + UefiConsolePromptObserved => "UEFI setup prompt" + UefiShellObserved => "UEFI shell" + GrubBannerObserved => "GRUB banner" + EfiStubLineObserved => "Linux EFI stub line" + KernelBannerObserved => "Linux kernel banner" + } +} + +fn console_milestone_sighting_text(s: ConsoleMilestoneSighting) -> String { + concat(console_milestone_name(m: s.milestone), " at line ", to_string(s.line_index), " (span ", to_string(s.span), "): ", s.line as String) +} diff --git a/dag/gunbc/machine_intake/mtcollins1_physical_orientation.dag b/dag/gunbc/machine_intake/mtcollins1_physical_orientation.dag index c63171c3721..ad809a676a7 100644 --- a/dag/gunbc/machine_intake/mtcollins1_physical_orientation.dag +++ b/dag/gunbc/machine_intake/mtcollins1_physical_orientation.dag @@ -826,9 +826,9 @@ fn join_outcome_text(outcome: MtCollins1SecondaryJoinOutcome) -> String { match outcome { SecondaryJoined { inter_socket_links_reported: l } => concat("SecondaryJoined { inter_socket_links_reported: ", bool_text(b: l), " }") SecondaryJoinUnclassified { reason: why } => concat("SecondaryJoinUnclassified: ", why) - SecondaryJoinTimeout { secondary_last_stage: st, inter_socket_links_reported: l, secondary_host_checkpoints: cps } => + SecondaryJoinTimeout { secondary_last_stage: st, inter_socket_links_reported: l } => concat(concat(concat("SecondaryJoinTimeout { secondary_last_stage: ", secondary_stage_text(st: st)), ", socket 0 continued alone"), - concat(concat(", inter_socket_links_reported: ", bool_text(b: l)), concat(", secondary_host_checkpoints: ", if list_length(items: cps) == 0 { "none }" } else { concat(join(cps, ", "), " }") }))) + concat(", inter_socket_links_reported: ", concat(bool_text(b: l), " }"))) } } diff --git a/dag/gunbc/machine_intake/mtcollins1_power_on_account.dag b/dag/gunbc/machine_intake/mtcollins1_power_on_account.dag new file mode 100644 index 00000000000..19122f798ac --- /dev/null +++ b/dag/gunbc/machine_intake/mtcollins1_power_on_account.dag @@ -0,0 +1,1287 @@ +module gunbc.machine_intake_mtcollins1_power_on_account + +import v2.std.algebra { any, filter } +import std.types { Bool, Int, List, NonEmptyStr, String } +import std.nat { Nat } +import std.checked_arithmetic { checked_int_to_nat } +import v2.std.optional { Present } +import std.decl_ref { DeclarationRef, decl_ref } +import extdeps.tools.sha256sum { Sha256FileDigest, Sha256FileDigestUnavailable } +import extdeps.ampere.scp_diagnostic { + AmpereBootStatus, NotStarted, Started, Completed, Failed, +} +import extdeps.ampere.smpro_register { + SmproBootProgress, SmproBootProgressRead, SmproBootProgressRefused, SmproStatusDefined, SmproStatusUndefined, + smpro_boot_progress_of, smpro_cur_bootstage_reg, smpro_bootstage_reg, smpro_stage_text, smpro_status_text, smpro_cur_bootstage_of, smpro_bootstage_of, +} +import extdeps.bmc.ipmi_sel { + SystemBootInitiatedOffset, InitiatedByPowerUp, InitiatedByHardReset, InitiatedByWarmReset, UserRequestedPxeBoot, + AutomaticBootToDiagnostic, OsInitiatedHardReset, OsInitiatedWarmReset, SystemRestart, + FirmwareError, FirmwareHang, FirmwareProgress, +} +import extdeps.bmc.ipmi_sdr { SdrTypeLine } +import extdeps.bmc.redfish_memory_inventory { RedfishMemoryMember, RedfishProcessorMember } +import extdeps.bootloader.grub { grub_console_report, GrubConsoleReport, GrubNothingStated, GrubBannerOnly, GrubStillTrying, GrubBootFailed, GrubError, grub_error_text } +import gunbc.machine_intake_pre_os_bringup_verdict { + SelWindowEvent, BootRestart, FirmwareProgressRecord, OemTimestamped, OtherStandardEvent, + firmware_console_report, FirmwareConsoleReport, NoTerminalStated, UndecodedErrorCodesOnly, FirmwareErrorStatementUnrecognized, TrainingRefusedByFirmware, +} +import gunbc.machine_intake_mtcollins1_smpro_observation { SmproProbeOutcome, SmproSocketRecords, SmproRecordSet, smpro_socket_word, smpro_outcome_word, mtcollins1_smpro_socket_records, outcome_socket } +import extdeps.ampere.smpro_internal_error { + SmproInternalRecord, SmproProcessor, SmproProcessorSmpro, SmproProcessorPmpro, + SmproRecordGate, PendingAndDecoded, NotPending, GpiUnavailable, PendingWithClearOrMissingType, TypePresentWithoutPending, + smpro_internal_record_text, smpro_record_gate_lines, +} +import gunbc.machine_intake_mtcollins1_socket1_investigation_observation { + MtCollins1SecondaryJoinReading, MtCollins1SecondaryJoinOutcome, SecondaryJoined, SecondaryJoinTimeout, SecondaryJoinUnclassified, + secondary_join_outcome, +} +import gunbc.machine_intake_ampere_dram_console_observation { + AmpereDramConsoleObservation, AmpereDramBlockNotPrinted, AmpereDramBlockObserved, ampere_dram_untrained_sockets, +} +import gunbc.machine_intake_ampere_checkpoint_console_observation { + AmpereCheckpointConsoleObservation, AmpereCheckpointSpan, AmpereCheckpointRelation, AmpereCheckpointToken, + ampere_checkpoint_relation_text, ampere_checkpoint_last_token, ampere_console_span_lines, +} +import gunbc.machine_intake_mtcollins1_console_milestone_observation { + ConsoleMilestone, ConsoleMilestoneObservation, ConsoleMilestoneSighting, ConsoleFailureLine, ConsoleFailureSighting, + FirmwareErrorStatementLine, GrubFailureLine, InitrdLoadFailureLine, KernelPanicLine, ModuleDecompressionRefusalLine, + DramFirmwareBannerObserved, FirmwareSummaryObserved, UefiConsolePromptObserved, UefiShellObserved, GrubBannerObserved, EfiStubLineObserved, KernelBannerObserved, + console_milestone_name, console_milestone_sighting_text, +} +import gunbc.machine_intake_ampere_socket_console_observation { AmpereSocketConsoleObservation, AmpereSummaryNotPrinted, AmpereSummaryObserved } +import gunbc.machine_intake_linux_boot_console_observation { + LinuxBootConsoleObservation, InitrdFailureNotPrinted, InitrdLoadFailed, EfiStubStatus, KernelPanicNotPrinted, KernelPanicked, efi_stub_status_text, initrd_reading_of, panic_reason_of, +} +import gunbc.machine_intake_kernel_module_decompression_observation { + KernelModuleDecompressionObservation, NoRefusalRecorded, RefusalRecorded, kernel_module_decompression_text, kernel_module_decompression_observation, +} + +// THE MT. COLLINS POWER-ON ACCOUNT: our best understanding of what happened when one attempt turned +// the machine on (docs/plans/power-on-sequence-model.md; operator requirement 2026-10-03). It is the +// one fold over a boot's evidence, and it is never a pass/fail bit. +// +// THREE STANDINGS THAT NEVER STAND IN FOR ONE ANOTHER. What the HOST did (cycles, milestones, socket +// progress, the ending, degradations, errors); what we OBSERVED (one coverage standing per carrier); +// and whether our WORKFLOW worked (media, handoff, override, bundle write -- the bundle's typed +// HarnessFinding, composed beside this account in gunbc.machine_intake_mtcollins1_boot_diagnostic_bundle, +// which owns those records). A coverage gap or a workflow defect never chooses the host ending: an +// empty SOL beside a census END marker is ReachedCensus plus an SOL gap. +// +// STAGES COME FROM SMpro ONLY (rulings R2, Q1). An extdeps.ampere.scp_diagnostic AmpereBootStage is +// established only by an SMpro register pair the Ampere ABI's own rule decodes (smpro_boot_progress_of); +// the console establishes component MILESTONES and opaque checkpoint RELATIONS, never a stage. The +// structure enforces it: only SocketProgressReading can carry an SmproBootProgress. +// +// CYCLES DO NOT DEPEND ON REACHING DRAM. Cycle 1 opens on the confirmed power-on; later cycles open on +// SEL restart records; a DRAM firmware banner binds a console span to a cycle and proves a boot, but a +// boot that stopped before printing one is still a cycle. The production console is not timestamped +// and the SEL clock is not joined to the observer's, so console spans bind to cycles only when the +// counts agree; otherwise the binding is an open question, never a guess. For the same reason SMpro +// passes are reported per pass at session level, not placed into a cycle. +// +// WHY IS PRIVATE. The checkpoint word's fields, the restart mechanism and the reset cause's decode +// come from sources this repository does not publish; their public standing is an open question here, +// and the gunbc-private refinement slice consumes this account to close them. +// +// MONOLITHIC FOR MT. COLLINS: the plan's section 9 records the seams for Mt. Jade and x86. + +// ── THE ATTEMPT CONFIGURATION RECEIPT ─────────────────────────────────────────────────────────── +// FROZEN BEFORE POWER-ON AND BOUND TO ONE ATTEMPT; the account reads nothing else about the +// configuration. Historical rows (the 2026-09-10/11 census slots, the 2026-09-23 orientation report) +// map labels to topology and never supply current occupancy, so a field nobody recorded for THIS +// attempt is NotRecorded and every question needing it is open. +type StimulusRequest + = StimulusRequested { description: NonEmptyStr } + | NoStimulusRequested + | StimulusRequestNotRecorded { reason: NonEmptyStr } + +// A request is not evidence that it happened: manual CPU or DIMM work is applied only by a human +// completion or inspection receipt the workflow consumed. +type StimulusApplication + = StimulusAppliedByReceipt { receipt: NonEmptyStr, attested_by: NonEmptyStr } + | StimulusApplicationNotRecorded { reason: NonEmptyStr } + +type SocketCpuRow { + socket: Int + installed: Bool +} + +type SlotRow { + label: NonEmptyStr + socket: Int + populated: Bool +} + +type PopulationReading + = PopulationLiveObserved { rows: List, source: NonEmptyStr } + | PopulationOperatorAttested { rows: List, receipt: NonEmptyStr } + | PopulationConflicted { readings: List } + | PopulationNotRecorded { reason: NonEmptyStr } + +type FirmwareReadbackRow { + component: NonEmptyStr + version: NonEmptyStr +} + +type FirmwareReadbacks + = FirmwareReadBeforeActuation { rows: List } + | FirmwareReadbacksNotRecorded { reason: NonEmptyStr } + +// THE EXPECTED TOPOLOGY THIS ATTEMPT IS JUDGED AGAINST, recorded for the attempt or not at all: a +// historical census row is not an expectation of this attempt, so with nothing recorded no topology +// degradation is minted and the question stays open (law 6). +type ExpectedTopology + = ExpectedSockets { sockets: List, source: NonEmptyStr } + | ExpectedTopologyNotRecorded { reason: NonEmptyStr } + +type AttemptConfigurationReceipt { + subject: NonEmptyStr + attempt: NonEmptyStr + expected: ExpectedTopology + requested: StimulusRequest + applied: StimulusApplication + cpus: PopulationReading + dimms: PopulationReading + firmware: FirmwareReadbacks +} + +// ── THE EVIDENCE ──────────────────────────────────────────────────────────────────────────────── +// Every field is an existing typed reading or a standing the bundle derives from one; this module +// re-reads nothing. +type Carrier + = SolConsole + | KvmScreen + | SmproRegisters + | SmproErrorRegisters + | SystemEventLog + | SensorTable + | ChassisPower + | RedfishInventory + | VirtualMedia + | HostCaptureMarkers + +type CarrierStanding + = CarrierRead + | CarrierReadEmpty + | CarrierPartial { detail: NonEmptyStr } + | CarrierRefused { cause: NonEmptyStr } + | CarrierNotTaken { reason: NonEmptyStr } + +type CarrierCoverage { + carrier: Carrier + standing: CarrierStanding +} + +type PowerOnHandoff + = HandoffPoweredOn + | HandoffNotConfirmed { reason: NonEmptyStr } + +type SmproPassEvidence { + label: NonEmptyStr + outcomes: List +} + +// WHAT ONE CONSOLE SPAN STATED, by the owning readers run over that span's lines alone (the bundle runs +// them over gunbc.machine_intake_ampere_checkpoint_console_observation ampere_console_span_lines), so a +// statement printed in two spans is two statements, each in its own span. +type SpanConsoleReading { + span: Int + firmware: FirmwareConsoleReport + loader: GrubConsoleReport + modules: KernelModuleDecompressionObservation +} + +fn span_console_readings(lines: List) -> List { + map(ampere_console_span_lines(lines: lines), g => SpanConsoleReading { + span: g.span, firmware: firmware_console_report(lines: g.lines), loader: grub_console_report(lines: g.lines), modules: kernel_module_decompression_observation(lines: g.lines), + }) +} + +type ConsoleEvidence + = ConsoleRead { + capture: Sha256FileDigest, + firmware: FirmwareConsoleReport, + firmware_error_lines: Int, + dram: AmpereDramConsoleObservation, + checkpoints: AmpereCheckpointConsoleObservation, + relations: List, + milestones: ConsoleMilestoneObservation, + sockets: AmpereSocketConsoleObservation, + loader: GrubConsoleReport, + linux: LinuxBootConsoleObservation, + modules: KernelModuleDecompressionObservation, + span_readings: List, + } + | ConsoleNotRead + +type SelEvidence + = SelEventsRead { events: List } + | SelEventsNotRead + +// THE BMC'S IPMI WATCHDOG ACROSS THE ATTEMPT: a candidate reset initiator only while armed with an +// action, so the reading carries its state, not an opinion (moved here from the deleted +// gunbc.machine_intake_boot_outcome, which #13041 authored for the 2026-10-03 loop). +type BmcWatchdogDisposition + = WatchdogStoppedNoAction + | WatchdogArmed { action: NonEmptyStr } + | WatchdogUnobserved { reason: NonEmptyStr } + +// THE ANOMALIES THE BMC'S OWN VIEW REPORTS. Each is decided by the module that owns its reading +// (the sensor rule in gunbc.machine_intake_mtcollins1_bmc_sensor_observation mtcollins1_sensor_anomalies; +// the inventory and SEL rules in the bundle) and only placed here. +type BmcAnomaly + = ProcessorMissingOnExpectedSocket { socket: Int } + | ProcessorSocketDuplicated { socket: Int, members: List } + | ProcessorSocketUnreadable { member: RedfishProcessorMember } + | ProcessorNotNominal { member: RedfishProcessorMember } + | ProcessorAbsentOnExpectedSocket { member: RedfishProcessorMember, socket: Int } + | MemoryBelowRecordedPopulation { socket: Int, present: Int, populated: Int } + | MemoryNotNominal { member: RedfishMemoryMember } + | SensorNotNominal { line: SdrTypeLine } + | SensorLineWithoutReadingNotNominal { text: NonEmptyStr } + | SocketSensorsAbsent { socket: Int } + | SelHardwareEvent { event: SelWindowEvent } + +type PowerOnEvidence { + configuration: AttemptConfigurationReceipt + handoff: PowerOnHandoff + smpro: List + console: ConsoleEvidence + sel: SelEvidence + watchdog: BmcWatchdogDisposition + bmc_anomalies: List + census_ended: Bool + coverage: List +} + +// ── THE ACCOUNT ───────────────────────────────────────────────────────────────────────────────── +type PowerOnSubject + = PlatformBmc + | PlatformChassisPower + | SocketSubject { socket: Int } + | SecondaryJoin + | DramFirmware + | Uefi + | BootLoader + | KernelStub + | Kernel + | Census + +// THE ONLY CARRIER OF A FIRMWARE STAGE. A refused pair is evidence and an open question, never a +// stage; a pair that could not be read says which register, as the dossier does. +type SocketProgressReading + = ProgressRead { cur_bootstage_word: Int, bootstage_word: Int, progress: SmproBootProgress } + | ProgressNotRead { reason: NonEmptyStr } + +type SocketProgressAccount { + pass: NonEmptyStr + socket: Int + reading: SocketProgressReading +} + +// WHAT EACH SEL BOOT RECORD IS, AS A BOUNDARY. The BMC's clock is not joined to ours, so a record is +// never coalesced by time: it is classified by its offset and its order. A first power-up record +// CORROBORATES the power-on (one boundary, not two); a later boot record is a DISTINCT boundary. A +// first boot record that is a restart after a confirmed power-on is AMBIGUOUS -- this MegaRAC can +// log the power-on's own first boot that way (step 1, 2026-10-03) -- so it opens no cycle and is an +// open question, never a second cycle and never evidence of a repeated restart. +type SelBootBoundaryStanding + = PowerOnCorroboration { record: SelWindowEvent } + | DistinctRestart { record: SelWindowEvent } + | BoundaryAmbiguous { record: SelWindowEvent, reason: NonEmptyStr } + +// ONLY TYPED POWER-ON OR RESET EVIDENCE OPENS A CYCLE. A console banner proves the DRAM firmware +// printed, not that a cycle began; a banner with no boundary is an unbound span and an open question. +type CycleBoundary + = OpenedByPowerOn + | OpenedBySelPowerUp { record: SelWindowEvent } + | OpenedBySelRestart { record: SelWindowEvent } + +type ConsoleSpanBinding + = SpanBound { span: Int } + | NoConsoleSpan + | SpanBindingAmbiguous + +// A MILESTONE WITH ITS PROVENANCE: the sighting, the digest of the capture it was read from, and the +// reader that read it, so one line from two differently digested captures stays two facts. +type MilestoneEvidence { + sighting: ConsoleMilestoneSighting + capture: Sha256FileDigest + reader: DeclarationRef +} + +// THE READER, NAMED BY DECLARATION: a citation the namespace tree can resolve, so a renamed reader is +// a resolution defect rather than a stale string. +data mtcollins1_console_milestone_reader: DeclarationRef = decl_ref(module_path: "gunbc.machine_intake_mtcollins1_console_milestone_observation", decl_name: "mtcollins1_console_milestones") + +type BootCycleAccount { + index: Int + boundary: CycleBoundary + console: ConsoleSpanBinding + milestones: List + ended_after: AmpereCheckpointRelation? +} + +type DecodedBootError + = FirmwareStatement { report: FirmwareConsoleReport } + | SmproReportedFailure { socket: Int, pass: NonEmptyStr, cur_bootstage_word: Int, bootstage_word: Int } + | SmproRecordStated { socket: Int, processor: SmproProcessor, pass: NonEmptyStr, record: SmproInternalRecord } + | SelFirmwareFault { event: SelWindowEvent } + | BootLoaderFailed { errors: List } + | InitrdLoadFailedByStub { status: EfiStubStatus } + | KernelPanic { reason: NonEmptyStr } + | KernelModuleRefused { observation: KernelModuleDecompressionObservation } + +// A STATED FAILURE WITH ITS CYCLE, when the evidence binds it to one: a console failure line located +// in a bound span. SMpro and SEL failures are read per pass and per window, never per cycle, so their +// cycle is absent -- and an error without a cycle is history; it never chooses the ending. +type LocatedError { + cycle: Int? + subject: PowerOnSubject + error: DecodedBootError +} + +type SmproWarningAccount { + socket: Int + processor: SmproProcessor + pass: NonEmptyStr + record: SmproInternalRecord +} + +type Degradation + = SocketNotJoined { outcome: MtCollins1SecondaryJoinOutcome } + | ActiveSocketsBelowExpected { active: Int, expected: Int } + | DramSocketUntrained { socket: Int } + | PendingErrorRecord { socket: Int, processor: SmproProcessor, record: SmproInternalRecord } + +type PlacedAnomaly + = BmcReported { subject: PowerOnSubject, anomaly: BmcAnomaly } + | DramRowMalformed { subsection: NonEmptyStr, line: String } + | CheckpointLinesMalformed { span: Int, lines: List } + | RecordUngated { socket: Int, processor: SmproProcessor, pass: NonEmptyStr, gate: SmproRecordGate } + | RecordGateContradiction { socket: Int, processor: SmproProcessor, pass: NonEmptyStr, gate: SmproRecordGate } + | SmproEventNonzero { socket: Int, pass: NonEmptyStr, event: String } + +type ResetCauseObservation + = ResetCauseUnobserved { searched: NonEmptyStr } + | ResetCauseObserved { raw: Int } + +data mt_collins_reset_cause_search: NonEmptyStr = "SMpro registers 0x00-0xFF on both sockets (branch mtcollins1-captures-2026-10-03, sweep-20261003T014255Z): no reset-cause window" + +type RestartCadence + = CadenceTimed { period_seconds: Int, cycles: Int } + | CadenceCounted { cycles: Int } + +type HostEnding + = ReachedCensus + | StoppedWithStatedFailure { subject: PowerOnSubject, error: DecodedBootError } + | RestartedRepeatedly { + cycles: Int, + last_cycle_ended_after: AmpereCheckpointRelation?, + cadence: RestartCadence, + console_errors: Int, + sel_restarts: Int, + watchdog: BmcWatchdogDisposition, + reset_cause: ResetCauseObservation, + } + | StoppedWithoutRestart { last_milestone: MilestoneEvidence?, ended_after: AmpereCheckpointRelation?, console_errors: Int, restarts_before: Int } + | HostEndingNotEstablished + +type ConfigurationField + = ConfiguredExpectedTopology + | ConfiguredStimulus + | ConfiguredCpuPopulation + | ConfiguredDimmPopulation + | ConfiguredFirmware + +type OpenQuestionKind + = ResetCauseNotPubliclyReadable + | RetryStatePersistence + | CheckpointMeaningNotPubliclyModeled + | SmproPairRefused { socket: Int, pass: NonEmptyStr } + | RestartCadenceNotTimed + | ConfigurationNotRecorded { field: ConfigurationField } + | ConfigurationConflicted { field: ConfigurationField } + | CarrierNotRead { carrier: Carrier } + | ConsoleSpansUnbound { spans: Int, cycles: Int } + | UngatedRecordUnexplained { socket: Int } + | BootBoundaryAmbiguous { record_id: Int } + +type OpenQuestion { + subject: PowerOnSubject + question: OpenQuestionKind +} + +type PowerOnAccount { + configuration: AttemptConfigurationReceipt + cycles: List + boundaries: List + socket_progress: List + join: MtCollins1SecondaryJoinOutcome + ending: HostEnding + degradations: List + errors: List + warnings: List + anomalies: List + coverage: List + open: List +} + +// ── SOCKET PROGRESS (SMpro only) ──────────────────────────────────────────────────────────────── +fn register_word(outcomes: List, socket: Int, register: NonEmptyStr) -> Int? { + match smpro_socket_word(outcomes: outcomes, socket: socket, register: register) { Absent => none Present { value: o } => smpro_outcome_word(o: o) } +} + +fn socket_progress(pass: SmproPassEvidence, socket: Int) -> SocketProgressAccount { + let cur = register_word(outcomes: pass.outcomes, socket: socket, register: smpro_cur_bootstage_reg.name) + let rep = register_word(outcomes: pass.outcomes, socket: socket, register: smpro_bootstage_reg.name) + SocketProgressAccount { pass: pass.label, socket: socket, reading: match cur { + Absent => ProgressNotRead { reason: "CUR_BOOTSTAGE was not read in this pass" } + Present { value: c } => match rep { + Absent => ProgressNotRead { reason: "BOOTSTAGE was not read beside CUR_BOOTSTAGE in this pass" } + Present { value: r } => ProgressRead { cur_bootstage_word: c, bootstage_word: r, progress: smpro_boot_progress_of(cur_bootstage_word: c, bootstage_word: r) } + } + } } +} + +fn progress_failed(p: SmproBootProgress) -> Bool { + match p { + SmproBootProgressRead { current: _, reported: b } => match b.status { SmproStatusDefined { status: s } => match s { Failed => true NotStarted => false Started => false Completed => false } SmproStatusUndefined { code: _ } => false } + SmproBootProgressRefused { cur_bootstage_word: _, bootstage_word: _ } => false + } +} + +fn smpro_failures(progress: List) -> List { + flat_map(progress, p => match p.reading { + ProgressRead { cur_bootstage_word: c, bootstage_word: r, progress: g } => + if progress_failed(p: g) { [LocatedError { cycle: none, subject: SocketSubject { socket: p.socket }, error: SmproReportedFailure { socket: p.socket, pass: p.pass, cur_bootstage_word: c, bootstage_word: r } }] } else { [] } + ProgressNotRead { reason: _ } => [] + }) +} + +fn refused_pair_questions(progress: List) -> List { + flat_map(progress, p => match p.reading { + ProgressRead { cur_bootstage_word: _, bootstage_word: _, progress: g } => match g { + SmproBootProgressRefused { cur_bootstage_word: _, bootstage_word: _ } => + [OpenQuestion { subject: if p.socket == 0 { SocketSubject { socket: 0 } } else { SecondaryJoin }, question: SmproPairRefused { socket: p.socket, pass: p.pass } }] + SmproBootProgressRead { current: _, reported: _ } => [] + } + ProgressNotRead { reason: _ } => [] + }) +} + +fn any_progress_read(progress: List) -> Bool { + any(progress, p => match p.reading { ProgressRead { cur_bootstage_word: _, bootstage_word: _, progress: _ } => true ProgressNotRead { reason: _ } => false }) +} + +// ── THE SECOND SOCKET'S JOIN (#13025's authority, consumed) ───────────────────────────────────── +fn digest_label(d: Sha256FileDigest) -> NonEmptyStr { + match d { Sha256FileDigest { digest: x } => x.hex Sha256FileDigestUnavailable { path: _, reason: r } => concat("digest unavailable: ", r) as NonEmptyStr } +} + +fn last_secondary_words(progress: List) -> List { + match filter(progress, p => p.socket == 1 && (match p.reading { ProgressRead { cur_bootstage_word: _, bootstage_word: _, progress: _ } => true ProgressNotRead { reason: _ } => false })).last() { + Absent => [] + Present { value: p } => match p.reading { ProgressRead { cur_bootstage_word: c, bootstage_word: r, progress: _ } => [c, r] ProgressNotRead { reason: _ } => [] } + } +} + +fn join_outcome(e: PowerOnEvidence, attempt: NonEmptyStr, progress: List) -> MtCollins1SecondaryJoinOutcome { + match e.console { + ConsoleNotRead => SecondaryJoinUnclassified { reason: "the console was not read, so no firmware summary is available" } + ConsoleRead { capture: d, firmware: _, firmware_error_lines: _, dram: _, checkpoints: _, relations: _, milestones: _, sockets: o, loader: _, linux: _, modules: _, span_readings: _ } => match o.summary { + AmpereSummaryNotPrinted => SecondaryJoinUnclassified { reason: "no firmware boot summary was printed in this capture" } + AmpereSummaryObserved { summary: s, summaries_printed: _ } => match s.active_sockets { + Absent => SecondaryJoinUnclassified { reason: "the firmware boot summary printed no active-socket count" } + Present { value: n } => match checked_int_to_nat(n: n) { + Absent => SecondaryJoinUnclassified { reason: "the firmware boot summary's active-socket count is negative" } + Present { value: active } => { + let words = last_secondary_words(progress: progress) + secondary_join_outcome(reading: MtCollins1SecondaryJoinReading { + run: attempt, capture_sha256: digest_label(d: d), smpro_artifact_sha256: none, + active_sockets: active, + inter_socket_connection_lines: map(s.inter_socket_links, l => l.value), + secondary_cur_bootstage_word: words.first(), + secondary_bootstage_word: words.skip(n: 1).first(), + }) + } + } + } + } + } +} + +// ── SMpro/PMpro ERROR RECORDS (#13058's gate, consumed) ───────────────────────────────────────── +// ONLY A GATED, PENDING RECORD IS A STATED ERROR (law 8). A record read while GPI_RAS_ERR was not +// read in the same window is carried UNGATED -- an anomaly and an open question, never a second +// error -- and a gate that contradicts TYPE is an anomaly; a nonzero VRD/DIMM event register is an +// anomaly. Each is kept with its pass, because the passes are not joined to cycles. +type SocketRecordsAccount { + pass: NonEmptyStr + records: SmproSocketRecords +} + +// THE SOCKETS A PASS PROBED, from its own outcomes: the probe plan's targets, never an expectation. +fn pass_sockets(p: SmproPassEvidence) -> List { + let none_yet: List = [] + fold(map(p.outcomes, o => outcome_socket(o: o)), init: none_yet, f: (acc, k) => if any(acc, j => j == k) { acc } else { concat(acc, [k]) }) +} + +fn socket_records(e: PowerOnEvidence) -> List { + flat_map(e.smpro, p => map(pass_sockets(p: p), k => SocketRecordsAccount { pass: p.label, records: mtcollins1_smpro_socket_records(outcomes: p.outcomes, socket: k) })) +} + +fn record_set_stated(pass: NonEmptyStr, socket: Int, processor: SmproProcessor, rs: SmproRecordSet) -> List { + match rs.gate { + PendingAndDecoded { error: e, warning: _ } => match e { Present { value: r } => [LocatedError { cycle: none, subject: SocketSubject { socket: socket }, error: SmproRecordStated { socket: socket, processor: processor, pass: pass, record: r } }] Absent => [] } + PendingAndDecoded { error: _, warning: _ } => [] + NotPending => [] + GpiUnavailable { ungated_error: _, ungated_warning: _ } => [] + PendingWithClearOrMissingType => [] + TypePresentWithoutPending { error: _, warning: _ } => [] + } +} + +fn record_set_anomalies(pass: NonEmptyStr, socket: Int, processor: SmproProcessor, rs: SmproRecordSet) -> List { + match rs.gate { + GpiUnavailable { ungated_error: e, ungated_warning: w } => + if (match e { Present { value: _ } => true Absent => false }) || (match w { Present { value: _ } => true Absent => false }) { [RecordUngated { socket: socket, processor: processor, pass: pass, gate: rs.gate }] } else { [] } + PendingWithClearOrMissingType => [RecordGateContradiction { socket: socket, processor: processor, pass: pass, gate: rs.gate }] + TypePresentWithoutPending { error: _, warning: _ } => [RecordGateContradiction { socket: socket, processor: processor, pass: pass, gate: rs.gate }] + PendingAndDecoded { error: _, warning: _ } => [] + NotPending => [] + } +} + +// A PENDING WARNING IS KEPT AS A TYPED WARNING, beside or without a pending error. +fn record_set_warnings(pass: NonEmptyStr, socket: Int, processor: SmproProcessor, rs: SmproRecordSet) -> List { + match rs.gate { + PendingAndDecoded { error: _, warning: w } => match w { Present { value: r } => [SmproWarningAccount { socket: socket, processor: processor, pass: pass, record: r }] Absent => [] } + PendingAndDecoded { error: _, warning: _ } => [] + NotPending => [] + GpiUnavailable { ungated_error: _, ungated_warning: _ } => [] + PendingWithClearOrMissingType => [] + TypePresentWithoutPending { error: _, warning: _ } => [] + } +} + +fn record_warnings(records: List) -> List { + flat_map(records, r => concat(record_set_warnings(pass: r.pass, socket: r.records.socket, processor: SmproProcessorSmpro, rs: r.records.smpro), + record_set_warnings(pass: r.pass, socket: r.records.socket, processor: SmproProcessorPmpro, rs: r.records.pmpro))) +} + +fn record_errors(records: List) -> List { + flat_map(records, r => concat(record_set_stated(pass: r.pass, socket: r.records.socket, processor: SmproProcessorSmpro, rs: r.records.smpro), + record_set_stated(pass: r.pass, socket: r.records.socket, processor: SmproProcessorPmpro, rs: r.records.pmpro))) +} + +fn record_anomalies(records: List) -> List { + flat_map(records, r => concat(record_set_anomalies(pass: r.pass, socket: r.records.socket, processor: SmproProcessorSmpro, rs: r.records.smpro), + concat(record_set_anomalies(pass: r.pass, socket: r.records.socket, processor: SmproProcessorPmpro, rs: r.records.pmpro), + map(r.records.nonzero_events, ev => SmproEventNonzero { socket: r.records.socket, pass: r.pass, event: ev })))) +} + +fn ungated_questions(anomalies: List) -> List { + flat_map(anomalies, a => match a { RecordUngated { socket: k, processor: _, pass: _, gate: _ } => [OpenQuestion { subject: SocketSubject { socket: k }, question: UngatedRecordUnexplained { socket: k } }] BmcReported { subject: _, anomaly: _ } => [] DramRowMalformed { subsection: _, line: _ } => [] CheckpointLinesMalformed { span: _, lines: _ } => [] RecordGateContradiction { socket: _, processor: _, pass: _, gate: _ } => [] SmproEventNonzero { socket: _, pass: _, event: _ } => [] }) +} + +// A pending error record degrades the attempt whatever the ending; the same record seen in several +// passes is one degradation, keyed by socket, processor and record. +fn record_degradations(errors: List) -> List { + let none_yet: List = [] + fold(errors, init: none_yet, f: (acc, x) => match x.error { + SmproRecordStated { socket: k, processor: p, pass: _, record: r } => { + let key = concat(to_string(k), "|", processor_text(p: p), "|", smpro_internal_record_text(r: r)) + if any(acc, y => degradation_record_key(d: y) == key) { acc } else { concat(acc, [PendingErrorRecord { socket: k, processor: p, record: r }]) } + } + FirmwareStatement { report: _ } => acc + SmproReportedFailure { socket: _, pass: _, cur_bootstage_word: _, bootstage_word: _ } => acc + SelFirmwareFault { event: _ } => acc + BootLoaderFailed { errors: _ } => acc + InitrdLoadFailedByStub { status: _ } => acc + KernelPanic { reason: _ } => acc + KernelModuleRefused { observation: _ } => acc + }) +} + +fn degradation_record_key(d: Degradation) -> String { + match d { PendingErrorRecord { socket: k, processor: p, record: r } => concat(to_string(k), "|", processor_text(p: p), "|", smpro_internal_record_text(r: r)) SocketNotJoined { outcome: _ } => "" ActiveSocketsBelowExpected { active: _, expected: _ } => "" DramSocketUntrained { socket: _ } => "" } +} + +// ── CYCLES ────────────────────────────────────────────────────────────────────────────────────── +fn is_restart_boundary(o: SystemBootInitiatedOffset) -> Bool { + match o { + InitiatedByPowerUp => false + InitiatedByHardReset => true InitiatedByWarmReset => true UserRequestedPxeBoot => true + AutomaticBootToDiagnostic => true OsInitiatedHardReset => true OsInitiatedWarmReset => true SystemRestart => true + } +} + +fn console_spans(e: PowerOnEvidence) -> List { + match e.console { + ConsoleNotRead => [] + ConsoleRead { capture: _, firmware: _, firmware_error_lines: _, dram: _, checkpoints: c, relations: _, milestones: _, sockets: _, loader: _, linux: _, modules: _, span_readings: _ } => c.spans + } +} + +fn console_capture(e: PowerOnEvidence) -> Sha256FileDigest { + match e.console { + ConsoleRead { capture: d, firmware: _, firmware_error_lines: _, dram: _, checkpoints: _, relations: _, milestones: _, sockets: _, loader: _, linux: _, modules: _, span_readings: _ } => d + ConsoleNotRead => Sha256FileDigestUnavailable { path: "console", reason: "the console was not read" } + } +} + +fn console_failure_sightings(e: PowerOnEvidence) -> List { + match e.console { + ConsoleNotRead => [] + ConsoleRead { capture: _, firmware: _, firmware_error_lines: _, dram: _, checkpoints: _, relations: _, milestones: m, sockets: _, loader: _, linux: _, modules: _, span_readings: _ } => m.failures + } +} + +fn console_sightings(e: PowerOnEvidence) -> List { + match e.console { + ConsoleNotRead => [] + ConsoleRead { capture: _, firmware: _, firmware_error_lines: _, dram: _, checkpoints: _, relations: _, milestones: m, sockets: _, loader: _, linux: _, modules: _, span_readings: _ } => m.sightings + } +} + +fn console_relations(e: PowerOnEvidence) -> List { + match e.console { + ConsoleNotRead => [] + ConsoleRead { capture: _, firmware: _, firmware_error_lines: _, dram: _, checkpoints: _, relations: r, milestones: _, sockets: _, loader: _, linux: _, modules: _, span_readings: _ } => r + } +} + +fn relation_for_span(e: PowerOnEvidence, span: Int) -> AmpereCheckpointRelation? { + filter(console_relations(e: e), r => r.span == span).first() +} + +fn max_int(a: Int, b: Int) -> Int { + if a > b { a } else { b } +} + +// The banner spans, in order: each proves one boot reached the DRAM firmware. A pre-banner span is +// console before the first banner and is bound with the first banner's cycle when binding holds. +fn banner_spans(e: PowerOnEvidence) -> List { + filter(console_spans(e: e), s => s.opened_by_banner) +} + +fn sel_boot_records(e: PowerOnEvidence) -> List { + match e.sel { + SelEventsNotRead => [] + SelEventsRead { events: es } => filter(es, ev => match ev.kind { BootRestart { offset: _ } => true FirmwareProgressRecord { offset: _ } => false OemTimestamped { payload: _ } => false OtherStandardEvent { rendering: _ } => false }) + } +} + +fn is_power_up_record(ev: SelWindowEvent) -> Bool { + match ev.kind { BootRestart { offset: o } => !is_restart_boundary(o: o) FirmwareProgressRecord { offset: _ } => false OemTimestamped { payload: _ } => false OtherStandardEvent { rendering: _ } => false } +} + +data mt_collins_ambiguous_first_restart: NonEmptyStr = "the first boot record after the confirmed power-on is a restart, not a power-up; on this MegaRAC that can be the power-on's own first boot" + +// AN ORDERED FOLD, NO LOOK-AHEAD: each record is classified from the records before it alone, so the +// classification of a prefix never changes when later records arrive. The FIRST boot record is the +// only one that can belong to the power-on: a power-up corroborates it; a restart after a confirmed +// power-on is ambiguous; with no confirmed power-on it opens the first boundary. Every LATER boot +// record -- a second power-up included -- is a distinct boundary. +fn sel_boundary_standings(e: PowerOnEvidence) -> List { + let powered = match e.handoff { HandoffPoweredOn => true HandoffNotConfirmed { reason: _ } => false } + let none_yet: List = [] + fold(sel_boot_records(e: e), init: none_yet, f: (acc, r) => + if count(acc) > 0 { concat(acc, [DistinctRestart { record: r }]) } + else if is_power_up_record(ev: r) { concat(acc, [PowerOnCorroboration { record: r }]) } + else if powered { concat(acc, [BoundaryAmbiguous { record: r, reason: mt_collins_ambiguous_first_restart }]) } + else { concat(acc, [DistinctRestart { record: r }]) }) +} + +// The first cycle opens on the confirmed power-on, or failing that on a corroborating power-up record +// (an unconfirmed handoff whose boot the SEL still saw); every DISTINCT restart opens the next. +fn cycle_boundaries(e: PowerOnEvidence) -> List { + let standings = sel_boundary_standings(e: e) + let first = match e.handoff { + HandoffPoweredOn => [OpenedByPowerOn] + HandoffNotConfirmed { reason: _ } => match flat_map(standings, b => match b { PowerOnCorroboration { record: r } => [r] DistinctRestart { record: _ } => [] BoundaryAmbiguous { record: _, reason: _ } => [] }).first() { Present { value: r } => [OpenedBySelPowerUp { record: r }] Absent => [] } + } + concat(first, flat_map(standings, b => match b { DistinctRestart { record: r } => [OpenedBySelRestart { record: r }] PowerOnCorroboration { record: _ } => [] BoundaryAmbiguous { record: _, reason: _ } => [] })) +} + +fn cycle_count(e: PowerOnEvidence) -> Int { + count(cycle_boundaries(e: e)) +} + +// Console spans bind to cycles in order only when there is one banner span per cycle; otherwise no +// span is bound and the mismatch is an open question. +fn spans_bind_in_order(e: PowerOnEvidence) -> Bool { + count(banner_spans(e: e)) == cycle_count(e: e) && cycle_count(e: e) > 0 +} + +// The cycle a console span belongs to, when spans bind: the pre-banner span goes with cycle 1. +fn cycle_of_span(e: PowerOnEvidence, span: Int) -> Int? { + if !spans_bind_in_order(e: e) { none } else { + let banners = banner_spans(e: e) + match filter(banners, b => b.index == span).first() { + Present { value: b } => Present { value: count(filter(banners, x => x.index < b.index)) + 1 } + Absent => if any(console_spans(e: e), x => x.index == span && !x.opened_by_banner) { Present { value: 1 } } else { none } + } + } +} + +fn milestone_evidence(e: PowerOnEvidence, m: ConsoleMilestoneSighting) -> MilestoneEvidence { + MilestoneEvidence { sighting: m, capture: console_capture(e: e), reader: mtcollins1_console_milestone_reader } +} + +fn cycle_account(e: PowerOnEvidence, index: Int, boundary: CycleBoundary) -> BootCycleAccount { + let bound = spans_bind_in_order(e: e) + let span = if bound { banner_spans(e: e).skip(n: index - 1).first() } else { none } + BootCycleAccount { + index: index, + boundary: boundary, + console: match span { Present { value: s } => SpanBound { span: s.index } Absent => if count(banner_spans(e: e)) == 0 { NoConsoleSpan } else { SpanBindingAmbiguous } }, + milestones: map(filter(console_sightings(e: e), m => match cycle_of_span(e: e, span: m.span) { Present { value: c } => c == index Absent => false }), m => milestone_evidence(e: e, m: m)), + ended_after: match span { Present { value: s } => relation_for_span(e: e, span: s.index) Absent => none }, + } +} + +fn cycles_of(e: PowerOnEvidence) -> List { + let none_yet: List = [] + fold(cycle_boundaries(e: e), init: none_yet, f: (acc, b) => concat(acc, [cycle_account(e: e, index: count(acc) + 1, boundary: b)])) +} + +fn host_evidence_seen(e: PowerOnEvidence, progress: List) -> Bool { + any_progress_read(progress: progress) || count(console_sightings(e: e)) > 0 || count(console_spans(e: e)) > 0 || e.census_ended +} + + + + + +// THE CYCLE INDICES 1..n, derived from the boundary evidence itself: every source that can open a +// cycle contributes one seed, plus one for the progress-only floor, so the seeds are never fewer than +// the cycles and no cycle is cut off by a fixed bound. + + +// ── STATED FAILURES FROM THE CONSOLE ──────────────────────────────────────────────────────────── +// The whole-capture console readers do not locate a statement in a span, so these errors carry their +// subject and no cycle. + +// The cycle a console error belongs to: the bound cycle of the span its first failure line was +// printed in. Unbound when the spans do not bind or no matching line was located. + +// ONE LOCATED ERROR PER OCCURRENCE. A kernel panic and an initrd failure are read per printed line +// (the line carries the reason or status); a firmware statement, GRUB's report and a module refusal are +// read per console SPAN, by their owning readers run over that span's lines. Each error is bound to the +// cycle its span binds to, so a GRUB failure in cycle 1 and another in cycle 2 are two errors. +fn span_errors(r: SpanConsoleReading) -> List { + concat(match r.firmware { + NoTerminalStated => [] + UndecodedErrorCodesOnly { err_lines: _ } => [LocatedError { cycle: none, subject: DramFirmware, error: FirmwareStatement { report: r.firmware } }] + FirmwareErrorStatementUnrecognized { statement: _ } => [LocatedError { cycle: none, subject: DramFirmware, error: FirmwareStatement { report: r.firmware } }] + TrainingRefusedByFirmware { statement: _, locus: _ } => [LocatedError { cycle: none, subject: DramFirmware, error: FirmwareStatement { report: r.firmware } }] + }, + concat(match r.loader { + GrubBootFailed { errors: es } => [LocatedError { cycle: none, subject: BootLoader, error: BootLoaderFailed { errors: es } }] + GrubNothingStated => [] + GrubBannerOnly => [] + GrubStillTrying { errors: _ } => [] + }, + match r.modules { + RefusalRecorded { refusals: _, unparsed_refusal_records: _, insert_failures: _, unparsed_insert_records: _, identity: _ } => [LocatedError { cycle: none, subject: Kernel, error: KernelModuleRefused { observation: r.modules } }] + NoRefusalRecorded { insert_failures: _, unparsed_insert_records: _ } => [] + })) +} + +fn console_span_readings(e: PowerOnEvidence) -> List { + match e.console { + ConsoleNotRead => [] + ConsoleRead { capture: _, firmware: _, firmware_error_lines: _, dram: _, checkpoints: _, relations: _, milestones: _, sockets: _, loader: _, linux: _, modules: _, span_readings: rs } => rs + } +} + +fn console_errors(e: PowerOnEvidence) -> List { + let sightings = console_failure_sightings(e: e) + let panics = flat_map(filter(sightings, f => match f.kind { KernelPanicLine => true FirmwareErrorStatementLine => false GrubFailureLine => false InitrdLoadFailureLine => false ModuleDecompressionRefusalLine => false }), f => match panic_reason_of(line: f.line as String) { + Present { value: r } => [LocatedError { cycle: cycle_of_span(e: e, span: f.span), subject: Kernel, error: KernelPanic { reason: r as NonEmptyStr } }] + Absent => [] + }) + let initrds = flat_map(filter(sightings, f => match f.kind { InitrdLoadFailureLine => true FirmwareErrorStatementLine => false GrubFailureLine => false KernelPanicLine => false ModuleDecompressionRefusalLine => false }), f => match initrd_reading_of(line: f.line as String) { + Present { value: x } => match x { InitrdLoadFailed { status: st, line: _ } => [LocatedError { cycle: cycle_of_span(e: e, span: f.span), subject: KernelStub, error: InitrdLoadFailedByStub { status: st } }] InitrdFailureNotPrinted => [] } + Absent => [] + }) + let per_span = flat_map(console_span_readings(e: e), r => map(span_errors(r: r), x => LocatedError { cycle: cycle_of_span(e: e, span: r.span), subject: x.subject, error: x.error })) + concat(per_span, concat(initrds, panics)) +} + + +fn sel_faults(e: PowerOnEvidence) -> List { + match e.sel { + SelEventsNotRead => [] + SelEventsRead { events: es } => flat_map(es, ev => match ev.kind { + FirmwareProgressRecord { offset: o } => match o { FirmwareError => [LocatedError { cycle: none, subject: Uefi, error: SelFirmwareFault { event: ev } }] FirmwareHang => [LocatedError { cycle: none, subject: Uefi, error: SelFirmwareFault { event: ev } }] FirmwareProgress { progress_rendering: _ } => [] } + BootRestart { offset: _ } => [] + OemTimestamped { payload: _ } => [] + OtherStandardEvent { rendering: _ } => [] + }) + } +} + +// ── THE ENDING ────────────────────────────────────────────────────────────────────────────────── +// THE SUBJECT ORDER ON SOCKET 0'S PATH, used only to ask whether a later cycle progressed PAST a +// stated failure's subject; it is console-milestone order, not stage numbering. +fn subject_rank(s: PowerOnSubject) -> Int { + match s { + PlatformBmc => 0 PlatformChassisPower => 1 SocketSubject { socket: _ } => 2 SecondaryJoin => 2 + DramFirmware => 3 Uefi => 4 BootLoader => 5 KernelStub => 6 Kernel => 7 Census => 8 + } +} + +fn milestone_rank(m: ConsoleMilestone) -> Int { + match m { + DramFirmwareBannerObserved => 3 FirmwareSummaryObserved => 4 UefiConsolePromptObserved => 4 UefiShellObserved => 4 + GrubBannerObserved => 5 EfiStubLineObserved => 6 KernelBannerObserved => 7 + } +} + +fn last_cycle(cycles: List) -> BootCycleAccount? { + cycles.last() +} + +fn furthest_milestone(c: BootCycleAccount) -> MilestoneEvidence? { + let start: MilestoneEvidence? = none + fold(c.milestones, init: start, f: (acc, m) => match acc { + Absent => Present { value: m } + Present { value: b } => if milestone_rank(m: m.sighting.milestone) >= milestone_rank(m: b.sighting.milestone) { Present { value: m } } else { Present { value: b } } + }) +} + +// WHICH STATED FAILURE ENDS THE ATTEMPT. Only a failure located in the LAST cycle may: an earlier +// cycle's failure, and an unbound one (SMpro, SEL, or a console line whose span is not bound), is +// history kept in `errors`. Within the last cycle a failure the cycle got past is overtaken, and of the +// rest the FURTHEST along socket 0's path is where the attempt stopped (run 37069907299: the stub's +// initrd EFI_LOAD_ERROR, then the kernel's panic -- the attempt stopped at the panic). +fn ending_failure(errors: List, cycles: List) -> LocatedError? { + match last_cycle(cycles: cycles) { + Absent => none + Present { value: c } => { + let reach = match furthest_milestone(c: c) { Absent => 0 - 1 Present { value: m } => milestone_rank(m: m.sighting.milestone) } + let start: LocatedError? = none + fold(filter(errors, x => (match x.cycle { Present { value: k } => k == c.index Absent => false }) && subject_rank(s: x.subject) >= reach), init: start, f: (acc, x) => match acc { + Absent => Present { value: x } + Present { value: b } => if subject_rank(s: x.subject) > subject_rank(s: b.subject) { Present { value: x } } else { Present { value: b } } + }) + } + } +} + +fn console_error_lines(e: PowerOnEvidence) -> Int { + match e.console { + ConsoleNotRead => 0 + ConsoleRead { capture: _, firmware: _, firmware_error_lines: n, dram: _, checkpoints: _, relations: _, milestones: _, sockets: _, loader: _, linux: _, modules: _, span_readings: _ } => n + } +} + +// HOW FAR A CYCLE GOT, by its furthest console milestone: -1 when it printed none. +fn cycle_reach(c: BootCycleAccount) -> Int { + match furthest_milestone(c: c) { Absent => 0 - 1 Present { value: m } => milestone_rank(m: m.sighting.milestone) } +} + +// A LOOP, NOT A RETRY THAT WENT ON: the attempt restarted, and its last cycle got no further than an +// earlier one did. The old firmware's run 37062170720 restarted once and its second cycle reached +// UEFI -- that is progress after a restart, ended by the last cycle, not a repeated restart. +fn last_cycle_repeats(cycles: List) -> Bool { + match cycles.last() { + Absent => false + Present { value: last } => count(cycles) >= 2 && any(cycles.take(n: count(cycles) - 1), c => cycle_reach(c: c) >= cycle_reach(c: last)) + } +} + +fn host_ending(e: PowerOnEvidence, progress: List, cycles: List, errors: List) -> HostEnding { + if e.census_ended { ReachedCensus } + else { + match ending_failure(errors: errors, cycles: cycles) { + Present { value: x } => StoppedWithStatedFailure { subject: x.subject, error: x.error } + Absent => + if last_cycle_repeats(cycles: cycles) { + RestartedRepeatedly { + cycles: count(cycles), + last_cycle_ended_after: match last_cycle(cycles: cycles) { Absent => none Present { value: c } => c.ended_after }, + cadence: CadenceCounted { cycles: count(cycles) }, + console_errors: console_error_lines(e: e), + sel_restarts: count(flat_map(sel_boundary_standings(e: e), b => match b { DistinctRestart { record: _ } => [b] PowerOnCorroboration { record: _ } => [] BoundaryAmbiguous { record: _, reason: _ } => [] })), + watchdog: e.watchdog, + reset_cause: ResetCauseUnobserved { searched: mt_collins_reset_cause_search }, + } + } else if count(cycles) >= 1 && host_evidence_seen(e: e, progress: progress) { + match cycles.last() { + Absent => HostEndingNotEstablished + Present { value: c } => StoppedWithoutRestart { last_milestone: furthest_milestone(c: c), ended_after: c.ended_after, console_errors: console_error_lines(e: e), restarts_before: count(cycles) - 1 } + } + } else { HostEndingNotEstablished } + } + } +} + +// ── DEGRADATIONS AND ANOMALIES ────────────────────────────────────────────────────────────────── +fn summary_active(e: PowerOnEvidence) -> Int? { + match e.console { + ConsoleNotRead => none + ConsoleRead { capture: _, firmware: _, firmware_error_lines: _, dram: _, checkpoints: _, relations: _, milestones: _, sockets: o, loader: _, linux: _, modules: _, span_readings: _ } => match o.summary { + AmpereSummaryNotPrinted => none + AmpereSummaryObserved { summary: s, summaries_printed: _ } => s.active_sockets + } + } +} + +fn degradations_of(e: PowerOnEvidence, join: MtCollins1SecondaryJoinOutcome) -> List { + match expected_sockets(c: e.configuration) { + Absent => [] + Present { value: ks } => { + let expected = count(ks) + let below = match summary_active(e: e) { Present { value: n } => if n < expected { [ActiveSocketsBelowExpected { active: n, expected: expected }] } else { [] } Absent => [] } + let joined = if expected < 2 { [] } else { match join { SecondaryJoined { inter_socket_links_reported: _ } => [] SecondaryJoinTimeout { secondary_last_stage: _, inter_socket_links_reported: _ } => [SocketNotJoined { outcome: join }] SecondaryJoinUnclassified { reason: _ } => [SocketNotJoined { outcome: join }] } } + let untrained = match e.console { + ConsoleNotRead => [] + ConsoleRead { capture: _, firmware: _, firmware_error_lines: _, dram: d, checkpoints: _, relations: _, milestones: _, sockets: _, loader: _, linux: _, modules: _, span_readings: _ } => + map(ampere_dram_untrained_sockets(observation: d, expected_sockets: ks), k => DramSocketUntrained { socket: k }) + } + concat(below, concat(joined, untrained)) + } + } +} + +fn bmc_anomaly_subject(a: BmcAnomaly) -> PowerOnSubject { + match a { + ProcessorMissingOnExpectedSocket { socket: _ } => PlatformBmc + ProcessorSocketDuplicated { socket: _, members: _ } => PlatformBmc + ProcessorSocketUnreadable { member: _ } => PlatformBmc + ProcessorNotNominal { member: _ } => PlatformBmc + ProcessorAbsentOnExpectedSocket { member: _, socket: _ } => PlatformBmc + MemoryBelowRecordedPopulation { socket: _, present: _, populated: _ } => PlatformBmc + MemoryNotNominal { member: _ } => PlatformBmc + SensorNotNominal { line: _ } => PlatformChassisPower + SensorLineWithoutReadingNotNominal { text: _ } => PlatformChassisPower + SocketSensorsAbsent { socket: _ } => PlatformChassisPower + SelHardwareEvent { event: _ } => DramFirmware + } +} + +fn anomalies_of(e: PowerOnEvidence) -> List { + concat(map(e.bmc_anomalies, a => BmcReported { subject: bmc_anomaly_subject(a: a), anomaly: a }), + match e.console { + ConsoleNotRead => [] + ConsoleRead { capture: _, firmware: _, firmware_error_lines: _, dram: d, checkpoints: c, relations: _, milestones: _, sockets: _, loader: _, linux: _, modules: _, span_readings: _ } => + concat(match d { AmpereDramBlockNotPrinted => [] AmpereDramBlockObserved { block: b, blocks_printed: _ } => map(b.malformed, m => DramRowMalformed { subsection: m.subsection, line: m.line }) }, + flat_map(c.spans, s => if count(s.malformed) == 0 { [] } else { [CheckpointLinesMalformed { span: s.index, lines: s.malformed }] })) + }) +} + +// ── OPEN QUESTIONS ────────────────────────────────────────────────────────────────────────────── +fn expected_sockets(c: AttemptConfigurationReceipt) -> List? { + match c.expected { ExpectedSockets { sockets: ks, source: _ } => Present { value: ks } ExpectedTopologyNotRecorded { reason: _ } => none } +} + +fn configuration_questions(c: AttemptConfigurationReceipt) -> List { + concat(match c.expected { ExpectedSockets { sockets: _, source: _ } => [] ExpectedTopologyNotRecorded { reason: _ } => [OpenQuestion { subject: SecondaryJoin, question: ConfigurationNotRecorded { field: ConfiguredExpectedTopology } }] }, + concat(match c.applied { StimulusAppliedByReceipt { receipt: _, attested_by: _ } => [] StimulusApplicationNotRecorded { reason: _ } => [OpenQuestion { subject: PlatformBmc, question: ConfigurationNotRecorded { field: ConfiguredStimulus } }] }, + concat(population_question(r: c.cpus, field: ConfiguredCpuPopulation), + concat(population_question(r: c.dimms, field: ConfiguredDimmPopulation), + match c.firmware { FirmwareReadBeforeActuation { rows: _ } => [] FirmwareReadbacksNotRecorded { reason: _ } => [OpenQuestion { subject: PlatformBmc, question: ConfigurationNotRecorded { field: ConfiguredFirmware } }] })))) +} + +fn population_question(r: PopulationReading, field: ConfigurationField) -> List { + match r { + PopulationLiveObserved { rows: _, source: _ } => [] + PopulationOperatorAttested { rows: _, receipt: _ } => [] + PopulationConflicted { readings: _ } => [OpenQuestion { subject: PlatformBmc, question: ConfigurationConflicted { field: field } }] + PopulationNotRecorded { reason: _ } => [OpenQuestion { subject: PlatformBmc, question: ConfigurationNotRecorded { field: field } }] + } +} + +fn carrier_subject(c: Carrier) -> PowerOnSubject { + match c { + SolConsole => DramFirmware KvmScreen => Uefi SmproRegisters => SocketSubject { socket: 0 } SmproErrorRegisters => SocketSubject { socket: 0 } SystemEventLog => PlatformBmc + SensorTable => PlatformChassisPower ChassisPower => PlatformChassisPower RedfishInventory => PlatformBmc VirtualMedia => BootLoader HostCaptureMarkers => Census + } +} + +fn coverage_questions(coverage: List) -> List { + flat_map(coverage, c => match c.standing { + CarrierRead => [] + CarrierReadEmpty => [OpenQuestion { subject: carrier_subject(c: c.carrier), question: CarrierNotRead { carrier: c.carrier } }] + CarrierPartial { detail: _ } => [OpenQuestion { subject: carrier_subject(c: c.carrier), question: CarrierNotRead { carrier: c.carrier } }] + CarrierRefused { cause: _ } => [OpenQuestion { subject: carrier_subject(c: c.carrier), question: CarrierNotRead { carrier: c.carrier } }] + CarrierNotTaken { reason: _ } => [OpenQuestion { subject: carrier_subject(c: c.carrier), question: CarrierNotRead { carrier: c.carrier } }] + }) +} + +fn ending_questions(ending: HostEnding) -> List { + match ending { + RestartedRepeatedly { cycles: _, last_cycle_ended_after: r, cadence: c, console_errors: _, sel_restarts: _, watchdog: _, reset_cause: rc } => + concat([OpenQuestion { subject: DramFirmware, question: RetryStatePersistence }], + concat(match rc { ResetCauseUnobserved { searched: _ } => [OpenQuestion { subject: DramFirmware, question: ResetCauseNotPubliclyReadable }] ResetCauseObserved { raw: _ } => [] }, + concat(match c { CadenceCounted { cycles: _ } => [OpenQuestion { subject: DramFirmware, question: RestartCadenceNotTimed }] CadenceTimed { period_seconds: _, cycles: _ } => [] }, + match r { Present { value: _ } => [OpenQuestion { subject: DramFirmware, question: CheckpointMeaningNotPubliclyModeled }] Absent => [] }))) + StoppedWithoutRestart { last_milestone: _, ended_after: r, console_errors: _, restarts_before: _ } => + match r { Present { value: _ } => [OpenQuestion { subject: DramFirmware, question: CheckpointMeaningNotPubliclyModeled }] Absent => [] } + ReachedCensus => [] + StoppedWithStatedFailure { subject: _, error: _ } => [] + HostEndingNotEstablished => [] + } +} + +fn boundary_questions(e: PowerOnEvidence) -> List { + flat_map(sel_boundary_standings(e: e), b => match b { BoundaryAmbiguous { record: r, reason: _ } => [OpenQuestion { subject: PlatformChassisPower, question: BootBoundaryAmbiguous { record_id: r.record_id } }] PowerOnCorroboration { record: _ } => [] DistinctRestart { record: _ } => [] }) +} + +fn binding_questions(e: PowerOnEvidence) -> List { + let b = count(banner_spans(e: e)) + if b == 0 || spans_bind_in_order(e: e) { [] } else { [OpenQuestion { subject: DramFirmware, question: ConsoleSpansUnbound { spans: b, cycles: cycle_count(e: e) } }] } +} + +// ── THE ONE FOLD ──────────────────────────────────────────────────────────────────────────────── +fn mtcollins1_power_on_account(e: PowerOnEvidence) -> PowerOnAccount { + let progress = flat_map(e.smpro, p => map(pass_sockets(p: p), k => socket_progress(pass: p, socket: k))) + let cycles = cycles_of(e: e) + let records = socket_records(e: e) + let errors = concat(console_errors(e: e), concat(smpro_failures(progress: progress), concat(record_errors(records: records), sel_faults(e: e)))) + let anomalies = concat(anomalies_of(e: e), record_anomalies(records: records)) + let join = join_outcome(e: e, attempt: e.configuration.attempt, progress: progress) + let ending = host_ending(e: e, progress: progress, cycles: cycles, errors: errors) + PowerOnAccount { + configuration: e.configuration, + cycles: cycles, + boundaries: sel_boundary_standings(e: e), + socket_progress: progress, + join: join, + ending: ending, + degradations: concat(degradations_of(e: e, join: join), record_degradations(errors: errors)), + errors: errors, + warnings: record_warnings(records: records), + anomalies: anomalies, + coverage: e.coverage, + open: concat(configuration_questions(c: e.configuration), concat(coverage_questions(coverage: e.coverage), concat(refused_pair_questions(progress: progress), concat(ungated_questions(anomalies: anomalies), concat(boundary_questions(e: e), concat(binding_questions(e: e), ending_questions(ending: ending))))))), + } +} + +// ── THE FIRMWARE-SET READBACK (ruling R1) ─────────────────────────────────────────────────────── +// What firmware convergence consumes after a set is flashed: a projection of the WHOLE account, never +// of the ending alone. A UEFI milestone in any cycle grounds the set, and the session ending and the +// account travel with it, so reaching UEFI cannot hide a later repeated-reset ending. +type FirmwareSetBootReadback + = BootReachedUefiAfterSet { cycle: Int, milestone: MilestoneEvidence, ending: HostEnding, degradations: List, account: PowerOnAccount } + | BootProgressLostAfterSet { ending: HostEnding, last_relation: AmpereCheckpointRelation?, account: PowerOnAccount } + | BootReadbackUnobservable { account: PowerOnAccount } + +fn is_uefi_milestone(m: ConsoleMilestone) -> Bool { + match m { FirmwareSummaryObserved => true UefiConsolePromptObserved => true UefiShellObserved => true DramFirmwareBannerObserved => false GrubBannerObserved => false EfiStubLineObserved => false KernelBannerObserved => false } +} + +fn first_uefi_sighting(cycles: List) -> BootCycleAccount? { + filter(cycles, c => any(c.milestones, m => is_uefi_milestone(m: m.sighting.milestone))).first() +} + +fn firmware_set_boot_readback(a: PowerOnAccount) -> FirmwareSetBootReadback { + match first_uefi_sighting(cycles: a.cycles) { + Present { value: c } => match filter(c.milestones, m => is_uefi_milestone(m: m.sighting.milestone)).first() { + Present { value: m } => BootReachedUefiAfterSet { cycle: c.index, milestone: m, ending: a.ending, degradations: a.degradations, account: a } + Absent => BootReadbackUnobservable { account: a } + } + Absent => match a.ending { + RestartedRepeatedly { cycles: _, last_cycle_ended_after: r, cadence: _, console_errors: _, sel_restarts: _, watchdog: _, reset_cause: _ } => BootProgressLostAfterSet { ending: a.ending, last_relation: r, account: a } + StoppedWithoutRestart { last_milestone: _, ended_after: r, console_errors: _, restarts_before: _ } => BootProgressLostAfterSet { ending: a.ending, last_relation: r, account: a } + StoppedWithStatedFailure { subject: _, error: _ } => BootProgressLostAfterSet { ending: a.ending, last_relation: none, account: a } + ReachedCensus => BootReadbackUnobservable { account: a } + HostEndingNotEstablished => BootReadbackUnobservable { account: a } + } + } +} + +// ── RENDER ────────────────────────────────────────────────────────────────────────────────────── +fn subject_text(s: PowerOnSubject) -> String { + match s { + PlatformBmc => "BMC" PlatformChassisPower => "chassis power" SocketSubject { socket: k } => concat("socket ", to_string(k)) + SecondaryJoin => "second socket's join" DramFirmware => "DRAM firmware" Uefi => "UEFI" BootLoader => "boot loader" + KernelStub => "Linux EFI stub" Kernel => "Linux kernel" Census => "census" + } +} + +fn carrier_text(c: Carrier) -> String { + match c { + SolConsole => "SOL console" KvmScreen => "KVM screen" SmproRegisters => "SMpro boot-stage registers" SmproErrorRegisters => "SMpro/PMpro error-record registers" SystemEventLog => "SEL" + SensorTable => "sensor table" ChassisPower => "chassis power" RedfishInventory => "Redfish inventory" VirtualMedia => "virtual media" HostCaptureMarkers => "host-capture markers" + } +} + +fn progress_text(p: SmproBootProgress) -> String { + match p { + SmproBootProgressRead { current: c, reported: b } => concat("current ", smpro_stage_text(r: c), "; reported ", smpro_stage_text(r: b.stage), ", ", smpro_status_text(r: b.status)) + SmproBootProgressRefused { cur_bootstage_word: c, bootstage_word: r } => concat("REFUSED pair (CUR_BOOTSTAGE ", to_string(c), ", BOOTSTAGE ", to_string(r), "): reported stage ahead of current; no stage is read from it") + } +} + +fn decoded_error_text(err: DecodedBootError) -> String { + match err { + FirmwareStatement { report: r } => match r { + NoTerminalStated => "firmware stated nothing" + UndecodedErrorCodesOnly { err_lines: n } => concat("firmware printed ", to_string(n), " ERR: code line(s) this repository does not decode") + FirmwareErrorStatementUnrecognized { statement: s } => concat("firmware error statement: ", s as String) + TrainingRefusedByFirmware { statement: s, locus: _ } => concat("firmware refused training: ", s as String) + } + SmproReportedFailure { socket: k, pass: p, cur_bootstage_word: c, bootstage_word: r } => concat("socket ", to_string(k), " SMpro reports a FAILED stage in the ", p as String, " pass: ", progress_text(p: smpro_boot_progress_of(cur_bootstage_word: c, bootstage_word: r))) + SmproRecordStated { socket: k, processor: p, pass: q, record: r } => concat("socket ", to_string(k), " ", processor_text(p: p), " reports a pending error record (", q as String, " pass): ", smpro_internal_record_text(r: r)) + SelFirmwareFault { event: ev } => concat("SEL record ", to_string(ev.record_id), ": system firmware ", match ev.kind { FirmwareProgressRecord { offset: o } => match o { FirmwareError => "error" FirmwareHang => "hang" FirmwareProgress { progress_rendering: _ } => "progress" } BootRestart { offset: _ } => "event" OemTimestamped { payload: _ } => "event" OtherStandardEvent { rendering: _ } => "event" }) + BootLoaderFailed { errors: es } => concat("GRUB failed: ", join(map(es, x => grub_error_text(error: x)), "; ")) + InitrdLoadFailedByStub { status: s } => concat("the EFI stub could not load the initrd: ", efi_stub_status_text(s: s)) + KernelPanic { reason: r } => concat("kernel panic: ", r as String) + KernelModuleRefused { observation: o } => concat("kernel module refused: ", kernel_module_decompression_text(o: o)) + } +} + +fn join_text(j: MtCollins1SecondaryJoinOutcome) -> String { + match j { + SecondaryJoined { inter_socket_links_reported: l } => concat("joined", if l { ", inter-socket links reported" } else { ", NO inter-socket link line" }) + SecondaryJoinTimeout { secondary_last_stage: p, inter_socket_links_reported: _ } => concat("did NOT join (socket 0 went on alone); socket 1 SMpro: ", progress_text(p: p)) + SecondaryJoinUnclassified { reason: r } => concat("unclassified: ", r as String) + } +} + +fn degradation_text(d: Degradation) -> String { + match d { + SocketNotJoined { outcome: o } => concat("second socket ", join_text(j: o)) + ActiveSocketsBelowExpected { active: a, expected: x } => concat("firmware boot summary: ", to_string(a), " active socket(s), ", to_string(x), " expected") + DramSocketUntrained { socket: k } => concat("the closed DRAM roster lists no DIMM on socket ", to_string(k)) + PendingErrorRecord { socket: k, processor: p, record: r } => concat("socket ", to_string(k), " ", processor_text(p: p), " error record pending: ", smpro_internal_record_text(r: r)) + } +} + +fn warning_text(w: SmproWarningAccount) -> String { + concat("socket ", to_string(w.socket), " ", processor_text(p: w.processor), " pending warning (", w.pass as String, " pass): ", smpro_internal_record_text(r: w.record)) +} + +fn processor_text(p: SmproProcessor) -> String { + match p { SmproProcessorSmpro => "SMpro" SmproProcessorPmpro => "PMpro" } +} + +fn bmc_anomaly_text(a: BmcAnomaly) -> String { + match a { + ProcessorMissingOnExpectedSocket { socket: k } => concat("BMC lists no Processor member on socket ", to_string(k), ", which the receipt expects") + ProcessorSocketDuplicated { socket: k, members: ms } => concat("BMC lists ", to_string(count(ms)), " Processor members on socket ", to_string(k), ": ", join(map(ms, m => m.id as String), ", ")) + ProcessorSocketUnreadable { member: p } => concat("BMC Processor ", p.id as String, " names no readable socket") + ProcessorNotNominal { member: p } => concat("BMC Processor ", p.id as String, " not nominal") + ProcessorAbsentOnExpectedSocket { member: p, socket: k } => concat("BMC Processor ", p.id as String, " State=Absent on socket ", to_string(k), ", which the receipt expects") + MemoryBelowRecordedPopulation { socket: k, present: n, populated: m } => concat("BMC shows ", to_string(n), " present Memory member(s) on socket ", to_string(k), "; the receipt records ", to_string(m), " populated") + MemoryNotNominal { member: m } => concat("BMC Memory ", m.id as String, " not nominal") + SensorNotNominal { line: l } => concat("sensor ", l.name as String, " status ", l.status as String, ": ", l.reading_text as String) + SensorLineWithoutReadingNotNominal { text: t } => concat("sensor line (no reading field): ", t as String) + SocketSensorsAbsent { socket: k } => concat("no socket-", to_string(k), " sensor in the table") + SelHardwareEvent { event: ev } => concat("SEL record ", to_string(ev.record_id), ": ", match ev.kind { OtherStandardEvent { rendering: r } => r as String BootRestart { offset: _ } => "memory or processor event" FirmwareProgressRecord { offset: _ } => "memory or processor event" OemTimestamped { payload: _ } => "memory or processor event" }) + } +} + +fn anomaly_text(a: PlacedAnomaly) -> String { + match a { + BmcReported { subject: s, anomaly: x } => concat(subject_text(s: s), ": ", bmc_anomaly_text(a: x)) + DramRowMalformed { subsection: sub, line: l } => concat("DRAM firmware row under '", sub as String, "' not in the transcribed grammar: ", l) + CheckpointLinesMalformed { span: k, lines: ls } => concat("span ", to_string(k), ": CP: line(s) whose word is not eight hex digits: ", join(ls, " | ")) + RecordUngated { socket: k, processor: p, pass: q, gate: g } => concat("socket ", to_string(k), " ", processor_text(p: p), " (", q as String, " pass): ", join(smpro_record_gate_lines(g: g), "; ")) + RecordGateContradiction { socket: k, processor: p, pass: q, gate: g } => concat("socket ", to_string(k), " ", processor_text(p: p), " (", q as String, " pass): ", join(smpro_record_gate_lines(g: g), "; ")) + SmproEventNonzero { socket: k, pass: q, event: ev } => concat("socket ", to_string(k), " (", q as String, " pass): event ", ev) + } +} + +fn boundary_text(b: CycleBoundary) -> String { + match b { + OpenedByPowerOn => "opened by the confirmed power-on" + OpenedBySelPowerUp { record: r } => concat("opened by SEL power-up record ", to_string(r.record_id)) + OpenedBySelRestart { record: r } => concat("opened by SEL boot record ", to_string(r.record_id)) + } +} + +fn cycle_text(c: BootCycleAccount) -> String { + concat("cycle ", to_string(c.index), ", ", boundary_text(b: c.boundary), "; console: ", + match c.console { SpanBound { span: k } => concat("span ", to_string(k)) NoConsoleSpan => "no console span" SpanBindingAmbiguous => "spans not bound (counts disagree)" }, + if count(c.milestones) == 0 { "; no milestone" } else { concat("; milestones: ", join(map(c.milestones, m => console_milestone_name(m: m.sighting.milestone)), ", ")) }, + match c.ended_after { Absent => "" Present { value: r } => concat("\n ", ampere_checkpoint_relation_text(r: r)) }) +} + +fn reset_cause_text(r: ResetCauseObservation) -> String { + match r { ResetCauseUnobserved { searched: s } => concat("unobserved (searched: ", s as String, ")") ResetCauseObserved { raw: w } => concat("raw word ", to_string(w), " (decode is private)") } +} + +fn watchdog_text(w: BmcWatchdogDisposition) -> String { + match w { WatchdogStoppedNoAction => "BMC watchdog stopped, no action" WatchdogArmed { action: a } => concat("BMC watchdog ARMED: ", a as String) WatchdogUnobserved { reason: r } => concat("BMC watchdog unobserved: ", r as String) } +} + +fn host_ending_text(e: HostEnding) -> String { + match e { + ReachedCensus => "reached the census capture" + StoppedWithStatedFailure { subject: s, error: err } => concat("stopped at the ", subject_text(s: s), " with a stated failure: ", decoded_error_text(err: err)) + RestartedRepeatedly { cycles: n, last_cycle_ended_after: r, cadence: c, console_errors: k, sel_restarts: x, watchdog: w, reset_cause: rc } => + concat("restarted repeatedly: ", to_string(n), " cycle(s), ", match c { CadenceTimed { period_seconds: p, cycles: _ } => concat("period ~", to_string(p), " s") CadenceCounted { cycles: _ } => "not timed" }, + "; last cycle ", match r { Absent => "printed no checkpoint token" Present { value: rel } => concat("ended after token ", rel.last.digits as String) }, + "; ", to_string(k), " console error line(s); ", to_string(x), " SEL restart record(s); ", watchdog_text(w: w), "; reset cause ", reset_cause_text(r: rc)) + StoppedWithoutRestart { last_milestone: m, ended_after: r, console_errors: k, restarts_before: n } => + concat(if n == 0 { "one cycle, no restart observed" } else { concat("the last of ", to_string(n + 1), " cycles went further than the earlier ones and was not followed by a restart") }, "; furthest milestone: ", match m { Absent => "none" Present { value: s } => console_milestone_name(m: s.sighting.milestone) }, + match r { Absent => "" Present { value: rel } => concat("; ended after token ", rel.last.digits as String) }, "; ", to_string(k), " console error line(s)") + HostEndingNotEstablished => "how the host's attempt ended is not established by the host evidence read" + } +} + +fn open_question_text(q: OpenQuestion) -> String { + concat(subject_text(s: q.subject), ": ", match q.question { + ResetCauseNotPubliclyReadable => "what caused the reset? no public register reaches the SCP's reset-cause record" + RetryStatePersistence => "does any failsafe or retry state survive the reset? it decides bounded retries from an endless loop" + CheckpointMeaningNotPubliclyModeled => "what does the last checkpoint token mean? its fields are not publicly modeled" + SmproPairRefused { socket: k, pass: p } => concat("why is socket ", to_string(k), "'s SMpro pair refused in the ", p as String, " pass (reported stage ahead of current)?") + RestartCadenceNotTimed => "how long is each cycle? this console is not timestamped" + ConfigurationNotRecorded { field: f } => concat(configuration_field_text(f: f), " was not recorded for this attempt") + ConfigurationConflicted { field: f } => concat(configuration_field_text(f: f), " has conflicting readings for this attempt") + CarrierNotRead { carrier: c } => concat(carrier_text(c: c), " was not fully read") + ConsoleSpansUnbound { spans: s, cycles: c } => concat(to_string(s), " console span(s) against ", to_string(c), " cycle(s): which span belongs to which cycle is not established") + BootBoundaryAmbiguous { record_id: r } => concat("SEL record ", to_string(r), " is a restart right after the power-on with no power-up record: is it the power-on's own boot or a second one? no shared clock can say") + UngatedRecordUnexplained { socket: k } => concat("socket ", to_string(k), " shows an error record with its GPI_RAS_ERR gate unread: is it the driver's record? only a read gate can say") + }) +} + +fn configuration_field_text(f: ConfigurationField) -> String { + match f { ConfiguredExpectedTopology => "the expected topology" ConfiguredStimulus => "the applied stimulus" ConfiguredCpuPopulation => "the CPU population" ConfiguredDimmPopulation => "the DIMM population" ConfiguredFirmware => "the firmware readback" } +} + +fn carrier_standing_text(s: CarrierStanding) -> String { + match s { + CarrierRead => "read" CarrierReadEmpty => "read, EMPTY" CarrierPartial { detail: d } => concat("partial: ", d as String) + CarrierRefused { cause: c } => concat("refused: ", c as String) CarrierNotTaken { reason: r } => concat("not taken: ", r as String) + } +} + +fn mtcollins1_power_on_account_text(a: PowerOnAccount) -> String { + join([ + concat("ending: ", host_ending_text(e: a.ending)), + "-- cycles --", + if count(a.cycles) == 0 { "none established" } else { join(map(a.cycles, c => cycle_text(c: c)), "\n") }, + "-- socket progress (SMpro, per pass) --", + if count(a.socket_progress) == 0 { "no pass" } else { join(map(a.socket_progress, p => concat(p.pass as String, " pass, socket ", to_string(p.socket), ": ", match p.reading { ProgressRead { cur_bootstage_word: _, bootstage_word: _, progress: g } => progress_text(p: g) ProgressNotRead { reason: r } => concat("not read: ", r as String) })), "\n") }, + concat("second socket: ", join_text(j: a.join)), + "-- degradations --", + if count(a.degradations) == 0 { "none" } else { join(map(a.degradations, d => degradation_text(d: d)), "\n") }, + "-- stated errors --", + if count(a.errors) == 0 { "none" } else { join(map(a.errors, x => concat(subject_text(s: x.subject), ": ", decoded_error_text(err: x.error))), "\n") }, + "-- pending warnings (SMpro/PMpro) --", + if count(a.warnings) == 0 { "none" } else { join(map(a.warnings, w => warning_text(w: w)), "\n") }, + "-- anomalies --", + if count(a.anomalies) == 0 { "none" } else { join(map(a.anomalies, x => anomaly_text(a: x)), "\n") }, + "-- coverage --", + join(map(a.coverage, c => concat(carrier_text(c: c.carrier), ": ", carrier_standing_text(s: c.standing))), "\n"), + "-- open questions --", + if count(a.open) == 0 { "none" } else { join(map(a.open, q => open_question_text(q: q)), "\n") }, + ], "\n") +} + +// What a one-line refusal reason carries: the ending, then every stated error, degradation, warning and +// anomaly, then every open question -- what we could not establish is as much the account as what we did. +fn mtcollins1_power_on_account_notes(a: PowerOnAccount) -> List { + concat([concat("ending: ", host_ending_text(e: a.ending))], + concat(map(a.errors, x => concat(subject_text(s: x.subject), ": ", decoded_error_text(err: x.error))), + concat(map(a.degradations, d => degradation_text(d: d)), concat(map(a.warnings, w => warning_text(w: w)), concat(map(a.anomalies, x => anomaly_text(a: x)), + map(a.open, q => concat("open: ", open_question_text(q: q)))))))) +} diff --git a/dag/gunbc/machine_intake/mtcollins1_smpro_observation.dag b/dag/gunbc/machine_intake/mtcollins1_smpro_observation.dag index 061640e2f6f..ea40315db69 100644 --- a/dag/gunbc/machine_intake/mtcollins1_smpro_observation.dag +++ b/dag/gunbc/machine_intake/mtcollins1_smpro_observation.dag @@ -13,7 +13,7 @@ import extdeps.bmc.ipmi_master_write_read { master_write_read_completion_of, master_write_read_completion_text, master_write_read_bytes_of, master_write_read_stderr_completion, } import extdeps.ampere.smpro_register { - SmproRegister, SmproBootStage, SmproStageReading, SmproStatusDefined, + SmproRegister, SmproBootStage, SmproStageReading, smpro_manufacturer_id_reg, smpro_cur_bootstage_reg, smpro_bootstage_lo_reg, smpro_bootstage_hi_reg, smpro_bootstage_reg, smpro_value_size_bytes, smpro_manufacturer_id_expected, smpro_word, smpro_bootstage_of, smpro_cur_bootstage_of, smpro_stage_text, smpro_status_text, @@ -27,7 +27,6 @@ import extdeps.ampere.smpro_internal_error { SmproInternalRecord, smpro_internal_error_of, smpro_internal_warning_of, smpro_internal_record_text, SmproProcessor, SmproProcessorSmpro, SmproProcessorPmpro, SmproRecordGate, smpro_record_gate, smpro_record_gate_lines, } -import extdeps.ampere.scp_diagnostic { Failed } import gunbc.clock_read { clock_now_probed_at_or_unknown } import gunbc.machine_intake_mtcollins1_access_observation { mtcollins1_endpoint } @@ -492,41 +491,3 @@ fn mtcollins1_smpro_dossier_text(outcomes: List) -> String { ], "\n") } -// FINDINGS: every socket's line, when the sockets DIFFER on either reading (an unavailable reading -// beside an established one is a difference), or a reported status is FAILED. Nothing when no -// reading was established anywhere (the dossier says why), or when every socket agrees. -fn reported_failed(s: SmproSocketStage) -> Bool { - match s.reported { - ReportedStageEstablished { bootstage: b } => match b.status { SmproStatusDefined { status: Failed } => true SmproStatusDefined { status: NotStarted } => false SmproStatusDefined { status: Started } => false SmproStatusDefined { status: Completed } => false SmproStatusUndefined { code: _ } => false } - ReportedStageUnavailable { reason: _ } => false - } -} - -fn established_any(s: SmproSocketStage) -> Bool { - (match s.current { CurrentStageEstablished { stage: _ } => true CurrentStageUnavailable { reason: _ } => false }) || (match s.reported { ReportedStageEstablished { bootstage: _ } => true ReportedStageUnavailable { reason: _ } => false }) -} - -// The comparison key is the readings alone: an unavailable reading keys as UNAVAILABLE whatever its -// reason, whose text names its own socket and would make every pair differ. -fn socket_key(s: SmproSocketStage) -> String { - concat( - match s.current { CurrentStageEstablished { stage: st } => smpro_stage_text(r: st) CurrentStageUnavailable { reason: _ } => "UNAVAILABLE" }, - "|", - match s.reported { ReportedStageEstablished { bootstage: _ } => reported_text(r: s.reported) ReportedStageUnavailable { reason: _ } => "UNAVAILABLE" }, - ) -} - -// Every decoded record and nonzero event is a finding on its own: a record says a firmware reported -// an error, whatever the stage comparison says. -fn mtcollins1_smpro_findings(outcomes: List) -> List { - concat(mtcollins1_smpro_stage_findings(outcomes: outcomes), flat_map(mtcollins1_smpro_targets, t => mtcollins1_smpro_record_lines(r: mtcollins1_smpro_socket_records(outcomes: outcomes, socket: t.socket)))) -} - -fn mtcollins1_smpro_stage_findings(outcomes: List) -> List { - let stages = map(mtcollins1_smpro_targets, t => mtcollins1_smpro_socket_stage(outcomes: outcomes, socket: t.socket)) - let first = match stages.first() { Absent => "" Present { value: s } => socket_key(s: s) } - let differ = any(stages, s => socket_key(s: s) != first) - if !any(stages, s => established_any(s: s)) { [] } - else if differ || any(stages, s => reported_failed(s: s)) { map(stages, s => mtcollins1_smpro_stage_text(s: s)) } - else { [] } -} diff --git a/dag/gunbc/machine_intake/mtcollins1_socket1_investigation_observation.dag b/dag/gunbc/machine_intake/mtcollins1_socket1_investigation_observation.dag index 8221aa1a0fc..ae8ae299364 100644 --- a/dag/gunbc/machine_intake/mtcollins1_socket1_investigation_observation.dag +++ b/dag/gunbc/machine_intake/mtcollins1_socket1_investigation_observation.dag @@ -394,15 +394,16 @@ data mtcollins1_socket1_hypotheses: List = [ // nominal, so "socket 1 is defective" says more than the readings do. What a boot establishes is // narrower: whether socket 1 joined the two-socket bring-up, and if not, the last stage its SMpro // reports while socket 0 carries on alone. A reading carries the firmware summary's active-socket -// count and Inter Socket Connection lines, the console checkpoints socket 1 posted (the 1xxxxxxx -// CP lines), and socket 1's SMpro CUR_BOOTSTAGE word as the BMC read it in that boot. +// count and Inter Socket Connection lines, and socket 1's SMpro CUR_BOOTSTAGE and BOOTSTAGE words as +// the BMC read them in that boot. It carries NO console checkpoint: attributing an opaque CP token to +// a socket decodes the token, which the public corpus does not do (docs/plans/power-on-sequence-model.md, +// ruling R2); that classification belongs to the gunbc-private refinement slice. type MtCollins1SecondaryJoinReading { run: NonEmptyStr capture_sha256: NonEmptyStr smpro_artifact_sha256: NonEmptyStr? active_sockets: Nat inter_socket_connection_lines: List - secondary_checkpoints: List secondary_cur_bootstage_word: Int? secondary_bootstage_word: Int? } @@ -419,7 +420,6 @@ type MtCollins1SecondaryJoinOutcome | SecondaryJoinTimeout { secondary_last_stage: SmproBootProgress inter_socket_links_reported: Bool - secondary_host_checkpoints: List } | SecondaryJoinUnclassified { reason: NonEmptyStr } @@ -436,7 +436,6 @@ fn secondary_join_outcome(reading: MtCollins1SecondaryJoinReading) -> MtCollins1 Present { value: r } => SecondaryJoinTimeout { secondary_last_stage: smpro_boot_progress_of(cur_bootstage_word: c, bootstage_word: r), inter_socket_links_reported: links, - secondary_host_checkpoints: reading.secondary_checkpoints, } } } @@ -454,14 +453,13 @@ data mtcollins1_secondary_join_reading_reference: MtCollins1SecondaryJoinReading "Inter Socket Connection 1 : Width: x16 / Speed 25 GT/s", ], smpro_artifact_sha256: Absent, - secondary_checkpoints: ["CP: 1ff00900", "CP: 10f01902", "CP: 1ff00a00", "CP: 1ff00b01", "CP: 1ff00c00", "CP: 1ff01000", "CP: 1ff01101", "CP: 1ff01200"], secondary_cur_bootstage_word: Absent, secondary_bootstage_word: Absent, } // Run 37062170720 (2026-10-02): a second, different CPU in socket 1 (an engineering sample) and its // module moved to J25 (SK1 MC4, the other memory-power half). The summary reports one active -// socket and no Inter Socket Connection lines, the console carries no socket-1 checkpoint, and the +// socket and no Inter Socket Connection lines, and the // BMC read socket 1's pair mid-boot as CUR_BOOTSTAGE 0x0001 and BOOTSTAGE 0x03ff, while socket 0 // read UEFI. That pair is inconsistent (reported stage DDR initialization ahead of current PMpro). data mtcollins1_secondary_join_reading_two_cpus: MtCollins1SecondaryJoinReading = MtCollins1SecondaryJoinReading { @@ -470,7 +468,6 @@ data mtcollins1_secondary_join_reading_two_cpus: MtCollins1SecondaryJoinReading smpro_artifact_sha256: Present { value: "023fa83b1814d1683d7bb835ec38a8bb0a2e54b86627283b3770ea89629aa7d9" }, active_sockets: 1, inter_socket_connection_lines: [], - secondary_checkpoints: [], secondary_cur_bootstage_word: Present { value: 1 }, secondary_bootstage_word: Present { value: 1023 }, } diff --git a/dag/test/claim/fleet/mtcollins_firmware_converge_witness_test.dag b/dag/test/claim/fleet/mtcollins_firmware_converge_witness_test.dag index 5f8d5c5c147..ff214e4f54a 100644 --- a/dag/test/claim/fleet/mtcollins_firmware_converge_witness_test.dag +++ b/dag/test/claim/fleet/mtcollins_firmware_converge_witness_test.dag @@ -25,7 +25,24 @@ import gunbc.machine_intake_bmc_secure { PublishedCredentialRetired, observed_bmc_credential_rotation, derive_bmc_secure, } import gunbc.machine_intake_phase { DevelopmentAndTest, intake_qualification_policy } -import gunbc.machine_intake_boot_outcome { HostBootObservation, HostReachedUefi, mtcollins1_post_firmware_set_boot_2026_10_03 } +import gunbc.machine_intake_mtcollins1_power_on_account { + PowerOnAccount, PowerOnEvidence, AttemptConfigurationReceipt, ConsoleRead, HandoffPoweredOn, SelEventsRead, WatchdogUnobserved, + ExpectedSockets, StimulusRequestNotRecorded, StimulusApplicationNotRecorded, PopulationNotRecorded, FirmwareReadbacksNotRecorded, + FirmwareSetBootReadback, RestartedRepeatedly, mtcollins1_power_on_account, firmware_set_boot_readback, span_console_readings, +} +import extdeps.crypto.hash { sha256_digest } +import extdeps.tools.sha256sum { Sha256FileDigest } +import extdeps.bootloader.grub { grub_console_report } +import gunbc.machine_intake_pre_os_bringup_verdict { firmware_console_report } +import gunbc.machine_intake_ampere_dram_console_observation { ampere_dram_console_observation } +import gunbc.machine_intake_ampere_checkpoint_console_observation { ampere_checkpoint_console_observation } +import gunbc.machine_intake_mtcollins1_console_milestone_observation { mtcollins1_console_milestones } +import gunbc.machine_intake_ampere_socket_console_observation { ampere_socket_console_observation } +import gunbc.machine_intake_linux_boot_console_observation { linux_boot_console_observation } +import gunbc.machine_intake_kernel_module_decompression_observation { kernel_module_decompression_observation } +import gunbc.machine_intake_host_capture_envelope { HostCaptureLiveStream, host_capture_lines } +import gunbc.machine_intake_pre_os_bringup_verdict { SelWindowEvent, BootRestart } +import extdeps.bmc.ipmi_sel { SystemRestart } import gunbc.machine_intake_mtcollins1_boot_diagnostic_bundle { SelSnapshotReading, SelSnapshotRead, SelSnapshotNotTaken } import gunbc.machine_intake_mtcollins1_socket1_investigation_observation { MtCollins1SocketCpu, ProductionPart, mtcollins1_socket_cpus, mtcollins1_cpu_report_2026_09_23, @@ -51,7 +68,7 @@ import gunbc.fleet.mtcollins_firmware_converge { RemoteMediaReceipt, RemoteMediaReadback, RemoteMediaImagesEnumerated, RemoteMediaNotConfigured, RemoteMediaNotRestored, KvmRight, mtcollins1_required_managed_rights, VersionReadback, UncitedReadback, hpm_check_readback, HostPoweredOff, HostPoweredOn, BmcAnswering, BmcNotAnswering, FirmwareCredentialReprovisionDue, FirmwareBootReadbackDue, - BmcCredentialReestablishment, ManagedRightNotRestored, ManagedRightMissingFromReceipt, BootProgressLostAfterFirmwareSet, + BmcCredentialReestablishment, ManagedRightNotRestored, ManagedRightMissingFromReceipt, BootProgressLostAfterFirmwareSet, BootReachedUefiThenRestartedRepeatedly, CredentialNotReestablished, CredentialEpochNotAdvancedPastFlash, CredentialForAnotherSubject, CredentialForAnotherEndpoint, CredentialProbePrecedesFlash, RightsReceiptForAnotherEndpointOrAccount, RightsReceiptPrecedesFlash, @@ -205,7 +222,7 @@ fn world_of(subject: MachineIntakeSubject, w: WorldShape) -> MtCollinsFirmwareWo // THE SUBJECT IS mtcollins1's RECORDED INTAKE SUBJECT. If it ever stops deriving, every claim must FAIL, // so the fallback is a refusal no claim accepts (observed -1 matches no CpuInventoryIncomplete claim). -fn run_full(w: WorldShape, baseline: FirmwareBaseline, records: List, boot: HostBootObservation?) -> FirmwareConvergenceVerdict { +fn run_full(w: WorldShape, baseline: FirmwareBaseline, records: List, boot: FirmwareSetBootReadback?) -> FirmwareConvergenceVerdict { match mtcollins1_intake_subject() { Present { value: s } => converge_firmware(baseline: baseline, world: world_of(subject: s, w: w), records: records, boot: boot) Absent => FirmwareConvergenceRefused { cause: CpuInventoryIncomplete { observed: -1, expected: -1 } } @@ -216,7 +233,7 @@ fn run(w: WorldShape, records: List) -> FirmwareConvergenceVerd run_full(w: w, baseline: baseline_ampere_ticket_7426_delivery, records: records, boot: Absent) } -fn run_booted(w: WorldShape, records: List, boot: HostBootObservation?) -> FirmwareConvergenceVerdict { +fn run_booted(w: WorldShape, records: List, boot: FirmwareSetBootReadback?) -> FirmwareConvergenceVerdict { run_full(w: w, baseline: baseline_ampere_ticket_7426_delivery, records: records, boot: boot) } @@ -903,20 +920,75 @@ test fn whole_set_grounded_asks_for_the_boot_readback() -> Bool { } } +// ── THE POST-SET READBACK IS DERIVED FROM A POWER-ON ACCOUNT, never authored (ruling R1) ───────── +// The accounts below run the real console readers over console text: the 2026-10-03 loop from its +// committed capture excerpt, and the UEFI cases from the lines the firmware summary and the AMI setup +// prompt print (gunbc.machine_intake_mtcollins1_console_milestone_observation). +// branch mtcollins1-captures-2026-10-03, mtcollins1/step1-j1j17-20261003T042447Z/sol.raw +// (sha256 6f33812ce2af74aacf9deba90931aa9144b1ba974532ae9e890b90a57c5900c1), lines 138-205; +// excerpt sha256 edbcaabead8194544201251f9a8a5eb26ba3dfbb8da1caec9627487f260d153d. +data step1_last_two_cycles: String = "NOTICE: DRAM FW version 220414\r\nCP: 0ff00900\r\nMEMC param:\r\n mcu_enable_mask = 00000001 [default: 000000ff]\r\nMCU param:\r\n ecc_en = 00000002 [default: 00000001]\r\nDRAM populated DIMMs:\r\n SK0 MC0 S0: RDIMM[ad:80] 16GB 2666 ECC 1R x4 RCD[32:86] HMA82GR7AFR4N-VK \r\nCP: 3ff00100\r\nCP: 3ff00200\r\nCP: 00f01902\r\nCP: 0ff00a00\r\nCP: 0ff00b01\r\nCP: 00f01901\r\nCP: 00f01904\r\nCP: 00f02901\r\nCP: 00f01905\r\nCP: 00f02902\r\nCP: 00f02903\r\nCP: 00f02904\r\nCP: 00f01907\r\nCP: 00f01906\r\nCP: 00f01908\r\nCP: 00f01909\r\nCP: 0ff00b02\r\nCP: 0ff00c00\r\nCP: 3ff00300\r\nCP: 00001a02\r\nCP: 00001a03\r\nCP: 00001a04\r\nCP: 00001a05\r\nCP: 00f01a07\r\nCP: 00001a09\r\nCP: 00001a0a\r\nNOTICE: DRAM FW version 220414\r\nCP: 0ff00900\r\nMEMC param:\r\n mcu_enable_mask = 00000001 [default: 000000ff]\r\nMCU param:\r\n ecc_en = 00000002 [default: 00000001]\r\nDRAM populated DIMMs:\r\n SK0 MC0 S0: RDIMM[ad:80] 16GB 2666 ECC 1R x4 RCD[32:86] HMA82GR7AFR4N-VK \r\nCP: 3ff00100\r\nCP: 3ff00200\r\nCP: 00f01902\r\nCP: 0ff00a00\r\nCP: 0ff00b01\r\nCP: 00f01901\r\nCP: 00f01904\r\nCP: 00f02901\r\nCP: 00f01905\r\nCP: 00f02902\r\nCP: 00f02903\r\nCP: 00f02904\r\nCP: 00f01907\r\nCP: 00f01906\r\nCP: 00f01908\r\nCP: 00f01909\r\nCP: 0ff00b02\r\nCP: 0ff00c00\r\nCP: 3ff00300\r\nCP: 00001a02\r\nCP: 00001a03\r\nCP: 00001a04\r\nCP: 00001a05\r\nCP: 00f01a07\r\nCP: 00001a09\r\nCP: 00001a0a\r\n" + +// Real "System Restart" rows of the step-1 loop (sel-after.txt), as the SEL reader types them. +// After a confirmed power-on with no power-up record, the first such row is ambiguous (it may be the +// power-on's own boot) and opens no cycle; each later one is a distinct reset (rows 33, 37, 3b). +data loop_restarts: List = [ + SelWindowEvent { record_id: 51, bmc_clock_rendering: "10/02/2026 07:32:01 PM UTC", kind: BootRestart { offset: SystemRestart } }, + SelWindowEvent { record_id: 55, bmc_clock_rendering: "10/02/2026 07:32:27 PM UTC", kind: BootRestart { offset: SystemRestart } }, + SelWindowEvent { record_id: 59, bmc_clock_rendering: "10/02/2026 07:32:52 PM UTC", kind: BootRestart { offset: SystemRestart } }, +] + +fn account_over(lines: List, restarts: List) -> PowerOnAccount { + let digest = Sha256FileDigest { digest: sha256_digest(hex: "6f33812ce2af74aacf9deba90931aa9144b1ba974532ae9e890b90a57c5900c1") } + mtcollins1_power_on_account(e: PowerOnEvidence { + configuration: AttemptConfigurationReceipt { + subject: "mtcollins1", attempt: "witness", expected: ExpectedSockets { sockets: [0, 1], source: "witness" }, + requested: StimulusRequestNotRecorded { reason: "witness" }, applied: StimulusApplicationNotRecorded { reason: "witness" }, + cpus: PopulationNotRecorded { reason: "witness" }, dimms: PopulationNotRecorded { reason: "witness" }, firmware: FirmwareReadbacksNotRecorded { reason: "witness" }, + }, + handoff: HandoffPoweredOn, smpro: [], + console: ConsoleRead { + capture: digest, firmware: firmware_console_report(lines: lines), firmware_error_lines: 0, + dram: ampere_dram_console_observation(lines: lines), checkpoints: ampere_checkpoint_console_observation(lines: lines), relations: [], + milestones: mtcollins1_console_milestones(lines: lines), sockets: ampere_socket_console_observation(lines: lines), + loader: grub_console_report(lines: lines), linux: linux_boot_console_observation(lines: lines), modules: kernel_module_decompression_observation(lines: lines), span_readings: span_console_readings(lines: lines), + }, + sel: SelEventsRead { events: restarts }, watchdog: WatchdogUnobserved { reason: "witness" }, bmc_anomalies: [], census_ended: false, coverage: [], + }) +} + +data uefi_cycle: List = ["NOTICE: DRAM FW version 220414", "CP: 0ff00900", "UEFI RC version: 2.10", " Number of active sockets : 1", "Press or to Entering the Setup."] + +data dram_only_cycle: List = ["NOTICE: DRAM FW version 220414", "CP: 0ff00900", "CP: 00001a0a"] + +fn readback_over(lines: List, restarts: List) -> FirmwareSetBootReadback? { + Present { value: firmware_set_boot_readback(a: account_over(lines: lines, restarts: restarts)) } +} + // The set converges on UEFI, and the converged verdict NAMES the SCP readback as grounded through the // uncited SMpro decode -- it is never promoted to an unqualified observation. test fn whole_set_reaching_uefi_converges_naming_the_uncited_scp_decode() -> Bool { - match run_booted(w: shape(power_off: true), records: whole_set(), boot: Present { value: HostReachedUefi }) { + match run_booted(w: shape(power_off: true), records: whole_set(), boot: readback_over(lines: uefi_cycle, restarts: [])) { FirmwareBaselineConverged { uncited_readbacks: u } => list_length(u) == 1 && any(u, x => (x.target == ScpFirmware)) _ => false } } -// RED, the 2026-10-03 observation: opaque checkpoint 0x1a0a (6666), then ~22 s of silence. -test fn post_ddr_progress_lost_after_the_set_refuses() -> Bool { - match refusal_of(verdict: run_booted(w: shape(power_off: true), records: whole_set(), boot: Present { value: mtcollins1_post_firmware_set_boot_2026_10_03 })) { - Present { value: c } => match c { BootProgressLostAfterFirmwareSet { last_checkpoint: k, soc_silent: q, initiator: _ } => k == 6666 && second_count(s: q) == 22 _ => false } +// RED, the 2026-10-03 loop, derived from its capture: the set did not reach UEFI and the host restarted +// repeatedly, so convergence refuses with the account's own ending. +test fn the_2026_10_03_loop_after_the_set_refuses_with_the_derived_ending() -> Bool { + match refusal_of(verdict: run_booted(w: shape(power_off: true), records: whole_set(), boot: readback_over(lines: host_capture_lines(capture: HostCaptureLiveStream { text: step1_last_two_cycles }), restarts: loop_restarts.take(n: 2)))) { + Present { value: c } => match c { BootProgressLostAfterFirmwareSet { ending: e, last_relation: _ } => (match e { RestartedRepeatedly { cycles: n, last_cycle_ended_after: _, cadence: _, console_errors: k, sel_restarts: _, watchdog: _, reset_cause: _ } => n == 2 && k == 0 _ => false }) _ => false } + Absent => false + } +} + +// RED: reaching UEFI is a milestone, not an ending. UEFI in cycle 1 and then a loop that never got as far +// again is not a converged boot. +test fn uefi_then_a_restart_loop_refuses() -> Bool { + match refusal_of(verdict: run_booted(w: shape(power_off: true), records: whole_set(), boot: readback_over(lines: concat(uefi_cycle, concat(dram_only_cycle, dram_only_cycle)), restarts: loop_restarts))) { + Present { value: c } => match c { BootReachedUefiThenRestartedRepeatedly { cycle: k, ending: _ } => k == 1 _ => false } Absent => false } } diff --git a/dag/test/claim/machine_intake/ampere_dram_console_observation_witness_test.dag b/dag/test/claim/machine_intake/ampere_dram_console_observation_witness_test.dag index 620fc13cfd6..1b0d877bc29 100644 --- a/dag/test/claim/machine_intake/ampere_dram_console_observation_witness_test.dag +++ b/dag/test/claim/machine_intake/ampere_dram_console_observation_witness_test.dag @@ -4,7 +4,7 @@ import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } import std.types { Bool, List, String } import gunbc.machine_intake_ampere_dram_console_observation { AmpereDramBlockNotPrinted, AmpereDramBlockObserved, RosterNotObserved, RosterOpen, RosterClosed, - ampere_dram_console_observation, ampere_dram_findings, ampere_dram_console_text, ampere_dram_dimms_on_socket, + ampere_dram_console_observation, ampere_dram_untrained_sockets, ampere_dram_console_text, ampere_dram_dimms_on_socket, } import gunbc.machine_intake_host_capture_envelope { HostCaptureLiveStream, host_capture_lines } @@ -66,15 +66,15 @@ test fn the_nul_before_the_first_notice_does_not_hide_the_block() -> Bool { } } -test fn only_the_empty_socket_is_a_finding_over_the_real_capture() -> Bool { - let f = ampere_dram_findings(observation: ampere_dram_console_observation(lines: lines_of(text: capture_35801475989_last_block)), expected_sockets: [0, 1]) - count(f) == 1 && any_contains(xs: f, pattern: "DRAM firmware 210525 printed no trained DIMM for socket 1") +test fn only_the_empty_socket_is_untrained_over_the_real_capture() -> Bool { + ampere_dram_untrained_sockets(observation: ampere_dram_console_observation(lines: lines_of(text: capture_35801475989_last_block)), expected_sockets: [0, 1]) == [1] } // A PARAM DIFFERING FROM ITS PRINTED DEFAULT IS AN OBSERVATION, NEVER A FINDING. test fn a_non_default_param_is_rendered_but_never_a_finding() -> Bool { let o = ampere_dram_console_observation(lines: lines_of(text: capture_35801475989_last_block)) - !any_contains(xs: ampere_dram_findings(observation: o, expected_sockets: [0, 1]), pattern: "mcu_enable_mask") + (match o { AmpereDramBlockObserved { block: b, blocks_printed: _ } => count(b.malformed) == 0 AmpereDramBlockNotPrinted => false }) + && ampere_dram_untrained_sockets(observation: o, expected_sockets: [0, 1]) == [1] && contains(ampere_dram_console_text(observation: o, expected_sockets: [0, 1]), " MEMC mcu_enable_mask = 00000011 [default 000000ff] (differs from printed default)") } @@ -85,9 +85,9 @@ test fn the_render_carries_the_roster_by_socket() -> Bool { // ── REDS: THE REAL BYTES WITH ONE THING CHANGED ────────────────────────────────────────────── // MUTATION: the SK0 rows dropped from the real capture make socket 0 empty too. -test fn dropping_the_sk0_rows_makes_socket_0_a_finding() -> Bool { +test fn dropping_the_sk0_rows_makes_socket_0_untrained() -> Bool { let kept = filter(lines_of(text: capture_35801475989_last_block), l => !contains(l, " SK0 ")) - any_contains(xs: ampere_dram_findings(observation: ampere_dram_console_observation(lines: kept), expected_sockets: [0, 1]), pattern: "no trained DIMM for socket 0") + ampere_dram_untrained_sockets(observation: ampere_dram_console_observation(lines: kept), expected_sockets: [0, 1]) == [0, 1] } // MUTATION: the param rows dropped leave no params, and that changes no finding. @@ -109,9 +109,12 @@ test fn the_joined_spelling_is_not_read_as_a_roster() -> Bool { test fn an_unparsable_row_inside_the_roster_is_a_typed_malformed_finding() -> Bool { let text = "NOTICE: DRAM FW version 210525\r\nDRAM populated DIMMs:\r\n socket0/mc0/slot0 RDIMM\r\n SK0 MC1 S0: RDIMM\r\nCP: 3ff00100\r\n" - let f = ampere_dram_findings(observation: ampere_dram_console_observation(lines: lines_of(text: text)), expected_sockets: [0]) - any_contains(xs: f, pattern: "row under 'DRAM populated DIMMs:' not in the transcribed grammar: socket0/mc0/slot0 RDIMM") - && !any_contains(xs: f, pattern: "socket 0") + let o = ampere_dram_console_observation(lines: lines_of(text: text)) + (match o { + AmpereDramBlockObserved { block: b, blocks_printed: _ } => any(b.malformed, m => (m.subsection as String) == "DRAM populated DIMMs:" && contains(m.line, "socket0/mc0/slot0 RDIMM")) + AmpereDramBlockNotPrinted => false + }) + && ampere_dram_untrained_sockets(observation: o, expected_sockets: [0]) == [] } test fn an_unindented_line_closes_the_roster() -> Bool { @@ -125,7 +128,7 @@ test fn an_unindented_line_closes_the_roster() -> Bool { test fn a_capture_without_the_block_asserts_no_roster() -> Bool { let o = ampere_dram_console_observation(lines: lines_of(text: "CP: 3ff00100\r\n SK0 MC0 S0: RDIMM\r\n")) (match o { AmpereDramBlockNotPrinted => true _ => false }) - && count(ampere_dram_findings(observation: o, expected_sockets: [0, 1])) == 0 + && ampere_dram_untrained_sockets(observation: o, expected_sockets: [0, 1]) == [] } test fn a_reset_loop_reads_the_last_block() -> Bool { @@ -154,7 +157,7 @@ test fn a_capture_cut_mid_roster_is_open_and_mints_no_empty_socket() -> Bool { AmpereDramBlockObserved { block: b, blocks_printed: _ } => (match b.roster { RosterOpen => true _ => false }) && ampere_dram_dimms_on_socket(block: b, socket: 0) == 2 AmpereDramBlockNotPrinted => false }) - && !any_contains(xs: ampere_dram_findings(observation: o, expected_sockets: [0, 1]), pattern: "no trained DIMM") + && ampere_dram_untrained_sockets(observation: o, expected_sockets: [0, 1]) == [] && contains(ampere_dram_console_text(observation: o, expected_sockets: [0, 1]), "roster: OPEN at the end of the capture") } @@ -166,7 +169,7 @@ test fn a_block_without_a_roster_header_mints_no_empty_socket() -> Bool { AmpereDramBlockObserved { block: b, blocks_printed: _ } => match b.roster { RosterNotObserved => true _ => false } AmpereDramBlockNotPrinted => false }) - && count(ampere_dram_findings(observation: o, expected_sockets: [0, 1])) == 0 + && ampere_dram_untrained_sockets(observation: o, expected_sockets: [0, 1]) == [] } // A new block header closes the previous block's roster; the last block's roster, cut, stays open. diff --git a/dag/test/claim/machine_intake/kernel_module_decompression_observation_witness_test.dag b/dag/test/claim/machine_intake/kernel_module_decompression_observation_witness_test.dag index 3a2a98c726d..ba3a2bc11ae 100644 --- a/dag/test/claim/machine_intake/kernel_module_decompression_observation_witness_test.dag +++ b/dag/test/claim/machine_intake/kernel_module_decompression_observation_witness_test.dag @@ -5,11 +5,12 @@ import std.types { Bool, List, String } import std.measure { byte_size } import extdeps.linux.module_decompress { ZstdCorruptionDetected, ZstdErrorCodeOther } import gunbc.machine_intake_kernel_module_decompression_observation { - kernel_module_decompression_observation, kernel_module_decompression_text, kernel_module_decompression_findings, + kernel_module_decompression_observation, kernel_module_decompression_text, KernelModuleDecompressionObservation, NoRefusalRecorded, RefusalRecorded, RefusalIdentityUnread, RefusalIdentityCandidates, ModuleInsertFailure, DriverPrintedLater, DriverNotSeenInCapture, } import gunbc.machine_intake_mtcollins1_boot_diagnostic_bundle { mtcollins1_boot_console_reading, ReferenceCaptureRead, ConsoleRetained } +import extdeps.tools.sha256sum { Sha256FileDigest, Sha256FileDigestUnavailable } data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly @@ -60,8 +61,7 @@ data instrumented_excerpt: List = [ // POSITIVE CONTROL: the census-reaching boot is not a refusal, renesas warning included. test fn the_census_reaching_capture_reads_as_not_refused() -> Bool { let o = kernel_module_decompression_observation(lines: census_reaching_excerpt) - count(kernel_module_decompression_findings(o: o)) == 0 - && match o { + match o { NoRefusalRecorded { insert_failures: fs, unparsed_insert_records: us } => count(fs) == 0 && count(us) == 0 _ => false } @@ -198,12 +198,14 @@ test fn unrelated_text_is_not_a_record() -> Bool { } } +data unread_digest: Sha256FileDigest = Sha256FileDigestUnavailable { path: "witness", reason: "not digested in this claim" } + // INHABITANCE: the attempt-bound console reading carries this observation from the capture text it // retains -- the real route the boot bundle renders, not a supplied observation. test fn the_retained_console_reading_carries_the_refusal() -> Bool { let capture = join(failed_attempt_excerpt, "\n") - match mtcollins1_boot_console_reading(path: "artifacts/mtcollins1-boot-sol.capture", size: byte_size(count: 10), capture: concat(capture, "\n"), read_ok: true, read_error: "", reference: ReferenceCaptureRead { text: "", ok: false, error: "not supplied" }) { - ConsoleRetained { path: _, size: _, lines: _, firmware: _, dram: _, sockets: _, against_reference: _, modules: m, nvparam: _ } => + match mtcollins1_boot_console_reading(path: "artifacts/mtcollins1-boot-sol.capture", size: byte_size(count: 10), capture: concat(capture, "\n"), read_ok: true, read_error: "", capture_digest: unread_digest, reference: ReferenceCaptureRead { text: "", ok: false, error: "not supplied", digest: unread_digest }) { + ConsoleRetained { path: _, size: _, lines: _, firmware: _, firmware_error_lines: _, capture_digest: _, dram: _, checkpoints: _, checkpoint_relations: _, milestones: _, sockets: _, against_reference: _, loader: _, linux: _, modules: m, nvparam: _, span_readings: _ } => match m { RefusalRecorded { refusals: rs, unparsed_refusal_records: _, insert_failures: _, unparsed_insert_records: _, identity: RefusalIdentityUnread } => count(rs) == 2 _ => false } _ => false } diff --git a/dag/test/claim/machine_intake/megarac_media_convergence_witness_test.dag b/dag/test/claim/machine_intake/megarac_media_convergence_witness_test.dag index 189611307d4..4a2dda0ae54 100644 --- a/dag/test/claim/machine_intake/megarac_media_convergence_witness_test.dag +++ b/dag/test/claim/machine_intake/megarac_media_convergence_witness_test.dag @@ -116,7 +116,7 @@ import gunbc.machine_intake_megarac_media_attach { SessionReleaseNotAttempted, } import extdeps.bmc.ipmi_chassis_control { ChassisPowerRead } -import gunbc.machine_intake_mtcollins1_boot_diagnostic_bundle { media_attach_text, media_attach_outcome_reason, MediaAttachAttempted, MtCollins1MediaAttachRecord, detach_evidence_text, HandoffMediaAttempted, HandoffConfirmed, HandoffAttemptedUnconfirmed, MediaAttachNotAttempted, StalePowerOffNotNeeded, end_media_is_owed, HandoffMediaWithheldAtRecheck, handoff_media_findings, MtCollins1HandoffMedia, MtCollins1EndMedia, EndMediaObserved, EndMediaNotTaken, media_loss_after_handoff, outcome_attributing_media_loss } +import gunbc.machine_intake_mtcollins1_boot_diagnostic_bundle { media_attach_text, media_attach_outcome_reason, MediaAttachAttempted, MtCollins1MediaAttachRecord, detach_evidence_text, HandoffMediaAttempted, HandoffConfirmed, HandoffAttemptedUnconfirmed, MediaAttachNotAttempted, StalePowerOffNotNeeded, end_media_is_owed, HandoffMediaWithheldAtRecheck, handoff_media_harness, harness_finding_text, MtCollins1HandoffMedia, MtCollins1EndMedia, EndMediaObserved, EndMediaNotTaken, media_loss_after_handoff, outcome_attributing_media_loss } import gunbc.machine_intake_mtcollins1_media_attach { MediaGateReady, MediaGateWithheld, mtcollins1_media_gate, mtcollins1_media_share } data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly @@ -673,6 +673,11 @@ fn observed(general: String, configurations: String) -> MegaRacAttachResult { } } +// The handoff's typed harness findings, rendered once each by the bundle's own renderer. +fn handoff_media_findings(h: MtCollins1HandoffMedia) -> List { + map(handoff_media_harness(h: h), f => harness_finding_text(f: f)) +} + fn only_finding_contains(findings: List, pattern: String) -> Bool { count(findings) == 1 && (match findings.first() { Present { value: f } => string_contains(s: f, pattern: pattern) Absent => false }) } diff --git a/dag/test/claim/machine_intake/mtcollins1_bmc_sensor_observation_witness_test.dag b/dag/test/claim/machine_intake/mtcollins1_bmc_sensor_observation_witness_test.dag index d94eb72a221..4be759fbe40 100644 --- a/dag/test/claim/machine_intake/mtcollins1_bmc_sensor_observation_witness_test.dag +++ b/dag/test/claim/machine_intake/mtcollins1_bmc_sensor_observation_witness_test.dag @@ -8,8 +8,9 @@ import extdeps.bmc.ipmi_sdr { SdrLineResult, SdrLineDecoded, sdr_decode_lines } import gunbc.machine_intake_mtcollins1_32dimm_bringup_observation { mtcollins1_sdr_capture } import gunbc.machine_intake_mtcollins1_bmc_sensor_observation { mtcollins1_sensor_socket, mtcollins1_socket_sensor_lines, mtcollins1_socket_unstructured_lines, - mtcollins1_sensor_findings, mtcollins1_sensor_table_text, + mtcollins1_sensor_anomalies, mtcollins1_sensor_table_text, } +import gunbc.machine_intake_mtcollins1_power_on_account { SensorNotNominal, SensorLineWithoutReadingNotNominal, SocketSensorsAbsent } import std.measure { byte_size_count } data live_tree_disposition: LiveTreeDisposition = ReadsLiveTree @@ -68,8 +69,8 @@ test fn the_discrete_status_sensors_are_kept_per_socket() -> Bool { table_read_whole() && any(mtcollins1_socket_unstructured_lines(lines: table(), socket: 1), t => starts_with(s: t, prefix: "CPU1_CPU_Status")) } -test fn a_two_socket_table_with_every_status_ok_has_no_finding() -> Bool { - table_read_whole() && count(mtcollins1_sensor_findings(lines: table(), sockets: [0, 1])) == 0 +test fn a_two_socket_table_with_every_status_ok_has_no_anomaly() -> Bool { + table_read_whole() && count(mtcollins1_sensor_anomalies(lines: table(), sockets: [0, 1], expected: Present { value: [0, 1] })) == 0 } // ── discriminating reds over the same bytes ────────────────────────────────────────────────── @@ -79,21 +80,27 @@ fn table_with(from: String, to: String) -> List { } test fn a_socket_rail_with_no_reading_is_named() -> Bool { - let f = mtcollins1_sensor_findings(lines: table_with(from: "CPU1_CPU_VCORE | 58h | ok | 19.51 | 1.03 Volts", to: "CPU1_CPU_VCORE | 58h | ns | 19.51 | No Reading"), sockets: [0, 1]) - table_read_whole() && count(f) == 1 && any(f, x => starts_with(s: x, prefix: "bmc sensor CPU1_CPU_VCORE status ns")) + let f = mtcollins1_sensor_anomalies(lines: table_with(from: "CPU1_CPU_VCORE | 58h | ok | 19.51 | 1.03 Volts", to: "CPU1_CPU_VCORE | 58h | ns | 19.51 | No Reading"), sockets: [0, 1], expected: Present { value: [0, 1] }) + table_read_whole() && count(f) == 1 && any(f, x => match x { SensorNotNominal { line: l } => (l.name as String) == "CPU1_CPU_VCORE" && (l.status as String) == "ns" _ => false }) } test fn a_discrete_socket_sensor_not_ok_is_named() -> Bool { - let f = mtcollins1_sensor_findings(lines: table_with(from: "CPU1_CPU_Status | DAh | ok ", to: "CPU1_CPU_Status | DAh | ns "), sockets: [0, 1]) - table_read_whole() && count(f) == 1 && any(f, x => string_contains(s: x, pattern: "CPU1_CPU_Status")) + let f = mtcollins1_sensor_anomalies(lines: table_with(from: "CPU1_CPU_Status | DAh | ok ", to: "CPU1_CPU_Status | DAh | ns "), sockets: [0, 1], expected: Present { value: [0, 1] }) + table_read_whole() && count(f) == 1 && any(f, x => match x { SensorLineWithoutReadingNotNominal { text: t } => string_contains(s: t as String, pattern: "CPU1_CPU_Status") _ => false }) } -test fn a_socket_with_no_sensor_at_all_is_a_finding() -> Bool { - let f = mtcollins1_sensor_findings(lines: table_with(from: "CPU1_", to: "GONE_"), sockets: [0, 1]) - table_read_whole() && join(f, ";") == "bmc sensors: no CPU1_ sensor in the table" +test fn a_socket_with_no_sensor_at_all_is_an_anomaly() -> Bool { + let f = mtcollins1_sensor_anomalies(lines: table_with(from: "CPU1_", to: "GONE_"), sockets: [0, 1], expected: Present { value: [0, 1] }) + table_read_whole() && count(f) == 1 && any(f, x => match x { SocketSensorsAbsent { socket: k } => k == 1 _ => false }) } test fn the_rendering_groups_by_socket() -> Bool { let t = mtcollins1_sensor_table_text(lines: table(), sockets: [0, 1]) table_read_whole() && string_contains(s: t, pattern: "socket 1 (CPU1_ sensors:") && string_contains(s: t, pattern: "CPU1_CPU_VCORE | 58h | ok | 19.51 | 1.03 Volts") } + +// GAP 2: AN EMPTY SOCKET'S MISSING SENSORS ARE AN ANOMALY ONLY WHEN THE RECEIPT EXPECTS THE SOCKET. +test fn a_socket_with_no_sensor_is_no_anomaly_when_only_socket_0_is_expected() -> Bool { + table_read_whole() && count(mtcollins1_sensor_anomalies(lines: table_with(from: "CPU1_", to: "GONE_"), sockets: [0, 1], expected: Present { value: [0] })) == 0 + && count(mtcollins1_sensor_anomalies(lines: table_with(from: "CPU1_", to: "GONE_"), sockets: [0, 1], expected: Absent)) == 0 +} diff --git a/dag/test/claim/machine_intake/mtcollins1_boot_diagnostic_bundle_witness_test.dag b/dag/test/claim/machine_intake/mtcollins1_boot_diagnostic_bundle_witness_test.dag index da13446611e..3d76d60a6fa 100644 --- a/dag/test/claim/machine_intake/mtcollins1_boot_diagnostic_bundle_witness_test.dag +++ b/dag/test/claim/machine_intake/mtcollins1_boot_diagnostic_bundle_witness_test.dag @@ -21,15 +21,15 @@ import gunbc.machine_intake_mtcollins1_boot_diagnostic_bundle { MtCollins1BootRunRecord, run_record_not_taken, BootParam5Read, BootParam5Unread, BootParam5NotTaken, BootOverrideConsumption, OverrideConsumedByCensusBoot, OverrideNoLongerPendingAfterAttempt, OverridePendingBeforeAttempt, OverrideStillPendingAfterAttempt, OverrideNotHandedOff, OverrideConsumptionUnread, - boot_override_consumption, override_findings, + boot_override_consumption, override_harness, PoweredOffAfterCensusEnd, PoweredOffWithoutCensusEnd, PoweredOnAfterAttempt, PowerAfterUnobserved, - power_after_attempt, power_after_findings, + power_after_attempt, power_after_harness, MtCollins1BootDiagnosticBundle, InventoryReading, MembersRead, MembersUnread, MembersNotTaken, SelSnapshotRead, SelSnapshotUnread, SelSnapshotNotTaken, ConsoleNotConfigured, SdrCacheDumped, SdrCacheUnread, SdrCacheNotTaken, SensorsRead, SensorsNotTaken, SmproProbed, SmproNotTaken, BmcUnreachable, BmcTimeout, BmcAuthRefused, BmcHttpRefused, BmcSessionUnestablished, bmc_curl_read_cause, bmc_ipmitool_read_cause, inventory_not_taken, - mtcollins1_boot_bundle_findings, mtcollins1_boot_bundle_text, mtcollins1_boot_outcome_with_findings, + mtcollins1_boot_findings, harness_finding_text, processor_anomalies, memory_anomalies, mtcollins1_attempt_configuration_receipt, mtcollins1_boot_bundle_text, mtcollins1_boot_outcome_with_findings, mtcollins1_boot_console_reading, ReferenceCaptureRead, ConsoleRetained, ConsoleNotRetained, ConsoleExceedsBound, mtcollins1_boot_console_bound, AttemptRunId, AttemptIdentityUnavailable, BmcDeadlineReached, BmcCollectionIncomplete, CollectionComplete, CollectionRefused, redfish_collection_assembly, redfish_budget_refusal, member_not_reached, @@ -42,8 +42,10 @@ import gunbc.actions_run_binding { ActionsVariablePresent, ActionsVariableAbsent import gunbc.machine_intake_pre_os_bringup_verdict { TrainingRefusedByFirmware } import std.measure { byte_size, byte_size_count, second_count } import extdeps.bmc.ipmi_sdr { sdr_decode_lines } -import extdeps.ampere.smpro_register { smpro_manufacturer_id_reg, smpro_bootstage_reg } -import gunbc.machine_intake_mtcollins1_smpro_observation { SmproProbeRaw, smpro_probe_outcome } +import extdeps.ampere.smpro_register { smpro_manufacturer_id_reg, smpro_bootstage_reg, smpro_cur_bootstage_reg, smpro_gpi_ras_err_reg, smpro_err_pmpro_type_reg } +import gunbc.machine_intake_mtcollins1_smpro_observation { SmproProbeRaw, smpro_probe_outcome, mtcollins1_smpro_read_order } +import gunbc.machine_intake_mtcollins1_power_on_account { AttemptConfigurationReceipt, ExpectedSockets, ProcessorMissingOnExpectedSocket, ProcessorSocketDuplicated, BmcAnomaly, MemoryBelowRecordedPopulation, SlotRow, PopulationOperatorAttested, StoppedWithoutRestart, SmproErrorRegisters, SmproRegisters, CarrierRead, CarrierRefused, CarrierPartial, mtcollins1_power_on_account_notes } +import extdeps.tools.sha256sum { Sha256FileDigest, Sha256FileDigestUnavailable } data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly @@ -143,6 +145,13 @@ fn bundle_bmc_silent() -> MtCollins1BootDiagnosticBundle { } } +// WHAT THE BUNDLE FOUND, rendered: the power-on fold's decoded errors and stage anomalies, then the +// harness findings, each by its one renderer. +fn found(bundle: MtCollins1BootDiagnosticBundle) -> List { + let f = mtcollins1_boot_findings(bundle: bundle) + concat(mtcollins1_power_on_account_notes(a: f.account), map(f.harness, h => harness_finding_text(f: h))) +} + fn any_contains(xs: List, pattern: String) -> Bool { any(xs, x => contains(x, pattern)) } @@ -261,16 +270,18 @@ test fn an_incomplete_memory_collection_mints_no_short_inventory_finding() -> Bo let bundle = MtCollins1BootDiagnosticBundle { attempt: b.attempt, outcome: b.outcome, sel_before: b.sel_before, sel_after: b.sel_after, sdr_cache: b.sdr_cache, sensors: b.sensors, smpro: b.smpro, inventory: InventoryReading { system_id: "/redfish/v1/Systems/Self", memory: MembersUnread { cause: cause }, processors: b.inventory.processors, credential_removed: true }, console: ConsoleNotConfigured, run: no_run(), screen: no_screen() } - !any_contains(xs: mtcollins1_boot_bundle_findings(bundle: bundle), pattern: "Memory member(s); the census board") + !any_contains(xs: found(bundle: bundle), pattern: "Memory member(s); the board has") && contains(mtcollins1_boot_bundle_text(bundle: bundle), "memory: UNREAD: collection incomplete at https://192.168.1.228/redfish/v1/Systems/Self/Memory") } // ── THE DIAGNOSTICS DEADLINE ───────────────────────────────────────────────────────────────── -// 2 SEL snapshots, 1 SDR dump, 1 sensor read and 10 SMpro probes (2 sockets x 5 registers) x (20 s -// + 5 s kill-after) + 48 Redfish reads x 15 s + 60 s publish. +// 2 SEL snapshots, 1 SDR dump, 1 sensor read and the SMpro probes (2 sockets x the planned read order, +// gunbc.machine_intake_mtcollins1_smpro_observation mtcollins1_smpro_read_order) x (20 s + 5 s +// kill-after) + 48 Redfish reads x 15 s + 60 s publish. The SMpro term was the literal 10 until #13058 +// grew the read order to 24 registers per socket, which left this claim red on main. test fn the_allowance_is_the_sum_of_the_bounds() -> Bool { - (second_count(s: mtcollins1_boot_diagnostics_allowance()) as Int) == 14 * 25 + mtcollins1_boot_redfish_read_budget * 15 + 60 + (second_count(s: mtcollins1_boot_diagnostics_allowance()) as Int) == (4 + 2 * count(mtcollins1_smpro_read_order)) * 25 + mtcollins1_boot_redfish_read_budget * 15 + 60 } test fn a_spent_budget_refuses_the_read_and_names_what_was_not_reached() -> Bool { @@ -318,7 +329,7 @@ test fn the_real_threshold_rows_of_this_bmc_are_read_not_refused() -> Bool { } test fn the_delta_names_the_memory_event_and_not_the_cpu_voltage_sensor() -> Bool { - let findings = mtcollins1_boot_bundle_findings(bundle: bundle_socket1_absent()) + let findings = found(bundle: bundle_socket1_absent()) any_contains(xs: findings, pattern: "Memory DIMM_S1_A1 | Memory training failure") && !any_contains(xs: findings, pattern: "Voltage CPU1_0V75_PCP") } @@ -332,18 +343,68 @@ test fn the_render_keeps_raw_records_beside_the_decode_and_counts_oem_opaque() - // ── DECISIVE INVENTORY FINDINGS ────────────────────────────────────────────────────────────── -test fn a_missing_processor_member_is_a_finding_against_the_census() -> Bool { - any_contains(xs: mtcollins1_boot_bundle_findings(bundle: bundle_socket1_absent()), pattern: "BMC lists 1 Processor member(s); the 2026-09-10 census observed 2 sockets") +// A SHORTFALL IS JUDGED AGAINST THE ATTEMPT'S RECEIPT, never the historical census (law 6). With the +// production receipt (nothing recorded) none is minted and the open question says so; a receipt that +// does expect two sockets finds the one listed processor short. +test fn with_no_recorded_topology_a_missing_processor_is_a_question_not_a_shortfall() -> Bool { + let f = found(bundle: bundle_socket1_absent()) + !any_contains(xs: f, pattern: "Processor member(s);") && any_contains(xs: f, pattern: "the expected topology was not recorded for this attempt") +} + +fn expecting_two_sockets() -> AttemptConfigurationReceipt { + let r = mtcollins1_attempt_configuration_receipt(attempt: AttemptRunId { run_id: "witness" }) + AttemptConfigurationReceipt { subject: r.subject, attempt: r.attempt, expected: ExpectedSockets { sockets: [0, 1], source: "witness" }, requested: r.requested, applied: r.applied, cpus: r.cpus, dimms: r.dimms, firmware: r.firmware } +} + +// A SECOND MEMBER THAT ALSO NAMES SOCKET 0: the same spec-shaped body with another Id. +fn processor_also_socket0() -> List { + match redfish_processor_member_of(body: "{\"Id\": \"CPU0B\", \"Socket\": \"CPU 0\", \"TotalCores\": 80, \"Status\": {\"State\": \"Enabled\", \"Health\": \"OK\"}}") { + ObservationEstablished { observed: p } => [p] + ObservationRefused { cause: _ } => [] + } +} + +test fn a_receipt_expecting_two_sockets_finds_socket_1_missing() -> Bool { + any(processor_anomalies(processors: MembersRead { members: processors_socket0_only() }, receipt: expecting_two_sockets()), a => match a { ProcessorMissingOnExpectedSocket { socket: k } => k == 1 _ => false }) +} + +// RED (count is not completeness): two members, both naming socket 0, against an expectation of [0, 1] +// -- a count would call it complete; per socket, socket 0 is duplicated and socket 1 is missing. +test fn two_members_both_on_socket_0_leave_socket_1_missing_and_socket_0_duplicated() -> Bool { + let xs = processor_anomalies(processors: MembersRead { members: concat(processors_socket0_only(), processor_also_socket0()) }, receipt: expecting_two_sockets()) + any(xs, a => match a { ProcessorMissingOnExpectedSocket { socket: k } => k == 1 _ => false }) + && any(xs, a => match a { ProcessorSocketDuplicated { socket: k, members: ms } => k == 0 && count(ms) == 2 _ => false }) +} + +// ABSENT MEMBERS ARE JUDGED AGAINST THE RECEIPT'S POPULATION (law 6): with the production receipt +// (nothing recorded) the socket-1 member reading Absent mints nothing; a receipt recording that slot +// populated makes it an anomaly, and one recording it unpopulated does not. +fn receipt_with_rows(rows: List) -> AttemptConfigurationReceipt { + let r = mtcollins1_attempt_configuration_receipt(attempt: AttemptRunId { run_id: "witness" }) + AttemptConfigurationReceipt { subject: r.subject, attempt: r.attempt, expected: r.expected, requested: r.requested, applied: r.applied, cpus: r.cpus, + dimms: PopulationOperatorAttested { rows: rows, receipt: "witness inspection" }, firmware: r.firmware } +} + +fn socket1_absent_kind(xs: List) -> Bool { + any(xs, a => match a { MemoryBelowRecordedPopulation { socket: k, present: _, populated: _ } => k == 1 _ => false }) +} + +// RED (the union of sockets): the receipt records one DIMM on socket 1 and the collection is complete +// with members on socket 0 only -- socket 1 never appears in the collection, and is still reported. +test fn a_recorded_socket_the_collection_never_mentions_is_reported() -> Bool { + socket1_absent_kind(xs: memory_anomalies(memory: MembersRead { members: parsed_memory(body: memory_member_present_fixture) }, receipt: receipt_with_rows(rows: [SlotRow { label: "DIMM 1", socket: 1, populated: true }]))) } -test fn socket1_absent_slots_are_named_by_socket() -> Bool { - any_contains(xs: mtcollins1_boot_bundle_findings(bundle: bundle_socket1_absent()), pattern: "BMC reports 1 of 1 socket-1 Memory member(s) State=Absent") +test fn an_absent_member_is_an_anomaly_only_against_a_recorded_population() -> Bool { + let members = MembersRead { members: concat(parsed_memory(body: memory_member_present_fixture), socket1_absent_members()) } + !any_contains(xs: found(bundle: bundle_socket1_absent()), pattern: "socket-1 Memory member(s) State=Absent") + && socket1_absent_kind(xs: memory_anomalies(memory: members, receipt: receipt_with_rows(rows: [SlotRow { label: "DIMM 1", socket: 1, populated: true }]))) + && !socket1_absent_kind(xs: memory_anomalies(memory: members, receipt: receipt_with_rows(rows: [SlotRow { label: "DIMM 1", socket: 1, populated: false }]))) } test fn the_findings_reach_the_one_line_refusal() -> Bool { - let findings = mtcollins1_boot_bundle_findings(bundle: bundle_socket1_absent()) - match mtcollins1_boot_outcome_with_findings(outcome: exit_failure(reason: "mtcollins1 boot: host qualification REFUSED"), findings: findings) { - ExitFailure { code: _, reason: r } => contains(r, "mtcollins1 boot: host qualification REFUSED | bmc diagnostics: ") && contains(r, "census observed 2 sockets") + match mtcollins1_boot_outcome_with_findings(outcome: exit_failure(reason: "mtcollins1 boot: host qualification REFUSED"), findings: mtcollins1_boot_findings(bundle: bundle_socket1_absent())) { + ExitFailure { code: _, reason: r } => contains(r, "mtcollins1 boot: host qualification REFUSED | power-on account: ") && contains(r, "sensor CPU1_CPU_VCORE status ns") ExitSuccess => false } } @@ -351,13 +412,13 @@ test fn the_findings_reach_the_one_line_refusal() -> Bool { // ── THE BUNDLE NEVER FLIPS THE VERDICT ─────────────────────────────────────────────────────── test fn findings_never_turn_a_success_into_a_failure() -> Bool { - let findings = mtcollins1_boot_bundle_findings(bundle: bundle_socket1_absent()) - count(findings) > 0 && match mtcollins1_boot_outcome_with_findings(outcome: ExitSuccess, findings: findings) { ExitSuccess => true _ => false } + count(found(bundle: bundle_socket1_absent())) > 0 && match mtcollins1_boot_outcome_with_findings(outcome: ExitSuccess, findings: mtcollins1_boot_findings(bundle: bundle_socket1_absent())) { ExitSuccess => true _ => false } } -test fn no_findings_leave_a_refusal_reason_unchanged() -> Bool { - match mtcollins1_boot_outcome_with_findings(outcome: exit_failure(reason: "r"), findings: []) { - ExitFailure { code: _, reason: r } => r == "r" +// A REFUSAL KEEPS ITS OWN REASON FIRST; the account is appended, never substituted. +test fn a_refusal_keeps_its_reason_first() -> Bool { + match mtcollins1_boot_outcome_with_findings(outcome: exit_failure(reason: "r"), findings: mtcollins1_boot_findings(bundle: bundle_bmc_silent())) { + ExitFailure { code: _, reason: r } => starts_with(s: r, prefix: "r | power-on account: ") ExitSuccess => false } } @@ -367,7 +428,7 @@ test fn no_findings_leave_a_refusal_reason_unchanged() -> Bool { // IPMI's session failure cannot say "did not answer", so beside a Redfish connect failure the // aggregate names both kinds instead of a verdict on the BMC. test fn an_ambiguous_ipmi_failure_is_not_rendered_as_did_not_answer() -> Bool { - let findings = mtcollins1_boot_bundle_findings(bundle: bundle_bmc_silent()) + let findings = found(bundle: bundle_bmc_silent()) any_contains(xs: findings, pattern: "every BMC read failed (no IPMI session (unreachable and credential-refused are indistinguishable); unreachable); first: no IPMI session at ipmi lanplus 192.168.1.228 (2026-09-23T00:59:00Z") && !any_contains(xs: findings, pattern: "did not answer") } @@ -377,7 +438,7 @@ test fn only_definitive_no_answers_say_the_bmc_did_not_answer() -> Bool { let b = bundle_bmc_silent() let timeout = bmc_ipmitool_read_cause(endpoint: "ipmi lanplus 192.168.1.228", at: "2026-09-23T00:59:00Z", exit_code: 124, stderr: "") let bundle = MtCollins1BootDiagnosticBundle { attempt: b.attempt, outcome: b.outcome, sel_before: SelSnapshotUnread { cause: timeout }, sel_after: SelSnapshotUnread { cause: timeout }, sdr_cache: SdrCacheUnread { cause: timeout }, sensors: b.sensors, smpro: b.smpro, inventory: b.inventory, console: b.console, run: no_run(), screen: no_screen() } - any_contains(xs: mtcollins1_boot_bundle_findings(bundle: bundle), pattern: "every BMC read failed and the BMC did not answer; first: BMC timed out at ipmi lanplus 192.168.1.228") + any_contains(xs: found(bundle: bundle), pattern: "every BMC read failed and the BMC did not answer; first: BMC timed out at ipmi lanplus 192.168.1.228") } // THE DISCRIMINATING CONTROL for the aggregate: the SDR dump and the sensor read answer while @@ -388,16 +449,16 @@ test fn a_bmc_that_answered_on_the_sensor_channel_is_not_all_failed() -> Bool { let good = bundle_socket1_absent() let bundle = MtCollins1BootDiagnosticBundle { attempt: b.attempt, outcome: b.outcome, sel_before: b.sel_before, sel_after: b.sel_after, sdr_cache: good.sdr_cache, sensors: good.sensors, smpro: b.smpro, inventory: b.inventory, console: b.console, run: no_run(), screen: no_screen() } - let findings = mtcollins1_boot_bundle_findings(bundle: bundle) + let findings = found(bundle: bundle) !any_contains(xs: findings, pattern: "every BMC read failed") - && any_contains(xs: findings, pattern: "bmc sensor CPU1_CPU_VCORE status ns") + && any_contains(xs: findings, pattern: "sensor CPU1_CPU_VCORE status ns") && contains(mtcollins1_boot_bundle_text(bundle: bundle), "before: UNREAD: no IPMI session") } // The same bundle with the sensor channel failing too is all-failed, and names every failed channel; // the sensor read that was never attempted (no cache) is not listed as failed. test fn the_all_failed_finding_names_its_channels() -> Bool { - let findings = mtcollins1_boot_bundle_findings(bundle: bundle_bmc_silent()) + let findings = found(bundle: bundle_bmc_silent()) any_contains(xs: findings, pattern: "; failed channels: ipmi sel before, ipmi sel after, ipmi sdr dump, redfish memory, redfish processors") && !any_contains(xs: findings, pattern: "ipmi sdr elist") } @@ -453,7 +514,7 @@ test fn an_early_refusal_carries_its_own_reason_into_the_bmc_sections() -> Bool // ── THE PRE-OS CONSOLE ─────────────────────────────────────────────────────────────────────── test fn a_capture_outside_the_uploaded_glob_is_reported_not_retained() -> Bool { - match mtcollins1_boot_console_reading(path: "/tmp/sol.capture", size: byte_size(count: 1), capture: "x", read_ok: true, read_error: "", reference: no_reference) { + match mtcollins1_boot_console_reading(path: "/tmp/sol.capture", size: byte_size(count: 1), capture: "x", read_ok: true, read_error: "", capture_digest: unread_digest, reference: no_reference) { ConsoleNotRetained { path: _ } => true _ => false } @@ -461,8 +522,8 @@ test fn a_capture_outside_the_uploaded_glob_is_reported_not_retained() -> Bool { test fn a_retained_capture_reports_its_firmware_statement() -> Bool { let capture = "ERROR: Non-identical DIMM mixture NOT supported!\n" - match mtcollins1_boot_console_reading(path: "/w/artifacts/mtcollins1-boot-sol.capture", size: byte_size(count: 51), capture: capture, read_ok: true, read_error: "", reference: no_reference) { - ConsoleRetained { path: _, size: b, lines: _, firmware: f, dram: _, sockets: _, against_reference: _, modules: _, nvparam: _ } => (byte_size_count(b: b) as Int) == 51 && match f { TrainingRefusedByFirmware { statement: _, locus: _ } => true _ => false } + match mtcollins1_boot_console_reading(path: "/w/artifacts/mtcollins1-boot-sol.capture", size: byte_size(count: 51), capture: capture, read_ok: true, read_error: "", capture_digest: unread_digest, reference: no_reference) { + ConsoleRetained { path: _, size: b, lines: _, firmware: f, firmware_error_lines: _, capture_digest: _, dram: _, checkpoints: _, checkpoint_relations: _, milestones: _, sockets: _, against_reference: _, loader: _, linux: _, modules: _, nvparam: _, span_readings: _ } => (byte_size_count(b: b) as Int) == 51 && match f { TrainingRefusedByFirmware { statement: _, locus: _ } => true _ => false } _ => false } } @@ -470,7 +531,7 @@ test fn a_retained_capture_reports_its_firmware_statement() -> Bool { // THE BOUND IS DECIDED ON stat's BYTE COUNT, not on the String: a capture whose stat size exceeds // the bound is reported over it even when its content is short. test fn a_capture_over_the_byte_bound_is_reported_whole() -> Bool { - match mtcollins1_boot_console_reading(path: "artifacts/mtcollins1-boot-sol.capture", size: byte_size(count: 16777217), capture: "", read_ok: true, read_error: "", reference: no_reference) { + match mtcollins1_boot_console_reading(path: "artifacts/mtcollins1-boot-sol.capture", size: byte_size(count: 16777217), capture: "", read_ok: true, read_error: "", capture_digest: unread_digest, reference: no_reference) { ConsoleExceedsBound { path: _, size: s, bound: b } => (byte_size_count(b: s) as Int) == 16777217 && (byte_size_count(b: b) as Int) == 16777216 _ => false } @@ -505,12 +566,12 @@ test fn an_empty_dram_socket_in_the_console_reaches_the_refusal() -> Bool { sensors: SensorsNotTaken { reason: "t" }, smpro: SmproNotTaken { reason: "t" }, inventory: inventory_not_taken(reason: "t"), - console: mtcollins1_boot_console_reading(path: "artifacts/mtcollins1-boot-sol.capture", size: byte_size(count: 200), capture: capture, read_ok: true, read_error: "", reference: no_reference), + console: mtcollins1_boot_console_reading(path: "artifacts/mtcollins1-boot-sol.capture", size: byte_size(count: 200), capture: capture, read_ok: true, read_error: "", capture_digest: unread_digest, reference: no_reference), run: no_run(), screen: no_screen(), } - match mtcollins1_boot_outcome_with_findings(outcome: exit_failure(reason: "mtcollins1 boot: host qualification REFUSED"), findings: mtcollins1_boot_bundle_findings(bundle: bundle)) { - ExitFailure { code: _, reason: r } => contains(r, "printed no trained DIMM for socket 1") && !contains(r, "mcu_enable_mask") + match mtcollins1_boot_outcome_with_findings(outcome: exit_failure(reason: "mtcollins1 boot: host qualification REFUSED"), findings: mtcollins1_boot_findings(bundle: bundle)) { + ExitFailure { code: _, reason: r } => contains(r, "the expected topology was not recorded for this attempt") && !contains(r, "the closed DRAM roster lists no DIMM") && !contains(r, "mcu_enable_mask") ExitSuccess => false } } @@ -520,15 +581,18 @@ test fn an_empty_dram_socket_in_the_console_reaches_the_refusal() -> Bool { // The reference comparison's own claims (over the committed two-socket capture) are in // ampere_socket_console_observation_witness_test; here the reference is a supplied unread, and the // bundle must say so rather than print "no difference". -data no_reference: ReferenceCaptureRead = ReferenceCaptureRead { text: "", ok: false, error: "not supplied" } +// A digest the claims do not need: supplied as unavailable, which the relation carries as such. +data unread_digest: Sha256FileDigest = Sha256FileDigestUnavailable { path: "witness", reason: "not digested in this claim" } + +data no_reference: ReferenceCaptureRead = ReferenceCaptureRead { text: "", ok: false, error: "not supplied", digest: unread_digest } fn reference_of(text: String) -> ReferenceCaptureRead { - ReferenceCaptureRead { text: text, ok: true, error: "" } + ReferenceCaptureRead { text: text, ok: true, error: "", digest: unread_digest } } test fn a_dark_socket_rail_reaches_the_refusal_through_the_bundle() -> Bool { - match mtcollins1_boot_outcome_with_findings(outcome: exit_failure(reason: "mtcollins1 boot: host qualification REFUSED"), findings: mtcollins1_boot_bundle_findings(bundle: bundle_socket1_absent())) { - ExitFailure { code: _, reason: r } => contains(r, "bmc sensor CPU1_CPU_VCORE status ns") && contains(r, "bmc sensor CPU1_0V75_PCP status ns") && !contains(r, "CPU0_") + match mtcollins1_boot_outcome_with_findings(outcome: exit_failure(reason: "mtcollins1 boot: host qualification REFUSED"), findings: mtcollins1_boot_findings(bundle: bundle_socket1_absent())) { + ExitFailure { code: _, reason: r } => contains(r, "sensor CPU1_CPU_VCORE status ns") && contains(r, "sensor CPU1_0V75_PCP status ns") && !contains(r, "CPU0_") ExitSuccess => false } } @@ -547,9 +611,9 @@ test fn a_one_socket_firmware_summary_reaches_the_refusal() -> Bool { let capture = "UEFI RC version: 1.08\r\n Number of active sockets : 1\r\n Number of active cores : 80\r\n Socket[0]: Core voltage : 1065\r\n[ 0.08] {2}[Hardware Error]: Hardware error from APEI Generic Hardware Error Source: 2\r\n[ 0.08] {2}[Hardware Error]: event severity: info\r\n[ 0.08] {2}[Hardware Error]: section type: unknown, e8ed898d-df16-43cc-8ecc-54f060ef157f\r\n[ 0.08] {2}[Hardware Error]: 00000000: 000003ce 22440074 00000000 00000000 ....t.D\"........\r\n" let b = bundle_socket1_absent() let bundle = MtCollins1BootDiagnosticBundle { attempt: b.attempt, outcome: b.outcome, sel_before: b.sel_before, sel_after: b.sel_after, sdr_cache: b.sdr_cache, sensors: b.sensors, smpro: b.smpro, inventory: b.inventory, - console: mtcollins1_boot_console_reading(path: "artifacts/mtcollins1-boot-sol.capture", size: byte_size(count: 400), capture: capture, read_ok: true, read_error: "", reference: no_reference), run: no_run(), screen: no_screen() } + console: mtcollins1_boot_console_reading(path: "artifacts/mtcollins1-boot-sol.capture", size: byte_size(count: 400), capture: capture, read_ok: true, read_error: "", capture_digest: unread_digest, reference: no_reference), run: no_run(), screen: no_screen() } let text = mtcollins1_boot_bundle_text(bundle: bundle) - any_contains(mtcollins1_boot_bundle_findings(bundle: bundle), "firmware boot summary: 1 active socket(s), the census holds 2") + any_contains(found(bundle: bundle), "the expected topology was not recorded for this attempt") && !any_contains(found(bundle: bundle), "active socket(s), 2 expected") && contains(text, "GHES 2 severity=info dword0=000003ce -> socket 0 PMPRO FW subtype 3") && contains(text, "against the two-socket reference boot artifacts/bmc/mtcollins1-hw-census-sol.log: UNREAD: not supplied") } @@ -558,11 +622,11 @@ test fn a_one_socket_firmware_summary_reaches_the_refusal() -> Bool { test fn a_supplied_reference_renders_its_differences() -> Bool { let reference = "UEFI RC version: 1.08\r\n Number of active sockets : 2\r\n Inter Socket Connection 0 : Width: x16 / Speed 25 GT/s\r\n Socket[0]: Core voltage : 1065\r\n Socket[1]: Core voltage : 1035\r\n" let capture = "UEFI RC version: 1.08\r\n Number of active sockets : 1\r\n Socket[0]: Core voltage : 1065\r\n" - match mtcollins1_boot_console_reading(path: "artifacts/mtcollins1-boot-sol.capture", size: byte_size(count: 100), capture: capture, read_ok: true, read_error: "", reference: reference_of(text: reference)) { - ConsoleRetained { path: _, size: _, lines: _, firmware: _, dram: _, sockets: _, against_reference: _, modules: _, nvparam: _ } => { + match mtcollins1_boot_console_reading(path: "artifacts/mtcollins1-boot-sol.capture", size: byte_size(count: 100), capture: capture, read_ok: true, read_error: "", capture_digest: unread_digest, reference: reference_of(text: reference)) { + ConsoleRetained { path: _, size: _, lines: _, firmware: _, firmware_error_lines: _, capture_digest: _, dram: _, checkpoints: _, checkpoint_relations: _, milestones: _, sockets: _, against_reference: _, loader: _, linux: _, modules: _, nvparam: _, span_readings: _ } => { let b = bundle_socket1_absent() let text = mtcollins1_boot_bundle_text(bundle: MtCollins1BootDiagnosticBundle { attempt: b.attempt, outcome: b.outcome, sel_before: b.sel_before, sel_after: b.sel_after, sdr_cache: b.sdr_cache, sensors: b.sensors, smpro: b.smpro, inventory: b.inventory, - console: mtcollins1_boot_console_reading(path: "artifacts/mtcollins1-boot-sol.capture", size: byte_size(count: 100), capture: capture, read_ok: true, read_error: "", reference: reference_of(text: reference)), run: no_run(), screen: no_screen() }) + console: mtcollins1_boot_console_reading(path: "artifacts/mtcollins1-boot-sol.capture", size: byte_size(count: 100), capture: capture, read_ok: true, read_error: "", capture_digest: unread_digest, reference: reference_of(text: reference)), run: no_run(), screen: no_screen() }) contains(text, "3 difference(s)") && contains(text, "Socket[1] printed in the reference summary, absent from this boot's") && contains(text, "Inter Socket Connection 0 (reference: Width: x16 / Speed 25 GT/s) not printed by this boot") } @@ -583,12 +647,13 @@ test fn a_socket_one_smpro_stage_reaches_the_refusal() -> Bool { smpro_probe_outcome(raw: smpro_raw(socket: 0, register: smpro_manufacturer_id_reg.name, exit_code: 0, stdout: "cd 3a", stderr: "")), smpro_probe_outcome(raw: smpro_raw(socket: 0, register: smpro_bootstage_reg.name, exit_code: 0, stdout: "09 02", stderr: "")), smpro_probe_outcome(raw: smpro_raw(socket: 1, register: smpro_manufacturer_id_reg.name, exit_code: 0, stdout: "cd 3a", stderr: "")), + smpro_probe_outcome(raw: smpro_raw(socket: 1, register: smpro_cur_bootstage_reg.name, exit_code: 0, stdout: "00 03", stderr: "")), smpro_probe_outcome(raw: smpro_raw(socket: 1, register: smpro_bootstage_reg.name, exit_code: 0, stdout: "03 03", stderr: "")), ] let bundle = MtCollins1BootDiagnosticBundle { attempt: b.attempt, outcome: b.outcome, sel_before: b.sel_before, sel_after: b.sel_after, sdr_cache: b.sdr_cache, sensors: b.sensors, smpro: SmproProbed { outcomes: outcomes }, inventory: b.inventory, console: b.console, run: no_run(), screen: no_screen() } - (match mtcollins1_boot_outcome_with_findings(outcome: exit_failure(reason: "q"), findings: mtcollins1_boot_bundle_findings(bundle: bundle)) { - ExitFailure { code: _, reason: r } => contains(r, "SMpro socket 1: CUR_BOOTSTAGE UNAVAILABLE (CUR_BOOTSTAGE was not probed); BOOTSTAGE DDR initialization, failed") + (match mtcollins1_boot_outcome_with_findings(outcome: exit_failure(reason: "q"), findings: mtcollins1_boot_findings(bundle: bundle)) { + ExitFailure { code: _, reason: r } => contains(r, "socket 1 SMpro reports a FAILED stage") && contains(r, "reported DDR initialization") ExitSuccess => false }) && contains(mtcollins1_boot_bundle_text(bundle: bundle), "== smpro boot stage (ipmi master write-read) ==") } @@ -600,8 +665,8 @@ test fn an_smpro_nak_means_the_bmc_answered() -> Bool { let nak = smpro_probe_outcome(raw: smpro_raw(socket: 0, register: smpro_manufacturer_id_reg.name, exit_code: 1, stdout: "", stderr: "Unable to send RAW command (channel=0x0 netfn=0x6 lun=0x0 cmd=0x52 rsp=0x83): Unknown (0x83)")) let bundle = MtCollins1BootDiagnosticBundle { attempt: b.attempt, outcome: b.outcome, sel_before: b.sel_before, sel_after: b.sel_after, sdr_cache: b.sdr_cache, sensors: b.sensors, smpro: SmproProbed { outcomes: [nak] }, inventory: b.inventory, console: b.console, run: no_run(), screen: no_screen() } - !any_contains(xs: mtcollins1_boot_bundle_findings(bundle: bundle), pattern: "every BMC read failed") - && any_contains(xs: mtcollins1_boot_bundle_findings(bundle: bundle_bmc_silent()), pattern: "every BMC read failed") + !any_contains(xs: found(bundle: bundle), pattern: "every BMC read failed") + && any_contains(xs: found(bundle: bundle_bmc_silent()), pattern: "every BMC read failed") } // AN EXIT-ZERO RESPONSE THAT DID NOT PARSE IS STILL THE BMC ANSWERING: with SEL, SDR and Redfish all @@ -612,7 +677,7 @@ test fn a_malformed_smpro_answer_means_the_bmc_answered() -> Bool { let bad = smpro_probe_outcome(raw: smpro_raw(socket: 0, register: smpro_manufacturer_id_reg.name, exit_code: 0, stdout: " cd\n", stderr: "")) let bundle = MtCollins1BootDiagnosticBundle { attempt: b.attempt, outcome: b.outcome, sel_before: b.sel_before, sel_after: b.sel_after, sdr_cache: b.sdr_cache, sensors: b.sensors, smpro: SmproProbed { outcomes: [bad] }, inventory: b.inventory, console: b.console, run: no_run(), screen: no_screen() } - let findings = mtcollins1_boot_bundle_findings(bundle: bundle) + let findings = found(bundle: bundle) !any_contains(xs: findings, pattern: "every BMC read failed") && !any_contains(xs: findings, pattern: "did not answer") && contains(mtcollins1_boot_bundle_text(bundle: bundle), "MANUFACTURER_ID_REG: MALFORMED") } @@ -732,7 +797,7 @@ test fn a_post_boot_sel_timeout_is_a_typed_after_section_and_moves_no_verdict() let timeout = bmc_ipmitool_read_cause(endpoint: "ipmi lanplus 192.168.1.228", at: "2026-09-23T15:12:43Z", exit_code: 124, stderr: "") let bundle = MtCollins1BootDiagnosticBundle { attempt: AttemptRunId { run_id: "35871654468" }, outcome: "accepted", sel_before: b.sel_before, sel_after: SelSnapshotUnread { cause: timeout }, sdr_cache: b.sdr_cache, sensors: b.sensors, smpro: b.smpro, inventory: b.inventory, console: ConsoleNotConfigured, run: no_run(), screen: no_screen() } let text = mtcollins1_boot_bundle_text(bundle: bundle) - let findings = mtcollins1_boot_bundle_findings(bundle: bundle) + let findings = mtcollins1_boot_findings(bundle: bundle) contains(text, "after: UNREAD: BMC timed out at ipmi lanplus 192.168.1.228 (2026-09-23T15:12:43Z): no answer within the diagnostics deadline (coreutils timeout exit 124)") && !contains(text, "Memory training failure") && (match mtcollins1_boot_outcome_with_findings(outcome: ExitSuccess, findings: findings) { ExitSuccess => true _ => false }) @@ -798,7 +863,7 @@ fn consumption(before: String, after: String) -> BootOverrideConsumption { test fn an_override_gone_after_a_census_boot_is_consumed_by_it() -> Bool { match consumption(before: p5_consumed, after: p5_consumed) { OverrideConsumedByCensusBoot => true _ => false } - && override_findings(c: consumption(before: p5_consumed, after: p5_consumed)) == [] + && count(override_harness(c: consumption(before: p5_consumed, after: p5_consumed))) == 0 } // REVIEW 71115: an override no longer pending is not a consumption without the discriminator. The @@ -809,7 +874,7 @@ test fn an_override_gone_without_a_census_boot_is_only_no_longer_pending() -> Bo test fn an_override_still_pending_after_the_attempt_was_not_consumed() -> Bool { match consumption(before: p5_consumed, after: p5_pending()) { OverrideStillPendingAfterAttempt => true _ => false } - && count(override_findings(c: consumption(before: p5_consumed, after: p5_pending()))) == 1 + && count(override_harness(c: consumption(before: p5_consumed, after: p5_pending()))) == 1 } test fn an_override_already_pending_before_the_attempt_is_not_attributed_to_it() -> Bool { @@ -827,9 +892,61 @@ test fn power_off_after_the_census_end_marker_is_expected_not_a_finding() -> Boo let expected = power_after_attempt(observation: ChassisPowerRead { power_on: false }, census_ended: true) let unexpected = power_after_attempt(observation: ChassisPowerRead { power_on: false }, census_ended: false) (match expected { PoweredOffAfterCensusEnd => true _ => false }) - && power_after_findings(p: expected) == [] + && count(power_after_harness(p: expected)) == 0 && (match unexpected { PoweredOffWithoutCensusEnd => true _ => false }) - && count(power_after_findings(p: unexpected)) == 1 + && count(power_after_harness(p: unexpected)) == 1 && (match power_after_attempt(observation: ChassisPowerRead { power_on: true }, census_ended: true) { PoweredOnAfterAttempt => true _ => false }) && (match power_after_attempt(observation: ChassisUnreadable, census_ended: true) { PowerAfterUnobserved { observation: _ } => true _ => false }) } + +// ── INHABITANCE: THE POWER-ON ACCOUNT OVER THE REAL ROUTE ──────────────────────────────────────── +// The account's own claims supply their evidence (mtcollins1_power_on_account_witness_test). This one +// runs the production route end to end on ONE real attempt, run 37069907299 -- the bundle's console +// reading over its SOL bytes, its SEL snapshots through the real SEL reader, the bundle's evidence +// assembly, the account -- and asserts the ROUTE: two cycles opened by the window's own typed records +// (c26 "Initiated by power up", c2b "System Restart"), the two DRAM spans bound to them, the first +// cycle ending after the token the console reader read and the second going on to the summary. +// run 37069907299, artifact mtcollins1-boot-receipts, file mtcollins1-boot-diagnostics.txt (sha256 +// 5d11c12a4bb1d9eda3b7b0c5568e46b91f57b93d68507f1e3e6f6969cc3946f6): the raw `sel elist` rows as the +// bundle rendered them, before lines 3133-3135 (excerpt sha256 f17b4783cbe06412610b564848d9609469e2f44158dabbe304e3a3c81ee807d1) and after lines 6240-6251 +// (excerpt sha256 f0a028e0bea44ec0b86f6fb9d3d9a16e64bacb44cbc7004991806dd0c8d1dced). +data run_37069907299_sel_before: String = " c20 | 10/02/2026 | 05:58:34 PM UTC | Power Supply PSU2_Status | Presence detected | Asserted\n c21 | 10/02/2026 | 05:58:34 PM UTC | Power Supply PSU2_Status | Power Supply AC lost | Asserted\n c22 | 10/02/2026 | 05:59:24 PM UTC | System Firmwares Boot_Progress | System boot initiated | Asserted\n" + +data run_37069907299_sel_after: String = " c20 | 10/02/2026 | 05:58:34 PM UTC | Power Supply PSU2_Status | Presence detected | Asserted\n c21 | 10/02/2026 | 05:58:34 PM UTC | Power Supply PSU2_Status | Power Supply AC lost | Asserted\n c22 | 10/02/2026 | 05:59:24 PM UTC | System Firmwares Boot_Progress | System boot initiated | Asserted\n c23 | 10/02/2026 | 06:12:19 PM UTC | Session Audit SOL_Status | | Asserted\n c24 | 10/02/2026 | 06:13:16 PM UTC | Power Unit ChassisPwrStatus | Soft-power control failure | Asserted\n c25 | 10/02/2026 | 06:13:23 PM UTC | Power Unit ChassisPwrStatus | Power off/down | Asserted\n c26 | 10/02/2026 | 06:13:25 PM UTC | System Boot Initiated Sys_Boot_Restart | Initiated by power up | Asserted\n c27 | 10/02/2026 | 06:13:25 PM UTC | Power Unit ChassisPwrStatus | Power cycle | Asserted\n c28 | 10/02/2026 | 06:13:43 PM UTC | OEM record c0 | 00cd3a | caee7114ffff\n c29 | 10/02/2026 | 06:13:44 PM UTC | OEM record c0 | 00cd3a | caeef114ffff\n c2a | 10/02/2026 | 06:14:08 PM UTC | OEM record c0 | 00cd3a | caee7114ffff\n c2b | 10/02/2026 | 06:14:09 PM UTC | System Boot Initiated Sys_Boot_Restart | System Restart | Asserted\n" + +// the same run's mtcollins1-boot-sol.capture (sha256 +// bfddc9ff3856bbdba500ee6b03571634a5b080588796c18c1b007ecc16361bf9), lines 1-89: both DRAM passes and +// the firmware summary's first line, all the route needs; excerpt sha256 f12b720e3e71d24ab05746ae4b7c8a91faae505759220faeccfb2942bb04e258. +data run_37069907299_firmware: String = "[SOL Session operational. Use ~? for help]\nCheckpoint B4\r\n\0NOTICE: DRAM FW version 210525\r\nCP: 0ff00900\r\nMEMC param:\r\n mcu_enable_mask = 00000001 [default: 000000ff]\r\nMCU param:\r\n ecc_en = 00000002 [default: 00000001]\r\nDRAM populated DIMMs:\r\n SK0 MC0 S0: RDIMM[ad:80] 16GB 2666 ECC 1R x4 RCD[32:86] HMA82GR7AFR4N-VK \r\nCP: 3ff00100\r\nCP: 3ff00200\r\nCP: 00f01902\r\nCP: 0ff00a00\r\nCP: 0ff00b01\r\nCP: 00f01901\r\nCP: 00f01904\r\nCP: 00f02901\r\nCP: 00f01905\r\nCP: 00f02902\r\nCP: 00f02903\r\nCP: 00f02904\r\nCP: 00f01907\r\nCP: 00f01906\r\nCP: 00f01908\r\nCP: 00f01909\r\nCP: 0ff00b02\r\nCP: 0ff00c00\r\nCP: 3ff00300\r\nCP: 00001a02\r\nCP: 00001a03\r\nCP: 00001a04\r\nCP: 00001a05\r\nCP: 00f01a07\r\nCP: 00001a09\r\nCP: 00001a0a\r\nNOTICE: DRAM FW version 210525\r\nCP: 0ff00900\r\nMEMC param:\r\n mcu_enable_mask = 00000001 [default: 000000ff]\r\nMCU param:\r\n ecc_en = 00000002 [default: 00000001]\r\nDRAM populated DIMMs:\r\n SK0 MC0 S0: RDIMM[ad:80] 16GB 2666 ECC 1R x4 RCD[32:86] HMA82GR7AFR4N-VK \r\nCP: 3ff00100\r\nCP: 3ff00200\r\nCP: 00f01902\r\nCP: 0ff00a00\r\nCP: 0ff00b01\r\nCP: 00f01901\r\nCP: 00f01904\r\nCP: 00f02901\r\nCP: 00f01905\r\nCP: 00f02902\r\nCP: 00f02903\r\nCP: 00f02904\r\nCP: 00f01907\r\nCP: 00f01906\r\nCP: 00f01908\r\nCP: 00f01909\r\nCP: 0ff00b02\r\nCP: 0ff00c00\r\nCP: 3ff00300\r\nCP: 00001a02\r\nCP: 00001a03\r\nCP: 00001a04\r\nCP: 00001a05\r\nCP: 00f01a07\r\nCP: 00001a09\r\nCP: 00001a0a\r\nCP: 00001a0b\r\nCP: 00001a0c\r\nCP: 00001a0c\r\nCP: 3ff00400\r\nCP: 0ff01000\r\nCP: 0ff01101\r\nDRAM ECC Initialization: [6] [ ==> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [12] [ ===> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [18] [ ====> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [25] [ ======> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [31] [ =======> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [37] [ ========> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [43] [ =========> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [50] [ ===========> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [56] [ ============> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [62] [ =============> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [68] [ ==============> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [75] [ ================> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [81] [ =================> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [87] [ ==================> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [93] [ ===================> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [100] [ ==================== ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\r\nCP: 0ff01200\r\nCP: 3ff00800\r\nDRAM memory regions:\r\n Node[0] Region[0]: 0x000080000000 - 0x0000bfffffff \r\n Node[0] Region[1]: 0x080000000000 - 0x08007fffffff \r\n Node[0] Region[2]: 0x0800c0000000 - 0x0803ffffffff \r\nDRAM: 16GB DDR4 2666 SYMBOL ECC\r\nCheckpoint 2F\r\nCheckpoint 2E\r\nCheckpoint 31\r\nCheckpoint 32\r\nUEFI RC version: 1.08\r\n" + +test fn the_real_route_binds_two_dram_spans_to_two_typed_cycles() -> Bool { + let b = bundle_bmc_silent() + let bundle = MtCollins1BootDiagnosticBundle { attempt: AttemptRunId { run_id: "37069907299" }, outcome: b.outcome, + sel_before: SelSnapshotRead { raw: run_37069907299_sel_before, at: "2026-10-02T22:12:06Z" }, sel_after: SelSnapshotRead { raw: run_37069907299_sel_after, at: "2026-10-02T22:21:30Z" }, + sdr_cache: b.sdr_cache, sensors: b.sensors, smpro: b.smpro, inventory: b.inventory, + console: mtcollins1_boot_console_reading(path: "artifacts/mtcollins1-boot-sol.capture", size: byte_size(count: 3465), capture: run_37069907299_firmware, read_ok: true, read_error: "", capture_digest: unread_digest, reference: no_reference), + run: no_run(), screen: no_screen() } + let a = mtcollins1_boot_findings(bundle: bundle).account + count(a.cycles) == 2 + && (match a.cycles.first() { Present { value: c } => (match c.ended_after { Present { value: r } => (r.last.digits as String) == "00001a0a" Absent => false }) Absent => false }) + && (match a.ending { StoppedWithoutRestart { last_milestone: _, ended_after: _, console_errors: _, restarts_before: n } => n == 1 _ => false }) +} + +// GAP 4: THE ERROR-RECORD REGISTERS ARE THEIR OWN CARRIER. CUR/BOOTSTAGE read while GPI_RAS_ERR and the +// record words refused is a read progress carrier and a refused or partial error-record carrier. +test fn refused_error_registers_beside_a_read_stage_pair_are_their_own_coverage_gap() -> Bool { + let b = bundle_socket1_absent() + let nak = "Unable to send RAW command (channel=0x0 netfn=0x6 lun=0x0 cmd=0x52 rsp=0x83): Unknown (0x83)" + let outcomes = [ + smpro_probe_outcome(raw: smpro_raw(socket: 0, register: smpro_manufacturer_id_reg.name, exit_code: 0, stdout: "cd 3a", stderr: "")), + smpro_probe_outcome(raw: smpro_raw(socket: 0, register: smpro_cur_bootstage_reg.name, exit_code: 0, stdout: "00 02", stderr: "")), + smpro_probe_outcome(raw: smpro_raw(socket: 0, register: smpro_bootstage_reg.name, exit_code: 0, stdout: "02 01", stderr: "")), + smpro_probe_outcome(raw: smpro_raw(socket: 0, register: smpro_gpi_ras_err_reg.name, exit_code: 1, stdout: "", stderr: nak)), + smpro_probe_outcome(raw: smpro_raw(socket: 0, register: smpro_err_pmpro_type_reg.name, exit_code: 1, stdout: "", stderr: nak)), + ] + let bundle = MtCollins1BootDiagnosticBundle { attempt: b.attempt, outcome: b.outcome, sel_before: b.sel_before, sel_after: b.sel_after, sdr_cache: b.sdr_cache, sensors: b.sensors, + smpro: SmproProbed { outcomes: outcomes }, inventory: b.inventory, console: b.console, run: no_run(), screen: no_screen() } + let cov = mtcollins1_boot_findings(bundle: bundle).account.coverage + any(cov, c => (match c.carrier { SmproRegisters => true _ => false }) && (match c.standing { CarrierRead => true _ => false })) + && any(cov, c => (match c.carrier { SmproErrorRegisters => true _ => false }) && (match c.standing { CarrierRefused { cause: _ } => true CarrierPartial { detail: _ } => true _ => false })) +} diff --git a/dag/test/claim/machine_intake/mtcollins1_physical_orientation_witness_test.dag b/dag/test/claim/machine_intake/mtcollins1_physical_orientation_witness_test.dag index c8351d032c7..0cd8651b124 100644 --- a/dag/test/claim/machine_intake/mtcollins1_physical_orientation_witness_test.dag +++ b/dag/test/claim/machine_intake/mtcollins1_physical_orientation_witness_test.dag @@ -440,7 +440,7 @@ test fn w_slot_map_carries_cpu_facts_and_hypothesis_standing() -> Bool { && string_contains(s: t, pattern: "matching characteristics, not a unique CPU identity.") && string_contains(s: t, pattern: concat(concat("\nSocket 1 pin damage (operator report, ", date_text(d: damage_date())), "): ")) && string_contains(s: t, pattern: "Gate: Socket 1 evidence after ") - && string_contains(s: t, pattern: "SecondaryJoinTimeout { secondary_last_stage: inconsistent SMpro pair (current: PMpro firmware booting; reported: DDR initialization, reported stage ahead of current), socket 0 continued alone, inter_socket_links_reported: false, secondary_host_checkpoints: none }") + && string_contains(s: t, pattern: "SecondaryJoinTimeout { secondary_last_stage: inconsistent SMpro pair (current: PMpro firmware booting; reported: DDR initialization, reported stage ahead of current), socket 0 continued alone, inter_socket_links_reported: false }") && string_contains(s: t, pattern: "SecondaryJoined { inter_socket_links_reported: true }") && string_contains(s: t, pattern: "\nRefuted hypothesis: The DIMM is at fault: a stick placed in J17 is bad. By the operator's swap report: stick B was silent in J17 (run 35810323032) and trained in J1 (run 35871654468). The J17 slot is not cleared.") && string_contains(s: t, pattern: "\nrun 35871654468: placed by the resolved mapping = physical J1, J17 -> trained SK0 MC0 S0; DRAM 16 GiB; mcu_enable_mask = 00000001") @@ -476,27 +476,27 @@ test fn w_slot_map_carries_cpu_facts_and_hypothesis_standing() -> Bool { test fn w_secondary_join_outcome_and_its_refutations_are_derived() -> Bool { let bad = mtcollins1_secondary_join_reading_two_cpus let inconsistent = match secondary_join_outcome(reading: bad) { - SecondaryJoinTimeout { secondary_last_stage: st, inter_socket_links_reported: l, secondary_host_checkpoints: cps } => !l && list_length(items: cps) == 0 && match st { + SecondaryJoinTimeout { secondary_last_stage: st, inter_socket_links_reported: l } => !l && match st { SmproBootProgressRefused { cur_bootstage_word: c, bootstage_word: r } => c == 1 && r == 1023 SmproBootProgressRead { current: _, reported: _ } => false } SecondaryJoined { inter_socket_links_reported: _ } => false SecondaryJoinUnclassified { reason: _ } => false } - let ordered = MtCollins1SecondaryJoinReading { run: bad.run, capture_sha256: bad.capture_sha256, smpro_artifact_sha256: bad.smpro_artifact_sha256, active_sockets: 1, inter_socket_connection_lines: [], secondary_checkpoints: [], secondary_cur_bootstage_word: Present { value: 1 }, secondary_bootstage_word: Present { value: 258 } } + let ordered = MtCollins1SecondaryJoinReading { run: bad.run, capture_sha256: bad.capture_sha256, smpro_artifact_sha256: bad.smpro_artifact_sha256, active_sockets: 1, inter_socket_connection_lines: [], secondary_cur_bootstage_word: Present { value: 1 }, secondary_bootstage_word: Present { value: 258 } } let red_consistent = match secondary_join_outcome(reading: ordered) { - SecondaryJoinTimeout { secondary_last_stage: st, inter_socket_links_reported: _, secondary_host_checkpoints: _ } => match st { + SecondaryJoinTimeout { secondary_last_stage: st, inter_socket_links_reported: _ } => match st { SmproBootProgressRead { current: _, reported: _ } => true SmproBootProgressRefused { cur_bootstage_word: _, bootstage_word: _ } => false } SecondaryJoined { inter_socket_links_reported: _ } => false SecondaryJoinUnclassified { reason: _ } => false } - let unread = MtCollins1SecondaryJoinReading { run: bad.run, capture_sha256: bad.capture_sha256, smpro_artifact_sha256: bad.smpro_artifact_sha256, active_sockets: 1, inter_socket_connection_lines: [], secondary_checkpoints: [], secondary_cur_bootstage_word: Present { value: 1 }, secondary_bootstage_word: Absent } + let unread = MtCollins1SecondaryJoinReading { run: bad.run, capture_sha256: bad.capture_sha256, smpro_artifact_sha256: bad.smpro_artifact_sha256, active_sockets: 1, inter_socket_connection_lines: [], secondary_cur_bootstage_word: Present { value: 1 }, secondary_bootstage_word: Absent } let red_unread = match secondary_join_outcome(reading: unread) { SecondaryJoinUnclassified { reason: _ } => true SecondaryJoined { inter_socket_links_reported: _ } => false - SecondaryJoinTimeout { secondary_last_stage: _, inter_socket_links_reported: _, secondary_host_checkpoints: _ } => false + SecondaryJoinTimeout { secondary_last_stage: _, inter_socket_links_reported: _ } => false } let c = mtcollins1_cpu0_fingerprint_comparison let other_cpu = MtCollins1CpuFingerprintComparison { reference: c.reference, reference_fingerprint: c.reference_fingerprint, current: c.current, current_fingerprint: MtCollins1SocketFingerprint { socket: 0, core_voltage: millivolt(count: 1035), avs_voltage: millivolt(count: 996), warranty: 1 } } diff --git a/dag/test/claim/machine_intake/mtcollins1_power_on_account_witness_test.dag b/dag/test/claim/machine_intake/mtcollins1_power_on_account_witness_test.dag new file mode 100644 index 00000000000..a4f3e8448a5 --- /dev/null +++ b/dag/test/claim/machine_intake/mtcollins1_power_on_account_witness_test.dag @@ -0,0 +1,448 @@ +module test.claim.machine_intake.mtcollins1_power_on_account_witness_test + +import v2.std.algebra { any } +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } +import std.types { Bool, Int, List, NonEmptyStr, String } +import v2.std.optional { Present } +import extdeps.crypto.hash { sha256_digest } +import extdeps.tools.sha256sum { Sha256FileDigest, Sha256FileDigestUnavailable } +import extdeps.bootloader.grub { grub_console_report, grub_error_text } +import extdeps.ampere.smpro_register { smpro_manufacturer_id_reg, smpro_cur_bootstage_reg, smpro_bootstage_reg, smpro_gpi_ras_err_reg, + smpro_err_pmpro_type_reg, smpro_err_pmpro_info_lo_reg, smpro_err_pmpro_info_hi_reg, smpro_err_pmpro_data_lo_reg, smpro_err_pmpro_data_hi_reg, + smpro_warn_pmpro_info_lo_reg, smpro_warn_pmpro_info_hi_reg, SmproRegister, SmproBootProgressRefused } +import gunbc.machine_intake_mtcollins1_smpro_observation { SmproProbeRaw, SmproProbeOutcome, smpro_probe_outcome } +import extdeps.bmc.ipmi_sel { InitiatedByPowerUp, SystemRestart } +import gunbc.machine_intake_pre_os_bringup_verdict { SelWindowEvent, BootRestart } +import gunbc.machine_intake_pre_os_bringup_verdict { firmware_console_report } +import gunbc.machine_intake_ampere_dram_console_observation { ampere_dram_console_observation } +import gunbc.machine_intake_ampere_checkpoint_console_observation { + AmpereCheckpointRelation, AmpereCheckpointToken, ReferenceContinues, ampere_checkpoint_console_observation, +} +import gunbc.machine_intake_mtcollins1_console_milestone_observation { mtcollins1_console_milestones, UefiConsolePromptObserved, UefiShellObserved, FirmwareSummaryObserved } +import gunbc.machine_intake_ampere_socket_console_observation { ampere_socket_console_observation } +import gunbc.machine_intake_linux_boot_console_observation { linux_boot_console_observation, EfiLoadError } +import gunbc.machine_intake_kernel_module_decompression_observation { kernel_module_decompression_observation } +import gunbc.machine_intake_mtcollins1_power_on_account { + PowerOnAccount, PowerOnEvidence, AttemptConfigurationReceipt, ConsoleEvidence, ConsoleRead, ConsoleNotRead, SmproPassEvidence, + HandoffPoweredOn, HandoffNotConfirmed, SelEventsRead, SelEventsNotRead, WatchdogUnobserved, + ExpectedSockets, ExpectedTopologyNotRecorded, ConfiguredExpectedTopology, ConsoleSpansUnbound, BootBoundaryAmbiguous, OpenedBySelRestart, SpanBindingAmbiguous, + MilestoneEvidence, SmproWarningAccount, + StimulusRequestNotRecorded, StimulusApplicationNotRecorded, PopulationNotRecorded, PopulationOperatorAttested, FirmwareReadbacksNotRecorded, SlotRow, + CarrierCoverage, SolConsole, CarrierReadEmpty, + ReachedCensus, StoppedWithStatedFailure, RestartedRepeatedly, StoppedWithoutRestart, HostEndingNotEstablished, + ResetCauseUnobserved, CadenceCounted, OpenedByPowerOn, NoConsoleSpan, SpanBound, + KernelStub, Kernel, KernelPanic, SecondaryJoin, InitrdLoadFailedByStub, PendingErrorRecord, SocketNotJoined, ActiveSocketsBelowExpected, + ExpectedTopology, SmproRecordStated, BoundaryAmbiguous, + RetryStatePersistence, ResetCauseNotPubliclyReadable, CheckpointMeaningNotPubliclyModeled, SmproPairRefused, + ConfigurationNotRecorded, ConfiguredDimmPopulation, CarrierNotRead, + BootReachedUefiAfterSet, BootProgressLostAfterSet, ProgressRead, BootLoaderFailed, FirmwareStatement, PowerOnCorroboration, DistinctRestart, + mtcollins1_power_on_account, span_console_readings, mtcollins1_power_on_account_text, firmware_set_boot_readback, +} +import gunbc.machine_intake_host_capture_envelope { HostCaptureLiveStream, host_capture_lines } + +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +// SUPPLIED FIXTURES (DESIGN section 3, a witness discriminates at one interface). The account's inputs +// are constructed here: console readings by running the real readers over EXACT BYTES of real +// captures, and the opaque checkpoint relation supplied as a value -- its production from a reference +// capture is held by power_on_console_readers_witness_test, and the whole bundle route by +// mtcollins1_boot_diagnostic_bundle_witness_test's inhabitance claim. +// branch mtcollins1-captures-2026-10-03, mtcollins1/step1-j1j17-20261003T042447Z/sol.raw +// (sha256 6f33812ce2af74aacf9deba90931aa9144b1ba974532ae9e890b90a57c5900c1), lines 138-205; +// excerpt sha256 edbcaabead8194544201251f9a8a5eb26ba3dfbb8da1caec9627487f260d153d. +data step1_last_two_cycles: String = "NOTICE: DRAM FW version 220414\r\nCP: 0ff00900\r\nMEMC param:\r\n mcu_enable_mask = 00000001 [default: 000000ff]\r\nMCU param:\r\n ecc_en = 00000002 [default: 00000001]\r\nDRAM populated DIMMs:\r\n SK0 MC0 S0: RDIMM[ad:80] 16GB 2666 ECC 1R x4 RCD[32:86] HMA82GR7AFR4N-VK \r\nCP: 3ff00100\r\nCP: 3ff00200\r\nCP: 00f01902\r\nCP: 0ff00a00\r\nCP: 0ff00b01\r\nCP: 00f01901\r\nCP: 00f01904\r\nCP: 00f02901\r\nCP: 00f01905\r\nCP: 00f02902\r\nCP: 00f02903\r\nCP: 00f02904\r\nCP: 00f01907\r\nCP: 00f01906\r\nCP: 00f01908\r\nCP: 00f01909\r\nCP: 0ff00b02\r\nCP: 0ff00c00\r\nCP: 3ff00300\r\nCP: 00001a02\r\nCP: 00001a03\r\nCP: 00001a04\r\nCP: 00001a05\r\nCP: 00f01a07\r\nCP: 00001a09\r\nCP: 00001a0a\r\nNOTICE: DRAM FW version 220414\r\nCP: 0ff00900\r\nMEMC param:\r\n mcu_enable_mask = 00000001 [default: 000000ff]\r\nMCU param:\r\n ecc_en = 00000002 [default: 00000001]\r\nDRAM populated DIMMs:\r\n SK0 MC0 S0: RDIMM[ad:80] 16GB 2666 ECC 1R x4 RCD[32:86] HMA82GR7AFR4N-VK \r\nCP: 3ff00100\r\nCP: 3ff00200\r\nCP: 00f01902\r\nCP: 0ff00a00\r\nCP: 0ff00b01\r\nCP: 00f01901\r\nCP: 00f01904\r\nCP: 00f02901\r\nCP: 00f01905\r\nCP: 00f02902\r\nCP: 00f02903\r\nCP: 00f02904\r\nCP: 00f01907\r\nCP: 00f01906\r\nCP: 00f01908\r\nCP: 00f01909\r\nCP: 0ff00b02\r\nCP: 0ff00c00\r\nCP: 3ff00300\r\nCP: 00001a02\r\nCP: 00001a03\r\nCP: 00001a04\r\nCP: 00001a05\r\nCP: 00f01a07\r\nCP: 00001a09\r\nCP: 00001a0a\r\n" + +// run 37069907299, artifact mtcollins1-boot-receipts, file mtcollins1-boot-sol.capture (sha256 +// bfddc9ff3856bbdba500ee6b03571634a5b080588796c18c1b007ecc16361bf9), lines 1-115: a first DRAM pass +// that stopped after 00001a0a, a second that went on to the firmware summary (1 active socket); +// excerpt sha256 4fed2ee92d07ff4d6a34d28395c95ef13cf7a7ff03a2ed6598758398ba28c33d. +data run_37069907299_firmware: String = "[SOL Session operational. Use ~? for help]\nCheckpoint B4\r\n\0NOTICE: DRAM FW version 210525\r\nCP: 0ff00900\r\nMEMC param:\r\n mcu_enable_mask = 00000001 [default: 000000ff]\r\nMCU param:\r\n ecc_en = 00000002 [default: 00000001]\r\nDRAM populated DIMMs:\r\n SK0 MC0 S0: RDIMM[ad:80] 16GB 2666 ECC 1R x4 RCD[32:86] HMA82GR7AFR4N-VK \r\nCP: 3ff00100\r\nCP: 3ff00200\r\nCP: 00f01902\r\nCP: 0ff00a00\r\nCP: 0ff00b01\r\nCP: 00f01901\r\nCP: 00f01904\r\nCP: 00f02901\r\nCP: 00f01905\r\nCP: 00f02902\r\nCP: 00f02903\r\nCP: 00f02904\r\nCP: 00f01907\r\nCP: 00f01906\r\nCP: 00f01908\r\nCP: 00f01909\r\nCP: 0ff00b02\r\nCP: 0ff00c00\r\nCP: 3ff00300\r\nCP: 00001a02\r\nCP: 00001a03\r\nCP: 00001a04\r\nCP: 00001a05\r\nCP: 00f01a07\r\nCP: 00001a09\r\nCP: 00001a0a\r\nNOTICE: DRAM FW version 210525\r\nCP: 0ff00900\r\nMEMC param:\r\n mcu_enable_mask = 00000001 [default: 000000ff]\r\nMCU param:\r\n ecc_en = 00000002 [default: 00000001]\r\nDRAM populated DIMMs:\r\n SK0 MC0 S0: RDIMM[ad:80] 16GB 2666 ECC 1R x4 RCD[32:86] HMA82GR7AFR4N-VK \r\nCP: 3ff00100\r\nCP: 3ff00200\r\nCP: 00f01902\r\nCP: 0ff00a00\r\nCP: 0ff00b01\r\nCP: 00f01901\r\nCP: 00f01904\r\nCP: 00f02901\r\nCP: 00f01905\r\nCP: 00f02902\r\nCP: 00f02903\r\nCP: 00f02904\r\nCP: 00f01907\r\nCP: 00f01906\r\nCP: 00f01908\r\nCP: 00f01909\r\nCP: 0ff00b02\r\nCP: 0ff00c00\r\nCP: 3ff00300\r\nCP: 00001a02\r\nCP: 00001a03\r\nCP: 00001a04\r\nCP: 00001a05\r\nCP: 00f01a07\r\nCP: 00001a09\r\nCP: 00001a0a\r\nCP: 00001a0b\r\nCP: 00001a0c\r\nCP: 00001a0c\r\nCP: 3ff00400\r\nCP: 0ff01000\r\nCP: 0ff01101\r\nDRAM ECC Initialization: [6] [ ==> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [12] [ ===> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [18] [ ====> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [25] [ ======> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [31] [ =======> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [37] [ ========> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [43] [ =========> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [50] [ ===========> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [56] [ ============> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [62] [ =============> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [68] [ ==============> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [75] [ ================> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [81] [ =================> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [87] [ ==================> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [93] [ ===================> ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08DRAM ECC Initialization: [100] [ ==================== ]\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\r\nCP: 0ff01200\r\nCP: 3ff00800\r\nDRAM memory regions:\r\n Node[0] Region[0]: 0x000080000000 - 0x0000bfffffff \r\n Node[0] Region[1]: 0x080000000000 - 0x08007fffffff \r\n Node[0] Region[2]: 0x0800c0000000 - 0x0803ffffffff \r\nDRAM: 16GB DDR4 2666 SYMBOL ECC\r\nCheckpoint 2F\r\nCheckpoint 2E\r\nCheckpoint 31\r\nCheckpoint 32\r\nUEFI RC version: 1.08\r\nSCP FW version : 1.08\r\nSCP FW build date : 20210825\r\nFailsafe status : 0\r\nReset status : 3\r\nCPU info\r\n CPU ID : 413FD0C1\r\n CPU Clock : 2800 MHz\r\n Number of active sockets : 1\r\n Number of active cores : 80\r\n Socket[0]: Core voltage : 1065\r\n Socket[0]: SCU ProductID : A10001\r\n Socket[0]: Max cores : 80\r\n Socket[0]: Warranty : 0\r\n Socket[0]: Subnuma : 0\r\n Socket[0]: RC disable mask : 0\r\n Socket[0]: AVS enabled : 1\r\n Socket[0]: AVS voltage : 1027\r\nSOC info\r\n DDR Frequency : 2666 MHz\r\n Socket[0]: Soc voltage : 750\r\n Socket[0]: DIMM1 voltage : 1235\r\n Socket[0]: DIMM2 voltage : 1235\r\n PCP Clock : 1550 MHz\r\n SOC Clock : 2000 MHz\r\n SYS Clock : 400 MHz\r\n AHB Clock : 200 MHz\r\n" + +// the same file, lines 421-431: GRUB, the EFI stub's initrd EFI_LOAD_ERROR, the kernel banner; +// excerpt sha256 a9a2bd3a9cbc6a31b55ea910915aa7a195212175d22fc845afdad3b2aac13349. +data run_37069907299_loader_and_stub: String = "\x1b[0;37;40m\x1b[0;30;40m\x1b[2J\x1b[1;1H\x1b[0;37;40m\x1b[2;30HGNU GRUB version 2.12\n\r\n\r\x1b[1;37;40m\x1b[4;2H/----------------------------------------------------------------------------\\\x1b[5;2H|\x1b[5;79H|\x1b[6;2H|\x1b[6;79H|\x1b[7;2H|\x1b[7;79H|\x1b[8;2H|\x1b[8;79H|\x1b[9;2H|\x1b[9;79H|\x1b[10;2H|\x1b[10;79H|\x1b[11;2H|\x1b[11;79H|\x1b[12;2H|\x1b[12;79H|\x1b[13;2H|\x1b[13;79H|\x1b[14;2H|\x1b[14;79H|\x1b[15;2H|\x1b[15;79H|\x1b[16;2H|\x1b[16;79H|\x1b[17;2H|\x1b[17;79H|\x1b[18;2H\\----------------------------------------------------------------------------/\x1b[19;2H\x1b[20;2H\x1b[0;37;40m Use the ^ and v keys to select which entry is highlighted. \n\r Press enter to boot the selected OS, `e' to edit the commands \n\r before booting or `c' for a command-line. \x1b[5;80H \x1b[0;30;47m\x1b[5;3H*Try or Install Ubuntu Server \x1b[1;37;40m\x1b[5;78H\x1b[0;37;40m\x1b[1;37;40m\x1b[6;3H Ubuntu Server with the HWE kernel \x1b[6;78H\x1b[0;37;40m\x1b[1;37;40m\x1b[7;3H Boot from next volume \x1b[7;78H\x1b[0;37;40m\x1b[1;37;40m\x1b[8;3H UEFI Firmware Settings \x1b[8;78H\x1b[0;37;40m\x1b[1;37;40m\x1b[9;3H \x1b[9;78H\x1b[0;37;40m\x1b[1;37;40m\x1b[10;3H \x1b[10;78H\x1b[0;37;40m\x1b[1;37;40m\x1b[11;3H \x1b[11;78H\x1b[0;37;40m\x1b[1;37;40m\x1b[12;3H \x1b[12;78H\x1b[0;37;40m\x1b[1;37;40m\x1b[13;3H \x1b[13;78H\x1b[0;37;40m\x1b[1;37;40m\x1b[14;3H \x1b[14;78H\x1b[0;37;40m\x1b[1;37;40m\x1b[15;3H \x1b[15;78H\x1b[0;37;40m\x1b[1;37;40m\x1b[16;3H \x1b[16;78H\x1b[0;37;40m\x1b[1;37;40m\x1b[17;3H \x1b[17;78H\x1b[0;37;40m\x1b[17;80H \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 30s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 29s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 28s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 27s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 26s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 25s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 24s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 23s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 22s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 21s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 20s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 19s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 18s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 17s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 16s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 15s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 14s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 13s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 12s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 11s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 10s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 9s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 8s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 7s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 6s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 5s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 4s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 3s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 2s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 1s. \x1b[5;78H\x1b[23;1H The highlighted entry will be executed automatically in 0s. \x1b[5;78H\x1b[0;30;40m\x1b[2J\x1b[1;1H\x1b[0;37;40m\x1b[0;30;40m\x1b[2J\x1b[1;1H\x1b[0;37;40mEFI stub: Booting Linux Kernel...\r\nEFI stub: WARNING: Working around broken SetVirtualAddressMap()\r\nEFI stub: ERROR: Failed to load initrd: 0x8000000000000001\r\nEFI stub: Generating empty DTB\r\nEFI stub: Exiting boot services...\r\n[ 0.000000] Booting Linux on physical CPU 0x0000120000 [0x413fd0c1]\r\n[ 0.000000] Linux version 6.8.0-71-generic (buildd@bos03-arm64-036) (aarch64-linux-gnu-gcc-13 (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0, GNU ld (GNU Binutils for Ubuntu) 2.42) #71-Ubuntu SMP PREEMPT_DYNAMIC Tue Jul 22 16:44:45 UTC 2025 (Ubuntu 6.8.0-71.71-generic 6.8.12)\r\n" + +// the same file, line 1879: the kernel's panic after the missing initrd; excerpt sha256 a602607e3bd95133ca09a71f3d24f3227cf4fd92e33bd108be22bf11b3848e51. +data run_37069907299_panic: String = "[ 9.962666] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)\r\n" + +fn lines_of(text: String) -> List { + host_capture_lines(capture: HostCaptureLiveStream { text: text }) +} + +data no_digest: Sha256FileDigest = Sha256FileDigest { digest: sha256_digest(hex: "6f33812ce2af74aacf9deba90931aa9144b1ba974532ae9e890b90a57c5900c1") } + +// The relation the bundle derives for step 1's last span against the census reference (supplied; see above). +fn step1_relation(span: Int) -> AmpereCheckpointRelation { + AmpereCheckpointRelation { + span: span, capture: no_digest, last: AmpereCheckpointToken { digits: "00001a0a" }, + reference_path: "artifacts/bmc/mtcollins1-hw-census-sol.log", reference_digest: no_digest, + reference_firmware: "the vendor firmware set installed before the 2026-10-02 update", reference_configuration: "two sockets, sixteen DIMMs", + continuation: ReferenceContinues { next: AmpereCheckpointToken { digits: "14001a0a" }, remaining: 91, occurrences: 1 }, + } +} + +fn console_of(lines: List, relations: List) -> ConsoleEvidence { + ConsoleRead { + capture: no_digest, firmware: firmware_console_report(lines: lines), firmware_error_lines: 0, + dram: ampere_dram_console_observation(lines: lines), checkpoints: ampere_checkpoint_console_observation(lines: lines), relations: relations, + milestones: mtcollins1_console_milestones(lines: lines), sockets: ampere_socket_console_observation(lines: lines), + loader: grub_console_report(lines: lines), linux: linux_boot_console_observation(lines: lines), modules: kernel_module_decompression_observation(lines: lines), span_readings: span_console_readings(lines: lines), + } +} + +fn receipt() -> AttemptConfigurationReceipt { + AttemptConfigurationReceipt { + subject: "mtcollins1", attempt: "witness", expected: ExpectedSockets { sockets: [0, 1], source: "witness: both sockets expected" }, + requested: StimulusRequestNotRecorded { reason: "witness" }, applied: StimulusApplicationNotRecorded { reason: "witness" }, + cpus: PopulationNotRecorded { reason: "witness" }, dimms: PopulationNotRecorded { reason: "witness" }, firmware: FirmwareReadbacksNotRecorded { reason: "witness" }, + } +} + +fn raw(socket: Int, reg: SmproRegister, stdout: String) -> SmproProbeOutcome { + smpro_probe_outcome(raw: SmproProbeRaw { sequence: 0, socket: socket, register: reg.name, request: "r", at: "2026-10-03T04:25:00Z", exit_code: 0, stdout: stdout, stderr: "" }) +} + +// THE 2026-10-03 LOOP's SMpro pass (eager-gull-22's synchronized capture): socket 0 CUR_BOOTSTAGE 0x0002 +// with BOOTSTAGE 0x0201; socket 1 CUR_BOOTSTAGE 0x0001 with BOOTSTAGE 0x03ff, a refused pair. +fn loop_pass() -> List { + [SmproPassEvidence { label: "power-on", outcomes: [ + raw(socket: 0, reg: smpro_manufacturer_id_reg, stdout: "cd 3a"), raw(socket: 0, reg: smpro_cur_bootstage_reg, stdout: "00 02"), raw(socket: 0, reg: smpro_bootstage_reg, stdout: "02 01"), + raw(socket: 1, reg: smpro_manufacturer_id_reg, stdout: "cd 3a"), raw(socket: 1, reg: smpro_cur_bootstage_reg, stdout: "00 01"), raw(socket: 1, reg: smpro_bootstage_reg, stdout: "03 ff"), + ] }] +} + +// Socket 0's GPI-pending CCIX record as #13058 decodes it (smpro-err-20261003T005609Z: 0x7E = 0x0002, +// 0xA6 = 0x0074, 0xA7 = 0x2244). +fn ccix_pass() -> List { + [SmproPassEvidence { label: "census", outcomes: [ + raw(socket: 0, reg: smpro_manufacturer_id_reg, stdout: "cd 3a"), raw(socket: 0, reg: smpro_gpi_ras_err_reg, stdout: "00 02"), + raw(socket: 0, reg: smpro_err_pmpro_type_reg, stdout: "00 02"), raw(socket: 0, reg: smpro_err_pmpro_info_hi_reg, stdout: "22 44"), + raw(socket: 0, reg: smpro_err_pmpro_info_lo_reg, stdout: "00 74"), raw(socket: 0, reg: smpro_err_pmpro_data_hi_reg, stdout: "00 00"), + raw(socket: 0, reg: smpro_err_pmpro_data_lo_reg, stdout: "00 00"), raw(socket: 0, reg: smpro_warn_pmpro_info_hi_reg, stdout: "00 00"), + raw(socket: 0, reg: smpro_warn_pmpro_info_lo_reg, stdout: "00 00"), + ] }] +} + +// REAL SEL ROWS, re-typed as the reader types them (gunbc.machine_intake_pre_os_bringup_verdict +// SelWindowEvent). Run 37069907299's own window (artifact mtcollins1-boot-diagnostics.txt, sha256 +// 5d11c12a4bb1d9eda3b7b0c5568e46b91f57b93d68507f1e3e6f6969cc3946f6): c26 "Initiated by power up" then +// c2b "System Restart" -- the power-on and the one restart before its second DRAM pass. Step 1's +// sel-after.txt (branch mtcollins1-captures-2026-10-03) row 3b, one "System Restart" of the loop. +data run_37069907299_power_up: SelWindowEvent = SelWindowEvent { record_id: 3110, bmc_clock_rendering: "10/02/2026 06:13:25 PM UTC", kind: BootRestart { offset: InitiatedByPowerUp } } + +data run_37069907299_restart: SelWindowEvent = SelWindowEvent { record_id: 3115, bmc_clock_rendering: "10/02/2026 06:14:09 PM UTC", kind: BootRestart { offset: SystemRestart } } + +data step1_restart: SelWindowEvent = SelWindowEvent { record_id: 59, bmc_clock_rendering: "10/02/2026 07:32:52 PM UTC", kind: BootRestart { offset: SystemRestart } } + +// Step 1's row 37, the "System Restart" before row 3b: after a confirmed power-on with no power-up +// record, the first such row is ambiguous (it may be the power-on's own boot) and 3b is a distinct reset. +data step1_first_restart: SelWindowEvent = SelWindowEvent { record_id: 55, bmc_clock_rendering: "10/02/2026 07:32:27 PM UTC", kind: BootRestart { offset: SystemRestart } } + +data step1_restarts: List = [step1_first_restart, step1_restart] + +fn evidence(console: ConsoleEvidence, smpro: List, census_ended: Bool) -> PowerOnEvidence { + evidence_with(console: console, smpro: smpro, census_ended: census_ended, sel: []) +} + +fn evidence_with(console: ConsoleEvidence, smpro: List, census_ended: Bool, sel: List) -> PowerOnEvidence { + PowerOnEvidence { + configuration: receipt(), handoff: HandoffPoweredOn, smpro: smpro, console: console, sel: SelEventsRead { events: sel }, + watchdog: WatchdogUnobserved { reason: "witness" }, bmc_anomalies: [], census_ended: census_ended, coverage: [], + } +} + +fn step1_account() -> PowerOnAccount { + let lines = lines_of(text: step1_last_two_cycles) + mtcollins1_power_on_account(e: evidence_with(console: console_of(lines: lines, relations: [step1_relation(span: 1)]), smpro: loop_pass(), census_ended: false, sel: step1_restarts)) +} + +// ── THE NEW-FIRMWARE LOOP ───────────────────────────────────────────────────────────────────── + +test fn the_step1_loop_is_a_repeated_restart_ending_after_an_opaque_token() -> Bool { + match step1_account().ending { + RestartedRepeatedly { cycles: n, last_cycle_ended_after: r, cadence: c, console_errors: k, sel_restarts: _, watchdog: _, reset_cause: rc } => + n == 2 && k == 0 + && (match r { Present { value: rel } => (rel.last.digits as String) == "00001a0a" Absent => false }) + && (match c { CadenceCounted { cycles: m } => m == 2 _ => false }) + && (match rc { ResetCauseUnobserved { searched: _ } => true _ => false }) + _ => false + } +} + +test fn the_loop_leaves_retry_state_reset_cause_and_meaning_open() -> Bool { + let a = step1_account() + any(a.open, q => match q.question { RetryStatePersistence => true _ => false }) + && any(a.open, q => match q.question { ResetCauseNotPubliclyReadable => true _ => false }) + && any(a.open, q => match q.question { CheckpointMeaningNotPubliclyModeled => true _ => false }) +} + +// LAW 5: the refused pair is evidence on the join subject and an open question, never a stage. +test fn socket_ones_refused_pair_is_an_open_question_on_the_join() -> Bool { + let a = step1_account() + any(a.open, q => match q.question { SmproPairRefused { socket: k, pass: _ } => k == 1 && (match q.subject { SecondaryJoin => true _ => false }) _ => false }) + && any(a.socket_progress, p => p.socket == 1 && (match p.reading { ProgressRead { cur_bootstage_word: _, bootstage_word: _, progress: g } => (match g { SmproBootProgressRefused { cur_bootstage_word: c, bootstage_word: r } => c == 1 && r == 1023 _ => false }) _ => false })) +} + +// LAW 4: mutating the last token changes only the relation; the ending's kind and cycle count stand. +test fn a_different_last_token_changes_only_the_relation() -> Bool { + let lines = lines_of(text: step1_last_two_cycles) + let other = AmpereCheckpointRelation { span: 1, capture: no_digest, last: AmpereCheckpointToken { digits: "00001a09" }, reference_path: "r", reference_digest: no_digest, reference_firmware: "f", reference_configuration: "c", continuation: ReferenceContinues { next: AmpereCheckpointToken { digits: "00001a0a" }, remaining: 1, occurrences: 1 } } + match mtcollins1_power_on_account(e: evidence_with(console: console_of(lines: lines, relations: [other]), smpro: loop_pass(), census_ended: false, sel: step1_restarts)).ending { + RestartedRepeatedly { cycles: n, last_cycle_ended_after: r, cadence: _, console_errors: _, sel_restarts: _, watchdog: _, reset_cause: _ } => + n == 2 && (match r { Present { value: rel } => (rel.last.digits as String) == "00001a09" Absent => false }) + _ => false + } +} + +// ── LAW 2: CYCLES ARE PRESERVED, AND PROGRESS AFTER A RESTART IS NOT A LOOP ────────────────── + +fn old_firmware_account(census_ended: Bool) -> PowerOnAccount { + let lines = concat(lines_of(text: run_37069907299_firmware), concat(lines_of(text: run_37069907299_loader_and_stub), lines_of(text: run_37069907299_panic))) + mtcollins1_power_on_account(e: evidence_with(console: console_of(lines: lines, relations: []), smpro: [], census_ended: census_ended, sel: [run_37069907299_power_up, run_37069907299_restart])) +} + +test fn the_old_firmware_first_cycle_stops_and_the_second_reaches_uefi() -> Bool { + let a = old_firmware_account(census_ended: false) + count(a.cycles) == 2 + && (match a.cycles.first() { Present { value: c } => !any(c.milestones, m => match m.sighting.milestone { FirmwareSummaryObserved => true _ => false }) Absent => false }) + && (match a.cycles.last() { Present { value: c } => any(c.milestones, m => match m.sighting.milestone { FirmwareSummaryObserved => true _ => false }) Absent => false }) +} + +// The stub's initrd failure led to the kernel's panic: the attempt stopped at the panic, and the +// initrd failure stays among the errors as what led there. +test fn an_initrd_failure_then_a_panic_ends_at_the_kernel_with_both_errors_kept() -> Bool { + let a = old_firmware_account(census_ended: false) + (match a.ending { StoppedWithStatedFailure { subject: s, error: e } => (match s { Kernel => true _ => false }) && (match e { KernelPanic { reason: _ } => true _ => false }) _ => false }) + && any(a.errors, x => match x.error { InitrdLoadFailedByStub { status: st } => (match st { EfiLoadError => true _ => false }) _ => false }) +} + +test fn the_readback_finds_uefi_in_cycle_two_and_carries_the_ending() -> Bool { + match firmware_set_boot_readback(a: old_firmware_account(census_ended: false)) { + BootReachedUefiAfterSet { cycle: c, milestone: _, ending: e, degradations: _, account: _ } => c == 2 && (match e { StoppedWithStatedFailure { subject: _, error: _ } => true _ => false }) + _ => false + } +} + +test fn the_step1_readback_is_progress_lost() -> Bool { + match firmware_set_boot_readback(a: step1_account()) { BootProgressLostAfterSet { ending: _, last_relation: r, account: _ } => (match r { Present { value: _ } => true Absent => false }) _ => false } +} + +// ── LAW 1: REACHING THE CENSUS IS ORTHOGONAL TO ERRORS ─────────────────────────────────────── + +test fn a_pending_ccix_record_and_a_one_socket_census_reach_the_census_degraded() -> Bool { + let a = mtcollins1_power_on_account(e: evidence(console: console_of(lines: lines_of(text: run_37069907299_firmware), relations: []), smpro: ccix_pass(), census_ended: true)) + (match a.ending { ReachedCensus => true _ => false }) + && any(a.degradations, d => match d { PendingErrorRecord { socket: k, processor: _, record: _ } => k == 0 _ => false }) + && any(a.degradations, d => match d { ActiveSocketsBelowExpected { active: n, expected: x } => n == 1 && x == 2 _ => false }) + && any(a.degradations, d => match d { SocketNotJoined { outcome: _ } => true _ => false }) +} + +// ── LAW 3: COVERAGE NEVER CHOOSES THE ENDING ───────────────────────────────────────────────── + +test fn an_empty_sol_beside_a_census_end_marker_is_reached_census_with_a_gap() -> Bool { + let a = mtcollins1_power_on_account(e: PowerOnEvidence { + configuration: receipt(), handoff: HandoffPoweredOn, smpro: [], console: console_of(lines: [], relations: []), sel: SelEventsNotRead, + watchdog: WatchdogUnobserved { reason: "witness" }, bmc_anomalies: [], census_ended: true, + coverage: [CarrierCoverage { carrier: SolConsole, standing: CarrierReadEmpty }], + }) + (match a.ending { ReachedCensus => true _ => false }) + && any(a.open, q => match q.question { CarrierNotRead { carrier: c } => (match c { SolConsole => true _ => false }) _ => false }) +} + +// ── LAW 7: A CYCLE EXISTS WITHOUT DRAM ─────────────────────────────────────────────────────── + +test fn power_on_and_smpro_progress_without_a_banner_is_one_partial_cycle() -> Bool { + let a = mtcollins1_power_on_account(e: evidence(console: ConsoleNotRead, smpro: loop_pass(), census_ended: false)) + count(a.cycles) == 1 + && (match a.cycles.first() { Present { value: c } => (match c.boundary { OpenedByPowerOn => true _ => false }) && (match c.console { NoConsoleSpan => true _ => false }) Absent => false }) + && (match a.ending { StoppedWithoutRestart { last_milestone: m, ended_after: _, console_errors: _, restarts_before: n } => n == 0 && (match m { Absent => true Present { value: _ } => false }) _ => false }) +} + +test fn with_nothing_read_the_host_ending_is_not_established() -> Bool { + let a = mtcollins1_power_on_account(e: PowerOnEvidence { + configuration: receipt(), handoff: HandoffNotConfirmed { reason: "witness" }, smpro: [], console: ConsoleNotRead, sel: SelEventsNotRead, + watchdog: WatchdogUnobserved { reason: "witness" }, bmc_anomalies: [], census_ended: false, coverage: [], + }) + count(a.cycles) == 0 && (match a.ending { HostEndingNotEstablished => true _ => false }) +} + +// ── LAW 6: CONFIGURATION QUESTIONS NEED THE RECEIPT ────────────────────────────────────────── + +test fn an_unrecorded_dimm_population_is_an_open_question_and_an_attested_one_is_not() -> Bool { + let lines = lines_of(text: step1_last_two_cycles) + let unrecorded = mtcollins1_power_on_account(e: evidence(console: console_of(lines: lines, relations: []), smpro: [], census_ended: false)) + let r = receipt() + let attested = mtcollins1_power_on_account(e: PowerOnEvidence { + configuration: AttemptConfigurationReceipt { subject: r.subject, attempt: r.attempt, expected: r.expected, requested: r.requested, applied: r.applied, cpus: r.cpus, + dimms: PopulationOperatorAttested { rows: [SlotRow { label: "J1", socket: 0, populated: true }, SlotRow { label: "J17", socket: 0, populated: true }], receipt: "witness inspection" }, firmware: r.firmware }, + handoff: HandoffPoweredOn, smpro: [], console: console_of(lines: lines, relations: []), sel: SelEventsNotRead, + watchdog: WatchdogUnobserved { reason: "witness" }, bmc_anomalies: [], census_ended: false, coverage: [], + }) + any(unrecorded.open, q => match q.question { ConfigurationNotRecorded { field: f } => (match f { ConfiguredDimmPopulation => true _ => false }) _ => false }) + && !any(attested.open, q => match q.question { ConfigurationNotRecorded { field: f } => (match f { ConfiguredDimmPopulation => true _ => false }) _ => false }) +} + +test fn the_rendering_names_the_ending_and_the_open_questions() -> Bool { + let t = mtcollins1_power_on_account_text(a: step1_account()) + contains(t, "restarted repeatedly: 2 cycle(s)") && contains(t, "ended after token 00001a0a") + && contains(t, "does any failsafe or retry state survive the reset?") +} + +// ── GAP 1: ONLY TYPED POWER-ON OR RESET EVIDENCE OPENS A CYCLE ─────────────────────────────── + +test fn two_banners_without_a_reset_are_one_cycle_with_unbound_spans() -> Bool { + let a = mtcollins1_power_on_account(e: evidence(console: console_of(lines: lines_of(text: step1_last_two_cycles), relations: []), smpro: [], census_ended: false)) + count(a.cycles) == 1 + && (match a.cycles.first() { Present { value: c } => (match c.console { SpanBindingAmbiguous => true _ => false }) Absent => false }) + && any(a.open, q => match q.question { ConsoleSpansUnbound { spans: n, cycles: k } => n == 2 && k == 1 _ => false }) + && (match a.ending { RestartedRepeatedly { cycles: _, last_cycle_ended_after: _, cadence: _, console_errors: _, sel_restarts: _, watchdog: _, reset_cause: _ } => false _ => true }) +} + +test fn the_same_console_plus_one_typed_reset_is_two_bound_cycles() -> Bool { + let lines = lines_of(text: run_37069907299_firmware) + let without = mtcollins1_power_on_account(e: evidence(console: console_of(lines: lines, relations: []), smpro: [], census_ended: false)) + let with = mtcollins1_power_on_account(e: evidence_with(console: console_of(lines: lines, relations: []), smpro: [], census_ended: false, sel: [run_37069907299_power_up, run_37069907299_restart])) + count(without.cycles) == 1 && count(with.cycles) == 2 + && (match with.cycles.last() { Present { value: c } => (match c.boundary { OpenedBySelRestart { record: r } => r.record_id == 3115 _ => false }) Absent => false }) + && !any(with.open, q => match q.question { ConsoleSpansUnbound { spans: _, cycles: _ } => true _ => false }) +} + +// A power-up record and the confirmed power-on are ONE boundary. +test fn a_power_up_record_does_not_add_a_cycle_to_the_power_on() -> Bool { + count(old_firmware_account(census_ended: false).cycles) == 2 +} + +// ── GAP 2: AN UNRECORDED TOPOLOGY MINTS NO TOPOLOGY DEGRADATION ────────────────────────────── + +fn with_expected(e: PowerOnEvidence, expected: ExpectedTopology) -> PowerOnEvidence { + let c = e.configuration + PowerOnEvidence { + configuration: AttemptConfigurationReceipt { subject: c.subject, attempt: c.attempt, expected: expected, requested: c.requested, applied: c.applied, cpus: c.cpus, dimms: c.dimms, firmware: c.firmware }, + handoff: e.handoff, smpro: e.smpro, console: e.console, sel: e.sel, watchdog: e.watchdog, bmc_anomalies: e.bmc_anomalies, census_ended: e.census_ended, coverage: e.coverage, + } +} + +test fn one_active_socket_with_no_recorded_topology_is_questions_only() -> Bool { + let a = mtcollins1_power_on_account(e: with_expected(e: evidence(console: console_of(lines: lines_of(text: run_37069907299_firmware), relations: []), smpro: [], census_ended: true), expected: ExpectedTopologyNotRecorded { reason: "witness" })) + count(a.degradations) == 0 + && any(a.open, q => match q.question { ConfigurationNotRecorded { field: f } => (match f { ConfiguredExpectedTopology => true _ => false }) _ => false }) +} + +test fn a_receipt_expecting_one_socket_is_not_degraded_by_one_active_socket() -> Bool { + let a = mtcollins1_power_on_account(e: with_expected(e: evidence(console: console_of(lines: lines_of(text: run_37069907299_firmware), relations: []), smpro: [], census_ended: true), expected: ExpectedSockets { sockets: [0], source: "witness: socket-1 CPU removed" })) + !any(a.degradations, d => match d { ActiveSocketsBelowExpected { active: _, expected: _ } => true SocketNotJoined { outcome: _ } => true _ => false }) +} + +// ── GAP 3: AN EARLIER CYCLE'S FAILURE IS HISTORY, NOT THE ENDING ───────────────────────────── + +test fn a_cycle_one_panic_then_a_reset_and_a_stable_uefi_shell_is_not_ended_by_the_panic() -> Bool { + let lines = concat(["NOTICE: DRAM FW version 210525", "CP: 0ff00900"], concat(lines_of(text: run_37069907299_panic), ["NOTICE: DRAM FW version 220414", "CP: 0ff00900", "UEFI Interactive Shell v2.2"])) + let a = mtcollins1_power_on_account(e: evidence_with(console: console_of(lines: lines, relations: []), smpro: [], census_ended: false, sel: step1_restarts)) + (match a.ending { StoppedWithoutRestart { last_milestone: m, ended_after: _, console_errors: _, restarts_before: n } => n == 1 && (match m { Present { value: x } => (match x.sighting.milestone { UefiShellObserved => true _ => false }) Absent => false }) _ => false }) + && any(a.errors, x => (match x.error { KernelPanic { reason: _ } => true _ => false }) && (match x.cycle { Present { value: k } => k == 1 Absent => false })) +} + +// ── GAP 4: A PENDING WARNING SURVIVES ───────────────────────────────────────────────────────── + +test fn a_pending_warning_without_an_error_is_kept() -> Bool { + let pass = [SmproPassEvidence { label: "census", outcomes: [ + raw(socket: 0, reg: smpro_manufacturer_id_reg, stdout: "cd 3a"), raw(socket: 0, reg: smpro_gpi_ras_err_reg, stdout: "00 02"), + raw(socket: 0, reg: smpro_err_pmpro_type_reg, stdout: "00 01"), raw(socket: 0, reg: smpro_err_pmpro_info_hi_reg, stdout: "00 00"), + raw(socket: 0, reg: smpro_err_pmpro_info_lo_reg, stdout: "00 00"), raw(socket: 0, reg: smpro_err_pmpro_data_hi_reg, stdout: "00 00"), + raw(socket: 0, reg: smpro_err_pmpro_data_lo_reg, stdout: "00 00"), raw(socket: 0, reg: smpro_warn_pmpro_info_hi_reg, stdout: "22 44"), + raw(socket: 0, reg: smpro_warn_pmpro_info_lo_reg, stdout: "00 74"), + ] }] + let a = mtcollins1_power_on_account(e: evidence(console: ConsoleNotRead, smpro: pass, census_ended: false)) + count(a.warnings) == 1 && !any(a.errors, x => match x.error { SmproRecordStated { socket: _, processor: _, pass: _, record: _ } => true _ => false }) +} + +// ── GAP 5: A MILESTONE CARRIES ITS CAPTURE AND ITS READER ──────────────────────────────────── + +test fn the_same_line_from_two_captures_stays_two_facts() -> Bool { + let lines = concat(["NOTICE: DRAM FW version 220414"], ["UEFI Interactive Shell v2.2"]) + let other = Sha256FileDigest { digest: sha256_digest(hex: "1e0c4bc3ab7f635f5522c23832e6d5db33d7c3b07e9467cea1f9814167bd0de8") } + let c1 = console_of(lines: lines, relations: []) + let c2 = match c1 { + ConsoleRead { capture: _, firmware: f, firmware_error_lines: e, dram: d, checkpoints: k, relations: r, milestones: m, sockets: o, loader: g, linux: l, modules: x, span_readings: sr } => + ConsoleRead { capture: other, firmware: f, firmware_error_lines: e, dram: d, checkpoints: k, relations: r, milestones: m, sockets: o, loader: g, linux: l, modules: x, span_readings: sr } + ConsoleNotRead => ConsoleNotRead + } + let m1 = flat_map(mtcollins1_power_on_account(e: evidence(console: c1, smpro: [], census_ended: false)).cycles, c => c.milestones) + let m2 = flat_map(mtcollins1_power_on_account(e: evidence(console: c2, smpro: [], census_ended: false)).cycles, c => c.milestones) + match m1.last() { + Present { value: a } => match m2.last() { + Present { value: b } => (a.sighting.line as String) == (b.sighting.line as String) && digest_hex(d: a.capture) != digest_hex(d: b.capture) && (a.reader.decl_name as String) == "mtcollins1_console_milestones" + Absent => false + } + Absent => false + } +} + +fn digest_hex(d: Sha256FileDigest) -> String { + match d { Sha256FileDigest { digest: x } => x.hex as String Sha256FileDigestUnavailable { path: _, reason: r } => r } +} + +// ── THE AMBIGUOUS FIRST RESTART (side-chat review of 21630c5) ──────────────────────────────── + +// A confirmed power-on, no power-up record, and a first "System Restart": the record may be the +// power-on's own boot, so it opens no cycle -- never a second cycle, never a repeated-restart ending. +test fn a_first_restart_after_power_on_without_a_power_up_record_is_ambiguous() -> Bool { + let a = mtcollins1_power_on_account(e: evidence_with(console: console_of(lines: lines_of(text: step1_last_two_cycles), relations: []), smpro: [], census_ended: false, sel: [step1_first_restart])) + count(a.cycles) == 1 + && any(a.open, q => match q.question { BootBoundaryAmbiguous { record_id: r } => r == 55 _ => false }) + && (match a.ending { RestartedRepeatedly { cycles: _, last_cycle_ended_after: _, cadence: _, console_errors: _, sel_restarts: _, watchdog: _, reset_cause: _ } => false _ => true }) +} + +// ── EVERY PANIC IS ITS OWN ERROR, IN ITS OWN CYCLE ────────────────────────────────────────── + +test fn two_panics_in_two_cycles_are_two_errors_and_the_second_ends_the_attempt() -> Bool { + let lines = ["NOTICE: DRAM FW version 220414", "[ 9.1] Kernel panic - not syncing: first", "NOTICE: DRAM FW version 220414", "[ 9.2] Kernel panic - not syncing: second"] + let a = mtcollins1_power_on_account(e: evidence_with(console: console_of(lines: lines, relations: []), smpro: [], census_ended: false, sel: step1_restarts)) + any(a.errors, x => (match x.error { KernelPanic { reason: r } => (r as String) == "first" _ => false }) && (match x.cycle { Present { value: k } => k == 1 Absent => false })) + && any(a.errors, x => (match x.error { KernelPanic { reason: r } => (r as String) == "second" _ => false }) && (match x.cycle { Present { value: k } => k == 2 Absent => false })) + && (match a.ending { StoppedWithStatedFailure { subject: _, error: e } => (match e { KernelPanic { reason: r } => (r as String) == "second" _ => false }) _ => false }) +} + +// ── ONE ERROR PER OCCURRENCE FOR WHOLE-SPAN STATEMENTS TOO (side-chat review of c7beccad) ─────── + +data grub_failed_line: String = "Failed to boot both default and fallback entries." + +test fn a_grub_failure_in_each_of_two_cycles_is_two_errors_and_the_second_ends() -> Bool { + let lines = ["NOTICE: DRAM FW version 220414", "GNU GRUB version 2.12", "error: no such device: first.", grub_failed_line, "NOTICE: DRAM FW version 220414", "GNU GRUB version 2.12", "error: no such device: second.", grub_failed_line] + let a = mtcollins1_power_on_account(e: evidence_with(console: console_of(lines: lines, relations: []), smpro: [], census_ended: false, sel: step1_restarts)) + let grub = filter(a.errors, x => match x.error { BootLoaderFailed { errors: _ } => true _ => false }) + count(grub) == 2 + && any(grub, x => match x.cycle { Present { value: k } => k == 1 Absent => false }) + && any(grub, x => match x.cycle { Present { value: k } => k == 2 Absent => false }) + && (match a.ending { StoppedWithStatedFailure { subject: _, error: e } => (match e { BootLoaderFailed { errors: es } => any(es, g => contains(grub_error_text(error: g), "second")) _ => false }) _ => false }) +} + +test fn a_firmware_statement_in_each_of_two_cycles_is_two_errors() -> Bool { + let lines = ["NOTICE: DRAM FW version 220414", "ERROR: Non-identical DIMM mixture NOT supported!", "NOTICE: DRAM FW version 220414", "ERROR: Non-identical DIMM mixture NOT supported!"] + let a = mtcollins1_power_on_account(e: evidence_with(console: console_of(lines: lines, relations: []), smpro: [], census_ended: false, sel: step1_restarts)) + let fw = filter(a.errors, x => match x.error { FirmwareStatement { report: _ } => true _ => false }) + count(fw) == 2 && any(fw, x => match x.cycle { Present { value: k } => k == 1 Absent => false }) && any(fw, x => match x.cycle { Present { value: k } => k == 2 Absent => false }) +} + +// ── THE SEL FOLD IS ORDERED, WITHOUT LOOK-AHEAD ────────────────────────────────────────────── + +data second_power_up: SelWindowEvent = SelWindowEvent { record_id: 3200, bmc_clock_rendering: "witness", kind: BootRestart { offset: InitiatedByPowerUp } } + +fn boundary_kinds(sel: List) -> List { + map(mtcollins1_power_on_account(e: evidence_with(console: ConsoleNotRead, smpro: [], census_ended: false, sel: sel)).boundaries, b => match b { + PowerOnCorroboration { record: r } => concat("corroborates ", to_string(r.record_id)) + DistinctRestart { record: r } => concat("distinct ", to_string(r.record_id)) + BoundaryAmbiguous { record: r, reason: _ } => concat("ambiguous ", to_string(r.record_id)) + }) +} + +test fn a_later_record_never_changes_how_a_prefix_was_classified() -> Bool { + let prefix = boundary_kinds(sel: [step1_first_restart]) + let longer = boundary_kinds(sel: [step1_first_restart, second_power_up]) + prefix == longer.take(n: count(prefix)) && prefix == ["ambiguous 55"] +} + +test fn a_power_on_and_two_ordered_power_ups_are_an_initial_cycle_and_one_later_cycle() -> Bool { + let a = mtcollins1_power_on_account(e: evidence_with(console: ConsoleNotRead, smpro: [], census_ended: false, sel: [run_37069907299_power_up, second_power_up])) + count(a.cycles) == 2 && boundary_kinds(sel: [run_37069907299_power_up, second_power_up]) == ["corroborates 3110", "distinct 3200"] +} diff --git a/dag/test/claim/machine_intake/mtcollins1_smpro_observation_witness_test.dag b/dag/test/claim/machine_intake/mtcollins1_smpro_observation_witness_test.dag index d39d407a83c..5d06de076d4 100644 --- a/dag/test/claim/machine_intake/mtcollins1_smpro_observation_witness_test.dag +++ b/dag/test/claim/machine_intake/mtcollins1_smpro_observation_witness_test.dag @@ -16,10 +16,17 @@ import extdeps.ampere.scp_diagnostic { DdrTraining, DdrInitialization, Operating import gunbc.machine_intake_mtcollins1_smpro_observation { mtcollins1_smpro_socket_stage, CurrentStageEstablished, ReportedStageUnavailable, SmproProbeRaw, SmproProbeOutcome, SmproWordRead, SmproControllerRefused, SmproTransportFailed, SmproResponseMalformed, SmproNotSent, - smpro_probe_outcome, smpro_control_refusal, smpro_request_text, mtcollins1_smpro_findings, mtcollins1_smpro_dossier_text, + smpro_probe_outcome, smpro_control_refusal, smpro_request_text, mtcollins1_smpro_dossier_text, mtcollins1_smpro_probe_count, mtcollins1_smpro_read_order, mtcollins1_smpro_sequenced, mtcollins1_smpro_plan, SmproTarget, } -import extdeps.ampere.smpro_register { SmproRegister } +import extdeps.ampere.smpro_register { SmproRegister, SmproBootProgressRead, SmproBootProgressRefused } +import gunbc.machine_intake_mtcollins1_power_on_account { + PowerOnAccount, PowerOnEvidence, AttemptConfigurationReceipt, SmproPassEvidence, SocketProgressAccount, + HandoffPoweredOn, ConsoleNotRead, SelEventsNotRead, WatchdogUnobserved, + ExpectedSockets, StimulusRequestNotRecorded, StimulusApplicationNotRecorded, PopulationNotRecorded, FirmwareReadbacksNotRecorded, + ProgressRead, ProgressNotRead, SmproReportedFailure, SmproPairRefused, + mtcollins1_power_on_account, +} data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly @@ -120,20 +127,53 @@ fn socket_reads_at(socket: Int, current: String, bootstage: String) -> List AttemptConfigurationReceipt { + AttemptConfigurationReceipt { + subject: "mtcollins1", attempt: "witness", expected: ExpectedSockets { sockets: [0, 1], source: "witness" }, + requested: StimulusRequestNotRecorded { reason: "witness" }, applied: StimulusApplicationNotRecorded { reason: "witness" }, + cpus: PopulationNotRecorded { reason: "witness" }, dimms: PopulationNotRecorded { reason: "witness" }, firmware: FirmwareReadbacksNotRecorded { reason: "witness" }, + } +} + +fn account_of(outcomes: List) -> PowerOnAccount { + mtcollins1_power_on_account(e: PowerOnEvidence { + configuration: receipt(), handoff: HandoffPoweredOn, + smpro: [SmproPassEvidence { label: "pass", outcomes: outcomes }], + console: ConsoleNotRead, sel: SelEventsNotRead, watchdog: WatchdogUnobserved { reason: "witness" }, + bmc_anomalies: [], census_ended: false, coverage: [], + }) +} + +fn progress_of(a: PowerOnAccount, socket: Int) -> SocketProgressAccount? { + filter(a.socket_progress, p => p.socket == socket).first() +} + +fn current_code(a: PowerOnAccount, socket: Int) -> Int { + match progress_of(a: a, socket: socket) { + Present { value: p } => match p.reading { ProgressRead { cur_bootstage_word: c, bootstage_word: _, progress: _ } => c % 256 ProgressNotRead { reason: _ } => 0 - 1 } + Absent => 0 - 1 + } +} + test fn socket_one_failing_ddr_training_beside_a_booted_socket_zero_is_named() -> Bool { - let f = mtcollins1_smpro_findings(outcomes: concat(socket_reads(socket: 0, bootstage: "09 02"), socket_reads(socket: 1, bootstage: "04 03"))) - count(f) == 2 && any(f, x => starts_with(s: x, prefix: "SMpro socket 1:") && contains(x, "BOOTSTAGE DDR training report status, failed")) - && any(f, x => starts_with(s: x, prefix: "SMpro socket 0:") && contains(x, "BOOTSTAGE OS booting, completed without error")) + let a = account_of(outcomes: concat(socket_reads(socket: 0, bootstage: "09 02"), socket_reads(socket: 1, bootstage: "04 03"))) + any(a.errors, x => match x.error { SmproReportedFailure { socket: k, pass: _, cur_bootstage_word: _, bootstage_word: r } => k == 1 && r == 1027 _ => false }) + && !any(a.errors, x => match x.error { SmproReportedFailure { socket: k, pass: _, cur_bootstage_word: _, bootstage_word: _ } => k == 0 _ => false }) } -test fn two_sockets_at_the_same_stage_add_no_finding() -> Bool { - count(mtcollins1_smpro_findings(outcomes: concat(socket_reads(socket: 0, bootstage: "09 02"), socket_reads(socket: 1, bootstage: "09 02")))) == 0 +test fn two_sockets_at_the_same_stage_add_nothing() -> Bool { + let a = account_of(outcomes: concat(socket_reads(socket: 0, bootstage: "09 02"), socket_reads(socket: 1, bootstage: "09 02"))) + count(a.errors) == 0 && !any(a.open, q => match q.question { SmproPairRefused { socket: _, pass: _ } => true _ => false }) } test fn socket_one_unreadable_beside_a_read_socket_zero_is_named() -> Bool { let s1 = [smpro_probe_outcome(raw: raw(socket: 1, register: smpro_manufacturer_id_reg.name, exit_code: 1, stdout: "", stderr: nak_stderr))] - let f = mtcollins1_smpro_findings(outcomes: concat(socket_reads(socket: 0, bootstage: "09 02"), s1)) - any(f, x => contains(x, "SMpro socket 1: CUR_BOOTSTAGE UNAVAILABLE")) + let a = account_of(outcomes: concat(socket_reads(socket: 0, bootstage: "09 02"), s1)) + current_code(a: a, socket: 0) == 9 + && (match progress_of(a: a, socket: 1) { Present { value: p } => (match p.reading { ProgressNotRead { reason: r } => contains(r as String, "CUR_BOOTSTAGE") _ => false }) Absent => false }) } // THE DOSSIER EXISTS WHEN NOTHING WAS ANSWERED. @@ -141,7 +181,7 @@ test fn a_pass_where_nothing_was_sent_still_renders_every_socket() -> Bool { let os = [SmproNotSent { socket: 0, register: smpro_bootstage_reg.name, reason: "socket 0 control: MANUFACTURER_ID TRANSPORT FAILED" }, SmproNotSent { socket: 1, register: smpro_bootstage_reg.name, reason: "socket 0 control did not establish the transport" }] let t = mtcollins1_smpro_dossier_text(outcomes: os) contains(t, "SMpro socket 0: CUR_BOOTSTAGE UNAVAILABLE") && contains(t, "SMpro socket 1: CUR_BOOTSTAGE UNAVAILABLE") && contains(t, "(no request sent)") - && count(mtcollins1_smpro_findings(outcomes: os)) == 0 + && count(account_of(outcomes: os).errors) == 0 } // ── THE ORDER IS ENFORCED BY THE FOLD (the production fold, with a supplied read) ─────────────── @@ -211,9 +251,8 @@ test fn a_failed_socket_zero_control_sends_nothing_further() -> Bool { // Same reported BOOTSTAGE on both sockets, different CURRENT stage: that is a difference. test fn a_differing_current_stage_with_the_same_bootstage_is_named() -> Bool { - let f = mtcollins1_smpro_findings(outcomes: concat(socket_reads_at(socket: 0, current: "00 09", bootstage: "03 01"), socket_reads_at(socket: 1, current: "00 03", bootstage: "03 01"))) - count(f) == 2 && any(f, x => starts_with(s: x, prefix: "SMpro socket 1: CUR_BOOTSTAGE DDR initialization")) - && any(f, x => starts_with(s: x, prefix: "SMpro socket 0: CUR_BOOTSTAGE OS booting")) + let a = account_of(outcomes: concat(socket_reads_at(socket: 0, current: "00 09", bootstage: "03 01"), socket_reads_at(socket: 1, current: "00 03", bootstage: "03 01"))) + current_code(a: a, socket: 0) == 9 && current_code(a: a, socket: 1) == 3 } // A CUR_BOOTSTAGE that read survives a BOOTSTAGE that did not. diff --git a/dag/test/claim/machine_intake/power_on_console_readers_witness_test.dag b/dag/test/claim/machine_intake/power_on_console_readers_witness_test.dag new file mode 100644 index 00000000000..3a868ee2dbe --- /dev/null +++ b/dag/test/claim/machine_intake/power_on_console_readers_witness_test.dag @@ -0,0 +1,189 @@ +module test.claim.machine_intake.power_on_console_readers_witness_test + +import v2.std.algebra { any, filter } +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } +import std.types { Bool, List, String } +import extdeps.crypto.hash { sha256_digest } +import extdeps.tools.sha256sum { Sha256FileDigest } +import gunbc.machine_intake_ampere_checkpoint_console_observation { + AmpereCheckpointConsoleObservation, AmpereCheckpointSpan, AmpereCheckpointReference, AmpereCheckpointRelation, + ReferenceContinues, NotInReference, + ampere_checkpoint_console_observation, ampere_checkpoint_relation, ampere_checkpoint_last_token, +} +import gunbc.machine_intake_linux_boot_console_observation { + InitrdFailureNotPrinted, InitrdLoadFailed, EfiLoadError, KernelPanicNotPrinted, KernelPanicked, + linux_boot_console_observation, +} +import gunbc.machine_intake_mtcollins1_console_milestone_observation { + mtcollins1_console_milestones, UefiConsolePromptObserved, DramFirmwareBannerObserved, GrubBannerObserved, EfiStubLineObserved, KernelBannerObserved, +} +import gunbc.machine_intake_host_capture_envelope { HostCaptureLiveStream, host_capture_lines } + +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +// SUPPLIED FIXTURES: EXACT BYTES of real mtcollins1 SOL captures, escaped only as .dag string +// syntax requires. Each names its source file, the file's sha256, the line range and the excerpt's +// sha256. +// branch mtcollins1-captures-2026-10-03, mtcollins1/step1-j1j17-20261003T042447Z/sol.raw +// (sha256 6f33812ce2af74aacf9deba90931aa9144b1ba974532ae9e890b90a57c5900c1), lines 138-205: the +// loop's last two cycles and the cut seventh banner; excerpt sha256 edbcaabead8194544201251f9a8a5eb26ba3dfbb8da1caec9627487f260d153d. +data step1_last_two_cycles: String = "NOTICE: DRAM FW version 220414\r\nCP: 0ff00900\r\nMEMC param:\r\n mcu_enable_mask = 00000001 [default: 000000ff]\r\nMCU param:\r\n ecc_en = 00000002 [default: 00000001]\r\nDRAM populated DIMMs:\r\n SK0 MC0 S0: RDIMM[ad:80] 16GB 2666 ECC 1R x4 RCD[32:86] HMA82GR7AFR4N-VK \r\nCP: 3ff00100\r\nCP: 3ff00200\r\nCP: 00f01902\r\nCP: 0ff00a00\r\nCP: 0ff00b01\r\nCP: 00f01901\r\nCP: 00f01904\r\nCP: 00f02901\r\nCP: 00f01905\r\nCP: 00f02902\r\nCP: 00f02903\r\nCP: 00f02904\r\nCP: 00f01907\r\nCP: 00f01906\r\nCP: 00f01908\r\nCP: 00f01909\r\nCP: 0ff00b02\r\nCP: 0ff00c00\r\nCP: 3ff00300\r\nCP: 00001a02\r\nCP: 00001a03\r\nCP: 00001a04\r\nCP: 00001a05\r\nCP: 00f01a07\r\nCP: 00001a09\r\nCP: 00001a0a\r\nNOTICE: DRAM FW version 220414\r\nCP: 0ff00900\r\nMEMC param:\r\n mcu_enable_mask = 00000001 [default: 000000ff]\r\nMCU param:\r\n ecc_en = 00000002 [default: 00000001]\r\nDRAM populated DIMMs:\r\n SK0 MC0 S0: RDIMM[ad:80] 16GB 2666 ECC 1R x4 RCD[32:86] HMA82GR7AFR4N-VK \r\nCP: 3ff00100\r\nCP: 3ff00200\r\nCP: 00f01902\r\nCP: 0ff00a00\r\nCP: 0ff00b01\r\nCP: 00f01901\r\nCP: 00f01904\r\nCP: 00f02901\r\nCP: 00f01905\r\nCP: 00f02902\r\nCP: 00f02903\r\nCP: 00f02904\r\nCP: 00f01907\r\nCP: 00f01906\r\nCP: 00f01908\r\nCP: 00f01909\r\nCP: 0ff00b02\r\nCP: 0ff00c00\r\nCP: 3ff00300\r\nCP: 00001a02\r\nCP: 00001a03\r\nCP: 00001a04\r\nCP: 00001a05\r\nCP: 00f01a07\r\nCP: 00001a09\r\nCP: 00001a0a\r\n" + +// artifacts/bmc/mtcollins1-hw-census-sol.log (sha256 +// 1e0c4bc3ab7f635f5522c23832e6d5db33d7c3b07e9467cea1f9814167bd0de8), lines 344-356: the two-socket +// census boot's checkpoints around 00001a0a -- the slice the relation's claim is about, supplied +// rather than parsing the whole capture (DESIGN section 3, a witness discriminates at one interface; +// the whole-capture route is the bundle's route claim); excerpt sha256 8e8025982c3d95ee7c24436a5eaf05d9de50aa547cfbb06dcccb02a4c68c8cb4. +data reference_good_boot_checkpoints: String = "CP: 17f01a07\r\nCP: 17001a09\r\nCP: 04101a09\r\nCP: 01101a09\r\nCP: 07101a09\r\nCP: 17101a09\r\nCP: 00001a0a\r\nCP: 14001a0a\r\nCP: 05101a09\r\nCP: 13001a0a\r\nCP: 10001a0a\r\nCP: 12101a09\r\nCP: 03001a0a\r\n" + +// run 37069907299, artifact mtcollins1-boot-receipts, file mtcollins1-boot-sol.capture (sha256 +// bfddc9ff3856bbdba500ee6b03571634a5b080588796c18c1b007ecc16361bf9), lines 426-431; excerpt +// sha256 12111d44b87cc877e42bf58be0f0dcf3671a5c1a1081c1a3cf86abbc03c37adc. +data run_37069907299_stub_and_banner: String = "EFI stub: WARNING: Working around broken SetVirtualAddressMap()\r\nEFI stub: ERROR: Failed to load initrd: 0x8000000000000001\r\nEFI stub: Generating empty DTB\r\nEFI stub: Exiting boot services...\r\n[ 0.000000] Booting Linux on physical CPU 0x0000120000 [0x413fd0c1]\r\n[ 0.000000] Linux version 6.8.0-71-generic (buildd@bos03-arm64-036) (aarch64-linux-gnu-gcc-13 (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0, GNU ld (GNU Binutils for Ubuntu) 2.42) #71-Ubuntu SMP PREEMPT_DYNAMIC Tue Jul 22 16:44:45 UTC 2025 (Ubuntu 6.8.0-71.71-generic 6.8.12)\r\n" + +// the same file, line 1879; excerpt sha256 a602607e3bd95133ca09a71f3d24f3227cf4fd92e33bd108be22bf11b3848e51. +data run_37069907299_panic: String = "[ 9.962666] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)\r\n" + +// the same file, line 1899, the panic's end marker; excerpt sha256 b54cbc78545d7e4d0bc4ad452d7f63d92af9b5742cc43613f829522cb3136ef1. +data run_37069907299_panic_end: String = "[ 10.055713] ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ]---\r\n" + +// artifacts/bmc/mtcollins1-first-own-image-boot.log (sha256 +// 19076537c45f8f7ad3c82cf14abd6d9d17fa2875dc0e5fce227f9aa532f4feb3), lines 849-854: a stub that +// loaded its initrd; excerpt sha256 a109b1b2cc1770667eb891bd42472e7cc3358e3976e4d97aac632fc05b8d8498. +data own_image_stub_and_banner: String = "EFI stub: Booting Linux Kernel...\nEFI stub: WARNING: Working around broken SetVirtualAddressMap()\nEFI stub: Generating empty DTB\nEFI stub: Exiting boot services...\n[ 0.000000] Booting Linux on physical CPU 0x0000120000 [0x413fd0c1]\n[ 0.000000] Linux version 6.12.108 (briansrls@srv2) (gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0, GNU ld (GNU Binutils for Ubuntu) 2.42) #3 SMP PREEMPT Sun Sep 6 19:47:30 UTC 2026\n" + +// run 37069907299, the same file, lines 399-402: the AMI Aptio setup prompt after a screen clear; +// excerpt sha256 7e5a8749b10b643ca9d0b6a4f0fa950b8c1a8a3237839bf60b7708acd39e5e3f. +data run_37069907299_setup_prompt: String = "\x1b[2J\x1b[1;1H\x1b[2J\x1b[1;1H\x1b[2;1H\r\nPress or to Entering the Setup.\r\nPress to Entering the Boot Menu.\r\nPress to PXE Boot.Checkpoint 98\r\n" + +fn lines_of(text: String) -> List { + host_capture_lines(capture: HostCaptureLiveStream { text: text }) +} + +fn last_span(text: String) -> AmpereCheckpointSpan? { + ampere_checkpoint_console_observation(lines: lines_of(text: text)).spans.last() +} + +fn last_token_digits(text: String) -> String { + match last_span(text: text) { + Absent => "" + Present { value: s } => match ampere_checkpoint_last_token(s: s) { Absent => "" Present { value: t } => t.digits as String } + } +} + +fn without_line(text: String, line: String) -> String { + join(filter(split(s: text, delimiter: "\n"), l => l != line), "\n") +} + +// The reference as the bundle names it: the committed census capture, its sha256 (the file's, as +// above), and the scope it was taken under. +fn census_reference() -> AmpereCheckpointReference { + AmpereCheckpointReference { + path: "artifacts/bmc/mtcollins1-hw-census-sol.log", + digest: Sha256FileDigest { digest: sha256_digest(hex: "1e0c4bc3ab7f635f5522c23832e6d5db33d7c3b07e9467cea1f9814167bd0de8") }, + firmware_scope: "the vendor firmware set installed before the 2026-10-02 update", + configuration_scope: "two sockets, sixteen DIMMs", + observation: ampere_checkpoint_console_observation(lines: lines_of(text: reference_good_boot_checkpoints)), + } +} + +data step1_digest: Sha256FileDigest = Sha256FileDigest { digest: sha256_digest(hex: "6f33812ce2af74aacf9deba90931aa9144b1ba974532ae9e890b90a57c5900c1") } + +test fn the_step1_loop_prints_two_banner_spans_and_the_last_ends_at_00001a0a() -> Bool { + let o = ampere_checkpoint_console_observation(lines: lines_of(text: step1_last_two_cycles)) + count(o.spans) == 2 && all(o.spans, s => s.opened_by_banner && count(s.malformed) == 0) + && (match o.spans.last() { Present { value: s } => count(s.tokens) == 27 Absent => false }) + && last_token_digits(text: step1_last_two_cycles) == "00001a0a" +} + +test fn dropping_the_last_token_moves_the_stop_to_00001a09() -> Bool { + last_token_digits(text: without_line(text: step1_last_two_cycles, line: "CP: 00001a0a\r")) == "00001a09" +} + +// R2: the relation names the capture, the reference, its digest and its scope, and nothing more. +test fn the_relation_says_the_reference_continued_and_under_what_scope() -> Bool { + match last_span(text: step1_last_two_cycles) { + Absent => false + Present { value: s } => match ampere_checkpoint_relation(span: s, capture: step1_digest, reference: census_reference()) { + Absent => false + Present { value: r } => (r.last.digits as String) == "00001a0a" && (r.reference_path as String) == "artifacts/bmc/mtcollins1-hw-census-sol.log" + && contains(r.reference_configuration as String, "two sockets") + && (match r.continuation { ReferenceContinues { next: n, remaining: k, occurrences: o } => (n.digits as String) == "14001a0a" && k == 6 && o == 1 _ => false }) + } + } +} + +test fn a_token_the_reference_never_printed_is_not_placed_in_it() -> Bool { + match ampere_checkpoint_console_observation(lines: ["CP: deadbeef"]).spans.first() { + Absent => false + Present { value: s } => match ampere_checkpoint_relation(span: s, capture: step1_digest, reference: census_reference()) { + Present { value: r } => (match r.continuation { NotInReference => true _ => false }) + Absent => false + } + } +} + +test fn a_short_word_is_kept_as_malformed_not_dropped() -> Bool { + match ampere_checkpoint_console_observation(lines: ["NOTICE: DRAM FW version 220414", "CP: 0ff00900", "CP: 1a0a"]).spans.first() { + Present { value: s } => s.opened_by_banner && count(s.tokens) == 1 && s.malformed == ["CP: 1a0a"] + Absent => false + } +} + +// Tokens before the first banner are their own span, which no banner opened; the banner then opens span 1. +test fn tokens_before_the_first_banner_are_a_span_no_banner_opened() -> Bool { + let o = ampere_checkpoint_console_observation(lines: ["CP: 04001a0a", "NOTICE: DRAM FW version 220414", "CP: 0ff00900"]) + count(o.spans) == 2 + && (match o.spans.first() { Present { value: s } => !s.opened_by_banner && s.index == 0 Absent => false }) + && (match o.spans.last() { Present { value: s } => s.opened_by_banner && s.index == 1 Absent => false }) +} + +test fn a_console_without_checkpoints_or_banners_has_no_span_and_no_relation() -> Bool { + count(ampere_checkpoint_console_observation(lines: lines_of(text: run_37069907299_panic)).spans) == 0 +} + +test fn run_37069907299_states_an_initrd_efi_load_error_and_a_panic() -> Bool { + let o = linux_boot_console_observation(lines: concat(lines_of(text: run_37069907299_stub_and_banner), concat(lines_of(text: run_37069907299_panic), lines_of(text: run_37069907299_panic_end)))) + o.stub_lines == 4 && o.kernel_banner_printed + && (match o.initrd { InitrdLoadFailed { status: s, line: _ } => (match s { EfiLoadError => true _ => false }) _ => false }) + && (match o.panic { KernelPanicked { reason: r } => (r as String) == "VFS: Unable to mount root fs on unknown-block(0,0)" KernelPanicNotPrinted => false }) +} + +test fn the_panic_end_marker_alone_is_not_a_panic() -> Bool { + match linux_boot_console_observation(lines: lines_of(text: run_37069907299_panic_end)).panic { KernelPanicNotPrinted => true KernelPanicked { reason: _ } => false } +} + +test fn a_stub_that_loaded_its_initrd_states_no_failure() -> Bool { + let o = linux_boot_console_observation(lines: lines_of(text: own_image_stub_and_banner)) + o.stub_lines == 4 && o.kernel_banner_printed + && (match o.initrd { InitrdFailureNotPrinted => true _ => false }) + && (match o.panic { KernelPanicNotPrinted => true _ => false }) +} + +// ── CONSOLE MILESTONES (ruling Q1) ──────────────────────────────────────────────────────────── + +test fn the_ami_setup_prompt_is_a_uefi_console_prompt_milestone_with_its_line() -> Bool { + match mtcollins1_console_milestones(lines: lines_of(text: run_37069907299_setup_prompt)).sightings.first() { + Present { value: m } => (match m.milestone { UefiConsolePromptObserved => true _ => false }) && contains(m.line as String, "Press or ") && m.line_index == 1 + Absent => false + } +} + +test fn grub_stub_and_kernel_lines_are_their_components_milestones() -> Bool { + let ms = mtcollins1_console_milestones(lines: lines_of(text: run_37069907299_stub_and_banner)).sightings + any(ms, m => match m.milestone { EfiStubLineObserved => true _ => false }) + && any(ms, m => match m.milestone { KernelBannerObserved => true _ => false }) +} + +// A milestone's span is the checkpoint reader's span for the same line, by construction. +test fn a_milestone_falls_in_the_checkpoint_readers_span() -> Bool { + let lines = lines_of(text: step1_last_two_cycles) + let banners = filter(mtcollins1_console_milestones(lines: lines).sightings, m => match m.milestone { DramFirmwareBannerObserved => true _ => false }) + let spans = ampere_checkpoint_console_observation(lines: lines).spans + count(banners) == 2 && map(banners, m => m.span) == map(spans, s => s.index) +} + +// RED: a line that only mentions UEFI is not a milestone. +test fn a_line_mentioning_uefi_is_not_a_milestone() -> Bool { + count(mtcollins1_console_milestones(lines: ["the UEFI firmware is booting", "Shell> "]).sightings) == 0 +} diff --git a/dag/test/claim/machine_intake/smpro_internal_error_witness_test.dag b/dag/test/claim/machine_intake/smpro_internal_error_witness_test.dag index 183f43edfe7..83760dd470b 100644 --- a/dag/test/claim/machine_intake/smpro_internal_error_witness_test.dag +++ b/dag/test/claim/machine_intake/smpro_internal_error_witness_test.dag @@ -20,7 +20,14 @@ import gunbc.machine_intake_mtcollins1_smpro_error_reading { mtcollins1_smpro_error_windows, mtcollins1_smpro_error_reads_20261003, } import gunbc.machine_intake_mtcollins1_smpro_observation { - SmproProbeRaw, SmproProbeOutcome, smpro_probe_outcome, mtcollins1_smpro_findings, mtcollins1_smpro_dossier_text, + SmproProbeRaw, SmproProbeOutcome, smpro_probe_outcome, mtcollins1_smpro_dossier_text, +} +import gunbc.machine_intake_mtcollins1_power_on_account { + PowerOnAccount, PowerOnEvidence, AttemptConfigurationReceipt, SmproPassEvidence, + HandoffPoweredOn, ConsoleNotRead, SelEventsNotRead, WatchdogUnobserved, + ExpectedSockets, StimulusRequestNotRecorded, StimulusApplicationNotRecorded, PopulationNotRecorded, FirmwareReadbacksNotRecorded, + SmproRecordStated, PendingErrorRecord, RecordUngated, UngatedRecordUnexplained, + mtcollins1_power_on_account, mtcollins1_power_on_account_notes, } data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly @@ -226,20 +233,46 @@ fn pmpro_words(socket: Int, info_hi: String) -> List { ] } -test fn a_pending_pmpro_record_is_a_finding_naming_the_decode() -> Bool { - let f = mtcollins1_smpro_findings(outcomes: concat(pmpro_words(socket: 1, info_hi: "22 44"), [read_ok(socket: 1, reg: smpro_gpi_ras_err_reg, stdout: "00 02")])) - any(f, x => starts_with(s: x, prefix: "SMpro socket 1: PMpro error: ERR_CCIX_RCA_LINKUP_FAIL (116) at CCIX initialization (68), ENTER")) +// WHAT A RECORD MEANS FOR THE ATTEMPT is the power-on account's (gunbc.machine_intake_mtcollins1_power_on_account): +// a gated, pending record is a stated error and a degradation; an ungated one is an anomaly and an +// open question, never an error (docs/plans/power-on-sequence-model.md, law 8). +fn account_of(outcomes: List) -> PowerOnAccount { + mtcollins1_power_on_account(e: PowerOnEvidence { + configuration: AttemptConfigurationReceipt { + subject: "mtcollins1", attempt: "witness", expected: ExpectedSockets { sockets: [0, 1], source: "witness" }, + requested: StimulusRequestNotRecorded { reason: "witness" }, applied: StimulusApplicationNotRecorded { reason: "witness" }, + cpus: PopulationNotRecorded { reason: "witness" }, dimms: PopulationNotRecorded { reason: "witness" }, firmware: FirmwareReadbacksNotRecorded { reason: "witness" }, + }, + handoff: HandoffPoweredOn, smpro: [SmproPassEvidence { label: "pass", outcomes: outcomes }], + console: ConsoleNotRead, sel: SelEventsNotRead, watchdog: WatchdogUnobserved { reason: "witness" }, + bmc_anomalies: [], census_ended: false, coverage: [], + }) +} + +data ccix_decode: String = "ERR_CCIX_RCA_LINKUP_FAIL (116) at CCIX initialization (68), ENTER" + +test fn a_pending_pmpro_record_is_a_stated_error_naming_the_decode() -> Bool { + let a = account_of(outcomes: concat(pmpro_words(socket: 1, info_hi: "22 44"), [read_ok(socket: 1, reg: smpro_gpi_ras_err_reg, stdout: "00 02")])) + any(a.errors, x => match x.error { SmproRecordStated { socket: k, processor: _, pass: _, record: r } => k == 1 && contains(smpro_internal_record_text(r: r), ccix_decode) _ => false }) + && any(a.degradations, d => match d { PendingErrorRecord { socket: k, processor: _, record: _ } => k == 1 _ => false }) + && any(mtcollins1_power_on_account_notes(a: a), l => contains(l, ccix_decode)) +} + +test fn red_without_a_gpi_read_the_record_is_ungated_not_stated() -> Bool { + let a = account_of(outcomes: pmpro_words(socket: 1, info_hi: "22 44")) + !any(a.errors, x => match x.error { SmproRecordStated { socket: _, processor: _, pass: _, record: _ } => true _ => false }) + && !any(a.degradations, d => match d { PendingErrorRecord { socket: _, processor: _, record: _ } => true _ => false }) } -test fn red_without_a_gpi_read_the_finding_says_ungated() -> Bool { - let f = mtcollins1_smpro_findings(outcomes: pmpro_words(socket: 1, info_hi: "22 44")) - any(f, x => starts_with(s: x, prefix: "SMpro socket 1: PMpro UNGATED (GPI_RAS_ERR not read in this window) error: ERR_CCIX_RCA_LINKUP_FAIL (116)")) - && !any(f, x => starts_with(s: x, prefix: "SMpro socket 1: PMpro error:")) +test fn red_an_ungated_record_is_an_anomaly_and_an_open_question() -> Bool { + let a = account_of(outcomes: pmpro_words(socket: 1, info_hi: "22 44")) + any(a.anomalies, x => match x { RecordUngated { socket: k, processor: _, pass: _, gate: _ } => k == 1 _ => false }) + && any(a.open, q => match q.question { UngatedRecordUnexplained { socket: k } => k == 1 _ => false }) } test fn red_a_missing_info_word_decodes_nothing_and_says_why() -> Bool { let os = pmpro_words(socket: 1, info_hi: "22 44") let without_hi = concat(os.take(n: 3), os.skip(n: 4)) - !any(mtcollins1_smpro_findings(outcomes: without_hi), x => contains(x, "ERR_CCIX")) + !any(mtcollins1_power_on_account_notes(a: account_of(outcomes: without_hi)), x => contains(x, "ERR_CCIX")) && contains(mtcollins1_smpro_dossier_text(outcomes: without_hi), "SMpro socket 1: PMpro records UNAVAILABLE (ERR_PMPRO_INFO_HI was not probed)") } 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 a89b8d62f15..01d20a5690f 100644 --- a/src/v2/workflow/floor_unimported_bare_provider_debt_roster.dag +++ b/src/v2/workflow/floor_unimported_bare_provider_debt_roster.dag @@ -609,7 +609,7 @@ data unimported_bare_provider_dispositions: List