From d29565e60d63ece4dfcc246d7b30ee6eb0f7e839 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 19 Jun 2026 02:45:17 +0100 Subject: [PATCH] Expose service audit runbook command --- .../src/bin/epiphany-daemon-supervisor.rs | 83 +++++++++++++++++- notes/fresh-workspace-handoff.md | 1 + state/ledgers.msgpack | Bin 293599 -> 294194 bytes state/map.yaml | 1 + tools/epiphany_local_run.ps1 | 4 +- 5 files changed, 86 insertions(+), 3 deletions(-) diff --git a/epiphany-core/src/bin/epiphany-daemon-supervisor.rs b/epiphany-core/src/bin/epiphany-daemon-supervisor.rs index cfd350f7..9ead5694 100644 --- a/epiphany-core/src/bin/epiphany-daemon-supervisor.rs +++ b/epiphany-core/src/bin/epiphany-daemon-supervisor.rs @@ -11,6 +11,7 @@ use epiphany_core::EpiphanyCultMeshDaemonSchedulerReceiptEntry; use epiphany_core::EpiphanyCultMeshDaemonServiceLifecycleReceiptEntry; use epiphany_core::EpiphanyCultMeshDaemonStatusEntry; use epiphany_core::EpiphanyLocalVerseContext; +use epiphany_core::EpiphanyServiceExecutionAuditReport; use epiphany_core::epiphany_cluster_service_execution_audit_report; use epiphany_core::epiphany_cultmesh_daemon_poke_intent_from_status; use epiphany_core::epiphany_cultmesh_daemon_poke_receipt_for_intent; @@ -27,6 +28,8 @@ use epiphany_core::write_epiphany_cultmesh_daemon_service_lifecycle_receipt; use epiphany_core::write_epiphany_cultmesh_daemon_status; use serde_json::Value; use serde_json::json; +use sha2::Digest; +use sha2::Sha256; use std::env; use std::fs; use std::path::PathBuf; @@ -1092,6 +1095,8 @@ fn windows_service_execution_audit(args: Args) -> Result<()> { .filter(|receipt| receipt.service_id == args.service_id) .collect::>(); let report = epiphany_service_execution_audit_report(&receipts); + let runbook_witness = + service_execution_runbook_witness(&report, "windows-service-execution-runbook"); let receipt = service_lifecycle_receipt( &args, "windows-service-execution-audit", @@ -1124,6 +1129,12 @@ fn windows_service_execution_audit(args: Args) -> Result<()> { "receiptCount": report.receipt_count, "missingCount": report.missing_count, "failedCount": report.failed_count, + "runbookPath": runbook_witness.runbook_path, + "runbookSha256": runbook_witness.runbook_sha256, + "elevatedCommand": runbook_witness.elevated_command, + "requiresElevatedAuthority": runbook_witness.requires_elevated_authority, + "aftercareMode": "service-execution-audit", + "aftercareCommand": "tools/epiphany_local_run.ps1 -Mode service-execution-audit", "checks": report.checks, "privateStateExposed": written.private_state_exposed || report.private_state_exposed, }))? @@ -1334,6 +1345,8 @@ fn cluster_windows_service_execution_audit(args: Args) -> Result<()> { .filter(|receipt| receipt.service_id == args.service_id) .collect::>(); let report = epiphany_cluster_service_execution_audit_report(&receipts); + let runbook_witness = + service_execution_runbook_witness(&report, "cluster-windows-service-execution-runbook"); let receipt = service_lifecycle_receipt( &args, "cluster-windows-service-execution-audit", @@ -1365,6 +1378,12 @@ fn cluster_windows_service_execution_audit(args: Args) -> Result<()> { "receiptCount": report.receipt_count, "missingCount": report.missing_count, "failedCount": report.failed_count, + "runbookPath": runbook_witness.runbook_path, + "runbookSha256": runbook_witness.runbook_sha256, + "elevatedCommand": runbook_witness.elevated_command, + "requiresElevatedAuthority": runbook_witness.requires_elevated_authority, + "aftercareMode": "cluster-service-execution-audit", + "aftercareCommand": "tools/epiphany_local_run.ps1 -Mode cluster-service-execution-audit", "checks": report.checks, "privateStateExposed": written.private_state_exposed || report.private_state_exposed, }))? @@ -1471,6 +1490,57 @@ fn cluster_windows_service_execution_audit_smoke(args: Args) -> Result<()> { Ok(()) } +struct ServiceExecutionRunbookWitness { + runbook_path: String, + runbook_sha256: String, + elevated_command: String, + requires_elevated_authority: bool, +} + +fn service_execution_runbook_witness( + report: &EpiphanyServiceExecutionAuditReport, + runbook_action: &str, +) -> ServiceExecutionRunbookWitness { + let runbook_path = report + .checks + .iter() + .find(|check| check.action == runbook_action) + .and_then(|check| check.operator_artifact_ref.clone()) + .unwrap_or_else(|| "none".to_string()); + let runbook_sha256 = local_file_sha256(&runbook_path).unwrap_or_else(|| "none".to_string()); + let elevated_command = if runbook_sha256 == "none" { + "none".to_string() + } else { + elevated_powershell_runbook_command(&runbook_path) + }; + ServiceExecutionRunbookWitness { + runbook_path, + runbook_sha256, + elevated_command, + requires_elevated_authority: true, + } +} + +fn local_file_sha256(path: &str) -> Option { + if path.trim().is_empty() || path == "none" { + return None; + } + let path = PathBuf::from(path); + if !path.is_file() { + return None; + } + let bytes = fs::read(path).ok()?; + let digest = Sha256::digest(&bytes); + Some(format!("{digest:x}")) +} + +fn elevated_powershell_runbook_command(path: &str) -> String { + let escaped = path.replace('\'', "''"); + format!( + "Start-Process PowerShell -Verb RunAs -Wait -ArgumentList @('-NoProfile','-ExecutionPolicy','Bypass','-File','{escaped}')" + ) +} + fn windows_service_execution_runbook(args: Args) -> Result<()> { seed_epiphany_local_verse_context( &args.store, @@ -2901,6 +2971,7 @@ impl Args { let mut values = env::args().skip(1); let command = values.next().unwrap_or_else(|| "reconcile".to_string()); let mut store = PathBuf::from(".epiphany-run/cultmesh/local-verse.ccmp"); + let mut store_explicit = false; let mut runtime_id = "epiphany-local".to_string(); let mut daemon_id = None; let mut restart_command = None; @@ -2935,7 +3006,10 @@ impl Args { while let Some(arg) = values.next() { match arg.as_str() { - "--store" => store = PathBuf::from(values.next().context("missing --store value")?), + "--store" => { + store = PathBuf::from(values.next().context("missing --store value")?); + store_explicit = true; + } "--runtime-id" => { runtime_id = values.next().context("missing --runtime-id value")? } @@ -3059,6 +3133,13 @@ impl Args { } } + if !store_explicit && command.contains("smoke") { + store = PathBuf::from(format!( + ".epiphany-smoke/daemon-supervisor-{}/local-verse.ccmp", + sanitize_id(&command) + )); + } + if let Some(parent) = store.parent() { std::fs::create_dir_all(parent)?; } diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index b6527249..12f97011 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -119,6 +119,7 @@ remembers doctrine; the branch remembers the blade. - `tool-invoke` now embeds compact readbacks for common daemon-hosted tools. Status calls return `daemonStatusReadback`, Eve calls return `eveConnectionReadback`, Self `service-health` returns Idunn-owned `serviceHealthReadback`, and Hands/Soul authority tools return `authorityToolReadback` with gate, input contract, receipt contract, intent/receipt routes, host cluster, and private-state seal. This proves global tool ergonomics without moving action, verification, or daemon lifecycle ownership into the wrapper; live proofs routed Persona to Hands `repo-action` (`local-20260619-021628-143-7bf7f2e0`) and Soul `verify` (`local-20260619-021701-458-852cfa6f`) with `privateStateExposed=false`. - `gjallar` is the non-mutating whole-Verse herald to run first when reorienting; `swarm-overview` remains its compatibility name. It locates agents, cluster/private-Verse topology, daemon liveness, Eve surfaces, and daemon tools in one compact report without opening private Verse state or full-context debug serialization. - Idunn, not Codex/Self/Gjallar, owns daemon survival and service lifecycle physiology. `tools/epiphany_local_run.ps1 -Mode swarm-online-runbook` now generates an Idunn-owned service lifecycle runbook for the Epiphany hosted body: the artifact verifies each sealed child runbook artifact exists and matches its advertised SHA-256 before launching it with `Start-Process ... -PassThru`, records child exit codes, continues after child failure, runs aftercare audits through `tools\epiphany_local_run.ps1` with explicit arguments instead of string evaluation, and exits nonzero if any child or aftercare step fails. Its wrapper JSON and one-line summary print the exact swarm-level `elevatedCommand=Start-Process PowerShell -Verb RunAs -Wait ...`, parent artifact SHA-256, `verifiesChildArtifactSha256=True`, `usesExplicitAftercareArguments=True`, child/aftercare row counts, aftercare commands, owner, hosted body, and private-state seal; JSON now includes structured `childRunbookRows` and `aftercareRows` with service ids, routes, SHA-256s, audit modes, authority gates, failed-check counts, and private-state seals. Proof `local-20260619-013021-159-c93088be` generated and inspected that guard without mutating services. It still requires explicit elevated operator authority before service mutation. +- Service execution audits now expose the exact Idunn runbook route at the audit mouth: `cluster-service-execution-audit` and `service-execution-audit` JSON and wrapper summaries include the sealed runbook path, SHA-256, elevated `Start-Process PowerShell -Verb RunAs -Wait ...` command, aftercare command, and `requiresElevatedAuthority=True`. Daemon-supervisor `*smoke` commands default to `.epiphany-smoke/daemon-supervisor-/local-verse.ccmp` unless `--store` is explicit, keeping synthetic smoke receipts out of the shared live local Verse. - `swarm-triage` is the compact responsibility rite after sight: it no-ops when liveness is ready, carries blocked tool-host/service lifecycle attention plus an ordered action queue as sealed sight, and emits topology-aware typed daemon poke receipts only for current daemon liveness attention rows. - `epiphany-openai-runtime` worker result ingress now parses assistant output directly into typed role/reorient ingress structs selected from the durable launch document, stores nested `statePatch` and `selfPatch` as typed MessagePack in `EpiphanyRuntimeRoleWorkerResult`, and no longer passes worker result data through generic `serde_json::Value`. JSON survives there only as the OpenAI text-output edge before typed deserialization. - Agent self-memory review/application now has typed `AgentSelfPatch` core APIs. JSON-value selfPatch functions are compatibility wrappers for CLI/artifact ingress; native callers should parse once at the edge and then call `review_agent_self_patch_document` / `apply_agent_self_patch_document`. Role-result self-persistence no longer exports a JSON-value reviewer; the live API is `review_role_self_patch_document`. diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index 5ffada6e46b4cc984236b768162ae50e573f95a1..022bf863d10952ff280419a9faa85e4f9bcec95c 100644 GIT binary patch delta 557 zcmZ8e&1w`u5O#L<+sLB7z6WL4_3$o>J+l$u#Nd?sWI;x*#m# z!545Duf9RhgZKizhEJg2!HXw*CKAL$(@@_})mQcH*Y?NX+vhvc)_8A!GJEm$%J9|I zPq(A9DEj_pGkCihMB#OR{CP7N9=^Qx?n{e9<66jt>)w$a9!7^iVv=x2@rNk2`grzn2as@&GmyHYRyH2PRNq|iuJRHuSJceKmON+*k z4ReN8R*>6;he)+n%r&9D5KafM)~A=PFLD)BVk13x9lANA9UcesT{6N{s8-Bv^&023 zCD$JPh;OY&U@R?2`7j8$mGTvN!T*|D;v|fBP+DDwl*(f*6!u&x76ejrl<=Ia>y{Y? z3rjUt>+%44ywBw>XskO` for each failed/missing check so the compact row names the receipt-producing or readback route without opening raw audit payloads. Live wrapper proof `local-20260618-224925-221-8e114063` printed cluster readiness/install/start/audit/stop failures with follow-ups such as `cluster-service-execution-readiness`, `cluster-service-install-execute`, and `cluster-service-execution-audit`, plus supervisor-service missing checks with follow-ups such as `service-execution-readiness`, `service-install-execute`, `service-status`, and `service-reconcile`, all sealed with `privateStateExposed=false`. The action queue now carries scoped service-execution wound counters on service-lifecycle readback rows as well as elevated authority rows, so a compact row that says `attention` also says why: live Gjallar proof `local-20260619-012440-577-3f18a143` printed priority 40 `cluster-service-execution-audit` with `failedChecks=5` and priority 41 `service-execution-audit` with `failedChecks=6`, while both rows stayed non-mutating, non-elevated, Idunn-owned, and `privateStateExposed=false`. Source smoke seeds both cluster and single-service runbook witnesses and proves distinct authority rows. Live overview/triage proofs `local-20260618-221339-826-b1981064` and `local-20260618-221340-099-4b4baba8` printed priority 50 `cluster-service-execution-runbook` with `failedChecks=5`, `missingChecks=0`, SHA-256 `3239ac17008b1ce857970e40b17d37a1ecf1631a3a6636bb2b8678312fb604a0`, and aftercare `cluster-service-execution-audit`, plus priority 51 `service-execution-runbook` with `failedChecks=6`, `missingChecks=6`, SHA-256 `b213a3c11a8f11a03061fcd743f507edaa59bc6767257b79cef63329596375e7`, and aftercare `service-execution-audit`, with `privateStateExposed=false`. `SwarmActionRow` now exposes explicit `serviceId` and `serviceRoute` fields so agents can identify the body/action without parsing artifact paths or prose; Rust TUI and wrapper action queues render `service=` and `route=` for service lifecycle and service authority rows, while non-service rows render `none`. Source smoke guards both fields, and live overview/triage proofs `local-20260618-221912-231-805389bf` and `local-20260618-221912-282-a79ba8bd` showed the cluster and supervisor-service identities on readback and elevated runbook rows with `privateStateExposed=false`. `ReceiptDirectoryRow` now owns the same explicit `serviceId`/`serviceRoute` fields, so action rows consume typed service identity from the receipt-directory organ instead of parsing `route`; non-service receipt families carry `none`, while service lifecycle/runbook rows carry the typed body and route. Source smoke guards the fields, and live receipt-directory proof `local-20260618-222641-218-60106c79` printed `service=epiphany-cluster-daemon-services` and `service=epiphany-daemon-supervisor-service` on service attention/readiness rows and runbook witnesses with no private-state exposure. - `SwarmActionRow` now carries `lifecycleOwner` and `hostedBody` so Gjallar/overview/triage action queues distinguish sight from daemon-keeper authority. Daemon liveness, restart-policy, service-lifecycle readback, and elevated service-control rows carry `Idunn` / `Epiphany`; non-lifecycle rows carry `none`. Rust TUI rows plus wrapper `actionQueue` render `owner=Idunn` and `hostedBody=Epiphany` for service readback and elevated service-control rows. Focused live proofs `local-20260619-004523-347-620b40e9` (`gjallar`) and `local-20260619-004523-003-a44ad06d` (`swarm-triage`) show the ownership fields on all service action rows with no daemon poke when liveness is ready and no private-state exposure. The broad `epiphany-verse-query smoke` Windows debug stack-overflow wound is now cut by running the CLI body on a larger worker-thread stack; direct smoke returns `status=ok` with seven clusters, seven daemon statuses, seventeen daemon tools, and the sealed latest receipt ids. - Direct wrapper audit summaries now expose the same follow-up ergonomics as the global sight rows. `tools/epiphany_local_run.ps1 -Mode cluster-service-execution-audit` and `-Mode service-execution-audit` print `failed check rows` with `serviceId::action=status`, artifact ref, local artifact SHA-256 when present, and `followUp=` for each failed/missing check. Live proofs `local-20260618-225923-468-d818951a` and `local-20260618-225923-529-7cfa2bb3` showed cluster readiness/install/start/audit/stop failures and supervisor-service readiness/install/start/status/reconcile/stop missing checks with actionable follow-up wrappers and `privateStateExposed=false`. + - Service execution audits now carry their own elevated runbook readback instead of relying on Gjallar or wrapper memory. `epiphany-daemon-supervisor windows-service-execution-audit` and `cluster-windows-service-execution-audit` return `runbookPath`, `runbookSha256`, `elevatedCommand`, `requiresElevatedAuthority`, `aftercareMode`, and `aftercareCommand` from the sealed runbook witness; the wrapper summaries print those fields directly for `service-execution-audit` and `cluster-service-execution-audit`. Live proofs `local-20260619-024011-505-a18982fa` and `local-20260619-024010-980-842fc3a6` showed incomplete audits with repaired runbook hashes, exact `Start-Process PowerShell -Verb RunAs -Wait ... -File ` commands, explicit aftercare, and no private-state exposure. `epiphany-daemon-supervisor` smoke commands now default to `.epiphany-smoke/daemon-supervisor-/local-verse.ccmp` unless `--store` is explicit, so synthetic smoke receipts cannot become the latest live local Verse service proof by accident. - Service lifecycle receipt history is now event-only: `load_epiphany_cultmesh_daemon_service_lifecycle_receipts` filters out the `epiphany-local/daemon-service-lifecycle-receipt/latest` mirror, while `load_latest_epiphany_cultmesh_daemon_service_lifecycle_receipt` remains the latest-state reader. Focused unit test `service_lifecycle_receipt_history_excludes_latest_mirror` proves two per-receipt writes return exactly two history records and latest points at the second. Fresh smoke `verse-query-lifecycle-history-filter-smoke` and live overview/triage `local-20260618-145632-497-efac6f0d` / `local-20260618-145632-321-867947af` prove compact swarm sights still expose service lifecycle attention plus the present elevated runbook artifact without daemon pokes or service mutation. Do not use a broad unscoped cargo test filter for this check on Windows; it fans out across bin harnesses and hit a broken pipe during this pass. - `epiphany-verse-query seed-compact` is the compact seed receipt for wrappers and read-only rites. The older `seed` command still prints the full local Verse context for explicit debugging, but compact wrapper modes now avoid full `seed`/final `query` readback after the combined overview proof exposed Windows debug stack overflow from full-context serialization pressure. next_action: Continue the swarm-readiness cuts toward elevated operator install/start execution and completed audit/readiness for the seven installed services. Run generated service execution/install/control runbooks only when the operator grants that authority. The current local Verse proves liveness readiness, scheduler-policy coverage, daemon-body invocation, per-organ launch artifact generation, per-organ install-plan generation, non-elevated install/start/stop execution refusal, read-only installed-service audit, cluster-wide service control planning, service-lifecycle attention surfacing, and cluster execution audit acceptance criteria: `swarm-overview` now reports `status=attention`, `livenessStatus=ready`, `recoveryStatus=attention`, `recommendedWrapperMode=swarm-online-runbook`, and `recommendedWrapperCommand=tools/epiphany_local_run.ps1 -Mode swarm-online-runbook`; `service-tick` can refresh all seven daemon heartbeats through sealed scheduler receipts; `cluster-service-runbook` writes seven explicit `epiphany-cluster-daemon serve` commands with `privateStateExposed=false`; `cluster-service-install-plan` writes seven distinct Windows service install commands with `privateStateExposed=false`; `cluster-service-install-execute`, `cluster-service-start-execute`, and `cluster-service-stop-execute` refuse as `execution-refused-not-elevated` with `executed=false` when the shell lacks service authority; `cluster-service-start-plan` and `cluster-service-stop-plan` emit seven non-mutating planned rows; `cluster-service-audit` currently reports `incomplete` with seven missing services in this non-installed environment; `service-execution-runbook` and `cluster-service-execution-runbook` write elevated wrapper sequences that continue after per-step failure, run final audits in `finally`, exit nonzero after audit when any step failed, and seal typed runbook receipts with aftercare args (`stepFailurePolicy`, `continueAfterStepFailure`, `nonzeroExitFailsStep`, `exitsNonzeroAfterFinalAudit`); and `service-execution-audit` plus `cluster-service-execution-audit` now require those runbook witnesses before considering elevated-ready/install/start/audit/status/reconcile/stop effect receipts. Preserve the existing typed local Verse surfaces: `service-plan`, `service-launch`, `service-runbook`, `cluster-service-runbook`, `cluster-service-install-plan`, `cluster-service-install-execute`, `cluster-service-audit`, `cluster-service-start-plan`, `cluster-service-stop-plan`, `cluster-service-start-execute`, `cluster-service-stop-execute`, `cluster-service-execution-readiness`, `cluster-service-execution-runbook`, `cluster-service-execution-audit`, `service-execution-runbook`, `service-execution-audit`, `service-tick`, `service-policy-directory`, `agent-state-soa`, `swarm-status`, `swarm-poke-down`, `swarm-triage`, `cluster-topology`, `eve-surfaces`, `eve-connect`, `collaboration-feedback`, `bifrost-publication`, `bifrost-ledger`, `receipt-directory`, `tool-directory`, `tool-invoke`, `swarm-overview`, `swarm-online-runbook`, `seed-compact`, service manager status/reconcile/readiness/control wrappers, compact daemon scheduler/policy receipt rows, daemon liveness-gated tool routing, Bifrost/Imagination chains, coordinator-run receipts, Hands gates, role review mirrors, and sealed agent-state SoA summary. Keep the seven-standing-faculty SoA, stale-turn repair, memory lifecycle, shared local Verse witnesses, and typed CultMesh Persona speech gate intact. diff --git a/tools/epiphany_local_run.ps1 b/tools/epiphany_local_run.ps1 index 68724b9d..c6e3a7e7 100644 --- a/tools/epiphany_local_run.ps1 +++ b/tools/epiphany_local_run.ps1 @@ -2322,7 +2322,7 @@ if ($resultPath -ne "" -and (Test-Path -LiteralPath $resultPath)) { $artifactSha256 = Get-LocalArtifactSha256 $result.runbookPath Write-Host "Cluster daemon service execution runbook: service=$($result.serviceId), status=$($result.status), finalAuditInFinally=$($result.finalAuditRunsInFinally), continueAfterStepFailure=$($result.continueAfterStepFailure), nonzeroExitFailsStep=$($result.nonzeroExitFailsStep), exitsNonzeroAfterFinalAudit=$($result.exitsNonzeroAfterFinalAudit), artifactSha256=$artifactSha256, elevatedCommand=$elevatedCommand, aftercare=tools/epiphany_local_run.ps1 -Mode cluster-service-execution-audit, path=$($result.runbookPath)" } elseif ($Mode -eq "cluster-service-execution-audit") { - Write-Host "Cluster daemon service execution audit: service=$($result.serviceId), status=$($result.status), missing=$($result.missingCount), failed=$($result.failedCount), receipt=$($result.receiptId)" + Write-Host "Cluster daemon service execution audit: service=$($result.serviceId), status=$($result.status), missing=$($result.missingCount), failed=$($result.failedCount), receipt=$($result.receiptId), runbookSha256=$($result.runbookSha256), elevatedCommand=$($result.elevatedCommand), aftercare=$($result.aftercareCommand), requiresElevatedAuthority=$($result.requiresElevatedAuthority)" $failedCheckRows = Format-ServiceExecutionFailedChecks @($result.checks | Where-Object { -not $_.ok }) $runbookWitnessChecks = @($result.checks | Where-Object { $_.ok -and $null -ne $_.operatorArtifactRef -and $_.operatorArtifactRef -ne "" -and $_.operatorArtifactRef -ne "none" }) if ($runbookWitnessChecks.Count -gt 0) { @@ -2372,7 +2372,7 @@ if ($resultPath -ne "" -and (Test-Path -LiteralPath $resultPath)) { } elseif ($Mode -eq "service-execution-readiness") { Write-Host "Service execution readiness: service=$($result.serviceId), name=$($result.serviceName), status=$($result.status), elevated=$($result.elevated), receipt=$($result.receiptId), followUp=tools/epiphany_local_run.ps1 -Mode service-execution-runbook, aftercare=tools/epiphany_local_run.ps1 -Mode service-execution-audit" } elseif ($Mode -eq "service-execution-audit") { - Write-Host "Service execution audit: service=$($result.serviceId), name=$($result.serviceName), status=$($result.status), missing=$($result.missingCount), failed=$($result.failedCount), receipt=$($result.receiptId)" + Write-Host "Service execution audit: service=$($result.serviceId), name=$($result.serviceName), status=$($result.status), missing=$($result.missingCount), failed=$($result.failedCount), receipt=$($result.receiptId), runbookSha256=$($result.runbookSha256), elevatedCommand=$($result.elevatedCommand), aftercare=$($result.aftercareCommand), requiresElevatedAuthority=$($result.requiresElevatedAuthority)" $failedCheckRows = Format-ServiceExecutionFailedChecks @($result.checks | Where-Object { -not $_.ok }) $runbookWitnessChecks = @($result.checks | Where-Object { $_.ok -and $null -ne $_.operatorArtifactRef -and $_.operatorArtifactRef -ne "" -and $_.operatorArtifactRef -ne "none" }) if ($runbookWitnessChecks.Count -gt 0) {