Delete unconsumed Epiphany app-server routes

This commit is contained in:
Meta Vens 2026-07-12 06:40:12 +01:00
commit 5fd927481c
11 changed files with 116 additions and 1893 deletions

View file

@ -22,20 +22,20 @@ These are retained only while the named compatibility binaries remain. They do
not justify route-local policy, implicit triggers, Codex state ownership, or new
clients.
## Unconsumed advertised routes
## Removed unconsumed routes
| Route | Finding | Deletion owner |
| Route | Finding | Result |
|---|---|---|
| `roleLaunch` | no executable caller | remove request registration, dispatch, handler, tests/docs |
| `roleAccept` | no executable caller | remove request registration, dispatch, handler, tests/docs |
| `reorientLaunch` | no executable caller | remove request registration, dispatch, handler, tests/docs |
| `reorientAccept` | no executable caller | remove request registration, dispatch, handler, tests/docs |
| `index` | no executable caller | remove request registration, dispatch, handler, tests/docs |
| `distill` | no executable caller | remove request registration, dispatch, handler, tests/docs |
| `propose` | no executable caller | remove request registration, dispatch, handler, tests/docs |
| `promote` | no executable caller | remove request registration, dispatch, handler, tests/docs |
| `jobLaunch` | no executable caller | remove request registration, dispatch, handler, tests/docs |
| `retrieve` | no executable caller | remove request registration, dispatch, handler, tests/docs |
| `roleLaunch` | no executable caller | request surface deleted |
| `roleAccept` | no executable caller | request surface deleted |
| `reorientLaunch` | no executable caller | request surface deleted |
| `reorientAccept` | no executable caller | request surface deleted |
| `index` | no executable caller | request surface deleted |
| `distill` | no executable caller | request surface deleted |
| `propose` | no executable caller | request surface deleted |
| `promote` | no executable caller | request surface deleted |
| `jobLaunch` | no executable caller | request surface deleted |
| `retrieve` | no executable caller | request surface deleted |
Protocol DTOs may remain temporarily only when retained response projections or
bridge conversion tests still compile against them. A DTO is not authority and

View file

@ -1952,8 +1952,12 @@ compatibility API with no named living client. The client matrix now lives at
`notes/epiphany-app-server-client-matrix.md`: eight routes have named phase-6
smoke or `epiphany-mvp-status --codex` consumers; ten advertised routes have no
executable caller beyond definitions/tests/docs and are marked for deletion.
Cut those ten request registrations, dispatch arms, handlers, tests, and README
claims next; do not let generated bindings count as clients.
Those ten request registrations, dispatch arms, handlers, protocol tests, and
README claims are now deleted. A source guard fixes registration to the eight
named-client routes and explicitly rejects the removed names. Protocol compile,
app-server compile, and four compatibility-architecture tests pass. Next audit
zero-caller protocol DTOs and bridge conversion helpers left by this cut, then
migrate or retire the phase-6 smoke and `epiphany-mvp-status --codex` clients.
## Immediate Re-entry Instruction

View file

@ -365,9 +365,8 @@ routes, notifications, and read projection only. A source guard rejects renewed
automation authority in the event loop, processor module list, or thread state.
The remaining app-server surface is now mechanically classified and guarded.
Mutation routes must call one of nine explicit bridge delegates for role launch,
role acceptance, reorientation launch/acceptance, indexing, promotion, update,
job launch, or interruption; forbidden local receipt/binding/runtime writes,
Mutation routes may call only the explicit update or interruption bridge
delegate; forbidden local receipt/binding/runtime writes,
prompt steering, compaction submission, and automation selection fail the
source test. Read routes reject every mutation, launch, interruption, indexing,
prompt-steering, compaction, or scheduler symbol. Migration, read projection,
@ -382,5 +381,6 @@ in-repo consumers, all explicit phase-6 smokes or
unconsumed: role/reorientation launch and accept, index, distill, propose,
promote, job launch, and retrieve. Definitions, generated bindings, README
claims, and serialization tests do not count as clients. Those ten request
registrations, dispatch arms, handlers, tests, and docs are the next deletion
line.
registrations, dispatch arms, handlers, tests, and README claims are deleted.
The registration guard now fixes the compatibility surface at the eight named
client routes and rejects restoration of the unconsumed methods.

Binary file not shown.

View file

