Remove global thread state model

This commit is contained in:
Meta Vens 2026-08-18 04:51:18 +02:00
commit ec1431fff8
23 changed files with 139 additions and 4547 deletions

View file

@ -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"

View file

@ -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<u64>,
max_runtime_seconds: Option<u64>,
state: &EpiphanyThreadState,
) -> Result<EpiphanyJobLaunchRequest, String> {
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<u64>,
max_runtime_seconds: Option<u64>,
state: &EpiphanyThreadState,
dynamic_prompt_context: Option<String>,
) -> Result<EpiphanyJobLaunchRequest, String> {
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<u64>,
max_runtime_seconds: Option<u64>,
state: &EpiphanyThreadState,
request_id: String,
) -> Result<EpiphanyJobLaunchRequest, String> {
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<u64>,
max_runtime_seconds: Option<u64>,
state: &EpiphanyThreadState,
request_id: String,
) -> Result<EpiphanyJobLaunchRequest, String> {
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<u64>,
binding_id: String,
kind: CoreEpiphanyJobKind,
scope: String,
owner_role: String,
authority_scope: String,
linked_subgoal_ids: Vec<String>,
linked_graph_node_ids: Vec<String>,
instruction: String,
launch_document: EpiphanyWorkerLaunchDocument,
output_contract_id: String,
max_runtime_seconds: Option<u64>,
) -> 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<String>, values: impl IntoIterator<Ite
}
}
fn epiphany_active_subgoal_ids(state: Option<&EpiphanyThreadState>) -> Vec<String> {
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<String> {
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::*;

View file

@ -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("<epiphany_dynamic_context>"),
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()
}
}

View file

@ -81,7 +81,6 @@ fn prepare_authority_family(store: &Path) -> Result<EpiphanyToolInvocationIntent
let launch_document = EpiphanyWorkerLaunchDocument::Role(EpiphanyRoleWorkerLaunchDocument {
thread_id: "public-source-no-grant-thread".into(),
role_id: "research".into(),
state_revision: 0,
objective: Some("Prove the exact public-source grant boundary.".into()),
dynamic_prompt_context: None,
repository_body_observation_basis: None,

View file

@ -1,790 +0,0 @@
use crate::EpiphanyMemoryContextPacket;
use crate::EpiphanyMemoryContextQuery;
use crate::EpiphanyMemoryProfile;
use crate::EpiphanyPromptContextInput;
use crate::EpiphanyRepoModelView;
use crate::assemble_repo_model_view;
use crate::load_epiphany_cultmesh_cluster_topology;
use crate::load_epiphany_cultmesh_status;
use crate::plan_memory_graph_context_cut;
use crate::query_epiphany_local_verse_context;
use crate::render_epiphany_prompt_context;
use epiphany_state_model::EpiphanyThreadState;
use sha2::Digest;
use std::path::Path;
use std::path::PathBuf;
pub const EPIPHANY_LOCAL_VERSE_RUNTIME_ID: &str = "epiphany-local";
pub fn local_verse_store_path(runtime_store_path: &Path) -> 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<String, String> {
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<String, String> {
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<String, String> {
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<canonical_repo_model_shape>\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::<Vec<_>>()
.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::<Vec<_>>()
.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("</canonical_repo_model_shape>");
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::<Vec<_>>()
.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("<canonical_repo_model_shape>"));
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("<epiphany_dynamic_context>"));
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(())
}
}

View file

@ -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

View file

@ -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)
}

View file

@ -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,
]
}

View file

@ -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;

View file

@ -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::*;

View file

@ -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;

View file

