From c6201363f4275e3eec5f2d45b239cd4e5bd0c63e Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 30 Apr 2026 10:06:51 +0100 Subject: [PATCH] Keep regather in fixed coordinator lanes --- notes/epiphany-current-algorithmic-map.md | 3 +- notes/fresh-workspace-handoff.md | 16 +- state/evidence.jsonl | 2 + state/map.yaml | 5 +- tools/epiphany_mvp_coordinator.py | 49 ++++ tools/epiphany_mvp_coordinator_smoke.py | 2 +- .../app-server/src/codex_message_processor.rs | 233 +++++++++++++++--- 7 files changed, 271 insertions(+), 39 deletions(-) diff --git a/notes/epiphany-current-algorithmic-map.md b/notes/epiphany-current-algorithmic-map.md index cfcb799a..a9143ef5 100644 --- a/notes/epiphany-current-algorithmic-map.md +++ b/notes/epiphany-current-algorithmic-map.md @@ -35,7 +35,7 @@ The spine exists in twenty-five live paths: - CRRC coordinator recommendation: app-server declares experimental read-only `thread/epiphany/crrc`, reuses the same pressure/freshness/watcher/reorientation mapping plus the fixed `reorient-worker` binding and result read-back helper, recognizes already accepted reorientation findings so an accepted result is not recommended for acceptance again, and returns one bounded recommendation such as continue, prepare checkpoint, launch worker, wait, review, accept, or regather manually without mutating, launching, accepting, compacting, notifying, scheduling, or silently continuing work in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:370), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4661), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4546). - role ownership projection: app-server declares experimental read-only `thread/epiphany/roles`, reuses the same state/jobs/pressure/reorient/result/CRRC signals as the coordinator loop, and projects implementation, modeling/checkpoint, verification/review, and reorientation lanes with owner roles, status, linked jobs, authority scopes, and recommended scene actions without launching, scheduling, accepting, compacting, notifying, or mutating state in [common.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/common.rs:350), [v2.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server-protocol/src/protocol/v2.rs:4161), and [codex_message_processor.rs](E:/Projects/EpiphanyAgent/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs:4310). - role specialist launch/read-back/acceptance: app-server declares experimental `thread/epiphany/roleLaunch`, read-only `thread/epiphany/roleResult`, and write-gated `thread/epiphany/roleAccept`. `roleLaunch` accepts only fixed modeling/checkpoint and verification/review role ids, builds bounded specialist payloads from authoritative Epiphany state, routes them through the existing `epiphany_launch_job` job-control seam, and emits the normal `jobLaunch` state update for `jobBindings` only. `roleResult` resolves the fixed or requested binding through the current `agent_jobs` backend and projects structured output as a reviewable finding with verdict, summary, next safe move, inspected files, frontier/evidence ids, optional modeling `statePatch`, raw result, and errors without mutation, promotion, scheduling, or hidden continuation. `roleAccept` currently accepts only completed modeling/checkpoint findings with a valid modeling-safe `statePatch`, applies it through the existing Epiphany state validator, appends audit observation/evidence, and emits `stateUpdated` with source `roleAccept`. Implementation stays owned by the main coding agent, verification output remains review-only, and reorientation stays on the CRRC-specific `reorientLaunch` / `reorientResult` / `reorientAccept` path. -- fixed-lane coordinator: app-server declares experimental read-only `thread/epiphany/coordinator`, composes role, pressure, reorient, CRRC, role result, and reorient result signals, and returns one current action without mutation. Review acceptance and implementation clearance are intentionally different: an accepted verification finding clears implementation only when the verifier verdict is `pass`; accepted `needs-evidence`, `needs-review`, or `fail` findings route back toward modeling/checkpoint strengthening before code work resumes. +- fixed-lane coordinator: app-server declares experimental read-only `thread/epiphany/coordinator`, composes role, pressure, reorient, CRRC, role result, and reorient result signals, and returns one current action without mutation. Review acceptance and implementation clearance are intentionally different: an accepted verification finding clears implementation only when the verifier verdict is `pass`; accepted `needs-evidence`, `needs-review`, or `fail` findings route back toward modeling/checkpoint strengthening before code work resumes. `regatherManually` is now fallback, not a supervisor-puppeteering excuse: completed unaccepted modeling findings stop at `reviewModelingResult`, modeling acceptance precedes verification, and fixed lane actions are considered before manual regather. - MVP operator view: `tools/epiphany_mvp_status.py` launches app-server over stdio, starts or reads one thread, gathers scene, pressure, reorient, jobs, roles, modeling/verification role results, reorient result, and CRRC responses, and renders them as text or JSON so the loop can be dogfooded without reading Rust. - MVP dogfood runner: `tools/epiphany_mvp_dogfood.py` launches app-server over stdio, drives a bounded state/role/CRRC/reorientation loop, and writes operator-safe JSON/text artifacts plus sealed transcript diagnostics under `.epiphany-dogfood/mvp-loop` so a human can audit the run through projections and compare it against a vanilla Codex reference. - Agent function/API telemetry: `tools/epiphany_agent_telemetry.py` mechanically parses sealed AppServerClient JSONL transcripts into operator-safe telemetry with method counts, request shape, job/status/path fields, and visible function/tool names while sealing text, turn items, raw results, and direct agent messages. @@ -1448,6 +1448,7 @@ stored Epiphany state -> fixed modeling/checkpoint and verification/review role workers can be launched explicitly -> fixed role worker results can be read back for review without mutation -> reviewed modeling/checkpoint statePatch findings can be accepted explicitly to grow graph/checkpoint state +-> completed unaccepted modeling findings stop for review before verification -> accepted verifier findings clear implementation only on verifier verdict pass; non-pass findings send the loop back toward modeling/checkpoint repair -> can be consumed explicitly by one bounded reorient-worker launch surface -> retrieval/indexing can inform future work diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 5e788dd4..637520a6 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -44,8 +44,9 @@ Do not trust this file for the exact live HEAD. Always check git. - `tools/epiphany_agent_telemetry.py` is the safe instrument panel for sealed runs. Status/coordinator/GUI/dogfood/live-specialist tools generate telemetry JSON from sealed transcripts that preserves method names, call shape, job/status/path counts, and any visible function/tool names while sealing text, direct messages, and raw results. - Latest Aetheria supervised dogfood thread: `019ddc52-4ee8-7203-b6c0-106a9c270067` with codex-home `.epiphany-dogfood/aetheria-supervised/codex-home`. CRRC compact/reorient/reorientAccept reached Epiphany state revision 16 after rollout replay fixes. Later sanity review showed the modeler and verifier were doing the right work: modeling emitted source-grounded graph/checkpoint/frontier artifacts, while verification returned `needs-evidence` and explicitly did not bless implementation. - Coordinator policy now treats accepted verifier results as implementation clearance only when the verifier verdict is `pass`. Accepting a `needs-evidence`, `needs-review`, or `fail` verifier finding records the review but routes the loop back toward modeling/checkpoint strengthening or reorientation. The policy was tightened after a too-permissive coordinator path treated reviewed verifier output as a green light. +- A later dogfood attempt exposed another supervision scar: manually launching modeling, reorientation, and verification from the supervisor is not clean dogfood. The coordinator now treats `regatherManually` as fallback only after fixed lanes cannot advance, stops at `reviewModelingResult` for completed unaccepted modeling findings, and the CLI runner's explicit test-only `--auto-review` mode can accept modeling `statePatch` findings before launching verification. Production semantic findings remain review-gated. - The implementation lane is the current product blocker, but it is now harness-visible. The latest audited artifact is `.epiphany-dogfood/aetheria-supervised/gui-actions/continueImplementation-1777528376766401900-25000`: it shows `workspaceChanged: false`, wrote `implementation-audit.md`, applied a no-diff `thread/epiphany/update`, advanced Epiphany state to revision 17, and marked the investigation checkpoint `regather_required`. The next clean run should retry through the operator-safe coordinator lanes after the pre-compaction handoff repair, not by reading sealed worker thoughts. -- Follow-up coordinator plans at `.epiphany-dogfood/aetheria-supervised/coordinator-after-nodiff-state-20260430-plan` and `.epiphany-dogfood/aetheria-supervised/coordinator-after-verification-verdict-policy-20260430-plan` return `regatherManually` with implementation blocked, instead of continuing fake progress. +- Follow-up coordinator plans at `.epiphany-dogfood/aetheria-supervised/coordinator-after-nodiff-state-20260430-plan` and `.epiphany-dogfood/aetheria-supervised/coordinator-after-verification-verdict-policy-20260430-plan` returned `regatherManually` with implementation blocked. After the latest coordinator fix, `.epiphany-dogfood/aetheria-supervised/supervisor-runs/dogfood-20260430-094721/coordinator-post-puppet-fix-plan` returns `reviewModelingResult`: the harness now surfaces the unaccepted modeling finding instead of asking the supervisor to improvise. - The GUI parses `implementation-result.json` into artifact metadata, surfaces the latest implementation diff/no-diff outcome, and pauses immediate `Continue Implementation` repeats when the newest artifact is a no-diff implementation audit. - The repo is an Epiphany fork of Codex, not a Codex preset. - `vendor/codex` is tracked directly, not a submodule. @@ -359,11 +360,14 @@ bounded browser-fallback controls. A live bridge probe also proved The next real move is not more direct Aetheria implementation. The clean supervised lane reached implementation too early because the coordinator treated an accepted verifier finding as a green light even when the verifier verdict was -`needs-evidence`. That policy is now tightened: only an accepted verifier -`pass` clears implementation. The current Aetheria thread should therefore -start with bounded regather/modeling repair for the `regather_required` -checkpoint and verifier evidence gaps, then run verification again before any -implementation retry. Read only operator-safe projections; do not +`needs-evidence`. The next dogfood attempt then proved that supervisor-clicked +lane launches are theatre, not harness autonomy. Both policies are now +tightened: only an accepted verifier `pass` clears implementation, and +`regatherManually` no longer preempts fixed modeling/verification review lanes. +The current Aetheria thread should stop at `reviewModelingResult` because the +latest modeler produced a useful source-grounded finding but no reviewable +`statePatch`; fix that modeling output contract before another implementation +retry. Read only operator-safe projections; do not open raw worker transcripts, direct worker messages, or `rawResult` payloads unless the user explicitly asks for forensic debugging. Use generated function/API telemetry for call-shape diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 1ee7b6aa..1a7c5f01 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -45,3 +45,5 @@ {"ts": "2026-04-30T08:18:46+00:00", "type": "implementation", "status": "ok", "note": "CRRC pre-compaction steering now creates a turn-scoped pending compact handoff after successful steer_input, and clean turn completion forces the existing compact automation even if the ordinary coordinator would continue; aborted turns clear the handoff. Focused Rust tests, coordinator smoke, and reorient-launch smoke passed."} {"ts": "2026-04-30T08:31:23+00:00", "type": "implementation", "status": "ok", "note": "CRRC pressure now uses current context usage from last token telemetry instead of cumulative thread token spend, treats cumulative-only telemetry as unknown, and starts pre-compaction preparation at 80% of the active auto-compact/context limit. Focused pressure/coordinator tests plus pressure and coordinator smokes passed."} {"ts": "2026-04-30T09:40:22+01:00", "type": "implementation", "status": "ok", "note": "Aetheria dogfood sanity review found the specialists were not the weak link: modeling emitted source-grounded graph/checkpoint/frontier state, and verification returned needs-evidence rather than blessing implementation. The coordinator policy was tightened so only an accepted verifier pass clears implementation; accepted non-pass verifier findings route back toward modeling/checkpoint repair or reorientation. Focused Rust coordinator tests, coordinator smoke, and an operator-safe replay now block implementation with regatherManually on the current Aetheria thread."} +{"ts": "2026-04-30T10:05:00+01:00", "type": "verification", "status": "blocked", "note": "Aetheria dogfood attempt dogfood-20260430-094721 was contaminated by supervisor lane puppeteering: manual launchModeling, launchReorient, and launchVerification calls tested the buttons, not autonomous Epiphany coordination. The manual artifacts still showed useful facts: modeling and reorientation stayed source-grounded, verification returned needs-evidence, and implementation remained blocked."} +{"ts": "2026-04-30T10:05:10+01:00", "type": "implementation", "status": "ok", "note": "Coordinator regather policy now keeps fixed lanes in charge: regatherManually no longer preempts modeling/verification review, completed unaccepted modeling findings produce reviewModelingResult before verification, modeling acceptance can be driven only by explicit test auto-review or operator review, and the current Aetheria coordinator replay now stops at reviewModelingResult instead of supervisor improvisation. Focused Rust coordinator tests and MVP coordinator smoke passed."} diff --git a/state/map.yaml b/state/map.yaml index 928dbcca..5c8f9b2a 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -96,6 +96,7 @@ accepted_design: - A narrow auditable coordinator is likely central to the MVP because implementation, modeling/checkpoint, verification/review, and CRRC automation depend on each other's signals. CRRC itself is harness workflow automation, not a specialist-agent role: it watches pressure/continuity, ensures persistence, triggers compaction/rehydration/reorientation when needed, and may launch a bounded reorient-worker specialist when semantic regathering is required. - `thread/epiphany/coordinator` is the first harness-native fixed-lane coordinator surface: it composes roles, pressure, reorient, CRRC, roleResult, and reorientResult into a read-only action recommendation with target role, source signals, review requirements, automation eligibility, reason, and current lane summaries without mutating state or launching work. - Coordinator implementation clearance is stricter than review acceptance: an accepted verification/review finding only clears the implementation lane when its verdict is `pass`; accepted `needs-evidence`, `needs-review`, or `fail` findings send the loop back toward modeling/checkpoint strengthening or reorientation instead of treating the review as a green light. + - Coordinator regather handling is now fixed-lane first, not supervisor improvisation: `regatherManually` is a fallback only after reorient/modeling/verification review and launch lanes cannot advance the state, completed modeling findings must be reviewed before verification, and the CLI coordinator's explicit test-only `--auto-review` mode may accept modeling state patches before launching verification while production semantics remain review-gated. - `tools/epiphany_mvp_coordinator.py` is the first auditable coordinator runner: it drives the fixed single-user lanes through app-server APIs, may auto-launch modeling, verification, or reorient-worker jobs, keeps semantic findings review-gated by default, and writes coordinator summaries, JSONL steps, rendered snapshots, transcript, stderr, and final next-action artifacts. - Limited native CRRC automation is now wired only at turn-complete safe boundaries: it can submit a compact operation when the read-only coordinator recommends `compactRehydrateReorient` or when a successful pre-compaction checkpoint intervention left a pending compact handoff for the completed turn, and it can launch the fixed `reorient-worker` when the coordinator recommends `launchReorientWorker`. It does not auto-launch modeling or verification, auto-accept findings, promote evidence, edit implementation code, or continue after reviewable semantic results. - Pre-compaction checkpoint intervention is now wired at token-count boundaries: when a loaded Epiphany thread crosses the existing `shouldPrepareCompaction` pressure threshold during an active turn, the harness steers the active agent once for that turn with a bounded CRRC directive to bank scratch/checkpoint/map/evidence before compaction/reorientation. A successful steer marks a turn-scoped pending compact handoff consumed at clean turn completion; this is still steering plus compaction handoff, not semantic acceptance, broad scheduling, or automatic implementation continuation. @@ -181,5 +182,5 @@ rejected_paths: reason: persistent state that accumulates every local truth becomes memory cruft and weakens rehydration current_status: - summary: Phase 1 durable state through the Phase 6 GUI/coordinator/specialist/CRRC MVP surfaces are landed. Aetheria dogfood first exposed a supervisor contamination scar, so the quarantine now keeps the supervisor on operator-safe projections and sealed transcripts. The clean supervised pass proved the CRRC compact/reorient/reorientAccept lane can reach state revision 16, and later operator-safe review showed the modeler and verifier behaved correctly: modeling grew source-grounded graph/checkpoint/frontier state while verification returned `needs-evidence`, not a pass. The coordinator was too permissive, so it now treats only an accepted verifier `pass` as implementation clearance; accepted non-pass verifier findings route back toward modeling/checkpoint repair or reorientation. The follow-up implementation pass still produced no tracked Aetheria diff, wrote audit artifacts, marked the investigation checkpoint `regather_required`, advanced state to revision 17, and current coordinator replay blocks implementation with `regatherManually` instead of fake progress. - next_action: Retry Aetheria dogfood from the operator-safe coordinator lanes. Start with bounded regather/modeling repair for the `regather_required` checkpoint and verifier `needs-evidence` finding, then rerun verification; only retry implementation after a passing verifier result or an explicit reviewable failure artifact says the lane is still blocked. Do not inspect raw worker transcripts or `rawResult` payloads unless the user explicitly requests forensic debugging. + summary: Phase 1 durable state through the Phase 6 GUI/coordinator/specialist/CRRC MVP surfaces are landed. Aetheria dogfood first exposed a supervisor contamination scar, so the quarantine now keeps the supervisor on operator-safe projections and sealed transcripts. A second dogfood attempt exposed a subtler supervision scar: manually clicking launchModeling, launchReorient, and launchVerification was theatre, not autonomous harness dogfood. The harness was repaired so `regatherManually` no longer preempts fixed modeling/verification review lanes, completed modeling must be reviewed before verification, accepted verifier `pass` is still required before implementation, and the coordinator now recommends `reviewModelingResult` on the current Aetheria thread instead of asking the supervisor to improvise. + next_action: Fix the modeling output contract before another implementation dogfood run: the latest Aetheria modeler produced a source-grounded `checkpoint-ready` finding but no `statePatch`, so the coordinator correctly stops at `reviewModelingResult` and implementation stays blocked. Strengthen modeling prompt/schema/acceptance feedback so regather/modeling can emit a reviewable typed compatibility-contract/checkpoint patch, then let the coordinator runner drive modeling acceptance and verification without manual lane puppeteering. Do not inspect raw worker transcripts or `rawResult` payloads unless the user explicitly requests forensic debugging. diff --git a/tools/epiphany_mvp_coordinator.py b/tools/epiphany_mvp_coordinator.py index 924f1bdc..3bca009d 100644 --- a/tools/epiphany_mvp_coordinator.py +++ b/tools/epiphany_mvp_coordinator.py @@ -373,6 +373,55 @@ def run_coordinator(args: argparse.Namespace) -> dict[str, Any]: break revision = state_revision(status) + if action == "reviewModelingResult": + result = client.send( + "thread/epiphany/roleResult", + {"threadId": thread_id, "roleId": "modeling"}, + ) + step["events"].append( + { + "type": "roleResult", + "roleId": "modeling", + "result": sanitize_for_operator(result), + } + ) + finding = result.get("finding") if isinstance(result, dict) else None + if not ( + args.auto_review + and isinstance(finding, dict) + and isinstance(finding.get("statePatch"), dict) + and revision is not None + ): + final_action = { + "action": "reviewModelingResult", + "reason": result.get("note") if isinstance(result, dict) else coordinator.get("reason"), + } + append_jsonl(steps_path, step) + break + accepted = client.send( + "thread/epiphany/roleAccept", + { + "threadId": thread_id, + "roleId": "modeling", + "expectedRevision": revision, + }, + ) + step["events"].append( + { + "type": "roleAccept", + "roleId": "modeling", + "accepted": sanitize_for_operator(accepted), + } + ) + final_status = collect_coordinator_status( + client, + thread_id=thread_id, + cwd=cwd, + ephemeral=args.ephemeral, + ) + append_jsonl(steps_path, step) + continue + if action == "launchModeling": launch = launch_role( client, diff --git a/tools/epiphany_mvp_coordinator_smoke.py b/tools/epiphany_mvp_coordinator_smoke.py index 1d0639e2..82b9aba2 100644 --- a/tools/epiphany_mvp_coordinator_smoke.py +++ b/tools/epiphany_mvp_coordinator_smoke.py @@ -133,7 +133,7 @@ def run_smoke(args: argparse.Namespace) -> dict[str, Any]: mode="run", bootstrap_smoke_state=True, auto_review=True, - max_steps=3, + max_steps=4, ) ) require( diff --git a/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs b/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs index 369420b8..1b29da63 100644 --- a/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs +++ b/vendor/codex/codex-rs/app-server/src/codex_message_processor.rs @@ -4628,21 +4628,28 @@ impl CodexMessageProcessor { reorient_job.as_ref(), ); - let (modeling_result_status, _, _) = if let Some(state) = thread.epiphany_state.as_ref() { - load_epiphany_role_result_snapshot( - state, - state_db_ctx.as_ref(), - ThreadEpiphanyRoleId::Modeling, - EPIPHANY_MODELING_ROLE_BINDING_ID, - ) - .await - } else { - ( - ThreadEpiphanyRoleResultStatus::MissingState, - None, - "No authoritative Epiphany state exists for this thread.".to_string(), - ) - }; + let (modeling_result_status, modeling_finding, _) = + if let Some(state) = thread.epiphany_state.as_ref() { + load_epiphany_role_result_snapshot( + state, + state_db_ctx.as_ref(), + ThreadEpiphanyRoleId::Modeling, + EPIPHANY_MODELING_ROLE_BINDING_ID, + ) + .await + } else { + ( + ThreadEpiphanyRoleResultStatus::MissingState, + None, + "No authoritative Epiphany state exists for this thread.".to_string(), + ) + }; + let modeling_result_accepted = modeling_finding.as_ref().is_some_and(|finding| { + thread + .epiphany_state + .as_ref() + .is_some_and(|state| epiphany_role_finding_already_accepted(state, finding)) + }); let (verification_result_status, verification_finding, _) = if let Some(state) = thread.epiphany_state.as_ref() { load_epiphany_role_result_snapshot( @@ -4665,6 +4672,14 @@ impl CodexMessageProcessor { .as_ref() .is_some_and(|state| epiphany_role_finding_already_accepted(state, finding)) }); + let modeling_result_accepted_after_verification = + thread.epiphany_state.as_ref().is_some_and(|state| { + role_finding_accepted_after( + state, + modeling_finding.as_ref(), + verification_finding.as_ref(), + ) + }); let verification_result_allows_implementation = verification_result_accepted && verification_finding .as_ref() @@ -4686,6 +4701,8 @@ impl CodexMessageProcessor { &recommendation, &roles, &source_signals, + modeling_result_accepted, + modeling_result_accepted_after_verification, verification_result_accepted, verification_result_allows_implementation, reorient_finding_accepted, @@ -13997,6 +14014,8 @@ fn map_epiphany_coordinator( recommendation: &ThreadEpiphanyCrrcRecommendation, roles: &[ThreadEpiphanyRoleLane], signals: &ThreadEpiphanyCoordinatorSignals, + modeling_result_accepted: bool, + modeling_result_accepted_after_verification: bool, verification_result_accepted: bool, verification_result_allows_implementation: bool, reorient_finding_accepted: bool, @@ -14048,18 +14067,9 @@ fn map_epiphany_coordinator( "A CRRC reorientation finding needs human review before continuation.", ); } - ThreadEpiphanyCrrcAction::RegatherManually => { - return build( - ThreadEpiphanyCoordinatorAction::RegatherManually, - Some(ThreadEpiphanyRoleId::Reorientation), - recommendation.recommended_scene_action, - true, - false, - "CRRC cannot safely continue automatically; manual regathering is required.", - ); - } ThreadEpiphanyCrrcAction::PrepareCheckpoint | ThreadEpiphanyCrrcAction::LaunchReorientWorker + | ThreadEpiphanyCrrcAction::RegatherManually | ThreadEpiphanyCrrcAction::Continue => {} } @@ -14088,6 +14098,19 @@ fn map_epiphany_coordinator( ); } + if signals.modeling_result_status == ThreadEpiphanyRoleResultStatus::Completed + && !modeling_result_accepted + { + return build( + ThreadEpiphanyCoordinatorAction::ReviewModelingResult, + Some(ThreadEpiphanyRoleId::Modeling), + Some(ThreadEpiphanySceneAction::RoleResult), + true, + false, + "A modeling/checkpoint finding is complete and must be reviewed before verification or implementation continues.", + ); + } + if signals.verification_result_status == ThreadEpiphanyRoleResultStatus::Completed && !verification_result_accepted { @@ -14104,6 +14127,7 @@ fn map_epiphany_coordinator( if signals.verification_result_status == ThreadEpiphanyRoleResultStatus::Completed && verification_result_accepted && !verification_result_allows_implementation + && !modeling_result_accepted_after_verification { return build( ThreadEpiphanyCoordinatorAction::LaunchModeling, @@ -14116,7 +14140,8 @@ fn map_epiphany_coordinator( } if signals.modeling_result_status == ThreadEpiphanyRoleResultStatus::Completed - && !verification_result_accepted + && modeling_result_accepted + && !verification_result_allows_implementation { return build( ThreadEpiphanyCoordinatorAction::LaunchVerification, @@ -14202,6 +14227,17 @@ fn map_epiphany_coordinator( ); } + if recommendation.action == ThreadEpiphanyCrrcAction::RegatherManually { + return build( + ThreadEpiphanyCoordinatorAction::RegatherManually, + Some(ThreadEpiphanyRoleId::Reorientation), + recommendation.recommended_scene_action, + true, + false, + "CRRC cannot safely continue automatically and no fixed specialist lane is currently able to advance the regather.", + ); + } + build( ThreadEpiphanyCoordinatorAction::ContinueImplementation, Some(ThreadEpiphanyRoleId::Implementation), @@ -14238,15 +14274,42 @@ fn epiphany_role_finding_already_accepted( state: &EpiphanyThreadState, finding: &ThreadEpiphanyRoleFinding, ) -> bool { + epiphany_role_finding_accepted_index(state, finding).is_some() +} + +fn role_finding_accepted_after( + state: &EpiphanyThreadState, + later: Option<&ThreadEpiphanyRoleFinding>, + earlier: Option<&ThreadEpiphanyRoleFinding>, +) -> bool { + let Some(later) = later else { + return false; + }; + let Some(later_index) = epiphany_role_finding_accepted_index(state, later) else { + return false; + }; + let Some(earlier) = earlier else { + return true; + }; + let Some(earlier_index) = epiphany_role_finding_accepted_index(state, earlier) else { + return true; + }; + later_index > earlier_index +} + +fn epiphany_role_finding_accepted_index( + state: &EpiphanyThreadState, + finding: &ThreadEpiphanyRoleFinding, +) -> Option { let accepted_kind = match finding.role_id { ThreadEpiphanyRoleId::Modeling => "modeling_result", ThreadEpiphanyRoleId::Verification => "verification_result", ThreadEpiphanyRoleId::Implementation | ThreadEpiphanyRoleId::Reorientation => { - return false; + return None; } }; let accepted_summary = role_finding_summary(finding); - state.recent_evidence.iter().any(|evidence| { + state.recent_evidence.iter().position(|evidence| { evidence.kind == accepted_kind && evidence.status == "accepted" && evidence.summary == accepted_summary @@ -16377,13 +16440,16 @@ pub(crate) async fn maybe_run_epiphany_coordinator_automation_for_turn_boundary( reorient_result_status, reorient_job.as_ref(), ); - let (modeling_result_status, _, _) = load_epiphany_role_result_snapshot( + let (modeling_result_status, modeling_finding, _) = load_epiphany_role_result_snapshot( &state, state_db_ctx.as_ref(), ThreadEpiphanyRoleId::Modeling, EPIPHANY_MODELING_ROLE_BINDING_ID, ) .await; + let modeling_result_accepted = modeling_finding + .as_ref() + .is_some_and(|finding| epiphany_role_finding_already_accepted(&state, finding)); let (verification_result_status, verification_finding, _) = load_epiphany_role_result_snapshot( &state, state_db_ctx.as_ref(), @@ -16394,6 +16460,11 @@ pub(crate) async fn maybe_run_epiphany_coordinator_automation_for_turn_boundary( let verification_result_accepted = verification_finding .as_ref() .is_some_and(|finding| epiphany_role_finding_already_accepted(&state, finding)); + let modeling_result_accepted_after_verification = role_finding_accepted_after( + &state, + modeling_finding.as_ref(), + verification_finding.as_ref(), + ); let verification_result_allows_implementation = verification_result_accepted && verification_finding .as_ref() @@ -16414,6 +16485,8 @@ pub(crate) async fn maybe_run_epiphany_coordinator_automation_for_turn_boundary( &crrc_recommendation, &roles, &source_signals, + modeling_result_accepted, + modeling_result_accepted_after_verification, verification_result_accepted, verification_result_allows_implementation, reorient_finding_accepted, @@ -19668,6 +19741,8 @@ mod tests { false, false, false, + false, + false, ); assert_eq!( @@ -19708,6 +19783,8 @@ mod tests { false, false, false, + false, + false, ); assert_eq!( @@ -19750,6 +19827,8 @@ mod tests { &signals, false, false, + false, + false, true, ); @@ -19806,6 +19885,8 @@ mod tests { false, false, false, + false, + false, ); assert_eq!( @@ -19849,6 +19930,8 @@ mod tests { false, false, false, + false, + false, ); assert_eq!( @@ -19862,6 +19945,65 @@ mod tests { ); } + #[test] + fn map_epiphany_coordinator_uses_fixed_lanes_before_manual_regather() { + let pressure = map_epiphany_pressure(None); + let recommendation = ThreadEpiphanyCrrcRecommendation { + action: ThreadEpiphanyCrrcAction::RegatherManually, + recommended_scene_action: Some(ThreadEpiphanySceneAction::Reorient), + reason: "checkpoint requested regather".to_string(), + }; + let roles = base_coordinator_roles(); + let missing = coordinator_signals( + ThreadEpiphanyCrrcAction::RegatherManually, + ThreadEpiphanyReorientResultStatus::Completed, + ThreadEpiphanyRoleResultStatus::MissingBinding, + ThreadEpiphanyRoleResultStatus::MissingBinding, + ); + + let launch_modeling = map_epiphany_coordinator( + ThreadEpiphanyReorientStateStatus::Ready, + true, + &pressure, + &recommendation, + &roles, + &missing, + false, + false, + false, + false, + true, + ); + assert_eq!( + launch_modeling.action, + ThreadEpiphanyCoordinatorAction::LaunchModeling + ); + + let modeling_done = coordinator_signals( + ThreadEpiphanyCrrcAction::RegatherManually, + ThreadEpiphanyReorientResultStatus::Completed, + ThreadEpiphanyRoleResultStatus::Completed, + ThreadEpiphanyRoleResultStatus::MissingBinding, + ); + let review_modeling = map_epiphany_coordinator( + ThreadEpiphanyReorientStateStatus::Ready, + true, + &pressure, + &recommendation, + &roles, + &modeling_done, + false, + false, + false, + false, + true, + ); + assert_eq!( + review_modeling.action, + ThreadEpiphanyCoordinatorAction::ReviewModelingResult + ); + } + #[test] fn map_epiphany_coordinator_runs_modeling_then_verification_then_continue() { let pressure = map_epiphany_pressure(None); @@ -19888,6 +20030,8 @@ mod tests { false, false, false, + false, + false, ); assert_eq!( launch_modeling.action, @@ -19914,6 +20058,8 @@ mod tests { false, false, false, + false, + false, ); assert_eq!( relaunch_modeling.action, @@ -19926,6 +20072,25 @@ mod tests { ThreadEpiphanyRoleResultStatus::Completed, ThreadEpiphanyRoleResultStatus::MissingBinding, ); + let review_modeling = map_epiphany_coordinator( + ThreadEpiphanyReorientStateStatus::Ready, + true, + &pressure, + &recommendation, + &roles, + &modeling_done, + false, + false, + false, + false, + false, + ); + assert_eq!( + review_modeling.action, + ThreadEpiphanyCoordinatorAction::ReviewModelingResult + ); + assert!(review_modeling.requires_review); + let launch_verification = map_epiphany_coordinator( ThreadEpiphanyReorientStateStatus::Ready, true, @@ -19933,6 +20098,8 @@ mod tests { &recommendation, &roles, &modeling_done, + true, + false, false, false, false, @@ -19959,6 +20126,8 @@ mod tests { &recommendation, &roles, &verification_done, + true, + false, false, false, false, @@ -19978,6 +20147,8 @@ mod tests { &verification_done, true, false, + true, + false, false, ); assert_eq!( @@ -19997,6 +20168,8 @@ mod tests { &roles, &verification_done, true, + false, + true, true, false, ); @@ -20022,6 +20195,8 @@ mod tests { false, false, false, + false, + false, ); assert_eq!( continue_implementation.action,