Skip to content

Vesper Review — fix/startup-mode-reset

To: Orion (he/him), Katja (Captain) From: Vesper (she/her) CC: Atlas (he/him) Date: 2026-04-21 Status: APPROVED — apply and merge


Verdict: APPROVED

2 commits, 3 new tests, 250/250 regression green. Spec compliance confirmed on all points. No deviations. Apply instructions correct (defensive branch delete, Get-ChildItem form). Cleared for merge.


Spec Compliance

Fix location correct: _startup()'s parent_session_id is None block at ~line 578. The three new calls (KEY_MODE → MODE_OK, KEY_DEGRADED_SINCE → "", KEY_DEGRADED_REASON → "") are co-located with the existing halt.reason / halt.detail clears — correct placement, same exception handler, same scope. ✅

No new imports: KEY_MODE, KEY_DEGRADED_SINCE, KEY_DEGRADED_REASON, MODE_OK were already imported from neo_engine.inventory_truth_checker at the top of main_loop.py. Confirmed by the patch diff (0 import changes in commit 1). ✅

Recovery restart path preserved: The fix is inside if parent_session_id is None:. Recovery restarts (parent_session_id is not None) never enter this block — they inherit parent state by omission, unchanged. ✅

Comment accurate: The FLAG-041 follow-up comment in the fix correctly names the causal chain (MODE_HALT persists → tick 1 mode gate fires → no halt.reason → unexpected_halt). Flags the recovery restart exception explicitly. ✅

Test 1 (canonical S43 regression): Seeds MODE_HALT + stale degraded_since + degraded_reason, runs fresh _startup(parent_session_id=None), asserts all three flip to their clean defaults. This is the exact failure mode — the right test, the right path. ✅

Test 2 (mid-DEGRADED crash path): Same contract for MODE_DEGRADED carry-over. Covers the case where the engine crashes or is killed while in DEGRADED — equally important to pin. ✅

Test 3 (recovery restart preservation): parent_session_id=37, confirms all three fields survive startup unchanged. Symmetric with the existing test_startup_preserves_halt_reason_on_recovery_restart contract. ✅

Apply instructions: Defensive git branch -D fix/startup-mode-reset 2>$null present. PowerShell form Get-ChildItem ... | Sort-Object Name | ForEach-Object { git am $_.FullName } correct. Patch path is the workspace patches directory. ✅


Ordering Note (informational — not a blocker)

Orion's branch was built on main before fix/halt-reason-taxonomy-leak (FLAG-041) merged. The test count in his delivery (7 = 4 pre-existing + 3 new) reflects that pre-merge base. On Katja's machine, main already includes FLAG-041 (8 tests). Applied on top: test total will be 11 (8 + 3), all green.

No conflict expected. The two branches touch different hunks: - main_loop.py: FLAG-041 at ~line 4282, this branch at ~line 578 — no overlap. - test_halt_reason_lifecycle.py: FLAG-041 added TestShutdownHaltReasonPreservationFlag041 before TestStartupHaltReasonLifecycle; this branch appends TestStartupModeReset after TestStartupHaltReasonLifecycle. git am uses context lines — applies cleanly in either order.


Deviations

None.


Apply Instructions

cd C:\Users\Katja\Documents\NEO GitHub\neo-2026
git fetch
git checkout main
git pull
git branch -D fix/startup-mode-reset 2>$null
git checkout -b fix/startup-mode-reset
Get-ChildItem "C:\Users\Katja\Documents\Claude Homebase Neo\02 Projects\NEO Trading Engine\patches\fix-startup-mode-reset" -Filter "*.patch" | Sort-Object Name | ForEach-Object { git am $_.FullName }
python -m pytest tests/test_halt_reason_lifecycle.py -v

Expected on Katja's machine (main already has FLAG-041): 11 passed (8 from FLAG-041 merge + 3 new).

Then merge to main and push.


What This Means

After this merge, S43 startup after a prior HALT session will behave:

Field Pre-fix (S43 on post-FLAG-041 code) Post-fix
inventory_truth.mode at session start halt (stale from S42) ok (reset at startup)
Tick 1 mode gate Fires, returns False Passes cleanly
halt.reason at session end unexpected_halt Not set at startup — session runs
Session outcome Insta-halt (0.94s) Runs to duration or real trigger

S43 is cleared to run after this merge.

— Vesper