Validate training completion handoff
GeoIntel release gates / Compile, test, contracts and builds (push) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (push) Canceled after 0s
GeoIntel release gates / GIS image, SBOM and container scan (push) Canceled after 0s

This commit is contained in:
Jens
2026-07-29 15:48:51 +02:00
parent d6502140da
commit f18a23c060
3 changed files with 30 additions and 4 deletions
+14 -2
View File
@@ -33,5 +33,17 @@ def test_container_running_fails_closed_on_inspect_error(monkeypatch) -> None:
def test_completion_command_requires_non_empty_string_list(tmp_path: Path) -> None: def test_completion_command_requires_non_empty_string_list(tmp_path: Path) -> None:
invalid = tmp_path / "command.json" invalid = tmp_path / "command.json"
invalid.write_text('{"shell": "unsafe"}', encoding="utf-8") invalid.write_text('{"shell": "unsafe"}', encoding="utf-8")
value = MODULE.json.loads(invalid.read_text(encoding="utf-8")) try:
assert not isinstance(value, list) MODULE.load_completion_command(invalid)
except ValueError as exc:
assert "non-empty JSON list" in str(exc)
else:
raise AssertionError("mapping-shaped completion command was accepted")
def test_completion_command_preserves_exact_argv_without_shell_parsing(tmp_path: Path) -> None:
command = tmp_path / "command.json"
command.write_text('["docker", "exec", "-d", "geointel", "python", "loop.py"]', encoding="utf-8")
assert MODULE.load_completion_command(command) == [
"docker", "exec", "-d", "geointel", "python", "loop.py"
]
+5
View File
@@ -11799,6 +11799,11 @@ Deployment evidence:
`results.png` appears. The bound command permits up to 20 iterations, keeps `results.png` appears. The bound command permits up to 20 iterations, keeps
protected test/background closed until calibration passes, and preserves protected test/background closed until calibration passes, and preserves
the exact AdamW, 180-degree rotation, flip, scale and translation contract. the exact AdamW, 180-degree rotation, flip, scale and translation contract.
- Extracted and tested strict completion-command validation. Only a non-empty
JSON list of non-empty argv strings is accepted; mappings, empty arguments,
malformed JSON and filesystem errors produce an explicit fail-closed
supervisor state before any subprocess is started. The updated supervisor
was activated live with one remaining v37 GPU process.
- Added those aerial augmentation parameters to the orchestrator CLI and - Added those aerial augmentation parameters to the orchestrator CLI and
training command, preventing later failure-driven checkpoints from silently training command, preventing later failure-driven checkpoints from silently
reverting to generic orientation assumptions. reverting to generic orientation assumptions.
+11 -2
View File
@@ -37,6 +37,13 @@ def write_state(path: Path, payload: dict) -> None:
temporary.replace(path) temporary.replace(path)
def load_completion_command(path: Path) -> list[str]:
command = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(command, list) or not command or not all(isinstance(x, str) and x for x in command):
raise ValueError("completion command must be a non-empty JSON list of non-empty strings")
return command
def main() -> int: def main() -> int:
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("--container", required=True) parser.add_argument("--container", required=True)
@@ -64,9 +71,11 @@ def main() -> int:
write_state(state_path, state) write_state(state_path, state)
return 0 return 0
if args.completion_command_json: if args.completion_command_json:
command = json.loads(args.completion_command_json.read_text(encoding="utf-8")) try:
if not isinstance(command, list) or not command or not all(isinstance(x, str) and x for x in command): command = load_completion_command(args.completion_command_json)
except (OSError, json.JSONDecodeError, ValueError) as exc:
state["status"] = "invalid_completion_command" state["status"] = "invalid_completion_command"
state["completion_handoff_error"] = str(exc)
write_state(state_path, state) write_state(state_path, state)
return 4 return 4
result = subprocess.run(command, check=False) result = subprocess.run(command, check=False)