fix(registrar): bound slow consistency runs
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02b22-9638-76d2-bbff-b7ea1770b118
This commit is contained in:
parent
7a15f1da21
commit
7b9fdaa734
4 changed files with 65 additions and 2 deletions
|
|
@ -86,6 +86,10 @@ the consistency report, but do not block assignment of unrelated canonical
|
|||
records. Empty-projection bootstrap remains a full-repository operation and
|
||||
therefore still requires the complete identity set to pass.
|
||||
|
||||
The child consistency pass has a 15-minute ceiling. Exceeding it returns a
|
||||
structured `statehub_timeout` result with output tails and the post-timeout
|
||||
missing-ID scan; it must not terminate the caller with an uncaught traceback.
|
||||
|
||||
Work: `RMGR-WP-0005-T01`.
|
||||
|
||||
## Coding-assistant commit provenance
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ from repo_manager.parse.workplan import parse_workplan_file
|
|||
from repo_manager.record_identity import scan_record_identities
|
||||
|
||||
LOCK_PATH = Path("/tmp/repo-manager-identifier-registrar.lock")
|
||||
STATEHUB_TIMEOUT_SECONDS = 900
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
@ -382,7 +383,7 @@ def _run_statehub(command: list[str], *, env: dict[str, str]) -> subprocess.Comp
|
|||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
timeout=300,
|
||||
timeout=STATEHUB_TIMEOUT_SECONDS,
|
||||
env=env,
|
||||
)
|
||||
|
||||
|
|
@ -573,7 +574,26 @@ def registrar_reconcile(
|
|||
]
|
||||
if bootstrap_empty_projection:
|
||||
command.append("--bootstrap-empty-projection")
|
||||
completed = _run_statehub(command, env=child_env)
|
||||
try:
|
||||
completed = _run_statehub(command, env=child_env)
|
||||
except subprocess.TimeoutExpired as exc:
|
||||
stdout = exc.stdout.decode() if isinstance(exc.stdout, bytes) else (exc.stdout or "")
|
||||
stderr = exc.stderr.decode() if isinstance(exc.stderr, bytes) else (exc.stderr or "")
|
||||
evidence["statehub_stdout_tail"] = stdout[-4000:]
|
||||
evidence["statehub_stderr_tail"] = stderr[-2000:]
|
||||
evidence["missing_after"] = _missing_identifiers(repo)
|
||||
return RegistrarResult(
|
||||
"failed",
|
||||
evidence,
|
||||
{
|
||||
"code": "statehub_timeout",
|
||||
"message": (
|
||||
"State Hub reconciliation exceeded "
|
||||
f"{STATEHUB_TIMEOUT_SECONDS} seconds"
|
||||
),
|
||||
},
|
||||
cid,
|
||||
)
|
||||
|
||||
evidence["statehub_exit_code"] = completed.returncode
|
||||
evidence["statehub_stdout_tail"] = completed.stdout[-4000:]
|
||||
|
|
|
|||
|
|
@ -8,6 +8,12 @@ import pytest
|
|||
from repo_manager.commands import registrar_reconcile as rr
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def isolate_registrar_lock(tmp_path: Path, monkeypatch) -> None:
|
||||
"""Keep unit tests independent from a live workstation registrar run."""
|
||||
monkeypatch.setattr(rr, "LOCK_PATH", tmp_path / "registrar.lock")
|
||||
|
||||
|
||||
def _git(repo: Path, *args: str) -> None:
|
||||
subprocess.run(["git", *args], cwd=repo, check=True, capture_output=True)
|
||||
|
||||
|
|
@ -324,6 +330,33 @@ def test_accepts_unrelated_assessment_fail_after_exact_requested_verification(
|
|||
assert result.evidence["requested_projection"]["expected_tasks"] == 1
|
||||
|
||||
|
||||
def test_statehub_timeout_returns_structured_failure(tmp_path: Path, monkeypatch) -> None:
|
||||
repo = _fixture(tmp_path)
|
||||
monkeypatch.setattr(
|
||||
rr,
|
||||
"_check_primary",
|
||||
lambda _api: ({"status": "ok", "db": "connected"}, None),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
rr,
|
||||
"_run_statehub",
|
||||
lambda command, *, env: (_ for _ in ()).throw(
|
||||
subprocess.TimeoutExpired(command, rr.STATEHUB_TIMEOUT_SECONDS, output="partial")
|
||||
),
|
||||
)
|
||||
|
||||
result = rr.registrar_reconcile(
|
||||
repo,
|
||||
statehub_bin="statehub",
|
||||
confirm_primary=True,
|
||||
)
|
||||
|
||||
assert result.status == "failed"
|
||||
assert result.error and result.error["code"] == "statehub_timeout"
|
||||
assert result.evidence["statehub_stdout_tail"] == "partial"
|
||||
assert result.evidence["missing_after"] == result.evidence["missing_before"]
|
||||
|
||||
|
||||
def test_repairs_an_already_identified_workplan_projection(tmp_path: Path, monkeypatch) -> None:
|
||||
repo = _fixture(tmp_path)
|
||||
workplan = repo / "workplans" / "DEMO-WP-0001.md"
|
||||
|
|
|
|||
|
|
@ -316,6 +316,12 @@ A requested defect still fails closed; unrelated history stays in evidence;
|
|||
empty-projection bootstrap retains the full-repository gate. Regression coverage
|
||||
proves all three cases.
|
||||
|
||||
The first Custodian proof passed the repaired identity gate but exceeded the
|
||||
old five-minute child timeout without writing an identifier. The wrapper now
|
||||
allows 15 minutes for large legacy repositories, returns structured timeout
|
||||
evidence instead of a traceback, and isolates its unit-test lock from live
|
||||
registrar activity.
|
||||
|
||||
## Derive identifiers deterministically
|
||||
|
||||
```task
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue