From f229731d7caf62ded335b4e8ef082891ef3d9865 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 12 May 2026 07:20:31 +0100 Subject: [PATCH] Extract Epiphany role board into core --- epiphany-core/src/lib.rs | 10 + epiphany-core/src/surfaces/mod.rs | 11 + epiphany-core/src/surfaces/role_board.rs | 484 ++++++++++++++++++ notes/epiphany-current-algorithmic-map.md | 4 +- notes/fresh-workspace-handoff.md | 11 +- state/ledgers.msgpack | Bin 63172 -> 63464 bytes state/map.yaml | 4 +- .../app-server/src/codex_message_processor.rs | 468 ++++++----------- 8 files changed, 684 insertions(+), 308 deletions(-) create mode 100644 epiphany-core/src/surfaces/role_board.rs diff --git a/epiphany-core/src/lib.rs b/epiphany-core/src/lib.rs index 0bb6c5cb..0befca92 100644 --- a/epiphany-core/src/lib.rs +++ b/epiphany-core/src/lib.rs @@ -153,12 +153,22 @@ pub use surfaces::EpiphanyPressure; pub use surfaces::EpiphanyPressureBasis; pub use surfaces::EpiphanyPressureLevel; pub use surfaces::EpiphanyPressureStatus; +pub use surfaces::EpiphanyRoleBoardCheckpointSummary; +pub use surfaces::EpiphanyRoleBoardInput; +pub use surfaces::EpiphanyRoleBoardJob; +pub use surfaces::EpiphanyRoleBoardJobStatus; +pub use surfaces::EpiphanyRoleBoardLane; +pub use surfaces::EpiphanyRoleBoardPlanningSummary; pub use surfaces::EpiphanyViewInput; pub use surfaces::EpiphanyViewLens; pub use surfaces::EpiphanyViewOutput; pub use surfaces::coordinator_automation_action; pub use surfaces::crrc_scene_action_to_coordinator_scene_action; pub use surfaces::derive_pressure_view; +pub use surfaces::derive_role_board; +pub use surfaces::render_role_board_note; pub use surfaces::recommend_coordinator_action; pub use surfaces::recommend_crrc_action; +pub use surfaces::reorientation_role_status; +pub use surfaces::role_board_job_status_to_role_status; pub use surfaces::select_coordinator_automation_action; diff --git a/epiphany-core/src/surfaces/mod.rs b/epiphany-core/src/surfaces/mod.rs index b1e8fab5..606eb64b 100644 --- a/epiphany-core/src/surfaces/mod.rs +++ b/epiphany-core/src/surfaces/mod.rs @@ -1,6 +1,7 @@ mod coordinator; mod crrc; mod pressure; +mod role_board; pub use coordinator::EpiphanyCoordinatorAction; pub use coordinator::EpiphanyCoordinatorAutomationAction; @@ -30,6 +31,16 @@ pub use pressure::EpiphanyPressureBasis; pub use pressure::EpiphanyPressureLevel; pub use pressure::EpiphanyPressureStatus; pub use pressure::derive_pressure_view; +pub use role_board::EpiphanyRoleBoardCheckpointSummary; +pub use role_board::EpiphanyRoleBoardInput; +pub use role_board::EpiphanyRoleBoardJob; +pub use role_board::EpiphanyRoleBoardJobStatus; +pub use role_board::EpiphanyRoleBoardLane; +pub use role_board::EpiphanyRoleBoardPlanningSummary; +pub use role_board::derive_role_board; +pub use role_board::render_role_board_note; +pub use role_board::reorientation_role_status; +pub use role_board::role_board_job_status_to_role_status; #[derive(Debug, Clone, PartialEq, Eq)] pub struct EpiphanyViewInput { diff --git a/epiphany-core/src/surfaces/role_board.rs b/epiphany-core/src/surfaces/role_board.rs new file mode 100644 index 00000000..f74be548 --- /dev/null +++ b/epiphany-core/src/surfaces/role_board.rs @@ -0,0 +1,484 @@ +use super::EpiphanyCoordinatorRoleId; +use super::EpiphanyCoordinatorRoleStatus; +use super::EpiphanyCoordinatorSceneAction; +use super::EpiphanyCrrcAction; +use super::EpiphanyCrrcResultStatus; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EpiphanyRoleBoardJobStatus { + Idle, + Needed, + Pending, + Running, + Completed, + Failed, + Cancelled, + Blocked, + Unavailable, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EpiphanyRoleBoardJob { + pub id: String, + pub owner_role: String, + pub status: EpiphanyRoleBoardJobStatus, + pub progress_note: Option, + pub blocking_reason: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EpiphanyRoleBoardPlanningSummary { + pub capture_count: usize, + pub backlog_item_count: usize, + pub roadmap_stream_count: usize, + pub objective_draft_count: usize, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EpiphanyRoleBoardCheckpointSummary { + pub disposition: Option, + pub next_action: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EpiphanyRoleBoardInput { + pub state_present: bool, + pub planning: EpiphanyRoleBoardPlanningSummary, + pub checkpoint: Option, + pub reorient_next_action: String, + pub jobs: Vec, + pub crrc_action: EpiphanyCrrcAction, + pub crrc_recommended_scene_action: Option, + pub crrc_reason: String, + pub reorient_decision_action: String, + pub pressure_level: String, + pub reorient_result_status: EpiphanyCrrcResultStatus, + pub reorient_job: Option, + pub imagination_binding_id: String, + pub modeling_binding_id: String, + pub verification_binding_id: String, + pub reorient_owner_role: String, + pub imagination_owner_role: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EpiphanyRoleBoardLane { + pub id: EpiphanyCoordinatorRoleId, + pub title: String, + pub owner_role: String, + pub status: EpiphanyCoordinatorRoleStatus, + pub note: String, + pub jobs: Vec, + pub authority_scopes: Vec, + pub recommended_action: Option, +} + +pub fn derive_role_board(input: EpiphanyRoleBoardInput) -> Vec { + let state_present = input.state_present; + let checkpoint_present = input.checkpoint.is_some(); + let imagination_jobs = input + .jobs + .iter() + .filter(|job| job.id == input.imagination_binding_id) + .cloned() + .collect::>(); + let imagination_bound_job = imagination_jobs + .iter() + .find(|job| job.id == input.imagination_binding_id); + let imagination_has_bound_job = imagination_bound_job.is_some(); + let imagination_status = imagination_bound_job + .map(|job| role_board_job_status_to_role_status(job.status)) + .unwrap_or_else(|| imagination_role_status(state_present, &input.planning)); + let imagination_note = imagination_bound_job + .and_then(job_note) + .unwrap_or_else(|| render_imagination_role_note(state_present, &input.planning)); + let imagination_recommended_action = if imagination_has_bound_job { + Some(EpiphanyCoordinatorSceneAction::RoleResult) + } else if state_present { + Some(EpiphanyCoordinatorSceneAction::RoleLaunch) + } else { + Some(EpiphanyCoordinatorSceneAction::Update) + }; + + let modeling_jobs = input + .jobs + .iter() + .filter(|job| job.id == input.modeling_binding_id || job.id == "graph-remap") + .cloned() + .collect::>(); + let modeling_bound_job = modeling_jobs + .iter() + .find(|job| job.id == input.modeling_binding_id); + let modeling_has_bound_job = modeling_bound_job.is_some(); + let modeling_status = modeling_role_status( + checkpoint_present, + modeling_bound_job.or_else(|| modeling_jobs.first()), + ); + let modeling_recommended_action = if modeling_has_bound_job { + Some(EpiphanyCoordinatorSceneAction::RoleResult) + } else if state_present { + Some(EpiphanyCoordinatorSceneAction::RoleLaunch) + } else { + Some(EpiphanyCoordinatorSceneAction::Update) + }; + + let verification_jobs = input + .jobs + .iter() + .filter(|job| job.id == input.verification_binding_id || job.id == "verification") + .cloned() + .collect::>(); + let verification_bound_job = verification_jobs + .iter() + .find(|job| job.id == input.verification_binding_id); + let verification_has_bound_job = verification_bound_job.is_some(); + let verification_primary_job = verification_bound_job.or_else(|| verification_jobs.first()); + let verification_status = verification_primary_job + .map(|job| role_board_job_status_to_role_status(job.status)) + .unwrap_or(EpiphanyCoordinatorRoleStatus::Unavailable); + let verification_note = verification_primary_job + .and_then(job_note) + .unwrap_or_else(|| { + "Review evidence, worker findings, and verifier results before promotion.".to_string() + }); + let verification_recommended_action = if verification_has_bound_job { + Some(EpiphanyCoordinatorSceneAction::RoleResult) + } else if state_present { + Some(EpiphanyCoordinatorSceneAction::RoleLaunch) + } else { + None + }; + let reorientation_jobs = input.reorient_job.clone().into_iter().collect::>(); + + vec![ + EpiphanyRoleBoardLane { + id: EpiphanyCoordinatorRoleId::Implementation, + title: "Implementation".to_string(), + owner_role: "coding-agent".to_string(), + status: if input.crrc_action == EpiphanyCrrcAction::Continue { + EpiphanyCoordinatorRoleStatus::Ready + } else { + EpiphanyCoordinatorRoleStatus::Blocked + }, + note: if input.crrc_action == EpiphanyCrrcAction::Continue { + "Continue the bounded coding task.".to_string() + } else { + format!("Wait for CRRC action: {:?}.", input.crrc_action) + }, + jobs: input + .jobs + .iter() + .filter(|job| { + matches!( + job.owner_role.as_str(), + "coding-agent" | "implementation" | "epiphany-implementation" + ) + }) + .cloned() + .collect(), + authority_scopes: Vec::new(), + recommended_action: if input.crrc_action == EpiphanyCrrcAction::Continue { + None + } else { + input.crrc_recommended_scene_action + }, + }, + EpiphanyRoleBoardLane { + id: EpiphanyCoordinatorRoleId::Imagination, + title: "Imagination / Planning".to_string(), + owner_role: input.imagination_owner_role, + status: imagination_status, + note: imagination_note, + jobs: imagination_jobs, + authority_scopes: vec![ + "thread/epiphany/roleLaunch".to_string(), + "thread/epiphany/roleResult".to_string(), + "thread/epiphany/roleAccept".to_string(), + "thread/epiphany/update".to_string(), + ], + recommended_action: imagination_recommended_action, + }, + EpiphanyRoleBoardLane { + id: EpiphanyCoordinatorRoleId::Modeling, + title: "Modeling / Checkpoint".to_string(), + owner_role: "epiphany-modeler".to_string(), + status: modeling_status, + note: render_modeling_role_note(input.checkpoint.as_ref(), &input.reorient_next_action), + jobs: modeling_jobs, + authority_scopes: vec![ + "thread/epiphany/roleLaunch".to_string(), + "thread/epiphany/roleResult".to_string(), + "thread/epiphany/roleAccept".to_string(), + "thread/epiphany/update".to_string(), + ], + recommended_action: modeling_recommended_action, + }, + EpiphanyRoleBoardLane { + id: EpiphanyCoordinatorRoleId::Verification, + title: "Verification / Review".to_string(), + owner_role: "epiphany-verifier".to_string(), + status: verification_status, + note: verification_note, + jobs: verification_jobs, + authority_scopes: vec![ + "thread/epiphany/roleLaunch".to_string(), + "thread/epiphany/roleResult".to_string(), + "thread/epiphany/roleAccept".to_string(), + "thread/epiphany/distill".to_string(), + "thread/epiphany/propose".to_string(), + "thread/epiphany/promote".to_string(), + ], + recommended_action: verification_recommended_action, + }, + EpiphanyRoleBoardLane { + id: EpiphanyCoordinatorRoleId::Reorientation, + title: "Reorientation".to_string(), + owner_role: input.reorient_owner_role, + status: reorientation_role_status(input.crrc_action, input.reorient_result_status), + note: format!( + "{} verdict, result {:?}, pressure {}. {}", + input.reorient_decision_action, + input.reorient_result_status, + input.pressure_level, + input.crrc_reason + ), + jobs: reorientation_jobs, + authority_scopes: vec![ + "thread/epiphany/reorientLaunch".to_string(), + "thread/epiphany/reorientResult".to_string(), + "thread/epiphany/reorientAccept".to_string(), + ], + recommended_action: input.crrc_recommended_scene_action, + }, + ] +} + +pub fn role_board_job_status_to_role_status( + status: EpiphanyRoleBoardJobStatus, +) -> EpiphanyCoordinatorRoleStatus { + match status { + EpiphanyRoleBoardJobStatus::Idle => EpiphanyCoordinatorRoleStatus::Ready, + EpiphanyRoleBoardJobStatus::Needed => EpiphanyCoordinatorRoleStatus::Needed, + EpiphanyRoleBoardJobStatus::Pending | EpiphanyRoleBoardJobStatus::Running => { + EpiphanyCoordinatorRoleStatus::Running + } + EpiphanyRoleBoardJobStatus::Completed => EpiphanyCoordinatorRoleStatus::Completed, + EpiphanyRoleBoardJobStatus::Failed + | EpiphanyRoleBoardJobStatus::Cancelled + | EpiphanyRoleBoardJobStatus::Blocked => EpiphanyCoordinatorRoleStatus::Blocked, + EpiphanyRoleBoardJobStatus::Unavailable => EpiphanyCoordinatorRoleStatus::Unavailable, + } +} + +pub fn reorientation_role_status( + action: EpiphanyCrrcAction, + result_status: EpiphanyCrrcResultStatus, +) -> EpiphanyCoordinatorRoleStatus { + match result_status { + EpiphanyCrrcResultStatus::Pending | EpiphanyCrrcResultStatus::Running => { + return EpiphanyCoordinatorRoleStatus::Waiting; + } + EpiphanyCrrcResultStatus::Failed + | EpiphanyCrrcResultStatus::Cancelled + | EpiphanyCrrcResultStatus::BackendUnavailable + | EpiphanyCrrcResultStatus::BackendMissing => { + return EpiphanyCoordinatorRoleStatus::Blocked; + } + _ => {} + } + + match action { + EpiphanyCrrcAction::Continue => EpiphanyCoordinatorRoleStatus::Ready, + EpiphanyCrrcAction::PrepareCheckpoint + | EpiphanyCrrcAction::LaunchReorientWorker + | EpiphanyCrrcAction::RegatherManually => EpiphanyCoordinatorRoleStatus::Needed, + EpiphanyCrrcAction::WaitForReorientWorker => EpiphanyCoordinatorRoleStatus::Waiting, + EpiphanyCrrcAction::ReviewReorientResult | EpiphanyCrrcAction::AcceptReorientResult => { + EpiphanyCoordinatorRoleStatus::Review + } + } +} + +pub fn render_role_board_note( + roles: &[EpiphanyRoleBoardLane], + state_status: &str, + recommendation: EpiphanyCrrcAction, +) -> String { + let blocked_count = roles + .iter() + .filter(|role| { + matches!( + role.status, + EpiphanyCoordinatorRoleStatus::Blocked | EpiphanyCoordinatorRoleStatus::Needed + ) + }) + .count(); + format!( + "Role ownership is derived read-only from Epiphany state, jobs, and CRRC. State: {state_status}; recommendation: {recommendation:?}; blocked-or-needed lanes: {blocked_count}.", + ) +} + +fn imagination_role_status( + state_present: bool, + planning: &EpiphanyRoleBoardPlanningSummary, +) -> EpiphanyCoordinatorRoleStatus { + if !state_present { + return EpiphanyCoordinatorRoleStatus::Blocked; + } + if planning.capture_count == 0 + && planning.backlog_item_count == 0 + && planning.roadmap_stream_count == 0 + && planning.objective_draft_count == 0 + { + EpiphanyCoordinatorRoleStatus::Needed + } else { + EpiphanyCoordinatorRoleStatus::Ready + } +} + +fn render_imagination_role_note( + state_present: bool, + planning: &EpiphanyRoleBoardPlanningSummary, +) -> String { + if !state_present { + return "No authoritative Epiphany state exists for planning synthesis.".to_string(); + } + if planning.capture_count == 0 + && planning.backlog_item_count == 0 + && planning.roadmap_stream_count == 0 + && planning.objective_draft_count == 0 + { + return "Planning substrate is empty; capture or import backlog material before synthesis." + .to_string(); + } + format!( + "Planning material ready: {} captures, {} backlog items, {} roadmap streams, {} objective drafts.", + planning.capture_count, + planning.backlog_item_count, + planning.roadmap_stream_count, + planning.objective_draft_count + ) +} + +fn modeling_role_status( + checkpoint_present: bool, + graph_remap_job: Option<&EpiphanyRoleBoardJob>, +) -> EpiphanyCoordinatorRoleStatus { + if let Some(job) = graph_remap_job + && matches!( + job.status, + EpiphanyRoleBoardJobStatus::Pending | EpiphanyRoleBoardJobStatus::Running + ) + { + return EpiphanyCoordinatorRoleStatus::Running; + } + if checkpoint_present { + EpiphanyCoordinatorRoleStatus::Ready + } else { + EpiphanyCoordinatorRoleStatus::Needed + } +} + +fn render_modeling_role_note( + checkpoint: Option<&EpiphanyRoleBoardCheckpointSummary>, + reorient_next_action: &str, +) -> String { + let Some(checkpoint) = checkpoint else { + return format!("Checkpoint missing: {reorient_next_action}"); + }; + let next_action = checkpoint + .next_action + .as_deref() + .unwrap_or(reorient_next_action); + let disposition = checkpoint.disposition.as_deref().unwrap_or("unknown"); + format!("{disposition}: {next_action}") +} + +fn job_note(job: &EpiphanyRoleBoardJob) -> Option { + job.blocking_reason + .clone() + .or_else(|| job.progress_note.clone()) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn input() -> EpiphanyRoleBoardInput { + EpiphanyRoleBoardInput { + state_present: true, + planning: EpiphanyRoleBoardPlanningSummary { + capture_count: 0, + backlog_item_count: 0, + roadmap_stream_count: 0, + objective_draft_count: 0, + }, + checkpoint: Some(EpiphanyRoleBoardCheckpointSummary { + disposition: Some("resume_ready".to_string()), + next_action: Some("keep cutting".to_string()), + }), + reorient_next_action: "regather".to_string(), + jobs: Vec::new(), + crrc_action: EpiphanyCrrcAction::Continue, + crrc_recommended_scene_action: Some(EpiphanyCoordinatorSceneAction::Reorient), + crrc_reason: "continue".to_string(), + reorient_decision_action: "Resume".to_string(), + pressure_level: "Low".to_string(), + reorient_result_status: EpiphanyCrrcResultStatus::MissingBinding, + reorient_job: None, + imagination_binding_id: "imagination-synthesis-worker".to_string(), + modeling_binding_id: "modeling-checkpoint-worker".to_string(), + verification_binding_id: "verification-review-worker".to_string(), + reorient_owner_role: "epiphany-reorienter".to_string(), + imagination_owner_role: "epiphany-imagination".to_string(), + } + } + + #[test] + fn projects_mvp_lanes() { + let roles = derive_role_board(EpiphanyRoleBoardInput { + jobs: vec![EpiphanyRoleBoardJob { + id: "verification".to_string(), + owner_role: "epiphany-verifier".to_string(), + status: EpiphanyRoleBoardJobStatus::Needed, + progress_note: None, + blocking_reason: None, + }], + ..input() + }); + + assert_eq!(roles.len(), 5); + assert_eq!(roles[0].id, EpiphanyCoordinatorRoleId::Implementation); + assert_eq!(roles[0].status, EpiphanyCoordinatorRoleStatus::Ready); + assert_eq!(roles[1].status, EpiphanyCoordinatorRoleStatus::Needed); + assert_eq!(roles[2].status, EpiphanyCoordinatorRoleStatus::Ready); + assert_eq!(roles[3].status, EpiphanyCoordinatorRoleStatus::Needed); + assert_eq!(roles[4].status, EpiphanyCoordinatorRoleStatus::Ready); + } + + #[test] + fn blocks_implementation_when_crrc_blocks() { + let roles = derive_role_board(EpiphanyRoleBoardInput { + crrc_action: EpiphanyCrrcAction::LaunchReorientWorker, + crrc_recommended_scene_action: Some(EpiphanyCoordinatorSceneAction::ReorientLaunch), + ..input() + }); + + assert_eq!(roles[0].status, EpiphanyCoordinatorRoleStatus::Blocked); + assert_eq!( + roles[0].recommended_action, + Some(EpiphanyCoordinatorSceneAction::ReorientLaunch) + ); + } + + #[test] + fn reorientation_waits_for_running_worker() { + assert_eq!( + reorientation_role_status( + EpiphanyCrrcAction::Continue, + EpiphanyCrrcResultStatus::Running + ), + EpiphanyCoordinatorRoleStatus::Waiting + ); + } +} diff --git a/notes/epiphany-current-algorithmic-map.md b/notes/epiphany-current-algorithmic-map.md index f95d113a..501ab0e3 100644 --- a/notes/epiphany-current-algorithmic-map.md +++ b/notes/epiphany-current-algorithmic-map.md @@ -35,7 +35,7 @@ The spine exists in more than twenty-five live paths: - pre-compaction checkpoint intervention: app-server `TokenCount` handling now checks the same pressure mapper, and for loaded Epiphany threads crossing `shouldPrepareCompaction` it steers the active turn once with a bounded CRRC checkpoint directive to bank working context before compaction/reorientation. The per-turn dedupe bit and the pending compact handoff live in listener `ThreadState`; successful steering marks the active turn for a forced compact action at clean turn completion, while aborted turns clear the handoff. The intervention uses existing turn steering and turn-boundary automation rather than a new scheduler in [bespoke_event_handling.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/bespoke_event_handling.rs:1467), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:15405), and [thread_state.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/thread_state.rs:144). - reorientation policy: the `reorient` view lens reuses the same live/stored thread view plus mapped pressure/freshness/watcher signals, and derives a bounded `resume`/`regather` verdict with checkpoint-path/frontier reasons without mutating, compacting, scheduling, notifying, or continuing work in [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4514), [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4394), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:11424). - CRRC coordinator recommendation: the `crrc` view lens reuses pressure/freshness/watcher/reorientation mapping plus the fixed `reorient-worker` binding and result read-back helper, recognizes already accepted reorientation findings so an accepted result is not recommended for acceptance again, and calls pure `epiphany-core` CRRC policy to return one bounded recommendation such as continue, prepare checkpoint, launch worker, wait, review, accept, or regather manually without mutating, launching, accepting, compacting, notifying, scheduling, or silently continuing work in [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4661), [crrc.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/crrc.rs:60), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4546). -- role ownership projection: the `roles` view lens reuses the same state/jobs/pressure/reorient/result/CRRC signals as the coordinator loop, and projects implementation, Imagination/planning, modeling/checkpoint, verification/review, and reorientation lanes with owner roles, status, linked jobs, authority scopes, and recommended scene actions without launching, scheduling, accepting, compacting, notifying, or mutating state. +- role ownership projection: the `roles` view lens reuses the same state/jobs/pressure/reorient/result/CRRC signals as the coordinator loop, and projects implementation, Imagination/planning, modeling/checkpoint, verification/review, and reorientation lanes with owner roles, status, linked jobs, authority scopes, and recommended scene actions without launching, scheduling, accepting, compacting, notifying, or mutating state. App-server adapts concrete protocol jobs and thread state, while the pure role-board projection policy now lives in [role_board.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/role_board.rs:70). - role specialist launch/read-back/acceptance: app-server declares experimental `thread/epiphany/roleLaunch`, read-only `thread/epiphany/roleResult`, and write-gated `thread/epiphany/roleAccept`. `roleLaunch` accepts fixed Imagination/planning, modeling/checkpoint, and verification/review role ids, builds bounded specialist payloads from authoritative Epiphany state, routes them through the heartbeat-backed `epiphany_launch_job` seam, opens a typed runtime-spine job receipt, and emits the normal `jobLaunch` state update for `jobBindings` only. `roleResult` resolves heartbeat bindings through typed runtime-spine job results and seals legacy `agent_jobs` bindings, projecting structured output as a reviewable finding with verdict, summary, next safe move, inspected files, frontier/evidence ids, optional `statePatch`, optional `selfPatch`, coordinator `selfPersistence` review, raw result, and errors without mutation, promotion, scheduling, or hidden continuation. `selfPatch` is a Ghostlight-shaped lane-memory request, not project truth: the coordinator accepts only role-matched, bounded, evidence-grounded memory/goal/value/private-note mutations and refuses objectives, graphs, checkpoints, scratch, planning, jobs, authority, code edits, or raw transcripts with reasons. `roleAccept` accepts completed heartbeat findings from typed runtime-spine results, accepts completed Imagination findings only when they contain a valid planning-only patch with draft Objective Draft review gates, accepts completed modeling/checkpoint findings only with a modeling-safe `statePatch`, applies accepted project patches through the existing Epiphany state validator, appends audit observation/evidence, and carries the same `selfPersistence` review. GUI/coordinator accept paths then apply accepted `selfPatch` requests to typed CultCache role dossiers in `state/agents.msgpack` through the native `epiphany-agent-memory-store` binary. Implementation stays owned by the main coding agent, verification output remains review-only, and reorientation stays on the CRRC-specific `reorientLaunch` / `reorientResult` / `reorientAccept` path. - shared prompt memory projection and role dossiers: the prompt config now carries `[shared].persistent_memory` in [epiphany_specialists.toml](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/prompts/epiphany_specialists.toml:2). App-server parses that block, prepends it through `epiphany_agent_prompt_with_memory`, and therefore projects the same Ghostlight-derived memory into fixed role specialist instructions, reorientation-worker instructions, coordinator notes, and CRRC checkpoint steering. The base `` doctrine reinforces the Perfect Machine rule through [prompt.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/prompt.rs:31) and [epiphany_doctrine.md](E:/Projects/EpiphanyAgent/epiphany-core/src/prompts/epiphany_doctrine.md:1). Individual Ghostlight-shaped role dossiers now live in typed CultCache `state/agents.msgpack`, and the native `epiphany-agent-memory-store` binary validates, reviews, and applies bounded self-memory patches through the Rust store. - fixed-lane coordinator: the `coordinator` view lens composes role, pressure, reorient, CRRC, role result, and reorient result signals, and returns one current action without mutation. App-server assembles host/protocol inputs, but the pure fixed-lane decision ladder now lives in `epiphany-core` in [coordinator.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/coordinator.rs:132). Review acceptance and implementation clearance are intentionally different: an accepted verification finding clears implementation only when the verifier verdict is `pass`; accepted `needs-evidence`, `needs-review`, or `fail` findings route back toward modeling/checkpoint strengthening before code work resumes. `regatherManually` is now fallback, not a supervisor-puppeteering excuse: completed unaccepted modeling findings stop at `reviewModelingResult`, modeling acceptance precedes verification, and fixed lane actions are considered before manual regather. @@ -55,7 +55,7 @@ The thick Epiphany-owned implementation is mostly outside vendored Codex now: - deterministic observation distillation lives in [epiphany-core/src/distillation.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/distillation.rs:28). - deterministic map/churn proposal from verified, evidence-backed observations lives in [epiphany-core/src/proposal.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/proposal.rs:115). - verifier-backed promotion policy lives in [epiphany-core/src/promotion.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/promotion.rs:33). -- pressure, pure CRRC recommendation policy, and fixed-lane coordinator decision policy live in [pressure.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/pressure.rs:15), [crrc.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/crrc.rs:60), and [coordinator.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/coordinator.rs:132). +- pressure, pure CRRC recommendation policy, fixed-lane coordinator decision policy, and role-board projection policy live in [pressure.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/pressure.rs:15), [crrc.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/crrc.rs:60), [coordinator.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/coordinator.rs:132), and [role_board.rs](E:/Projects/EpiphanyAgent/epiphany-core/src/surfaces/role_board.rs:70). ## Whole Control Flow diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 4353aa9c..db7a464c 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -32,7 +32,7 @@ remembers doctrine; the branch remembers the blade. ## Current Orientation - Do not copy exact branch or HEAD from this note. Run `git status --short --branch` and `git log --oneline -5`. -- Top priority is still foundation cleanup, not outward feature work. Read `notes/epiphany-architectural-teardown.md` before touching Epiphany control-plane code. The May 2026 suspicion pass found that the app-server Epiphany control plane had become architectural Jenga: too much policy, projection, lifecycle glue, prompt packet assembly, coordinator sequencing, and test authority lives in `vendor/codex/codex-rs/app-server/src/codex_message_processor.rs`. The rebuild has begun: pressure policy, pure CRRC recommendation policy, and fixed-lane coordinator decision policy moved into `epiphany-core`, typed acceptance receipts replaced summary-string identity for live accept paths, runtime links are dual-written on launch, and result read-back now prefers runtime links. +- Top priority is still foundation cleanup, not outward feature work. Read `notes/epiphany-architectural-teardown.md` before touching Epiphany control-plane code. The May 2026 suspicion pass found that the app-server Epiphany control plane had become architectural Jenga: too much policy, projection, lifecycle glue, prompt packet assembly, coordinator sequencing, and test authority lives in `vendor/codex/codex-rs/app-server/src/codex_message_processor.rs`. The rebuild has begun: pressure policy, pure CRRC recommendation policy, fixed-lane coordinator decision policy, and role-board projection policy moved into `epiphany-core`, typed acceptance receipts replaced summary-string identity for live accept paths, runtime links are dual-written on launch, and result read-back now prefers runtime links. - Read `notes/epiphany-ideal-architecture-rebuild-plan.md` immediately after the teardown. It defines the smallest coherent replacement architecture: durable `EpiphanyState`, runtime-spine-owned `RuntimeState`, separate role memory/heartbeat state, `epiphany-core` as policy owner, app-server as adapter, Aquarium as reflector, typed acceptance receipts, and derived view lenses instead of a protocol verb zoo. - Do not continue Aquarium UI, bridge, Face, or dogfood expansion until the teardown has a source-grounded cleanup slice plan. Epiphany is the foundation; patches on patches are not a purification rite, they are how the altar becomes load-bearing garbage. - Phase 1 through Phase 5 are complete enough. @@ -367,14 +367,15 @@ The next real move is still foundation cleanup. Start from `notes/epiphany-ideal-architecture-rebuild-plan.md`, not from the older outward Phase 6 bridge/UI trail. Landed rebuild cuts: `epiphany-core/src/surfaces/` exists, pressure view derivation/tests, pure CRRC recommendation policy/tests, -and fixed-lane coordinator decision policy/tests live in core, live acceptance writes typed receipts keyed by runtime result id, +fixed-lane coordinator decision policy/tests, and role-board projection +policy/tests live in core, live acceptance writes typed receipts keyed by runtime result id, summary-string acceptance identity is gone for live paths, launches dual-write runtime links, result read-back prefers those runtime links, normal public `raw_result` is sealed, MVP consumers use `thread/epiphany/view`, heartbeat cognition is quarantined, and the old scene/jobs/roles/planning/pressure/reorient/ -CRRC/coordinator read wrappers are deleted. The next cut is role-board -projection policy or role-result interpretation extraction into `epiphany-core`. -No Agile confetti. No new balcony on the swaying tower. +CRRC/coordinator read wrappers are deleted. The next cut is role-result +interpretation or acceptance policy extraction into `epiphany-core`. No Agile +confetti. No new balcony on the swaying tower. The Phase 6 freshness slice is landed. It exposes read-only `thread/epiphany/freshness` from live retrieval summaries plus graph diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index 96ea08e96127f754b09c80547af48aae47634a9d..beb2c7e23d84488c71c7a8913ba16cb73f6dceff 100644 GIT binary patch delta 206 zcmX@|mifhd<_U^S-#2Vjp2o, ) -> Vec { - let state_present = state.is_some(); + let planning = state.map(|state| &state.planning); let checkpoint = state.and_then(|state| state.investigation_checkpoint.as_ref()); - let imagination_jobs = jobs - .iter() - .filter(|job| job.id == EPIPHANY_IMAGINATION_ROLE_BINDING_ID) - .cloned() - .collect::>(); - let imagination_bound_job = imagination_jobs - .iter() - .find(|job| job.id == EPIPHANY_IMAGINATION_ROLE_BINDING_ID); - let imagination_has_bound_job = imagination_bound_job.is_some(); - let imagination_status = imagination_bound_job - .as_ref() - .map(|job| map_epiphany_job_status_to_role_status(job.status)) - .unwrap_or_else(|| map_epiphany_imagination_role_status(state)); - let imagination_note = imagination_bound_job - .as_ref() - .and_then(|job| { - job.blocking_reason - .clone() - .or_else(|| job.progress_note.clone()) - }) - .unwrap_or_else(|| render_epiphany_imagination_role_note(state)); - let imagination_recommended_action = if imagination_has_bound_job { - Some(ThreadEpiphanySceneAction::RoleResult) - } else if state_present { - Some(ThreadEpiphanySceneAction::RoleLaunch) - } else { - Some(ThreadEpiphanySceneAction::Update) - }; - let modeling_jobs = jobs - .iter() - .filter(|job| { - matches!( - job.id.as_str(), - EPIPHANY_MODELING_ROLE_BINDING_ID | "graph-remap" - ) - }) - .cloned() - .collect::>(); - let modeling_bound_job = modeling_jobs - .iter() - .find(|job| job.id == EPIPHANY_MODELING_ROLE_BINDING_ID); - let modeling_has_bound_job = modeling_bound_job.is_some(); - let modeling_status = map_epiphany_modeling_role_status( - checkpoint.is_some(), - modeling_bound_job.or_else(|| modeling_jobs.first()), - ); - let modeling_recommended_action = if modeling_has_bound_job { - Some(ThreadEpiphanySceneAction::RoleResult) - } else if state_present { - Some(ThreadEpiphanySceneAction::RoleLaunch) - } else { - Some(ThreadEpiphanySceneAction::Update) - }; - let verification_jobs = jobs - .iter() - .filter(|job| { - matches!( - job.id.as_str(), - EPIPHANY_VERIFICATION_ROLE_BINDING_ID | "verification" - ) - }) - .cloned() - .collect::>(); - let verification_bound_job = verification_jobs - .iter() - .find(|job| job.id == EPIPHANY_VERIFICATION_ROLE_BINDING_ID); - let verification_has_bound_job = verification_bound_job.is_some(); - let verification_primary_job = verification_bound_job.or_else(|| verification_jobs.first()); - let verification_status = verification_primary_job - .as_ref() - .map(|job| map_epiphany_job_status_to_role_status(job.status)) - .unwrap_or(ThreadEpiphanyRoleStatus::Unavailable); - let verification_note = verification_primary_job - .as_ref() - .and_then(|job| { - job.blocking_reason - .clone() - .or_else(|| job.progress_note.clone()) - }) - .unwrap_or_else(|| { - "Review evidence, worker findings, and verifier results before promotion.".to_string() - }); - let verification_recommended_action = if verification_has_bound_job { - Some(ThreadEpiphanySceneAction::RoleResult) - } else if state_present { - Some(ThreadEpiphanySceneAction::RoleLaunch) - } else { - None - }; - let reorientation_jobs = reorient_job.cloned().into_iter().collect::>(); - - vec![ - ThreadEpiphanyRoleLane { - id: ThreadEpiphanyRoleId::Implementation, - title: "Implementation".to_string(), - owner_role: "coding-agent".to_string(), - status: if recommendation.action == ThreadEpiphanyCrrcAction::Continue { - ThreadEpiphanyRoleStatus::Ready - } else { - ThreadEpiphanyRoleStatus::Blocked - }, - note: if recommendation.action == ThreadEpiphanyCrrcAction::Continue { - "Continue the bounded coding task.".to_string() - } else { - format!("Wait for CRRC action: {:?}.", recommendation.action) - }, - jobs: jobs - .iter() - .filter(|job| { - matches!( - job.owner_role.as_str(), - "coding-agent" | "implementation" | "epiphany-implementation" - ) - }) - .cloned() - .collect(), - authority_scopes: Vec::new(), - recommended_action: if recommendation.action == ThreadEpiphanyCrrcAction::Continue { - None - } else { - recommendation.recommended_scene_action - }, - }, - ThreadEpiphanyRoleLane { - id: ThreadEpiphanyRoleId::Imagination, - title: "Imagination / Planning".to_string(), - owner_role: EPIPHANY_IMAGINATION_OWNER_ROLE.to_string(), - status: imagination_status, - note: imagination_note, - jobs: imagination_jobs, - authority_scopes: vec![ - "thread/epiphany/roleLaunch".to_string(), - "thread/epiphany/roleResult".to_string(), - "thread/epiphany/roleAccept".to_string(), - "thread/epiphany/update".to_string(), - ], - recommended_action: imagination_recommended_action, - }, - ThreadEpiphanyRoleLane { - id: ThreadEpiphanyRoleId::Modeling, - title: "Modeling / Checkpoint".to_string(), - owner_role: "epiphany-modeler".to_string(), - status: modeling_status, - note: render_epiphany_modeling_role_note(checkpoint, decision), - jobs: modeling_jobs, - authority_scopes: vec![ - "thread/epiphany/roleLaunch".to_string(), - "thread/epiphany/roleResult".to_string(), - "thread/epiphany/roleAccept".to_string(), - "thread/epiphany/update".to_string(), - ], - recommended_action: modeling_recommended_action, - }, - ThreadEpiphanyRoleLane { - id: ThreadEpiphanyRoleId::Verification, - title: "Verification / Review".to_string(), - owner_role: "epiphany-verifier".to_string(), - status: verification_status, - note: verification_note, - jobs: verification_jobs, - authority_scopes: vec![ - "thread/epiphany/roleLaunch".to_string(), - "thread/epiphany/roleResult".to_string(), - "thread/epiphany/roleAccept".to_string(), - "thread/epiphany/distill".to_string(), - "thread/epiphany/propose".to_string(), - "thread/epiphany/promote".to_string(), - ], - recommended_action: verification_recommended_action, - }, - ThreadEpiphanyRoleLane { - id: ThreadEpiphanyRoleId::Reorientation, - title: "Reorientation".to_string(), - owner_role: EPIPHANY_REORIENT_OWNER_ROLE.to_string(), - status: map_epiphany_reorientation_role_status(recommendation.action, result_status), - note: format!( - "{:?} verdict, result {:?}, pressure {:?}. {}", - decision.action, result_status, pressure.level, recommendation.reason - ), - jobs: reorientation_jobs, - authority_scopes: vec![ - "thread/epiphany/reorientLaunch".to_string(), - "thread/epiphany/reorientResult".to_string(), - "thread/epiphany/reorientAccept".to_string(), - ], - recommended_action: recommendation.recommended_scene_action, - }, - ] -} - -fn map_epiphany_imagination_role_status( - state: Option<&EpiphanyThreadState>, -) -> ThreadEpiphanyRoleStatus { - let Some(state) = state else { - return ThreadEpiphanyRoleStatus::Blocked; - }; - if state.planning.is_empty() { - ThreadEpiphanyRoleStatus::Needed - } else { - ThreadEpiphanyRoleStatus::Ready - } -} - -fn render_epiphany_imagination_role_note(state: Option<&EpiphanyThreadState>) -> String { - let Some(state) = state else { - return "No authoritative Epiphany state exists for planning synthesis.".to_string(); - }; - let planning = &state.planning; - if planning.is_empty() { - return "Planning substrate is empty; capture or import backlog material before synthesis." - .to_string(); - } - format!( - "Planning material ready: {} captures, {} backlog items, {} roadmap streams, {} objective drafts.", - planning.captures.len(), - planning.backlog_items.len(), - planning.roadmap_streams.len(), - planning.objective_drafts.len() - ) -} - -fn map_epiphany_modeling_role_status( - checkpoint_present: bool, - graph_remap_job: Option<&ThreadEpiphanyJob>, -) -> ThreadEpiphanyRoleStatus { - if let Some(job) = graph_remap_job - && matches!( - job.status, - ThreadEpiphanyJobStatus::Pending | ThreadEpiphanyJobStatus::Running - ) + let mut source_jobs = jobs.to_vec(); + if let Some(job) = reorient_job + && !source_jobs + .iter() + .any(|source_job| source_job.id == job.id && source_job.owner_role == job.owner_role) { - return ThreadEpiphanyRoleStatus::Running; + source_jobs.push(job.clone()); } - if checkpoint_present { - ThreadEpiphanyRoleStatus::Ready - } else { - ThreadEpiphanyRoleStatus::Needed + derive_role_board(EpiphanyRoleBoardInput { + state_present: state.is_some(), + planning: EpiphanyRoleBoardPlanningSummary { + capture_count: planning + .map(|planning| planning.captures.len()) + .unwrap_or(0), + backlog_item_count: planning + .map(|planning| planning.backlog_items.len()) + .unwrap_or(0), + roadmap_stream_count: planning + .map(|planning| planning.roadmap_streams.len()) + .unwrap_or(0), + objective_draft_count: planning + .map(|planning| planning.objective_drafts.len()) + .unwrap_or(0), + }, + checkpoint: checkpoint.map(|checkpoint| EpiphanyRoleBoardCheckpointSummary { + disposition: Some(format!("{:?}", checkpoint.disposition)), + next_action: checkpoint.next_action.clone(), + }), + reorient_next_action: decision.next_action.clone(), + jobs: source_jobs.iter().map(map_core_role_board_job).collect(), + crrc_action: map_core_crrc_action_from_protocol(recommendation.action), + crrc_recommended_scene_action: recommendation + .recommended_scene_action + .map(map_core_coordinator_scene_action_from_protocol), + crrc_reason: recommendation.reason.clone(), + reorient_decision_action: format!("{:?}", decision.action), + pressure_level: format!("{:?}", pressure.level), + reorient_result_status: map_core_crrc_result_status(result_status), + reorient_job: reorient_job.map(map_core_role_board_job), + imagination_binding_id: EPIPHANY_IMAGINATION_ROLE_BINDING_ID.to_string(), + modeling_binding_id: EPIPHANY_MODELING_ROLE_BINDING_ID.to_string(), + 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() +} + +fn map_core_role_board_job(job: &ThreadEpiphanyJob) -> EpiphanyRoleBoardJob { + EpiphanyRoleBoardJob { + id: job.id.clone(), + owner_role: job.owner_role.clone(), + status: map_core_role_board_job_status(job.status), + progress_note: job.progress_note.clone(), + blocking_reason: job.blocking_reason.clone(), } } -fn render_epiphany_modeling_role_note( - checkpoint: Option<&EpiphanyInvestigationCheckpoint>, - decision: &ThreadEpiphanyReorientDecision, -) -> String { - let Some(checkpoint) = checkpoint else { - return format!("Checkpoint missing: {}", decision.next_action); - }; - let next_action = checkpoint - .next_action - .as_deref() - .unwrap_or(decision.next_action.as_str()); - format!("{:?}: {next_action}", checkpoint.disposition) +fn map_core_role_board_job_status(status: ThreadEpiphanyJobStatus) -> 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, + } } -fn map_epiphany_job_status_to_role_status( - status: ThreadEpiphanyJobStatus, +fn map_protocol_role_board_lane( + lane: EpiphanyRoleBoardLane, + source_jobs: &[ThreadEpiphanyJob], +) -> ThreadEpiphanyRoleLane { + ThreadEpiphanyRoleLane { + id: map_protocol_coordinator_role_id(lane.id), + title: lane.title, + owner_role: lane.owner_role, + status: map_protocol_coordinator_role_status(lane.status), + note: lane.note, + jobs: lane + .jobs + .iter() + .map(|job| map_protocol_role_board_job(job, source_jobs)) + .collect(), + authority_scopes: lane.authority_scopes, + recommended_action: lane + .recommended_action + .map(map_protocol_coordinator_scene_action), + } +} + +fn map_protocol_role_board_job( + job: &EpiphanyRoleBoardJob, + source_jobs: &[ThreadEpiphanyJob], +) -> 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(), + }) +} + +fn map_protocol_role_board_job_status( + status: EpiphanyRoleBoardJobStatus, +) -> ThreadEpiphanyJobStatus { + 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, + } +} + +fn map_protocol_coordinator_role_status( + status: CoreEpiphanyCoordinatorRoleStatus, ) -> ThreadEpiphanyRoleStatus { match status { - ThreadEpiphanyJobStatus::Idle => ThreadEpiphanyRoleStatus::Ready, - ThreadEpiphanyJobStatus::Needed => ThreadEpiphanyRoleStatus::Needed, - ThreadEpiphanyJobStatus::Pending | ThreadEpiphanyJobStatus::Running => { - ThreadEpiphanyRoleStatus::Running - } - ThreadEpiphanyJobStatus::Completed => ThreadEpiphanyRoleStatus::Completed, - ThreadEpiphanyJobStatus::Failed - | ThreadEpiphanyJobStatus::Cancelled - | ThreadEpiphanyJobStatus::Blocked => ThreadEpiphanyRoleStatus::Blocked, - ThreadEpiphanyJobStatus::Unavailable => ThreadEpiphanyRoleStatus::Unavailable, - } -} - -fn map_epiphany_reorientation_role_status( - action: ThreadEpiphanyCrrcAction, - result_status: ThreadEpiphanyReorientResultStatus, -) -> ThreadEpiphanyRoleStatus { - match result_status { - ThreadEpiphanyReorientResultStatus::Pending - | ThreadEpiphanyReorientResultStatus::Running => { - return ThreadEpiphanyRoleStatus::Waiting; - } - ThreadEpiphanyReorientResultStatus::Failed - | ThreadEpiphanyReorientResultStatus::Cancelled - | ThreadEpiphanyReorientResultStatus::BackendUnavailable - | ThreadEpiphanyReorientResultStatus::BackendMissing => { - return ThreadEpiphanyRoleStatus::Blocked; - } - _ => {} - } - - match action { - ThreadEpiphanyCrrcAction::Continue => ThreadEpiphanyRoleStatus::Ready, - ThreadEpiphanyCrrcAction::PrepareCheckpoint - | ThreadEpiphanyCrrcAction::LaunchReorientWorker - | ThreadEpiphanyCrrcAction::RegatherManually => ThreadEpiphanyRoleStatus::Needed, - ThreadEpiphanyCrrcAction::WaitForReorientWorker => ThreadEpiphanyRoleStatus::Waiting, - ThreadEpiphanyCrrcAction::ReviewReorientResult - | ThreadEpiphanyCrrcAction::AcceptReorientResult => ThreadEpiphanyRoleStatus::Review, + CoreEpiphanyCoordinatorRoleStatus::Ready => ThreadEpiphanyRoleStatus::Ready, + CoreEpiphanyCoordinatorRoleStatus::Needed => ThreadEpiphanyRoleStatus::Needed, + CoreEpiphanyCoordinatorRoleStatus::Running => ThreadEpiphanyRoleStatus::Running, + CoreEpiphanyCoordinatorRoleStatus::Waiting => ThreadEpiphanyRoleStatus::Waiting, + CoreEpiphanyCoordinatorRoleStatus::Review => ThreadEpiphanyRoleStatus::Review, + CoreEpiphanyCoordinatorRoleStatus::Blocked => ThreadEpiphanyRoleStatus::Blocked, + CoreEpiphanyCoordinatorRoleStatus::Unavailable => ThreadEpiphanyRoleStatus::Unavailable, + CoreEpiphanyCoordinatorRoleStatus::Completed => ThreadEpiphanyRoleStatus::Completed, } } @@ -15217,18 +15080,25 @@ fn render_epiphany_roles_note( state_status: ThreadEpiphanyReorientStateStatus, recommendation: ThreadEpiphanyCrrcAction, ) -> String { - let blocked_count = roles + let core_roles = roles .iter() - .filter(|role| { - matches!( - role.status, - ThreadEpiphanyRoleStatus::Blocked | ThreadEpiphanyRoleStatus::Needed - ) + .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), }) - .count(); - format!( - "Role ownership is derived read-only from Epiphany state, jobs, and CRRC. State: {:?}; recommendation: {:?}; blocked-or-needed lanes: {blocked_count}.", - state_status, recommendation + .collect::>(); + render_role_board_note( + &core_roles, + format!("{:?}", state_status).as_str(), + map_core_crrc_action_from_protocol(recommendation), ) }