@ -659,4 +659,4 @@ current_status:
- Service lifecycle receipt history is now event-only: `load_epiphany_cultmesh_daemon_service_lifecycle_receipts` filters out the `epiphany-local/daemon-service-lifecycle-receipt/latest` mirror, while `load_latest_epiphany_cultmesh_daemon_service_lifecycle_receipt` remains the latest-state reader. Focused unit test `service_lifecycle_receipt_history_excludes_latest_mirror` proves two per-receipt writes return exactly two history records and latest points at the second. Fresh smoke `verse-query-lifecycle-history-filter-smoke` and live overview/triage `local-20260618-145632-497-efac6f0d` / `local-20260618-145632-321-867947af` prove compact swarm sights still expose service lifecycle attention plus the present elevated runbook artifact without daemon pokes or service mutation. Do not use a broad unscoped cargo test filter for this check on Windows; it fans out across bin harnesses and hit a broken pipe during this pass.
- `epiphany-verse-query seed-compact` is the compact seed receipt for wrappers and read-only rites. The older `seed` command still prints the full local Verse context for explicit debugging, but compact wrapper modes now avoid full `seed`/final `query` readback after the combined overview proof exposed Windows debug stack overflow from full-context serialization pressure.
last_completed_repo_swarm_cut: Huginn live-fire has crossed the first real repo Body from Persona pressure to branch-local Hands commit and Soul/Mind closure, and now has repeatable closure and redacted-public-proof mouths. On `E:\Projects\Huginn` branch `epiphany/huginn/first-awakening`, `tools/epiphany_local_run.ps1 -Mode repo-persona-intake` accepted item `huginn-inspection-readback-livefire`, Weksa/Persona intake remained sealed, and `epiphany-work derive-plan` lowered it to model-authored safe family `repo.status_section` for `README.md` only. `tools/epiphany_local_run.ps1 -Mode repo-swarm-run` advanced four bounded pulses: run-from-plan, adopt-from-plan, execute-from-plan, and close-from-execute. Huginn commit `e9b07c38fceabbdc89ea35c2156326c80c0393a2` updated only `README.md`; closure receipt `.epiphany\work\work-close-huginn-inspection-readback-livefire.json` records status `closed`, Soul verdict `passed`, Mind state commit `repo-work-close-huginn-inspection-readback-livefire-mind-commit`, safe family `repo.status_section`, path scope `README.md`, Bifrost publication gate, and `privateStateExposed=false`. Verification also ran Huginn's TypeScript compiler through concrete Node path: `C:\Program Files\nodejs\node.exe E:\Projects\Huginn\node_modules\typescript\bin\tsc -p tsconfig.json` passed. Native verifier `epiphany-repo-livefire-closure-smoke --workspace E:\Projects\Huginn --item huginn-inspection-readback-livefire --expected-path README.md --expected-family repo.status_section --expected-gate awaiting-publication --expected-blocker bifrost-publication-missing` checks closure receipt, Mind map entry, overview gate/blocker, authority seals, and `git show` changed paths, returning `status=ok`, `soulVerdict=passed`, `publicationGate=Bifrost`, and `privateStateExposed=false`. Wrapper `tools/epiphany_local_run.ps1 -Mode repo-livefire-closure -Workspace E:\Projects\Huginn -RepoWorkItem huginn-inspection-readback-livefire -SkipBuild` exposes the same read-only proof through the local operator mouth; artifact `local-20260621-190156-803-2f85ba15` printed commit, path, gate, blocker, Soul verdict, Mind commit receipt, and `privateStateExposed=False` without push, merge, publication, or target mutation. New wrapper `tools/epiphany_local_run.ps1 -Mode repo-work-public-proof -Workspace E:\Projects\Huginn -RepoWorkItem huginn-inspection-readback-livefire -SkipBuild` exports the redacted proof bundle and repo-local Verse index without publishing; artifact `local-20260621-190645-345-4555b1dc` wrote `.epiphany\public\proof-bundles\repo-work-public-proof-huginn-inspection-readback-livefire.json`, proof id `repo-work-public-proof-huginn-inspection-readback-livefire`, SHA-256 `857545b199f3e37eb06c2e757e5cfc201958e46667bb1f8403fd333a22620f63`, nine artifact rows, zero publication rows, and `privateStateExposed=False`. Gjallar readback `local-20260621-190701-140-b9519ec6` sees the `REPO-WORK-PUBLIC-PROOF` row and action row from Huginn's repo-local Verse with the same SHA-256 while Bifrost publication remains missing. Launcher compact modes are now named compact operator-context modes instead of read-only modes, and `bifrost-public-proof` summaries print item, target public Verse, room, proof SHA-256, review receipts, and credit receipts so publication receipts are auditable without claiming upstream sync.
next_action: Use `notes/epiphany-app-server-client-matrix.md` to delete the ten unconsumed advertised routes: roleLaunch, roleAccept, reorientLaunch, reorientAccept, index, distill, propose, promote, jobLaunch, and retrieve. Remove request registration, dispatch, handlers, route tests, and README claims; retain DTOs only where a living retained projection still needs them. Keep the eight named smoke/`--codex` fallback routes guarded as compatibility-only.
next_action: Audit protocol DTOs and bridge conversion helpers left behind by the ten deleted app-server request methods; delete zero-caller shapes without disturbing the eight guarded compatibility routes. Then migrate or retire the phase-6 smoke and `epiphany-mvp-status --codex` consumers so the remaining compatibility surface can contract again.

File diff suppressed because it is too large Load diff

View file

