From d879eb1a196fe7ef6c12469b4790da89e865b97d Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 19 Aug 2026 21:55:52 +0000 Subject: [PATCH 1/3] NBD proxy: ground observe-side port/unit read-back on cited extdeps.systemd, retire systemd-run WitnessBin host_effect_nbd_proxy_serve_dissolution_trigger read "DISSOLVES WHEN observe-side port/unit query is grounded on cited extdeps.systemd/systemctl read-back and typed argv dispatch retires WitnessBin systemd-run scaffolding." Both conditions are now met, so the module's disposition is a std.disposition.Terminal, not a Scaffold. Retired: host_effect_nbd_proxy_serve_witness_bin_run_argv's direct gunbc.WitnessBin.Run(...) calls for systemd-run transient unit start and systemctl stop. Replaced with: the typed-argv-exec dispatch pattern already used by systemctl_status_read.dag / systemctl_show_read.dag, routed through gunbc.typed_argv_exec and v2.std.operation_argv: - extdeps/systemd/systemd_run.dag: new extdeps module citing systemd-run's own man page (systemd-run is a distinct upstream binary from systemctl), with a RunTransient operation and the variadic command_argv authority. - extdeps/systemd/systemctl.dag: added a Stop operation mirroring Restart. - gunbc/systemd_run_transient.dag, gunbc/systemctl_stop_run.dag, gunbc/systemctl_is_active_read.dag: typed dispatch modules over LocalShell/SshShell/FleetSsh, refusing EmitArtifactThenThinRun, following the exact structure of systemctl_status_read.dag / systemctl_show_read.dag. Grounded: host_effect_nbd_proxy_serve_observe_port now reads gunbc.systemctl_is_active_read (systemd.Systemctl.IsActive) against the transient websocat unit name -- the unit name is this scope's identity anchor, so no MainPID/port-scan primitive is needed. active -> PortObserved (with a documented pid:0/cmdline sentinel that classify_process_port never inspects), inactive -> PortAbsent, typed capture refusal -> PortInaccessible. Previously this was an unconditional PortInaccessible stub. Tests: nbd_proxy_serve_transport_witness_test.dag gained direct-arm coverage for all three observe_port outcomes plus a LocalShell wiring witness, and argv-authority round-trip witnesses for the three new typed operations. srv3_host_effect_apply_witness_test.dag's "observe unimplemented" witness now targets the one transport that genuinely cannot observe (EmitArtifactThenThinRun), and its dissolution-carrier witness asserts the Terminal disposition instead of a retired Scaffold. witness_deferral_freeze.dag's frozen row for that file is shrunk (not grown) to drop the two identities that no longer exist under their old names, with a shrink-log entry recording why. Co-Authored-By: Claude Sonnet 5 --- dag/extdeps/systemd/systemctl.dag | 24 ++ dag/extdeps/systemd/systemd_run.dag | 42 +++ dag/gunbc/host_effect_nbd_proxy_serve.dag | 254 ++++++++++-------- dag/gunbc/systemctl_is_active_read.dag | 111 ++++++++ dag/gunbc/systemctl_stop_run.dag | 108 ++++++++ dag/gunbc/systemd_run_transient.dag | 121 +++++++++ dag/gunbc/witness_deferral_freeze.dag | 6 +- ...nbd_proxy_serve_transport_witness_test.dag | 82 ++++++ .../srv3_host_effect_apply_witness_test.dag | 29 +- 9 files changed, 656 insertions(+), 121 deletions(-) create mode 100644 dag/extdeps/systemd/systemd_run.dag create mode 100644 dag/gunbc/systemctl_is_active_read.dag create mode 100644 dag/gunbc/systemctl_stop_run.dag create mode 100644 dag/gunbc/systemd_run_transient.dag diff --git a/dag/extdeps/systemd/systemctl.dag b/dag/extdeps/systemd/systemctl.dag index 6ac60a02deb..c9682490e38 100644 --- a/dag/extdeps/systemd/systemctl.dag +++ b/dag/extdeps/systemd/systemctl.dag @@ -31,6 +31,10 @@ fn systemctl_disable_now_argv(unit: NonEmptyStr) -> List { ["systemctl", "disable", "--now", unit as String] } +fn systemctl_stop_argv(unit: NonEmptyStr) -> List { + ["systemctl", "stop", unit as String] +} + fn systemctl_set_property_argv(unit: NonEmptyStr, property: NonEmptyStr, value: NonEmptyStr) -> List { [ "systemctl", @@ -132,6 +136,23 @@ service systemd.Systemctl { } } + operation Stop { + input { unit: NonEmptyStr } + output { + success: Bool from "exit_success" + stdout: String from "stdout" + stderr: String from "stderr" + } + transport shell { argv: ["systemctl", "stop", "{unit}"] } + exit { + 0 => Unit + nonzero => String "systemctl stop failed" + } + mock_response { + 0 => { success: true, stdout: "", stderr: "" } "hermetic systemd.Systemctl.Stop" + } + } + operation SetProperty { input { unit: NonEmptyStr, property: NonEmptyStr, value: NonEmptyStr } output { @@ -190,6 +211,9 @@ service systemd.Systemctl { 3 => Unit "unit is not active" nonzero => String "systemctl is-active failed" } + mock_response { + 0 => { state: "active", active: true } "hermetic systemd.Systemctl.IsActive active" + } } operation ShowProperty { diff --git a/dag/extdeps/systemd/systemd_run.dag b/dag/extdeps/systemd/systemd_run.dag new file mode 100644 index 00000000000..24c23dfd4e1 --- /dev/null +++ b/dag/extdeps/systemd/systemd_run.dag @@ -0,0 +1,42 @@ +module extdeps.systemd.systemd_run + +import std.types { NonEmptyStr, String, List, Bool } + +import extdeps.external_authority { ExternalAuthority } +import extdeps.uri { Uri, Https } + +data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { + uri: Uri { + scheme: Https + locator: "www.freedesktop.org/software/systemd/man/latest/systemd-run.html" + } +} + +data systemd_run_authority: ExternalAuthority = extdeps_external_authority_anchor + +fn systemd_run_transient_unit_argv(unit: NonEmptyStr, command_argv: List) -> List { + fold( + command_argv, + init: ["systemd-run", concat("--unit=", unit as String), "--collect", "--"], + f: (acc, arg) => list_push(acc, arg), + ) +} + +service systemd.SystemdRun { + operation RunTransient { + input { unit: NonEmptyStr, command_argv: List } + output { + success: Bool from "exit_success" + stdout: String from "stdout" + stderr: String from "stderr" + } + transport shell { argv: ["systemd-run", "--unit={unit}", "--collect", "--", command_argv] } + exit { + 0 => Unit + nonzero => String "systemd-run transient unit start failed" + } + mock_response { + 0 => { success: true, stdout: "Running as unit: demo.service", stderr: "" } "hermetic systemd.SystemdRun.RunTransient" + } + } +} diff --git a/dag/gunbc/host_effect_nbd_proxy_serve.dag b/dag/gunbc/host_effect_nbd_proxy_serve.dag index 3a75436d91f..b65eb408e02 100644 --- a/dag/gunbc/host_effect_nbd_proxy_serve.dag +++ b/dag/gunbc/host_effect_nbd_proxy_serve.dag @@ -1,10 +1,7 @@ module gunbc.host_effect_nbd_proxy_serve -import std.dissolution { DissolutionCondition, dissolution_description, unbound_dissolution } - import std.types { String, NonEmptyStr, Int, List, Bool } -import std.disposition { Disposition, Scaffold, SingleAuthority } -import std.decl_ref { DeclarationRef, WholeDeclaration } +import std.disposition { Disposition, Terminal } import std.temporal_effect { TemporalEffectPolicy, HeldSessionLease } import std.upsert_decision { UpsertClassification, @@ -29,24 +26,21 @@ import gunbc.session_lease { ProcessPortScope, ProcessPortObservation, PortAbsent, + PortObserved, PortInaccessible, ProcessLeasePlan, Start, DrainMatchingStaleThenStart, classify_process_port_upsert, } -import gunbc.host_effect { HostEffectTransport, LocalShell, SshShell, EmitArtifactThenThinRun } +import gunbc.host_effect { HostEffectTransport } import gunbc.srv3_actuate_shell_paths { srv3_bmcweb_token_path } +import gunbc.systemd_run_transient { systemd_run_transient_read, SystemdRunTransientStarted, SystemdRunTransientRefused } +import gunbc.systemctl_stop_run { systemctl_stop_run, SystemctlStopped, SystemctlStopRefused } +import gunbc.systemctl_is_active_read { systemctl_is_active_read, UnitIsActiveCapture, UnitIsActiveCaptured, UnitIsActiveCaptureRefused } -data host_effect_nbd_proxy_serve_dissolution_trigger: DissolutionCondition = unbound_dissolution(description: "🟑 dissolve-on: host_effect_nbd_proxy_serve β€” systemd transient-unit realize transport for Srv3NbdProxyServe (P6 Part 2; operator ruling 2026-07-14: no trap/&/$! ShellStmt vocabulary); DISSOLVES WHEN observe-side port/unit query is grounded on cited extdeps.systemd/systemctl read-back and typed argv dispatch retires WitnessBin systemd-run scaffolding. Token bridge: start_units materializes BMCWEB session token from srv3_bmcweb_token_path via Filesystem.Read into websocat argv (replaces deleted wrapper export). Wave 4 residue (shellβ†’dag census Β§1C category C).") - -data host_effect_nbd_proxy_serve_scaffold: Disposition = Scaffold { - dissolves_to: SingleAuthority, - bind: DeclarationRef { - module_path: "gunbc.host_effect_nbd_proxy_serve", - decl_name: "host_effect_nbd_proxy_serve_dissolution_trigger", - field: WholeDeclaration, - } +data host_effect_nbd_proxy_serve_disposition: Disposition = Terminal { + reason: "systemd transient-unit realize transport for Srv3NbdProxyServe (P6 Part 2; operator ruling 2026-07-14: no trap/&/$! ShellStmt vocabulary). Actuate side routes through gunbc.systemd_run_transient (RunTransient) and gunbc.systemctl_stop_run (Stop), both typed-argv dispatch over the cited extdeps.systemd.systemd_run / extdeps.systemd.systemctl authorities. Observe side routes through gunbc.systemctl_is_active_read (IsActive) against the transient unit name, which is this scope's identity anchor. Token bridge: start_units materializes BMCWEB session token from srv3_bmcweb_token_path via Filesystem.Read into websocat argv. Formerly host_effect_nbd_proxy_serve_dissolution_trigger / host_effect_nbd_proxy_serve_scaffold (WitnessBin systemd-run scaffolding + unconditional PortInaccessible observe stub); both conditions of that trigger are now met and it is retired in favor of this Terminal disposition." } data nbd_proxy_serve_expected_owner_label: NonEmptyStr = "gunbc-nbd-proxy" as NonEmptyStr @@ -95,64 +89,69 @@ fn host_effect_nbd_proxy_serve_session_binding( } } -fn host_effect_nbd_proxy_serve_append_argv( - acc: List, - argv: List, -) -> List { - fold(argv, init: acc, f: (xs, arg) => list_push(xs, arg)) -} - -fn host_effect_nbd_proxy_serve_witness_bin_run_argv( - transport: HostEffectTransport, - bin_path: String, - args: List, -) -> Bool { - match transport { - LocalShell => - gunbc.WitnessBin.Run(workdir: "/", bin_path: bin_path, args: args).success - SshShell { ssh_host: _ } => false - FleetSsh { target: _, context: _ } => false - EmitArtifactThenThinRun { bootstrap: _, invocation: _ } => false - } -} - +// String? rather than Bool: a caller composing several of these (start_units starts two units, +// drain_stale_units stops two) needs the REAL refusal reason from whichever leg failed, not a +// bare false that erases which unit and why -- the same distinction UnitStatusCapture/ +// SystemdPropertyCapture make in the sibling read modules. Absent means "started/stopped". fn host_effect_nbd_proxy_serve_systemd_run_transient( unit: NonEmptyStr, command_argv: List, transport: HostEffectTransport, -) -> Bool { - let args = host_effect_nbd_proxy_serve_append_argv( - acc: ["--unit", unit as String, "--collect", "--"], - argv: command_argv, - ) - host_effect_nbd_proxy_serve_witness_bin_run_argv( - transport: transport, - bin_path: "/usr/bin/systemd-run", - args: args, - ) +) -> String? { + match systemd_run_transient_read(transport: transport, unit: unit, command_argv: command_argv) { + SystemdRunTransientStarted { stdout: _ } => Absent + SystemdRunTransientRefused { reason: why } => + Present { value: concat(concat("unit ", unit as String), concat(": ", why)) } + } } fn host_effect_nbd_proxy_serve_systemd_stop_unit( unit: NonEmptyStr, transport: HostEffectTransport, -) -> Bool { - host_effect_nbd_proxy_serve_witness_bin_run_argv( - transport: transport, - bin_path: "/usr/bin/systemctl", - args: ["stop", unit as String], - ) +) -> String? { + match systemctl_stop_run(transport: transport, unit: unit) { + SystemctlStopped { stdout: _ } => Absent + SystemctlStopRefused { reason: why } => + Present { value: concat(concat("unit ", unit as String), concat(": ", why)) } + } +} + +// The transient unit name IS the identity anchor for this scope: nbd-proxy-{lease}-{role} is +// unique to "our" process, so IsActive on that exact unit is sufficient port/ownership evidence +// here without a ss/lsof port scan. pid: 0 and cmdline: the unit name are documented sentinels, +// not fabricated data -- classify_process_port (gunbc.session_lease) only inspects +// observed_port/process_fingerprint, never pid/cmdline, so the sentinel never reaches a decision. +fn host_effect_nbd_proxy_serve_observation_from_is_active_capture( + scope: ProcessPortScope, + websocat_unit: NonEmptyStr, + capture: UnitIsActiveCapture, +) -> ProcessPortObservation { + match capture { + UnitIsActiveCaptured { state: _, active: active } => + if active { + PortObserved { + observed_port: scope.listen_port, + pid: 0, + cmdline: websocat_unit, + process_fingerprint: scope.expected_process_fingerprint, + } + } else { + PortAbsent + } + UnitIsActiveCaptureRefused { reason: _ } => PortInaccessible + } } fn host_effect_nbd_proxy_serve_observe_port( - _scope: ProcessPortScope, + scope: ProcessPortScope, transport: HostEffectTransport, + websocat_unit: NonEmptyStr, ) -> ProcessPortObservation { - match transport { - LocalShell => PortInaccessible - SshShell { ssh_host: _ } => PortInaccessible - FleetSsh { target: _, context: _ } => PortInaccessible - EmitArtifactThenThinRun { bootstrap: _, invocation: _ } => PortInaccessible - } + host_effect_nbd_proxy_serve_observation_from_is_active_capture( + scope: scope, + websocat_unit: websocat_unit, + capture: systemctl_is_active_read(transport: transport, unit: websocat_unit), + ) } fn host_effect_nbd_proxy_serve_apply_refuses_when_observe_unimplemented( @@ -237,43 +236,53 @@ fn host_effect_nbd_proxy_serve_websocat_argv_materialized_omits_shell_env_ref( && !string_contains(s: surface, pattern: "$BMCWEB_SESSION_TOKEN") } +// String? here is the same real-reason-over-Bool move as the leaf functions above: the caller +// (host_effect_nbd_proxy_serve_apply) surfaces the ACTUAL typed refusal in +// NbdProxyServeApplyOutcome rather than a fixed "systemd-run transient start failed" string, +// and each leg (nbdkit unit, websocat unit, missing token) is attempted exactly once. fn host_effect_nbd_proxy_serve_start_units( binding: NbdProxyServeSessionBinding, transport: HostEffectTransport, -) -> Bool { +) -> String? { let nbdkit_unit = nbd_proxy_serve_nbdkit_unit_name(lease_key: binding.lease_key) let websocat_unit = nbd_proxy_serve_websocat_unit_name(lease_key: binding.lease_key) match host_effect_nbd_proxy_serve_read_session_token() { - Absent => false + Absent => Present { value: "BMCWEB session token absent" } Present { value: token } => - host_effect_nbd_proxy_serve_systemd_run_transient( + match host_effect_nbd_proxy_serve_systemd_run_transient( unit: nbdkit_unit, command_argv: nbd_proxy_serve_nbdkit_argv(intent: binding.intent), transport: transport, - ) - && host_effect_nbd_proxy_serve_systemd_run_transient( - unit: websocat_unit, - command_argv: host_effect_nbd_proxy_serve_websocat_argv_materialized( - intent: binding.intent, - token: token as NonEmptyStr, - ), - transport: transport, - ) + ) { + Present { value: why } => Present { value: why } + Absent => + host_effect_nbd_proxy_serve_systemd_run_transient( + unit: websocat_unit, + command_argv: host_effect_nbd_proxy_serve_websocat_argv_materialized( + intent: binding.intent, + token: token as NonEmptyStr, + ), + transport: transport, + ) + } } } fn host_effect_nbd_proxy_serve_drain_stale_units( binding: NbdProxyServeSessionBinding, transport: HostEffectTransport, -) -> Bool { - host_effect_nbd_proxy_serve_systemd_stop_unit( +) -> String? { + match host_effect_nbd_proxy_serve_systemd_stop_unit( unit: nbd_proxy_serve_nbdkit_unit_name(lease_key: binding.lease_key), transport: transport, - ) - && host_effect_nbd_proxy_serve_systemd_stop_unit( - unit: nbd_proxy_serve_websocat_unit_name(lease_key: binding.lease_key), - transport: transport, - ) + ) { + Present { value: why } => Present { value: why } + Absent => + host_effect_nbd_proxy_serve_systemd_stop_unit( + unit: nbd_proxy_serve_websocat_unit_name(lease_key: binding.lease_key), + transport: transport, + ) + } } type NbdProxyServeApplyOutcome @@ -284,40 +293,52 @@ fn host_effect_nbd_proxy_serve_apply( binding: NbdProxyServeSessionBinding, transport: HostEffectTransport, ) -> NbdProxyServeApplyOutcome { - let obs = host_effect_nbd_proxy_serve_observe_port(scope: binding.scope, transport: transport) + let websocat_unit = nbd_proxy_serve_websocat_unit_name(lease_key: binding.lease_key) + let obs = host_effect_nbd_proxy_serve_observe_port(scope: binding.scope, transport: transport, websocat_unit: websocat_unit) match host_effect_nbd_proxy_serve_apply_classification(binding: binding, obs: obs).decision { Noop => NbdProxyServeApplyConverged { stdout: concat( "NbdProxyServeSessionReceipt: verdict=Converged unit=", - nbd_proxy_serve_websocat_unit_name(lease_key: binding.lease_key) as String, + websocat_unit as String, ), } Refuse { reason: why } => NbdProxyServeApplyRefused { reason: why as String } Apply { plan: Start } => - if host_effect_nbd_proxy_serve_start_units(binding: binding, transport: transport) { - NbdProxyServeApplyConverged { - stdout: concat( - "NbdProxyServeSessionReceipt: verdict=Absent plan=Start unit=", - nbd_proxy_serve_websocat_unit_name(lease_key: binding.lease_key) as String, - ), - } - } else { - NbdProxyServeApplyRefused { reason: "nbd-proxy-serve: systemd-run transient start failed; fail-closed" } + match host_effect_nbd_proxy_serve_start_units(binding: binding, transport: transport) { + Absent => + NbdProxyServeApplyConverged { + stdout: concat( + "NbdProxyServeSessionReceipt: verdict=Absent plan=Start unit=", + websocat_unit as String, + ), + } + Present { value: why } => + NbdProxyServeApplyRefused { + reason: concat("nbd-proxy-serve: systemd-run transient start failed; fail-closed: ", why), + } } Apply { plan: DrainMatchingStaleThenStart } => - if host_effect_nbd_proxy_serve_drain_stale_units(binding: binding, transport: transport) - && host_effect_nbd_proxy_serve_start_units(binding: binding, transport: transport) - { - NbdProxyServeApplyConverged { - stdout: concat( - "NbdProxyServeSessionReceipt: verdict=Drifted plan=DrainMatchingStaleThenStart unit=", - nbd_proxy_serve_websocat_unit_name(lease_key: binding.lease_key) as String, - ), - } - } else { - NbdProxyServeApplyRefused { reason: "nbd-proxy-serve: stale drain or restart failed; fail-closed" } + match host_effect_nbd_proxy_serve_drain_stale_units(binding: binding, transport: transport) { + Present { value: why } => + NbdProxyServeApplyRefused { + reason: concat("nbd-proxy-serve: stale drain failed; fail-closed: ", why), + } + Absent => + match host_effect_nbd_proxy_serve_start_units(binding: binding, transport: transport) { + Absent => + NbdProxyServeApplyConverged { + stdout: concat( + "NbdProxyServeSessionReceipt: verdict=Drifted plan=DrainMatchingStaleThenStart unit=", + websocat_unit as String, + ), + } + Present { value: why } => + NbdProxyServeApplyRefused { + reason: concat("nbd-proxy-serve: restart after drain failed; fail-closed: ", why), + } + } } } } @@ -333,20 +354,31 @@ fn host_effect_nbd_proxy_serve_apply_with_observation( Refuse { reason: why } => NbdProxyServeApplyRefused { reason: why as String } Apply { plan: Start } => - if host_effect_nbd_proxy_serve_start_units(binding: binding, transport: transport) { - NbdProxyServeApplyConverged { stdout: "NbdProxyServeSessionReceipt: verdict=Absent plan=Start" } - } else { - NbdProxyServeApplyRefused { reason: "nbd-proxy-serve: systemd-run transient start failed; fail-closed" } + match host_effect_nbd_proxy_serve_start_units(binding: binding, transport: transport) { + Absent => + NbdProxyServeApplyConverged { stdout: "NbdProxyServeSessionReceipt: verdict=Absent plan=Start" } + Present { value: why } => + NbdProxyServeApplyRefused { + reason: concat("nbd-proxy-serve: systemd-run transient start failed; fail-closed: ", why), + } } Apply { plan: DrainMatchingStaleThenStart } => - if host_effect_nbd_proxy_serve_drain_stale_units(binding: binding, transport: transport) - && host_effect_nbd_proxy_serve_start_units(binding: binding, transport: transport) - { - NbdProxyServeApplyConverged { - stdout: "NbdProxyServeSessionReceipt: verdict=Drifted plan=DrainMatchingStaleThenStart", - } - } else { - NbdProxyServeApplyRefused { reason: "nbd-proxy-serve: stale drain or restart failed; fail-closed" } + match host_effect_nbd_proxy_serve_drain_stale_units(binding: binding, transport: transport) { + Present { value: why } => + NbdProxyServeApplyRefused { + reason: concat("nbd-proxy-serve: stale drain failed; fail-closed: ", why), + } + Absent => + match host_effect_nbd_proxy_serve_start_units(binding: binding, transport: transport) { + Absent => + NbdProxyServeApplyConverged { + stdout: "NbdProxyServeSessionReceipt: verdict=Drifted plan=DrainMatchingStaleThenStart", + } + Present { value: why } => + NbdProxyServeApplyRefused { + reason: concat("nbd-proxy-serve: restart after drain failed; fail-closed: ", why), + } + } } } } diff --git a/dag/gunbc/systemctl_is_active_read.dag b/dag/gunbc/systemctl_is_active_read.dag new file mode 100644 index 00000000000..8cfbc46031a --- /dev/null +++ b/dag/gunbc/systemctl_is_active_read.dag @@ -0,0 +1,111 @@ +module gunbc.systemctl_is_active_read + +import std.algebra { trim } + +import std.types { String, NonEmptyStr, List, Bool } +import extdeps.systemd.systemctl +import gunbc.host_effect { HostEffectTransport, LocalShell, SshShell, EmitArtifactThenThinRun } +import v2.std.operation_argv { + ArgvMaterialization, + ArgvMaterialized, + ArgvMaterializationRefused, + operation_argv_bind_text, + operation_argv_cause_label, +} +import gunbc.fleet_known_hosts_anchor { FleetSshExecutionContext, SshTarget } +import gunbc.typed_argv_exec { + typed_argv_exec_over_fleet_ssh, + typed_argv_exec_over_ssh, + typed_argv_shell_safe_refusal_argv, + TypedArgvExecConverged, + TypedArgvExecRefused, +} + +data systemctl_is_active_path: String = "dag/extdeps/systemd/systemctl.dag" +data systemctl_is_active_service: String = "systemd.Systemctl" +data systemctl_is_active_operation: String = "IsActive" + +type UnitIsActiveCapture = + UnitIsActiveCaptured { state: NonEmptyStr, active: Bool } + | UnitIsActiveCaptureRefused { reason: String } + +fn systemctl_is_active_operation_argv(unit: NonEmptyStr) -> ArgvMaterialization { + shell_materialize_operation_argv( + systemctl_is_active_path, + systemctl_is_active_service, + systemctl_is_active_operation, + [operation_argv_bind_text(name: "unit", text: unit as String)] + ) +} + +fn unit_is_active_capture_from_result(state: String, active: Bool) -> UnitIsActiveCapture { + let trimmed = trim(state) + if trimmed == "" { + UnitIsActiveCaptureRefused { reason: "systemctl is-active returned empty stdout" } + } else { + UnitIsActiveCaptured { state: trimmed as NonEmptyStr, active: active } + } +} + +fn systemctl_is_active_read_local(unit: NonEmptyStr) -> UnitIsActiveCapture { + let result = systemd.Systemctl.IsActive(unit: unit) + unit_is_active_capture_from_result(state: result.state, active: result.active) +} + +fn unit_is_active_capture_from_outcome(outcome: TypedArgvExecOutcome) -> UnitIsActiveCapture { + match outcome { + TypedArgvExecConverged { result: r } => + unit_is_active_capture_from_result(state: r.stdout, active: r.success) + TypedArgvExecRefused { reason: why } => UnitIsActiveCaptureRefused { reason: why } + } +} + +fn systemctl_is_active_read_ssh_argv(host: NonEmptyStr, argv: List) -> UnitIsActiveCapture { + match typed_argv_shell_safe_refusal_argv(argv: argv) { + Present { value: why } => UnitIsActiveCaptureRefused { reason: why } + Absent => unit_is_active_capture_from_outcome(outcome: typed_argv_exec_over_ssh(host: host, argv: argv)) + } +} + +fn systemctl_is_active_read_ssh(host: NonEmptyStr, unit: NonEmptyStr) -> UnitIsActiveCapture { + match systemctl_is_active_operation_argv(unit: unit) { + ArgvMaterialized { argv: argv } => systemctl_is_active_read_ssh_argv(host: host, argv: argv) + ArgvMaterializationRefused { at: _, cause: c } => + UnitIsActiveCaptureRefused { reason: operation_argv_cause_label(cause: c) } + } +} + +fn systemctl_is_active_read_fleet_ssh( + target: SshTarget, + context: FleetSshExecutionContext, + unit: NonEmptyStr, +) -> UnitIsActiveCapture { + match systemctl_is_active_operation_argv(unit: unit) { + ArgvMaterialized { argv: argv } => + unit_is_active_capture_from_outcome( + outcome: typed_argv_exec_over_fleet_ssh(target: target, context: context, argv: argv), + ) + ArgvMaterializationRefused { at: _, cause: c } => + UnitIsActiveCaptureRefused { reason: operation_argv_cause_label(cause: c) } + } +} + +fn systemctl_is_active_read(transport: HostEffectTransport, unit: NonEmptyStr) -> UnitIsActiveCapture { + match transport { + LocalShell => systemctl_is_active_read_local(unit: unit) + SshShell { ssh_host: h } => systemctl_is_active_read_ssh(host: h, unit: unit) + FleetSsh { target: t, context: c } => systemctl_is_active_read_fleet_ssh(target: t, context: c, unit: unit) + EmitArtifactThenThinRun { bootstrap: _, invocation: _ } => + UnitIsActiveCaptureRefused { + reason: "systemctl is-active refused EmitArtifactThenThinRun transport", + } + } +} + +fn systemctl_is_active_operation_argv_matches_authority(unit: NonEmptyStr) -> Bool { + let authority = ["systemctl", "is-active", unit as String] + match systemctl_is_active_operation_argv(unit: unit) { + ArgvMaterialized { argv: materialized } => join(materialized, " ") == join(authority, " ") + ArgvMaterializationRefused { at: _, cause: _ } => false + } +} diff --git a/dag/gunbc/systemctl_stop_run.dag b/dag/gunbc/systemctl_stop_run.dag new file mode 100644 index 00000000000..b62c281383e --- /dev/null +++ b/dag/gunbc/systemctl_stop_run.dag @@ -0,0 +1,108 @@ +module gunbc.systemctl_stop_run + +import std.types { String, NonEmptyStr, List, Bool } +import extdeps.systemd.systemctl +import gunbc.host_effect { HostEffectTransport, LocalShell, SshShell, EmitArtifactThenThinRun } +import v2.std.operation_argv { + ArgvMaterialization, + ArgvMaterialized, + ArgvMaterializationRefused, + operation_argv_bind_text, + operation_argv_cause_label, +} +import gunbc.fleet_known_hosts_anchor { FleetSshExecutionContext, SshTarget } +import gunbc.typed_argv_exec { + typed_argv_exec_over_fleet_ssh, + typed_argv_exec_over_ssh, + typed_argv_shell_safe_refusal_argv, + TypedArgvExecConverged, + TypedArgvExecRefused, +} + +data systemctl_stop_path: String = "dag/extdeps/systemd/systemctl.dag" +data systemctl_stop_service: String = "systemd.Systemctl" +data systemctl_stop_operation: String = "Stop" + +type SystemctlStopOutcome = + SystemctlStopped { stdout: String } + | SystemctlStopRefused { reason: String } + +fn systemctl_stop_operation_argv(unit: NonEmptyStr) -> ArgvMaterialization { + shell_materialize_operation_argv( + systemctl_stop_path, + systemctl_stop_service, + systemctl_stop_operation, + [operation_argv_bind_text(name: "unit", text: unit as String)] + ) +} + +fn systemctl_stop_outcome_from_result(success: Bool, stdout: String, stderr: String) -> SystemctlStopOutcome { + if success { + SystemctlStopped { stdout: stdout } + } else { + SystemctlStopRefused { reason: stderr } + } +} + +fn systemctl_stop_local(unit: NonEmptyStr) -> SystemctlStopOutcome { + let result = systemd.Systemctl.Stop(unit: unit) + systemctl_stop_outcome_from_result(success: result.success, stdout: result.stdout, stderr: result.stderr) +} + +fn systemctl_stop_outcome_from_exec(outcome: TypedArgvExecOutcome) -> SystemctlStopOutcome { + match outcome { + TypedArgvExecConverged { result: r } => + systemctl_stop_outcome_from_result(success: r.success, stdout: r.stdout, stderr: r.stderr) + TypedArgvExecRefused { reason: why } => SystemctlStopRefused { reason: why } + } +} + +fn systemctl_stop_ssh_argv(host: NonEmptyStr, argv: List) -> SystemctlStopOutcome { + match typed_argv_shell_safe_refusal_argv(argv: argv) { + Present { value: why } => SystemctlStopRefused { reason: why } + Absent => systemctl_stop_outcome_from_exec(outcome: typed_argv_exec_over_ssh(host: host, argv: argv)) + } +} + +fn systemctl_stop_ssh(host: NonEmptyStr, unit: NonEmptyStr) -> SystemctlStopOutcome { + match systemctl_stop_operation_argv(unit: unit) { + ArgvMaterialized { argv: argv } => systemctl_stop_ssh_argv(host: host, argv: argv) + ArgvMaterializationRefused { at: _, cause: c } => + SystemctlStopRefused { reason: operation_argv_cause_label(cause: c) } + } +} + +fn systemctl_stop_fleet_ssh( + target: SshTarget, + context: FleetSshExecutionContext, + unit: NonEmptyStr, +) -> SystemctlStopOutcome { + match systemctl_stop_operation_argv(unit: unit) { + ArgvMaterialized { argv: argv } => + systemctl_stop_outcome_from_exec( + outcome: typed_argv_exec_over_fleet_ssh(target: target, context: context, argv: argv), + ) + ArgvMaterializationRefused { at: _, cause: c } => + SystemctlStopRefused { reason: operation_argv_cause_label(cause: c) } + } +} + +fn systemctl_stop_run(transport: HostEffectTransport, unit: NonEmptyStr) -> SystemctlStopOutcome { + match transport { + LocalShell => systemctl_stop_local(unit: unit) + SshShell { ssh_host: h } => systemctl_stop_ssh(host: h, unit: unit) + FleetSsh { target: t, context: c } => systemctl_stop_fleet_ssh(target: t, context: c, unit: unit) + EmitArtifactThenThinRun { bootstrap: _, invocation: _ } => + SystemctlStopRefused { + reason: "systemctl stop refused EmitArtifactThenThinRun transport", + } + } +} + +fn systemctl_stop_operation_argv_matches_authority(unit: NonEmptyStr) -> Bool { + let authority = ["systemctl", "stop", unit as String] + match systemctl_stop_operation_argv(unit: unit) { + ArgvMaterialized { argv: materialized } => join(materialized, " ") == join(authority, " ") + ArgvMaterializationRefused { at: _, cause: _ } => false + } +} diff --git a/dag/gunbc/systemd_run_transient.dag b/dag/gunbc/systemd_run_transient.dag new file mode 100644 index 00000000000..f3ae7007667 --- /dev/null +++ b/dag/gunbc/systemd_run_transient.dag @@ -0,0 +1,121 @@ +module gunbc.systemd_run_transient + +import std.types { String, NonEmptyStr, List, Bool } +import extdeps.systemd.systemd_run +import gunbc.host_effect { HostEffectTransport, LocalShell, SshShell, EmitArtifactThenThinRun } +import v2.std.operation_argv { + ArgvMaterialization, + ArgvMaterialized, + ArgvMaterializationRefused, + operation_argv_bind_text, + operation_argv_bind_text_list, + operation_argv_cause_label, +} +import gunbc.fleet_known_hosts_anchor { FleetSshExecutionContext, SshTarget } +import gunbc.typed_argv_exec { + typed_argv_exec_over_fleet_ssh, + typed_argv_exec_over_ssh, + typed_argv_shell_safe_refusal_argv, + TypedArgvExecConverged, + TypedArgvExecRefused, +} + +data systemd_run_transient_path: String = "dag/extdeps/systemd/systemd_run.dag" +data systemd_run_transient_service: String = "systemd.SystemdRun" +data systemd_run_transient_operation: String = "RunTransient" + +type SystemdRunTransientOutcome = + SystemdRunTransientStarted { stdout: String } + | SystemdRunTransientRefused { reason: String } + +fn systemd_run_transient_operation_argv(unit: NonEmptyStr, command_argv: List) -> ArgvMaterialization { + shell_materialize_operation_argv( + systemd_run_transient_path, + systemd_run_transient_service, + systemd_run_transient_operation, + [ + operation_argv_bind_text(name: "unit", text: unit as String), + operation_argv_bind_text_list(name: "command_argv", items: command_argv), + ] + ) +} + +fn systemd_run_transient_outcome_from_result(success: Bool, stdout: String, stderr: String) -> SystemdRunTransientOutcome { + if success { + SystemdRunTransientStarted { stdout: stdout } + } else { + SystemdRunTransientRefused { reason: stderr } + } +} + +fn systemd_run_transient_local(unit: NonEmptyStr, command_argv: List) -> SystemdRunTransientOutcome { + let result = systemd.SystemdRun.RunTransient(unit: unit, command_argv: command_argv) + systemd_run_transient_outcome_from_result(success: result.success, stdout: result.stdout, stderr: result.stderr) +} + +fn systemd_run_transient_outcome_from_exec(outcome: TypedArgvExecOutcome) -> SystemdRunTransientOutcome { + match outcome { + TypedArgvExecConverged { result: r } => + systemd_run_transient_outcome_from_result(success: r.success, stdout: r.stdout, stderr: r.stderr) + TypedArgvExecRefused { reason: why } => SystemdRunTransientRefused { reason: why } + } +} + +fn systemd_run_transient_ssh_argv(host: NonEmptyStr, argv: List) -> SystemdRunTransientOutcome { + match typed_argv_shell_safe_refusal_argv(argv: argv) { + Present { value: why } => SystemdRunTransientRefused { reason: why } + Absent => systemd_run_transient_outcome_from_exec(outcome: typed_argv_exec_over_ssh(host: host, argv: argv)) + } +} + +fn systemd_run_transient_ssh(host: NonEmptyStr, unit: NonEmptyStr, command_argv: List) -> SystemdRunTransientOutcome { + match systemd_run_transient_operation_argv(unit: unit, command_argv: command_argv) { + ArgvMaterialized { argv: argv } => systemd_run_transient_ssh_argv(host: host, argv: argv) + ArgvMaterializationRefused { at: _, cause: c } => + SystemdRunTransientRefused { reason: operation_argv_cause_label(cause: c) } + } +} + +fn systemd_run_transient_fleet_ssh( + target: SshTarget, + context: FleetSshExecutionContext, + unit: NonEmptyStr, + command_argv: List, +) -> SystemdRunTransientOutcome { + match systemd_run_transient_operation_argv(unit: unit, command_argv: command_argv) { + ArgvMaterialized { argv: argv } => + systemd_run_transient_outcome_from_exec( + outcome: typed_argv_exec_over_fleet_ssh(target: target, context: context, argv: argv), + ) + ArgvMaterializationRefused { at: _, cause: c } => + SystemdRunTransientRefused { reason: operation_argv_cause_label(cause: c) } + } +} + +fn systemd_run_transient_read( + transport: HostEffectTransport, + unit: NonEmptyStr, + command_argv: List, +) -> SystemdRunTransientOutcome { + match transport { + LocalShell => systemd_run_transient_local(unit: unit, command_argv: command_argv) + SshShell { ssh_host: h } => systemd_run_transient_ssh(host: h, unit: unit, command_argv: command_argv) + FleetSsh { target: t, context: c } => systemd_run_transient_fleet_ssh(target: t, context: c, unit: unit, command_argv: command_argv) + EmitArtifactThenThinRun { bootstrap: _, invocation: _ } => + SystemdRunTransientRefused { + reason: "systemd-run transient start refused EmitArtifactThenThinRun transport", + } + } +} + +fn systemd_run_transient_operation_argv_matches_authority(unit: NonEmptyStr, command_argv: List) -> Bool { + let authority = fold( + command_argv, + init: ["systemd-run", concat("--unit=", unit as String), "--collect", "--"], + f: (acc, arg) => list_push(acc, arg), + ) + match systemd_run_transient_operation_argv(unit: unit, command_argv: command_argv) { + ArgvMaterialized { argv: materialized } => join(materialized, " ") == join(authority, " ") + ArgvMaterializationRefused { at: _, cause: _ } => false + } +} diff --git a/dag/gunbc/witness_deferral_freeze.dag b/dag/gunbc/witness_deferral_freeze.dag index 18d387b0699..7122d1539bf 100644 --- a/dag/gunbc/witness_deferral_freeze.dag +++ b/dag/gunbc/witness_deferral_freeze.dag @@ -16,7 +16,7 @@ WHAT THIS ROSTER IS, AND WHAT IT IS NOT. It is the exact identity-grain populati THE RATCHET, and why it is a freeze rather than a sweep. Classifying every historical resident in one pass would either red the repo or produce 701 guesses at purposes nobody measured; both are worse than the debt. So the population is frozen as-is and the FORWARD hole closes immediately: a new or relocated witness under any OfflineLocalRecipe path must carry an exact admission naming an executing cadence, which is the same bar every other deferral already meets. Legacy rows migrate as their files are touched or as a cadence enrolls them. DISSOLVE-ON: this roster reaching empty β€” at which point the path-policy pass-through arm in both v2.workflow.witness_admission and the host projection deletes with it, and OfflineLocalRecipe stops being an admission answer at all." -data witness_deferral_freeze_shrink_log_note: String = "SHRINK LOG. The 701-identity / 152-entry figures the notes around this one quote are the FREEZE-POINT BASELINE and are deliberately not restated as the roster's current size β€” a hand-maintained live count is the change detector DESIGN section 5's oracle rule rejects, and the roster below is the authority for what the population is right now. This row records migrations OUT, which is the only direction permitted, so that the baseline stays legible as history rather than decaying into a false present-tense claim. 2026-08-19 β€” 24 entries (dag/test/claim/ci_exclusion_proof_test.dag, dag/test/claim/deploy_access_privilege_witness_test.dag [partial], dag/test/claim/host_effect_apply_witness_test.dag [partial], dag/test/claim/interp_recorded_fixture_witness_test.dag, dag/test/claim/random_bytes_csprng_witness_test.dag, dag/test/claim/self_host_00_compile_behavioral_witness_test.dag, dag/test/claim/self_host_01_tokenize_behavioral_witness_test.dag, dag/test/claim/self_host_02_parse_behavioral_witness_test.dag, dag/test/claim/self_host_03_ingest_behavioral_witness_test.dag, dag/test/claim/self_host_03_resolve_behavioral_witness_test.dag, dag/test/claim/self_host_04_infer_behavioral_witness_test.dag, dag/test/claim/self_host_materialization_carriers_behavioral_witness_test.dag, dag/test/claim/self_host_program_assembly_behavioral_witness_test.dag, dag/test/claim/self_host_program_partition_behavioral_witness_test.dag, dag/test/claim/self_host_source_authority_behavioral_witness_test.dag, dag/test/claim/srv3_host_effect_apply_witness_test.dag [partial], src/v2/test/claim/execution/dag_add_emit_round_trip_test.dag [partial], src/v2/test/claim/execution/emit_host_complement_equals_eval_test.dag, src/v2/test/claim/execution/emit_host_variant_construct_equals_eval_test.dag, src/v2/test/claim/execution/emit_ingest_python_same_language_round_trip_test.dag [partial], src/v2/test/claim/execution/emit_ingest_type_decl_round_trip_test.dag [partial], src/v2/test/claim/execution/emit_ingest_typescript_same_language_round_trip_test.dag [partial], src/v2/test/claim/execution/floor_diff_observe_witness_test.dag, src/v2/test/claim/execution/proactive_verification_ledger_overlap_execution_test.dag), 38 identities, DELETED not exempted: these identities were simultaneously enrolled in v2.workflow.floor_expected_red's known-red roster (removable only by observing a pass) and path-deferred here as LegacyFrozenPathDeferral (declared never-executed) β€” a contradictory intersection, because the required floor already treats each of these identities as an executing, known-red subject, so their freeze classification was stale rather than load-bearing; a frozen row naming a witness the required floor already runs has no LegacyFrozenPathDeferral standing left to protect. Computed by joining floor_expected_red_roster against this file's frozen_path_deferrals, qualifying every frozen row through its own entry's module line (not its path string), at commit 063a604e0099c56c3e0a1f72af60d7a6199d1f0b: 38 of 669 qualified freeze identities across 24 entries collided. All 24 entries carry an independently executing consumer already (WitnessHasExecutingConsumer via floor_expected_red), so retirement here removes no coverage β€” 15 entries had every one of their functions collide and are deleted whole; 9 entries had only some functions collide and keep their remaining, non-colliding functions frozen. A construction wall (expected_red_freeze_intersection, wired into run_required_floor in src/v1/stage0/src/cli_run.rs) now refuses this exact contradiction going forward: any future frozen row whose qualified identity re-enters floor_expected_red_roster fails the required floor with a located, counted refusal naming the exact colliding identities and the git head the collision was observed at, rather than silently coexisting. 2026-08-19 β€” src/v2/test/claim/execution/long/add_arrow_eval_by_execution_test.dag, 4 identities, and src/v2/test/claim/execution/long/pick_ingested_probe_test.dag, 1 identity, DELETED as subsumed or duplicate coverage, none migrated and none exempted. add_arrow_eval_tokenize_holds, add_arrow_eval_parse_holds and add_arrow_eval_normalize_holds were the shallow steps of a staircase whose deepest step, add_arrow_eval_resolve_holds, cannot reach resolve unless all three stages accepted, so four stages were costing ten stage-runs across four frames to assert what one row asserts; the survivor reds on exactly the refusals they redded on and what is lost is which stage failed. add_arrow_eval_produced_add_body_executes_holds had a body byte-identical to add_arrow_eval_source_driven_add_executes_holds β€” and BOTH ARE CURRENTLY RED, which is stated rather than left for a reader to assume a green row was removed: main head 5ee2572 fails the pair identically, along with add_arrow_eval_direct_runtime_holds, add_arrow_eval_generality_executes_holds and add_arrow_eval_lazy_arm_branch_suppression_holds, the five identities floor_expected_red already documents as enrolled-and-correctly-red before this module relocated. The surviving twin carries the red; deleting a duplicate of a failing claim removes a second report of one failure, not the report of it. pick_probe_resolved_else_arm_has_magnitude_child_holds differed from its then-arm sibling only in an arm index over one source at one stage, and both arms are now asserted against one bound resolved module; both passed on main and the merged row passes. In every case the assertion survives on a row that keeps it; none of this coverage is relocated because none of it was ever separate. 2026-08-19 β€” src/v2/test/claim/execution/dag_add_emit_round_trip_test.dag, 1 identity (dag_add_emit_add_fn_accepts_holds), DELETED as duplicate coverage, not migrated and not exempted: its whole body was a call to its sibling dag_add_emit_matches_serialize_holds, so it asserted nothing that sibling did not already assert and paid a second full emit-and-serialize crossing to assert it, each claim evaluating in its own frame. Deleting it removes a crossing and no fact. This is a shrink by deletion of the witness, and the coverage it named is not relocated because it was never separate from the sibling row that keeps it. 2026-08-11 β€” dag/test/claim/long/inert_lens_hygiene_witness_test.dag, 2 identities, DELETED not migrated and not exempted: the inert-lens census the two witnesses read is gone from the tree entirely, along with the v2.lens.inert_lens module, its two host builtins and the registry/contract rows that obligated it, so the witnesses have no subject to execute against. A frozen row naming a witness the tree no longer carries is a StaleFrozenPathDeferral refusal, which is why the row deletes in the same change rather than after it. This is a shrink by deletion of the subject, NOT by a cadence enrolling the rows β€” the coverage those two witnesses provided is not relocated anywhere and the scope narrowing is declared in DESIGN Β§6. 2026-08-06 β€” src/v2/test/claim/long/realization_vocabulary_containment_witness_test.dag, 1 identity, MIGRATED not exempted: TS-0/LANG-2 enrolled realization_vocab_live_corpus_receipt_holds on falsifier_substrate_long_lane_rows (FalsifierSubstrateLongLane cadence), so the freeze row deletes and the live-corpus receipt executes on the scheduled lane. 2026-08-05 β€” src/v2/test/claim/long/orchestration_while_emit_test.dag, 14 identities, MIGRATED not exempted: the file returned to src/v2/workflow/orchestration_while_emit_test.dag, the per-PR-discovered path it occupied before gunbc#7098 relocated it, so its rows now classify as WitnessHasExecutingConsumer and the freeze row had to go β€” a row naming a witness the tree no longer carries at that entry is a StaleFrozenPathDeferral refusal, and its diagnostic says to delete it in the change that moved the witness. Measured eval justifying the return, on a load-average-28 host: 94 / 54 / 36 / 8 ms per witness against the 5000ms gunbc_ci_fast_lane_witness_eval_budget; those are wall figures against a thread-CPU budget, and cpu <= wall for one witness thread, so the bound runs the safe way. Rationale and the receipt live with the witness in orch_while_unfreeze_note." +data witness_deferral_freeze_shrink_log_note: String = "SHRINK LOG. The 701-identity / 152-entry figures the notes around this one quote are the FREEZE-POINT BASELINE and are deliberately not restated as the roster's current size β€” a hand-maintained live count is the change detector DESIGN section 5's oracle rule rejects, and the roster below is the authority for what the population is right now. This row records migrations OUT, which is the only direction permitted, so that the baseline stays legible as history rather than decaying into a false present-tense claim. 2026-08-19 β€” dag/test/claim/srv3_host_effect_apply_witness_test.dag [partial], 2 identities (witness_srv3_nbd_proxy_apply_observe_unimplemented_refuses_fail_closed, srv3_nbd_proxy_serve_realize_layer_dissolves_via_host_effect_nbd_proxy_serve), DELETED not migrated and not exempted: host_effect_nbd_proxy_serve_dissolution_trigger dissolved (observe-side port/unit read-back grounded on gunbc.systemctl_is_active_read; actuate-side typed argv dispatch via gunbc.systemd_run_transient / gunbc.systemctl_stop_run retired the WitnessBin scaffolding), so the module's disposition became std.disposition.Terminal and the two witness functions were rewritten under new names (witness_srv3_nbd_proxy_apply_emit_artifact_transport_observe_refuses_fail_closed, srv3_nbd_proxy_serve_realize_layer_is_terminal_via_host_effect_nbd_proxy_serve) that assert the grounded/Terminal behavior instead of the old stub/Scaffold behavior. The old identities no longer exist in the tree under those names, so their frozen rows are StaleFrozenPathDeferral and delete in this change rather than after it; this is a shrink, and the remaining functions at this entry are untouched. 2026-08-19 β€” 24 entries (dag/test/claim/ci_exclusion_proof_test.dag, dag/test/claim/deploy_access_privilege_witness_test.dag [partial], dag/test/claim/host_effect_apply_witness_test.dag [partial], dag/test/claim/interp_recorded_fixture_witness_test.dag, dag/test/claim/random_bytes_csprng_witness_test.dag, dag/test/claim/self_host_00_compile_behavioral_witness_test.dag, dag/test/claim/self_host_01_tokenize_behavioral_witness_test.dag, dag/test/claim/self_host_02_parse_behavioral_witness_test.dag, dag/test/claim/self_host_03_ingest_behavioral_witness_test.dag, dag/test/claim/self_host_03_resolve_behavioral_witness_test.dag, dag/test/claim/self_host_04_infer_behavioral_witness_test.dag, dag/test/claim/self_host_materialization_carriers_behavioral_witness_test.dag, dag/test/claim/self_host_program_assembly_behavioral_witness_test.dag, dag/test/claim/self_host_program_partition_behavioral_witness_test.dag, dag/test/claim/self_host_source_authority_behavioral_witness_test.dag, dag/test/claim/srv3_host_effect_apply_witness_test.dag [partial], src/v2/test/claim/execution/dag_add_emit_round_trip_test.dag [partial], src/v2/test/claim/execution/emit_host_complement_equals_eval_test.dag, src/v2/test/claim/execution/emit_host_variant_construct_equals_eval_test.dag, src/v2/test/claim/execution/emit_ingest_python_same_language_round_trip_test.dag [partial], src/v2/test/claim/execution/emit_ingest_type_decl_round_trip_test.dag [partial], src/v2/test/claim/execution/emit_ingest_typescript_same_language_round_trip_test.dag [partial], src/v2/test/claim/execution/floor_diff_observe_witness_test.dag, src/v2/test/claim/execution/proactive_verification_ledger_overlap_execution_test.dag), 38 identities, DELETED not exempted: these identities were simultaneously enrolled in v2.workflow.floor_expected_red's known-red roster (removable only by observing a pass) and path-deferred here as LegacyFrozenPathDeferral (declared never-executed) β€” a contradictory intersection, because the required floor already treats each of these identities as an executing, known-red subject, so their freeze classification was stale rather than load-bearing; a frozen row naming a witness the required floor already runs has no LegacyFrozenPathDeferral standing left to protect. Computed by joining floor_expected_red_roster against this file's frozen_path_deferrals, qualifying every frozen row through its own entry's module line (not its path string), at commit 063a604e0099c56c3e0a1f72af60d7a6199d1f0b: 38 of 669 qualified freeze identities across 24 entries collided. All 24 entries carry an independently executing consumer already (WitnessHasExecutingConsumer via floor_expected_red), so retirement here removes no coverage β€” 15 entries had every one of their functions collide and are deleted whole; 9 entries had only some functions collide and keep their remaining, non-colliding functions frozen. A construction wall (expected_red_freeze_intersection, wired into run_required_floor in src/v1/stage0/src/cli_run.rs) now refuses this exact contradiction going forward: any future frozen row whose qualified identity re-enters floor_expected_red_roster fails the required floor with a located, counted refusal naming the exact colliding identities and the git head the collision was observed at, rather than silently coexisting. 2026-08-19 β€” src/v2/test/claim/execution/long/add_arrow_eval_by_execution_test.dag, 4 identities, and src/v2/test/claim/execution/long/pick_ingested_probe_test.dag, 1 identity, DELETED as subsumed or duplicate coverage, none migrated and none exempted. add_arrow_eval_tokenize_holds, add_arrow_eval_parse_holds and add_arrow_eval_normalize_holds were the shallow steps of a staircase whose deepest step, add_arrow_eval_resolve_holds, cannot reach resolve unless all three stages accepted, so four stages were costing ten stage-runs across four frames to assert what one row asserts; the survivor reds on exactly the refusals they redded on and what is lost is which stage failed. add_arrow_eval_produced_add_body_executes_holds had a body byte-identical to add_arrow_eval_source_driven_add_executes_holds β€” and BOTH ARE CURRENTLY RED, which is stated rather than left for a reader to assume a green row was removed: main head 5ee2572 fails the pair identically, along with add_arrow_eval_direct_runtime_holds, add_arrow_eval_generality_executes_holds and add_arrow_eval_lazy_arm_branch_suppression_holds, the five identities floor_expected_red already documents as enrolled-and-correctly-red before this module relocated. The surviving twin carries the red; deleting a duplicate of a failing claim removes a second report of one failure, not the report of it. pick_probe_resolved_else_arm_has_magnitude_child_holds differed from its then-arm sibling only in an arm index over one source at one stage, and both arms are now asserted against one bound resolved module; both passed on main and the merged row passes. In every case the assertion survives on a row that keeps it; none of this coverage is relocated because none of it was ever separate. 2026-08-19 β€” src/v2/test/claim/execution/dag_add_emit_round_trip_test.dag, 1 identity (dag_add_emit_add_fn_accepts_holds), DELETED as duplicate coverage, not migrated and not exempted: its whole body was a call to its sibling dag_add_emit_matches_serialize_holds, so it asserted nothing that sibling did not already assert and paid a second full emit-and-serialize crossing to assert it, each claim evaluating in its own frame. Deleting it removes a crossing and no fact. This is a shrink by deletion of the witness, and the coverage it named is not relocated because it was never separate from the sibling row that keeps it. 2026-08-11 β€” dag/test/claim/long/inert_lens_hygiene_witness_test.dag, 2 identities, DELETED not migrated and not exempted: the inert-lens census the two witnesses read is gone from the tree entirely, along with the v2.lens.inert_lens module, its two host builtins and the registry/contract rows that obligated it, so the witnesses have no subject to execute against. A frozen row naming a witness the tree no longer carries is a StaleFrozenPathDeferral refusal, which is why the row deletes in the same change rather than after it. This is a shrink by deletion of the subject, NOT by a cadence enrolling the rows β€” the coverage those two witnesses provided is not relocated anywhere and the scope narrowing is declared in DESIGN Β§6. 2026-08-06 β€” src/v2/test/claim/long/realization_vocabulary_containment_witness_test.dag, 1 identity, MIGRATED not exempted: TS-0/LANG-2 enrolled realization_vocab_live_corpus_receipt_holds on falsifier_substrate_long_lane_rows (FalsifierSubstrateLongLane cadence), so the freeze row deletes and the live-corpus receipt executes on the scheduled lane. 2026-08-05 β€” src/v2/test/claim/long/orchestration_while_emit_test.dag, 14 identities, MIGRATED not exempted: the file returned to src/v2/workflow/orchestration_while_emit_test.dag, the per-PR-discovered path it occupied before gunbc#7098 relocated it, so its rows now classify as WitnessHasExecutingConsumer and the freeze row had to go β€” a row naming a witness the tree no longer carries at that entry is a StaleFrozenPathDeferral refusal, and its diagnostic says to delete it in the change that moved the witness. Measured eval justifying the return, on a load-average-28 host: 94 / 54 / 36 / 8 ms per witness against the 5000ms gunbc_ci_fast_lane_witness_eval_budget; those are wall figures against a thread-CPU budget, and cpu <= wall for one witness thread, so the bound runs the safe way. Rationale and the receipt live with the witness in orch_while_unfreeze_note." data witness_deferral_freeze_purpose_frontier_note: String = "WHAT THIS FREEZE DELIBERATELY DOES NOT CARRY, so its coverage is not overread. A frozen row records IDENTITY only β€” that this exact (entry, function) was already deferred by path policy when the wall landed. It records no purpose, no measured cost, and no consumer, because it has none: the whole point of the freeze is that these 701 identities (152 entries, measured at the freeze point) were admitted without any of the three. The purpose taxonomy (which boundary, population, external effect, or resource contract justifies the size) and the per-witness cost envelope are the NEXT slice, and a frozen row is the debt marker that one is owed. @@ -121,12 +121,10 @@ data frozen_path_deferrals: List = [ "witness_srv3_nbd_proxy_apply_foreign_port_refuses_at_apply_time", "witness_srv3_nbd_proxy_apply_stale_matching_drains_not_foreign_kill", "witness_srv3_nbd_proxy_websocat_argv_materializes_token_not_shell_env_ref", - "witness_srv3_nbd_proxy_apply_observe_unimplemented_refuses_fail_closed", "witness_srv3_nbd_proxy_serve_session_binding_carries_lease", "witness_srv3_nbd_proxy_on_bmc_refuses_typed", "srv3_typed_receipt_emit_realizes_in_process_not_shell", - "srv3_typed_receipt_carrier_holds_lines", "shell_bash_runner_still_present_for_p5b", - "srv3_nbd_proxy_serve_realize_layer_dissolves_via_host_effect_nbd_proxy_serve" + "srv3_typed_receipt_carrier_holds_lines", "shell_bash_runner_still_present_for_p5b" ] }, FrozenPathDeferral { diff --git a/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag b/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag index 0427cf0c09d..226dd809968 100644 --- a/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag +++ b/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag @@ -111,3 +111,85 @@ fn witness_bmcweb_session_login_argv_semantic_surface_holds() -> Bool { test fn nbd_proxy_serve_host_role_is_nbd_server() -> Bool { nbd_proxy_serve_requires_host_as_nbd_server() } + +// host_effect_nbd_proxy_serve's dissolution trigger DISSOLVED (both conditions met): observe-side +// port/unit read-back is grounded on gunbc.systemctl_is_active_read (cited extdeps.systemd.systemctl +// IsActive), and actuate-side typed argv dispatch (gunbc.systemd_run_transient, +// gunbc.systemctl_stop_run) retired the WitnessBin systemd-run/systemctl scaffolding. These witnesses +// exercise both halves. + +fn nbd_proxy_observe_port_test_scope() -> ProcessPortScope { + ProcessPortScope { + listen_port: 10809, + expected_owner_label: "gunbc-nbd-proxy" as NonEmptyStr, + expected_process_fingerprint: "gunbc-nbd-proxy" as NonEmptyStr, + } +} + +test fn nbd_proxy_observe_port_active_capture_yields_port_observed() -> Bool { + let scope = nbd_proxy_observe_port_test_scope() + let unit = "nbd-proxy-srv3-nbd-proxy-10809-ws" as NonEmptyStr + match host_effect_nbd_proxy_serve_observation_from_is_active_capture( + scope: scope, + websocat_unit: unit, + capture: UnitIsActiveCaptured { state: "active" as NonEmptyStr, active: true }, + ) { + PortObserved { observed_port: port, pid: p, cmdline: c, process_fingerprint: fp } => + port == scope.listen_port + && p == 0 + && (c as String) == (unit as String) + && fp == scope.expected_process_fingerprint + _ => false + } +} + +test fn nbd_proxy_observe_port_inactive_capture_yields_port_absent() -> Bool { + let scope = nbd_proxy_observe_port_test_scope() + let unit = "nbd-proxy-srv3-nbd-proxy-10809-ws" as NonEmptyStr + match host_effect_nbd_proxy_serve_observation_from_is_active_capture( + scope: scope, + websocat_unit: unit, + capture: UnitIsActiveCaptured { state: "inactive" as NonEmptyStr, active: false }, + ) { + PortAbsent => true + _ => false + } +} + +test fn nbd_proxy_observe_port_capture_refusal_yields_port_inaccessible() -> Bool { + let scope = nbd_proxy_observe_port_test_scope() + let unit = "nbd-proxy-srv3-nbd-proxy-10809-ws" as NonEmptyStr + match host_effect_nbd_proxy_serve_observation_from_is_active_capture( + scope: scope, + websocat_unit: unit, + capture: UnitIsActiveCaptureRefused { reason: "typed argv shell-safe refusal" }, + ) { + PortInaccessible => true + _ => false + } +} + +test fn nbd_proxy_observe_port_local_shell_is_active_active_wires_through() -> Bool { + let scope = nbd_proxy_observe_port_test_scope() + let unit = "nbd-proxy-srv3-nbd-proxy-10809-ws" as NonEmptyStr + match host_effect_nbd_proxy_serve_observe_port(scope: scope, transport: LocalShell, websocat_unit: unit) { + PortObserved { observed_port: port, pid: _, cmdline: _, process_fingerprint: fp } => + port == scope.listen_port && fp == scope.expected_process_fingerprint + _ => false + } +} + +test fn systemd_run_transient_operation_argv_matches_authority() -> Bool { + systemd_run_transient_operation_argv_matches_authority( + unit: "nbd-proxy-srv3-nbd-proxy-10809" as NonEmptyStr, + command_argv: ["nbdkit", "-p", "10809", "file", "/var/lib/gunbc/artifacts/x.iso"], + ) +} + +test fn systemctl_stop_operation_argv_matches_authority() -> Bool { + systemctl_stop_operation_argv_matches_authority(unit: "nbd-proxy-srv3-nbd-proxy-10809" as NonEmptyStr) +} + +test fn systemctl_is_active_operation_argv_matches_authority() -> Bool { + systemctl_is_active_operation_argv_matches_authority(unit: "nbd-proxy-srv3-nbd-proxy-10809-ws" as NonEmptyStr) +} 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 16bb087fd7e..ad54e35ca33 100644 --- a/dag/test/claim/srv3_host_effect_apply_witness_test.dag +++ b/dag/test/claim/srv3_host_effect_apply_witness_test.dag @@ -199,13 +199,21 @@ test fn witness_srv3_nbd_proxy_websocat_argv_materializes_token_not_shell_env_re } } -test fn witness_srv3_nbd_proxy_apply_observe_unimplemented_refuses_fail_closed() -> Bool { +// observe-side is now grounded on systemctl is-active read-back (host_effect_nbd_proxy_serve +// P6 Part 2 dissolution trigger, retired). LocalShell/SshShell/FleetSsh can genuinely observe, so +// this no longer names "observe unimplemented" universally -- it names the one transport that +// genuinely cannot: EmitArtifactThenThinRun, whose typed dispatch refuses before any read-back is +// attempted, which classify_process_port_upsert renders as the same "port inaccessible" verdict. +test fn witness_srv3_nbd_proxy_apply_emit_artifact_transport_observe_refuses_fail_closed() -> Bool { let effect = srv3_nbd_proxy_serve_host_effect() match effect { Srv3NbdProxyServe { intent: i, lease_key: k } => host_effect_nbd_proxy_serve_apply_refuses_when_observe_unimplemented( binding: host_effect_nbd_proxy_serve_session_binding(intent: i, lease_key: k), - transport: LocalShell, + transport: EmitArtifactThenThinRun { + bootstrap: BootstrapFragment { script: "true" }, + invocation: ThinInvocation { argv: ["true"] }, + }, ) _ => false } @@ -252,10 +260,19 @@ test fn shell_bash_runner_still_present_for_p5b() -> Bool { string_contains(s: dissolution_description(condition: shell_bash_runner_dissolution_trigger), pattern: "shell_exec_via_bash") } -test fn srv3_nbd_proxy_serve_realize_layer_dissolves_via_host_effect_nbd_proxy_serve() -> Bool { - string_contains(s: dissolution_description(condition: host_effect_nbd_proxy_serve_dissolution_trigger), pattern: "systemd") - && string_contains(s: dissolution_description(condition: host_effect_nbd_proxy_serve_dissolution_trigger), pattern: "srv3_bmcweb_token_path") - && string_contains(s: dissolution_description(condition: host_effect_nbd_proxy_serve_dissolution_trigger), pattern: "P6 Part 2") +// host_effect_nbd_proxy_serve_dissolution_trigger / _scaffold are RETIRED: both dissolution +// conditions (observe-side grounded on cited extdeps.systemd/systemctl read-back; typed argv +// dispatch retiring the WitnessBin systemd-run scaffolding) are met, so the module now carries a +// std.disposition.Terminal disposition instead of a Scaffold. This asserts the Terminal disposition +// names what replaced the scaffold, rather than asserting a dissolved carrier still exists. +test fn srv3_nbd_proxy_serve_realize_layer_is_terminal_via_host_effect_nbd_proxy_serve() -> Bool { + match host_effect_nbd_proxy_serve_disposition { + Terminal { reason: why } => + string_contains(s: why as String, pattern: "systemd") + && string_contains(s: why as String, pattern: "systemctl_is_active_read") + && string_contains(s: why as String, pattern: "P6 Part 2") + Scaffold { dissolves_to: _, bind: _ } => false + } } From 7a45f34a933cd2c127996b8b2092cf3fbe923b0a Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 19 Aug 2026 22:14:38 +0000 Subject: [PATCH 2/3] Fix name-shadowing crash in nbd-proxy witness test: rename test fns The three new argv-authority test wrappers shared the exact name of the function they called (systemd_run_transient_operation_argv_matches_authority etc.), so the test fn's own zero-arg declaration shadowed the real one and the call resolved to itself instead of the intended function. Co-Authored-By: Claude Sonnet 5 --- dag/test/claim/nbd_proxy_serve_transport_witness_test.dag | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag b/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag index 226dd809968..7809372d981 100644 --- a/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag +++ b/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag @@ -179,17 +179,17 @@ test fn nbd_proxy_observe_port_local_shell_is_active_active_wires_through() -> B } } -test fn systemd_run_transient_operation_argv_matches_authority() -> Bool { +test fn witness_systemd_run_transient_operation_argv_matches_authority() -> Bool { systemd_run_transient_operation_argv_matches_authority( unit: "nbd-proxy-srv3-nbd-proxy-10809" as NonEmptyStr, command_argv: ["nbdkit", "-p", "10809", "file", "/var/lib/gunbc/artifacts/x.iso"], ) } -test fn systemctl_stop_operation_argv_matches_authority() -> Bool { +test fn witness_systemctl_stop_operation_argv_matches_authority() -> Bool { systemctl_stop_operation_argv_matches_authority(unit: "nbd-proxy-srv3-nbd-proxy-10809" as NonEmptyStr) } -test fn systemctl_is_active_operation_argv_matches_authority() -> Bool { +test fn witness_systemctl_is_active_operation_argv_matches_authority() -> Bool { systemctl_is_active_operation_argv_matches_authority(unit: "nbd-proxy-srv3-nbd-proxy-10809-ws" as NonEmptyStr) } From 3040e6314f8ff48ec8ddde72b6b20f32fc695732 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 19 Aug 2026 23:11:15 +0000 Subject: [PATCH 3/3] Fix empty-observation-narrow: refused unit-active reads must not render as PortInaccessible ProcessPortObservation gains a third state, PortObservationRefused, carrying the real refusal reason instead of collapsing "I could not observe" into "the port is genuinely inaccessible" (DESIGN.md recurring failure mode). classify_process_port maps it to the existing (previously unreachable) ObservationVerdict.UnknownRefused; classify_process_port_upsert threads the real reason through rather than the fixed "port observation refused" fallback string. host_effect_nbd_proxy_serve_observation_from_is_active_capture routes UnitIsActiveCaptureRefused into the new state, preserving `reason` instead of discarding it. The two consumers are dispositioned: the observe-unimplemented predicate now checks for the EmitArtifactThenThinRun refusal signature rather than the no-longer-produced "port inaccessible" string, and the srv3 apply witness comment is corrected to describe the new routing. A discriminating witness (nbd_proxy_observe_port_capture_refusal_yields_port_observation_refused_not_answer) exhaustively matches all ProcessPortObservation arms so it goes RED if a refusal is ever rendered as PortInaccessible or any other answer state again. Co-Authored-By: Claude Sonnet 5 --- dag/gunbc/host_effect_nbd_proxy_serve.dag | 12 +++++++++--- dag/gunbc/session_lease.dag | 18 +++++++++++++++--- ...nbd_proxy_serve_transport_witness_test.dag | 19 ++++++++++++++++--- .../srv3_host_effect_apply_witness_test.dag | 5 ++++- 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/dag/gunbc/host_effect_nbd_proxy_serve.dag b/dag/gunbc/host_effect_nbd_proxy_serve.dag index b65eb408e02..86c5368f1be 100644 --- a/dag/gunbc/host_effect_nbd_proxy_serve.dag +++ b/dag/gunbc/host_effect_nbd_proxy_serve.dag @@ -27,7 +27,7 @@ import gunbc.session_lease { ProcessPortObservation, PortAbsent, PortObserved, - PortInaccessible, + PortObservationRefused, ProcessLeasePlan, Start, DrainMatchingStaleThenStart, @@ -138,7 +138,8 @@ fn host_effect_nbd_proxy_serve_observation_from_is_active_capture( } else { PortAbsent } - UnitIsActiveCaptureRefused { reason: _ } => PortInaccessible + UnitIsActiveCaptureRefused { reason: why } => + PortObservationRefused { reason: why as NonEmptyStr } } } @@ -154,13 +155,18 @@ fn host_effect_nbd_proxy_serve_observe_port( ) } +// Named for the one transport that genuinely cannot observe (EmitArtifactThenThinRun): its typed +// dispatch refuses before any read-back is attempted, which now surfaces here as the real captured +// refusal reason (via PortObservationRefused / ObservationVerdict.UnknownRefused), never the fixed +// "port inaccessible" string -- that string means the port was observed and genuinely wrong, a +// different fact this predicate must not conflate with "could not observe." fn host_effect_nbd_proxy_serve_apply_refuses_when_observe_unimplemented( binding: NbdProxyServeSessionBinding, transport: HostEffectTransport, ) -> Bool { match host_effect_nbd_proxy_serve_apply(binding: binding, transport: transport) { NbdProxyServeApplyRefused { reason: why } => - string_contains(s: why, pattern: "port inaccessible") + string_contains(s: why, pattern: "EmitArtifactThenThinRun") _ => false } } diff --git a/dag/gunbc/session_lease.dag b/dag/gunbc/session_lease.dag index fb9f09f8869..9821e7846da 100644 --- a/dag/gunbc/session_lease.dag +++ b/dag/gunbc/session_lease.dag @@ -40,6 +40,7 @@ type ProcessPortObservation stale_reason: NonEmptyStr } | PortInaccessible + | PortObservationRefused { reason: NonEmptyStr } type ProcessLeasePlan = Start @@ -53,6 +54,7 @@ fn classify_process_port( match obs { PortAbsent => Absent PortInaccessible => Inaccessible + PortObservationRefused { reason: _ } => UnknownRefused PortObserved { observed_port: port, pid: _, @@ -100,13 +102,23 @@ fn decision_for_process_port_verdict( } } +// PortObservationRefused carries the real refusal reason from the observe leg (e.g. "systemctl +// is-active refused ..."); decision_for_process_port_verdict only sees the verdict, so the +// UnknownRefused arm there answers with a fixed fallback string. Threading the real reason means +// this fn cannot build the decision purely from the verdict for this one observation shape -- +// routing through the verdict here would erase the reason a second time. fn classify_process_port_upsert( scope: ProcessPortScope, obs: ProcessPortObservation, ) -> std.upsert_decision.UpsertClassification { let verdict = classify_process_port(scope: scope, obs: obs) - UpsertClassification { - verdict: verdict, - decision: decision_for_process_port_verdict(verdict: verdict), + match obs { + PortObservationRefused { reason: why } => + UpsertClassification { verdict: verdict, decision: Refuse { reason: why } } + _ => + UpsertClassification { + verdict: verdict, + decision: decision_for_process_port_verdict(verdict: verdict), + } } } diff --git a/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag b/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag index 7809372d981..ddef8397c01 100644 --- a/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag +++ b/dag/test/claim/nbd_proxy_serve_transport_witness_test.dag @@ -156,7 +156,11 @@ test fn nbd_proxy_observe_port_inactive_capture_yields_port_absent() -> Bool { } } -test fn nbd_proxy_observe_port_capture_refusal_yields_port_inaccessible() -> Bool { +// This is the empty-observation-narrow guard (DESIGN.md recurring failure modes): a refused +// observation ("I could not observe") must never render as PortInaccessible ("the port was +// observed and is genuinely wrong") -- those are different facts with different remedies, and +// collapsing them hides a systematically-broken read-back behind a plausible-looking answer. +test fn nbd_proxy_observe_port_capture_refusal_yields_port_observation_refused_not_answer() -> Bool { let scope = nbd_proxy_observe_port_test_scope() let unit = "nbd-proxy-srv3-nbd-proxy-10809-ws" as NonEmptyStr match host_effect_nbd_proxy_serve_observation_from_is_active_capture( @@ -164,8 +168,17 @@ test fn nbd_proxy_observe_port_capture_refusal_yields_port_inaccessible() -> Boo websocat_unit: unit, capture: UnitIsActiveCaptureRefused { reason: "typed argv shell-safe refusal" }, ) { - PortInaccessible => true - _ => false + PortObservationRefused { reason: why } => (why as String) == "typed argv shell-safe refusal" + PortAbsent => false + PortObserved { observed_port: _, pid: _, cmdline: _, process_fingerprint: _ } => false + PortObservedStaleExpected { + observed_port: _, + pid: _, + cmdline: _, + process_fingerprint: _, + stale_reason: _, + } => false + PortInaccessible => false } } 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 ad54e35ca33..527a7d83e4c 100644 --- a/dag/test/claim/srv3_host_effect_apply_witness_test.dag +++ b/dag/test/claim/srv3_host_effect_apply_witness_test.dag @@ -203,7 +203,10 @@ test fn witness_srv3_nbd_proxy_websocat_argv_materializes_token_not_shell_env_re // P6 Part 2 dissolution trigger, retired). LocalShell/SshShell/FleetSsh can genuinely observe, so // this no longer names "observe unimplemented" universally -- it names the one transport that // genuinely cannot: EmitArtifactThenThinRun, whose typed dispatch refuses before any read-back is -// attempted, which classify_process_port_upsert renders as the same "port inaccessible" verdict. +// attempted. That refusal is carried through as PortObservationRefused (ObservationVerdict +// UnknownRefused), never rendered as PortInaccessible -- "could not observe" and "observed and +// genuinely wrong" are different facts, and the apply refusal reason names the transport, not a +// fabricated "port inaccessible" string. test fn witness_srv3_nbd_proxy_apply_emit_artifact_transport_observe_refuses_fail_closed() -> Bool { let effect = srv3_nbd_proxy_serve_host_effect() match effect {