Repository navigation
power-on account slice B1: host-generic boot attempt admission, wired into the boot run - #13230
Conversation
…rollment, dispatch input Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…d; bundle carries the frozen configuration; delete slice A placeholder Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
briansrls
left a comment
There was a problem hiding this comment.
Reviewed exact head 064622b08ace28e833ff63fda50d59692305704a.
The three implementation guardrails from the plan review are substantially present:
- the slot key is an injective encoding of stable host identity plus nonce, not a serialization of
ManagedHostor a 64-bit hash; admit_named_receiptreaches the create-once CAS only fromReceiptValidated, after the file, schema, subject, attempt, plan and attested-order checks;- production derives the store-host executor from the
UnitHoldProofand admits no independently supplied host label; - the absent-input arm remains
NotRecorded, while a named invalid receipt refuses and releases the unit hold before actuation.
Five blockers remain.
1. Admission is not before the attempt's pre-power reads
The approved placement is stronger than “after UnitHeld and before actuation”: clearance must exist before any pre-power read. In the real BMC-user branch, mtcollins1_boot_sdr_cache and mtcollins1_boot_sel_snapshot execute before mtcollins1_boot_under_live_unit_hold, which is where the hold and admit_boot_attempt occur.
So a malformed, mismatched or replayed receipt still causes BMC reads before it is refused. It also means those baseline readings are taken outside the unit hold that defines this attempt.
Move the unit-hold acquisition and attempt admission ahead of those reads, then take the SDR/SEL baseline only under the resulting clearance. Required control: a named receipt that refuses must dispatch neither the BMC baseline reads nor an actuation.
2. ReceiptEvidence is neither the approved SHA-256 evidence nor actually joined to the named commit
The approved carrier is ReceiptEvidence { path, digest: Sha256FileDigest, commit }. This cut instead stores digest: NonEmptyStr produced by content_hash_of_value, which is this corpus's 64-bit FNV structural hash. That is computation identity, not byte-integrity evidence.
Separately, revision is copied beside a normal Filesystem.Read(path). Nothing proves the bytes came from that revision. The lexical artifacts/receipts/*.json check also follows a committed symlink, so a path inside the directory can read bytes outside the checkout and still be rendered as path@GITHUB_SHA.
Read the regular tracked blob at the bound revision, or establish an equivalent typed tracked-file/regular-file join, and digest the exact admitted bytes through the existing SHA-256 file-digest authority. Required controls: a symlink or untracked/worktree-only file cannot mint a receipt; changing one byte changes the SHA-256 evidence.
3. The new time parser repeats an existing authority and accepts impossible dates
rfc3339_utc_seconds checks only width, punctuation and digit positions. It accepts values such as 2026-99-99T99:99:99Z and 2026-02-30T12:00:00Z, then the plan orders them lexicographically as though they were instants.
The repository already has the exact authority in gunbc.auth.approval_capability: utc_instant_is_canonical validates field ranges and the Gregorian calendar, and utc_instant_before names the comparison over canonical inputs. Reuse/extract that authority rather than creating a second, weaker timestamp contract here.
Required REDs: invalid month, invalid day-for-month and hour 24 each refuse; equal canonical instants remain admitted because the plan says fixed-at is at or before completed-at.
4. The production attempt-slot directory has no provisioned standing
This cut writes /var/lib/gunbc/boot-attempts, but no changed provisioning/grant row creates that directory or establishes its owner and mode. file_compare_and_set creates the generation file, not its parent. The wet witness passes because mktemp hands it an already-existing directory; it does not exercise the production root.
The neighbouring unit-hold store has an explicit EnsureOwnedDirectory route in gunbc.runner_host_grants. Give the attempt-admission namespace the same provisioned standing, or place it in an already admitted durable namespace without letting hold cleanup reach its keys. Add an executed control on the real store host showing the admitted process reaches the production store.
5. Partial population lists are promoted to complete operator attestations
cpu_rows enforces only a non-empty list with unique socket numbers. It does not require coverage of plan.expected; [socket 0] is accepted beside expected sockets [0,1] despite the refusal text saying “one per socket.”
dimm_rows likewise enforces only a non-empty list with unique labels. A one-row list, an unknown label, or a label paired with the wrong socket becomes PopulationOperatorAttested. Downstream, recorded_slot_rows treats that arm as the recorded population and compares the BMC only with the populated rows supplied, so an omitted populated DIMM silently lowers the expected count instead of remaining an open question.
Join CPU rows exactly to the plan's expected socket roster. Join DIMM rows to the host's admitted static slot topology, including label-to-socket identity and complete coverage, or introduce an explicit partial standing that cannot become PopulationOperatorAttested.
Required REDs: missing expected CPU socket; unknown DIMM label; known label on another socket; and an omitted slot.
The B1/B2 split itself is honest: leaving firmware and controller-freshness corroboration NotRecorded for B2 is fine. Exact-head required CI was still queued at review time; the findings above are semantic and independent of its result.
…ead; receipt is the tracked regular blob at the bound revision with SHA-256 evidence; canonical UTC instants from approval_capability; provisioned attempt store; exact CPU/DIMM roster joins Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
briansrls
left a comment
There was a problem hiding this comment.
Reviewed exact head b1d4bab18dfc865239713a8c7e967164ce691a82.
Four and a half of the five prior blockers are closed:
- The unit hold and
BootAttemptClearednow precede the SDR/SEL baseline; a refused named receipt releases the hold and carries a not-taken baseline, so it neither reads the controller nor actuates it. - The receipt is read as the regular tracked blob at the bound revision (
ls-treesubject check, thengit show ref:path), andReceiptEvidencecarries the SHA-256 of those exact bytes. Symlink and untracked controls, plus the one-byte digest control, discriminate the old path. - Attested times now reuse
utc_instant_is_canonical/utc_instant_before, including the impossible-calendar REDs and equal-instant positive control. /var/lib/gunbc/boot-attemptshas a desiredEnsureOwnedDirectorystanding and the generated srv1 sudoers line.- CPU rows join exactly to the expected socket set; DIMM rows join for completeness, known identity and socket placement rather than becoming a partial
PopulationOperatorAttested.
One semantic blocker remains in the DIMM identity spelling.
The production roster drops the authority's J prefix
mt_collins_slot_roster currently mints labels with to_string(connector), so the accepted roster is "1" through "32". The Getting Started Guide authority it consumes declares dimm_figure_connector_label_prefix = "J", describes these as J-number keys, and the existing gunbc.mt_collins_dimm_physical_identity authority derives the diagram label as J + connector number.
That means an operator receipt using the machine vocabulary already used throughout this investigation—J1, J17, J25, etc.—will refuse as ReceiptDimmUnknownSlot, while the non-authoritative bare spelling "25" is admitted. The current real-roster witness bakes in that wrong spelling.
Please construct each roster label through the existing prefix authority and update the controls. Required discrimination:
J25 on socket 1 -> admitted
25 -> ReceiptDimmUnknownSlot
J25 on socket 0 -> ReceiptDimmOnWrongSocket
omitted J25 -> ReceiptDimmSlotOmitted
Ruling on the unexecuted srv1 control
The missing real-host execution does not block B1 by itself. The model establishes the desired directory grant, and runtime remains fail-closed: until grant convergence creates the directory, the create-once CAS yields a typed store refusal before the BMC baseline and before actuation. This PR does not claim that the directory has already been observed on srv1.
It is, however, presently an owed wet execution stated in prose, not a completed control and not a typed frontier row. Keep it explicitly open; a countable wet-execution/frontier row would be preferable, but I am not making that a second blocker while mtcollins1's BMC is unavailable.
Exact-head required CI is green. GitHub currently reports the branch as needing reconciliation; that is operational, separate from the label blocker above.
…5 controls Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
briansrls
left a comment
There was a problem hiding this comment.
Reviewed exact head 6fdab372cc438900e0a272212ed69919f46de7ff.
The remaining DIMM-identity blocker is closed. mt_collins_slot_roster now derives every label through dimm_figure_connector_label_prefix plus the connector number, matching both the Getting Started Guide authority and gunbc.mt_collins_dimm_physical_identity; the production roster is therefore J1–J32, not bare numerals.
The controls discriminate the intended wall on the real validation path:
J25on socket 1 is admitted;- bare
25isReceiptDimmUnknownSlot; J25on socket 0 isReceiptDimmOnWrongSocket;- omitting
J25isReceiptDimmSlotOmitted; - the full roster establishes 32 connectors,
J16on socket 0,J25on socket 1, and no bare25identity.
The five earlier findings remain repaired: clearance precedes all BMC baseline reads, receipt bytes are the regular tracked blob at the bound revision with SHA-256 evidence, canonical UTC validation is reused, the attempt namespace has a provisioned desired state, and CPU/DIMM populations join exactly.
The unexecuted srv1 control remains an explicit wet obligation, not a claimed receipt. It is non-blocking here because the production path fails closed with a typed store refusal before baseline reads or actuation until grant convergence creates the directory.
No semantic blocker remains. Exact-head required CI is queued; land after it is green.
… actuation takes AdmittedBootSubject Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Slice B1 of
docs/plans/power-on-sequence-model.md§12: admitting one boot attempt's configuration receipt, so the power-on account's attempt-configuration fields can be recorded from what a person saw on the machine. Before this, those fields were alwaysNotRecorded.Wired.
mtcollins1_boot_under_live_unit_holdcallsadmit_boot_attempt(proof, revision)right afterUnitHeld. That is after the boot subject and the unit hold, and beforemtcollins1_boot_actuate, so before any pre-power read or write.NotRecordedplaceholder (mtcollins1_attempt_configuration_receipt) is deleted.Still owed (slice B2): the pre-power-on
hpm checkfirmware readback bound to the plan, and the controller population reading (PopulationControllerReading,FreshnessEstablished | CachePossible). Until B2 lands, firmware staysNotRecordedwith that trigger.What it adds
gunbc.host_boot_attempt_admission. It is host-generic: it takes aManagedHostBindingand the unit hold'sUnitHoldStoreExecutor, and names no unit except in its route row.AttemptConfigurationPlanandAttemptInspectionReceipt(bothsole_constructor),OperatorAttestedTime, andReceiptEvidence{path, digest, commit}.artifacts/receipts/*.json. Every refusal is a typedAttemptReceiptRefusalarm: wrong schema, missing field, subject, attempt or plan mismatch, attested ordering, malformed or absent file.transaction_nonce, joined to the receipt's attempt, then consumed in a create-once slot./var/lib/gunbc/boot-attempts, through the file CAS withExpectSlotAbsent.[A-Za-z0-9_-]. It is not a hash, because the corpus hash is 64-bit FNV and not collision-safe.admit_callers-restricted:admit_boot_attempt→admit_named_receipt→consume_attempt_slot.BootAttemptClearancewith the configurationNotRecorded; the reason names theHumanInterventionstep that would record it. A named receipt that fails any check givesBootAttemptRefusedbefore any pre-power read or actuation.attempt_configuration_receiptfills the account's expected topology, requested and applied stimulus, and operator-attested CPU and DIMM population. Firmware staysNotRecordeduntil slice B2 adds the pre-power-onhpm checkread and the controller population reading.attempt_receiptdispatch input. The boot step's environment maps it andtransaction_nonceintoGUNBC_BOOT_ATTEMPT_RECEIPTandGUNBC_BOOT_ATTEMPT_NONCE. Thetransaction_noncedescription no longer says "not consumed by any step".fleet-converge.ymlwas regenerated viagenerated_artifact_gate main_wet.Evidence
claim_batchHermetic after the wiring: the diagnostic bundle (71/71), the boot run (35/35) and the acceptance matrix (28/28).test.claim.host_boot_attempt_admission_witness: 10/10 underclaim_batchHermetic. These are the validation fold's arms with supplied reads: the positive projection, explicit null vs missing request, attempt A dispatched as B, another host, a crossed plan, attested ordering, time shape, malformed and absent file, nonce/path refused before any read, key injectivity, and not-named giving every fieldNotRecorded.test.claim.host_boot_attempt_admission_wet_witness: PASS under--wetagainst a real temp directory. In one ordered run: first A consumed; repeated A refused as already consumed; B consumed; A still refused after B; and A still refused after a unit hold for the same host is taken and released in the same directory.ci_layer_rootsexclusion row, alocal_repo_wet_schedulerow, and afloor_route_gapDirWithTemplate expectation.Rework for the side-chat review at 064622b
mtcollins1_boot_baseline) now run only under the unit hold and afterBootAttemptCleared; a hold or receipt refusal records them as not taken, with the reason.a_refused_named_receipt_reads_no_baseline_and_writes_nothing. A named receipt outsideartifacts/receipts/refuses with no SDR or SEL read and no power action. The matrix passes 29/29.extdeps.git.inspectListTreeEntryAtPath(new, path-scopedls-tree -z), decoded by the existinggunbc.namespace_step0_subject_collectorreader, thengit show <rev>:<path>.ReceiptNotTracked; a symlink, gitlink or directory isReceiptNotRegularFile.ReceiptEvidence.digest: Sha256FileDigestis the SHA-256 of exactly those bytes, viaextdeps.tools.sha256sumsha256sum_stdin_digest_via_shell.w_only_a_regular_tracked_blob_at_the_revision_is_a_receipt(regular admits, symlink and untracked refuse), and the wetone_changed_byte_changes_the_receipt_digest_by_real_execution.extdeps.git; extracting it is left to the namespace lane.rfc3339_utc_secondsis deleted; times now usegunbc.auth.approval_capabilityutc_instant_is_canonicalandutc_instant_before. Control:w_an_impossible_calendar_time_refuses_and_equal_instants_are_admitted(month 99, Feb 30 and hour 24 refuse; equal instants are admitted).gunbc.runner_host_grantsunit_hold_store_operationsnow also ensures/var/lib/gunbc/boot-attempts, with the same hosts, owner and mode as the unit-hold store, as a separate directory.provisioning/srv1/gunbc-ghrunner.sudoersgains exactly thatinstall -dline.plan.expected.dimm_figure_banks), labelled by connector number, on their bank's socket.ReceiptCpusNotTheExpectedSockets,ReceiptDimmUnknownSlot,ReceiptDimmOnWrongSocket,ReceiptDimmSlotOmitted.w_a_population_that_does_not_join_its_roster_refuses(all four REDs, plus a positive) andw_the_mt_collins_roster_is_the_guides_32_connectors_by_socket.Local evidence at this head (
claim_batch):🤖 Generated with Claude Code