diff --git a/epiphany-codex-bridge/src/coordinator.rs b/epiphany-codex-bridge/src/coordinator.rs index 4720da64..b0344ac2 100644 --- a/epiphany-codex-bridge/src/coordinator.rs +++ b/epiphany-codex-bridge/src/coordinator.rs @@ -56,7 +56,6 @@ use epiphany_state_model::EpiphanyJobKind as CoreEpiphanyJobKind; use epiphany_state_model::EpiphanyRetrievalState; use epiphany_state_model::EpiphanyThreadState; -use crate::jobs::map_core_epiphany_job_view; use crate::launch::EPIPHANY_IMAGINATION_OWNER_ROLE; use crate::launch::EPIPHANY_IMAGINATION_ROLE_BINDING_ID; use crate::launch::EPIPHANY_MODELING_ROLE_BINDING_ID; @@ -66,6 +65,7 @@ use crate::launch::EPIPHANY_VERIFICATION_ROLE_BINDING_ID; use crate::launch::build_epiphany_reorient_launch_request; use crate::launch::render_epiphany_coordinator_note; use crate::pressure::derive_epiphany_pressure; +use crate::protocol_edge::protocol_job_from_surface; use crate::reorient::EpiphanyFreshnessWatcherSnapshot; use crate::reorient::derive_epiphany_freshness_view; use crate::reorient::derive_epiphany_reorient; @@ -713,25 +713,29 @@ fn map_protocol_role_board_job( .iter() .find(|source_job| source_job.id == job.id && source_job.owner_role == job.owner_role) .cloned() - .map(map_core_epiphany_job_view) + .map(|job| protocol_job_from_surface(job, None, None)) .unwrap_or_else(|| { - map_core_epiphany_job_view(EpiphanyJobView { - id: job.id.clone(), - kind: CoreEpiphanyJobKind::Specialist, - scope: job.id.clone(), - owner_role: job.owner_role.clone(), - authority_scope: None, - runtime_job_id: None, - status: map_core_epiphany_job_status_from_role_board(job.status), - items_processed: None, - items_total: None, - progress_note: job.progress_note.clone(), - last_checkpoint_at_unix_seconds: None, - blocking_reason: job.blocking_reason.clone(), - active_thread_ids: Vec::new(), - linked_subgoal_ids: Vec::new(), - linked_graph_node_ids: Vec::new(), - }) + protocol_job_from_surface( + EpiphanyJobView { + id: job.id.clone(), + kind: CoreEpiphanyJobKind::Specialist, + scope: job.id.clone(), + owner_role: job.owner_role.clone(), + authority_scope: None, + runtime_job_id: None, + status: map_core_epiphany_job_status_from_role_board(job.status), + items_processed: None, + items_total: None, + progress_note: job.progress_note.clone(), + last_checkpoint_at_unix_seconds: None, + blocking_reason: job.blocking_reason.clone(), + active_thread_ids: Vec::new(), + linked_subgoal_ids: Vec::new(), + linked_graph_node_ids: Vec::new(), + }, + None, + None, + ) }) } diff --git a/epiphany-codex-bridge/src/jobs.rs b/epiphany-codex-bridge/src/jobs.rs index a1700e3f..666b0443 100644 --- a/epiphany-codex-bridge/src/jobs.rs +++ b/epiphany-codex-bridge/src/jobs.rs @@ -1,6 +1,3 @@ -use codex_app_server_protocol::ThreadEpiphanyJob; -use codex_app_server_protocol::ThreadEpiphanyJobKind; -use codex_app_server_protocol::ThreadEpiphanyJobStatus; use epiphany_core::EpiphanyJobStatus as CoreEpiphanyJobStatus; use epiphany_core::EpiphanyJobView; use epiphany_core::EpiphanyJobsInput; @@ -26,46 +23,6 @@ pub fn map_epiphany_jobs( derive_epiphany_jobs(state, retrieval_override) } -pub fn map_protocol_epiphany_jobs( - state: Option<&EpiphanyThreadState>, - retrieval_override: Option<&EpiphanyRetrievalState>, -) -> Vec { - derive_epiphany_jobs(state, retrieval_override) - .into_iter() - .map(map_core_epiphany_job_view) - .collect() -} - -pub fn map_core_epiphany_job_view(job: EpiphanyJobView) -> ThreadEpiphanyJob { - ThreadEpiphanyJob { - id: job.id, - kind: map_core_epiphany_job_kind(job.kind), - scope: job.scope, - owner_role: job.owner_role, - launcher_job_id: None, - authority_scope: job.authority_scope, - backend_job_id: job.runtime_job_id, - status: map_core_epiphany_job_status(job.status), - items_processed: job.items_processed, - items_total: job.items_total, - progress_note: job.progress_note, - last_checkpoint_at_unix_seconds: job.last_checkpoint_at_unix_seconds, - blocking_reason: job.blocking_reason, - active_thread_ids: job.active_thread_ids, - linked_subgoal_ids: job.linked_subgoal_ids, - linked_graph_node_ids: job.linked_graph_node_ids, - } -} - -pub fn map_core_epiphany_job_kind(kind: CoreEpiphanyJobKind) -> ThreadEpiphanyJobKind { - match kind { - CoreEpiphanyJobKind::Indexing => ThreadEpiphanyJobKind::Indexing, - CoreEpiphanyJobKind::Remap => ThreadEpiphanyJobKind::Remap, - CoreEpiphanyJobKind::Verification => ThreadEpiphanyJobKind::Verification, - CoreEpiphanyJobKind::Specialist => ThreadEpiphanyJobKind::Specialist, - } -} - pub fn map_launched_epiphany_job( state: &EpiphanyThreadState, binding_id: &str, @@ -112,20 +69,6 @@ pub fn map_interrupted_epiphany_job( }) } -fn map_core_epiphany_job_status(status: CoreEpiphanyJobStatus) -> ThreadEpiphanyJobStatus { - match status { - CoreEpiphanyJobStatus::Idle => ThreadEpiphanyJobStatus::Idle, - CoreEpiphanyJobStatus::Needed => ThreadEpiphanyJobStatus::Needed, - CoreEpiphanyJobStatus::Pending => ThreadEpiphanyJobStatus::Pending, - CoreEpiphanyJobStatus::Running => ThreadEpiphanyJobStatus::Running, - CoreEpiphanyJobStatus::Completed => ThreadEpiphanyJobStatus::Completed, - CoreEpiphanyJobStatus::Failed => ThreadEpiphanyJobStatus::Failed, - CoreEpiphanyJobStatus::Cancelled => ThreadEpiphanyJobStatus::Cancelled, - CoreEpiphanyJobStatus::Blocked => ThreadEpiphanyJobStatus::Blocked, - CoreEpiphanyJobStatus::Unavailable => ThreadEpiphanyJobStatus::Unavailable, - } -} - pub fn epiphany_blocked_state_job( id: &str, kind: CoreEpiphanyJobKind, diff --git a/epiphany-codex-bridge/src/view.rs b/epiphany-codex-bridge/src/view.rs index 0041a965..b8cde76f 100644 --- a/epiphany-codex-bridge/src/view.rs +++ b/epiphany-codex-bridge/src/view.rs @@ -31,11 +31,11 @@ use crate::coordinator::map_protocol_role_board_lanes; use crate::coordinator::render_epiphany_roles_note; use crate::cultnet::EpiphanyFreshnessSurface; use crate::cultnet::EpiphanySurfaceSource; -use crate::jobs::map_core_epiphany_job_view; use crate::jobs::map_epiphany_jobs; use crate::launch::EPIPHANY_REORIENT_LAUNCH_BINDING_ID; use crate::pressure::derive_epiphany_pressure; use crate::pressure::map_epiphany_pressure; +use crate::protocol_edge::protocol_job_from_surface; use crate::reorient::EpiphanyFreshnessWatcherSnapshot; use crate::reorient::derive_epiphany_freshness_view; use crate::reorient::derive_epiphany_reorient; @@ -307,7 +307,7 @@ pub async fn map_epiphany_view_response( jobs: if lenses.contains(&EpiphanyViewLens::Jobs) { jobs.iter() .cloned() - .map(map_core_epiphany_job_view) + .map(|job| protocol_job_from_surface(job, None, None)) .collect() } else { Vec::new() @@ -413,7 +413,9 @@ pub async fn map_epiphany_view_response( recommendation: protocol_recommendation, reorient_binding_id: EPIPHANY_REORIENT_LAUNCH_BINDING_ID.to_string(), reorient_result_status, - reorient_job: reorient_job.clone().map(map_core_epiphany_job_view), + reorient_job: reorient_job + .clone() + .map(|job| protocol_job_from_surface(job, None, None)), reorient_finding: reorient_finding.clone(), available_actions, note, @@ -468,7 +470,7 @@ pub async fn map_epiphany_role_result_response( let job = map_epiphany_jobs(Some(state), None) .into_iter() .find(|job| job.id == binding_id) - .map(map_core_epiphany_job_view); + .map(|job| protocol_job_from_surface(job, None, None)); let result = load_core_epiphany_role_result_snapshot(state, runtime_store_path, role_id, &binding_id) .await; @@ -525,7 +527,7 @@ pub async fn map_epiphany_reorient_result_response( let job = map_epiphany_jobs(Some(state), None) .into_iter() .find(|job| job.id == binding_id) - .map(map_core_epiphany_job_view); + .map(|job| protocol_job_from_surface(job, None, None)); let result = load_core_epiphany_reorient_result_snapshot(Some(state), runtime_store_path, &binding_id) .await; diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 0b5a7206..8f54525d 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -80,6 +80,7 @@ remembers doctrine; the branch remembers the blade. - Correction: app-server must not own Epiphany request mapping. Codex JSON-RPC request DTO conversion now lives in `epiphany-codex-bridge::protocol_edge`; vendored app-server calls those edge adapters and no longer depends directly on `epiphany-core`. - Context, graph-query, distill, generic job-launch document, and generic update/promote patch request cargo now enters bridge as core `EpiphanyContextParams`, `EpiphanyGraphQuery`, `EpiphanyDistillInput`, `EpiphanyWorkerLaunchDocument`, and `EpiphanyRoleStatePatchDocument`. - Freshness/job/reorient response projection also belongs to the bridge edge now. Vendored app-server no longer keeps local helpers for `EpiphanyFreshnessSurface`, `EpiphanyJobView`, `EpiphanySurfaceSource`, or reorient decision/status enum projection; it gathers host facts, invokes bridge/core services, and sends the Codex JSON-RPC response. +- Job projection has one mapper now. `epiphany-codex-bridge::jobs` derives typed `EpiphanyJobView` and no longer imports app-server protocol DTOs; `protocol_edge::protocol_job_from_surface` is the only `EpiphanyJobView` -> `ThreadEpiphanyJob` conversion path. - 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 979fdba9..92143291 100644 Binary files a/state/ledgers.msgpack and b/state/ledgers.msgpack differ diff --git a/state/map.yaml b/state/map.yaml index a73b62e2..eee2c572 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -398,6 +398,10 @@ current_status: invokes Epiphany bridge services, and sends Codex JSON-RPC responses, while the bridge owns the enum/DTO conversion from typed Epiphany surfaces into `ThreadEpiphany*` edge payloads. + The duplicate job projection authority in `epiphany-codex-bridge::jobs` + has been collapsed too: `jobs.rs` derives typed `EpiphanyJobView` only, + and `protocol_edge::protocol_job_from_surface` is the single + `EpiphanyJobView` -> `ThreadEpiphanyJob` mapper. 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 response/projection authority. Prioritize the next coordinator/view seam that still composes policy-shaped facts instead of merely loading host facts and projecting core surfaces; keep MCP/OpenAI JSON only at their true protocol/text edges. Do not add compatibility adapters unless they protect a named ownership invariant.