From 24d229983b3d68e2fcd81070575cbe4a1f975662 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 20 May 2026 01:39:27 +0100 Subject: [PATCH] Accept typed runtime findings --- epiphany-codex-bridge/src/mutation.rs | 75 +++++++++++++----- epiphany-codex-bridge/src/mutation_service.rs | 16 ++-- epiphany-codex-bridge/src/runtime_results.rs | 35 ++++++-- notes/fresh-workspace-handoff.md | 1 + state/ledgers.msgpack | Bin 135487 -> 135713 bytes state/map.yaml | 4 +- 6 files changed, 95 insertions(+), 36 deletions(-) diff --git a/epiphany-codex-bridge/src/mutation.rs b/epiphany-codex-bridge/src/mutation.rs index 70d36172..4a0a9d43 100644 --- a/epiphany-codex-bridge/src/mutation.rs +++ b/epiphany-codex-bridge/src/mutation.rs @@ -8,7 +8,9 @@ use codex_app_server_protocol::ThreadEpiphanyStateUpdatedNotification; use codex_app_server_protocol::ThreadEpiphanyStateUpdatedSource; use codex_app_server_protocol::ThreadEpiphanyUpdatePatch; use epiphany_core::EpiphanyReorientAcceptanceFinding; +use epiphany_core::EpiphanyReorientFindingInterpretation as CoreEpiphanyReorientFinding; use epiphany_core::EpiphanyRoleAcceptanceFinding; +use epiphany_core::EpiphanyRoleFindingInterpretation as CoreEpiphanyRoleFinding; use epiphany_core::EpiphanyRoleStatePatchDocument; use epiphany_core::EpiphanyStateUpdate; use epiphany_core::build_reorient_acceptance_bundle; @@ -21,10 +23,6 @@ use epiphany_state_model::EpiphanyInvestigationDisposition; use epiphany_state_model::EpiphanyScratchPad; use crate::results::map_core_role_result_role_id; -use crate::runtime_results::reorient_finding_runtime_job_id; -use crate::runtime_results::reorient_finding_runtime_result_id; -use crate::runtime_results::role_finding_runtime_job_id; -use crate::runtime_results::role_finding_runtime_result_id; pub struct RoleAcceptanceUpdate { pub patch: ThreadEpiphanyUpdatePatch, @@ -51,6 +49,15 @@ pub fn parse_role_finding_state_patch( .ok_or_else(|| "completed role finding did not include a reviewable statePatch".to_string()) } +pub fn parse_core_role_finding_state_patch( + finding: &CoreEpiphanyRoleFinding, +) -> Result { + finding + .state_patch + .clone() + .ok_or_else(|| "completed role finding did not include a reviewable statePatch".to_string()) +} + pub fn core_state_patch_from_protocol( patch: &ThreadEpiphanyUpdatePatch, ) -> EpiphanyRoleStatePatchDocument { @@ -75,6 +82,30 @@ pub fn core_state_patch_from_protocol( } } +pub fn protocol_patch_from_core( + patch: EpiphanyRoleStatePatchDocument, +) -> ThreadEpiphanyUpdatePatch { + ThreadEpiphanyUpdatePatch { + objective: patch.objective, + active_subgoal_id: patch.active_subgoal_id, + subgoals: patch.subgoals, + invariants: patch.invariants, + graphs: patch.graphs, + graph_frontier: patch.graph_frontier, + graph_checkpoint: patch.graph_checkpoint, + scratch: patch.scratch, + investigation_checkpoint: patch.investigation_checkpoint, + job_bindings: patch.job_bindings, + acceptance_receipts: patch.acceptance_receipts, + runtime_links: patch.runtime_links, + observations: patch.observations, + evidence: patch.evidence, + churn: patch.churn, + mode: patch.mode, + planning: patch.planning, + } +} + pub fn state_update_from_thread_patch( expected_revision: Option, patch: ThreadEpiphanyUpdatePatch, @@ -148,15 +179,15 @@ pub fn epiphany_imagination_finding_has_reviewable_state_patch( pub fn build_role_acceptance_update( role_id: ThreadEpiphanyRoleId, binding_id: &str, - finding: &ThreadEpiphanyRoleFinding, + finding: &CoreEpiphanyRoleFinding, accepted_evidence_id: String, accepted_observation_id: String, accepted_at: String, ) -> Result { - let mut patch = match role_id { + let mut core_patch = match role_id { ThreadEpiphanyRoleId::Imagination => { - let patch = parse_role_finding_state_patch(finding)?; - let patch_errors = imagination_role_accept_patch_errors(&patch); + let patch = parse_core_role_finding_state_patch(finding)?; + let patch_errors = imagination_role_state_patch_policy_errors(&patch); if !patch_errors.is_empty() { return Err(format!( "imagination role state patch is not acceptable: {}", @@ -166,8 +197,8 @@ pub fn build_role_acceptance_update( patch } ThreadEpiphanyRoleId::Modeling => { - let patch = parse_role_finding_state_patch(finding)?; - let patch_errors = modeling_role_accept_patch_errors(&patch); + let patch = parse_core_role_finding_state_patch(finding)?; + let patch_errors = modeling_role_state_patch_policy_errors(&patch); if !patch_errors.is_empty() { return Err(format!( "modeling role state patch is not acceptable: {}", @@ -176,7 +207,7 @@ pub fn build_role_acceptance_update( } patch } - ThreadEpiphanyRoleId::Verification => ThreadEpiphanyUpdatePatch::default(), + ThreadEpiphanyRoleId::Verification => EpiphanyRoleStatePatchDocument::default(), ThreadEpiphanyRoleId::Implementation | ThreadEpiphanyRoleId::Reorientation => { return Err(format!( "role {:?} cannot be accepted through roleAccept", @@ -185,7 +216,8 @@ pub fn build_role_acceptance_update( } }; - let projected_fields = epiphany_update_patch_changed_fields(&patch) + let patch_for_projection = protocol_patch_from_core(core_patch.clone()); + let projected_fields = epiphany_update_patch_changed_fields(&patch_for_projection) .into_iter() .map(|field| format!("{field:?}")) .collect(); @@ -197,8 +229,8 @@ pub fn build_role_acceptance_update( summary: finding.summary.clone(), next_safe_move: finding.next_safe_move.clone(), files_inspected: finding.files_inspected.clone(), - runtime_result_id: role_finding_runtime_result_id(finding), - runtime_job_id: role_finding_runtime_job_id(finding), + runtime_result_id: finding.runtime_result_id.clone(), + runtime_job_id: finding.runtime_job_id.clone(), projected_fields, }, accepted_evidence_id, @@ -208,9 +240,12 @@ pub fn build_role_acceptance_update( let accepted_receipt_id = acceptance_bundle.accepted_receipt_id.clone(); let accepted_observation_id = acceptance_bundle.accepted_observation_id.clone(); let accepted_evidence_id = acceptance_bundle.accepted_evidence_id.clone(); - patch.evidence.push(acceptance_bundle.evidence); - patch.observations.push(acceptance_bundle.observation); - patch.acceptance_receipts.push(acceptance_bundle.receipt); + core_patch.evidence.push(acceptance_bundle.evidence); + core_patch.observations.push(acceptance_bundle.observation); + core_patch + .acceptance_receipts + .push(acceptance_bundle.receipt); + let patch = protocol_patch_from_core(core_patch); let changed_fields = epiphany_update_patch_changed_fields(&patch); Ok(RoleAcceptanceUpdate { @@ -225,7 +260,7 @@ pub fn build_role_acceptance_update( pub fn build_reorient_acceptance_update( expected_revision: Option, binding_id: &str, - finding: &ThreadEpiphanyReorientFinding, + finding: &CoreEpiphanyReorientFinding, accepted_evidence_id: String, accepted_observation_id: String, accepted_at: String, @@ -241,8 +276,8 @@ pub fn build_reorient_acceptance_update( next_safe_move: finding.next_safe_move.clone(), checkpoint_still_valid: finding.checkpoint_still_valid, files_inspected: finding.files_inspected.clone(), - runtime_result_id: reorient_finding_runtime_result_id(finding), - runtime_job_id: reorient_finding_runtime_job_id(finding), + runtime_result_id: finding.runtime_result_id.clone(), + runtime_job_id: finding.runtime_job_id.clone(), }, accepted_evidence_id, accepted_observation_id, diff --git a/epiphany-codex-bridge/src/mutation_service.rs b/epiphany-codex-bridge/src/mutation_service.rs index a368209d..cb2121eb 100644 --- a/epiphany-codex-bridge/src/mutation_service.rs +++ b/epiphany-codex-bridge/src/mutation_service.rs @@ -35,8 +35,8 @@ use epiphany_state_model::EpiphanyThreadState; use crate::error::EpiphanyBridgeError; use crate::error::Result as BridgeResult; -use crate::jobs::epiphany_blocked_state_job; use crate::jobs::derive_epiphany_jobs; +use crate::jobs::epiphany_blocked_state_job; use crate::jobs::map_interrupted_epiphany_job; use crate::jobs::map_launched_epiphany_job; use crate::launch::EPIPHANY_REORIENT_LAUNCH_BINDING_ID; @@ -54,8 +54,10 @@ use crate::pressure::derive_epiphany_pressure; use crate::reorient::EpiphanyFreshnessWatcherSnapshot; use crate::reorient::derive_epiphany_freshness_view; use crate::reorient::derive_epiphany_reorient; -use crate::runtime_results::load_completed_epiphany_reorient_finding; -use crate::runtime_results::load_completed_epiphany_role_finding; +use crate::results::map_protocol_reorient_finding; +use crate::results::map_protocol_role_finding; +use crate::runtime_results::load_completed_core_epiphany_reorient_finding; +use crate::runtime_results::load_completed_core_epiphany_role_finding; use uuid::Uuid; #[allow(async_fn_in_trait)] @@ -381,7 +383,7 @@ pub async fn apply_thread_epiphany_role_accept( validate_expected_revision(expected_revision, state.revision)?; let runtime_store_path = thread.epiphany_runtime_spine_store_path().await; - let finding = load_completed_epiphany_role_finding( + let finding = load_completed_core_epiphany_role_finding( Some(runtime_store_path.as_path()), &state, role_id, @@ -418,7 +420,7 @@ pub async fn apply_thread_epiphany_role_accept( accepted_observation_id, accepted_evidence_id, applied_patch, - finding, + finding: map_protocol_role_finding(role_id, finding), }) } @@ -444,7 +446,7 @@ pub async fn apply_thread_epiphany_reorient_accept( } let runtime_store_path = thread.epiphany_runtime_spine_store_path().await; - let finding = load_completed_epiphany_reorient_finding( + let finding = load_completed_core_epiphany_reorient_finding( Some(runtime_store_path.as_path()), &state, binding_id, @@ -477,7 +479,7 @@ pub async fn apply_thread_epiphany_reorient_accept( accepted_receipt_id, accepted_observation_id, accepted_evidence_id, - finding, + finding: map_protocol_reorient_finding(finding), }) } diff --git a/epiphany-codex-bridge/src/runtime_results.rs b/epiphany-codex-bridge/src/runtime_results.rs index 6950cbc6..08f25c2a 100644 --- a/epiphany-codex-bridge/src/runtime_results.rs +++ b/epiphany-codex-bridge/src/runtime_results.rs @@ -392,19 +392,29 @@ pub fn load_completed_epiphany_role_finding( role_id: ThreadEpiphanyRoleId, binding_id: &str, ) -> BridgeResult { + load_completed_core_epiphany_role_finding(runtime_store_path, state, role_id, binding_id) + .map(|finding| map_protocol_role_finding(role_id, finding)) +} + +pub fn load_completed_core_epiphany_role_finding( + runtime_store_path: Option<&Path>, + state: &EpiphanyThreadState, + role_id: ThreadEpiphanyRoleId, + binding_id: &str, +) -> BridgeResult { if let Some(link) = latest_epiphany_runtime_link_for_binding(state, binding_id) { - let (status, finding, _note) = load_epiphany_role_result_from_runtime_spine_job( + let snapshot = load_core_epiphany_role_result_from_runtime_spine_job( link.runtime_job_id.as_str(), runtime_store_path, role_id, ); - if status != ThreadEpiphanyRoleResultStatus::Completed { + if snapshot.status != CoreEpiphanyCoordinatorRoleResultStatus::Completed { return Err(EpiphanyBridgeError::InvalidRequest(format!( "cannot accept role result while worker status is {:?}", - status + snapshot.status ))); } - return finding.ok_or_else(|| { + return snapshot.finding.ok_or_else(|| { EpiphanyBridgeError::InvalidRequest( "cannot accept completed role worker because no typed runtime-spine result was recorded" .to_string(), @@ -489,18 +499,27 @@ pub fn load_completed_epiphany_reorient_finding( state: &EpiphanyThreadState, binding_id: &str, ) -> BridgeResult { + load_completed_core_epiphany_reorient_finding(runtime_store_path, state, binding_id) + .map(map_protocol_reorient_finding) +} + +pub fn load_completed_core_epiphany_reorient_finding( + runtime_store_path: Option<&Path>, + state: &EpiphanyThreadState, + binding_id: &str, +) -> BridgeResult { if let Some(link) = latest_epiphany_runtime_link_for_binding(state, binding_id) { - let (status, finding, _note) = load_epiphany_reorient_result_from_runtime_spine_job( + let snapshot = load_core_epiphany_reorient_result_from_runtime_spine_job( link.runtime_job_id.as_str(), runtime_store_path, ); - if status != ThreadEpiphanyReorientResultStatus::Completed { + if snapshot.status != CoreEpiphanyCrrcResultStatus::Completed { return Err(EpiphanyBridgeError::InvalidRequest(format!( "cannot accept reorientation result while worker status is {:?}", - status + snapshot.status ))); } - return finding.ok_or_else(|| { + return snapshot.finding.ok_or_else(|| { EpiphanyBridgeError::InvalidRequest( "cannot accept completed reorientation worker because no typed runtime-spine result was recorded" .to_string(), diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 962aba45..9c861d51 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -52,6 +52,7 @@ remembers doctrine; the branch remembers the blade. - Core role/reorient finding interpretations and acceptance bundles now serialize natively too. Use those typed result surfaces as the next authority target; do not let `ThreadEpiphanyRoleFinding` or `ThreadEpiphanyReorientFinding` remain the bridge's internal result truth just because legacy view/accept responses still expose them. - Runtime-result loading now has core typed snapshot APIs for role and reorient worker results, with legacy protocol tuple wrappers layered over them. This intentionally creates temporary duplicate note rendering; the next cut should move coordinator and acceptance consumers to typed snapshots, then delete protocol-shaped result rendering instead of preserving both. - Coordinator result gating now consumes typed runtime result snapshots and core finding interpretations directly. `ThreadEpiphanyRoleFinding` / `ThreadEpiphanyReorientFinding` remain in legacy runtime wrapper/view/mutation response paths; the next cut is acceptance/mutation so protocol findings stop being the accepted-result authority. +- Acceptance/mutation now builds role/reorient receipts, evidence, observations, scratch, and checkpoint updates from core typed finding interpretations. Protocol findings remain as legacy response payloads and helper residue; delete those wrappers once view/result responses are also typed internally. - MCP remains JSON because MCP is a JSON protocol. The latest processor cut only moved MCP refresh/OAuth/status/resource/tool-call route handling into `codex_message_processor/mcp_routes.rs`, reducing the root processor to about 8,977 lines; it did not claim MCP payloads are CultNet or make MCP an Epiphany-owned runtime contract. - Codex account auth route handling remains in vendored Codex but now lives in `codex_message_processor/auth_routes.rs`: API-key login, ChatGPT browser/device-code/external-token login, logout, auth status, account, rate limits, and add-credits nudge handling. This preserves Codex ownership of subscription auth while cutting the root processor to about 8,094 lines. - One-off command execution and command stream write/resize/terminate route handling now live in `codex_message_processor/command_routes.rs`; `CommandExecManager` remains the lifecycle owner, while the root processor is about 7,800 lines. diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index 89835513b36832c746069adebc25ad2217cf24c8..287a0170282d94b59575d347d214d070710e5c62 100644 GIT binary patch delta 215 zcmdnLh-2XrjtMG^&o`