From 03c66ac11f93a96629dc71cfaf4a2c7b4ea181e9 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 14 Jul 2026 06:18:45 +0000 Subject: [PATCH 1/6] =?UTF-8?q?WIP:=20Shell=E2=86=92dag=20P5a:=20the=207?= =?UTF-8?q?=20MECHANICAL=20srv3=20tail=20files=20=E2=86=92=20typed=20obser?= =?UTF-8?q?ve/effect=20r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dag/gunbc/host_effect.dag | 12 + dag/gunbc/srv3_host_effect_script.dag | 253 ++++++++++++++++++ .../srv3_seeded_install_media_artifact.dag | 45 ++++ 3 files changed, 310 insertions(+) create mode 100644 dag/gunbc/srv3_host_effect_script.dag create mode 100644 dag/gunbc/srv3_seeded_install_media_artifact.dag diff --git a/dag/gunbc/host_effect.dag b/dag/gunbc/host_effect.dag index b3345ba052a..b228f7f514d 100644 --- a/dag/gunbc/host_effect.dag +++ b/dag/gunbc/host_effect.dag @@ -21,6 +21,18 @@ type HostEffect = ShellCommand { script: String } | RedfishAction { action: RedfishSystemAction } | ConvergePlan { policy: HostConverge } + | Srv3InstallReconcileObserve { include_git_meta: Bool } + | DurableApprovalGrantRecord + | DurableApprovalGrantRead + | Srv3InstallMediaFetch + | Srv3SeededInstallMediaRemaster + | Srv3SeededInstallMediaToolchainEnsure + | Srv3SolConsoleCapture + | Srv3BmcwebSessionLogin + | Srv3BmcwebTokenExtract + | Srv3NbdProxyServe + | Srv3BootOnceReadBackSleep + | Srv3ReceiptEmit { lines: List } type NodeControlPlane = HostOs { node: ComputeHost } diff --git a/dag/gunbc/srv3_host_effect_script.dag b/dag/gunbc/srv3_host_effect_script.dag new file mode 100644 index 00000000000..0aee740c5d7 --- /dev/null +++ b/dag/gunbc/srv3_host_effect_script.dag @@ -0,0 +1,253 @@ +module gunbc.srv3_host_effect_script + +import gunbc.host_effect { + HostEffect, + Srv3InstallReconcileObserve, + DurableApprovalGrantRecord, + DurableApprovalGrantRead, + Srv3InstallMediaFetch, + Srv3SeededInstallMediaRemaster, + Srv3SeededInstallMediaToolchainEnsure, + Srv3SolConsoleCapture, + Srv3BmcwebSessionLogin, + Srv3BmcwebTokenExtract, + Srv3NbdProxyServe, + Srv3BootOnceReadBackSleep, + Srv3ReceiptEmit, + ShellCommand, + RedfishAction, + ConvergePlan, +} +import gunbc.shell_bash_runner { shell_exec_via_bash, shell_printf_line } +import gunbc.srv3_install_diagnostic_checklist { srv3_install_diagnostic_observe_script } +import gunbc.srv3_os_install_diagnostic { srv3_os_install_attempt_intent } +import gunbc.srv3_os_install_reconcile { srv3_install_reconcile_observe_meta_script } +import gunbc.srv3_os_install_reconcile_receipt { srv3_install_record_boot_once_approval_script } +import gunbc.srv3_os_install_reconcile_types { + srv3_boot_once_approval_grant_path, + srv3_boot_once_apply_read_back_sleep, +} +import gunbc.srv3_seeded_install_media_artifact { srv3_seeded_install_media_artifact } +import extdeps.os.ubuntu_install_media { noble_numbat_2404_3_live_server_arm64 } +import extdeps.os.ubuntu_install_media_fetch { install_media_fetch_script } +import extdeps.os.ubuntu_seeded_install_media_remaster { install_media_remaster_script } +import extdeps.bmc.serial_console { + SolConsoleCaptureIntent, + ObmcConsoleCli, + sol_console_capture_script, +} +import extdeps.boards.asrock_rack { asrock_srv3_live_bmc_host } +import extdeps.bmc.webui.nbd_proxy_serve { + NbdProxyServeTransportIntent, + bmcweb_session_login_program, + nbd_proxy_serve_script, + nbd_proxy_serve_transport_intent, + srv3_nbd_proxy_local_port, +} +import extdeps.bmc.webui.nbd_proxy { + bmcweb_nbd_proxy_ws_path, +} +import extdeps.boards.asrock_rack { + asrock_srv3_live_bmc_host, + asrock_altrad8ud_openbmc_2_07_00_nbd_proxy_primary_endpoint, +} +import extdeps.languages.bash.program { serialize_bash } +import gunbc.srv3_os_install_actuate_scope { srv3_actuator_virtual_media_iso_install_path } +import gunbc.ci_deploy_access { + ci_deploy_srv1_access, + deploy_access_apt_get_privileged_command, + deploy_access_preflight_check_block, + deploy_access_sudo_n_apt_get_install_command, +} +import extdeps.tools.xorriso { xorriso_cli_tool } +import extdeps.apt { package_xorriso } +import std.types { String, NonEmptyStr, List, Bool } +import std.measure { second_count } +import std.disposition { Disposition, Scaffold, SingleAuthority } +import std.decl_ref { DeclarationRef, WholeDeclaration } + +data srv3_host_effect_script_scaffold: Disposition = Scaffold { + dissolves_to: SingleAuthority, + bind: DeclarationRef { + module_path: "gunbc.srv3_os_install_reconcile", + decl_name: "srv3_os_install_reconcile_shell_runner_dissolution_trigger", + field: WholeDeclaration, + } +} + +data srv3_bmcweb_login_body_path: String = "/tmp/srv3_bmcweb_login_body.json" +data srv3_bmcweb_login_response_path: String = "/tmp/srv3_bmcweb_login_response.json" +data srv3_bmcweb_token_path: String = "/tmp/srv3_bmcweb_token" +data srv3_nbd_proxy_serve_script_path: String = "/tmp/srv3_nbd_proxy_serve.sh" +data srv3_sol_console_log_path: NonEmptyStr = "/var/lib/gunbc/logs/srv3-install-sol-console.log" as NonEmptyStr + +data srv3_sol_console_capture_intent: SolConsoleCaptureIntent = SolConsoleCaptureIntent { + bmc_host: asrock_srv3_live_bmc_host, + transport: ObmcConsoleCli, + output_path: srv3_sol_console_log_path, +} + +fn install_media_remaster_toolchain_ensure_script() -> String { + let tool_name = xorriso_cli_tool.name as String + let apt_package = package_xorriso as String + let apt_preflight = deploy_access_preflight_check_block( + access: ci_deploy_srv1_access, + cmd: deploy_access_apt_get_privileged_command(), + ) + let privileged_install = deploy_access_sudo_n_apt_get_install_command(package: package_xorriso) + concat( + concat( + concat( + concat("#!/usr/bin/env bash\nset -euo pipefail\nif command -v ", tool_name), + " >/dev/null 2>&1 && ", + ), + concat(tool_name, " --version >/dev/null 2>&1; then\n"), + ), + concat( + concat( + concat( + concat(" echo \"InstallMediaRemasterToolchainReceipt: outcome=Present tool=", tool_name), + concat(" package=", apt_package), + ), + "\"\n exit 0\nfi\n", + ), + concat( + concat( + concat( + concat(apt_preflight, privileged_install), + "\n", + ), + concat(tool_name, " --version >/dev/null\n"), + ), + concat( + concat("echo \"InstallMediaRemasterToolchainReceipt: outcome=Installed tool=", tool_name), + concat(" package=", apt_package, "\"\n"), + ), + ), + ), + ) +} + +fn srv3_install_reconcile_observe_script(include_git_meta: Bool) -> String { + let diagnostic = srv3_install_diagnostic_observe_script(intent: srv3_os_install_attempt_intent) + if include_git_meta { + concat(diagnostic, srv3_install_reconcile_observe_meta_script()) + } else { + diagnostic + } +} + +fn srv3_nbd_proxy_serve_intent() -> NbdProxyServeTransportIntent { + nbd_proxy_serve_transport_intent( + bmc_host: asrock_srv3_live_bmc_host, + session_token: concat("$", "BMCWEB_SESSION_TOKEN") as NonEmptyStr, + ws_path: bmcweb_nbd_proxy_ws_path( + variant: asrock_altrad8ud_openbmc_2_07_00_nbd_proxy_primary_endpoint, + slot: 0, + index: 0, + ), + iso_path: srv3_actuator_virtual_media_iso_install_path, + local_nbd_port: srv3_nbd_proxy_local_port, + ) +} + +fn srv3_bmcweb_session_login_script() -> String { + let login_prog = bmcweb_session_login_program( + bmc_host: asrock_srv3_live_bmc_host, + request_body_file: srv3_bmcweb_login_body_path as NonEmptyStr, + response_path: srv3_bmcweb_login_response_path as NonEmptyStr, + ) + serialize_bash(p: login_prog) +} + +fn srv3_bmcweb_token_extract_script() -> String { + concat( + concat( + concat("jq -r '.token // empty' ", srv3_bmcweb_login_response_path), + " > ", + ), + srv3_bmcweb_token_path, + ) +} + +fn srv3_nbd_proxy_serve_wrapper_script() -> String { + concat( + concat( + concat("export BMCWEB_SESSION_TOKEN=$(cat ", srv3_bmcweb_token_path), + "); bash ", + ), + srv3_nbd_proxy_serve_script_path, + ) +} + +fn srv3_receipt_emit_script(lines: List) -> String { + fold( + lines, + init: "" as String, + f: (acc, line) => concat(acc, shell_printf_line(line: line)), + ) +} + +fn srv3_host_effect_is_srv3_variant(effect: HostEffect) -> Bool { + match effect { + Srv3InstallReconcileObserve { include_git_meta: _ } => true + DurableApprovalGrantRecord => true + DurableApprovalGrantRead => true + Srv3InstallMediaFetch => true + Srv3SeededInstallMediaRemaster => true + Srv3SeededInstallMediaToolchainEnsure => true + Srv3SolConsoleCapture => true + Srv3BmcwebSessionLogin => true + Srv3BmcwebTokenExtract => true + Srv3NbdProxyServe => true + Srv3BootOnceReadBackSleep => true + Srv3ReceiptEmit { lines: _ } => true + ShellCommand { script: _ } => false + RedfishAction { action: _ } => false + ConvergePlan { policy: _ } => false + } +} + +fn srv3_host_effect_script_body(effect: HostEffect) -> String { + match effect { + Srv3InstallReconcileObserve { include_git_meta: meta } => + srv3_install_reconcile_observe_script(include_git_meta: meta) + DurableApprovalGrantRecord => + srv3_install_record_boot_once_approval_script() + DurableApprovalGrantRead => + concat("cat ", concat(srv3_boot_once_approval_grant_path, " 2>/dev/null || true")) + Srv3InstallMediaFetch => + install_media_fetch_script(artifact: noble_numbat_2404_3_live_server_arm64) + Srv3SeededInstallMediaRemaster => + install_media_remaster_script(artifact: srv3_seeded_install_media_artifact) + Srv3SeededInstallMediaToolchainEnsure => + install_media_remaster_toolchain_ensure_script() + Srv3SolConsoleCapture => + sol_console_capture_script(intent: srv3_sol_console_capture_intent) + Srv3BmcwebSessionLogin => + srv3_bmcweb_session_login_script() + Srv3BmcwebTokenExtract => + srv3_bmcweb_token_extract_script() + Srv3NbdProxyServe => + srv3_nbd_proxy_serve_wrapper_script() + Srv3BootOnceReadBackSleep => + concat("sleep ", to_string(second_count(s: srv3_boot_once_apply_read_back_sleep))) + Srv3ReceiptEmit { lines: lines } => + srv3_receipt_emit_script(lines: lines) + ShellCommand { script: _ } => "" + RedfishAction { action: _ } => "" + ConvergePlan { policy: _ } => "" + } +} + +fn srv3_host_effect_script_for(effect: HostEffect) -> String? { + if srv3_host_effect_is_srv3_variant(effect: effect) { + Present { value: shell_exec_via_bash(body: srv3_host_effect_script_body(effect: effect)) } + } else { + none + } +} + +fn srv3_host_effect_script_body_is_nonempty(effect: HostEffect) -> Bool { + srv3_host_effect_script_body(effect: effect) != "" +} diff --git a/dag/gunbc/srv3_seeded_install_media_artifact.dag b/dag/gunbc/srv3_seeded_install_media_artifact.dag new file mode 100644 index 00000000000..d286955dcdf --- /dev/null +++ b/dag/gunbc/srv3_seeded_install_media_artifact.dag @@ -0,0 +1,45 @@ +module gunbc.srv3_seeded_install_media_artifact + +import extdeps.os.ubuntu_install_media { noble_numbat_2404_3_live_server_arm64 } +import extdeps.os.ubuntu_seeded_install_media { + UbuntuSeededInstallMediaArtifactRow, + ubuntu_seeded_install_media_default_nocloud_dir, + ubuntu_seeded_install_media_grub_kernel_cmdline, + seeded_install_media_content_hash, + ubuntu_seeded_install_media_install_path, +} +import gunbc.os_install { srv3_ubuntu_autoinstall_on_iso } +import gunbc.os_install_emit { autoinstall_user_data } +import std.types { NonEmptyStr, ContentHash } + +data srv3_autoinstall_user_data_body: NonEmptyStr = autoinstall_user_data( + payload: srv3_ubuntu_autoinstall_on_iso, +) as NonEmptyStr + +data srv3_seeded_nocloud_dir_on_iso: NonEmptyStr = ubuntu_seeded_install_media_default_nocloud_dir( + hostname: srv3_ubuntu_autoinstall_on_iso.identity.hostname, +) + +data srv3_seeded_grub_kernel_cmdline: NonEmptyStr = ubuntu_seeded_install_media_grub_kernel_cmdline( + nocloud_dir: srv3_seeded_nocloud_dir_on_iso, +) + +data srv3_seeded_install_media_content_hash: ContentHash = seeded_install_media_content_hash( + stock_content_sha256: noble_numbat_2404_3_live_server_arm64.content_sha256 as NonEmptyStr, + autoinstall_user_data_body: srv3_autoinstall_user_data_body, + grub_kernel_cmdline: srv3_seeded_grub_kernel_cmdline, + nocloud_dir_on_iso: srv3_seeded_nocloud_dir_on_iso, +) + +data srv3_seeded_install_media_artifact: UbuntuSeededInstallMediaArtifactRow = UbuntuSeededInstallMediaArtifactRow { + stock_artifact: noble_numbat_2404_3_live_server_arm64, + autoinstall: srv3_ubuntu_autoinstall_on_iso, + autoinstall_user_data_body: srv3_autoinstall_user_data_body, + grub_kernel_cmdline: srv3_seeded_grub_kernel_cmdline, + nocloud_dir_on_iso: srv3_seeded_nocloud_dir_on_iso, + content_hash: srv3_seeded_install_media_content_hash, +} + +data srv3_seeded_ubuntu_install_media_install_path: NonEmptyStr = ubuntu_seeded_install_media_install_path( + artifact: srv3_seeded_install_media_artifact, +) From d31dfdf3f47f3220c84b462cb8339e1650e82bb6 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 14 Jul 2026 06:29:21 +0000 Subject: [PATCH 2/6] =?UTF-8?q?WIP:=20Shell=E2=86=92dag=20P5a:=20the=207?= =?UTF-8?q?=20MECHANICAL=20srv3=20tail=20files=20=E2=86=92=20typed=20obser?= =?UTF-8?q?ve/effect=20r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dag/gunbc/host_effect_realize.dag | 80 ++++++-- dag/gunbc/srv3_actuate_shell_paths.dag | 9 + dag/gunbc/srv3_host_effect_apply.dag | 172 ++++++++++++++++++ dag/gunbc/srv3_host_effect_script.dag | 27 ++- .../srv3_install_diagnostic_checklist.dag | 2 +- dag/gunbc/srv3_install_media_fetch.dag | 18 +- dag/gunbc/srv3_os_install_actuate.dag | 62 +++---- dag/gunbc/srv3_os_install_actuate_scope.dag | 2 +- dag/gunbc/srv3_os_install_diagnostic.dag | 9 +- dag/gunbc/srv3_os_install_reconcile.dag | 2 +- dag/gunbc/srv3_os_install_reconcile_apply.dag | 99 +++++----- .../srv3_os_install_reconcile_dry_run.dag | 49 ++--- ...3_os_install_reconcile_record_approval.dag | 23 +-- dag/gunbc/srv3_os_install_reconcile_types.dag | 2 +- dag/gunbc/srv3_seeded_install_media.dag | 148 ++++----------- dag/gunbc/srv3_sol_console_capture.dag | 31 +--- dag/gunbc/srv3_sol_console_intent.dag | 14 ++ .../srv3_host_effect_apply_witness_test.dag | 78 ++++++++ ...tall_diagnostic_checklist_witness_test.dag | 2 +- .../srv3_os_install_actuate_witness_test.dag | 2 + ...rv3_os_install_diagnostic_witness_test.dag | 2 +- ...srv3_os_install_reconcile_witness_test.dag | 2 +- ...srv3_seeded_install_media_witness_test.dag | 4 +- .../srv3_sol_console_capture_witness_test.dag | 2 +- 24 files changed, 522 insertions(+), 319 deletions(-) create mode 100644 dag/gunbc/srv3_actuate_shell_paths.dag create mode 100644 dag/gunbc/srv3_host_effect_apply.dag create mode 100644 dag/gunbc/srv3_sol_console_intent.dag create mode 100644 dag/test/claim/srv3_host_effect_apply_witness_test.dag diff --git a/dag/gunbc/host_effect_realize.dag b/dag/gunbc/host_effect_realize.dag index b4cbf22cc69..7871c6cc4f3 100644 --- a/dag/gunbc/host_effect_realize.dag +++ b/dag/gunbc/host_effect_realize.dag @@ -2,6 +2,18 @@ module gunbc.host_effect_realize import gunbc.host_effect { HostEffect, ShellCommand, RedfishAction, ConvergePlan, + Srv3InstallReconcileObserve, + DurableApprovalGrantRecord, + DurableApprovalGrantRead, + Srv3InstallMediaFetch, + Srv3SeededInstallMediaRemaster, + Srv3SeededInstallMediaToolchainEnsure, + Srv3SolConsoleCapture, + Srv3BmcwebSessionLogin, + Srv3BmcwebTokenExtract, + Srv3NbdProxyServe, + Srv3BootOnceReadBackSleep, + Srv3ReceiptEmit, NodeControlPlane, HostOs, BmcController, Drive, OneShot, ConvergeLoop, HostEffectIntent, HostEffectEvidence, ShellEffectApplied, ConvergePlanApplied, @@ -32,6 +44,7 @@ import gunbc.ci_deploy_access { deploy_access_check, deploy_access_check_observed, deploy_access_decision_reason, } +import gunbc.srv3_host_effect_script { srv3_host_effect_script_for } import std.types { String, NonEmptyStr, List } type ResolvedHostEffectCell = @@ -50,27 +63,64 @@ fn converge_plan_identity_mismatch_reason(node: ComputeHost, policy: HostConverg ) } +fn srv3_host_effect_script_resolution_failed_cell() -> ResolvedHostEffectCell { + IncompatibleCell { reason: "host_effect: srv3 typed effect script resolution failed; fail-closed" } +} + fn resolve_host_effect_cell(target: NodeControlPlane, effect: HostEffect) -> ResolvedHostEffectCell { match target { HostOs { node: n } => - match effect { - ShellCommand { script: s } => ShellOnHost { node: n, script: s } - RedfishAction { action: _ } => - IncompatibleCell { reason: "host_effect: RedfishAction is out-of-band (Redfish) and cannot target HostOs (in-band shell/systemd); target BmcController instead. Typed mismatch via total fold, DESIGN section 5." } - ConvergePlan { policy: p } => - if n.identity != p.identity { - IncompatibleCell { reason: converge_plan_identity_mismatch_reason(node: n, policy: p) } - } else { - ConvergeOnHost { node: n, host_converge: p } + match srv3_host_effect_script_for(effect: effect) { + Present { value: script } => ShellOnHost { node: n, script: script } + Absent => + match effect { + ShellCommand { script: s } => ShellOnHost { node: n, script: s } + RedfishAction { action: _ } => + IncompatibleCell { reason: "host_effect: RedfishAction is out-of-band (Redfish) and cannot target HostOs (in-band shell/systemd); target BmcController instead. Typed mismatch via total fold, DESIGN section 5." } + ConvergePlan { policy: p } => + if n.identity != p.identity { + IncompatibleCell { reason: converge_plan_identity_mismatch_reason(node: n, policy: p) } + } else { + ConvergeOnHost { node: n, host_converge: p } + } + Srv3InstallReconcileObserve { include_git_meta: _ } => srv3_host_effect_script_resolution_failed_cell() + DurableApprovalGrantRecord => srv3_host_effect_script_resolution_failed_cell() + DurableApprovalGrantRead => srv3_host_effect_script_resolution_failed_cell() + Srv3InstallMediaFetch => srv3_host_effect_script_resolution_failed_cell() + Srv3SeededInstallMediaRemaster => srv3_host_effect_script_resolution_failed_cell() + Srv3SeededInstallMediaToolchainEnsure => srv3_host_effect_script_resolution_failed_cell() + Srv3SolConsoleCapture => srv3_host_effect_script_resolution_failed_cell() + Srv3BmcwebSessionLogin => srv3_host_effect_script_resolution_failed_cell() + Srv3BmcwebTokenExtract => srv3_host_effect_script_resolution_failed_cell() + Srv3NbdProxyServe => srv3_host_effect_script_resolution_failed_cell() + Srv3BootOnceReadBackSleep => srv3_host_effect_script_resolution_failed_cell() + Srv3ReceiptEmit { lines: _ } => srv3_host_effect_script_resolution_failed_cell() } } BmcController { node: n } => - match effect { - ShellCommand { script: _ } => - IncompatibleCell { reason: "host_effect: ShellCommand is in-band (shell/systemd) and cannot target BmcController (out-of-band Redfish only); target HostOs instead. Typed mismatch via total fold, DESIGN section 5." } - RedfishAction { action: a } => RedfishOnBmc { node: n, action: a } - ConvergePlan { policy: _ } => - IncompatibleCell { reason: "host_effect: ConvergePlan is in-band (host OS converge) and cannot target BmcController (out-of-band Redfish only); target HostOs instead. Typed mismatch via total fold, DESIGN section 5." } + match srv3_host_effect_script_for(effect: effect) { + Present { value: _ } => + IncompatibleCell { reason: "host_effect: srv3 shell effects are in-band (actuator host LocalShell) and cannot target BmcController (out-of-band Redfish only); target HostOs instead. Typed mismatch via total fold, DESIGN section 5." } + Absent => + match effect { + ShellCommand { script: _ } => + IncompatibleCell { reason: "host_effect: ShellCommand is in-band (shell/systemd) and cannot target BmcController (out-of-band Redfish only); target HostOs instead. Typed mismatch via total fold, DESIGN section 5." } + RedfishAction { action: a } => RedfishOnBmc { node: n, action: a } + ConvergePlan { policy: _ } => + IncompatibleCell { reason: "host_effect: ConvergePlan is in-band (host OS converge) and cannot target BmcController (out-of-band Redfish only); target HostOs instead. Typed mismatch via total fold, DESIGN section 5." } + Srv3InstallReconcileObserve { include_git_meta: _ } => srv3_host_effect_script_resolution_failed_cell() + DurableApprovalGrantRecord => srv3_host_effect_script_resolution_failed_cell() + DurableApprovalGrantRead => srv3_host_effect_script_resolution_failed_cell() + Srv3InstallMediaFetch => srv3_host_effect_script_resolution_failed_cell() + Srv3SeededInstallMediaRemaster => srv3_host_effect_script_resolution_failed_cell() + Srv3SeededInstallMediaToolchainEnsure => srv3_host_effect_script_resolution_failed_cell() + Srv3SolConsoleCapture => srv3_host_effect_script_resolution_failed_cell() + Srv3BmcwebSessionLogin => srv3_host_effect_script_resolution_failed_cell() + Srv3BmcwebTokenExtract => srv3_host_effect_script_resolution_failed_cell() + Srv3NbdProxyServe => srv3_host_effect_script_resolution_failed_cell() + Srv3BootOnceReadBackSleep => srv3_host_effect_script_resolution_failed_cell() + Srv3ReceiptEmit { lines: _ } => srv3_host_effect_script_resolution_failed_cell() + } } } } diff --git a/dag/gunbc/srv3_actuate_shell_paths.dag b/dag/gunbc/srv3_actuate_shell_paths.dag new file mode 100644 index 00000000000..3eda1ccd960 --- /dev/null +++ b/dag/gunbc/srv3_actuate_shell_paths.dag @@ -0,0 +1,9 @@ +module gunbc.srv3_actuate_shell_paths + +import std.types { String, NonEmptyStr } + +data srv3_bmcweb_login_body_path: String = "/tmp/srv3_bmcweb_login_body.json" +data srv3_bmcweb_login_response_path: String = "/tmp/srv3_bmcweb_login_response.json" +data srv3_bmcweb_token_path: String = "/tmp/srv3_bmcweb_token" +data srv3_nbd_proxy_serve_script_path: String = "/tmp/srv3_nbd_proxy_serve.sh" +data srv3_sol_console_log_path: NonEmptyStr = "/var/lib/gunbc/logs/srv3-install-sol-console.log" as NonEmptyStr diff --git a/dag/gunbc/srv3_host_effect_apply.dag b/dag/gunbc/srv3_host_effect_apply.dag new file mode 100644 index 00000000000..273a340690e --- /dev/null +++ b/dag/gunbc/srv3_host_effect_apply.dag @@ -0,0 +1,172 @@ +module gunbc.srv3_host_effect_apply + +import gunbc.host_effect { + HostEffect, + NodeControlPlane, + HostOs, + HostEffectIntent, + HostEffectEvidence, + ShellEffectApplied, + ConvergePlanApplied, + Srv3InstallReconcileObserve, + DurableApprovalGrantRecord, + DurableApprovalGrantRead, + Srv3InstallMediaFetch, + Srv3SeededInstallMediaRemaster, + Srv3SeededInstallMediaToolchainEnsure, + Srv3SolConsoleCapture, + Srv3BmcwebSessionLogin, + Srv3BmcwebTokenExtract, + Srv3NbdProxyServe, + Srv3BootOnceReadBackSleep, + Srv3ReceiptEmit, + host_effect_read_evidence, + host_effect_identity_evidence, + LocalShell, +} +import gunbc.host_effect_realize { host_effect_apply } +import gunbc.os_install_actuator_selection { srv3_os_install_actuator_host } +import std.realization_reconcile { + Reconciliation, + Converged, + NotConverged, + reconciliation_converged, +} +import std.process { ProcessExit, ExitSuccess, exit_failure } +import std.types { String, List } + +type Srv3TypedReceipt { + lines: List +} + +fn srv3_actuator_target() -> NodeControlPlane { + HostOs { node: srv3_os_install_actuator_host } +} + +fn srv3_host_effect_apply( + effect: HostEffect, +) -> Reconciliation { + host_effect_apply( + target: srv3_actuator_target(), + effect: effect, + evidence: host_effect_identity_evidence, + transport: LocalShell, + ) +} + +fn srv3_host_effect_observe_apply( + include_git_meta: Bool, +) -> Reconciliation { + srv3_host_effect_apply( + effect: Srv3InstallReconcileObserve { include_git_meta: include_git_meta }, + ) +} + +fn srv3_host_effect_observe_read_apply() -> Reconciliation { + host_effect_apply( + target: srv3_actuator_target(), + effect: Srv3InstallReconcileObserve { include_git_meta: true }, + evidence: host_effect_read_evidence, + transport: LocalShell, + ) +} + +fn srv3_durable_approval_grant_record_apply() -> Reconciliation { + srv3_host_effect_apply(effect: DurableApprovalGrantRecord) +} + +fn srv3_durable_approval_grant_read_apply() -> Reconciliation { + host_effect_apply( + target: srv3_actuator_target(), + effect: DurableApprovalGrantRead, + evidence: host_effect_read_evidence, + transport: LocalShell, + ) +} + +fn srv3_install_media_fetch_apply() -> Reconciliation { + srv3_host_effect_apply(effect: Srv3InstallMediaFetch) +} + +fn srv3_seeded_install_media_toolchain_ensure_apply() -> Reconciliation { + srv3_host_effect_apply(effect: Srv3SeededInstallMediaToolchainEnsure) +} + +fn srv3_seeded_install_media_remaster_apply() -> Reconciliation { + srv3_host_effect_apply(effect: Srv3SeededInstallMediaRemaster) +} + +fn srv3_sol_console_capture_apply() -> Reconciliation { + srv3_host_effect_apply(effect: Srv3SolConsoleCapture) +} + +fn srv3_bmcweb_session_login_apply() -> Reconciliation { + srv3_host_effect_apply(effect: Srv3BmcwebSessionLogin) +} + +fn srv3_bmcweb_token_extract_apply() -> Reconciliation { + srv3_host_effect_apply(effect: Srv3BmcwebTokenExtract) +} + +fn srv3_nbd_proxy_serve_apply() -> Reconciliation { + srv3_host_effect_apply(effect: Srv3NbdProxyServe) +} + +fn srv3_boot_once_read_back_sleep_apply() -> Reconciliation { + srv3_host_effect_apply(effect: Srv3BootOnceReadBackSleep) +} + +fn srv3_typed_receipt(receipt: Srv3TypedReceipt) -> Srv3TypedReceipt { + receipt +} + +fn srv3_typed_receipt_from_lines(lines: List) -> Srv3TypedReceipt { + Srv3TypedReceipt { lines: lines } +} + +fn srv3_receipt_emit_apply(receipt: Srv3TypedReceipt) -> Reconciliation { + srv3_host_effect_apply(effect: Srv3ReceiptEmit { lines: receipt.lines }) +} + +fn srv3_shell_stdout_from_apply( + r: Reconciliation, +) -> String? { + match r { + Converged { evidence: e, applied: _ } => + match e { + ShellEffectApplied { stdout: out, exit_code: _ } => Present { value: out } + ConvergePlanApplied { stdout: _, exit_code: _ } => none + } + NotConverged { reason: _, applied: _ } => none + } +} + +fn srv3_reconciliation_failure_reason( + r: Reconciliation, +) -> String? { + match r { + Converged { evidence: _, applied: _ } => none + NotConverged { reason: why, applied: _ } => Present { value: why } + } +} + +fn srv3_reconciliation_to_process_exit( + r: Reconciliation, + failure_prefix: String, +) -> ProcessExit { + if reconciliation_converged(r: r) { + ExitSuccess + } else { + match srv3_reconciliation_failure_reason(r: r) { + Present { value: why } => exit_failure(reason: concat(failure_prefix, why)) + Absent => exit_failure(reason: concat(failure_prefix, "host_effect_apply failed")) + } + } +} + +fn srv3_emit_typed_receipt(receipt: Srv3TypedReceipt) -> ProcessExit { + srv3_reconciliation_to_process_exit( + r: srv3_receipt_emit_apply(receipt: receipt), + failure_prefix: "srv3-receipt-emit: ", + ) +} diff --git a/dag/gunbc/srv3_host_effect_script.dag b/dag/gunbc/srv3_host_effect_script.dag index 0aee740c5d7..3ab7cd45dfd 100644 --- a/dag/gunbc/srv3_host_effect_script.dag +++ b/dag/gunbc/srv3_host_effect_script.dag @@ -21,22 +21,26 @@ import gunbc.host_effect { import gunbc.shell_bash_runner { shell_exec_via_bash, shell_printf_line } import gunbc.srv3_install_diagnostic_checklist { srv3_install_diagnostic_observe_script } import gunbc.srv3_os_install_diagnostic { srv3_os_install_attempt_intent } -import gunbc.srv3_os_install_reconcile { srv3_install_reconcile_observe_meta_script } import gunbc.srv3_os_install_reconcile_receipt { srv3_install_record_boot_once_approval_script } import gunbc.srv3_os_install_reconcile_types { srv3_boot_once_approval_grant_path, srv3_boot_once_apply_read_back_sleep, } +import gunbc.srv3_actuate_shell_paths { + srv3_bmcweb_login_body_path, + srv3_bmcweb_login_response_path, + srv3_bmcweb_token_path, + srv3_nbd_proxy_serve_script_path, + srv3_sol_console_log_path, +} import gunbc.srv3_seeded_install_media_artifact { srv3_seeded_install_media_artifact } import extdeps.os.ubuntu_install_media { noble_numbat_2404_3_live_server_arm64 } import extdeps.os.ubuntu_install_media_fetch { install_media_fetch_script } import extdeps.os.ubuntu_seeded_install_media_remaster { install_media_remaster_script } import extdeps.bmc.serial_console { - SolConsoleCaptureIntent, - ObmcConsoleCli, sol_console_capture_script, } -import extdeps.boards.asrock_rack { asrock_srv3_live_bmc_host } +import gunbc.srv3_sol_console_intent { srv3_sol_console_capture_intent } import extdeps.bmc.webui.nbd_proxy_serve { NbdProxyServeTransportIntent, bmcweb_session_login_program, @@ -51,7 +55,6 @@ import extdeps.boards.asrock_rack { asrock_srv3_live_bmc_host, asrock_altrad8ud_openbmc_2_07_00_nbd_proxy_primary_endpoint, } -import extdeps.languages.bash.program { serialize_bash } import gunbc.srv3_os_install_actuate_scope { srv3_actuator_virtual_media_iso_install_path } import gunbc.ci_deploy_access { ci_deploy_srv1_access, @@ -75,16 +78,10 @@ data srv3_host_effect_script_scaffold: Disposition = Scaffold { } } -data srv3_bmcweb_login_body_path: String = "/tmp/srv3_bmcweb_login_body.json" -data srv3_bmcweb_login_response_path: String = "/tmp/srv3_bmcweb_login_response.json" -data srv3_bmcweb_token_path: String = "/tmp/srv3_bmcweb_token" -data srv3_nbd_proxy_serve_script_path: String = "/tmp/srv3_nbd_proxy_serve.sh" -data srv3_sol_console_log_path: NonEmptyStr = "/var/lib/gunbc/logs/srv3-install-sol-console.log" as NonEmptyStr - -data srv3_sol_console_capture_intent: SolConsoleCaptureIntent = SolConsoleCaptureIntent { - bmc_host: asrock_srv3_live_bmc_host, - transport: ObmcConsoleCli, - output_path: srv3_sol_console_log_path, +fn srv3_install_reconcile_observe_meta_script() -> String { + concat( + "printf '%s\\n' \"Srv3InstallObserveReceipt: surface=observed_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\nprintf '%s\\n' \"Srv3InstallObserveReceipt: surface=git branch=$(git branch --show-current 2>/dev/null || echo unknown) commit=$(git rev-parse --short HEAD 2>/dev/null || echo unknown)\"\n", + ) } fn install_media_remaster_toolchain_ensure_script() -> String { diff --git a/dag/gunbc/srv3_install_diagnostic_checklist.dag b/dag/gunbc/srv3_install_diagnostic_checklist.dag index 770eb2a95b3..6225b61df1f 100644 --- a/dag/gunbc/srv3_install_diagnostic_checklist.dag +++ b/dag/gunbc/srv3_install_diagnostic_checklist.dag @@ -3,7 +3,7 @@ module gunbc.srv3_install_diagnostic_checklist import extdeps.shell import extdeps.filesystem.filesystem_io { Filesystem } import gunbc.shell_bash_runner { shell_exec_via_bash, shell_printf_line } -import gunbc.srv3_os_install_actuate { srv3_bmcweb_token_path } +import gunbc.srv3_actuate_shell_paths { srv3_bmcweb_token_path } import gunbc.srv3_os_install_diagnostic { srv3_os_install_attempt_intent, OsInstallAttemptIntent, diff --git a/dag/gunbc/srv3_install_media_fetch.dag b/dag/gunbc/srv3_install_media_fetch.dag index 392a4ab3a7d..6a6f788441e 100644 --- a/dag/gunbc/srv3_install_media_fetch.dag +++ b/dag/gunbc/srv3_install_media_fetch.dag @@ -1,6 +1,5 @@ module gunbc.srv3_install_media_fetch -import extdeps.shell import extdeps.os.ubuntu_install_media { UbuntuInstallMediaArtifactRow, UbuntuInstallMediaMirrorHost, @@ -11,12 +10,14 @@ import extdeps.os.ubuntu_install_media { ubuntu_install_media_mirror_url, } import extdeps.os.ubuntu_install_media_fetch { - install_media_fetch_script, install_media_fetch_bash_emit_dissolution_trigger, } import gunbc.os_install_actuator_selection { srv3_os_install_actuator_host } import gunbc.fleet_intent { srv1_host, ComputeHost } -import gunbc.shell_bash_runner { shell_exec_via_bash } +import gunbc.srv3_host_effect_apply { + srv3_install_media_fetch_apply, + srv3_reconciliation_to_process_exit, +} import std.effects { EffectShape, CreateEffect, @@ -132,13 +133,10 @@ fn srv3_install_media_fetch_actuator_host() -> ComputeHost { func srv3_install_media_fetch() -> ProcessExit uses net: Network { - let script = install_media_fetch_script(artifact: srv3_ubuntu_install_media_artifact) - let run = shell.Exec.Run(script: shell_exec_via_bash(body: script)) - if run.success { - ExitSuccess - } else { - exit_failure(reason: concat("install-media-fetch: ", run.stderr)) - } + srv3_reconciliation_to_process_exit( + r: srv3_install_media_fetch_apply(), + failure_prefix: "install-media-fetch: ", + ) } fn srv3_install_media_fetch_install_path_is_versioned() -> Bool { diff --git a/dag/gunbc/srv3_os_install_actuate.dag b/dag/gunbc/srv3_os_install_actuate.dag index dafeb696ae6..1498f901d54 100644 --- a/dag/gunbc/srv3_os_install_actuate.dag +++ b/dag/gunbc/srv3_os_install_actuate.dag @@ -29,7 +29,18 @@ import gunbc.srv3_bmc_credential_resolve { UseFactoryCredential, UseRotatedSecretRef, } -import gunbc.shell_bash_runner { shell_exec_via_bash } +import gunbc.srv3_host_effect_apply { + srv3_bmcweb_session_login_apply, + srv3_bmcweb_token_extract_apply, + srv3_nbd_proxy_serve_apply, + srv3_reconciliation_to_process_exit, +} +import gunbc.srv3_actuate_shell_paths { + srv3_bmcweb_login_body_path, + srv3_bmcweb_login_response_path, + srv3_bmcweb_token_path, + srv3_nbd_proxy_serve_script_path, +} import gunbc.nbd_proxy_virtual_media_install { srv3_os_install_actuator_plan, os_install_actuator_step_count, @@ -53,11 +64,6 @@ data srv3_os_install_actuate_shell_runner_scaffold: Disposition = Scaffold { } } -data srv3_bmcweb_login_body_path: String = "/tmp/srv3_bmcweb_login_body.json" -data srv3_bmcweb_login_response_path: String = "/tmp/srv3_bmcweb_login_response.json" -data srv3_bmcweb_token_path: String = "/tmp/srv3_bmcweb_token" -data srv3_nbd_proxy_serve_script_path: String = "/tmp/srv3_nbd_proxy_serve.sh" - data srv3_os_install_actuate_operator_runbook: NonEmptyStr = "docs/runbooks/srv3-os-install-actuate.md" fn srv3_nbd_proxy_serve_intent() -> NbdProxyServeTransportIntent { @@ -135,27 +141,22 @@ func srv3_bmcweb_session_login_resolved(resolution: BmcCredentialResolution) -> return exit_failure(reason: concat("bmcweb-login: body write failed: ", body_write.error)) } - let login_prog = bmcweb_session_login_program( - bmc_host: asrock_srv3_live_bmc_host, - request_body_file: srv3_bmcweb_login_body_path as NonEmptyStr, - response_path: srv3_bmcweb_login_response_path as NonEmptyStr, + let login_exit = srv3_reconciliation_to_process_exit( + r: srv3_bmcweb_session_login_apply(), + failure_prefix: "bmcweb-login: curl login failed: ", ) - let login_script = serialize_bash(p: login_prog) - let login_run = shell.Exec.Run(script: shell_exec_via_bash(body: login_script)) - if login_run.success == false { - return exit_failure(reason: concat("bmcweb-login: curl login failed: ", login_run.stderr)) + match login_exit { + ExitSuccess => {} + _ => return login_exit } - let extract_script = concat( - concat( - concat("jq -r '.token // empty' ", srv3_bmcweb_login_response_path), - " > ", - ), - srv3_bmcweb_token_path, + let extract_exit = srv3_reconciliation_to_process_exit( + r: srv3_bmcweb_token_extract_apply(), + failure_prefix: "bmcweb-login: token extract failed: ", ) - let extract_run = shell.Exec.Run(script: shell_exec_via_bash(body: extract_script)) - if extract_run.success == false { - return exit_failure(reason: concat("bmcweb-login: token extract failed: ", extract_run.stderr)) + match extract_exit { + ExitSuccess => {} + _ => return extract_exit } let verify_run = shell.Exec.Run( @@ -177,19 +178,10 @@ func srv3_nbd_proxy_serve() -> ProcessExit return exit_failure(reason: concat("nbd-proxy-serve: script write failed: ", write.error)) } - let serve_script = concat( - concat( - concat("export BMCWEB_SESSION_TOKEN=$(cat ", srv3_bmcweb_token_path), - "); bash ", - ), - srv3_nbd_proxy_serve_script_path, + srv3_reconciliation_to_process_exit( + r: srv3_nbd_proxy_serve_apply(), + failure_prefix: "nbd-proxy-serve: foreground pipeline exited: ", ) - let serve_run = shell.Exec.Run(script: shell_exec_via_bash(body: serve_script)) - if serve_run.success { - ExitSuccess - } else { - exit_failure(reason: concat("nbd-proxy-serve: foreground pipeline exited: ", serve_run.stderr)) - } } fn srv3_os_install_actuator_step_count_is_three() -> Bool { diff --git a/dag/gunbc/srv3_os_install_actuate_scope.dag b/dag/gunbc/srv3_os_install_actuate_scope.dag index acec196c7ab..7de2803a396 100644 --- a/dag/gunbc/srv3_os_install_actuate_scope.dag +++ b/dag/gunbc/srv3_os_install_actuate_scope.dag @@ -6,7 +6,7 @@ import gunbc.os_install { srv3_os_install_plan_autoinstall, fleet_install_server_specs, } -import gunbc.srv3_seeded_install_media { srv3_seeded_ubuntu_install_media_install_path } +import gunbc.srv3_seeded_install_media_artifact { srv3_seeded_ubuntu_install_media_install_path } import gunbc.assimilate.bmc_wif_delegation_chain { bmc_srv3_admin_secret_ref } import gunbc.assimilate.bmc_token_federation { bmc_srv3_secret_name } import gunbc.os_install_mechanism { srv3_install_mechanism, NbdProxyVirtualMediaInstall } diff --git a/dag/gunbc/srv3_os_install_diagnostic.dag b/dag/gunbc/srv3_os_install_diagnostic.dag index 1d9ce9b714f..98353c8f2c1 100644 --- a/dag/gunbc/srv3_os_install_diagnostic.dag +++ b/dag/gunbc/srv3_os_install_diagnostic.dag @@ -38,10 +38,11 @@ import gunbc.network_identity_subsumption { srv3_bmc_static_lease_row, } import gunbc.srv3_os_install_actuate_scope { srv3_actuator_virtual_media_iso_install_path } -import gunbc.srv3_seeded_install_media { srv3_seeded_install_media_content_hash } -import product.placement_supply { HostIdentity } -import gunbc.srv3_os_install_actuate { srv3_bmcweb_token_path } -import gunbc.srv3_sol_console_capture { srv3_sol_console_log_path } +import gunbc.srv3_seeded_install_media_artifact { srv3_seeded_install_media_content_hash } +import gunbc.srv3_actuate_shell_paths { + srv3_bmcweb_token_path, + srv3_sol_console_log_path, +} data srv3_nbd_proxy_local_port: Int = 10809 diff --git a/dag/gunbc/srv3_os_install_reconcile.dag b/dag/gunbc/srv3_os_install_reconcile.dag index ad5b7f704a8..17f82f96183 100644 --- a/dag/gunbc/srv3_os_install_reconcile.dag +++ b/dag/gunbc/srv3_os_install_reconcile.dag @@ -100,7 +100,7 @@ import std.disposition { Disposition, Scaffold, SingleAuthority } import std.decl_ref { DeclarationRef, WholeDeclaration } import extdeps.bmc.types { BootOverrideDisabled, BootTargetNone, BootOverrideOnce, BootTargetCd } import gunbc.fleet_intent_network { operator_host_srv1, operator_host_srv3 } -import gunbc.srv3_seeded_install_media { srv3_seeded_install_media_content_hash } +import gunbc.srv3_seeded_install_media_artifact { srv3_seeded_install_media_content_hash } import gunbc.srv3_os_install_actuate_workflow { srv3_boot_once_cd_escalation } import extdeps.bmc.webui.nbd_proxy_serve { srv3_nbd_proxy_local_port } import gunbc.srv3_os_install_reconcile_types { diff --git a/dag/gunbc/srv3_os_install_reconcile_apply.dag b/dag/gunbc/srv3_os_install_reconcile_apply.dag index 94d2a9be108..ebdf16ea078 100644 --- a/dag/gunbc/srv3_os_install_reconcile_apply.dag +++ b/dag/gunbc/srv3_os_install_reconcile_apply.dag @@ -1,16 +1,20 @@ module gunbc.srv3_os_install_reconcile_apply -import extdeps.shell -import gunbc.shell_bash_runner { shell_exec_via_bash, shell_printf_line } +import gunbc.srv3_host_effect_apply { + srv3_host_effect_observe_read_apply, + srv3_durable_approval_grant_read_apply, + srv3_boot_once_read_back_sleep_apply, + srv3_emit_typed_receipt, + srv3_typed_receipt_from_lines, + srv3_shell_stdout_from_apply, + srv3_reconciliation_failure_reason, + srv3_reconciliation_to_process_exit, +} import std.process { ProcessExit, ExitSuccess, exit_failure } -import std.resources { Network } import std.types { NonEmptyStr, Timestamp } import std.disposition { Disposition, Scaffold, SingleAuthority } import std.decl_ref { DeclarationRef, WholeDeclaration } -import std.measure { second_count } -import gunbc.srv3_install_diagnostic_checklist { - srv3_install_diagnostic_observe_script, -} +import std.resources { Network } import gunbc.srv3_os_install_diagnostic { srv3_os_install_attempt_intent, parse_srv3_install_debug_observation, @@ -23,12 +27,9 @@ import gunbc.srv3_os_install_reconcile { workflow_approval_from_durable_grant, boot_once_apply_read_back_ok, consumed_grant_after_boot_once, - srv3_install_reconcile_observe_meta_script, } import gunbc.srv3_os_install_reconcile_types { srv3_install_reconcile_default_disk, - srv3_boot_once_approval_grant_path, - srv3_boot_once_apply_read_back_sleep, } import gunbc.srv3_os_install_reconcile_receipt { srv3_install_reconcile_full_receipt, @@ -56,17 +57,6 @@ data srv3_os_install_reconcile_apply_shell_runner_scaffold: Disposition = Scaffo } } -fn load_durable_approval_grant_script() -> String { - concat("cat ", concat(srv3_boot_once_approval_grant_path, " 2>/dev/null || true")) -} - -fn srv3_install_reconcile_run_observe_script() -> String { - concat( - srv3_install_diagnostic_observe_script(intent: srv3_os_install_attempt_intent), - srv3_install_reconcile_observe_meta_script(), - ) -} - fn process_exit_succeeded(exit: ProcessExit) -> Bool { match exit { ExitSuccess => true @@ -74,35 +64,40 @@ fn process_exit_succeeded(exit: ProcessExit) -> Bool { } } -fn sleep_for_boot_once_read_back_script() -> String { - concat("sleep ", to_string(second_count(s: srv3_boot_once_apply_read_back_sleep))) -} - func srv3_os_install_reconcile_apply() -> ProcessExit uses net: Network { let intent = srv3_os_install_attempt_intent - let grant_run = shell.Exec.Run(script: shell_exec_via_bash(body: load_durable_approval_grant_script())) - if !grant_run.success { - return exit_failure(reason: concat("srv3-install-reconcile-apply: read grant: ", grant_run.stderr)) + let grant_r = srv3_durable_approval_grant_read_apply() + let grant_stdout = match srv3_shell_stdout_from_apply(r: grant_r) { + Present { value: out } => out + Absent => + return match srv3_reconciliation_failure_reason(r: grant_r) { + Present { value: why } => exit_failure(reason: concat("srv3-install-reconcile-apply: read grant: ", why)) + Absent => exit_failure(reason: "srv3-install-reconcile-apply: read grant: host_effect_apply failed") + } } - let grant = parse_durable_approval_grant_file(contents: grant_run.stdout) + let grant = parse_durable_approval_grant_file(contents: grant_stdout) if !durable_grant_is_active(grant: grant) { return exit_failure(reason: "srv3-install-reconcile-apply: durable approval grant missing or invalid") } - let pre_observe_script = srv3_install_reconcile_run_observe_script() - let pre_observe = shell.Exec.Run(script: shell_exec_via_bash(body: pre_observe_script)) - if !pre_observe.success { - return exit_failure(reason: concat("srv3-install-reconcile-apply: pre-observe: ", pre_observe.stderr)) + let pre_observe_r = srv3_host_effect_observe_read_apply() + let pre_observe_stdout = match srv3_shell_stdout_from_apply(r: pre_observe_r) { + Present { value: out } => out + Absent => + return match srv3_reconciliation_failure_reason(r: pre_observe_r) { + Present { value: why } => exit_failure(reason: concat("srv3-install-reconcile-apply: pre-observe: ", why)) + Absent => exit_failure(reason: "srv3-install-reconcile-apply: pre-observe: host_effect_apply failed") + } } - let pre_observed_at = match parse_observed_at_from_stdout(stdout: pre_observe.stdout) { + let pre_observed_at = match parse_observed_at_from_stdout(stdout: pre_observe_stdout) { Present { value: t } => t as Timestamp Absent => return exit_failure(reason: "srv3-install-reconcile-apply: pre-observe missing surface=observed_at=") } let pre_observation = parse_srv3_install_debug_observation( - stdout: pre_observe.stdout, + stdout: pre_observe_stdout, intent: intent, ) let pre_result = reconcile_srv3_os_install( @@ -132,22 +127,30 @@ func srv3_os_install_reconcile_apply() -> ProcessExit return exit_failure(reason: "srv3-install-reconcile-apply: boot-once-cd apply failed") } - let sleep_run = shell.Exec.Run(script: shell_exec_via_bash(body: sleep_for_boot_once_read_back_script())) - if !sleep_run.success { - return exit_failure(reason: concat("srv3-install-reconcile-apply: read-back sleep: ", sleep_run.stderr)) + let sleep_exit = srv3_reconciliation_to_process_exit( + r: srv3_boot_once_read_back_sleep_apply(), + failure_prefix: "srv3-install-reconcile-apply: read-back sleep: ", + ) + if !process_exit_succeeded(exit: sleep_exit) { + return sleep_exit } - let post_observe = shell.Exec.Run(script: shell_exec_via_bash(body: pre_observe_script)) - if !post_observe.success { - return exit_failure(reason: concat("srv3-install-reconcile-apply: post-observe: ", post_observe.stderr)) + let post_observe_r = srv3_host_effect_observe_read_apply() + let post_observe_stdout = match srv3_shell_stdout_from_apply(r: post_observe_r) { + Present { value: out } => out + Absent => + return match srv3_reconciliation_failure_reason(r: post_observe_r) { + Present { value: why } => exit_failure(reason: concat("srv3-install-reconcile-apply: post-observe: ", why)) + Absent => exit_failure(reason: "srv3-install-reconcile-apply: post-observe: host_effect_apply failed") + } } - let post_observed_at = match parse_observed_at_from_stdout(stdout: post_observe.stdout) { + let post_observed_at = match parse_observed_at_from_stdout(stdout: post_observe_stdout) { Present { value: t } => t as Timestamp Absent => return exit_failure(reason: "srv3-install-reconcile-apply: post-observe missing surface=observed_at=") } let post_observation = parse_srv3_install_debug_observation( - stdout: post_observe.stdout, + stdout: post_observe_stdout, intent: intent, ) let read_back_ok = boot_once_apply_read_back_ok( @@ -192,13 +195,11 @@ func srv3_os_install_reconcile_apply() -> ProcessExit apply_succeeded: apply_succeeded, read_back_ok: read_back_ok, ) - let echo_body = concat( - shell_printf_line(line: apply_receipt), - shell_printf_line(line: dry_receipt), + let echo_exit = srv3_emit_typed_receipt( + receipt: srv3_typed_receipt_from_lines(lines: [apply_receipt, dry_receipt]), ) - let echo_run = shell.Exec.Run(script: shell_exec_via_bash(body: echo_body)) - if !echo_run.success { - return exit_failure(reason: concat("srv3-install-reconcile-apply-receipt: ", echo_run.stderr)) + if !process_exit_succeeded(exit: echo_exit) { + return echo_exit } if !read_back_ok { return exit_failure(reason: "srv3-install-reconcile-apply: read-back failed after boot-once-cd") diff --git a/dag/gunbc/srv3_os_install_reconcile_dry_run.dag b/dag/gunbc/srv3_os_install_reconcile_dry_run.dag index c916b62ac7d..1b90ab53218 100644 --- a/dag/gunbc/srv3_os_install_reconcile_dry_run.dag +++ b/dag/gunbc/srv3_os_install_reconcile_dry_run.dag @@ -1,14 +1,16 @@ module gunbc.srv3_os_install_reconcile_dry_run -import extdeps.shell -import gunbc.shell_bash_runner { shell_exec_via_bash, shell_printf_line } +import gunbc.srv3_host_effect_apply { + srv3_host_effect_observe_read_apply, + srv3_emit_typed_receipt, + srv3_typed_receipt_from_lines, + srv3_shell_stdout_from_apply, + srv3_reconciliation_failure_reason, +} import std.process { ProcessExit, ExitSuccess, exit_failure } import std.types { String, NonEmptyStr } import std.disposition { Disposition, Scaffold, SingleAuthority } import std.decl_ref { DeclarationRef, WholeDeclaration } -import gunbc.srv3_install_diagnostic_checklist { - srv3_install_diagnostic_observe_script, -} import gunbc.srv3_os_install_diagnostic { srv3_os_install_attempt_intent, parse_srv3_install_debug_observation, @@ -16,7 +18,6 @@ import gunbc.srv3_os_install_diagnostic { import gunbc.srv3_os_install_reconcile { reconcile_srv3_os_install, srv3_held_lease_from_serve, - srv3_install_reconcile_observe_meta_script, } import gunbc.srv3_os_install_reconcile_types { srv3_install_reconcile_default_disk, @@ -37,10 +38,6 @@ data srv3_os_install_reconcile_dry_run_shell_runner_scaffold: Disposition = Scaf } } -fn srv3_install_reconcile_git_meta_script() -> String { - srv3_install_reconcile_observe_meta_script() -} - fn parse_reconcile_git_branch(stdout: String) -> NonEmptyStr { let parts = split(s: stdout, delimiter: "surface=git branch=") if parts.length() <= 1 { @@ -65,16 +62,17 @@ fn parse_reconcile_git_commit(stdout: String) -> NonEmptyStr { func srv3_os_install_reconcile_dry_run() -> ProcessExit { let intent = srv3_os_install_attempt_intent - let observe_script = concat( - srv3_install_diagnostic_observe_script(intent: intent), - srv3_install_reconcile_git_meta_script(), - ) - let observe_run = shell.Exec.Run(script: shell_exec_via_bash(body: observe_script)) - if !observe_run.success { - return exit_failure(reason: concat("srv3-install-reconcile-observe: ", observe_run.stderr)) + let observe_r = srv3_host_effect_observe_read_apply() + let observe_stdout = match srv3_shell_stdout_from_apply(r: observe_r) { + Present { value: out } => out + Absent => + return match srv3_reconciliation_failure_reason(r: observe_r) { + Present { value: why } => exit_failure(reason: concat("srv3-install-reconcile-observe: ", why)) + Absent => exit_failure(reason: "srv3-install-reconcile-observe: host_effect_apply failed") + } } let observation = parse_srv3_install_debug_observation( - stdout: observe_run.stdout, + stdout: observe_stdout, intent: intent, ) let grant = GrantAbsent @@ -88,8 +86,8 @@ func srv3_os_install_reconcile_dry_run() -> ProcessExit { prior: prior, ) let lease = srv3_held_lease_from_serve(serve: observation.actuator_serve) - let git_branch = parse_reconcile_git_branch(stdout: observe_run.stdout) - let git_commit = parse_reconcile_git_commit(stdout: observe_run.stdout) + let git_branch = parse_reconcile_git_branch(stdout: observe_stdout) + let git_commit = parse_reconcile_git_commit(stdout: observe_stdout) let receipt = srv3_install_reconcile_full_receipt( result: result, intent: intent, @@ -101,14 +99,7 @@ func srv3_os_install_reconcile_dry_run() -> ProcessExit { git_commit: git_commit, ) let action = srv3_install_reconcile_action_line(result: result) - let echo_body = concat( - shell_printf_line(line: receipt), - shell_printf_line(line: action), + srv3_emit_typed_receipt( + receipt: srv3_typed_receipt_from_lines(lines: [receipt, action]), ) - let echo_run = shell.Exec.Run(script: shell_exec_via_bash(body: echo_body)) - if echo_run.success { - ExitSuccess - } else { - return exit_failure(reason: concat("srv3-install-reconcile-receipt: ", echo_run.stderr)) - } } diff --git a/dag/gunbc/srv3_os_install_reconcile_record_approval.dag b/dag/gunbc/srv3_os_install_reconcile_record_approval.dag index cb4a9189d09..ff41b08a890 100644 --- a/dag/gunbc/srv3_os_install_reconcile_record_approval.dag +++ b/dag/gunbc/srv3_os_install_reconcile_record_approval.dag @@ -1,16 +1,12 @@ module gunbc.srv3_os_install_reconcile_record_approval -import extdeps.shell -import gunbc.shell_bash_runner { shell_exec_via_bash } +import gunbc.srv3_host_effect_apply { + srv3_durable_approval_grant_record_apply, + srv3_reconciliation_to_process_exit, +} import std.process { ProcessExit, ExitSuccess, exit_failure } import std.disposition { Disposition, Scaffold, SingleAuthority } import std.decl_ref { DeclarationRef, WholeDeclaration } -import gunbc.srv3_os_install_reconcile_types { - srv3_boot_once_approval_grant_path, -} -import gunbc.srv3_os_install_reconcile_receipt { - srv3_install_record_boot_once_approval_script, -} data srv3_os_install_reconcile_record_approval_shell_runner_scaffold: Disposition = Scaffold { dissolves_to: SingleAuthority, @@ -22,11 +18,8 @@ data srv3_os_install_reconcile_record_approval_shell_runner_scaffold: Dispositio } func srv3_os_install_record_boot_once_approval() -> ProcessExit { - let script = srv3_install_record_boot_once_approval_script() - let run = shell.Exec.Run(script: shell_exec_via_bash(body: script)) - if run.success { - ExitSuccess - } else { - return exit_failure(reason: concat("srv3-install-record-approval: ", run.stderr)) - } + srv3_reconciliation_to_process_exit( + r: srv3_durable_approval_grant_record_apply(), + failure_prefix: "srv3-install-record-approval: ", + ) } diff --git a/dag/gunbc/srv3_os_install_reconcile_types.dag b/dag/gunbc/srv3_os_install_reconcile_types.dag index 7b31fb9ca43..1f3636e34e3 100644 --- a/dag/gunbc/srv3_os_install_reconcile_types.dag +++ b/dag/gunbc/srv3_os_install_reconcile_types.dag @@ -11,7 +11,7 @@ import gunbc.os_install_deduction { OsInstallRuntimeDiagnosticVerdict, } import gunbc.srv3_os_install_diagnostic { Srv3InstallDiagnostic } -import gunbc.srv3_seeded_install_media { srv3_seeded_install_media_content_hash } +import gunbc.srv3_seeded_install_media_artifact { srv3_seeded_install_media_content_hash } type DeductionRuntimeHint = DeductionRuntimeAbsent diff --git a/dag/gunbc/srv3_seeded_install_media.dag b/dag/gunbc/srv3_seeded_install_media.dag index 6764a5a8a7b..f7d9880bf0e 100644 --- a/dag/gunbc/srv3_seeded_install_media.dag +++ b/dag/gunbc/srv3_seeded_install_media.dag @@ -1,15 +1,6 @@ module gunbc.srv3_seeded_install_media -import extdeps.os.ubuntu_install_media { noble_numbat_2404_3_live_server_arm64 } -import extdeps.os.ubuntu_seeded_install_media { - UbuntuSeededInstallMediaArtifactRow, - ubuntu_seeded_install_media_default_nocloud_dir, - ubuntu_seeded_install_media_grub_kernel_cmdline, - seeded_install_media_content_hash, - ubuntu_seeded_install_media_install_path, -} import extdeps.os.ubuntu_seeded_install_media_remaster { - install_media_remaster_script, install_media_remaster_script_applies_grub_cmdline, install_media_remaster_bash_emit_dissolution_trigger, } @@ -17,25 +8,27 @@ import extdeps.os.ubuntu_seeded_install_media_toolchain { install_media_remaster_toolchain_requirement, install_media_remaster_toolchain_is_complete, } -import extdeps.tools.xorriso { xorriso_cli_tool } -import extdeps.apt { package_xorriso } import extdeps.os.ubuntu_autoinstall { NoCloudLocal, NoCloudNet } import gunbc.os_install { srv3_ubuntu_autoinstall_on_iso } -import gunbc.os_install_emit { autoinstall_user_data } -import gunbc.shell_bash_runner { shell_exec_via_bash } +import gunbc.srv3_seeded_install_media_artifact { + srv3_seeded_install_media_artifact, + srv3_seeded_ubuntu_install_media_install_path, + srv3_seeded_grub_kernel_cmdline, + srv3_seeded_install_media_content_hash, + srv3_autoinstall_user_data_body, +} +import gunbc.srv3_host_effect_apply { + srv3_seeded_install_media_toolchain_ensure_apply, + srv3_seeded_install_media_remaster_apply, + srv3_reconciliation_to_process_exit, +} +import gunbc.srv3_host_effect_script { install_media_remaster_toolchain_ensure_script } import gunbc.os_install_actuator_selection { srv3_os_install_actuator_host } import gunbc.fleet_intent { srv1_host, ComputeHost } -import gunbc.ci_deploy_access { - ci_deploy_srv1_access, - deploy_access_apt_get_privileged_command, - deploy_access_preflight_check_block, - deploy_access_sudo_n_apt_get_install_command, -} -import extdeps.shell import std.disposition { Disposition, RealizationDispatch, Scaffold, SingleAuthority, Terminal } import std.decl_ref { DeclarationRef, WholeDeclaration } import std.process { ProcessExit, ExitSuccess, exit_failure } -import std.types { NonEmptyStr, String, Bool, ContentHash } +import std.types { NonEmptyStr, String, Bool } import std.resources { Network } data gunbc_srv3_seeded_install_media_scope_disposition: Disposition = Terminal { @@ -54,8 +47,8 @@ data srv3_seeded_install_media_remaster_scaffold: Disposition = Scaffold { data srv3_seeded_install_media_toolchain_ensure_scaffold: Disposition = Scaffold { dissolves_to: SingleAuthority, bind: DeclarationRef { - module_path: "gunbc.srv3_seeded_install_media", - decl_name: "install_media_remaster_toolchain_ensure_dissolution_trigger", + module_path: "gunbc.srv3_host_effect_script", + decl_name: "install_media_remaster_toolchain_ensure_script", field: WholeDeclaration, } } @@ -63,53 +56,12 @@ data srv3_seeded_install_media_toolchain_ensure_scaffold: Disposition = Scaffold data install_media_remaster_toolchain_ensure_dissolution_trigger: Disposition = Scaffold { dissolves_to: RealizationDispatch, bind: DeclarationRef { - module_path: "gunbc.srv3_seeded_install_media", + module_path: "gunbc.srv3_host_effect_script", decl_name: "install_media_remaster_toolchain_ensure_script", field: WholeDeclaration } } -fn install_media_remaster_toolchain_ensure_script() -> String { - let tool_name = xorriso_cli_tool.name as String - let apt_package = package_xorriso as String - let apt_preflight = deploy_access_preflight_check_block( - access: ci_deploy_srv1_access, - cmd: deploy_access_apt_get_privileged_command(), - ) - let privileged_install = deploy_access_sudo_n_apt_get_install_command(package: package_xorriso) - concat( - concat( - concat( - concat("#!/usr/bin/env bash\nset -euo pipefail\nif command -v ", tool_name), - " >/dev/null 2>&1 && ", - ), - concat(tool_name, " --version >/dev/null 2>&1; then\n"), - ), - concat( - concat( - concat( - concat(" echo \"InstallMediaRemasterToolchainReceipt: outcome=Present tool=", tool_name), - concat(" package=", apt_package), - ), - "\"\n exit 0\nfi\n", - ), - concat( - concat( - concat( - concat(apt_preflight, privileged_install), - "\n", - ), - concat(tool_name, " --version >/dev/null\n"), - ), - concat( - concat("echo \"InstallMediaRemasterToolchainReceipt: outcome=Installed tool=", tool_name), - concat(" package=", apt_package, "\"\n"), - ), - ), - ), - ) -} - data srv3_seeded_install_media_unattended_not_proven_marker: Disposition = Scaffold { dissolves_to: SingleAuthority, bind: DeclarationRef { @@ -119,66 +71,30 @@ data srv3_seeded_install_media_unattended_not_proven_marker: Disposition = Scaff } } -data srv3_autoinstall_user_data_body: NonEmptyStr = autoinstall_user_data( - payload: srv3_ubuntu_autoinstall_on_iso, -) as NonEmptyStr - -data srv3_seeded_nocloud_dir_on_iso: NonEmptyStr = ubuntu_seeded_install_media_default_nocloud_dir( - hostname: srv3_ubuntu_autoinstall_on_iso.identity.hostname, -) - -data srv3_seeded_grub_kernel_cmdline: NonEmptyStr = ubuntu_seeded_install_media_grub_kernel_cmdline( - nocloud_dir: srv3_seeded_nocloud_dir_on_iso, -) - -data srv3_seeded_install_media_content_hash: ContentHash = seeded_install_media_content_hash( - stock_content_sha256: noble_numbat_2404_3_live_server_arm64.content_sha256 as NonEmptyStr, - autoinstall_user_data_body: srv3_autoinstall_user_data_body, - grub_kernel_cmdline: srv3_seeded_grub_kernel_cmdline, - nocloud_dir_on_iso: srv3_seeded_nocloud_dir_on_iso, -) - -data srv3_seeded_install_media_artifact: UbuntuSeededInstallMediaArtifactRow = UbuntuSeededInstallMediaArtifactRow { - stock_artifact: noble_numbat_2404_3_live_server_arm64, - autoinstall: srv3_ubuntu_autoinstall_on_iso, - autoinstall_user_data_body: srv3_autoinstall_user_data_body, - grub_kernel_cmdline: srv3_seeded_grub_kernel_cmdline, - nocloud_dir_on_iso: srv3_seeded_nocloud_dir_on_iso, - content_hash: srv3_seeded_install_media_content_hash, -} - -data srv3_seeded_ubuntu_install_media_install_path: NonEmptyStr = ubuntu_seeded_install_media_install_path( - artifact: srv3_seeded_install_media_artifact, -) - func srv3_seeded_install_media_toolchain_ensure() -> ProcessExit uses net: Network { - let script = install_media_remaster_toolchain_ensure_script() - let run = shell.Exec.Run(script: shell_exec_via_bash(body: script)) - if run.success { - ExitSuccess - } else { - exit_failure(reason: concat("seeded-install-media-toolchain-ensure: ", run.stderr)) - } + srv3_reconciliation_to_process_exit( + r: srv3_seeded_install_media_toolchain_ensure_apply(), + failure_prefix: "seeded-install-media-toolchain-ensure: ", + ) } func srv3_seeded_install_media_remaster() -> ProcessExit uses net: Network { - let ensure_script = install_media_remaster_toolchain_ensure_script() - let ensure_run = shell.Exec.Run(script: shell_exec_via_bash(body: ensure_script)) - if ensure_run.success == false { - return exit_failure(reason: concat("seeded-install-media-remaster: toolchain ensure failed: ", ensure_run.stderr)) - } - - let script = install_media_remaster_script(artifact: srv3_seeded_install_media_artifact) - let run = shell.Exec.Run(script: shell_exec_via_bash(body: script)) - if run.success { - ExitSuccess - } else { - exit_failure(reason: concat("seeded-install-media-remaster: ", run.stderr)) + let ensure_exit = srv3_reconciliation_to_process_exit( + r: srv3_seeded_install_media_toolchain_ensure_apply(), + failure_prefix: "seeded-install-media-remaster: toolchain ensure failed: ", + ) + match ensure_exit { + ExitSuccess => {} + _ => return ensure_exit } + srv3_reconciliation_to_process_exit( + r: srv3_seeded_install_media_remaster_apply(), + failure_prefix: "seeded-install-media-remaster: ", + ) } fn srv3_seeded_install_media_actuator_host() -> ComputeHost { diff --git a/dag/gunbc/srv3_sol_console_capture.dag b/dag/gunbc/srv3_sol_console_capture.dag index 7696c887962..a4bd13c7364 100644 --- a/dag/gunbc/srv3_sol_console_capture.dag +++ b/dag/gunbc/srv3_sol_console_capture.dag @@ -1,14 +1,14 @@ module gunbc.srv3_sol_console_capture import extdeps.bmc.serial_console { - SolConsoleCaptureIntent, - ObmcConsoleCli, - sol_console_capture_script, sol_console_capture_bash_emit_dissolution_trigger, } -import extdeps.boards.asrock_rack { asrock_srv3_live_bmc_host } -import extdeps.shell -import gunbc.shell_bash_runner { shell_exec_via_bash } +import gunbc.srv3_host_effect_apply { + srv3_sol_console_capture_apply, + srv3_reconciliation_to_process_exit, +} +import gunbc.srv3_sol_console_intent { srv3_sol_console_capture_intent } +import gunbc.srv3_actuate_shell_paths { srv3_sol_console_log_path } import std.disposition { Disposition, Scaffold, SingleAuthority, Terminal } import std.decl_ref { DeclarationRef, WholeDeclaration } import std.process { ProcessExit, ExitSuccess, exit_failure } @@ -23,22 +23,11 @@ data srv3_sol_console_capture_scaffold: Disposition = Scaffold { } } -data srv3_sol_console_log_path: NonEmptyStr = "/var/lib/gunbc/logs/srv3-install-sol-console.log" as NonEmptyStr - -data srv3_sol_console_capture_intent: SolConsoleCaptureIntent = SolConsoleCaptureIntent { - bmc_host: asrock_srv3_live_bmc_host, - transport: ObmcConsoleCli, - output_path: srv3_sol_console_log_path, -} - func srv3_sol_console_capture() -> ProcessExit { - let script = sol_console_capture_script(intent: srv3_sol_console_capture_intent) - let run = shell.Exec.Run(script: shell_exec_via_bash(body: script)) - if run.success { - ExitSuccess - } else { - exit_failure(reason: concat("sol-console-capture: ", run.stderr)) - } + srv3_reconciliation_to_process_exit( + r: srv3_sol_console_capture_apply(), + failure_prefix: "sol-console-capture: ", + ) } fn srv3_sol_console_capture_log_path_is_declared() -> Bool { diff --git a/dag/gunbc/srv3_sol_console_intent.dag b/dag/gunbc/srv3_sol_console_intent.dag new file mode 100644 index 00000000000..00532fb3072 --- /dev/null +++ b/dag/gunbc/srv3_sol_console_intent.dag @@ -0,0 +1,14 @@ +module gunbc.srv3_sol_console_intent + +import extdeps.bmc.serial_console { + SolConsoleCaptureIntent, + ObmcConsoleCli, +} +import extdeps.boards.asrock_rack { asrock_srv3_live_bmc_host } +import gunbc.srv3_actuate_shell_paths { srv3_sol_console_log_path } + +data srv3_sol_console_capture_intent: SolConsoleCaptureIntent = SolConsoleCaptureIntent { + bmc_host: asrock_srv3_live_bmc_host, + transport: ObmcConsoleCli, + output_path: srv3_sol_console_log_path, +} diff --git a/dag/test/claim/srv3_host_effect_apply_witness_test.dag b/dag/test/claim/srv3_host_effect_apply_witness_test.dag new file mode 100644 index 00000000000..8664addb530 --- /dev/null +++ b/dag/test/claim/srv3_host_effect_apply_witness_test.dag @@ -0,0 +1,78 @@ +module test.claim.srv3_host_effect_apply_witness + +import std.logic { Bool } +import gunbc.host_effect { + Srv3InstallReconcileObserve, + DurableApprovalGrantRecord, + DurableApprovalGrantRead, + Srv3InstallMediaFetch, + Srv3SeededInstallMediaRemaster, + Srv3SeededInstallMediaToolchainEnsure, + Srv3SolConsoleCapture, + Srv3BmcwebSessionLogin, + Srv3BmcwebTokenExtract, + Srv3NbdProxyServe, + Srv3BootOnceReadBackSleep, + Srv3ReceiptEmit, +} +import gunbc.srv3_host_effect_script { + srv3_host_effect_script_body, + srv3_host_effect_script_body_is_nonempty, + srv3_receipt_emit_script, +} +import gunbc.srv3_host_effect_apply { + srv3_typed_receipt_from_lines, + srv3_typed_receipt, +} +import gunbc.shell_bash_runner { shell_bash_runner_dissolution_trigger } + +test fn srv3_host_effect_observe_script_is_nonempty() -> Bool { + srv3_host_effect_script_body_is_nonempty( + effect: Srv3InstallReconcileObserve { include_git_meta: true }, + ) + && string_contains( + s: srv3_host_effect_script_body( + effect: Srv3InstallReconcileObserve { include_git_meta: true }, + ), + pattern: "Srv3InstallObserveReceipt: surface=serve", + ) +} + +test fn srv3_host_effect_mechanical_variants_resolve_nonempty_scripts() -> Bool { + srv3_host_effect_script_body_is_nonempty(effect: DurableApprovalGrantRecord) + && srv3_host_effect_script_body_is_nonempty(effect: DurableApprovalGrantRead) + && srv3_host_effect_script_body_is_nonempty(effect: Srv3InstallMediaFetch) + && srv3_host_effect_script_body_is_nonempty(effect: Srv3SeededInstallMediaRemaster) + && srv3_host_effect_script_body_is_nonempty(effect: Srv3SeededInstallMediaToolchainEnsure) + && srv3_host_effect_script_body_is_nonempty(effect: Srv3SolConsoleCapture) + && srv3_host_effect_script_body_is_nonempty(effect: Srv3BmcwebSessionLogin) + && srv3_host_effect_script_body_is_nonempty(effect: Srv3BmcwebTokenExtract) + && srv3_host_effect_script_body_is_nonempty(effect: Srv3NbdProxyServe) + && srv3_host_effect_script_body_is_nonempty(effect: Srv3BootOnceReadBackSleep) + && srv3_host_effect_script_body_is_nonempty( + effect: Srv3ReceiptEmit { lines: ["Srv3InstallReconcileReceipt: attempt_id=test"] }, + ) +} + +test fn srv3_typed_receipt_emit_uses_printf_not_naive_echo() -> Bool { + let script = srv3_receipt_emit_script(lines: ["line-one", "line-two"]) + string_contains(s: script, pattern: "printf '%s\\n'") + && !string_contains(s: script, pattern: "echo 'line-one'") +} + +test fn srv3_typed_receipt_carrier_holds_lines() -> Bool { + let receipt = srv3_typed_receipt_from_lines(lines: ["a", "b"]) + srv3_typed_receipt(receipt: receipt).lines.length() == 2 +} + +test fn shell_bash_runner_still_present_for_p5b() -> Bool { + string_contains(s: shell_bash_runner_dissolution_trigger, pattern: "shell_exec_via_bash") +} + +test fn srv3_host_effect_apply_holds() -> Bool { + srv3_host_effect_observe_script_is_nonempty() + && srv3_host_effect_mechanical_variants_resolve_nonempty_scripts() + && srv3_typed_receipt_emit_uses_printf_not_naive_echo() + && srv3_typed_receipt_carrier_holds_lines() + && shell_bash_runner_still_present_for_p5b() +} diff --git a/dag/test/claim/srv3_install_diagnostic_checklist_witness_test.dag b/dag/test/claim/srv3_install_diagnostic_checklist_witness_test.dag index 2d2e600cf43..b1a594c04e8 100644 --- a/dag/test/claim/srv3_install_diagnostic_checklist_witness_test.dag +++ b/dag/test/claim/srv3_install_diagnostic_checklist_witness_test.dag @@ -29,7 +29,7 @@ import gunbc.srv3_os_install_diagnostic { BootProgressNone, SolConsoleContentObservation, } -import gunbc.srv3_seeded_install_media { srv3_seeded_install_media_content_hash } +import gunbc.srv3_seeded_install_media_artifact { srv3_seeded_install_media_content_hash } import gunbc.network_identity_subsumption { srv3_pre_install_lease_table } import extdeps.bmc.types { BootOverrideDisabled, BootTargetNone, On } import extdeps.bmc.webui.nbd_proxy_serve { srv3_nbd_proxy_local_port } diff --git a/dag/test/claim/srv3_os_install_actuate_witness_test.dag b/dag/test/claim/srv3_os_install_actuate_witness_test.dag index f8c31ce53b6..d9ec879affc 100644 --- a/dag/test/claim/srv3_os_install_actuate_witness_test.dag +++ b/dag/test/claim/srv3_os_install_actuate_witness_test.dag @@ -8,6 +8,8 @@ import gunbc.srv3_os_install_actuate { srv3_os_install_actuate_shell_runner_scaffold, srv3_nbd_proxy_serve_intent, srv3_bmcweb_login_body_content, +} +import gunbc.srv3_actuate_shell_paths { srv3_nbd_proxy_serve_script_path, srv3_bmcweb_token_path, } diff --git a/dag/test/claim/srv3_os_install_diagnostic_witness_test.dag b/dag/test/claim/srv3_os_install_diagnostic_witness_test.dag index 273f87202f1..b4f57ed6ae9 100644 --- a/dag/test/claim/srv3_os_install_diagnostic_witness_test.dag +++ b/dag/test/claim/srv3_os_install_diagnostic_witness_test.dag @@ -43,7 +43,7 @@ import gunbc.srv3_os_install_diagnostic { OsInstalledVerdict, OnlyBmcLeasePresent, } -import gunbc.srv3_seeded_install_media { srv3_seeded_install_media_content_hash } +import gunbc.srv3_seeded_install_media_artifact { srv3_seeded_install_media_content_hash } import gunbc.network_identity_subsumption { srv3_pre_install_lease_table, srv3_post_install_lease_table_fixture, diff --git a/dag/test/claim/srv3_os_install_reconcile_witness_test.dag b/dag/test/claim/srv3_os_install_reconcile_witness_test.dag index 66222a25b3b..02d370fe07d 100644 --- a/dag/test/claim/srv3_os_install_reconcile_witness_test.dag +++ b/dag/test/claim/srv3_os_install_reconcile_witness_test.dag @@ -45,7 +45,7 @@ import gunbc.srv3_os_install_diagnostic { BootProgressNone, SolConsoleContentObservation, } -import gunbc.srv3_seeded_install_media { srv3_seeded_install_media_content_hash } +import gunbc.srv3_seeded_install_media_artifact { srv3_seeded_install_media_content_hash } import gunbc.network_identity_subsumption { srv3_pre_install_lease_table } import extdeps.bmc.types { BootOverrideDisabled, BootTargetNone, BootOverrideOnce, BootTargetCd, On } import extdeps.bmc.webui.nbd_proxy_serve { srv3_nbd_proxy_local_port } diff --git a/dag/test/claim/srv3_seeded_install_media_witness_test.dag b/dag/test/claim/srv3_seeded_install_media_witness_test.dag index a8dce99f02f..23ee727a1c3 100644 --- a/dag/test/claim/srv3_seeded_install_media_witness_test.dag +++ b/dag/test/claim/srv3_seeded_install_media_witness_test.dag @@ -8,11 +8,11 @@ import gunbc.srv3_seeded_install_media { srv3_seeded_autoinstall_delivery_is_on_iso, srv3_seeded_grub_cmdline_names_nocloud, srv3_seeded_install_media_unattended_not_proven_yet, - srv3_autoinstall_user_data_body, srv3_seeded_install_media_remaster_toolchain_is_modeled, srv3_seeded_install_media_toolchain_ensure_is_privileged_gated, - install_media_remaster_toolchain_ensure_script, } +import gunbc.srv3_seeded_install_media_artifact { srv3_autoinstall_user_data_body } +import gunbc.srv3_host_effect_script { install_media_remaster_toolchain_ensure_script } import extdeps.os.ubuntu_seeded_install_media_remaster { install_media_remaster_bash_emit_dissolution_trigger, install_media_remaster_script, diff --git a/dag/test/claim/srv3_sol_console_capture_witness_test.dag b/dag/test/claim/srv3_sol_console_capture_witness_test.dag index bdcadb5842b..8c5b399093a 100644 --- a/dag/test/claim/srv3_sol_console_capture_witness_test.dag +++ b/dag/test/claim/srv3_sol_console_capture_witness_test.dag @@ -3,8 +3,8 @@ module test.claim.srv3_sol_console_capture import gunbc.srv3_sol_console_capture { srv3_sol_console_capture_log_path_is_declared, srv3_sol_console_capture_carries_dissolution_trigger, - srv3_sol_console_capture_intent, } +import gunbc.srv3_sol_console_intent { srv3_sol_console_capture_intent } import extdeps.bmc.serial_console { ObmcConsoleCli, RedfishSerialInterface, From 63ae438bc75fcd3c732e6f399d22af5da94f607a Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 14 Jul 2026 07:08:37 +0000 Subject: [PATCH 3/6] fix(srv3): restore HostIdentity import and roster srv3_host_effect_script CI compile-clean failed because srv3_os_install_diagnostic lost its HostIdentity import during the actuate-path refactor. Also roster the new concat-shell handler module and import Bool in srv3_host_effect_apply. Co-authored-by: Cursor --- dag/gunbc/srv3_host_effect_apply.dag | 2 +- dag/gunbc/srv3_os_install_diagnostic.dag | 1 + src/v2/lens/medium_structure_containment.dag | 4 +++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/dag/gunbc/srv3_host_effect_apply.dag b/dag/gunbc/srv3_host_effect_apply.dag index 273a340690e..e1097f46ecf 100644 --- a/dag/gunbc/srv3_host_effect_apply.dag +++ b/dag/gunbc/srv3_host_effect_apply.dag @@ -33,7 +33,7 @@ import std.realization_reconcile { reconciliation_converged, } import std.process { ProcessExit, ExitSuccess, exit_failure } -import std.types { String, List } +import std.types { String, List, Bool } type Srv3TypedReceipt { lines: List diff --git a/dag/gunbc/srv3_os_install_diagnostic.dag b/dag/gunbc/srv3_os_install_diagnostic.dag index 98353c8f2c1..854ef8abfa2 100644 --- a/dag/gunbc/srv3_os_install_diagnostic.dag +++ b/dag/gunbc/srv3_os_install_diagnostic.dag @@ -39,6 +39,7 @@ import gunbc.network_identity_subsumption { } import gunbc.srv3_os_install_actuate_scope { srv3_actuator_virtual_media_iso_install_path } import gunbc.srv3_seeded_install_media_artifact { srv3_seeded_install_media_content_hash } +import product.placement_supply { HostIdentity } import gunbc.srv3_actuate_shell_paths { srv3_bmcweb_token_path, srv3_sol_console_log_path, diff --git a/src/v2/lens/medium_structure_containment.dag b/src/v2/lens/medium_structure_containment.dag index 15ded5edaf7..c12befb1279 100644 --- a/src/v2/lens/medium_structure_containment.dag +++ b/src/v2/lens/medium_structure_containment.dag @@ -208,13 +208,15 @@ data medium_structure_exception_roster: List = [ "dag/gunbc/srv3_os_install_reconcile_dry_run.dag", "dag/gunbc/srv3_os_install_reconcile_record_approval.dag", + "dag/gunbc/srv3_host_effect_script.dag", + "dag/tools/dag_compile_clean_seam_transport.dag", "dag/tools/dag_compile_clean_shard_totality_transport.dag" ] data medium_structure_clean_tree_unquarantine_note: String = "Pre-existing rot surfaced by live host census at discovery-flip unquarantine (2026-07-09; not debt introduced by this PR): dag_compile_clean_shard_transport is shell-transport sibling to already-rostered seam/shard_totality transports; os_install_deduction + v1_dag_parse witness tests pair string_contains with emit-fn output (same pattern as ci_yaml_serializer_witness_test.dag)." -data medium_structure_exception_roster_size_baseline: Int = 61 +data medium_structure_exception_roster_size_baseline: Int = 62 fn roster_growth_ratchet_holds(live_size: Int, baseline: Int) -> Bool { live_size <= baseline From 1c2c918c66879dfe47d1fa5bf7ddfe8ba5781bb4 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 14 Jul 2026 07:43:18 +0000 Subject: [PATCH 4/6] =?UTF-8?q?fix(srv3):=20address=20P5a=20review=20?= =?UTF-8?q?=E2=80=94=20RED=20witness,=20IsNonEmpty,=20=C2=A73=20cleanups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add apply-refusal RED controls to srv3_host_effect_apply_witness (failed apply refuses typed; sibling script_for rows still resolve). - Replace actuate token verify shell.Exec.Run with shell.Test.IsNonEmpty. - Bind srv3_host_effect_script scaffold to bash-emit (#5828) dissolution, not P5b executor deletion; Wave 4 residue named explicitly. - Remove is_* predicates; fold witness through exhaustive srv3_host_effect_script_for (delete body_is_nonempty fork). - Single authority for observe_meta_script (delete reconcile.dag orphan). - Extract srv3_ubuntu_install_media_artifact module; script handler uses it. - Add missing serialize_bash import; drop unused actuate import. Co-authored-by: Cursor --- dag/gunbc/srv3_host_effect_script.dag | 101 ++++++------ dag/gunbc/srv3_install_media_fetch.dag | 4 +- dag/gunbc/srv3_os_install_actuate.dag | 7 +- dag/gunbc/srv3_os_install_reconcile.dag | 6 - .../srv3_ubuntu_install_media_artifact.dag | 8 + .../srv3_host_effect_apply_witness_test.dag | 153 +++++++++++++++--- .../srv3_install_media_fetch_witness_test.dag | 2 +- 7 files changed, 191 insertions(+), 90 deletions(-) create mode 100644 dag/gunbc/srv3_ubuntu_install_media_artifact.dag diff --git a/dag/gunbc/srv3_host_effect_script.dag b/dag/gunbc/srv3_host_effect_script.dag index 3ab7cd45dfd..88ccdc4e33e 100644 --- a/dag/gunbc/srv3_host_effect_script.dag +++ b/dag/gunbc/srv3_host_effect_script.dag @@ -34,9 +34,10 @@ import gunbc.srv3_actuate_shell_paths { srv3_sol_console_log_path, } import gunbc.srv3_seeded_install_media_artifact { srv3_seeded_install_media_artifact } -import extdeps.os.ubuntu_install_media { noble_numbat_2404_3_live_server_arm64 } +import gunbc.srv3_ubuntu_install_media_artifact { srv3_ubuntu_install_media_artifact } import extdeps.os.ubuntu_install_media_fetch { install_media_fetch_script } import extdeps.os.ubuntu_seeded_install_media_remaster { install_media_remaster_script } +import extdeps.languages.bash.program { serialize_bash } import extdeps.bmc.serial_console { sol_console_capture_script, } @@ -69,11 +70,13 @@ import std.measure { second_count } import std.disposition { Disposition, Scaffold, SingleAuthority } import std.decl_ref { DeclarationRef, WholeDeclaration } +data srv3_host_effect_script_dissolution_trigger: String = "🟡 dissolve-on: srv3_host_effect_script — concat-built bash bodies for srv3 observe/effect intents (centralized handler staging post-P5a call-site migration); DISSOLVES WHEN bash-emit (#5828 / shell-emission slice0) realizes srv3 observe/effect intents as typed argv invocations / grammar-emitted nodes and srv3_host_effect_script_for retires without concat-built transport strings; retire medium_structure_exception_roster entry for dag/gunbc/srv3_host_effect_script.dag. Wave 4 residue (shell→dag census §1C category C). NOT gated on shell_exec_via_bash executor deletion (P5b)." + data srv3_host_effect_script_scaffold: Disposition = Scaffold { dissolves_to: SingleAuthority, bind: DeclarationRef { - module_path: "gunbc.srv3_os_install_reconcile", - decl_name: "srv3_os_install_reconcile_shell_runner_dissolution_trigger", + module_path: "gunbc.srv3_host_effect_script", + decl_name: "srv3_host_effect_script_dissolution_trigger", field: WholeDeclaration, } } @@ -185,66 +188,62 @@ fn srv3_receipt_emit_script(lines: List) -> String { ) } -fn srv3_host_effect_is_srv3_variant(effect: HostEffect) -> Bool { - match effect { - Srv3InstallReconcileObserve { include_git_meta: _ } => true - DurableApprovalGrantRecord => true - DurableApprovalGrantRead => true - Srv3InstallMediaFetch => true - Srv3SeededInstallMediaRemaster => true - Srv3SeededInstallMediaToolchainEnsure => true - Srv3SolConsoleCapture => true - Srv3BmcwebSessionLogin => true - Srv3BmcwebTokenExtract => true - Srv3NbdProxyServe => true - Srv3BootOnceReadBackSleep => true - Srv3ReceiptEmit { lines: _ } => true - ShellCommand { script: _ } => false - RedfishAction { action: _ } => false - ConvergePlan { policy: _ } => false - } -} - -fn srv3_host_effect_script_body(effect: HostEffect) -> String { +fn srv3_host_effect_script_for(effect: HostEffect) -> String? { match effect { Srv3InstallReconcileObserve { include_git_meta: meta } => - srv3_install_reconcile_observe_script(include_git_meta: meta) + Present { + value: shell_exec_via_bash( + body: srv3_install_reconcile_observe_script(include_git_meta: meta), + ), + } DurableApprovalGrantRecord => - srv3_install_record_boot_once_approval_script() + Present { + value: shell_exec_via_bash(body: srv3_install_record_boot_once_approval_script()), + } DurableApprovalGrantRead => - concat("cat ", concat(srv3_boot_once_approval_grant_path, " 2>/dev/null || true")) + Present { + value: shell_exec_via_bash( + body: concat("cat ", concat(srv3_boot_once_approval_grant_path, " 2>/dev/null || true")), + ), + } Srv3InstallMediaFetch => - install_media_fetch_script(artifact: noble_numbat_2404_3_live_server_arm64) + Present { + value: shell_exec_via_bash( + body: install_media_fetch_script(artifact: srv3_ubuntu_install_media_artifact), + ), + } Srv3SeededInstallMediaRemaster => - install_media_remaster_script(artifact: srv3_seeded_install_media_artifact) + Present { + value: shell_exec_via_bash( + body: install_media_remaster_script(artifact: srv3_seeded_install_media_artifact), + ), + } Srv3SeededInstallMediaToolchainEnsure => - install_media_remaster_toolchain_ensure_script() + Present { + value: shell_exec_via_bash(body: install_media_remaster_toolchain_ensure_script()), + } Srv3SolConsoleCapture => - sol_console_capture_script(intent: srv3_sol_console_capture_intent) + Present { + value: shell_exec_via_bash( + body: sol_console_capture_script(intent: srv3_sol_console_capture_intent), + ), + } Srv3BmcwebSessionLogin => - srv3_bmcweb_session_login_script() + Present { value: shell_exec_via_bash(body: srv3_bmcweb_session_login_script()) } Srv3BmcwebTokenExtract => - srv3_bmcweb_token_extract_script() + Present { value: shell_exec_via_bash(body: srv3_bmcweb_token_extract_script()) } Srv3NbdProxyServe => - srv3_nbd_proxy_serve_wrapper_script() + Present { value: shell_exec_via_bash(body: srv3_nbd_proxy_serve_wrapper_script()) } Srv3BootOnceReadBackSleep => - concat("sleep ", to_string(second_count(s: srv3_boot_once_apply_read_back_sleep))) + Present { + value: shell_exec_via_bash( + body: concat("sleep ", to_string(second_count(s: srv3_boot_once_apply_read_back_sleep))), + ), + } Srv3ReceiptEmit { lines: lines } => - srv3_receipt_emit_script(lines: lines) - ShellCommand { script: _ } => "" - RedfishAction { action: _ } => "" - ConvergePlan { policy: _ } => "" + Present { value: shell_exec_via_bash(body: srv3_receipt_emit_script(lines: lines)) } + ShellCommand { script: _ } => Absent + RedfishAction { action: _ } => Absent + ConvergePlan { policy: _ } => Absent } } - -fn srv3_host_effect_script_for(effect: HostEffect) -> String? { - if srv3_host_effect_is_srv3_variant(effect: effect) { - Present { value: shell_exec_via_bash(body: srv3_host_effect_script_body(effect: effect)) } - } else { - none - } -} - -fn srv3_host_effect_script_body_is_nonempty(effect: HostEffect) -> Bool { - srv3_host_effect_script_body(effect: effect) != "" -} diff --git a/dag/gunbc/srv3_install_media_fetch.dag b/dag/gunbc/srv3_install_media_fetch.dag index 6a6f788441e..ec11df00e16 100644 --- a/dag/gunbc/srv3_install_media_fetch.dag +++ b/dag/gunbc/srv3_install_media_fetch.dag @@ -4,7 +4,6 @@ import extdeps.os.ubuntu_install_media { UbuntuInstallMediaArtifactRow, UbuntuInstallMediaMirrorHost, UbuntuInstallMediaMirrorRow, - noble_numbat_2404_3_live_server_arm64, ubuntu_install_media_install_path, ubuntu_install_media_mirror_rows, ubuntu_install_media_mirror_url, @@ -12,6 +11,7 @@ import extdeps.os.ubuntu_install_media { import extdeps.os.ubuntu_install_media_fetch { install_media_fetch_bash_emit_dissolution_trigger, } +import gunbc.srv3_ubuntu_install_media_artifact { srv3_ubuntu_install_media_artifact } import gunbc.os_install_actuator_selection { srv3_os_install_actuator_host } import gunbc.fleet_intent { srv1_host, ComputeHost } import gunbc.srv3_host_effect_apply { @@ -44,8 +44,6 @@ data srv3_install_media_fetch_shell_runner_scaffold: Disposition = Scaffold { } } -data srv3_ubuntu_install_media_artifact: UbuntuInstallMediaArtifactRow = noble_numbat_2404_3_live_server_arm64 - data srv3_ubuntu_install_media_install_path: NonEmptyStr = ubuntu_install_media_install_path( artifact: srv3_ubuntu_install_media_artifact, ) diff --git a/dag/gunbc/srv3_os_install_actuate.dag b/dag/gunbc/srv3_os_install_actuate.dag index 1498f901d54..864ed2db73f 100644 --- a/dag/gunbc/srv3_os_install_actuate.dag +++ b/dag/gunbc/srv3_os_install_actuate.dag @@ -17,7 +17,6 @@ import extdeps.bmc.webui.nbd_proxy_serve { bmcweb_session_login_program, srv3_nbd_proxy_local_port, } -import extdeps.languages.bash.program { serialize_bash } import gunbc.srv3_os_install_actuate_scope { srv3_actuator_virtual_media_iso_install_path } import gunbc.srv3_bmc_credential_resolve { srv3_os_install_actuate_credential_intent, @@ -159,10 +158,8 @@ func srv3_bmcweb_session_login_resolved(resolution: BmcCredentialResolution) -> _ => return extract_exit } - let verify_run = shell.Exec.Run( - script: concat("test -s ", srv3_bmcweb_token_path), - ) - if verify_run.success == false { + let verify = shell.Test.IsNonEmpty(path: srv3_bmcweb_token_path) + if verify.nonempty == false { return exit_failure(reason: "bmcweb-login: empty token — check BMC credentials and /login response") } ExitSuccess diff --git a/dag/gunbc/srv3_os_install_reconcile.dag b/dag/gunbc/srv3_os_install_reconcile.dag index 17f82f96183..49d89a24f47 100644 --- a/dag/gunbc/srv3_os_install_reconcile.dag +++ b/dag/gunbc/srv3_os_install_reconcile.dag @@ -418,12 +418,6 @@ fn parse_observed_at_from_stdout(stdout: String) -> String? { } } -fn srv3_install_reconcile_observe_meta_script() -> String { - concat( - "printf '%s\\n' \"Srv3InstallObserveReceipt: surface=observed_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\nprintf '%s\\n' \"Srv3InstallObserveReceipt: surface=git branch=$(git branch --show-current 2>/dev/null || echo unknown) commit=$(git rev-parse --short HEAD 2>/dev/null || echo unknown)\"\n", - ) -} - fn sol_has_new_installer_markers( pre: Srv3InstallDebugObservation, post: Srv3InstallDebugObservation, diff --git a/dag/gunbc/srv3_ubuntu_install_media_artifact.dag b/dag/gunbc/srv3_ubuntu_install_media_artifact.dag new file mode 100644 index 00000000000..cb38a30971f --- /dev/null +++ b/dag/gunbc/srv3_ubuntu_install_media_artifact.dag @@ -0,0 +1,8 @@ +module gunbc.srv3_ubuntu_install_media_artifact + +import extdeps.os.ubuntu_install_media { + UbuntuInstallMediaArtifactRow, + noble_numbat_2404_3_live_server_arm64, +} + +data srv3_ubuntu_install_media_artifact: UbuntuInstallMediaArtifactRow = noble_numbat_2404_3_live_server_arm64 diff --git a/dag/test/claim/srv3_host_effect_apply_witness_test.dag b/dag/test/claim/srv3_host_effect_apply_witness_test.dag index 8664addb530..a5087408fcc 100644 --- a/dag/test/claim/srv3_host_effect_apply_witness_test.dag +++ b/dag/test/claim/srv3_host_effect_apply_witness_test.dag @@ -2,6 +2,13 @@ module test.claim.srv3_host_effect_apply_witness import std.logic { Bool } import gunbc.host_effect { + HostEffect, + ShellCommand, + NodeControlPlane, + HostOs, + BmcController, + host_effect_identity_evidence, + LocalShell, Srv3InstallReconcileObserve, DurableApprovalGrantRecord, DurableApprovalGrantRead, @@ -15,43 +22,128 @@ import gunbc.host_effect { Srv3BootOnceReadBackSleep, Srv3ReceiptEmit, } +import gunbc.host_effect_realize { host_effect_apply } +import gunbc.os_install_actuator_selection { srv3_os_install_actuator_host } +import std.realization_reconcile { reconciliation_converged, NotConverged } import gunbc.srv3_host_effect_script { - srv3_host_effect_script_body, - srv3_host_effect_script_body_is_nonempty, + srv3_host_effect_script_for, srv3_receipt_emit_script, + srv3_host_effect_script_dissolution_trigger, } import gunbc.srv3_host_effect_apply { srv3_typed_receipt_from_lines, srv3_typed_receipt, + srv3_shell_stdout_from_apply, } import gunbc.shell_bash_runner { shell_bash_runner_dissolution_trigger } +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } -test fn srv3_host_effect_observe_script_is_nonempty() -> Bool { - srv3_host_effect_script_body_is_nonempty( +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +fn srv3_actuator_target() -> NodeControlPlane { + HostOs { node: srv3_os_install_actuator_host } +} + +fn srv3_bmc_target() -> NodeControlPlane { + BmcController { node: srv3_os_install_actuator_host } +} + +fn srv3_host_effect_script_resolves(effect: HostEffect) -> Bool { + match srv3_host_effect_script_for(effect: effect) { + Present { value: script } => script != "" + Absent => false + } +} + +fn witness_non_srv3_effect_script_for_absent() -> Bool { + match srv3_host_effect_script_for(effect: ShellCommand { script: "true" }) { + Absent => true + Present { value: _ } => false + } +} + +fn witness_srv3_apply_shell_failure_refuses_typed() -> Bool { + !reconciliation_converged(r: host_effect_apply( + target: srv3_actuator_target(), + effect: ShellCommand { script: "exit 1" }, + evidence: host_effect_identity_evidence, + transport: LocalShell + )) +} + +fn witness_srv3_apply_failure_surfaces_captured_stdout_not_fabricated() -> Bool { + match host_effect_apply( + target: srv3_actuator_target(), + effect: ShellCommand { script: "echo srv3-apply-red-marker && exit 1" }, + evidence: host_effect_identity_evidence, + transport: LocalShell + ) { + NotConverged { reason: why, applied: _ } => + string_contains(s: why, pattern: "srv3-apply-red-marker") + && string_contains(s: why, pattern: "fail-closed") + Converged { evidence: _, applied: _ } => false + } +} + +fn witness_srv3_apply_failure_no_fabricated_stdout() -> Bool { + let r = host_effect_apply( + target: srv3_actuator_target(), + effect: ShellCommand { script: "exit 1" }, + evidence: host_effect_identity_evidence, + transport: LocalShell + ) + !reconciliation_converged(r: r) && srv3_shell_stdout_from_apply(r: r) == none +} + +fn witness_srv3_typed_effect_on_bmc_refuses_typed() -> Bool { + match host_effect_apply( + target: srv3_bmc_target(), + effect: Srv3InstallMediaFetch, + evidence: host_effect_identity_evidence, + transport: LocalShell + ) { + NotConverged { reason: why, applied: _ } => + string_contains(s: why, pattern: "cannot target BmcController") + Converged { evidence: _, applied: _ } => false + } +} + +fn witness_srv3_sibling_variants_still_resolve_after_apply_failure() -> Bool { + witness_srv3_apply_shell_failure_refuses_typed() + && srv3_host_effect_script_resolves(effect: Srv3InstallMediaFetch) + && srv3_host_effect_script_resolves(effect: DurableApprovalGrantRecord) + && srv3_host_effect_script_resolves(effect: Srv3SolConsoleCapture) + && srv3_host_effect_script_resolves(effect: Srv3BmcwebSessionLogin) +} + +test fn srv3_host_effect_observe_script_resolves_via_script_for() -> Bool { + srv3_host_effect_script_resolves( effect: Srv3InstallReconcileObserve { include_git_meta: true }, ) - && string_contains( - s: srv3_host_effect_script_body( - effect: Srv3InstallReconcileObserve { include_git_meta: true }, - ), - pattern: "Srv3InstallObserveReceipt: surface=serve", - ) + && match srv3_host_effect_script_for( + effect: Srv3InstallReconcileObserve { include_git_meta: true }, + ) { + Present { value: script } => + string_contains(s: script, pattern: "Srv3InstallObserveReceipt: surface=serve") + Absent => false + } } -test fn srv3_host_effect_mechanical_variants_resolve_nonempty_scripts() -> Bool { - srv3_host_effect_script_body_is_nonempty(effect: DurableApprovalGrantRecord) - && srv3_host_effect_script_body_is_nonempty(effect: DurableApprovalGrantRead) - && srv3_host_effect_script_body_is_nonempty(effect: Srv3InstallMediaFetch) - && srv3_host_effect_script_body_is_nonempty(effect: Srv3SeededInstallMediaRemaster) - && srv3_host_effect_script_body_is_nonempty(effect: Srv3SeededInstallMediaToolchainEnsure) - && srv3_host_effect_script_body_is_nonempty(effect: Srv3SolConsoleCapture) - && srv3_host_effect_script_body_is_nonempty(effect: Srv3BmcwebSessionLogin) - && srv3_host_effect_script_body_is_nonempty(effect: Srv3BmcwebTokenExtract) - && srv3_host_effect_script_body_is_nonempty(effect: Srv3NbdProxyServe) - && srv3_host_effect_script_body_is_nonempty(effect: Srv3BootOnceReadBackSleep) - && srv3_host_effect_script_body_is_nonempty( +test fn srv3_host_effect_mechanical_variants_resolve_via_script_for() -> Bool { + srv3_host_effect_script_resolves(effect: DurableApprovalGrantRecord) + && srv3_host_effect_script_resolves(effect: DurableApprovalGrantRead) + && srv3_host_effect_script_resolves(effect: Srv3InstallMediaFetch) + && srv3_host_effect_script_resolves(effect: Srv3SeededInstallMediaRemaster) + && srv3_host_effect_script_resolves(effect: Srv3SeededInstallMediaToolchainEnsure) + && srv3_host_effect_script_resolves(effect: Srv3SolConsoleCapture) + && srv3_host_effect_script_resolves(effect: Srv3BmcwebSessionLogin) + && srv3_host_effect_script_resolves(effect: Srv3BmcwebTokenExtract) + && srv3_host_effect_script_resolves(effect: Srv3NbdProxyServe) + && srv3_host_effect_script_resolves(effect: Srv3BootOnceReadBackSleep) + && srv3_host_effect_script_resolves( effect: Srv3ReceiptEmit { lines: ["Srv3InstallReconcileReceipt: attempt_id=test"] }, ) + && witness_non_srv3_effect_script_for_absent() } test fn srv3_typed_receipt_emit_uses_printf_not_naive_echo() -> Bool { @@ -69,10 +161,23 @@ test fn shell_bash_runner_still_present_for_p5b() -> Bool { string_contains(s: shell_bash_runner_dissolution_trigger, pattern: "shell_exec_via_bash") } +test fn srv3_host_effect_script_dissolves_via_bash_emit_not_p5b_executor() -> Bool { + string_contains(s: srv3_host_effect_script_dissolution_trigger, pattern: "bash-emit") + && string_contains(s: srv3_host_effect_script_dissolution_trigger, pattern: "#5828") + && string_contains(s: srv3_host_effect_script_dissolution_trigger, pattern: "Wave 4 residue") + && string_contains(s: srv3_host_effect_script_dissolution_trigger, pattern: "NOT gated on shell_exec_via_bash executor deletion") +} + test fn srv3_host_effect_apply_holds() -> Bool { - srv3_host_effect_observe_script_is_nonempty() - && srv3_host_effect_mechanical_variants_resolve_nonempty_scripts() + srv3_host_effect_observe_script_resolves_via_script_for() + && srv3_host_effect_mechanical_variants_resolve_via_script_for() && srv3_typed_receipt_emit_uses_printf_not_naive_echo() && srv3_typed_receipt_carrier_holds_lines() && shell_bash_runner_still_present_for_p5b() + && srv3_host_effect_script_dissolves_via_bash_emit_not_p5b_executor() + && witness_srv3_apply_shell_failure_refuses_typed() + && witness_srv3_apply_failure_surfaces_captured_stdout_not_fabricated() + && witness_srv3_apply_failure_no_fabricated_stdout() + && witness_srv3_typed_effect_on_bmc_refuses_typed() + && witness_srv3_sibling_variants_still_resolve_after_apply_failure() } diff --git a/dag/test/claim/srv3_install_media_fetch_witness_test.dag b/dag/test/claim/srv3_install_media_fetch_witness_test.dag index 1981066121d..332ac8fe888 100644 --- a/dag/test/claim/srv3_install_media_fetch_witness_test.dag +++ b/dag/test/claim/srv3_install_media_fetch_witness_test.dag @@ -3,8 +3,8 @@ module test.claim.srv3_install_media_fetch import std.logic { Bool } import std.disposition { Scaffold, Terminal } import std.effects { CreateEffect, CreateIfAbsent, InputField } +import gunbc.srv3_ubuntu_install_media_artifact { srv3_ubuntu_install_media_artifact } import gunbc.srv3_install_media_fetch { - srv3_ubuntu_install_media_artifact, srv3_ubuntu_install_media_install_path, install_media_fetch_effect_shape, classify_install_media_fetch, From 5794628c0cbe8b6c7d58f272648b987e722c5c77 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 14 Jul 2026 07:57:32 +0000 Subject: [PATCH 5/6] fix(srv3): drop dead nbd intent dup; rebind reconcile scaffolds Remove unused srv3_nbd_proxy_serve_intent copy and trailing imports from srv3_host_effect_script.dag. Discharge reconcile hub shell-runner scaffold to Terminal (P5a call-site migration done); rebind apply/dry_run/ record_approval child scaffolds to srv3_host_effect_script_dissolution_trigger. Co-authored-by: Cursor --- dag/gunbc/srv3_host_effect_script.dag | 23 ------------------- dag/gunbc/srv3_os_install_reconcile.dag | 14 +++-------- dag/gunbc/srv3_os_install_reconcile_apply.dag | 4 ++-- .../srv3_os_install_reconcile_dry_run.dag | 4 ++-- ...3_os_install_reconcile_record_approval.dag | 4 ++-- 5 files changed, 9 insertions(+), 40 deletions(-) diff --git a/dag/gunbc/srv3_host_effect_script.dag b/dag/gunbc/srv3_host_effect_script.dag index 88ccdc4e33e..0ad970547c6 100644 --- a/dag/gunbc/srv3_host_effect_script.dag +++ b/dag/gunbc/srv3_host_effect_script.dag @@ -43,20 +43,11 @@ import extdeps.bmc.serial_console { } import gunbc.srv3_sol_console_intent { srv3_sol_console_capture_intent } import extdeps.bmc.webui.nbd_proxy_serve { - NbdProxyServeTransportIntent, bmcweb_session_login_program, - nbd_proxy_serve_script, - nbd_proxy_serve_transport_intent, - srv3_nbd_proxy_local_port, -} -import extdeps.bmc.webui.nbd_proxy { - bmcweb_nbd_proxy_ws_path, } import extdeps.boards.asrock_rack { asrock_srv3_live_bmc_host, - asrock_altrad8ud_openbmc_2_07_00_nbd_proxy_primary_endpoint, } -import gunbc.srv3_os_install_actuate_scope { srv3_actuator_virtual_media_iso_install_path } import gunbc.ci_deploy_access { ci_deploy_srv1_access, deploy_access_apt_get_privileged_command, @@ -137,20 +128,6 @@ fn srv3_install_reconcile_observe_script(include_git_meta: Bool) -> String { } } -fn srv3_nbd_proxy_serve_intent() -> NbdProxyServeTransportIntent { - nbd_proxy_serve_transport_intent( - bmc_host: asrock_srv3_live_bmc_host, - session_token: concat("$", "BMCWEB_SESSION_TOKEN") as NonEmptyStr, - ws_path: bmcweb_nbd_proxy_ws_path( - variant: asrock_altrad8ud_openbmc_2_07_00_nbd_proxy_primary_endpoint, - slot: 0, - index: 0, - ), - iso_path: srv3_actuator_virtual_media_iso_install_path, - local_nbd_port: srv3_nbd_proxy_local_port, - ) -} - fn srv3_bmcweb_session_login_script() -> String { let login_prog = bmcweb_session_login_program( bmc_host: asrock_srv3_live_bmc_host, diff --git a/dag/gunbc/srv3_os_install_reconcile.dag b/dag/gunbc/srv3_os_install_reconcile.dag index 49d89a24f47..ce1ef931360 100644 --- a/dag/gunbc/srv3_os_install_reconcile.dag +++ b/dag/gunbc/srv3_os_install_reconcile.dag @@ -96,8 +96,7 @@ import gunbc.srv3_os_install_diagnostic { install_serve_session_active, srv3_install_pre_boot_ready_to_boot, } -import std.disposition { Disposition, Scaffold, SingleAuthority } -import std.decl_ref { DeclarationRef, WholeDeclaration } +import std.disposition { Disposition, Terminal } import extdeps.bmc.types { BootOverrideDisabled, BootTargetNone, BootOverrideOnce, BootTargetCd } import gunbc.fleet_intent_network { operator_host_srv1, operator_host_srv3 } import gunbc.srv3_seeded_install_media_artifact { srv3_seeded_install_media_content_hash } @@ -115,17 +114,10 @@ import gunbc.srv3_os_install_reconcile_types { srv3_install_reconcile_dry_run_observed_at, } -data srv3_os_install_reconcile_shell_runner_scaffold: Disposition = Scaffold { - dissolves_to: SingleAuthority, - bind: DeclarationRef { - module_path: "gunbc.srv3_os_install_reconcile", - decl_name: "srv3_os_install_reconcile_shell_runner_dissolution_trigger", - field: WholeDeclaration, - } +data srv3_os_install_reconcile_shell_runner_scaffold: Disposition = Terminal { + reason: "P5a discharged (2026-07-14): dry_run/apply/record_approval call sites migrated to srv3_host_effect_apply typed HostEffect rows — no shell.Exec.Run or shell_exec_via_bash in those modules. Remaining concat transport debt is centralized in gunbc.srv3_host_effect_script (srv3_host_effect_script_dissolution_trigger; bash-emit #5828 Wave 4 residue)." } -data srv3_os_install_reconcile_shell_runner_dissolution_trigger: String = "🟡 dissolve-on: srv3_os_install_reconcile shell runner glue — dry_run/apply/record_approval orchestrate via shell.Exec.Run over concat-built observe meta, durable-approval grant write, grant read, read-back sleep, and receipt echo scripts (srv3_install_record_boot_once_approval_script, srv3_install_reconcile_observe_meta_script); receipt echo via shell_printf_line (printf '%s\\n' + bash double-quote escape — not naive echo single-quote concat); diagnostic observer concat freeze is on srv3_install_diagnostic_checklist_shell_runner_dissolution_trigger (post-merge bound, not retroactive); next node is typed Observe* effect rows + Srv3InstallObserveViaShell handler only; DISSOLVES WHEN host_effect_apply binds Srv3InstallReconcileObserve + DurableApprovalGrantRecord + Srv3InstallReconcileApply without shell_exec_via_bash runner glue; retire medium_structure_exception_roster entries for dag/gunbc/srv3_os_install_reconcile*.dag" - fn held_lease_observed_from_serve(serve: NbdServeObservation) -> HeldLeaseObservedState { match serve { ServeUnknown => LeaseInaccessible diff --git a/dag/gunbc/srv3_os_install_reconcile_apply.dag b/dag/gunbc/srv3_os_install_reconcile_apply.dag index ebdf16ea078..5530e512ef2 100644 --- a/dag/gunbc/srv3_os_install_reconcile_apply.dag +++ b/dag/gunbc/srv3_os_install_reconcile_apply.dag @@ -51,8 +51,8 @@ import std.temporal_effect { data srv3_os_install_reconcile_apply_shell_runner_scaffold: Disposition = Scaffold { dissolves_to: SingleAuthority, bind: DeclarationRef { - module_path: "gunbc.srv3_os_install_reconcile", - decl_name: "srv3_os_install_reconcile_shell_runner_dissolution_trigger", + module_path: "gunbc.srv3_host_effect_script", + decl_name: "srv3_host_effect_script_dissolution_trigger", field: WholeDeclaration, } } diff --git a/dag/gunbc/srv3_os_install_reconcile_dry_run.dag b/dag/gunbc/srv3_os_install_reconcile_dry_run.dag index 1b90ab53218..ad3876cd70e 100644 --- a/dag/gunbc/srv3_os_install_reconcile_dry_run.dag +++ b/dag/gunbc/srv3_os_install_reconcile_dry_run.dag @@ -32,8 +32,8 @@ import std.temporal_effect { GrantAbsent, NoPriorReceipt } data srv3_os_install_reconcile_dry_run_shell_runner_scaffold: Disposition = Scaffold { dissolves_to: SingleAuthority, bind: DeclarationRef { - module_path: "gunbc.srv3_os_install_reconcile", - decl_name: "srv3_os_install_reconcile_shell_runner_dissolution_trigger", + module_path: "gunbc.srv3_host_effect_script", + decl_name: "srv3_host_effect_script_dissolution_trigger", field: WholeDeclaration, } } diff --git a/dag/gunbc/srv3_os_install_reconcile_record_approval.dag b/dag/gunbc/srv3_os_install_reconcile_record_approval.dag index ff41b08a890..d4a640360dc 100644 --- a/dag/gunbc/srv3_os_install_reconcile_record_approval.dag +++ b/dag/gunbc/srv3_os_install_reconcile_record_approval.dag @@ -11,8 +11,8 @@ import std.decl_ref { DeclarationRef, WholeDeclaration } data srv3_os_install_reconcile_record_approval_shell_runner_scaffold: Disposition = Scaffold { dissolves_to: SingleAuthority, bind: DeclarationRef { - module_path: "gunbc.srv3_os_install_reconcile", - decl_name: "srv3_os_install_reconcile_shell_runner_dissolution_trigger", + module_path: "gunbc.srv3_host_effect_script", + decl_name: "srv3_host_effect_script_dissolution_trigger", field: WholeDeclaration, } } From 465c4bee82d290297a3723f65a396e748617fccc Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Tue, 14 Jul 2026 07:58:51 +0000 Subject: [PATCH 6/6] fix(lens): roster srv3_host_effect_script for serialize_bash import Move realization_vocab exception from actuate (no longer imports bash.program) to srv3_host_effect_script where login script serialize lives. Co-authored-by: Cursor --- src/v2/lens/realization_vocabulary_containment.dag | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v2/lens/realization_vocabulary_containment.dag b/src/v2/lens/realization_vocabulary_containment.dag index 65fd622ca0f..f550eeccef9 100644 --- a/src/v2/lens/realization_vocabulary_containment.dag +++ b/src/v2/lens/realization_vocabulary_containment.dag @@ -39,7 +39,7 @@ data realization_vocab_exception_roster: List = [ "dag/extdeps/os/ubuntu_install_media_fetch.dag", "dag/extdeps/os/ubuntu_seeded_install_media_remaster.dag", "dag/gunbc/node_http_server_emit.dag", - "dag/gunbc/srv3_os_install_actuate.dag", + "dag/gunbc/srv3_host_effect_script.dag", "dag/tools/build_step.dag", "dag/tools/host_prelude.dag", "src/v2/lens/duplicate_computation.dag",