Move Epiphany update routes into bridge service
This commit is contained in:
parent
410c5c8661
commit
f3ae9f4cb5
6 changed files with 138 additions and 53 deletions
|
|
@ -3,6 +3,7 @@ pub mod coordinator;
|
|||
pub mod jobs;
|
||||
pub mod launch;
|
||||
pub mod mutation;
|
||||
pub mod mutation_service;
|
||||
pub mod pressure;
|
||||
pub mod reorient;
|
||||
pub mod results;
|
||||
|
|
|
|||
84
epiphany-codex-bridge/src/mutation_service.rs
Normal file
84
epiphany-codex-bridge/src/mutation_service.rs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
use codex_app_server_protocol::ThreadEpiphanyStateUpdatedField;
|
||||
use codex_app_server_protocol::ThreadEpiphanyUpdatePatch;
|
||||
use codex_core::CodexThread;
|
||||
use codex_core::EpiphanyPromotionInput;
|
||||
use codex_core::evaluate_promotion;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::protocol::EpiphanyEvidenceRecord;
|
||||
use codex_protocol::protocol::EpiphanyThreadState;
|
||||
|
||||
use crate::mutation::epiphany_promote_changed_fields;
|
||||
use crate::mutation::epiphany_update_patch_changed_fields;
|
||||
use crate::mutation::state_update_from_thread_patch;
|
||||
use crate::mutation::thread_epiphany_patch_has_state_replacements;
|
||||
use crate::state::client_visible_live_thread_epiphany_state;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EpiphanyThreadUpdateApplied {
|
||||
pub revision: u64,
|
||||
pub changed_fields: Vec<ThreadEpiphanyStateUpdatedField>,
|
||||
pub epiphany_state: EpiphanyThreadState,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EpiphanyThreadPromoteApplied {
|
||||
Rejected { reasons: Vec<String> },
|
||||
Accepted(EpiphanyThreadUpdateApplied),
|
||||
}
|
||||
|
||||
pub async fn apply_thread_epiphany_update(
|
||||
thread: &CodexThread,
|
||||
expected_revision: Option<u64>,
|
||||
patch: ThreadEpiphanyUpdatePatch,
|
||||
) -> Result<EpiphanyThreadUpdateApplied, CodexErr> {
|
||||
let changed_fields = epiphany_update_patch_changed_fields(&patch);
|
||||
let update = state_update_from_thread_patch(expected_revision, patch);
|
||||
let epiphany_state = thread.epiphany_update_state(update).await?;
|
||||
let epiphany_state = client_visible_live_thread_epiphany_state(thread, epiphany_state).await;
|
||||
Ok(EpiphanyThreadUpdateApplied {
|
||||
revision: epiphany_state.revision,
|
||||
changed_fields,
|
||||
epiphany_state,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn apply_thread_epiphany_promote(
|
||||
thread: &CodexThread,
|
||||
expected_revision: Option<u64>,
|
||||
patch: ThreadEpiphanyUpdatePatch,
|
||||
verifier_evidence: EpiphanyEvidenceRecord,
|
||||
) -> Result<EpiphanyThreadPromoteApplied, CodexErr> {
|
||||
let decision = evaluate_promotion(EpiphanyPromotionInput {
|
||||
has_state_replacements: thread_epiphany_patch_has_state_replacements(&patch),
|
||||
active_subgoal_id: patch.active_subgoal_id.clone(),
|
||||
subgoals: patch.subgoals.clone(),
|
||||
invariants: patch.invariants.clone(),
|
||||
graphs: patch.graphs.clone(),
|
||||
graph_frontier: patch.graph_frontier.clone(),
|
||||
graph_checkpoint: patch.graph_checkpoint.clone(),
|
||||
investigation_checkpoint: patch.investigation_checkpoint.clone(),
|
||||
churn: patch.churn.clone(),
|
||||
observations: patch.observations.clone(),
|
||||
evidence: patch.evidence.clone(),
|
||||
verifier_evidence: verifier_evidence.clone(),
|
||||
});
|
||||
if !decision.accepted {
|
||||
return Ok(EpiphanyThreadPromoteApplied::Rejected {
|
||||
reasons: decision.reasons,
|
||||
});
|
||||
}
|
||||
|
||||
let changed_fields = epiphany_promote_changed_fields(&patch);
|
||||
let mut patch = patch;
|
||||
patch.evidence.push(verifier_evidence);
|
||||
let update = state_update_from_thread_patch(expected_revision, patch);
|
||||
let epiphany_state = thread.epiphany_update_state(update).await?;
|
||||
let epiphany_state = client_visible_live_thread_epiphany_state(thread, epiphany_state).await;
|
||||
Ok(EpiphanyThreadPromoteApplied::Accepted(
|
||||
EpiphanyThreadUpdateApplied {
|
||||
revision: epiphany_state.revision,
|
||||
changed_fields,
|
||||
epiphany_state,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
|
@ -615,6 +615,16 @@ persistence validation, and rollout/session writeback. The remaining impurity
|
|||
is route-facing orchestration in `codex_message_processor.rs` /
|
||||
`epiphany_mutation_routes.rs`; move that behind a native service boundary next.
|
||||
|
||||
The first route-facing mutation service cut has started. Update/promote
|
||||
mutation application now routes through
|
||||
`epiphany-codex-bridge::mutation_service::{apply_thread_epiphany_update,
|
||||
apply_thread_epiphany_promote}` so the vendored app-server handler no longer
|
||||
owns promotion evaluation, patch-to-update projection, changed-field derivation,
|
||||
or state application for those two write verbs. The handler still owns
|
||||
thread-id parsing, loaded-thread lookup, JSON-RPC response shaping, and
|
||||
notification emission; role/reorient accept plus launch/interrupt route
|
||||
orchestration remain to cut.
|
||||
|
||||
Also: MCP itself is allowed to be JSON. The target is not "replace MCP JSON";
|
||||
the target is an Epiphany-owned boundary that speaks typed Epiphany
|
||||
intent/result/receipt documents internally and normal MCP JSON-RPC externally.
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -279,5 +279,5 @@ rejected_paths:
|
|||
reason: making Epiphany cleaner inside Codex still preserves the wrong organ; Codex must be starved down to OpenAI subscription auth/model transport, while Epiphany data and subsystem communication move to typed CultCache/CultNet.
|
||||
|
||||
current_status:
|
||||
summary: The Codex starvation pass has five typing receipts, seventeen processor cuts, a real physical extraction in `epiphany-codex-bridge`, a source-grounded Codex auth spine inventory, the native `epiphany-openai-adapter` document boundary, a buildable Codex auth/model wrapper in `epiphany-openai-codex-spine`, the first native runtime caller in `epiphany-openai-runtime`, native heartbeat launch planning/job opening in `epiphany-core`, and native state-update contract/validation/application in `epiphany-core`. The bridge owns Codex-facing Epiphany adapters for scene, jobs, context/planning/graph query, retrieval, result projection, runtime-spine result adaptation, launch/prompt doctrine, mutation document mechanics, live-state projection, pressure/pre-compaction rendering, freshness/reorientation projection, CRRC/coordinator/role-board projection, and acceptance/evidence signal helpers. Vendored app-server now retains only `epiphany_invalidation.rs` as a root Epiphany module because watcher lifecycle is still app-server machinery; `codex_message_processor.rs` still contains route dispatch and mutation orchestration. The processor is about 10,339 lines, `epiphany_mutation_routes.rs` is about 1,071 lines, `epiphany_state_helpers.rs` is down to a 19-line rollout-state loader, and `codex_thread.rs` is about 1,425 lines after state-update law moved native. `epiphany-openai-adapter` defines CultCache `DatabaseEntry` request/event/receipt/auth-status documents with no Codex app-server or generic JSON dependency. `epiphany-openai-codex-spine` projects `AuthManager` / `CodexAuth` into typed `EpiphanyOpenAiAdapterStatus`, maps typed `EpiphanyOpenAiModelRequest` into Codex API `ResponsesApiRequest`, resolves auth/provider through keeper Codex crates, opens HTTP Responses streams through `codex-api`, and maps deltas/completion into typed stream events and receipts. `epiphany-openai-runtime` writes OpenAI status/request/stream-event/receipt records plus runtime sessions/jobs/results/events into `epiphany-core::runtime_spine_cache`, and its smoke proves the route without network. `epiphany-core::plan_runtime_spine_heartbeat_launch` validates launch contracts/conflicts and projects durable job bindings/runtime links; `epiphany-core::open_runtime_spine_heartbeat_job` owns the initialize-runtime/ensure-session/create-job sequence; `epiphany-core::EpiphanyStateUpdate`, `epiphany_state_update_validation_errors`, and `apply_epiphany_state_update` own state-update document shape and mutation law while `codex-core` calls them for compatibility. Vendored `CodexThread::epiphany_launch_job` is now mostly revision check, persistence validation, and rollout/session writeback for the compatibility route. `epiphany-runtime-spine` advertises OpenAI adapter status/model-request/stream-event/receipt CultNet schemas and mutation contracts, so the native caller now has a real storage route instead of only JSON-RPC. MCP itself is correctly JSON at its protocol edge; the target is typed Epiphany intent/result/receipt wrappers around MCP, not replacing MCP JSON. The current OpenAI spine still pulls `codex-api` and a large transport dependency stack, so its next cut is dependency starvation after live callers move.
|
||||
next_action: Continue Codex starvation by moving mutation route orchestration out of `codex_message_processor.rs` / `epiphany_mutation_routes.rs` behind a native service boundary, leaving Codex as persistence/JSON-RPC compatibility only. Then move live model-turn callers onto `epiphany-openai-runtime` or the same native CultNet contract and shrink the surviving Codex dependency surface to credential refresh, provider/model routing, and the smallest OpenAI Responses call required for Codex subscription compatibility.
|
||||
summary: The Codex starvation pass has five typing receipts, seventeen processor cuts, a real physical extraction in `epiphany-codex-bridge`, a source-grounded Codex auth spine inventory, the native `epiphany-openai-adapter` document boundary, a buildable Codex auth/model wrapper in `epiphany-openai-codex-spine`, the first native runtime caller in `epiphany-openai-runtime`, native heartbeat launch planning/job opening in `epiphany-core`, native state-update contract/validation/application in `epiphany-core`, and the first route-facing mutation service in `epiphany-codex-bridge`. The bridge owns Codex-facing Epiphany adapters for scene, jobs, context/planning/graph query, retrieval, result projection, runtime-spine result adaptation, launch/prompt doctrine, mutation document mechanics, mutation service update/promote application, live-state projection, pressure/pre-compaction rendering, freshness/reorientation projection, CRRC/coordinator/role-board projection, and acceptance/evidence signal helpers. Vendored app-server now retains only `epiphany_invalidation.rs` as a root Epiphany module because watcher lifecycle is still app-server machinery; `codex_message_processor.rs` still contains route dispatch and mutation orchestration. The processor is about 10,339 lines, `epiphany_mutation_routes.rs` is about 1,061 lines after the update/promote service cut, `epiphany_state_helpers.rs` is down to a 19-line rollout-state loader, and `codex_thread.rs` is about 1,425 lines after state-update law moved native. `epiphany-openai-adapter` defines CultCache `DatabaseEntry` request/event/receipt/auth-status documents with no Codex app-server or generic JSON dependency. `epiphany-openai-codex-spine` projects `AuthManager` / `CodexAuth` into typed `EpiphanyOpenAiAdapterStatus`, maps typed `EpiphanyOpenAiModelRequest` into Codex API `ResponsesApiRequest`, resolves auth/provider through keeper Codex crates, opens HTTP Responses streams through `codex-api`, and maps deltas/completion into typed stream events and receipts. `epiphany-openai-runtime` writes OpenAI status/request/stream-event/receipt records plus runtime sessions/jobs/results/events into `epiphany-core::runtime_spine_cache`, and its smoke proves the route without network. `epiphany-core::plan_runtime_spine_heartbeat_launch` validates launch contracts/conflicts and projects durable job bindings/runtime links; `epiphany-core::open_runtime_spine_heartbeat_job` owns the initialize-runtime/ensure-session/create-job sequence; `epiphany-core::EpiphanyStateUpdate`, `epiphany_state_update_validation_errors`, and `apply_epiphany_state_update` own state-update document shape and mutation law while `codex-core` calls them for compatibility. Vendored `CodexThread::epiphany_launch_job` is now mostly revision check, persistence validation, and rollout/session writeback for the compatibility route. `epiphany-runtime-spine` advertises OpenAI adapter status/model-request/stream-event/receipt CultNet schemas and mutation contracts, so the native caller now has a real storage route instead of only JSON-RPC. MCP itself is correctly JSON at its protocol edge; the target is typed Epiphany intent/result/receipt wrappers around MCP, not replacing MCP JSON. The current OpenAI spine still pulls `codex-api` and a large transport dependency stack, so its next cut is dependency starvation after live callers move.
|
||||
next_action: Continue Codex starvation by moving role/reorient accept plus launch/interrupt route orchestration out of `codex_message_processor.rs` / `epiphany_mutation_routes.rs` behind native service boundaries, leaving Codex as persistence/JSON-RPC compatibility only. Then move live model-turn callers onto `epiphany-openai-runtime` or the same native CultNet contract and shrink the surviving Codex dependency surface to credential refresh, provider/model routing, and the smallest OpenAI Responses call required for Codex subscription compatibility.
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@ use chrono::Utc;
|
|||
use codex_app_server_protocol::*;
|
||||
use codex_core::EpiphanyJobInterruptRequest;
|
||||
use codex_core::EpiphanyJobLaunchRequest;
|
||||
use codex_core::EpiphanyPromotionInput;
|
||||
use codex_core::evaluate_promotion;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::protocol::EpiphanyJobKind as CoreEpiphanyJobKind;
|
||||
|
|
@ -21,11 +19,11 @@ use epiphany_codex_bridge::launch::map_core_worker_launch_document;
|
|||
use epiphany_codex_bridge::mutation::build_reorient_acceptance_update;
|
||||
use epiphany_codex_bridge::mutation::build_role_acceptance_update;
|
||||
use epiphany_codex_bridge::mutation::epiphany_job_launch_changed_fields;
|
||||
use epiphany_codex_bridge::mutation::epiphany_promote_changed_fields;
|
||||
use epiphany_codex_bridge::mutation::epiphany_state_updated_notification;
|
||||
use epiphany_codex_bridge::mutation::epiphany_update_patch_changed_fields;
|
||||
use epiphany_codex_bridge::mutation::state_update_from_thread_patch;
|
||||
use epiphany_codex_bridge::mutation::thread_epiphany_patch_has_state_replacements;
|
||||
use epiphany_codex_bridge::mutation_service::EpiphanyThreadPromoteApplied;
|
||||
use epiphany_codex_bridge::mutation_service::apply_thread_epiphany_promote;
|
||||
use epiphany_codex_bridge::mutation_service::apply_thread_epiphany_update;
|
||||
use epiphany_codex_bridge::pressure::map_epiphany_pressure;
|
||||
use epiphany_codex_bridge::reorient::map_epiphany_freshness;
|
||||
use epiphany_codex_bridge::reorient::map_epiphany_reorient;
|
||||
|
|
@ -727,42 +725,15 @@ impl CodexMessageProcessor {
|
|||
}
|
||||
};
|
||||
|
||||
let decision = evaluate_promotion(EpiphanyPromotionInput {
|
||||
has_state_replacements: thread_epiphany_patch_has_state_replacements(&patch),
|
||||
active_subgoal_id: patch.active_subgoal_id.clone(),
|
||||
subgoals: patch.subgoals.clone(),
|
||||
invariants: patch.invariants.clone(),
|
||||
graphs: patch.graphs.clone(),
|
||||
graph_frontier: patch.graph_frontier.clone(),
|
||||
graph_checkpoint: patch.graph_checkpoint.clone(),
|
||||
investigation_checkpoint: patch.investigation_checkpoint.clone(),
|
||||
churn: patch.churn.clone(),
|
||||
observations: patch.observations.clone(),
|
||||
evidence: patch.evidence.clone(),
|
||||
verifier_evidence: verifier_evidence.clone(),
|
||||
});
|
||||
if !decision.accepted {
|
||||
self.outgoing
|
||||
.send_response(
|
||||
request_id,
|
||||
ThreadEpiphanyPromoteResponse {
|
||||
accepted: false,
|
||||
reasons: decision.reasons,
|
||||
revision: None,
|
||||
changed_fields: Vec::new(),
|
||||
epiphany_state: None,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
let changed_fields = epiphany_promote_changed_fields(&patch);
|
||||
let mut patch = patch;
|
||||
patch.evidence.push(verifier_evidence);
|
||||
let update = state_update_from_thread_patch(expected_revision, patch);
|
||||
let epiphany_state = match thread.epiphany_update_state(update).await {
|
||||
Ok(epiphany_state) => epiphany_state,
|
||||
let applied = match apply_thread_epiphany_promote(
|
||||
thread.as_ref(),
|
||||
expected_revision,
|
||||
patch,
|
||||
verifier_evidence,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(applied) => applied,
|
||||
Err(CodexErr::InvalidRequest(message)) => {
|
||||
self.send_invalid_request_error(request_id, message).await;
|
||||
return;
|
||||
|
|
@ -776,12 +747,31 @@ impl CodexMessageProcessor {
|
|||
return;
|
||||
}
|
||||
};
|
||||
let epiphany_state =
|
||||
client_visible_live_thread_epiphany_state(thread.as_ref(), epiphany_state).await;
|
||||
|
||||
let applied = match applied {
|
||||
EpiphanyThreadPromoteApplied::Accepted(applied) => applied,
|
||||
EpiphanyThreadPromoteApplied::Rejected { reasons } => {
|
||||
self.outgoing
|
||||
.send_response(
|
||||
request_id,
|
||||
ThreadEpiphanyPromoteResponse {
|
||||
accepted: false,
|
||||
reasons,
|
||||
revision: None,
|
||||
changed_fields: Vec::new(),
|
||||
epiphany_state: None,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
let changed_fields = applied.changed_fields;
|
||||
let epiphany_state = applied.epiphany_state;
|
||||
let response = ThreadEpiphanyPromoteResponse {
|
||||
accepted: true,
|
||||
reasons: Vec::new(),
|
||||
revision: Some(epiphany_state.revision),
|
||||
revision: Some(applied.revision),
|
||||
changed_fields: changed_fields.clone(),
|
||||
epiphany_state: Some(epiphany_state.clone()),
|
||||
};
|
||||
|
|
@ -832,10 +822,10 @@ impl CodexMessageProcessor {
|
|||
}
|
||||
};
|
||||
|
||||
let changed_fields = epiphany_update_patch_changed_fields(&patch);
|
||||
let update = state_update_from_thread_patch(expected_revision, patch);
|
||||
let epiphany_state = match thread.epiphany_update_state(update).await {
|
||||
Ok(epiphany_state) => epiphany_state,
|
||||
let applied = match apply_thread_epiphany_update(thread.as_ref(), expected_revision, patch)
|
||||
.await
|
||||
{
|
||||
Ok(applied) => applied,
|
||||
Err(CodexErr::InvalidRequest(message)) => {
|
||||
self.send_invalid_request_error(request_id, message).await;
|
||||
return;
|
||||
|
|
@ -849,10 +839,10 @@ impl CodexMessageProcessor {
|
|||
return;
|
||||
}
|
||||
};
|
||||
let epiphany_state =
|
||||
client_visible_live_thread_epiphany_state(thread.as_ref(), epiphany_state).await;
|
||||
let changed_fields = applied.changed_fields;
|
||||
let epiphany_state = applied.epiphany_state;
|
||||
let response = ThreadEpiphanyUpdateResponse {
|
||||
revision: epiphany_state.revision,
|
||||
revision: applied.revision,
|
||||
changed_fields: changed_fields.clone(),
|
||||
epiphany_state: epiphany_state.clone(),
|
||||
};
|
||||
|
|
|
|||
Reference in a new issue