diff --git a/epiphany-core/src/runtime_spine.rs b/epiphany-core/src/runtime_spine.rs index a22c6711..48c11634 100644 --- a/epiphany-core/src/runtime_spine.rs +++ b/epiphany-core/src/runtime_spine.rs @@ -32,6 +32,10 @@ pub const RUNTIME_SESSION_TYPE: &str = "epiphany.runtime.session"; pub const RUNTIME_JOB_TYPE: &str = "epiphany.runtime.job"; pub const RUNTIME_JOB_RESULT_TYPE: &str = "epiphany.runtime.job_result"; pub const RUNTIME_EVENT_TYPE: &str = "epiphany.runtime.event"; +pub const OPENAI_ADAPTER_STATUS_TYPE: &str = "epiphany.openai_adapter_status.v0"; +pub const OPENAI_MODEL_REQUEST_TYPE: &str = "epiphany.openai_model_request.v0"; +pub const OPENAI_MODEL_STREAM_EVENT_TYPE: &str = "epiphany.openai_model_stream_event.v0"; +pub const OPENAI_MODEL_RECEIPT_TYPE: &str = "epiphany.openai_model_receipt.v0"; pub const SURFACE_SCENE_TYPE: &str = "epiphany.surface.scene"; pub const SURFACE_FRESHNESS_TYPE: &str = "epiphany.surface.freshness"; pub const SURFACE_CONTEXT_TYPE: &str = "epiphany.surface.context"; @@ -53,6 +57,10 @@ pub const SURFACE_RIDER_BRIDGE_TYPE: &str = "epiphany.surface.rider_bridge"; pub const SURFACE_UNITY_BRIDGE_TYPE: &str = "epiphany.surface.unity_bridge"; pub const RUNTIME_IDENTITY_KEY: &str = "self"; pub const RUNTIME_SPINE_SCHEMA_VERSION: &str = "epiphany.runtime_spine.v0"; +pub const OPENAI_ADAPTER_STATUS_SCHEMA_VERSION: &str = "epiphany.openai_adapter_status.v0"; +pub const OPENAI_MODEL_REQUEST_SCHEMA_VERSION: &str = "epiphany.openai_model_request.v0"; +pub const OPENAI_MODEL_STREAM_EVENT_SCHEMA_VERSION: &str = "epiphany.openai_model_stream_event.v0"; +pub const OPENAI_MODEL_RECEIPT_SCHEMA_VERSION: &str = "epiphany.openai_model_receipt.v0"; pub const SCENE_SURFACE_SCHEMA_VERSION: &str = "epiphany.scene_surface.v0"; pub const FRESHNESS_SURFACE_SCHEMA_VERSION: &str = "epiphany.freshness_surface.v0"; pub const CONTEXT_SURFACE_SCHEMA_VERSION: &str = "epiphany.context_surface.v0"; @@ -942,6 +950,50 @@ fn epiphany_mutation_contracts() -> Vec { vec![], vec!["Runtime events are append-only projections for inspection."], ), + mutation_contract( + OPENAI_ADAPTER_STATUS_TYPE, + OPENAI_ADAPTER_STATUS_SCHEMA_VERSION, + vec![CultNetDocumentOperation::Snapshot], + CultNetMutationAuthority::ReadOnly, + vec![], + vec![], + vec![ + "OpenAI adapter status is a typed document; it is not a Codex app-server status blob.", + ], + ), + mutation_contract( + OPENAI_MODEL_REQUEST_TYPE, + OPENAI_MODEL_REQUEST_SCHEMA_VERSION, + vec![ + CultNetDocumentOperation::IntentSubmit, + CultNetDocumentOperation::ReceiptWatch, + ], + CultNetMutationAuthority::Coordinator, + vec![OPENAI_MODEL_REQUEST_TYPE], + vec![OPENAI_MODEL_STREAM_EVENT_TYPE, OPENAI_MODEL_RECEIPT_TYPE], + vec![ + "Model turns enter through typed Epiphany request documents and return typed stream events/receipts.", + "The Codex spine may authenticate and transport; it must not own Epiphany state or scheduling.", + ], + ), + mutation_contract( + OPENAI_MODEL_STREAM_EVENT_TYPE, + OPENAI_MODEL_STREAM_EVENT_SCHEMA_VERSION, + vec![CultNetDocumentOperation::ReceiptWatch], + CultNetMutationAuthority::ReadOnly, + vec![], + vec![], + vec!["OpenAI stream events are receipts from a typed model request."], + ), + mutation_contract( + OPENAI_MODEL_RECEIPT_TYPE, + OPENAI_MODEL_RECEIPT_SCHEMA_VERSION, + vec![CultNetDocumentOperation::ReceiptWatch], + CultNetMutationAuthority::ReadOnly, + vec![], + vec![], + vec!["Terminal model receipts carry response id, usage, and transport evidence."], + ), mutation_contract( AGENT_MEMORY_TYPE, AGENT_MEMORY_PAYLOAD_SCHEMA_VERSION, @@ -1401,12 +1453,17 @@ mod tests { .iter() .find(|contract| contract.document_type == SURFACE_FACE_TYPE) .expect("face surface should advertise an interactive contract"); - assert_eq!(face_contract.authority, CultNetMutationAuthority::Coordinator); + assert_eq!( + face_contract.authority, + CultNetMutationAuthority::Coordinator + ); assert!( face_contract .receipt_document_types .as_ref() - .is_some_and(|items| items.iter().any(|item| item == "epiphany.face_bubble.v0")) + .is_some_and(|items| items + .iter() + .any(|item| item == "epiphany.face_bubble.v0")) ); let unity_contract = contracts .iter() @@ -1416,7 +1473,9 @@ mod tests { unity_contract .intent_document_types .as_ref() - .is_some_and(|items| items.iter().any(|item| item == "epiphany.unity_probe_intent.v0")) + .is_some_and(|items| items + .iter() + .any(|item| item == "epiphany.unity_probe_intent.v0")) ); let birth_contract = contracts .iter() @@ -1426,7 +1485,25 @@ mod tests { birth_contract .receipt_document_types .as_ref() - .is_some_and(|items| items.iter().any(|item| item == "epiphany.repo_birth_runner.v0")) + .is_some_and(|items| items + .iter() + .any(|item| item == "epiphany.repo_birth_runner.v0")) + ); + let openai_contract = contracts + .iter() + .find(|contract| contract.document_type == OPENAI_MODEL_REQUEST_TYPE) + .expect("OpenAI adapter should advertise typed model request contract"); + assert_eq!( + openai_contract.authority, + CultNetMutationAuthority::Coordinator + ); + assert!( + openai_contract + .receipt_document_types + .as_ref() + .is_some_and(|items| items + .iter() + .any(|item| item == OPENAI_MODEL_RECEIPT_TYPE)) ); } other => panic!("expected hello, got {other:?}"), @@ -1458,6 +1535,14 @@ mod tests { schema.document_type.as_deref() == Some("epiphany.swarm_control_receipt.v0") && schema.schema_version.as_deref() == Some("epiphany.swarm_control_receipt.v0") })); + assert!(schemas.iter().any(|schema| { + schema.document_type.as_deref() == Some(OPENAI_MODEL_REQUEST_TYPE) + && schema.schema_version.as_deref() == Some(OPENAI_MODEL_REQUEST_SCHEMA_VERSION) + })); + assert!(schemas.iter().any(|schema| { + schema.document_type.as_deref() == Some(OPENAI_MODEL_RECEIPT_TYPE) + && schema.schema_version.as_deref() == Some(OPENAI_MODEL_RECEIPT_SCHEMA_VERSION) + })); Ok(()) } } diff --git a/epiphany-openai-adapter/src/lib.rs b/epiphany-openai-adapter/src/lib.rs index cd32b457..e3bc5f9a 100644 --- a/epiphany-openai-adapter/src/lib.rs +++ b/epiphany-openai-adapter/src/lib.rs @@ -4,6 +4,7 @@ use serde::Serialize; pub const OPENAI_ADAPTER_REQUEST_SCHEMA_ID: &str = "epiphany.openai_model_request.v0"; pub const OPENAI_ADAPTER_EVENT_SCHEMA_ID: &str = "epiphany.openai_model_stream_event.v0"; pub const OPENAI_ADAPTER_RECEIPT_SCHEMA_ID: &str = "epiphany.openai_model_receipt.v0"; +pub const OPENAI_ADAPTER_STATUS_SCHEMA_ID: &str = "epiphany.openai_adapter_status.v0"; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum EpiphanyOpenAiAuthMode { @@ -15,6 +16,7 @@ pub enum EpiphanyOpenAiAuthMode { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct EpiphanyOpenAiAdapterStatus { + pub schema_id: String, pub adapter_id: String, pub auth_mode: EpiphanyOpenAiAuthMode, pub account_id: Option, diff --git a/epiphany-openai-codex-spine/src/lib.rs b/epiphany-openai-codex-spine/src/lib.rs index aa08bb0a..e78e5c0a 100644 --- a/epiphany-openai-codex-spine/src/lib.rs +++ b/epiphany-openai-codex-spine/src/lib.rs @@ -29,6 +29,7 @@ use epiphany_openai_adapter::EpiphanyOpenAiModelReceipt; use epiphany_openai_adapter::EpiphanyOpenAiModelRequest; use epiphany_openai_adapter::EpiphanyOpenAiStreamEvent; use epiphany_openai_adapter::EpiphanyOpenAiStreamPayload; +use epiphany_openai_adapter::OPENAI_ADAPTER_STATUS_SCHEMA_ID; use futures::StreamExt; pub const CODEX_SPINE_ADAPTER_ID: &str = "codex-openai-subscription-spine"; @@ -47,6 +48,7 @@ pub async fn status_from_auth_manager( .map(|plan| format!("{plan:?}")); EpiphanyOpenAiAdapterStatus { + schema_id: OPENAI_ADAPTER_STATUS_SCHEMA_ID.to_string(), adapter_id: CODEX_SPINE_ADAPTER_ID.to_string(), auth_mode, account_id, @@ -63,6 +65,7 @@ pub fn status_from_codex_auth( supports_websockets: bool, ) -> EpiphanyOpenAiAdapterStatus { EpiphanyOpenAiAdapterStatus { + schema_id: OPENAI_ADAPTER_STATUS_SCHEMA_ID.to_string(), adapter_id: CODEX_SPINE_ADAPTER_ID.to_string(), auth_mode: auth_mode_from_codex_auth(auth), account_id: auth.and_then(CodexAuth::get_account_id), diff --git a/notes/codex-auth-spine-inventory.md b/notes/codex-auth-spine-inventory.md index 1726be7e..de6338ed 100644 --- a/notes/codex-auth-spine-inventory.md +++ b/notes/codex-auth-spine-inventory.md @@ -134,5 +134,12 @@ the next cut is to make an Epiphany-native runtime call this transport boundary directly instead of reaching model turns through `thread/epiphany/*` JSON-RPC or the Codex host brain. +The CultNet paperwork is now public too. `schemas/cultnet/` contains typed +schemas for OpenAI adapter status, model request, stream event, and terminal +receipt; `epiphany-runtime-spine` advertises those document types and mutation +contracts in its hello/schema catalog. This is the contract bridge the native +runtime should consume next. Do not add another JSON-RPC model endpoint and call +it progress. + The point is ownership: Epiphany calls a model adapter; it does not live inside the Codex host brain. diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 16cbc900..ee1221e3 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -561,7 +561,11 @@ same spine now owns the first HTTP Responses transport wrapper: typed `ResponsesApiRequest`, auth/provider resolves through `codex-login` and `codex-model-provider`, the stream opens through `codex-api`, and deltas / completion map back into typed `EpiphanyOpenAiStreamEvent` and -`EpiphanyOpenAiModelReceipt` documents. +`EpiphanyOpenAiModelReceipt` documents. The CultNet schema catalog now advertises +OpenAI adapter status, model request, stream event, and receipt document types +plus the coordinator-owned model request contract. The native runtime should +consume that contract next; do not make a new JSON-RPC model endpoint and +pretend the whale got lighter. 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 diff --git a/schemas/cultnet/epiphany.openai-adapter-status.schema.json b/schemas/cultnet/epiphany.openai-adapter-status.schema.json new file mode 100644 index 00000000..d3d80985 --- /dev/null +++ b/schemas/cultnet/epiphany.openai-adapter-status.schema.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://gamecult.dev/epiphany/cultnet/epiphany.openai-adapter-status.schema.json", + "title": "Epiphany OpenAI Adapter Status", + "type": "object", + "required": ["schema_id", "adapter_id", "auth_mode", "supports_websockets", "codex_transport_attached"], + "properties": { + "schema_id": { "const": "epiphany.openai_adapter_status.v0" }, + "adapter_id": { "type": "string" }, + "auth_mode": { "enum": ["ChatGptSubscription", "ApiKey", "ExternalBearer", "Unknown"] }, + "account_id": { "type": ["string", "null"] }, + "plan_type": { "type": ["string", "null"] }, + "default_model": { "type": ["string", "null"] }, + "supports_websockets": { "type": "boolean" }, + "codex_transport_attached": { "type": "boolean" } + }, + "additionalProperties": false +} diff --git a/schemas/cultnet/epiphany.openai-model-receipt.schema.json b/schemas/cultnet/epiphany.openai-model-receipt.schema.json new file mode 100644 index 00000000..861c4a8d --- /dev/null +++ b/schemas/cultnet/epiphany.openai-model-receipt.schema.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://gamecult.dev/epiphany/cultnet/epiphany.openai-model-receipt.schema.json", + "title": "Epiphany OpenAI Model Receipt", + "type": "object", + "required": ["schema_id", "request_id", "model"], + "properties": { + "schema_id": { "const": "epiphany.openai_model_receipt.v0" }, + "request_id": { "type": "string" }, + "model": { "type": "string" }, + "response_id": { "type": ["string", "null"] }, + "input_tokens": { "type": ["integer", "null"], "minimum": 0 }, + "output_tokens": { "type": ["integer", "null"], "minimum": 0 }, + "reasoning_output_tokens": { "type": ["integer", "null"], "minimum": 0 }, + "transport": { "type": ["string", "null"] } + }, + "additionalProperties": false +} diff --git a/schemas/cultnet/epiphany.openai-model-request.schema.json b/schemas/cultnet/epiphany.openai-model-request.schema.json new file mode 100644 index 00000000..d7c8ad4b --- /dev/null +++ b/schemas/cultnet/epiphany.openai-model-request.schema.json @@ -0,0 +1,71 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://gamecult.dev/epiphany/cultnet/epiphany.openai-model-request.schema.json", + "title": "Epiphany OpenAI Model Request", + "type": "object", + "required": ["schema_id", "request_id", "conversation_id", "model", "instructions", "input"], + "properties": { + "schema_id": { "const": "epiphany.openai_model_request.v0" }, + "request_id": { "type": "string" }, + "conversation_id": { "type": "string" }, + "model": { "type": "string" }, + "instructions": { "type": "string" }, + "input": { + "type": "array", + "items": { "$ref": "#/$defs/input_item" } + }, + "reasoning_effort": { "type": ["string", "null"] }, + "reasoning_summary": { "type": ["string", "null"] }, + "service_tier": { "type": ["string", "null"] }, + "output_contract_id": { "type": ["string", "null"] } + }, + "additionalProperties": false, + "$defs": { + "input_item": { + "oneOf": [ + { + "type": "object", + "required": ["UserText"], + "properties": { + "UserText": { + "type": "object", + "required": ["text"], + "properties": { "text": { "type": "string" } }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": ["AssistantText"], + "properties": { + "AssistantText": { + "type": "object", + "required": ["text"], + "properties": { "text": { "type": "string" } }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": ["ToolResult"], + "properties": { + "ToolResult": { + "type": "object", + "required": ["call_id", "output"], + "properties": { + "call_id": { "type": "string" }, + "output": { "type": "string" } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/schemas/cultnet/epiphany.openai-model-stream-event.schema.json b/schemas/cultnet/epiphany.openai-model-stream-event.schema.json new file mode 100644 index 00000000..265bab5e --- /dev/null +++ b/schemas/cultnet/epiphany.openai-model-stream-event.schema.json @@ -0,0 +1,93 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://gamecult.dev/epiphany/cultnet/epiphany.openai-model-stream-event.schema.json", + "title": "Epiphany OpenAI Model Stream Event", + "type": "object", + "required": ["schema_id", "request_id", "sequence", "payload"], + "properties": { + "schema_id": { "const": "epiphany.openai_model_stream_event.v0" }, + "request_id": { "type": "string" }, + "sequence": { "type": "integer", "minimum": 0 }, + "payload": { + "oneOf": [ + { "$ref": "#/$defs/text_delta" }, + { "$ref": "#/$defs/reasoning_delta" }, + { "$ref": "#/$defs/tool_call" }, + { "$ref": "#/$defs/completed" }, + { "$ref": "#/$defs/failed" } + ] + } + }, + "additionalProperties": false, + "$defs": { + "text_delta": { + "type": "object", + "required": ["TextDelta"], + "properties": { + "TextDelta": { + "type": "object", + "required": ["text"], + "properties": { "text": { "type": "string" } }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "reasoning_delta": { + "type": "object", + "required": ["ReasoningDelta"], + "properties": { + "ReasoningDelta": { + "type": "object", + "required": ["text"], + "properties": { "text": { "type": "string" } }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "tool_call": { + "type": "object", + "required": ["ToolCall"], + "properties": { + "ToolCall": { + "type": "object", + "required": ["call_id", "name", "arguments"], + "properties": { + "call_id": { "type": "string" }, + "name": { "type": "string" }, + "arguments": { "type": "string" } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "completed": { + "type": "object", + "required": ["Completed"], + "properties": { + "Completed": { + "type": "object", + "required": ["receipt"], + "properties": { "receipt": { "$ref": "epiphany.openai-model-receipt.schema.json" } }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "failed": { + "type": "object", + "required": ["Failed"], + "properties": { + "Failed": { + "type": "object", + "required": ["message"], + "properties": { "message": { "type": "string" } }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + } +} diff --git a/schemas/cultnet/index.json b/schemas/cultnet/index.json index 29e7ea65..f2c39a66 100644 --- a/schemas/cultnet/index.json +++ b/schemas/cultnet/index.json @@ -56,6 +56,50 @@ "title": "Epiphany Runtime Event", "path": "epiphany.runtime.event.schema.json" }, + { + "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.openai-adapter-status.schema.json", + "kind": "document_payload", + "wireContracts": [ + "cultnet.schema.v0" + ], + "schemaVersion": "epiphany.openai_adapter_status.v0", + "documentType": "epiphany.openai_adapter_status.v0", + "title": "Epiphany OpenAI Adapter Status", + "path": "epiphany.openai-adapter-status.schema.json" + }, + { + "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.openai-model-request.schema.json", + "kind": "document_payload", + "wireContracts": [ + "cultnet.schema.v0" + ], + "schemaVersion": "epiphany.openai_model_request.v0", + "documentType": "epiphany.openai_model_request.v0", + "title": "Epiphany OpenAI Model Request", + "path": "epiphany.openai-model-request.schema.json" + }, + { + "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.openai-model-stream-event.schema.json", + "kind": "document_payload", + "wireContracts": [ + "cultnet.schema.v0" + ], + "schemaVersion": "epiphany.openai_model_stream_event.v0", + "documentType": "epiphany.openai_model_stream_event.v0", + "title": "Epiphany OpenAI Model Stream Event", + "path": "epiphany.openai-model-stream-event.schema.json" + }, + { + "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.openai-model-receipt.schema.json", + "kind": "document_payload", + "wireContracts": [ + "cultnet.schema.v0" + ], + "schemaVersion": "epiphany.openai_model_receipt.v0", + "documentType": "epiphany.openai_model_receipt.v0", + "title": "Epiphany OpenAI Model Receipt", + "path": "epiphany.openai-model-receipt.schema.json" + }, { "schemaId": "https://gamecult.dev/epiphany/cultnet/epiphany.agent.memory.schema.json", "kind": "document_payload", diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index 0e6db883..be1f785a 100644 Binary files a/state/ledgers.msgpack and b/state/ledgers.msgpack differ diff --git a/state/map.yaml b/state/map.yaml index cdc6ab93..e5e39963 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -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 pure native `epiphany-openai-adapter` document boundary, and a workspace-verified Codex auth/model wrapper in `epiphany-openai-codex-spine`. 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, and `epiphany_state_helpers.rs` is down to a 19-line rollout-state loader. `epiphany-openai-adapter` defines typed 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. MCP itself is correctly JSON at its protocol edge; the target is typed Epiphany intent/result/receipt wrappers around MCP, not replacing MCP JSON. - next_action: Continue Codex starvation by making an Epiphany-native runtime surface call the typed OpenAI adapter/spine directly, then starve the old model-turn path out of `thread/epiphany/*` JSON-RPC and `codex_message_processor.rs`, without adding Codex app-server, apps, skills, marketplace, or Epiphany JSON-RPC dependencies. Do not resume app/skill/marketplace expansion. + 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 pure native `epiphany-openai-adapter` document boundary, and a workspace-verified Codex auth/model wrapper in `epiphany-openai-codex-spine`. 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, and `epiphany_state_helpers.rs` is down to a 19-line rollout-state loader. `epiphany-openai-adapter` defines typed 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-runtime-spine` now advertises OpenAI adapter status/model-request/stream-event/receipt CultNet schemas and mutation contracts, so the next caller can be native instead of 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. + next_action: Continue Codex starvation by making an Epiphany-native runtime surface consume the advertised OpenAI adapter CultNet contract and call the typed spine directly, then starve the old model-turn path out of `thread/epiphany/*` JSON-RPC and `codex_message_processor.rs`, without adding Codex app-server, apps, skills, marketplace, or Epiphany JSON-RPC dependencies. Do not resume app/skill/marketplace expansion.