Skip to content

Commit bb11b56

Browse files
PhysShellcodex
andcommitted
P-037 A2.0: wire provenance controls into evidence gates
Upgrade the verdict snapshot to the shared A2.0 provenance contract and add an explicit verify mode. Pin the freshness predicate with live controls for closure coverage, input-manifest tampering, and the evidence bit. Wire those controls plus the whole-MOS reader selftest into the existing Linux P-037 dogfood step. The expensive verdict and two-source MOS sweeps remain per-step evidence, not per-push CI. Refs #304. Co-Authored-By: ChatGPT <noreply@openai.com>
1 parent efb0d55 commit bb11b56

3 files changed

Lines changed: 151 additions & 11 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3007,10 +3007,11 @@ jobs:
30073007
# Windows leg of this job exists for own-check.ps1, which is a separate
30083008
# launcher implementation and is the thing Windows actually tests.
30093009
#
3010-
# NOT here: scripts/p037_verdict_snapshot.py. It is 137 files times two
3011-
# engines, roughly fourteen minutes, and it is the per-STEP gate each A1.1
3012-
# commit runs and records under docs/evidence/ — not a per-push check. Saying
3013-
# so out loud because an unstated omission looks identical to an oversight.
3010+
# NOT here: the full P-037 verdict/MOS snapshots. The verdict sweep is
3011+
# 137 files times two engines and the MOS gate additionally measures the
3012+
# independent repository C# tree. They are per-STEP evidence recorded under
3013+
# docs/evidence/, not per-push work. The cheap provenance and MOS-reader
3014+
# controls below ARE per-push so forgetting or hollowing the heavy gate is red.
30143015
- name: P-037 conformance controls + fact-shape census
30153016
if: matrix.os == 'ubuntu-latest'
30163017
run: |
@@ -3024,6 +3025,13 @@ jobs:
30243025
# turned six jobs in this very workflow red: verdict coverage is not
30253026
# syntax/fact-shape coverage, and one fixture per shape fails by NAME.
30263027
python scripts/p037_fact_shapes.py check --engine both
3028+
# A2.0: a recorded run remains evidence only while its measured
3029+
# implementation closure and exact input blob set stay unchanged.
3030+
python tests/test_p037_evidence.py
3031+
# Level 2 reads the whole MOS document through the existing common
3032+
# P-022 capture envelope. This cheap control pins that returns and
3033+
# unresolved fields cannot disappear behind a transfer-only projection.
3034+
python scripts/p037_mos_snapshot.py --selftest
30273035
# The Windows launcher is a separate implementation, not a wrapper around
30283036
# the shell one, so the Windows leg has to go through it to mean anything.
30293037
- name: Own.NET's own tree, through own-check.ps1 on the Rust core

‎scripts/p037_verdict_snapshot.py‎

100755100644
Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,14 @@
4747
from pathlib import Path
4848
from typing import Any
4949

50+
from p037_evidence import (
51+
CORPUS_DIRS,
52+
EvidenceRefused,
53+
evidence_fields,
54+
provenance_problems,
55+
)
56+
5057
ROOT = Path(__file__).resolve().parent.parent
51-
CORPUS_DIRS = ("corpus/real-world", "corpus/wpf", "corpus/di", "corpus/fixtures",
52-
"corpus/p036-bakeoff")
5358
VERDICT_LEVELS = ("error", "warning")
5459

5560

@@ -102,16 +107,19 @@ def take(engine: str, out: Path, dirs: tuple[str, ...]) -> int:
102107
if not files:
103108
print("no corpus files found", file=sys.stderr)
104109
return 2
105-
dirty = bool(git("status", "--porcelain"))
110+
try:
111+
provenance = evidence_fields(dirs)
112+
except EvidenceRefused as exc:
113+
print(f"REFUSED: {exc}", file=sys.stderr)
114+
return 2
106115
snap: dict[str, Any] = {
107-
"schema": "p037-verdict-snapshot/1",
116+
"schema": "p037-verdict-snapshot/2",
108117
"engine": engine,
109-
"source_commit": git("rev-parse", "HEAD"),
110-
"dirty": dirty,
111-
"is_evidence": not dirty,
118+
**provenance,
112119
"corpus": list(dirs),
113120
"files": {},
114121
}
122+
dirty = bool(snap["dirty"])
115123
for i, f in enumerate(files, 1):
116124
rel = f.relative_to(ROOT).as_posix()
117125
snap["files"][rel] = run_one(f, engine)
@@ -141,6 +149,34 @@ def take(engine: str, out: Path, dirs: tuple[str, ...]) -> int:
141149
return 0
142150

143151

152+
def verify(path: Path) -> int:
153+
try:
154+
snap: dict[str, Any] = json.loads(path.read_text(encoding="utf-8"))
155+
except (OSError, json.JSONDecodeError) as exc:
156+
print(f"REFUSED: {path}: {exc}", file=sys.stderr)
157+
return 2
158+
if snap.get("schema") != "p037-verdict-snapshot/2":
159+
print(
160+
f"REFUSED: {path}: schema {snap.get('schema')!r} has no A2.0 provenance closure",
161+
file=sys.stderr,
162+
)
163+
return 2
164+
try:
165+
problems = provenance_problems(snap)
166+
except EvidenceRefused as exc:
167+
print(f"REFUSED: {exc}", file=sys.stderr)
168+
return 2
169+
if problems:
170+
for problem in problems:
171+
print(f"FAIL[provenance]: {problem}")
172+
return 1
173+
print(
174+
f"OK: {path} is fresh evidence at HEAD; "
175+
f"source={snap['source_commit'][:12]}, inputs={len(snap['input_manifest'])}"
176+
)
177+
return 0
178+
179+
144180
def key_set(rec: dict[str, Any], level: str) -> set[tuple[int, str, str]]:
145181
return {(f["line"], f["code"], f["level"]) for f in rec["findings"]
146182
if level == "all" or f["level"] in VERDICT_LEVELS}
@@ -205,10 +241,14 @@ def main(argv: list[str]) -> int:
205241
c.add_argument("--after", required=True, type=Path)
206242
c.add_argument("--level", default="verdict", choices=("verdict", "all"),
207243
help="verdict = error/warning only (default); all = advisories too")
244+
v = sub.add_parser("verify", help="prove a snapshot is still fresh evidence at HEAD")
245+
v.add_argument("snapshot", type=Path)
208246
args = ap.parse_args(argv)
209247
if args.cmd == "take":
210248
dirs = tuple(args.corpus) if args.corpus else CORPUS_DIRS
211249
return take(args.engine, args.out, dirs)
250+
if args.cmd == "verify":
251+
return verify(args.snapshot)
212252
return compare(args.before, args.after, args.level)
213253

214254

‎tests/test_p037_evidence.py‎

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#!/usr/bin/env python3
2+
"""Controls for P-037's A2.0 evidence freshness predicate.
3+
4+
These are structure controls, not a second implementation of the predicate:
5+
the live repository supplies one real evidence record, then each control
6+
corrupts exactly one claim and requires the shared gate to refuse it.
7+
"""
8+
9+
from __future__ import annotations
10+
11+
import copy
12+
import sys
13+
from pathlib import Path
14+
15+
ROOT = Path(__file__).resolve().parent.parent
16+
sys.path.insert(0, str(ROOT / "scripts"))
17+
18+
import p037_evidence as ev # noqa: E402
19+
20+
21+
failures = 0
22+
23+
24+
def ok(name: str) -> None:
25+
print(f"ok[{name}]")
26+
27+
28+
def fail(name: str, detail: str) -> None:
29+
global failures
30+
failures += 1
31+
print(f"FAIL[{name}]: {detail}")
32+
33+
34+
def require_problem(name: str, record: dict[str, object], needle: str) -> None:
35+
problems = ev.provenance_problems(record)
36+
if any(needle in problem for problem in problems):
37+
ok(name)
38+
else:
39+
fail(name, f"expected a problem containing {needle!r}, got {problems!r}")
40+
41+
42+
def main() -> int:
43+
problems = ev.closure_problems()
44+
if problems:
45+
fail("runtime-closure-covered", "; ".join(problems))
46+
return 1
47+
ok("runtime-closure-covered")
48+
49+
record = ev.evidence_fields(("corpus/p037-shapes",))
50+
live = ev.provenance_problems(record)
51+
if live:
52+
fail("fresh-record-valid", "; ".join(live))
53+
return 1
54+
ok("fresh-record-valid")
55+
56+
missing_subject = copy.deepcopy(record)
57+
missing_subject["subject_paths"] = list(ev.SUBJECT_PATHS[:-1])
58+
require_problem(
59+
"closure-expansion-invalidates",
60+
missing_subject,
61+
"subject_paths differ",
62+
)
63+
64+
tampered_manifest = copy.deepcopy(record)
65+
manifest = tampered_manifest["input_manifest"]
66+
assert isinstance(manifest, list) and manifest
67+
first = manifest[0]
68+
assert isinstance(first, dict)
69+
first["blob"] = "0" * 40
70+
require_problem(
71+
"input-manifest-tamper-refused",
72+
tampered_manifest,
73+
"input_manifest",
74+
)
75+
76+
non_evidence = copy.deepcopy(record)
77+
non_evidence["is_evidence"] = False
78+
require_problem(
79+
"non-evidence-refused",
80+
non_evidence,
81+
"is_evidence=true",
82+
)
83+
84+
if failures:
85+
print(f"RESULT: {failures} provenance control(s) failed")
86+
return 1
87+
print("RESULT: all P-037 evidence provenance controls passed")
88+
return 0
89+
90+
91+
if __name__ == "__main__":
92+
raise SystemExit(main())

0 commit comments

Comments
 (0)