Keep jobs core-shaped in bridge

This commit is contained in:
Meta Vens 2026-05-20 02:19:06 +01:00
commit e9c0b2aa78
6 changed files with 78 additions and 57 deletions

View file

@ -3,8 +3,6 @@ use codex_app_server_protocol::ThreadEpiphanyCoordinatorSignals;
use codex_app_server_protocol::ThreadEpiphanyCrrcAction;
use codex_app_server_protocol::ThreadEpiphanyCrrcRecommendation;
use codex_app_server_protocol::ThreadEpiphanyJob;
use codex_app_server_protocol::ThreadEpiphanyJobKind;
use codex_app_server_protocol::ThreadEpiphanyJobStatus;
use codex_app_server_protocol::ThreadEpiphanyReorientAction;
use codex_app_server_protocol::ThreadEpiphanyReorientResultStatus;
use codex_app_server_protocol::ThreadEpiphanyReorientStateStatus;
@ -34,6 +32,8 @@ use epiphany_core::EpiphanyCrrcResultStatus as CoreEpiphanyCrrcResultStatus;
use epiphany_core::EpiphanyCrrcSceneAction as CoreEpiphanyCrrcSceneAction;
use epiphany_core::EpiphanyCrrcStateStatus as CoreEpiphanyCrrcStateStatus;
use epiphany_core::EpiphanyJobLaunchRequest;
use epiphany_core::EpiphanyJobStatus as CoreEpiphanyJobStatus;
use epiphany_core::EpiphanyJobView;
use epiphany_core::EpiphanyReorientAction as CoreEpiphanyReorientAction;
use epiphany_core::EpiphanyReorientDecision as CoreEpiphanyReorientDecision;
use epiphany_core::EpiphanyReorientFindingInterpretation as CoreEpiphanyReorientFinding;
@ -54,9 +54,11 @@ use epiphany_core::recommend_crrc_action;
use epiphany_core::render_role_board_note;
use epiphany_core::select_coordinator_automation_action;
use epiphany_state_model::EpiphanyAcceptanceReceipt;
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;
@ -997,17 +999,17 @@ fn reorient_finding_runtime_result_id(finding: &CoreEpiphanyReorientFinding) ->
#[derive(Debug, Clone)]
pub struct EpiphanyRoleBoardStatus {
pub roles: Vec<EpiphanyRoleBoardLane>,
source_jobs: Vec<ThreadEpiphanyJob>,
source_jobs: Vec<EpiphanyJobView>,
}
pub fn map_epiphany_roles(
state: Option<&EpiphanyThreadState>,
jobs: &[ThreadEpiphanyJob],
jobs: &[EpiphanyJobView],
decision: &CoreEpiphanyReorientDecision,
pressure: &epiphany_core::EpiphanyPressure,
recommendation: &CoreEpiphanyCrrcRecommendation,
result_status: CoreEpiphanyCrrcResultStatus,
reorient_job: Option<&ThreadEpiphanyJob>,
reorient_job: Option<&EpiphanyJobView>,
) -> EpiphanyRoleBoardStatus {
let planning = state.map(|state| &state.planning);
let checkpoint = state.and_then(|state| state.investigation_checkpoint.as_ref());
@ -1060,7 +1062,7 @@ pub fn map_epiphany_roles(
EpiphanyRoleBoardStatus { roles, source_jobs }
}
fn map_core_role_board_job(job: &ThreadEpiphanyJob) -> EpiphanyRoleBoardJob {
fn map_core_role_board_job(job: &EpiphanyJobView) -> EpiphanyRoleBoardJob {
EpiphanyRoleBoardJob {
id: job.id.clone(),
owner_role: job.owner_role.clone(),
@ -1070,17 +1072,17 @@ fn map_core_role_board_job(job: &ThreadEpiphanyJob) -> EpiphanyRoleBoardJob {
}
}
fn map_core_role_board_job_status(status: ThreadEpiphanyJobStatus) -> EpiphanyRoleBoardJobStatus {
fn map_core_role_board_job_status(status: CoreEpiphanyJobStatus) -> EpiphanyRoleBoardJobStatus {
match status {
ThreadEpiphanyJobStatus::Idle => EpiphanyRoleBoardJobStatus::Idle,
ThreadEpiphanyJobStatus::Needed => EpiphanyRoleBoardJobStatus::Needed,
ThreadEpiphanyJobStatus::Pending => EpiphanyRoleBoardJobStatus::Pending,
ThreadEpiphanyJobStatus::Running => EpiphanyRoleBoardJobStatus::Running,
ThreadEpiphanyJobStatus::Completed => EpiphanyRoleBoardJobStatus::Completed,
ThreadEpiphanyJobStatus::Failed => EpiphanyRoleBoardJobStatus::Failed,
ThreadEpiphanyJobStatus::Cancelled => EpiphanyRoleBoardJobStatus::Cancelled,
ThreadEpiphanyJobStatus::Blocked => EpiphanyRoleBoardJobStatus::Blocked,
ThreadEpiphanyJobStatus::Unavailable => EpiphanyRoleBoardJobStatus::Unavailable,
CoreEpiphanyJobStatus::Idle => EpiphanyRoleBoardJobStatus::Idle,
CoreEpiphanyJobStatus::Needed => EpiphanyRoleBoardJobStatus::Needed,
CoreEpiphanyJobStatus::Pending => EpiphanyRoleBoardJobStatus::Pending,
CoreEpiphanyJobStatus::Running => EpiphanyRoleBoardJobStatus::Running,
CoreEpiphanyJobStatus::Completed => EpiphanyRoleBoardJobStatus::Completed,
CoreEpiphanyJobStatus::Failed => EpiphanyRoleBoardJobStatus::Failed,
CoreEpiphanyJobStatus::Cancelled => EpiphanyRoleBoardJobStatus::Cancelled,
CoreEpiphanyJobStatus::Blocked => EpiphanyRoleBoardJobStatus::Blocked,
CoreEpiphanyJobStatus::Unavailable => EpiphanyRoleBoardJobStatus::Unavailable,
}
}
@ -1097,7 +1099,7 @@ pub fn map_protocol_role_board_lanes(
fn map_protocol_role_board_lane(
lane: EpiphanyRoleBoardLane,
source_jobs: &[ThreadEpiphanyJob],
source_jobs: &[EpiphanyJobView],
) -> ThreadEpiphanyRoleLane {
ThreadEpiphanyRoleLane {
id: map_protocol_coordinator_role_id(lane.id),
@ -1119,45 +1121,47 @@ fn map_protocol_role_board_lane(
fn map_protocol_role_board_job(
job: &EpiphanyRoleBoardJob,
source_jobs: &[ThreadEpiphanyJob],
source_jobs: &[EpiphanyJobView],
) -> ThreadEpiphanyJob {
source_jobs
.iter()
.find(|source_job| source_job.id == job.id && source_job.owner_role == job.owner_role)
.cloned()
.unwrap_or_else(|| ThreadEpiphanyJob {
id: job.id.clone(),
kind: ThreadEpiphanyJobKind::Specialist,
scope: job.id.clone(),
owner_role: job.owner_role.clone(),
launcher_job_id: None,
authority_scope: None,
backend_job_id: None,
status: map_protocol_role_board_job_status(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(),
.map(map_core_epiphany_job_view)
.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(),
})
})
}
fn map_protocol_role_board_job_status(
fn map_core_epiphany_job_status_from_role_board(
status: EpiphanyRoleBoardJobStatus,
) -> ThreadEpiphanyJobStatus {
) -> CoreEpiphanyJobStatus {
match status {
EpiphanyRoleBoardJobStatus::Idle => ThreadEpiphanyJobStatus::Idle,
EpiphanyRoleBoardJobStatus::Needed => ThreadEpiphanyJobStatus::Needed,
EpiphanyRoleBoardJobStatus::Pending => ThreadEpiphanyJobStatus::Pending,
EpiphanyRoleBoardJobStatus::Running => ThreadEpiphanyJobStatus::Running,
EpiphanyRoleBoardJobStatus::Completed => ThreadEpiphanyJobStatus::Completed,
EpiphanyRoleBoardJobStatus::Failed => ThreadEpiphanyJobStatus::Failed,
EpiphanyRoleBoardJobStatus::Cancelled => ThreadEpiphanyJobStatus::Cancelled,
EpiphanyRoleBoardJobStatus::Blocked => ThreadEpiphanyJobStatus::Blocked,
EpiphanyRoleBoardJobStatus::Unavailable => ThreadEpiphanyJobStatus::Unavailable,
EpiphanyRoleBoardJobStatus::Idle => CoreEpiphanyJobStatus::Idle,
EpiphanyRoleBoardJobStatus::Needed => CoreEpiphanyJobStatus::Needed,
EpiphanyRoleBoardJobStatus::Pending => CoreEpiphanyJobStatus::Pending,
EpiphanyRoleBoardJobStatus::Running => CoreEpiphanyJobStatus::Running,
EpiphanyRoleBoardJobStatus::Completed => CoreEpiphanyJobStatus::Completed,
EpiphanyRoleBoardJobStatus::Failed => CoreEpiphanyJobStatus::Failed,
EpiphanyRoleBoardJobStatus::Cancelled => CoreEpiphanyJobStatus::Cancelled,
EpiphanyRoleBoardJobStatus::Blocked => CoreEpiphanyJobStatus::Blocked,
EpiphanyRoleBoardJobStatus::Unavailable => CoreEpiphanyJobStatus::Unavailable,
}
}

View file

@ -22,14 +22,21 @@ pub fn derive_epiphany_jobs(
pub fn map_epiphany_jobs(
state: Option<&EpiphanyThreadState>,
retrieval_override: Option<&EpiphanyRetrievalState>,
) -> Vec<ThreadEpiphanyJob> {
) -> Vec<EpiphanyJobView> {
derive_epiphany_jobs(state, retrieval_override)
.into_iter()
.map(map_core_epiphany_job_view)
.collect()
}
fn map_core_epiphany_job_view(job: EpiphanyJobView) -> ThreadEpiphanyJob {
pub fn map_protocol_epiphany_jobs(
state: Option<&EpiphanyThreadState>,
retrieval_override: Option<&EpiphanyRetrievalState>,
) -> Vec<ThreadEpiphanyJob> {
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),

View file

@ -24,6 +24,7 @@ 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;
@ -322,7 +323,10 @@ pub async fn map_epiphany_view_response(
.contains(&ThreadEpiphanyViewLens::Scene)
.then(|| map_epiphany_scene(state, loaded, EPIPHANY_REORIENT_LAUNCH_BINDING_ID)),
jobs: if lenses.contains(&ThreadEpiphanyViewLens::Jobs) {
jobs.clone()
jobs.iter()
.cloned()
.map(map_core_epiphany_job_view)
.collect()
} else {
Vec::new()
},
@ -427,7 +431,7 @@ 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(),
reorient_job: reorient_job.clone().map(map_core_epiphany_job_view),
reorient_finding: reorient_finding.clone(),
available_actions,
note,
@ -476,7 +480,8 @@ pub async fn map_epiphany_role_result_response(
let job = map_epiphany_jobs(Some(state), None)
.into_iter()
.find(|job| job.id == binding_id);
.find(|job| job.id == binding_id)
.map(map_core_epiphany_job_view);
let result =
load_core_epiphany_role_result_snapshot(state, runtime_store_path, role_id, &binding_id)
.await;
@ -531,7 +536,8 @@ pub async fn map_epiphany_reorient_result_response(
let job = map_epiphany_jobs(Some(state), None)
.into_iter()
.find(|job| job.id == binding_id);
.find(|job| job.id == binding_id)
.map(map_core_epiphany_job_view);
let result =
load_core_epiphany_reorient_result_snapshot(Some(state), runtime_store_path, &binding_id)
.await;

View file

@ -57,6 +57,7 @@ remembers doctrine; the branch remembers the blade.
- Runtime result view responses now project directly from typed result snapshots; the old protocol tuple wrappers in `runtime_results.rs` are gone, and runtime-result tests assert core typed statuses.
- Role acceptance now applies core `EpiphanyStateUpdate` from typed findings; `ThreadEpiphanyUpdatePatch` survives in that path only as legacy accept-response projection.
- Role-board derivation consumes core `EpiphanyPressure` directly now; `ThreadEpiphanyPressure` is only response projection.
- Job reflection is now core-shaped inside the bridge. `map_epiphany_jobs` returns typed `EpiphanyJobView`, role-board/view/result assembly consumes that typed job surface, and `ThreadEpiphanyJob` is projected only when building legacy JSON-RPC response structs.
- 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.

Binary file not shown.

View file

@ -328,7 +328,10 @@ current_status:
wrappers themselves are gone. Role acceptance now produces a core
`EpiphanyStateUpdate`; `ThreadEpiphanyUpdatePatch` is retained there only as
legacy accept-response projection. Role-board derivation now consumes core
pressure directly instead of `ThreadEpiphanyPressure`. The bridge still
depends on vendored Codex for app-server protocol DTOs and absolute path
validation, so the purification is not done.
pressure directly instead of `ThreadEpiphanyPressure`. Job reflection now
stays core-shaped inside the bridge as `EpiphanyJobView`; role-board and
view assembly consume those typed job documents, and `ThreadEpiphanyJob` is
projected only at legacy response edges. 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-result/reorient-result 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.