@ -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<String>,
) -> 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<String>,
graphs: &EpiphanyGraphs,
source_identity: impl Into<String>,
source_revision: u64,
repo_root: &Path,
) -> Result<EpiphanyMemoryGraphSnapshot> {
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<String, String>,
nodes: &mut Vec<EpiphanyMemoryNode>,
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<EpiphanyMemoryNode> {
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<String, String>,
edges: &mut Vec<EpiphanyMemoryEdge>,
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<String, String>,
edges: &mut Vec<EpiphanyMemoryEdge>,
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::<Vec<_>>();
let covers_edge_ids = edges
.iter()
.filter(|edge| edge.profile == profile)
.map(|edge| edge.id.clone())
.collect::<Vec<_>>();
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<Vec<EpiphanyMemoryAnchor>> {
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,
}
}

View file

@ -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,

View file

@ -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<CultCacheEnvelope>,
}
#[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<String>,
pub linked_graph_node_ids: Vec<String>,
pub instruction: String,
pub launch_document: EpiphanyWorkerLaunchDocument,
pub output_contract_id: String,
pub organ_launch_contract: EpiphanyLaunchOrganContract,
pub max_runtime_seconds: Option<u64>,
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<u64>,
pub binding_id: String,
pub kind: EpiphanyJobKind,
pub scope: String,
pub owner_role: String,
pub authority_scope: String,
pub linked_subgoal_ids: Vec<String>,
pub linked_graph_node_ids: Vec<String>,
pub instruction: String,
pub launch_document: EpiphanyWorkerLaunchDocument,
pub output_contract_id: String,
pub organ_launch_contract: EpiphanyLaunchOrganContract,
pub max_runtime_seconds: Option<u64>,
pub proposal_modeling_request_id: Option<String>,
pub frontier_planning_request_id: Option<String>,
pub frontier_plan_mind_request_id: Option<String>,
pub imagination_consideration_request_id: Option<String>,
pub admitted_model_direction_consideration_request_id: Option<String>,
pub repo_frontier_modeling_request_id: Option<String>,
pub repo_frontier_research_request_id: Option<String>,
pub repo_frontier_verification_request_id: Option<String>,
}
#[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<u64>,
pub binding_id: String,
pub reason: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EpiphanyJobInterruptResult {
pub epiphany_state: EpiphanyThreadState,
pub binding_id: String,
pub cancel_requested: bool,
pub interrupted_thread_ids: Vec<String>,
}
#[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<RuntimeSpineHeartbeatLaunchPlan> {
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<EpiphanyJobBinding>,
replacement: EpiphanyJobBinding,
) -> Vec<EpiphanyJobBinding> {
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<EpiphanyJobBinding>,
binding_index: usize,
blocking_reason: &str,
) -> Vec<EpiphanyJobBinding> {
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<Path>,
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,

View file

@ -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<SubstrateGateCultNetCon
]
}
pub fn substrate_gate_repo_access_grant_for_launch(
receipt_id: String,
runtime_job_id: String,
request: &EpiphanyJobLaunchRequest,
granted_at: String,
) -> 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()));

View file

@ -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<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_indexed_at_unix_seconds: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub indexed_file_count: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub indexed_chunk_count: Option<u32>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub dirty_paths: Vec<PathBuf>,
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<String>,
pub dirty_path_count: u32,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub dirty_paths: Vec<PathBuf>,
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<PathBuf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub observed_at_unix_seconds: Option<i64>,
pub changed_path_count: u32,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub changed_paths: Vec<PathBuf>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub graph_node_ids: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub active_frontier_node_ids: Vec<String>,
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<i64>,
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<EpiphanyFreshnessWatcherInput<'a>>,
}
#[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<u64>,
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<EpiphanyFreshnessWatcherInput<'_>>,
) -> 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<String> {
let changed_path_keys: HashSet<String> = 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<String> {
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()]
);
}
}

View file

