- parse extracts caller-declared verification_commands (schema + initial_state);
never invents commands. parse gains fallback + state_updates capturing the
failure string (TASK-016 fallback-capture warning stays silent).
- new run_checks script node (facts -> run_checks -> map_criteria, timeout
3600): runs each declared command in the project dir (900s per-command
timeout), records exec_results {cmd, exit, duration_s, tail~50}; 'none
declared' / ENVIRONMENT markers instead of guessing or failing the node.
- check_criterion judges execution criteria against {{exec_results}} only:
recorded green run = MET citing it, recorded failure = UNMET, markers =
PARTIAL with an ENVIRONMENT note.
- fail-closed routing (R1/R2): parse/facts fallback -> pipeline_fault marker
script -> verdict; holistic fallback -> verdict with holistic_failure
captured. verdict.py: any PIPELINE-FAULT forces DIVERGES with the fault as
complaint #1, per-criterion results still reported; report gains a
'Verification runs:' section. Non-degraded semantics unchanged.
- R5 rust tests (python3-guarded, local cmd_available): verdict fault path,
holistic-failure fault, CONFORMS regression, run_checks markers + recorded
runs, crit_gate contract regression.
114 lines
3.4 KiB
Python
114 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Deterministic execution-evidence recorder for the adversary graph.
|
|
|
|
Runs ONLY the caller-declared verification commands (verification_commands)
|
|
in the project dir and records the outcomes as `exec_results` — a list of
|
|
{cmd, exit, duration_s, tail} records. Criterion branches cite this record;
|
|
they never execute anything themselves.
|
|
|
|
NO auto-detection: undeclared commands mean a "none declared" marker
|
|
(fail-visible, not fail-guessed). Any runner error degrades into an
|
|
ENVIRONMENT marker — this script never fails the node.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import time
|
|
|
|
PER_COMMAND_TIMEOUT_SECS = 900
|
|
TAIL_LINES = 50
|
|
MAX_TAIL_CHARS = 8000
|
|
|
|
|
|
def load_state():
|
|
if path := os.environ.get("GRAPH_STATE_FILE"):
|
|
with open(path) as f:
|
|
return json.load(f)
|
|
return json.loads(os.environ.get("GRAPH_STATE", "{}"))
|
|
|
|
|
|
def tail_of(text):
|
|
tail = "\n".join(text.splitlines()[-TAIL_LINES:])
|
|
if len(tail) > MAX_TAIL_CHARS:
|
|
tail = "…" + tail[-MAX_TAIL_CHARS:]
|
|
return tail
|
|
|
|
|
|
def main():
|
|
state = load_state()
|
|
declared = state.get("verification_commands")
|
|
cmds = (
|
|
[c.strip() for c in declared if isinstance(c, str) and c.strip()]
|
|
if isinstance(declared, list)
|
|
else []
|
|
)
|
|
if not cmds:
|
|
print(
|
|
json.dumps(
|
|
{
|
|
"exec_results": (
|
|
"none declared — the caller supplied no verification_commands; "
|
|
"execution criteria (build/test/lint) cannot be independently "
|
|
"confirmed. Declare the exact commands in the spawn prompt to "
|
|
"get recorded runs."
|
|
)
|
|
}
|
|
)
|
|
)
|
|
return
|
|
|
|
proj = os.path.expanduser(
|
|
(state.get("project_dir") or "").strip() or "."
|
|
)
|
|
results = []
|
|
for cmd in cmds:
|
|
start = time.monotonic()
|
|
try:
|
|
r = subprocess.run(
|
|
cmd,
|
|
shell=True,
|
|
cwd=proj,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=PER_COMMAND_TIMEOUT_SECS,
|
|
)
|
|
combined = (r.stdout or "") + (("\n" + r.stderr) if r.stderr else "")
|
|
results.append(
|
|
{
|
|
"cmd": cmd,
|
|
"exit": r.returncode,
|
|
"duration_s": round(time.monotonic() - start, 1),
|
|
"tail": tail_of(combined),
|
|
}
|
|
)
|
|
except subprocess.TimeoutExpired:
|
|
results.append(
|
|
{
|
|
"cmd": cmd,
|
|
"exit": -1,
|
|
"duration_s": round(time.monotonic() - start, 1),
|
|
"tail": (
|
|
f"TIMEOUT: no exit within {PER_COMMAND_TIMEOUT_SECS}s — "
|
|
"recorded as a failing run"
|
|
),
|
|
}
|
|
)
|
|
print(json.dumps({"exec_results": results}))
|
|
|
|
|
|
try:
|
|
main()
|
|
except Exception as e: # noqa: BLE001 — never fail the node; degrade visibly
|
|
print(
|
|
json.dumps(
|
|
{
|
|
"exec_results": (
|
|
f"ENVIRONMENT: verification runner error: {e} — the declared "
|
|
"commands could not be executed; treat execution criteria as "
|
|
"PARTIAL (unproven), not as failed"
|
|
)
|
|
}
|
|
)
|
|
)
|