diff --git a/epiphany-core/src/bin/epiphany-verse-query.rs b/epiphany-core/src/bin/epiphany-verse-query.rs index a0e39dc0..01dfb026 100644 --- a/epiphany-core/src/bin/epiphany-verse-query.rs +++ b/epiphany-core/src/bin/epiphany-verse-query.rs @@ -268,139 +268,7 @@ fn main() -> Result<()> { ); } "invoke-tool" | "tool-invocation" | "tool-intent" => { - seed_epiphany_local_verse_context( - &args.store, - args.runtime_id.clone(), - Utc::now().to_rfc3339(), - )?; - let context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?; - let capability_id = args - .capability_id - .as_deref() - .context("invoke-tool requires --capability-id from the tool directory")?; - let capability = context - .daemon_tool_capabilities - .iter() - .find(|capability| capability.capability_id == capability_id) - .with_context(|| { - format!("local Verse has no daemon tool capability {capability_id:?}") - })?; - assert_swarm_brake_allows_surface( - &context, - "daemon.tool_invocation", - &capability.host_cluster_id, - &capability.host_daemon_id, - )?; - let daemon_status = daemon_status_for_capability(&context, capability)?; - assert_daemon_ready_for_tool_invocation(daemon_status, capability)?; - let requesting_agent_id = args - .requesting_agent_id - .clone() - .or_else(|| args.source_agent_id.clone()) - .unwrap_or_else(|| "epiphany.Self".to_string()); - let requesting_cluster_id = args - .source_cluster_id - .clone() - .unwrap_or_else(|| "epiphany.cluster.self".to_string()); - let intent_id = args - .intent_id - .clone() - .unwrap_or_else(|| format!("daemon-tool-intent-{}", capability.tool_name)); - let receipt_id = args - .receipt_id - .clone() - .unwrap_or_else(|| format!("daemon-tool-receipt-{}", capability.tool_name)); - let payload_ref = args.invocation_ref.clone().unwrap_or_else(|| { - format!("cultmesh://epiphany-local/tool-invocation/{intent_id}") - }); - let payload_summary = args.reason.clone().unwrap_or_else(|| { - format!( - "{requesting_agent_id} requests {} through the local CultMesh daemon tool directory.", - capability.tool_name - ) - }); - let intent = epiphany_cultmesh_daemon_tool_invocation_intent_from_capability( - intent_id, - requesting_agent_id, - requesting_cluster_id, - capability, - payload_ref, - payload_summary, - ); - let written_intent = write_epiphany_cultmesh_daemon_tool_invocation_intent( - &args.store, - args.runtime_id.clone(), - intent.clone(), - )?; - let receipt_status = args - .receipt_status - .clone() - .unwrap_or_else(|| "accepted-for-daemon-routing".to_string()); - let result_ref = args - .result_ref - .clone() - .unwrap_or_else(|| format!("cultmesh://epiphany-local/tool-receipt/{receipt_id}")); - let result_summary = args.receipt_summary.clone().unwrap_or_else(|| { - format!( - "{} accepted typed invocation routing for {}.", - capability.host_daemon_id, capability.tool_name - ) - }); - let receipt = epiphany_cultmesh_daemon_tool_invocation_receipt_for_intent( - receipt_id, - &intent, - receipt_status, - capability.receipt_contract_type.clone(), - result_ref, - result_summary, - ); - let written_receipt = write_epiphany_cultmesh_daemon_tool_invocation_receipt( - &args.store, - args.runtime_id.clone(), - receipt, - )?; - let context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?; - if context.latest_daemon_tool_invocation_intent.is_none() - || context.latest_daemon_tool_invocation_receipt.is_none() - { - anyhow::bail!( - "local Verse query lost daemon tool invocation intent/receipt after write" - ); - } - let requesting_cluster = - cluster_topology_for_id(&context, &written_intent.requesting_cluster_id)?; - let host_cluster = cluster_topology_for_id(&context, &written_intent.host_cluster_id)?; - println!( - "{}", - serde_json::to_string_pretty(&json!({ - "status": "ok", - "store": args.store, - "runtimeId": context.runtime_id, - "capabilityId": written_intent.capability_id, - "requestingAgentId": written_intent.requesting_agent_id, - "requestingClusterId": written_intent.requesting_cluster_id, - "requestingDisplayName": requesting_cluster.display_name.clone(), - "requestingBodyDomain": requesting_cluster.body_domain.clone(), - "requestingPrivateVerseId": requesting_cluster.private_verse_id.clone(), - "requestingEveSurfaceId": requesting_cluster.eve_surface_id.clone(), - "hostClusterId": written_intent.host_cluster_id, - "hostDaemonId": written_intent.host_daemon_id, - "hostDisplayName": host_cluster.display_name.clone(), - "hostBodyDomain": host_cluster.body_domain.clone(), - "hostPrivateVerseId": host_cluster.private_verse_id.clone(), - "hostEveSurfaceId": host_cluster.eve_surface_id.clone(), - "toolName": written_intent.tool_name, - "operation": written_intent.operation, - "intentId": written_intent.intent_id, - "receiptId": written_receipt.receipt_id, - "receiptStatus": written_receipt.status, - "availableToAllAgents": capability.available_to_all_agents, - "requiresReceipt": written_intent.requires_receipt, - "authorityGate": written_intent.authority_gate, - "privateStateRequested": written_intent.private_state_requested, - "privateStateExposed": written_receipt.private_state_exposed, - }))? - ); + run_invoke_tool_command(&args)?; } "swarm-brake" | "brake" => { seed_epiphany_local_verse_context( @@ -1805,6 +1673,48 @@ fn main() -> Result<()> { { anyhow::bail!("local Verse query smoke lost daemon tool invocation intent/receipt"); } + let persona_cluster = cluster_topology_for_id(&context, "epiphany.cluster.persona")?; + let hands_cluster = cluster_topology_for_id(&context, "epiphany.cluster.hands")?; + let tool_invocation_tui_row = + daemon_tool_invocation_tui_row(DaemonToolInvocationTuiFields { + requester: &persona_cluster.display_name, + requesting_agent_id: "epiphany.Persona", + requesting_private_verse: &persona_cluster.private_verse_id, + requesting_surface: &persona_cluster.eve_surface_id, + host: &hands_cluster.display_name, + host_daemon_id: &hands_repo_action.host_daemon_id, + host_private_verse: &hands_cluster.private_verse_id, + host_surface: &hands_cluster.eve_surface_id, + capability_id: &hands_repo_action.capability_id, + tool_name: &hands_repo_action.tool_name, + operation: &hands_repo_action.operation, + intent_id: "daemon-tool-intent-smoke", + receipt_id: "daemon-tool-receipt-smoke", + receipt_status: "accepted-for-hands-review", + authority_gate: &hands_repo_action.authority_gate, + all_agents: hands_repo_action.available_to_all_agents, + requires_receipt: hands_repo_action.requires_receipt, + private_state_exposed: false, + }); + if !tool_invocation_tui_row.contains("INVOKE") + || !tool_invocation_tui_row.contains("Persona") + || !tool_invocation_tui_row.contains("host=Hands") + || !tool_invocation_tui_row.contains("hostDaemon=epiphany-daemon-hands") + || !tool_invocation_tui_row + .contains("requestPrivateVerse=epiphany.cluster.persona.private") + || !tool_invocation_tui_row + .contains("hostPrivateVerse=epiphany.cluster.hands.private") + || !tool_invocation_tui_row + .contains("capability=epiphany.cluster.hands.tool.repo-action") + || !tool_invocation_tui_row.contains("receipt=daemon-tool-receipt-smoke") + || !tool_invocation_tui_row.contains("allAgents=true") + || !tool_invocation_tui_row.contains("receiptRequired=true") + || !tool_invocation_tui_row.contains("private=false") + { + anyhow::bail!( + "local Verse query smoke lost compact daemon tool invocation routing row" + ); + } let no_op_pokes = write_poke_receipts_for_non_ready_daemons(&args, &context)?; if !no_op_pokes.is_empty() { anyhow::bail!("local Verse query smoke poked ready daemons during no-op sweep"); @@ -5210,6 +5120,162 @@ fn write_poke_receipts_for_non_ready_daemons( Ok(pokes) } +fn run_invoke_tool_command(args: &Args) -> Result<()> { + seed_epiphany_local_verse_context( + &args.store, + args.runtime_id.clone(), + Utc::now().to_rfc3339(), + )?; + let context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?; + let capability_id = args + .capability_id + .as_deref() + .context("invoke-tool requires --capability-id from the tool directory")?; + let capability = context + .daemon_tool_capabilities + .iter() + .find(|capability| capability.capability_id == capability_id) + .with_context(|| format!("local Verse has no daemon tool capability {capability_id:?}"))?; + assert_swarm_brake_allows_surface( + &context, + "daemon.tool_invocation", + &capability.host_cluster_id, + &capability.host_daemon_id, + )?; + let daemon_status = daemon_status_for_capability(&context, capability)?; + assert_daemon_ready_for_tool_invocation(daemon_status, capability)?; + let requesting_agent_id = args + .requesting_agent_id + .clone() + .or_else(|| args.source_agent_id.clone()) + .unwrap_or_else(|| "epiphany.Self".to_string()); + let requesting_cluster_id = args + .source_cluster_id + .clone() + .unwrap_or_else(|| "epiphany.cluster.self".to_string()); + let intent_id = args + .intent_id + .clone() + .unwrap_or_else(|| format!("daemon-tool-intent-{}", capability.tool_name)); + let receipt_id = args + .receipt_id + .clone() + .unwrap_or_else(|| format!("daemon-tool-receipt-{}", capability.tool_name)); + let payload_ref = args + .invocation_ref + .clone() + .unwrap_or_else(|| format!("cultmesh://epiphany-local/tool-invocation/{intent_id}")); + let payload_summary = args.reason.clone().unwrap_or_else(|| { + format!( + "{requesting_agent_id} requests {} through the local CultMesh daemon tool directory.", + capability.tool_name + ) + }); + let intent = epiphany_cultmesh_daemon_tool_invocation_intent_from_capability( + intent_id, + requesting_agent_id, + requesting_cluster_id, + capability, + payload_ref, + payload_summary, + ); + let written_intent = write_epiphany_cultmesh_daemon_tool_invocation_intent( + &args.store, + args.runtime_id.clone(), + intent.clone(), + )?; + let receipt_status = args + .receipt_status + .clone() + .unwrap_or_else(|| "accepted-for-daemon-routing".to_string()); + let result_ref = args + .result_ref + .clone() + .unwrap_or_else(|| format!("cultmesh://epiphany-local/tool-receipt/{receipt_id}")); + let result_summary = args.receipt_summary.clone().unwrap_or_else(|| { + format!( + "{} accepted typed invocation routing for {}.", + capability.host_daemon_id, capability.tool_name + ) + }); + let receipt = epiphany_cultmesh_daemon_tool_invocation_receipt_for_intent( + receipt_id, + &intent, + receipt_status, + capability.receipt_contract_type.clone(), + result_ref, + result_summary, + ); + let written_receipt = write_epiphany_cultmesh_daemon_tool_invocation_receipt( + &args.store, + args.runtime_id.clone(), + receipt, + )?; + let context = query_epiphany_local_verse_context(&args.store, args.runtime_id.clone())?; + if context.latest_daemon_tool_invocation_intent.is_none() + || context.latest_daemon_tool_invocation_receipt.is_none() + { + anyhow::bail!("local Verse query lost daemon tool invocation intent/receipt after write"); + } + let requesting_cluster = + cluster_topology_for_id(&context, &written_intent.requesting_cluster_id)?; + let host_cluster = cluster_topology_for_id(&context, &written_intent.host_cluster_id)?; + let invocation_tui_row = daemon_tool_invocation_tui_row(DaemonToolInvocationTuiFields { + requester: &requesting_cluster.display_name, + requesting_agent_id: &written_intent.requesting_agent_id, + requesting_private_verse: &requesting_cluster.private_verse_id, + requesting_surface: &requesting_cluster.eve_surface_id, + host: &host_cluster.display_name, + host_daemon_id: &written_intent.host_daemon_id, + host_private_verse: &host_cluster.private_verse_id, + host_surface: &host_cluster.eve_surface_id, + capability_id: &written_intent.capability_id, + tool_name: &written_intent.tool_name, + operation: &written_intent.operation, + intent_id: &written_intent.intent_id, + receipt_id: &written_receipt.receipt_id, + receipt_status: &written_receipt.status, + authority_gate: &written_intent.authority_gate, + all_agents: capability.available_to_all_agents, + requires_receipt: written_intent.requires_receipt, + private_state_exposed: written_receipt.private_state_exposed, + }); + println!( + "{}", + serde_json::to_string_pretty(&json!({ + "status": "ok", + "store": args.store, + "runtimeId": context.runtime_id, + "capabilityId": written_intent.capability_id, + "requestingAgentId": written_intent.requesting_agent_id, + "requestingClusterId": written_intent.requesting_cluster_id, + "requestingDisplayName": requesting_cluster.display_name.clone(), + "requestingBodyDomain": requesting_cluster.body_domain.clone(), + "requestingPrivateVerseId": requesting_cluster.private_verse_id.clone(), + "requestingEveSurfaceId": requesting_cluster.eve_surface_id.clone(), + "hostClusterId": written_intent.host_cluster_id, + "hostDaemonId": written_intent.host_daemon_id, + "hostDisplayName": host_cluster.display_name.clone(), + "hostBodyDomain": host_cluster.body_domain.clone(), + "hostPrivateVerseId": host_cluster.private_verse_id.clone(), + "hostEveSurfaceId": host_cluster.eve_surface_id.clone(), + "toolName": written_intent.tool_name, + "operation": written_intent.operation, + "intentId": written_intent.intent_id, + "receiptId": written_receipt.receipt_id, + "receiptStatus": written_receipt.status, + "availableToAllAgents": capability.available_to_all_agents, + "requiresReceipt": written_intent.requires_receipt, + "authorityGate": written_intent.authority_gate, + "privateStateRequested": written_intent.private_state_requested, + "privateStateExposed": written_receipt.private_state_exposed, + "invocationRows": [invocation_tui_row.clone()], + "tuiRows": [invocation_tui_row], + }))? + ); + Ok(()) +} + fn poke_result_tui_row(row: &serde_json::Value) -> String { let target_display_name = row["targetDisplayName"].as_str().unwrap_or("unknown"); let target_daemon_id = row["targetDaemonId"].as_str().unwrap_or("unknown"); @@ -5227,6 +5293,51 @@ fn poke_result_tui_row(row: &serde_json::Value) -> String { ) } +struct DaemonToolInvocationTuiFields<'a> { + requester: &'a str, + requesting_agent_id: &'a str, + requesting_private_verse: &'a str, + requesting_surface: &'a str, + host: &'a str, + host_daemon_id: &'a str, + host_private_verse: &'a str, + host_surface: &'a str, + capability_id: &'a str, + tool_name: &'a str, + operation: &'a str, + intent_id: &'a str, + receipt_id: &'a str, + receipt_status: &'a str, + authority_gate: &'a str, + all_agents: bool, + requires_receipt: bool, + private_state_exposed: bool, +} + +fn daemon_tool_invocation_tui_row(fields: DaemonToolInvocationTuiFields<'_>) -> String { + let requester = fields.requester; + let requesting_agent_id = fields.requesting_agent_id; + let requesting_private_verse = fields.requesting_private_verse; + let requesting_surface = fields.requesting_surface; + let host = fields.host; + let host_daemon_id = fields.host_daemon_id; + let host_private_verse = fields.host_private_verse; + let host_surface = fields.host_surface; + let capability_id = fields.capability_id; + let tool_name = fields.tool_name; + let operation = fields.operation; + let intent_id = fields.intent_id; + let receipt_id = fields.receipt_id; + let receipt_status = fields.receipt_status; + let authority_gate = fields.authority_gate; + let all_agents = fields.all_agents; + let requires_receipt = fields.requires_receipt; + let private_state_exposed = fields.private_state_exposed; + format!( + "INVOKE | {requester} | agent={requesting_agent_id} | requestPrivateVerse={requesting_private_verse} | requestSurface={requesting_surface} | host={host} | hostDaemon={host_daemon_id} | hostPrivateVerse={host_private_verse} | hostSurface={host_surface} | tool={tool_name} | operation={operation} | capability={capability_id} | intent={intent_id} | receipt={receipt_id} | receiptStatus={receipt_status} | authority={authority_gate} | allAgents={all_agents} | receiptRequired={requires_receipt} | private={private_state_exposed}" + ) +} + fn sanitize_id(value: &str) -> String { value .chars() diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 577989d9..0cab313e 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -41,7 +41,7 @@ remembers doctrine; the branch remembers the blade. - This Codex operator's own roadmap/todo posture has been retuned for this scale target, with the Epiphany Imagination specialist as an optional sub-agent rail rather than the whole point. `notes/epiphany-investor-readiness-roadmap.md` now says: make the local Mom MVP a repeatable proof bundle; build the Bifrost-first external-work bridge; add compute/review/accepted-artifact accounting; prove fresh-repo operation without supervisor contamination; keep public/operator-safe exports sealed; and treat angel capital as fuel for capacity, not command authority. The Imagination specialist prompt was also updated so investor-scale Objective Drafts must name Bifrost provenance, bounded Epiphany proof, maintainer review, receipts/ledger evidence, compute/output metrics, public/private export safety, consent, and legal/security gates. - Local operator status runs now write typed CultMesh operator witnesses into `.epiphany-run/cultmesh/local-verse.ccmp`: `epiphany-operator-run` records the run intent before acting, `epiphany-operator-snapshot` distills the operator-safe MVP status artifact, and `epiphany-operator-run` records the completion receipt after success. The final `local-verse-context.json` is queried after those witnesses exist, so the operator read path sees latest run intent, snapshot, and receipt from the same local Verse vessel instead of treating separate JSON artifacts or side stores as truth. - `epiphany-operator-snapshot from-status` also mirrors the latest native status `tools.invocations[]` row into the same local Verse as `epiphany.cultmesh.daemon_tool_invocation_intent.v0`, plus `epiphany.cultmesh.daemon_tool_invocation_receipt.v0` when runtime-spine has a receipt id. Runtime-spine remains the tool lifecycle owner and raw MCP JSON stays quarantined behind `epiphany.tool_invocation_intent.v0` / `epiphany.tool_invocation_receipt.v0`; the local Verse mirror is operator-safe routing/readback. -- `epiphany-verse-query invoke-tool --capability-id ` now exercises the same global daemon tool directory directly: it records a typed daemon tool invocation intent plus receipt from an advertised capability and reports requesting/host display names, body domains, private Verse ids, and Eve surfaces from cluster topology, while the target daemon remains the execution owner and private Verse payloads stay sealed. `tools/epiphany_local_run.ps1 -Mode tool-invoke` wraps that same receipt path with explicit capability/requester/intent/receipt parameters, so operator use follows the same daemon-owned contract instead of a wrapper-owned shortcut. Tool routing reads the target daemon's typed liveness status and refuses when the host daemon is not `ready` or does not advertise `submitTypedToolIntent`. `epiphany-verse-query daemon-status --daemon-id --daemon-status ready|down|degraded` updates one daemon's status without repainting the whole status table, and local Verse seeding preserves existing daemon statuses and swarm-brake state instead of overwriting live telemetry. The CLI proof used Persona requesting Hands `repo-action` through `epiphany.cluster.hands.tool.repo-action`, returned Persona/Hands topology metadata, and refused the same invocation when Hands was degraded; the wrapper proof used stable ids `daemon-tool-intent-wrapper-persona-hands` / `daemon-tool-receipt-wrapper-persona-hands` and returned `availableToAllAgents=true`, `requiresReceipt=true`, and `privateStateExposed=false`. +- `epiphany-verse-query invoke-tool --capability-id ` now exercises the same global daemon tool directory directly: it records a typed daemon tool invocation intent plus receipt from an advertised capability and reports requesting/host display names, body domains, private Verse ids, and Eve surfaces from cluster topology, while the target daemon remains the execution owner and private Verse payloads stay sealed. `tools/epiphany_local_run.ps1 -Mode tool-invoke` wraps that same receipt path with explicit capability/requester/intent/receipt parameters, so operator use follows the same daemon-owned contract instead of a wrapper-owned shortcut. Tool routing reads the target daemon's typed liveness status and refuses when the host daemon is not `ready` or does not advertise `submitTypedToolIntent`. `epiphany-verse-query daemon-status --daemon-id --daemon-status ready|down|degraded` updates one daemon's status without repainting the whole status table, and local Verse seeding preserves existing daemon statuses and swarm-brake state instead of overwriting live telemetry. The CLI proof used Persona requesting Hands `repo-action` through `epiphany.cluster.hands.tool.repo-action`, returned Persona/Hands topology metadata, and refused the same invocation when Hands was degraded; the wrapper proof used stable ids `daemon-tool-intent-wrapper-persona-hands` / `daemon-tool-receipt-wrapper-persona-hands` and returned `availableToAllAgents=true`, `requiresReceipt=true`, and `privateStateExposed=false`. The invocation command body is now extracted out of the monolithic CLI `main` so compact row work does not tip the Windows debug stack; live wrapper artifact `local-20260618-181426-520-e3b051b8` routed Persona to Hands `repo-action` and printed one compact `INVOKE` row with requester/host private Verses, Eve surfaces, host daemon id, operation, capability, intent, receipt/status, authority, all-agents, receipt-required, and `private=false`. - `epiphany-verse-query swarm-overview` and `epiphany-verse-query swarm-triage` now publish direct and wrapper command hints for the compact operator paths: topology, liveness, surfaces, tool directory, receipt directory, poke-down, Eve connection, Bifrost ledger readback, and `wrapperInvokeTool=tools/epiphany_local_run.ps1 -Mode tool-invoke -ToolCapabilityId -ToolRequestingAgentId -ToolRequestingClusterId `. The hints are display/ergonomics only; daemon-hosted tool invocation still routes through the typed intent/receipt path and host-daemon liveness gate, Bifrost ledger readback still reads Bifrost/Imagination-owned receipts, and receipt directory readback only indexes latest sealed local Verse witnesses. - `epiphany-daemon-supervisor policy --daemon-id --restart-command [--restart-arg ...]` writes durable local Verse restart policy as `epiphany.cultmesh.daemon_restart_policy.v0` with command/args/cwd/cooldown/backoff/attempt state, reconcile cadence, stale-heartbeat threshold, and last scheduled pulse. `epiphany-daemon-supervisor reconcile --daemon-id ` loads that policy when no override command is supplied, refuses under a matching `daemon.lifecycle_poke` brake before launching anything, refuses during cooldown/backoff, writes daemon poke intent/receipt documents, updates daemon status to `ready` or `down` from the command exit, and updates policy attempt state. `epiphany-daemon-supervisor tick [--daemon-id ]` is an explicit scheduler pulse: it scans durable policies, records ready/noop pulses, skips before the configured interval, marks stale heartbeat status degraded, routes restarts through the same brake/cooldown/poke receipt path, and writes latest `epiphany.cultmesh.daemon_scheduler_receipt.v0`. `epiphany-daemon-supervisor serve` runs that same pulse on `--loop-interval-seconds`, with bounded `--max-iterations` proof mode or unbounded service mode. `epiphany-daemon-supervisor service-plan`/`install-service` writes a planned local Verse `epiphany.cultmesh.daemon_service_lifecycle_receipt.v0` for the `serve` command; `service-launch`/`start-service` spawns that loop through the current executable or `--service-command`, can `--wait-child` for bounded proof, and records command/args/cwd, scheduler id, daemon selector, pid/exit/status, artifact ref, and private-state guard; `service-runbook` writes a PowerShell operator runbook artifact for the same `serve` command and records it as a lifecycle receipt without spawning; `windows-service-install`/`service-install-plan` writes a Windows `sc.exe create` install script for that same `serve` command and records a lifecycle receipt, with optional `--execute-install` reserved for explicit elevated authority; `windows-service-status`, `windows-service-reconcile`, `windows-service-execution-readiness`, `windows-service-execution-audit`, `windows-service-execution-runbook`, `windows-service-start`, and `windows-service-stop` query/request/audit/document the Windows service manager path and record lifecycle receipts for missing/running/stopped/query-failed, in-sync/drift/missing/query-failed, elevated-ready/not-elevated, complete/incomplete, runbook written, start/stop planned, execution-refused-not-elevated, or start/stop requested/failed outcomes. `tools/epiphany_local_run.ps1 -Mode service-runbook`, `-Mode service-install-plan`, `-Mode service-install-execute`, `-Mode service-status`, `-Mode service-reconcile`, `-Mode service-execution-readiness`, `-Mode service-execution-runbook`, `-Mode service-execution-audit`, `-Mode service-start-plan`, `-Mode service-stop-plan`, `-Mode service-start-execute`, and `-Mode service-stop-execute` wrap the artifact/status/reconcile/readiness/runbook/audit/control-plan/execution paths with operator run intent/receipt and final local Verse context. Service lifecycle actions check the local Verse swarm brake before spawning, writing runbook/install artifacts, querying service-manager state, reconciling service policy, checking execution readiness, auditing execution receipts, planning control, or requesting start/stop. The local wrapper builds/checks this binary with the other Epiphany operator tools. Installed-service status reconciliation has now been proved read-only against the real Windows `EventLog` service: `service-status` wrote `daemon-service-lifecycle-receipt-windows-eventlog-proof-windows-service-status` with status `running`, exit code `0`, and `privateStateExposed=false`. The new read-only policy reconciliation path was also proved: direct missing-service reconcile wrote `missing`, direct real-service reconcile wrote `drift`, brake engagement refused before query with exit `1`, and wrapper `service-reconcile` wrote `daemon-service-lifecycle-receipt-windows-eventlog-reconcile-proof-windows-service-reconcile` with status `drift`, exit code `0`, artifact ref `service-manager://windows/EventLog/reconcile`, and `privateStateExposed=false`. Start/stop now plan by default unless `--execute-control`/`--execute-service-control` is passed: direct start/stop wrote `start-planned`/`stop-planned` with `executed=false`, wrapper `service-start-plan` and `service-stop-plan` wrote local Verse receipts against `EventLog`, and brake engagement refused the plan path before any control action. Explicit execution is also elevation-gated before mutation: non-elevated `--execute-control` and `--execute-install` wrote `execution-refused-not-elevated` lifecycle receipts with `executed=false`, and a matching brake still refuses before the elevation check. Execution readiness is queryable without mutation: direct and wrapper `windows-service-execution-readiness` wrote `not-elevated` receipts for `EventLog`, with `privateStateExposed=false`, and brake engagement refused readiness as lifecycle activity. The explicit wrapper execution modes have also been proved in this non-elevated shell: `service-install-execute`, `service-start-execute`, and `service-stop-execute` reached the elevation gate, wrote `execution-refused-not-elevated` receipts, and did not mutate Windows services. Execution audit is now the post-run proof target: it scans service lifecycle receipts through CultMesh typed enumeration, writes its own audit receipt, and returns `complete` only when the sealed `windows-service-execution-runbook` witness plus elevated-ready/install/start/status/reconcile/stop effect receipts are present, successful, and sealed. Audit check rows carry `operatorArtifactRef`, and wrapper audit summaries print OK runbook witnesses with local SHA-256 before failed checks. Focused test `service_execution_audit_checks_expose_operator_artifact_refs` proves the typed field; live cluster wrapper proof `local-20260618-170322-330-e6cb2117` printed the cluster runbook witness hash while still reporting the five expected failed checks. This is a durable policy-backed process-owner seam; the remaining scar is an actual elevated install/start/stop execution proof under operator authority followed by a `complete` audit receipt. - `tools/epiphany_local_run.ps1 -Mode service-execution-runbook` writes a non-mutating `#Requires -RunAsAdministrator` PowerShell artifact that sequences `service-execution-readiness`, `service-install-execute`, `service-start-execute`, `service-status`, `service-reconcile`, `service-stop-execute`, final `service-status`, and `service-execution-audit` through the existing typed wrapper modes, then asks `epiphany-daemon-supervisor windows-service-execution-runbook` to seal the artifact as a `written` lifecycle receipt. The artifact now uses `Invoke-EpiphanyRunbookStep` so each step resets `$LASTEXITCODE`, catches thrown/nonzero failures, keeps running to gather more lifecycle receipts, runs `service-execution-audit` in `finally`, and exits nonzero only after the audit if any step failed. Latest proof `local-20260618-160641-685-331af57c` generated and parsed the artifact with `privateStateExposed=false`; the next elevated scar is to run it under explicit operator authority and preserve its effect receipts. diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index 00969831..9dfad1f9 100644 Binary files a/state/ledgers.msgpack and b/state/ledgers.msgpack differ diff --git a/state/map.yaml b/state/map.yaml index 59010dd7..cb0b06ec 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -615,7 +615,7 @@ current_status: - `epiphany-verse-query restart-policy-directory` and `tools/epiphany_local_run.ps1 -Mode service-policy-directory` provide the compact scheduler coverage sight organ. It reads daemon topology/status plus durable `epiphany.cultmesh.daemon_restart_policy.v0` rows through a narrow CultMesh restart-policy directory loader, reports each daemon as `ENABLED`, `DISABLED`, or `MISSING`, treats disabled and missing policies as recovery attention, returns covered/enabled/disabled/missing/attention counts, and publishes explicit policy-write plus scheduler-tick follow-up commands without creating or mutating policies. Compact TUI rows now expose cooldown, reconcile interval, heartbeat stale threshold, failure count, last result, `followUp=`, and `private=false`; wrapper proof `local-20260618-175008-830-e470a387` printed all seven faculties as `enabled` with `last=ready`, `reconcile=60s`, `stale=300s`, and `tools/epiphany_local_run.ps1 -Mode service-tick` follow-up. Direct and wrapper proof now report seven daemons, seven covered policies, seven enabled policies, zero disabled, zero missing, zero attention, `status=ok`, and `privateStateExposed=false`. This is a real daemon heartbeat body with non-mutating per-organ launch and install artifacts, but not yet executed installed per-organ OS services. - `https://gamecult.org/Blog/purge-the-heretek-from-our-daemonic-swarm` is a public doctrine receipt for the Perfect Machine audit pressure: organs must not borrow each other's crowns, witnesses need owning mouths, surfaces reflect rather than rule, and cross-repo purification needs Bifrost-shaped ledger evidence instead of relying on recoverable transcripts or commit trails alone. - `tools/epiphany_local_run.ps1` now uses collision-resistant local run ids (`local-YYYYMMDD-HHMMSS-fff-`) for artifact roots and operator run receipts. Same-second parallel compact wrapper proofs for `collaboration-feedback` and `bifrost-publication` produce separate artifact drawers and both complete, so operator rites no longer collide on second-resolution run ids. - - `epiphany-verse-query tool-directory` and `tools/epiphany_local_run.ps1 -Mode tool-directory` provide the compact daemon tool sight organ over the same local Verse capability records used by `invoke-tool`. It returns sixteen globally available daemon-hosted capabilities, host readiness counts, direct `invocationCommand`, wrapper `wrapperInvocationCommand=tools/epiphany_local_run.ps1 -Mode tool-invoke -ToolCapabilityId -ToolRequestingAgentId -ToolRequestingClusterId `, and the invariants `availableToAllAgents=true`, `requiresReceipt=true`, `privateStateExposed=false`. Agent-friendly TUI rows now carry the same contract bits, e.g. `READY | Hands | repo-action | submitHandsActionIntent | epiphany.cluster.hands.tool.repo-action | eve://epiphany/hands | allAgents=true | receipt=true | private=false`, and wrapper proof `local-20260618-171552-983-cd0d0ef7` prints those invariants in the one-line summary. + - `epiphany-verse-query tool-directory` and `tools/epiphany_local_run.ps1 -Mode tool-directory` provide the compact daemon tool sight organ over the same local Verse capability records used by `invoke-tool`. It returns sixteen globally available daemon-hosted capabilities, host readiness counts, direct `invocationCommand`, wrapper `wrapperInvocationCommand=tools/epiphany_local_run.ps1 -Mode tool-invoke -ToolCapabilityId -ToolRequestingAgentId -ToolRequestingClusterId `, and the invariants `availableToAllAgents=true`, `requiresReceipt=true`, `privateStateExposed=false`. Agent-friendly TUI rows now carry the same contract bits, e.g. `READY | Hands | repo-action | submitHandsActionIntent | epiphany.cluster.hands.tool.repo-action | eve://epiphany/hands | allAgents=true | receipt=true | private=false`, and wrapper proof `local-20260618-171552-983-cd0d0ef7` prints those invariants in the one-line summary. `invoke-tool` now runs through a smaller extracted command handler and emits compact invocation rows with requester/host display names, requesting/host private Verses and Eve surfaces, host daemon id, tool operation, capability id, intent id, receipt id/status, authority gate, `allAgents=true`, `receiptRequired=true`, and `private=false`; live wrapper proof `local-20260618-181426-520-e3b051b8` routed Persona to Hands `repo-action` and printed the full compact row. - `epiphany-verse-query cluster-topology` and `tools/epiphany_local_run.ps1 -Mode cluster-topology` provide the compact cluster/body/private-Verse/daemon sight organ over local Verse topology rows through a narrow CultMesh loader. The report returns seven clusters, seven private Verse ids, seven daemon ids, one public Persona discussion cluster, agent-friendly TUI rows such as `PUBLIC | Persona | epiphany.cluster.persona.private | repo:E:/Projects/EpiphanyAgent | epiphany-daemon-persona | eve://epiphany/persona`, and `privateStateExposed=false`; `epiphany-verse-query smoke` now source-proves the compact topology invariants. - `epiphany-verse-query swarm-overview` and `tools/epiphany_local_run.ps1 -Mode swarm-overview` provide the one-command compact global-agent sight rite. It composes narrow cluster topology, liveness, Odin/Eve surface, daemon tool, restart-policy coverage, receipt-directory service-lifecycle attention, and the shared core service-execution audit report without full local Verse serialization, returning seven agents/daemons, seven clusters, seven private Verse ids, seven Eve surfaces, one public Persona discussion surface, sixteen globally invokable receipt-gated tools, command hints for topology/liveness/surfaces/tools/receipts/poke/connect/invoke/Bifrost-ledger/restart-policy coverage, wrapper command hints for overview/receipts/poke/connect/tool-invoke/Bifrost-ledger/service-policy-directory/service-execution-audit, sealed `toolHostAttentionRows`/TUI rows for blocked daemon-hosted capabilities, sealed `serviceLifecycleAttentionRows`/TUI rows copied from the receipt directory, `serviceExecutionFailedCheckRows`/TUI rows naming failed sealed execution checks, explicit `serviceLifecycleRecommended*` fields so service-manager follow-up remains visible even when global `recommendedWrapperMode` is occupied by liveness or policy, and ordered `swarmActionRows`/TUI rows with authority gates, effect class, wrapper command, mutation flag, elevation flag, sealed `operatorArtifactRef`, `operatorArtifactStatus`, `operatorArtifactSha256`, `operatorArtifactExecutionCommand`, `operatorAftercareCommand`, and `completionAuditWrapper*` for operator-runbook rows. Current live overview `local-20260618-174345-036-0ab0cdc2` returns `status=attention`, `livenessStatus=ready`, `recoveryStatus=attention`, `recommendedWrapperMode=cluster-service-execution-audit`, `serviceLifecycleRecommendedWrapperMode=cluster-service-execution-audit`, `toolHostAttentionCount=0`, `serviceLifecycleAttentionCount=2`, `swarmActionCount=2`, a non-mutating/non-elevated `priority=40` service lifecycle readback row with `command=tools/epiphany_local_run.ps1 -Mode cluster-service-execution-audit`, and a mutating/elevated `priority=50`, `family=service-execution-authority`, `status=operator-authority-required`, `effectClass=elevated-service-control`, `wrapperMode=operator-elevated-runbook`, `wrapperCommand=tools/epiphany_local_run.ps1 -Mode cluster-service-execution-runbook # refreshes the sealed runbook; execute the generated artifact only with explicit elevated operator authority`, `authorityGate=operator.elevated_windows_service_control`, `operatorArtifactStatus=present`, `operatorArtifactSha256=3239ac17008b1ce857970e40b17d37a1ecf1631a3a6636bb2b8678312fb604a0`, `operatorArtifactRef=E:\Projects\EpiphanyAgent\.epiphany-run\local-20260618-161632-376-436227d8\epiphany-cluster-daemon-services-execution-runbook.ps1`, `operatorArtifactExecutionCommand=Start-Process PowerShell -Verb RunAs -Wait -ArgumentList @('-NoProfile','-ExecutionPolicy','Bypass','-File','...epiphany-cluster-daemon-services-execution-runbook.ps1')`, `operatorAftercareCommand=tools/epiphany_local_run.ps1 -Mode cluster-service-execution-audit`, `completionAuditWrapperMode=cluster-service-execution-audit`, `completionAuditWrapperCommand=tools/epiphany_local_run.ps1 -Mode cluster-service-execution-audit`, `privateStateExposed=false`, and compact `serviceExecutionFailedChecks=cluster-windows-service-execution-readiness=not-elevated; cluster-windows-service-install=planned; cluster-windows-service-start=execution-refused-not-elevated; cluster-windows-service-execution-audit=incomplete; cluster-windows-service-stop=execution-refused-not-elevated`. If that local artifact is missing, source smoke proves the same row is demoted to `status=runbook-artifact-missing`, `wrapperMode=cluster-service-execution-runbook`, `effectClass=service-lifecycle-runbook-regeneration`, `operatorArtifactSha256=none`, `operatorArtifactExecutionCommand=none`, `operatorAftercareCommand=none`, `mutatesState=false`, and `requiresElevatedAuthority=false` instead of pretending elevated service authority is actionable. Degraded daemon overview still routes global recommendation to `swarm-poke-down` first while independent action rows preserve the liveness/tool-host duties; liveness action rows are explicitly mutating typed lifecycle pokes but do not require elevation, while service execution authority rows make the operator grant boundary visible. `epiphany-verse-query` defaults to the shared `.epiphany-run\cultmesh\local-verse.ccmp` store for live query/readback, while bare `epiphany-verse-query smoke` now uses a disposable `.epiphany-smoke\verse-query-default\local-verse.ccmp` fixture unless an explicit `--store` is provided. - `epiphany-verse-query swarm-triage` and `tools/epiphany_local_run.ps1 -Mode swarm-triage` compose the compact swarm overview with the existing brake-aware daemon poke receipt writer. Triage pokes only daemon liveness attention; service-manager lifecycle wounds are reflected from overview as sealed `serviceLifecycleAttentionRows`/TUI rows, `serviceExecutionFailedCheckRows`/TUI rows, explicit `serviceLifecycleRecommended*` fields, and ordered `swarmActionRows`, but do not cause daemon poke receipts, while blocked daemon-hosted tools ride as sealed `toolHostAttentionRows`/TUI rows so agents can see which advertised capabilities are unavailable because their host body is sick. Current live triage `local-20260618-150015-844-aba20563` returns `status=noop`, `overviewStatus=attention`, `livenessStatus=ready`, `recoveryStatus=attention`, `clusterCount=7`, `privateVerseCount=7`, `pokedDaemonCount=0`, `recommendedWrapperMode=cluster-service-execution-audit`, `serviceLifecycleRecommendedWrapperMode=cluster-service-execution-audit`, `swarmActionCount=2`, the same sealed service readback plus operator-elevated runbook rows, including `operatorArtifactStatus=present`, `operatorArtifactRef=E:\Projects\EpiphanyAgent\.epiphany-run\local-20260618-132257-139-26213a29\epiphany-cluster-daemon-services-execution-runbook.ps1`, and `completionAuditWrapperCommand=tools/epiphany_local_run.ps1 -Mode cluster-service-execution-audit` on the elevated row, empty daemon/tool-host attention arrays, `serviceLifecycleAttentionCount=2`, routes `epiphany-cluster-daemon-services::cluster-windows-service-execution-audit` and `epiphany-daemon-supervisor-service::windows-service-execution-audit`, direct and wrapper command hints including `wrapperInvokeTool` and `wrapperBifrostLedger=tools/epiphany_local_run.ps1 -Mode bifrost-ledger`, and `privateStateExposed=false`; degraded Hands proof returns `status=ok`, `overviewStatus=attention`, topology and daemon TUI rows, blocked Hands-hosted tool rows, one sealed topology-aware poke receipt for `epiphany-daemon-hands` with `bodyDomain=repo:E:/Projects/EpiphanyAgent`, `privateVerseId=epiphany.cluster.hands.private`, `eveSurfaceId=eve://epiphany/hands`, `observedStatus=degraded`, `attentionDaemonIds=[epiphany-daemon-hands]`, `attentionToolHostDaemonIds=[epiphany-daemon-hands]`, and liveness/tool-host action rows with no private-state exposure. Source smoke `verse-query-service-execution-failed-checks-clean` proves a physical synthetic written runbook plus incomplete service audit receipts expose both service lifecycle readback and failed-check anatomy with no Windows service mutation. diff --git a/tools/epiphany_local_run.ps1 b/tools/epiphany_local_run.ps1 index 29ffd322..f51b12a9 100644 --- a/tools/epiphany_local_run.ps1 +++ b/tools/epiphany_local_run.ps1 @@ -1697,7 +1697,11 @@ if ($resultPath -ne "" -and (Test-Path -LiteralPath $resultPath)) { } elseif ($Mode -eq "tool-directory") { Write-Host "Tool directory: status=$($result.status), tools=$($result.toolCount), hostReady=$($result.hostReadyCount), hostAttention=$($result.hostAttentionCount), availableToAllAgents=$($result.invariants.availableToAllAgents), requiresReceipt=$($result.invariants.requiresReceipt), privateStateExposed=$($result.invariants.privateStateExposed)" } elseif ($Mode -eq "tool-invoke") { - Write-Host "Tool invoke: status=$($result.status), requester=$($result.requestingDisplayName), host=$($result.hostDisplayName), tool=$($result.toolName), receipt=$($result.receiptId), privateStateExposed=$($result.privateStateExposed)" + $invocationRows = "none" + if ($null -ne $result.invocationRows -and $result.invocationRows.Count -gt 0) { + $invocationRows = ($result.invocationRows -join "; ") + } + Write-Host "Tool invoke: status=$($result.status), requester=$($result.requestingDisplayName), host=$($result.hostDisplayName), tool=$($result.toolName), receipt=$($result.receiptId), invocationRows=$invocationRows, privateStateExposed=$($result.privateStateExposed)" } elseif ($Mode -eq "swarm-overview") { $attention = "none" if ($null -ne $result.attentionDaemonIds -and $result.attentionDaemonIds.Count -gt 0) {