docs: preserve Ninja failure and bound environment recovery

This commit is contained in:
KOKO\Mimi
2026-09-06 17:36:18 +09:00
parent b81f7cf636
commit 87ffa4f4e5
5 changed files with 316 additions and 8 deletions
@@ -716,3 +716,168 @@ report/top/raw의 현재 hash를 확인한다. Phase control 한 경로만 commi
Native exit를 즉시 전파하고 commit 전후 snapshot 및 clean preflight를 감사한다.
실패하면 bytes/원시 로그/이미 생성된 commit을 보존하고 다음 invocation을 시작하지 않는다.
E8 setup 또는 native 성공을 이 status checkpoint 성공에서 추론하지 않는다.
## R10. Attempt4의 error/history4 보존
[Executor 결과](../../.harness/build/mitc4-eigen-evidence/prep/attempt4/executor-result.json)와
[terminal archive](../../.harness/build/mitc4-eigen-evidence/prep/attempt4/terminal-archive.json)는
native PowerShell1, Codex0, transportFailure null과 report1/controls2만의 변경을 기록한다.
현재 Step0/top은 error/history4다. Classifier37/focused136은 통과했지만 required manual
build는 GoogleTest JSON parsing에서 실패했고 manual CTest/full pytest는 미실행이다.
후속 단독 discovery와 등록 CTest 성공으로 최초 실패를 대체하지 않는다.
[프로세스 관찰](../../.harness/build/mitc4-eigen-evidence/prep/attempt4/root-process-observations.json)의
registered Ninja build/CTest0·362pass 및 Python 두 개0과 외부 pwsh1은 별개의 실제 결과다.
Whole Stop0으로 합치지 않는다. [원시 선택 이벤트](../../.harness/build/mitc4-eigen-evidence/prep/attempt4/raw-audit-selected-events.json)의
item67 direct discovery0은 뒤의 빈 log 조회 때문에 전체 shell1이고 item68은 memo/hash0이다.
176-byte log는 후속 관찰 메모이며 해당 direct 실행 stdout 원본으로 치환하지 않는다.
Root가 frozen 관리/Planning 문서만 exact checkpoint한 뒤 Coordinator가 아래 unchanged
Executor method invocation으로 report1 WIP와 controls2 error 보존을 분리 commit한다.
사용자 요청 [Root 설명문서](msvc-build-access-error.md)의 기존 commit도 origin 이후
허용 문서에 포함하며 현재 bytes/hash를 보존한다. Source/Hook/config/cache 변경,
`_commit_step`, 상태 성공·reset, guard override와 직접 Git commit은 없다.
```powershell
$ErrorActionPreference = 'Stop'
$mfePython = Join-Path (Get-Location).Path '.harness/build/mitc4-eigen-evidence/prep/classifier-correction/pytest-venv/Scripts/python.exe'
if (-not (Test-Path -LiteralPath $mfePython -PathType Leaf)) { throw 'Named Python missing' }
@'
import ast,hashlib,json
from pathlib import Path
from scripts import execute
phase="mitc4-shell-feast-eigen-execution-prep"
origin="23c4ebe526260d584247804d8cbe2f7aa8cc60ac"
ex=execute.StepExecutor(phase); root=Path(ex._root)
assert Path.cwd().resolve()==root
assert Path(ex._run_git_checked("rev-parse","--show-toplevel").stdout.strip()).resolve()==root
assert ex._is_isolated_worktree() and ex._codex_sandbox_mode()=="workspace-write"
assert ex._implementation_agent_settings()==("gpt-5.6-terra","xhigh")
branch,head=ex._git_execution_identity()
assert branch=="refs/heads/feat-"+phase
ex._run_git_checked("merge-base","--is-ancestor",origin,"HEAD")
docs={"docs/mitc4-shell-feast-eigen/"+n for n in ("FEATURED_AGENTS.md","PRD.md","ADR.md","coordination.md","msvc-build-access-error.md","implementation-plan.md","implementation-environment.md","implementation-recovery.md")}
assert set(ex._run_git_checked("diff","--name-only","--no-renames",origin,"HEAD").stdout.splitlines())<=docs
assert not ex._run_git_checked("diff","--cached","--name-only").stdout.strip()
expected={".codex/hooks.json":"e7e5d3c0c4fcd1e33b60680122ea7c42298d5cbde78a7f87d9e0007bf60a0fa4",
"docs/mitc4-shell-feast-eigen/implementation-prep-evidence.md":"fc88f1c943f7f25d7eeece87dbbd967a6007b14b0adce342ced9aa78f4cd20b2",
"phases/index.json":"33d911587305cc96148a3e60f9ca32f9ec114ce75e49118391bcc1ef3a6692c3",
"phases/"+phase+"/index.json":"3f4ce6178172819f3e1ab4310db61bc318348aeea822b806d4dcca676b53a60f",
"phases/"+phase+"/step0-output.json":"c74f18d0b681f6ab7480b825de4458188fb6b204147ab84b08582820039cab61",
"scripts/execute.py":"64f5165fc544b3b17904c633ef3b222de79b52a0960e254c5b5c4eb99e79c2d0",
"tests/test_execute.py":"0eb0c1ca0f25199e00686b7e3ab57672b1390437d09da218a929added0346dd6"}
expected.update({
"docs/mitc4-shell-feast-eigen/msvc-build-access-error.md":"0722dff075e706977d55eb1ac2514d4318b1341adab30a51fc82c744315f1c9f",
".harness/config.json":"3ebdfa80124f1eb96909b2738d6f2a96fecd55cce61150a9cd7ef27d7c5fd892",
".harness/build-ninja-msvc-debug/CMakeCache.txt":"bf024961a7469672d1fd4f9e8b6606fc1ccaac3fa40dea25709ec4c00ddd5d0e",
".harness/build/CMakeCache.txt":"d880104fdc3f1411119ce787c0ddb9a63db23de3f571ba2fedce1c56cb91085b"})
for p,h in expected.items():assert hashlib.sha256((root/p).read_bytes()).hexdigest()==h,p
assert ast.dump(ast.parse((root/"scripts/execute.py").read_text(encoding="utf-8")))==ast.dump(ast.parse(ex._run_git_checked("show",origin+":scripts/execute.py").stdout))
old_hook=json.loads(ex._run_git_checked("show",origin+":.codex/hooks.json").stdout)
assert json.loads((root/".codex/hooks.json").read_bytes())==old_hook
idx=ex._read_phase_index();top=ex._read_top_index()
assert idx["steps"][0]["status"]=="error" and len(idx["steps"][0]["attempt_history"])==4
assert next(p for p in top["phases"] if p["dir"]==phase)["status"]=="error"
owned=ex._step_owned_paths(idx["steps"][0]);assert len(owned)==9
code=["docs/mitc4-shell-feast-eigen/implementation-prep-evidence.md"]
control=["phases/index.json","phases/"+phase+"/index.json"]
assert set(ex._assert_step_scope(owned))==set(code+control)
frozen={p:(root/p).read_bytes() for p in list(owned)+list(expected)+["phases/"+phase+"/step0.md","phases/mitc4-shell-feast-eigen/index.json"]}
for paths,message in (
(code,"chore(mitc4-shell-feast-eigen-execution-prep): WIP prep report with discovery build failure"),
(control,"chore(mitc4-shell-feast-eigen-execution-prep): preserve attempt 4 error metadata")):
assert all((root/p).read_bytes()==v for p,v in frozen.items())
assert ex._git_execution_identity()==(branch,head)
assert ex._commit_paths(paths,message,owned_paths=owned)
new_branch,new_head=ex._git_execution_identity()
assert new_branch==branch and new_head!=head
actual=ex._run_git_checked("diff-tree","--no-commit-id","--name-only","--no-renames","-r",new_head).stdout.splitlines()
assert set(actual)==set(paths)
assert all((root/p).read_bytes()==v for p,v in frozen.items())
assert ex._read_phase_index()==idx and ex._read_top_index()==top
print(json.dumps({"commit":new_head,"paths":actual,"status":"error","history_count":4}))
head=new_head
assert not ex._worktree_paths()
ex._preflight_repository()
print(json.dumps({"head":head,"branch":branch,"clean":True}))
'@ | & $mfePython -B -X utf8 -
$mfeWipExit = $LASTEXITCODE
if ($null -eq $mfeWipExit) { throw 'Native exit unavailable' }
exit $mfeWipExit
```
검사 실패 시 이미 생성된 commit, 남은 dirty 파일과 원시 기록을 보존하고 반환한다.
이 명령 성공은 실패의 안전한 보존일 뿐 prep gate 통과가 아니다.
R11 status edit와 E9 실행은 별도 할당 전 미실행이다. 현재 resolver의 canonical Stop1800
해석은 확인됐지만 정상 override 지원·적용 방법이 미확정이므로 현재는 보존만 진행한다.
기존 exact owned9와 r2 source Correction1회 소진을 유지한다.
## R11. R10 뒤의 별도 status-only 복구
R10 실제 두 commit/clean 감사와 [E9 환경 진입 조건](implementation-environment.md#e9-attempt4-후의-직렬-ninja-검증)이
해소된 뒤 Coordinator가 별도 할당할 때만 Planning이 Step0의 `status` 한 값을
`error -> pending`으로 바꾼다. 나머지 모든 JSON/bytes, error_message/failed_at/started_at,
created_at, history4, owned_paths, top/raw는 보존한다. 이번 문서 작업에서는 바꾸지 않는다.
그 한 값 diff/HEAD/staging/hash 감사를 받은 Coordinator만 아래 control checkpoint를 실행한다.
```powershell
$ErrorActionPreference = 'Stop'
$mfePython = Join-Path (Get-Location).Path '.harness/build/mitc4-eigen-evidence/prep/classifier-correction/pytest-venv/Scripts/python.exe'
if (-not (Test-Path -LiteralPath $mfePython -PathType Leaf)) { throw 'Named Python missing' }
@'
import hashlib,json
from pathlib import Path
from scripts import execute
phase="mitc4-shell-feast-eigen-execution-prep"
ex=execute.StepExecutor(phase);root=Path(ex._root)
assert Path.cwd().resolve()==root
assert Path(ex._run_git_checked("rev-parse","--show-toplevel").stdout.strip()).resolve()==root
assert ex._is_isolated_worktree() and ex._codex_sandbox_mode()=="workspace-write"
branch,head=ex._git_execution_identity();assert branch=="refs/heads/feat-"+phase
origin="23c4ebe526260d584247804d8cbe2f7aa8cc60ac"
ex._run_git_checked("merge-base","--is-ancestor",origin,"HEAD")
report="docs/mitc4-shell-feast-eigen/implementation-prep-evidence.md"
controls={"phases/index.json","phases/"+phase+"/index.json"}
docs={"docs/mitc4-shell-feast-eigen/"+n for n in ("FEATURED_AGENTS.md","PRD.md","ADR.md",
"coordination.md","msvc-build-access-error.md","implementation-plan.md","implementation-environment.md","implementation-recovery.md")}
assert set(ex._run_git_checked("diff","--name-only","--no-renames",origin,"HEAD").stdout.splitlines())<=docs|controls|{report}
for paths,probe in ((controls,"phases/"+phase+"/index.json"),({report},report)):
commit=ex._run_git_checked("log","-1","--format=%H","--",probe).stdout.strip()
actual=set(ex._run_git_checked("diff-tree","--no-commit-id","--name-only","--no-renames","-r",commit).stdout.splitlines())
assert actual==paths
protected={".codex/hooks.json":"e7e5d3c0c4fcd1e33b60680122ea7c42298d5cbde78a7f87d9e0007bf60a0fa4",
"scripts/execute.py":"64f5165fc544b3b17904c633ef3b222de79b52a0960e254c5b5c4eb99e79c2d0",
"tests/test_execute.py":"0eb0c1ca0f25199e00686b7e3ab57672b1390437d09da218a929added0346dd6",
report:"fc88f1c943f7f25d7eeece87dbbd967a6007b14b0adce342ced9aa78f4cd20b2",
"phases/index.json":"33d911587305cc96148a3e60f9ca32f9ec114ce75e49118391bcc1ef3a6692c3",
"phases/"+phase+"/step0-output.json":"c74f18d0b681f6ab7480b825de4458188fb6b204147ab84b08582820039cab61",
".harness/config.json":"3ebdfa80124f1eb96909b2738d6f2a96fecd55cce61150a9cd7ef27d7c5fd892",
".harness/build-ninja-msvc-debug/CMakeCache.txt":"bf024961a7469672d1fd4f9e8b6606fc1ccaac3fa40dea25709ec4c00ddd5d0e",
".harness/build/CMakeCache.txt":"d880104fdc3f1411119ce787c0ddb9a63db23de3f571ba2fedce1c56cb91085b"}
for p,h in protected.items():assert hashlib.sha256((root/p).read_bytes()).hexdigest()==h,p
path="phases/"+phase+"/index.json"
before=json.loads(ex._run_git_checked("show","HEAD:"+path).stdout)
assert before["steps"][0]["status"]=="error" and len(before["steps"][0]["attempt_history"])==4
expected=json.loads(json.dumps(before));expected["steps"][0]["status"]="pending"
assert ex._read_phase_index()==expected
assert set(ex._assert_step_scope(()))=={path}
assert not ex._run_git_checked("diff","--cached","--name-only").stdout.strip()
frozen={p:(root/p).read_bytes() for p in [path,*protected]}
assert ex._commit_paths([path],"chore(mitc4-shell-feast-eigen-execution-prep): queue serial Ninja environment recovery")
new_branch,new_head=ex._git_execution_identity()
assert new_branch==branch and new_head!=head
assert ex._run_git_checked("diff-tree","--no-commit-id","--name-only","-r",new_head).stdout.splitlines()==[path]
assert all((root/p).read_bytes()==v for p,v in frozen.items())
assert ex._read_phase_index()==expected and not ex._worktree_paths()
ex._preflight_repository()
print(json.dumps({"commit":new_head,"status":"pending","history_count":4,"clean":True}))
'@ | & $mfePython -B -X utf8 -
$mfeStatusExit = $LASTEXITCODE
if ($null -eq $mfeStatusExit) { throw 'Native exit unavailable' }
exit $mfeStatusExit
```
R10 report1/controls2 commit 경로와 HEAD의 error/history4를 검사하므로 미보존 상태에서
pending 재실행을 시작하지 않는다. 실제 source/Hook/config 및 기존 cache는 그대로다.
각 native exit와 clean을 확인하고, 다음 E9 invocation은 Coordinator가 새 process로
별도 할당한다. 실패를 재분류해 새 Correction budget을 만들거나 자동 재시도하지 않는다.