diff --git a/epiphany-codex-bridge/src/coordinator.rs b/epiphany-codex-bridge/src/coordinator.rs index be9fe0b9..e1a0d3a9 100644 --- a/epiphany-codex-bridge/src/coordinator.rs +++ b/epiphany-codex-bridge/src/coordinator.rs @@ -281,7 +281,7 @@ pub struct EpiphanyCoordinatorSourceSignals { pub struct EpiphanyCoordinatorStatus { pub decision: EpiphanyCoordinatorDecision, pub source_signals: EpiphanyCoordinatorSourceSignals, - pub roles: Vec, + pub roles: Vec, pub note: String, pub modeling_result_accepted: bool, pub verification_result_accepted: bool, @@ -294,7 +294,7 @@ pub async fn derive_epiphany_coordinator_status( state_status: ThreadEpiphanyReorientStateStatus, pressure: &epiphany_core::EpiphanyPressure, recommendation: &CoreEpiphanyCrrcRecommendation, - roles: Vec, + roles: Vec, reorient_decision: Option<&ThreadEpiphanyReorientDecision>, reorient_result_status: ThreadEpiphanyReorientResultStatus, reorient_finding: Option<&ThreadEpiphanyReorientFinding>, @@ -429,6 +429,7 @@ pub fn map_epiphany_coordinator_view( state_status: ThreadEpiphanyReorientStateStatus, state_revision: Option, status: EpiphanyCoordinatorStatus, + roles: Vec, ) -> ThreadEpiphanyViewCoordinator { ThreadEpiphanyViewCoordinator { thread_id, @@ -452,7 +453,7 @@ pub fn map_epiphany_coordinator_view( can_auto_run: status.decision.can_auto_run, reason: status.decision.reason, source_signals: map_protocol_coordinator_source_signals(status.source_signals), - roles: status.roles, + roles, note: status.note, } } @@ -565,7 +566,7 @@ pub async fn select_epiphany_coordinator_automation( state_status, &core_pressure, &crrc_recommendation, - roles, + roles.roles, Some(&reorient_decision), reorient_result_status, reorient_finding.as_ref(), @@ -616,7 +617,7 @@ pub fn map_epiphany_coordinator( checkpoint_present: bool, pressure: &epiphany_core::EpiphanyPressure, recommendation: &CoreEpiphanyCrrcRecommendation, - roles: &[ThreadEpiphanyRoleLane], + roles: &[EpiphanyRoleBoardLane], signals: &EpiphanyCoordinatorSourceSignals, modeling_result_accepted: bool, modeling_result_reviewable: bool, @@ -639,7 +640,10 @@ pub fn map_epiphany_coordinator( .recommended_scene_action .map(epiphany_core::crrc_scene_action_to_coordinator_scene_action), }, - roles: roles.iter().map(map_core_coordinator_role_lane).collect(), + roles: roles + .iter() + .map(map_core_coordinator_role_lane_from_role_board) + .collect(), signals: EpiphanyCoordinatorSignals { modeling_result_status: signals.modeling_result_status, verification_result_status: signals.verification_result_status, @@ -657,42 +661,12 @@ pub fn map_epiphany_coordinator( }) } -fn map_core_crrc_action_from_protocol(action: ThreadEpiphanyCrrcAction) -> CoreEpiphanyCrrcAction { - match action { - ThreadEpiphanyCrrcAction::Continue => CoreEpiphanyCrrcAction::Continue, - ThreadEpiphanyCrrcAction::PrepareCheckpoint => CoreEpiphanyCrrcAction::PrepareCheckpoint, - ThreadEpiphanyCrrcAction::LaunchReorientWorker => { - CoreEpiphanyCrrcAction::LaunchReorientWorker - } - ThreadEpiphanyCrrcAction::WaitForReorientWorker => { - CoreEpiphanyCrrcAction::WaitForReorientWorker - } - ThreadEpiphanyCrrcAction::ReviewReorientResult => { - CoreEpiphanyCrrcAction::ReviewReorientResult - } - ThreadEpiphanyCrrcAction::AcceptReorientResult => { - CoreEpiphanyCrrcAction::AcceptReorientResult - } - ThreadEpiphanyCrrcAction::RegatherManually => CoreEpiphanyCrrcAction::RegatherManually, - } -} - -fn map_core_coordinator_role_lane( - role: &ThreadEpiphanyRoleLane, +fn map_core_coordinator_role_lane_from_role_board( + role: &EpiphanyRoleBoardLane, ) -> CoreEpiphanyCoordinatorRoleLane { CoreEpiphanyCoordinatorRoleLane { - id: map_core_coordinator_role_id(role.id), - status: map_core_coordinator_role_status(role.status), - } -} - -fn map_core_coordinator_role_id(role_id: ThreadEpiphanyRoleId) -> CoreEpiphanyCoordinatorRoleId { - match role_id { - ThreadEpiphanyRoleId::Implementation => CoreEpiphanyCoordinatorRoleId::Implementation, - ThreadEpiphanyRoleId::Imagination => CoreEpiphanyCoordinatorRoleId::Imagination, - ThreadEpiphanyRoleId::Modeling => CoreEpiphanyCoordinatorRoleId::Modeling, - ThreadEpiphanyRoleId::Verification => CoreEpiphanyCoordinatorRoleId::Verification, - ThreadEpiphanyRoleId::Reorientation => CoreEpiphanyCoordinatorRoleId::Reorientation, + id: role.id, + status: role.status, } } @@ -708,21 +682,6 @@ fn map_protocol_coordinator_role_id( } } -fn map_core_coordinator_role_status( - status: ThreadEpiphanyRoleStatus, -) -> CoreEpiphanyCoordinatorRoleStatus { - match status { - ThreadEpiphanyRoleStatus::Ready => CoreEpiphanyCoordinatorRoleStatus::Ready, - ThreadEpiphanyRoleStatus::Needed => CoreEpiphanyCoordinatorRoleStatus::Needed, - ThreadEpiphanyRoleStatus::Running => CoreEpiphanyCoordinatorRoleStatus::Running, - ThreadEpiphanyRoleStatus::Waiting => CoreEpiphanyCoordinatorRoleStatus::Waiting, - ThreadEpiphanyRoleStatus::Review => CoreEpiphanyCoordinatorRoleStatus::Review, - ThreadEpiphanyRoleStatus::Blocked => CoreEpiphanyCoordinatorRoleStatus::Blocked, - ThreadEpiphanyRoleStatus::Unavailable => CoreEpiphanyCoordinatorRoleStatus::Unavailable, - ThreadEpiphanyRoleStatus::Completed => CoreEpiphanyCoordinatorRoleStatus::Completed, - } -} - fn map_core_coordinator_role_result_status( status: ThreadEpiphanyRoleResultStatus, ) -> CoreEpiphanyCoordinatorRoleResultStatus { @@ -779,45 +738,6 @@ fn map_protocol_coordinator_role_result_status( } } -fn map_core_coordinator_scene_action_from_protocol( - action: ThreadEpiphanySceneAction, -) -> CoreEpiphanyCoordinatorSceneAction { - match action { - ThreadEpiphanySceneAction::Update => CoreEpiphanyCoordinatorSceneAction::Update, - ThreadEpiphanySceneAction::Reorient => CoreEpiphanyCoordinatorSceneAction::Reorient, - ThreadEpiphanySceneAction::ReorientLaunch => { - CoreEpiphanyCoordinatorSceneAction::ReorientLaunch - } - ThreadEpiphanySceneAction::ReorientResult => { - CoreEpiphanyCoordinatorSceneAction::ReorientResult - } - ThreadEpiphanySceneAction::ReorientAccept => { - CoreEpiphanyCoordinatorSceneAction::ReorientAccept - } - ThreadEpiphanySceneAction::RoleLaunch => CoreEpiphanyCoordinatorSceneAction::RoleLaunch, - ThreadEpiphanySceneAction::RoleResult => CoreEpiphanyCoordinatorSceneAction::RoleResult, - ThreadEpiphanySceneAction::Index - | ThreadEpiphanySceneAction::Retrieve - | ThreadEpiphanySceneAction::Distill - | ThreadEpiphanySceneAction::Context - | ThreadEpiphanySceneAction::Planning - | ThreadEpiphanySceneAction::GraphQuery - | ThreadEpiphanySceneAction::Jobs - | ThreadEpiphanySceneAction::Roles - | ThreadEpiphanySceneAction::Coordinator - | ThreadEpiphanySceneAction::RoleAccept - | ThreadEpiphanySceneAction::JobLaunch - | ThreadEpiphanySceneAction::JobInterrupt - | ThreadEpiphanySceneAction::Freshness - | ThreadEpiphanySceneAction::Pressure - | ThreadEpiphanySceneAction::Crrc - | ThreadEpiphanySceneAction::Propose - | ThreadEpiphanySceneAction::Promote => { - unreachable!("unsupported CRRC coordinator scene action: {action:?}") - } - } -} - fn map_protocol_coordinator_scene_action( action: CoreEpiphanyCoordinatorSceneAction, ) -> ThreadEpiphanySceneAction { @@ -1083,6 +1003,12 @@ fn reorient_finding_runtime_result_id(finding: &ThreadEpiphanyReorientFinding) - finding.runtime_result_id.clone() } +#[derive(Debug, Clone)] +pub struct EpiphanyRoleBoardStatus { + pub roles: Vec, + source_jobs: Vec, +} + pub fn map_epiphany_roles( state: Option<&EpiphanyThreadState>, jobs: &[ThreadEpiphanyJob], @@ -1091,7 +1017,7 @@ pub fn map_epiphany_roles( recommendation: &CoreEpiphanyCrrcRecommendation, result_status: ThreadEpiphanyReorientResultStatus, reorient_job: Option<&ThreadEpiphanyJob>, -) -> Vec { +) -> EpiphanyRoleBoardStatus { let planning = state.map(|state| &state.planning); let checkpoint = state.and_then(|state| state.investigation_checkpoint.as_ref()); let mut source_jobs = jobs.to_vec(); @@ -1102,7 +1028,7 @@ pub fn map_epiphany_roles( { source_jobs.push(job.clone()); } - derive_role_board(EpiphanyRoleBoardInput { + let roles = derive_role_board(EpiphanyRoleBoardInput { state_present: state.is_some(), planning: EpiphanyRoleBoardPlanningSummary { capture_count: planning @@ -1138,10 +1064,9 @@ pub fn map_epiphany_roles( verification_binding_id: EPIPHANY_VERIFICATION_ROLE_BINDING_ID.to_string(), reorient_owner_role: EPIPHANY_REORIENT_OWNER_ROLE.to_string(), imagination_owner_role: EPIPHANY_IMAGINATION_OWNER_ROLE.to_string(), - }) - .into_iter() - .map(|lane| map_protocol_role_board_lane(lane, &source_jobs)) - .collect() + }); + + EpiphanyRoleBoardStatus { roles, source_jobs } } fn map_core_role_board_job(job: &ThreadEpiphanyJob) -> EpiphanyRoleBoardJob { @@ -1168,6 +1093,17 @@ fn map_core_role_board_job_status(status: ThreadEpiphanyJobStatus) -> EpiphanyRo } } +pub fn map_protocol_role_board_lanes( + role_board: &EpiphanyRoleBoardStatus, +) -> Vec { + role_board + .roles + .iter() + .cloned() + .map(|lane| map_protocol_role_board_lane(lane, &role_board.source_jobs)) + .collect() +} + fn map_protocol_role_board_lane( lane: EpiphanyRoleBoardLane, source_jobs: &[ThreadEpiphanyJob], @@ -1250,28 +1186,13 @@ fn map_protocol_coordinator_role_status( } pub fn render_epiphany_roles_note( - roles: &[ThreadEpiphanyRoleLane], + roles: &[EpiphanyRoleBoardLane], state_status: ThreadEpiphanyReorientStateStatus, - recommendation: ThreadEpiphanyCrrcAction, + recommendation: CoreEpiphanyCrrcAction, ) -> String { - let core_roles = roles - .iter() - .map(|role| EpiphanyRoleBoardLane { - id: map_core_coordinator_role_id(role.id), - title: role.title.clone(), - owner_role: role.owner_role.clone(), - status: map_core_coordinator_role_status(role.status), - note: role.note.clone(), - jobs: role.jobs.iter().map(map_core_role_board_job).collect(), - authority_scopes: role.authority_scopes.clone(), - recommended_action: role - .recommended_action - .map(map_core_coordinator_scene_action_from_protocol), - }) - .collect::>(); render_role_board_note( - &core_roles, + roles, format!("{:?}", state_status).as_str(), - map_core_crrc_action_from_protocol(recommendation), + recommendation, ) } diff --git a/epiphany-codex-bridge/src/view.rs b/epiphany-codex-bridge/src/view.rs index 88a786ce..a9c5f092 100644 --- a/epiphany-codex-bridge/src/view.rs +++ b/epiphany-codex-bridge/src/view.rs @@ -19,8 +19,8 @@ use crate::coordinator::epiphany_reorient_finding_already_accepted; use crate::coordinator::map_epiphany_coordinator_view; use crate::coordinator::map_epiphany_crrc_recommendation; use crate::coordinator::map_epiphany_roles; -use crate::coordinator::map_protocol_crrc_action; use crate::coordinator::map_protocol_crrc_recommendation; +use crate::coordinator::map_protocol_role_board_lanes; use crate::coordinator::render_epiphany_roles_note; use crate::cultnet::EpiphanyFreshnessSurface; use crate::cultnet::EpiphanySurfaceSource; @@ -269,13 +269,14 @@ pub async fn map_epiphany_view_response( recommendation.as_ref(), roles.clone(), ) { + let protocol_roles = map_protocol_role_board_lanes(&roles); let status = derive_epiphany_coordinator_status( state, runtime_store_path, reorient_state_status, core_pressure, recommendation, - roles, + roles.roles.clone(), reorient_decision.as_ref(), reorient_result_status, reorient_finding.as_ref(), @@ -288,6 +289,7 @@ pub async fn map_epiphany_view_response( reorient_state_status, state_revision, status, + protocol_roles, )) } else { None @@ -307,7 +309,11 @@ pub async fn map_epiphany_view_response( Vec::new() }, roles: lenses.contains(&ThreadEpiphanyViewLens::Roles).then(|| { - let roles = roles.clone().unwrap_or_default(); + let role_board = roles.clone(); + let protocol_roles = role_board + .as_ref() + .map(map_protocol_role_board_lanes) + .unwrap_or_default(); ThreadEpiphanyViewRoles { thread_id: thread_id.clone(), source: if loaded { @@ -318,14 +324,17 @@ pub async fn map_epiphany_view_response( state_status: reorient_state_status, state_revision, note: render_epiphany_roles_note( - &roles, + role_board + .as_ref() + .map(|role_board| role_board.roles.as_slice()) + .unwrap_or(&[]), reorient_state_status, recommendation .as_ref() - .map(|recommendation| map_protocol_crrc_action(recommendation.action)) - .unwrap_or(ThreadEpiphanyCrrcAction::Continue), + .map(|recommendation| recommendation.action) + .unwrap_or(epiphany_core::EpiphanyCrrcAction::Continue), ), - roles, + roles: protocol_roles, } }), planning: lenses.contains(&ThreadEpiphanyViewLens::Planning).then(|| { diff --git a/epiphany-core/src/surfaces/role_board.rs b/epiphany-core/src/surfaces/role_board.rs index f74be548..f11445fc 100644 --- a/epiphany-core/src/surfaces/role_board.rs +++ b/epiphany-core/src/surfaces/role_board.rs @@ -3,8 +3,10 @@ use super::EpiphanyCoordinatorRoleStatus; use super::EpiphanyCoordinatorSceneAction; use super::EpiphanyCrrcAction; use super::EpiphanyCrrcResultStatus; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub enum EpiphanyRoleBoardJobStatus { Idle, Needed, @@ -17,7 +19,8 @@ pub enum EpiphanyRoleBoardJobStatus { Unavailable, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct EpiphanyRoleBoardJob { pub id: String, pub owner_role: String, @@ -26,7 +29,8 @@ pub struct EpiphanyRoleBoardJob { pub blocking_reason: Option, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct EpiphanyRoleBoardPlanningSummary { pub capture_count: usize, pub backlog_item_count: usize, @@ -34,13 +38,15 @@ pub struct EpiphanyRoleBoardPlanningSummary { pub objective_draft_count: usize, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct EpiphanyRoleBoardCheckpointSummary { pub disposition: Option, pub next_action: Option, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct EpiphanyRoleBoardInput { pub state_present: bool, pub planning: EpiphanyRoleBoardPlanningSummary, @@ -61,7 +67,8 @@ pub struct EpiphanyRoleBoardInput { pub imagination_owner_role: String, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct EpiphanyRoleBoardLane { pub id: EpiphanyCoordinatorRoleId, pub title: String, diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 349c0093..cac85e11 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -48,7 +48,7 @@ remembers doctrine; the branch remembers the blade. - `epiphany-codex-bridge` no longer depends on vendored `codex-core` at all. Retrieval takes workspace/Codex-home paths, invalidation owns a native `notify` watcher, runtime-result acceptance takes typed state plus runtime-store path, live rollout/thread reads moved into app-server `epiphany_thread_host`, and mutation policy depends on the small `EpiphanyMutationHost` trait implemented by app-server's `EpiphanyCodexThreadHost` wrapper. The bridge owns Epiphany law; vendored app-server owns CodexThread access. - `epiphany-codex-bridge` also no longer depends on `codex-protocol`. Bridge durable-state imports point at `epiphany-state-model`, token pressure takes native `EpiphanyTokenUsageSnapshot`, app-server maps Codex token telemetry at the host wall, Codex token usage rollout replay lives in `codex_message_processor/token_usage_replay.rs`, and bridge errors are `EpiphanyBridgeError`. The only remaining vendored bridge dependencies are app-server protocol DTOs and absolute path validation. - The first bridge JSON-RPC starvation cuts are landed. Freshness now returns a serializable `EpiphanyFreshnessSurface`, job launch/interrupt mutation results carry typed `EpiphanyJobView` plus separate host-edge launcher/backend ids, reorient/pre-compaction decisions consume typed `EpiphanyPressure`, reorient policy consumes typed freshness directly, and reorient launch documents/results use typed core reorient decisions/status before app-server projects them into legacy JSON-RPC DTOs. Continue by cutting the full view/coordinator board into typed surfaces, not by inventing adapters with nicer uniforms. -- Coordinator decision/status is less contaminated now: `epiphany-codex-bridge` stores coordinator decisions, source signals, and CRRC recommendations as core-shaped types, core CRRC/coordinator surfaces serialize natively, and `ThreadEpiphany*` projection happens at the legacy view emission boundary. Keep cutting remaining role-board/result/reorient inputs until Aquarium/CultNet can read coordinator surfaces without JSON-RPC DTO authority. +- Coordinator decision/status is less contaminated now: `epiphany-codex-bridge` stores coordinator decisions, source signals, CRRC recommendations, and role-board lanes as core-shaped types, core CRRC/coordinator/role-board surfaces serialize natively, and `ThreadEpiphany*` projection happens at the legacy view emission boundary. Keep cutting remaining result/reorient inputs until Aquarium/CultNet can read coordinator surfaces without JSON-RPC DTO authority. - 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 62974424..0311dedd 100644 Binary files a/state/ledgers.msgpack and b/state/ledgers.msgpack differ diff --git a/state/map.yaml b/state/map.yaml index a56b92f0..f94f186f 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -311,10 +311,10 @@ current_status: reorient launch documents are built from typed core decisions, reorient launch mutation results return typed decision/state-status values from the bridge, and core reorient decisions are serializable CultNet-ready state. - Coordinator decisions, coordinator source signals, and CRRC recommendations - now remain core-shaped inside the bridge; app-server still projects those - typed surfaces into `ThreadEpiphany*` responses at the legacy JSON-RPC - edge. The bridge still + Coordinator decisions, coordinator source signals, CRRC recommendations, + and role-board lanes now remain core-shaped inside the bridge; app-server + still projects those typed surfaces into `ThreadEpiphany*` responses at the + legacy JSON-RPC edge. The bridge still depends on vendored Codex for app-server protocol DTOs and absolute path validation, so the purification is not done. - next_action: Continue Codex starvation and CultNet liberation by cutting bridge-internal `ThreadEpiphany*` authority. Prioritize role-board/result/reorient DTO coupling next: derive typed CultNet surfaces first, push JSON-RPC projection into app-server, and keep MCP/OpenAI JSON only at their true protocol/text edges. Do not add compatibility adapters unless they protect a named ownership invariant. + next_action: Continue Codex starvation and CultNet liberation by cutting bridge-internal `ThreadEpiphany*` authority. Prioritize role-result/reorient-result/reorient DTO coupling next: derive typed CultNet surfaces first, push JSON-RPC projection into app-server, and keep MCP/OpenAI JSON only at their true protocol/text edges. Do not add compatibility adapters unless they protect a named ownership invariant.