Migrate Reorientation to keyed Mind authority
This commit is contained in:
parent
237fb81cf5
commit
d5df53ae40
17 changed files with 1542 additions and 1664 deletions
|
|
@ -8,14 +8,10 @@ use crate::EpiphanyJobLaunchRequest;
|
|||
use crate::EpiphanyPressure;
|
||||
use crate::EpiphanyPressureLevel as CoreEpiphanyPressureLevel;
|
||||
use crate::EpiphanyReorientAction as CoreEpiphanyReorientAction;
|
||||
use crate::EpiphanyReorientDecision as CoreEpiphanyReorientDecision;
|
||||
use crate::EpiphanyReorientLaunchRequestInput;
|
||||
use crate::EpiphanyRoleResultRoleId;
|
||||
use crate::EpiphanyRoleWorkerLaunchDocument;
|
||||
use crate::EpiphanyWorkerLaunchDocument;
|
||||
use crate::build_reorient_job_launch_request;
|
||||
use crate::default_launch_organ_contract;
|
||||
use epiphany_state_model::EpiphanyInvestigationCheckpoint;
|
||||
use epiphany_state_model::EpiphanyJobKind as CoreEpiphanyJobKind;
|
||||
use epiphany_state_model::EpiphanyThreadState;
|
||||
|
||||
|
|
@ -1238,49 +1234,6 @@ fn build_epiphany_role_launch_instruction(role_id: EpiphanyRoleResultRoleId) ->
|
|||
epiphany_worker_prompt(body)
|
||||
}
|
||||
|
||||
pub fn build_epiphany_reorient_launch_request(
|
||||
thread_id: &str,
|
||||
expected_revision: Option<u64>,
|
||||
max_runtime_seconds: Option<u64>,
|
||||
state: &EpiphanyThreadState,
|
||||
checkpoint: &EpiphanyInvestigationCheckpoint,
|
||||
decision: &CoreEpiphanyReorientDecision,
|
||||
) -> EpiphanyJobLaunchRequest {
|
||||
build_epiphany_reorient_launch_request_with_dynamic_context(
|
||||
thread_id,
|
||||
expected_revision,
|
||||
max_runtime_seconds,
|
||||
state,
|
||||
checkpoint,
|
||||
decision,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn build_epiphany_reorient_launch_request_with_dynamic_context(
|
||||
thread_id: &str,
|
||||
expected_revision: Option<u64>,
|
||||
max_runtime_seconds: Option<u64>,
|
||||
state: &EpiphanyThreadState,
|
||||
checkpoint: &EpiphanyInvestigationCheckpoint,
|
||||
decision: &CoreEpiphanyReorientDecision,
|
||||
dynamic_prompt_context: Option<String>,
|
||||
) -> EpiphanyJobLaunchRequest {
|
||||
let instruction = build_epiphany_reorient_launch_instruction(decision.action);
|
||||
build_reorient_job_launch_request(EpiphanyReorientLaunchRequestInput {
|
||||
thread_id,
|
||||
expected_revision,
|
||||
max_runtime_seconds,
|
||||
binding_id: EPIPHANY_REORIENT_LAUNCH_BINDING_ID,
|
||||
owner_role: EPIPHANY_REORIENT_OWNER_ROLE,
|
||||
instruction,
|
||||
state,
|
||||
checkpoint,
|
||||
decision,
|
||||
dynamic_prompt_context,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_epiphany_reorient_launch_instruction(action: CoreEpiphanyReorientAction) -> String {
|
||||
let prompts = &epiphany_specialist_prompt_config().reorientation;
|
||||
let body = match action {
|
||||
|
|
|
|||
|
|
@ -1072,15 +1072,20 @@ fn run_coordinator(args: &Args) -> Result<Value> {
|
|||
steps.push(step);
|
||||
break;
|
||||
}
|
||||
let can_accept = args.auto_review
|
||||
&& reorient_result_auto_acceptable(&result)
|
||||
&& revision.is_some();
|
||||
if result["status"].as_str() == Some("failed") {
|
||||
let recorded = accept_reorient(&runtime_store)?;
|
||||
push_event(
|
||||
&mut step,
|
||||
json!({"type": "reorientFailureRecorded", "recorded": status_cli::sanitize_for_operator(recorded)}),
|
||||
);
|
||||
final_status = collect_coordinator_status(&runtime_store, &thread_id)?;
|
||||
append_operator_step_jsonl(&steps_path, &step)?;
|
||||
steps.push(step);
|
||||
continue;
|
||||
}
|
||||
let can_accept = args.auto_review && reorient_result_auto_acceptable(&result);
|
||||
if can_accept {
|
||||
let accepted = accept_reorient(
|
||||
&runtime_store,
|
||||
&thread_id,
|
||||
revision.and_then(|value| u64::try_from(value).ok()),
|
||||
)?;
|
||||
let accepted = accept_reorient(&runtime_store)?;
|
||||
push_event(
|
||||
&mut step,
|
||||
json!({"type": "reorientAccept", "accepted": status_cli::sanitize_for_operator(accepted)}),
|
||||
|
|
@ -1858,29 +1863,6 @@ fn launch_role(
|
|||
}))
|
||||
}
|
||||
|
||||
fn launch_job_value(
|
||||
service: &epiphany_core::EpiphanyCoordinatorService,
|
||||
thread_id: &str,
|
||||
state: &epiphany_state_model::EpiphanyThreadState,
|
||||
request: &epiphany_core::EpiphanyJobLaunchRequest,
|
||||
) -> Result<Value> {
|
||||
let launched = service.launch_job(
|
||||
thread_id,
|
||||
state,
|
||||
request,
|
||||
format!("epiphany-heartbeat-launch-{}", Uuid::new_v4()),
|
||||
Uuid::new_v4().to_string(),
|
||||
now(),
|
||||
)?;
|
||||
Ok(json!({
|
||||
"bindingId": launched.binding_id,
|
||||
"launcherJobId": launched.launcher_job_id,
|
||||
"backendJobId": launched.backend_job_id,
|
||||
"revision": launched.epiphany_state.revision,
|
||||
"state": launched.epiphany_state,
|
||||
}))
|
||||
}
|
||||
|
||||
fn accept_role(
|
||||
runtime_store: &Path,
|
||||
thread_id: &str,
|
||||
|
|
@ -1919,52 +1901,30 @@ fn accept_role(
|
|||
}))
|
||||
}
|
||||
|
||||
fn accept_reorient(
|
||||
runtime_store: &Path,
|
||||
thread_id: &str,
|
||||
expected_revision: Option<u64>,
|
||||
) -> Result<Value> {
|
||||
let service = epiphany_core::EpiphanyCoordinatorService::new(runtime_store);
|
||||
let state = service
|
||||
.state()?
|
||||
.ok_or_else(|| anyhow!("cannot accept reorientation without native coordinator state"))?;
|
||||
let snapshot = service.reorient_result(epiphany_core::EPIPHANY_REORIENT_LAUNCH_BINDING_ID)?;
|
||||
if let Some(result_id) = snapshot
|
||||
.finding
|
||||
.as_ref()
|
||||
.and_then(|finding| finding.runtime_result_id.as_deref())
|
||||
&& let Some(existing) = state.acceptance_receipts.iter().find(|receipt| {
|
||||
receipt.result_id == result_id
|
||||
&& receipt.binding_id == epiphany_core::EPIPHANY_REORIENT_LAUNCH_BINDING_ID
|
||||
&& receipt.surface == "reorientAccept"
|
||||
&& receipt.status == "accepted"
|
||||
})
|
||||
{
|
||||
return Ok(json!({
|
||||
"revision": state.revision,
|
||||
"state": state,
|
||||
"acceptedReceiptId": existing.id,
|
||||
"changed": false,
|
||||
}));
|
||||
fn accept_reorient(runtime_store: &Path) -> Result<Value> {
|
||||
let work = epiphany_core::project_current_work(runtime_store)?
|
||||
.reorientation
|
||||
.ok_or_else(|| anyhow!("Mind has no reviewable reorientation work"))?;
|
||||
if work.action != epiphany_core::EpiphanyAgentPassContinuationAction::Review {
|
||||
return Err(anyhow!("reorientation work is not reviewable"));
|
||||
}
|
||||
let accepted = service.accept_reorient(
|
||||
thread_id,
|
||||
&state,
|
||||
epiphany_core::EPIPHANY_REORIENT_LAUNCH_BINDING_ID,
|
||||
expected_revision,
|
||||
None,
|
||||
now(),
|
||||
&Uuid::new_v4().to_string(),
|
||||
true,
|
||||
true,
|
||||
)?;
|
||||
let job_id = work
|
||||
.job_id
|
||||
.ok_or_else(|| anyhow!("reorientation review lost its runtime job"))?;
|
||||
let snapshot = epiphany_core::runtime_job_snapshot(runtime_store, &job_id)?
|
||||
.ok_or_else(|| anyhow!("reorientation review lost its runtime job"))?;
|
||||
let accepted = match snapshot.job.status {
|
||||
epiphany_core::EpiphanyRuntimeJobStatus::Completed => {
|
||||
epiphany_core::accept_reorientation_result(runtime_store, &job_id, &now())?
|
||||
}
|
||||
epiphany_core::EpiphanyRuntimeJobStatus::Failed => {
|
||||
epiphany_core::record_reorientation_pass_failure(runtime_store, &job_id)?
|
||||
}
|
||||
_ => return Err(anyhow!("reorientation work is not terminally reviewable")),
|
||||
};
|
||||
Ok(json!({
|
||||
"revision": accepted.state.revision,
|
||||
"state": accepted.state,
|
||||
"acceptedReceiptId": accepted.update.accepted_receipt_id,
|
||||
"acceptedObservationId": accepted.update.accepted_observation_id,
|
||||
"acceptedEvidenceId": accepted.update.accepted_evidence_id,
|
||||
"finding": accepted.finding,
|
||||
"mindCommitReceipt": accepted,
|
||||
"changed": true,
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
@ -2151,64 +2111,18 @@ fn default_binding_id_for_role(role_id: &str) -> String {
|
|||
|
||||
fn launch_reorient(
|
||||
runtime_store: &Path,
|
||||
local_verse_store: &Path,
|
||||
thread_id: &str,
|
||||
expected_revision: Option<i64>,
|
||||
max_runtime_seconds: u64,
|
||||
_local_verse_store: &Path,
|
||||
_thread_id: &str,
|
||||
_expected_revision: Option<i64>,
|
||||
_max_runtime_seconds: u64,
|
||||
) -> Result<Value> {
|
||||
let service = epiphany_core::EpiphanyCoordinatorService::new(runtime_store);
|
||||
let state = service
|
||||
.state()?
|
||||
.ok_or_else(|| anyhow!("cannot launch reorientation without native coordinator state"))?;
|
||||
let checkpoint = match state.investigation_checkpoint.clone() {
|
||||
Some(checkpoint) => checkpoint,
|
||||
None => {
|
||||
let projection = epiphany_core::runtime_modeling_semantic_projection_input(
|
||||
runtime_store,
|
||||
)
|
||||
.context("cannot launch reorientation without a valid admitted RepoModel checkpoint")?;
|
||||
epiphany_state_model::reorient_checkpoint_from_admitted_repo_model(
|
||||
projection.snapshot(),
|
||||
&projection.obligation().obligation_id,
|
||||
)
|
||||
}
|
||||
};
|
||||
let status = collect_coordinator_status(runtime_store, thread_id)?;
|
||||
let decision: epiphany_core::EpiphanyReorientDecision =
|
||||
serde_json::from_value(status["reorient"]["decision"].clone())
|
||||
.context("native reorientation status did not contain a typed decision")?;
|
||||
let context = epiphany_core::render_launch_dynamic_prompt_context(
|
||||
runtime_store,
|
||||
local_verse_store,
|
||||
&state,
|
||||
epiphany_core::reorient_launch_context_focus(&state, &decision.next_action),
|
||||
)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let expected_revision = expected_revision.and_then(|value| u64::try_from(value).ok());
|
||||
let request = epiphany_core::build_epiphany_reorient_launch_request_with_dynamic_context(
|
||||
thread_id,
|
||||
expected_revision,
|
||||
Some(max_runtime_seconds),
|
||||
&state,
|
||||
&checkpoint,
|
||||
&decision,
|
||||
Some(context),
|
||||
);
|
||||
let launched = service.launch_job(
|
||||
thread_id,
|
||||
&state,
|
||||
&request,
|
||||
format!("epiphany-heartbeat-launch-{}", Uuid::new_v4()),
|
||||
Uuid::new_v4().to_string(),
|
||||
now(),
|
||||
)?;
|
||||
let requested_at = now();
|
||||
let request = epiphany_core::request_current_reorientation(runtime_store, &requested_at)?;
|
||||
let job_id = epiphany_core::launch_current_reorientation_work(runtime_store, &requested_at)?;
|
||||
Ok(json!({
|
||||
"bindingId": launched.binding_id,
|
||||
"launcherJobId": launched.launcher_job_id,
|
||||
"backendJobId": launched.backend_job_id,
|
||||
"revision": launched.epiphany_state.revision,
|
||||
"state": launched.epiphany_state,
|
||||
"decision": decision,
|
||||
"bindingId": epiphany_core::EPIPHANY_REORIENT_LAUNCH_BINDING_ID,
|
||||
"backendJobId": job_id,
|
||||
"request": request,
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
@ -2344,8 +2258,35 @@ fn read_role_result(runtime_store: &Path, thread_id: &str, role_id: &str) -> Res
|
|||
Ok(collect_coordinator_status(runtime_store, thread_id)?["roleResults"][role_id].clone())
|
||||
}
|
||||
|
||||
fn read_reorient_result(runtime_store: &Path, thread_id: &str) -> Result<Value> {
|
||||
Ok(collect_coordinator_status(runtime_store, thread_id)?["reorientResult"].clone())
|
||||
fn read_reorient_result(runtime_store: &Path, _thread_id: &str) -> Result<Value> {
|
||||
let Some(work) = epiphany_core::project_current_work(runtime_store)?.reorientation else {
|
||||
return Ok(json!({
|
||||
"source": "native",
|
||||
"status": "backendMissing",
|
||||
"bindingId": epiphany_core::EPIPHANY_REORIENT_LAUNCH_BINDING_ID,
|
||||
"note": "No keyed reorientation request is active."
|
||||
}));
|
||||
};
|
||||
let Some(job_id) = work.job_id else {
|
||||
return Ok(json!({
|
||||
"source": "native",
|
||||
"status": "pending",
|
||||
"bindingId": epiphany_core::EPIPHANY_REORIENT_LAUNCH_BINDING_ID,
|
||||
"note": "The keyed reorientation request is ready to launch."
|
||||
}));
|
||||
};
|
||||
let snapshot = epiphany_core::read_runtime_reorient_result(Some(runtime_store), &job_id);
|
||||
let mut value = json!({
|
||||
"source": "native",
|
||||
"status": snapshot.status,
|
||||
"bindingId": epiphany_core::EPIPHANY_REORIENT_LAUNCH_BINDING_ID,
|
||||
"runtimeJobId": job_id,
|
||||
"note": snapshot.note,
|
||||
});
|
||||
if let Some(finding) = snapshot.finding {
|
||||
value["finding"] = json!(finding);
|
||||
}
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn maybe_apply_role_self_patch(accepted: &Value, agent_memory_dir: &Path) -> Result<Option<Value>> {
|
||||
|
|
|
|||
|
|
@ -4,20 +4,14 @@ use anyhow::anyhow;
|
|||
use epiphany_core::EpiphanyCoordinatorRoleResultStatus;
|
||||
use epiphany_core::EpiphanyCoordinatorStatus;
|
||||
use epiphany_core::EpiphanyCoordinatorStatusInput;
|
||||
use epiphany_core::EpiphanyCrrcInput;
|
||||
use epiphany_core::EpiphanyCrrcReorientAction;
|
||||
use epiphany_core::EpiphanyCrrcAction;
|
||||
use epiphany_core::EpiphanyCrrcRecommendation;
|
||||
use epiphany_core::EpiphanyCrrcResultStatus;
|
||||
use epiphany_core::EpiphanyCrrcSceneAction;
|
||||
use epiphany_core::EpiphanyCrrcStateStatus;
|
||||
use epiphany_core::EpiphanyFreshnessInput;
|
||||
use epiphany_core::EpiphanyGraphFreshnessStatus;
|
||||
use epiphany_core::EpiphanyInvalidationStatus;
|
||||
use epiphany_core::EpiphanyJobStatus;
|
||||
use epiphany_core::EpiphanyJobsInput;
|
||||
use epiphany_core::EpiphanyReorientAction;
|
||||
use epiphany_core::EpiphanyReorientFreshnessStatus;
|
||||
use epiphany_core::EpiphanyReorientInput;
|
||||
use epiphany_core::EpiphanyReorientPressureLevel;
|
||||
use epiphany_core::EpiphanyRetrievalFreshnessStatus;
|
||||
use epiphany_core::EpiphanyRoleBoardCheckpointSummary;
|
||||
use epiphany_core::EpiphanyRoleBoardInput;
|
||||
use epiphany_core::EpiphanyRoleBoardJob;
|
||||
|
|
@ -29,20 +23,16 @@ use epiphany_core::EpiphanyRuntimeJobSnapshot;
|
|||
use epiphany_core::EpiphanyRuntimeJobStatus;
|
||||
use epiphany_core::EpiphanySceneInput;
|
||||
use epiphany_core::EpiphanyTokenUsageSnapshot;
|
||||
use epiphany_core::derive_freshness;
|
||||
use epiphany_core::derive_jobs;
|
||||
use epiphany_core::derive_planning_view;
|
||||
use epiphany_core::derive_pressure_view;
|
||||
use epiphany_core::derive_role_board;
|
||||
use epiphany_core::derive_scene;
|
||||
use epiphany_core::read_accepted_coordinator_state;
|
||||
use epiphany_core::recommend_crrc_action;
|
||||
use epiphany_core::recommend_reorientation;
|
||||
use epiphany_core::runtime_job_snapshot;
|
||||
use epiphany_self_policy::crrc_scene_action_to_coordinator_scene_action;
|
||||
use epiphany_self_policy::derive_coordinator_finding_signals;
|
||||
use epiphany_self_policy::derive_coordinator_status;
|
||||
use epiphany_self_policy::reorient_finding_already_accepted;
|
||||
use epiphany_state_model::EpiphanyThreadState;
|
||||
use serde_json::Value;
|
||||
use serde_json::json;
|
||||
|
|
@ -279,7 +269,7 @@ pub fn native_coordinator_json(runtime_store: &Path, thread_id: &str) -> Result<
|
|||
}
|
||||
|
||||
fn run_native_status(args: &Args, include_auxiliary_status: bool) -> Result<Value> {
|
||||
let cwd = absolute_path(&args.cwd)?;
|
||||
let _cwd = absolute_path(&args.cwd)?;
|
||||
let store_path = absolute_path(&args.store)?;
|
||||
let runtime_store_path = store_path.clone();
|
||||
let state = if store_path.exists() {
|
||||
|
|
@ -303,53 +293,33 @@ fn run_native_status(args: &Args, include_auxiliary_status: bool) -> Result<Valu
|
|||
reorient_binding_id: REORIENT_BINDING_ID,
|
||||
});
|
||||
let pressure = derive_pressure_view(None::<&EpiphanyTokenUsageSnapshot>);
|
||||
let freshness = derive_freshness(EpiphanyFreshnessInput {
|
||||
state: state_ref,
|
||||
retrieval_override: None,
|
||||
watcher: None,
|
||||
});
|
||||
let reorient_pressure_level = match pressure.level {
|
||||
epiphany_core::EpiphanyPressureLevel::Unknown => EpiphanyReorientPressureLevel::Unknown,
|
||||
epiphany_core::EpiphanyPressureLevel::Low => EpiphanyReorientPressureLevel::Low,
|
||||
epiphany_core::EpiphanyPressureLevel::Elevated => EpiphanyReorientPressureLevel::Medium,
|
||||
epiphany_core::EpiphanyPressureLevel::High => EpiphanyReorientPressureLevel::High,
|
||||
epiphany_core::EpiphanyPressureLevel::Critical => EpiphanyReorientPressureLevel::Critical,
|
||||
};
|
||||
let reorient_checkpoint = match state_ref
|
||||
.and_then(|state| state.investigation_checkpoint.clone())
|
||||
{
|
||||
Some(checkpoint) => Some(checkpoint),
|
||||
None => match epiphany_core::runtime_current_repo_model(&runtime_store_path)? {
|
||||
None => None,
|
||||
Some(_) => {
|
||||
let projection =
|
||||
epiphany_core::runtime_modeling_semantic_projection_input(&runtime_store_path)
|
||||
.context("failed to validate current RepoModel continuity projection")?;
|
||||
Some(
|
||||
epiphany_state_model::reorient_checkpoint_from_admitted_repo_model(
|
||||
projection.snapshot(),
|
||||
&projection.obligation().obligation_id,
|
||||
),
|
||||
)
|
||||
let mind = epiphany_core::assemble_mind_view(&runtime_store_path)?;
|
||||
let durable_checkpoint_present = mind.investigation_checkpoint.is_some();
|
||||
let latest_reorientation_decision = mind.reorientation_decisions.last();
|
||||
let reorientation_work = current_work.reorientation.as_ref();
|
||||
let reorient_action = latest_reorientation_decision
|
||||
.map(|decision| {
|
||||
if decision.mode == "regather" {
|
||||
EpiphanyReorientAction::Regather
|
||||
} else {
|
||||
EpiphanyReorientAction::Resume
|
||||
}
|
||||
})
|
||||
.unwrap_or(EpiphanyReorientAction::Resume);
|
||||
let reorient_next_action = latest_reorientation_decision
|
||||
.map(|decision| decision.next_safe_move.clone())
|
||||
.unwrap_or_else(|| "No unresolved keyed reorientation obligation.".into());
|
||||
let reorient_state_status = "ready";
|
||||
let reorient_decision = json!({
|
||||
"action": match reorient_action {
|
||||
EpiphanyReorientAction::Resume => "resume",
|
||||
EpiphanyReorientAction::Regather => "regather",
|
||||
},
|
||||
};
|
||||
let durable_checkpoint_present = reorient_checkpoint.is_some();
|
||||
let (reorient_state_status, reorient_decision) =
|
||||
recommend_reorientation(EpiphanyReorientInput {
|
||||
checkpoint: reorient_checkpoint.as_ref(),
|
||||
state_present: state_ref.is_some(),
|
||||
pressure_level: reorient_pressure_level,
|
||||
retrieval_status: reorient_retrieval_status(freshness.retrieval.status),
|
||||
retrieval_dirty_paths: freshness.retrieval.dirty_paths.clone(),
|
||||
graph_status: reorient_graph_status(freshness.graph.status),
|
||||
graph_dirty_paths: freshness.graph.dirty_paths.clone(),
|
||||
watcher_status: reorient_watcher_status(freshness.watcher.status),
|
||||
watcher_changed_paths: freshness.watcher.changed_paths.clone(),
|
||||
watcher_graph_node_ids: freshness.watcher.graph_node_ids.clone(),
|
||||
active_frontier_node_ids: freshness.watcher.active_frontier_node_ids.clone(),
|
||||
watched_root: freshness.watcher.watched_root.clone().or(Some(cwd.clone())),
|
||||
});
|
||||
"nextAction": reorient_next_action,
|
||||
"requestId": reorientation_work.map(|work| work.request.request_id.as_str()),
|
||||
"basisDigest": reorientation_work.map(|work| work.request.projection.projection_digest.as_str()),
|
||||
"authority": "keyedMindCurrentWork",
|
||||
});
|
||||
let planning = derive_planning_view(state_ref);
|
||||
let mut jobs = derive_jobs(EpiphanyJobsInput {
|
||||
state: state_ref,
|
||||
|
|
@ -361,35 +331,23 @@ fn run_native_status(args: &Args, include_auxiliary_status: bool) -> Result<Valu
|
|||
} else {
|
||||
EpiphanyCrrcStateStatus::Missing
|
||||
};
|
||||
let reorient_action = match reorient_decision.action {
|
||||
EpiphanyReorientAction::Resume => EpiphanyCrrcReorientAction::Resume,
|
||||
EpiphanyReorientAction::Regather => EpiphanyCrrcReorientAction::Regather,
|
||||
};
|
||||
let result_status =
|
||||
native_reorient_result_status(state_ref, &runtime_store_path, REORIENT_BINDING_ID);
|
||||
let reorient_finding =
|
||||
native_reorient_finding(state_ref, &runtime_store_path, REORIENT_BINDING_ID);
|
||||
let reorient_finding_accepted = state_ref.is_some_and(|state| {
|
||||
reorient_finding
|
||||
.as_ref()
|
||||
.is_some_and(|finding| reorient_finding_already_accepted(state, finding))
|
||||
});
|
||||
let result_status = keyed_reorientation_result_status(
|
||||
&runtime_store_path,
|
||||
reorientation_work,
|
||||
latest_reorientation_decision.is_some(),
|
||||
)?;
|
||||
let reorient_finding_accepted = latest_reorientation_decision.is_some();
|
||||
let modeling_result_status =
|
||||
native_role_result_status(state_ref, &runtime_store_path, MODELING_BINDING_ID);
|
||||
let research_result_status =
|
||||
native_role_result_status(state_ref, &runtime_store_path, RESEARCH_BINDING_ID);
|
||||
let verification_result_status =
|
||||
native_role_result_status(state_ref, &runtime_store_path, VERIFICATION_BINDING_ID);
|
||||
let recommendation = recommend_crrc_action(EpiphanyCrrcInput {
|
||||
loaded,
|
||||
state_status,
|
||||
should_prepare_compaction: pressure.should_prepare_compaction,
|
||||
reorient_action,
|
||||
result_status,
|
||||
checkpoint_present: durable_checkpoint_present,
|
||||
finding_present: reorient_finding.is_some(),
|
||||
finding_accepted: reorient_finding_accepted,
|
||||
});
|
||||
let recommendation = keyed_reorientation_recommendation(
|
||||
reorientation_work,
|
||||
latest_reorientation_decision,
|
||||
pressure.should_prepare_compaction,
|
||||
);
|
||||
let role_jobs = jobs
|
||||
.iter()
|
||||
.map(|job| EpiphanyRoleBoardJob {
|
||||
|
|
@ -416,14 +374,14 @@ fn run_native_status(args: &Args, include_auxiliary_status: bool) -> Result<Valu
|
|||
}
|
||||
})
|
||||
}),
|
||||
reorient_next_action: reorient_decision.next_action.clone(),
|
||||
reorient_next_action: reorient_next_action.clone(),
|
||||
jobs: role_jobs.clone(),
|
||||
crrc_action: recommendation.action,
|
||||
crrc_recommended_scene_action: recommendation
|
||||
.recommended_scene_action
|
||||
.map(crrc_scene_action_to_coordinator_scene_action),
|
||||
crrc_reason: recommendation.reason.clone(),
|
||||
reorient_decision_action: format!("{:?}", reorient_decision.action),
|
||||
reorient_decision_action: format!("{:?}", reorient_action),
|
||||
pressure_level: format!("{:?}", pressure.level),
|
||||
reorient_result_status: result_status,
|
||||
reorient_job: role_jobs
|
||||
|
|
@ -461,7 +419,7 @@ fn run_native_status(args: &Args, include_auxiliary_status: bool) -> Result<Valu
|
|||
research_finding.as_ref(),
|
||||
modeling_finding.as_ref(),
|
||||
verification_finding.as_ref(),
|
||||
reorient_finding.as_ref(),
|
||||
None,
|
||||
);
|
||||
let modeling_result_proposal_bound =
|
||||
latest_runtime_job_id_for_binding(state_ref, MODELING_BINDING_ID)
|
||||
|
|
@ -493,20 +451,14 @@ fn run_native_status(args: &Args, include_auxiliary_status: bool) -> Result<Valu
|
|||
let frontier_relinquishment =
|
||||
epiphany_core::runtime_latest_repo_frontier_relinquishment(&runtime_store_path)
|
||||
.context("failed to derive latest frontier relinquishment from runtime-spine state")?;
|
||||
let crrc_regather_current = crrc_regather_current_after_relinquishment(
|
||||
state_ref,
|
||||
&runtime_store_path,
|
||||
frontier_relinquishment.as_ref(),
|
||||
)?;
|
||||
let proposal_modeling_work = current_work.proposal_modeling.as_ref();
|
||||
let coordinator = derive_coordinator_status(EpiphanyCoordinatorStatusInput {
|
||||
state_status,
|
||||
checkpoint_present: durable_checkpoint_present,
|
||||
pressure: pressure.clone(),
|
||||
recommendation: recommendation.clone(),
|
||||
crrc_regather_current,
|
||||
roles: roles.clone(),
|
||||
reorient_action: reorient_decision.action,
|
||||
reorient_action,
|
||||
research_result_status,
|
||||
modeling_result_status,
|
||||
verification_result_status,
|
||||
|
|
@ -534,7 +486,7 @@ fn run_native_status(args: &Args, include_auxiliary_status: bool) -> Result<Valu
|
|||
verification_result_allows_implementation: finding_signals
|
||||
.verification_result_allows_implementation,
|
||||
verification_result_needs_evidence: finding_signals.verification_result_needs_evidence,
|
||||
reorient_finding_accepted: finding_signals.reorient_finding_accepted,
|
||||
reorient_finding_accepted,
|
||||
hands_frontier_ready,
|
||||
research_continuation_action,
|
||||
frontier_planning_stage: frontier_planning.stage,
|
||||
|
|
@ -645,7 +597,7 @@ fn run_native_status(args: &Args, include_auxiliary_status: bool) -> Result<Valu
|
|||
"verificationResultAccepted": finding_signals.verification_result_accepted,
|
||||
"verificationResultFailureReviewed": finding_signals.verification_result_failure_reviewed,
|
||||
},
|
||||
"reorientResult": native_reorient_result(state_ref, &runtime_store_path, REORIENT_BINDING_ID),
|
||||
"reorientResult": keyed_reorientation_result(&runtime_store_path, reorientation_work, latest_reorientation_decision)?,
|
||||
"crrc": {
|
||||
"threadId": thread_id,
|
||||
"source": "native",
|
||||
|
|
@ -907,33 +859,6 @@ fn native_role_result(
|
|||
result
|
||||
}
|
||||
|
||||
fn native_reorient_result(
|
||||
state: Option<&EpiphanyThreadState>,
|
||||
runtime_store: &Path,
|
||||
binding_id: &str,
|
||||
) -> Value {
|
||||
let Some(job_id) = latest_runtime_job_id_for_binding(state, binding_id) else {
|
||||
return json!({
|
||||
"source": "native",
|
||||
"status": "backendMissing",
|
||||
"bindingId": binding_id,
|
||||
"note": "No runtime-spine job is linked to this native reorientation binding.",
|
||||
});
|
||||
};
|
||||
let snapshot = epiphany_core::read_runtime_reorient_result(Some(runtime_store), job_id);
|
||||
let mut result = json!({
|
||||
"source": "native",
|
||||
"status": snapshot.status,
|
||||
"bindingId": binding_id,
|
||||
"runtimeJobId": job_id,
|
||||
"note": snapshot.note,
|
||||
});
|
||||
if let Some(finding) = snapshot.finding {
|
||||
result["finding"] = json!(finding);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn native_role_result_status(
|
||||
state: Option<&EpiphanyThreadState>,
|
||||
runtime_store: &Path,
|
||||
|
|
@ -959,51 +884,6 @@ fn native_role_finding(
|
|||
epiphany_core::read_runtime_role_result(Some(runtime_store), job_id, role_id).finding
|
||||
}
|
||||
|
||||
fn native_reorient_finding(
|
||||
state: Option<&EpiphanyThreadState>,
|
||||
runtime_store: &Path,
|
||||
binding_id: &str,
|
||||
) -> Option<epiphany_core::EpiphanyReorientFindingInterpretation> {
|
||||
let job_id = latest_runtime_job_id_for_binding(state, binding_id)?;
|
||||
epiphany_core::read_runtime_reorient_result(Some(runtime_store), job_id).finding
|
||||
}
|
||||
|
||||
fn native_reorient_result_status(
|
||||
state: Option<&EpiphanyThreadState>,
|
||||
runtime_store: &Path,
|
||||
binding_id: &str,
|
||||
) -> EpiphanyCrrcResultStatus {
|
||||
let Some(job_id) = latest_runtime_job_id_for_binding(state, binding_id) else {
|
||||
return EpiphanyCrrcResultStatus::BackendMissing;
|
||||
};
|
||||
match runtime_job_snapshot(runtime_store, job_id) {
|
||||
Ok(Some(snapshot)) => map_runtime_reorient_result_status(&snapshot),
|
||||
Ok(None) => EpiphanyCrrcResultStatus::BackendMissing,
|
||||
Err(_) => EpiphanyCrrcResultStatus::BackendUnavailable,
|
||||
}
|
||||
}
|
||||
|
||||
fn crrc_regather_current_after_relinquishment(
|
||||
state: Option<&EpiphanyThreadState>,
|
||||
runtime_store: &Path,
|
||||
relinquishment: Option<&epiphany_core::RepoFrontierRelinquishmentReceipt>,
|
||||
) -> Result<bool> {
|
||||
let Some(relinquishment) = relinquishment else {
|
||||
return Ok(true);
|
||||
};
|
||||
let Some(job_id) = latest_runtime_job_id_for_binding(state, REORIENT_BINDING_ID) else {
|
||||
return Ok(false);
|
||||
};
|
||||
let Some(snapshot) = runtime_job_snapshot(runtime_store, job_id)? else {
|
||||
return Ok(false);
|
||||
};
|
||||
let reorient_updated_at = chrono::DateTime::parse_from_rfc3339(&snapshot.job.updated_at)
|
||||
.context("reorientation job updated_at is not RFC3339")?;
|
||||
let relinquished_at = chrono::DateTime::parse_from_rfc3339(&relinquishment.relinquished_at)
|
||||
.context("frontier relinquishment timestamp is not RFC3339")?;
|
||||
Ok(reorient_updated_at > relinquished_at)
|
||||
}
|
||||
|
||||
fn reconcile_native_runtime_jobs(
|
||||
jobs: &mut [epiphany_core::EpiphanyJobView],
|
||||
state: Option<&EpiphanyThreadState>,
|
||||
|
|
@ -1037,33 +917,6 @@ fn latest_runtime_job_id_for_binding<'a>(
|
|||
.map(|link| link.runtime_job_id.as_str())
|
||||
}
|
||||
|
||||
fn reorient_retrieval_status(
|
||||
status: EpiphanyRetrievalFreshnessStatus,
|
||||
) -> EpiphanyReorientFreshnessStatus {
|
||||
match status {
|
||||
EpiphanyRetrievalFreshnessStatus::Missing
|
||||
| EpiphanyRetrievalFreshnessStatus::Unavailable => EpiphanyReorientFreshnessStatus::Unknown,
|
||||
EpiphanyRetrievalFreshnessStatus::Ready => EpiphanyReorientFreshnessStatus::Clean,
|
||||
EpiphanyRetrievalFreshnessStatus::Stale => EpiphanyReorientFreshnessStatus::Stale,
|
||||
EpiphanyRetrievalFreshnessStatus::Indexing => EpiphanyReorientFreshnessStatus::Dirty,
|
||||
}
|
||||
}
|
||||
|
||||
fn reorient_graph_status(status: EpiphanyGraphFreshnessStatus) -> EpiphanyReorientFreshnessStatus {
|
||||
match status {
|
||||
EpiphanyGraphFreshnessStatus::Missing => EpiphanyReorientFreshnessStatus::Unknown,
|
||||
EpiphanyGraphFreshnessStatus::Stale => EpiphanyReorientFreshnessStatus::Stale,
|
||||
}
|
||||
}
|
||||
|
||||
fn reorient_watcher_status(status: EpiphanyInvalidationStatus) -> EpiphanyReorientFreshnessStatus {
|
||||
match status {
|
||||
EpiphanyInvalidationStatus::Unavailable => EpiphanyReorientFreshnessStatus::Unknown,
|
||||
EpiphanyInvalidationStatus::Clean => EpiphanyReorientFreshnessStatus::Clean,
|
||||
EpiphanyInvalidationStatus::Changed => EpiphanyReorientFreshnessStatus::Changed,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_runtime_role_result_status(
|
||||
snapshot: &EpiphanyRuntimeJobSnapshot,
|
||||
) -> EpiphanyCoordinatorRoleResultStatus {
|
||||
|
|
@ -1104,6 +957,103 @@ fn map_runtime_reorient_result_status(
|
|||
}
|
||||
}
|
||||
|
||||
fn keyed_reorientation_result_status(
|
||||
runtime_store: &Path,
|
||||
work: Option<&epiphany_core::EpiphanyReorientationWorkProjection>,
|
||||
accepted_decision_present: bool,
|
||||
) -> Result<EpiphanyCrrcResultStatus> {
|
||||
let Some(work) = work else {
|
||||
return Ok(if accepted_decision_present {
|
||||
EpiphanyCrrcResultStatus::Completed
|
||||
} else {
|
||||
EpiphanyCrrcResultStatus::MissingBinding
|
||||
});
|
||||
};
|
||||
let Some(job_id) = work.job_id.as_deref() else {
|
||||
return Ok(EpiphanyCrrcResultStatus::MissingBinding);
|
||||
};
|
||||
let snapshot = runtime_job_snapshot(runtime_store, job_id)?
|
||||
.ok_or_else(|| anyhow!("keyed reorientation work lost its runtime job"))?;
|
||||
Ok(map_runtime_reorient_result_status(&snapshot))
|
||||
}
|
||||
|
||||
fn keyed_reorientation_recommendation(
|
||||
work: Option<&epiphany_core::EpiphanyReorientationWorkProjection>,
|
||||
accepted: Option<&epiphany_core::EpiphanyMindReorientationDecisionDocument>,
|
||||
should_prepare_compaction: bool,
|
||||
) -> EpiphanyCrrcRecommendation {
|
||||
let build = |action, scene, reason: &str| EpiphanyCrrcRecommendation {
|
||||
action,
|
||||
recommended_scene_action: scene,
|
||||
reason: reason.into(),
|
||||
};
|
||||
if let Some(work) = work {
|
||||
return match work.action {
|
||||
epiphany_core::EpiphanyAgentPassContinuationAction::Launch => build(
|
||||
EpiphanyCrrcAction::LaunchReorientWorker,
|
||||
Some(EpiphanyCrrcSceneAction::ReorientLaunch),
|
||||
"A keyed Mind continuity request has no live attempt; launch its exact sealed projection.",
|
||||
),
|
||||
epiphany_core::EpiphanyAgentPassContinuationAction::Wait => build(
|
||||
EpiphanyCrrcAction::WaitForReorientWorker,
|
||||
Some(EpiphanyCrrcSceneAction::ReorientResult),
|
||||
"The exact keyed reorientation attempt is still live.",
|
||||
),
|
||||
epiphany_core::EpiphanyAgentPassContinuationAction::Review => build(
|
||||
EpiphanyCrrcAction::ReviewReorientResult,
|
||||
Some(EpiphanyCrrcSceneAction::ReorientResult),
|
||||
"The exact keyed reorientation pass has a terminal decision or model-backed failure to review.",
|
||||
),
|
||||
};
|
||||
}
|
||||
if should_prepare_compaction {
|
||||
return build(
|
||||
EpiphanyCrrcAction::LaunchReorientWorker,
|
||||
Some(EpiphanyCrrcSceneAction::ReorientLaunch),
|
||||
"Context pressure requires a new keyed continuity request over the current Mind projection.",
|
||||
);
|
||||
}
|
||||
if accepted.is_some_and(|decision| decision.mode == "regather") {
|
||||
return build(
|
||||
EpiphanyCrrcAction::RegatherManually,
|
||||
Some(EpiphanyCrrcSceneAction::Reorient),
|
||||
"The accepted keyed continuity decision requires explicit regather work.",
|
||||
);
|
||||
}
|
||||
build(
|
||||
EpiphanyCrrcAction::Continue,
|
||||
Some(EpiphanyCrrcSceneAction::Reorient),
|
||||
"No unresolved keyed reorientation obligation exists.",
|
||||
)
|
||||
}
|
||||
|
||||
fn keyed_reorientation_result(
|
||||
runtime_store: &Path,
|
||||
work: Option<&epiphany_core::EpiphanyReorientationWorkProjection>,
|
||||
accepted: Option<&epiphany_core::EpiphanyMindReorientationDecisionDocument>,
|
||||
) -> Result<Value> {
|
||||
let Some(work) = work else {
|
||||
return Ok(accepted
|
||||
.map(|decision| json!({"status": "accepted", "decision": decision}))
|
||||
.unwrap_or(Value::Null));
|
||||
};
|
||||
let Some(job_id) = work.job_id.as_deref() else {
|
||||
return Ok(json!({
|
||||
"status": "unlaunched",
|
||||
"requestId": work.request.request_id,
|
||||
}));
|
||||
};
|
||||
let snapshot = runtime_job_snapshot(runtime_store, job_id)?
|
||||
.ok_or_else(|| anyhow!("keyed reorientation work lost its runtime job"))?;
|
||||
let result = epiphany_core::runtime_reorient_worker_result(runtime_store, job_id)?;
|
||||
Ok(json!({
|
||||
"status": format!("{:?}", snapshot.job.status).to_ascii_lowercase(),
|
||||
"requestId": work.request.request_id,
|
||||
"jobId": job_id,
|
||||
"result": result,
|
||||
}))
|
||||
}
|
||||
|
||||
fn map_runtime_job_status(status: EpiphanyRuntimeJobStatus) -> EpiphanyJobStatus {
|
||||
match status {
|
||||
EpiphanyRuntimeJobStatus::Queued => EpiphanyJobStatus::Pending,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use crate::coordinator_results::latest_runtime_link;
|
||||
use crate::coordinator_state::changed_fields;
|
||||
use crate::*;
|
||||
use anyhow::Context;
|
||||
use epiphany_state_model::EpiphanyThreadState;
|
||||
use std::path::Path;
|
||||
|
||||
|
|
@ -37,16 +36,6 @@ pub struct EpiphanyRoleAcceptanceUpdate {
|
|||
pub mind_review: MindGatewayReview,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EpiphanyReorientAcceptanceUpdate {
|
||||
pub state_update: EpiphanyStateUpdate,
|
||||
pub changed_fields: Vec<EpiphanyStateUpdatedField>,
|
||||
pub accepted_receipt_id: String,
|
||||
pub accepted_observation_id: String,
|
||||
pub accepted_evidence_id: String,
|
||||
pub mind_review: MindGatewayReview,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EpiphanyNativeRoleAcceptance {
|
||||
pub state: EpiphanyThreadState,
|
||||
|
|
@ -54,118 +43,6 @@ pub struct EpiphanyNativeRoleAcceptance {
|
|||
pub update: EpiphanyRoleAcceptanceUpdate,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EpiphanyNativeReorientAcceptance {
|
||||
pub state: EpiphanyThreadState,
|
||||
pub finding: EpiphanyReorientFindingInterpretation,
|
||||
pub update: EpiphanyReorientAcceptanceUpdate,
|
||||
}
|
||||
|
||||
pub fn accept_coordinator_reorient_finding(
|
||||
store: &Path,
|
||||
thread_id: &str,
|
||||
state: &EpiphanyThreadState,
|
||||
binding_id: &str,
|
||||
expected_revision: Option<u64>,
|
||||
reference_turn_id: Option<String>,
|
||||
accepted_at: String,
|
||||
nonce: &str,
|
||||
update_scratch: bool,
|
||||
update_investigation_checkpoint: bool,
|
||||
) -> anyhow::Result<EpiphanyNativeReorientAcceptance> {
|
||||
let investigation_checkpoint = if update_investigation_checkpoint {
|
||||
match state.investigation_checkpoint.clone() {
|
||||
Some(checkpoint) => Some(checkpoint),
|
||||
None => {
|
||||
let model = crate::assemble_repo_model_view(store).context(
|
||||
"cannot accept reorientation without a current keyed RepoModel checkpoint",
|
||||
)?;
|
||||
Some(
|
||||
epiphany_state_model::reorient_checkpoint_from_admitted_repo_model(
|
||||
&model.memory_context_projection(),
|
||||
&model.projection_digest,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let snapshot = read_reorient_result_snapshot(Some(state), Some(store), binding_id);
|
||||
if snapshot.status != EpiphanyCrrcResultStatus::Completed {
|
||||
return Err(anyhow::anyhow!(
|
||||
"reorientation finding is not completed: {}",
|
||||
snapshot.note
|
||||
));
|
||||
}
|
||||
let finding = snapshot
|
||||
.finding
|
||||
.ok_or_else(|| anyhow::anyhow!("completed reorientation result has no typed finding"))?;
|
||||
let update = build_native_reorient_acceptance_update(
|
||||
expected_revision,
|
||||
binding_id,
|
||||
&finding,
|
||||
format!("ev-reorient-{nonce}"),
|
||||
format!("obs-reorient-{nonce}"),
|
||||
accepted_at.clone(),
|
||||
update_scratch,
|
||||
update_investigation_checkpoint,
|
||||
investigation_checkpoint,
|
||||
)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let contract = acceptance_launch_contract_for_binding(store, state, binding_id, "reorient")
|
||||
.map_err(|error| anyhow::anyhow!(error.to_string()))?;
|
||||
enforce_acceptance_receipt_proofs(
|
||||
&contract,
|
||||
&reorient_acceptance_claimed_effects(),
|
||||
&[
|
||||
MIND_GATEWAY_REVIEW_TYPE.to_string(),
|
||||
CONTINUITY_RECOVERY_RECEIPT_TYPE.to_string(),
|
||||
],
|
||||
&[
|
||||
MIND_GATEWAY_REVIEW_TYPE.to_string(),
|
||||
CONTINUITY_RECOVERY_RECEIPT_TYPE.to_string(),
|
||||
],
|
||||
)
|
||||
.map_err(|error| anyhow::anyhow!(error.to_string()))?;
|
||||
let recovery = continuity_recovery_receipt_from_reorient_finding(
|
||||
format!("continuity-recovery-{}", update.accepted_receipt_id),
|
||||
binding_id.to_string(),
|
||||
&finding,
|
||||
accepted_at.clone(),
|
||||
);
|
||||
let next_state = apply_coordinator_state_update_to_state(
|
||||
state,
|
||||
update.state_update.clone(),
|
||||
reference_turn_id,
|
||||
)?;
|
||||
let commit = mind_state_commit_receipt(
|
||||
format!("mind-commit-{}", update.accepted_receipt_id),
|
||||
&update.mind_review,
|
||||
next_state.revision,
|
||||
update
|
||||
.changed_fields
|
||||
.iter()
|
||||
.map(|field| format!("{field:?}"))
|
||||
.collect(),
|
||||
accepted_at,
|
||||
);
|
||||
commit_state_with_mind_witness(
|
||||
store,
|
||||
thread_id,
|
||||
state,
|
||||
&next_state,
|
||||
&update.mind_review,
|
||||
&commit,
|
||||
&[EpiphanyAcceptancePrerequisite::Continuity(recovery)],
|
||||
)?;
|
||||
Ok(EpiphanyNativeReorientAcceptance {
|
||||
state: next_state,
|
||||
finding,
|
||||
update,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn accept_coordinator_role_finding(
|
||||
store: &Path,
|
||||
thread_id: &str,
|
||||
|
|
@ -332,48 +209,6 @@ pub fn completed_role_finding(
|
|||
})
|
||||
}
|
||||
|
||||
pub fn completed_reorient_finding(
|
||||
runtime_store_path: Option<&Path>,
|
||||
state: &EpiphanyThreadState,
|
||||
binding_id: &str,
|
||||
) -> Result<EpiphanyReorientFindingInterpretation, EpiphanyCoordinatorAdmissionError> {
|
||||
let Some(link) = latest_runtime_link(state, binding_id) else {
|
||||
return if state
|
||||
.job_bindings
|
||||
.iter()
|
||||
.any(|binding| binding.id == binding_id)
|
||||
{
|
||||
Err(EpiphanyCoordinatorAdmissionError::InvalidRequest(
|
||||
"reorientation findings without runtime-spine results are unsupported; accept only typed runtime-spine results".to_string(),
|
||||
))
|
||||
} else {
|
||||
Err(EpiphanyCoordinatorAdmissionError::InvalidRequest(format!(
|
||||
"epiphany reorientation binding {binding_id:?} was not found"
|
||||
)))
|
||||
};
|
||||
};
|
||||
let snapshot = read_runtime_reorient_result(runtime_store_path, &link.runtime_job_id);
|
||||
if snapshot.status != EpiphanyCrrcResultStatus::Completed {
|
||||
return Err(EpiphanyCoordinatorAdmissionError::InvalidRequest(format!(
|
||||
"cannot accept reorientation result while worker status is {:?}",
|
||||
snapshot.status
|
||||
)));
|
||||
}
|
||||
let store = runtime_store_path.ok_or_else(|| {
|
||||
EpiphanyCoordinatorAdmissionError::InvalidRequest(
|
||||
"cannot accept completed reorientation worker without a loaded runtime-spine store"
|
||||
.to_string(),
|
||||
)
|
||||
})?;
|
||||
load_launch_organ_contract(store, &link.runtime_job_id, "reorient")?;
|
||||
snapshot.finding.ok_or_else(|| {
|
||||
EpiphanyCoordinatorAdmissionError::InvalidRequest(
|
||||
"cannot accept completed reorientation worker because no typed runtime-spine result was recorded"
|
||||
.to_string(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load_launch_organ_contract(
|
||||
runtime_store_path: &Path,
|
||||
job_id: &str,
|
||||
|
|
@ -495,13 +330,6 @@ pub fn role_acceptance_enforceable_receipts(role_id: EpiphanyRoleResultRoleId) -
|
|||
receipts
|
||||
}
|
||||
|
||||
pub fn reorient_acceptance_claimed_effects() -> Vec<EpiphanyReceiptEffectKind> {
|
||||
vec![
|
||||
EpiphanyReceiptEffectKind::StateAdmission,
|
||||
EpiphanyReceiptEffectKind::ContinuityRecovery,
|
||||
]
|
||||
}
|
||||
|
||||
pub fn coordinator_acceptance_cache(store_path: &Path) -> anyhow::Result<CultCache> {
|
||||
let mut cache = runtime_spine_cache(store_path)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
|
|
@ -649,76 +477,6 @@ pub fn build_native_role_acceptance_update(
|
|||
})
|
||||
}
|
||||
|
||||
pub fn build_native_reorient_acceptance_update(
|
||||
expected_revision: Option<u64>,
|
||||
binding_id: &str,
|
||||
finding: &EpiphanyReorientFindingInterpretation,
|
||||
accepted_evidence_id: String,
|
||||
accepted_observation_id: String,
|
||||
accepted_at: String,
|
||||
update_scratch: bool,
|
||||
update_investigation_checkpoint: bool,
|
||||
checkpoint: Option<epiphany_state_model::EpiphanyInvestigationCheckpoint>,
|
||||
) -> Result<EpiphanyReorientAcceptanceUpdate, String> {
|
||||
let mind_finding = EpiphanyReorientAcceptanceFinding {
|
||||
mode: finding.mode.clone(),
|
||||
summary: finding.summary.clone(),
|
||||
next_safe_move: finding.next_safe_move.clone(),
|
||||
checkpoint_still_valid: finding.checkpoint_still_valid,
|
||||
files_inspected: finding.files_inspected.clone(),
|
||||
runtime_result_id: finding.runtime_result_id.clone(),
|
||||
runtime_job_id: finding.runtime_job_id.clone(),
|
||||
};
|
||||
let mind_review = mind_review_reorient_acceptance(
|
||||
binding_id,
|
||||
&mind_finding,
|
||||
update_scratch,
|
||||
update_investigation_checkpoint,
|
||||
);
|
||||
mind_review_allows_state(&mind_review)?;
|
||||
let bundle = build_reorient_acceptance_bundle(
|
||||
binding_id,
|
||||
mind_finding,
|
||||
accepted_evidence_id,
|
||||
accepted_observation_id,
|
||||
accepted_at,
|
||||
update_scratch,
|
||||
update_investigation_checkpoint
|
||||
.then_some(checkpoint)
|
||||
.flatten(),
|
||||
)?;
|
||||
let accepted_receipt_id = bundle.accepted_receipt_id.clone();
|
||||
let accepted_observation_id = bundle.accepted_observation_id.clone();
|
||||
let accepted_evidence_id = bundle.accepted_evidence_id.clone();
|
||||
let mut changed_fields = vec![
|
||||
EpiphanyStateUpdatedField::AcceptanceReceipts,
|
||||
EpiphanyStateUpdatedField::Observations,
|
||||
EpiphanyStateUpdatedField::Evidence,
|
||||
];
|
||||
if bundle.scratch.is_some() {
|
||||
changed_fields.push(EpiphanyStateUpdatedField::Scratch);
|
||||
}
|
||||
if bundle.investigation_checkpoint.is_some() {
|
||||
changed_fields.push(EpiphanyStateUpdatedField::InvestigationCheckpoint);
|
||||
}
|
||||
Ok(EpiphanyReorientAcceptanceUpdate {
|
||||
state_update: EpiphanyStateUpdate {
|
||||
expected_revision,
|
||||
scratch: bundle.scratch,
|
||||
investigation_checkpoint: bundle.investigation_checkpoint,
|
||||
acceptance_receipts: vec![bundle.receipt],
|
||||
observations: vec![bundle.observation],
|
||||
evidence: vec![bundle.evidence],
|
||||
..Default::default()
|
||||
},
|
||||
changed_fields,
|
||||
accepted_receipt_id,
|
||||
accepted_observation_id,
|
||||
accepted_evidence_id,
|
||||
mind_review,
|
||||
})
|
||||
}
|
||||
|
||||
fn state_update_from_patch(
|
||||
expected_revision: Option<u64>,
|
||||
patch: EpiphanyRoleStatePatchDocument,
|
||||
|
|
|
|||
|
|
@ -5,11 +5,9 @@ use crate::EpiphanyJobInterruptRequest;
|
|||
use crate::EpiphanyJobInterruptResult;
|
||||
use crate::EpiphanyJobLaunchRequest;
|
||||
use crate::EpiphanyJobLaunchResult;
|
||||
use crate::EpiphanyNativeReorientAcceptance;
|
||||
use crate::EpiphanyNativeRoleAcceptance;
|
||||
use crate::EpiphanyRoleResultRoleId;
|
||||
use crate::EpiphanyStateUpdate;
|
||||
use crate::accept_coordinator_reorient_finding;
|
||||
use crate::accept_coordinator_role_finding;
|
||||
use crate::apply_coordinator_state_update;
|
||||
use crate::apply_coordinator_state_update_from_state;
|
||||
|
|
@ -162,32 +160,6 @@ impl EpiphanyCoordinatorService {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn accept_reorient(
|
||||
&self,
|
||||
thread_id: &str,
|
||||
state: &EpiphanyThreadState,
|
||||
binding_id: &str,
|
||||
expected_revision: Option<u64>,
|
||||
reference_turn_id: Option<String>,
|
||||
accepted_at: String,
|
||||
nonce: &str,
|
||||
update_scratch: bool,
|
||||
update_investigation_checkpoint: bool,
|
||||
) -> Result<EpiphanyNativeReorientAcceptance> {
|
||||
accept_coordinator_reorient_finding(
|
||||
&self.store,
|
||||
thread_id,
|
||||
state,
|
||||
binding_id,
|
||||
expected_revision,
|
||||
reference_turn_id,
|
||||
accepted_at,
|
||||
nonce,
|
||||
update_scratch,
|
||||
update_investigation_checkpoint,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn interrupt_job(
|
||||
&self,
|
||||
thread_id: &str,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ pub struct EpiphanyCurrentWorkProjection {
|
|||
pub proposal_modeling: Option<EpiphanyProposalModelingWorkProjection>,
|
||||
pub frontier_verdict_modeling: Option<EpiphanyFrontierVerdictModelingWorkProjection>,
|
||||
pub verification: Option<EpiphanyVerificationWorkProjection>,
|
||||
pub reorientation: Option<crate::EpiphanyReorientationWorkProjection>,
|
||||
pub hands_frontier_ready: bool,
|
||||
}
|
||||
|
||||
|
|
@ -339,6 +340,7 @@ pub fn project_current_work(store_path: impl AsRef<Path>) -> Result<EpiphanyCurr
|
|||
proposal_modeling: current_proposal_modeling_work(&cache)?,
|
||||
frontier_verdict_modeling: current_frontier_verdict_modeling_work(&cache)?,
|
||||
verification: current_verification_work(&cache)?,
|
||||
reorientation: crate::reorientation_work::current_reorientation_work(&cache)?,
|
||||
hands_frontier_ready: crate::runtime_has_actionable_hands_frontier(store_path)?,
|
||||
})
|
||||
}
|
||||
|
|
@ -2119,7 +2121,7 @@ pub fn accept_frontier_verdict_modeling_result(
|
|||
}
|
||||
}
|
||||
|
||||
fn commit_current_work_launch(
|
||||
pub(crate) fn commit_current_work_launch(
|
||||
store_path: &Path,
|
||||
cache: &CultCache,
|
||||
mut expected: Vec<CultCacheEnvelope>,
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ mod prompt_context;
|
|||
mod proposal;
|
||||
mod public_source_identity;
|
||||
mod reasoning_context;
|
||||
mod reorientation_work;
|
||||
mod repo_model_documents;
|
||||
mod repo_model_gateway;
|
||||
mod repository_body_observer;
|
||||
|
|
@ -106,8 +107,6 @@ pub use agent_launch::build_epiphany_admitted_model_direction_consideration_laun
|
|||
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_reorient_launch_request;
|
||||
pub use agent_launch::build_epiphany_reorient_launch_request_with_dynamic_context;
|
||||
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;
|
||||
|
|
@ -203,22 +202,16 @@ pub use causal_work_identity::*;
|
|||
pub use continuity_gateway::*;
|
||||
pub use coordinator_acceptance::EpiphanyAcceptancePrerequisite;
|
||||
pub use coordinator_acceptance::EpiphanyCoordinatorAdmissionError;
|
||||
pub use coordinator_acceptance::EpiphanyNativeReorientAcceptance;
|
||||
pub use coordinator_acceptance::EpiphanyNativeRoleAcceptance;
|
||||
pub use coordinator_acceptance::EpiphanyReorientAcceptanceUpdate;
|
||||
pub use coordinator_acceptance::EpiphanyRoleAcceptanceUpdate;
|
||||
pub use coordinator_acceptance::accept_coordinator_reorient_finding;
|
||||
pub use coordinator_acceptance::accept_coordinator_role_finding;
|
||||
pub use coordinator_acceptance::acceptance_launch_contract_for_binding;
|
||||
pub use coordinator_acceptance::build_native_reorient_acceptance_update;
|
||||
pub use coordinator_acceptance::build_native_role_acceptance_update;
|
||||
pub use coordinator_acceptance::completed_reorient_finding;
|
||||
pub use coordinator_acceptance::completed_role_finding;
|
||||
pub use coordinator_acceptance::coordinator_acceptance_cache;
|
||||
pub use coordinator_acceptance::enforce_acceptance_receipt_proofs;
|
||||
pub use coordinator_acceptance::load_launch_organ_contract;
|
||||
pub use coordinator_acceptance::read_accepted_coordinator_state;
|
||||
pub use coordinator_acceptance::reorient_acceptance_claimed_effects;
|
||||
pub use coordinator_acceptance::role_acceptance_claimed_effects;
|
||||
pub use coordinator_acceptance::role_acceptance_enforceable_receipts;
|
||||
pub use coordinator_acceptance::state_update_from_role_patch;
|
||||
|
|
@ -974,6 +967,7 @@ pub use proposal::EpiphanyMapProposalInput;
|
|||
pub use proposal::propose_map_update;
|
||||
pub use public_source_identity::ImmutableGithubSource;
|
||||
pub use reasoning_context::*;
|
||||
pub use reorientation_work::*;
|
||||
pub use repo_model_documents::*;
|
||||
pub use repo_model_gateway::*;
|
||||
pub use repository_body_observer::*;
|
||||
|
|
@ -1267,15 +1261,7 @@ pub use surfaces::EpiphanyPressureStatus;
|
|||
pub use surfaces::EpiphanyReorientAcceptanceBundle;
|
||||
pub use surfaces::EpiphanyReorientAcceptanceFinding;
|
||||
pub use surfaces::EpiphanyReorientAction;
|
||||
pub use surfaces::EpiphanyReorientCheckpointStatus;
|
||||
pub use surfaces::EpiphanyReorientDecision;
|
||||
pub use surfaces::EpiphanyReorientFindingInterpretation;
|
||||
pub use surfaces::EpiphanyReorientFreshnessStatus;
|
||||
pub use surfaces::EpiphanyReorientInput;
|
||||
pub use surfaces::EpiphanyReorientLaunchRequestInput;
|
||||
pub use surfaces::EpiphanyReorientPressureLevel;
|
||||
pub use surfaces::EpiphanyReorientReason;
|
||||
pub use surfaces::EpiphanyReorientStateStatus;
|
||||
pub use surfaces::EpiphanyReorientWorkerLaunchDocument;
|
||||
pub use surfaces::EpiphanyRetrievalFreshness;
|
||||
pub use surfaces::EpiphanyRetrievalFreshnessStatus;
|
||||
|
|
@ -1327,7 +1313,6 @@ pub use surfaces::RepoFrontierProposalModelingContextProjection;
|
|||
pub use surfaces::RepoFrontierResearchContextProjection;
|
||||
pub use surfaces::RepoFrontierVerificationContextProjection;
|
||||
pub use surfaces::build_reorient_acceptance_bundle;
|
||||
pub use surfaces::build_reorient_job_launch_request;
|
||||
pub use surfaces::build_role_acceptance_bundle;
|
||||
pub use surfaces::default_epiphany_view_lenses;
|
||||
pub use surfaces::derive_context;
|
||||
|
|
@ -1347,7 +1332,6 @@ pub use surfaces::interpret_runtime_reorient_worker_result;
|
|||
pub use surfaces::interpret_runtime_role_worker_result;
|
||||
pub use surfaces::modeling_role_state_patch_policy_errors;
|
||||
pub use surfaces::recommend_crrc_action;
|
||||
pub use surfaces::recommend_reorientation;
|
||||
pub use surfaces::render_role_board_note;
|
||||
pub use surfaces::reorientation_role_status;
|
||||
pub use surfaces::research_role_state_patch_policy_errors;
|
||||
|
|
|
|||
|
|
@ -263,6 +263,8 @@ pub struct EpiphanyMindView {
|
|||
pub observations: Vec<EpiphanyObservation>,
|
||||
pub evidence: Vec<EpiphanyEvidenceRecord>,
|
||||
pub verification_audits: Vec<EpiphanyMindVerificationAuditDocument>,
|
||||
pub reorientation_decisions: Vec<crate::EpiphanyMindReorientationDecisionDocument>,
|
||||
pub reorientation_failures: Vec<crate::EpiphanyMindReorientationPassFailureDocument>,
|
||||
pub investigation_checkpoint: Option<EpiphanyInvestigationCheckpoint>,
|
||||
pub mode: Option<EpiphanyModeState>,
|
||||
pub planning: EpiphanyPlanningState,
|
||||
|
|
@ -280,6 +282,8 @@ pub(crate) fn register_mind_document_types(cache: &mut CultCache) -> Result<()>
|
|||
cache.register_entry_type::<EpiphanyMindObservationDocument>()?;
|
||||
cache.register_entry_type::<EpiphanyMindEvidenceDocument>()?;
|
||||
cache.register_entry_type::<EpiphanyMindVerificationAuditDocument>()?;
|
||||
cache.register_entry_type::<crate::EpiphanyMindReorientationDecisionDocument>()?;
|
||||
cache.register_entry_type::<crate::EpiphanyMindReorientationPassFailureDocument>()?;
|
||||
cache.register_entry_type::<EpiphanyMindInvestigationCheckpointDocument>()?;
|
||||
cache.register_entry_type::<EpiphanyMindPlanningCaptureDocument>()?;
|
||||
cache.register_entry_type::<EpiphanyMindBacklogItemDocument>()?;
|
||||
|
|
@ -398,6 +402,28 @@ pub(crate) fn validate_mind_write_envelope(envelope: &CultCacheEnvelope) -> Resu
|
|||
rmp_serde::from_slice(&envelope.payload)?;
|
||||
value.validate()?;
|
||||
value.audit_id
|
||||
} else if envelope.r#type == crate::EpiphanyMindReorientationDecisionDocument::TYPE {
|
||||
let value: crate::EpiphanyMindReorientationDecisionDocument =
|
||||
rmp_serde::from_slice(&envelope.payload)?;
|
||||
if value.schema_version != crate::MIND_REORIENTATION_DECISION_SCHEMA_VERSION
|
||||
|| value.decision_id.trim().is_empty()
|
||||
|| value.request_id.trim().is_empty()
|
||||
|| value.decision_context_id.trim().is_empty()
|
||||
{
|
||||
return Err(anyhow!("Mind reorientation decision is invalid"));
|
||||
}
|
||||
value.decision_id
|
||||
} else if envelope.r#type == crate::EpiphanyMindReorientationPassFailureDocument::TYPE {
|
||||
let value: crate::EpiphanyMindReorientationPassFailureDocument =
|
||||
rmp_serde::from_slice(&envelope.payload)?;
|
||||
if value.schema_version != crate::MIND_REORIENTATION_PASS_FAILURE_SCHEMA_VERSION
|
||||
|| value.failure_id.trim().is_empty()
|
||||
|| value.request_id.trim().is_empty()
|
||||
|| value.decision_context_id.trim().is_empty()
|
||||
{
|
||||
return Err(anyhow!("Mind reorientation failure is invalid"));
|
||||
}
|
||||
value.failure_id
|
||||
} else if envelope.r#type == EpiphanyMindInvestigationCheckpointDocument::TYPE {
|
||||
rmp_serde::from_slice::<EpiphanyMindInvestigationCheckpointDocument>(&envelope.payload)?
|
||||
.value
|
||||
|
|
@ -462,6 +488,10 @@ pub fn assemble_mind_view(store_path: impl AsRef<Path>) -> Result<EpiphanyMindVi
|
|||
values::<EpiphanyMindObservationDocument, _>(&cache, |value| value.value)?;
|
||||
let mut evidence = values::<EpiphanyMindEvidenceDocument, _>(&cache, |value| value.value)?;
|
||||
let mut verification_audits = cache.get_all::<EpiphanyMindVerificationAuditDocument>()?;
|
||||
let mut reorientation_decisions =
|
||||
cache.get_all::<crate::EpiphanyMindReorientationDecisionDocument>()?;
|
||||
let mut reorientation_failures =
|
||||
cache.get_all::<crate::EpiphanyMindReorientationPassFailureDocument>()?;
|
||||
let checkpoints =
|
||||
values::<EpiphanyMindInvestigationCheckpointDocument, _>(&cache, |value| value.value)?;
|
||||
let mut captures =
|
||||
|
|
@ -503,6 +533,8 @@ pub fn assemble_mind_view(store_path: impl AsRef<Path>) -> Result<EpiphanyMindVi
|
|||
observations.sort_by(|left, right| left.id.cmp(&right.id));
|
||||
evidence.sort_by(|left, right| left.id.cmp(&right.id));
|
||||
verification_audits.sort_by(|left, right| left.audit_id.cmp(&right.audit_id));
|
||||
reorientation_decisions.sort_by(|left, right| left.decision_id.cmp(&right.decision_id));
|
||||
reorientation_failures.sort_by(|left, right| left.failure_id.cmp(&right.failure_id));
|
||||
captures.sort_by(|left, right| left.id.cmp(&right.id));
|
||||
backlog_items.sort_by(|left, right| left.id.cmp(&right.id));
|
||||
roadmap_streams.sort_by(|left, right| left.id.cmp(&right.id));
|
||||
|
|
@ -545,6 +577,8 @@ pub fn assemble_mind_view(store_path: impl AsRef<Path>) -> Result<EpiphanyMindVi
|
|||
observations,
|
||||
evidence,
|
||||
verification_audits,
|
||||
reorientation_decisions,
|
||||
reorientation_failures,
|
||||
investigation_checkpoint,
|
||||
mode,
|
||||
planning: EpiphanyPlanningState {
|
||||
|
|
@ -583,6 +617,8 @@ fn canonical_mind_versions(
|
|||
| EpiphanyMindObservationDocument::TYPE
|
||||
| EpiphanyMindEvidenceDocument::TYPE
|
||||
| EpiphanyMindVerificationAuditDocument::TYPE
|
||||
| crate::EpiphanyMindReorientationDecisionDocument::TYPE
|
||||
| crate::EpiphanyMindReorientationPassFailureDocument::TYPE
|
||||
| EpiphanyMindInvestigationCheckpointDocument::TYPE
|
||||
| EpiphanyMindPlanningCaptureDocument::TYPE
|
||||
| EpiphanyMindBacklogItemDocument::TYPE
|
||||
|
|
|
|||
|
|
@ -110,6 +110,8 @@ pub struct EpiphanyMindPromptProjection {
|
|||
pub observations: Vec<epiphany_state_model::EpiphanyObservation>,
|
||||
pub evidence: Vec<epiphany_state_model::EpiphanyEvidenceRecord>,
|
||||
pub verification_audits: Vec<crate::EpiphanyMindVerificationAuditDocument>,
|
||||
pub reorientation_decisions: Vec<crate::EpiphanyMindReorientationDecisionDocument>,
|
||||
pub reorientation_failures: Vec<crate::EpiphanyMindReorientationPassFailureDocument>,
|
||||
pub investigation_checkpoint: Option<epiphany_state_model::EpiphanyInvestigationCheckpoint>,
|
||||
pub mode: Option<epiphany_state_model::EpiphanyModeState>,
|
||||
pub planning: epiphany_state_model::EpiphanyPlanningState,
|
||||
|
|
@ -130,6 +132,8 @@ impl From<crate::EpiphanyMindView> for EpiphanyMindPromptProjection {
|
|||
observations: value.observations,
|
||||
evidence: value.evidence,
|
||||
verification_audits: value.verification_audits,
|
||||
reorientation_decisions: value.reorientation_decisions,
|
||||
reorientation_failures: value.reorientation_failures,
|
||||
investigation_checkpoint: value.investigation_checkpoint,
|
||||
mode: value.mode,
|
||||
planning: value.planning,
|
||||
|
|
@ -145,6 +149,12 @@ pub struct EpiphanyRoleReasoningProjection {
|
|||
pub mind: EpiphanyMindPromptProjection,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct EpiphanyReorientationReasoningProjection {
|
||||
pub authority: crate::EpiphanyReorientWorkerLaunchDocument,
|
||||
pub request: crate::EpiphanyReorientationRequest,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct EpiphanyMindDocumentVersion {
|
||||
pub store_id: String,
|
||||
|
|
@ -194,7 +204,7 @@ impl EpiphanyMindDocumentVersion {
|
|||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum EpiphanyReasoningProjection {
|
||||
RolePass(EpiphanyRoleReasoningProjection),
|
||||
ReorientLaunch(crate::EpiphanyReorientWorkerLaunchDocument),
|
||||
ReorientLaunch(EpiphanyReorientationReasoningProjection),
|
||||
PersonaProjector(PersonaProjectorInput),
|
||||
PersonaTurn(PersonaTurnInput),
|
||||
PersonaInterpreter(PersonaInterpreterInput),
|
||||
|
|
@ -462,7 +472,7 @@ pub fn worker_reasoning_basis(
|
|||
) -> Result<EpiphanyReasoningBasis> {
|
||||
let mut cache = runtime_spine_cache(store_path)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
let launch_envelope = cache
|
||||
cache
|
||||
.get_envelope::<EpiphanyRuntimeWorkerLaunchRequest>(&launch.job_id)?
|
||||
.ok_or_else(|| anyhow!("worker reasoning basis lost its launch envelope"))?;
|
||||
let launch_document = launch.launch_document()?;
|
||||
|
|
@ -478,13 +488,21 @@ pub fn worker_reasoning_basis(
|
|||
}),
|
||||
)
|
||||
}
|
||||
EpiphanyWorkerLaunchDocument::Reorient(document) => (
|
||||
vec![EpiphanyMindDocumentVersion::from_envelope(
|
||||
"epiphany-mind",
|
||||
&launch_envelope,
|
||||
)?],
|
||||
EpiphanyReasoningProjection::ReorientLaunch(document),
|
||||
),
|
||||
EpiphanyWorkerLaunchDocument::Reorient(document) => {
|
||||
let request = cache
|
||||
.get::<crate::EpiphanyReorientationRequest>(&document.request_id)?
|
||||
.ok_or_else(|| anyhow!("reorientation reasoning basis lost its request"))?;
|
||||
crate::reorientation_work::validate_reorientation_request_current(&cache, &request)?;
|
||||
(
|
||||
request.source_documents.clone(),
|
||||
EpiphanyReasoningProjection::ReorientLaunch(
|
||||
EpiphanyReorientationReasoningProjection {
|
||||
authority: document,
|
||||
request,
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
};
|
||||
EpiphanyReasoningBasis::new(
|
||||
&launch.job_id,
|
||||
|
|
@ -545,29 +563,54 @@ pub fn put_reasoning_basis(
|
|||
basis.validate()?;
|
||||
let mut cache = runtime_spine_cache(store_path)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
if let EpiphanyReasoningProjection::RolePass(projection) = basis.projection()? {
|
||||
let launch = cache
|
||||
.get::<EpiphanyRuntimeWorkerLaunchRequest>(&basis.pass_id)?
|
||||
.ok_or_else(|| anyhow!("role reasoning basis lost its worker launch"))?;
|
||||
let EpiphanyWorkerLaunchDocument::Role(document) = launch.launch_document()? else {
|
||||
return Err(anyhow!("role reasoning basis cites a non-role launch"));
|
||||
};
|
||||
let current_mind = crate::assemble_mind_view(store_path)?;
|
||||
let modeling_body_mismatch = projection
|
||||
.authority
|
||||
.role_id
|
||||
.eq_ignore_ascii_case("modeling")
|
||||
&& projection.authority.repository_body_observation_basis
|
||||
!= current_mind.repository_body_observation;
|
||||
if projection.authority != document.into()
|
||||
|| projection.mind != current_mind.clone().into()
|
||||
|| basis.source_documents != current_mind.source_documents
|
||||
|| modeling_body_mismatch
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"role reasoning projection diverges from its exact launch or keyed Mind sources"
|
||||
));
|
||||
match basis.projection()? {
|
||||
EpiphanyReasoningProjection::RolePass(projection) => {
|
||||
let launch = cache
|
||||
.get::<EpiphanyRuntimeWorkerLaunchRequest>(&basis.pass_id)?
|
||||
.ok_or_else(|| anyhow!("role reasoning basis lost its worker launch"))?;
|
||||
let EpiphanyWorkerLaunchDocument::Role(document) = launch.launch_document()? else {
|
||||
return Err(anyhow!("role reasoning basis cites a non-role launch"));
|
||||
};
|
||||
let current_mind = crate::assemble_mind_view(store_path)?;
|
||||
let modeling_body_mismatch = projection
|
||||
.authority
|
||||
.role_id
|
||||
.eq_ignore_ascii_case("modeling")
|
||||
&& projection.authority.repository_body_observation_basis
|
||||
!= current_mind.repository_body_observation;
|
||||
if projection.authority != document.into()
|
||||
|| projection.mind != current_mind.clone().into()
|
||||
|| basis.source_documents != current_mind.source_documents
|
||||
|| modeling_body_mismatch
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"role reasoning projection diverges from its exact launch or keyed Mind sources"
|
||||
));
|
||||
}
|
||||
}
|
||||
EpiphanyReasoningProjection::ReorientLaunch(projection) => {
|
||||
let launch = cache
|
||||
.get::<EpiphanyRuntimeWorkerLaunchRequest>(&basis.pass_id)?
|
||||
.ok_or_else(|| anyhow!("reorientation reasoning basis lost its worker launch"))?;
|
||||
let EpiphanyWorkerLaunchDocument::Reorient(document) = launch.launch_document()? else {
|
||||
return Err(anyhow!("reorientation basis cites a non-reorient launch"));
|
||||
};
|
||||
if projection.authority != document
|
||||
|| projection.request.request_id != document.request_id
|
||||
|| projection.request.source_documents != basis.source_documents
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"reorientation reasoning projection diverges from its request"
|
||||
));
|
||||
}
|
||||
crate::reorientation_work::validate_reorientation_request_current(
|
||||
&cache,
|
||||
&projection.request,
|
||||
)?;
|
||||
}
|
||||
EpiphanyReasoningProjection::PersonaProjector(_)
|
||||
| EpiphanyReasoningProjection::PersonaTurn(_)
|
||||
| EpiphanyReasoningProjection::PersonaInterpreter(_) => {}
|
||||
}
|
||||
for source in &basis.source_documents {
|
||||
if source.store_id != "epiphany-mind" {
|
||||
|
|
@ -1334,6 +1377,8 @@ mod tests {
|
|||
observations: Vec::new(),
|
||||
evidence: Vec::new(),
|
||||
verification_audits: Vec::new(),
|
||||
reorientation_decisions: Vec::new(),
|
||||
reorientation_failures: Vec::new(),
|
||||
investigation_checkpoint: None,
|
||||
mode: None,
|
||||
planning: Default::default(),
|
||||
|
|
|
|||
1189
epiphany-core/src/reorientation_work.rs
Normal file
1189
epiphany-core/src/reorientation_work.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1161,6 +1161,10 @@ pub fn runtime_spine_cache(store_path: impl AsRef<Path>) -> Result<CultCache> {
|
|||
cache.register_entry_type::<crate::ImaginationConsiderationReviewRequest>()?;
|
||||
cache.register_entry_type::<RepoFrontierPlanCandidate>()?;
|
||||
cache.register_entry_type::<RepoFrontierPlanMindRequest>()?;
|
||||
cache.register_entry_type::<crate::EpiphanyReorientationRequest>()?;
|
||||
cache.register_entry_type::<crate::EpiphanyReorientationLaunchBinding>()?;
|
||||
cache.register_entry_type::<crate::EpiphanyMindReorientationDecisionDocument>()?;
|
||||
cache.register_entry_type::<crate::EpiphanyMindReorientationPassFailureDocument>()?;
|
||||
cache.register_entry_type::<RepoFrontierPlanMindLaunchBinding>()?;
|
||||
cache.register_entry_type::<RepoFrontierVerificationRequest>()?;
|
||||
cache.register_entry_type::<EpiphanyRuntimeReorientWorkerResult>()?;
|
||||
|
|
|
|||
|
|
@ -57,8 +57,7 @@ pub fn derive_coordinator_status(
|
|||
.iter()
|
||||
.map(coordinator_role_lane_from_role_board)
|
||||
.collect();
|
||||
let routing_recommendation =
|
||||
routing_crrc_recommendation(&input.recommendation, input.crrc_regather_current);
|
||||
let routing_recommendation = routing_crrc_recommendation(&input.recommendation);
|
||||
let decision = recommend_coordinator_action(EpiphanyCoordinatorInput {
|
||||
state_status: input.state_status,
|
||||
checkpoint_present: input.checkpoint_present,
|
||||
|
|
@ -110,14 +109,7 @@ pub fn derive_coordinator_status(
|
|||
|
||||
fn routing_crrc_recommendation(
|
||||
recommendation: &epiphany_core::EpiphanyCrrcRecommendation,
|
||||
regather_current: bool,
|
||||
) -> EpiphanyCoordinatorCrrcRecommendation {
|
||||
if recommendation.action == EpiphanyCrrcAction::RegatherManually && !regather_current {
|
||||
return EpiphanyCoordinatorCrrcRecommendation {
|
||||
action: EpiphanyCrrcAction::Continue,
|
||||
recommended_scene_action: None,
|
||||
};
|
||||
}
|
||||
EpiphanyCoordinatorCrrcRecommendation {
|
||||
action: recommendation.action,
|
||||
recommended_scene_action: recommendation
|
||||
|
|
@ -1057,26 +1049,6 @@ mod tests {
|
|||
use epiphany_state_model::EpiphanyAcceptanceReceipt;
|
||||
use epiphany_state_model::EpiphanyEvidenceRecord;
|
||||
|
||||
#[test]
|
||||
fn relinquishment_demotes_older_manual_regather_to_observation_only() {
|
||||
let recommendation = epiphany_core::EpiphanyCrrcRecommendation {
|
||||
action: EpiphanyCrrcAction::RegatherManually,
|
||||
recommended_scene_action: Some(EpiphanyCrrcSceneAction::ReorientResult),
|
||||
reason: "historical failed reorientation".to_string(),
|
||||
};
|
||||
|
||||
let stale = routing_crrc_recommendation(&recommendation, false);
|
||||
assert_eq!(stale.action, EpiphanyCrrcAction::Continue);
|
||||
assert_eq!(stale.recommended_scene_action, None);
|
||||
|
||||
let current = routing_crrc_recommendation(&recommendation, true);
|
||||
assert_eq!(current.action, EpiphanyCrrcAction::RegatherManually);
|
||||
assert_eq!(
|
||||
current.recommended_scene_action,
|
||||
Some(EpiphanyCoordinatorSceneAction::ReorientResult)
|
||||
);
|
||||
}
|
||||
|
||||
fn base_roles() -> Vec<EpiphanyCoordinatorRoleLane> {
|
||||
vec![
|
||||
role(
|
||||
|
|
|
|||
|
|
@ -194,10 +194,6 @@ pub struct EpiphanyCoordinatorStatusInput {
|
|||
pub checkpoint_present: bool,
|
||||
pub pressure: EpiphanyPressure,
|
||||
pub recommendation: EpiphanyCrrcRecommendation,
|
||||
/// True when CRRC's manual-regather pressure is causally newer than the
|
||||
/// latest typed frontier relinquishment. Raw CRRC remains observable when
|
||||
/// false, but it cannot route Self from stale continuity history.
|
||||
pub crrc_regather_current: bool,
|
||||
pub roles: Vec<EpiphanyRoleBoardLane>,
|
||||
pub reorient_action: EpiphanyReorientAction,
|
||||
pub research_result_status: EpiphanyCoordinatorRoleResultStatus,
|
||||
|
|
|
|||
|
|
@ -61,14 +61,6 @@ pub use pressure::EpiphanyPressureStatus;
|
|||
pub use pressure::EpiphanyTokenUsageSnapshot;
|
||||
pub use pressure::derive_pressure_view;
|
||||
pub use reorient::EpiphanyReorientAction;
|
||||
pub use reorient::EpiphanyReorientCheckpointStatus;
|
||||
pub use reorient::EpiphanyReorientDecision;
|
||||
pub use reorient::EpiphanyReorientFreshnessStatus;
|
||||
pub use reorient::EpiphanyReorientInput;
|
||||
pub use reorient::EpiphanyReorientPressureLevel;
|
||||
pub use reorient::EpiphanyReorientReason;
|
||||
pub use reorient::EpiphanyReorientStateStatus;
|
||||
pub use reorient::recommend_reorientation;
|
||||
pub use role_board::EpiphanyRoleBoardCheckpointSummary;
|
||||
pub use role_board::EpiphanyRoleBoardInput;
|
||||
pub use role_board::EpiphanyRoleBoardJob;
|
||||
|
|
@ -119,7 +111,6 @@ pub use view::epiphany_view_needs_pressure;
|
|||
pub use view::epiphany_view_needs_reorientation_inputs;
|
||||
pub use view::epiphany_view_needs_runtime_store;
|
||||
pub use worker_launch::AdmittedModelDirectionConsiderationContextProjection;
|
||||
pub use worker_launch::EpiphanyReorientLaunchRequestInput;
|
||||
pub use worker_launch::EpiphanyReorientWorkerLaunchDocument;
|
||||
pub use worker_launch::EpiphanyRoleWorkerLaunchDocument;
|
||||
pub use worker_launch::EpiphanyWorkerLaunchDocument;
|
||||
|
|
@ -141,4 +132,3 @@ pub use worker_launch::RepoFrontierPlanningContextProjection;
|
|||
pub use worker_launch::RepoFrontierProposalModelingContextProjection;
|
||||
pub use worker_launch::RepoFrontierResearchContextProjection;
|
||||
pub use worker_launch::RepoFrontierVerificationContextProjection;
|
||||
pub use worker_launch::build_reorient_job_launch_request;
|
||||
|
|
|
|||
|
|
@ -1,566 +1,13 @@
|
|||
use epiphany_state_model::EpiphanyInvestigationCheckpoint;
|
||||
use epiphany_state_model::EpiphanyInvestigationDisposition;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Display projection of an accepted keyed continuity decision.
|
||||
///
|
||||
/// This enum does not decide whether Reorientation should run. The keyed
|
||||
/// current-work owner in `reorientation_work` owns launch, wait, review, and
|
||||
/// admission behavior.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum EpiphanyReorientAction {
|
||||
Resume,
|
||||
Regather,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum EpiphanyReorientStateStatus {
|
||||
Missing,
|
||||
Ready,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum EpiphanyReorientCheckpointStatus {
|
||||
Missing,
|
||||
ResumeReady,
|
||||
RegatherRequired,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum EpiphanyReorientReason {
|
||||
MissingState,
|
||||
MissingCheckpoint,
|
||||
CheckpointReady,
|
||||
CheckpointRequestedRegather,
|
||||
CheckpointPathsDirty,
|
||||
CheckpointPathsChanged,
|
||||
FrontierChanged,
|
||||
UnanchoredCheckpointWhileStateStale,
|
||||
RetrievalFreshnessBlocked,
|
||||
GraphFreshnessBlocked,
|
||||
WatcherFreshnessBlocked,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum EpiphanyReorientPressureLevel {
|
||||
Unknown,
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
Critical,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum EpiphanyReorientFreshnessStatus {
|
||||
Unknown,
|
||||
Clean,
|
||||
Dirty,
|
||||
Stale,
|
||||
Changed,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EpiphanyReorientInput<'a> {
|
||||
pub checkpoint: Option<&'a EpiphanyInvestigationCheckpoint>,
|
||||
pub state_present: bool,
|
||||
pub pressure_level: EpiphanyReorientPressureLevel,
|
||||
pub retrieval_status: EpiphanyReorientFreshnessStatus,
|
||||
pub retrieval_dirty_paths: Vec<PathBuf>,
|
||||
pub graph_status: EpiphanyReorientFreshnessStatus,
|
||||
pub graph_dirty_paths: Vec<PathBuf>,
|
||||
pub watcher_status: EpiphanyReorientFreshnessStatus,
|
||||
pub watcher_changed_paths: Vec<PathBuf>,
|
||||
pub watcher_graph_node_ids: Vec<String>,
|
||||
pub active_frontier_node_ids: Vec<String>,
|
||||
pub watched_root: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EpiphanyReorientDecision {
|
||||
pub action: EpiphanyReorientAction,
|
||||
pub checkpoint_status: EpiphanyReorientCheckpointStatus,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub checkpoint_id: Option<String>,
|
||||
pub pressure_level: EpiphanyReorientPressureLevel,
|
||||
pub retrieval_status: EpiphanyReorientFreshnessStatus,
|
||||
pub graph_status: EpiphanyReorientFreshnessStatus,
|
||||
pub watcher_status: EpiphanyReorientFreshnessStatus,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub reasons: Vec<EpiphanyReorientReason>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub checkpoint_dirty_paths: Vec<PathBuf>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub checkpoint_changed_paths: Vec<PathBuf>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub active_frontier_node_ids: Vec<String>,
|
||||
pub next_action: String,
|
||||
pub note: String,
|
||||
}
|
||||
|
||||
pub fn recommend_reorientation(
|
||||
input: EpiphanyReorientInput<'_>,
|
||||
) -> (EpiphanyReorientStateStatus, EpiphanyReorientDecision) {
|
||||
let build_decision = |action: EpiphanyReorientAction,
|
||||
checkpoint_status: EpiphanyReorientCheckpointStatus,
|
||||
checkpoint_id: Option<String>,
|
||||
reasons: Vec<EpiphanyReorientReason>,
|
||||
checkpoint_dirty_paths: Vec<PathBuf>,
|
||||
checkpoint_changed_paths: Vec<PathBuf>,
|
||||
active_frontier_node_ids: Vec<String>,
|
||||
next_action: String,
|
||||
note: String| EpiphanyReorientDecision {
|
||||
action,
|
||||
checkpoint_status,
|
||||
checkpoint_id,
|
||||
pressure_level: input.pressure_level,
|
||||
retrieval_status: input.retrieval_status,
|
||||
graph_status: input.graph_status,
|
||||
watcher_status: input.watcher_status,
|
||||
reasons,
|
||||
checkpoint_dirty_paths,
|
||||
checkpoint_changed_paths,
|
||||
active_frontier_node_ids,
|
||||
next_action,
|
||||
note,
|
||||
};
|
||||
|
||||
let high_pressure = matches!(
|
||||
input.pressure_level,
|
||||
EpiphanyReorientPressureLevel::High | EpiphanyReorientPressureLevel::Critical
|
||||
);
|
||||
|
||||
if !input.state_present {
|
||||
let note = if high_pressure {
|
||||
"No Epiphany state survived, and the last recorded context pressure was high; re-gather before editing."
|
||||
.to_string()
|
||||
} else {
|
||||
"No Epiphany state survived, so there is no authoritative checkpoint to resume."
|
||||
.to_string()
|
||||
};
|
||||
return (
|
||||
EpiphanyReorientStateStatus::Missing,
|
||||
build_decision(
|
||||
EpiphanyReorientAction::Regather,
|
||||
EpiphanyReorientCheckpointStatus::Missing,
|
||||
None,
|
||||
vec![
|
||||
EpiphanyReorientReason::MissingState,
|
||||
EpiphanyReorientReason::MissingCheckpoint,
|
||||
],
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
"Re-gather source context before editing.".to_string(),
|
||||
note,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
let Some(checkpoint) = input.checkpoint else {
|
||||
let note = if high_pressure {
|
||||
"Epiphany state survived, but no durable investigation checkpoint was banked before high context pressure; re-gather before editing."
|
||||
.to_string()
|
||||
} else {
|
||||
"Epiphany state survived, but there is no durable investigation checkpoint to resume from."
|
||||
.to_string()
|
||||
};
|
||||
return (
|
||||
EpiphanyReorientStateStatus::Ready,
|
||||
build_decision(
|
||||
EpiphanyReorientAction::Regather,
|
||||
EpiphanyReorientCheckpointStatus::Missing,
|
||||
None,
|
||||
vec![EpiphanyReorientReason::MissingCheckpoint],
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
"Re-gather source context before editing.".to_string(),
|
||||
note,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
let checkpoint_status = match checkpoint.disposition {
|
||||
EpiphanyInvestigationDisposition::ResumeReady => {
|
||||
EpiphanyReorientCheckpointStatus::ResumeReady
|
||||
}
|
||||
EpiphanyInvestigationDisposition::RegatherRequired => {
|
||||
EpiphanyReorientCheckpointStatus::RegatherRequired
|
||||
}
|
||||
};
|
||||
let workspace_root = input.watched_root.as_deref();
|
||||
let checkpoint_dirty_paths = overlapping_checkpoint_paths(
|
||||
checkpoint,
|
||||
input
|
||||
.retrieval_dirty_paths
|
||||
.iter()
|
||||
.chain(input.graph_dirty_paths.iter()),
|
||||
workspace_root,
|
||||
);
|
||||
let checkpoint_changed_paths = overlapping_checkpoint_paths(
|
||||
checkpoint,
|
||||
input.watcher_changed_paths.iter(),
|
||||
workspace_root,
|
||||
);
|
||||
let frontier_changed = !input.active_frontier_node_ids.is_empty();
|
||||
let unanchored_checkpoint_while_state_stale = checkpoint.code_refs.is_empty()
|
||||
&& (!input.retrieval_dirty_paths.is_empty()
|
||||
|| !input.graph_dirty_paths.is_empty()
|
||||
|| !input.watcher_graph_node_ids.is_empty()
|
||||
|| frontier_changed);
|
||||
|
||||
let mut reasons = Vec::new();
|
||||
if checkpoint.disposition == EpiphanyInvestigationDisposition::RegatherRequired {
|
||||
reasons.push(EpiphanyReorientReason::CheckpointRequestedRegather);
|
||||
}
|
||||
if !checkpoint_dirty_paths.is_empty() {
|
||||
reasons.push(EpiphanyReorientReason::CheckpointPathsDirty);
|
||||
}
|
||||
if !checkpoint_changed_paths.is_empty() {
|
||||
reasons.push(EpiphanyReorientReason::CheckpointPathsChanged);
|
||||
}
|
||||
if frontier_changed {
|
||||
reasons.push(EpiphanyReorientReason::FrontierChanged);
|
||||
}
|
||||
if unanchored_checkpoint_while_state_stale {
|
||||
reasons.push(EpiphanyReorientReason::UnanchoredCheckpointWhileStateStale);
|
||||
}
|
||||
let retrieval_blocked = input.retrieval_status != EpiphanyReorientFreshnessStatus::Clean;
|
||||
let graph_blocked = input.graph_status != EpiphanyReorientFreshnessStatus::Clean;
|
||||
let watcher_blocked = matches!(
|
||||
input.watcher_status,
|
||||
EpiphanyReorientFreshnessStatus::Dirty
|
||||
| EpiphanyReorientFreshnessStatus::Stale
|
||||
| EpiphanyReorientFreshnessStatus::Changed
|
||||
);
|
||||
if retrieval_blocked {
|
||||
reasons.push(EpiphanyReorientReason::RetrievalFreshnessBlocked);
|
||||
}
|
||||
if graph_blocked {
|
||||
reasons.push(EpiphanyReorientReason::GraphFreshnessBlocked);
|
||||
}
|
||||
if watcher_blocked {
|
||||
reasons.push(EpiphanyReorientReason::WatcherFreshnessBlocked);
|
||||
}
|
||||
|
||||
let should_regather = checkpoint.disposition
|
||||
== EpiphanyInvestigationDisposition::RegatherRequired
|
||||
|| !checkpoint_dirty_paths.is_empty()
|
||||
|| !checkpoint_changed_paths.is_empty()
|
||||
|| frontier_changed
|
||||
|| unanchored_checkpoint_while_state_stale
|
||||
|| retrieval_blocked
|
||||
|| graph_blocked
|
||||
|| watcher_blocked;
|
||||
|
||||
if !should_regather {
|
||||
reasons.push(EpiphanyReorientReason::CheckpointReady);
|
||||
return (
|
||||
EpiphanyReorientStateStatus::Ready,
|
||||
build_decision(
|
||||
EpiphanyReorientAction::Resume,
|
||||
checkpoint_status,
|
||||
Some(checkpoint.checkpoint_id.clone()),
|
||||
reasons,
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
checkpoint.next_action.clone().unwrap_or_else(|| {
|
||||
"Resume from the durable checkpoint focus and verify the seam before broad edits."
|
||||
.to_string()
|
||||
}),
|
||||
"Resume-ready checkpoint remains aligned with current freshness and watcher signals."
|
||||
.to_string(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
let mut note_fragments = Vec::new();
|
||||
if checkpoint.disposition == EpiphanyInvestigationDisposition::RegatherRequired {
|
||||
note_fragments.push("the checkpoint explicitly requests re-gather".to_string());
|
||||
}
|
||||
if !checkpoint_dirty_paths.is_empty() {
|
||||
note_fragments.push(format!(
|
||||
"{} checkpoint path(s) are already marked dirty",
|
||||
checkpoint_dirty_paths.len()
|
||||
));
|
||||
}
|
||||
if !checkpoint_changed_paths.is_empty() {
|
||||
note_fragments.push(format!(
|
||||
"watcher observed {} changed checkpoint path(s)",
|
||||
checkpoint_changed_paths.len()
|
||||
));
|
||||
}
|
||||
if frontier_changed {
|
||||
note_fragments.push(format!(
|
||||
"watcher hit {} active frontier node(s)",
|
||||
input.active_frontier_node_ids.len()
|
||||
));
|
||||
}
|
||||
if unanchored_checkpoint_while_state_stale {
|
||||
note_fragments.push(
|
||||
"the checkpoint has no code refs while freshness signals already show drift"
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
if retrieval_blocked {
|
||||
note_fragments.push(format!(
|
||||
"retrieval freshness is {:?}, not clean",
|
||||
input.retrieval_status
|
||||
));
|
||||
}
|
||||
if graph_blocked {
|
||||
note_fragments.push(format!(
|
||||
"graph freshness is {:?}, not clean",
|
||||
input.graph_status
|
||||
));
|
||||
}
|
||||
if watcher_blocked {
|
||||
note_fragments.push(format!(
|
||||
"watcher freshness is {:?} and reports invalidation pressure",
|
||||
input.watcher_status
|
||||
));
|
||||
}
|
||||
|
||||
(
|
||||
EpiphanyReorientStateStatus::Ready,
|
||||
build_decision(
|
||||
EpiphanyReorientAction::Regather,
|
||||
checkpoint_status,
|
||||
Some(checkpoint.checkpoint_id.clone()),
|
||||
reasons,
|
||||
checkpoint_dirty_paths,
|
||||
checkpoint_changed_paths,
|
||||
input.active_frontier_node_ids,
|
||||
checkpoint
|
||||
.next_action
|
||||
.clone()
|
||||
.unwrap_or_else(|| "Re-gather source context before editing.".to_string()),
|
||||
format!("Re-gather before editing: {}.", note_fragments.join("; ")),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn overlapping_checkpoint_paths<'a>(
|
||||
checkpoint: &EpiphanyInvestigationCheckpoint,
|
||||
candidate_paths: impl IntoIterator<Item = &'a PathBuf>,
|
||||
workspace_root: Option<&Path>,
|
||||
) -> Vec<PathBuf> {
|
||||
let checkpoint_path_keys: HashSet<String> = checkpoint
|
||||
.code_refs
|
||||
.iter()
|
||||
.map(|code_ref| code_ref_path_key(code_ref.path.as_path(), workspace_root))
|
||||
.collect();
|
||||
if checkpoint_path_keys.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut overlaps = Vec::new();
|
||||
let mut seen = HashSet::new();
|
||||
for path in candidate_paths {
|
||||
let path_key = code_ref_path_key(path.as_path(), workspace_root);
|
||||
if checkpoint_path_keys.contains(&path_key) && seen.insert(path_key) {
|
||||
overlaps.push(path.clone());
|
||||
}
|
||||
}
|
||||
overlaps
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
fn checkpoint() -> EpiphanyInvestigationCheckpoint {
|
||||
EpiphanyInvestigationCheckpoint {
|
||||
checkpoint_id: "ix-1".to_string(),
|
||||
kind: "implementation".to_string(),
|
||||
disposition: EpiphanyInvestigationDisposition::ResumeReady,
|
||||
focus: "runtime seam".to_string(),
|
||||
summary: Some("resume here".to_string()),
|
||||
next_action: Some("Continue from the seam.".to_string()),
|
||||
captured_at_turn_id: Some("turn-1".to_string()),
|
||||
open_questions: Vec::new(),
|
||||
code_refs: vec![EpiphanyCodeRef {
|
||||
path: PathBuf::from("src/lib.rs"),
|
||||
start_line: None,
|
||||
end_line: None,
|
||||
symbol: None,
|
||||
note: None,
|
||||
}],
|
||||
evidence_ids: vec!["ev-1".to_string()],
|
||||
}
|
||||
}
|
||||
|
||||
fn input(checkpoint: Option<&EpiphanyInvestigationCheckpoint>) -> EpiphanyReorientInput<'_> {
|
||||
EpiphanyReorientInput {
|
||||
checkpoint,
|
||||
state_present: true,
|
||||
pressure_level: EpiphanyReorientPressureLevel::Low,
|
||||
retrieval_status: EpiphanyReorientFreshnessStatus::Clean,
|
||||
retrieval_dirty_paths: Vec::new(),
|
||||
graph_status: EpiphanyReorientFreshnessStatus::Clean,
|
||||
graph_dirty_paths: Vec::new(),
|
||||
watcher_status: EpiphanyReorientFreshnessStatus::Clean,
|
||||
watcher_changed_paths: Vec::new(),
|
||||
watcher_graph_node_ids: Vec::new(),
|
||||
active_frontier_node_ids: Vec::new(),
|
||||
watched_root: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resumes_when_checkpoint_is_clean() {
|
||||
let checkpoint = checkpoint();
|
||||
let (state_status, decision) = recommend_reorientation(input(Some(&checkpoint)));
|
||||
|
||||
assert_eq!(state_status, EpiphanyReorientStateStatus::Ready);
|
||||
assert_eq!(decision.action, EpiphanyReorientAction::Resume);
|
||||
assert_eq!(decision.checkpoint_id.as_deref(), Some("ix-1"));
|
||||
assert_eq!(
|
||||
decision.reasons,
|
||||
vec![EpiphanyReorientReason::CheckpointReady]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regathers_when_checkpoint_path_changes() {
|
||||
let checkpoint = checkpoint();
|
||||
let mut input = input(Some(&checkpoint));
|
||||
input.watcher_status = EpiphanyReorientFreshnessStatus::Changed;
|
||||
input.watcher_changed_paths = vec![PathBuf::from("./src/lib.rs")];
|
||||
|
||||
let (_state_status, decision) = recommend_reorientation(input);
|
||||
|
||||
assert_eq!(decision.action, EpiphanyReorientAction::Regather);
|
||||
assert_eq!(
|
||||
decision.reasons,
|
||||
vec![
|
||||
EpiphanyReorientReason::CheckpointPathsChanged,
|
||||
EpiphanyReorientReason::WatcherFreshnessBlocked,
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
decision.checkpoint_changed_paths,
|
||||
vec![PathBuf::from("./src/lib.rs")]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn freshness_authorities_block_resume_but_unknown_watcher_does_not() {
|
||||
let checkpoint = checkpoint();
|
||||
for (field, status, reason) in [
|
||||
(
|
||||
"retrieval",
|
||||
EpiphanyReorientFreshnessStatus::Unknown,
|
||||
EpiphanyReorientReason::RetrievalFreshnessBlocked,
|
||||
),
|
||||
(
|
||||
"retrieval",
|
||||
EpiphanyReorientFreshnessStatus::Dirty,
|
||||
EpiphanyReorientReason::RetrievalFreshnessBlocked,
|
||||
),
|
||||
(
|
||||
"retrieval",
|
||||
EpiphanyReorientFreshnessStatus::Stale,
|
||||
EpiphanyReorientReason::RetrievalFreshnessBlocked,
|
||||
),
|
||||
(
|
||||
"retrieval",
|
||||
EpiphanyReorientFreshnessStatus::Changed,
|
||||
EpiphanyReorientReason::RetrievalFreshnessBlocked,
|
||||
),
|
||||
(
|
||||
"graph",
|
||||
EpiphanyReorientFreshnessStatus::Unknown,
|
||||
EpiphanyReorientReason::GraphFreshnessBlocked,
|
||||
),
|
||||
(
|
||||
"graph",
|
||||
EpiphanyReorientFreshnessStatus::Dirty,
|
||||
EpiphanyReorientReason::GraphFreshnessBlocked,
|
||||
),
|
||||
(
|
||||
"graph",
|
||||
EpiphanyReorientFreshnessStatus::Stale,
|
||||
EpiphanyReorientReason::GraphFreshnessBlocked,
|
||||
),
|
||||
(
|
||||
"graph",
|
||||
EpiphanyReorientFreshnessStatus::Changed,
|
||||
EpiphanyReorientReason::GraphFreshnessBlocked,
|
||||
),
|
||||
(
|
||||
"watcher",
|
||||
EpiphanyReorientFreshnessStatus::Dirty,
|
||||
EpiphanyReorientReason::WatcherFreshnessBlocked,
|
||||
),
|
||||
(
|
||||
"watcher",
|
||||
EpiphanyReorientFreshnessStatus::Stale,
|
||||
EpiphanyReorientReason::WatcherFreshnessBlocked,
|
||||
),
|
||||
(
|
||||
"watcher",
|
||||
EpiphanyReorientFreshnessStatus::Changed,
|
||||
EpiphanyReorientReason::WatcherFreshnessBlocked,
|
||||
),
|
||||
] {
|
||||
let mut candidate = input(Some(&checkpoint));
|
||||
match field {
|
||||
"retrieval" => candidate.retrieval_status = status,
|
||||
"graph" => candidate.graph_status = status,
|
||||
"watcher" => candidate.watcher_status = status,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let (_, decision) = recommend_reorientation(candidate);
|
||||
assert_eq!(decision.action, EpiphanyReorientAction::Regather);
|
||||
assert!(decision.reasons.contains(&reason));
|
||||
}
|
||||
|
||||
let mut candidate = input(Some(&checkpoint));
|
||||
candidate.watcher_status = EpiphanyReorientFreshnessStatus::Unknown;
|
||||
let (_, decision) = recommend_reorientation(candidate);
|
||||
assert_eq!(decision.action, EpiphanyReorientAction::Resume);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_state_regathers() {
|
||||
let mut input = input(None);
|
||||
input.state_present = false;
|
||||
input.pressure_level = EpiphanyReorientPressureLevel::High;
|
||||
|
||||
let (state_status, decision) = recommend_reorientation(input);
|
||||
|
||||
assert_eq!(state_status, EpiphanyReorientStateStatus::Missing);
|
||||
assert_eq!(decision.action, EpiphanyReorientAction::Regather);
|
||||
assert!(
|
||||
decision
|
||||
.reasons
|
||||
.contains(&EpiphanyReorientReason::MissingState)
|
||||
);
|
||||
assert!(decision.note.contains("high"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,16 @@
|
|||
use epiphany_state_model::EpiphanyChurnState;
|
||||
use epiphany_state_model::EpiphanyCodeRef;
|
||||
use epiphany_state_model::EpiphanyEvidenceRecord;
|
||||
use epiphany_state_model::EpiphanyGraphCheckpoint;
|
||||
use epiphany_state_model::EpiphanyGraphFrontier;
|
||||
use epiphany_state_model::EpiphanyGraphs;
|
||||
use epiphany_state_model::EpiphanyInvariant;
|
||||
use epiphany_state_model::EpiphanyInvestigationCheckpoint;
|
||||
use epiphany_state_model::EpiphanyInvestigationDisposition;
|
||||
use epiphany_state_model::EpiphanyJobKind;
|
||||
use epiphany_state_model::EpiphanyObservation;
|
||||
use epiphany_state_model::EpiphanyPlanningState;
|
||||
use epiphany_state_model::EpiphanyScratchPad;
|
||||
use epiphany_state_model::EpiphanySubgoal;
|
||||
use epiphany_state_model::EpiphanyThreadState;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::EpiphanyJobLaunchRequest;
|
||||
use crate::EpiphanyReorientAction;
|
||||
use crate::EpiphanyReorientDecision;
|
||||
use crate::EpiphanyReorientFreshnessStatus;
|
||||
use crate::EpiphanyReorientPressureLevel;
|
||||
use crate::EpiphanyReorientReason;
|
||||
|
||||
pub const ROLE_WORKER_OUTPUT_CONTRACT_ID: &str = "epiphany.worker.role_result.v3";
|
||||
pub const REORIENT_WORKER_OUTPUT_CONTRACT_ID: &str = "epiphany.worker.reorient_result.v0";
|
||||
|
|
@ -80,14 +68,14 @@ impl EpiphanyWorkerLaunchDocument {
|
|||
pub fn thread_id(&self) -> &str {
|
||||
match self {
|
||||
Self::Role(document) => &document.thread_id,
|
||||
Self::Reorient(document) => &document.thread_id,
|
||||
Self::Reorient(document) => &document.creation_thread_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dynamic_prompt_context(&self) -> Option<&str> {
|
||||
match self {
|
||||
Self::Role(document) => document.dynamic_prompt_context.as_deref(),
|
||||
Self::Reorient(document) => document.dynamic_prompt_context.as_deref(),
|
||||
Self::Reorient(_) => None,
|
||||
}
|
||||
.map(str::trim)
|
||||
.filter(|context| !context.is_empty())
|
||||
|
|
@ -188,62 +176,9 @@ impl From<&crate::RepoFrontierResearchRequest> for RepoFrontierResearchContextPr
|
|||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EpiphanyReorientWorkerLaunchDocument {
|
||||
pub thread_id: String,
|
||||
pub mode: String,
|
||||
pub checkpoint_id: String,
|
||||
pub checkpoint_kind: String,
|
||||
pub checkpoint_disposition: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub dynamic_prompt_context: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub checkpoint_focus: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub checkpoint_summary: Option<String>,
|
||||
pub checkpoint_next_action: String,
|
||||
#[serde(default)]
|
||||
pub checkpoint_open_questions: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub checkpoint_evidence_ids: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub checkpoint_code_refs: Vec<EpiphanyCodeRef>,
|
||||
#[serde(default)]
|
||||
pub decision_reasons: Vec<String>,
|
||||
pub decision_note: String,
|
||||
pub pressure_level: String,
|
||||
pub retrieval_status: String,
|
||||
pub graph_status: String,
|
||||
pub watcher_status: String,
|
||||
#[serde(default)]
|
||||
pub checkpoint_dirty_paths: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub checkpoint_changed_paths: Vec<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub scratch: Option<EpiphanyScratchPad>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub graphs: Option<EpiphanyGraphs>,
|
||||
#[serde(default)]
|
||||
pub recent_evidence: Vec<EpiphanyEvidenceRecord>,
|
||||
#[serde(default)]
|
||||
pub recent_observations: Vec<EpiphanyObservation>,
|
||||
#[serde(default)]
|
||||
pub active_frontier_node_ids: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub linked_subgoal_ids: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub linked_graph_node_ids: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct EpiphanyReorientLaunchRequestInput<'a> {
|
||||
pub thread_id: &'a str,
|
||||
pub expected_revision: Option<u64>,
|
||||
pub max_runtime_seconds: Option<u64>,
|
||||
pub binding_id: &'a str,
|
||||
pub owner_role: &'a str,
|
||||
pub instruction: String,
|
||||
pub state: &'a EpiphanyThreadState,
|
||||
pub checkpoint: &'a EpiphanyInvestigationCheckpoint,
|
||||
pub decision: &'a EpiphanyReorientDecision,
|
||||
pub dynamic_prompt_context: Option<String>,
|
||||
pub schema_version: String,
|
||||
pub request_id: String,
|
||||
pub creation_thread_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
|
@ -402,301 +337,3 @@ impl RepoFrontierPlanMindContextProjection {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_reorient_job_launch_request(
|
||||
input: EpiphanyReorientLaunchRequestInput<'_>,
|
||||
) -> EpiphanyJobLaunchRequest {
|
||||
let linked_subgoal_ids = active_subgoal_ids(input.state);
|
||||
let linked_graph_node_ids = unique_strings(
|
||||
active_graph_node_ids(input.state)
|
||||
.into_iter()
|
||||
.chain(input.decision.active_frontier_node_ids.iter().cloned()),
|
||||
);
|
||||
let launch_document =
|
||||
EpiphanyWorkerLaunchDocument::Reorient(build_reorient_worker_launch_document(
|
||||
input.thread_id,
|
||||
input.state,
|
||||
input.checkpoint,
|
||||
input.decision,
|
||||
linked_subgoal_ids.clone(),
|
||||
linked_graph_node_ids.clone(),
|
||||
input.dynamic_prompt_context,
|
||||
));
|
||||
let output_contract_id = launch_document.output_contract_id().to_string();
|
||||
let organ_launch_contract = crate::default_launch_organ_contract(
|
||||
reorient_launch_authority_scope(input.decision.action),
|
||||
launch_document.document_kind(),
|
||||
&output_contract_id,
|
||||
);
|
||||
|
||||
EpiphanyJobLaunchRequest {
|
||||
expected_revision: input.expected_revision,
|
||||
binding_id: input.binding_id.to_string(),
|
||||
kind: EpiphanyJobKind::Specialist,
|
||||
scope: reorient_launch_scope(input.decision.action).to_string(),
|
||||
owner_role: input.owner_role.to_string(),
|
||||
authority_scope: reorient_launch_authority_scope(input.decision.action).to_string(),
|
||||
linked_subgoal_ids,
|
||||
linked_graph_node_ids,
|
||||
instruction: input.instruction,
|
||||
launch_document,
|
||||
output_contract_id,
|
||||
organ_launch_contract,
|
||||
max_runtime_seconds: input.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,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_reorient_worker_launch_document(
|
||||
thread_id: &str,
|
||||
state: &EpiphanyThreadState,
|
||||
checkpoint: &EpiphanyInvestigationCheckpoint,
|
||||
decision: &EpiphanyReorientDecision,
|
||||
linked_subgoal_ids: Vec<String>,
|
||||
linked_graph_node_ids: Vec<String>,
|
||||
dynamic_prompt_context: Option<String>,
|
||||
) -> EpiphanyReorientWorkerLaunchDocument {
|
||||
EpiphanyReorientWorkerLaunchDocument {
|
||||
thread_id: thread_id.to_string(),
|
||||
mode: reorient_action_label(decision.action).to_string(),
|
||||
checkpoint_id: checkpoint.checkpoint_id.clone(),
|
||||
checkpoint_kind: checkpoint.kind.clone(),
|
||||
checkpoint_disposition: investigation_disposition_label(checkpoint.disposition).to_string(),
|
||||
dynamic_prompt_context,
|
||||
checkpoint_focus: Some(checkpoint.focus.clone()),
|
||||
checkpoint_summary: checkpoint.summary.clone(),
|
||||
checkpoint_next_action: checkpoint
|
||||
.next_action
|
||||
.clone()
|
||||
.unwrap_or_else(|| decision.next_action.clone()),
|
||||
checkpoint_open_questions: checkpoint.open_questions.clone(),
|
||||
checkpoint_evidence_ids: checkpoint.evidence_ids.clone(),
|
||||
checkpoint_code_refs: checkpoint.code_refs.clone(),
|
||||
decision_reasons: decision
|
||||
.reasons
|
||||
.iter()
|
||||
.map(|reason| reorient_reason_label(*reason).to_string())
|
||||
.collect(),
|
||||
decision_note: decision.note.clone(),
|
||||
pressure_level: reorient_pressure_level_label(decision.pressure_level).to_string(),
|
||||
retrieval_status: reorient_freshness_status_label(decision.retrieval_status).to_string(),
|
||||
graph_status: reorient_freshness_status_label(decision.graph_status).to_string(),
|
||||
watcher_status: reorient_freshness_status_label(decision.watcher_status).to_string(),
|
||||
checkpoint_dirty_paths: decision
|
||||
.checkpoint_dirty_paths
|
||||
.iter()
|
||||
.map(path_to_display_string)
|
||||
.collect(),
|
||||
checkpoint_changed_paths: decision
|
||||
.checkpoint_changed_paths
|
||||
.iter()
|
||||
.map(path_to_display_string)
|
||||
.collect(),
|
||||
scratch: state.scratch.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(),
|
||||
active_frontier_node_ids: decision.active_frontier_node_ids.clone(),
|
||||
linked_subgoal_ids,
|
||||
linked_graph_node_ids,
|
||||
}
|
||||
}
|
||||
|
||||
fn active_subgoal_ids(state: &EpiphanyThreadState) -> Vec<String> {
|
||||
state
|
||||
.active_subgoal_id
|
||||
.clone()
|
||||
.map(|id| vec![id])
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn active_graph_node_ids(state: &EpiphanyThreadState) -> Vec<String> {
|
||||
state
|
||||
.graph_frontier
|
||||
.as_ref()
|
||||
.map(|frontier| frontier.active_node_ids.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn unique_strings(values: impl IntoIterator<Item = String>) -> Vec<String> {
|
||||
let mut unique = Vec::new();
|
||||
for value in values {
|
||||
if !unique.iter().any(|existing| existing == &value) {
|
||||
unique.push(value);
|
||||
}
|
||||
}
|
||||
unique
|
||||
}
|
||||
|
||||
fn path_to_display_string(path: impl AsRef<Path>) -> String {
|
||||
path.as_ref().to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
fn reorient_launch_authority_scope(action: EpiphanyReorientAction) -> &'static str {
|
||||
match action {
|
||||
EpiphanyReorientAction::Resume => "epiphany.reorient.resume",
|
||||
EpiphanyReorientAction::Regather => "epiphany.reorient.regather",
|
||||
}
|
||||
}
|
||||
|
||||
fn reorient_launch_scope(action: EpiphanyReorientAction) -> &'static str {
|
||||
match action {
|
||||
EpiphanyReorientAction::Resume => "reorient-guided checkpoint resume",
|
||||
EpiphanyReorientAction::Regather => "reorient-guided checkpoint regather",
|
||||
}
|
||||
}
|
||||
|
||||
fn reorient_action_label(action: EpiphanyReorientAction) -> &'static str {
|
||||
match action {
|
||||
EpiphanyReorientAction::Resume => "resume",
|
||||
EpiphanyReorientAction::Regather => "regather",
|
||||
}
|
||||
}
|
||||
|
||||
fn investigation_disposition_label(disposition: EpiphanyInvestigationDisposition) -> &'static str {
|
||||
match disposition {
|
||||
EpiphanyInvestigationDisposition::ResumeReady => "resume_ready",
|
||||
EpiphanyInvestigationDisposition::RegatherRequired => "regather_required",
|
||||
}
|
||||
}
|
||||
|
||||
fn reorient_reason_label(reason: EpiphanyReorientReason) -> &'static str {
|
||||
match reason {
|
||||
EpiphanyReorientReason::MissingState => "missingState",
|
||||
EpiphanyReorientReason::MissingCheckpoint => "missingCheckpoint",
|
||||
EpiphanyReorientReason::CheckpointRequestedRegather => "checkpointRequestedRegather",
|
||||
EpiphanyReorientReason::CheckpointPathsDirty => "checkpointPathsDirty",
|
||||
EpiphanyReorientReason::CheckpointPathsChanged => "checkpointPathsChanged",
|
||||
EpiphanyReorientReason::FrontierChanged => "frontierChanged",
|
||||
EpiphanyReorientReason::UnanchoredCheckpointWhileStateStale => {
|
||||
"unanchoredCheckpointWhileStateStale"
|
||||
}
|
||||
EpiphanyReorientReason::RetrievalFreshnessBlocked => "retrievalFreshnessBlocked",
|
||||
EpiphanyReorientReason::GraphFreshnessBlocked => "graphFreshnessBlocked",
|
||||
EpiphanyReorientReason::WatcherFreshnessBlocked => "watcherFreshnessBlocked",
|
||||
EpiphanyReorientReason::CheckpointReady => "checkpointReady",
|
||||
}
|
||||
}
|
||||
|
||||
fn reorient_pressure_level_label(level: EpiphanyReorientPressureLevel) -> &'static str {
|
||||
match level {
|
||||
EpiphanyReorientPressureLevel::Unknown => "unknown",
|
||||
EpiphanyReorientPressureLevel::Low => "low",
|
||||
EpiphanyReorientPressureLevel::Medium => "medium",
|
||||
EpiphanyReorientPressureLevel::High => "high",
|
||||
EpiphanyReorientPressureLevel::Critical => "critical",
|
||||
}
|
||||
}
|
||||
|
||||
fn reorient_freshness_status_label(status: EpiphanyReorientFreshnessStatus) -> &'static str {
|
||||
match status {
|
||||
EpiphanyReorientFreshnessStatus::Unknown => "unknown",
|
||||
EpiphanyReorientFreshnessStatus::Clean => "clean",
|
||||
EpiphanyReorientFreshnessStatus::Dirty => "dirty",
|
||||
EpiphanyReorientFreshnessStatus::Stale => "stale",
|
||||
EpiphanyReorientFreshnessStatus::Changed => "changed",
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use epiphany_state_model::EpiphanyCodeRef;
|
||||
use epiphany_state_model::EpiphanyGraphFrontier;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn reorient_launch_request_is_composed_from_typed_state() {
|
||||
let state = EpiphanyThreadState {
|
||||
revision: 7,
|
||||
active_subgoal_id: Some("sg-1".to_string()),
|
||||
graph_frontier: Some(EpiphanyGraphFrontier {
|
||||
active_node_ids: vec!["node-a".to_string()],
|
||||
active_edge_ids: Vec::new(),
|
||||
open_question_ids: Vec::new(),
|
||||
open_gap_ids: Vec::new(),
|
||||
dirty_paths: Vec::new(),
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
let checkpoint = EpiphanyInvestigationCheckpoint {
|
||||
checkpoint_id: "ix-1".to_string(),
|
||||
kind: "implementation".to_string(),
|
||||
disposition: EpiphanyInvestigationDisposition::RegatherRequired,
|
||||
focus: "runtime seam".to_string(),
|
||||
summary: Some("resume here".to_string()),
|
||||
next_action: None,
|
||||
captured_at_turn_id: Some("turn-1".to_string()),
|
||||
open_questions: Vec::new(),
|
||||
code_refs: vec![EpiphanyCodeRef {
|
||||
path: PathBuf::from("src/lib.rs"),
|
||||
start_line: None,
|
||||
end_line: None,
|
||||
symbol: None,
|
||||
note: None,
|
||||
}],
|
||||
evidence_ids: vec!["ev-1".to_string()],
|
||||
};
|
||||
let decision = EpiphanyReorientDecision {
|
||||
action: EpiphanyReorientAction::Regather,
|
||||
checkpoint_status: crate::EpiphanyReorientCheckpointStatus::RegatherRequired,
|
||||
checkpoint_id: Some("ix-1".to_string()),
|
||||
pressure_level: EpiphanyReorientPressureLevel::High,
|
||||
retrieval_status: EpiphanyReorientFreshnessStatus::Stale,
|
||||
graph_status: EpiphanyReorientFreshnessStatus::Changed,
|
||||
watcher_status: EpiphanyReorientFreshnessStatus::Dirty,
|
||||
reasons: vec![EpiphanyReorientReason::FrontierChanged],
|
||||
checkpoint_dirty_paths: vec![PathBuf::from("src/lib.rs")],
|
||||
checkpoint_changed_paths: vec![PathBuf::from("src/main.rs")],
|
||||
active_frontier_node_ids: vec!["node-b".to_string()],
|
||||
next_action: "Regather the seam.".to_string(),
|
||||
note: "continuity moved".to_string(),
|
||||
};
|
||||
|
||||
let request = build_reorient_job_launch_request(EpiphanyReorientLaunchRequestInput {
|
||||
thread_id: "thread-1",
|
||||
expected_revision: Some(state.revision),
|
||||
max_runtime_seconds: Some(60),
|
||||
binding_id: "reorient-worker",
|
||||
owner_role: "epiphany-reorient",
|
||||
instruction: "do the reorientation".to_string(),
|
||||
state: &state,
|
||||
checkpoint: &checkpoint,
|
||||
decision: &decision,
|
||||
dynamic_prompt_context: Some(
|
||||
"<epiphany_dynamic_context>\ncheckpoint weather\n</epiphany_dynamic_context>"
|
||||
.to_string(),
|
||||
),
|
||||
});
|
||||
|
||||
assert_eq!(request.binding_id, "reorient-worker");
|
||||
assert_eq!(request.scope, "reorient-guided checkpoint regather");
|
||||
assert_eq!(request.authority_scope, "epiphany.reorient.regather");
|
||||
assert_eq!(request.linked_subgoal_ids, vec!["sg-1"]);
|
||||
assert_eq!(
|
||||
request.linked_graph_node_ids,
|
||||
vec!["node-a".to_string(), "node-b".to_string()]
|
||||
);
|
||||
let EpiphanyWorkerLaunchDocument::Reorient(document) = request.launch_document else {
|
||||
panic!("expected reorient launch document");
|
||||
};
|
||||
assert_eq!(document.mode, "regather");
|
||||
assert_eq!(document.checkpoint_next_action, "Regather the seam.");
|
||||
assert_eq!(document.decision_reasons, vec!["frontierChanged"]);
|
||||
assert_eq!(document.pressure_level, "high");
|
||||
assert_eq!(document.retrieval_status, "stale");
|
||||
assert_eq!(document.graph_status, "changed");
|
||||
assert_eq!(document.watcher_status, "dirty");
|
||||
assert_eq!(
|
||||
document.dynamic_prompt_context.as_deref(),
|
||||
Some("<epiphany_dynamic_context>\ncheckpoint weather\n</epiphany_dynamic_context>")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -630,7 +630,7 @@ pub fn build_worker_model_request(
|
|||
(
|
||||
EpiphanyWorkerLaunchDocument::Reorient(document),
|
||||
epiphany_core::EpiphanyReasoningProjection::ReorientLaunch(projection),
|
||||
) if projection == document => {}
|
||||
) if projection.authority == *document => {}
|
||||
_ => {
|
||||
return Err(anyhow!(
|
||||
"worker model request reasoning projection does not match its launch authority"
|
||||
|
|
@ -2571,6 +2571,8 @@ mod tests {
|
|||
observations: Vec::new(),
|
||||
evidence: Vec::new(),
|
||||
verification_audits: Vec::new(),
|
||||
reorientation_decisions: Vec::new(),
|
||||
reorientation_failures: Vec::new(),
|
||||
investigation_checkpoint: None,
|
||||
mode: None,
|
||||
planning: Default::default(),
|
||||
|
|
|
|||
Reference in a new issue