Remediates reviewer findings F1, F2, and F3 from review 483 on PR #770. F1 (blocking): _current_runtime_mode_report derived the runtime-gate alignment input as realpath(workspace_root) == process_project_root, redefining workspace_roots_aligned. Its established meaning is ctx["roots_aligned"] (canonical_repo_root == process_project_root) — a repository-level question. Because the global worktree rule requires all task work to live in a branches/ worktree, the old derivation reported aligned False for exactly the sessions that are configured correctly, raising unsafe_process_root_workspace_alignment and refusing every non-read operation once an author, reviewer, or merger namespace held a worktree binding. The gate is now fed ctx["roots_aligned"]. F2 (blocking): _current_runtime_mode_report cached its first result into _STARTUP_RUNTIME_MODE, which was initialised to None rather than captured at import, and the read-only refresh=True path seeded it too. Session-scoped fields (active_task_workspace and its derived alignment) and mutable fields (dirty_files) were frozen for the process lifetime, so the acceptance criterion 7 dirty-runtime blocker stopped applying after the snapshot and one session's binding decided alignment for every later session. Immutable process facts (process root, branch, head, checkout-ness) are now captured at import as _STARTUP_RUNTIME_FACTS, matching the #420 parity baseline. Dirty state, workspace binding, and alignment are recomputed on every call. A new stable_control_runtime.observe_dirty_files() splits out the one runtime fact that legitimately changes during a process lifetime; observe_runtime() delegates to it so the parsing lives in one place. refresh is retained for the read-only reporting path but no longer selects a cache, so a read-only call can neither seed nor weaken a later mutation decision. F3 (major): no test exercised the real derivation — every server-wiring test asserting a healthy runtime permits mutations patched _current_runtime_mode_report with a fixture whose workspace_roots_aligned was True. New TestServerWiringRealDerivation patches only the derivation's inputs and lets the real function build the report: - a clean stable control checkout plus a correctly bound branches/ worktree permits an otherwise authorized mutation; - misaligned process/canonical roots fail closed; - newly dirty task state is detected after an earlier clean read; - a read-only refresh cannot freeze a permissive mutation result; - an unresolvable binding reports unknown alignment, never alignment proof. Acceptance criteria 6, 8, 10, and 11 are unchanged and untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
@@ -597,6 +597,20 @@ def _git_capture(root: str, *args: str) -> str | None:
|
||||
return (res.stdout or "").strip() or None
|
||||
|
||||
|
||||
def observe_dirty_files(process_root: str | None) -> list[str]:
|
||||
"""Read the live dirty-file list at *process_root*.
|
||||
|
||||
Split out from :func:`observe_runtime` because dirtiness is the one runtime
|
||||
fact that legitimately changes during a process lifetime. The mutation gate
|
||||
must re-read it per call rather than trust a startup snapshot, or a checkout
|
||||
that goes dirty after the snapshot is never blocked again (#615).
|
||||
"""
|
||||
if not process_root:
|
||||
return []
|
||||
porcelain = _git_capture(process_root, "status", "--porcelain") or ""
|
||||
return [line[3:].strip() for line in porcelain.splitlines() if line.strip()]
|
||||
|
||||
|
||||
def observe_runtime(process_root: str | None) -> dict:
|
||||
"""Read the runtime facts classification needs from *process_root*.
|
||||
|
||||
@@ -618,8 +632,7 @@ def observe_runtime(process_root: str | None) -> dict:
|
||||
branch = _git_capture(process_root, "rev-parse", "--abbrev-ref", "HEAD")
|
||||
if branch == "HEAD": # detached HEAD has no branch name
|
||||
branch = None
|
||||
porcelain = _git_capture(process_root, "status", "--porcelain") or ""
|
||||
dirty = [line[3:].strip() for line in porcelain.splitlines() if line.strip()]
|
||||
dirty = observe_dirty_files(process_root)
|
||||
return {
|
||||
"checkout_branch": branch,
|
||||
"runtime_head": _git_capture(process_root, "rev-parse", "HEAD"),
|
||||
|
||||
Reference in New Issue
Block a user