diff --git a/dag/gunbc/deployment_risk.dag b/dag/gunbc/deployment_risk.dag index b04312f36ec..1595054da79 100644 --- a/dag/gunbc/deployment_risk.dag +++ b/dag/gunbc/deployment_risk.dag @@ -25,8 +25,8 @@ import gunbc.auth.github_apps { DeclaredGitHubApp, gunbai_bot_declared } // // Machine qualification is a different subject. It shares DeploymentRiskClass only as what a // machine ADMITS (MachineQualificationPolicy { admits }); a machine is never itself ProdRisk and -// never confers the prod role. That replacement of gunbc.machine_intake_phase -// MachineOperatingEnvironment lands in its own one-motion migration on top of this module. +// never confers the prod role. The policy's home is gunbc.machine_intake_phase, which consumes +// this class and coins no machine-level environment vocabulary of its own. // ── The subject ───────────────────────────────────────────────────────────────────────────────── // THE STABLE IDENTITY OF ONE Deployment -- the convergence subject whose desired state, mutable diff --git a/dag/gunbc/machine_intake/bmc_secure.dag b/dag/gunbc/machine_intake/bmc_secure.dag index e18277ede98..04eb56100dc 100644 --- a/dag/gunbc/machine_intake/bmc_secure.dag +++ b/dag/gunbc/machine_intake/bmc_secure.dag @@ -74,19 +74,17 @@ import extdeps.network.ipv4 { Ipv4Address } import extdeps.cloud.gcp.secret_ref { SecretRef, HashPending, gcp_resource_names_this_secret } import gunbc.auth.github_gcp_federation { fleet_secrets_project_number } import gunbc.machine_intake_subject { MachineIntakeSubject } +import gunbc.deployment_risk { DeploymentRiskClass, ProdRisk, TestRisk } import gunbc.machine_intake_phase { Arrival, BmcAccess, BmcSecure, - DevelopmentAndTest, IntakePhase, - IntakeQualificationPolicy, - MachineOperatingEnvironment, + MachineQualificationPolicy, OperatorAction, - Production, QualificationPolicy, QualificationRefusalOwner, - intake_qualification_policy_digest, + machine_qualification_policy_digest, } import gunbc.machine_intake_receipt { EvidenceRef, @@ -223,7 +221,7 @@ type RetainedCredentialKind // (PublishedCredentialAccountPrecedesRotation); a pre-rotation user-list cannot // label the post-rotation standing. // RetainedByPolicy is a third arm: it satisfies the BMC-secure conjunct -// ONLY under DevelopmentAndTest. Production refuses it; that refusal is the +// ONLY under a TestRisk-admitting policy. A ProdRisk-admitting policy refuses it; that refusal is the // discriminating control for the day a machine is promoted. type PublishedCredentialAccountObservation = PublishedCredentialRetired { evidence: EvidenceRef, observed_at: EpochMs } @@ -337,7 +335,7 @@ type BmcSecureRefusalCause | PublishedCredentialCensusMemberUnprobed { channel: IpmiChannelNumber } | PublishedCredentialProbeDuplicate { channel: IpmiChannelNumber } | PublishedCredentialProbeUnmatched { channel: IpmiChannelNumber } - | PublishedCredentialRetainedNotAdmittedForProduction + | PublishedCredentialRetainedNotAdmittedByProdRiskPolicy | PublishedCredentialNotRetained { channel: IpmiChannelNumber } // NO ARM RETURNS UnitHardware, AND THAT IS THE POINT (design §6: a non-working @@ -373,7 +371,7 @@ fn bmc_secure_refusal_owner(cause: BmcSecureRefusalCause) -> QualificationRefusa PublishedCredentialCensusMemberUnprobed { channel: _ } => BmcAccess PublishedCredentialProbeDuplicate { channel: _ } => QualificationPolicy PublishedCredentialProbeUnmatched { channel: _ } => QualificationPolicy - PublishedCredentialRetainedNotAdmittedForProduction => QualificationPolicy + PublishedCredentialRetainedNotAdmittedByProdRiskPolicy => QualificationPolicy PublishedCredentialNotRetained { channel: _ } => OperatorAction } } @@ -403,7 +401,7 @@ fn bmc_secure_refusal_label(cause: BmcSecureRefusalCause) -> NonEmptyStr { PublishedCredentialCensusMemberUnprobed { channel: _ } => "a census LAN member has no matching published-credential probe" PublishedCredentialProbeDuplicate { channel: _ } => "more than one published-credential probe matched the same census LAN member" PublishedCredentialProbeUnmatched { channel: _ } => "a published-credential probe matched no census LAN member" - PublishedCredentialRetainedNotAdmittedForProduction => "a retained BMC backdoor is not admitted for production; a fresh census pass that retires or disables the published credential is required" + PublishedCredentialRetainedNotAdmittedByProdRiskPolicy => "a retained BMC backdoor is not admitted under a ProdRisk-admitting qualification policy; a fresh census pass that retires or disables the published credential is required" PublishedCredentialNotRetained { channel: _ } => "the declared factory-credential retention was not observed on a census LAN member" } } @@ -439,13 +437,13 @@ type BmcSecureOutcome // outcome alone and had bmc_secure_phase_receipt take the subject as a // separate argument, so a standing legitimately derived for host A could be // handed to the receipt function beside host B's subject. The same hatch -// existed for policy: derive wrote DevelopmentAndTest and the receipt took a -// second Production policy, restamping the digest. The receipt now reads +// existed for policy: derive wrote a TestRisk-admitting policy and the receipt took a +// second ProdRisk-admitting policy, restamping the digest. The receipt now reads // subject and policy off the standing; there is no parameter left to swap. -// Promotion is a fresh derive_bmc_secure under Production, which refuses -// RetainedByPolicy. There is no observed environment on the unit; the policy +// Promotion is a fresh derive_bmc_secure under a ProdRisk-admitting policy, which refuses +// RetainedByPolicy. There is no observed risk class on the unit, and a machine never holds one; the policy // is the declared qualification (FleetAdmissionReceipt already binds its -// digest). A caller can still declare DevelopmentAndTest — that is the policy +// digest). A caller can still declare admits: TestRisk — that is the policy // fact, not a second knob beside the receipt. // // So the subject and the outcome are ONE record and the record is @@ -475,7 +473,7 @@ type BmcSecureOutcome // the construction and not the type's usability. type BmcSecureStanding sole_constructor { subject: MachineIntakeSubject - policy: IntakeQualificationPolicy + policy: MachineQualificationPolicy outcome: BmcSecureOutcome } @@ -940,7 +938,7 @@ fn adjudicate_published_census( probes: List, published_user: IpmiUserId, published_account: PublishedCredentialAccountObservation, - environment: MachineOperatingEnvironment, + admits: DeploymentRiskClass, ) -> BmcSecureOutcome { let account_at = published_account_observed_at(account: published_account) if account_at < applied_at { @@ -950,9 +948,9 @@ fn adjudicate_published_census( } else { match published_account { PublishedCredentialRetainedByPolicy { retention: _, evidence: _, observed_at: _ } => - match environment { - Production => BmcSecureRefused { cause: PublishedCredentialRetainedNotAdmittedForProduction } - DevelopmentAndTest => + match admits { + ProdRisk => BmcSecureRefused { cause: PublishedCredentialRetainedNotAdmittedByProdRiskPolicy } + TestRisk => adjudicate_published_census_join( endpoint: endpoint, reference: reference, @@ -1011,7 +1009,7 @@ fn adjudicate_managed_probe( applied_at: EpochMs, rotation_evidence: EvidenceRef, managed: CredentialProbeOutcome, - environment: MachineOperatingEnvironment, + admits: DeploymentRiskClass, ) -> BmcSecureOutcome { match managed { CredentialRejected { evidence: _, observed_at: _ } => BmcSecureRefused { cause: ManagedCredentialRejected } @@ -1033,7 +1031,7 @@ fn adjudicate_managed_probe( probes: observation.published_probes, published_user: observation.published_user, published_account: observation.published_account, - environment: environment, + admits: admits, ) } } @@ -1044,7 +1042,7 @@ fn adjudicate_rotation( reference: ManagedCredentialReference, applied_at: EpochMs, rotation_evidence: EvidenceRef, - environment: MachineOperatingEnvironment, + admits: DeploymentRiskClass, ) -> BmcSecureOutcome { if (reference.credential_epoch <= observation.previous_credential_epoch) { BmcSecureRefused { @@ -1060,14 +1058,14 @@ fn adjudicate_rotation( applied_at: applied_at, rotation_evidence: rotation_evidence, managed: observation.managed_probe, - environment: environment, + admits: admits, ) } } fn adjudicate_bootstrap( observation: BmcCredentialRotationObservation, - environment: MachineOperatingEnvironment, + admits: DeploymentRiskClass, ) -> BmcSecureOutcome { match observation.bootstrap_probe { CredentialProbeUnestablished { detail: d } => BmcSecureRefused { cause: CredentialStateUnknown { detail: d } } @@ -1082,7 +1080,7 @@ fn adjudicate_bootstrap( reference: r, applied_at: at, rotation_evidence: e, - environment: environment, + admits: admits, ) } } @@ -1091,12 +1089,12 @@ fn adjudicate_bootstrap( fn derive_bmc_secure( subject: MachineIntakeSubject, observation: BmcCredentialRotationObservation, - policy: IntakeQualificationPolicy, + policy: MachineQualificationPolicy, ) -> BmcSecureStanding { let outcome = if same_intake_subject(left: subject, right: observation.subject) == false { BmcSecureRefused { cause: RotationObservationForOtherAttempt { expected: subject, found: observation.subject } } } else { - adjudicate_bootstrap(observation: observation, environment: policy.environment) + adjudicate_bootstrap(observation: observation, admits: policy.admits) } BmcSecureStanding { subject: subject, policy: policy, outcome: outcome } } @@ -1130,7 +1128,7 @@ fn bmc_secure_phase_receipt( subject: standing.subject, phase: bmc_secure_phase, producer: producer, - policy_digest: intake_qualification_policy_digest(policy: standing.policy), + policy_digest: machine_qualification_policy_digest(policy: standing.policy), started_at: started_at, ended_at: ended_at, observations: bmc_secure_receipt_observations(standing: standing), @@ -1319,7 +1317,7 @@ fn managed_reference_same(a: ManagedCredentialReference, b: ManagedCredentialRef type BmcSecureGoal { managed: ManagedCredentialReference published_break_glass: ManagedCredentialReference - policy: IntakeQualificationPolicy + policy: MachineQualificationPolicy } // WHAT AN ASSESSMENT FOUND. The conjunction's own causes, plus the published account's replacement: @@ -1361,7 +1359,7 @@ type BmcSecureStateRefusal // published one still accepted or still holding channel access). An unknown is a reading that did not // establish a state; it is never a deviation, because writing on an unread controller is the // absorbing fallback DESIGN §5 forbids. Everything else -- an ordering, a duplicated or unmatched -// reading, a production retention -- refuses the assessment. +// reading, a retention under a ProdRisk-admitting policy -- refuses the assessment. // THE ONLY ADMITTED ACTION IS A PASSWORD WRITE (IpmiSetUserPassword over a grounded route). It cannot // give a LAN channel an address, set a channel's privilege to NO ACCESS, or put the factory password // back, so a deviation only those operations change is not remediable here: it refuses with the @@ -1402,7 +1400,7 @@ fn conjunction_cause_class(cause: BmcSecureRefusalCause) -> ConjunctionCauseClas PublishedCredentialCensusMemberUnprobed { channel: _ } => CauseUnestablishedReading PublishedCredentialProbeDuplicate { channel: _ } => CauseRefusesAssessment PublishedCredentialProbeUnmatched { channel: _ } => CauseRefusesAssessment - PublishedCredentialRetainedNotAdmittedForProduction => CauseRefusesAssessment + PublishedCredentialRetainedNotAdmittedByProdRiskPolicy => CauseRefusesAssessment PublishedCredentialNotRetained { channel: _ } => CauseNeedsUngroundedOperation { operation: PublishedFactoryCredentialRestore } } } @@ -1431,7 +1429,7 @@ fn conjunction_over_observation(goal: BmcSecureGoal, observed: BmcAccountStateOb probes: map(observed.published_probes, p => PublishedCredentialLanProbe { channel: p.channel, address: p.address, outcome: observed_probe_at_join(probe: p.probe) }), published_user: observed.published_user, published_account: observed_published_account_at_join(account: observed.published_account), - environment: goal.policy.environment, + admits: goal.policy.admits, ) } } diff --git a/dag/gunbc/machine_intake/disposition.dag b/dag/gunbc/machine_intake/disposition.dag index 2673bfb063b..388baf05c65 100644 --- a/dag/gunbc/machine_intake/disposition.dag +++ b/dag/gunbc/machine_intake/disposition.dag @@ -10,22 +10,20 @@ import gunbc.machine_intake_subject { merge_subject_currency, compare_qualification_subject, } +import gunbc.deployment_risk { DeploymentRiskClass, ProdRisk, TestRisk } import gunbc.machine_intake_phase { ArrivalHardwareQualification, - DevelopmentAndTest, IntakePhase, - IntakeQualificationPolicy, + MachineQualificationPolicy, IntakeTransaction, - MachineOperatingEnvironment, OsProvisioning, PlacementCommissioning, - Production, QualificationRefusalOwner, UnitHardware, fleet_admission_required_phases, intake_phase_transaction, - intake_qualification_policy, - intake_qualification_policy_digest, + machine_qualification_policy, + machine_qualification_policy_digest, } import gunbc.machine_intake_receipt { EvidenceRef, @@ -125,7 +123,7 @@ type FleetAdmissionReceipt { subject: QualificationSubject ledger_terminal_fingerprint: ContentHash policy_digest: ContentHash - environment: MachineOperatingEnvironment + admits: DeploymentRiskClass satisfied: List derived_at: EpochMs } @@ -138,7 +136,7 @@ type FleetAdmissionStanding subject_mismatched: List subject_currency: SubjectCurrency } - | FleetPolicyBarMismatch { ledger_digest: ContentHash, bar: IntakeQualificationPolicy } + | FleetPolicyBarMismatch { ledger_digest: ContentHash, bar: MachineQualificationPolicy } type AdmissionFold { satisfied: List @@ -163,10 +161,10 @@ type AdmissionFold { fn derive_fleet_admission( observed_subject: QualificationSubject, ledger: ValidatedIntakeReceiptLedger, - policy: IntakeQualificationPolicy, + policy: MachineQualificationPolicy, now: EpochMs, ) -> FleetAdmissionStanding { - if content_hash_equal(left: ledger.policy_digest, right: intake_qualification_policy_digest(policy: policy)) == false { + if content_hash_equal(left: ledger.policy_digest, right: machine_qualification_policy_digest(policy: policy)) == false { FleetPolicyBarMismatch { ledger_digest: ledger.policy_digest, bar: policy } } else { let receipts = ledger.ordered_receipts @@ -194,7 +192,7 @@ fn derive_fleet_admission( subject: observed_subject, ledger_terminal_fingerprint: ledger.terminal_fingerprint, policy_digest: ledger.policy_digest, - environment: policy.environment, + admits: policy.admits, satisfied: folded.satisfied, derived_at: now, }, @@ -225,7 +223,7 @@ type PartsHoldReason | PhaseNotYetObserved { phase: IntakePhase } | PlacementQualificationPending | QualificationSubjectStale { currency: SubjectCurrency } - | QualificationPolicyBarMismatch { ledger_digest: ContentHash, bar: IntakeQualificationPolicy } + | QualificationPolicyBarMismatch { ledger_digest: ContentHash, bar: MachineQualificationPolicy } type MachineIntakeDisposition = Admitted { admission_receipt: FleetAdmissionReceipt } @@ -262,34 +260,34 @@ fn first_phase(phases: List) -> IntakePhase? { // defect inside an open return window is the only path to ReturnWindow; every // other owner's refusal, every unobserved phase and every stale subject is a // PartsHold with its reason — loud, and never a false hardware diagnosis. -fn environment_of_qualification_digest(digest: ContentHash) -> MachineOperatingEnvironment? { - if content_hash_equal(left: digest, right: intake_qualification_policy_digest(policy: intake_qualification_policy(environment: DevelopmentAndTest))) { - Present { value: DevelopmentAndTest } - } else if content_hash_equal(left: digest, right: intake_qualification_policy_digest(policy: intake_qualification_policy(environment: Production))) { - Present { value: Production } +fn admitted_class_of_qualification_digest(digest: ContentHash) -> DeploymentRiskClass? { + if content_hash_equal(left: digest, right: machine_qualification_policy_digest(policy: machine_qualification_policy(admits: TestRisk))) { + Present { value: TestRisk } + } else if content_hash_equal(left: digest, right: machine_qualification_policy_digest(policy: machine_qualification_policy(admits: ProdRisk))) { + Present { value: ProdRisk } } else { none } } -fn policy_bar_mismatch_cause(ledger_digest: ContentHash, bar: IntakeQualificationPolicy) -> NonEmptyStr { - match environment_of_qualification_digest(digest: ledger_digest) { +fn policy_bar_mismatch_cause(ledger_digest: ContentHash, bar: MachineQualificationPolicy) -> NonEmptyStr { + match admitted_class_of_qualification_digest(digest: ledger_digest) { Absent => - match bar.environment { - Production => "ledger policy digest does not match the Production admission bar" - DevelopmentAndTest => "ledger policy digest does not match the DevelopmentAndTest admission bar" + match bar.admits { + ProdRisk => "ledger policy digest does not match the ProdRisk-admitting admission bar" + TestRisk => "ledger policy digest does not match the TestRisk-admitting admission bar" } Present { value: ledger_env } => match ledger_env { - DevelopmentAndTest => - match bar.environment { - Production => "ledger was derived under DevelopmentAndTest; the admission bar is Production" - DevelopmentAndTest => "ledger policy digest does not match the DevelopmentAndTest admission bar" + TestRisk => + match bar.admits { + ProdRisk => "ledger was derived under a TestRisk-admitting policy; the admission bar admits ProdRisk" + TestRisk => "ledger policy digest does not match the TestRisk-admitting admission bar" } - Production => - match bar.environment { - DevelopmentAndTest => "ledger was derived under Production; the admission bar is DevelopmentAndTest" - Production => "ledger policy digest does not match the Production admission bar" + ProdRisk => + match bar.admits { + TestRisk => "ledger was derived under a ProdRisk-admitting policy; the admission bar admits TestRisk" + ProdRisk => "ledger policy digest does not match the ProdRisk-admitting admission bar" } } } diff --git a/dag/gunbc/machine_intake/mtcollins1_bmc_secure_observation.dag b/dag/gunbc/machine_intake/mtcollins1_bmc_secure_observation.dag index 7f238c93b21..7c31d35ce7b 100644 --- a/dag/gunbc/machine_intake/mtcollins1_bmc_secure_observation.dag +++ b/dag/gunbc/machine_intake/mtcollins1_bmc_secure_observation.dag @@ -43,7 +43,8 @@ import gunbc.machine_intake_subject { qualification_subject_of, } import gunbc.machine_intake_receipt { EvidenceRef } -import gunbc.machine_intake_phase { DevelopmentAndTest, intake_qualification_policy } +import gunbc.deployment_risk { TestRisk } +import gunbc.machine_intake_phase { machine_qualification_policy } import gunbc.machine_intake_bmc_secure { BmcCredentialRotationObservation, BmcSecured, @@ -243,7 +244,7 @@ fn mtcollins1_bmc_secure_standing() -> BmcSecureStanding? { value: derive_bmc_secure( subject: subject, observation: observation, - policy: intake_qualification_policy(environment: DevelopmentAndTest), + policy: machine_qualification_policy(admits: TestRisk), ), } } diff --git a/dag/gunbc/machine_intake/phase.dag b/dag/gunbc/machine_intake/phase.dag index 7fe5592ef2f..8a2cc129b39 100644 --- a/dag/gunbc/machine_intake/phase.dag +++ b/dag/gunbc/machine_intake/phase.dag @@ -2,6 +2,7 @@ module gunbc.machine_intake_phase import std.types { Int, List, NonEmptyStr, String } import std.content_hash { ContentHash, content_hash_of_value } +import gunbc.deployment_risk { DeploymentRiskClass, ProdRisk, TestRisk } // THREE TRANSACTIONS, ONE LIFECYCLE (machine-intake ruling 2026-08-29 §4). Hardware // qualification, OS provisioning and placement commissioning are separate transactions: they @@ -197,37 +198,31 @@ fn refusal_owner_label(owner: QualificationRefusalOwner) -> NonEmptyStr { } } -// A MACHINE'S OPERATING ENVIRONMENT IS A DECLARED QUALIFICATION-POLICY -// PROPERTY, not a dashboard instance field and not a host toolchain list. -// gunbc.roadmap_dashboard_instance distinguishes a lab from a production -// slot (DispatchStateScope: instance-owned vs host-shared) and holds -// production to a stricter bar; that SEMANTIC is reused here. The carrier is -// not: DispatchHostEnvironment is a list of capability realizations (git, -// cargo, tmux), and HostRole is PrimaryInstrument | CompanionInput. No -// machine-level DevelopmentAndTest | Production type existed; minting it on -// the policy FleetAdmissionReceipt already binds is the home, so BMC-secure -// adjudication and promotion consult the same digest the ledger already -// carries. Not std: this is a product qualification fact, not a universal -// framework. -type MachineOperatingEnvironment - = DevelopmentAndTest - | Production - -type IntakeQualificationPolicy { - environment: MachineOperatingEnvironment +// A MACHINE QUALIFICATION POLICY NAMES THE DEPLOYMENT RISK CLASS A MACHINE +// ADMITS (operator ruling 2026-10-03, docs/plans/deployment-risk-conformance.md). +// The class vocabulary is gunbc.deployment_risk DeploymentRiskClass and is not +// re-coined here. A machine is never itself ProdRisk: it only admits a class, +// it never confers the prod role (gunbc.deployment_risk ProdRoleSelection is +// the sole switch), and a machine qualified to admit ProdRisk may host a +// TestRisk deployment. The policy is the bar FleetAdmissionReceipt already +// binds by digest, so BMC-secure adjudication and promotion consult the same +// digest the ledger carries. Not std: this is a product qualification fact, +// not a universal framework. +type MachineQualificationPolicy { + admits: DeploymentRiskClass } -fn intake_qualification_policy(environment: MachineOperatingEnvironment) -> IntakeQualificationPolicy { - IntakeQualificationPolicy { environment: environment } +fn machine_qualification_policy(admits: DeploymentRiskClass) -> MachineQualificationPolicy { + MachineQualificationPolicy { admits: admits } } -fn production_qualification_policy() -> IntakeQualificationPolicy { - intake_qualification_policy(environment: Production) +fn prod_risk_admitting_qualification_policy() -> MachineQualificationPolicy { + machine_qualification_policy(admits: ProdRisk) } -fn intake_qualification_policy_digest(policy: IntakeQualificationPolicy) -> ContentHash { - match policy.environment { - DevelopmentAndTest => content_hash_of_value(value: "intake-qualification-policy:development-and-test") - Production => content_hash_of_value(value: "intake-qualification-policy:production") +fn machine_qualification_policy_digest(policy: MachineQualificationPolicy) -> ContentHash { + match policy.admits { + TestRisk => content_hash_of_value(value: "machine-qualification-policy:admits-test-risk") + ProdRisk => content_hash_of_value(value: "machine-qualification-policy:admits-prod-risk") } } diff --git a/dag/test/claim/fleet/mtcollins_firmware_converge_witness_test.dag b/dag/test/claim/fleet/mtcollins_firmware_converge_witness_test.dag index f3d2d4ffd4a..56384fd2dfd 100644 --- a/dag/test/claim/fleet/mtcollins_firmware_converge_witness_test.dag +++ b/dag/test/claim/fleet/mtcollins_firmware_converge_witness_test.dag @@ -24,7 +24,8 @@ import gunbc.machine_intake_bmc_secure { BmcSecureStanding, ManagedCredentialReference, CredentialAccepted, CredentialRejected, CredentialProbeOutcome, RotationApplied, PublishedCredentialRetired, observed_bmc_credential_rotation, derive_bmc_secure, } -import gunbc.machine_intake_phase { DevelopmentAndTest, intake_qualification_policy } +import gunbc.deployment_risk { TestRisk } +import gunbc.machine_intake_phase { machine_qualification_policy } import gunbc.machine_intake_mtcollins1_power_on_account { PowerOnAccount, PowerOnEvidence, AttemptConfigurationReceipt, ConsoleRead, HandoffPoweredOn, SelEventsRead, WatchdogUnobserved, ExpectedSockets, StimulusRequestNotRecorded, StimulusApplicationNotRecorded, PopulationNotRecorded, FirmwareReadbacksNotRecorded, @@ -281,7 +282,7 @@ fn reestablished(epoch: Int, previous: Int, managed_accepted: Bool) -> BmcSecure published_user: mtcollins1_published_user, published_account: PublishedCredentialRetired { evidence: mtcollins1_user2_retire_evidence, observed_at: mtcollins1_user2_retire_at }, ), - policy: intake_qualification_policy(environment: DevelopmentAndTest), + policy: machine_qualification_policy(admits: TestRisk), ) } } } diff --git a/dag/test/claim/machine_intake/machine_intake_bmc_secure_state_witness_test.dag b/dag/test/claim/machine_intake/machine_intake_bmc_secure_state_witness_test.dag index b63302194a6..8fae6df35ac 100644 --- a/dag/test/claim/machine_intake/machine_intake_bmc_secure_state_witness_test.dag +++ b/dag/test/claim/machine_intake/machine_intake_bmc_secure_state_witness_test.dag @@ -39,7 +39,8 @@ import gunbc.machine_intake_subject { UnitKey, qualification_subject_of, } -import gunbc.machine_intake_phase { DevelopmentAndTest, intake_qualification_policy } +import gunbc.deployment_risk { ProdRisk, TestRisk } +import gunbc.machine_intake_phase { machine_qualification_policy } import gunbc.machine_intake_receipt { EvidenceRef } import std.scoped_authorization { AttemptIdentity } import extdeps.crypto.mac { MacKeyId } @@ -85,6 +86,7 @@ import gunbc.machine_intake_bmc_secure { AccountStateForOtherManagedReference, AccountReadingPrecedesReadFloor, AccountStateConjunctionRefused, + PublishedCredentialRetainedNotAdmittedByProdRiskPolicy, ChannelAccessWrite, PublishedFactoryCredentialRestore, BmcAccountPlanOutcome, @@ -248,7 +250,7 @@ fn goal_break_glass_ref() -> ManagedCredentialReference { } fn goal_v1() -> BmcSecureGoal { - BmcSecureGoal { managed: managed_ref(version: "1", epoch: 1), published_break_glass: goal_break_glass_ref(), policy: intake_qualification_policy(environment: DevelopmentAndTest) } + BmcSecureGoal { managed: managed_ref(version: "1", epoch: 1), published_break_glass: goal_break_glass_ref(), policy: machine_qualification_policy(admits: TestRisk) } } // ── the observer clock of these claims ────────────────────────────────────────────────────────── @@ -725,7 +727,7 @@ test fn the_post_read_is_independent_of_the_reading_that_diverged() -> Bool { test fn the_plan_refuses_a_stale_or_foreign_generation_and_a_foreign_reading() -> Bool { let reading = read(w: world_factory_open(), subject: lab_subject(), managed: managed_ref(version: "1", epoch: 1), managed_password: managed_password_v1, closed: true, at: t_read) let foreign = read(w: world_factory_open(), subject: other_subject(), managed: managed_ref(version: "1", epoch: 1), managed_password: managed_password_v1, closed: true, at: t_read) - let stale_goal = BmcSecureGoal { managed: managed_ref(version: "2", epoch: 2), published_break_glass: goal_break_glass_ref(), policy: intake_qualification_policy(environment: DevelopmentAndTest) } + let stale_goal = BmcSecureGoal { managed: managed_ref(version: "2", epoch: 2), published_break_glass: goal_break_glass_ref(), policy: machine_qualification_policy(admits: TestRisk) } plan_label(o: plan_with(reading: reading, goal: goal_v1(), managed_route: grounded_route(user: managed_user), managed_generation_value: break_glass_generation(), planned_at: t_plan)) == "generation-another-account" && plan_label(o: plan_with(reading: read(w: world_factory_open(), subject: lab_subject(), managed: managed_ref(version: "2", epoch: 2), managed_password: managed_password_v1, closed: true, at: t_read), goal: stale_goal, managed_route: grounded_route(user: managed_user), managed_generation_value: managed_generation(version: "2", fetch_at: t_fetch), planned_at: t_plan)) == "generation-not-advanced" && plan_label(o: plan_default(reading: foreign)) == "unobserved" @@ -818,6 +820,48 @@ test fn a_retained_credential_must_be_the_goal_bound_generation() -> Bool { && assessment_on(reading: secured_read(census_value: closed, account: PublishedAccountRetainedByPolicy { retention: PublishedFactoryCredential })) == "needs:factory-credential-restore/refuse" } +// The same retention decision on the goal path, one control per arm. The goal binds a break-glass +// generation, so the goal-bound retained account satisfies a TestRisk-admitting goal (above) and the +// SAME reading refuses a ProdRisk-admitting goal by the policy cause, not by the generation; a retired +// published account satisfies the ProdRisk-admitting goal. +fn goal_admitting_prod_risk() -> BmcSecureGoal { + BmcSecureGoal { managed: managed_ref(version: "1", epoch: 1), published_break_glass: goal_break_glass_ref(), policy: machine_qualification_policy(admits: ProdRisk) } +} + +fn prod_risk_goal_refuses_retention(reading: BmcAccountStateObservation) -> Bool { + match inspect_bmc_secure_state(subject: lab_subject(), goal: goal_admitting_prod_risk(), reading: reading) { + BmcSecureStateUnobserved { cause: _ } => false + BmcSecureStateInspected { observed: _, assessment: a, decision: d } => + match a { + GoalAssessmentRefused { cause: c } => + match c { + AccountStateConjunctionRefused { cause: inner } => inner == PublishedCredentialRetainedNotAdmittedByProdRiskPolicy && decision_label(d: d) == "refuse" + _ => false + } + _ => false + } + } +} + +fn prod_risk_goal_is_satisfied(reading: BmcAccountStateObservation) -> Bool { + match inspect_bmc_secure_state(subject: lab_subject(), goal: goal_admitting_prod_risk(), reading: reading) { + BmcSecureStateUnobserved { cause: _ } => false + BmcSecureStateInspected { observed: _, assessment: a, decision: d } => + match a { + GoalSatisfied { evidence: _ } => decision_label(d: d) == "noop" + _ => false + } + } +} + +test fn a_goal_bound_retention_refuses_a_prod_risk_admitting_goal_and_a_closed_account_satisfies_it() -> Bool { + let closed = census_with(serial_privilege_open: false, unaddressed_lan_open: false) + let retained = PublishedAccountRetainedByPolicy { retention: BreakGlassAccount { secret: goal_break_glass_ref().secret } } + assessment_on(reading: secured_read(census_value: closed, account: retained)) == "satisfied/noop" + && prod_risk_goal_refuses_retention(reading: secured_read(census_value: closed, account: retained)) + && prod_risk_goal_is_satisfied(reading: secured_read(census_value: closed, account: PublishedAccountRetired)) +} + // ── the published replacement is observed, not only planned (side-chat review of 86b27f3) ───────── // Factory rejected beside an ARBITRARY replacement on the published account, channels closed: the diff --git a/dag/test/claim/machine_intake/machine_intake_bmc_secure_witness_test.dag b/dag/test/claim/machine_intake/machine_intake_bmc_secure_witness_test.dag index 57bb9ba8741..b0881d3d937 100644 --- a/dag/test/claim/machine_intake/machine_intake_bmc_secure_witness_test.dag +++ b/dag/test/claim/machine_intake/machine_intake_bmc_secure_witness_test.dag @@ -35,23 +35,22 @@ import gunbc.machine_intake_subject { UnitKey, qualification_subject_of, } +import gunbc.deployment_risk { ProdRisk, TestRisk } import gunbc.machine_intake_phase { Arrival, BmcAccess, BmcSecure, - DevelopmentAndTest, IntakePhase, - IntakeQualificationPolicy, + MachineQualificationPolicy, OperatorAction, - Production, QualificationPolicy, QualificationRefusalOwner, StagingInfrastructure, UnitHardware, fleet_admission_required_phases, - intake_qualification_policy, - intake_qualification_policy_digest, - production_qualification_policy, + machine_qualification_policy, + machine_qualification_policy_digest, + prod_risk_admitting_qualification_policy, } import gunbc.machine_intake_receipt { EvidenceRef, @@ -122,7 +121,7 @@ import gunbc.machine_intake_bmc_secure { PublishedCredentialRetired, PublishedCredentialRetirementUnestablished, PublishedCredentialRetainedByPolicy, - PublishedCredentialRetainedNotAdmittedForProduction, + PublishedCredentialRetainedNotAdmittedByProdRiskPolicy, PublishedCredentialNotRetained, PublishedCredentialStillAccepted, BreakGlassAccount, @@ -360,8 +359,8 @@ fn both_lan_accepting() -> List { ] } -fn derive_dev(observation: BmcCredentialRotationObservation) -> BmcSecureStanding { - derive_bmc_secure(subject: intake_subject, observation: observation, policy: intake_qualification_policy(environment: DevelopmentAndTest)) +fn derive_admitting_test_risk(observation: BmcCredentialRotationObservation) -> BmcSecureStanding { + derive_bmc_secure(subject: intake_subject, observation: observation, policy: machine_qualification_policy(admits: TestRisk)) } // One rotation observation. Every test below perturbs exactly one reading. @@ -432,7 +431,7 @@ fn refused_with(standing: BmcSecureStanding, expected: BmcSecureRefusalCause) -> // the published credential does not — and the phase is satisfied. Without this // arm every refusal below would also pass on a function that refused always. test fn nominal_rotation_secures_the_bmc() -> Bool { - let standing = derive_dev(observation: nominal_rotation()) + let standing = derive_admitting_test_risk(observation: nominal_rotation()) let satisfied = match standing.outcome { BmcSecured { endpoint: e, reference: r, evidence: ev, latest_admitted_probe_at: at, published_credential: acc } => e == controller && r.credential_epoch == 1 && list_length(items: ev) == 7 && at == 1788000008000 && acc == published_account_retired @@ -446,7 +445,7 @@ test fn nominal_rotation_secures_the_bmc() -> Bool { test fn latest_admitted_probe_at_is_max_not_census_order() -> Bool { let later_on_first = CredentialRejected { evidence: evidence(label: "published-login-401"), observed_at: 1788000009000 } let earlier_on_second = CredentialRejected { evidence: evidence(label: "published-login-401-231"), observed_at: 1788000005500 } - let standing = derive_dev( + let standing = derive_admitting_test_risk( observation: rotation_observed( subj: intake_subject, previous_epoch: 0, @@ -488,7 +487,7 @@ test fn published_credential_still_working_refuses() -> Bool { ], account: published_account_retired, ) - let standing = derive_dev(observation: observation) + let standing = derive_admitting_test_risk(observation: observation) refused_with(standing: standing, expected: PublishedCredentialStillAccepted { channel: channel_one }) && (is_secured(standing: standing) == false) } @@ -511,7 +510,7 @@ test fn published_credential_retirement_unestablished_refuses() -> Bool { account: published_account_retired, ) refused_with( - standing: derive_dev(observation: observation), + standing: derive_admitting_test_risk(observation: observation), expected: PublishedCredentialRetirementUnestablished { detail: "connection reset before the login response" }, ) } @@ -535,7 +534,7 @@ test fn published_probe_before_rotation_refuses() -> Bool { account: published_account_retired, ) refused_with( - standing: derive_dev(observation: observation), + standing: derive_admitting_test_risk(observation: observation), expected: PublishedCredentialProbePrecedesRotation { applied_at: rotation_at, probed_at: 1788000002000 }, ) } @@ -556,7 +555,7 @@ test fn split_lan_census_one_accepting_refuses() -> Bool { account: published_account_retired, ) refused_with( - standing: derive_dev(observation: observation), + standing: derive_admitting_test_risk(observation: observation), expected: PublishedCredentialStillAccepted { channel: channel_eight }, ) } @@ -578,7 +577,7 @@ test fn single_address_without_census_refuses() -> Bool { account: published_account_retired, ) refused_with( - standing: derive_dev(observation: observation), + standing: derive_admitting_test_risk(observation: observation), expected: PublishedCredentialCensusNotTaken, ) } @@ -601,7 +600,7 @@ test fn census_member_unprobed_refuses() -> Bool { account: published_account_retired, ) refused_with( - standing: derive_dev(observation: observation), + standing: derive_admitting_test_risk(observation: observation), expected: PublishedCredentialCensusMemberUnprobed { channel: channel_eight }, ) } @@ -625,7 +624,7 @@ test fn probe_that_agrees_on_channel_but_not_address_does_not_cover() -> Bool { account: published_account_retired, ) refused_with( - standing: derive_dev(observation: observation), + standing: derive_admitting_test_risk(observation: observation), expected: PublishedCredentialCensusMemberUnprobed { channel: channel_eight }, ) } @@ -645,7 +644,7 @@ test fn published_probe_unmatched_refuses() -> Bool { account: published_account_retired, ) refused_with( - standing: derive_dev(observation: observation), + standing: derive_admitting_test_risk(observation: observation), expected: PublishedCredentialProbeUnmatched { channel: channel_two }, ) } @@ -667,7 +666,7 @@ test fn published_probe_duplicate_refuses() -> Bool { account: published_account_retired, ) refused_with( - standing: derive_dev(observation: observation), + standing: derive_admitting_test_risk(observation: observation), expected: PublishedCredentialProbeDuplicate { channel: channel_one }, ) } @@ -685,7 +684,7 @@ test fn serial_only_census_is_not_vacuous_secure() -> Bool { account: published_account_retired, ) refused_with( - standing: derive_dev(observation: observation), + standing: derive_admitting_test_risk(observation: observation), expected: PublishedCredentialNoAddressableLanMember, ) } @@ -705,7 +704,7 @@ test fn unaddressed_lan_with_administrator_access_refuses() -> Bool { account: published_account_retired, ) refused_with( - standing: derive_dev(observation: observation), + standing: derive_admitting_test_risk(observation: observation), expected: PublishedCredentialLanMemberUnaddressed { channel: channel_seven }, ) } @@ -725,7 +724,7 @@ test fn unaddressed_lan_with_no_access_secures() -> Bool { probes: both_lan_rejecting(), account: published_account_retired, ), - policy: intake_qualification_policy(environment: DevelopmentAndTest), + policy: machine_qualification_policy(admits: TestRisk), ) is_secured(standing: standing) } @@ -745,7 +744,7 @@ test fn unaddressed_lan_no_access_for_other_user_does_not_close() -> Bool { account: published_account_retired, ) refused_with( - standing: derive_dev(observation: observation), + standing: derive_admitting_test_risk(observation: observation), expected: PublishedCredentialUserAccessUnestablished { channel: channel_seven }, ) } @@ -769,7 +768,7 @@ test fn census_before_rotation_refuses() -> Bool { published_account: published_account_retired, ) refused_with( - standing: derive_dev(observation: observation), + standing: derive_admitting_test_risk(observation: observation), expected: PublishedCredentialCensusPrecedesRotation { applied_at: rotation_at, observed_at: 1788000002000 }, ) } @@ -790,7 +789,7 @@ test fn unaddressed_no_access_census_before_rotation_refuses() -> Bool { published_account: published_account_retired, ) refused_with( - standing: derive_dev(observation: observation), + standing: derive_admitting_test_risk(observation: observation), expected: PublishedCredentialCensusPrecedesRotation { applied_at: rotation_at, observed_at: 1788000002000 }, ) } @@ -815,7 +814,7 @@ test fn published_account_before_rotation_refuses() -> Bool { }, ) refused_with( - standing: derive_dev(observation: observation), + standing: derive_admitting_test_risk(observation: observation), expected: PublishedCredentialAccountPrecedesRotation { applied_at: rotation_at, observed_at: 1788000002000 }, ) } @@ -834,7 +833,7 @@ test fn serial_channel_no_access_does_not_block_lan_secure() -> Bool { probes: both_lan_rejecting(), account: published_account_retired, ), - policy: intake_qualification_policy(environment: DevelopmentAndTest), + policy: machine_qualification_policy(admits: TestRisk), ) is_secured(standing: standing) } @@ -852,7 +851,7 @@ test fn serial_channel_with_administrator_access_refuses() -> Bool { account: published_account_retired, ) refused_with( - standing: derive_dev(observation: observation), + standing: derive_admitting_test_risk(observation: observation), expected: PublishedCredentialChannelAccessNotClosed { channel: channel_two }, ) } @@ -871,7 +870,7 @@ test fn disabled_not_retired_is_a_secured_residual() -> Bool { probes: both_lan_rejecting(), account: published_account_disabled, ), - policy: intake_qualification_policy(environment: DevelopmentAndTest), + policy: machine_qualification_policy(admits: TestRisk), ) match standing.outcome { BmcSecured { endpoint: _, reference: _, evidence: _, latest_admitted_probe_at: _, published_credential: acc } => @@ -894,7 +893,7 @@ test fn disabled_administrator_reenable_is_a_secured_residual() -> Bool { probes: both_lan_rejecting(), account: published_account_disabled_reenable, ), - policy: intake_qualification_policy(environment: DevelopmentAndTest), + policy: machine_qualification_policy(admits: TestRisk), ) match standing.outcome { BmcSecured { endpoint: _, reference: _, evidence: _, latest_admitted_probe_at: _, published_credential: acc } => @@ -903,8 +902,8 @@ test fn disabled_administrator_reenable_is_a_secured_residual() -> Bool { } } -test fn break_glass_retention_secures_dev_test() -> Bool { - let standing = derive_dev(observation: rotation_observed( +test fn break_glass_retention_secures_under_test_risk_admission() -> Bool { + let standing = derive_admitting_test_risk(observation: rotation_observed( subj: intake_subject, previous_epoch: 0, bootstrap: bootstrap_accepted, @@ -922,7 +921,7 @@ test fn break_glass_retention_secures_dev_test() -> Bool { } } -test fn retained_credential_refuses_under_production() -> Bool { +test fn retained_credential_refuses_under_prod_risk_admission() -> Bool { let observation = rotation_observed( subj: intake_subject, previous_epoch: 0, @@ -935,13 +934,24 @@ test fn retained_credential_refuses_under_production() -> Bool { account: published_account_break_glass, ) refused_with( - standing: derive_bmc_secure(subject: intake_subject, observation: observation, policy: intake_qualification_policy(environment: Production)), - expected: PublishedCredentialRetainedNotAdmittedForProduction, + standing: derive_bmc_secure(subject: intake_subject, observation: observation, policy: machine_qualification_policy(admits: ProdRisk)), + expected: PublishedCredentialRetainedNotAdmittedByProdRiskPolicy, ) } -test fn published_factory_retention_secures_dev_test_only_when_explicit() -> Bool { - let standing = derive_dev(observation: rotation_observed( +// The ProdRisk arm's positive control: a ProdRisk-admitting policy requires the published credential +// closed, and a closed one secures. Without it the refusal above could be a policy that refuses everything. +test fn closed_published_credential_secures_under_prod_risk_admission() -> Bool { + let standing = derive_bmc_secure(subject: intake_subject, observation: nominal_rotation(), policy: machine_qualification_policy(admits: ProdRisk)) + match standing.outcome { + BmcSecured { endpoint: _, reference: _, evidence: _, latest_admitted_probe_at: _, published_credential: acc } => + acc == published_account_retired && standing.policy.admits == ProdRisk + BmcSecureRefused { cause: _ } => false + } +} + +test fn published_factory_retention_secures_under_test_risk_admission_only_when_explicit() -> Bool { + let standing = derive_admitting_test_risk(observation: rotation_observed( subj: intake_subject, previous_epoch: 0, bootstrap: bootstrap_accepted, @@ -959,7 +969,7 @@ test fn published_factory_retention_secures_dev_test_only_when_explicit() -> Boo } } -test fn published_factory_retention_refuses_under_production() -> Bool { +test fn published_factory_retention_refuses_under_prod_risk_admission() -> Bool { let observation = rotation_observed( subj: intake_subject, previous_epoch: 0, @@ -972,15 +982,15 @@ test fn published_factory_retention_refuses_under_production() -> Bool { account: published_account_factory_retained, ) refused_with( - standing: derive_bmc_secure(subject: intake_subject, observation: observation, policy: intake_qualification_policy(environment: Production)), - expected: PublishedCredentialRetainedNotAdmittedForProduction, + standing: derive_bmc_secure(subject: intake_subject, observation: observation, policy: machine_qualification_policy(admits: ProdRisk)), + expected: PublishedCredentialRetainedNotAdmittedByProdRiskPolicy, ) } -// review 65136: a DevTest factory-retention standing is BMC-secured on purpose, -// but derive_fleet_admission under the Production bar must not mint FleetAdmissible. -test fn factory_retention_dev_test_ledger_is_not_production_fleet_admissible() -> Bool { - let standing = derive_dev(observation: rotation_observed( +// review 65136: a TestRisk-admitting factory-retention standing is BMC-secured on purpose, +// but derive_fleet_admission under the ProdRisk-admitting bar must not mint FleetAdmissible. +test fn factory_retention_test_risk_ledger_is_not_fleet_admissible_under_prod_risk_bar() -> Bool { + let standing = derive_admitting_test_risk(observation: rotation_observed( subj: intake_subject, previous_epoch: 0, bootstrap: bootstrap_accepted, @@ -994,7 +1004,7 @@ test fn factory_retention_dev_test_ledger_is_not_production_fleet_admissible() - match validate_intake_receipt_ledger(receipts: chain(receipts: ledger_with_bmc_secure(standing: standing))) { LedgerRefused { cause: _ } => false LedgerValidated { ledger: l } => - match derive_fleet_admission(observed_subject: subject, ledger: l, policy: production_qualification_policy(), now: 1788000100000) { + match derive_fleet_admission(observed_subject: subject, ledger: l, policy: prod_risk_admitting_qualification_policy(), now: 1788000100000) { FleetPolicyBarMismatch { ledger_digest: _, bar: _ } => is_secured(standing: standing) FleetAdmissible { receipt: _ } => false FleetNotAdmissible { missing: _, refused: _, subject_mismatched: _, subject_currency: _ } => false @@ -1015,7 +1025,7 @@ test fn factory_retention_without_acceptance_is_not_retained() -> Bool { account: published_account_factory_retained, ) refused_with( - standing: derive_dev(observation: observation), + standing: derive_admitting_test_risk(observation: observation), expected: PublishedCredentialNotRetained { channel: channel_one }, ) } @@ -1032,7 +1042,7 @@ test fn managed_credential_rejected_refuses() -> Bool { probes: both_lan_rejecting(), account: published_account_retired, ) - refused_with(standing: derive_dev(observation: observation), expected: ManagedCredentialRejected) + refused_with(standing: derive_admitting_test_risk(observation: observation), expected: ManagedCredentialRejected) } test fn managed_credential_unestablished_refuses() -> Bool { @@ -1048,7 +1058,7 @@ test fn managed_credential_unestablished_refuses() -> Bool { account: published_account_retired, ) refused_with( - standing: derive_dev(observation: observation), + standing: derive_admitting_test_risk(observation: observation), expected: ManagedCredentialUnestablished { detail: "no route to the account service" }, ) } @@ -1066,7 +1076,7 @@ test fn managed_probe_before_rotation_refuses() -> Bool { account: published_account_retired, ) refused_with( - standing: derive_dev(observation: observation), + standing: derive_admitting_test_risk(observation: observation), expected: ManagedCredentialProbePrecedesRotation { applied_at: rotation_at, probed_at: 1788000004000 }, ) } @@ -1087,7 +1097,7 @@ test fn credential_epoch_not_advanced_refuses() -> Bool { account: published_account_retired, ) refused_with( - standing: derive_dev(observation: observation), + standing: derive_admitting_test_risk(observation: observation), expected: CredentialEpochNotAdvanced { previous: 1, observed: 1 }, ) } @@ -1107,7 +1117,7 @@ test fn rotation_not_attempted_refuses() -> Bool { published_user: published_user, published_account: published_account_retired, ) - refused_with(standing: derive_dev(observation: observation), expected: CredentialRotationNotApplied) + refused_with(standing: derive_admitting_test_risk(observation: observation), expected: CredentialRotationNotApplied) } test fn account_management_unavailable_refuses() -> Bool { @@ -1126,7 +1136,7 @@ test fn account_management_unavailable_refuses() -> Bool { published_account: published_account_retired, ) refused_with( - standing: derive_dev(observation: observation), + standing: derive_admitting_test_risk(observation: observation), expected: AccountManagementUnreachable { detail: "AccountService returned 501" }, ) } @@ -1146,7 +1156,7 @@ test fn bootstrap_credential_already_rejected_refuses() -> Bool { probes: both_lan_rejecting(), account: published_account_retired, ) - refused_with(standing: derive_dev(observation: observation), expected: PreviouslyRotatedCredentialRequired) + refused_with(standing: derive_admitting_test_risk(observation: observation), expected: PreviouslyRotatedCredentialRequired) } test fn bootstrap_credential_state_unknown_refuses() -> Bool { @@ -1162,7 +1172,7 @@ test fn bootstrap_credential_state_unknown_refuses() -> Bool { account: published_account_retired, ) refused_with( - standing: derive_dev(observation: observation), + standing: derive_admitting_test_risk(observation: observation), expected: CredentialStateUnknown { detail: "tls handshake failed" }, ) } @@ -1182,7 +1192,7 @@ test fn rotation_observation_for_other_attempt_refuses() -> Bool { account: published_account_retired, ) refused_with( - standing: derive_dev(observation: observation), + standing: derive_admitting_test_risk(observation: observation), expected: RotationObservationForOtherAttempt { expected: intake_subject, found: other_attempt_subject }, ) } @@ -1211,7 +1221,7 @@ data every_refusal_cause: List = [ PublishedCredentialCensusMemberUnprobed { channel: channel_eight }, PublishedCredentialProbeDuplicate { channel: channel_one }, PublishedCredentialProbeUnmatched { channel: channel_eight }, - PublishedCredentialRetainedNotAdmittedForProduction, + PublishedCredentialRetainedNotAdmittedByProdRiskPolicy, PublishedCredentialNotRetained { channel: channel_eight }, ] @@ -1255,7 +1265,7 @@ fn refusal_cause_ordinal(cause: BmcSecureRefusalCause) -> Int { PublishedCredentialCensusMemberUnprobed { channel: _ } => 20 PublishedCredentialProbeDuplicate { channel: _ } => 21 PublishedCredentialProbeUnmatched { channel: _ } => 22 - PublishedCredentialRetainedNotAdmittedForProduction => 23 + PublishedCredentialRetainedNotAdmittedByProdRiskPolicy => 23 PublishedCredentialNotRetained { channel: _ } => 24 } } @@ -1295,9 +1305,9 @@ data producer: IntakeProducerIdentity = IntakeProducerIdentity { agent_artifact: none, } -data policy_devtest: IntakeQualificationPolicy = intake_qualification_policy(environment: DevelopmentAndTest) -data policy_production: IntakeQualificationPolicy = intake_qualification_policy(environment: Production) -data policy_digest: ContentHash = intake_qualification_policy_digest(policy: policy_devtest) +data policy_admits_test_risk: MachineQualificationPolicy = machine_qualification_policy(admits: TestRisk) +data policy_admits_prod_risk: MachineQualificationPolicy = machine_qualification_policy(admits: ProdRisk) +data policy_digest: ContentHash = machine_qualification_policy_digest(policy: policy_admits_test_risk) fn satisfied_receipt_with(phase: IntakePhase, digest: ContentHash) -> IntakeReceiptEnvelope { IntakeReceiptEnvelope { @@ -1365,7 +1375,7 @@ fn ledger_with_bmc_secure(standing: BmcSecureStanding) -> List Bool { probes: split_lan_probes(), account: published_account_retired, ) - match disposition_with_bmc_secure(standing: derive_dev(observation: observation)) { + match disposition_with_bmc_secure(standing: derive_admitting_test_risk(observation: observation)) { Present { value: d } => match d { PartsHold { reason: r, next_action: _ } => @@ -1423,7 +1433,7 @@ test fn published_credential_still_working_blocks_fleet_admission() -> Bool { // admits, so the refusal above is attributable to the published credential and // not to anything else in the roster. test fn rotated_bmc_admits_the_unit() -> Bool { - match disposition_with_bmc_secure(standing: derive_dev(observation: nominal_rotation())) { + match disposition_with_bmc_secure(standing: derive_admitting_test_risk(observation: nominal_rotation())) { Present { value: d } => match d { Admitted { admission_receipt: _ } => true @@ -1438,7 +1448,7 @@ test fn rotated_bmc_admits_the_unit() -> Bool { // satisfied one carries the four readings it rests on — so a reader of the // durable spine can tell which rotation secured the unit without this module. test fn phase_receipt_projects_the_standing() -> Bool { - let secured = derive_dev(observation: nominal_rotation()) + let secured = derive_admitting_test_risk(observation: nominal_rotation()) let good = bmc_secure_phase_receipt(standing: secured, producer: producer, started_at: 1788000000000, ended_at: 1788000009000, previous_receipt: none) let refused_standing = derive_bmc_secure( subject: intake_subject, @@ -1453,7 +1463,7 @@ test fn phase_receipt_projects_the_standing() -> Bool { probes: split_lan_probes(), account: published_account_retired, ), - policy: intake_qualification_policy(environment: DevelopmentAndTest), + policy: machine_qualification_policy(admits: TestRisk), ) let bad = bmc_secure_phase_receipt(standing: refused_standing, producer: producer, started_at: 1788000000000, ended_at: 1788000009000, previous_receipt: none) let good_ok = match good.result { @@ -1479,7 +1489,7 @@ test fn phase_receipt_projects_the_standing() -> Bool { // receipts that a ledger tells apart: attempt one's receipt placed inside a // ledger whose genesis is attempt two's is refused on the subject it bears. test fn a_standing_cannot_be_replayed_onto_another_attempt() -> Bool { - let standing_one = derive_dev(observation: nominal_rotation()) + let standing_one = derive_admitting_test_risk(observation: nominal_rotation()) let standing_two = derive_bmc_secure( subject: other_attempt_subject, observation: rotation_observed( @@ -1493,7 +1503,7 @@ test fn a_standing_cannot_be_replayed_onto_another_attempt() -> Bool { probes: both_lan_rejecting(), account: published_account_retired, ), - policy: intake_qualification_policy(environment: DevelopmentAndTest), + policy: machine_qualification_policy(admits: TestRisk), ) let receipt_one = bmc_secure_phase_receipt(standing: standing_one, producer: producer, started_at: 1788000000000, ended_at: 1788000009000, previous_receipt: none) let receipt_two = bmc_secure_phase_receipt(standing: standing_two, producer: producer, started_at: 1788000000000, ended_at: 1788000009000, previous_receipt: none) @@ -1511,8 +1521,8 @@ test fn a_standing_cannot_be_replayed_onto_another_attempt() -> Bool { both_secured && carries_own_attempt && mixed } -test fn dev_test_standing_cannot_be_inherited_for_production() -> Bool { - let standing = derive_dev(observation: nominal_rotation()) +test fn test_risk_admitting_standing_cannot_be_inherited_under_prod_risk_bar() -> Bool { + let standing = derive_admitting_test_risk(observation: nominal_rotation()) let bmc_receipt = bmc_secure_phase_receipt( standing: standing, producer: producer, @@ -1520,18 +1530,18 @@ test fn dev_test_standing_cannot_be_inherited_for_production() -> Bool { ended_at: 1788000009000, previous_receipt: none, ) - let production_digest = intake_qualification_policy_digest(policy: policy_production) + let prod_risk_digest = machine_qualification_policy_digest(policy: policy_admits_prod_risk) let mixed = fold( fleet_admission_required_phases, init: [], - f: (acc, p) => if (p == bmc_secure_phase) { concat(acc, [bmc_receipt]) } else { concat(acc, [satisfied_receipt_with(phase: p, digest: production_digest)]) }, + f: (acc, p) => if (p == bmc_secure_phase) { concat(acc, [bmc_receipt]) } else { concat(acc, [satisfied_receipt_with(phase: p, digest: prod_risk_digest)]) }, ) - let digest_stays_devtest = + let digest_stays_test_risk = content_hash_equal( left: bmc_receipt.policy_digest, - right: intake_qualification_policy_digest(policy: standing.policy), + right: machine_qualification_policy_digest(policy: standing.policy), ) - && (content_hash_equal(left: bmc_receipt.policy_digest, right: production_digest) == false) + && (content_hash_equal(left: bmc_receipt.policy_digest, right: prod_risk_digest) == false) let ledger_refuses_restamp = match validate_intake_receipt_ledger(receipts: chain(receipts: mixed)) { LedgerValidated { ledger: _ } => false LedgerRefused { cause: c } => match c { @@ -1539,5 +1549,5 @@ test fn dev_test_standing_cannot_be_inherited_for_production() -> Bool { _ => false } } - is_secured(standing: standing) && digest_stays_devtest && ledger_refuses_restamp + is_secured(standing: standing) && digest_stays_test_risk && ledger_refuses_restamp } diff --git a/dag/test/claim/machine_intake/machine_intake_disposition_witness_test.dag b/dag/test/claim/machine_intake/machine_intake_disposition_witness_test.dag index 3c8d6b63454..79999843dd1 100644 --- a/dag/test/claim/machine_intake/machine_intake_disposition_witness_test.dag +++ b/dag/test/claim/machine_intake/machine_intake_disposition_witness_test.dag @@ -23,15 +23,14 @@ import gunbc.machine_intake_subject { UnitKey, qualification_subject_of, } +import gunbc.deployment_risk { ProdRisk, TestRisk } import gunbc.machine_intake_phase { AcceptanceSoak, Arrival, BmcAccess, BootDeliveryEstablish, - DevelopmentAndTest, IntakePhase, - IntakeQualificationPolicy, - Production, + MachineQualificationPolicy, MemoryAuthoritativeQualify, Placement, PlacementIdentityBound, @@ -40,9 +39,9 @@ import gunbc.machine_intake_phase { UnitHardware, fleet_admission_required_phases, intake_phase_rank, - intake_qualification_policy, - intake_qualification_policy_digest, - production_qualification_policy, + machine_qualification_policy, + machine_qualification_policy_digest, + prod_risk_admitting_qualification_policy, arrival_phases_all, provisioning_phases_all, placement_phases_all, @@ -150,8 +149,8 @@ data producer: IntakeProducerIdentity = IntakeProducerIdentity { agent_artifact: none, } -data admission_bar: IntakeQualificationPolicy = intake_qualification_policy(environment: DevelopmentAndTest) -data policy_digest: ContentHash = intake_qualification_policy_digest(policy: admission_bar) +data admission_bar: MachineQualificationPolicy = machine_qualification_policy(admits: TestRisk) +data policy_digest: ContentHash = machine_qualification_policy_digest(policy: admission_bar) data attempt_two: IntakeAttemptId = "intake-20260829-0002" as IntakeAttemptId @@ -520,9 +519,9 @@ fn receipts_under_digest(subject: QualificationSubject, digest: ContentHash) -> } // review 65171: a digest mismatch is not a BmcSecure inheritance failure. -// A Production ledger under the DevelopmentAndTest bar must name the bar. -test fn production_ledger_under_dev_test_bar_names_the_bar_not_bmc_secure() -> Bool { - let digest = intake_qualification_policy_digest(policy: production_qualification_policy()) +// A ProdRisk-admitting ledger under the TestRisk-admitting bar must name the bar. +test fn prod_risk_ledger_under_test_risk_bar_names_the_bar_not_bmc_secure() -> Bool { + let digest = machine_qualification_policy_digest(policy: prod_risk_admitting_qualification_policy()) match disposition_over(subject: subject_before, receipts: receipts_under_digest(subject: subject_before, digest: digest), window: ReturnWindowClosed) { Present { value: d } => match d { @@ -530,8 +529,8 @@ test fn production_ledger_under_dev_test_bar_names_the_bar_not_bmc_secure() -> B match r { QualificationPolicyBarMismatch { ledger_digest: d, bar: bar } => hashes_equal(left: d, right: digest) - && bar.environment == DevelopmentAndTest - && action == "ledger was derived under Production; the admission bar is DevelopmentAndTest" + && bar.admits == TestRisk + && action == "ledger was derived under a ProdRisk-admitting policy; the admission bar admits TestRisk" _ => false } Admitted { admission_receipt: _ } => false @@ -541,12 +540,12 @@ test fn production_ledger_under_dev_test_bar_names_the_bar_not_bmc_secure() -> B } } -test fn dev_test_ledger_under_production_bar_names_the_environments_not_bmc_secure() -> Bool { +test fn test_risk_ledger_under_prod_risk_bar_names_the_admitted_classes_not_bmc_secure() -> Bool { match validate_intake_receipt_ledger(receipts: chain(receipts: full_ledger(subject: subject_before))) { LedgerRefused { cause: _ } => false LedgerValidated { ledger: l } => match derive_disposition( - admission: derive_fleet_admission(observed_subject: subject_before, ledger: l, policy: production_qualification_policy(), now: 1788000002000), + admission: derive_fleet_admission(observed_subject: subject_before, ledger: l, policy: prod_risk_admitting_qualification_policy(), now: 1788000002000), return_window: ReturnWindowClosed, evidence: [], ) { @@ -554,8 +553,8 @@ test fn dev_test_ledger_under_production_bar_names_the_environments_not_bmc_secu match r { QualificationPolicyBarMismatch { ledger_digest: d, bar: bar } => hashes_equal(left: d, right: policy_digest) - && bar.environment == Production - && action == "ledger was derived under DevelopmentAndTest; the admission bar is Production" + && bar.admits == ProdRisk + && action == "ledger was derived under a TestRisk-admitting policy; the admission bar admits ProdRisk" _ => false } Admitted { admission_receipt: _ } => false @@ -697,7 +696,7 @@ test fn immediate_same_rank_retry_validates_and_binds_the_retry() -> Bool { binding_is_retry && hashes_equal(left: r.ledger_terminal_fingerprint, right: l.terminal_fingerprint) && hashes_equal(left: r.policy_digest, right: policy_digest) - && r.environment == DevelopmentAndTest + && r.admits == TestRisk && list_length(items: r.satisfied) == list_length(items: fleet_admission_required_phases) } } diff --git a/dag/test/claim/machine_intake/mtcollins1_bmc_secure_standing_witness_test.dag b/dag/test/claim/machine_intake/mtcollins1_bmc_secure_standing_witness_test.dag index 5ad76947b38..c0ca36f39a7 100644 --- a/dag/test/claim/machine_intake/mtcollins1_bmc_secure_standing_witness_test.dag +++ b/dag/test/claim/machine_intake/mtcollins1_bmc_secure_standing_witness_test.dag @@ -4,7 +4,8 @@ import std.types { Bool } import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } import extdeps.bmc.ipmi_channel { ipmi_controller_channel_census } import gunbc.secret_provision { mtcollins1_bmc_gunbc_secret_ref } -import gunbc.machine_intake_phase { DevelopmentAndTest, intake_qualification_policy } +import gunbc.deployment_risk { TestRisk } +import gunbc.machine_intake_phase { machine_qualification_policy } import gunbc.machine_intake_subject { UnitKeyBound, UnitKeyRefused } import gunbc.machine_intake_bmc_secure { BmcSecured, @@ -68,7 +69,7 @@ test fn unit1_channel_census_mints_bmc_secured_with_managed_secret() -> Bool { && standing == derive_bmc_secure( subject: subject, observation: observation, - policy: intake_qualification_policy(environment: DevelopmentAndTest), + policy: machine_qualification_policy(admits: TestRisk), ) && match mtcollins1_secured_account() { Present { value: account } => account == r.account @@ -104,7 +105,7 @@ test fn unit1_single_address_without_census_refuses() -> Bool { published_user: observation.published_user, published_account: observation.published_account, ), - policy: intake_qualification_policy(environment: DevelopmentAndTest), + policy: machine_qualification_policy(admits: TestRisk), ).outcome { BmcSecureRefused { cause: c } => c == PublishedCredentialCensusNotTaken BmcSecured { endpoint: _, reference: _, evidence: _, latest_admitted_probe_at: _, published_credential: _ } => false @@ -140,7 +141,7 @@ test fn unit1_census_before_rotation_refuses() -> Bool { published_user: mtcollins1_published_user, published_account: observation.published_account, ), - policy: intake_qualification_policy(environment: DevelopmentAndTest), + policy: machine_qualification_policy(admits: TestRisk), ).outcome { BmcSecureRefused { cause: c } => c == PublishedCredentialCensusPrecedesRotation {