@ -136,16 +136,12 @@ Example with notification opt-out:
Hydrated thread responses such as `thread/start`, `thread/resume`, `thread/fork`, `thread/read`, and `thread/unarchive` may include `thread.epiphanyState` when the thread has persisted Epiphany state. When the thread is currently loaded, app-server prefers the live in-memory value; otherwise it reconstructs the latest surviving snapshot from rollout items using the same rollback/compaction semantics as core session replay. For live threads with Epiphany state but no persisted retrieval summary yet, app-server backfills a lightweight query-time retrieval summary instead of pretending the retrieval field is absent.
Experimental Epiphany retrieval:
Experimental Epiphany state access:
- `thread/epiphany/index` runs an explicit loaded-thread-only semantic indexing pass that writes a Qdrant-backed workspace index under Codex home metadata, using local Ollama embeddings by default. It is the write path for persistent semantic retrieval and keeps `thread/epiphany/retrieve` read-only.
- `thread/epiphany/retrieve` runs a loaded-thread-only hybrid repo query that combines `codex_file_search` path hits with BM25 chunk retrieval and returns typed result metadata plus an Epiphany retrieval summary.
- `thread/epiphany/distill` is a loaded-thread-only read surface that turns one explicit observation input into a deterministic observation/evidence patch plus the current `expectedRevision`. It does not mutate thread state; callers must pass the returned patch to `thread/epiphany/update` to promote it.
- `thread/epiphany/context` is a targeted read-only state-shard surface. It selects graph nodes/edges, active frontier, checkpoint, observations, and evidence from authoritative typed state without mutating or notifying.
- `thread/epiphany/graphQuery` is a read-only graph traversal surface over authoritative typed state. It can return explicit nodes, path/symbol matches, one-to-three-hop neighbors, and the current frontier neighborhood while preserving architecture/dataflow links; it does not mutate, index, schedule, or promote.
- `thread/epiphany/promote` is an explicit loaded-thread-only promotion gate. It evaluates a patch plus verifier evidence, rejects with reasons without mutating state when policy fails, and applies the patch through the durable update path when accepted. Accepted responses include the landed `revision` and `changedFields`.
- `thread/epiphany/update` is the explicit loaded-thread-only control-plane write path for Epiphany state. It can append observations/evidence and replace bounded typed state fields such as objective, subgoals, graph frontier/checkpoint, scratch, churn, and mode; it bumps the Epiphany revision, returns `revision` plus `changedFields`, and persists a rollout snapshot immediately.
- `thread/epiphany/stateUpdated` is an experimental server notification emitted after successful `thread/epiphany/update` and accepted `thread/epiphany/promote`. It carries the updated typed `epiphanyState`, a `source` of `update` or `promote`, event-level `revision`, and typed `changedFields`; rejected promotions do not emit it.
- `thread/epiphany/stateUpdated` is an experimental server notification emitted after successful `thread/epiphany/update`. It carries the updated typed `epiphanyState`, an `update` source, event-level `revision`, and typed `changedFields`.
- `thread/epiphany/jobsUpdated` is a sealed legacy notification shape. Epiphany no longer emits it from Codex batch-job progress; heartbeat/runtime-spine owns live activation telemetry, while the `jobs` lens on `thread/epiphany/view` remains a read-only projection over current typed state.
- `thread/epiphany/freshness` is a read-only reflection surface for retrieval and graph staleness. It reports revision/source identity, retrieval freshness, graph freshness, and exact dirty-path pressure, and for loaded threads now also reports watcher-backed invalidation inputs with watched root, changed paths, mapped graph-node hits, and active-frontier hits. It still does not mutate state, schedule refresh work, or perform automatic watcher-driven semantic invalidation.
- `thread/epiphany/reorient` is a read-only CRRC policy surface. It consumes the durable investigation checkpoint plus freshness, watcher, and pressure signals to return a bounded `resume` versus `regather` verdict with explicit checkpoint-path/frontier reasons. It does not mutate state, compact, notify, schedule, or silently continue work on the caller's behalf.

View file