@ -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<EpiphanyGraphNode>,
pub architecture_edges: Vec<EpiphanyGraphEdge>,
pub dataflow_nodes: Vec<EpiphanyGraphNode>,
pub dataflow_edges: Vec<EpiphanyGraphEdge>,
pub links: Vec<EpiphanyGraphLink>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct EpiphanyContext {
pub graph: EpiphanyGraphContext,
pub frontier: Option<EpiphanyGraphFrontier>,
pub checkpoint: Option<EpiphanyGraphCheckpoint>,
pub investigation_checkpoint: Option<EpiphanyInvestigationCheckpoint>,
pub observations: Vec<EpiphanyObservation>,
pub evidence: Vec<EpiphanyEvidenceRecord>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct EpiphanyContextMissing {
pub graph_node_ids: Vec<String>,
pub graph_edge_ids: Vec<String>,
pub observation_ids: Vec<String>,
pub evidence_ids: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct EpiphanyContextParams {
pub graph_node_ids: Vec<String>,
pub graph_edge_ids: Vec<String>,
pub observation_ids: Vec<String>,
pub evidence_ids: Vec<String>,
pub include_active_frontier: Option<bool>,
pub include_linked_evidence: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EpiphanyContextView {
pub state_status: EpiphanyContextStateStatus,
pub state_revision: Option<u64>,
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<String>,
pub edge_ids: Vec<String>,
pub paths: Vec<PathBuf>,
pub symbols: Vec<String>,
pub edge_kinds: Vec<String>,
pub direction: Option<EpiphanyGraphQueryDirection>,
pub depth: Option<u32>,
pub include_links: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct EpiphanyGraphQueryMatched {
pub node_ids: Vec<String>,
pub edge_ids: Vec<String>,
pub paths: Vec<PathBuf>,
pub symbols: Vec<String>,
pub edge_kinds: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct EpiphanyGraphQueryMissing {
pub node_ids: Vec<String>,
pub edge_ids: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EpiphanyGraphQueryView {
pub state_status: EpiphanyContextStateStatus,
pub state_revision: Option<u64>,
pub graph: EpiphanyGraphContext,
pub frontier: Option<EpiphanyGraphFrontier>,
pub checkpoint: Option<EpiphanyGraphCheckpoint>,
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::<Vec<_>>();
let dataflow_nodes = state
.graphs
.dataflow
.nodes
.iter()
.filter(|node| graph_node_id_set.contains(node.id.as_str()))
.cloned()
.collect::<Vec<_>>();
let found_node_ids: HashSet<String> = 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<String> = 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::<Vec<_>>();
let found_observation_ids: HashSet<String> = 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::<Vec<_>>();
let found_evidence_ids: HashSet<String> = 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::<Vec<_>>();
let dataflow_nodes = state
.graphs
.dataflow
.nodes
.iter()
.filter(|node| node_id_set.contains(node.id.as_str()))
.cloned()
.collect::<Vec<_>>();
let found_node_ids: HashSet<String> = 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::<Vec<_>>();
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<String> = 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::<Vec<_>>();
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::<Vec<_>>();
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<String>,
edge_ids: &mut Vec<String>,
matched_paths: &mut Vec<PathBuf>,
matched_symbols: &mut Vec<String>,
) {
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<String>,
edge_ids: &mut Vec<String>,
matched_edge_kinds: &mut Vec<String>,
) {
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<String>,
edge_ids: &mut Vec<String>,
) {
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<String> = 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<String>) {
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<PathBuf>,
matched_symbols: &mut Vec<String>,
) -> 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<PathBuf>, values: impl IntoIterator<Item = PathBuf>) {
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<EpiphanyGraphEdge> {
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<Item = String>) -> Vec<String> {
let mut unique = Vec::new();
extend_unique_strings(&mut unique, values);
unique
}
fn extend_unique_strings(target: &mut Vec<String>, values: impl IntoIterator<Item = String>) {
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()]);
}
}

View file

@ -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;

View file

@ -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<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]

View file

@ -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,

View file

@ -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(
"<verification_work_loop_telemetry>hands receipts</verification_work_loop_telemetry>"

View file

@ -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<String>,
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<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(type = "string | null")]
pub active_subgoal_id: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<EpiphanySubgoal>")]
pub subgoals: Vec<EpiphanySubgoal>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<EpiphanyInvariant>")]
pub invariants: Vec<EpiphanyInvariant>,
#[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<EpiphanyGraphFrontier>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(type = "EpiphanyGraphCheckpoint | null")]
pub graph_checkpoint: Option<EpiphanyGraphCheckpoint>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(type = "EpiphanyRetrievalState | null")]
pub retrieval: Option<EpiphanyRetrievalState>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(type = "EpiphanyScratchPad | null")]
pub scratch: Option<EpiphanyScratchPad>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(type = "EpiphanyInvestigationCheckpoint | null")]
pub investigation_checkpoint: Option<EpiphanyInvestigationCheckpoint>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<EpiphanyJobBinding>")]
pub job_bindings: Vec<EpiphanyJobBinding>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<EpiphanyAcceptanceReceipt>")]
pub acceptance_receipts: Vec<EpiphanyAcceptanceReceipt>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<EpiphanyRuntimeLink>")]
pub runtime_links: Vec<EpiphanyRuntimeLink>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<EpiphanyObservation>")]
pub observations: Vec<EpiphanyObservation>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
#[ts(type = "Array<EpiphanyEvidenceRecord>")]
pub recent_evidence: Vec<EpiphanyEvidenceRecord>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(type = "EpiphanyChurnState | null")]
pub churn: Option<EpiphanyChurnState>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(type = "EpiphanyModeState | null")]
pub mode: Option<EpiphanyModeState>,
#[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<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, TS, Default)]
pub struct EpiphanyAcceptanceReceipt {
pub id: String,

View file

@ -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<String> {
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<String> {
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::<Vec<_>>()
.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<String> {
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<String> {
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<String>, 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::<Vec<_>>()
.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<String>, 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<String>,
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::<HashSet<_>>();
let active_edge_ids = frontier
.map(|graph_frontier| {
graph_frontier
.active_edge_ids
.iter()
.map(String::as_str)
.collect::<HashSet<_>>()
})
.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<String> {
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<String> {
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::<Vec<_>>()
.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<String> {
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<String> {
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<String> {
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<String> {
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<String> {
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::<HashSet<_>>();
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::<HashSet<_>>();
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<String> {
let mut rendered = code_refs
.iter()
.take(CODE_REF_LIMIT)
.map(render_code_ref)
.collect::<Vec<_>>();
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::<Vec<_>>()
.join(", ")
}
fn normalize_path(path: &Path) -> String {
path.to_string_lossy().replace('\\', "/")
}
fn compact_text(value: &str) -> String {
value.split_whitespace().collect::<Vec<_>>().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<String>, 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"));
}
}