Accept typed runtime findings
This commit is contained in:
parent
d72bb81f49
commit
24d229983b
6 changed files with 95 additions and 36 deletions
|
|
@ -8,7 +8,9 @@ use codex_app_server_protocol::ThreadEpiphanyStateUpdatedNotification;
|
|||
use codex_app_server_protocol::ThreadEpiphanyStateUpdatedSource;
|
||||
use codex_app_server_protocol::ThreadEpiphanyUpdatePatch;
|
||||
use epiphany_core::EpiphanyReorientAcceptanceFinding;
|
||||
use epiphany_core::EpiphanyReorientFindingInterpretation as CoreEpiphanyReorientFinding;
|
||||
use epiphany_core::EpiphanyRoleAcceptanceFinding;
|
||||
use epiphany_core::EpiphanyRoleFindingInterpretation as CoreEpiphanyRoleFinding;
|
||||
use epiphany_core::EpiphanyRoleStatePatchDocument;
|
||||
use epiphany_core::EpiphanyStateUpdate;
|
||||
use epiphany_core::build_reorient_acceptance_bundle;
|
||||
|
|
@ -21,10 +23,6 @@ use epiphany_state_model::EpiphanyInvestigationDisposition;
|
|||
use epiphany_state_model::EpiphanyScratchPad;
|
||||
|
||||
use crate::results::map_core_role_result_role_id;
|
||||
use crate::runtime_results::reorient_finding_runtime_job_id;
|
||||
use crate::runtime_results::reorient_finding_runtime_result_id;
|
||||
use crate::runtime_results::role_finding_runtime_job_id;
|
||||
use crate::runtime_results::role_finding_runtime_result_id;
|
||||
|
||||
pub struct RoleAcceptanceUpdate {
|
||||
pub patch: ThreadEpiphanyUpdatePatch,
|
||||
|
|
@ -51,6 +49,15 @@ pub fn parse_role_finding_state_patch(
|
|||
.ok_or_else(|| "completed role finding did not include a reviewable statePatch".to_string())
|
||||
}
|
||||
|
||||
pub fn parse_core_role_finding_state_patch(
|
||||
finding: &CoreEpiphanyRoleFinding,
|
||||
) -> Result<EpiphanyRoleStatePatchDocument, String> {
|
||||
finding
|
||||
.state_patch
|
||||
.clone()
|
||||
.ok_or_else(|| "completed role finding did not include a reviewable statePatch".to_string())
|
||||
}
|
||||
|
||||
pub fn core_state_patch_from_protocol(
|
||||
patch: &ThreadEpiphanyUpdatePatch,
|
||||
) -> EpiphanyRoleStatePatchDocument {
|
||||
|
|
@ -75,6 +82,30 @@ pub fn core_state_patch_from_protocol(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn protocol_patch_from_core(
|
||||
patch: EpiphanyRoleStatePatchDocument,
|
||||
) -> ThreadEpiphanyUpdatePatch {
|
||||
ThreadEpiphanyUpdatePatch {
|
||||
objective: patch.objective,
|
||||
active_subgoal_id: patch.active_subgoal_id,
|
||||
subgoals: patch.subgoals,
|
||||
invariants: patch.invariants,
|
||||
graphs: patch.graphs,
|
||||
graph_frontier: patch.graph_frontier,
|
||||
graph_checkpoint: patch.graph_checkpoint,
|
||||
scratch: patch.scratch,
|
||||
investigation_checkpoint: patch.investigation_checkpoint,
|
||||
job_bindings: patch.job_bindings,
|
||||
acceptance_receipts: patch.acceptance_receipts,
|
||||
runtime_links: patch.runtime_links,
|
||||
observations: patch.observations,
|
||||
evidence: patch.evidence,
|
||||
churn: patch.churn,
|
||||
mode: patch.mode,
|
||||
planning: patch.planning,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn state_update_from_thread_patch(
|
||||
expected_revision: Option<u64>,
|
||||
patch: ThreadEpiphanyUpdatePatch,
|
||||
|
|
@ -148,15 +179,15 @@ pub fn epiphany_imagination_finding_has_reviewable_state_patch(
|
|||
pub fn build_role_acceptance_update(
|
||||
role_id: ThreadEpiphanyRoleId,
|
||||
binding_id: &str,
|
||||
finding: &ThreadEpiphanyRoleFinding,
|
||||
finding: &CoreEpiphanyRoleFinding,
|
||||
accepted_evidence_id: String,
|
||||
accepted_observation_id: String,
|
||||
accepted_at: String,
|
||||
) -> Result<RoleAcceptanceUpdate, String> {
|
||||
let mut patch = match role_id {
|
||||
let mut core_patch = match role_id {
|
||||
ThreadEpiphanyRoleId::Imagination => {
|
||||
let patch = parse_role_finding_state_patch(finding)?;
|
||||
let patch_errors = imagination_role_accept_patch_errors(&patch);
|
||||
let patch = parse_core_role_finding_state_patch(finding)?;
|
||||
let patch_errors = imagination_role_state_patch_policy_errors(&patch);
|
||||
if !patch_errors.is_empty() {
|
||||
return Err(format!(
|
||||
"imagination role state patch is not acceptable: {}",
|
||||
|
|
@ -166,8 +197,8 @@ pub fn build_role_acceptance_update(
|
|||
patch
|
||||
}
|
||||
ThreadEpiphanyRoleId::Modeling => {
|
||||
let patch = parse_role_finding_state_patch(finding)?;
|
||||
let patch_errors = modeling_role_accept_patch_errors(&patch);
|
||||
let patch = parse_core_role_finding_state_patch(finding)?;
|
||||
let patch_errors = modeling_role_state_patch_policy_errors(&patch);
|
||||
if !patch_errors.is_empty() {
|
||||
return Err(format!(
|
||||
"modeling role state patch is not acceptable: {}",
|
||||
|
|
@ -176,7 +207,7 @@ pub fn build_role_acceptance_update(
|
|||
}
|
||||
patch
|
||||
}
|
||||
ThreadEpiphanyRoleId::Verification => ThreadEpiphanyUpdatePatch::default(),
|
||||
ThreadEpiphanyRoleId::Verification => EpiphanyRoleStatePatchDocument::default(),
|
||||
ThreadEpiphanyRoleId::Implementation | ThreadEpiphanyRoleId::Reorientation => {
|
||||
return Err(format!(
|
||||
"role {:?} cannot be accepted through roleAccept",
|
||||
|
|
@ -185,7 +216,8 @@ pub fn build_role_acceptance_update(
|
|||
}
|
||||
};
|
||||
|
||||
let projected_fields = epiphany_update_patch_changed_fields(&patch)
|
||||
let patch_for_projection = protocol_patch_from_core(core_patch.clone());
|
||||
let projected_fields = epiphany_update_patch_changed_fields(&patch_for_projection)
|
||||
.into_iter()
|
||||
.map(|field| format!("{field:?}"))
|
||||
.collect();
|
||||
|
|
@ -197,8 +229,8 @@ pub fn build_role_acceptance_update(
|
|||
summary: finding.summary.clone(),
|
||||
next_safe_move: finding.next_safe_move.clone(),
|
||||
files_inspected: finding.files_inspected.clone(),
|
||||
runtime_result_id: role_finding_runtime_result_id(finding),
|
||||
runtime_job_id: role_finding_runtime_job_id(finding),
|
||||
runtime_result_id: finding.runtime_result_id.clone(),
|
||||
runtime_job_id: finding.runtime_job_id.clone(),
|
||||
projected_fields,
|
||||
},
|
||||
accepted_evidence_id,
|
||||
|
|
@ -208,9 +240,12 @@ pub fn build_role_acceptance_update(
|
|||
let accepted_receipt_id = acceptance_bundle.accepted_receipt_id.clone();
|
||||
let accepted_observation_id = acceptance_bundle.accepted_observation_id.clone();
|
||||
let accepted_evidence_id = acceptance_bundle.accepted_evidence_id.clone();
|
||||
patch.evidence.push(acceptance_bundle.evidence);
|
||||
patch.observations.push(acceptance_bundle.observation);
|
||||
patch.acceptance_receipts.push(acceptance_bundle.receipt);
|
||||
core_patch.evidence.push(acceptance_bundle.evidence);
|
||||
core_patch.observations.push(acceptance_bundle.observation);
|
||||
core_patch
|
||||
.acceptance_receipts
|
||||
.push(acceptance_bundle.receipt);
|
||||
let patch = protocol_patch_from_core(core_patch);
|
||||
let changed_fields = epiphany_update_patch_changed_fields(&patch);
|
||||
|
||||
Ok(RoleAcceptanceUpdate {
|
||||
|
|
@ -225,7 +260,7 @@ pub fn build_role_acceptance_update(
|
|||
pub fn build_reorient_acceptance_update(
|
||||
expected_revision: Option<u64>,
|
||||
binding_id: &str,
|
||||
finding: &ThreadEpiphanyReorientFinding,
|
||||
finding: &CoreEpiphanyReorientFinding,
|
||||
accepted_evidence_id: String,
|
||||
accepted_observation_id: String,
|
||||
accepted_at: String,
|
||||
|
|
@ -241,8 +276,8 @@ pub fn build_reorient_acceptance_update(
|
|||
next_safe_move: finding.next_safe_move.clone(),
|
||||
checkpoint_still_valid: finding.checkpoint_still_valid,
|
||||
files_inspected: finding.files_inspected.clone(),
|
||||
runtime_result_id: reorient_finding_runtime_result_id(finding),
|
||||
runtime_job_id: reorient_finding_runtime_job_id(finding),
|
||||
runtime_result_id: finding.runtime_result_id.clone(),
|
||||
runtime_job_id: finding.runtime_job_id.clone(),
|
||||
},
|
||||
accepted_evidence_id,
|
||||
accepted_observation_id,
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ use epiphany_state_model::EpiphanyThreadState;
|
|||
|
||||
use crate::error::EpiphanyBridgeError;
|
||||
use crate::error::Result as BridgeResult;
|
||||
use crate::jobs::epiphany_blocked_state_job;
|
||||
use crate::jobs::derive_epiphany_jobs;
|
||||
use crate::jobs::epiphany_blocked_state_job;
|
||||
use crate::jobs::map_interrupted_epiphany_job;
|
||||
use crate::jobs::map_launched_epiphany_job;
|
||||
use crate::launch::EPIPHANY_REORIENT_LAUNCH_BINDING_ID;
|
||||
|
|
@ -54,8 +54,10 @@ use crate::pressure::derive_epiphany_pressure;
|
|||
use crate::reorient::EpiphanyFreshnessWatcherSnapshot;
|
||||
use crate::reorient::derive_epiphany_freshness_view;
|
||||
use crate::reorient::derive_epiphany_reorient;
|
||||
use crate::runtime_results::load_completed_epiphany_reorient_finding;
|
||||
use crate::runtime_results::load_completed_epiphany_role_finding;
|
||||
use crate::results::map_protocol_reorient_finding;
|
||||
use crate::results::map_protocol_role_finding;
|
||||
use crate::runtime_results::load_completed_core_epiphany_reorient_finding;
|
||||
use crate::runtime_results::load_completed_core_epiphany_role_finding;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[allow(async_fn_in_trait)]
|
||||
|
|
@ -381,7 +383,7 @@ pub async fn apply_thread_epiphany_role_accept(
|
|||
validate_expected_revision(expected_revision, state.revision)?;
|
||||
|
||||
let runtime_store_path = thread.epiphany_runtime_spine_store_path().await;
|
||||
let finding = load_completed_epiphany_role_finding(
|
||||
let finding = load_completed_core_epiphany_role_finding(
|
||||
Some(runtime_store_path.as_path()),
|
||||
&state,
|
||||
role_id,
|
||||
|
|
@ -418,7 +420,7 @@ pub async fn apply_thread_epiphany_role_accept(
|
|||
accepted_observation_id,
|
||||
accepted_evidence_id,
|
||||
applied_patch,
|
||||
finding,
|
||||
finding: map_protocol_role_finding(role_id, finding),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -444,7 +446,7 @@ pub async fn apply_thread_epiphany_reorient_accept(
|
|||
}
|
||||
|
||||
let runtime_store_path = thread.epiphany_runtime_spine_store_path().await;
|
||||
let finding = load_completed_epiphany_reorient_finding(
|
||||
let finding = load_completed_core_epiphany_reorient_finding(
|
||||
Some(runtime_store_path.as_path()),
|
||||
&state,
|
||||
binding_id,
|
||||
|
|
@ -477,7 +479,7 @@ pub async fn apply_thread_epiphany_reorient_accept(
|
|||
accepted_receipt_id,
|
||||
accepted_observation_id,
|
||||
accepted_evidence_id,
|
||||
finding,
|
||||
finding: map_protocol_reorient_finding(finding),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -392,19 +392,29 @@ pub fn load_completed_epiphany_role_finding(
|
|||
role_id: ThreadEpiphanyRoleId,
|
||||
binding_id: &str,
|
||||
) -> BridgeResult<ThreadEpiphanyRoleFinding> {
|
||||
load_completed_core_epiphany_role_finding(runtime_store_path, state, role_id, binding_id)
|
||||
.map(|finding| map_protocol_role_finding(role_id, finding))
|
||||
}
|
||||
|
||||
pub fn load_completed_core_epiphany_role_finding(
|
||||
runtime_store_path: Option<&Path>,
|
||||
state: &EpiphanyThreadState,
|
||||
role_id: ThreadEpiphanyRoleId,
|
||||
binding_id: &str,
|
||||
) -> BridgeResult<EpiphanyRoleFindingInterpretation> {
|
||||
if let Some(link) = latest_epiphany_runtime_link_for_binding(state, binding_id) {
|
||||
let (status, finding, _note) = load_epiphany_role_result_from_runtime_spine_job(
|
||||
let snapshot = load_core_epiphany_role_result_from_runtime_spine_job(
|
||||
link.runtime_job_id.as_str(),
|
||||
runtime_store_path,
|
||||
role_id,
|
||||
);
|
||||
if status != ThreadEpiphanyRoleResultStatus::Completed {
|
||||
if snapshot.status != CoreEpiphanyCoordinatorRoleResultStatus::Completed {
|
||||
return Err(EpiphanyBridgeError::InvalidRequest(format!(
|
||||
"cannot accept role result while worker status is {:?}",
|
||||
status
|
||||
snapshot.status
|
||||
)));
|
||||
}
|
||||
return finding.ok_or_else(|| {
|
||||
return snapshot.finding.ok_or_else(|| {
|
||||
EpiphanyBridgeError::InvalidRequest(
|
||||
"cannot accept completed role worker because no typed runtime-spine result was recorded"
|
||||
.to_string(),
|
||||
|
|
@ -489,18 +499,27 @@ pub fn load_completed_epiphany_reorient_finding(
|
|||
state: &EpiphanyThreadState,
|
||||
binding_id: &str,
|
||||
) -> BridgeResult<ThreadEpiphanyReorientFinding> {
|
||||
load_completed_core_epiphany_reorient_finding(runtime_store_path, state, binding_id)
|
||||
.map(map_protocol_reorient_finding)
|
||||
}
|
||||
|
||||
pub fn load_completed_core_epiphany_reorient_finding(
|
||||
runtime_store_path: Option<&Path>,
|
||||
state: &EpiphanyThreadState,
|
||||
binding_id: &str,
|
||||
) -> BridgeResult<EpiphanyReorientFindingInterpretation> {
|
||||
if let Some(link) = latest_epiphany_runtime_link_for_binding(state, binding_id) {
|
||||
let (status, finding, _note) = load_epiphany_reorient_result_from_runtime_spine_job(
|
||||
let snapshot = load_core_epiphany_reorient_result_from_runtime_spine_job(
|
||||
link.runtime_job_id.as_str(),
|
||||
runtime_store_path,
|
||||
);
|
||||
if status != ThreadEpiphanyReorientResultStatus::Completed {
|
||||
if snapshot.status != CoreEpiphanyCrrcResultStatus::Completed {
|
||||
return Err(EpiphanyBridgeError::InvalidRequest(format!(
|
||||
"cannot accept reorientation result while worker status is {:?}",
|
||||
status
|
||||
snapshot.status
|
||||
)));
|
||||
}
|
||||
return finding.ok_or_else(|| {
|
||||
return snapshot.finding.ok_or_else(|| {
|
||||
EpiphanyBridgeError::InvalidRequest(
|
||||
"cannot accept completed reorientation worker because no typed runtime-spine result was recorded"
|
||||
.to_string(),
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ remembers doctrine; the branch remembers the blade.
|
|||
- Core role/reorient finding interpretations and acceptance bundles now serialize natively too. Use those typed result surfaces as the next authority target; do not let `ThreadEpiphanyRoleFinding` or `ThreadEpiphanyReorientFinding` remain the bridge's internal result truth just because legacy view/accept responses still expose them.
|
||||
- Runtime-result loading now has core typed snapshot APIs for role and reorient worker results, with legacy protocol tuple wrappers layered over them. This intentionally creates temporary duplicate note rendering; the next cut should move coordinator and acceptance consumers to typed snapshots, then delete protocol-shaped result rendering instead of preserving both.
|
||||
- Coordinator result gating now consumes typed runtime result snapshots and core finding interpretations directly. `ThreadEpiphanyRoleFinding` / `ThreadEpiphanyReorientFinding` remain in legacy runtime wrapper/view/mutation response paths; the next cut is acceptance/mutation so protocol findings stop being the accepted-result authority.
|
||||
- Acceptance/mutation now builds role/reorient receipts, evidence, observations, scratch, and checkpoint updates from core typed finding interpretations. Protocol findings remain as legacy response payloads and helper residue; delete those wrappers once view/result responses are also typed internally.
|
||||
- MCP remains JSON because MCP is a JSON protocol. The latest processor cut only moved MCP refresh/OAuth/status/resource/tool-call route handling into `codex_message_processor/mcp_routes.rs`, reducing the root processor to about 8,977 lines; it did not claim MCP payloads are CultNet or make MCP an Epiphany-owned runtime contract.
|
||||
- Codex account auth route handling remains in vendored Codex but now lives in `codex_message_processor/auth_routes.rs`: API-key login, ChatGPT browser/device-code/external-token login, logout, auth status, account, rate limits, and add-credits nudge handling. This preserves Codex ownership of subscription auth while cutting the root processor to about 8,094 lines.
|
||||
- One-off command execution and command stream write/resize/terminate route handling now live in `codex_message_processor/command_routes.rs`; `CommandExecManager` remains the lifecycle owner, while the root processor is about 7,800 lines.
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -320,7 +320,9 @@ current_status:
|
|||
policy now consumes those typed result snapshots directly for role and
|
||||
reorient acceptance/gating signals. Delete the duplicate protocol-shaped
|
||||
result rendering once acceptance and view response edges finish moving off
|
||||
protocol finding authority. The bridge still
|
||||
protocol finding authority. Role/reorient acceptance now builds receipts
|
||||
and evidence from core typed findings, mapping to protocol findings only for
|
||||
legacy accept response cargo. The bridge still
|
||||
depends on vendored Codex for app-server protocol DTOs and absolute path
|
||||
validation, so the purification is not done.
|
||||
next_action: Continue Codex starvation and CultNet liberation by cutting bridge-internal `ThreadEpiphany*` authority. Prioritize role-result/reorient-result DTO coupling next: derive typed CultNet surfaces first, push JSON-RPC projection into app-server, and keep MCP/OpenAI JSON only at their true protocol/text edges. Do not add compatibility adapters unless they protect a named ownership invariant.
|
||||
|
|
|
|||
Reference in a new issue