Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions dag/gunbc/auth/privileged_effect_census.dag
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ fn accessor_grant(subject: NonEmptyStr) -> PrivilegedEffect {
data mtcollins1_operator_ruling: StandingDestructiveAuthorization = StandingDestructiveAuthorization {
ruling_text: "the fleet-converge mtcollins1_boot and host_reset_return modes, dispatched from gunb-ai/gunbc main under their workload identity, may attach media to, set the boot device of, power-cycle and capture the console of Mt. Collins unit 1 on every dispatch with no per-run or standing approval, provided each run first takes the unit's maintenance hold. Operator chat rulings 2026-09-23 ('we technically don't even need my approval - would you mind getting rid of that step or making WIF just trust this workflow') and 2026-09-25 ('no I don't want that - can you just remove that check asap')" as NonEmptyStr,
effect_subject: "mtcollins1 destructive controller effect: BMC virtual-media attach, one-shot boot-device set, power cycle and SOL capture" as NonEmptyStr,
interlock: decl_ref(module_path: "gunbc.machine_intake_mtcollins1_maintenance_hold", decl_name: "mtcollins1_unit_hold_key"),
interlock: decl_ref(module_path: "gunbc.managed_host_unit_hold", decl_name: "UnitHoldProof"),
}

data mtcollins1_operator_ruling_roots: List<DeclarationRef> = [
Expand Down Expand Up @@ -353,14 +353,14 @@ data privileged_effect_interlocks: List<PrivilegedEffectInterlock> = [
PrivilegedEffectInterlock {
site: site(module_path: "gunbc.machine_intake_mtcollins1_boot_run", decl_name: "mtcollins1_boot_wet"),
hold: mtcollins1_operator_ruling.interlock,
refusal: site(module_path: "gunbc.machine_intake_mtcollins1_maintenance_hold", decl_name: "unit_hold_acquire"),
refusal: site(module_path: "gunbc.managed_host_unit_hold", decl_name: "unit_hold_acquire"),
premise: "the boot takes the unit's one durable exclusive hold before any BMC write and refuses FileHoldOccupied naming the holder" as NonEmptyStr,
hold_owning_roots: mtcollins1_hold_owning_roots,
},
PrivilegedEffectInterlock {
site: site(module_path: "gunbc.host_reset_return_run", decl_name: "host_reset_return_wet"),
hold: mtcollins1_operator_ruling.interlock,
refusal: site(module_path: "gunbc.machine_intake_mtcollins1_maintenance_hold", decl_name: "unit_hold_acquire"),
refusal: site(module_path: "gunbc.managed_host_unit_hold", decl_name: "unit_hold_acquire"),
premise: "host reset-return takes the same unit hold before it sets the boot device or power-cycles the unit" as NonEmptyStr,
hold_owning_roots: mtcollins1_hold_owning_roots,
},
Expand Down
2 changes: 1 addition & 1 deletion dag/gunbc/durable_exclusive_hold_file_store.dag
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ fn file_hold_recovery_assess(
released_by: DurableHoldReleaseRef,
) -> FileHoldRecoveryAssessment
admit_callers: [
decl_ref(module_path: "gunbc.machine_intake_mtcollins1_maintenance_hold", decl_name: "unit_hold_recover_dead_holder"),
decl_ref(module_path: "gunbc.managed_host_unit_hold", decl_name: "unit_hold_recover_dead_holder"),
]
{
match durable_hold_recovery_assess(observed: observe_file_hold(root: root, key: slot_key), report: report) {
Expand Down
48 changes: 45 additions & 3 deletions dag/gunbc/fleet/fleet_converge_workflow.dag
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,11 @@ import gunbc.fleet_workflow_steps {
}
import gunbc.runner_spec_from_offer { gunbc_ci_runner_spec }
import gunbc.host_reset_return_run { host_reset_dispatch_mode_env }
import gunbc.machine_intake_mtcollins1_maintenance_hold { mtcollins1_unit_hold_store_host }
import gunbc.managed_host_unit_hold {
UnitHoldHostAdmission, UnitHoldHostAdmitted, UnitHoldHostNotManaged, UnitHoldHostNotRequired,
unit_hold_admission_for, unit_hold_host_admission_refusal,
}
import gunbc.fleet_intent_network { operator_host_mtcollins1 }
import gunbc.auth.gcp_iam_converge_federation { gcp_iam_converge_environment, gcp_iam_observe_wif_provider_resource }
import gunbc.auth.gcp_iam_converge { iam_observe_service_account, iam_converge_service_account, iam_converge_token_lifetime }
import gunbc.auth.gcp_iam_converge_run { gcp_iam_converge_step_timeout_minutes, iam_converge_token_env, gcp_iam_converge_approved_output }
Expand Down Expand Up @@ -3750,13 +3754,44 @@ fn fleet_converge_boot_federation_admitted() -> Bool {
// mtcollins1_boot_admit_executor) refuses such a dispatch with a typed reason before the WIF token
// or the fleet key exist; the wet entry re-runs the same fold.
data fleet_converge_mtcollins1_boot_job_if: String = fleet_converge_mtcollins1_boot_step_if
data fleet_converge_mtcollins1_boot_concurrency_group: String = join([fleet_host_mutation_group_prefix, mtcollins1_unit_hold_store_host as String], "")
data fleet_converge_mtcollins1_boot_concurrency_group: String = join([fleet_host_mutation_group_prefix, fleet_converge_mtcollins1_boot_store_host()], "")

// THE STORE HOST IS READ OFF THE UNIT'S MANAGED ROW, through the hold admission
// (gunbc.managed_host_unit_hold unit_hold_admission_for): the row's hold decision names where its
// store lives, and this job is pinned there. A unit that is not a managed host, or whose row declares
// no hold, has no store host, and there is no label to fall back to: the reading below carries the
// refusal in place of a host, and fleet_converge_yml_after_reset_assignment refuses to emit any yaml
// while it stands, so the refusal text never reaches a runs-on.
data fleet_converge_mtcollins1_boot_hold_admission: UnitHoldHostAdmission = unit_hold_admission_for(host: operator_host_mtcollins1 as NonEmptyStr)

fn fleet_converge_mtcollins1_boot_store_host_refusal() -> String? {
match unit_hold_host_admission_refusal(admission: fleet_converge_mtcollins1_boot_hold_admission) {
Absent => none
Present { value: r } =>
Present { value: join(["REFUSED gunbc.fleet_converge_workflow: the mtcollins1 boot job runs on its unit hold's store host, and ", r as String, ". No workflow yaml was emitted."], "") }
}
}

fn fleet_converge_mtcollins1_boot_store_host() -> String {
match fleet_converge_mtcollins1_boot_hold_admission {
UnitHoldHostAdmitted { subject: s } => s.store_host as String
UnitHoldHostNotManaged { host: _ } => fleet_converge_mtcollins1_boot_store_host_unread()
UnitHoldHostNotRequired { host: _ } => fleet_converge_mtcollins1_boot_store_host_unread()
}
}

fn fleet_converge_mtcollins1_boot_store_host_unread() -> String {
match fleet_converge_mtcollins1_boot_store_host_refusal() {
Present { value: r } => r
Absent => "REFUSED gunbc.fleet_converge_workflow: the mtcollins1 unit hold admission is unclassified. No workflow yaml was emitted."
}
}

// THE BOOT JOB'S RUNNER AND STEPS ARE THEIR OWN PRODUCERS for the same reason as the credential
// prelude below: which runner holds the credentials, and which bytes the steps fetch and execute, are
// facts a witness reads, and reading them here costs those parts rather than the whole job's.
fn fleet_converge_mtcollins1_boot_runner() -> RunnerSpec {
SelfHosted { labels: ["self-hosted", "linux", "arm64", mtcollins1_unit_hold_store_host as String] }
SelfHosted { labels: ["self-hosted", "linux", "arm64", fleet_converge_mtcollins1_boot_store_host()] }
}

// The two rendered credential scripts -- the fleet key step and the boot step -- are the job's
Expand Down Expand Up @@ -4564,6 +4599,13 @@ fn fleet_converge_yml_after_toolchain_admission() -> FleetConvergeYamlGeneration
}

fn fleet_converge_yml_after_reset_assignment() -> FleetConvergeYamlGenerationOutcome {
match fleet_converge_mtcollins1_boot_store_host_refusal() {
Present { value: r } => FleetConvergeYamlGenerationRefused { reason: r }
Absent => fleet_converge_yml_after_unit_hold_admission()
}
}

fn fleet_converge_yml_after_unit_hold_admission() -> FleetConvergeYamlGenerationOutcome {
if fleet_converge_build_job_capability_closure_holds() {
match checked_job_timeout_minutes(computed: gunbc_ci_fleet_job_backstop_timeout_minutes()) {
JobTimeoutExceedsCeiling { minutes: _, ceiling: _ } =>
Expand Down
20 changes: 20 additions & 0 deletions dag/gunbc/host/host_maintenance_hold_reason.dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module gunbc.host_maintenance_hold_reason

import std.types { NonEmptyStr }
import product.placement_supply { host_identity_eq }
import gunbc.fleet_intent_network { operator_host_mtcollins1 }
import gunbc.managed_host { ManagedHost }

// THE VARIABLE AN OPERATOR NAMES THEIR MAINTENANCE REASON THROUGH IS MAINTENANCE POLICY, not a fact
// about the host, so it lives beside the maintenance operation rather than on gunbc.managed_host
// ManagedHost, and this is its one authority: gunbc.managed_host_unit_hold admit_maintenance_hold_in
// reads it to admit a host to the operator's take and release. Mt. Collins unit 1's variable keeps
// the name operators already export. A host with no row here has no maintenance entry, however its
// hold is declared: the admission refuses it instead of deriving a variable name from its label.
fn maintenance_hold_reason_env(host: ManagedHost) -> NonEmptyStr? {
if host_identity_eq(a: host.host, b: operator_host_mtcollins1) {
Present { value: "GUNBC_MTCOLLINS1_MAINTENANCE_REASON" as NonEmptyStr }
} else {
none
}
}
29 changes: 14 additions & 15 deletions dag/gunbc/host/host_reset_return_run.dag
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module gunbc.host_reset_return_run

import gunbc.machine_intake_mtcollins1_maintenance_hold {
import gunbc.managed_host_unit_hold {
UnitHoldProof, UnitHeld, UnitHoldRefused, unit_hold_acquire, unit_hold_release, unit_hold_proof_refusal,
unit_hold_store_root, host_reset_owner,
host_reset_owner, admit_unit_hold_subject, UnitHoldSubjectAdmitted, UnitHoldNotRequired,
}
import std.decl_ref { decl_ref }
import std.types { Bool, EpochMs, Int, List, NonEmptyStr, String }
Expand Down Expand Up @@ -80,7 +80,6 @@ import gunbc.host_reset_return {
subject_for_observer,
}
import gunbc.host_reset_subject_roster { AdmittedResetSubject, reset_observer_peer_roster, reset_subject_for, reset_subject_roster }
import gunbc.managed_host { ResetNoUnitHold, ResetUnitHoldRequired }
import gunbc.machine_intake_oob_boot_handoff {
oob_boot_handoff,
BootControlIssued,
Expand Down Expand Up @@ -393,13 +392,13 @@ data host_reset_down_watch_samples: Nat = 120
data host_reset_back_watch_samples: Nat = 200

fn reset_unit_hold_gate(candidate: AdmittedResetSubject, proof: UnitHoldProof?) -> NonEmptyStr? {
match candidate.binding.host.unit_hold {
ResetNoUnitHold => none
ResetUnitHoldRequired { key: k, store_host: _ } =>
match admit_unit_hold_subject(host: candidate.binding.host) {
UnitHoldNotRequired { host: _ } => none
UnitHoldSubjectAdmitted { subject: _ } =>
match proof {
Absent => Present { value: join(["subject ", candidate.binding.host.host as String, " is hold-gated and no unit hold proof was presented; no controller write was attempted"], "") as NonEmptyStr }
Present { value: p } =>
match unit_hold_proof_refusal(proof: p, key: k, label: "host reset-return") {
match unit_hold_proof_refusal(proof: p, host: candidate.binding.host.host, label: "host reset-return") {
Present { value: why } => Present { value: why as NonEmptyStr }
Absent => none
}
Expand Down Expand Up @@ -478,7 +477,7 @@ fn host_reset_return_attempt(
}

// A HOLD-GATED SUBJECT IS NOT WRITTEN WITHOUT A PROOF FOR ITS SLOT, and the proof exists only if the
// entry's live acquire committed (gunbc.machine_intake_mtcollins1_maintenance_hold UnitHoldProof).
// entry's live acquire committed (gunbc.managed_host_unit_hold UnitHoldProof).
fn host_reset_return_issue_and_watch(
proof: UnitHoldProof?,
candidate: AdmittedResetSubject,
Expand Down Expand Up @@ -688,21 +687,21 @@ fn host_reset_return_wet() -> ProcessExit
remedy: "the assigned subject has no roster row carrying its controller evidence",
)
Present { value: candidate } =>
match candidate.binding.host.unit_hold {
ResetNoUnitHold =>
match admit_unit_hold_subject(host: candidate.binding.host) {
UnitHoldNotRequired { host: _ } =>
host_reset_return_attempt_exit(proof: none, candidate: candidate, observer: observer, subject: subject, route: route, password_file: password_file_raw as NonEmptyStr)
ResetUnitHoldRequired { key: k, store_host: h } =>
if (observer as String) != (h as String) {
UnitHoldSubjectAdmitted { subject: held } =>
if (observer as String) != (held.store_host as String) {
host_reset_return_preflight_exit(
refusal: UnitHoldStoreNotOnObserver { subject: subject, observer: observer, store_host: h },
refusal: UnitHoldStoreNotOnObserver { subject: subject, observer: observer, store_host: held.store_host },
remedy: "the subject's maintenance hold lives on the store host; a reset observed from anywhere else would take a hold in a store the operator never sees",
)
} else {
match unit_hold_acquire(root: unit_hold_store_root, key: k, owner: host_reset_owner(attempt: host_reset_return_attempt_label(observer: observer, subject: subject))) {
match unit_hold_acquire(subject: held, owner: host_reset_owner(attempt: host_reset_return_attempt_label(observer: observer, subject: subject))) {
UnitHoldRefused { reason: r } =>
host_reset_return_preflight_exit(
refusal: UnitHeldForMaintenance { subject: subject, reason: r },
remedy: "the unit is held (maintenance or another run); nothing was written to its controller",
remedy: "the unit hold was not acquired: it is held (maintenance or another run), or this runner was not observed on the hold store's host; nothing was written to its controller",
)
UnitHeld { proof: p } =>
unit_hold_release(
Expand Down
15 changes: 9 additions & 6 deletions dag/gunbc/host/managed_host.dag
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ import extdeps.bmc.endpoint { BmcControllerEndpoint }
import extdeps.bmc.access_profile { BmcAccessObservation, BmcAccessObservationStanding, BmcAccessObserved, BmcAccessUnobserved }
import extdeps.bmc.capability { BmcFirmwareReleaseCapabilityRow }
import gunbc.machine_intake_bmc_secure { BmcSecureRefusalCause, BmcSecureRefused, BmcSecureStanding, BmcSecured }
import gunbc.fleet_intent_network { operator_host_mtcollins1 }
import gunbc.fleet_intent_network { operator_host_mtcollins1, operator_host_srv1 }
import gunbc.machine_intake_mtcollins1_access_observation { mtcollins1_access_standing, mtcollins1_capability_row }
import gunbc.machine_intake_mtcollins1_bmc_secure_observation { mtcollins1_bmc_secure_standing }
import gunbc.machine_intake_mtcollins1_maintenance_hold { mtcollins1_unit_hold_key, mtcollins1_unit_hold_store_host }

// A HOST THIS FLEET DRIVES THROUGH ITS BASEBOARD CONTROLLER, and the one record a host-generic
// procedure takes instead of a unit's constants (docs/plans/managed-host-untangle.md, cut 0). It is
Expand All @@ -37,9 +36,10 @@ type ManagedHost sole_constructor {
}

// WHETHER DRIVING THIS HOST MUST TAKE ITS MAINTENANCE HOLD FIRST, and on which host the hold store
// lives. Mt. Collins unit 1 is serviced by hand (gunbc.machine_intake_mtcollins1_maintenance_hold),
// so a power cycle of it is gated by the same slot the boot takes; a write may only run where that
// store is, or it would acquire a hold in an empty store the operator never sees.
// lives. Mt. Collins unit 1 is serviced by hand, so a power cycle of it is gated by the same slot the
// boot takes; a write may only run where that store is, or it would acquire a hold in an empty store
// the operator never sees. This decision is the row's; gunbc.managed_host_unit_hold admits a host to
// the hold procedure from it and is the only reader that turns it into a slot.
type ResetUnitHold
= ResetUnitHoldRequired { key: NonEmptyStr, store_host: NonEmptyStr }
| ResetNoUnitHold
Expand Down Expand Up @@ -93,7 +93,7 @@ fn managed_hosts() -> List<ManagedHost> {
access: mtcollins1_access_standing,
capability_row: mtcollins1_capability_row,
secure: secure,
unit_hold: ResetUnitHoldRequired { key: mtcollins1_unit_hold_key, store_host: mtcollins1_unit_hold_store_host },
unit_hold: ResetUnitHoldRequired { key: operator_host_mtcollins1 as NonEmptyStr, store_host: operator_host_srv1 as NonEmptyStr },
},
]
}
Expand All @@ -113,6 +113,9 @@ fn managed_host_fixture(
admit_callers: [
decl_ref(module_path: "test.claim.managed_host_witness", decl_name: "w_a_secured_account_beside_another_controllers_access_is_not_bound_or_rostered"),
decl_ref(module_path: "test.claim.managed_host_witness", decl_name: "w_a_capable_host_with_no_reset_selection_is_absent_from_the_roster"),
decl_ref(module_path: "test.claim.managed_host_unit_hold_witness", decl_name: "w_a_managed_host_with_no_unit_hold_is_refused_and_has_no_store"),
decl_ref(module_path: "test.claim.managed_host_unit_hold_witness", decl_name: "w_a_held_host_with_no_reason_variable_is_refused_for_maintenance"),
decl_ref(module_path: "test.claim.managed_host_unit_hold_witness", decl_name: "w_a_store_host_with_no_allocated_hostname_refuses_the_executor"),
decl_ref(module_path: "test.claim.managed_host_boot_federation_witness", decl_name: "w_a_bound_host_with_no_boot_policy_is_unprovisioned"),
]
{
Expand Down
Loading