From 5f66d6c95eb00fc20443f90e5f9cff7a0742f5ca Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 21 Aug 2026 20:40:54 +0200 Subject: [PATCH] Expose transcript-free decision discovery --- epiphany-core/src/reasoning_context.rs | 82 +++++++++++++++++-- .../src/bin/epiphany-openai-runtime.rs | 27 +++++- 2 files changed, 103 insertions(+), 6 deletions(-) diff --git a/epiphany-core/src/reasoning_context.rs b/epiphany-core/src/reasoning_context.rs index b10313b0..b62a0639 100644 --- a/epiphany-core/src/reasoning_context.rs +++ b/epiphany-core/src/reasoning_context.rs @@ -26,6 +26,7 @@ pub const DECISION_CONTEXT_TYPE: &str = "epiphany.decision_context.v1"; pub const MODEL_PASS_FAILURE_TYPE: &str = "epiphany.model_pass_failure.v1"; pub const MIND_COMMIT_RECEIPT_TYPE: &str = "epiphany.mind_commit_receipt.v1"; pub const DECISION_AUDIT_PROJECTION_SCHEMA_VERSION: &str = "epiphany.decision_audit_projection.v1"; +pub const DECISION_AUDIT_INDEX_SCHEMA_VERSION: &str = "epiphany.decision_audit_index.v1"; pub const WORKER_REASONING_PROJECTION_POLICY: &str = "epiphany.reasoning_projection.worker_launch.v2"; @@ -646,6 +647,29 @@ pub struct EpiphanyDecisionAuditProjection { pub transcript_required: bool, } +/// Read-only, deterministic discovery surface for decisions that already have +/// a complete durable audit chain. This is not persisted state and cannot +/// create, complete, or admit a decision. +#[derive(Clone, Debug, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EpiphanyDecisionAuditIndexProjection { + pub schema_version: String, + pub decisions: Vec, + pub transcript_required: bool, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EpiphanyDecisionAuditIndexEntry { + pub context_id: String, + pub basis_id: String, + pub pass_id: String, + pub organ_id: String, + pub terminal_request_id: String, + pub terminal_record_count: usize, + pub mind_commit_receipt_count: usize, +} + #[derive(Clone, Debug, Default, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct EpiphanyDecisionTerminalRecords { @@ -716,6 +740,48 @@ pub fn audit_decision_context( let context = cache .get::(context_id)? .ok_or_else(|| anyhow!("decision audit context is absent"))?; + audit_decision_context_from_cache(&cache, &context)? + .ok_or_else(|| anyhow!("decision audit context has no durable terminal decision record")) +} + +/// Enumerate only decisions whose complete transcript-free audit projection +/// validates against the current durable store. Sealed contexts that have not +/// terminalized yet are pass physiology, not decisions, and are omitted. +pub fn list_auditable_decision_contexts( + store_path: impl AsRef, +) -> Result { + let mut cache = runtime_spine_cache(store_path)?; + cache.pull_all_backing_stores()?; + let mut contexts = cache.get_all::()?; + contexts.sort_by(|left, right| left.context_id.cmp(&right.context_id)); + let mut decisions = Vec::new(); + for context in contexts { + let Some(audit) = audit_decision_context_from_cache(&cache, &context)? else { + continue; + }; + let terminal_record_count = audit.terminal_records.len(); + let mind_commit_receipt_count = audit.mind_commit_receipts.len(); + decisions.push(EpiphanyDecisionAuditIndexEntry { + context_id: audit.context_id, + basis_id: audit.reasoning_basis.basis_id, + pass_id: audit.reasoning_basis.pass_id, + organ_id: audit.reasoning_basis.organ_id, + terminal_request_id: audit.decision_context.terminal_request_id, + terminal_record_count, + mind_commit_receipt_count, + }); + } + Ok(EpiphanyDecisionAuditIndexProjection { + schema_version: DECISION_AUDIT_INDEX_SCHEMA_VERSION.to_string(), + decisions, + transcript_required: false, + }) +} + +fn audit_decision_context_from_cache( + cache: &CultCache, + context: &EpiphanyDecisionContext, +) -> Result> { let basis = cache .get::(&context.basis_id)? .ok_or_else(|| anyhow!("decision audit context lost its reasoning basis"))?; @@ -785,9 +851,7 @@ pub fn audit_decision_context( }; terminal_records.canonicalize(); if terminal_records.len() == 0 { - return Err(anyhow!( - "decision audit context has no durable terminal decision record" - )); + return Ok(None); } validate_decision_terminal_records(&cache, &basis, &context, &terminal_records)?; @@ -808,7 +872,7 @@ pub fn audit_decision_context( } mind_commit_receipts.sort_by(|left, right| left.receipt_id.cmp(&right.receipt_id)); - Ok(EpiphanyDecisionAuditProjection { + Ok(Some(EpiphanyDecisionAuditProjection { schema_version: DECISION_AUDIT_PROJECTION_SCHEMA_VERSION.to_string(), context_id: context.context_id.clone(), decision_context: context.clone(), @@ -820,7 +884,7 @@ pub fn audit_decision_context( terminal_records, mind_commit_receipts, transcript_required: false, - }) + })) } fn validate_decision_terminal_records( @@ -2255,6 +2319,14 @@ mod tests { vec![failure.clone()] ); assert!(!audit.transcript_required); + let index = list_auditable_decision_contexts(&store)?; + assert_eq!(index.schema_version, DECISION_AUDIT_INDEX_SCHEMA_VERSION); + assert!(!index.transcript_required); + assert_eq!(index.decisions.len(), 1); + assert_eq!(index.decisions[0].context_id, context.context_id); + assert_eq!(index.decisions[0].pass_id, basis.pass_id); + assert_eq!(index.decisions[0].organ_id, basis.organ_id); + assert_eq!(index.decisions[0].terminal_record_count, 1); let before = SingleFileMessagePackBackingStore::new(&store).pull_all()?; let mut replay = options.clone(); diff --git a/epiphany-openai-runtime/src/bin/epiphany-openai-runtime.rs b/epiphany-openai-runtime/src/bin/epiphany-openai-runtime.rs index 500ee484..9e3b2610 100644 --- a/epiphany-openai-runtime/src/bin/epiphany-openai-runtime.rs +++ b/epiphany-openai-runtime/src/bin/epiphany-openai-runtime.rs @@ -163,6 +163,22 @@ async fn main() -> Result<()> { print_json(&audit)?; } } + "list-decisions" => { + let mut store = PathBuf::from(DEFAULT_STORE); + let mut args = args.peekable(); + while let Some(arg) = args.next() { + match arg.as_str() { + "--store" => { + store = PathBuf::from( + args.next() + .context("list-decisions missing --store value")?, + ) + } + other => return Err(anyhow!("unknown list-decisions argument: {other}")), + } + } + print_json(&epiphany_core::list_auditable_decision_contexts(&store)?)?; + } "model-turn" => { let options = parse_model_turn_options(args.collect())?; require_supported_provider(&options.provider)?; @@ -1237,6 +1253,15 @@ mod tests { use epiphany_tool_adapter::EpiphanyToolInvocationIntent; use tempfile::tempdir; + #[test] + fn packaged_runtime_exposes_read_only_decision_discovery_and_exact_audit() { + let source = include_str!("epiphany-openai-runtime.rs"); + assert!(source.contains("\"list-decisions\" =>")); + assert!(source.contains("list_auditable_decision_contexts")); + assert!(source.contains("\"audit-decision\" =>")); + assert!(!source.contains(concat!("put_decision_", "context"))); + } + fn assert_typed_model_pass_failure( store: &Path, outer_job_id: &str, @@ -2084,5 +2109,5 @@ fn now() -> String { } fn usage() -> &'static str { - "usage: epiphany-model-runtime [--provider openai-codex] [--store path] [--codex-home path] [--request path] [--request-id id] [--context-id id] [--followup-request-id id] [--output path] [--session-id id] [--job-id id] [--activation-token-sha256 hex] [--objective text] [--default-model model] [--output-last-message path] [--auto-tools --tool-adapter-bin path --mcp-config path --cwd path --max-tool-rounds n]" + "usage: epiphany-model-runtime [--provider openai-codex] [--store path] [--codex-home path] [--request path] [--request-id id] [--context-id id] [--followup-request-id id] [--output path] [--session-id id] [--job-id id] [--activation-token-sha256 hex] [--objective text] [--default-model model] [--output-last-message path] [--auto-tools --tool-adapter-bin path --mcp-config path --cwd path --max-tool-rounds n]" }