chore(mitc4-shell-feast-eigen-execution-prep): WIP blocked partial deadline preparation
This commit is contained in:
@@ -523,8 +523,10 @@ Stop 검증이 실패한다.
|
||||
- 명령별 제한시간과 Stop 전체 제한시간 중 더 짧은 값을 적용한다.
|
||||
- 종료 코드가 0이 아니면 즉시 해당 stage를 실패 처리한다.
|
||||
|
||||
Stop 훅의 전체 제한시간은 저장소 탐색, toolchain 탐색, configure, build, test discovery,
|
||||
test를 모두 포함해 1,800초다. `.codex/hooks.json`의 Stop command timeout도 1,800초다.
|
||||
Stop 훅의 shared 전체 제한시간은 저장소 탐색, toolchain 탐색, configure, build,
|
||||
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
@@ -76,7 +76,7 @@ class StepExecutor:
|
||||
"""Phase 디렉토리 안의 step들을 순차 실행하는 하네스."""
|
||||
|
||||
MAX_TRANSPORT_ATTEMPTS = 2
|
||||
CODEX_TIMEOUT_SECONDS = 1800
|
||||
CODEX_TIMEOUT_SECONDS = 14400
|
||||
CODEX_SANDBOX_ENV = "FEM_HARNESS_CODEX_SANDBOX"
|
||||
CODEX_SANDBOX_MODES = frozenset({"workspace-write", "danger-full-access"})
|
||||
PHASE_NAME = re.compile(r"^[a-z0-9]+(?:[._-][a-z0-9]+)*$")
|
||||
|
||||
@@ -24,7 +24,7 @@ from hooks.validation_scope import is_document_only_turn
|
||||
|
||||
|
||||
REENTRY_ENV = "CODEX_STOP_VALIDATION_ACTIVE"
|
||||
TOTAL_TIMEOUT_SECONDS = 1800
|
||||
TOTAL_TIMEOUT_SECONDS = 7200
|
||||
|
||||
|
||||
def _remaining(deadline, clock, stage):
|
||||
|
||||
@@ -71,7 +71,7 @@ class CommandSpec:
|
||||
argv: tuple[str, ...]
|
||||
cwd: Path
|
||||
stage: str
|
||||
timeout_seconds: int = 1800
|
||||
timeout_seconds: int = 7200
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
||||
@@ -63,7 +63,7 @@ def execute_plan(
|
||||
root,
|
||||
*,
|
||||
env=None,
|
||||
total_timeout_seconds=1800,
|
||||
total_timeout_seconds=7200,
|
||||
deadline=None,
|
||||
run=subprocess.run,
|
||||
clock=time.monotonic,
|
||||
|
||||
@@ -183,7 +183,7 @@ def test_invoke_codex_timeout_writes_structured_failure(tmp_path, monkeypatch):
|
||||
assert output["exitCode"] == 124
|
||||
assert output["transportFailure"] == "timeout"
|
||||
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"))
|
||||
assert stored == output
|
||||
|
||||
@@ -232,7 +232,7 @@ def test_execute_single_step_retries_structured_codex_timeout(tmp_path, monkeypa
|
||||
assert len(attempts) == 2
|
||||
index = json.loads((phase_dir / "index.json").read_text(encoding="utf-8"))
|
||||
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"]
|
||||
top = json.loads((phase_dir.parent / "index.json").read_text(encoding="utf-8"))
|
||||
assert top["phases"][0]["status"] == "error"
|
||||
|
||||
@@ -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]
|
||||
Reference in New Issue
Block a user