Non-bug cleanups from the PR #86 final review, batched:
- Centralize the restore-latch preconditions in
runs.rearm_escalation (single seam): the worktree/containment checks live in cli._resolve_restore_patch while the sentinel + spec-less rejections live in rearm_escalation — any future caller of rearm_escalation (TUI restore parity, programmatic) bypasses the CLI half. The CLI's pre-session fail-fast can call the same shared validator.
- One restore-path normalizer: the maybe-relative→absolute normalization of
task.restore_patch exists in three copies resolving against two different bases (engine._restore_patch → workspace.root; verify.verify_dev_exclude_relpaths → paths.project; cli._resolve_restore_patch → project). The CLI always latches an absolute resolved path today, so the relative branches are dead — either delete them and document the latch as absolute-only (model.py currently documents 'repo-relative-or-absolute'), or extract one named helper.
- Vacuous phase guard in
engine._restore_patch: at its sole call site the task is always DEV_RUNNING, so if task.phase == Phase.DEV_RUNNING is always true — and the comment's PENDING case wouldn't survive _escalate anyway (PENDING→ESCALATED is not a legal transition). Replace with an unconditional advance + call-site-invariant comment.
- T4 altitude:
verify_dev_exclude_relpaths's restore_patch param is threaded through three call sites above _verify_shared_gates — the anti-drift seam that already receives task. Deriving the exclusion inside the shared gate removes the default-None foot-gun for a future fourth mode.
- Test dedup: third
_escalated_run fixture copy (test_runs / test_resolve / test_cli — promote a parameterized builder to conftest); E2E scaffolds hand-roll the skill list three times (loop over install.DEV_BASE_SKILLS like tests/test_install.py does); tests/test_engine.py::_restoring_dev_effect re-builds the result_json envelope that conftest's dev_effect factory owns.
Non-bug cleanups from the PR #86 final review, batched:
runs.rearm_escalation(single seam): the worktree/containment checks live incli._resolve_restore_patchwhile the sentinel + spec-less rejections live inrearm_escalation— any future caller ofrearm_escalation(TUI restore parity, programmatic) bypasses the CLI half. The CLI's pre-session fail-fast can call the same shared validator.task.restore_patchexists in three copies resolving against two different bases (engine._restore_patch→workspace.root;verify.verify_dev_exclude_relpaths→paths.project;cli._resolve_restore_patch→project). The CLI always latches an absolute resolved path today, so the relative branches are dead — either delete them and document the latch as absolute-only (model.py currently documents 'repo-relative-or-absolute'), or extract one named helper.engine._restore_patch: at its sole call site the task is always DEV_RUNNING, soif task.phase == Phase.DEV_RUNNINGis always true — and the comment's PENDING case wouldn't survive_escalateanyway (PENDING→ESCALATED is not a legal transition). Replace with an unconditional advance + call-site-invariant comment.verify_dev_exclude_relpaths'srestore_patchparam is threaded through three call sites above_verify_shared_gates— the anti-drift seam that already receivestask. Deriving the exclusion inside the shared gate removes the default-None foot-gun for a future fourth mode._escalated_runfixture copy (test_runs / test_resolve / test_cli — promote a parameterized builder to conftest); E2E scaffolds hand-roll the skill list three times (loop overinstall.DEV_BASE_SKILLSliketests/test_install.pydoes);tests/test_engine.py::_restoring_dev_effectre-builds the result_json envelope that conftest'sdev_effectfactory owns.