Skip to content

Model firmware targets, boot diagnostics, and memory part selection - #8171

Merged
briansrls merged 15 commits into
mainfrom
session/quiet-badger-563
Aug 12, 2026
Merged

briansrls merged 15 commits into
mainfrom
session/quiet-badger-563

Conversation

@briansrls

@briansrls briansrls commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Modeling work from a live srv3/srv4 incident: both hosts failed to boot after a DIMM upgrade. The first failure was host firmware three years stale — fixed, and both hosts now train all eight modules. The residual failure is a DDR4 LRDIMM data-buffer training step.

Nothing here is a blocklist. Each avoidance is derived from a mechanism, so a future ordering workflow can shop the whole market and explain every refusal by naming the failing step, rather than being locked to a vendor list.

New modules

extdeps.firmware.types — FirmwareTarget = BmcController | HostBios. The fleet could express which BMC image a host should run and had no way to express which BIOS, though BIOS owns DDR training. FirmwareImageKind distinguishes the OpenBMC tar (pushable to a dead host) from the Instant Flash ROM (needs a booting host). FirmwareReleaseRow carries a prerequisite, so an unappliable push is refused from the catalog rather than discovered from a vendor shell script after a 33 MB upload — which is exactly how it was discovered.

Consolidates BmcFirmwareSemanticVersion to FirmwareSemanticVersion across 8 files. A version triple is one concept; minting a second name for host firmware would be the CpuArchitecture/TargetArchitecture class section 3 names.

extdeps.ampere.scp_diagnostic — SCP console tokens as class + number, with meaning held separately. Deliberately no token-to-meaning function: no codebook was obtained, so every implementation of one would be invention. The E prefix classifies a namespace, not a consequence — E559/E571 appear on a same-family machine that boots normally. DiagnosticReadEffect records that several of these interfaces mutate what they report, which bit us: the error queues were polled repeatedly before their clear-on-read semantics were understood.

extdeps.memory.training — training steps with the required set derived from buffering. A registered module has no data buffer and therefore cannot fail buffer calibration: a difference in kind, not a probability, which is what makes it sound as a purchase filter. capacity_reachable_as_dual_rank_registered derives from JEDEC die density why cheap high-capacity DDR4 is disproportionately load-reduced.

gunbc.host_memory_qualification — four independent standings: board class support, specific part qualification, DDR training outcome, host boot outcome. None derives another. Their collapse into one "does the RAM work" question cost three refuted hypotheses during the incident. Causal experiments land as receipts, including HypothesisWeakenedByIntervention for an intervention weaker than the one that would settle the question — the CMOS cable pull, versus the vendor's documented battery plus CLRMOS1 procedure.

gunbc.memory_part_selection — a candidate is refused iff it must execute a step this board has been observed to fail, independent of vendor, part number, capacity, speed grade, or AVL membership. Implication is not a Pareto objective: implicated parts are refused before the frontier is computed, because trading "might not train" against price is how the failing modules were bought.

gunbc.host_boot_supervision and gunbc.host_diagnostic_channel — two independent reset supervisors kept separate because only one is configurable. Interval typed by how it is known: powermon's 306s is IntervalMeasured from eleven journal timestamps, not IntervalDeclared — storing it in a field called interval would present a measurement as a setting. Five diagnostic authorities with their transports, plus the standing that a missing projection does not establish a missing source.

Evidence discipline

  • Every digest was measured by acquiring the artifact and hashing it. Nothing transcribed.
  • The MREP evidence is typed AssociatedByIdenticalSignature, not DecodedFromPlatformSyndrome — we never read our own syndrome register. Ampere documents SCP register B5 as carrying the per-DIMM training syndrome that would upgrade this; it was not read because neither the BMC nor the session host has ipmitool. Recorded as the upgrade path.
  • The BMC 03.22.00 row stays digest-Absent with an unobserved locator, and the note says so rather than implying it is fetchable.

Witnesses

dag/test/claim/host_memory_qualification_witness_test.dag covers the eight properties an external review required, with RED controls: population slot identity, training does not imply boot (plus positive control), class support does not imply part qualification, P-code does not imply error, E-code does not imply fatal, projection absence does not imply source absence, destructive read is not a snapshot.

Not verified

This has not been compiled. The local gunbc compile --target dag path rejects // at 1536 files corpus-wide, nearly all untouched by this branch, so it does not implement the section 4c annotation channel and cannot check this work. CI is the first real compile, and I expect it to find things.

No new scaffolds and no dissolution obligations introduced.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

Modeling work from a live srv3/srv4 incident: both hosts failed to boot
after a DIMM upgrade. Root cause of the first failure was host firmware
three years stale; the residual failure is a DDR4 LRDIMM data-buffer
training step. Nothing here is a blocklist -- each avoidance is derived
from a mechanism, so a future ordering workflow can shop the whole market
and explain every refusal by naming the failing step.

extdeps.firmware.types (new)
  FirmwareTarget = BmcController | HostBios. The fleet could express which
  BMC image a host should run and had no way to express which BIOS, though
  BIOS owns DDR training. FirmwareImageKind distinguishes the OpenBMC tar
  (pushable to a dead host) from the Instant Flash ROM (needs a booting
  host). FirmwareReleaseRow carries a prerequisite, so an unappliable push
  is refused from the catalog rather than discovered from a vendor shell
  script after a 33MB upload.

  Consolidates BmcFirmwareSemanticVersion -> FirmwareSemanticVersion across
  8 files: a version triple is one concept, and minting a second name for
  host firmware would be the CpuArchitecture/TargetArchitecture class.

extdeps.ampere.scp_diagnostic (new)
  SCP console tokens as class+number with meaning held separately.
  Deliberately no token-to-meaning function: no codebook was obtained, so
  every implementation of one would be invention. The E prefix classifies a
  namespace, not a consequence -- E559/E571 appear on a same-family machine
  that boots normally. DiagnosticReadEffect records that several of these
  interfaces mutate what they report.

extdeps.memory.training (new)
  Training steps, with the required set DERIVED from buffering. A registered
  module has no data buffer and cannot fail buffer calibration -- a
  difference in kind, which is what makes it sound as a purchase filter.
  capacity_reachable_as_dual_rank_registered derives from JEDEC die density
  why cheap high-capacity DDR4 is disproportionately load-reduced.

gunbc.host_memory_qualification (new)
  Four independent standings: board class support, specific part
  qualification, DDR training outcome, host boot outcome. None derives
  another. Their collapse into one "does the RAM work" question cost three
  refuted hypotheses during the incident. Causal experiments land as
  receipts, including HypothesisWeakenedByIntervention for an intervention
  weaker than the one that would settle the question.

gunbc.memory_part_selection (new)
  A candidate is refused iff it must execute a step this board has been
  observed to fail -- independent of vendor, part number, capacity, speed
  grade, or AVL membership. Implication is not a Pareto objective: implicated
  parts are refused before the frontier is computed.

gunbc.host_boot_supervision, gunbc.host_diagnostic_channel (new)
  Two independent reset supervisors, kept separate because only one is
  configurable. Interval typed by how it is KNOWN: powermon's 306s is
  measured from eleven journal timestamps, not read from configuration.
  Five diagnostic authorities with their transports, and the standing that
  a missing projection does not establish a missing source.

Witnesses cover the eight properties an external review required. Every
digest was measured by acquiring the artifact; nothing is transcribed.

NOT COMPILED. The local `gunbc compile --target dag` path rejects `//` at
1536 files corpus-wide, nearly all untouched, so it does not implement the
section 4c annotation channel and cannot check this. CI is the first real
compile.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 12, 2026 02:55
@gunbai-bot gunbai-bot Bot changed the title srv3+4 upgrade check Model firmware targets, boot diagnostics, and memory part selection Aug 12, 2026
Brian Searls and others added 3 commits August 12, 2026 03:07
The parse error CI named (expected name, found LBrace) was a misplaced
import: the migration insert landed between the opening brace of a
multi-line import block and its first name. Only this one file was
affected; the other five insert sites were checked and are well-formed.

Review 51271, both blockers, verified present and fixed:

- FirmwareReleaseRow.image_bytes was a flat Nat beside a sibling
  capacity_bytes already carrying ByteSize in the same diff. Now
  ByteSize; the three ASRock call sites construct through byte_size.

- PartCandidate carried unit_price_minor: Int and currency: NonEmptyStr,
  with a comment saying prices are deliberately not compared across
  currencies. That is exactly the invariant the type should enforce, so
  it is now MoneyAmountMicro + std.currency.CurrencyCode, and admission
  returns a typed CandidateAdmission whose CandidateCurrencyMismatch arm
  refuses a candidate priced in an undeclared currency rather than
  ranking it. The narration is now a wall.

The selection rule had no executing consumer, which the unit fix made
conspicuous. Adds memory_part_selection_witness_test with the currency
refusal and its positive control, the implication rule and its RED
control (a registered module of the same vendor/capacity/generation is
NOT implicated, so the rule is discriminating on buffering rather than
on part identity), and a no-observed-failure control proving the refusal
comes from the observation rather than from a preference in the model.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS
data observed_failures was written without a type annotation, which the
parser refuses (expected Colon, found Eq). Mine, in the witness added by
the previous commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS
Review 51280, blocking item, verified present at
extdeps.boards.asrock_rack asrock_altrad8ud_bios_3_10_obmc_push_artifact_bytes
(the review cited line 369; the symbol is at 350 — naming it symbolically
per DESIGN section 3). It was the last bare-Nat byte count in the diff and
sits beside FirmwareReleaseRow.image_bytes, which the previous commit had
already moved to ByteSize, so it was a fork of the carrier in one file.
Now byte_size(count: 33566720). It has no consumers, so nothing downstream
changes shape.

Not taking the non-blocking import cleanup in extdeps.memory.training; see
the PR reply for why.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS
@gunbai-bot

gunbai-bot Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Both items from review 51280 verified against the current code.

Blocking — bare Nat byte count: fixed. Confirmed present. Naming it symbolically rather than positionally (the review cited line 369; the symbol is at 350): extdeps.boards.asrock_rack asrock_altrad8ud_bios_3_10_obmc_push_artifact_bytes was the last bare-Nat byte count in the diff, and it sat in the same file as FirmwareReleaseRow.image_bytes, which had already moved to ByteSize — so it was a fork of the carrier within one file, which is the sharper version of the finding. Now byte_size(count: 33566720). It has no consumers, so nothing downstream changed shape.

Non-blocking — unused imports in extdeps.memory.training: declining, with a reason. The observation is correct: DimmBuffering, DramGeneration, bit_width, byte_size, byte_size_count, bit_width_count, ByteSize, BitWidth and NonEmptyStr each occur exactly once in that file — on the import line itself. I am not removing them, because removing an unused .dag import is not a no-op in this repository right now. DESIGN.md's #6985 open thread records Class B: a stripped file's bare cross-module references resolve only by pool-membership coincidence — resolve_in finds a target module exactly when some unrelated unstripped import elsewhere in the assembled closure has already dragged it into the pool — and concludes verbatim that this "blocks all further dag/** import-stripping until a closure-independent binding fix or a provable-coverage construction check lands." Neither has landed. So a cosmetically clean import list here can red an unrelated module whose binding was riding on this one's pool contribution, and the failure would surface far from this PR.

That is a standing repository constraint rather than a preference of mine, so I would rather leave the noise visible than take a housekeeping win that the corpus explicitly says is unsafe. If the intent is to clean these up, the honest route is the closure-independent binding fix that thread names, which retires the whole class at once instead of one file at a time.

— sent from quiet-badger-563

