From 2a797370f60dbc3a74ca3da75beceb842ff3cf6d Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 12 Jul 2026 20:33:30 +0100 Subject: [PATCH] Bind operator run receipts to result evidence --- .../src/bin/epiphany-operator-run.rs | 86 ++++++++++++++++-- notes/epiphany-current-algorithmic-map.md | 7 ++ notes/fresh-workspace-handoff.md | 4 + ...eipt-writer-provenance-audit-2026-07-12.md | 11 ++- state/ledgers.msgpack | Bin 432317 -> 432708 bytes state/map.yaml | 2 +- tools/epiphany_local_run.ps1 | 1 - 7 files changed, 95 insertions(+), 16 deletions(-) diff --git a/epiphany-core/src/bin/epiphany-operator-run.rs b/epiphany-core/src/bin/epiphany-operator-run.rs index db772f8a..c18056e2 100644 --- a/epiphany-core/src/bin/epiphany-operator-run.rs +++ b/epiphany-core/src/bin/epiphany-operator-run.rs @@ -1,11 +1,6 @@ use anyhow::Context; use anyhow::Result; use chrono::Utc; -use epiphany_core::EPIPHANY_CULTMESH_INTERNAL_VERSE_ID; -use epiphany_core::EPIPHANY_CULTMESH_OPERATOR_RUN_INTENT_SCHEMA_VERSION; -use epiphany_core::EPIPHANY_CULTMESH_OPERATOR_RUN_RECEIPT_SCHEMA_VERSION; -use epiphany_core::EpiphanyCultMeshOperatorRunIntentEntry; -use epiphany_core::EpiphanyCultMeshOperatorRunReceiptEntry; use epiphany_core::epiphany_cultmesh_coordinator_run_receipt_from_summary_json; use epiphany_core::epiphany_cultmesh_hands_action_gate_from_summary_json; use epiphany_core::epiphany_cultmesh_role_review_event_from_summary_json; @@ -16,8 +11,13 @@ use epiphany_core::write_epiphany_cultmesh_hands_action_gate; use epiphany_core::write_epiphany_cultmesh_operator_run_intent; use epiphany_core::write_epiphany_cultmesh_operator_run_receipt; use epiphany_core::write_epiphany_cultmesh_role_review_event; -use serde_json::Value; +use epiphany_core::EpiphanyCultMeshOperatorRunIntentEntry; +use epiphany_core::EpiphanyCultMeshOperatorRunReceiptEntry; +use epiphany_core::EPIPHANY_CULTMESH_INTERNAL_VERSE_ID; +use epiphany_core::EPIPHANY_CULTMESH_OPERATOR_RUN_INTENT_SCHEMA_VERSION; +use epiphany_core::EPIPHANY_CULTMESH_OPERATOR_RUN_RECEIPT_SCHEMA_VERSION; use serde_json::json; +use serde_json::Value; use std::env; use std::fs; use std::path::PathBuf; @@ -49,6 +49,51 @@ fn main() -> Result<()> { print_json(json!({"status": "written", "store": args.store, "intent": written}))?; } "receipt" => { + let intent = + load_latest_epiphany_cultmesh_operator_run_intent(&args.store, &args.runtime_id)? + .context("operator-run receipt requires a persisted run intent")?; + if intent.run_id != args.run_id || intent.mode != args.mode { + anyhow::bail!( + "operator-run receipt does not match latest intent: requested run={} mode={}, latest run={} mode={}", + args.run_id, + args.mode, + intent.run_id, + intent.mode + ); + } + let result_path = canonical_file(&args.result_path, "--result-path")?; + let artifact_root = canonical_directory(&args.artifact_root, "--artifact-root")?; + if !result_path.starts_with(&artifact_root) { + anyhow::bail!( + "operator-run result {} is outside artifact root {}", + result_path.display(), + artifact_root.display() + ); + } + let result_source = fs::read_to_string(&result_path).with_context(|| { + format!("failed to read result artifact {}", result_path.display()) + })?; + let _: Value = serde_json::from_str(result_source.trim_start_matches('\u{feff}')) + .with_context(|| { + format!( + "operator-run result is not valid JSON: {}", + result_path.display() + ) + })?; + let requested_at = chrono::DateTime::parse_from_rfc3339(&intent.requested_at_utc) + .context("operator-run intent has invalid requested_at_utc")? + .with_timezone(&Utc); + let modified_at: chrono::DateTime = fs::metadata(&result_path)? + .modified() + .context("operator-run result has no modification time")? + .into(); + if modified_at < requested_at { + anyhow::bail!( + "operator-run result predates its intent: result={} intent={}", + modified_at.to_rfc3339(), + requested_at.to_rfc3339() + ); + } let mut artifact_refs = Vec::new(); push_non_empty(&mut artifact_refs, &args.result_path); push_non_empty(&mut artifact_refs, &args.artifact_root); @@ -60,7 +105,7 @@ fn main() -> Result<()> { run_id: args.run_id.clone(), completed_at_utc: Utc::now().to_rfc3339(), mode: args.mode.clone(), - status: args.status.clone(), + status: "completed".to_string(), result_path: args.result_path.clone(), artifact_root: args.artifact_root.clone(), dogfood_root: args.dogfood_root.clone(), @@ -212,7 +257,6 @@ struct Args { artifact_root: String, dogfood_root: String, result_path: String, - status: String, operator_snapshot_store: String, operator_snapshot_id: String, coordinator_summary: Option, @@ -241,7 +285,6 @@ impl Args { artifact_root: String::new(), dogfood_root: String::new(), result_path: String::new(), - status: "completed".to_string(), operator_snapshot_store: String::new(), operator_snapshot_id: String::new(), coordinator_summary: None, @@ -268,7 +311,6 @@ impl Args { "--artifact-root" => args.artifact_root = next(&mut values, "--artifact-root")?, "--dogfood-root" => args.dogfood_root = next(&mut values, "--dogfood-root")?, "--result-path" => args.result_path = next(&mut values, "--result-path")?, - "--status" => args.status = next(&mut values, "--status")?, "--operator-snapshot-store" => { args.operator_snapshot_store = next(&mut values, "--operator-snapshot-store")? } @@ -319,6 +361,30 @@ fn push_non_empty(items: &mut Vec, value: &str) { } } +fn canonical_file(value: &str, name: &str) -> Result { + if value.trim().is_empty() { + anyhow::bail!("operator-run receipt requires {name}"); + } + let path = + fs::canonicalize(value).with_context(|| format!("{name} does not exist: {value}"))?; + if !path.is_file() { + anyhow::bail!("{name} is not a file: {}", path.display()); + } + Ok(path) +} + +fn canonical_directory(value: &str, name: &str) -> Result { + if value.trim().is_empty() { + anyhow::bail!("operator-run receipt requires {name}"); + } + let path = + fs::canonicalize(value).with_context(|| format!("{name} does not exist: {value}"))?; + if !path.is_dir() { + anyhow::bail!("{name} is not a directory: {}", path.display()); + } + Ok(path) +} + fn print_json(value: Value) -> Result<()> { println!("{}", serde_json::to_string_pretty(&value)?); Ok(()) diff --git a/notes/epiphany-current-algorithmic-map.md b/notes/epiphany-current-algorithmic-map.md index 9258f7f6..d98b0555 100644 --- a/notes/epiphany-current-algorithmic-map.md +++ b/notes/epiphany-current-algorithmic-map.md @@ -86,6 +86,13 @@ - Negative proof: targeting `state/local-verse.ccmp` was rejected and its SHA-256 did not change. - Positive proof: the built-in quarantined smoke completes successfully and reports its quarantine coordinates. +## Operator-run completion receipt boundary (2026-07-12) + +- Intent owner: operator orchestration writes run id, mode, roots, limits, and artifact coordinates before execution. +- Completion evidence: matching latest intent plus a valid JSON result inside the canonical artifact root, modified at or after intent creation. +- Receipt status is derived as `completed`; callers cannot submit status. +- Forbidden evidence: missing, non-JSON, pre-intent, out-of-root, or mismatched-run artifacts. + This is the source-grounded map of the live machine. Historical route and bridge anatomy belongs in git history and evidence ledgers, not here. diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 2a4c31c4..1fbe42c8 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -40,6 +40,10 @@ The first call-site audit found a new canonical-promotion substitution in `epiph The monolithic `epiphany-verse-query smoke` fixture writer can no longer accept `--store` or `--runtime-id`; it is hard-bound to `.epiphany-smoke/verse-query-default/local-verse.ccmp` and `verse-query-default-smoke`. A direct canonical-store attempt was rejected before mutation and `state/local-verse.ccmp` retained the same SHA-256. The quarantined smoke now explicitly seeds fixture daemon statuses, expects intent-only batch poke rows, and completes `status=ok` within quarantine. Next inspect operator-run/supervisor provenance and quarantine standalone smoke binaries that accept arbitrary roots. +## 2026-07-12 operator-run completion evidence binding + +`epiphany-operator-run receipt` no longer accepts `--status`. It loads the latest operator intent and requires matching run id/mode, canonicalizes the result and artifact-root paths, requires the result to be a file contained under the artifact root, parses it as JSON, and rejects it if its modification time predates the intent. Only then does it derive `status=completed`. The wrapper no longer passes a completion assertion. Negative proof rejects caller status and out-of-root artifacts; positive proof uses a post-intent JSON artifact. Next audit daemon-supervisor planning/rehearsal receipts against actual service/process evidence. + This is the re-entry rite for `E:\Projects\EpiphanyAgent`: the waking chant for the local machine-spirit before it touches the forge. diff --git a/notes/receipt-writer-provenance-audit-2026-07-12.md b/notes/receipt-writer-provenance-audit-2026-07-12.md index 2c25bd7f..4ae45a12 100644 --- a/notes/receipt-writer-provenance-audit-2026-07-12.md +++ b/notes/receipt-writer-provenance-audit-2026-07-12.md @@ -19,11 +19,14 @@ A forged operator-status artifact containing `intentId=forged-intent`, `receiptI ## Remaining scrutiny -1. Inspect `epiphany-operator-run` receipt construction against actual subprocess/runtime evidence rather than command arguments. -2. Inspect daemon-supervisor plan/rehearsal receipts separately from execution receipts so planning cannot masquerade as service mutation. -3. Extend the same store quarantine invariant to standalone smoke binaries that accept output paths. -4. Distill or remove dead public receipt constructors once every named provider executable exists. +1. Inspect daemon-supervisor plan/rehearsal receipts separately from execution receipts so planning cannot masquerade as service mutation. +2. Extend the same store quarantine invariant to standalone smoke binaries that accept output paths. +3. Distill or remove dead public receipt constructors once every named provider executable exists. ## Smoke quarantine update `epiphany-verse-query smoke` is now hard-bound to `.epiphany-smoke/verse-query-default/local-verse.ccmp` and runtime `verse-query-default-smoke`. Store or runtime overrides are rejected before the fixture body runs. A direct attempt against `state/local-verse.ccmp` was refused and its SHA-256 remained unchanged. The quarantined smoke was repaired to seed fixture liveness explicitly and to expect requester-only poke output; it completes successfully inside the quarantine. + +## Operator-run evidence binding update + +The standalone `receipt` command previously accepted caller status and path strings, then wrote `completed` evidence without inspecting the run. It now requires the latest persisted intent to match run id and mode, derives status internally as `completed`, requires an existing valid JSON result contained by the canonical artifact root, and requires the result modification time to be no earlier than the intent request time. `--status` no longer exists. The PowerShell orchestrator supplies only the result coordinates after its checked subprocess completes. diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index 5c07026df5725fe4494f6cf3ed29b9b9e8ebf43f..6f36504f2041e3c60d60c2145429c1b716eceba6 100644 GIT binary patch delta 371 zcmXX>Jxjzu5H(TL*w_eig^XTflM^qU7Jif#CukwqEt|>Rz-BkMI|*1_VPPjWG59YO z1uHx8cR8mi-s~{*=DoLHZ-eiT!RvOkk!45Y@$Jt_^0NCrjOI}k-?#BY8%KiWyDC@{ ztbev~vj5zjJSCe^)*YTkJByohSKAz1gsp0_a02|8DY8=7U z|5Uc~nmh(573&$lc!AglPZ( delta 57 zcmX@IMQZOxsSOg0+(wpG21ZfG7}%!I+N{i2$jHdpJe8?^Dib3RgJ@?Ngc9 HG^POn73vXb diff --git a/state/map.yaml b/state/map.yaml index 4d1ba431..4638e538 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -70,7 +70,7 @@ data_flow: - move historical proof to git, commits, smoke artifacts, or distilled evidence rather than plan prose active_subgoals: - - Use `notes/conceptual-substitution-audit-2026-07-12.md` plus `notes/receipt-writer-provenance-audit-2026-07-12.md` as the current authority-corrosion maps. Operator-snapshot cannot promote edge JSON into canonical tool receipts. `epiphany-verse-query smoke` is now hard-bound to its `.epiphany-smoke` store/runtime; overrides are refused before fixture writes, and canonical-store hash proof passed. Next audit operator-run and daemon-supervisor evidence provenance, then extend quarantine to standalone smoke binaries with configurable paths. + - Use `notes/conceptual-substitution-audit-2026-07-12.md` plus `notes/receipt-writer-provenance-audit-2026-07-12.md` as the current authority-corrosion maps. Operator-snapshot promotion is cut; verse-query smoke is quarantined. Operator-run completion now binds to a matching persisted intent and a fresh valid JSON result contained under the canonical artifact root; callers cannot supply completion status. Next audit daemon-supervisor plan/rehearsal versus execution provenance, then extend quarantine to standalone smoke binaries. - `swarm-overview` is an Epiphany-local, renderer-neutral diagnostic projection over provider-owned typed state. It owns no lifecycle, discovery, composition, or mutation authority. Epiphany publishes clean provider-owned Eve surfaces; Odin may discover them; downstream lowerers and compositors remain outside Epiphany's concern. - Idunn owns daemon survival and service lifecycle physiology. Codex/operator sessions may inspect, generate runbooks, and route explicit operator authority, but keeping daemons alive belongs to Idunn-owned service lifecycle receipts, scheduler policy, audits, and aftercare. Do not let Self, diagnostic projections, or ad hoc wrapper summaries become a second daemon-keeper. Global tool readbacks for Self `service-health`, `service-policy-directory`, and `swarm-online-runbook` state `lifecycleOwner=Idunn` and `hostedBody=Epiphany` without service mutation or private-state exposure. - Use `notes/epiphany-cultmesh-dreaming-roadmap.md` as the design authority for distributed Epiphany dreaming. Public dreams are separately authored typed documents distributed through CultMesh/CultNet; private state remains local and cannot be shared by flipping a visibility flag. Foreign dreams are external thought weather until a reviewed local adoption receipt digests them into memory, planning, doctrine, or governance. diff --git a/tools/epiphany_local_run.ps1 b/tools/epiphany_local_run.ps1 index 53272228..d99a9883 100644 --- a/tools/epiphany_local_run.ps1 +++ b/tools/epiphany_local_run.ps1 @@ -3379,7 +3379,6 @@ $operatorRunReceiptArgs = @( "--runtime-id", $LocalVerseRuntimeId, "--run-id", $runId, "--mode", $Mode, - "--status", "completed", "--result-path", $resultPath, "--artifact-root", $artifactRoot, "--dogfood-root", $dogfoodRoot,