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
17 changes: 2 additions & 15 deletions dag/gunbc/fleet_converge_apply.dag
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import gunbc.host_converge {
ConvergePolicyUnsound,
HostConverge,
fleet_converge_policy,
find_by_identity,
}
import gunbc.host_effect {
HostEffect,
Expand All @@ -32,21 +33,7 @@ import std.realization_reconcile {
}

fn fleet_compute_host_for_identity(identity: HostIdentity) -> ComputeHost? {
fold(
fleet_intent_known_hosts,
init: none,
f: fn(acc, host) {
match acc {
Present { value: _ } => acc
Absent =>
if host.identity == identity {
Present { value: host }
} else {
none
}
}
}
)
find_by_identity(fleet_intent_known_hosts, identity: identity, key: fn(host) { host.identity })
}

fn converge_plan_effect(host_converge: HostConverge) -> HostEffect {
Expand Down
151 changes: 151 additions & 0 deletions dag/gunbc/fleet_converge_cli.dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
module gunbc.fleet_converge_cli

import std.types { String, List, Int }
import std.disposition { Disposition, Terminal }
import product.placement_supply { HostIdentity }
import gunbc.host_converge {
ConvergePolicyDerived,
ConvergePolicyUnsound,
HostConverge,
ConvergeVerdict,
VerdictConverged,
converge_verdict_label,
fleet_converge_policy,
find_by_identity,
}
import gunbc.host_effect { LocalShell, HostEffectIntent, HostEffect, ConvergePlan, ShellCommand, RedfishAction }
import gunbc.fleet_converge_apply { converge_apply_for_host }
import std.realization_reconcile { Converged, NotConverged }

data gunbc_converge_cli_stage0_receiver_disposition: Disposition = Terminal {
reason: "stage0's `gunbc converge --host <h>` argv dispatch (src/v1/stage0/src/cli_run.rs::handle_converge) is the CLI transport handler for this .dag interface (DESIGN §3 transport-is-a-handler): argv parse -> resolve_entry_graph_shared -> run_in_context_with_args(converge_cli_output) -> stdout/exit-code projection, nothing else. All interpretation, the byte-locked receipt grammar, and typed refusal causes are modeled here (gunbc.fleet_converge_cli) and in gunbc.host_converge/gunbc.fleet_converge_apply; the Rust side carries no converge logic and is not scaffold pending dissolution."
}

type ConvergeCliRefusalCause
= PolicyUnsound { reason: String }
| UnknownHost { host: String }
| ComputeInventoryAbsent { host: String }
| KnobFrontier { reason: String }

fn converge_cli_unknown_host_reason(host: String) -> String {
concat("gunbc converge: unknown fleet host identity ", host)
}

fn converge_cli_compute_inventory_absent_reason(host: String) -> String {
concat("gunbc converge: host resolved by fleet converge policy but absent from compute inventory ", host)
}

fn converge_cli_refusal_message(cause: ConvergeCliRefusalCause) -> String {
match cause {
PolicyUnsound { reason: why } => concat("gunbc converge: fleet policy unsound — ", why)
UnknownHost { host: h } => converge_cli_unknown_host_reason(host: h)
ComputeInventoryAbsent { host: h } => converge_cli_compute_inventory_absent_reason(host: h)
KnobFrontier { reason: why } => why
}
}

type ConvergeCliReceipt {
host: String
applied: Int
drifted: Int
verdict: ConvergeVerdict
reason: ConvergeCliRefusalCause?
}

fn converge_cli_receipt_line(r: ConvergeCliReceipt) -> String {
concat(
concat(concat("converge-receipt host=", r.host), concat(" applied=", to_string(r.applied))),
concat(concat(" drifted=", to_string(r.drifted)), concat(" verdict=", converge_verdict_label(v: r.verdict)))
)
}

fn converge_cli_receipt_converged(r: ConvergeCliReceipt) -> Bool {
r.verdict == VerdictConverged
}

fn host_converge_for_identity(hosts: List<HostConverge>, identity: HostIdentity) -> HostConverge? {
find_by_identity(hosts, identity: identity, key: fn(h) { h.identity })
}

fn converge_cli_applied_knob_count(intent: HostEffectIntent, fallback: Int) -> Int {
match intent.effect {
ConvergePlan { policy: p } => p.knobs.length()
ShellCommand { script: _ } => fallback
RedfishAction { action: _ } => fallback
}
}

fn converge_cli_receipt_for_host(host_converge: HostConverge) -> ConvergeCliReceipt {
match converge_apply_for_host(host_converge: host_converge, transport: LocalShell) {
Present { value: r } =>
match r {
Converged { evidence: _, applied: intent } =>
ConvergeCliReceipt {
host: host_converge.identity as String,
applied: converge_cli_applied_knob_count(intent: intent, fallback: host_converge.knobs.length()),
drifted: 0,
verdict: VerdictConverged,
reason: none,
}
NotConverged { reason: why, applied: _ } =>
ConvergeCliReceipt {
host: host_converge.identity as String,
applied: 0,
drifted: 0,
verdict: Absent,
reason: Present { value: KnobFrontier { reason: why } },
}
}
Absent =>
ConvergeCliReceipt {
host: host_converge.identity as String,
applied: 0,
drifted: 0,
verdict: Absent,
reason: Present { value: ComputeInventoryAbsent { host: host_converge.identity as String } },
}
}
}

type ConvergeCliOutput {
line: String
converged: Bool
reason: String?
}

fn converge_cli_output(host: String) -> ConvergeCliOutput {
let r = converge_cli_run(host: host)
ConvergeCliOutput {
line: converge_cli_receipt_line(r: r),
converged: converge_cli_receipt_converged(r: r),
reason: match r.reason {
Present { value: cause } => Present { value: converge_cli_refusal_message(cause: cause) }
Absent => none
},
}
}

fn converge_cli_run(host: String) -> ConvergeCliReceipt {
match fleet_converge_policy() {
ConvergePolicyUnsound { reason: why } =>
ConvergeCliReceipt {
host: host,
applied: 0,
drifted: 0,
verdict: Absent,
reason: Present { value: PolicyUnsound { reason: why } },
}
ConvergePolicyDerived { hosts: hosts } =>
match host_converge_for_identity(hosts: hosts, identity: host as HostIdentity) {
Present { value: hc } => converge_cli_receipt_for_host(host_converge: hc)
Absent =>
ConvergeCliReceipt {
host: host,
applied: 0,
drifted: 0,
verdict: Absent,
reason: Present { value: UnknownHost { host: host } },
}
}
}
}
20 changes: 19 additions & 1 deletion dag/gunbc/host_converge.dag
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module gunbc.host_converge

import std.types { String, List, Int, Bool, NonEmptyStr }
import std.measure { ByteSize, byte_size_count, hardware_thread_count_value, percent_count, Second, second, second_count }
import std.disposition { Disposition, Scaffold, SingleAuthority }
import std.disposition { Disposition, Terminal, Scaffold, SingleAuthority }
import std.decl_ref { DeclarationRef, WholeDeclaration }
import product.placement_supply { HostIdentity }
import gunbc.ci_runner_placement {
Expand Down Expand Up @@ -239,6 +239,24 @@ type HostConverge {
membership: SessionsMembershipProbe
}

fn find_by_identity<T>(items: List<T>, identity: HostIdentity, key: fn(T) -> HostIdentity) -> T? {
fold(
items,
init: none,
f: fn(acc, item) {
match acc {
Present { value: _ } => acc
Absent =>
if key(item) == identity {
Present { value: item }
} else {
none
}
}
}
)
}

type ConvergePolicy
= ConvergePolicyDerived { hosts: List<HostConverge> }
| ConvergePolicyUnsound { reason: String }
Expand Down
138 changes: 138 additions & 0 deletions dag/test/claim/fleet_converge_cli_witness_test.dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
module test.claim.fleet_converge_cli_witness

import std.logic { Bool }
import gunbc.host_converge { VerdictConverged, Absent }
import gunbc.fleet_converge_cli {
ConvergeCliReceipt,
ConvergeCliRefusalCause,
PolicyUnsound,
UnknownHost,
ComputeInventoryAbsent,
KnobFrontier,
converge_cli_run,
converge_cli_receipt_line,
converge_cli_receipt_converged,
converge_cli_output,
converge_cli_refusal_message,
}
import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly }

data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly

fn witness_converge_cli_run_known_host_fails_closed_on_knob_frontier() -> Bool {
let r = converge_cli_run(host: "srv1")
r.host == "srv1"
&& r.applied == 0
&& r.drifted == 0
&& r.verdict == Absent
&& !converge_cli_receipt_converged(r: r)
&& match r.reason {
Present { value: cause } =>
match cause {
KnobFrontier { reason: why } => string_contains(s: why, pattern: "host_converge_realize")
PolicyUnsound { reason: _ } => false
UnknownHost { host: _ } => false
ComputeInventoryAbsent { host: _ } => false
}
Absent => false
}
}

fn witness_converge_cli_run_unknown_host_refuses() -> Bool {
let r = converge_cli_run(host: "no-such-fleet-host")
r.host == "no-such-fleet-host"
&& r.applied == 0
&& r.drifted == 0
&& r.verdict == Absent
&& !converge_cli_receipt_converged(r: r)
&& match r.reason {
Present { value: cause } =>
match cause {
UnknownHost { host: h } => h == "no-such-fleet-host"
PolicyUnsound { reason: _ } => false
KnobFrontier { reason: _ } => false
ComputeInventoryAbsent { host: _ } => false
}
Absent => false
}
}

fn witness_converge_cli_unknown_host_and_compute_inventory_absent_are_distinct_causes() -> Bool {
let unknown = UnknownHost { host: "no-such-fleet-host" }
let inventory_absent = ComputeInventoryAbsent { host: "gunbc-ci-fleet" }
match unknown {
UnknownHost { host: _ } =>
match inventory_absent {
ComputeInventoryAbsent { host: _ } => true
UnknownHost { host: _ } => false
PolicyUnsound { reason: _ } => false
KnobFrontier { reason: _ } => false
}
ComputeInventoryAbsent { host: _ } => false
PolicyUnsound { reason: _ } => false
KnobFrontier { reason: _ } => false
}
&& converge_cli_refusal_message(cause: unknown) != converge_cli_refusal_message(cause: inventory_absent)
&& string_contains(s: converge_cli_refusal_message(cause: unknown), pattern: "unknown fleet host identity")
&& string_contains(
s: converge_cli_refusal_message(cause: inventory_absent),
pattern: "absent from compute inventory"
)
}

fn witness_converge_cli_refusal_causes_discriminate() -> Bool {
let known = converge_cli_run(host: "srv1")
let unknown = converge_cli_run(host: "no-such-fleet-host")
match known.reason {
Present { value: known_cause } =>
match unknown.reason {
Present { value: unknown_cause } =>
match known_cause {
KnobFrontier { reason: _ } =>
match unknown_cause {
UnknownHost { host: _ } => true
PolicyUnsound { reason: _ } => false
KnobFrontier { reason: _ } => false
ComputeInventoryAbsent { host: _ } => false
}
PolicyUnsound { reason: _ } => false
UnknownHost { host: _ } => false
ComputeInventoryAbsent { host: _ } => false
}
Absent => false
}
Absent => false
}
}

fn witness_converge_cli_receipt_line_matches_byte_locked_grammar() -> Bool {
let converged = ConvergeCliReceipt { host: "srv9", applied: 3, drifted: 0, verdict: VerdictConverged, reason: none }
let refused = ConvergeCliReceipt {
host: "srv1",
applied: 0,
drifted: 0,
verdict: Absent,
reason: Present { value: KnobFrontier { reason: "refused" } },
}
converge_cli_receipt_line(r: converged) == "converge-receipt host=srv9 applied=3 drifted=0 verdict=converged"
&& converge_cli_receipt_line(r: refused) == "converge-receipt host=srv1 applied=0 drifted=0 verdict=absent"
}

fn witness_converge_cli_output_projects_receipt_and_exit_signal() -> Bool {
let out = converge_cli_output(host: "no-such-fleet-host")
out.line == "converge-receipt host=no-such-fleet-host applied=0 drifted=0 verdict=absent"
&& !out.converged
&& match out.reason {
Present { value: why } => string_contains(s: why, pattern: "unknown fleet host identity")
Absent => false
}
}

test fn fleet_converge_cli_witness_holds() -> Bool {
witness_converge_cli_run_known_host_fails_closed_on_knob_frontier()
&& witness_converge_cli_run_unknown_host_refuses()
&& witness_converge_cli_refusal_causes_discriminate()
&& witness_converge_cli_unknown_host_and_compute_inventory_absent_are_distinct_causes()
&& witness_converge_cli_receipt_line_matches_byte_locked_grammar()
&& witness_converge_cli_output_projects_receipt_and_exit_signal()
}
Loading
Loading