Brian Searls and others added 3 commits August 12, 2026 03:29
DESIGN section 4c admits only standalone leading // blocks attached to
module-scope declarations; body-position annotations refuse. Every new
module in this change had them — comments sitting beside a field or a
coproduct arm — so the heal job refused with a located diagnostic per
line. Each block moves above its enclosing declaration and keeps its
referent by naming it ("On prerequisite:", "On
FirmwarePrerequisiteUnobserved:"), so no rationale is lost in the move.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS
Review 51292, both findings verified: firmware_release_applicability and
host_boot_loop_cause had zero importing consumers and zero witnesses, so
the PR's two central fail-closed claims were specification without
execution — the exact bar this PR's own prose sets (DESIGN section 5).

The applicability rows bind the PRODUCTION release rows rather than
fixtures, so the gate runs against the prerequisite the catalog actually
declares:
- unreadable target version refuses (the srv3 BIOS-null case that
  justified the third arm);
- the srv3 push at BMC 2.07.00 is Unmet and the verdict carries what it
  observed;
- the srv4 push at 3.22.00 is Applicable (positive control);
- 3.06.00 is Unmet — higher major, lower minor, so a major-only compare
  would wrongly admit it, and that version is a real catalogued image
  rather than an invented one;
- the Instant Flash ROM row is Applicable on the SAME Absent observation
  that refuses on the tar row. Same input, opposite verdict, because the
  prerequisite belongs to the packaging.

For the loop cause the discriminating pair is the intervention, not the
signature: one E588 signature answers HostSelfResetOnError after the
supervisor was stopped and the loop persisted, and CONFOUNDED with the
identical signature when it was not. That is the state the incident sat
in while the watchdog theory looked plausible, and it is why period
alone cannot decide — a 306-second cycle looks the same whether or not a
306-second timeout causes it. A third row keeps a clean stop with no
error code at Unobserved rather than inferring a supervisor verdict from
silence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS
The rule refused any module that merely EXECUTES a step some platform was
observed to fail — on any board, at any evidence standing, including
EvidenceUnobserved. Because capacities near a generation's die-density
ceiling are disproportionately load-reduced, that removed most of the
64GB DDR4 market by construction: the blocklist this module's own prose
claimed to avoid, reached by a different route, and the opposite of
letting a buyer shop the whole market instead of an AVL.

Severity is now a function of evidence, which is what the standing type
was carrying all along and the verdict was discarding:
- DecodedFromPlatformSyndrome refuses;
- AssociatedByIdenticalSignature and InferredFromSequencePosition
  require a SAMPLE and stay on the Pareto frontier;
- EvidenceUnobserved implicates nothing.

gunbc holds MREP at association standing, so the honest verdict for
another ALTRAD8UD LRDIMM is buy-one-and-test. Failures are also scoped to
the board observed, so an ALTRAD8UD receipt cannot speak for a different
platform.

Witnesses discriminate on evidence with the board, step and module held
fixed: association -> sample-required, decoded -> refused, unobserved ->
nothing, registered module untouched at any standing. Two frontier rows
show the market is not shrunk by an association (both candidates remain,
the load-reduced one flagged) while a decoded refusal still drops one.

KNOWN GAP, declared rather than hidden: BaseboardModel has exactly one
variant today, so board scoping is real in the code but has no executing
cross-board control. Minting a second board purely to test it would be a
fabricated entity; the control lands when a second real board is modeled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS
@gunbai-bot

gunbai-bot Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Two rounds of feedback addressed here, plus a defect I found in my own model that neither review caught.

review 51292 — both findings verified and fixed. firmware_release_applicability and host_boot_loop_cause had zero importing consumers and zero witnesses, which is specification-without-execution on the two functions this PR's fail-closed claims rest on. Applicability now has five rows binding the production release rows rather than fixtures: unreadable version refuses (the srv3 BIOS-null case), the real srv3 push at 2.07.00 is Unmet carrying what it observed, srv4's at 3.22.00 is Applicable, 3.06.00 is Unmet (higher major, lower minor — a major-only compare would wrongly admit it, and it's a real catalogued image), and the Instant Flash ROM row is Applicable on the same Absent observation that refuses on the tar row. Loop cause gets the powermon refutation as an executing check: the same E588 signature answers HostSelfResetOnError after the supervisor was stopped and the loop persisted, and LoopCauseConfounded when it wasn't — period alone can't decide it, since a 306-second cycle looks identical whether or not a 306-second timeout causes it.

The bigger problem, which I'd built in myself. The selection rule refused any module that merely executes a step some platform was observed to fail — on any board, at any evidence standing, including EvidenceUnobserved. Since capacities near a generation's die-density ceiling are disproportionately load-reduced, that removed most of the 64GB DDR4 market by construction. It was the blocklist this module's own prose claims to avoid, reached by a different route, and it defeated the actual purchasing goal it was written to serve.

Severity is now a function of evidence — which TrainingEvidenceStanding was already carrying and the verdict was discarding:

standing verdict
DecodedFromPlatformSyndrome refuse
AssociatedByIdenticalSignature / InferredFromSequencePosition sample required, stays on the frontier
EvidenceUnobserved implicates nothing

gunbc holds MREP at association standing, so the honest verdict for another ALTRAD8UD LRDIMM is buy-one-and-test, not reject. Failures are also scoped to the board they were observed on. Witnesses hold board, step and module fixed and vary only the evidence, so the grading is discriminating rather than incidental; two frontier rows show an association leaves both candidates ranked while a decoded refusal still drops one.

Declared gap rather than a silent one: BaseboardModel has exactly one variant today, so the board scoping is real in the code but has no executing cross-board control. Minting a second board purely to test it would be a fabricated entity — that control lands when a second real board is modeled.

— sent from quiet-badger-563

Brian Searls and others added 4 commits August 12, 2026 04:03
review 51303, same finding as review 51298 on the stacked PR. I fixed it
there and not here, but host_boot_supervision.dag is introduced by THIS
PR, so the fix belonged at its origin — the reviewer was correctly seeing
bare Nat on the branch that declares the type.

std.measure Second is the carrier siblings already use for this concept.
The declared-vs-measured epistemic split is unchanged; only the unit
moves. Duration is not a separate std.measure carrier in this corpus, so
Second is the existing single authority rather than a new one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS
…mports

Cherry-picked to the branch that INTRODUCES these modules. I committed
this fix on the stacked derivation branch instead, so #8171 — where
extdeps.firmware.types, extdeps.memory.training and
extdeps.ampere.scp_diagnostic are actually added — kept failing the
placement gate it was failing before. The stack direction makes that
invisible: merging the parent forward into the child hides that the
child, not the parent, carries the fix.

The extdeps scope placement gate refuses a new dag/extdeps file enrolled
in neither scope_carrier_paths nor the machinery exemption, by design —
the frozen legacy manifest is explicitly not an admission channel. Each
module now declares extdeps_model_scope naming its exact upstream
subject and joins the carrier roster:

- extdeps.firmware.types -> FirmwareReleaseRow: the vendor release and
  its applicability precondition, not the flashing mechanism and not
  fleet policy about which release to run.
- extdeps.memory.training -> MemoryTrainingStep: the JEDEC DDR4
  initialization calibration sequence, cited to JESD79-4A.
- extdeps.ampere.scp_diagnostic -> AmpereScpConsoleToken: the token
  stream and structured boot-progress interface as an upstream surface
  only, read-effect axis included because an acknowledged queue is not a
  sampled register, and still no token-to-meaning function.

Also fixes Present/Absent, imported from std.types where they do not
live; the corpus idiom is v2.std.optional. This branch's copy of
extdeps.firmware.types needs them for firmware_release_applicability but
not List/list_length, which belong to the evidence carrier that lives
only on the stacked branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS
One conflict, purely additive: main enrolled
dag/extdeps/rust/cargo_message.dag in scope_carrier_paths while this
branch enrolled its three new extdeps modules at the same position.
Both belong; resolved to the union.

Merged rather than rebased per the repo's merge policy — branch history
is flattened by squash-merge, so rebasing to curate commits buys nothing
and a merge commit avoids a force-push. Same end state.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS
review 51323. The finding as stated does not reproduce, and the defect
underneath it is real but mirrored — both worth recording.

NOT REPRODUCED: the cited candidate ["A1","A1","B1","E1"] is REFUSED
today. population_slots_match folds `a` over `b`, and the call site
passes a = documented row, b = candidate — so the subset test runs
documented-into-candidate, not candidate-into-documented. A duplicated
candidate slot shrinks its distinct set, so a same-length documented row
with four distinct slots cannot be covered. Simulated over all four
documented rows before changing anything.

REAL, AND LATENT: the guard was equal-length-plus-subset, not multiset
equality, and it relied on an unstated precondition that no DOCUMENTED
row carries a duplicate. Violate that and it fails in the opposite
direction from the report: documented ["A1","A1"] admits candidate
["A1","B1"], since both documented entries are found and lengths agree.
No board row has a duplicate today, which is why it was latent — and why
leaving correctness resting on that staying true is the wrong place to
stop.

Now compares occurrence counts, which is multiset equality given equal
lengths and removes the precondition. Three controls: the duplicated
candidate stays refused (the property callers rely on, previously true
only incidentally); a synthetic duplicated documented row does NOT admit
a distinct pair — the case that was actually broken; and that same row
still admits its own multiset, so the refusal is discrimination rather
than a blanket rejection of duplicated rows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS
@gunbai-bot

gunbai-bot Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Checked review 51323 by simulating the exact code before changing anything. The finding as stated does not reproduce — but there is a real defect underneath it, in the opposite direction. Both are worth recording.

Not reproduced. The cited candidate ["A1","A1","B1","E1"] is refused today. population_slots_match folds a over b, and the call site passes a = p.slots (the documented row) and b = candidate — so the subset test runs documented-into-candidate, not candidate-into-documented as the finding describes. A duplicated candidate slot shrinks its distinct set, so a same-length documented row with four distinct slots can't be covered by it. I ran all four documented rows against that candidate and the other cases to confirm.

Real, and latent. The guard was equal-length-plus-subset rather than multiset equality, and it rested on an unstated precondition: that no documented row carries a duplicate. Violate that and it fails in the mirror direction of the report — documented ["A1","A1"] admits candidate ["A1","B1"], because both documented entries are found and the lengths agree. No board row has a duplicate today, which is exactly why it was latent, and why leaving correctness resting on that staying true is the wrong place to stop. So the conclusion — this needs multiset equality — was right even though the failing case wasn't.

Now compares occurrence counts. Three controls:

  • the duplicated candidate stays refused (the property callers rely on, previously true only incidentally);
  • a synthetic duplicated documented row does not admit a distinct pair — the case that was actually broken;
  • that same row still admits its own multiset, so the refusal is multiset discrimination rather than a blanket rejection of duplicated rows.

The synthetic row is deliberate: no real board row has a duplicate, so the control has to construct one, and that's the point — the guard shouldn't depend on the corpus happening to stay duplicate-free.

— sent from quiet-badger-563

Brian Searls and others added 4 commits August 12, 2026 05:19
Parse error I shipped in the previous commit: expected RParen, found
EqEq at dag/extdeps/boards/types.dag. The comparison was written with a
line break BEFORE the ==, inside fold's argument list, so the expression
terminated at the newline and the parser then wanted the arg list to
close.

The corpus does carry `&& x == 5` unparenthesized, so the operator
combination was never the problem — the continuation position was.
Leading && and || continuations at statement level are used throughout
and parse fine; a leading == inside a call's argument list is the shape
that fails. Now one line, parenthesized, matching the style of
slot_occurrence_count directly above it. Swept the rest of the change
for the same shape: this was the only occurrence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS
The witness floor caught a real error, and it was in my test design
rather than in the model: an_association_leaves_both_candidates_on_the
_frontier returned false because both fixtures carried the SAME price
and capacity, leaving data rate as the only difference. The registered
module therefore strictly DOMINATED the load-reduced one on the Pareto
axes and the frontier correctly dropped it — I read that dominance as an
admission failure.

Admitted and undominated are different properties, and a fixture pair
differing on exactly one axis cannot distinguish them. The pair now
differs on two: the load-reduced part is cheaper and slower, the
registered part dearer and faster, so neither dominates and frontier
membership is decided by admission alone — which is what the test claims
to check. That also matches the market fact the module models, where the
part near the density ceiling is the cheap one.

Verified both frontier assertions by simulating the dominance relation
over the new fixtures before pushing: association -> 2, decoded -> 1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS
review 51333, verified: candidate_at_price took unit_price_micros as a
bare Nat and wrapped it in money_amount_micro internally, so the unit
lived inside the helper rather than at its boundary. Every caller was
free to pass a raw number meaning anything.

Now the parameter is MoneyAmountMicro and callers construct the carrier
at the call site. Same class as the earlier ByteSize / MoneyAmountMicro
/ Second fixes in this stack; I introduced this one two commits ago
while fixing the frontier fixtures and reached for a scalar out of
habit, which is precisely why the rule is worth having as a wall rather
than a preference.

std.nat is dropped from this witness: Nat became unused with the
parameter retyped, and I had added that import in the same commit that
introduced the scalar, so this returns the file to its prior import set
rather than stripping anything pre-existing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS
review 51335 asked for this on the ground that the file "has unresolved
constructors and won't compile". That reasoning is wrong, and the check
is cheap: host_boot_loop_cause executed GREEN in CI run 31566167444 —
all three of its witnesses passed, including the arms that match Present
and Absent. Bare Absent resolves in this corpus without the import;
extdeps.bmc.capability and bmc_fan_converge_witness_test both do the
same and are green on main.

Taking the change anyway, because it is right for the OTHER reason. That
resolution works by pool-membership coincidence — the target module is
in the assembled closure only because some unrelated import elsewhere
dragged it there — which is exactly the #6985 Class B hazard DESIGN
records, and the remedy that thread names is making the binding
closure-independent with an explicit import. asrock_live_probe_witness
carries the same repair for the same reason. So this is not a no-op: it
removes a dependency on accidental closure membership that would break
the next time an unrelated lane changes its imports.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS
@gunbai-bot

gunbai-bot Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Taken, but the stated reasoning doesn't hold and the record is worth correcting.

The claim that it won't compile is false, and it was cheap to check. host_boot_loop_cause executed green in CI run 31566167444 — all three of its witnesses passed, including loop_persisting_after_supervisor_stop_is_host_self_reset and no_error_code_after_a_clean_stop_is_unobserved_not_attributed, which exercise the Present and Absent arms respectively. Bare Absent resolves in this corpus without the import: extdeps.bmc.capability matches on it in three arms and bmc_fan_converge_witness_test in two, both without a v2.std.optional import, and both green on main.

I've made the change anyway, because it's right for a different reason. That resolution works by pool-membership coincidence — the target module lands in the assembled closure only because some unrelated import elsewhere dragged it there. That's exactly the #6985 Class B hazard DESIGN records, and the remedy that thread names is making the binding closure-independent with an explicit import. asrock_live_probe_witness_test carries the same repair with the same rationale in its own note. So it isn't cosmetic: it removes a dependency on accidental closure membership that breaks the next time an unrelated lane edits its imports.

Flagging the distinction because it changes what the finding is worth. "This is broken" would have meant the witnesses I added were never executing — they were, and they're green. "This is fragile" is true and worth fixing. Those warrant different confidence, and I'd rather not let a fixed-because-asked read as a defect that existed.

Related, since it came up in review 51318 and again here: I fixed this same import class in extdeps.firmware.types and two witnesses earlier tonight after importing Present/Absent from std.types, where they genuinely don't live — that one was a hard resolution error. Omitting the import and importing it from the wrong module are different failures, and only the second breaks the build.

— sent from quiet-badger-563

@briansrls
briansrls merged commit bee9ebe into main Aug 12, 2026
5 checks passed
@briansrls
briansrls deleted the session/quiet-badger-563 branch August 12, 2026 09:58
gunbai-bot Bot pushed a commit that referenced this pull request Aug 12, 2026
#8171 landed as a single squashed commit, so this branch's copies of
extdeps/firmware/types.dag and extdeps/boards/asrock_rack.dag conflicted
add/add rather than fast-forwarding.

Both files resolve to this branch's side, which is a strict superset in
every hunk: the List/list_length import and the FirmwarePrerequisite-
Observation carriers exist only here. The one substantive replacement is
asrock_altrad8ud_bios_openbmc_prerequisite_receipt, where the prose
receipt is superseded by the typed observation rows review 51290 asked
for. The fact that prose carried and the rows must not lose -- that the
minimum is an UPPER bound, with 3.06.00 untested between the two
executed pushes -- survives as PrerequisiteConsistentUpperBound plus the
executing RED control a_higher_major_with_lower_minor_is_still_unmet,
which is stronger than the sentence it replaces because it runs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS
gunbai-bot Bot pushed a commit that referenced this pull request Aug 12, 2026
CI's located cause: expected expression, found EqEq in
firmware_prerequisite_evidence_witness_test.dag. A line break before ==
terminates the expression, so the operator began a new statement. Same
class as the earlier fold-argument break; leading && is fine, leading ==
is not, so the comparison is now one line.

This file had never been parsed by CI. It reached main's floor only
because #8171 merged and GitHub retargeted this PR, which is what a
stacked branch buys: approvals on a branch that no required check
was ever configured to run.

Reading the failing test to fix it showed it did not test what it
claimed. It compared two orderings of a two-element list holding ONE
refusal and ONE acceptance -- an ordering over which first-wins and
max/min agree, so it could not distinguish them, though its comment
said that was its whole purpose. Both lists now carry a second, lower
refusal at 1.00.00: a first-wins fold reports 1.00.00 on the reversed
ordering and reds. Simulated in both directions before pushing; the
expected bounds are held in one helper so a disagreeing ordering cannot
be absorbed by a second copy of the numbers.

Also drops FirmwareSemanticVersion, unused here before this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS
briansrls added a commit that referenced this pull request Aug 13, 2026
* Model firmware targets, boot diagnostics, and memory part selection

Modeling work from a live srv3/srv4 incident: both hosts failed to boot
after a DIMM upgrade. Root cause of the first failure was host firmware
three years stale; the residual failure is a DDR4 LRDIMM data-buffer
training step. Nothing here is a blocklist -- each avoidance is derived
from a mechanism, so a future ordering workflow can shop the whole market
and explain every refusal by naming the failing step.

extdeps.firmware.types (new)
  FirmwareTarget = BmcController | HostBios. The fleet could express which
  BMC image a host should run and had no way to express which BIOS, though
  BIOS owns DDR training. FirmwareImageKind distinguishes the OpenBMC tar
  (pushable to a dead host) from the Instant Flash ROM (needs a booting
  host). FirmwareReleaseRow carries a prerequisite, so an unappliable push
  is refused from the catalog rather than discovered from a vendor shell
  script after a 33MB upload.

  Consolidates BmcFirmwareSemanticVersion -> FirmwareSemanticVersion across
  8 files: a version triple is one concept, and minting a second name for
  host firmware would be the CpuArchitecture/TargetArchitecture class.

extdeps.ampere.scp_diagnostic (new)
  SCP console tokens as class+number with meaning held separately.
  Deliberately no token-to-meaning function: no codebook was obtained, so
  every implementation of one would be invention. The E prefix classifies a
  namespace, not a consequence -- E559/E571 appear on a same-family machine
  that boots normally. DiagnosticReadEffect records that several of these
  interfaces mutate what they report.

extdeps.memory.training (new)
  Training steps, with the required set DERIVED from buffering. A registered
  module has no data buffer and cannot fail buffer calibration -- a
  difference in kind, which is what makes it sound as a purchase filter.
  capacity_reachable_as_dual_rank_registered derives from JEDEC die density
  why cheap high-capacity DDR4 is disproportionately load-reduced.

gunbc.host_memory_qualification (new)
  Four independent standings: board class support, specific part
  qualification, DDR training outcome, host boot outcome. None derives
  another. Their collapse into one "does the RAM work" question cost three
  refuted hypotheses during the incident. Causal experiments land as
  receipts, including HypothesisWeakenedByIntervention for an intervention
  weaker than the one that would settle the question.

gunbc.memory_part_selection (new)
  A candidate is refused iff it must execute a step this board has been
  observed to fail -- independent of vendor, part number, capacity, speed
  grade, or AVL membership. Implication is not a Pareto objective: implicated
  parts are refused before the frontier is computed.

gunbc.host_boot_supervision, gunbc.host_diagnostic_channel (new)
  Two independent reset supervisors, kept separate because only one is
  configurable. Interval typed by how it is KNOWN: powermon's 306s is
  measured from eleven journal timestamps, not read from configuration.
  Five diagnostic authorities with their transports, and the standing that
  a missing projection does not establish a missing source.

Witnesses cover the eight properties an external review required. Every
digest was measured by acquiring the artifact; nothing is transcribed.

NOT COMPILED. The local `gunbc compile --target dag` path rejects `//` at
1536 files corpus-wide, nearly all untouched, so it does not implement the
section 4c annotation channel and cannot check this. CI is the first real
compile.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Fix import placement, ground firmware/price units on std.measure

The parse error CI named (expected name, found LBrace) was a misplaced
import: the migration insert landed between the opening brace of a
multi-line import block and its first name. Only this one file was
affected; the other five insert sites were checked and are well-formed.

Review 51271, both blockers, verified present and fixed:

- FirmwareReleaseRow.image_bytes was a flat Nat beside a sibling
  capacity_bytes already carrying ByteSize in the same diff. Now
  ByteSize; the three ASRock call sites construct through byte_size.

- PartCandidate carried unit_price_minor: Int and currency: NonEmptyStr,
  with a comment saying prices are deliberately not compared across
  currencies. That is exactly the invariant the type should enforce, so
  it is now MoneyAmountMicro + std.currency.CurrencyCode, and admission
  returns a typed CandidateAdmission whose CandidateCurrencyMismatch arm
  refuses a candidate priced in an undeclared currency rather than
  ranking it. The narration is now a wall.

The selection rule had no executing consumer, which the unit fix made
conspicuous. Adds memory_part_selection_witness_test with the currency
refusal and its positive control, the implication rule and its RED
control (a registered module of the same vendor/capacity/generation is
NOT implicated, so the rule is discriminating on buffering rather than
on part identity), and a no-observed-failure control proving the refusal
comes from the observation rather than from a preference in the model.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Annotate observed_failures with its type

data observed_failures was written without a type annotation, which the
parser refuses (expected Colon, found Eq). Mine, in the witness added by
the previous commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Ground the obmc push artifact byte count on ByteSize

Review 51280, blocking item, verified present at
extdeps.boards.asrock_rack asrock_altrad8ud_bios_3_10_obmc_push_artifact_bytes
(the review cited line 369; the symbol is at 350 — naming it symbolically
per DESIGN section 3). It was the last bare-Nat byte count in the diff and
sits beside FirmwareReleaseRow.image_bytes, which the previous commit had
already moved to ByteSize, so it was a fork of the carrier in one file.
Now byte_size(count: 33566720). It has no consumers, so nothing downstream
changes shape.

Not taking the non-blocking import cleanup in extdeps.memory.training; see
the PR reply for why.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Derive the BMC Redfish surface from firmware instead of storing it per host

The srv3 flash exposed a dual representation. Which thermal resources
bmcweb serves, how many sensors the chassis collection carries, and
whether a fan-control resource exists are decided by the firmware BUILD
for a board, but BmcRedfishSurfaceObservation stored those three fields
per host, beside the firmware version they are a function of. So when
srv3 went 2.07.00 -> 3.22.00 on 2026-08-11 its hand-authored row went on
claiming ThermalSubsystemSensorCollection with 51 sensors, and two
witnesses ASSERTED those constants as literals — the corpus stayed green
while describing a surface the host did not have. There was no second
authority that could disagree, so nothing could have caught it.

MEASURED 2026-08-12, both hosts at once, which is what makes this a
catalog row rather than a per-host note: srv3 (192.168.1.192) and srv4
(192.168.1.195), flashed independently on different days, both returned
legacy Chassis Thermal 200, ThermalSubsystem 404, ThermalSubsystem/Fans
404, a 9-member Sensors collection, and a byte-identical Thermal key
shape (12974 / 12975 bytes, differing in one reading value). Two
independently-arrived-at hosts agreeing on every axis is the evidence
that the shape follows the image; one host would only have established
what that host does. An earlier probe returned 401 on BOTH hosts, which
was a credential fact rather than a surface fact — checking the pair is
what distinguished them.

- extdeps.bmc.types gains BmcRedfishSurfaceShape, a cited
  BmcRedfishSurfaceRow catalog keyed by version, and
  bmc_redfish_surface_for / bmc_redfish_surface_of_host. An uncatalogued
  image derives SurfaceShapeUncatalogued carrying the version rather
  than a nearest-neighbour or default shape, so the next flash is loud
  at derivation instead of silently wrong at use.
- BmcRedfishSurfaceObservation is DELETED rather than left beside the
  derivation; a dead carrier that can still be constructed re-opens the
  fork it was replaced for (DESIGN section 4b, dissolution on climb).
- srv3's firmware observation is re-observed to 3.22.00 (VERSION_ID from
  /etc/os-release, authenticated BMC SSH). Its surface moves with it in
  one motion, because there is no longer a second place to update.

Witnesses: the two that pinned the fork are replaced by the property
that actually holds — hosts on the same image derive the same surface —
plus a RED control proving the derivation discriminates on version (the
2.07.00 row still derives the old shape) and a fail-closed control that
an uncatalogued image refuses. Per section 4b the evidence stays
enrolled rather than retiring with the machinery it replaced.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Move source annotations to module-item grain

DESIGN section 4c admits only standalone leading // blocks attached to
module-scope declarations; body-position annotations refuse. Every new
module in this change had them — comments sitting beside a field or a
coproduct arm — so the heal job refused with a located diagnostic per
line. Each block moves above its enclosing declaration and keeps its
referent by naming it ("On prerequisite:", "On
FirmwarePrerequisiteUnobserved:"), so no rationale is lost in the move.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Carry the firmware prerequisite's evidence as data, not prose

Review 51290 (non-blocking) on the receipt Strings. Taking the one with
a live consequence rather than the whole corpus-wide class: the BIOS-tar
prerequisite declares BMC >= 3.22.00 as a LITERAL while the two executed
pushes that established it sat only in an unstructured String, so the
release row and its evidence could not be joined and a declared minimum
the evidence contradicted read exactly like one it supported.

FirmwarePrerequisiteObservation now carries each executed push (board,
subject, packaging, gating target and version, outcome, timestamp), with
the vendor diagnostic held on the refusal arm. The two ALTRAD8UD rows are
the discriminating pair: identical bytes refused at BMC 2.07.00, accepted
at 3.22.00.

prerequisite_evidence_verdict checks a declared minimum against them, and
its positive arm is deliberately PrerequisiteConsistentUpperBound rather
than a bare "consistent": two pushes BOUND a threshold, they do not
locate it — 3.06.00 is in the capability catalog and was never pushed, so
the declared 3.22.00 may be stricter than the vendor requires. Claiming
the threshold was measured would be the stronger-than-evidence reading
the arm exists to prevent.

Witness: the pair supports the declared minimum as an upper bound; a
minimum inflated above the accepting version is contradicted BY the
acceptance; a minimum relaxed to or below the refusing version is
contradicted BY the refusal (the direction that would admit an
unappliable push); and an empty observation list yields Absent, never
consistent.

The prose row keeps only what the carrier cannot hold: that the vendor
page documents no such dependency, that the cause was legible only in the
BMC journal because Redfish reported Exception with none, and why the
prerequisite belongs to the packaging rather than the release.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Execute the prerequisite gate and the loop-cause fold

Review 51292, both findings verified: firmware_release_applicability and
host_boot_loop_cause had zero importing consumers and zero witnesses, so
the PR's two central fail-closed claims were specification without
execution — the exact bar this PR's own prose sets (DESIGN section 5).

The applicability rows bind the PRODUCTION release rows rather than
fixtures, so the gate runs against the prerequisite the catalog actually
declares:
- unreadable target version refuses (the srv3 BIOS-null case that
  justified the third arm);
- the srv3 push at BMC 2.07.00 is Unmet and the verdict carries what it
  observed;
- the srv4 push at 3.22.00 is Applicable (positive control);
- 3.06.00 is Unmet — higher major, lower minor, so a major-only compare
  would wrongly admit it, and that version is a real catalogued image
  rather than an invented one;
- the Instant Flash ROM row is Applicable on the SAME Absent observation
  that refuses on the tar row. Same input, opposite verdict, because the
  prerequisite belongs to the packaging.

For the loop cause the discriminating pair is the intervention, not the
signature: one E588 signature answers HostSelfResetOnError after the
supervisor was stopped and the loop persisted, and CONFOUNDED with the
identical signature when it was not. That is the state the incident sat
in while the watchdog theory looked plausible, and it is why period
alone cannot decide — a 306-second cycle looks the same whether or not a
306-second timeout causes it. A third row keeps a clean stop with no
error code at Unobserved rather than inferring a supervisor verdict from
silence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Keep observations immutable; split inventory from image; refuse duplicate keys

Four findings, all verified against the code before acting.

IMMUTABLE OBSERVATIONS (mine, introduced by this PR). Editing the single
srv3 firmware row in place from 2.07.00 to 3.22.00 moved every receipt
that referenced it, so srv3's JULY update-service snapshot began claiming
it had been observed under firmware installed in AUGUST — the same
temporal-authority defect this PR set out to fix, relocated one field
sideways. The 2.07.00 and 3.22.00 observations are now separate immutable
rows; srv3_bmc_firmware_observation is the CURRENT standing and advances
by re-binding, not mutation; the update-service receipt binds the 2.07.00
row it was actually taken under. A 3.22.00 update-service receipt will
land as a new row when one is measured, not by re-pointing this one.

INVENTORY IS NOT AN IMAGE FACT. chassis_sensor_collection_member_count
sat inside the image-derived shape. Two hosts returning nine members at
one moment establishes agreement at those observations; it does not make
cardinality invariant under fan, FRU, or sensor-service changes — and
this same session measured installed fan sets DIFFERING under identical
firmware, which is the same nearby-question mistake one axis over. The
count is dropped from the shape rather than being derived from something
that does not decide it. Route and resource availability stay, since
those are compiled into bmcweb.

DUPLICATE KEYS REFUSE. bmc_redfish_surface_for took the first match, so
two rows for one version resolved by list position — a silent
order-dependent answer where the catalog actually contradicts itself. It
now answers SurfaceShapeAmbiguous with the collision count, with a RED
control that plants the duplicate.

SECOND, NOT NAT (review 51298, verified). SupervisorIntervalKnowledge
carried seconds on bare Nat while std.measure Second is the authority
used for the same concept elsewhere. The declared/measured epistemic
split is unchanged; only the carrier moves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Grade the memory refusal by evidence and scope it to the board

The rule refused any module that merely EXECUTES a step some platform was
observed to fail — on any board, at any evidence standing, including
EvidenceUnobserved. Because capacities near a generation's die-density
ceiling are disproportionately load-reduced, that removed most of the
64GB DDR4 market by construction: the blocklist this module's own prose
claimed to avoid, reached by a different route, and the opposite of
letting a buyer shop the whole market instead of an AVL.

Severity is now a function of evidence, which is what the standing type
was carrying all along and the verdict was discarding:
- DecodedFromPlatformSyndrome refuses;
- AssociatedByIdenticalSignature and InferredFromSequencePosition
  require a SAMPLE and stay on the Pareto frontier;
- EvidenceUnobserved implicates nothing.

gunbc holds MREP at association standing, so the honest verdict for
another ALTRAD8UD LRDIMM is buy-one-and-test. Failures are also scoped to
the board observed, so an ALTRAD8UD receipt cannot speak for a different
platform.

Witnesses discriminate on evidence with the board, step and module held
fixed: association -> sample-required, decoded -> refused, unobserved ->
nothing, registered module untouched at any standing. Two frontier rows
show the market is not shrunk by an association (both candidates remain,
the load-reduced one flagged) while a decoded refusal still drops one.

KNOWN GAP, declared rather than hidden: BaseboardModel has exactly one
variant today, so board scoping is real in the code but has no executing
cross-board control. Minting a second board purely to test it would be a
fabricated entity; the control lands when a second real board is modeled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Carry the supervisor interval on Second, not bare Nat

review 51303, same finding as review 51298 on the stacked PR. I fixed it
there and not here, but host_boot_supervision.dag is introduced by THIS
PR, so the fix belonged at its origin — the reviewer was correctly seeing
bare Nat on the branch that declares the type.

std.measure Second is the carrier siblings already use for this concept.
The declared-vs-measured epistemic split is unchanged; only the unit
moves. Duration is not a separate std.measure carrier in this corpus, so
Second is the existing single authority rather than a new one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Stop fabricating evidence bounds; add the two missing witness imports

review 51304 (non-blocking, but the finding is a section 5 violation by
this repo's own standard, so taking it): prerequisite_evidence_verdict
stored the DECLARED MINIMUM into whichever bound had no observation yet.
A verdict backed only by an acceptance therefore reported a
highest_refusal that never happened — a fabricated plausible value in the
exact field a consumer reads to judge how well-bounded the threshold is.
It was also order-dependent: once a bound was set, later observations
were discarded, so the same evidence authored in the other order gave
different bounds. The reviewer correctly noted my positive test greened
only because the list happens to be authored refused-then-accepted.

Both bounds are now Optional and computed as a real max-refusal /
min-acceptance over all observations, which is associative and
commutative and so cannot depend on authoring order. Absent means absent.
Two controls added: the reversed list must produce an equal verdict, and
an acceptance-only history must report NO refusal bound.

review 51307 (blocking, verified): bmc_firmware_thermal_witness_test
referenced firmware_semantic_version with no extdeps.firmware.types
import at all, and asrock_altrad8ud_openbmc_3_22_00_capabilities while
the asrock block imported only the 2.07.00 and 3.06.00 rows. Both were
mine, from scripted edits that added references without their imports.
Both added; the other changed witnesses were checked for the same class
and are clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Declare ExternalModelScope on the new extdeps modules; fix optional imports

Two real CI failures, both mine.

RESOLUTION: Present and Absent were imported from std.types, where they
do not live. The corpus idiom is v2.std.optional. Fixed in
extdeps.firmware.types and both witnesses that matched on them. I had
"verified imports" after the last review with a targeted check that only
confirmed a symbol appeared in SOME import block — it never checked the
symbol was exported by the module named, so it passed a wrong-module
import cleanly. The check was weaker than the claim I made from it.

EXTDEPS SCOPE PLACEMENT GATE: three new dag/extdeps modules were enrolled
in neither scope_carrier_paths nor the machinery exemption, which the
per-PR gate refuses by design — a new extdeps file's only landing states
are scope carrier or genuine machinery, and the frozen legacy manifest is
explicitly not an admission channel. Each now declares
extdeps_model_scope naming its exact upstream subject and joins the
carrier roster:

- extdeps.firmware.types -> FirmwareReleaseRow: the vendor release and
  its applicability precondition, not the flashing mechanism and not
  fleet policy about which release to run.
- extdeps.memory.training -> MemoryTrainingStep: the JEDEC DDR4
  initialization calibration sequence, cited to JESD79-4A; platform
  failure receipts and purchasing policy are deliberately outside it.
- extdeps.ampere.scp_diagnostic -> AmpereScpConsoleToken: the token
  stream and structured boot-progress interface as an upstream surface
  only, with the read-effect axis included because an acknowledged queue
  is not a sampled register, and with no token-to-meaning function since
  no codebook has been obtained.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Restore the deleted contradiction fn; rebind drift fixtures to 2.07.00

review 51313, all four findings verified and real.

COMPILE BREAK, mine: rewriting the evidence-bounds block deleted
prerequisite_observation_contradicts along with the accumulator it sat
beside, while prerequisite_contradiction still called it. Nothing
defined it anywhere in the tree, so the module could not compile and the
contradiction arms were unreachable — the arms that do the actual
gating. Restored with its own rationale: an acceptance strictly below the
minimum falsifies it, a refusal at or above it falsifies it the other
way, and consistent is not the same as supporting, which is what the
bounds report separately.

STALE FIXTURES, caused by this PR: three classification witnesses used
srv3 as their drifted-host fixture and asserted 2.07.00. Advancing srv3's
current standing to 3.22.00 did not merely break them — once srv3
satisfies the pinned track-3 intent, Drifted/Apply becomes Converged/Noop
and the tests would have inverted what they claim to check. They now bind
srv3_bmc_firmware_2_07_00_observation, the immutable historical row,
which is what each test was always about: the classification logic on a
host behind its intent, not srv3's present state.

That also resolves the fourth finding without a separate change: the
update-service receipt is bound to the 2.07.00 observation, so the plan
test now pairs one firmware identity with itself and the update-surface
identity check no longer refuses.

This is the immutable-observation split paying for itself immediately —
the historical row existing is what let three tests keep their meaning
instead of being rewritten around a moved fact.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Join the prerequisite oracle to the catalog instead of copying it

review 51318, verified and correct. declared_minimum hand-wrote
3.22.00 while a comment two lines above claimed the witness "compares the
SAME number the catalog declares" — it did not. Editing
asrock_altrad8ud_bios_3_10_openbmc_release.prerequisite would have left
executed_pair_supports_the_declared_minimum_as_an_upper_bound and
the_bounds_do_not_depend_on_authoring_order green until someone
remembered to edit the witness too. That is the tree-copied oracle
DESIGN section 5 forbids, and the comment asserting otherwise is what
made it look already checked.

verdict_against_declared_minimum now matches the release row's own
prerequisite and passes its minimum through, so the catalog and the tests
move together. The sibling witness already used this join, so the pattern
was in the tree and I did not follow it.

The RED controls deliberately do NOT join: a falsifying probe is an
authored input chosen to contradict the evidence, not an oracle, and
joining it to the catalog would make it unauthorable. That split is
stated in the module so the next reader does not "fix" it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Declare ExternalModelScope on the new extdeps modules; fix optional imports

Cherry-picked to the branch that INTRODUCES these modules. I committed
this fix on the stacked derivation branch instead, so #8171 — where
extdeps.firmware.types, extdeps.memory.training and
extdeps.ampere.scp_diagnostic are actually added — kept failing the
placement gate it was failing before. The stack direction makes that
invisible: merging the parent forward into the child hides that the
child, not the parent, carries the fix.

The extdeps scope placement gate refuses a new dag/extdeps file enrolled
in neither scope_carrier_paths nor the machinery exemption, by design —
the frozen legacy manifest is explicitly not an admission channel. Each
module now declares extdeps_model_scope naming its exact upstream
subject and joins the carrier roster:

- extdeps.firmware.types -> FirmwareReleaseRow: the vendor release and
  its applicability precondition, not the flashing mechanism and not
  fleet policy about which release to run.
- extdeps.memory.training -> MemoryTrainingStep: the JEDEC DDR4
  initialization calibration sequence, cited to JESD79-4A.
- extdeps.ampere.scp_diagnostic -> AmpereScpConsoleToken: the token
  stream and structured boot-progress interface as an upstream surface
  only, read-effect axis included because an acknowledged queue is not a
  sampled register, and still no token-to-meaning function.

Also fixes Present/Absent, imported from std.types where they do not
live; the corpus idiom is v2.std.optional. This branch's copy of
extdeps.firmware.types needs them for firmware_release_applicability but
not List/list_length, which belong to the evidence carrier that lives
only on the stacked branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Drop two dead surface imports from the live-receipts witness

review 51322, verified: srv3_bmc_redfish_surface and
srv4_bmc_redfish_surface appear only on their own import lines in
bmc_live_receipts_witness_test, left behind when the per-host surface
assertions were replaced by the derivation.

Removing these is NOT the case I declined on the earlier PR citing the
#6985 Class B hazard, and the distinction is worth stating since I
invoked that rule already. Class B is about a stripped file's bare
cross-module references resolving only by pool-membership coincidence —
the danger is dropping a MODULE from the assembled closure. Here
gunbc.fleet_bmc_observation stays imported via ten other symbols, so the
closure is unchanged and only two dead names go. The derivation itself
keeps its real consumers in bmc_firmware_thermal_witness_test, which is
where the surface is actually asserted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Make population admission true multiset equality

review 51323. The finding as stated does not reproduce, and the defect
underneath it is real but mirrored — both worth recording.

NOT REPRODUCED: the cited candidate ["A1","A1","B1","E1"] is REFUSED
today. population_slots_match folds `a` over `b`, and the call site
passes a = documented row, b = candidate — so the subset test runs
documented-into-candidate, not candidate-into-documented. A duplicated
candidate slot shrinks its distinct set, so a same-length documented row
with four distinct slots cannot be covered. Simulated over all four
documented rows before changing anything.

REAL, AND LATENT: the guard was equal-length-plus-subset, not multiset
equality, and it relied on an unstated precondition that no DOCUMENTED
row carries a duplicate. Violate that and it fails in the opposite
direction from the report: documented ["A1","A1"] admits candidate
["A1","B1"], since both documented entries are found and lengths agree.
No board row has a duplicate today, which is why it was latent — and why
leaving correctness resting on that staying true is the wrong place to
stop.

Now compares occurrence counts, which is multiset equality given equal
lengths and removes the precondition. Three controls: the duplicated
candidate stays refused (the property callers rely on, previously true
only incidentally); a synthetic duplicated documented row does NOT admit
a distinct pair — the case that was actually broken; and that same row
still admits its own multiset, so the refusal is discrimination rather
than a blanket rejection of duplicated rows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Flatten the multiset comparison onto one line

Parse error I shipped in the previous commit: expected RParen, found
EqEq at dag/extdeps/boards/types.dag. The comparison was written with a
line break BEFORE the ==, inside fold's argument list, so the expression
terminated at the newline and the parser then wanted the arg list to
close.

The corpus does carry `&& x == 5` unparenthesized, so the operator
combination was never the problem — the continuation position was.
Leading && and || continuations at statement level are used throughout
and parse fine; a leading == inside a call's argument list is the shape
that fails. Now one line, parenthesized, matching the style of
slot_occurrence_count directly above it. Swept the rest of the change
for the same shape: this was the only occurrence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Stop conflating admitted with undominated in the frontier witness

The witness floor caught a real error, and it was in my test design
rather than in the model: an_association_leaves_both_candidates_on_the
_frontier returned false because both fixtures carried the SAME price
and capacity, leaving data rate as the only difference. The registered
module therefore strictly DOMINATED the load-reduced one on the Pareto
axes and the frontier correctly dropped it — I read that dominance as an
admission failure.

Admitted and undominated are different properties, and a fixture pair
differing on exactly one axis cannot distinguish them. The pair now
differs on two: the load-reduced part is cheaper and slower, the
registered part dearer and faster, so neither dominates and frontier
membership is decided by admission alone — which is what the test claims
to check. That also matches the market fact the module models, where the
part near the density ceiling is the cheap one.

Verified both frontier assertions by simulating the dominance relation
over the new fixtures before pushing: association -> 2, decoded -> 1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Take the money carrier at the helper's parameter, not inside it

review 51333, verified: candidate_at_price took unit_price_micros as a
bare Nat and wrapped it in money_amount_micro internally, so the unit
lived inside the helper rather than at its boundary. Every caller was
free to pass a raw number meaning anything.

Now the parameter is MoneyAmountMicro and callers construct the carrier
at the call site. Same class as the earlier ByteSize / MoneyAmountMicro
/ Second fixes in this stack; I introduced this one two commits ago
while fixing the frontier fixtures and reached for a scalar out of
habit, which is precisely why the rule is worth having as a wall rather
than a preference.

std.nat is dropped from this witness: Nat became unused with the
parameter retyped, and I had added that import in the same commit that
introduced the scalar, so this returns the file to its prior import set
rather than stripping anything pre-existing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Import Present/Absent explicitly in host_boot_supervision

review 51335 asked for this on the ground that the file "has unresolved
constructors and won't compile". That reasoning is wrong, and the check
is cheap: host_boot_loop_cause executed GREEN in CI run 31566167444 —
all three of its witnesses passed, including the arms that match Present
and Absent. Bare Absent resolves in this corpus without the import;
extdeps.bmc.capability and bmc_fan_converge_witness_test both do the
same and are green on main.

Taking the change anyway, because it is right for the OTHER reason. That
resolution works by pool-membership coincidence — the target module is
in the assembled closure only because some unrelated import elsewhere
dragged it there — which is exactly the #6985 Class B hazard DESIGN
records, and the remedy that thread names is making the binding
closure-independent with an explicit import. asrock_live_probe_witness
carries the same repair for the same reason. So this is not a no-op: it
removes a dependency on accidental closure membership that would break
the next time an unrelated lane changes its imports.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Fix the parse error CI found, and make the order control discriminating

CI's located cause: expected expression, found EqEq in
firmware_prerequisite_evidence_witness_test.dag. A line break before ==
terminates the expression, so the operator began a new statement. Same
class as the earlier fold-argument break; leading && is fine, leading ==
is not, so the comparison is now one line.

This file had never been parsed by CI. It reached main's floor only
because #8171 merged and GitHub retargeted this PR, which is what a
stacked branch buys: approvals on a branch that no required check
was ever configured to run.

Reading the failing test to fix it showed it did not test what it
claimed. It compared two orderings of a two-element list holding ONE
refusal and ONE acceptance -- an ordering over which first-wins and
max/min agree, so it could not distinguish them, though its comment
said that was its whole purpose. Both lists now carry a second, lower
refusal at 1.00.00: a first-wins fold reports 1.00.00 on the reversed
ordering and reds. Simulated in both directions before pushing; the
expected bounds are held in one helper so a disagreeing ordering cannot
be absorbed by a second copy of the numbers.

Also drops FirmwareSemanticVersion, unused here before this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Reclassify srv3's install path as unestablished, not refused-forever

CI's 13 reds were one derivation, and they were the tripwire in
os_install_mechanism working exactly as written: "if srv3's BMC is
upgraded and its evidence row follows, this witness reds instead of the
fleet silently planning a delivery mechanism the firmware no longer
implements." srv3 was measured live on BMC 3.22.00, BIOS 3.10, host
Running; re-binding its firmware standing to that made the tripwire fire.

WHAT THESE WITNESSES NOW SAY, and what they deliberately do not. The
catalogued 3.22.00 row lists neither Redfish virtual media nor the
bmcweb nbd-proxy, and no host has an established network-boot delivery,
so the solver has nothing to select and refuses with
InstallNoVirtualMediaAndNetworkBootUnestablished. That cause names
UNESTABLISHED. It is NOT a claim that the host cannot be installed --
srv4 runs this same firmware and was installed over the configfs
USB-gadget transport, which extdeps.bmc.virtual_media already models as
ConfigfsGadgetTransport and which this solver cannot see because
OsInstallMechanism predates that authority.

WHAT I DID NOT DO, having started it and backed it out. The first
attempt added CapabilityConfigfsVirtualMedia plus a matching
OsInstallMechanism variant. That would have minted a third name for a
transport VirtualMediaTransport already names -- the section 3
nicknaming violation -- and it surfaced that
CapabilityNbdProxyVirtualMedia is already that same fork for
NbdProxyWebsocatTransport. Widening a fork to turn CI green is the wrong
trade. Reverted whole; the real repair is the install lane consuming
VirtualMediaIntent/VirtualMediaTransport, which is INSTALL-0, not this
PR.

Each rewritten witness keeps its original load-bearing claim rather than
being flipped to expect a refusal:

  no silent PXE fallback -- previously "is nbd-proxy, no fallback"; the
    fallback arm is now exactly where a defective solver would land, so
    refusing is the proof it does not
  plan is solver-derived -- a hardcoded plan would still be Ready after
    the firmware change; both halves refusing is strictly more
    discriminating than the old version, which could not tell a derived
    plan from a hardcoded one while the solve agreed with it
  boot target follows mechanism -- the mapping is pinned on the
    mechanisms directly, where the claim always lived
  prep is not install -- credential wiring and the seeded ISO path hold;
    the plan and seed halves refuse with the solve, so nothing here can
    read as a solved install
  srv4 has none -- srv3 has now joined it; both hosts run 3.22.00 and
    refuse with the same cause, so asserting a difference would assert a
    fact that stopped being true

Also corrects srv3_install_mechanism_board_default_residue, which still
said the legacy row "agrees TODAY" and cited a witness by its old name --
a stale citation in the note that predicted this very divergence.

The target facts the plan witnesses used to assert are not lost: they are
what INSTALL-0 must reproduce once srv3 has an established path, at which
point those witnesses red and are replaced rather than silently passing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* INSTALL-0 C1: make "nobody probed this" representable

The defect this lane opened on was not a wrong capability row. It was
that BmcFirmwareCapabilityRow has no way to say nobody looked, so
firmware_has_capability reads list non-membership as false and a
hand-authored list that never mentioned configfs is indistinguishable
from a probe that looked and found it missing. srv3 was refused an
install path on that reading while its sibling srv4, on the same
firmware, was installed over exactly the transport the list omits.

This commit makes the distinction representable at the grain where it is
decidable: not one flag per firmware, but one standing per requirement
the transport rests on.

  observed absent  -> Refused        a probe looked; the thing is missing
  unobserved       -> Unestablished  nobody looked; the answer is owed
  all present      -> Established

Absence outranks ignorance, deliberately: one requirement observed
missing refuses even with others unprobed, because no remaining probe
can overturn it, and answering Unestablished there would send someone to
run probes that decide nothing.

srv3's four live probes from 2026-08-13 land as receipts carrying the
command and its output, so a standing can be re-derived by someone who
doubts it rather than resting on a Bool whose provenance is a memory.
The other four requirements have NO row: an unobserved requirement is
the absence of an observation, and giving TransportProbeOutcome a third
arm would let an unrun probe be authored as though it had run. srv3
therefore stands Unestablished owing exactly those four.

WHAT THIS DOES NOT DO, and why. It does not wire the standing into
gunbc.os_install_mechanism. That solver reads its own OsInstallMechanism
enum rather than extdeps.bmc.virtual_media VirtualMediaTransport, and
teaching it to consume the transport authority drags the actuator step
lists with it -- the next commit, not this one. Until then this row is an
authority with a witness and no production consumer, which is stated
rather than left to be discovered.

It also does NOT fix catalog_has_vm_capable_firmware, which checks
CapabilityVirtualMedia only and so cannot see that the 2.07.00 row is
nbd-proxy capable. That looks like an isolated one-line defect and is
not: fixing it flips srv3/srv4 from
InstallNoVirtualMediaAndNetworkBootUnestablished to
InstallFirmwareTransitionUnestablished, which is a different refusal
cause and would red the witnesses reclassified in #8174. It belongs with
the solver rewiring, where both halves move together.

Stacked on the #8174 branch because it edits the same lane; it receives
no CI until that merges and GitHub retargets it, same as #8174 did.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Keep the standing's expression on its = line

CI's located cause: expected expression, found Newline in
fleet_install_transport_observations.dag. The srv3 standing broke the
line after `data ... =` and put the call beneath it; a .dag value
expression must begin on its operator's line.

Third instance of this class in this session -- the other two were a
leading == on a continuation line and a line break before == inside a
fold argument list. The boundary is narrower than "leading operators are
bad": a leading && parses and is idiomatic in the corpus, and `type X =`
followed by variants on the next line is a different production that has
always parsed. It is specifically a VALUE expression that cannot start
on the next line.

Swept the whole branch for both shapes; this was the only site. The
remaining `=`-terminated lines are all `type X =` coproducts in
extdeps/bmc/types.dag, untouched here and long green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Move 18 body-grain source annotations to module-item grain

DESIGN section 4c models source annotations only as standalone leading
blocks on module-scope declarations. Eighteen comment blocks across five
witness modules sat INSIDE declaration bodies -- annotating individual
clauses of a conjunction, arms of a match, or lets in the middle of a
function -- which the compile-clean gate refuses with:

  compile-clean: source annotation sits inside a declaration body. Only
  module-item grain is modeled; move it above the declaration it describes.

This is pre-existing main debt, not a regression from any one branch. The
count is exact: 18 in-body blocks under dag/ against 18 diagnostics, and
the same refusal reproduces on two unrelated branches whose only common
factor is that both force the whole-tree compile-clean baseline. Main's
own recent runs are cancelled or in-flight, so no green verdict has
observed it -- the debt has been accumulating unseen across at least five
merged PRs (#8045, #8073, #8122, #8147, #8184-era files).

The prose is preserved, not deleted. Each block is hoisted into its
declaration's leading block, merged with any block already there, keeping
the clause labels as sentences. Where a leading block already said the
same thing -- fleet_converge_cli's join-1 header covers every clause note
it carried -- the in-body copy is dropped as the duplicate it was.

Comment-only: `git diff` contains no non-comment line, so no behavior,
no witness assertion, and no emitted byte moves.

WHAT THIS DOES NOT DO: the diagnostic is typed but UNLOCATED -- it names
no file, line, or declaration, though AnnotationAttachmentRefusal carries
`origin: SourceSpan` on every arm and BodyGrainNotModeled is constructed
with it. The consumer discards the span it was handed. That is a section 5
located-ness gap which cost this diagnosis a full CI cycle and a bisection
commit; it is left as a separate finding rather than repaired here, so
this stays comment-only and reviewable as such.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Attach the two subject-less trailing annotations to the declarations they discuss

CORRECTION TO THIS PR'S OWN DIAGNOSIS. The first commit claimed 18
in-body blocks were the whole annotation debt, on the strength of an exact
count match. The match was real but the population was not the whole one:
I grepped the failing log for the body-grain message and never checked
whether a SECOND refusal class was also present. It was. The #8211 log
carried 11 "source annotation names no subject: no module item follows it"
refusals beside the 18 body-grain ones, and clearing the body-grain class
left that second class as the visible failure.

That is the nearby-question error: I confirmed the count of the class I
was looking at and read it as the count of the failure.

The unattached class is 7 at this PR's base, and it splits in two.

FIXED HERE -- two genuine trailing notes, each explaining why something
does NOT exist and therefore having no following declaration to attach to.
Both move above the declaration they already discuss, which is where they
belong on the merits and not only to satisfy the gate:

  dag/product/rack_mount.dag -- the note argues the mounting APERTURE is
    not the external envelope, which is exactly what the function directly
    below it computes; it now leads rack_mounting_aperture_height.
  resolved_graph_cache_hand_rust_witness_test.dag -- the note records a
    deleted tautological witness and points at the surviving positive
    control; it now leads that control.

NOT FIXED HERE, deliberately -- the remaining 5 are comments sitting
directly above `service` declarations (extdeps git.Inspect,
git.PublicationTransport, sec.Edgar, tailscale.Serve, gcp.IamRoles). A
`service` produces no depth-1 occurrence index entry, so it is not an
eligible annotation subject and prose above it attaches to nothing.

Relocating that prose would make the corpus worse. git/inspect.dag's block
is 24 lines of measured rationale for the service's operations -- which
porcelain version is pinned and why, why IgnoredFiles is a second
operation rather than a flag, verified by execution against git 2.43.0.
Moving it off the declaration it describes to satisfy a gate would be
contorting the source to fit a model gap, which is the opposite of what
section 4c is for.

So the honest reading is that `service` SHOULD be an eligible annotation
subject and is not. That is a change to the occurrence index / annotation
subject model in the v1 seed, not a comment edit, and it is raised rather
than improvised: this PR stays comment-only, and the gate stays red on
those 5 until that decision is made.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Hoist 13 body-grain annotations out of four modules main landed while this branch was open

The compile-clean gate failed on this branch's merge head with the body-grain
refusal class -- the same class this PR was opened to clear. The refusals are
NOT this branch's: they arrived on main in four files added after this PR's
base (dag/std/affine_space.dag, dag/std/planar_projection.dag,
dag/product/rack_power.dag, dag/product/spatial_projection.dag), and merging
main brought them in.

That is worth naming rather than just fixing. Main cleared this class in the
witness corpus and simultaneously re-introduced it in new modules, so the
population is not shrinking on its own -- clearing it is a moving target until
the rule is enforced where the module is parsed, not swept afterwards.

Two shapes, both moved to module-item grain with the subject named in the
hoisted text so the prose does not become unmoored from what it discusses:

  - variant-attached blocks (OpProjectPoint3, ProjectionSourceFrameRequired,
    ViewUnitsAreNotMillimetres, OutletsExhausted, DepthOrderAmbiguous, and the
    two borrowed ProjectionRefusal causes) hoisted into the leading block of
    their type declaration;
  - in-body blocks inside fn bodies (the zero-page-right refusal, the dimetric
    direction, the outlet-before-capacity ordering, the load-count crossing,
    the nearer-candidate role naming) hoisted above their enclosing fn.

One trailing block was also refusing under the second class, "names no subject":
rack_power.dag's NO DERATING note sat at end of file with nothing following it.
It now leads budget_capacity, the accessor returning the un-derated cited
capacity the note is about. Its dissolve-on trigger is carried across verbatim
and is pre-existing debt from main, not introduced here.

Corpus scan after this change: zero indented // lines and zero trailing blocks
across dag/ and src/v2.

NOT verified locally: the shimmed gunbc on this host predates //-annotation
support and reports a parse error on every module header block, so it is not an
oracle for this class. CI is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls added a commit that referenced this pull request Aug 13, 2026
…to its rows (#8174)

* Model firmware targets, boot diagnostics, and memory part selection

Modeling work from a live srv3/srv4 incident: both hosts failed to boot
after a DIMM upgrade. Root cause of the first failure was host firmware
three years stale; the residual failure is a DDR4 LRDIMM data-buffer
training step. Nothing here is a blocklist -- each avoidance is derived
from a mechanism, so a future ordering workflow can shop the whole market
and explain every refusal by naming the failing step.

extdeps.firmware.types (new)
  FirmwareTarget = BmcController | HostBios. The fleet could express which
  BMC image a host should run and had no way to express which BIOS, though
  BIOS owns DDR training. FirmwareImageKind distinguishes the OpenBMC tar
  (pushable to a dead host) from the Instant Flash ROM (needs a booting
  host). FirmwareReleaseRow carries a prerequisite, so an unappliable push
  is refused from the catalog rather than discovered from a vendor shell
  script after a 33MB upload.

  Consolidates BmcFirmwareSemanticVersion -> FirmwareSemanticVersion across
  8 files: a version triple is one concept, and minting a second name for
  host firmware would be the CpuArchitecture/TargetArchitecture class.

extdeps.ampere.scp_diagnostic (new)
  SCP console tokens as class+number with meaning held separately.
  Deliberately no token-to-meaning function: no codebook was obtained, so
  every implementation of one would be invention. The E prefix classifies a
  namespace, not a consequence -- E559/E571 appear on a same-family machine
  that boots normally. DiagnosticReadEffect records that several of these
  interfaces mutate what they report.

extdeps.memory.training (new)
  Training steps, with the required set DERIVED from buffering. A registered
  module has no data buffer and cannot fail buffer calibration -- a
  difference in kind, which is what makes it sound as a purchase filter.
  capacity_reachable_as_dual_rank_registered derives from JEDEC die density
  why cheap high-capacity DDR4 is disproportionately load-reduced.

gunbc.host_memory_qualification (new)
  Four independent standings: board class support, specific part
  qualification, DDR training outcome, host boot outcome. None derives
  another. Their collapse into one "does the RAM work" question cost three
  refuted hypotheses during the incident. Causal experiments land as
  receipts, including HypothesisWeakenedByIntervention for an intervention
  weaker than the one that would settle the question.

gunbc.memory_part_selection (new)
  A candidate is refused iff it must execute a step this board has been
  observed to fail -- independent of vendor, part number, capacity, speed
  grade, or AVL membership. Implication is not a Pareto objective: implicated
  parts are refused before the frontier is computed.

gunbc.host_boot_supervision, gunbc.host_diagnostic_channel (new)
  Two independent reset supervisors, kept separate because only one is
  configurable. Interval typed by how it is KNOWN: powermon's 306s is
  measured from eleven journal timestamps, not read from configuration.
  Five diagnostic authorities with their transports, and the standing that
  a missing projection does not establish a missing source.

Witnesses cover the eight properties an external review required. Every
digest was measured by acquiring the artifact; nothing is transcribed.

NOT COMPILED. The local `gunbc compile --target dag` path rejects `//` at
1536 files corpus-wide, nearly all untouched, so it does not implement the
section 4c annotation channel and cannot check this. CI is the first real
compile.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Fix import placement, ground firmware/price units on std.measure

The parse error CI named (expected name, found LBrace) was a misplaced
import: the migration insert landed between the opening brace of a
multi-line import block and its first name. Only this one file was
affected; the other five insert sites were checked and are well-formed.

Review 51271, both blockers, verified present and fixed:

- FirmwareReleaseRow.image_bytes was a flat Nat beside a sibling
  capacity_bytes already carrying ByteSize in the same diff. Now
  ByteSize; the three ASRock call sites construct through byte_size.

- PartCandidate carried unit_price_minor: Int and currency: NonEmptyStr,
  with a comment saying prices are deliberately not compared across
  currencies. That is exactly the invariant the type should enforce, so
  it is now MoneyAmountMicro + std.currency.CurrencyCode, and admission
  returns a typed CandidateAdmission whose CandidateCurrencyMismatch arm
  refuses a candidate priced in an undeclared currency rather than
  ranking it. The narration is now a wall.

The selection rule had no executing consumer, which the unit fix made
conspicuous. Adds memory_part_selection_witness_test with the currency
refusal and its positive control, the implication rule and its RED
control (a registered module of the same vendor/capacity/generation is
NOT implicated, so the rule is discriminating on buffering rather than
on part identity), and a no-observed-failure control proving the refusal
comes from the observation rather than from a preference in the model.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Annotate observed_failures with its type

data observed_failures was written without a type annotation, which the
parser refuses (expected Colon, found Eq). Mine, in the witness added by
the previous commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Ground the obmc push artifact byte count on ByteSize

Review 51280, blocking item, verified present at
extdeps.boards.asrock_rack asrock_altrad8ud_bios_3_10_obmc_push_artifact_bytes
(the review cited line 369; the symbol is at 350 — naming it symbolically
per DESIGN section 3). It was the last bare-Nat byte count in the diff and
sits beside FirmwareReleaseRow.image_bytes, which the previous commit had
already moved to ByteSize, so it was a fork of the carrier in one file.
Now byte_size(count: 33566720). It has no consumers, so nothing downstream
changes shape.

Not taking the non-blocking import cleanup in extdeps.memory.training; see
the PR reply for why.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Derive the BMC Redfish surface from firmware instead of storing it per host

The srv3 flash exposed a dual representation. Which thermal resources
bmcweb serves, how many sensors the chassis collection carries, and
whether a fan-control resource exists are decided by the firmware BUILD
for a board, but BmcRedfishSurfaceObservation stored those three fields
per host, beside the firmware version they are a function of. So when
srv3 went 2.07.00 -> 3.22.00 on 2026-08-11 its hand-authored row went on
claiming ThermalSubsystemSensorCollection with 51 sensors, and two
witnesses ASSERTED those constants as literals — the corpus stayed green
while describing a surface the host did not have. There was no second
authority that could disagree, so nothing could have caught it.

MEASURED 2026-08-12, both hosts at once, which is what makes this a
catalog row rather than a per-host note: srv3 (192.168.1.192) and srv4
(192.168.1.195), flashed independently on different days, both returned
legacy Chassis Thermal 200, ThermalSubsystem 404, ThermalSubsystem/Fans
404, a 9-member Sensors collection, and a byte-identical Thermal key
shape (12974 / 12975 bytes, differing in one reading value). Two
independently-arrived-at hosts agreeing on every axis is the evidence
that the shape follows the image; one host would only have established
what that host does. An earlier probe returned 401 on BOTH hosts, which
was a credential fact rather than a surface fact — checking the pair is
what distinguished them.

- extdeps.bmc.types gains BmcRedfishSurfaceShape, a cited
  BmcRedfishSurfaceRow catalog keyed by version, and
  bmc_redfish_surface_for / bmc_redfish_surface_of_host. An uncatalogued
  image derives SurfaceShapeUncatalogued carrying the version rather
  than a nearest-neighbour or default shape, so the next flash is loud
  at derivation instead of silently wrong at use.
- BmcRedfishSurfaceObservation is DELETED rather than left beside the
  derivation; a dead carrier that can still be constructed re-opens the
  fork it was replaced for (DESIGN section 4b, dissolution on climb).
- srv3's firmware observation is re-observed to 3.22.00 (VERSION_ID from
  /etc/os-release, authenticated BMC SSH). Its surface moves with it in
  one motion, because there is no longer a second place to update.

Witnesses: the two that pinned the fork are replaced by the property
that actually holds — hosts on the same image derive the same surface —
plus a RED control proving the derivation discriminates on version (the
2.07.00 row still derives the old shape) and a fail-closed control that
an uncatalogued image refuses. Per section 4b the evidence stays
enrolled rather than retiring with the machinery it replaced.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Move source annotations to module-item grain

DESIGN section 4c admits only standalone leading // blocks attached to
module-scope declarations; body-position annotations refuse. Every new
module in this change had them — comments sitting beside a field or a
coproduct arm — so the heal job refused with a located diagnostic per
line. Each block moves above its enclosing declaration and keeps its
referent by naming it ("On prerequisite:", "On
FirmwarePrerequisiteUnobserved:"), so no rationale is lost in the move.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Carry the firmware prerequisite's evidence as data, not prose

Review 51290 (non-blocking) on the receipt Strings. Taking the one with
a live consequence rather than the whole corpus-wide class: the BIOS-tar
prerequisite declares BMC >= 3.22.00 as a LITERAL while the two executed
pushes that established it sat only in an unstructured String, so the
release row and its evidence could not be joined and a declared minimum
the evidence contradicted read exactly like one it supported.

FirmwarePrerequisiteObservation now carries each executed push (board,
subject, packaging, gating target and version, outcome, timestamp), with
the vendor diagnostic held on the refusal arm. The two ALTRAD8UD rows are
the discriminating pair: identical bytes refused at BMC 2.07.00, accepted
at 3.22.00.

prerequisite_evidence_verdict checks a declared minimum against them, and
its positive arm is deliberately PrerequisiteConsistentUpperBound rather
than a bare "consistent": two pushes BOUND a threshold, they do not
locate it — 3.06.00 is in the capability catalog and was never pushed, so
the declared 3.22.00 may be stricter than the vendor requires. Claiming
the threshold was measured would be the stronger-than-evidence reading
the arm exists to prevent.

Witness: the pair supports the declared minimum as an upper bound; a
minimum inflated above the accepting version is contradicted BY the
acceptance; a minimum relaxed to or below the refusing version is
contradicted BY the refusal (the direction that would admit an
unappliable push); and an empty observation list yields Absent, never
consistent.

The prose row keeps only what the carrier cannot hold: that the vendor
page documents no such dependency, that the cause was legible only in the
BMC journal because Redfish reported Exception with none, and why the
prerequisite belongs to the packaging rather than the release.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Execute the prerequisite gate and the loop-cause fold

Review 51292, both findings verified: firmware_release_applicability and
host_boot_loop_cause had zero importing consumers and zero witnesses, so
the PR's two central fail-closed claims were specification without
execution — the exact bar this PR's own prose sets (DESIGN section 5).

The applicability rows bind the PRODUCTION release rows rather than
fixtures, so the gate runs against the prerequisite the catalog actually
declares:
- unreadable target version refuses (the srv3 BIOS-null case that
  justified the third arm);
- the srv3 push at BMC 2.07.00 is Unmet and the verdict carries what it
  observed;
- the srv4 push at 3.22.00 is Applicable (positive control);
- 3.06.00 is Unmet — higher major, lower minor, so a major-only compare
  would wrongly admit it, and that version is a real catalogued image
  rather than an invented one;
- the Instant Flash ROM row is Applicable on the SAME Absent observation
  that refuses on the tar row. Same input, opposite verdict, because the
  prerequisite belongs to the packaging.

For the loop cause the discriminating pair is the intervention, not the
signature: one E588 signature answers HostSelfResetOnError after the
supervisor was stopped and the loop persisted, and CONFOUNDED with the
identical signature when it was not. That is the state the incident sat
in while the watchdog theory looked plausible, and it is why period
alone cannot decide — a 306-second cycle looks the same whether or not a
306-second timeout causes it. A third row keeps a clean stop with no
error code at Unobserved rather than inferring a supervisor verdict from
silence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Keep observations immutable; split inventory from image; refuse duplicate keys

Four findings, all verified against the code before acting.

IMMUTABLE OBSERVATIONS (mine, introduced by this PR). Editing the single
srv3 firmware row in place from 2.07.00 to 3.22.00 moved every receipt
that referenced it, so srv3's JULY update-service snapshot began claiming
it had been observed under firmware installed in AUGUST — the same
temporal-authority defect this PR set out to fix, relocated one field
sideways. The 2.07.00 and 3.22.00 observations are now separate immutable
rows; srv3_bmc_firmware_observation is the CURRENT standing and advances
by re-binding, not mutation; the update-service receipt binds the 2.07.00
row it was actually taken under. A 3.22.00 update-service receipt will
land as a new row when one is measured, not by re-pointing this one.

INVENTORY IS NOT AN IMAGE FACT. chassis_sensor_collection_member_count
sat inside the image-derived shape. Two hosts returning nine members at
one moment establishes agreement at those observations; it does not make
cardinality invariant under fan, FRU, or sensor-service changes — and
this same session measured installed fan sets DIFFERING under identical
firmware, which is the same nearby-question mistake one axis over. The
count is dropped from the shape rather than being derived from something
that does not decide it. Route and resource availability stay, since
those are compiled into bmcweb.

DUPLICATE KEYS REFUSE. bmc_redfish_surface_for took the first match, so
two rows for one version resolved by list position — a silent
order-dependent answer where the catalog actually contradicts itself. It
now answers SurfaceShapeAmbiguous with the collision count, with a RED
control that plants the duplicate.

SECOND, NOT NAT (review 51298, verified). SupervisorIntervalKnowledge
carried seconds on bare Nat while std.measure Second is the authority
used for the same concept elsewhere. The declared/measured epistemic
split is unchanged; only the carrier moves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Grade the memory refusal by evidence and scope it to the board

The rule refused any module that merely EXECUTES a step some platform was
observed to fail — on any board, at any evidence standing, including
EvidenceUnobserved. Because capacities near a generation's die-density
ceiling are disproportionately load-reduced, that removed most of the
64GB DDR4 market by construction: the blocklist this module's own prose
claimed to avoid, reached by a different route, and the opposite of
letting a buyer shop the whole market instead of an AVL.

Severity is now a function of evidence, which is what the standing type
was carrying all along and the verdict was discarding:
- DecodedFromPlatformSyndrome refuses;
- AssociatedByIdenticalSignature and InferredFromSequencePosition
  require a SAMPLE and stay on the Pareto frontier;
- EvidenceUnobserved implicates nothing.

gunbc holds MREP at association standing, so the honest verdict for
another ALTRAD8UD LRDIMM is buy-one-and-test. Failures are also scoped to
the board observed, so an ALTRAD8UD receipt cannot speak for a different
platform.

Witnesses discriminate on evidence with the board, step and module held
fixed: association -> sample-required, decoded -> refused, unobserved ->
nothing, registered module untouched at any standing. Two frontier rows
show the market is not shrunk by an association (both candidates remain,
the load-reduced one flagged) while a decoded refusal still drops one.

KNOWN GAP, declared rather than hidden: BaseboardModel has exactly one
variant today, so board scoping is real in the code but has no executing
cross-board control. Minting a second board purely to test it would be a
fabricated entity; the control lands when a second real board is modeled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Carry the supervisor interval on Second, not bare Nat

review 51303, same finding as review 51298 on the stacked PR. I fixed it
there and not here, but host_boot_supervision.dag is introduced by THIS
PR, so the fix belonged at its origin — the reviewer was correctly seeing
bare Nat on the branch that declares the type.

std.measure Second is the carrier siblings already use for this concept.
The declared-vs-measured epistemic split is unchanged; only the unit
moves. Duration is not a separate std.measure carrier in this corpus, so
Second is the existing single authority rather than a new one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Stop fabricating evidence bounds; add the two missing witness imports

review 51304 (non-blocking, but the finding is a section 5 violation by
this repo's own standard, so taking it): prerequisite_evidence_verdict
stored the DECLARED MINIMUM into whichever bound had no observation yet.
A verdict backed only by an acceptance therefore reported a
highest_refusal that never happened — a fabricated plausible value in the
exact field a consumer reads to judge how well-bounded the threshold is.
It was also order-dependent: once a bound was set, later observations
were discarded, so the same evidence authored in the other order gave
different bounds. The reviewer correctly noted my positive test greened
only because the list happens to be authored refused-then-accepted.

Both bounds are now Optional and computed as a real max-refusal /
min-acceptance over all observations, which is associative and
commutative and so cannot depend on authoring order. Absent means absent.
Two controls added: the reversed list must produce an equal verdict, and
an acceptance-only history must report NO refusal bound.

review 51307 (blocking, verified): bmc_firmware_thermal_witness_test
referenced firmware_semantic_version with no extdeps.firmware.types
import at all, and asrock_altrad8ud_openbmc_3_22_00_capabilities while
the asrock block imported only the 2.07.00 and 3.06.00 rows. Both were
mine, from scripted edits that added references without their imports.
Both added; the other changed witnesses were checked for the same class
and are clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Declare ExternalModelScope on the new extdeps modules; fix optional imports

Two real CI failures, both mine.

RESOLUTION: Present and Absent were imported from std.types, where they
do not live. The corpus idiom is v2.std.optional. Fixed in
extdeps.firmware.types and both witnesses that matched on them. I had
"verified imports" after the last review with a targeted check that only
confirmed a symbol appeared in SOME import block — it never checked the
symbol was exported by the module named, so it passed a wrong-module
import cleanly. The check was weaker than the claim I made from it.

EXTDEPS SCOPE PLACEMENT GATE: three new dag/extdeps modules were enrolled
in neither scope_carrier_paths nor the machinery exemption, which the
per-PR gate refuses by design — a new extdeps file's only landing states
are scope carrier or genuine machinery, and the frozen legacy manifest is
explicitly not an admission channel. Each now declares
extdeps_model_scope naming its exact upstream subject and joins the
carrier roster:

- extdeps.firmware.types -> FirmwareReleaseRow: the vendor release and
  its applicability precondition, not the flashing mechanism and not
  fleet policy about which release to run.
- extdeps.memory.training -> MemoryTrainingStep: the JEDEC DDR4
  initialization calibration sequence, cited to JESD79-4A; platform
  failure receipts and purchasing policy are deliberately outside it.
- extdeps.ampere.scp_diagnostic -> AmpereScpConsoleToken: the token
  stream and structured boot-progress interface as an upstream surface
  only, with the read-effect axis included because an acknowledged queue
  is not a sampled register, and with no token-to-meaning function since
  no codebook has been obtained.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Restore the deleted contradiction fn; rebind drift fixtures to 2.07.00

review 51313, all four findings verified and real.

COMPILE BREAK, mine: rewriting the evidence-bounds block deleted
prerequisite_observation_contradicts along with the accumulator it sat
beside, while prerequisite_contradiction still called it. Nothing
defined it anywhere in the tree, so the module could not compile and the
contradiction arms were unreachable — the arms that do the actual
gating. Restored with its own rationale: an acceptance strictly below the
minimum falsifies it, a refusal at or above it falsifies it the other
way, and consistent is not the same as supporting, which is what the
bounds report separately.

STALE FIXTURES, caused by this PR: three classification witnesses used
srv3 as their drifted-host fixture and asserted 2.07.00. Advancing srv3's
current standing to 3.22.00 did not merely break them — once srv3
satisfies the pinned track-3 intent, Drifted/Apply becomes Converged/Noop
and the tests would have inverted what they claim to check. They now bind
srv3_bmc_firmware_2_07_00_observation, the immutable historical row,
which is what each test was always about: the classification logic on a
host behind its intent, not srv3's present state.

That also resolves the fourth finding without a separate change: the
update-service receipt is bound to the 2.07.00 observation, so the plan
test now pairs one firmware identity with itself and the update-surface
identity check no longer refuses.

This is the immutable-observation split paying for itself immediately —
the historical row existing is what let three tests keep their meaning
instead of being rewritten around a moved fact.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Join the prerequisite oracle to the catalog instead of copying it

review 51318, verified and correct. declared_minimum hand-wrote
3.22.00 while a comment two lines above claimed the witness "compares the
SAME number the catalog declares" — it did not. Editing
asrock_altrad8ud_bios_3_10_openbmc_release.prerequisite would have left
executed_pair_supports_the_declared_minimum_as_an_upper_bound and
the_bounds_do_not_depend_on_authoring_order green until someone
remembered to edit the witness too. That is the tree-copied oracle
DESIGN section 5 forbids, and the comment asserting otherwise is what
made it look already checked.

verdict_against_declared_minimum now matches the release row's own
prerequisite and passes its minimum through, so the catalog and the tests
move together. The sibling witness already used this join, so the pattern
was in the tree and I did not follow it.

The RED controls deliberately do NOT join: a falsifying probe is an
authored input chosen to contradict the evidence, not an oracle, and
joining it to the catalog would make it unauthorable. That split is
stated in the module so the next reader does not "fix" it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Declare ExternalModelScope on the new extdeps modules; fix optional imports

Cherry-picked to the branch that INTRODUCES these modules. I committed
this fix on the stacked derivation branch instead, so #8171 — where
extdeps.firmware.types, extdeps.memory.training and
extdeps.ampere.scp_diagnostic are actually added — kept failing the
placement gate it was failing before. The stack direction makes that
invisible: merging the parent forward into the child hides that the
child, not the parent, carries the fix.

The extdeps scope placement gate refuses a new dag/extdeps file enrolled
in neither scope_carrier_paths nor the machinery exemption, by design —
the frozen legacy manifest is explicitly not an admission channel. Each
module now declares extdeps_model_scope naming its exact upstream
subject and joins the carrier roster:

- extdeps.firmware.types -> FirmwareReleaseRow: the vendor release and
  its applicability precondition, not the flashing mechanism and not
  fleet policy about which release to run.
- extdeps.memory.training -> MemoryTrainingStep: the JEDEC DDR4
  initialization calibration sequence, cited to JESD79-4A.
- extdeps.ampere.scp_diagnostic -> AmpereScpConsoleToken: the token
  stream and structured boot-progress interface as an upstream surface
  only, read-effect axis included because an acknowledged queue is not a
  sampled register, and still no token-to-meaning function.

Also fixes Present/Absent, imported from std.types where they do not
live; the corpus idiom is v2.std.optional. This branch's copy of
extdeps.firmware.types needs them for firmware_release_applicability but
not List/list_length, which belong to the evidence carrier that lives
only on the stacked branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Drop two dead surface imports from the live-receipts witness

review 51322, verified: srv3_bmc_redfish_surface and
srv4_bmc_redfish_surface appear only on their own import lines in
bmc_live_receipts_witness_test, left behind when the per-host surface
assertions were replaced by the derivation.

Removing these is NOT the case I declined on the earlier PR citing the
#6985 Class B hazard, and the distinction is worth stating since I
invoked that rule already. Class B is about a stripped file's bare
cross-module references resolving only by pool-membership coincidence —
the danger is dropping a MODULE from the assembled closure. Here
gunbc.fleet_bmc_observation stays imported via ten other symbols, so the
closure is unchanged and only two dead names go. The derivation itself
keeps its real consumers in bmc_firmware_thermal_witness_test, which is
where the surface is actually asserted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Make population admission true multiset equality

review 51323. The finding as stated does not reproduce, and the defect
underneath it is real but mirrored — both worth recording.

NOT REPRODUCED: the cited candidate ["A1","A1","B1","E1"] is REFUSED
today. population_slots_match folds `a` over `b`, and the call site
passes a = documented row, b = candidate — so the subset test runs
documented-into-candidate, not candidate-into-documented. A duplicated
candidate slot shrinks its distinct set, so a same-length documented row
with four distinct slots cannot be covered. Simulated over all four
documented rows before changing anything.

REAL, AND LATENT: the guard was equal-length-plus-subset, not multiset
equality, and it relied on an unstated precondition that no DOCUMENTED
row carries a duplicate. Violate that and it fails in the opposite
direction from the report: documented ["A1","A1"] admits candidate
["A1","B1"], since both documented entries are found and lengths agree.
No board row has a duplicate today, which is why it was latent — and why
leaving correctness resting on that staying true is the wrong place to
stop.

Now compares occurrence counts, which is multiset equality given equal
lengths and removes the precondition. Three controls: the duplicated
candidate stays refused (the property callers rely on, previously true
only incidentally); a synthetic duplicated documented row does NOT admit
a distinct pair — the case that was actually broken; and that same row
still admits its own multiset, so the refusal is discrimination rather
than a blanket rejection of duplicated rows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Flatten the multiset comparison onto one line

Parse error I shipped in the previous commit: expected RParen, found
EqEq at dag/extdeps/boards/types.dag. The comparison was written with a
line break BEFORE the ==, inside fold's argument list, so the expression
terminated at the newline and the parser then wanted the arg list to
close.

The corpus does carry `&& x == 5` unparenthesized, so the operator
combination was never the problem — the continuation position was.
Leading && and || continuations at statement level are used throughout
and parse fine; a leading == inside a call's argument list is the shape
that fails. Now one line, parenthesized, matching the style of
slot_occurrence_count directly above it. Swept the rest of the change
for the same shape: this was the only occurrence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Stop conflating admitted with undominated in the frontier witness

The witness floor caught a real error, and it was in my test design
rather than in the model: an_association_leaves_both_candidates_on_the
_frontier returned false because both fixtures carried the SAME price
and capacity, leaving data rate as the only difference. The registered
module therefore strictly DOMINATED the load-reduced one on the Pareto
axes and the frontier correctly dropped it — I read that dominance as an
admission failure.

Admitted and undominated are different properties, and a fixture pair
differing on exactly one axis cannot distinguish them. The pair now
differs on two: the load-reduced part is cheaper and slower, the
registered part dearer and faster, so neither dominates and frontier
membership is decided by admission alone — which is what the test claims
to check. That also matches the market fact the module models, where the
part near the density ceiling is the cheap one.

Verified both frontier assertions by simulating the dominance relation
over the new fixtures before pushing: association -> 2, decoded -> 1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Take the money carrier at the helper's parameter, not inside it

review 51333, verified: candidate_at_price took unit_price_micros as a
bare Nat and wrapped it in money_amount_micro internally, so the unit
lived inside the helper rather than at its boundary. Every caller was
free to pass a raw number meaning anything.

Now the parameter is MoneyAmountMicro and callers construct the carrier
at the call site. Same class as the earlier ByteSize / MoneyAmountMicro
/ Second fixes in this stack; I introduced this one two commits ago
while fixing the frontier fixtures and reached for a scalar out of
habit, which is precisely why the rule is worth having as a wall rather
than a preference.

std.nat is dropped from this witness: Nat became unused with the
parameter retyped, and I had added that import in the same commit that
introduced the scalar, so this returns the file to its prior import set
rather than stripping anything pre-existing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Import Present/Absent explicitly in host_boot_supervision

review 51335 asked for this on the ground that the file "has unresolved
constructors and won't compile". That reasoning is wrong, and the check
is cheap: host_boot_loop_cause executed GREEN in CI run 31566167444 —
all three of its witnesses passed, including the arms that match Present
and Absent. Bare Absent resolves in this corpus without the import;
extdeps.bmc.capability and bmc_fan_converge_witness_test both do the
same and are green on main.

Taking the change anyway, because it is right for the OTHER reason. That
resolution works by pool-membership coincidence — the target module is
in the assembled closure only because some unrelated import elsewhere
dragged it there — which is exactly the #6985 Class B hazard DESIGN
records, and the remedy that thread names is making the binding
closure-independent with an explicit import. asrock_live_probe_witness
carries the same repair for the same reason. So this is not a no-op: it
removes a dependency on accidental closure membership that would break
the next time an unrelated lane changes its imports.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Fix the parse error CI found, and make the order control discriminating

CI's located cause: expected expression, found EqEq in
firmware_prerequisite_evidence_witness_test.dag. A line break before ==
terminates the expression, so the operator began a new statement. Same
class as the earlier fold-argument break; leading && is fine, leading ==
is not, so the comparison is now one line.

This file had never been parsed by CI. It reached main's floor only
because #8171 merged and GitHub retargeted this PR, which is what a
stacked branch buys: approvals on a branch that no required check
was ever configured to run.

Reading the failing test to fix it showed it did not test what it
claimed. It compared two orderings of a two-element list holding ONE
refusal and ONE acceptance -- an ordering over which first-wins and
max/min agree, so it could not distinguish them, though its comment
said that was its whole purpose. Both lists now carry a second, lower
refusal at 1.00.00: a first-wins fold reports 1.00.00 on the reversed
ordering and reds. Simulated in both directions before pushing; the
expected bounds are held in one helper so a disagreeing ordering cannot
be absorbed by a second copy of the numbers.

Also drops FirmwareSemanticVersion, unused here before this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Reclassify srv3's install path as unestablished, not refused-forever

CI's 13 reds were one derivation, and they were the tripwire in
os_install_mechanism working exactly as written: "if srv3's BMC is
upgraded and its evidence row follows, this witness reds instead of the
fleet silently planning a delivery mechanism the firmware no longer
implements." srv3 was measured live on BMC 3.22.00, BIOS 3.10, host
Running; re-binding its firmware standing to that made the tripwire fire.

WHAT THESE WITNESSES NOW SAY, and what they deliberately do not. The
catalogued 3.22.00 row lists neither Redfish virtual media nor the
bmcweb nbd-proxy, and no host has an established network-boot delivery,
so the solver has nothing to select and refuses with
InstallNoVirtualMediaAndNetworkBootUnestablished. That cause names
UNESTABLISHED. It is NOT a claim that the host cannot be installed --
srv4 runs this same firmware and was installed over the configfs
USB-gadget transport, which extdeps.bmc.virtual_media already models as
ConfigfsGadgetTransport and which this solver cannot see because
OsInstallMechanism predates that authority.

WHAT I DID NOT DO, having started it and backed it out. The first
attempt added CapabilityConfigfsVirtualMedia plus a matching
OsInstallMechanism variant. That would have minted a third name for a
transport VirtualMediaTransport already names -- the section 3
nicknaming violation -- and it surfaced that
CapabilityNbdProxyVirtualMedia is already that same fork for
NbdProxyWebsocatTransport. Widening a fork to turn CI green is the wrong
trade. Reverted whole; the real repair is the install lane consuming
VirtualMediaIntent/VirtualMediaTransport, which is INSTALL-0, not this
PR.

Each rewritten witness keeps its original load-bearing claim rather than
being flipped to expect a refusal:

  no silent PXE fallback -- previously "is nbd-proxy, no fallback"; the
    fallback arm is now exactly where a defective solver would land, so
    refusing is the proof it does not
  plan is solver-derived -- a hardcoded plan would still be Ready after
    the firmware change; both halves refusing is strictly more
    discriminating than the old version, which could not tell a derived
    plan from a hardcoded one while the solve agreed with it
  boot target follows mechanism -- the mapping is pinned on the
    mechanisms directly, where the claim always lived
  prep is not install -- credential wiring and the seeded ISO path hold;
    the plan and seed halves refuse with the solve, so nothing here can
    read as a solved install
  srv4 has none -- srv3 has now joined it; both hosts run 3.22.00 and
    refuse with the same cause, so asserting a difference would assert a
    fact that stopped being true

Also corrects srv3_install_mechanism_board_default_residue, which still
said the legacy row "agrees TODAY" and cited a witness by its old name --
a stale citation in the note that predicted this very divergence.

The target facts the plan witnesses used to assert are not lost: they are
what INSTALL-0 must reproduce once srv3 has an established path, at which
point those witnesses red and are replaced rather than silently passing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Bisect the annotation gate: strip this branch's new comment blocks

DELIBERATELY INFORMATION-SEEKING, not a final state. The compile-clean
gate refuses with "source annotation sits inside a declaration body" and
carries NO file, line, or declaration name -- 18 identical diagnostics
with nothing to locate them by. That is itself a floor gap: section 5
requires a refusal to be typed AND located, and this one is typed only.

What I ruled out first, by measurement rather than by guessing, so this
strip is a last resort and not a first reflex:

  brace-depth scan of every changed file, with string literals stripped
    and comment lines excluded from the count -- zero comments inside any
    declaration body
  unattached and blank-separated comment blocks -- none; every block is
    a standalone leading block immediately above a module-scope
    declaration
  em-dashes in comments -- present in 306 files on main
  double quotes in comments -- present in 132 files on main
  scope widening -- compile-clean units moved 1316 -> 1319, so the gate
    is not newly reaching pre-existing corpus files; it passed at
    51cc0cf and fails here, which places the cause in this commit

So the violation is real and mine, and I cannot see it. This strips
exactly the 93 comment lines b28b73c added, across all nine files it
touched, to answer one question the log cannot: are the new annotations
the cause at all. Green means yes and the prose comes back placed
correctly; still red means the diagnostic is pointing somewhere other
than my annotations and I look elsewhere.

The prose is not lost -- it is in b28b73c, and restoring it is the
next commit either way. Structure re-verified after the strip: every
touched file returns to brace depth 0 with no orphaned comment block.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Revert "Bisect the annotation gate: strip this branch's new comment blocks"

This reverts commit ab627dd.

* Join the prerequisite bounds oracle to its rows instead of transcribing them

review 51524 (cursor/composer-2.5, REQUEST_CHANGES) is correct, and the
finding is exact: this file already identity-joins the declared minimum to
asrock_altrad8ud_bios_3_10_openbmc_release.prerequisite and carries a
comment explaining why tree-copied literals are forbidden, while two
witnesses a few lines below still hard-coded 2/7 and 3/22. Those numbers
have a single authority -- the gating_version on each observation row --
so a re-flash at a different version would move the rows and leave these
green until someone remembered to edit the witness. That is precisely the
failure this PR removes from the minimum path, left in place on the bounds
path.

bounds_are_the_executed_pair and an_acceptance_only_history_reports_no_
refusal_bound now join to asrock_altrad8ud_bios_310_push_refused_at_bmc_207
.gating_version and asrock_altrad8ud_bios_310_push_accepted_at_bmc_322
.gating_version through one same_firmware_version helper.

FIXED BEYOND THE REVIEW, because the same defect sat in two more witnesses
it did not name: a_minimum_above_the_accepting_version_is_contradicted and
a_minimum_at_or_below_the_refusing_version_is_contradicted transcribed the
same two versions as their expected values. Joining only the two flagged
sites would have left identical rot one screen down.

That required splitting a distinction the file previously got wrong in
prose. A RED control has two halves under different rules: its falsifying
INPUT (a minimum deliberately set above or below the evidence) stays
authored, since a probe joined to the catalog it contradicts would be
unauthorable; its EXPECTED value is an oracle like any other and joins.
The old note claimed the RED controls "deliberately do NOT join" without
that split, which is why the transcription there read as intentional. The
note now states both halves.

DISCRIMINATION IS UNCHANGED, which is what makes the join safe rather than
merely tidier: both orderings feeding bounds_are_the_executed_pair carry a
third, LOWER synthetic refusal at 1.00.00, so a first-wins fold still
reports 1.00.00 against the refused row's 2.07.00 and still fails. The
order-independence claim is not weakened by sourcing the expected value
from the row.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

* Take the singleton directly instead of folding a fabricated init over it

Both reviewers noted bmc_redfish_surface_for's count==1 arm as awkward,
each on the premise that no head-of-list primitive was at hand (review
51522, review 51533). That premise is false -- `.first()` is used widely
in the corpus and returns the element directly (steps.first(),
srv1_live_deploy_privileged_commands().first()).

That makes this more than the stylistic nit both flagged it as. The fold
carried `init: SurfaceShapeUncatalogued { firmware }`, a fabricated
plausible value that is NEVER the correct answer in an arm already guarded
by count == 1: it is a well-formed derivation saying "this firmware is not
in the catalog" sitting as the default of a branch reached only because it
IS in the catalog exactly once. Nothing could return it today, because the
guard holds and the fold visits its one element -- but the shape is the
fail-open one DESIGN section 5 names, and it survives only as long as the
guard above it does. Removing the fold removes the fabricated value with
it; the arm now reads the single match it is defined over.

Behavior is unchanged: for a one-element list the fold's f ran exactly
once and returned SurfaceShapeKnown { shape: row.shape }, which is what
the direct read produces.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CuRVynkbQYGqjmf9AnyxsS

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant