Skip to content

Commit c0094b6

Browse files
committed
feat(engine)!: ship-check Gate 5 re-executes via oracle; remove self-grantable override
Gate 5 was a FAKABLE deterministic grep over .atlas-ai/evidence/ that SILENTLY PASSED when no evidence existed, and was bypassable via the self-grantable --override SHIP_CHECK_OVERRIDE_ADMIN token. Both are removed. skel/ship-check.py (standalone, stdlib-only — shells `atlas oracle grade`): - Delete OVERRIDE_TOKEN, log_override, --override arg, override_active, and the [OVERRIDE] suffix. run_all_gates loses the override_active param. - Replace gate_exit_codes with gate_oracle(repo_root, tasks, head_commit): for every DONE task, re-grade its CDD card via the oracle CLI. FAIL-CLOSED on a missing card, a card with no grading block, a CLI crash, unparseable output, or any verdict != "PASS". Add _head_commit() (git rev-parse HEAD; fail-closed to "UNKNOWN"). - Gates 1-4 and --dry-run/--cwd unchanged. Stdout contract unchanged: exactly "SHIP_CHECK_OK" on success, nothing on failure. tests/core/test_ship_check_oracle.py (new): oracle PASS ships; oracle FAIL blocks; non-JSON fail-closed; missing-card fail-closed; override token removed; Gates 1-4 regression. tests/core/test_ship_check.py (updated to new contract): green-tree CDD cards carry a grading block + a fake ATLAS_ORACLE_CMD; the old Gate-5 grep + override test replaced with an oracle-FAIL-blocks + override-is-gone test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 9525b1c commit c0094b6

3 files changed

Lines changed: 374 additions & 86 deletions

File tree

skel/ship-check.py

Lines changed: 112 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
Emits SHIP_CHECK_OK to stdout ONLY when all gates pass. Called by execute-task
55
at termination AND by Step 9 (--dry-run) as a per-task predicate.
66
7+
Standalone: this file ships into user projects as `.atlas-ai/ship-check.py`. It
8+
imports ONLY the stdlib and MUST stay that way (no `import prd_taskmaster` — that
9+
package is not importable in a user project). The oracle gate therefore shells
10+
the `atlas oracle grade` CLI directly via subprocess.
11+
712
Gate logic (grounded against actual pipeline.json / tasks.json schemas
813
observed 2026-06-04 in ai-human-tasker):
914
@@ -27,36 +32,37 @@
2732
skel checked .atlas-ai/ralph-loop-prompt.md — wrong path and irrelevant
2833
after /goal migration.
2934
30-
Gate 5 (HARD) — No non-zero "Exit status N" line in any .atlas-ai/evidence/
31-
file. This is the convergent must-do from the 2026-06-04 forensic audit
32-
(T12 marked DONE while pnpm test exited 1 with 11 failing tests).
33-
Override only via SHIP_CHECK_OVERRIDE_ADMIN token; overrides are logged
34-
to .atlas-ai/state/execute-log.jsonl as an audit record.
35+
Gate 5 (HARD, ORACLE) — Each DONE task is RE-GRADED by the atlas oracle.
36+
The submitter's own .atlas-ai/evidence/ is NOT trusted; the oracle
37+
re-executes the CDD card's grading against HEAD and writes its own
38+
evidence/ledger. This replaces the fakable "no non-zero Exit status N in
39+
evidence" grep — which silently PASSED when no evidence existed and had a
40+
self-grantable bypass token. Both the silent-pass loophole and the bypass
41+
token are GONE. The gate is FAIL-CLOSED: a missing card, a card with no
42+
grading block, a CLI crash, unparseable output, or any verdict other than
43+
"PASS" all BLOCK the ship.
3544
3645
Interface:
3746
python3 .atlas-ai/ship-check.py # standard gate
3847
python3 .atlas-ai/ship-check.py --dry-run # always exit 0; report on stderr
39-
python3 .atlas-ai/ship-check.py --override SHIP_CHECK_OVERRIDE_ADMIN # bypass Gate 5
4048
python3 .atlas-ai/ship-check.py --cwd /path/to/project # explicit project root
4149
4250
Exit codes:
43-
0 — SHIP_CHECK_OK (stdout: exactly "SHIP_CHECK_OK\n", with " [OVERRIDE]" suffix if applicable)
51+
0 — SHIP_CHECK_OK (stdout: exactly "SHIP_CHECK_OK\n")
4452
1 — gate failures (stderr only; nothing on stdout — log watchers must not see partial matches)
45-
2 — script error (IO, JSON parse, bad token)
53+
2 — script error (IO, JSON parse)
4654
"""
4755
from __future__ import annotations
4856

4957
import argparse
5058
import json
51-
import re
59+
import os
60+
import shlex
61+
import subprocess
5262
import sys
53-
from datetime import datetime, timezone
5463
from pathlib import Path
5564
from typing import List, Tuple
5665

57-
OVERRIDE_TOKEN = "SHIP_CHECK_OVERRIDE_ADMIN"
58-
EXIT_STATUS_RE = re.compile(r"\bExit status\s+(\d+)\b", re.IGNORECASE)
59-
6066

6167
def gate_pipeline(atlas: Path) -> Tuple[bool, List[str]]:
6268
pf = atlas / "state" / "pipeline.json"
@@ -138,46 +144,101 @@ def gate_plan(repo_root: Path) -> Tuple[bool, List[str]]:
138144
return False, ["no plan file at .taskmaster/docs/plan.md or docs/superpowers/plans/*.md"]
139145

140146

141-
def gate_exit_codes(atlas: Path) -> Tuple[bool, List[str]]:
147+
def _oracle_cmd() -> List[str]:
148+
"""Configurable oracle CLI invocation. Default: the 'atlas' binary on PATH.
149+
150+
Set ATLAS_ORACLE_CMD (shell-split) to change it, e.g.:
151+
ATLAS_ORACLE_CMD="node /path/to/atlas-protocol/apps/cli/dist/index.js"
152+
"""
153+
raw = os.environ.get("ATLAS_ORACLE_CMD")
154+
if raw:
155+
return shlex.split(raw)
156+
return ["atlas"]
157+
158+
159+
def _head_commit(repo_root: Path) -> str:
160+
"""HEAD sha via `git rev-parse HEAD`. FAIL-CLOSED: any failure returns the
161+
sentinel "UNKNOWN" so the oracle mismatches the working tree and blocks."""
162+
try:
163+
proc = subprocess.run(
164+
["git", "-C", str(repo_root), "rev-parse", "HEAD"],
165+
capture_output=True, text=True, timeout=30,
166+
)
167+
except (OSError, subprocess.SubprocessError):
168+
return "UNKNOWN"
169+
if proc.returncode != 0:
170+
return "UNKNOWN"
171+
sha = (proc.stdout or "").strip()
172+
return sha or "UNKNOWN"
173+
174+
175+
def gate_oracle(repo_root: Path, tasks: list, head_commit: str) -> Tuple[bool, List[str]]:
176+
"""Re-grade every DONE task via the atlas oracle CLI. FAIL-CLOSED.
177+
178+
For each done task: the CDD card must exist and carry a 'grading' block,
179+
and the oracle must return verdict=="PASS". Anything else — missing card,
180+
no grading block, CLI crash, unparseable output, a non-PASS verdict —
181+
appends a failure. The submitter's evidence is NOT read here; the oracle
182+
re-executes the grading and writes its own evidence/ledger.
183+
"""
142184
failures: List[str] = []
143-
evidence_dir = atlas / "evidence"
144-
if not evidence_dir.exists():
145-
# Gate 3 (CDD) catches missing evidence; this gate is silent when no evidence exists
146-
return True, []
147-
for f in evidence_dir.rglob("*"):
148-
if not f.is_file():
185+
atlas = repo_root / ".atlas-ai"
186+
held = atlas / "held-out"
187+
evidence = atlas / "evidence"
188+
ledger = atlas / "ledger"
189+
cmd_base = _oracle_cmd()
190+
191+
for t in tasks:
192+
if t.get("status") != "done":
193+
continue
194+
tid = t.get("id")
195+
card_path = atlas / "cdd" / f"task-{tid}.json"
196+
if not card_path.exists():
197+
failures.append(f"task {tid}: no CDD card to grade")
149198
continue
150199
try:
151-
text = f.read_text(errors="ignore")
152-
except OSError:
200+
card = json.loads(card_path.read_text())
201+
except (OSError, json.JSONDecodeError) as exc:
202+
failures.append(f"task {tid}: cannot read CDD card ({exc})")
203+
continue
204+
if "grading" not in card:
205+
failures.append(f"task {tid}: CDD card has no grading block")
206+
continue
207+
208+
cmd = cmd_base + [
209+
"oracle", "grade",
210+
"--repo", str(repo_root),
211+
"--commit", head_commit,
212+
"--card", str(card_path),
213+
"--held", str(held),
214+
"--evidence", str(evidence),
215+
"--ledger", str(ledger),
216+
]
217+
try:
218+
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=600)
219+
except (OSError, subprocess.SubprocessError) as exc:
220+
failures.append(f"task {tid}: oracle CLI invocation failed ({exc})")
221+
continue
222+
223+
try:
224+
parsed = json.loads(proc.stdout)
225+
except (json.JSONDecodeError, TypeError):
226+
failures.append(f"task {tid}: oracle produced no parseable JSON verdict (rc={proc.returncode})")
153227
continue
154-
for match in EXIT_STATUS_RE.finditer(text):
155-
try:
156-
code = int(match.group(1))
157-
except (ValueError, IndexError):
158-
continue
159-
if code != 0:
160-
rel = f.relative_to(atlas.parent) if atlas.parent in f.parents else f
161-
failures.append(f"non-zero exit in {rel}: Exit status {code}")
162-
break # one report per file is enough
163-
return len(failures) == 0, failures
164228

229+
verdict = parsed.get("verdict") if isinstance(parsed, dict) else None
230+
if verdict not in ("PASS", "FAIL"):
231+
failures.append(f"task {tid}: oracle verdict missing/invalid ({verdict!r})")
232+
continue
233+
if verdict == "FAIL":
234+
failures.append(f"task {tid}: oracle verdict FAIL")
235+
continue
236+
# verdict == "PASS" — the only path that does NOT append a failure.
165237

166-
def log_override(atlas: Path, message: str) -> None:
167-
log = atlas / "state" / "execute-log.jsonl"
168-
log.parent.mkdir(parents=True, exist_ok=True)
169-
entry = {
170-
"iteration": "OVERRIDE",
171-
"timestamp": datetime.now(timezone.utc).isoformat(),
172-
"task_id": "SHIP_CHECK",
173-
"event": "override_invoked",
174-
"message": message,
175-
}
176-
with log.open("a") as fp:
177-
fp.write(json.dumps(entry) + "\n")
238+
return len(failures) == 0, failures
178239

179240

180-
def run_all_gates(repo_root: Path, override_active: bool) -> Tuple[bool, List[str]]:
241+
def run_all_gates(repo_root: Path) -> Tuple[bool, List[str]]:
181242
failures: List[str] = []
182243
atlas = repo_root / ".atlas-ai"
183244

@@ -191,38 +252,28 @@ def run_all_gates(repo_root: Path, override_active: bool) -> Tuple[bool, List[st
191252
_, f3 = gate_cdd(atlas, tasks)
192253
failures.extend(f3)
193254

255+
head = _head_commit(repo_root)
256+
_, f5 = gate_oracle(repo_root, tasks, head)
257+
failures.extend(f5)
258+
194259
_, f4 = gate_plan(repo_root)
195260
failures.extend(f4)
196261

197-
ok5, f5 = gate_exit_codes(atlas)
198-
if not ok5:
199-
if override_active:
200-
log_override(atlas, f"Gate 5 bypassed: {'; '.join(f5[:3])}{' ...' if len(f5) > 3 else ''}")
201-
# Override accepts the failures; no append to global failures list
202-
else:
203-
failures.extend(f5)
204-
205262
return len(failures) == 0, failures
206263

207264

208265
def main() -> int:
209266
parser = argparse.ArgumentParser(description="Deterministic ship-check for prd-taskmaster pipelines.")
210267
parser.add_argument("--dry-run", action="store_true",
211268
help="Run all gates but always exit 0. Report goes to stderr. Used by execute-task Step 9 as a per-task predicate.")
212-
parser.add_argument("--override", type=str, default=None,
213-
help=f"Bypass Gate 5 (exit codes) if value equals {OVERRIDE_TOKEN}. Logged to execute-log.jsonl.")
214269
parser.add_argument("--cwd", type=str, default=None,
215270
help="Project root (defaults to current working directory).")
216271
args = parser.parse_args()
217272

218273
repo_root = Path(args.cwd).resolve() if args.cwd else Path.cwd()
219-
override_active = args.override == OVERRIDE_TOKEN
220-
if args.override is not None and not override_active:
221-
print("FAIL: --override value does not match expected token", file=sys.stderr)
222-
return 2
223274

224275
try:
225-
ok, failures = run_all_gates(repo_root, override_active=override_active)
276+
ok, failures = run_all_gates(repo_root)
226277
except Exception as exc: # noqa: BLE001 — top-level guard
227278
print(f"FAIL: ship-check script error: {exc!r}", file=sys.stderr)
228279
return 2
@@ -237,8 +288,7 @@ def main() -> int:
237288
return 0
238289

239290
if ok:
240-
suffix = " [OVERRIDE]" if override_active else ""
241-
print(f"SHIP_CHECK_OK{suffix}")
291+
print("SHIP_CHECK_OK")
242292
return 0
243293

244294
for f in failures:

tests/core/test_ship_check.py

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,26 @@
1212
Gate 3 — for each task id, a CDD card at .atlas-ai/cdd/task-<id>.json (or a
1313
combined card whose filename contains the id).
1414
Gate 4 — plan at .taskmaster/docs/plan.md OR docs/superpowers/plans/*.md.
15-
Gate 5 (HARD) — no non-zero "Exit status N" in any .atlas-ai/evidence/ file.
16-
Bypass via --override SHIP_CHECK_OVERRIDE_ADMIN.
17-
Success → stdout exactly "SHIP_CHECK_OK\n" (+ " [OVERRIDE]"), exit 0.
15+
Gate 5 (HARD, ORACLE) — every DONE task is RE-GRADED by the atlas oracle CLI
16+
(shelled out; configurable via ATLAS_ORACLE_CMD). FAIL-CLOSED: a
17+
missing card, a card with no grading block, a CLI crash, unparseable
18+
output, or any non-PASS verdict BLOCKS the ship. The old fakable
19+
"no non-zero Exit status N in evidence" grep and the self-grantable
20+
--override SHIP_CHECK_OVERRIDE_ADMIN token are BOTH GONE (see the
21+
dedicated coverage in test_ship_check_oracle.py).
22+
Success → stdout exactly "SHIP_CHECK_OK\n", exit 0.
1823
Failure → nothing on stdout, FAIL detail on stderr, exit 1.
24+
25+
NOTE: these tests drive the script as a real subprocess, so the oracle gate is
26+
satisfied by a real fake-oracle command (a tiny executable shell script) wired
27+
through ATLAS_ORACLE_CMD. Pure-monkeypatch oracle coverage lives in
28+
test_ship_check_oracle.py.
1929
"""
2030
from __future__ import annotations
2131

2232
import json
33+
import os
34+
import stat
2335
import subprocess
2436
from pathlib import Path
2537

@@ -31,18 +43,41 @@
3143
SHIP_CHECK = REPO_ROOT / "skel" / "ship-check.py"
3244

3345

34-
def _run(cwd: Path, *extra: str) -> subprocess.CompletedProcess[str]:
46+
def _fake_oracle(tmp_path: Path, verdict: str = "PASS") -> str:
47+
"""Write an executable fake `atlas` that emits {"verdict": <verdict>} for an
48+
`oracle grade` invocation. Return an ATLAS_ORACLE_CMD value pointing at it."""
49+
script = tmp_path / "fake_atlas.sh"
50+
script.write_text(
51+
"#!/bin/sh\n"
52+
'for a in "$@"; do\n'
53+
' if [ "$a" = "grade" ]; then\n'
54+
f' printf \'{{"verdict":"{verdict}"}}\'\n'
55+
" exit 0\n"
56+
" fi\n"
57+
"done\n"
58+
"exit 0\n"
59+
)
60+
script.chmod(script.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
61+
return f"sh {script}"
62+
63+
64+
def _run(cwd: Path, *extra: str, oracle_cmd: str | None = None) -> subprocess.CompletedProcess[str]:
65+
env = dict(os.environ)
66+
if oracle_cmd is not None:
67+
env["ATLAS_ORACLE_CMD"] = oracle_cmd
3568
return subprocess.run(
3669
["python3", str(SHIP_CHECK), *extra],
3770
cwd=str(cwd),
3871
capture_output=True,
3972
text=True,
4073
check=False,
74+
env=env,
4175
)
4276

4377

4478
def _green(tmp_path: Path) -> None:
45-
"""Build an all-gates-green project tree under tmp_path."""
79+
"""Build an all-gates-green project tree under tmp_path. CDD cards carry a
80+
`grading` block so the oracle gate can re-grade them."""
4681
atlas = tmp_path / ".atlas-ai"
4782
(atlas / "state").mkdir(parents=True)
4883
(atlas / "state" / "pipeline.json").write_text(
@@ -64,8 +99,9 @@ def _green(tmp_path: Path) -> None:
6499
)
65100
cdd = atlas / "cdd"
66101
cdd.mkdir(parents=True)
67-
(cdd / "task-1.json").write_text(json.dumps({"id": 1}))
68-
(cdd / "task-2.json").write_text(json.dumps({"id": 2}))
102+
grading = {"grading": {"command": ["sh", "grade.sh"]}}
103+
(cdd / "task-1.json").write_text(json.dumps({"id": 1, **grading}))
104+
(cdd / "task-2.json").write_text(json.dumps({"id": 2, **grading}))
69105
docs = tmp_path / ".taskmaster" / "docs"
70106
docs.mkdir(parents=True)
71107
(docs / "plan.md").write_text("# Plan\n")
@@ -80,7 +116,7 @@ def test_ship_check_fails_on_empty_state(tmp_path: Path) -> None:
80116

81117
def test_ship_check_passes_on_all_gates_green(tmp_path: Path) -> None:
82118
_green(tmp_path)
83-
r = _run(tmp_path)
119+
r = _run(tmp_path, oracle_cmd=_fake_oracle(tmp_path, "PASS"))
84120
assert r.returncode == 0, f"stderr={r.stderr!r} stdout={r.stdout!r}"
85121
assert "SHIP_CHECK_OK" in r.stdout
86122

@@ -118,25 +154,27 @@ def test_ship_check_fails_when_done_task_has_no_cdd_card(tmp_path: Path) -> None
118154
assert "task 2: no CDD card" in r.stderr
119155

120156

121-
def test_ship_check_gate5_blocks_on_nonzero_exit_and_override_passes(tmp_path: Path) -> None:
122-
"""Gate 5 (HARD): a non-zero 'Exit status N' in evidence blocks; the
123-
override token bypasses it."""
157+
def test_ship_check_gate5_oracle_fail_blocks_and_override_is_gone(tmp_path: Path) -> None:
158+
"""Gate 5 (HARD, ORACLE): an oracle FAIL verdict blocks the ship, and the
159+
old self-grantable --override token no longer exists (argparse rejects it)."""
124160
_green(tmp_path)
125-
evidence = tmp_path / ".atlas-ai" / "evidence"
126-
evidence.mkdir(parents=True)
127-
(evidence / "run.log").write_text("pnpm test\nExit status 1\n")
128161

129-
# Without override → blocked.
130-
r = _run(tmp_path)
162+
# An oracle FAIL verdict → blocked, nothing on stdout.
163+
r = _run(tmp_path, oracle_cmd=_fake_oracle(tmp_path, "FAIL"))
131164
assert r.returncode != 0
132165
assert "SHIP_CHECK_OK" not in r.stdout
133-
assert "Exit status 1" in r.stderr
134-
135-
# With the override token → passes, OVERRIDE suffix on stdout.
136-
r2 = _run(tmp_path, "--override", "SHIP_CHECK_OVERRIDE_ADMIN")
137-
assert r2.returncode == 0, f"stderr={r2.stderr!r} stdout={r2.stdout!r}"
138-
assert "SHIP_CHECK_OK" in r2.stdout
139-
assert "[OVERRIDE]" in r2.stdout
166+
assert "oracle verdict FAIL" in r.stderr
167+
168+
# The override token is GONE: argparse rejects the unknown flag (exit 2)
169+
# and never emits SHIP_CHECK_OK — there is no bypass path.
170+
r2 = _run(
171+
tmp_path,
172+
"--override",
173+
"SHIP_CHECK_OVERRIDE_ADMIN",
174+
oracle_cmd=_fake_oracle(tmp_path, "PASS"),
175+
)
176+
assert r2.returncode == 2
177+
assert "SHIP_CHECK_OK" not in r2.stdout
140178

141179

142180
# ─── Python API agreement (prd_taskmaster.shipcheck.run_ship_check) ──────────
@@ -171,6 +209,6 @@ def test_ship_check_passes_with_flat_tasks_format(tmp_path):
171209
(tmp_path / ".taskmaster" / "tasks" / "tasks.json").write_text(json.dumps(
172210
{"tasks": [{"id": 1, "status": "done"}, {"id": 2, "status": "done"}]}
173211
))
174-
r = _run(tmp_path)
212+
r = _run(tmp_path, oracle_cmd=_fake_oracle(tmp_path, "PASS"))
175213
assert r.returncode == 0, f"stderr={r.stderr!r}"
176214
assert "SHIP_CHECK_OK" in r.stdout

0 commit comments

Comments
 (0)