chore(mitc4-shell-feast-eigen-execution-prep): WIP blocked partial deadline preparation

This commit is contained in:
KOKO\Mimi
2026-09-06 10:56:52 +09:00
parent 7dca487da1
commit 6e00dba117
8 changed files with 268 additions and 8 deletions
+4 -2
View File
@@ -523,8 +523,10 @@ Stop 검증이 실패한다.
- 명령별 제한시간과 Stop 전체 제한시간 중 더 짧은 값을 적용한다. - 명령별 제한시간과 Stop 전체 제한시간 중 더 짧은 값을 적용한다.
- 종료 코드가 0이 아니면 즉시 해당 stage를 실패 처리한다. - 종료 코드가 0이 아니면 즉시 해당 stage를 실패 처리한다.
Stop 훅의 전체 제한시간은 저장소 탐색, toolchain 탐색, configure, build, test discovery, Stop 훅의 shared 전체 제한시간은 저장소 탐색, toolchain 탐색, configure, build,
test를 모두 포함해 1,800초다. `.codex/hooks.json`의 Stop command timeout도 1,800초다. test discovery, test를 모두 포함해 7,200초다. 각 command는 자체 제한시간과 남은
shared deadline 중 더 짧은 값을 사용한다. `.codex/hooks.json`의 Stop command timeout은
진단 여유를 포함해 7,500초다.
실패 메시지에는 다음 진단 정보를 포함한다. 실패 메시지에는 다음 진단 정보를 포함한다.
@@ -0,0 +1,58 @@
# Execution-prep evidence
- Feature ID: `mitc4-shell-feast-eigen`
- Owner: `implementation-agent`
- Input contract revision: `r2`
- Step: `mitc4-shell-feast-eigen-execution-prep` / `0 bounded-execution-deadlines`
- Outcome: `blocked-environment`
## RED
`uv run --with pytest python -B -m pytest -v -rs tests/test_harness_timeouts.py`
returned exit code `1` twice. The first run exposed one test-double setup issue; after
that test-only correction, the second run failed `4/4` on the intended pre-change
defaults: Executor `1800`, Stop shared deadline `1800`, `CommandSpec` `1800`, and
registered Stop `1800`.
## Applied scope and partial GREEN
The following owned runtime defaults now match the approved finite limits:
- `scripts/execute.py`: `StepExecutor.CODEX_TIMEOUT_SECONDS = 14400`
- `scripts/hooks/stop_validation.py`: `TOTAL_TIMEOUT_SECONDS = 7200`
- `scripts/msvc_harness/models.py`: `CommandSpec.timeout_seconds = 7200`
- `scripts/msvc_harness/process.py`: `execute_plan(... total_timeout_seconds=7200)`
- `docs/HARNESS_WORKFLOW.md`: shared `7200`-second deadline and `7500`-second
registered Stop explanation
The next focused command returned exit code `1`: `2 passed, 2 failed`. The executor
and shared-deadline/process tests passed. The remaining hook contract test fails
because `.codex/hooks.json` still contains registered Stop timeout `1800`.
## Blocker and unexecuted verification
The assigned `apply_patch` edit for `.codex/hooks.json` was rejected twice by the
workspace sandbox as outside the writable project scope. The file is therefore
unchanged; no alternate write mechanism was used. This is an `environment` failure,
not a tolerance, contract, or implementation reinterpretation.
Focused verification cannot reach exit `0` until the approved `1800 -> 7500` hook
edit is writable. Consequently the prescribed CMake configure/build/CTest and full
pytest verification were not run. No reference comparison is applicable to this
operational Step. The source currently loads an Executor wrapper deadline of `14400`,
but this session's actual registered Stop/Codex exit remains unobserved and the
registered config still declares `1800`.
`git diff --check` completed with exit code `0`. A later read-only JSON status check
through `uv run python -B -c ...` could not initialize the user `uv` cache because
access to `C:\Users\baram\AppData\Local\uv\cache\sdists-v9\.git` was denied; it did
not modify the Step status and is additional environment evidence rather than a
replacement for the required verification.
## Handoff
Required environment action: make the owned repository file `.codex/hooks.json`
writable to this Step, then apply only its registered Stop timeout change to `7500`
and rerun Focused VERIFY followed by Full VERIFY. Existing `started_at` is preserved;
no `attempt_history`, timestamp, branch, commit, upstream contract, or reference
artifact was changed.
+1 -1
View File
@@ -76,7 +76,7 @@ class StepExecutor:
"""Phase 디렉토리 안의 step들을 순차 실행하는 하네스.""" """Phase 디렉토리 안의 step들을 순차 실행하는 하네스."""
MAX_TRANSPORT_ATTEMPTS = 2 MAX_TRANSPORT_ATTEMPTS = 2
CODEX_TIMEOUT_SECONDS = 1800 CODEX_TIMEOUT_SECONDS = 14400
CODEX_SANDBOX_ENV = "FEM_HARNESS_CODEX_SANDBOX" CODEX_SANDBOX_ENV = "FEM_HARNESS_CODEX_SANDBOX"
CODEX_SANDBOX_MODES = frozenset({"workspace-write", "danger-full-access"}) CODEX_SANDBOX_MODES = frozenset({"workspace-write", "danger-full-access"})
PHASE_NAME = re.compile(r"^[a-z0-9]+(?:[._-][a-z0-9]+)*$") PHASE_NAME = re.compile(r"^[a-z0-9]+(?:[._-][a-z0-9]+)*$")
+1 -1
View File
@@ -24,7 +24,7 @@ from hooks.validation_scope import is_document_only_turn
REENTRY_ENV = "CODEX_STOP_VALIDATION_ACTIVE" REENTRY_ENV = "CODEX_STOP_VALIDATION_ACTIVE"
TOTAL_TIMEOUT_SECONDS = 1800 TOTAL_TIMEOUT_SECONDS = 7200
def _remaining(deadline, clock, stage): def _remaining(deadline, clock, stage):
+1 -1
View File
@@ -71,7 +71,7 @@ class CommandSpec:
argv: tuple[str, ...] argv: tuple[str, ...]
cwd: Path cwd: Path
stage: str stage: str
timeout_seconds: int = 1800 timeout_seconds: int = 7200
@dataclass(frozen=True) @dataclass(frozen=True)
+1 -1
View File
@@ -63,7 +63,7 @@ def execute_plan(
root, root,
*, *,
env=None, env=None,
total_timeout_seconds=1800, total_timeout_seconds=7200,
deadline=None, deadline=None,
run=subprocess.run, run=subprocess.run,
clock=time.monotonic, clock=time.monotonic,
+2 -2
View File
@@ -183,7 +183,7 @@ def test_invoke_codex_timeout_writes_structured_failure(tmp_path, monkeypatch):
assert output["exitCode"] == 124 assert output["exitCode"] == 124
assert output["transportFailure"] == "timeout" assert output["transportFailure"] == "timeout"
assert output["stdout"] == "partial stdout" assert output["stdout"] == "partial stdout"
assert "Codex timed out after 1800 seconds." in output["stderr"] assert "Codex timed out after 14400 seconds." in output["stderr"]
stored = json.loads((phase_dir / "step0-output.json").read_text(encoding="utf-8")) stored = json.loads((phase_dir / "step0-output.json").read_text(encoding="utf-8"))
assert stored == output assert stored == output
@@ -232,7 +232,7 @@ def test_execute_single_step_retries_structured_codex_timeout(tmp_path, monkeypa
assert len(attempts) == 2 assert len(attempts) == 2
index = json.loads((phase_dir / "index.json").read_text(encoding="utf-8")) index = json.loads((phase_dir / "index.json").read_text(encoding="utf-8"))
assert index["steps"][0]["status"] == "error" assert index["steps"][0]["status"] == "error"
assert "Codex timed out after 1800 seconds." in index["steps"][0]["error_message"] assert "Codex timed out after 14400 seconds." in index["steps"][0]["error_message"]
assert index["steps"][0]["attempt_history"][-1]["reason"] == index["steps"][0]["error_message"] assert index["steps"][0]["attempt_history"][-1]["reason"] == index["steps"][0]["error_message"]
top = json.loads((phase_dir.parent / "index.json").read_text(encoding="utf-8")) top = json.loads((phase_dir.parent / "index.json").read_text(encoding="utf-8"))
assert top["phases"][0]["status"] == "error" assert top["phases"][0]["status"] == "error"
+200
View File
@@ -0,0 +1,200 @@
import io
import json
import subprocess
from pathlib import Path
from types import SimpleNamespace
import pytest
from scripts import execute
from scripts.hooks import stop_validation
from scripts.msvc_harness.models import (
CommandSpec,
ProjectKind,
ValidationPlan,
ValidationStep,
)
from scripts.msvc_harness.process import ValidationFailure, execute_plan
def WriteExecutor(tmp_path, monkeypatch):
phase_dir = tmp_path / "phases" / "timeout-phase"
phase_dir.mkdir(parents=True)
(phase_dir.parent / "index.json").write_text(
json.dumps({"phases": [{"dir": "timeout-phase", "status": "pending"}]}),
encoding="utf-8",
)
(phase_dir / "index.json").write_text(
json.dumps(
{
"project": "Harness Tests",
"phase": "timeout-phase",
"steps": [{"step": 0, "name": "timeout", "status": "pending"}],
}
),
encoding="utf-8",
)
(phase_dir / "step0.md").write_text("timeout", encoding="utf-8")
profile = tmp_path / ".codex" / "agents" / "implementation-agent.toml"
profile.parent.mkdir(parents=True)
profile.write_text(
'model = "gpt-5.6-terra"\n'
'model_reasoning_effort = "xhigh"\n'
'developer_instructions = """test"""\n',
encoding="utf-8",
)
monkeypatch.setattr(execute, "ROOT", tmp_path)
return execute.StepExecutor("timeout-phase")
def TestClock(values):
iterator = iter(values)
return lambda: next(iterator)
def test_executor_uses_finite_default_for_actual_timeout(tmp_path, monkeypatch):
executor = WriteExecutor(tmp_path, monkeypatch)
timeouts = []
def timeout_run(command, **kwargs):
timeouts.append(kwargs["timeout"])
raise subprocess.TimeoutExpired(command, kwargs["timeout"])
monkeypatch.setattr(execute.subprocess, "run", timeout_run)
output = executor._invoke_codex({"step": 0, "name": "timeout"}, "")
assert execute.StepExecutor.CODEX_TIMEOUT_SECONDS == 14400
assert timeouts == [14400]
assert output["transportFailure"] == "timeout"
assert "Codex timed out after 14400 seconds." in output["stderr"]
def test_stop_passes_one_shared_deadline_to_root_toolchain_and_plan(
tmp_path, monkeypatch
):
root = tmp_path
selection = SimpleNamespace(kind=stop_validation.ProjectKind.CMAKE)
calls = {}
monkeypatch.setattr(stop_validation, "load_config", lambda _: object())
monkeypatch.setattr(
stop_validation,
"discover_project",
lambda _, __: SimpleNamespace(selection=selection),
)
def discover_toolchain(kind, *, deadline, clock):
calls["toolchain"] = (kind, deadline, clock())
return object()
class Adapter:
def create_plan(self, *args):
return object()
def capture_plan(plan, plan_root, *, env, deadline, clock):
calls["plan"] = (plan, plan_root, env, deadline, clock())
monkeypatch.setattr(stop_validation, "discover_toolchain", discover_toolchain)
monkeypatch.setattr(stop_validation, "CMakeAdapter", Adapter)
monkeypatch.setattr(stop_validation, "MSBuildAdapter", Adapter)
monkeypatch.setattr(stop_validation, "execute_plan", capture_plan)
clock = lambda: 100.0
assert stop_validation.run_validations(root, clock=clock) == (True, "")
assert stop_validation.TOTAL_TIMEOUT_SECONDS == 7200
assert calls["toolchain"][0].value == ProjectKind.CMAKE.value
assert calls["toolchain"][1] == 7300.0
assert calls["plan"][1] == root
assert calls["plan"][3] == 7300.0
assert calls["plan"][2][stop_validation.REENTRY_ENV] == "1"
observed = {}
payload = {"cwd": str(root), "stop_hook_active": False}
monkeypatch.setattr(stop_validation.sys, "stdin", io.StringIO(json.dumps(payload)))
monkeypatch.setattr(stop_validation, "is_document_only_turn", lambda *_: False)
monkeypatch.setattr(
stop_validation,
"_project_root",
lambda *args, **kwargs: observed.setdefault("root", kwargs["deadline"]) and root,
)
monkeypatch.setattr(
stop_validation,
"run_validations",
lambda *args, **kwargs: observed.setdefault("validation", kwargs["deadline"])
and (True, ""),
)
assert stop_validation.main(clock=clock) == 0
assert observed == {"root": 7300.0, "validation": 7300.0}
def test_command_defaults_and_shared_remaining_cap_are_preserved(tmp_path):
default_command = CommandSpec(("default",), tmp_path, "default")
assert default_command.timeout_seconds == 7200
plan = ValidationPlan(
ProjectKind.CMAKE,
(
ValidationStep(default_command),
ValidationStep(CommandSpec(("remaining",), tmp_path, "remaining")),
ValidationStep(CommandSpec(("explicit",), tmp_path, "explicit", 5)),
),
)
observed = []
def run(command, **kwargs):
observed.append(kwargs["timeout"])
return subprocess.CompletedProcess(command, 0, stdout="", stderr="")
execute_plan(
plan,
tmp_path,
run=run,
clock=TestClock((0.0, 0.0, 2000.0, 2000.0)),
)
assert observed == [7200, 5200.0, 5]
expired_plan = ValidationPlan(
ProjectKind.CMAKE,
(ValidationStep(default_command),),
)
with pytest.raises(ValidationFailure, match="timed out before it could start"):
execute_plan(expired_plan, tmp_path, deadline=0.0, clock=lambda: 0.0)
def nonzero(command, **kwargs):
return subprocess.CompletedProcess(command, 1, stdout="", stderr="failed")
with pytest.raises(ValidationFailure, match="failed with exit code 1"):
execute_plan(expired_plan, tmp_path, run=nonzero, clock=TestClock((0.0, 0.0)))
def test_registered_hook_and_one_time_invocation_contract(monkeypatch):
root = Path(__file__).resolve().parents[1]
hooks = json.loads((root / ".codex" / "hooks.json").read_text(encoding="utf-8"))
pre_tool = hooks["hooks"]["PreToolUse"][0]["hooks"][0]
stop = hooks["hooks"]["Stop"][0]["hooks"][0]
assert stop["timeout"] == 7500
assert pre_tool["timeout"] == 30
assert execute.StepExecutor.CODEX_TIMEOUT_SECONDS > stop["timeout"]
assert stop["timeout"] > stop_validation.TOTAL_TIMEOUT_SECONDS
assert all(
timeout > 0
for timeout in (
execute.StepExecutor.CODEX_TIMEOUT_SECONDS,
stop["timeout"],
stop_validation.TOTAL_TIMEOUT_SECONDS,
CommandSpec(("test",), root, "test").timeout_seconds,
)
)
main_calls = []
monkeypatch.setattr(execute.StepExecutor, "CODEX_TIMEOUT_SECONDS", 1800)
monkeypatch.setattr(
execute,
"main",
lambda: main_calls.append(execute.StepExecutor.CODEX_TIMEOUT_SECONDS),
)
exec(
"execute.StepExecutor.CODEX_TIMEOUT_SECONDS = 14400; execute.main()",
{"execute": execute},
)
assert main_calls == [14400]