Expose transcript-free decision discovery

This commit is contained in:
Meta Vens 2026-08-21 20:40:54 +02:00
commit 5f66d6c95e
2 changed files with 103 additions and 6 deletions

View file

@ -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<EpiphanyDecisionAuditIndexEntry>,
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::<EpiphanyDecisionContext>(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<Path>,
) -> Result<EpiphanyDecisionAuditIndexProjection> {
let mut cache = runtime_spine_cache(store_path)?;
cache.pull_all_backing_stores()?;
let mut contexts = cache.get_all::<EpiphanyDecisionContext>()?;
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<Option<EpiphanyDecisionAuditProjection>> {
let basis = cache
.get::<EpiphanyReasoningBasis>(&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();

View file

@ -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 <audit-decision|model-turn|run-worker|tool-followup|tool-followup-turn|smoke> [--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 <list-decisions|audit-decision|model-turn|run-worker|tool-followup|tool-followup-turn|smoke> [--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]"
}