Keep role board lanes core-shaped

This commit is contained in:
Meta Vens 2026-05-19 22:06:09 +01:00
commit f9bc809d6f
6 changed files with 75 additions and 138 deletions

View file

@ -281,7 +281,7 @@ pub struct EpiphanyCoordinatorSourceSignals {
pub struct EpiphanyCoordinatorStatus {
pub decision: EpiphanyCoordinatorDecision,
pub source_signals: EpiphanyCoordinatorSourceSignals,
pub roles: Vec<ThreadEpiphanyRoleLane>,
pub roles: Vec<EpiphanyRoleBoardLane>,
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<ThreadEpiphanyRoleLane>,
roles: Vec<EpiphanyRoleBoardLane>,
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<u64>,
status: EpiphanyCoordinatorStatus,
roles: Vec<ThreadEpiphanyRoleLane>,
) -> 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<EpiphanyRoleBoardLane>,
source_jobs: Vec<ThreadEpiphanyJob>,
}
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<ThreadEpiphanyRoleLane> {
) -> 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<ThreadEpiphanyRoleLane> {
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::<Vec<_>>();
render_role_board_note(
&core_roles,
roles,
format!("{:?}", state_status).as_str(),
map_core_crrc_action_from_protocol(recommendation),
recommendation,
)
}

View file

@ -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(|| {

View file

@ -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<String>,
}
#[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<String>,
pub next_action: Option<String>,
}
#[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,

View file

@ -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.

Binary file not shown.

View file

@ -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.