From ec1431fff8968b8a30356ef8a1a01e675d848d06 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 18 Aug 2026 04:51:18 +0200 Subject: [PATCH] Remove global thread state model --- epiphany-core/Cargo.toml | 4 - epiphany-core/src/agent_launch.rs | 240 +---- .../src/bin/epiphany-prompt-context-smoke.rs | 183 ---- .../bin/epiphany-public-source-gate-probe.rs | 1 - .../src/coordinator_launch_context.rs | 790 --------------- epiphany-core/src/current_work.rs | 9 - epiphany-core/src/frontier_plan_chain.rs | 123 +++ .../src/heartbeat_state/heartbeat_roles.rs | 2 +- epiphany-core/src/lib.rs | 57 +- epiphany-core/src/memory_graph/mod.rs | 4 - .../src/memory_graph/profiles/mod.rs | 4 - .../src/memory_graph/profiles/repo.rs | 502 ---------- epiphany-core/src/reasoning_context.rs | 1 - epiphany-core/src/runtime_spine.rs | 203 ---- epiphany-core/src/substrate_gate.rs | 84 +- epiphany-core/src/surfaces/freshness.rs | 638 ------------ epiphany-core/src/surfaces/graph_context.rs | 760 -------------- epiphany-core/src/surfaces/mod.rs | 26 - epiphany-core/src/surfaces/worker_launch.rs | 1 - .../src/bin/epiphany-openai-runtime.rs | 39 +- epiphany-openai-runtime/src/lib.rs | 4 - epiphany-state-model/src/lib.rs | 73 -- epiphany-state-model/src/prompt.rs | 938 ------------------ 23 files changed, 139 insertions(+), 4547 deletions(-) delete mode 100644 epiphany-core/src/bin/epiphany-prompt-context-smoke.rs delete mode 100644 epiphany-core/src/coordinator_launch_context.rs create mode 100644 epiphany-core/src/frontier_plan_chain.rs delete mode 100644 epiphany-core/src/memory_graph/profiles/repo.rs delete mode 100644 epiphany-core/src/surfaces/freshness.rs delete mode 100644 epiphany-core/src/surfaces/graph_context.rs delete mode 100644 epiphany-state-model/src/prompt.rs diff --git a/epiphany-core/Cargo.toml b/epiphany-core/Cargo.toml index 66c40cb5..52565f25 100644 --- a/epiphany-core/Cargo.toml +++ b/epiphany-core/Cargo.toml @@ -174,10 +174,6 @@ path = "src/bin/epiphany-persona-reddit.rs" name = "epiphany-prepare-compaction" path = "src/bin/epiphany-prepare-compaction.rs" -[[bin]] -name = "epiphany-prompt-context-smoke" -path = "src/bin/epiphany-prompt-context-smoke.rs" - [[bin]] name = "epiphany-repo" path = "src/bin/epiphany-repo.rs" diff --git a/epiphany-core/src/agent_launch.rs b/epiphany-core/src/agent_launch.rs index a75177b3..89e54fcf 100644 --- a/epiphany-core/src/agent_launch.rs +++ b/epiphany-core/src/agent_launch.rs @@ -4,16 +4,10 @@ use crate::EpiphanyCoordinatorAction as CoreEpiphanyCoordinatorAction; use crate::EpiphanyCoordinatorRoleResultStatus as CoreEpiphanyCoordinatorRoleResultStatus; use crate::EpiphanyCrrcAction as CoreEpiphanyCrrcAction; use crate::EpiphanyCrrcResultStatus as CoreEpiphanyCrrcResultStatus; -use crate::EpiphanyJobLaunchRequest; use crate::EpiphanyPressure; use crate::EpiphanyPressureLevel as CoreEpiphanyPressureLevel; use crate::EpiphanyReorientAction as CoreEpiphanyReorientAction; use crate::EpiphanyRoleResultRoleId; -use crate::EpiphanyRoleWorkerLaunchDocument; -use crate::EpiphanyWorkerLaunchDocument; -use crate::default_launch_organ_contract; -use epiphany_state_model::EpiphanyJobKind as CoreEpiphanyJobKind; -use epiphany_state_model::EpiphanyThreadState; pub const EPIPHANY_IMAGINATION_ROLE_BINDING_ID: &str = "planning-synthesis-worker"; pub const EPIPHANY_IMAGINATION_OWNER_ROLE: &str = "epiphany-imagination"; @@ -1045,181 +1039,7 @@ pub fn epiphany_worker_prompt(body: &str) -> String { } } -pub fn build_epiphany_role_launch_request( - thread_id: &str, - role_id: EpiphanyRoleResultRoleId, - expected_revision: Option, - max_runtime_seconds: Option, - state: &EpiphanyThreadState, -) -> Result { - build_epiphany_role_launch_request_with_dynamic_context( - thread_id, - role_id, - expected_revision, - max_runtime_seconds, - state, - None, - ) -} - -pub fn build_epiphany_role_launch_request_with_dynamic_context( - thread_id: &str, - role_id: EpiphanyRoleResultRoleId, - expected_revision: Option, - max_runtime_seconds: Option, - state: &EpiphanyThreadState, - dynamic_prompt_context: Option, -) -> Result { - let binding_id = epiphany_role_binding_id(role_id)?; - let owner_role = epiphany_role_owner(role_id)?; - let linked_subgoal_ids = epiphany_active_subgoal_ids(Some(state)); - let linked_graph_node_ids = epiphany_active_graph_node_ids(Some(state)); - let (scope, authority_scope, instruction) = match role_id { - EpiphanyRoleResultRoleId::Imagination => ( - "role-scoped planning synthesis", - "epiphany.role.imagination", - build_epiphany_role_launch_instruction(role_id), - ), - EpiphanyRoleResultRoleId::Research => ( - "role-scoped source gathering", - "epiphany.role.research", - build_epiphany_role_launch_instruction(role_id), - ), - EpiphanyRoleResultRoleId::Modeling => ( - "role-scoped modeling/checkpoint maintenance", - "epiphany.role.modeling", - build_epiphany_role_launch_instruction(role_id), - ), - EpiphanyRoleResultRoleId::Verification => ( - "role-scoped verification/review", - "epiphany.role.verification", - build_epiphany_role_launch_instruction(role_id), - ), - EpiphanyRoleResultRoleId::Implementation | EpiphanyRoleResultRoleId::Reorientation => { - return Err(epiphany_role_binding_id(role_id).unwrap_err()); - } - }; - let launch_document = EpiphanyWorkerLaunchDocument::Role(EpiphanyRoleWorkerLaunchDocument { - thread_id: thread_id.to_string(), - role_id: epiphany_role_label(role_id).to_string(), - state_revision: state.revision, - objective: state.objective.clone(), - dynamic_prompt_context, - repository_body_observation_basis: None, - proposal_modeling_context: None, - frontier_verdict_modeling_context: None, - frontier_planning_context: None, - frontier_research_context: None, - frontier_verification_context: None, - frontier_plan_mind_context: None, - imagination_consideration_context: None, - admitted_model_direction_consideration_context: None, - active_subgoal_id: state.active_subgoal_id.clone(), - active_subgoals: state - .subgoals - .iter() - .filter(|subgoal| Some(subgoal.id.as_str()) == state.active_subgoal_id.as_deref()) - .cloned() - .collect(), - active_graph_node_ids: linked_graph_node_ids.clone(), - investigation_checkpoint: state.investigation_checkpoint.clone(), - scratch: state.scratch.clone(), - invariants: state.invariants.clone(), - graphs: Some(state.graphs.clone()), - recent_evidence: state.recent_evidence.iter().take(8).cloned().collect(), - recent_observations: state.observations.iter().take(8).cloned().collect(), - graph_frontier: state.graph_frontier.clone(), - graph_checkpoint: state.graph_checkpoint.clone(), - planning: Some(state.planning.clone()), - churn: state.churn.clone(), - }); - let output_contract_id = launch_document.output_contract_id().to_string(); - let organ_launch_contract = default_launch_organ_contract( - authority_scope, - launch_document.document_kind(), - &output_contract_id, - ); - - Ok(EpiphanyJobLaunchRequest { - expected_revision, - binding_id: binding_id.to_string(), - kind: CoreEpiphanyJobKind::Specialist, - scope: scope.to_string(), - owner_role: owner_role.to_string(), - authority_scope: authority_scope.to_string(), - linked_subgoal_ids, - linked_graph_node_ids: epiphany_active_graph_node_ids(Some(state)), - instruction, - launch_document, - output_contract_id, - organ_launch_contract, - max_runtime_seconds, - proposal_modeling_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_verification_request_id: None, - }) -} - -pub fn build_epiphany_imagination_consideration_launch_request( - thread_id: &str, - expected_revision: Option, - max_runtime_seconds: Option, - state: &EpiphanyThreadState, - request_id: String, -) -> Result { - let mut launch = build_epiphany_role_launch_request_with_dynamic_context( - thread_id, - EpiphanyRoleResultRoleId::Imagination, - expected_revision, - max_runtime_seconds, - state, - None, - )?; - launch.scope = "role-scoped organizational feedback consideration".into(); - launch.authority_scope = "epiphany.imagination.consideration.proposal_only".into(); - launch.instruction = "Await coordinator-owned typed consideration context.".into(); - launch.organ_launch_contract = default_launch_organ_contract( - &launch.authority_scope, - launch.launch_document.document_kind(), - &launch.output_contract_id, - ); - launch.imagination_consideration_request_id = Some(request_id); - Ok(launch) -} - -pub fn build_epiphany_admitted_model_direction_consideration_launch_request( - thread_id: &str, - expected_revision: Option, - max_runtime_seconds: Option, - state: &EpiphanyThreadState, - request_id: String, -) -> Result { - let mut launch = build_epiphany_role_launch_request_with_dynamic_context( - thread_id, - EpiphanyRoleResultRoleId::Imagination, - expected_revision, - max_runtime_seconds, - state, - None, - )?; - launch.scope = "role-scoped admitted Modeling-map direction consideration".into(); - launch.authority_scope = - "epiphany.imagination.admitted_model_direction_consideration.proposal_only".into(); - launch.instruction = "Await coordinator-owned typed admitted model context.".into(); - launch.organ_launch_contract = default_launch_organ_contract( - &launch.authority_scope, - launch.launch_document.document_kind(), - &launch.output_contract_id, - ); - launch.admitted_model_direction_consideration_request_id = Some(request_id); - Ok(launch) -} - +#[cfg(test)] fn build_epiphany_role_launch_instruction(role_id: EpiphanyRoleResultRoleId) -> String { let prompts = &epiphany_specialist_prompt_config().roles; let body = match role_id { @@ -1243,50 +1063,6 @@ pub fn build_epiphany_reorient_launch_instruction(action: CoreEpiphanyReorientAc epiphany_worker_prompt(body) } -pub fn build_epiphany_job_launch_request( - expected_revision: Option, - binding_id: String, - kind: CoreEpiphanyJobKind, - scope: String, - owner_role: String, - authority_scope: String, - linked_subgoal_ids: Vec, - linked_graph_node_ids: Vec, - instruction: String, - launch_document: EpiphanyWorkerLaunchDocument, - output_contract_id: String, - max_runtime_seconds: Option, -) -> EpiphanyJobLaunchRequest { - let organ_launch_contract = default_launch_organ_contract( - &authority_scope, - launch_document.document_kind(), - &output_contract_id, - ); - EpiphanyJobLaunchRequest { - expected_revision, - binding_id, - kind, - scope, - owner_role, - authority_scope, - linked_subgoal_ids, - linked_graph_node_ids, - instruction, - launch_document, - output_contract_id, - organ_launch_contract, - max_runtime_seconds, - proposal_modeling_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_verification_request_id: None, - } -} - pub fn render_epiphany_coordinator_note( crrc_action: CoreEpiphanyCrrcAction, pressure_level: CoreEpiphanyPressureLevel, @@ -1373,20 +1149,6 @@ fn extend_unique_strings(target: &mut Vec, values: impl IntoIterator) -> Vec { - state - .and_then(|state| state.active_subgoal_id.clone()) - .map(|id| vec![id]) - .unwrap_or_default() -} - -fn epiphany_active_graph_node_ids(state: Option<&EpiphanyThreadState>) -> Vec { - state - .and_then(|state| state.graph_frontier.as_ref()) - .map(|frontier| frontier.active_node_ids.clone()) - .unwrap_or_default() -} - #[cfg(test)] mod tests { use super::*; diff --git a/epiphany-core/src/bin/epiphany-prompt-context-smoke.rs b/epiphany-core/src/bin/epiphany-prompt-context-smoke.rs deleted file mode 100644 index 47ea67ae..00000000 --- a/epiphany-core/src/bin/epiphany-prompt-context-smoke.rs +++ /dev/null @@ -1,183 +0,0 @@ -use anyhow::Result; -use epiphany_core::EpiphanyMemoryContextQuery; -use epiphany_core::EpiphanyMemoryDomain; -use epiphany_core::EpiphanyMemoryFreshnessStatus; -use epiphany_core::EpiphanyMemoryGraphSnapshot; -use epiphany_core::EpiphanyMemoryLifecycle; -use epiphany_core::EpiphanyMemoryNode; -use epiphany_core::EpiphanyMemoryNodeKind; -use epiphany_core::EpiphanyMemoryProfile; -use epiphany_core::EpiphanyMemorySummary; -use epiphany_core::EpiphanyPromptContextInput; -use epiphany_core::EpiphanyRoleResultRoleId; -use epiphany_core::EpiphanyThreadState; -use epiphany_core::EpiphanyWorkerLaunchDocument; -use epiphany_core::build_epiphany_role_launch_request_with_dynamic_context; -use epiphany_core::memory_graph_domain_id; -use epiphany_core::memory_graph_node_id; -use epiphany_core::plan_memory_graph_context_cut; -use epiphany_core::query_epiphany_local_verse_context; -use epiphany_core::render_epiphany_prompt_context; -use epiphany_core::seed_epiphany_local_verse_context; -use serde_json::json; -use std::path::PathBuf; - -fn main() -> Result<()> { - if std::env::args().nth(1).is_some() { - anyhow::bail!( - "prompt-context smoke accepts no arguments and writes only beneath .epiphany-smoke" - ); - } - let store = PathBuf::from(".epiphany-smoke") - .join("cultmesh") - .join("epiphany-prompt-context.ccmp"); - if let Some(parent) = store.parent() { - std::fs::create_dir_all(parent)?; - } - - seed_epiphany_local_verse_context( - &store, - "epiphany-prompt-context-smoke", - "2026-06-02T00:00:00Z", - "repo:C:/fixture/Epiphany", - )?; - let local_verse = query_epiphany_local_verse_context(&store, "epiphany-prompt-context-smoke")?; - let memory_context = plan_memory_graph_context_cut( - &smoke_snapshot(), - &EpiphanyMemoryContextQuery { - id: "prompt-context-smoke-query".to_string(), - profile: Some(EpiphanyMemoryProfile::RepoArchitecture), - text: Some("Verse prompt assembly".to_string()), - budget: Some(4), - ..Default::default() - }, - ); - let rendered = render_epiphany_prompt_context(&EpiphanyPromptContextInput { - focus: "Build a bounded worker prompt from local Verse and memory context.".to_string(), - local_verse, - memory_context, - }); - - let checks = [ - rendered.contains(""), - rendered.contains("Odin"), - rendered.contains("Yggdrasil"), - rendered.contains("Bifrost"), - rendered.contains("gamecult-local"), - rendered.contains("Verse-backed prompt assembly"), - rendered.contains("Mind reviews durable state effects"), - !rendered.contains("raw private worker transcript should never render"), - ]; - if checks.iter().any(|passed| !passed) { - anyhow::bail!("prompt context smoke failed rendered packet checks:\n{rendered}"); - } - - let launch_request = build_epiphany_role_launch_request_with_dynamic_context( - "thread-prompt-context-smoke", - EpiphanyRoleResultRoleId::Modeling, - Some(1), - Some(60), - &EpiphanyThreadState { - revision: 1, - objective: Some("Launch a worker with bounded dynamic context.".to_string()), - ..Default::default() - }, - Some(rendered.clone()), - ) - .map_err(anyhow::Error::msg)?; - let EpiphanyWorkerLaunchDocument::Role(role_document) = &launch_request.launch_document else { - anyhow::bail!("prompt context smoke expected a role launch document"); - }; - if role_document.dynamic_prompt_context.as_deref() != Some(rendered.as_str()) { - anyhow::bail!("prompt context smoke failed to preserve dynamic launch context"); - } - - println!( - "{}", - serde_json::to_string_pretty(&json!({ - "status": "ok", - "store": store, - "renderedBytes": rendered.len(), - "containsVerseContext": true, - "containsMemoryContext": true, - "launchDocumentCarriesContext": true, - "omitsUnselectedPrivateText": true, - }))? - ); - Ok(()) -} - -fn smoke_snapshot() -> EpiphanyMemoryGraphSnapshot { - let domain_id = memory_graph_domain_id( - EpiphanyMemoryProfile::RepoArchitecture, - "crate", - "epiphany-core", - ); - let selected_node = memory_graph_node_id( - &domain_id, - "module", - "epiphany-core/src/prompt_context.rs", - Some("prompt_context"), - ); - let unselected_node = memory_graph_node_id( - &domain_id, - "module", - "sealed/raw_worker_transcript.rs", - Some("sealed_private_text"), - ); - - EpiphanyMemoryGraphSnapshot { - graph_id: "prompt-context-smoke".to_string(), - domains: vec![EpiphanyMemoryDomain { - id: domain_id.clone(), - profile: EpiphanyMemoryProfile::RepoArchitecture, - title: "epiphany-core".to_string(), - lifecycle: EpiphanyMemoryLifecycle::Accepted, - ..Default::default() - }], - nodes: vec![ - EpiphanyMemoryNode { - id: selected_node.clone(), - domain_id: domain_id.clone(), - profile: EpiphanyMemoryProfile::RepoArchitecture, - kind: EpiphanyMemoryNodeKind::Module, - title: "prompt_context".to_string(), - claim: "Verse-backed prompt assembly injects bounded local Verse and semantic memory packets.".to_string(), - action_implication: - "Use compact dynamic context before launching broad swarm turns." - .to_string(), - lifecycle: EpiphanyMemoryLifecycle::Accepted, - confidence: 90, - salience: 90, - ..Default::default() - }, - EpiphanyMemoryNode { - id: unselected_node, - domain_id: domain_id.clone(), - profile: EpiphanyMemoryProfile::RepoArchitecture, - kind: EpiphanyMemoryNodeKind::Module, - title: "sealed_private_text".to_string(), - claim: "raw private worker transcript should never render".to_string(), - action_implication: "This text must stay absent from the smoke output.".to_string(), - lifecycle: EpiphanyMemoryLifecycle::Accepted, - confidence: 90, - salience: 10, - ..Default::default() - }, - ], - summaries: vec![EpiphanyMemorySummary { - id: "summary-prompt-context".to_string(), - domain_id, - covers_node_ids: vec![selected_node], - target: "prompt_context".to_string(), - claim: "Verse-backed prompt assembly can ask the local Verse and memory graph for compact context.".to_string(), - action_implication: - "Render this packet into worker prompts, then keep Mind as the durable-state gate." - .to_string(), - freshness: EpiphanyMemoryFreshnessStatus::Ready, - confidence: 95, - ..Default::default() - }], - ..Default::default() - } -} diff --git a/epiphany-core/src/bin/epiphany-public-source-gate-probe.rs b/epiphany-core/src/bin/epiphany-public-source-gate-probe.rs index 2efa5c43..b038d38d 100644 --- a/epiphany-core/src/bin/epiphany-public-source-gate-probe.rs +++ b/epiphany-core/src/bin/epiphany-public-source-gate-probe.rs @@ -81,7 +81,6 @@ fn prepare_authority_family(store: &Path) -> Result PathBuf { - sibling_state_store_path(runtime_store_path, "local-verse.ccmp") -} - -pub fn memory_graph_store_path(runtime_store_path: &Path) -> PathBuf { - sibling_state_store_path(runtime_store_path, "memory-graph.msgpack") -} - -fn sibling_state_store_path(runtime_store_path: &Path, filename: &str) -> PathBuf { - runtime_store_path - .parent() - .unwrap_or_else(|| Path::new(".")) - .join(filename) -} - -pub fn role_launch_context_focus(state: &EpiphanyThreadState, role_label: &str) -> String { - let objective = state - .objective - .as_deref() - .map(str::trim) - .filter(|objective| !objective.is_empty()) - .unwrap_or("Epiphany worker launch"); - format!("Launch `{role_label}` worker for: {objective}") -} - -pub fn reorient_launch_context_focus(state: &EpiphanyThreadState, next_action: &str) -> String { - let objective = state - .objective - .as_deref() - .map(str::trim) - .filter(|objective| !objective.is_empty()) - .unwrap_or("Epiphany reorientation"); - format!("Launch reorientation worker for: {objective}. Next action: {next_action}") -} - -pub fn render_launch_dynamic_prompt_context( - runtime_store_path: &Path, - local_verse_store: &Path, - state: &EpiphanyThreadState, - focus: String, -) -> Result { - render_launch_dynamic_prompt_context_with_snapshot( - runtime_store_path, - local_verse_store, - state, - focus, - ) - .map(|(context, _)| context) -} - -pub fn render_modeling_launch_dynamic_prompt_context( - runtime_store_path: &Path, - local_verse_store: &Path, - state: &EpiphanyThreadState, - focus: String, -) -> Result { - let (context, snapshot) = render_launch_dynamic_prompt_context_with_snapshot( - runtime_store_path, - local_verse_store, - state, - focus, - )?; - Ok(append_modeling_repo_model_shape_snapshot( - context, &snapshot, - )) -} - -fn render_launch_dynamic_prompt_context_with_snapshot( - runtime_store_path: &Path, - local_verse_store: &Path, - state: &EpiphanyThreadState, - focus: String, -) -> Result<(String, EpiphanyRepoModelView), String> { - load_epiphany_cultmesh_status(local_verse_store, EPIPHANY_LOCAL_VERSE_RUNTIME_ID) - .map_err(|error| { - format!( - "failed to inspect local Verse context store {}: {error}", - local_verse_store.display() - ) - })? - .ok_or_else(|| { - format!( - "local Verse is not bootstrapped at {}; initialize it before building worker launch context", - local_verse_store.display() - ) - })?; - let topology = - load_epiphany_cultmesh_cluster_topology(local_verse_store, EPIPHANY_LOCAL_VERSE_RUNTIME_ID) - .map_err(|error| { - format!( - "failed to inspect local Verse topology store {}: {error}", - local_verse_store.display() - ) - })?; - if topology.is_empty() { - return Err(format!( - "local Verse has no persisted cluster topology at {}; initialize it before building worker launch context", - local_verse_store.display() - )); - } - let local_verse = - query_epiphany_local_verse_context(local_verse_store, EPIPHANY_LOCAL_VERSE_RUNTIME_ID) - .map_err(|error| { - format!( - "failed to query local Verse context store {}: {error}", - local_verse_store.display() - ) - })?; - let (memory_context, snapshot) = - launch_memory_context(runtime_store_path, state, focus.as_str()).map_err(|error| { - format!( - "failed to build launch memory context beside {}: {error}", - runtime_store_path.display() - ) - })?; - Ok(( - render_epiphany_prompt_context(&EpiphanyPromptContextInput { - focus, - local_verse, - memory_context, - }), - snapshot, - )) -} - -pub fn validate_frontier_plan_decision_chain( - decision: &crate::RepoFrontierPlanDecisionReceipt, - route: &crate::RepoFrontierRoute, - current_model: &EpiphanyRepoModelView, -) -> Result<(), String> { - validate_current_frontier_route(route, current_model, None)?; - let plan = route - .adopted_plan - .as_ref() - .ok_or_else(|| "Soul frontier plan decision has no adopted plan".to_string())?; - let source_is_complete = match decision.decision_source.as_ref() { - Some(crate::RepoFrontierPlanDecisionSource::MindWorker { result_id, job_id }) => { - !result_id.trim().is_empty() && !job_id.trim().is_empty() - } - Some(crate::RepoFrontierPlanDecisionSource::AuthenticatedOperatorReview { - command_id, - admission_id, - packet_sha256, - source_actor_id, - }) => [command_id, admission_id, packet_sha256, source_actor_id] - .into_iter() - .all(|value| !value.trim().is_empty()), - None => false, - }; - let decision_basis = crate::EpiphanyRepoModelBasis { - projection_digest: decision.model_projection_digest.clone(), - source_documents: decision.model_source_documents.clone(), - }; - decision_basis.validate().map_err(|error| { - format!("Soul frontier plan decision has an invalid keyed basis: {error}") - })?; - if decision.schema_version != crate::REPO_FRONTIER_PLAN_DECISION_RECEIPT_SCHEMA_VERSION - || decision.contract != crate::REPO_FRONTIER_PLAN_DECISION_CONTRACT - || decision.decision != crate::RepoFrontierPlanDecision::Adopt - || decision.legacy_mind_worker_result_id.is_some() - || decision.legacy_mind_worker_job_id.is_some() - || decision.planning_request_id != plan.planning_request_id - || decision.candidate_id != plan.candidate_id - || decision.candidate_sha256 != plan.candidate_sha256 - || decision_basis != current_model.reasoning_basis() - || decision.frontier_item_id != route.frontier_item_id - || decision.frontier_item_hash != route.frontier_item_hash - || !source_is_complete - { - return Err( - "Soul frontier plan decision does not exactly bind its keyed basis, adopted plan, route, and decision source" - .to_string(), - ); - } - Ok(()) -} - -fn validate_current_frontier_route<'a>( - route: &crate::RepoFrontierRoute, - current_model: &'a EpiphanyRepoModelView, - verification_request: Option<&crate::RepoFrontierVerificationRequest>, -) -> Result<&'a crate::RepoFrontierItem, String> { - let route_basis = crate::EpiphanyRepoModelBasis { - projection_digest: route.model_projection_digest.clone(), - source_documents: route.model_source_documents.clone(), - }; - route_basis - .validate() - .map_err(|error| format!("frontier route has an invalid keyed basis: {error}"))?; - if route_basis != current_model.reasoning_basis() { - return Err("frontier route does not bind the current keyed RepoModel view".to_string()); - } - let current_item = current_model - .frontier - .iter() - .find(|item| item.id == route.frontier_item_id) - .ok_or_else(|| { - "routed frontier item disappeared from the current keyed view".to_string() - })?; - let current_item_hash = format!( - "{:x}", - sha2::Sha256::digest( - rmp_serde::to_vec_named(current_item) - .map_err(|error| format!("failed to hash current frontier item: {error}"))? - ) - ); - let expected_source_scope = current_item - .adopted_plan - .as_ref() - .map(|plan| plan.safe_paths.as_slice()) - .unwrap_or(current_item.source_scope.as_slice()); - if route.schema_version != crate::REPO_FRONTIER_ROUTE_SCHEMA_VERSION - || route.contract != crate::REPO_FRONTIER_ROUTE_CONTRACT - || route.next_organ != crate::RepoFrontierNextOrgan::Hands - || route.frontier_item_hash != current_item_hash - || route.migration_body != current_item.migration_body - || route.question != current_item.question - || route.gap != current_item.gap - || route.target_claim_ids != current_item.target_claim_ids - || route.source_scope != expected_source_scope - || route.adopted_plan != current_item.adopted_plan - || current_item.status != crate::RepoFrontierStatus::Active - || current_item.recommended_next_organ != "Hands" - { - return Err( - "frontier route does not exactly bind its current keyed frontier item".to_string(), - ); - } - if let Some(request) = verification_request { - if request.schema_version != crate::REPO_FRONTIER_VERIFICATION_REQUEST_SCHEMA_VERSION - || request.contract != crate::REPO_FRONTIER_VERIFICATION_REQUEST_CONTRACT - || request.route_id != route.route_id - || request.model_projection_digest != route.model_projection_digest - || request.model_source_documents != route.model_source_documents - || request.frontier_item_id != route.frontier_item_id - || request.frontier_item_hash != route.frontier_item_hash - { - return Err( - "Soul verification request does not exactly bind the current keyed route" - .to_string(), - ); - } - } - Ok(current_item) -} - -pub fn append_modeling_repo_model_shape_context( - context: String, - runtime_store_path: &Path, -) -> Result { - let view = assemble_repo_model_view(runtime_store_path) - .map_err(|error| format!("failed to load canonical RepoModel shape: {error}"))?; - Ok(append_modeling_repo_model_shape_snapshot(context, &view)) -} - -fn append_modeling_repo_model_shape_snapshot( - mut context: String, - view: &EpiphanyRepoModelView, -) -> String { - context.push_str("\n\n\n"); - context.push_str(&format!("projectionDigest: {}\n", view.projection_digest)); - context.push_str("existingDomains:\n"); - for domain in &view.domains { - let profile = debug_variant_snake_case(domain.profile); - let lifecycle = debug_variant_snake_case(domain.lifecycle); - context.push_str(&format!( - "- id={} profile={} lifecycle={} title={}\n", - domain.id, profile, lifecycle, domain.title - )); - } - context.push_str("existingClaims:\n"); - for node in &view.nodes { - context.push_str(&format!( - "- id={} domain={} title={}\n", - node.id, node.domain_id, node.title - )); - } - context.push_str("existingFrontier:\n"); - for item in &view.frontier { - let status = debug_variant_snake_case(item.status); - context.push_str(&format!( - "- id={} status={} recommendedNextOrgan={} targetClaims={} sourceScope={}\n", - item.id, - status, - item.recommended_next_organ, - item.target_claim_ids.join(" | "), - item.source_scope.join(" | "), - )); - } - context.push_str("existingSurfaceOffers:\n"); - for offer in &view.surface_offers { - context.push_str(&format!( - "- surfaceId={} label={} contract={} lifecycle={} sourceRefs={}\n", - offer.surface_id, - offer.label, - render_atlas_contract_descriptor(&offer.contract), - render_atlas_offer_lifecycle(&offer.lifecycle), - offer - .body_evidence - .iter() - .map(|source| format!("{}@{}", source.path, source.raw_sha256)) - .collect::>() - .join(" | "), - )); - } - context.push_str("existingDependencyClaims:\n"); - for claim in &view.dependency_claims { - context.push_str(&format!( - "- claimId={} label={} target={} kind={} failureSemantics={} impactScope={} lifecycle={} sourceRefs={}\n", - claim.claim_id, - claim.label, - render_atlas_dependency_target(&claim.target), - debug_variant_snake_case(claim.entanglement_kind), - debug_variant_snake_case(claim.failure_semantics), - render_atlas_impact_scope(&claim.impact_scope), - debug_variant_snake_case(claim.lifecycle), - claim - .body_evidence - .iter() - .map(|source| format!("{}@{}", source.path, source.raw_sha256)) - .collect::>() - .join(" | "), - )); - } - context.push_str("currentDependencyVerifications:\n"); - for verification in &view.dependency_verifications { - context.push_str(&format!( - "- claimId={} verdict={} claimPublication={} offerPublication={}\n", - verification.claim_id, - debug_variant_snake_case(verification.verdict), - verification.claim_publication_id, - verification.offer_publication_id, - )); - } - context.push_str("currentDependencyImpacts:\n"); - for impact in &view.dependency_impacts { - context.push_str(&format!( - "- impactId={} claimId={} criticality={} projection={}\n", - impact.impact_id, - impact.claim_id, - debug_variant_snake_case(impact.criticality), - impact.projection_sha256, - )); - } - context.push_str("RepoModel mutations address semantic document identities directly. New nodes must reference one exact existing domain id or a domain created by the same mutation. Every unresolved frontier item must target at least one exact existing claim id or a claim created by the same mutation. Every frontier source_scope must be non-empty and contain safe relative paths in strict lexicographic ascending order without duplicates. Recommended next organ must use its exact canonical spelling. RepoArchitecture and RepoDataflow nodes/edges may use only observed, proposed, accepted, stale, or retired lifecycle. Atlas create operations do not accept caller-authored offer or claim ids: runtime derives opaque UUIDs from the admitted proposal. Lifecycle operations may name only ids listed above. Exact dependency targets must name a provider repository and surface; unresolved targets remain unresolved. Local-surface impact scopes may name only locally owned surface ids listed above. The projection digest is display and audit identity, never a global stale-write fence.\n"); - context.push_str(""); - context -} - -fn render_atlas_contract_descriptor(contract: &crate::AtlasContractDescriptor) -> String { - match contract { - crate::AtlasContractDescriptor::Semver { - contract_id, - version, - } => format!("semver:{contract_id}@{version}"), - crate::AtlasContractDescriptor::ExactSchema { - contract_id, - schema_id, - } => format!("exact_schema:{contract_id}@{schema_id}"), - crate::AtlasContractDescriptor::ExactDigest { - contract_id, - sha256, - } => format!("exact_digest:{contract_id}@{sha256}"), - } -} - -fn render_atlas_contract_requirement(requirement: &crate::AtlasContractRequirement) -> String { - match requirement { - crate::AtlasContractRequirement::Semver { - contract_id, - requirement, - } => format!("semver:{contract_id}@{requirement}"), - crate::AtlasContractRequirement::ExactSchema { - contract_id, - schema_id, - } => format!("exact_schema:{contract_id}@{schema_id}"), - crate::AtlasContractRequirement::ExactDigest { - contract_id, - sha256, - } => format!("exact_digest:{contract_id}@{sha256}"), - } -} - -fn render_atlas_offer_lifecycle(lifecycle: &crate::AtlasOfferLifecycle) -> String { - match lifecycle { - crate::AtlasOfferLifecycle::Active => "active".to_string(), - crate::AtlasOfferLifecycle::Deprecated { - replacement_surface_id: Some(replacement_surface_id), - } => format!("deprecated:replacement={replacement_surface_id}"), - crate::AtlasOfferLifecycle::Deprecated { - replacement_surface_id: None, - } => "deprecated".to_string(), - crate::AtlasOfferLifecycle::Withdrawn => "withdrawn".to_string(), - } -} - -fn render_atlas_dependency_target(target: &crate::AtlasDependencyTarget) -> String { - match target { - crate::AtlasDependencyTarget::Exact { - provider, - surface_id, - requirement, - } => format!( - "exact:{}#{} requires {}", - provider.repository_uri, - surface_id, - render_atlas_contract_requirement(requirement) - ), - crate::AtlasDependencyTarget::Unresolved { requirement } => format!( - "unresolved:{}", - render_atlas_contract_requirement(requirement) - ), - } -} - -fn render_atlas_impact_scope(scope: &crate::AtlasImpactScope) -> String { - match scope { - crate::AtlasImpactScope::WholeRepository => "whole_repository".to_string(), - crate::AtlasImpactScope::LocalSurfaces { surface_ids } => format!( - "local_surfaces:{}", - surface_ids - .iter() - .map(ToString::to_string) - .collect::>() - .join("|") - ), - } -} - -fn debug_variant_snake_case(value: impl std::fmt::Debug) -> String { - let name = format!("{value:?}"); - let mut output = String::with_capacity(name.len() + 4); - for (index, character) in name.chars().enumerate() { - if character.is_ascii_uppercase() && index > 0 { - output.push('_'); - } - output.push(character.to_ascii_lowercase()); - } - output -} - -fn launch_memory_context( - runtime_store_path: &Path, - _state: &EpiphanyThreadState, - focus: &str, -) -> Result<(EpiphanyMemoryContextPacket, EpiphanyRepoModelView), String> { - let view = assemble_repo_model_view(runtime_store_path) - .map_err(|error| format!("failed to load keyed RepoModel view: {error}"))?; - let projection = view.memory_context_projection(); - - let mut packet = plan_memory_graph_context_cut( - &projection, - &EpiphanyMemoryContextQuery { - id: format!("launch-context-query-{}", view.projection_digest), - profile: Some(EpiphanyMemoryProfile::RepoArchitecture), - text: Some(focus.to_string()), - budget: Some(5), - ..Default::default() - }, - ); - packet.warnings.push(format!( - "RepoModel context is assembled from exact keyed Mind documents at projection digest {}.", - view.projection_digest - )); - if packet.nodes.is_empty() && packet.summaries.is_empty() { - packet.warnings.push( - "Memory graph context is empty for this launch focus; the accepted repo graph may be thin or stale.".to_string(), - ); - } - Ok((packet, view)) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::EpiphanyMemoryDomain; - use crate::EpiphanyMemoryLifecycle; - use crate::EpiphanyMemoryNode; - use crate::EpiphanyMemoryNodeKind; - use crate::EpiphanyRoleResultRoleId; - use crate::RepoFrontierItem; - use crate::RepoFrontierStatus; - use crate::RuntimeSpineHeartbeatJobOptions; - use crate::RuntimeSpineInitOptions; - use crate::build_epiphany_role_launch_request_with_dynamic_context; - use crate::initialize_runtime_spine; - use crate::open_runtime_spine_heartbeat_job; - use crate::runtime_worker_launch_request; - use crate::seed_epiphany_local_verse_context; - use std::fs; - use uuid::Uuid; - - #[test] - fn native_launch_context_has_no_bridge_or_host_dependency() { - let source = include_str!("coordinator_launch_context.rs"); - let production = source.split("#[cfg(test)]").next().unwrap_or(source); - for forbidden in [ - "epiphany_codex_bridge", - "epiphany_core::", - "AppServerClient", - "serde_json", - ] { - assert!( - !production.contains(forbidden), - "native launch context contains host marker {forbidden:?}" - ); - } - } - - #[test] - fn canonical_model_frontier_survives_newer_thread_state_and_guides_launch() -> anyhow::Result<()> - { - let temp = std::env::temp_dir().join(format!( - "epiphany-canonical-model-launch-{}", - Uuid::new_v4() - )); - fs::create_dir(&temp)?; - let runtime_store = temp.join("runtime-spine.msgpack"); - initialize_runtime_spine( - &runtime_store, - RuntimeSpineInitOptions { - runtime_id: "canonical-model-launch-test".to_string(), - display_name: "Canonical Model Launch Test".to_string(), - created_at: "2026-06-12T00:00:00Z".to_string(), - }, - )?; - crate::runtime_spine::tests::bind_test_runtime_swarm( - &runtime_store, - "canonical-model-launch-swarm", - )?; - let documents = crate::EpiphanyRepoModelSeedDocuments { - domains: vec![EpiphanyMemoryDomain { - id: "repo".to_string(), - profile: EpiphanyMemoryProfile::RepoArchitecture, - title: "Canonical repository model".to_string(), - lifecycle: EpiphanyMemoryLifecycle::Accepted, - ..Default::default() - }], - nodes: vec![EpiphanyMemoryNode { - id: "claim-modeling-authority".to_string(), - domain_id: "repo".to_string(), - profile: EpiphanyMemoryProfile::RepoArchitecture, - kind: EpiphanyMemoryNodeKind::RuntimeContract, - title: "Modeling authority".to_string(), - claim: "Canonical Modeling state survives transcript revision churn.".to_string(), - question: "Which downstream organ consumes it?".to_string(), - action_implication: "Route the exact claim into launch context.".to_string(), - source_hashes: vec!["anchor:missing".to_string()], - lifecycle: EpiphanyMemoryLifecycle::Accepted, - ..Default::default() - }], - frontier: vec![RepoFrontierItem { - id: "frontier-modeling-handoff".to_string(), - migration_body: "Carry the canonical repository frontier into organ prompts." - .to_string(), - question: "Can Hands see the exact target claim?".to_string(), - gap: "Launch assembly previously saw only semantically similar prose.".to_string(), - target_claim_ids: vec!["claim-modeling-authority".to_string()], - source_scope: vec!["epiphany-core/src".to_string()], - recommended_next_organ: "Hands".to_string(), - status: RepoFrontierStatus::Active, - ..Default::default() - }], - edges: Vec::new(), - summaries: Vec::new(), - lifecycle_receipts: Vec::new(), - }; - let seed = crate::EpiphanyRepoModelSeed::new( - "canonical-model-seed", - "canonical-model", - "canonical-model-launch-swarm", - "canonical-model-workspace", - "sha256:canonical-model-body", - documents.clone(), - )?; - crate::initialize_keyed_repo_model(&runtime_store, &seed, "2026-06-12T00:00:01Z")?; - let newer_thread_state = EpiphanyThreadState { - revision: 999, - objective: Some("Discuss irrelevant weather bananas.".to_string()), - ..Default::default() - }; - - let (packet, _) = launch_memory_context( - &runtime_store, - &newer_thread_state, - "irrelevant weather bananas", - ) - .map_err(anyhow::Error::msg)?; - - assert_eq!(packet.frontier[0].id, "frontier-modeling-handoff"); - assert!( - packet - .nodes - .iter() - .any(|node| node.id == "claim-modeling-authority") - ); - let preserved = crate::assemble_repo_model_view(&runtime_store)?; - assert_eq!(preserved.frontier, documents.frontier); - let shape = append_modeling_repo_model_shape_context("base".to_string(), &runtime_store) - .map_err(anyhow::Error::msg)?; - assert_eq!( - shape, - append_modeling_repo_model_shape_snapshot("base".to_string(), &preserved) - ); - assert!(shape.contains("")); - assert!(shape.contains("id=repo")); - assert!(shape.contains("existingClaims:")); - assert!(shape.contains("id=claim-modeling-authority domain=repo")); - assert!(shape.contains("existingFrontier:")); - assert!(shape.contains("id=frontier-modeling-handoff status=active")); - assert!(shape.contains("projectionDigest: sha256:")); - assert!(shape.contains("semantic document identities directly")); - assert!(shape.contains("strict lexicographic ascending order without duplicates")); - assert!(shape.contains("projection digest is display and audit identity")); - fs::remove_dir_all(&temp)?; - Ok(()) - } - - #[test] - fn launch_context_persists_on_runtime_worker_request() -> anyhow::Result<()> { - let temp = - std::env::temp_dir().join(format!("epiphany-bridge-launch-context-{}", Uuid::new_v4())); - fs::create_dir(&temp)?; - let runtime_store = temp.join("runtime-spine.msgpack"); - crate::initialize_runtime_spine( - &runtime_store, - crate::RuntimeSpineInitOptions { - runtime_id: "launch-context-runtime".to_string(), - display_name: "Launch context test".to_string(), - created_at: "2026-07-12T00:00:00Z".to_string(), - }, - )?; - let agent_store = temp.join("agents.msgpack"); - crate::ensure_agent_memory_swarm_identity(&agent_store, "launch-context-swarm")?; - crate::bind_runtime_to_agent_memory_swarm( - &runtime_store, - &agent_store, - "2026-07-12T00:00:01Z", - )?; - crate::runtime_spine::tests::bind_test_repository_body( - &runtime_store, - "launch-context-workspace", - )?; - let body_basis = crate::observe_runtime_repository_body_basis(&runtime_store)?; - let seed = crate::EpiphanyRepoModelSeed::new( - "launch-context-seed", - "launch-context-graph", - body_basis.swarm_id.clone(), - body_basis.workspace_id.clone(), - body_basis.body_binding_sha256.clone(), - crate::EpiphanyRepoModelSeedDocuments { - domains: Vec::new(), - nodes: Vec::new(), - edges: Vec::new(), - summaries: Vec::new(), - frontier: Vec::new(), - lifecycle_receipts: Vec::new(), - }, - )?; - crate::initialize_keyed_repo_model(&runtime_store, &seed, "2026-07-12T00:00:02Z")?; - let state = EpiphanyThreadState { - revision: 7, - objective: Some("Test launch context.".to_string()), - ..Default::default() - }; - seed_epiphany_local_verse_context( - local_verse_store_path(&runtime_store), - EPIPHANY_LOCAL_VERSE_RUNTIME_ID, - "2026-07-12T00:00:00Z", - "repo:C:/fixture/Epiphany", - )?; - - let rendered = render_launch_dynamic_prompt_context( - &runtime_store, - &local_verse_store_path(&runtime_store), - &state, - role_launch_context_focus(&state, "modeling"), - ) - .map_err(anyhow::Error::msg)?; - - assert!(rendered.contains("")); - assert!(rendered.contains("Test launch context.")); - assert!(rendered.contains("Odin")); - assert!(rendered.contains("Yggdrasil")); - assert!(rendered.contains("Memory graph")); - assert!(local_verse_store_path(&runtime_store).exists()); - assert!(runtime_store.exists()); - - let mut launch_request = build_epiphany_role_launch_request_with_dynamic_context( - "thread-1", - EpiphanyRoleResultRoleId::Modeling, - Some(state.revision), - Some(60), - &state, - Some(rendered.clone()), - ) - .map_err(anyhow::Error::msg)?; - let crate::EpiphanyWorkerLaunchDocument::Role(role_document) = - &mut launch_request.launch_document - else { - unreachable!("role launch builder returned reorient document") - }; - role_document.repository_body_observation_basis = Some(body_basis); - open_runtime_spine_heartbeat_job( - &runtime_store, - RuntimeSpineHeartbeatJobOptions { - runtime_id: "launch-context-runtime".to_string(), - display_name: "Launch context test".to_string(), - session_id: "epiphany-main".to_string(), - objective: "Test persisted launch context.".to_string(), - coordinator_note: "Bridge launch-context smoke.".to_string(), - job_id: "job-launch-context".to_string(), - role: launch_request.owner_role.clone(), - binding_id: launch_request.binding_id.clone(), - authority_scope: launch_request.authority_scope.clone(), - instruction: launch_request.instruction.clone(), - launch_document: launch_request.launch_document.clone(), - output_contract_id: launch_request.output_contract_id.clone(), - organ_launch_contract: launch_request.organ_launch_contract.clone(), - proposal_modeling_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_verification_request_id: None, - created_at: "2026-06-02T00:00:00Z".to_string(), - }, - )?; - let stored = runtime_worker_launch_request(&runtime_store, "job-launch-context")? - .expect("runtime worker launch request should be persisted"); - let stored_document = stored.launch_document()?; - let stored_context = stored_document - .dynamic_prompt_context() - .expect("stored launch document should carry dynamic context"); - assert!(stored_context.contains("Odin")); - assert!(stored_context.contains("Memory graph")); - assert!(stored_context.contains("Test launch context.")); - - fs::remove_dir_all(&temp)?; - Ok(()) - } - - #[test] - fn launch_context_refuses_to_bootstrap_shared_state() -> anyhow::Result<()> { - let temp = std::env::temp_dir().join(format!( - "epiphany-launch-context-unbootstrapped-{}", - Uuid::new_v4() - )); - fs::create_dir(&temp)?; - let runtime_store = temp.join("runtime-spine.msgpack"); - let local_verse_store = local_verse_store_path(&runtime_store); - let state = EpiphanyThreadState { - revision: 1, - objective: Some("Prove launch assembly cannot initialize shared state.".to_string()), - ..Default::default() - }; - - let error = render_launch_dynamic_prompt_context( - &runtime_store, - &local_verse_store, - &state, - role_launch_context_focus(&state, "modeling"), - ) - .expect_err("unbootstrapped launch context must fail closed"); - - assert!(error.contains("local Verse is not bootstrapped")); - assert!(!local_verse_store.exists()); - Ok(()) - } -} diff --git a/epiphany-core/src/current_work.rs b/epiphany-core/src/current_work.rs index 84e85da4..4fd1a2d0 100644 --- a/epiphany-core/src/current_work.rs +++ b/epiphany-core/src/current_work.rs @@ -954,7 +954,6 @@ pub fn launch_current_frontier_planning_work( crate::EpiphanyWorkerLaunchDocument::Role(crate::EpiphanyRoleWorkerLaunchDocument { thread_id: job_id.clone(), role_id: "imagination".into(), - state_revision: 0, objective: None, dynamic_prompt_context: None, repository_body_observation_basis: None, @@ -1147,7 +1146,6 @@ pub fn launch_current_frontier_plan_mind_work( crate::EpiphanyWorkerLaunchDocument::Role(crate::EpiphanyRoleWorkerLaunchDocument { thread_id: job_id.clone(), role_id: "mindAdmissionReview".into(), - state_revision: 0, objective: None, dynamic_prompt_context: None, repository_body_observation_basis: None, @@ -1336,7 +1334,6 @@ pub fn launch_current_frontier_verification_work( crate::EpiphanyWorkerLaunchDocument::Role(crate::EpiphanyRoleWorkerLaunchDocument { thread_id: job_id.clone(), role_id: "verification".into(), - state_revision: 0, objective: None, dynamic_prompt_context: None, repository_body_observation_basis: None, @@ -1515,7 +1512,6 @@ pub fn launch_current_frontier_research_work( crate::EpiphanyWorkerLaunchDocument::Role(crate::EpiphanyRoleWorkerLaunchDocument { thread_id: job_id.clone(), role_id: "research".into(), - state_revision: 0, objective: None, dynamic_prompt_context: None, repository_body_observation_basis: None, @@ -2443,7 +2439,6 @@ fn launch_consideration_work( crate::EpiphanyWorkerLaunchDocument::Role(crate::EpiphanyRoleWorkerLaunchDocument { thread_id: job_id.clone(), role_id: "imagination".into(), - state_revision: 0, objective: None, dynamic_prompt_context: Some(dynamic_prompt_context), repository_body_observation_basis: None, @@ -2679,7 +2674,6 @@ pub fn launch_current_body_modeling_work( crate::EpiphanyWorkerLaunchDocument::Role(crate::EpiphanyRoleWorkerLaunchDocument { thread_id: binding_record_id.clone(), role_id: "modeling".into(), - state_revision: 0, objective: None, dynamic_prompt_context: None, repository_body_observation_basis: Some(work.body_basis.clone()), @@ -2850,7 +2844,6 @@ pub fn launch_current_proposal_modeling_work( crate::EpiphanyWorkerLaunchDocument::Role(crate::EpiphanyRoleWorkerLaunchDocument { thread_id: request.thread_id.clone(), role_id: "modeling".into(), - state_revision: 0, objective: None, dynamic_prompt_context: None, repository_body_observation_basis: Some(body_basis), @@ -3023,7 +3016,6 @@ pub fn launch_current_frontier_verdict_modeling_work( crate::EpiphanyWorkerLaunchDocument::Role(crate::EpiphanyRoleWorkerLaunchDocument { thread_id: job_id.clone(), role_id: "modeling".into(), - state_revision: 0, objective: None, dynamic_prompt_context: None, repository_body_observation_basis: Some(body_basis), @@ -3627,7 +3619,6 @@ mod tests { let crate::EpiphanyWorkerLaunchDocument::Role(proposal_document) = proposal_document else { panic!("proposal Modeling must be a role pass") }; - assert_eq!(proposal_document.state_revision, 0); assert!(proposal_document.proposal_modeling_context.is_some()); assert!( proposal_document diff --git a/epiphany-core/src/frontier_plan_chain.rs b/epiphany-core/src/frontier_plan_chain.rs new file mode 100644 index 00000000..ccb61994 --- /dev/null +++ b/epiphany-core/src/frontier_plan_chain.rs @@ -0,0 +1,123 @@ +use crate::EpiphanyRepoModelView; +use sha2::Digest; + +pub fn validate_frontier_plan_decision_chain( + decision: &crate::RepoFrontierPlanDecisionReceipt, + route: &crate::RepoFrontierRoute, + current_model: &EpiphanyRepoModelView, +) -> Result<(), String> { + validate_current_frontier_route(route, current_model, None)?; + let plan = route + .adopted_plan + .as_ref() + .ok_or_else(|| "Soul frontier plan decision has no adopted plan".to_string())?; + let source_is_complete = match decision.decision_source.as_ref() { + Some(crate::RepoFrontierPlanDecisionSource::MindWorker { result_id, job_id }) => { + !result_id.trim().is_empty() && !job_id.trim().is_empty() + } + Some(crate::RepoFrontierPlanDecisionSource::AuthenticatedOperatorReview { + command_id, + admission_id, + packet_sha256, + source_actor_id, + }) => [command_id, admission_id, packet_sha256, source_actor_id] + .into_iter() + .all(|value| !value.trim().is_empty()), + None => false, + }; + let decision_basis = crate::EpiphanyRepoModelBasis { + projection_digest: decision.model_projection_digest.clone(), + source_documents: decision.model_source_documents.clone(), + }; + decision_basis.validate().map_err(|error| { + format!("Soul frontier plan decision has an invalid keyed basis: {error}") + })?; + if decision.schema_version != crate::REPO_FRONTIER_PLAN_DECISION_RECEIPT_SCHEMA_VERSION + || decision.contract != crate::REPO_FRONTIER_PLAN_DECISION_CONTRACT + || decision.decision != crate::RepoFrontierPlanDecision::Adopt + || decision.legacy_mind_worker_result_id.is_some() + || decision.legacy_mind_worker_job_id.is_some() + || decision.planning_request_id != plan.planning_request_id + || decision.candidate_id != plan.candidate_id + || decision.candidate_sha256 != plan.candidate_sha256 + || decision_basis != current_model.reasoning_basis() + || decision.frontier_item_id != route.frontier_item_id + || decision.frontier_item_hash != route.frontier_item_hash + || !source_is_complete + { + return Err( + "Soul frontier plan decision does not exactly bind its keyed basis, adopted plan, route, and decision source" + .to_string(), + ); + } + Ok(()) +} + +fn validate_current_frontier_route<'a>( + route: &crate::RepoFrontierRoute, + current_model: &'a EpiphanyRepoModelView, + verification_request: Option<&crate::RepoFrontierVerificationRequest>, +) -> Result<&'a crate::RepoFrontierItem, String> { + let route_basis = crate::EpiphanyRepoModelBasis { + projection_digest: route.model_projection_digest.clone(), + source_documents: route.model_source_documents.clone(), + }; + route_basis + .validate() + .map_err(|error| format!("frontier route has an invalid keyed basis: {error}"))?; + if route_basis != current_model.reasoning_basis() { + return Err("frontier route does not bind the current keyed RepoModel view".to_string()); + } + let current_item = current_model + .frontier + .iter() + .find(|item| item.id == route.frontier_item_id) + .ok_or_else(|| { + "routed frontier item disappeared from the current keyed view".to_string() + })?; + let current_item_hash = format!( + "{:x}", + sha2::Sha256::digest( + rmp_serde::to_vec_named(current_item) + .map_err(|error| format!("failed to hash current frontier item: {error}"))? + ) + ); + let expected_source_scope = current_item + .adopted_plan + .as_ref() + .map(|plan| plan.safe_paths.as_slice()) + .unwrap_or(current_item.source_scope.as_slice()); + if route.schema_version != crate::REPO_FRONTIER_ROUTE_SCHEMA_VERSION + || route.contract != crate::REPO_FRONTIER_ROUTE_CONTRACT + || route.next_organ != crate::RepoFrontierNextOrgan::Hands + || route.frontier_item_hash != current_item_hash + || route.migration_body != current_item.migration_body + || route.question != current_item.question + || route.gap != current_item.gap + || route.target_claim_ids != current_item.target_claim_ids + || route.source_scope != expected_source_scope + || route.adopted_plan != current_item.adopted_plan + || current_item.status != crate::RepoFrontierStatus::Active + || current_item.recommended_next_organ != "Hands" + { + return Err( + "frontier route does not exactly bind its current keyed frontier item".to_string(), + ); + } + if let Some(request) = verification_request { + if request.schema_version != crate::REPO_FRONTIER_VERIFICATION_REQUEST_SCHEMA_VERSION + || request.contract != crate::REPO_FRONTIER_VERIFICATION_REQUEST_CONTRACT + || request.route_id != route.route_id + || request.model_projection_digest != route.model_projection_digest + || request.model_source_documents != route.model_source_documents + || request.frontier_item_id != route.frontier_item_id + || request.frontier_item_hash != route.frontier_item_hash + { + return Err( + "Soul verification request does not exactly bind the current keyed route" + .to_string(), + ); + } + } + Ok(current_item) +} diff --git a/epiphany-core/src/heartbeat_state/heartbeat_roles.rs b/epiphany-core/src/heartbeat_state/heartbeat_roles.rs index 51126b09..8c8b55fe 100644 --- a/epiphany-core/src/heartbeat_state/heartbeat_roles.rs +++ b/epiphany-core/src/heartbeat_state/heartbeat_roles.rs @@ -276,7 +276,7 @@ fn participant_constraints(role_id: &str) -> Vec<&'static str> { vec![ "Runs Ghostlight-shaped persistent role memory.", "May improve lane memory when awake and idle.", - "Project truth belongs in EpiphanyThreadState, not role memory.", + "Project truth belongs in keyed Mind documents, not role memory.", role_specific, ] } diff --git a/epiphany-core/src/lib.rs b/epiphany-core/src/lib.rs index e46bcfb0..306a4d05 100644 --- a/epiphany-core/src/lib.rs +++ b/epiphany-core/src/lib.rs @@ -4,13 +4,13 @@ mod agent_utterance_state; pub mod atlas; mod causal_work_identity; mod continuity_gateway; -mod coordinator_launch_context; mod coordinator_objective_intake; mod coordinator_results; mod cultmesh_integration; mod current_work; mod distillation; mod eyes_gateway; +mod frontier_plan_chain; mod hands_gateway; mod heartbeat_state; mod host_identity; @@ -94,12 +94,7 @@ pub use agent_launch::EpiphanyReorientationPromptConfig; pub use agent_launch::EpiphanyRolePromptConfig; pub use agent_launch::EpiphanySharedPromptConfig; pub use agent_launch::EpiphanySpecialistPromptConfig; -pub use agent_launch::build_epiphany_admitted_model_direction_consideration_launch_request; -pub use agent_launch::build_epiphany_imagination_consideration_launch_request; -pub use agent_launch::build_epiphany_job_launch_request; pub use agent_launch::build_epiphany_reorient_launch_instruction; -pub use agent_launch::build_epiphany_role_launch_request; -pub use agent_launch::build_epiphany_role_launch_request_with_dynamic_context; pub use agent_launch::epiphany_admitted_model_direction_consideration_output_schema; pub use agent_launch::epiphany_agent_prompt_with_memory; pub use agent_launch::epiphany_frontier_plan_mind_output_schema; @@ -191,15 +186,7 @@ pub use agent_utterance_state::derive_agent_utterance_state; pub use atlas::*; pub use causal_work_identity::*; pub use continuity_gateway::*; -pub use coordinator_launch_context::EPIPHANY_LOCAL_VERSE_RUNTIME_ID; -pub use coordinator_launch_context::append_modeling_repo_model_shape_context; -pub use coordinator_launch_context::local_verse_store_path; -pub use coordinator_launch_context::memory_graph_store_path; -pub use coordinator_launch_context::render_launch_dynamic_prompt_context; -pub use coordinator_launch_context::render_modeling_launch_dynamic_prompt_context; -pub use coordinator_launch_context::reorient_launch_context_focus; -pub use coordinator_launch_context::role_launch_context_focus; -pub use coordinator_launch_context::validate_frontier_plan_decision_chain; +pub use frontier_plan_chain::validate_frontier_plan_decision_chain; pub use coordinator_objective_intake::USER_OBJECTIVE_INTAKE_CONTRACT; pub use coordinator_objective_intake::USER_OBJECTIVE_INTAKE_SCHEMA_VERSION; pub use coordinator_objective_intake::USER_OBJECTIVE_INTAKE_TYPE; @@ -566,8 +553,6 @@ pub use epiphany_state_model::EpiphanyMemoryLifecycle; pub use epiphany_state_model::EpiphanyMemoryNode; pub use epiphany_state_model::EpiphanyMemoryProfile; pub use epiphany_state_model::EpiphanyMemorySummary; -pub use epiphany_state_model::EpiphanyThreadState; -pub use epiphany_state_model::render_epiphany_state; pub use eyes_gateway::EYES_EVIDENCE_PACKET_SCHEMA_VERSION; pub use eyes_gateway::EYES_EVIDENCE_PACKET_TYPE; pub use eyes_gateway::EYES_EVIDENCE_REFUSAL_RECEIPT_SCHEMA_VERSION; @@ -742,7 +727,6 @@ pub use memory_graph::RepoFrontierAdoptedPlan; pub use memory_graph::RepoFrontierExecutionAmendment; pub use memory_graph::RepoFrontierItem; pub use memory_graph::RepoFrontierStatus; -pub use memory_graph::RepoMemoryGraphRefresh; pub use memory_graph::SEMANTIC_PROJECTION_SCHEMA_VERSION; pub use memory_graph::SemanticCanonicalLocator; pub use memory_graph::SemanticDocumentKind; @@ -771,7 +755,6 @@ pub use memory_graph::load_memory_semantic_projection_success; pub use memory_graph::memory_graph_domain_id; pub use memory_graph::memory_graph_edge_id; pub use memory_graph::memory_graph_from_agent_memories; -pub use memory_graph::memory_graph_from_epiphany_graphs; pub use memory_graph::memory_graph_from_heartbeat_cognition; pub use memory_graph::memory_graph_model_hash; pub use memory_graph::memory_graph_node_id; @@ -780,7 +763,6 @@ pub use memory_graph::migrate_memory_semantic_projection_attempts_v0; pub use memory_graph::observe_memory_semantic_projection; pub use memory_graph::plan_memory_graph_context_cut; pub use memory_graph::plan_memory_graph_context_cut_with_ranked_ids; -pub use memory_graph::refresh_or_validate_repo_memory_graph; pub use memory_graph::resolve_semantic_candidate; pub use memory_graph::retain_memory_semantic_projection_lifecycles; pub use memory_graph::retire_memory_semantic_projection_claims_v0; @@ -948,10 +930,6 @@ pub use runtime_spine::EpiphanyArchivedRuntimeWorkerAttempt; pub use runtime_spine::EpiphanyCoordinatorDeathRecovery; pub use runtime_spine::EpiphanyCoordinatorRunReceipt; pub use runtime_spine::EpiphanyCoordinatorRunReceiptRetentionHead; -pub use runtime_spine::EpiphanyJobInterruptRequest; -pub use runtime_spine::EpiphanyJobInterruptResult; -pub use runtime_spine::EpiphanyJobLaunchRequest; -pub use runtime_spine::EpiphanyJobLaunchResult; pub use runtime_spine::EpiphanyRuntimeEvent; pub use runtime_spine::EpiphanyRuntimeIdentity; pub use runtime_spine::EpiphanyRuntimeJob; @@ -998,8 +976,6 @@ pub use runtime_spine::RepoFrontierVerdictModelingLaunchAuthority; pub use runtime_spine::RuntimeHandsReceiptChainSummary; pub use runtime_spine::RuntimeSpineEventOptions; pub use runtime_spine::RuntimeSpineHeartbeatJobOptions; -pub use runtime_spine::RuntimeSpineHeartbeatLaunchPlan; -pub use runtime_spine::RuntimeSpineHeartbeatLaunchPlanOptions; pub use runtime_spine::RuntimeSpineInitOptions; pub use runtime_spine::RuntimeSpineJobOptions; pub use runtime_spine::RuntimeSpineJobResultOptions; @@ -1018,7 +994,6 @@ pub use runtime_spine::bind_runtime_repository_domain; pub use runtime_spine::bind_runtime_to_agent_memory_swarm; pub use runtime_spine::canonical_repo_frontier_plan_candidate_id; pub use runtime_spine::claim_runtime_worker_process; -pub use runtime_spine::clear_epiphany_job_binding_backend; pub use runtime_spine::close_runtime_session; pub use runtime_spine::commit_operator_repo_frontier_plan_review; pub use runtime_spine::commit_repo_frontier_modeling_request; @@ -1040,7 +1015,6 @@ pub use runtime_spine::open_runtime_model_execution; pub use runtime_spine::open_runtime_spine_heartbeat_job; pub use runtime_spine::operator_repo_frontier_plan_review_is_current; pub use runtime_spine::pending_repo_frontier_plan_reviews; -pub use runtime_spine::plan_runtime_spine_heartbeat_launch; pub use runtime_spine::prepare_runtime_spine_heartbeat_job; pub use runtime_spine::promote_autonomous_direction_options_for_modeling; pub use runtime_spine::put_coordinator_run_receipt; @@ -1063,7 +1037,6 @@ pub use runtime_spine::put_substrate_gate_repo_access_grant_receipt; pub use runtime_spine::relinquish_repo_frontier_hands_route; pub use runtime_spine::repair_legacy_terminal_coordinator_sessions; pub use runtime_spine::repair_runtime_root_session_after_invalid_completion; -pub use runtime_spine::replace_or_append_epiphany_job_binding; pub use runtime_spine::require_runtime_tool_execution_binding; pub use runtime_spine::retain_completed_runtime_sessions; pub use runtime_spine::retain_coordinator_run_receipts; @@ -1160,15 +1133,10 @@ pub use substrate_gate::SubstrateGateRepoAccessGrantReceipt; pub use substrate_gate::default_substrate_gate_cultnet_contracts; pub use substrate_gate::substrate_gate_coordinator_implementation_grant; pub use substrate_gate::substrate_gate_operation_for_governed_tool; -pub use substrate_gate::substrate_gate_repo_access_grant_for_launch; +pub use substrate_gate::substrate_gate_repo_access_grant_for_worker; pub use substrate_gate::substrate_gate_repo_work_planning_grant; pub use surfaces::AdmittedModelDirectionConsiderationContextProjection; pub use surfaces::EpiphanyAcceptanceBundle; -pub use surfaces::EpiphanyContext; -pub use surfaces::EpiphanyContextMissing; -pub use surfaces::EpiphanyContextParams; -pub use surfaces::EpiphanyContextStateStatus; -pub use surfaces::EpiphanyContextView; pub use surfaces::EpiphanyCoordinatorAction; pub use surfaces::EpiphanyCoordinatorAutomationAction; pub use surfaces::EpiphanyCoordinatorCrrcRecommendation; @@ -1188,20 +1156,6 @@ pub use surfaces::EpiphanyCrrcReorientAction; pub use surfaces::EpiphanyCrrcResultStatus; pub use surfaces::EpiphanyCrrcSceneAction; pub use surfaces::EpiphanyCrrcStateStatus; -pub use surfaces::EpiphanyFreshnessInput; -pub use surfaces::EpiphanyFreshnessView; -pub use surfaces::EpiphanyFreshnessWatcherInput; -pub use surfaces::EpiphanyGraphContext; -pub use surfaces::EpiphanyGraphFreshness; -pub use surfaces::EpiphanyGraphFreshnessStatus; -pub use surfaces::EpiphanyGraphQuery; -pub use surfaces::EpiphanyGraphQueryDirection; -pub use surfaces::EpiphanyGraphQueryKind; -pub use surfaces::EpiphanyGraphQueryMatched; -pub use surfaces::EpiphanyGraphQueryMissing; -pub use surfaces::EpiphanyGraphQueryView; -pub use surfaces::EpiphanyInvalidationInput; -pub use surfaces::EpiphanyInvalidationStatus; pub use surfaces::EpiphanyJobStatus; pub use surfaces::EpiphanyJobView; pub use surfaces::EpiphanyJobsInput; @@ -1217,8 +1171,6 @@ pub use surfaces::EpiphanyReorientAcceptanceFinding; pub use surfaces::EpiphanyReorientAction; pub use surfaces::EpiphanyReorientFindingInterpretation; pub use surfaces::EpiphanyReorientWorkerLaunchDocument; -pub use surfaces::EpiphanyRetrievalFreshness; -pub use surfaces::EpiphanyRetrievalFreshnessStatus; pub use surfaces::EpiphanyRoleAcceptanceFinding; pub use surfaces::EpiphanyRoleBoardInput; pub use surfaces::EpiphanyRoleBoardJob; @@ -1267,9 +1219,6 @@ pub use surfaces::RepoFrontierVerificationContextProjection; pub use surfaces::build_reorient_acceptance_bundle; pub use surfaces::build_role_acceptance_bundle; pub use surfaces::default_epiphany_view_lenses; -pub use surfaces::derive_context; -pub use surfaces::derive_freshness; -pub use surfaces::derive_graph_query; pub use surfaces::derive_jobs; pub use surfaces::derive_planning_view; pub use surfaces::derive_pressure_view; diff --git a/epiphany-core/src/memory_graph/mod.rs b/epiphany-core/src/memory_graph/mod.rs index 3213f7b2..f8e7b458 100644 --- a/epiphany-core/src/memory_graph/mod.rs +++ b/epiphany-core/src/memory_graph/mod.rs @@ -19,7 +19,6 @@ pub use epiphany_state_model::EpiphanyMemoryEmbeddingManifest; pub use epiphany_state_model::EpiphanyMemoryFreshness; pub use epiphany_state_model::EpiphanyMemoryFreshnessStatus; pub use epiphany_state_model::EpiphanyMemoryGraphSnapshot; -pub use epiphany_state_model::EpiphanyMemoryGraphSource; pub use epiphany_state_model::EpiphanyMemoryLifecycle; pub use epiphany_state_model::EpiphanyMemoryLifecycleReceipt; pub use epiphany_state_model::EpiphanyMemoryNode; @@ -40,11 +39,8 @@ pub use freshness::derive_memory_graph_freshness; pub use ids::memory_graph_domain_id; pub use ids::memory_graph_edge_id; pub use ids::memory_graph_node_id; -pub use profiles::RepoMemoryGraphRefresh; pub use profiles::memory_graph_from_agent_memories; -pub use profiles::memory_graph_from_epiphany_graphs; pub use profiles::memory_graph_from_heartbeat_cognition; -pub use profiles::refresh_or_validate_repo_memory_graph; pub use semantic_index::*; pub use semantic_projection::*; pub use semantic_projector::*; diff --git a/epiphany-core/src/memory_graph/profiles/mod.rs b/epiphany-core/src/memory_graph/profiles/mod.rs index e9de15e3..658f2c8f 100644 --- a/epiphany-core/src/memory_graph/profiles/mod.rs +++ b/epiphany-core/src/memory_graph/profiles/mod.rs @@ -1,9 +1,5 @@ pub mod agent; pub mod heartbeat; -pub mod repo; pub use agent::memory_graph_from_agent_memories; pub use heartbeat::memory_graph_from_heartbeat_cognition; -pub use repo::RepoMemoryGraphRefresh; -pub use repo::memory_graph_from_epiphany_graphs; -pub use repo::refresh_or_validate_repo_memory_graph; diff --git a/epiphany-core/src/memory_graph/profiles/repo.rs b/epiphany-core/src/memory_graph/profiles/repo.rs deleted file mode 100644 index 928bdb6a..00000000 --- a/epiphany-core/src/memory_graph/profiles/repo.rs +++ /dev/null @@ -1,502 +0,0 @@ -use crate::memory_graph::EpiphanyMemoryAnchor; -use crate::memory_graph::EpiphanyMemoryDomain; -use crate::memory_graph::EpiphanyMemoryEdge; -use crate::memory_graph::EpiphanyMemoryEdgeKind; -use crate::memory_graph::EpiphanyMemoryFreshnessStatus; -use crate::memory_graph::EpiphanyMemoryGraphSnapshot; -use crate::memory_graph::EpiphanyMemoryLifecycle; -use crate::memory_graph::EpiphanyMemoryNode; -use crate::memory_graph::EpiphanyMemoryNodeKind; -use crate::memory_graph::EpiphanyMemoryProfile; -use crate::memory_graph::EpiphanyMemorySummary; -use crate::memory_graph::derive_memory_graph_freshness; -use crate::memory_graph::memory_graph_domain_id; -use crate::memory_graph::memory_graph_edge_id; -use crate::memory_graph::memory_graph_node_id; -use anyhow::{Context, Result}; -use epiphany_state_model::EpiphanyCodeRef; -use epiphany_state_model::EpiphanyGraph; -use epiphany_state_model::EpiphanyGraphEdge; -use epiphany_state_model::EpiphanyGraphLink; -use epiphany_state_model::EpiphanyGraphNode; -use epiphany_state_model::EpiphanyGraphs; -use epiphany_state_model::EpiphanyThreadState; -use sha2::{Digest, Sha256}; -use std::collections::HashMap; -use std::fs; -use std::path::Path; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum RepoMemoryGraphRefresh { - Reused, - Refreshed, -} - -pub fn refresh_or_validate_repo_memory_graph( - _store_path: &Path, - source_identity: &str, - state: &EpiphanyThreadState, - repo_root: &Path, - graph_id: impl Into, -) -> Result<(EpiphanyMemoryGraphSnapshot, RepoMemoryGraphRefresh)> { - Ok(( - memory_graph_from_epiphany_graphs( - graph_id, - &state.graphs, - source_identity, - state.revision, - repo_root, - )?, - RepoMemoryGraphRefresh::Refreshed, - )) -} - -pub fn memory_graph_from_epiphany_graphs( - graph_id: impl Into, - graphs: &EpiphanyGraphs, - source_identity: impl Into, - source_revision: u64, - repo_root: &Path, -) -> Result { - let architecture_domain = memory_graph_domain_id( - EpiphanyMemoryProfile::RepoArchitecture, - "accepted_graph", - "architecture", - ); - let dataflow_domain = memory_graph_domain_id( - EpiphanyMemoryProfile::RepoDataflow, - "accepted_graph", - "dataflow", - ); - - let mut domains = Vec::new(); - let mut nodes = Vec::new(); - let mut edges = Vec::new(); - let mut summaries = Vec::new(); - let mut node_map = HashMap::new(); - - if !graphs.architecture.is_empty() { - domains.push(repo_domain( - architecture_domain.clone(), - EpiphanyMemoryProfile::RepoArchitecture, - "Accepted architecture graph", - )); - import_graph_nodes( - &graphs.architecture, - &architecture_domain, - EpiphanyMemoryProfile::RepoArchitecture, - &mut node_map, - &mut nodes, - repo_root, - ); - import_graph_edges( - &graphs.architecture, - EpiphanyMemoryProfile::RepoArchitecture, - &node_map, - &mut edges, - repo_root, - ); - summaries.push(repo_summary( - "summary-accepted-architecture-graph", - &architecture_domain, - "accepted architecture graph", - "Accepted architecture graph nodes are available as memory graph repo-profile claims.", - &nodes, - &edges, - EpiphanyMemoryProfile::RepoArchitecture, - )); - } - - if !graphs.dataflow.is_empty() { - domains.push(repo_domain( - dataflow_domain.clone(), - EpiphanyMemoryProfile::RepoDataflow, - "Accepted dataflow graph", - )); - import_graph_nodes( - &graphs.dataflow, - &dataflow_domain, - EpiphanyMemoryProfile::RepoDataflow, - &mut node_map, - &mut nodes, - repo_root, - ); - import_graph_edges( - &graphs.dataflow, - EpiphanyMemoryProfile::RepoDataflow, - &node_map, - &mut edges, - repo_root, - ); - summaries.push(repo_summary( - "summary-accepted-dataflow-graph", - &dataflow_domain, - "accepted dataflow graph", - "Accepted dataflow graph nodes are available as memory graph repo-profile claims.", - &nodes, - &edges, - EpiphanyMemoryProfile::RepoDataflow, - )); - } - - import_graph_links(graphs.links.as_slice(), &node_map, &mut edges, repo_root); - - let stale = nodes - .iter() - .any(|node| node.lifecycle == EpiphanyMemoryLifecycle::Stale) - || edges - .iter() - .any(|edge| edge.lifecycle == EpiphanyMemoryLifecycle::Stale); - if stale { - for summary in &mut summaries { - summary.freshness = EpiphanyMemoryFreshnessStatus::Stale; - } - } - let mut snapshot = EpiphanyMemoryGraphSnapshot { - schema_version: Some("epiphany.memory_graph.v1".to_string()), - graph_id: graph_id.into(), - source: Some(crate::memory_graph::EpiphanyMemoryGraphSource { - kind: "thread_state_repo_graph".to_string(), - identity: source_identity.into(), - revision: source_revision, - }), - domains, - nodes, - edges, - summaries, - ..Default::default() - }; - snapshot.freshness = Some(derive_memory_graph_freshness(&snapshot, &[])); - Ok(snapshot) -} - -fn repo_domain(id: String, profile: EpiphanyMemoryProfile, title: &str) -> EpiphanyMemoryDomain { - EpiphanyMemoryDomain { - id, - profile, - title: title.to_string(), - description: Some("Repo profile imported from accepted Epiphany graph state.".to_string()), - lifecycle: EpiphanyMemoryLifecycle::Accepted, - } -} - -fn import_graph_nodes( - graph: &EpiphanyGraph, - domain_id: &str, - profile: EpiphanyMemoryProfile, - node_map: &mut HashMap, - nodes: &mut Vec, - repo_root: &Path, -) { - for node in &graph.nodes { - let memory_id = memory_graph_node_id(domain_id, "accepted_graph_node", &node.id, None); - node_map.insert(node.id.clone(), memory_id.clone()); - nodes.push( - memory_node_from_graph_node(memory_id.clone(), domain_id, profile, node, repo_root) - .unwrap_or_else(|_| stale_memory_node(memory_id, domain_id, profile, node)), - ); - } -} - -fn memory_node_from_graph_node( - id: String, - domain_id: &str, - profile: EpiphanyMemoryProfile, - node: &EpiphanyGraphNode, - repo_root: &Path, -) -> Result { - let anchors = anchors_from_code_refs(&id, &node.code_refs, repo_root)?; - let source_hashes = if anchors.is_empty() { - vec!["anchor:missing".to_string()] - } else { - anchors - .iter() - .filter_map(|anchor| anchor.source_hash.clone()) - .collect() - }; - let grounded = !anchors.is_empty(); - Ok(EpiphanyMemoryNode { - id, - domain_id: domain_id.to_string(), - profile, - kind: memory_node_kind(node), - title: node.title.clone(), - claim: graph_node_claim(node), - question: "What changes if this accepted graph node is stale?".to_string(), - tension: node.mechanism.clone().unwrap_or_default(), - action_implication: format!( - "Use accepted graph node {} when selecting repo context.", - node.id - ), - anchors, - source_hashes, - lifecycle: if grounded { - graph_status_lifecycle(node.status.as_deref()) - } else { - EpiphanyMemoryLifecycle::Stale - }, - salience: 70, - confidence: if grounded { 80 } else { 0 }, - ..Default::default() - }) -} - -fn stale_memory_node( - id: String, - domain_id: &str, - profile: EpiphanyMemoryProfile, - node: &EpiphanyGraphNode, -) -> EpiphanyMemoryNode { - EpiphanyMemoryNode { - id, - domain_id: domain_id.to_string(), - profile, - kind: memory_node_kind(node), - title: node.title.clone(), - claim: graph_node_claim(node), - question: "Source anchor is unavailable; refresh after restoring it.".to_string(), - tension: "Accepted anatomy cannot be verified against source bytes.".to_string(), - action_implication: "Do not use this claim as fresh compressed anatomy.".to_string(), - source_hashes: vec!["anchor:missing".to_string()], - lifecycle: EpiphanyMemoryLifecycle::Stale, - salience: 70, - confidence: 0, - ..Default::default() - } -} - -fn import_graph_edges( - graph: &EpiphanyGraph, - profile: EpiphanyMemoryProfile, - node_map: &HashMap, - edges: &mut Vec, - repo_root: &Path, -) { - for edge in &graph.edges { - let source_id = node_map - .get(&edge.source_id) - .cloned() - .unwrap_or_else(|| edge.source_id.clone()); - let target_id = node_map - .get(&edge.target_id) - .cloned() - .unwrap_or_else(|| edge.target_id.clone()); - edges.push(memory_edge_from_graph_edge( - source_id, target_id, profile, edge, repo_root, - )); - } -} - -fn memory_edge_from_graph_edge( - source_id: String, - target_id: String, - profile: EpiphanyMemoryProfile, - edge: &EpiphanyGraphEdge, - repo_root: &Path, -) -> EpiphanyMemoryEdge { - let id = edge.id.clone().unwrap_or_else(|| { - memory_graph_edge_id( - &source_id, - &target_id, - edge.kind.as_str(), - edge.code_refs.iter().map(code_ref_key), - ) - }); - let anchors = anchors_from_code_refs( - edge.id.as_deref().unwrap_or("edge"), - &edge.code_refs, - repo_root, - ); - let grounded = edge.code_refs.is_empty() || anchors.is_ok(); - EpiphanyMemoryEdge { - id, - source_id, - target_id, - kind: memory_edge_kind(edge.kind.as_str()), - profile, - claim: graph_edge_claim(edge), - anchors: anchors.unwrap_or_default(), - lifecycle: if grounded { - EpiphanyMemoryLifecycle::Accepted - } else { - EpiphanyMemoryLifecycle::Stale - }, - confidence: if grounded { 80 } else { 0 }, - } -} - -fn import_graph_links( - links: &[EpiphanyGraphLink], - node_map: &HashMap, - edges: &mut Vec, - repo_root: &Path, -) { - for link in links { - let Some(source_id) = node_map.get(&link.dataflow_node_id).cloned() else { - continue; - }; - let Some(target_id) = node_map.get(&link.architecture_node_id).cloned() else { - continue; - }; - let anchors = anchors_from_code_refs("link", &link.code_refs, repo_root); - let grounded = link.code_refs.is_empty() || anchors.is_ok(); - edges.push(EpiphanyMemoryEdge { - id: memory_graph_edge_id( - &source_id, - &target_id, - "grounds", - link.code_refs.iter().map(code_ref_key), - ), - source_id, - target_id, - kind: EpiphanyMemoryEdgeKind::Grounds, - profile: EpiphanyMemoryProfile::RepoDataflow, - claim: link - .relationship - .clone() - .unwrap_or_else(|| "Dataflow node is linked to architecture node.".to_string()), - anchors: anchors.unwrap_or_default(), - lifecycle: if grounded { - EpiphanyMemoryLifecycle::Accepted - } else { - EpiphanyMemoryLifecycle::Stale - }, - confidence: if grounded { 75 } else { 0 }, - }); - } -} - -fn repo_summary( - id: &str, - domain_id: &str, - target: &str, - claim: &str, - nodes: &[EpiphanyMemoryNode], - edges: &[EpiphanyMemoryEdge], - profile: EpiphanyMemoryProfile, -) -> EpiphanyMemorySummary { - let covers_node_ids = nodes - .iter() - .filter(|node| node.profile == profile) - .map(|node| node.id.clone()) - .collect::>(); - let covers_edge_ids = edges - .iter() - .filter(|edge| edge.profile == profile) - .map(|edge| edge.id.clone()) - .collect::>(); - let anchor_count = nodes - .iter() - .filter(|node| node.profile == profile) - .map(|node| node.anchors.len() as u32) - .sum(); - EpiphanyMemorySummary { - id: id.to_string(), - domain_id: domain_id.to_string(), - covers_node_ids, - covers_edge_ids, - target: target.to_string(), - claim: claim.to_string(), - question: "Which profile producer should refresh this accepted graph next?".to_string(), - tension: String::new(), - action_implication: "Use this summary for broad repo context; descend when exact nodes are stale or relevant.".to_string(), - anchor_count, - freshness: EpiphanyMemoryFreshnessStatus::Ready, - confidence: 80, - ..Default::default() - } -} - -fn anchors_from_code_refs( - prefix: &str, - code_refs: &[EpiphanyCodeRef], - repo_root: &Path, -) -> Result> { - code_refs - .iter() - .enumerate() - .map(|(index, code_ref)| { - let bytes = fs::read(repo_root.join(&code_ref.path)) - .with_context(|| format!("missing source anchor {}", code_ref.path.display()))?; - let digest = format!("sha256:{:x}", Sha256::digest(&bytes)); - Ok(EpiphanyMemoryAnchor { - id: format!("anchor-{prefix}-{index}"), - kind: "code_ref".to_string(), - target: code_ref_key(code_ref), - code_ref: Some(code_ref.clone()), - source_hash: Some(digest), - ..Default::default() - }) - }) - .collect() -} - -fn code_ref_key(code_ref: &EpiphanyCodeRef) -> String { - let mut key = code_ref.path.to_string_lossy().replace('\\', "/"); - if let Some(symbol) = code_ref.symbol.as_deref() { - key.push('#'); - key.push_str(symbol); - } - key -} - -fn graph_node_claim(node: &EpiphanyGraphNode) -> String { - if !node.purpose.trim().is_empty() { - return node.purpose.clone(); - } - format!("Accepted graph node {} exists.", node.id) -} - -fn graph_edge_claim(edge: &EpiphanyGraphEdge) -> String { - edge.mechanism - .clone() - .or_else(|| edge.label.clone()) - .unwrap_or_else(|| { - format!( - "Accepted graph edge {} connects {} to {}.", - edge.kind, edge.source_id, edge.target_id - ) - }) -} - -fn graph_status_lifecycle(status: Option<&str>) -> EpiphanyMemoryLifecycle { - match status.unwrap_or_default() { - "observed" => EpiphanyMemoryLifecycle::Observed, - "proposed" => EpiphanyMemoryLifecycle::Proposed, - "stale" => EpiphanyMemoryLifecycle::Stale, - "retired" => EpiphanyMemoryLifecycle::Retired, - _ => EpiphanyMemoryLifecycle::Accepted, - } -} - -fn memory_node_kind(node: &EpiphanyGraphNode) -> EpiphanyMemoryNodeKind { - let text = format!("{} {}", node.title, node.purpose).to_lowercase(); - if text.contains("schema") { - EpiphanyMemoryNodeKind::Schema - } else if text.contains("runtime") || text.contains("contract") { - EpiphanyMemoryNodeKind::RuntimeContract - } else if text.contains("adapter") || text.contains("bridge") { - EpiphanyMemoryNodeKind::Adapter - } else if text.contains("test") || text.contains("smoke") { - EpiphanyMemoryNodeKind::TestSeam - } else if text.contains("state") || text.contains("store") { - EpiphanyMemoryNodeKind::StateStore - } else { - EpiphanyMemoryNodeKind::Module - } -} - -fn memory_edge_kind(kind: &str) -> EpiphanyMemoryEdgeKind { - match kind { - "owns" => EpiphanyMemoryEdgeKind::Owns, - "reads" => EpiphanyMemoryEdgeKind::Reads, - "writes" => EpiphanyMemoryEdgeKind::Writes, - "derives" => EpiphanyMemoryEdgeKind::Derives, - "adapts" => EpiphanyMemoryEdgeKind::Adapts, - "persists" => EpiphanyMemoryEdgeKind::Persists, - "launches" => EpiphanyMemoryEdgeKind::Launches, - "verifies" | "tests" => EpiphanyMemoryEdgeKind::Verifies, - "depends_on" | "depends" => EpiphanyMemoryEdgeKind::DependsOn, - _ => EpiphanyMemoryEdgeKind::Other, - } -} diff --git a/epiphany-core/src/reasoning_context.rs b/epiphany-core/src/reasoning_context.rs index bc680eca..acf031a7 100644 --- a/epiphany-core/src/reasoning_context.rs +++ b/epiphany-core/src/reasoning_context.rs @@ -1293,7 +1293,6 @@ mod tests { 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, diff --git a/epiphany-core/src/runtime_spine.rs b/epiphany-core/src/runtime_spine.rs index 9c30dca2..475e7347 100644 --- a/epiphany-core/src/runtime_spine.rs +++ b/epiphany-core/src/runtime_spine.rs @@ -134,10 +134,6 @@ use epiphany_openai_adapter::EpiphanyOpenAiModelReceipt; use epiphany_openai_adapter::EpiphanyOpenAiModelRequest; use epiphany_openai_adapter::EpiphanyOpenAiStreamEvent; use epiphany_openai_adapter::EpiphanyOpenAiStreamPayload; -use epiphany_state_model::EpiphanyJobBinding; -use epiphany_state_model::EpiphanyJobKind; -use epiphany_state_model::EpiphanyRuntimeLink; -use epiphany_state_model::EpiphanyThreadState; use epiphany_tool_adapter::EpiphanyToolCapability; use epiphany_tool_adapter::EpiphanyToolInvocationIntent; use epiphany_tool_adapter::EpiphanyToolInvocationReceipt; @@ -1023,77 +1019,6 @@ pub struct PreparedRuntimeSpineHeartbeatJob { pub envelopes: Vec, } -#[derive(Clone, Debug, PartialEq)] -pub struct RuntimeSpineHeartbeatLaunchPlanOptions { - pub binding_id: String, - pub kind: EpiphanyJobKind, - pub scope: String, - pub owner_role: String, - pub authority_scope: String, - pub linked_subgoal_ids: Vec, - pub linked_graph_node_ids: Vec, - pub instruction: String, - pub launch_document: EpiphanyWorkerLaunchDocument, - pub output_contract_id: String, - pub organ_launch_contract: EpiphanyLaunchOrganContract, - pub max_runtime_seconds: Option, - pub runtime_job_id: String, -} - -#[derive(Clone, Debug, PartialEq)] -pub struct RuntimeSpineHeartbeatLaunchPlan { - pub binding: EpiphanyJobBinding, - pub runtime_link: EpiphanyRuntimeLink, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct EpiphanyJobLaunchRequest { - pub expected_revision: Option, - pub binding_id: String, - pub kind: EpiphanyJobKind, - pub scope: String, - pub owner_role: String, - pub authority_scope: String, - pub linked_subgoal_ids: Vec, - pub linked_graph_node_ids: Vec, - pub instruction: String, - pub launch_document: EpiphanyWorkerLaunchDocument, - pub output_contract_id: String, - pub organ_launch_contract: EpiphanyLaunchOrganContract, - pub max_runtime_seconds: Option, - pub proposal_modeling_request_id: Option, - pub frontier_planning_request_id: Option, - pub frontier_plan_mind_request_id: Option, - pub imagination_consideration_request_id: Option, - pub admitted_model_direction_consideration_request_id: Option, - pub repo_frontier_modeling_request_id: Option, - pub repo_frontier_research_request_id: Option, - pub repo_frontier_verification_request_id: Option, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct EpiphanyJobLaunchResult { - pub epiphany_state: EpiphanyThreadState, - pub binding_id: String, - pub launcher_job_id: String, - pub backend_job_id: String, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct EpiphanyJobInterruptRequest { - pub expected_revision: Option, - pub binding_id: String, - pub reason: Option, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct EpiphanyJobInterruptResult { - pub epiphany_state: EpiphanyThreadState, - pub binding_id: String, - pub cancel_requested: bool, - pub interrupted_thread_ids: Vec, -} - #[derive(Clone, Debug, PartialEq)] pub struct EpiphanyRuntimeJobSnapshot { pub job: EpiphanyRuntimeJob, @@ -3055,64 +2980,6 @@ where Ok(archive) } -pub fn plan_runtime_spine_heartbeat_launch( - state: &EpiphanyThreadState, - options: RuntimeSpineHeartbeatLaunchPlanOptions, -) -> Result { - validate_heartbeat_launch_options(state, &options)?; - Ok(RuntimeSpineHeartbeatLaunchPlan { - binding: EpiphanyJobBinding { - id: options.binding_id.clone(), - kind: options.kind, - scope: options.scope.clone(), - owner_role: options.owner_role.clone(), - authority_scope: Some(options.authority_scope.clone()), - linked_subgoal_ids: options.linked_subgoal_ids.clone(), - linked_graph_node_ids: options.linked_graph_node_ids.clone(), - blocking_reason: None, - }, - runtime_link: EpiphanyRuntimeLink { - id: format!( - "runtime-link-{}-{}", - options.binding_id, options.runtime_job_id - ), - binding_id: options.binding_id, - surface: "jobLaunch".to_string(), - role_id: options.owner_role, - authority_scope: options.authority_scope, - runtime_job_id: options.runtime_job_id, - runtime_result_id: None, - linked_subgoal_ids: options.linked_subgoal_ids, - linked_graph_node_ids: options.linked_graph_node_ids, - }, - }) -} - -pub fn replace_or_append_epiphany_job_binding( - mut bindings: Vec, - replacement: EpiphanyJobBinding, -) -> Vec { - if let Some(existing) = bindings - .iter_mut() - .find(|binding| binding.id == replacement.id) - { - *existing = replacement; - return bindings; - } - bindings.push(replacement); - bindings -} - -pub fn clear_epiphany_job_binding_backend( - mut bindings: Vec, - binding_index: usize, - blocking_reason: &str, -) -> Vec { - let binding = &mut bindings[binding_index]; - binding.blocking_reason = Some(blocking_reason.to_string()); - bindings -} - pub fn open_runtime_spine_heartbeat_job( store_path: impl AsRef, options: RuntimeSpineHeartbeatJobOptions, @@ -13856,76 +13723,6 @@ where .transpose() } -fn validate_heartbeat_launch_options( - state: &EpiphanyThreadState, - options: &RuntimeSpineHeartbeatLaunchPlanOptions, -) -> Result<()> { - validate_non_empty(&options.binding_id, "epiphany job launch binding_id")?; - if matches!( - options.binding_id.as_str(), - "retrieval-index" | "graph-remap" | "verification" - ) { - return Err(anyhow!( - "epiphany job launch binding_id {:?} is reserved for a derived built-in slot", - options.binding_id - )); - } - if options.kind != EpiphanyJobKind::Specialist { - return Err(anyhow!( - "epiphany job launch currently supports only specialist heartbeat turns" - )); - } - validate_non_empty(&options.scope, "epiphany job launch scope")?; - validate_non_empty(&options.owner_role, "epiphany job launch owner_role")?; - validate_non_empty( - &options.authority_scope, - "epiphany job launch authority_scope", - )?; - validate_non_empty(&options.instruction, "epiphany job launch instruction")?; - validate_non_empty( - options.launch_document.thread_id(), - "epiphany job launch document thread id", - )?; - validate_non_empty( - &options.output_contract_id, - "epiphany job launch output_contract_id", - )?; - if options.output_contract_id != options.launch_document.output_contract_id() { - return Err(anyhow!( - "epiphany job launch output_contract_id must match the typed launch document" - )); - } - validate_launch_organ_contract( - &options.organ_launch_contract, - &options.authority_scope, - options.launch_document.document_kind(), - &options.output_contract_id, - )?; - if let Some(max_runtime_seconds) = options.max_runtime_seconds - && max_runtime_seconds == 0 - { - return Err(anyhow!( - "epiphany job launch max_runtime_seconds must be >= 1" - )); - } - let existing_binding = state - .job_bindings - .iter() - .find(|binding| binding.id == options.binding_id); - let latest_runtime_link = state.runtime_links.iter().find(|link| { - link.binding_id == options.binding_id && !link.runtime_job_id.trim().is_empty() - }); - if latest_runtime_link.is_some_and(|link| link.runtime_result_id.is_none()) - && existing_binding.is_none_or(|binding| binding.blocking_reason.is_none()) - { - return Err(anyhow!( - "epiphany job binding {:?} is already bound to an active heartbeat turn; interrupt it before launching a replacement", - options.binding_id - )); - } - Ok(()) -} - fn terminal_status_for_verdict(verdict: &str) -> EpiphanyRuntimeJobStatus { if matches!( verdict, diff --git a/epiphany-core/src/substrate_gate.rs b/epiphany-core/src/substrate_gate.rs index 5f2f4d56..758d4edc 100644 --- a/epiphany-core/src/substrate_gate.rs +++ b/epiphany-core/src/substrate_gate.rs @@ -1,4 +1,3 @@ -use crate::EpiphanyJobLaunchRequest; use cultcache_rs::DatabaseEntry; use serde::Deserialize; use serde::Serialize; @@ -139,23 +138,6 @@ pub fn default_substrate_gate_cultnet_contracts() -> Vec SubstrateGateRepoAccessGrantReceipt { - substrate_gate_repo_access_grant_for_worker( - receipt_id, - runtime_job_id, - request.binding_id.clone(), - request.owner_role.clone(), - request.authority_scope.clone(), - request.binding_id == crate::EPIPHANY_RESEARCH_ROLE_BINDING_ID, - granted_at, - ) -} - pub fn substrate_gate_repo_access_grant_for_worker( receipt_id: String, runtime_job_id: String, @@ -277,68 +259,14 @@ mod tests { } #[test] - fn substrate_gate_grant_for_launch_is_read_only() { - let request = EpiphanyJobLaunchRequest { - expected_revision: Some(7), - binding_id: "research-source-gather-worker".to_string(), - kind: epiphany_state_model::EpiphanyJobKind::Specialist, - scope: "research".to_string(), - owner_role: "epiphany-eyes".to_string(), - authority_scope: "epiphany.role.research".to_string(), - linked_subgoal_ids: Vec::new(), - linked_graph_node_ids: Vec::new(), - instruction: "Gather source evidence.".to_string(), - launch_document: crate::EpiphanyWorkerLaunchDocument::Role( - crate::EpiphanyRoleWorkerLaunchDocument { - thread_id: "thread-1".to_string(), - role_id: "research".to_string(), - state_revision: 7, - objective: None, - dynamic_prompt_context: None, - repository_body_observation_basis: None, - proposal_modeling_context: None, - frontier_verdict_modeling_context: None, - frontier_planning_context: None, - frontier_research_context: None, - frontier_verification_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, - }, - ), - output_contract_id: crate::ROLE_WORKER_OUTPUT_CONTRACT_ID.to_string(), - organ_launch_contract: crate::default_launch_organ_contract( - "epiphany.role.research", - "role", - crate::ROLE_WORKER_OUTPUT_CONTRACT_ID, - ), - max_runtime_seconds: None, - proposal_modeling_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_verification_request_id: None, - }; - let grant = substrate_gate_repo_access_grant_for_launch( + fn substrate_gate_grant_for_worker_is_read_only() { + let grant = substrate_gate_repo_access_grant_for_worker( "grant-1".to_string(), "job-1".to_string(), - &request, + "research-source-gather-worker".to_string(), + "epiphany-eyes".to_string(), + "epiphany.role.research".to_string(), + true, "2026-05-30T00:00:00Z".to_string(), ); assert!(grant.granted_operations.contains(&"read".to_string())); diff --git a/epiphany-core/src/surfaces/freshness.rs b/epiphany-core/src/surfaces/freshness.rs deleted file mode 100644 index 7ac2d43f..00000000 --- a/epiphany-core/src/surfaces/freshness.rs +++ /dev/null @@ -1,638 +0,0 @@ -use epiphany_state_model::EpiphanyRetrievalState; -use epiphany_state_model::EpiphanyRetrievalStatus; -use epiphany_state_model::EpiphanyThreadState; -use serde::Deserialize; -use serde::Serialize; -use std::collections::HashSet; -use std::path::Path; -use std::path::PathBuf; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum EpiphanyRetrievalFreshnessStatus { - Missing, - Ready, - Stale, - Indexing, - Unavailable, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct EpiphanyRetrievalFreshness { - pub status: EpiphanyRetrievalFreshnessStatus, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub semantic_available: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub last_indexed_at_unix_seconds: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub indexed_file_count: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub indexed_chunk_count: Option, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub dirty_paths: Vec, - pub note: String, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum EpiphanyGraphFreshnessStatus { - Missing, - Stale, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct EpiphanyGraphFreshness { - pub status: EpiphanyGraphFreshnessStatus, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub checkpoint_id: Option, - pub dirty_path_count: u32, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub dirty_paths: Vec, - pub open_question_count: u32, - pub open_gap_count: u32, - pub note: String, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum EpiphanyInvalidationStatus { - Unavailable, - Clean, - Changed, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct EpiphanyInvalidationInput { - pub status: EpiphanyInvalidationStatus, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub watched_root: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub observed_at_unix_seconds: Option, - pub changed_path_count: u32, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub changed_paths: Vec, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub graph_node_ids: Vec, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub active_frontier_node_ids: Vec, - pub note: String, -} - -#[derive(Debug, Clone, Copy)] -pub struct EpiphanyFreshnessWatcherInput<'a> { - pub available: bool, - pub workspace_root: Option<&'a Path>, - pub observed_at_unix_seconds: Option, - pub changed_paths: &'a [PathBuf], -} - -#[derive(Debug, Clone, Copy)] -pub struct EpiphanyFreshnessInput<'a> { - pub state: Option<&'a EpiphanyThreadState>, - pub retrieval_override: Option<&'a EpiphanyRetrievalState>, - pub watcher: Option>, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct EpiphanyFreshnessView { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub state_revision: Option, - pub retrieval: EpiphanyRetrievalFreshness, - pub graph: EpiphanyGraphFreshness, - pub watcher: EpiphanyInvalidationInput, -} - -pub fn derive_freshness(input: EpiphanyFreshnessInput<'_>) -> EpiphanyFreshnessView { - let retrieval = retrieval_state_for_reflection(input.state, input.retrieval_override); - EpiphanyFreshnessView { - state_revision: input.state.map(|state| state.revision), - retrieval: retrieval_freshness(retrieval), - graph: graph_freshness(input.state), - watcher: invalidation_input(input.state, input.watcher), - } -} - -fn retrieval_state_for_reflection<'a>( - state: Option<&'a EpiphanyThreadState>, - retrieval_override: Option<&'a EpiphanyRetrievalState>, -) -> Option<&'a EpiphanyRetrievalState> { - retrieval_override.or_else(|| state.and_then(|state| state.retrieval.as_ref())) -} - -fn retrieval_freshness(retrieval: Option<&EpiphanyRetrievalState>) -> EpiphanyRetrievalFreshness { - let Some(retrieval) = retrieval else { - return EpiphanyRetrievalFreshness { - status: EpiphanyRetrievalFreshnessStatus::Missing, - semantic_available: None, - last_indexed_at_unix_seconds: None, - indexed_file_count: None, - indexed_chunk_count: None, - dirty_paths: Vec::new(), - note: "No retrieval freshness is available for this thread view.".to_string(), - }; - }; - - let dirty_path_count = retrieval.dirty_paths.len(); - let note = match retrieval.status { - EpiphanyRetrievalStatus::Ready if dirty_path_count == 0 => { - "Legacy retrieval reports Ready, but has no Body-bound coverage receipt.".to_string() - } - EpiphanyRetrievalStatus::Ready => { - format!("Retrieval catalog is stale because {dirty_path_count} dirty path(s) remain.") - } - EpiphanyRetrievalStatus::Stale => { - format!("Retrieval catalog is stale; {dirty_path_count} dirty path(s) need refresh.") - } - EpiphanyRetrievalStatus::Indexing => "Retrieval catalog is indexing.".to_string(), - EpiphanyRetrievalStatus::Unavailable => "Retrieval catalog is unavailable.".to_string(), - }; - - EpiphanyRetrievalFreshness { - status: match retrieval.status { - EpiphanyRetrievalStatus::Ready if dirty_path_count == 0 => { - EpiphanyRetrievalFreshnessStatus::Missing - } - EpiphanyRetrievalStatus::Ready => EpiphanyRetrievalFreshnessStatus::Stale, - EpiphanyRetrievalStatus::Stale => EpiphanyRetrievalFreshnessStatus::Stale, - EpiphanyRetrievalStatus::Indexing => EpiphanyRetrievalFreshnessStatus::Indexing, - EpiphanyRetrievalStatus::Unavailable => EpiphanyRetrievalFreshnessStatus::Unavailable, - }, - semantic_available: Some(retrieval.semantic_available), - last_indexed_at_unix_seconds: retrieval.last_indexed_at_unix_seconds, - indexed_file_count: retrieval.indexed_file_count, - indexed_chunk_count: retrieval.indexed_chunk_count, - dirty_paths: retrieval.dirty_paths.clone(), - note, - } -} - -pub(super) fn graph_freshness(state: Option<&EpiphanyThreadState>) -> EpiphanyGraphFreshness { - let Some(state) = state else { - return EpiphanyGraphFreshness { - status: EpiphanyGraphFreshnessStatus::Missing, - checkpoint_id: None, - dirty_path_count: 0, - dirty_paths: Vec::new(), - open_question_count: 0, - open_gap_count: 0, - note: "Epiphany state is missing, so graph freshness cannot be assessed.".to_string(), - }; - }; - - let frontier = state.graph_frontier.as_ref(); - let dirty_paths = frontier - .map(|frontier| frontier.dirty_paths.clone()) - .unwrap_or_default(); - let dirty_path_count = dirty_paths.len() as u32; - let open_question_count = frontier - .map(|frontier| frontier.open_question_ids.len() as u32) - .unwrap_or_default(); - let open_gap_count = frontier - .map(|frontier| frontier.open_gap_ids.len() as u32) - .unwrap_or_default(); - let has_pressure = dirty_path_count > 0 || open_question_count > 0 || open_gap_count > 0; - let checkpoint_id = state - .graph_checkpoint - .as_ref() - .map(|checkpoint| checkpoint.checkpoint_id.clone()); - let status = if has_pressure { - EpiphanyGraphFreshnessStatus::Stale - } else { - EpiphanyGraphFreshnessStatus::Missing - }; - let note = if status == EpiphanyGraphFreshnessStatus::Stale { - format!( - "Graph freshness is stale; frontier has {dirty_path_count} dirty path(s), {open_question_count} open question id(s), and {open_gap_count} open gap id(s)." - ) - } else { - "Canonical RepoModel admission freshness is not available to this legacy thread projection; legacy checkpoint and churn hints cannot prove readiness." - .to_string() - }; - - EpiphanyGraphFreshness { - status, - checkpoint_id, - dirty_path_count, - dirty_paths, - open_question_count, - open_gap_count, - note, - } -} - -fn invalidation_input( - state: Option<&EpiphanyThreadState>, - watcher: Option>, -) -> EpiphanyInvalidationInput { - let Some(watcher) = watcher else { - return EpiphanyInvalidationInput { - status: EpiphanyInvalidationStatus::Unavailable, - watched_root: None, - observed_at_unix_seconds: None, - changed_path_count: 0, - changed_paths: Vec::new(), - graph_node_ids: Vec::new(), - active_frontier_node_ids: Vec::new(), - note: "Watcher-backed invalidation inputs are only available for loaded threads." - .to_string(), - }; - }; - - if !watcher.available { - return EpiphanyInvalidationInput { - status: EpiphanyInvalidationStatus::Unavailable, - watched_root: None, - observed_at_unix_seconds: None, - changed_path_count: 0, - changed_paths: Vec::new(), - graph_node_ids: Vec::new(), - active_frontier_node_ids: Vec::new(), - note: "The workspace watcher is unavailable for this app-server process.".to_string(), - }; - } - - let changed_paths = watcher.changed_paths.to_vec(); - let changed_path_count = changed_paths.len() as u32; - let watched_root = watcher.workspace_root.map(Path::to_path_buf); - - if changed_paths.is_empty() { - return EpiphanyInvalidationInput { - status: EpiphanyInvalidationStatus::Unavailable, - watched_root, - observed_at_unix_seconds: watcher.observed_at_unix_seconds, - changed_path_count, - changed_paths, - graph_node_ids: Vec::new(), - active_frontier_node_ids: Vec::new(), - note: "Watcher supplied no observation; absence of changed paths is not proof of a clean workspace." - .to_string(), - }; - } - - let graph_node_ids = state - .map(|state| { - graph_node_ids_for_changed_paths(state, &changed_paths, watcher.workspace_root) - }) - .unwrap_or_default(); - let active_frontier_node_ids = state - .map(|state| { - active_frontier_node_ids_for_changed_paths( - state, - &changed_paths, - watcher.workspace_root, - ) - }) - .unwrap_or_default(); - let note = if graph_node_ids.is_empty() { - format!( - "Watcher observed {changed_path_count} recent changed path(s), but no mapped graph node code refs matched yet." - ) - } else if active_frontier_node_ids.is_empty() { - format!( - "Watcher observed {changed_path_count} recent changed path(s) touching {} mapped graph node(s).", - graph_node_ids.len() - ) - } else { - format!( - "Watcher observed {changed_path_count} recent changed path(s) touching {} mapped graph node(s), including {} active frontier node(s).", - graph_node_ids.len(), - active_frontier_node_ids.len() - ) - }; - - EpiphanyInvalidationInput { - status: EpiphanyInvalidationStatus::Changed, - watched_root, - observed_at_unix_seconds: watcher.observed_at_unix_seconds, - changed_path_count, - changed_paths, - graph_node_ids, - active_frontier_node_ids, - note, - } -} - -fn graph_node_ids_for_changed_paths( - state: &EpiphanyThreadState, - changed_paths: &[PathBuf], - workspace_root: Option<&Path>, -) -> Vec { - let changed_path_keys: HashSet = changed_paths - .iter() - .map(|path| epiphany_path_key(path.as_path())) - .collect(); - let mut node_ids = Vec::new(); - let mut seen = HashSet::new(); - - for node in state - .graphs - .architecture - .nodes - .iter() - .chain(state.graphs.dataflow.nodes.iter()) - { - let matches_changed_path = node.code_refs.iter().any(|code_ref| { - changed_path_keys.contains(&code_ref_path_key( - Path::new(&code_ref.path), - workspace_root, - )) - }); - if matches_changed_path && seen.insert(node.id.as_str()) { - node_ids.push(node.id.clone()); - } - } - - node_ids -} - -fn active_frontier_node_ids_for_changed_paths( - state: &EpiphanyThreadState, - changed_paths: &[PathBuf], - workspace_root: Option<&Path>, -) -> Vec { - let graph_node_ids = graph_node_ids_for_changed_paths(state, changed_paths, workspace_root); - let frontier_node_ids = state - .graph_frontier - .as_ref() - .map(|frontier| frontier.active_node_ids.as_slice()) - .unwrap_or_default(); - let graph_node_ids: HashSet<&str> = graph_node_ids.iter().map(String::as_str).collect(); - - frontier_node_ids - .iter() - .filter(|node_id| graph_node_ids.contains(node_id.as_str())) - .cloned() - .collect() -} - -fn code_ref_path_key(path: &Path, workspace_root: Option<&Path>) -> String { - if let Some(workspace_root) = workspace_root - && let Ok(relative_path) = path.strip_prefix(workspace_root) - { - return epiphany_path_key(relative_path); - } - epiphany_path_key(path) -} - -fn epiphany_path_key(path: &Path) -> String { - path.to_string_lossy() - .replace('\\', "/") - .trim_start_matches("./") - .to_string() -} - -#[cfg(test)] -mod tests { - use super::*; - use epiphany_state_model::EpiphanyCodeRef; - use epiphany_state_model::EpiphanyGraphCheckpoint; - use epiphany_state_model::EpiphanyGraphFrontier; - use epiphany_state_model::EpiphanyGraphNode; - use epiphany_state_model::EpiphanyGraphs; - - #[test] - fn missing_state_reports_missing_freshness() { - let view = derive_freshness(EpiphanyFreshnessInput { - state: None, - retrieval_override: None, - watcher: None, - }); - - assert_eq!(view.state_revision, None); - assert_eq!( - view.retrieval.status, - EpiphanyRetrievalFreshnessStatus::Missing - ); - assert_eq!(view.graph.status, EpiphanyGraphFreshnessStatus::Missing); - assert_eq!(view.watcher.status, EpiphanyInvalidationStatus::Unavailable); - } - - #[test] - fn default_state_is_not_graph_ready() { - let state = EpiphanyThreadState::default(); - let view = derive_freshness(EpiphanyFreshnessInput { - state: Some(&state), - retrieval_override: None, - watcher: None, - }); - - assert_eq!(view.graph.status, EpiphanyGraphFreshnessStatus::Missing); - } - - #[test] - fn legacy_checkpoint_cannot_authorize_graph_ready() { - let state = EpiphanyThreadState { - graph_checkpoint: Some(EpiphanyGraphCheckpoint { - checkpoint_id: "graph-1".to_string(), - ..Default::default() - }), - ..Default::default() - }; - let view = derive_freshness(EpiphanyFreshnessInput { - state: Some(&state), - retrieval_override: None, - watcher: None, - }); - - assert_eq!(view.graph.status, EpiphanyGraphFreshnessStatus::Missing); - assert!(view.graph.note.contains("Canonical RepoModel admission")); - } - - #[test] - fn legacy_checkpoint_alone_is_missing() { - let checkpoint_only = EpiphanyThreadState { - graph_checkpoint: Some(EpiphanyGraphCheckpoint { - checkpoint_id: "graph-1".to_string(), - ..Default::default() - }), - ..Default::default() - }; - let view = derive_freshness(EpiphanyFreshnessInput { - state: Some(&checkpoint_only), - retrieval_override: None, - watcher: None, - }); - - assert_eq!(view.graph.status, EpiphanyGraphFreshnessStatus::Missing); - } - - #[test] - fn graph_pressure_makes_legacy_checkpoint_stale() { - let state = EpiphanyThreadState { - graph_checkpoint: Some(EpiphanyGraphCheckpoint { - checkpoint_id: "graph-1".to_string(), - ..Default::default() - }), - graph_frontier: Some(EpiphanyGraphFrontier { - open_question_ids: vec!["q-1".to_string()], - ..Default::default() - }), - ..Default::default() - }; - let view = derive_freshness(EpiphanyFreshnessInput { - state: Some(&state), - retrieval_override: None, - watcher: None, - }); - - assert_eq!(view.graph.status, EpiphanyGraphFreshnessStatus::Stale); - } - - #[test] - fn available_watcher_without_observation_is_not_clean() { - let changed_paths = Vec::new(); - let view = derive_freshness(EpiphanyFreshnessInput { - state: None, - retrieval_override: None, - watcher: Some(EpiphanyFreshnessWatcherInput { - available: true, - workspace_root: None, - observed_at_unix_seconds: None, - changed_paths: &changed_paths, - }), - }); - - assert_eq!(view.watcher.status, EpiphanyInvalidationStatus::Unavailable); - } - - #[test] - fn stale_retrieval_and_frontier_make_graph_stale() { - let state = EpiphanyThreadState { - revision: 7, - retrieval: Some(EpiphanyRetrievalState { - workspace_root: PathBuf::from("E:/repo"), - status: EpiphanyRetrievalStatus::Stale, - semantic_available: true, - indexed_file_count: Some(12), - dirty_paths: vec![PathBuf::from("src/lib.rs")], - ..Default::default() - }), - graph_frontier: Some(EpiphanyGraphFrontier { - dirty_paths: vec![PathBuf::from("src/lib.rs")], - open_gap_ids: vec!["gap-1".to_string()], - ..Default::default() - }), - ..Default::default() - }; - - let view = derive_freshness(EpiphanyFreshnessInput { - state: Some(&state), - retrieval_override: None, - watcher: None, - }); - - assert_eq!(view.state_revision, Some(7)); - assert_eq!( - view.retrieval.status, - EpiphanyRetrievalFreshnessStatus::Stale - ); - assert_eq!(view.retrieval.indexed_file_count, Some(12)); - assert_eq!(view.graph.status, EpiphanyGraphFreshnessStatus::Stale); - assert_eq!(view.graph.open_gap_count, 1); - } - - #[test] - fn ready_retrieval_with_dirty_paths_is_stale() { - let state = EpiphanyThreadState { - retrieval: Some(EpiphanyRetrievalState { - workspace_root: PathBuf::from("E:/repo"), - status: EpiphanyRetrievalStatus::Ready, - dirty_paths: vec![PathBuf::from("src/dirty.rs")], - ..Default::default() - }), - ..Default::default() - }; - - let view = derive_freshness(EpiphanyFreshnessInput { - state: Some(&state), - retrieval_override: None, - watcher: None, - }); - - assert_eq!( - view.retrieval.status, - EpiphanyRetrievalFreshnessStatus::Stale - ); - } - - #[test] - fn legacy_ready_retrieval_cannot_mint_body_coverage() { - let state = EpiphanyThreadState { - retrieval: Some(EpiphanyRetrievalState { - workspace_root: PathBuf::from("E:/repo"), - status: EpiphanyRetrievalStatus::Ready, - semantic_available: true, - dirty_paths: Vec::new(), - ..Default::default() - }), - ..Default::default() - }; - - let view = derive_freshness(EpiphanyFreshnessInput { - state: Some(&state), - retrieval_override: None, - watcher: None, - }); - - assert_eq!( - view.retrieval.status, - EpiphanyRetrievalFreshnessStatus::Missing - ); - assert!(view.retrieval.note.contains("Body-bound coverage receipt")); - } - - #[test] - fn watcher_maps_changed_paths_to_graph_and_frontier() { - let state = EpiphanyThreadState { - graph_frontier: Some(EpiphanyGraphFrontier { - active_node_ids: vec!["node-1".to_string()], - ..Default::default() - }), - graphs: EpiphanyGraphs { - architecture: epiphany_state_model::EpiphanyGraph { - nodes: vec![EpiphanyGraphNode { - id: "node-1".to_string(), - title: "node".to_string(), - purpose: "module".to_string(), - mechanism: None, - metaphor: None, - status: None, - code_refs: vec![EpiphanyCodeRef { - path: PathBuf::from("src/lib.rs"), - start_line: None, - end_line: None, - symbol: None, - note: None, - }], - }], - ..Default::default() - }, - ..Default::default() - }, - ..Default::default() - }; - let changed_paths = vec![PathBuf::from("./src/lib.rs")]; - - let view = derive_freshness(EpiphanyFreshnessInput { - state: Some(&state), - retrieval_override: None, - watcher: Some(EpiphanyFreshnessWatcherInput { - available: true, - workspace_root: None, - observed_at_unix_seconds: Some(42), - changed_paths: &changed_paths, - }), - }); - - assert_eq!(view.watcher.status, EpiphanyInvalidationStatus::Changed); - assert_eq!(view.watcher.graph_node_ids, vec!["node-1".to_string()]); - assert_eq!( - view.watcher.active_frontier_node_ids, - vec!["node-1".to_string()] - ); - } -} diff --git a/epiphany-core/src/surfaces/graph_context.rs b/epiphany-core/src/surfaces/graph_context.rs deleted file mode 100644 index d6cca172..00000000 --- a/epiphany-core/src/surfaces/graph_context.rs +++ /dev/null @@ -1,760 +0,0 @@ -use epiphany_state_model::EpiphanyCodeRef; -use epiphany_state_model::EpiphanyEvidenceRecord; -use epiphany_state_model::EpiphanyGraphCheckpoint; -use epiphany_state_model::EpiphanyGraphEdge; -use epiphany_state_model::EpiphanyGraphFrontier; -use epiphany_state_model::EpiphanyGraphLink; -use epiphany_state_model::EpiphanyGraphNode; -use epiphany_state_model::EpiphanyInvestigationCheckpoint; -use epiphany_state_model::EpiphanyObservation; -use epiphany_state_model::EpiphanyThreadState; -use std::collections::HashSet; -use std::path::PathBuf; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum EpiphanyContextStateStatus { - Missing, - Ready, -} - -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct EpiphanyGraphContext { - pub architecture_nodes: Vec, - pub architecture_edges: Vec, - pub dataflow_nodes: Vec, - pub dataflow_edges: Vec, - pub links: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct EpiphanyContext { - pub graph: EpiphanyGraphContext, - pub frontier: Option, - pub checkpoint: Option, - pub investigation_checkpoint: Option, - pub observations: Vec, - pub evidence: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct EpiphanyContextMissing { - pub graph_node_ids: Vec, - pub graph_edge_ids: Vec, - pub observation_ids: Vec, - pub evidence_ids: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct EpiphanyContextParams { - pub graph_node_ids: Vec, - pub graph_edge_ids: Vec, - pub observation_ids: Vec, - pub evidence_ids: Vec, - pub include_active_frontier: Option, - pub include_linked_evidence: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct EpiphanyContextView { - pub state_status: EpiphanyContextStateStatus, - pub state_revision: Option, - pub context: EpiphanyContext, - pub missing: EpiphanyContextMissing, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum EpiphanyGraphQueryKind { - Node, - Path, - FrontierNeighborhood, - Neighbors, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum EpiphanyGraphQueryDirection { - Incoming, - Outgoing, - Both, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct EpiphanyGraphQuery { - pub kind: EpiphanyGraphQueryKind, - pub node_ids: Vec, - pub edge_ids: Vec, - pub paths: Vec, - pub symbols: Vec, - pub edge_kinds: Vec, - pub direction: Option, - pub depth: Option, - pub include_links: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct EpiphanyGraphQueryMatched { - pub node_ids: Vec, - pub edge_ids: Vec, - pub paths: Vec, - pub symbols: Vec, - pub edge_kinds: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct EpiphanyGraphQueryMissing { - pub node_ids: Vec, - pub edge_ids: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct EpiphanyGraphQueryView { - pub state_status: EpiphanyContextStateStatus, - pub state_revision: Option, - pub graph: EpiphanyGraphContext, - pub frontier: Option, - pub checkpoint: Option, - pub matched: EpiphanyGraphQueryMatched, - pub missing: EpiphanyGraphQueryMissing, -} - -pub fn derive_context( - state: Option<&EpiphanyThreadState>, - params: &EpiphanyContextParams, -) -> EpiphanyContextView { - let include_active_frontier = params.include_active_frontier.unwrap_or(true); - let include_linked_evidence = params.include_linked_evidence.unwrap_or(true); - let Some(state) = state else { - return EpiphanyContextView { - state_status: EpiphanyContextStateStatus::Missing, - state_revision: None, - context: EpiphanyContext::default(), - missing: EpiphanyContextMissing { - graph_node_ids: unique_strings(params.graph_node_ids.iter().cloned()), - graph_edge_ids: unique_strings(params.graph_edge_ids.iter().cloned()), - observation_ids: unique_strings(params.observation_ids.iter().cloned()), - evidence_ids: unique_strings(params.evidence_ids.iter().cloned()), - }, - }; - }; - - let mut graph_node_ids = unique_strings(params.graph_node_ids.iter().cloned()); - let mut graph_edge_ids = unique_strings(params.graph_edge_ids.iter().cloned()); - if include_active_frontier && let Some(frontier) = state.graph_frontier.as_ref() { - extend_unique_strings( - &mut graph_node_ids, - frontier.active_node_ids.iter().cloned(), - ); - extend_unique_strings( - &mut graph_edge_ids, - frontier.active_edge_ids.iter().cloned(), - ); - } - - let graph_node_id_set: HashSet<&str> = graph_node_ids.iter().map(String::as_str).collect(); - let graph_edge_id_set: HashSet<&str> = graph_edge_ids.iter().map(String::as_str).collect(); - - let architecture_nodes = state - .graphs - .architecture - .nodes - .iter() - .filter(|node| graph_node_id_set.contains(node.id.as_str())) - .cloned() - .collect::>(); - let dataflow_nodes = state - .graphs - .dataflow - .nodes - .iter() - .filter(|node| graph_node_id_set.contains(node.id.as_str())) - .cloned() - .collect::>(); - - let found_node_ids: HashSet = architecture_nodes - .iter() - .chain(dataflow_nodes.iter()) - .map(|node| node.id.clone()) - .collect(); - - let architecture_edges = select_graph_edges( - &state.graphs.architecture.edges, - &graph_node_id_set, - &graph_edge_id_set, - ); - let dataflow_edges = select_graph_edges( - &state.graphs.dataflow.edges, - &graph_node_id_set, - &graph_edge_id_set, - ); - - let found_edge_ids: HashSet = architecture_edges - .iter() - .chain(dataflow_edges.iter()) - .filter_map(|edge| edge.id.clone()) - .filter(|id| graph_edge_id_set.contains(id.as_str())) - .collect(); - - let links = state - .graphs - .links - .iter() - .filter(|link| { - graph_node_id_set.contains(link.architecture_node_id.as_str()) - || graph_node_id_set.contains(link.dataflow_node_id.as_str()) - }) - .cloned() - .collect(); - - let observation_ids = unique_strings(params.observation_ids.iter().cloned()); - let observation_id_set: HashSet<&str> = observation_ids.iter().map(String::as_str).collect(); - let observations = state - .observations - .iter() - .filter(|observation| observation_id_set.contains(observation.id.as_str())) - .cloned() - .collect::>(); - let found_observation_ids: HashSet = observations - .iter() - .map(|observation| observation.id.clone()) - .collect(); - - let mut evidence_ids = unique_strings(params.evidence_ids.iter().cloned()); - if include_linked_evidence { - for observation in &observations { - extend_unique_strings(&mut evidence_ids, observation.evidence_ids.iter().cloned()); - } - } - let evidence_id_set: HashSet<&str> = evidence_ids.iter().map(String::as_str).collect(); - let evidence = state - .recent_evidence - .iter() - .filter(|evidence| evidence_id_set.contains(evidence.id.as_str())) - .cloned() - .collect::>(); - let found_evidence_ids: HashSet = evidence - .iter() - .map(|evidence| evidence.id.clone()) - .collect(); - - EpiphanyContextView { - state_status: EpiphanyContextStateStatus::Ready, - state_revision: Some(state.revision), - context: EpiphanyContext { - graph: EpiphanyGraphContext { - architecture_nodes, - architecture_edges, - dataflow_nodes, - dataflow_edges, - links, - }, - frontier: include_active_frontier - .then(|| state.graph_frontier.clone()) - .flatten(), - checkpoint: state.graph_checkpoint.clone(), - investigation_checkpoint: state.investigation_checkpoint.clone(), - observations, - evidence, - }, - missing: EpiphanyContextMissing { - graph_node_ids: graph_node_ids - .iter() - .filter(|id| !found_node_ids.contains(*id)) - .cloned() - .collect(), - graph_edge_ids: graph_edge_ids - .iter() - .filter(|id| !found_edge_ids.contains(*id)) - .cloned() - .collect(), - observation_ids: observation_ids - .iter() - .filter(|id| !found_observation_ids.contains(*id)) - .cloned() - .collect(), - evidence_ids: evidence_ids - .iter() - .filter(|id| !found_evidence_ids.contains(*id)) - .cloned() - .collect(), - }, - } -} - -pub fn derive_graph_query( - state: Option<&EpiphanyThreadState>, - query: &EpiphanyGraphQuery, -) -> EpiphanyGraphQueryView { - let Some(state) = state else { - return EpiphanyGraphQueryView { - state_status: EpiphanyContextStateStatus::Missing, - state_revision: None, - graph: EpiphanyGraphContext::default(), - frontier: None, - checkpoint: None, - matched: EpiphanyGraphQueryMatched::default(), - missing: EpiphanyGraphQueryMissing { - node_ids: unique_strings(query.node_ids.iter().cloned()), - edge_ids: unique_strings(query.edge_ids.iter().cloned()), - }, - }; - }; - - let mut node_ids = unique_strings(query.node_ids.iter().cloned()); - let mut edge_ids = unique_strings(query.edge_ids.iter().cloned()); - let mut matched_paths = Vec::new(); - let mut matched_symbols = Vec::new(); - let mut matched_edge_kinds = Vec::new(); - - match query.kind { - EpiphanyGraphQueryKind::Node => {} - EpiphanyGraphQueryKind::Path => { - collect_graph_query_path_matches( - state, - query, - &mut node_ids, - &mut edge_ids, - &mut matched_paths, - &mut matched_symbols, - ); - } - EpiphanyGraphQueryKind::FrontierNeighborhood => { - if let Some(frontier) = state.graph_frontier.as_ref() { - extend_unique_strings(&mut node_ids, frontier.active_node_ids.iter().cloned()); - extend_unique_strings(&mut edge_ids, frontier.active_edge_ids.iter().cloned()); - } - expand_graph_query_neighbors(state, query, &mut node_ids, &mut edge_ids); - } - EpiphanyGraphQueryKind::Neighbors => { - expand_graph_query_neighbors(state, query, &mut node_ids, &mut edge_ids); - } - } - - collect_graph_query_edge_kind_matches( - state, - query, - &mut node_ids, - &mut edge_ids, - &mut matched_edge_kinds, - ); - - if query.include_links.unwrap_or(true) { - expand_graph_query_links(state, &mut node_ids); - } - - let node_id_set: HashSet<&str> = node_ids.iter().map(String::as_str).collect(); - let edge_id_set: HashSet<&str> = edge_ids.iter().map(String::as_str).collect(); - - let architecture_nodes = state - .graphs - .architecture - .nodes - .iter() - .filter(|node| node_id_set.contains(node.id.as_str())) - .cloned() - .collect::>(); - let dataflow_nodes = state - .graphs - .dataflow - .nodes - .iter() - .filter(|node| node_id_set.contains(node.id.as_str())) - .cloned() - .collect::>(); - let found_node_ids: HashSet = architecture_nodes - .iter() - .chain(dataflow_nodes.iter()) - .map(|node| node.id.clone()) - .collect(); - let matched_node_ids = architecture_nodes - .iter() - .chain(dataflow_nodes.iter()) - .map(|node| node.id.clone()) - .collect::>(); - - let architecture_edges = - select_graph_edges(&state.graphs.architecture.edges, &node_id_set, &edge_id_set); - let dataflow_edges = - select_graph_edges(&state.graphs.dataflow.edges, &node_id_set, &edge_id_set); - let found_edge_ids: HashSet = architecture_edges - .iter() - .chain(dataflow_edges.iter()) - .filter_map(|edge| edge.id.clone()) - .collect(); - let matched_edge_ids = architecture_edges - .iter() - .chain(dataflow_edges.iter()) - .filter_map(|edge| edge.id.clone()) - .collect::>(); - - let links = state - .graphs - .links - .iter() - .filter(|link| { - node_id_set.contains(link.architecture_node_id.as_str()) - || node_id_set.contains(link.dataflow_node_id.as_str()) - }) - .cloned() - .collect::>(); - - EpiphanyGraphQueryView { - state_status: EpiphanyContextStateStatus::Ready, - state_revision: Some(state.revision), - graph: EpiphanyGraphContext { - architecture_nodes, - architecture_edges, - dataflow_nodes, - dataflow_edges, - links, - }, - frontier: state.graph_frontier.clone(), - checkpoint: state.graph_checkpoint.clone(), - matched: EpiphanyGraphQueryMatched { - node_ids: matched_node_ids, - edge_ids: matched_edge_ids, - paths: matched_paths, - symbols: matched_symbols, - edge_kinds: matched_edge_kinds, - }, - missing: EpiphanyGraphQueryMissing { - node_ids: node_ids - .iter() - .filter(|id| !found_node_ids.contains(*id)) - .cloned() - .collect(), - edge_ids: edge_ids - .iter() - .filter(|id| !found_edge_ids.contains(*id)) - .cloned() - .collect(), - }, - } -} - -fn collect_graph_query_path_matches( - state: &EpiphanyThreadState, - query: &EpiphanyGraphQuery, - node_ids: &mut Vec, - edge_ids: &mut Vec, - matched_paths: &mut Vec, - matched_symbols: &mut Vec, -) { - if query.paths.is_empty() && query.symbols.is_empty() { - return; - } - for node in state - .graphs - .architecture - .nodes - .iter() - .chain(state.graphs.dataflow.nodes.iter()) - { - if code_refs_match_query(&node.code_refs, query, matched_paths, matched_symbols) { - extend_unique_strings(node_ids, [node.id.clone()]); - } - } - for edge in state - .graphs - .architecture - .edges - .iter() - .chain(state.graphs.dataflow.edges.iter()) - { - if code_refs_match_query(&edge.code_refs, query, matched_paths, matched_symbols) { - if let Some(id) = edge.id.clone() { - extend_unique_strings(edge_ids, [id]); - } - extend_unique_strings(node_ids, [edge.source_id.clone(), edge.target_id.clone()]); - } - } - for link in &state.graphs.links { - if code_refs_match_query(&link.code_refs, query, matched_paths, matched_symbols) { - extend_unique_strings( - node_ids, - [ - link.dataflow_node_id.clone(), - link.architecture_node_id.clone(), - ], - ); - } - } -} - -fn collect_graph_query_edge_kind_matches( - state: &EpiphanyThreadState, - query: &EpiphanyGraphQuery, - node_ids: &mut Vec, - edge_ids: &mut Vec, - matched_edge_kinds: &mut Vec, -) { - if query.edge_kinds.is_empty() { - return; - } - let wanted: HashSet<&str> = query.edge_kinds.iter().map(String::as_str).collect(); - for edge in state - .graphs - .architecture - .edges - .iter() - .chain(state.graphs.dataflow.edges.iter()) - { - if wanted.contains(edge.kind.as_str()) { - extend_unique_strings(matched_edge_kinds, [edge.kind.clone()]); - if let Some(id) = edge.id.clone() { - extend_unique_strings(edge_ids, [id]); - } - extend_unique_strings(node_ids, [edge.source_id.clone(), edge.target_id.clone()]); - } - } -} - -fn expand_graph_query_neighbors( - state: &EpiphanyThreadState, - query: &EpiphanyGraphQuery, - node_ids: &mut Vec, - edge_ids: &mut Vec, -) { - let direction = query.direction.unwrap_or(EpiphanyGraphQueryDirection::Both); - let depth = query.depth.unwrap_or(1).clamp(1, 3); - for _ in 0..depth { - let current: HashSet = node_ids.iter().cloned().collect(); - let before = node_ids.len(); - for edge in state - .graphs - .architecture - .edges - .iter() - .chain(state.graphs.dataflow.edges.iter()) - { - let source_selected = current.contains(&edge.source_id); - let target_selected = current.contains(&edge.target_id); - let include_outgoing = matches!( - direction, - EpiphanyGraphQueryDirection::Outgoing | EpiphanyGraphQueryDirection::Both - ) && source_selected; - let include_incoming = matches!( - direction, - EpiphanyGraphQueryDirection::Incoming | EpiphanyGraphQueryDirection::Both - ) && target_selected; - if include_outgoing || include_incoming { - if let Some(id) = edge.id.clone() { - extend_unique_strings(edge_ids, [id]); - } - } - if include_outgoing { - extend_unique_strings(node_ids, [edge.target_id.clone()]); - } - if include_incoming { - extend_unique_strings(node_ids, [edge.source_id.clone()]); - } - } - if node_ids.len() == before { - break; - } - } -} - -fn expand_graph_query_links(state: &EpiphanyThreadState, node_ids: &mut Vec) { - let current: HashSet<&str> = node_ids.iter().map(String::as_str).collect(); - let mut linked = Vec::new(); - for link in &state.graphs.links { - if current.contains(link.dataflow_node_id.as_str()) { - linked.push(link.architecture_node_id.clone()); - } - if current.contains(link.architecture_node_id.as_str()) { - linked.push(link.dataflow_node_id.clone()); - } - } - extend_unique_strings(node_ids, linked); -} - -fn code_refs_match_query( - code_refs: &[EpiphanyCodeRef], - query: &EpiphanyGraphQuery, - matched_paths: &mut Vec, - matched_symbols: &mut Vec, -) -> bool { - code_refs.iter().any(|code_ref| { - let path_matches = query.paths.is_empty() - || query.paths.iter().any(|path| { - code_ref.path == *path - || code_ref.path.ends_with(path) - || path.ends_with(code_ref.path.as_path()) - }); - let symbol_matches = query.symbols.is_empty() - || code_ref - .symbol - .as_deref() - .is_some_and(|symbol| query.symbols.iter().any(|wanted| wanted == symbol)); - if path_matches && symbol_matches { - if !query.paths.is_empty() { - extend_unique_paths(matched_paths, [code_ref.path.clone()]); - } - if let Some(symbol) = code_ref.symbol.clone() - && !query.symbols.is_empty() - { - extend_unique_strings(matched_symbols, [symbol]); - } - true - } else { - false - } - }) -} - -fn extend_unique_paths(target: &mut Vec, values: impl IntoIterator) { - for value in values { - if !target.iter().any(|existing| existing == &value) { - target.push(value); - } - } -} - -fn select_graph_edges( - edges: &[EpiphanyGraphEdge], - graph_node_id_set: &HashSet<&str>, - graph_edge_id_set: &HashSet<&str>, -) -> Vec { - edges - .iter() - .filter(|edge| { - edge.id - .as_deref() - .is_some_and(|id| graph_edge_id_set.contains(id)) - || graph_node_id_set.contains(edge.source_id.as_str()) - || graph_node_id_set.contains(edge.target_id.as_str()) - }) - .cloned() - .collect() -} - -fn unique_strings(values: impl IntoIterator) -> Vec { - let mut unique = Vec::new(); - extend_unique_strings(&mut unique, values); - unique -} - -fn extend_unique_strings(target: &mut Vec, values: impl IntoIterator) { - for value in values { - if !target.iter().any(|existing| existing == &value) { - target.push(value); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn graph_state() -> EpiphanyThreadState { - EpiphanyThreadState { - revision: 9, - graphs: epiphany_state_model::EpiphanyGraphs { - architecture: epiphany_state_model::EpiphanyGraph { - nodes: vec![ - EpiphanyGraphNode { - id: "a".to_string(), - title: "A".to_string(), - purpose: "source".to_string(), - mechanism: None, - metaphor: None, - status: None, - code_refs: vec![EpiphanyCodeRef { - path: PathBuf::from("src/a.rs"), - start_line: None, - end_line: None, - symbol: Some("Alpha".to_string()), - note: None, - }], - }, - EpiphanyGraphNode { - id: "b".to_string(), - title: "B".to_string(), - purpose: "target".to_string(), - mechanism: None, - metaphor: None, - status: None, - code_refs: Vec::new(), - }, - ], - edges: vec![EpiphanyGraphEdge { - source_id: "a".to_string(), - target_id: "b".to_string(), - kind: "calls".to_string(), - id: Some("edge-1".to_string()), - label: None, - mechanism: None, - code_refs: Vec::new(), - }], - }, - ..Default::default() - }, - graph_frontier: Some(EpiphanyGraphFrontier { - active_node_ids: vec!["a".to_string()], - ..Default::default() - }), - ..Default::default() - } - } - - #[test] - fn context_includes_active_frontier() { - let state = graph_state(); - let view = derive_context( - Some(&state), - &EpiphanyContextParams { - include_active_frontier: Some(true), - ..Default::default() - }, - ); - - assert_eq!(view.state_status, EpiphanyContextStateStatus::Ready); - assert_eq!(view.state_revision, Some(9)); - assert_eq!(view.context.graph.architecture_nodes[0].id, "a"); - assert!(view.missing.graph_node_ids.is_empty()); - } - - #[test] - fn graph_query_expands_neighbors() { - let state = graph_state(); - let view = derive_graph_query( - Some(&state), - &EpiphanyGraphQuery { - kind: EpiphanyGraphQueryKind::Neighbors, - node_ids: vec!["a".to_string()], - edge_ids: Vec::new(), - paths: Vec::new(), - symbols: Vec::new(), - edge_kinds: Vec::new(), - direction: Some(EpiphanyGraphQueryDirection::Outgoing), - depth: Some(1), - include_links: Some(false), - }, - ); - - assert_eq!(view.state_status, EpiphanyContextStateStatus::Ready); - assert_eq!(view.graph.architecture_nodes.len(), 2); - assert_eq!(view.matched.edge_ids, vec!["edge-1".to_string()]); - } - - #[test] - fn graph_query_matches_paths_and_symbols() { - let state = graph_state(); - let view = derive_graph_query( - Some(&state), - &EpiphanyGraphQuery { - kind: EpiphanyGraphQueryKind::Path, - node_ids: Vec::new(), - edge_ids: Vec::new(), - paths: vec![PathBuf::from("src/a.rs")], - symbols: vec!["Alpha".to_string()], - edge_kinds: Vec::new(), - direction: None, - depth: None, - include_links: Some(false), - }, - ); - - assert_eq!(view.matched.node_ids, vec!["a".to_string()]); - assert_eq!(view.matched.symbols, vec!["Alpha".to_string()]); - } -} diff --git a/epiphany-core/src/surfaces/mod.rs b/epiphany-core/src/surfaces/mod.rs index f64ee3ff..f84fd0c0 100644 --- a/epiphany-core/src/surfaces/mod.rs +++ b/epiphany-core/src/surfaces/mod.rs @@ -1,7 +1,5 @@ mod coordinator_contract; mod crrc; -mod freshness; -mod graph_context; mod jobs; mod planning; mod pressure; @@ -22,30 +20,6 @@ pub use crrc::EpiphanyCrrcResultStatus; pub use crrc::EpiphanyCrrcSceneAction; pub use crrc::EpiphanyCrrcStateStatus; pub use crrc::recommend_crrc_action; -pub use freshness::EpiphanyFreshnessInput; -pub use freshness::EpiphanyFreshnessView; -pub use freshness::EpiphanyFreshnessWatcherInput; -pub use freshness::EpiphanyGraphFreshness; -pub use freshness::EpiphanyGraphFreshnessStatus; -pub use freshness::EpiphanyInvalidationInput; -pub use freshness::EpiphanyInvalidationStatus; -pub use freshness::EpiphanyRetrievalFreshness; -pub use freshness::EpiphanyRetrievalFreshnessStatus; -pub use freshness::derive_freshness; -pub use graph_context::EpiphanyContext; -pub use graph_context::EpiphanyContextMissing; -pub use graph_context::EpiphanyContextParams; -pub use graph_context::EpiphanyContextStateStatus; -pub use graph_context::EpiphanyContextView; -pub use graph_context::EpiphanyGraphContext; -pub use graph_context::EpiphanyGraphQuery; -pub use graph_context::EpiphanyGraphQueryDirection; -pub use graph_context::EpiphanyGraphQueryKind; -pub use graph_context::EpiphanyGraphQueryMatched; -pub use graph_context::EpiphanyGraphQueryMissing; -pub use graph_context::EpiphanyGraphQueryView; -pub use graph_context::derive_context; -pub use graph_context::derive_graph_query; pub use jobs::EpiphanyJobStatus; pub use jobs::EpiphanyJobView; pub use jobs::EpiphanyJobsInput; diff --git a/epiphany-core/src/surfaces/worker_launch.rs b/epiphany-core/src/surfaces/worker_launch.rs index 3a033fbe..7d71a7e9 100644 --- a/epiphany-core/src/surfaces/worker_launch.rs +++ b/epiphany-core/src/surfaces/worker_launch.rs @@ -87,7 +87,6 @@ impl EpiphanyWorkerLaunchDocument { pub struct EpiphanyRoleWorkerLaunchDocument { pub thread_id: String, pub role_id: String, - pub state_revision: u64, #[serde(default, skip_serializing_if = "Option::is_none")] pub objective: Option, #[serde(default, skip_serializing_if = "Option::is_none")] diff --git a/epiphany-openai-runtime/src/bin/epiphany-openai-runtime.rs b/epiphany-openai-runtime/src/bin/epiphany-openai-runtime.rs index 9c9cd64b..e67715ae 100644 --- a/epiphany-openai-runtime/src/bin/epiphany-openai-runtime.rs +++ b/epiphany-openai-runtime/src/bin/epiphany-openai-runtime.rs @@ -1538,39 +1538,13 @@ mod tests { store: &Path, launch: &epiphany_core::EpiphanyRuntimeWorkerLaunchRequest, ) -> Result<()> { - let request = epiphany_core::EpiphanyJobLaunchRequest { - expected_revision: None, - binding_id: launch.binding_id.clone(), - kind: epiphany_state_model::EpiphanyJobKind::Specialist, - scope: launch.authority_scope.clone(), - owner_role: launch.role.clone(), - authority_scope: launch.authority_scope.clone(), - linked_subgoal_ids: Vec::new(), - linked_graph_node_ids: Vec::new(), - instruction: launch.instruction.clone(), - launch_document: launch.launch_document()?, - output_contract_id: launch.output_contract_id.clone(), - organ_launch_contract: launch.organ_launch_contract.clone(), - max_runtime_seconds: None, - proposal_modeling_request_id: launch.proposal_modeling_request_id.clone(), - frontier_planning_request_id: launch.frontier_planning_request_id.clone(), - frontier_plan_mind_request_id: launch.frontier_plan_mind_request_id.clone(), - imagination_consideration_request_id: launch - .imagination_consideration_request_id - .clone(), - admitted_model_direction_consideration_request_id: launch - .admitted_model_direction_consideration_request_id - .clone(), - repo_frontier_modeling_request_id: launch.repo_frontier_modeling_request_id.clone(), - repo_frontier_research_request_id: launch.repo_frontier_research_request_id.clone(), - repo_frontier_verification_request_id: launch - .repo_frontier_verification_request_id - .clone(), - }; - let grant = epiphany_core::substrate_gate_repo_access_grant_for_launch( + let grant = epiphany_core::substrate_gate_repo_access_grant_for_worker( format!("substrate-grant-{}", launch.job_id), launch.job_id.clone(), - &request, + launch.binding_id.clone(), + launch.role.clone(), + launch.authority_scope.clone(), + launch.binding_id == epiphany_core::EPIPHANY_RESEARCH_ROLE_BINDING_ID, now(), ); epiphany_core::put_substrate_gate_repo_access_grant_receipt(store, &grant)?; @@ -1598,7 +1572,6 @@ mod tests { epiphany_core::EpiphanyRoleWorkerLaunchDocument { thread_id: "thread-1".to_string(), role_id: "verification".to_string(), - state_revision: 1, objective: Some("Verify the worker loop.".to_string()), dynamic_prompt_context: None, repository_body_observation_basis: None, @@ -1711,7 +1684,6 @@ mod tests { epiphany_core::EpiphanyRoleWorkerLaunchDocument { thread_id: "thread-1".to_string(), role_id: "verification".to_string(), - state_revision: 1, objective: Some("Verify runtime error sealing.".to_string()), dynamic_prompt_context: None, repository_body_observation_basis: None, @@ -1848,7 +1820,6 @@ mod tests { epiphany_core::EpiphanyRoleWorkerLaunchDocument { thread_id: "thread-1".to_string(), role_id: "verification".to_string(), - state_revision: 1, objective: Some("Verify the worker loop ceiling.".to_string()), dynamic_prompt_context: None, repository_body_observation_basis: None, diff --git a/epiphany-openai-runtime/src/lib.rs b/epiphany-openai-runtime/src/lib.rs index 14929e36..bd99bf9b 100644 --- a/epiphany-openai-runtime/src/lib.rs +++ b/epiphany-openai-runtime/src/lib.rs @@ -3008,7 +3008,6 @@ mod tests { EpiphanyWorkerLaunchDocument::Role(epiphany_core::EpiphanyRoleWorkerLaunchDocument { thread_id: "thread-1".into(), role_id: "modeling".into(), - state_revision: 1, objective: Some("obsolete aggregate objective must not cross inference".into()), dynamic_prompt_context: None, repository_body_observation_basis: None, @@ -3418,7 +3417,6 @@ mod tests { EpiphanyWorkerLaunchDocument::Role(epiphany_core::EpiphanyRoleWorkerLaunchDocument { thread_id: "thread-1".into(), role_id: "imagination".into(), - state_revision: 1, objective: Some("Plan one frontier.".into()), dynamic_prompt_context: None, repository_body_observation_basis: None, @@ -3913,7 +3911,6 @@ mod tests { epiphany_core::EpiphanyRoleWorkerLaunchDocument { thread_id: "thread-1".to_string(), role_id: "modeling".to_string(), - state_revision: 1, objective: Some("Map the machine.".to_string()), dynamic_prompt_context: None, repository_body_observation_basis: Some(body_basis.clone()), @@ -4203,7 +4200,6 @@ mod tests { epiphany_core::EpiphanyRoleWorkerLaunchDocument { thread_id: "thread-1".to_string(), role_id: "verification".to_string(), - state_revision: 1, objective: Some("Verify Hands receipts.".to_string()), dynamic_prompt_context: Some( "hands receipts" diff --git a/epiphany-state-model/src/lib.rs b/epiphany-state-model/src/lib.rs index bea69597..c504e8be 100644 --- a/epiphany-state-model/src/lib.rs +++ b/epiphany-state-model/src/lib.rs @@ -5,79 +5,6 @@ use serde::Deserialize; use serde::Serialize; use ts_rs::TS; -mod prompt; - -pub use prompt::render_epiphany_state; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, TS)] -pub struct EpiphanyStateItem { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub turn_id: Option, - pub state: EpiphanyThreadState, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, TS, Default)] -pub struct EpiphanyThreadState { - pub revision: u64, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(type = "string | null")] - pub objective: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(type = "string | null")] - pub active_subgoal_id: Option, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - #[ts(type = "Array")] - pub subgoals: Vec, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - #[ts(type = "Array")] - pub invariants: Vec, - #[serde(default, skip_serializing_if = "EpiphanyGraphs::is_empty")] - #[ts(type = "EpiphanyGraphs")] - pub graphs: EpiphanyGraphs, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(type = "EpiphanyGraphFrontier | null")] - pub graph_frontier: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(type = "EpiphanyGraphCheckpoint | null")] - pub graph_checkpoint: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(type = "EpiphanyRetrievalState | null")] - pub retrieval: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(type = "EpiphanyScratchPad | null")] - pub scratch: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(type = "EpiphanyInvestigationCheckpoint | null")] - pub investigation_checkpoint: Option, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - #[ts(type = "Array")] - pub job_bindings: Vec, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - #[ts(type = "Array")] - pub acceptance_receipts: Vec, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - #[ts(type = "Array")] - pub runtime_links: Vec, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - #[ts(type = "Array")] - pub observations: Vec, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - #[ts(type = "Array")] - pub recent_evidence: Vec, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(type = "EpiphanyChurnState | null")] - pub churn: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(type = "EpiphanyModeState | null")] - pub mode: Option, - #[serde(default, skip_serializing_if = "EpiphanyPlanningState::is_empty")] - #[ts(type = "EpiphanyPlanningState")] - pub planning: EpiphanyPlanningState, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(type = "string | null")] - pub last_updated_turn_id: Option, -} - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, TS, Default)] pub struct EpiphanyAcceptanceReceipt { pub id: String, diff --git a/epiphany-state-model/src/prompt.rs b/epiphany-state-model/src/prompt.rs deleted file mode 100644 index 3af3ae30..00000000 --- a/epiphany-state-model/src/prompt.rs +++ /dev/null @@ -1,938 +0,0 @@ -use crate::EpiphanyChurnState; -use crate::EpiphanyCodeRef; -use crate::EpiphanyEvidenceRecord; -use crate::EpiphanyGraph; -use crate::EpiphanyGraphCheckpoint; -use crate::EpiphanyGraphEdge; -use crate::EpiphanyGraphFrontier; -use crate::EpiphanyGraphLink; -use crate::EpiphanyGraphNode; -use crate::EpiphanyInvestigationCheckpoint; -use crate::EpiphanyInvestigationDisposition; -use crate::EpiphanyModeState; -use crate::EpiphanyObservation; -use crate::EpiphanyPlanningState; -use crate::EpiphanyScratchPad; -use crate::EpiphanyThreadState; -use std::collections::HashSet; -use std::path::Path; - -const SUBGOAL_LIMIT: usize = 4; -const INVARIANT_LIMIT: usize = 4; -const NODE_LIMIT: usize = 3; -const EDGE_LIMIT: usize = 2; -const LINK_LIMIT: usize = 3; -const OBSERVATION_LIMIT: usize = 3; -const EVIDENCE_LIMIT: usize = 3; -const PLANNING_LIMIT: usize = 3; -const CODE_REF_LIMIT: usize = 2; -const DIRTY_PATH_LIMIT: usize = 4; -const EPIPHANY_STATE_INTRO: &str = include_str!("prompts/epiphany_state_intro.md"); -const EPIPHANY_DOCTRINE_SECTION: &str = include_str!("prompts/epiphany_doctrine.md"); - -pub fn render_epiphany_state(state: &EpiphanyThreadState) -> String { - let mut sections = vec![EPIPHANY_STATE_INTRO.trim_end().to_string()]; - - sections.push(render_overview(state)); - sections.push(EPIPHANY_DOCTRINE_SECTION.trim_end().to_string()); - - if let Some(planning) = render_planning(&state.planning) { - sections.push(planning); - } - if let Some(subgoals) = render_subgoals(state) { - sections.push(subgoals); - } - if let Some(invariants) = render_invariants(state) { - sections.push(invariants); - } - if let Some(graphs) = render_graphs(state) { - sections.push(graphs); - } - if let Some(checkpoint) = - render_investigation_checkpoint(state.investigation_checkpoint.as_ref()) - { - sections.push(checkpoint); - } - if let Some(scratch) = render_scratch(state.scratch.as_ref()) { - sections.push(scratch); - } - if let Some(observations) = render_observations(&state.observations) { - sections.push(observations); - } - if let Some(evidence) = render_evidence(&state.recent_evidence) { - sections.push(evidence); - } - if let Some(churn) = render_churn(state.churn.as_ref()) { - sections.push(churn); - } - if let Some(mode) = render_mode(state.mode.as_ref()) { - sections.push(mode); - } - - format!("{}\n", sections.join("\n\n")) -} - -fn render_overview(state: &EpiphanyThreadState) -> String { - let mut lines = vec![format!("- Revision: {}", state.revision)]; - if let Some(objective) = state.objective.as_deref() { - lines.push(format!("- Objective: {}", compact_text(objective))); - } - if let Some(active_subgoal_id) = state.active_subgoal_id.as_deref() { - lines.push(format!("- Active subgoal: `{active_subgoal_id}`")); - } - if let Some(last_updated_turn_id) = state.last_updated_turn_id.as_deref() { - lines.push(format!("- Last updated turn: `{last_updated_turn_id}`")); - } - if let Some(frontier) = state.graph_frontier.as_ref() { - let focus_ids = collect_focus_node_ids(Some(frontier), state.graph_checkpoint.as_ref()); - if !focus_ids.is_empty() { - lines.push(format!( - "- Frontier focus: {}", - render_inline_ids(&focus_ids) - )); - } - } - format!("## Overview\n{}", lines.join("\n")) -} - -fn render_subgoals(state: &EpiphanyThreadState) -> Option { - if state.subgoals.is_empty() { - return None; - } - - let mut lines = Vec::new(); - for subgoal in state.subgoals.iter().take(SUBGOAL_LIMIT) { - let active_suffix = state - .active_subgoal_id - .as_deref() - .filter(|active_id| *active_id == subgoal.id) - .map(|_| "; active") - .unwrap_or(""); - lines.push(format!( - "- `{}` [{}{}]: {}", - subgoal.id, - subgoal.status, - active_suffix, - compact_text(&subgoal.title) - )); - if let Some(summary) = subgoal.summary.as_deref() { - lines.push(format!(" summary: {}", compact_text(summary))); - } - } - push_omitted_count(&mut lines, state.subgoals.len(), SUBGOAL_LIMIT, "subgoals"); - - Some(format!("## Subgoals\n{}", lines.join("\n"))) -} - -fn render_planning(planning: &EpiphanyPlanningState) -> Option { - if planning.is_empty() { - return None; - } - - let mut lines = vec![format!( - "- Counts: {} captures, {} backlog items, {} roadmap streams, {} objective drafts", - planning.captures.len(), - planning.backlog_items.len(), - planning.roadmap_streams.len(), - planning.objective_drafts.len() - )]; - lines.push( - "- Boundary: planning records are not the active objective until explicit human adoption." - .to_string(), - ); - - for draft in planning.objective_drafts.iter().take(PLANNING_LIMIT) { - lines.push(format!( - "- Objective draft `{}` [{}]: {}", - draft.id, - draft.status, - compact_text(&draft.title) - )); - if !draft.acceptance_criteria.is_empty() { - lines.push(format!( - " acceptance: {}", - draft - .acceptance_criteria - .iter() - .take(2) - .map(|criterion| compact_text(criterion)) - .collect::>() - .join(" | ") - )); - } - } - push_omitted_count( - &mut lines, - planning.objective_drafts.len(), - PLANNING_LIMIT, - "objective drafts", - ); - - for item in planning.backlog_items.iter().take(PLANNING_LIMIT) { - lines.push(format!( - "- Backlog `{}` [{} / {} / {}]: {}", - item.id, - item.status, - item.priority.value, - item.horizon, - compact_text(&item.title) - )); - } - push_omitted_count( - &mut lines, - planning.backlog_items.len(), - PLANNING_LIMIT, - "backlog items", - ); - - for capture in planning.captures.iter().take(PLANNING_LIMIT) { - lines.push(format!( - "- Capture `{}` [{} via {}]: {}", - capture.id, - capture.status, - capture.source.kind, - compact_text(&capture.title) - )); - } - push_omitted_count( - &mut lines, - planning.captures.len(), - PLANNING_LIMIT, - "captures", - ); - - Some(format!("## Planning\n{}", lines.join("\n"))) -} - -fn render_invariants(state: &EpiphanyThreadState) -> Option { - if state.invariants.is_empty() { - return None; - } - - let mut lines = Vec::new(); - for invariant in state.invariants.iter().take(INVARIANT_LIMIT) { - lines.push(format!( - "- `{}` [{}]: {}", - invariant.id, - invariant.status, - compact_text(&invariant.description) - )); - if let Some(rationale) = invariant.rationale.as_deref() { - lines.push(format!(" rationale: {}", compact_text(rationale))); - } - } - push_omitted_count( - &mut lines, - state.invariants.len(), - INVARIANT_LIMIT, - "invariants", - ); - - Some(format!("## Invariants\n{}", lines.join("\n"))) -} - -fn render_graphs(state: &EpiphanyThreadState) -> Option { - if state.graphs.is_empty() && state.graph_frontier.is_none() && state.graph_checkpoint.is_none() - { - return None; - } - - let mut lines = Vec::new(); - let focus_node_ids = collect_focus_node_ids( - state.graph_frontier.as_ref(), - state.graph_checkpoint.as_ref(), - ); - - if let Some(frontier) = state.graph_frontier.as_ref() { - append_frontier_lines(&mut lines, frontier); - } - if let Some(checkpoint) = state.graph_checkpoint.as_ref() { - append_checkpoint_lines(&mut lines, checkpoint); - } - - append_graph_lines( - &mut lines, - "Architecture graph", - &state.graphs.architecture, - &focus_node_ids, - state.graph_frontier.as_ref(), - ); - append_graph_lines( - &mut lines, - "Dataflow graph", - &state.graphs.dataflow, - &focus_node_ids, - state.graph_frontier.as_ref(), - ); - - if !state.graphs.links.is_empty() { - for link in select_links(&state.graphs.links, &focus_node_ids) - .into_iter() - .take(LINK_LIMIT) - { - let relationship = link - .relationship - .as_deref() - .map(|value| format!(" ({})", compact_text(value))) - .unwrap_or_default(); - let code_refs = render_code_refs(&link.code_refs); - lines.push(format!( - "- Link: `{}` -> `{}`{}{}", - link.dataflow_node_id, - link.architecture_node_id, - relationship, - render_ref_suffix(&code_refs) - )); - } - push_omitted_count( - &mut lines, - state.graphs.links.len(), - LINK_LIMIT, - "graph links", - ); - } - - Some(format!("## Graphs\n{}", lines.join("\n"))) -} - -fn append_frontier_lines(lines: &mut Vec, frontier: &EpiphanyGraphFrontier) { - if !frontier.active_node_ids.is_empty() { - lines.push(format!( - "- Frontier active nodes: {}", - render_inline_ids(&frontier.active_node_ids) - )); - } - if !frontier.active_edge_ids.is_empty() { - lines.push(format!( - "- Frontier active edges: {}", - render_inline_ids(&frontier.active_edge_ids) - )); - } - if !frontier.open_question_ids.is_empty() { - lines.push(format!( - "- Open questions: {}", - render_inline_ids(&frontier.open_question_ids) - )); - } - if !frontier.open_gap_ids.is_empty() { - lines.push(format!( - "- Open gaps: {}", - render_inline_ids(&frontier.open_gap_ids) - )); - } - if !frontier.dirty_paths.is_empty() { - let dirty_paths = frontier - .dirty_paths - .iter() - .take(DIRTY_PATH_LIMIT) - .map(|path| format!("`{}`", normalize_path(path))) - .collect::>() - .join(", "); - let omitted_suffix = - omitted_suffix(frontier.dirty_paths.len(), DIRTY_PATH_LIMIT, "dirty paths"); - lines.push(format!("- Dirty paths: {dirty_paths}{omitted_suffix}")); - } -} - -fn append_checkpoint_lines(lines: &mut Vec, checkpoint: &EpiphanyGraphCheckpoint) { - lines.push(format!("- Checkpoint: `{}`", checkpoint.checkpoint_id)); - if let Some(summary) = checkpoint.summary.as_deref() { - lines.push(format!("- Checkpoint summary: {}", compact_text(summary))); - } - if !checkpoint.frontier_node_ids.is_empty() { - lines.push(format!( - "- Checkpoint frontier nodes: {}", - render_inline_ids(&checkpoint.frontier_node_ids) - )); - } - if !checkpoint.open_question_ids.is_empty() { - lines.push(format!( - "- Checkpoint open questions: {}", - render_inline_ids(&checkpoint.open_question_ids) - )); - } - if !checkpoint.open_gap_ids.is_empty() { - lines.push(format!( - "- Checkpoint open gaps: {}", - render_inline_ids(&checkpoint.open_gap_ids) - )); - } -} - -fn append_graph_lines( - lines: &mut Vec, - label: &str, - graph: &EpiphanyGraph, - focus_node_ids: &[String], - frontier: Option<&EpiphanyGraphFrontier>, -) { - if graph.is_empty() { - return; - } - - let selected_nodes = select_nodes(graph, focus_node_ids); - let selected_node_ids = selected_nodes - .iter() - .map(|node| node.id.as_str()) - .collect::>(); - let active_edge_ids = frontier - .map(|graph_frontier| { - graph_frontier - .active_edge_ids - .iter() - .map(String::as_str) - .collect::>() - }) - .unwrap_or_default(); - - lines.push(format!( - "- {label}: {} nodes, {} edges", - graph.nodes.len(), - graph.edges.len() - )); - - for node in &selected_nodes { - let status = node - .status - .as_deref() - .map(|value| format!(" [{}]", compact_text(value))) - .unwrap_or_default(); - lines.push(format!( - " - Focus node `{}`{}: {}", - node.id, - status, - compact_text(&node.title) - )); - lines.push(format!(" purpose: {}", compact_text(&node.purpose))); - if let Some(mechanism) = node.mechanism.as_deref() { - lines.push(format!(" mechanism: {}", compact_text(mechanism))); - } - if let Some(metaphor) = node.metaphor.as_deref() { - lines.push(format!(" metaphor: {}", compact_text(metaphor))); - } - let code_refs = render_code_refs(&node.code_refs); - if !code_refs.is_empty() { - lines.push(format!(" code refs: {}", code_refs.join(", "))); - } - } - push_omitted_count(lines, graph.nodes.len(), NODE_LIMIT, "graph nodes"); - - let selected_edges = select_edges(graph, &selected_node_ids, &active_edge_ids); - for edge in selected_edges.iter().take(EDGE_LIMIT) { - let edge_id = edge - .id - .as_deref() - .map(|value| format!(" `{value}`")) - .unwrap_or_default(); - let label_suffix = edge - .label - .as_deref() - .map(|value| format!(" [{}]", compact_text(value))) - .unwrap_or_default(); - let mechanism_suffix = edge - .mechanism - .as_deref() - .map(|value| format!("; {}", compact_text(value))) - .unwrap_or_default(); - let code_refs = render_code_refs(&edge.code_refs); - lines.push(format!( - " - Edge{edge_id}{label_suffix}: `{}` -> `{}` ({}){}{}", - edge.source_id, - edge.target_id, - edge.kind, - mechanism_suffix, - render_ref_suffix(&code_refs) - )); - } - push_omitted_count( - lines, - selected_edges.len(), - EDGE_LIMIT, - "focused graph edges", - ); -} - -fn render_scratch(scratch: Option<&EpiphanyScratchPad>) -> Option { - let scratch = scratch?; - let mut lines = Vec::new(); - if let Some(summary) = scratch.summary.as_deref() { - lines.push(format!("- Summary: {}", compact_text(summary))); - } - if let Some(hypothesis) = scratch.hypothesis.as_deref() { - lines.push(format!("- Hypothesis: {}", compact_text(hypothesis))); - } - if let Some(next_probe) = scratch.next_probe.as_deref() { - lines.push(format!("- Next probe: {}", compact_text(next_probe))); - } - - if lines.is_empty() { - None - } else { - Some(format!("## Scratch\n{}", lines.join("\n"))) - } -} - -fn render_investigation_checkpoint( - checkpoint: Option<&EpiphanyInvestigationCheckpoint>, -) -> Option { - let checkpoint = checkpoint?; - let mut lines = vec![ - format!("- Id: `{}`", checkpoint.checkpoint_id), - format!("- Kind: {}", compact_text(&checkpoint.kind)), - format!( - "- Disposition: {}", - match checkpoint.disposition { - EpiphanyInvestigationDisposition::ResumeReady => "resume_ready", - EpiphanyInvestigationDisposition::RegatherRequired => "regather_required", - } - ), - format!("- Focus: {}", compact_text(&checkpoint.focus)), - ]; - if let Some(summary) = checkpoint.summary.as_deref() { - lines.push(format!("- Summary: {}", compact_text(summary))); - } - if let Some(next_action) = checkpoint.next_action.as_deref() { - lines.push(format!("- Next action: {}", compact_text(next_action))); - } - if let Some(turn_id) = checkpoint.captured_at_turn_id.as_deref() { - lines.push(format!("- Captured at turn: `{turn_id}`")); - } - if !checkpoint.open_questions.is_empty() { - lines.push(format!( - "- Open questions: {}", - checkpoint - .open_questions - .iter() - .map(|question| compact_text(question)) - .collect::>() - .join(" | ") - )); - } - if !checkpoint.evidence_ids.is_empty() { - lines.push(format!( - "- Evidence ids: {}", - render_inline_ids(&checkpoint.evidence_ids) - )); - } - let code_refs = render_code_refs(&checkpoint.code_refs); - if !code_refs.is_empty() { - lines.push(format!("- Code refs: {}", code_refs.join(", "))); - } - - Some(format!("## Investigation Checkpoint\n{}", lines.join("\n"))) -} - -fn render_observations(observations: &[EpiphanyObservation]) -> Option { - if observations.is_empty() { - return None; - } - - let mut lines = Vec::new(); - for observation in observations.iter().take(OBSERVATION_LIMIT) { - lines.push(format!( - "- `{}` [{} via {}]: {}", - observation.id, - observation.status, - observation.source_kind, - compact_text(&observation.summary) - )); - if !observation.evidence_ids.is_empty() { - lines.push(format!( - " evidence: {}", - render_inline_ids(&observation.evidence_ids) - )); - } - let code_refs = render_code_refs(&observation.code_refs); - if !code_refs.is_empty() { - lines.push(format!(" code refs: {}", code_refs.join(", "))); - } - } - push_omitted_count( - &mut lines, - observations.len(), - OBSERVATION_LIMIT, - "observations", - ); - - Some(format!("## Recent Observations\n{}", lines.join("\n"))) -} - -fn render_evidence(evidence: &[EpiphanyEvidenceRecord]) -> Option { - if evidence.is_empty() { - return None; - } - - let mut lines = Vec::new(); - for record in evidence.iter().take(EVIDENCE_LIMIT) { - let code_refs = render_code_refs(&record.code_refs); - lines.push(format!( - "- `{}` [{} / {}]: {}{}", - record.id, - record.kind, - record.status, - compact_text(&record.summary), - render_ref_suffix(&code_refs) - )); - } - push_omitted_count( - &mut lines, - evidence.len(), - EVIDENCE_LIMIT, - "evidence records", - ); - - Some(format!("## Recent Evidence\n{}", lines.join("\n"))) -} - -fn render_churn(churn: Option<&EpiphanyChurnState>) -> Option { - let churn = churn?; - let mut lines = vec![ - format!( - "- Understanding status: {}", - compact_text(&churn.understanding_status) - ), - format!("- Diff pressure: {}", compact_text(&churn.diff_pressure)), - ]; - if let Some(warning) = churn.warning.as_deref() { - lines.push(format!("- Warning: {}", compact_text(warning))); - } - if let Some(unexplained_writes) = churn.unexplained_writes { - lines.push(format!("- Unexplained writes: {unexplained_writes}")); - } - - Some(format!("## Churn\n{}", lines.join("\n"))) -} - -fn render_mode(mode: Option<&EpiphanyModeState>) -> Option { - let mode = mode?; - let mut line = format!("- Name: {}", compact_text(&mode.name)); - if let Some(kind) = mode.kind { - line.push_str(&format!(" ({kind:?})")); - } - Some(format!("## Mode\n{line}")) -} - -fn collect_focus_node_ids( - frontier: Option<&EpiphanyGraphFrontier>, - checkpoint: Option<&EpiphanyGraphCheckpoint>, -) -> Vec { - let mut focus_node_ids = Vec::new(); - let mut seen = HashSet::new(); - for node_id in frontier - .into_iter() - .flat_map(|value| value.active_node_ids.iter()) - .chain( - checkpoint - .into_iter() - .flat_map(|value| value.frontier_node_ids.iter()), - ) - { - if seen.insert(node_id.as_str()) { - focus_node_ids.push(node_id.clone()); - } - } - focus_node_ids -} - -fn select_nodes<'a>( - graph: &'a EpiphanyGraph, - focus_node_ids: &[String], -) -> Vec<&'a EpiphanyGraphNode> { - let mut selected = Vec::new(); - let mut seen = HashSet::new(); - let focus_set = focus_node_ids - .iter() - .map(String::as_str) - .collect::>(); - - for node in &graph.nodes { - if focus_set.contains(node.id.as_str()) && seen.insert(node.id.as_str()) { - selected.push(node); - if selected.len() == NODE_LIMIT { - return selected; - } - } - } - - for node in &graph.nodes { - if seen.insert(node.id.as_str()) { - selected.push(node); - if selected.len() == NODE_LIMIT { - break; - } - } - } - - selected -} - -fn select_edges<'a>( - graph: &'a EpiphanyGraph, - selected_node_ids: &HashSet<&'a str>, - active_edge_ids: &HashSet<&str>, -) -> Vec<&'a EpiphanyGraphEdge> { - graph - .edges - .iter() - .filter(|edge| { - edge.id - .as_deref() - .is_some_and(|edge_id| active_edge_ids.contains(edge_id)) - || selected_node_ids.contains(edge.source_id.as_str()) - || selected_node_ids.contains(edge.target_id.as_str()) - }) - .collect() -} - -fn select_links<'a>( - links: &'a [EpiphanyGraphLink], - focus_node_ids: &[String], -) -> Vec<&'a EpiphanyGraphLink> { - let focus_set = focus_node_ids - .iter() - .map(String::as_str) - .collect::>(); - let mut focused = Vec::new(); - let mut fallback = Vec::new(); - for link in links { - if focus_set.contains(link.dataflow_node_id.as_str()) - || focus_set.contains(link.architecture_node_id.as_str()) - { - focused.push(link); - } else { - fallback.push(link); - } - } - focused.extend(fallback); - focused -} - -fn render_code_refs(code_refs: &[EpiphanyCodeRef]) -> Vec { - let mut rendered = code_refs - .iter() - .take(CODE_REF_LIMIT) - .map(render_code_ref) - .collect::>(); - if code_refs.len() > CODE_REF_LIMIT { - rendered.push(format!( - "... {} more refs", - code_refs.len() - CODE_REF_LIMIT - )); - } - rendered -} - -fn render_code_ref(code_ref: &EpiphanyCodeRef) -> String { - let mut location = normalize_path(&code_ref.path); - match (code_ref.start_line, code_ref.end_line) { - (Some(start_line), Some(end_line)) if end_line != start_line => { - location.push_str(&format!(":{start_line}-{end_line}")); - } - (Some(start_line), _) => { - location.push_str(&format!(":{start_line}")); - } - _ => {} - } - - let mut metadata = Vec::new(); - if let Some(symbol) = code_ref.symbol.as_deref() { - metadata.push(format!("symbol: {}", compact_text(symbol))); - } - if let Some(note) = code_ref.note.as_deref() { - metadata.push(compact_text(note)); - } - - if metadata.is_empty() { - format!("`{location}`") - } else { - format!("`{location}` ({})", metadata.join("; ")) - } -} - -fn render_ref_suffix(code_refs: &[String]) -> String { - if code_refs.is_empty() { - String::new() - } else { - format!("; refs: {}", code_refs.join(", ")) - } -} - -fn render_inline_ids(ids: &[String]) -> String { - ids.iter() - .map(|id| format!("`{id}`")) - .collect::>() - .join(", ") -} - -fn normalize_path(path: &Path) -> String { - path.to_string_lossy().replace('\\', "/") -} - -fn compact_text(value: &str) -> String { - value.split_whitespace().collect::>().join(" ") -} - -fn omitted_suffix(total: usize, shown: usize, label: &str) -> String { - if total > shown { - format!("; ... {} more {label} not shown", total - shown) - } else { - String::new() - } -} - -fn push_omitted_count(lines: &mut Vec, total: usize, shown: usize, label: &str) { - if total > shown { - lines.push(format!("- ... {} more {label} not shown", total - shown)); - } -} - -#[cfg(test)] -mod tests { - use super::render_epiphany_state; - use crate::EpiphanyChurnState; - use crate::EpiphanyCodeRef; - use crate::EpiphanyGraph; - use crate::EpiphanyGraphCheckpoint; - use crate::EpiphanyGraphEdge; - use crate::EpiphanyGraphFrontier; - use crate::EpiphanyGraphNode; - use crate::EpiphanyGraphs; - use crate::EpiphanyInvestigationCheckpoint; - use crate::EpiphanyInvestigationDisposition; - use crate::EpiphanyModeKind; - use crate::EpiphanyModeState; - use crate::EpiphanyThreadState; - use std::path::PathBuf; - - #[test] - fn render_prefers_frontier_focus_and_omits_empty_sections() { - let state = EpiphanyThreadState { - revision: 7, - objective: Some("Map the actual turn loop.".to_string()), - graphs: EpiphanyGraphs { - architecture: EpiphanyGraph { - nodes: vec![ - EpiphanyGraphNode { - id: "arch-overview".to_string(), - title: "Overview".to_string(), - purpose: "Loose context nobody is touching.".to_string(), - ..Default::default() - }, - EpiphanyGraphNode { - id: "arch-session".to_string(), - title: "Session".to_string(), - purpose: "Owns prompt assembly.".to_string(), - mechanism: Some("Builds the initial context bundle.".to_string()), - metaphor: Some("The foreman keeping the queue honest.".to_string()), - code_refs: vec![EpiphanyCodeRef { - path: PathBuf::from("core/src/session/mod.rs"), - start_line: Some(2433), - end_line: Some(2617), - symbol: Some("build_initial_context".to_string()), - note: None, - }], - ..Default::default() - }, - EpiphanyGraphNode { - id: "arch-rollout".to_string(), - title: "Rollout".to_string(), - purpose: "Persists thread state.".to_string(), - ..Default::default() - }, - EpiphanyGraphNode { - id: "arch-extra".to_string(), - title: "Extra".to_string(), - purpose: "Should be trimmed.".to_string(), - ..Default::default() - }, - ], - edges: vec![EpiphanyGraphEdge { - source_id: "arch-session".to_string(), - target_id: "arch-rollout".to_string(), - kind: "writes".to_string(), - id: Some("edge-session-rollout".to_string()), - label: Some("persists".to_string()), - mechanism: Some("Writes baseline state after a turn.".to_string()), - ..Default::default() - }], - }, - dataflow: EpiphanyGraph { - nodes: vec![EpiphanyGraphNode { - id: "flow-build-context".to_string(), - title: "Build context".to_string(), - purpose: "Turns state into prompt context.".to_string(), - ..Default::default() - }], - ..Default::default() - }, - ..Default::default() - }, - graph_frontier: Some(EpiphanyGraphFrontier { - active_node_ids: vec!["arch-session".to_string(), "flow-build-context".to_string()], - active_edge_ids: vec!["edge-session-rollout".to_string()], - dirty_paths: vec![PathBuf::from("core/src/session/mod.rs")], - ..Default::default() - }), - graph_checkpoint: Some(EpiphanyGraphCheckpoint { - checkpoint_id: "cp-7".to_string(), - frontier_node_ids: vec!["arch-session".to_string()], - ..Default::default() - }), - investigation_checkpoint: Some(EpiphanyInvestigationCheckpoint { - checkpoint_id: "ix-1".to_string(), - kind: "slice_planning".to_string(), - disposition: EpiphanyInvestigationDisposition::ResumeReady, - focus: "Map the state update seam before broad edits.".to_string(), - next_action: Some( - "Re-read the write path, then patch one bounded field.".to_string(), - ), - open_questions: vec![ - "Do scene and context both need the resume packet?".to_string(), - ], - code_refs: vec![EpiphanyCodeRef { - path: PathBuf::from("core/src/codex_thread.rs"), - start_line: Some(376), - end_line: Some(424), - symbol: Some("epiphany_update_state".to_string()), - note: Some("Checkpoint writes must use the same red pen.".to_string()), - }], - evidence_ids: vec!["ev-1".to_string()], - ..Default::default() - }), - churn: Some(EpiphanyChurnState { - understanding_status: "stable".to_string(), - diff_pressure: "low".to_string(), - warning: None, - unexplained_writes: Some(0), - }), - mode: Some(EpiphanyModeState { - name: "epiphany".to_string(), - kind: Some(EpiphanyModeKind::Default), - }), - ..Default::default() - }; - - let rendered = render_epiphany_state(&state); - - assert!(rendered.contains("`arch-session`")); - assert!(rendered.contains("`flow-build-context`")); - assert!(rendered.contains("## Epiphany Doctrine")); - assert!(rendered.contains("become the Perfect Machine")); - assert!(rendered.contains("Persistent state is the mind")); - assert!(rendered.contains("Imagination makes futures more selectable")); - assert!(rendered.contains("evidence is a distilled durable ledger")); - assert!(rendered.contains("modeling is Modeling")); - assert!(rendered.contains("the Body is the substrate being sensed")); - assert!(rendered.contains("implementation is the Hands")); - assert!(rendered.contains("research is the Eyes")); - assert!(rendered.contains("Implement user-specified algorithms first")); - assert!(rendered.contains("bank scratch/map/evidence before the dark")); - assert!(rendered.contains("## Investigation Checkpoint")); - assert!(rendered.contains("resume_ready")); - assert!(rendered.contains("Map the state update seam before broad edits.")); - assert!(!rendered.contains("## Scratch")); - assert!(!rendered.contains("## Invariants")); - assert!(rendered.contains("`core/src/session/mod.rs:2433-2617`")); - assert!(rendered.contains("... 1 more graph nodes not shown")); - } -}