From 79346523819fed1389a400b8a01ede18aba9b6b6 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 17 Aug 2026 19:29:43 +0200 Subject: [PATCH] Seal role passes from keyed Mind --- epiphany-core/src/coordinator_acceptance.rs | 54 ++- epiphany-core/src/current_work.rs | 248 ++++++++++- epiphany-core/src/mind_documents.rs | 40 +- epiphany-core/src/reasoning_context.rs | 395 +++++++++++++++--- .../src/bin/epiphany-openai-runtime.rs | 2 +- epiphany-openai-runtime/src/lib.rs | 98 ++++- 6 files changed, 760 insertions(+), 77 deletions(-) diff --git a/epiphany-core/src/coordinator_acceptance.rs b/epiphany-core/src/coordinator_acceptance.rs index 72ece4d2..58a3e661 100644 --- a/epiphany-core/src/coordinator_acceptance.rs +++ b/epiphany-core/src/coordinator_acceptance.rs @@ -316,13 +316,52 @@ pub fn accept_coordinator_role_finding( anyhow::anyhow!("Modeling result has no Repository Body observation basis") })?; validate_repository_body_observation_basis(store, &repository_body_observation_basis)?; - if let Some(proposal) = result.repo_model_mutation_proposal()? { + let proposal = result.repo_model_mutation_proposal()?; + let is_body_modeling = result.proposal_modeling_request_id.is_none() + && result.claim_repair_request_id.is_none() + && result.repo_frontier_modeling_request_id.is_none(); + if proposal.is_none() + && !finding.verdict.as_deref().is_some_and(|verdict| { + verdict.eq_ignore_ascii_case("checkpoint-ready") + || verdict.eq_ignore_ascii_case("regather-needed") + }) + { + return Err(anyhow::anyhow!( + "Modeling result requiring RepoModel change has no semantic mutation proposal" + )); + } + let mut strong_reads = Vec::new(); + let mut writes = Vec::new(); + if let Some(proposal) = proposal.as_ref() { if proposal.proposal_id != format!("repo-model-mutation-proposal-{job_id}") { return Err(anyhow::anyhow!( "Modeling RepoModel mutation proposal identity is not runtime-owned" )); } let plan = plan_repo_model_mutation(store, &proposal)?; + strong_reads = plan.strong_reads; + writes = plan.writes; + } + if is_body_modeling { + let disposition = if proposal.is_some() { + "modeled" + } else if finding + .verdict + .as_deref() + .is_some_and(|verdict| verdict.eq_ignore_ascii_case("checkpoint-ready")) + { + "checkpoint-ready" + } else { + "regather-needed" + }; + writes.push(crate::body_modeling_decision_envelope( + store, + &result, + disposition, + &accepted_at, + )?); + } + if !writes.is_empty() { let invariant_owner = if result.proposal_modeling_request_id.is_some() { "Modeling.proposal_frontier" } else if result.claim_repair_request_id.is_some() { @@ -330,14 +369,14 @@ pub fn accept_coordinator_role_finding( } else if result.repo_frontier_modeling_request_id.is_some() { "Modeling.frontier_verdict" } else { - "Modeling.repo_model" + "Modeling.body_projection" }; match commit_mind_mutation( store, &result.decision_context_id, invariant_owner, - plan.strong_reads, - plan.writes, + strong_reads, + writes, &accepted_at, )? { EpiphanyMindCommitOutcome::Committed(_) => {} @@ -349,13 +388,6 @@ pub fn accept_coordinator_role_finding( )); } } - } else if !finding.verdict.as_deref().is_some_and(|verdict| { - verdict.eq_ignore_ascii_case("checkpoint-ready") - || verdict.eq_ignore_ascii_case("regather-needed") - }) { - return Err(anyhow::anyhow!( - "Modeling result requiring RepoModel change has no semantic mutation proposal" - )); } } let commit = mind_state_commit_receipt( diff --git a/epiphany-core/src/current_work.rs b/epiphany-core/src/current_work.rs index 907d9d1a..792602b8 100644 --- a/epiphany-core/src/current_work.rs +++ b/epiphany-core/src/current_work.rs @@ -1,5 +1,5 @@ use anyhow::{Result, anyhow}; -use cultcache_rs::{CultCache, DatabaseEntry}; +use cultcache_rs::{CultCache, CultCacheEnvelope, DatabaseEntry}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use std::path::Path; @@ -108,7 +108,7 @@ impl EpiphanyBodyModelingDecisionReceipt { .any(str::is_empty) || !matches!( self.disposition.as_str(), - "checkpoint-ready" | "regather-needed" + "modeled" | "checkpoint-ready" | "regather-needed" ) || chrono::DateTime::parse_from_rfc3339(&self.decided_at).is_err() { @@ -125,6 +125,65 @@ pub(crate) fn register_current_work_types(cache: &mut CultCache) -> Result<()> { Ok(()) } +pub(crate) fn body_modeling_decision_envelope( + store_path: &Path, + result: &crate::EpiphanyRuntimeRoleWorkerResult, + disposition: &str, + decided_at: &str, +) -> Result { + let body_basis = result + .repository_body_observation_basis + .clone() + .ok_or_else(|| anyhow!("Body Modeling result has no Body observation basis"))?; + let mut cache = crate::runtime_spine_cache(store_path)?; + cache.pull_all_backing_stores()?; + let context = cache + .get::(&result.decision_context_id)? + .ok_or_else(|| anyhow!("Body Modeling result has no decision context"))?; + let reasoning_basis = cache + .get::(&context.basis_id)? + .ok_or_else(|| anyhow!("Body Modeling decision context lost its reasoning basis"))?; + context.validate(&reasoning_basis)?; + let crate::EpiphanyReasoningProjection::RolePass(role_projection) = + reasoning_basis.projection()? + else { + return Err(anyhow!( + "Body Modeling decision has no sealed role projection" + )); + }; + if role_projection.authority.role_id.to_ascii_lowercase() != "modeling" + || role_projection + .authority + .repository_body_observation_basis + .as_ref() + != Some(&body_basis) + { + return Err(anyhow!( + "Body Modeling decision does not bind its projected organ and Body basis" + )); + } + let work = EpiphanyBodyModelingWorkProjection::derive( + body_basis.runtime_id.clone(), + body_basis.clone(), + crate::reasoning_repo_model_basis(&reasoning_basis)?, + )?; + let receipt = EpiphanyBodyModelingDecisionReceipt { + schema_version: BODY_MODELING_DECISION_RECEIPT_SCHEMA_VERSION.to_string(), + work_id: work.work_id.clone(), + runtime_id: work.runtime_id.clone(), + body_basis, + repo_model_projection_digest: work.repo_model_basis.projection_digest.clone(), + repo_model_source_documents: work.repo_model_basis.source_documents.clone(), + decision_context_id: result.decision_context_id.clone(), + result_id: result.result_id.clone(), + job_id: result.job_id.clone(), + disposition: disposition.to_string(), + decided_at: decided_at.to_string(), + }; + receipt.validate(&work)?; + Ok(cache.prepare_entry(&receipt.work_id, &receipt)?.0) +} + pub fn current_body_modeling_work( store_path: impl AsRef, ) -> Result { @@ -175,6 +234,10 @@ fn resolve_body_modeling_work( #[cfg(test)] mod tests { use super::*; + use epiphany_state_model::{ + EpiphanyMemoryDomain, EpiphanyMemoryLifecycle, EpiphanyMemoryNode, EpiphanyMemoryNodeKind, + EpiphanyMemoryProfile, + }; fn basis(observation_id: &str, generation: u64) -> RepositoryBodyObservationBasis { RepositoryBodyObservationBasis { @@ -267,4 +330,185 @@ mod tests { assert!(resolve_body_modeling_work(current_work, Some(receipt))?.is_none()); Ok(()) } + + #[test] + fn body_decision_is_derived_from_the_sealed_role_projection() -> Result<()> { + let temp = tempfile::tempdir()?; + let store = temp.path().join("body-decision.cc"); + crate::initialize_runtime_spine( + &store, + crate::RuntimeSpineInitOptions { + runtime_id: "runtime".into(), + display_name: "Body decision".into(), + created_at: "2026-08-17T00:00:00Z".into(), + }, + )?; + crate::runtime_spine::tests::bind_test_runtime_swarm(&store, "swarm")?; + crate::runtime_spine::tests::bind_test_repository_body(&store, "workspace")?; + let body = crate::observe_runtime_repository_body_basis(&store)?; + let domain = EpiphanyMemoryDomain { + id: "body-domain".into(), + profile: EpiphanyMemoryProfile::RepoArchitecture, + title: "Body".into(), + lifecycle: EpiphanyMemoryLifecycle::Accepted, + ..Default::default() + }; + let node = EpiphanyMemoryNode { + id: "body-node".into(), + domain_id: domain.id.clone(), + profile: EpiphanyMemoryProfile::RepoArchitecture, + kind: EpiphanyMemoryNodeKind::Module, + title: "Body node".into(), + claim: "The Body is typed".into(), + question: "What does the Body contain?".into(), + action_implication: "Model it".into(), + source_hashes: vec!["anchor:missing".into()], + lifecycle: EpiphanyMemoryLifecycle::Accepted, + ..Default::default() + }; + crate::initialize_keyed_repo_model( + &store, + &crate::EpiphanyRepoModelSeed::new( + "body-seed", + "body-graph", + "swarm", + "workspace", + body.body_binding_sha256.clone(), + crate::EpiphanyRepoModelSeedDocuments { + domains: vec![domain], + nodes: vec![node], + edges: Vec::new(), + summaries: Vec::new(), + frontier: Vec::new(), + lifecycle_receipts: Vec::new(), + }, + )?, + "2026-08-17T00:00:01Z", + )?; + let document = + crate::EpiphanyWorkerLaunchDocument::Role(crate::EpiphanyRoleWorkerLaunchDocument { + thread_id: "creation-thread".into(), + role_id: "modeling".into(), + state_revision: 99, + objective: Some("obsolete aggregate objective".into()), + dynamic_prompt_context: None, + repository_body_observation_basis: Some(body.clone()), + proposal_modeling_context: None, + claim_repair_context: None, + frontier_planning_context: None, + frontier_research_context: None, + frontier_plan_mind_context: None, + imagination_consideration_context: None, + admitted_model_direction_consideration_context: None, + active_subgoal_id: None, + active_subgoals: Vec::new(), + active_graph_node_ids: Vec::new(), + investigation_checkpoint: None, + scratch: None, + invariants: Vec::new(), + graphs: None, + recent_evidence: Vec::new(), + recent_observations: Vec::new(), + graph_frontier: None, + graph_checkpoint: None, + planning: None, + churn: None, + }); + let launch = crate::EpiphanyRuntimeWorkerLaunchRequest { + schema_version: crate::RUNTIME_WORKER_LAUNCH_REQUEST_SCHEMA_VERSION.into(), + job_id: "body-job".into(), + binding_id: crate::EPIPHANY_MODELING_ROLE_BINDING_ID.into(), + role: crate::EPIPHANY_MODELING_OWNER_ROLE.into(), + authority_scope: "epiphany.role.modeling".into(), + instruction: "Model the Body".into(), + output_contract_id: crate::ROLE_WORKER_OUTPUT_CONTRACT_ID.into(), + document_kind: "role".into(), + launch_document_msgpack: rmp_serde::to_vec_named(&document)?, + metadata: Default::default(), + organ_launch_contract: crate::default_launch_organ_contract( + "epiphany.role.modeling", + "role", + crate::ROLE_WORKER_OUTPUT_CONTRACT_ID, + ), + proposal_modeling_request_id: None, + claim_repair_request_id: None, + frontier_planning_request_id: None, + frontier_plan_mind_request_id: None, + imagination_consideration_request_id: None, + admitted_model_direction_consideration_request_id: None, + repo_frontier_modeling_request_id: None, + repo_frontier_research_request_id: None, + repo_frontier_verdict_modeling_authority_msgpack: None, + }; + let mut cache = crate::runtime_spine_cache(&store)?; + cache.put(&launch.job_id, &launch)?; + let reasoning_basis = crate::worker_reasoning_basis(&store, &launch)?; + crate::put_reasoning_basis(&store, &reasoning_basis)?; + let mut native = epiphany_model_adapter::EpiphanyModelRequest::new( + "body-request", + "body-conversation", + "openai-codex", + "gpt-test", + "model", + ); + native.reasoning_basis_id = Some(reasoning_basis.basis_id.clone()); + native.source_worker_job_id = Some(launch.job_id.clone()); + let provider = epiphany_openai_adapter::request_from_native(&native); + let context = + crate::EpiphanyDecisionContext::new(&reasoning_basis, native, provider, Vec::new())?; + cache.put(&context.context_id, &context)?; + let result = crate::EpiphanyRuntimeRoleWorkerResult { + schema_version: crate::RUNTIME_ROLE_WORKER_RESULT_SCHEMA_VERSION.into(), + result_id: "body-result".into(), + job_id: launch.job_id.clone(), + role_id: "modeling".into(), + verdict: "checkpoint-ready".into(), + summary: "modeled".into(), + next_safe_move: "admit".into(), + checkpoint_summary: None, + scratch_summary: None, + files_inspected: Vec::new(), + frontier_node_ids: Vec::new(), + evidence_ids: Vec::new(), + artifact_refs: Vec::new(), + open_questions: Vec::new(), + evidence_gaps: Vec::new(), + risks: Vec::new(), + state_patch_msgpack: None, + self_patch_msgpack: None, + item_error: None, + metadata: Default::default(), + repo_model_mutation_proposal_msgpack: None, + verification_request_id: None, + frontier_route_id: None, + repo_frontier_modeling_request_id: None, + proposal_modeling_request_id: None, + claim_repair_request_id: None, + frontier_planning_request_id: None, + frontier_plan_candidate_msgpack: None, + frontier_plan_mind_request_id: None, + frontier_plan_mind_decision_msgpack: None, + repository_body_observation_basis: Some(body.clone()), + imagination_consideration_request_id: None, + imagination_consideration_candidate_msgpack: None, + admitted_model_direction_consideration_request_id: None, + admitted_model_direction_consideration_result_msgpack: None, + decision_context_id: context.context_id, + }; + let envelope = body_modeling_decision_envelope( + &store, + &result, + "checkpoint-ready", + "2026-08-17T00:00:02Z", + )?; + crate::mind_documents::validate_mind_write_envelope(&envelope)?; + let receipt: EpiphanyBodyModelingDecisionReceipt = + rmp_serde::from_slice(&envelope.payload)?; + assert_eq!(receipt.body_basis, body); + assert_eq!( + receipt.repo_model_projection_digest, + crate::assemble_repo_model_view(&store)?.projection_digest + ); + Ok(()) + } } diff --git a/epiphany-core/src/mind_documents.rs b/epiphany-core/src/mind_documents.rs index 6eec6a16..e435b4cd 100644 --- a/epiphany-core/src/mind_documents.rs +++ b/epiphany-core/src/mind_documents.rs @@ -145,6 +145,8 @@ pub struct EpiphanyMindObjectiveDraftDocument { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct EpiphanyMindView { + pub schema_epoch: String, + pub runtime_id: String, pub projection_digest: String, pub source_documents: Vec, pub objective: Option, @@ -363,11 +365,22 @@ pub fn assemble_mind_view(store_path: impl AsRef) -> Result None, }; let mut source_documents = canonical_mind_versions(&cache.snapshot_envelopes())?; + if let Some(repo_model) = &repo_model { + source_documents.extend(repo_model.source_documents.clone()); + } source_documents.sort_by(|left, right| { (&left.document_type, &left.document_key).cmp(&(&right.document_type, &right.document_key)) }); - let projection_digest = digest_versions(&source_documents)?; + if source_documents + .windows(2) + .any(|pair| pair[0].identity() == pair[1].identity()) + { + return Err(anyhow!("Mind view repeats a canonical source document")); + } + let projection_digest = mind_view_digest(&source_documents)?; Ok(EpiphanyMindView { + schema_epoch: identity.schema_epoch, + runtime_id: identity.runtime_id, projection_digest, source_documents, objective, @@ -401,18 +414,39 @@ fn canonical_mind_versions( ) -> Result> { envelopes .iter() - .filter(|envelope| envelope.r#type.starts_with("epiphany.mind.")) + .filter(|envelope| { + matches!( + envelope.r#type.as_str(), + EpiphanyMindIdentity::TYPE + | EpiphanyMindObjectiveDocument::TYPE + | EpiphanyMindFocusDocument::TYPE + | EpiphanyMindModeDocument::TYPE + | EpiphanyMindSubgoalDocument::TYPE + | EpiphanyMindInvariantDocument::TYPE + | EpiphanyMindObservationDocument::TYPE + | EpiphanyMindEvidenceDocument::TYPE + | EpiphanyMindInvestigationCheckpointDocument::TYPE + | EpiphanyMindPlanningCaptureDocument::TYPE + | EpiphanyMindBacklogItemDocument::TYPE + | EpiphanyMindRoadmapStreamDocument::TYPE + | EpiphanyMindObjectiveDraftDocument::TYPE + ) + }) .map(|envelope| EpiphanyMindDocumentVersion::from_envelope("epiphany-mind", envelope)) .collect() } -fn digest_versions(versions: &[EpiphanyMindDocumentVersion]) -> Result { +pub(crate) fn mind_view_digest(versions: &[EpiphanyMindDocumentVersion]) -> Result { let mut digest = Sha256::new(); digest.update(b"epiphany.mind_view.v1\0"); digest.update(rmp_serde::to_vec_named(versions)?); Ok(format!("sha256:{:x}", digest.finalize())) } +pub fn epiphany_mind_projection_digest(versions: &[EpiphanyMindDocumentVersion]) -> Result { + mind_view_digest(versions) +} + #[cfg(test)] mod tests { use super::*; diff --git a/epiphany-core/src/reasoning_context.rs b/epiphany-core/src/reasoning_context.rs index 86578d87..9555a5e3 100644 --- a/epiphany-core/src/reasoning_context.rs +++ b/epiphany-core/src/reasoning_context.rs @@ -21,7 +21,122 @@ pub const REASONING_BASIS_SCHEMA_VERSION: &str = "epiphany.reasoning_basis.v1"; pub const DECISION_CONTEXT_SCHEMA_VERSION: &str = "epiphany.decision_context.v1"; pub const MIND_COMMIT_RECEIPT_SCHEMA_VERSION: &str = "epiphany.mind_commit_receipt.v1"; pub const WORKER_REASONING_PROJECTION_POLICY: &str = - "epiphany.reasoning_projection.worker_launch.v1"; + "epiphany.reasoning_projection.worker_launch.v2"; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct EpiphanyRolePassAuthorityProjection { + pub creation_thread_id: String, + pub role_id: String, + pub dynamic_prompt_context: Option, + pub repository_body_observation_basis: Option, + pub proposal_modeling_context: Option, + pub claim_repair_context: Option, + pub frontier_planning_context: Option, + pub frontier_research_context: Option, + pub frontier_plan_mind_context: Option, + pub imagination_consideration_context: Option, + pub admitted_model_direction_consideration_context: + Option, +} + +impl From for EpiphanyRolePassAuthorityProjection { + fn from(value: EpiphanyRoleWorkerLaunchDocument) -> Self { + Self { + creation_thread_id: value.thread_id, + role_id: value.role_id, + dynamic_prompt_context: value.dynamic_prompt_context, + repository_body_observation_basis: value.repository_body_observation_basis, + proposal_modeling_context: value.proposal_modeling_context, + claim_repair_context: value.claim_repair_context, + frontier_planning_context: value.frontier_planning_context, + frontier_research_context: value.frontier_research_context, + frontier_plan_mind_context: value.frontier_plan_mind_context, + imagination_consideration_context: value.imagination_consideration_context, + admitted_model_direction_consideration_context: value + .admitted_model_direction_consideration_context, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct EpiphanyRepoModelPromptProjection { + pub identity: crate::EpiphanyRepoModelIdentityDocument, + pub projection_digest: String, + pub domains: Vec, + pub nodes: Vec, + pub edges: Vec, + pub summaries: Vec, + pub frontier: Vec, + pub lifecycle_receipts: Vec, + pub claim_obligations: Vec, + pub surface_offers: Vec, + pub dependency_claims: Vec, + pub dependency_verifications: Vec, + pub dependency_impacts: Vec, +} + +impl From for EpiphanyRepoModelPromptProjection { + fn from(value: crate::EpiphanyRepoModelView) -> Self { + Self { + identity: value.identity, + projection_digest: value.projection_digest, + domains: value.domains, + nodes: value.nodes, + edges: value.edges, + summaries: value.summaries, + frontier: value.frontier, + lifecycle_receipts: value.lifecycle_receipts, + claim_obligations: value.claim_obligations, + surface_offers: value.surface_offers, + dependency_claims: value.dependency_claims, + dependency_verifications: value.dependency_verifications, + dependency_impacts: value.dependency_impacts, + } + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct EpiphanyMindPromptProjection { + pub schema_epoch: String, + pub runtime_id: String, + pub projection_digest: String, + pub objective: Option, + pub active_subgoal_id: Option, + pub subgoals: Vec, + pub invariants: Vec, + pub observations: Vec, + pub evidence: Vec, + pub investigation_checkpoint: Option, + pub mode: Option, + pub planning: epiphany_state_model::EpiphanyPlanningState, + pub repo_model: Option, +} + +impl From for EpiphanyMindPromptProjection { + fn from(value: crate::EpiphanyMindView) -> Self { + Self { + schema_epoch: value.schema_epoch, + runtime_id: value.runtime_id, + projection_digest: value.projection_digest, + objective: value.objective, + active_subgoal_id: value.active_subgoal_id, + subgoals: value.subgoals, + invariants: value.invariants, + observations: value.observations, + evidence: value.evidence, + investigation_checkpoint: value.investigation_checkpoint, + mode: value.mode, + planning: value.planning, + repo_model: value.repo_model.map(Into::into), + } + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct EpiphanyRoleReasoningProjection { + pub authority: EpiphanyRolePassAuthorityProjection, + pub mind: EpiphanyMindPromptProjection, +} #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct EpiphanyMindDocumentVersion { @@ -71,22 +186,13 @@ impl EpiphanyMindDocumentVersion { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum EpiphanyReasoningProjection { - RoleLaunch(EpiphanyRoleWorkerLaunchDocument), + RolePass(EpiphanyRoleReasoningProjection), ReorientLaunch(crate::EpiphanyReorientWorkerLaunchDocument), PersonaProjector(PersonaProjectorInput), PersonaTurn(PersonaTurnInput), PersonaInterpreter(PersonaInterpreterInput), } -impl From for EpiphanyReasoningProjection { - fn from(value: EpiphanyWorkerLaunchDocument) -> Self { - match value { - EpiphanyWorkerLaunchDocument::Role(document) => Self::RoleLaunch(document), - EpiphanyWorkerLaunchDocument::Reorient(document) => Self::ReorientLaunch(document), - } - } -} - #[derive(Clone, Debug, PartialEq, DatabaseEntry)] #[cultcache( type = "epiphany.reasoning_basis.v1", @@ -157,8 +263,21 @@ impl EpiphanyReasoningBasis { "reasoning basis source documents are not canonical" )); } - let _: EpiphanyReasoningProjection = rmp_serde::from_slice(&self.projection_msgpack) - .map_err(|error| anyhow!("reasoning projection is invalid: {error}"))?; + let projection: EpiphanyReasoningProjection = + rmp_serde::from_slice(&self.projection_msgpack) + .map_err(|error| anyhow!("reasoning projection is invalid: {error}"))?; + if let EpiphanyReasoningProjection::RolePass(role) = &projection { + if role.authority.creation_thread_id.trim().is_empty() + || role.authority.role_id.trim().is_empty() + || self.source_documents.is_empty() + || role.mind.projection_digest + != crate::mind_documents::mind_view_digest(&self.source_documents)? + { + return Err(anyhow!( + "role reasoning projection does not bind its exact keyed Mind sources" + )); + } + } if self .predecessor_decision_context_ids .iter() @@ -339,19 +458,79 @@ pub fn worker_reasoning_basis( let launch_envelope = cache .get_envelope::(&launch.job_id)? .ok_or_else(|| anyhow!("worker reasoning basis lost its launch envelope"))?; - let source_documents = vec![EpiphanyMindDocumentVersion::from_envelope( - "epiphany-mind", - &launch_envelope, - )?]; + let launch_document = launch.launch_document()?; + let (source_documents, projection) = match launch_document { + EpiphanyWorkerLaunchDocument::Role(document) => { + let mind = crate::assemble_mind_view(store_path)?; + let source_documents = mind.source_documents.clone(); + ( + source_documents, + EpiphanyReasoningProjection::RolePass(EpiphanyRoleReasoningProjection { + authority: document.into(), + mind: mind.into(), + }), + ) + } + EpiphanyWorkerLaunchDocument::Reorient(document) => ( + vec![EpiphanyMindDocumentVersion::from_envelope( + "epiphany-mind", + &launch_envelope, + )?], + EpiphanyReasoningProjection::ReorientLaunch(document), + ), + }; EpiphanyReasoningBasis::new( &launch.job_id, &launch.role, WORKER_REASONING_PROJECTION_POLICY, source_documents, - launch.launch_document()?.into(), + projection, ) } +pub fn reasoning_repo_model_basis( + basis: &EpiphanyReasoningBasis, +) -> Result { + basis.validate()?; + let source_documents = basis + .source_documents + .iter() + .filter(|source| { + source + .document_type + .starts_with("epiphany.mind.repo_model.") + || matches!( + source.document_type.as_str(), + crate::AtlasSurfaceOffer::TYPE + | crate::AtlasDependencyClaim::TYPE + | crate::AtlasDependencyVerification::TYPE + | crate::AtlasDependencyImpact::TYPE + ) + }) + .cloned() + .collect::>(); + let repo_model_basis = crate::EpiphanyRepoModelBasis { + projection_digest: sha256(&rmp_serde::to_vec_named(&source_documents)?), + source_documents, + }; + repo_model_basis.validate()?; + let EpiphanyReasoningProjection::RolePass(role) = basis.projection()? else { + return Err(anyhow!("RepoModel reasoning basis is not a role pass")); + }; + if role + .mind + .repo_model + .as_ref() + .map(|model| model.projection_digest.as_str()) + != Some(repo_model_basis.projection_digest.as_str()) + { + return Err(anyhow!( + "role reasoning projection RepoModel diverges from its exact sources" + )); + } + Ok(repo_model_basis) +} + pub fn put_reasoning_basis( store_path: &Path, basis: &EpiphanyReasoningBasis, @@ -359,6 +538,23 @@ pub fn put_reasoning_basis( basis.validate()?; let mut cache = runtime_spine_cache(store_path)?; cache.pull_all_backing_stores()?; + if let EpiphanyReasoningProjection::RolePass(projection) = basis.projection()? { + let launch = cache + .get::(&basis.pass_id)? + .ok_or_else(|| anyhow!("role reasoning basis lost its worker launch"))?; + let EpiphanyWorkerLaunchDocument::Role(document) = launch.launch_document()? else { + return Err(anyhow!("role reasoning basis cites a non-role launch")); + }; + let current_mind = crate::assemble_mind_view(store_path)?; + if projection.authority != document.into() + || projection.mind != current_mind.clone().into() + || basis.source_documents != current_mind.source_documents + { + return Err(anyhow!( + "role reasoning projection diverges from its exact launch or keyed Mind sources" + )); + } + } for source in &basis.source_documents { if source.store_id != "epiphany-mind" { return Err(anyhow!( @@ -979,41 +1175,104 @@ mod tests { use epiphany_model_adapter::EpiphanyModelInputItem; use tempfile::tempdir; + fn role_document() -> EpiphanyRoleWorkerLaunchDocument { + EpiphanyRoleWorkerLaunchDocument { + thread_id: "creation-thread".into(), + role_id: "Modeling".into(), + state_revision: 7, + objective: Some("Map the Body".into()), + dynamic_prompt_context: Some("typed projection".into()), + repository_body_observation_basis: None, + proposal_modeling_context: None, + claim_repair_context: None, + frontier_planning_context: None, + frontier_research_context: None, + frontier_plan_mind_context: None, + imagination_consideration_context: None, + admitted_model_direction_consideration_context: None, + active_subgoal_id: None, + active_subgoals: Vec::new(), + active_graph_node_ids: Vec::new(), + investigation_checkpoint: None, + scratch: None, + invariants: Vec::new(), + graphs: None, + recent_evidence: Vec::new(), + recent_observations: Vec::new(), + graph_frontier: None, + graph_checkpoint: None, + planning: None, + churn: None, + } + } + + fn role_launch() -> Result { + let document = EpiphanyWorkerLaunchDocument::Role(role_document()); + Ok(EpiphanyRuntimeWorkerLaunchRequest { + schema_version: crate::RUNTIME_WORKER_LAUNCH_REQUEST_SCHEMA_VERSION.into(), + job_id: "pass-1".into(), + binding_id: crate::EPIPHANY_MODELING_ROLE_BINDING_ID.into(), + role: "Modeling".into(), + authority_scope: "epiphany.role.modeling".into(), + instruction: "Map the Body".into(), + output_contract_id: crate::ROLE_WORKER_OUTPUT_CONTRACT_ID.into(), + document_kind: "role".into(), + launch_document_msgpack: rmp_serde::to_vec_named(&document)?, + metadata: Default::default(), + organ_launch_contract: crate::default_launch_organ_contract( + "epiphany.role.modeling", + "role", + crate::ROLE_WORKER_OUTPUT_CONTRACT_ID, + ), + proposal_modeling_request_id: None, + claim_repair_request_id: None, + frontier_planning_request_id: None, + frontier_plan_mind_request_id: None, + imagination_consideration_request_id: None, + admitted_model_direction_consideration_request_id: None, + repo_frontier_modeling_request_id: None, + repo_frontier_research_request_id: None, + repo_frontier_verdict_modeling_authority_msgpack: None, + }) + } + fn basis() -> Result { - let projection = - EpiphanyReasoningProjection::RoleLaunch(EpiphanyRoleWorkerLaunchDocument { - thread_id: "creation-thread".into(), - role_id: "Modeling".into(), - state_revision: 7, - objective: Some("Map the Body".into()), - dynamic_prompt_context: Some("typed projection".into()), - repository_body_observation_basis: None, - proposal_modeling_context: None, - claim_repair_context: None, - frontier_planning_context: None, - frontier_research_context: None, - frontier_plan_mind_context: None, - imagination_consideration_context: None, - admitted_model_direction_consideration_context: None, + let authority = EpiphanyRolePassAuthorityProjection::from(role_document()); + let payload_msgpack = rmp_serde::to_vec_named(&crate::EpiphanyMindIdentity { + schema_epoch: crate::MIND_SCHEMA_EPOCH.into(), + runtime_id: "test-runtime".into(), + })?; + let source_documents = vec![EpiphanyMindDocumentVersion { + store_id: "epiphany-mind".into(), + document_type: "epiphany.mind.identity.v1".into(), + document_key: crate::MIND_SCHEMA_EPOCH.into(), + schema_id: None, + payload_sha256: sha256(&payload_msgpack), + payload_msgpack, + }]; + let projection = EpiphanyReasoningProjection::RolePass(EpiphanyRoleReasoningProjection { + authority, + mind: EpiphanyMindPromptProjection { + schema_epoch: crate::MIND_SCHEMA_EPOCH.into(), + runtime_id: "test-runtime".into(), + projection_digest: crate::epiphany_mind_projection_digest(&source_documents)?, + objective: None, active_subgoal_id: None, - active_subgoals: Vec::new(), - active_graph_node_ids: Vec::new(), - investigation_checkpoint: None, - scratch: None, + subgoals: Vec::new(), invariants: Vec::new(), - graphs: None, - recent_evidence: Vec::new(), - recent_observations: Vec::new(), - graph_frontier: None, - graph_checkpoint: None, - planning: None, - churn: None, - }); + observations: Vec::new(), + evidence: Vec::new(), + investigation_checkpoint: None, + mode: None, + planning: Default::default(), + repo_model: None, + }, + }); EpiphanyReasoningBasis::new( "pass-1", "Modeling", WORKER_REASONING_PROJECTION_POLICY, - Vec::new(), + source_documents, projection, ) } @@ -1066,6 +1325,44 @@ mod tests { Ok(()) } + #[test] + fn role_basis_refuses_projection_not_derived_from_its_mind_sources() -> Result<()> { + let temp = tempdir()?; + let store = temp.path().join("mind.cc"); + initialize_runtime_spine( + &store, + RuntimeSpineInitOptions { + runtime_id: "mind-projection-test".into(), + display_name: "Mind projection test".into(), + created_at: "2026-08-17T00:00:00Z".into(), + }, + )?; + let launch = role_launch()?; + let mut cache = runtime_spine_cache(&store)?; + cache.put(&launch.job_id, &launch)?; + let canonical = worker_reasoning_basis(&store, &launch)?; + let EpiphanyReasoningProjection::RolePass(mut forged_projection) = + canonical.projection()? + else { + unreachable!(); + }; + forged_projection.mind.objective = Some("invented aggregate objective".into()); + let forged = EpiphanyReasoningBasis::new( + &canonical.pass_id, + &canonical.organ_id, + &canonical.projection_policy_id, + canonical.source_documents.clone(), + EpiphanyReasoningProjection::RolePass(forged_projection), + )?; + let before = SingleFileMessagePackBackingStore::new(&store).pull_all()?; + assert!(put_reasoning_basis(&store, &forged).is_err()); + assert_eq!( + SingleFileMessagePackBackingStore::new(&store).pull_all()?, + before + ); + Ok(()) + } + #[test] fn disjoint_mind_mutations_merge_and_same_identity_conflicts() -> Result<()> { let temp = tempdir()?; @@ -1078,7 +1375,11 @@ mod tests { created_at: "2026-08-14T00:00:00Z".into(), }, )?; - let basis = put_reasoning_basis(&store, &basis()?)?; + let launch = role_launch()?; + let mut cache = runtime_spine_cache(&store)?; + cache.put(&launch.job_id, &launch)?; + let basis = worker_reasoning_basis(&store, &launch)?; + let basis = put_reasoning_basis(&store, &basis)?; let (native, provider) = requests(&basis); crate::open_runtime_model_execution( &store, diff --git a/epiphany-openai-runtime/src/bin/epiphany-openai-runtime.rs b/epiphany-openai-runtime/src/bin/epiphany-openai-runtime.rs index d196d644..2b7f59b4 100644 --- a/epiphany-openai-runtime/src/bin/epiphany-openai-runtime.rs +++ b/epiphany-openai-runtime/src/bin/epiphany-openai-runtime.rs @@ -943,7 +943,7 @@ async fn run_worker_launch_with_tool_continuation( &launch_request, &options.provider, &options.model, - &basis.basis_id, + &basis, )?; let requested_public_source_intents = if launch_request.repo_frontier_research_request_id.is_some() { diff --git a/epiphany-openai-runtime/src/lib.rs b/epiphany-openai-runtime/src/lib.rs index c991fadc..0d8d055a 100644 --- a/epiphany-openai-runtime/src/lib.rs +++ b/epiphany-openai-runtime/src/lib.rs @@ -348,7 +348,7 @@ where &launch_request, &options.provider, &options.model, - &basis.basis_id, + &basis, )?; let openai_options = EpiphanyOpenAiRuntimeOptions { store_path: options.store_path.clone(), @@ -615,20 +615,40 @@ pub fn build_worker_model_request( launch_request: &EpiphanyRuntimeWorkerLaunchRequest, provider: &str, model: &str, - reasoning_basis_id: &str, + reasoning_basis: &epiphany_core::EpiphanyReasoningBasis, ) -> Result { - if reasoning_basis_id.trim().is_empty() { - return Err(anyhow!("worker model request requires a reasoning basis")); + reasoning_basis.validate()?; + if reasoning_basis.pass_id != launch_request.job_id { + return Err(anyhow!( + "worker model request reasoning basis belongs to another pass" + )); } let launch_document = launch_request.launch_document()?; + let reasoning_projection = reasoning_basis.projection()?; + match (&launch_document, &reasoning_projection) { + ( + EpiphanyWorkerLaunchDocument::Role(document), + epiphany_core::EpiphanyReasoningProjection::RolePass(projection), + ) if projection.authority + == epiphany_core::EpiphanyRolePassAuthorityProjection::from(document.clone()) => {} + ( + EpiphanyWorkerLaunchDocument::Reorient(document), + epiphany_core::EpiphanyReasoningProjection::ReorientLaunch(projection), + ) if projection == document => {} + _ => { + return Err(anyhow!( + "worker model request reasoning projection does not match its launch authority" + )); + } + } let output_schema_json = worker_output_schema_json(launch_request, &launch_document)?; let request_id = format!( "worker-{}-{}", sanitize_request_id(&launch_request.job_id), chrono::Utc::now().timestamp_millis() ); - let launch_document_text = serde_json::to_string_pretty(&launch_document) - .context("failed to render worker launch document for model input")?; + let reasoning_projection_text = serde_json::to_string_pretty(&reasoning_projection) + .context("failed to render sealed worker reasoning projection for model input")?; let mut instructions = worker_instructions(launch_request, &launch_document); if launch_request.binding_id == epiphany_core::EPIPHANY_VERIFICATION_ROLE_BINDING_ID { instructions.push_str("\n\nTool mandate: before returning `needs-evidence` because source files, artifact directories, command artifacts, commit diffs, Hands receipt bodies, or resident grant lifecycle are not inspectable, call the governed read-only tools available on this request. Use `mcp__epiphany_source__read_file` for cited source/artifact files, `mcp__epiphany_source__directory_inventory` for bounded workspace directory counts and bytes, `mcp__epiphany_source__git_show` for commit diffs, `mcp__epiphany_source__read_hands_receipt` for Hands patch/command/commit receipts, and `mcp__epiphany_state__resident_grant_lifecycle` for exact or bounded recent grant-owned lifecycle state. Directory totals are authoritative only when the tool reports `complete=true`. Grant launchability is authoritative only from the typed state projection, never artifact names or acknowledgement presence. If a tool fails, cite that failed tool result and the exact remaining blocker."); @@ -646,7 +666,7 @@ pub fn build_worker_model_request( ); request.input.push(EpiphanyModelInputItem::UserText { text: format!( - "Execute this Epiphany worker launch document.\n\n```json\n{launch_document_text}\n```" + "Execute this sealed Epiphany reasoning projection.\n\n```json\n{reasoning_projection_text}\n```" ), }); request.reasoning_effort = Some("low".to_string()); @@ -654,7 +674,7 @@ pub fn build_worker_model_request( request.output_contract_id = Some(launch_request.output_contract_id.clone()); request.output_schema_json = Some(output_schema_json); request.source_worker_job_id = Some(launch_request.job_id.clone()); - request.reasoning_basis_id = Some(reasoning_basis_id.to_string()); + request.reasoning_basis_id = Some(reasoning_basis.basis_id.clone()); if matches!( launch_request.binding_id.as_str(), epiphany_core::EPIPHANY_RESEARCH_ROLE_BINDING_ID @@ -2509,8 +2529,56 @@ mod tests { use epiphany_core::open_runtime_spine_heartbeat_job; use epiphany_core::runtime_job_snapshot; use epiphany_openai_adapter::EpiphanyOpenAiModelReceipt; + use sha2::{Digest, Sha256}; use tempfile::tempdir; + fn test_reasoning_basis( + launch: &EpiphanyRuntimeWorkerLaunchRequest, + ) -> Result { + let EpiphanyWorkerLaunchDocument::Role(document) = launch.launch_document()? else { + return Err(anyhow!("test fixture requires a role launch")); + }; + let payload_msgpack = rmp_serde::to_vec_named(&epiphany_core::EpiphanyMindIdentity { + schema_epoch: epiphany_core::MIND_SCHEMA_EPOCH.into(), + runtime_id: "test-runtime".into(), + })?; + let source_documents = vec![epiphany_core::EpiphanyMindDocumentVersion { + store_id: "epiphany-mind".into(), + document_type: "epiphany.mind.identity.v1".into(), + document_key: epiphany_core::MIND_SCHEMA_EPOCH.into(), + schema_id: None, + payload_sha256: format!("sha256:{:x}", Sha256::digest(&payload_msgpack)), + payload_msgpack, + }]; + let mind = epiphany_core::EpiphanyMindPromptProjection { + schema_epoch: epiphany_core::MIND_SCHEMA_EPOCH.into(), + runtime_id: "test-runtime".into(), + projection_digest: epiphany_core::epiphany_mind_projection_digest(&source_documents)?, + objective: None, + active_subgoal_id: None, + subgoals: Vec::new(), + invariants: Vec::new(), + observations: Vec::new(), + evidence: Vec::new(), + investigation_checkpoint: None, + mode: None, + planning: Default::default(), + repo_model: None, + }; + epiphany_core::EpiphanyReasoningBasis::new( + &launch.job_id, + &launch.role, + epiphany_core::WORKER_REASONING_PROJECTION_POLICY, + source_documents, + epiphany_core::EpiphanyReasoningProjection::RolePass( + epiphany_core::EpiphanyRoleReasoningProjection { + authority: document.into(), + mind, + }, + ), + ) + } + #[test] fn model_authored_body_basis_has_no_ingress_authority() -> Result<()> { let parsed = parse_assistant_json::( @@ -2937,7 +3005,7 @@ mod tests { thread_id: "thread-1".into(), role_id: "modeling".into(), state_revision: 1, - objective: None, + objective: Some("obsolete aggregate objective must not cross inference".into()), dynamic_prompt_context: None, repository_body_observation_basis: None, proposal_modeling_context: Some(context), @@ -2992,7 +3060,7 @@ mod tests { &launch, DEFAULT_MODEL_PROVIDER, "gpt-5.4", - "reasoning-basis-test", + &test_reasoning_basis(&launch)?, )?; let schema: serde_json::Value = serde_json::from_str( request @@ -3009,6 +3077,10 @@ mod tests { .instructions .contains("Emit only the semantic frontier draft") ); + let rendered_input = serde_json::to_string(&request.input)?; + assert!(rendered_input.contains("sealed Epiphany reasoning projection")); + assert!(!rendered_input.contains("obsolete aggregate objective")); + assert!(!rendered_input.contains("stateRevision")); Ok(()) } @@ -3808,7 +3880,7 @@ mod tests { &launch_request, DEFAULT_MODEL_PROVIDER, "gpt-5.4", - &reasoning_basis.basis_id, + &reasoning_basis, )?; let provider_request = openai_request_from_model_request(&model_request); epiphany_core::open_runtime_model_execution( @@ -4037,7 +4109,7 @@ mod tests { &launch_request, DEFAULT_MODEL_PROVIDER, "gpt-5.4", - "reasoning-basis-test", + &test_reasoning_basis(&launch_request)?, )?; let tool_names = model_request .tools @@ -4075,7 +4147,7 @@ mod tests { &research_launch, DEFAULT_MODEL_PROVIDER, "gpt-5.4", - "reasoning-basis-test", + &test_reasoning_basis(&research_launch)?, )?; assert!( !research_request