@ -641,18 +641,10 @@ impl CodexMessageProcessor {
self.thread_epiphany_view(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ThreadEpiphanyRoleLaunch { request_id, params } => {
self.thread_epiphany_role_launch(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ThreadEpiphanyRoleResult { request_id, params } => {
self.thread_epiphany_role_result(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ThreadEpiphanyRoleAccept { request_id, params } => {
self.thread_epiphany_role_accept(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ThreadEpiphanyFreshness { request_id, params } => {
self.thread_epiphany_freshness(to_connection_request_id(request_id), params)
.await;
@ -665,38 +657,10 @@ impl CodexMessageProcessor {
self.thread_epiphany_graph_query(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ThreadEpiphanyReorientLaunch { request_id, params } => {
self.thread_epiphany_reorient_launch(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ThreadEpiphanyReorientResult { request_id, params } => {
self.thread_epiphany_reorient_result(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ThreadEpiphanyReorientAccept { request_id, params } => {
self.thread_epiphany_reorient_accept(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ThreadEpiphanyIndex { request_id, params } => {
self.thread_epiphany_index(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ThreadEpiphanyDistill { request_id, params } => {
self.thread_epiphany_distill(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ThreadEpiphanyPropose { request_id, params } => {
self.thread_epiphany_propose(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ThreadEpiphanyPromote { request_id, params } => {
self.thread_epiphany_promote(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ThreadEpiphanyJobLaunch { request_id, params } => {
self.thread_epiphany_job_launch(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ThreadEpiphanyJobInterrupt { request_id, params } => {
self.thread_epiphany_job_interrupt(to_connection_request_id(request_id), params)
.await;
@ -705,10 +669,6 @@ impl CodexMessageProcessor {
self.thread_epiphany_update(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ThreadEpiphanyRetrieve { request_id, params } => {
self.thread_epiphany_retrieve(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::ThreadTurnsList { request_id, params } => {
self.thread_turns_list(to_connection_request_id(request_id), params)
.await;

View file

@ -2,14 +2,7 @@
fn mutation_routes_are_explicit_compatibility_delegates() {
let source = include_str!("epiphany_mutation_routes.rs");
for delegate in [
"launch_thread_epiphany_role(",
"apply_thread_epiphany_role_accept(",
"launch_thread_epiphany_reorient(",
"apply_thread_epiphany_reorient_accept(",
"index_epiphany_retrieval_for_paths(",
"apply_thread_epiphany_promote(",
"apply_thread_epiphany_update(",
"launch_thread_epiphany_job(",
"interrupt_thread_epiphany_job(",
] {
assert!(source.contains(delegate), "missing compatibility delegate {delegate}");
@ -30,6 +23,41 @@ fn mutation_routes_are_explicit_compatibility_delegates() {
}
}
#[test]
fn only_named_client_routes_remain_registered() {
let protocol = include_str!("../../../app-server-protocol/src/protocol/common.rs");
let registrations = protocol
.split("ThreadTurnsList =>")
.next()
.expect("client request registration section");
for retained in [
"thread/epiphany/view",
"thread/epiphany/roleResult",
"thread/epiphany/freshness",
"thread/epiphany/context",
"thread/epiphany/graphQuery",
"thread/epiphany/reorientResult",
"thread/epiphany/jobInterrupt",
"thread/epiphany/update",
] {
assert!(registrations.contains(retained), "missing retained client route {retained}");
}
for removed in [
"thread/epiphany/roleLaunch",
"thread/epiphany/roleAccept",
"thread/epiphany/reorientLaunch",
"thread/epiphany/reorientAccept",
"thread/epiphany/index",
"thread/epiphany/distill",
"thread/epiphany/propose",
"thread/epiphany/promote",
"thread/epiphany/jobLaunch",
"thread/epiphany/retrieve",
] {
assert!(!registrations.contains(removed), "unconsumed route remains registered: {removed}");
}
}
#[test]
fn read_routes_have_no_mutation_or_scheduler_authority() {
let source = include_str!("epiphany_read_routes.rs");

View file

@ -1,45 +1,20 @@
use codex_app_server_protocol::*;
use codex_core::CodexThread;
use codex_protocol::ThreadId;
use epiphany_state_model::EpiphanyRetrievalState;
use epiphany_state_model::EpiphanyThreadState;
use epiphany_codex_bridge::cultnet::EpiphanyStateUpdatedField;
use epiphany_codex_bridge::error::EpiphanyBridgeError;
use epiphany_codex_bridge::invalidation::epiphany_freshness_watcher_snapshot;
use epiphany_codex_bridge::launch::EPIPHANY_REORIENT_LAUNCH_BINDING_ID;
use epiphany_codex_bridge::launch::build_epiphany_job_launch_request;
use epiphany_codex_bridge::launch::epiphany_role_binding_id;
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_reorient_accept;
use epiphany_codex_bridge::mutation_service::apply_thread_epiphany_role_accept;
use epiphany_codex_bridge::mutation_service::apply_thread_epiphany_update;
use epiphany_codex_bridge::mutation_service::interrupt_thread_epiphany_job;
use epiphany_codex_bridge::mutation_service::launch_thread_epiphany_job;
use epiphany_codex_bridge::mutation_service::launch_thread_epiphany_reorient;
use epiphany_codex_bridge::mutation_service::launch_thread_epiphany_role;
use epiphany_codex_bridge::protocol_edge::protocol_job_from_surface;
use epiphany_codex_bridge::protocol_edge::protocol_patch_from_core;
use epiphany_codex_bridge::protocol_edge::protocol_reorient_decision;
use epiphany_codex_bridge::protocol_edge::protocol_reorient_finding;
use epiphany_codex_bridge::protocol_edge::protocol_reorient_source;
use epiphany_codex_bridge::protocol_edge::protocol_reorient_state_status;
use epiphany_codex_bridge::protocol_edge::protocol_role_finding;
use epiphany_codex_bridge::protocol_edge::protocol_role_id_to_core;
use epiphany_codex_bridge::protocol_edge::protocol_state_updated_fields;
use epiphany_codex_bridge::protocol_edge::protocol_state_updated_notification;
use epiphany_codex_bridge::protocol_edge::protocol_update_patch_to_core;
use epiphany_codex_bridge::protocol_edge::protocol_worker_launch_document_to_core;
use epiphany_codex_bridge::retrieve::epiphany_retrieval_state_for_paths;
use epiphany_codex_bridge::retrieve::index_epiphany_retrieval_for_paths;
use epiphany_codex_bridge::retrieve_protocol::protocol_index_response;
use std::sync::Arc;
use super::CodexMessageProcessor;
use super::ConnectionRequestId;
use super::ThreadReadViewError;
use super::epiphany_thread_host::EpiphanyCodexThreadHost;
use super::epiphany_thread_host::epiphany_token_usage_snapshot;
impl CodexMessageProcessor {
async fn load_epiphany_thread(
@ -94,450 +69,6 @@ impl CodexMessageProcessor {
.await;
}
pub(super) async fn thread_epiphany_role_launch(
&self,
request_id: ConnectionRequestId,
params: ThreadEpiphanyRoleLaunchParams,
) {
let ThreadEpiphanyRoleLaunchParams {
thread_id,
role_id,
expected_revision,
max_runtime_seconds,
} = params;
let core_role_id = protocol_role_id_to_core(role_id);
let (thread_uuid, loaded_thread) =
match self.load_epiphany_thread(&request_id, &thread_id).await {
Some(thread) => thread,
None => return,
};
let host = EpiphanyCodexThreadHost::new(loaded_thread.as_ref());
let applied = match launch_thread_epiphany_role(
&host,
&thread_id,
core_role_id,
expected_revision,
max_runtime_seconds,
)
.await
{
Ok(applied) => applied,
Err(EpiphanyBridgeError::InvalidRequest(message)) => {
self.send_invalid_request_error(request_id, message).await;
return;
}
Err(err) => {
self.send_internal_error(
request_id,
format!("failed to launch Epiphany role specialist for {thread_uuid}: {err}"),
)
.await;
return;
}
};
let changed_fields = applied.changed_fields;
let protocol_changed_fields = protocol_state_updated_fields(changed_fields.clone());
let epiphany_state = applied.epiphany_state;
self.outgoing
.send_response(
request_id,
ThreadEpiphanyRoleLaunchResponse {
thread_id: thread_uuid.to_string(),
role_id,
revision: applied.revision,
changed_fields: protocol_changed_fields,
epiphany_state: epiphany_state.clone(),
job: protocol_job_from_surface(
applied.job,
Some(applied.launcher_job_id),
Some(applied.backend_job_id),
),
},
)
.await;
self.send_epiphany_state_updated(
thread_uuid,
ThreadEpiphanyStateUpdatedSource::JobLaunch,
changed_fields,
epiphany_state,
)
.await;
}
pub(super) async fn thread_epiphany_role_accept(
&self,
request_id: ConnectionRequestId,
params: ThreadEpiphanyRoleAcceptParams,
) {
let ThreadEpiphanyRoleAcceptParams {
thread_id,
role_id,
expected_revision,
binding_id,
} = params;
let core_role_id = protocol_role_id_to_core(role_id);
let default_binding_id = match epiphany_role_binding_id(core_role_id) {
Ok(binding_id) => binding_id,
Err(message) => {
self.send_invalid_request_error(request_id, message).await;
return;
}
};
let binding_id = binding_id.unwrap_or_else(|| default_binding_id.into());
let (thread_uuid, loaded_thread) =
match self.load_epiphany_thread(&request_id, &thread_id).await {
Some(thread) => thread,
None => return,
};
let host = EpiphanyCodexThreadHost::new(loaded_thread.as_ref());
let applied = match apply_thread_epiphany_role_accept(
&host,
core_role_id,
expected_revision,
&binding_id,
)
.await
{
Ok(applied) => applied,
Err(EpiphanyBridgeError::InvalidRequest(message)) => {
self.send_invalid_request_error(request_id, message).await;
return;
}
Err(err) => {
self.send_internal_error(
request_id,
format!("failed to apply Epiphany role finding: {err}"),
)
.await;
return;
}
};
let changed_fields = applied.changed_fields;
let protocol_changed_fields = protocol_state_updated_fields(changed_fields.clone());
let epiphany_state = applied.epiphany_state;
self.outgoing
.send_response(
request_id,
ThreadEpiphanyRoleAcceptResponse {
revision: applied.revision,
changed_fields: protocol_changed_fields,
epiphany_state: epiphany_state.clone(),
role_id,
binding_id: binding_id.clone(),
accepted_receipt_id: applied.accepted_receipt_id,
accepted_observation_id: applied.accepted_observation_id,
accepted_evidence_id: applied.accepted_evidence_id,
applied_patch: protocol_patch_from_core(applied.applied_patch),
finding: protocol_role_finding(role_id, applied.finding),
},
)
.await;
self.send_epiphany_state_updated(
thread_uuid,
ThreadEpiphanyStateUpdatedSource::RoleAccept,
changed_fields,
epiphany_state,
)
.await;
}
pub(super) async fn thread_epiphany_reorient_launch(
&self,
request_id: ConnectionRequestId,
params: ThreadEpiphanyReorientLaunchParams,
) {
let ThreadEpiphanyReorientLaunchParams {
thread_id,
expected_revision,
max_runtime_seconds,
} = params;
let (thread_uuid, loaded_thread) =
match self.load_epiphany_thread(&request_id, &thread_id).await {
Some(thread) => thread,
None => return,
};
let thread = match self.read_thread_view(thread_uuid, false).await {
Ok(thread) => thread,
Err(ThreadReadViewError::InvalidRequest(message)) => {
self.send_invalid_request_error(request_id, message).await;
return;
}
Err(ThreadReadViewError::Internal(message)) => {
self.send_internal_error(request_id, message).await;
return;
}
};
let retrieval_override = thread_epiphany_retrieval_state(loaded_thread.as_ref()).await;
let config_snapshot = loaded_thread.config_snapshot().await;
self.epiphany_invalidation_manager
.ensure_thread_watch(&thread_id, &config_snapshot.cwd)
.await;
let watcher_snapshot = self
.epiphany_invalidation_manager
.snapshot(&thread_id)
.await;
let token_usage_info = loaded_thread.token_usage_info().await;
let host = EpiphanyCodexThreadHost::new(loaded_thread.as_ref());
let token_usage_snapshot = epiphany_token_usage_snapshot(token_usage_info.as_ref());
let applied = match launch_thread_epiphany_reorient(
&host,
&thread_id,
expected_revision,
max_runtime_seconds,
thread.epiphany_state.as_ref(),
Some(&retrieval_override),
Some(epiphany_freshness_watcher_snapshot(&watcher_snapshot)),
token_usage_snapshot.as_ref(),
)
.await
{
Ok(applied) => applied,
Err(EpiphanyBridgeError::InvalidRequest(message)) => {
self.send_invalid_request_error(request_id, message).await;
return;
}
Err(err) => {
self.send_internal_error(
request_id,
format!(
"failed to launch Epiphany reorientation worker for {thread_uuid}: {err}"
),
)
.await;
return;
}
};
let changed_fields = applied.changed_fields;
let protocol_changed_fields = protocol_state_updated_fields(changed_fields.clone());
let epiphany_state = applied.epiphany_state;
self.outgoing
.send_response(
request_id,
ThreadEpiphanyReorientLaunchResponse {
thread_id: thread_uuid.to_string(),
source: protocol_reorient_source(applied.source),
state_status: protocol_reorient_state_status(applied.state_status),
state_revision: applied.state_revision,
decision: protocol_reorient_decision(applied.decision),
revision: applied.revision,
changed_fields: protocol_changed_fields,
epiphany_state: epiphany_state.clone(),
job: protocol_job_from_surface(
applied.job,
Some(applied.launcher_job_id),
Some(applied.backend_job_id),
),
},
)
.await;
self.send_epiphany_state_updated(
thread_uuid,
ThreadEpiphanyStateUpdatedSource::JobLaunch,
changed_fields,
epiphany_state,
)
.await;
}
pub(super) async fn thread_epiphany_reorient_accept(
&self,
request_id: ConnectionRequestId,
params: ThreadEpiphanyReorientAcceptParams,
) {
let ThreadEpiphanyReorientAcceptParams {
thread_id,
expected_revision,
binding_id,
update_scratch,
update_investigation_checkpoint,
} = params;
let binding_id = binding_id.unwrap_or_else(|| EPIPHANY_REORIENT_LAUNCH_BINDING_ID.into());
let (thread_uuid, loaded_thread) =
match self.load_epiphany_thread(&request_id, &thread_id).await {
Some(thread) => thread,
None => return,
};
let host = EpiphanyCodexThreadHost::new(loaded_thread.as_ref());
let applied = match apply_thread_epiphany_reorient_accept(
&host,
expected_revision,
binding_id.as_str(),
update_scratch,
update_investigation_checkpoint,
)
.await
{
Ok(applied) => applied,
Err(EpiphanyBridgeError::InvalidRequest(message)) => {
self.send_invalid_request_error(request_id, message).await;
return;
}
Err(err) => {
self.send_internal_error(
request_id,
format!("failed to accept Epiphany reorientation finding: {err}"),
)
.await;
return;
}
};
let changed_fields = applied.changed_fields;
let protocol_changed_fields = protocol_state_updated_fields(changed_fields.clone());
let epiphany_state = applied.epiphany_state;
self.outgoing
.send_response(
request_id,
ThreadEpiphanyReorientAcceptResponse {
revision: applied.revision,
changed_fields: protocol_changed_fields,
epiphany_state: epiphany_state.clone(),
binding_id: binding_id.clone(),
accepted_receipt_id: applied.accepted_receipt_id,
accepted_observation_id: applied.accepted_observation_id,
accepted_evidence_id: applied.accepted_evidence_id,
finding: protocol_reorient_finding(applied.finding),
},
)
.await;
self.send_epiphany_state_updated(
thread_uuid,
ThreadEpiphanyStateUpdatedSource::ReorientAccept,
changed_fields,
epiphany_state,
)
.await;
}
pub(super) async fn thread_epiphany_index(
&self,
request_id: ConnectionRequestId,
params: ThreadEpiphanyIndexParams,
) {
let ThreadEpiphanyIndexParams {
thread_id,
force_full_rebuild,
} = params;
let (thread_uuid, thread) = match self.load_epiphany_thread(&request_id, &thread_id).await {
Some(thread) => thread,
None => return,
};
let config = thread.config_snapshot().await;
let codex_home = thread.codex_home().await;
let response = match index_epiphany_retrieval_for_paths(
config.cwd.to_path_buf(),
codex_home,
force_full_rebuild,
)
.await
.and_then(protocol_index_response)
{
Ok(response) => response,
Err(err) => {
self.send_internal_error(
request_id,
format!("failed to index Epiphany retrieval state for {thread_uuid}: {err}"),
)
.await;
return;
}
};
self.outgoing.send_response(request_id, response).await;
}
pub(super) async fn thread_epiphany_promote(
&self,
request_id: ConnectionRequestId,
params: ThreadEpiphanyPromoteParams,
) {
let ThreadEpiphanyPromoteParams {
thread_id,
expected_revision,
patch,
verifier_evidence,
} = params;
let (thread_uuid, thread) = match self.load_epiphany_thread(&request_id, &thread_id).await {
Some(thread) => thread,
None => return,
};
let host = EpiphanyCodexThreadHost::new(thread.as_ref());
let core_patch = protocol_update_patch_to_core(&patch);
let applied = match apply_thread_epiphany_promote(
&host,
expected_revision,
core_patch,
verifier_evidence,
)
.await
{
Ok(applied) => applied,
Err(EpiphanyBridgeError::InvalidRequest(message)) => {
self.send_invalid_request_error(request_id, message).await;
return;
}
Err(err) => {
self.send_internal_error(
request_id,
format!("failed to promote Epiphany state update: {err}"),
)
.await;
return;
}
};
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 protocol_changed_fields = protocol_state_updated_fields(changed_fields.clone());
let epiphany_state = applied.epiphany_state;
let response = ThreadEpiphanyPromoteResponse {
accepted: true,
reasons: Vec::new(),
revision: Some(applied.revision),
changed_fields: protocol_changed_fields,
epiphany_state: Some(epiphany_state.clone()),
};
self.outgoing.send_response(request_id, response).await;
self.send_epiphany_state_updated(
thread_uuid,
ThreadEpiphanyStateUpdatedSource::Promote,
changed_fields,
epiphany_state,
)
.await;
}
pub(super) async fn thread_epiphany_update(
&self,
request_id: ConnectionRequestId,
@ -591,97 +122,6 @@ impl CodexMessageProcessor {
.await;
}
pub(super) async fn thread_epiphany_job_launch(
&self,
request_id: ConnectionRequestId,
params: ThreadEpiphanyJobLaunchParams,
) {
let ThreadEpiphanyJobLaunchParams {
thread_id,
expected_revision,
binding_id,
kind,
scope,
owner_role,
authority_scope,
linked_subgoal_ids,
linked_graph_node_ids,
instruction,
launch_document,
output_contract_id,
max_runtime_seconds,
} = params;
let (thread_uuid, thread) = match self.load_epiphany_thread(&request_id, &thread_id).await {
Some(thread) => thread,
None => return,
};
let host = EpiphanyCodexThreadHost::new(thread.as_ref());
let core_launch_document = protocol_worker_launch_document_to_core(launch_document);
let applied = match launch_thread_epiphany_job(
&host,
build_epiphany_job_launch_request(
expected_revision,
binding_id.clone(),
kind,
scope,
owner_role,
authority_scope,
linked_subgoal_ids,
linked_graph_node_ids,
instruction,
core_launch_document,
output_contract_id,
max_runtime_seconds,
),
kind,
"missing launched job projection",
)
.await
{
Ok(applied) => applied,
Err(EpiphanyBridgeError::InvalidRequest(message)) => {
self.send_invalid_request_error(request_id, message).await;
return;
}
Err(err) => {
self.send_internal_error(
request_id,
format!("failed to launch Epiphany job for {thread_uuid}: {err}"),
)
.await;
return;
}
};
let changed_fields = applied.changed_fields;
let protocol_changed_fields = protocol_state_updated_fields(changed_fields.clone());
let epiphany_state = applied.epiphany_state;
self.outgoing
.send_response(
request_id,
ThreadEpiphanyJobLaunchResponse {
revision: applied.revision,
changed_fields: protocol_changed_fields,
epiphany_state: epiphany_state.clone(),
job: protocol_job_from_surface(
applied.job,
Some(applied.launcher_job_id),
Some(applied.backend_job_id),
),
},
)
.await;
self.send_epiphany_state_updated(
thread_uuid,
ThreadEpiphanyStateUpdatedSource::JobLaunch,
changed_fields,
epiphany_state,
)
.await;
}
pub(super) async fn thread_epiphany_job_interrupt(
&self,
request_id: ConnectionRequestId,
@ -748,9 +188,3 @@ impl CodexMessageProcessor {
.await;
}
}
async fn thread_epiphany_retrieval_state(thread: &CodexThread) -> EpiphanyRetrievalState {
let config = thread.config_snapshot().await;
let codex_home = thread.codex_home().await;
epiphany_retrieval_state_for_paths(config.cwd.to_path_buf(), codex_home).await
}

View file

@ -2,7 +2,6 @@ use codex_app_server_protocol::*;
use codex_core::CodexThread;
use codex_protocol::ThreadId;
use epiphany_state_model::EpiphanyRetrievalState;
use super::epiphany_thread_host::load_authoritative_epiphany_state;
use super::epiphany_thread_host::runtime_spine_store_path;
use epiphany_codex_bridge::cultnet::EpiphanySurfaceSource;
use epiphany_codex_bridge::invalidation::epiphany_freshness_watcher_snapshot;
@ -14,24 +13,18 @@ use epiphany_codex_bridge::protocol_edge::core_epiphany_view_needs_reorientation
use epiphany_codex_bridge::protocol_edge::core_epiphany_view_needs_runtime_store;
use epiphany_codex_bridge::protocol_edge::default_core_epiphany_view_lenses;
use epiphany_codex_bridge::protocol_edge::protocol_context_params_to_core;
use epiphany_codex_bridge::protocol_edge::protocol_distill_params_to_core;
use epiphany_codex_bridge::protocol_edge::protocol_freshness_response_from_surface;
use epiphany_codex_bridge::protocol_edge::protocol_graph_query_to_core;
use epiphany_codex_bridge::protocol_edge::protocol_role_id_to_core;
use epiphany_codex_bridge::protocol_edge::protocol_view_lenses_to_core;
use epiphany_codex_bridge::retrieve::epiphany_retrieval_state_for_paths;
use epiphany_codex_bridge::retrieve::normalize_thread_epiphany_retrieve_query;
use epiphany_codex_bridge::retrieve::retrieve_epiphany_for_paths;
use epiphany_codex_bridge::retrieve_protocol::protocol_retrieve_response;
use epiphany_codex_bridge::view_protocol::EpiphanyFreshnessResponseInput;
use epiphany_codex_bridge::view_protocol::EpiphanyReorientResultResponseInput;
use epiphany_codex_bridge::view_protocol::EpiphanyRoleResultResponseInput;
use epiphany_codex_bridge::view_protocol::EpiphanyViewResponseInput;
use epiphany_codex_bridge::view_protocol::derive_epiphany_freshness_surface;
use epiphany_codex_bridge::view_protocol::map_epiphany_context_response;
use epiphany_codex_bridge::view_protocol::map_epiphany_distill_response;
use epiphany_codex_bridge::view_protocol::map_epiphany_graph_query_response;
use epiphany_codex_bridge::view_protocol::map_epiphany_propose_response;
use epiphany_codex_bridge::view_protocol::map_epiphany_reorient_result_response;
use epiphany_codex_bridge::view_protocol::map_epiphany_role_result_response;
use epiphany_codex_bridge::view_protocol::map_epiphany_view_response;
@ -415,171 +408,7 @@ impl CodexMessageProcessor {
self.outgoing.send_response(request_id, response).await;
}
pub(super) async fn thread_epiphany_retrieve(
&self,
request_id: ConnectionRequestId,
params: ThreadEpiphanyRetrieveParams,
) {
let ThreadEpiphanyRetrieveParams {
thread_id,
query,
limit,
path_prefixes,
} = params;
let thread_uuid = match ThreadId::from_string(&thread_id) {
Ok(id) => id,
Err(err) => {
self.send_invalid_request_error(request_id, format!("invalid thread id: {err}"))
.await;
return;
}
};
let query = match normalize_thread_epiphany_retrieve_query(query, limit, path_prefixes) {
Ok(query) => query,
Err(message) => {
self.send_invalid_request_error(request_id, message.to_string())
.await;
return;
}
};
let thread = match self.thread_manager.get_thread(thread_uuid).await {
Ok(thread) => thread,
Err(_) => {
self.send_invalid_request_error(
request_id,
format!("thread not loaded: {thread_uuid}"),
)
.await;
return;
}
};
let config = thread.config_snapshot().await;
let codex_home = thread.codex_home().await;
let response =
match retrieve_epiphany_for_paths(config.cwd.to_path_buf(), codex_home, query)
.await
.and_then(protocol_retrieve_response)
{
Ok(response) => response,
Err(err) => {
self.send_internal_error(
request_id,
format!("failed to retrieve Epiphany results for {thread_uuid}: {err}"),
)
.await;
return;
}
};
self.outgoing.send_response(request_id, response).await;
}
pub(super) async fn thread_epiphany_distill(
&self,
request_id: ConnectionRequestId,
params: ThreadEpiphanyDistillParams,
) {
let thread_id = params.thread_id.clone();
let thread_uuid = match ThreadId::from_string(&thread_id) {
Ok(id) => id,
Err(err) => {
self.send_invalid_request_error(request_id, format!("invalid thread id: {err}"))
.await;
return;
}
};
let thread = match self.thread_manager.get_thread(thread_uuid).await {
Ok(thread) => thread,
Err(_) => {
self.send_invalid_request_error(
request_id,
format!("thread not loaded: {thread_uuid}"),
)
.await;
return;
}
};
let expected_revision = load_authoritative_epiphany_state(&thread)
.await
.map(|state| state.revision)
.unwrap_or(0);
let input = protocol_distill_params_to_core(params);
let response = match map_epiphany_distill_response(expected_revision, input) {
Ok(response) => response,
Err(err) => {
self.send_invalid_request_error(
request_id,
format!("failed to distill Epiphany observation: {err}"),
)
.await;
return;
}
};
self.outgoing.send_response(request_id, response).await;
}
pub(super) async fn thread_epiphany_propose(
&self,
request_id: ConnectionRequestId,
params: ThreadEpiphanyProposeParams,
) {
let ThreadEpiphanyProposeParams { thread_id, .. } = &params;
let thread_id = thread_id.clone();
let thread_uuid = match ThreadId::from_string(&thread_id) {
Ok(id) => id,
Err(err) => {
self.send_invalid_request_error(request_id, format!("invalid thread id: {err}"))
.await;
return;
}
};
let thread = match self.thread_manager.get_thread(thread_uuid).await {
Ok(thread) => thread,
Err(_) => {
self.send_invalid_request_error(
request_id,
format!("thread not loaded: {thread_uuid}"),
)
.await;
return;
}
};
let state = match load_authoritative_epiphany_state(&thread).await {
Some(state) => state,
None => {
self.send_invalid_request_error(
request_id,
format!("thread has no Epiphany state: {thread_uuid}"),
)
.await;
return;
}
};
let response = match map_epiphany_propose_response(state, params.observation_ids) {
Ok(response) => response,
Err(err) => {
self.send_invalid_request_error(
request_id,
format!("failed to propose Epiphany map update: {err}"),
)
.await;
return;
}
};
self.outgoing.send_response(request_id, response).await;
}
}
async fn thread_epiphany_retrieval_state(thread: &CodexThread) -> EpiphanyRetrievalState {