Skip to content

Commit 13eb322

Browse files
feat(oscal): runnable OSCAL 1.1.2 catalog conformance validator (12th assurance check)
Closes a real compliance-as-code integrity gap: the OSCAL control catalogs carried machine-readable cross-references (tla-spec / rego-policy / circuit / simulator props and regime #href links) that nothing verified, and the regime links were in fact DANGLING (catalogs had no back-matter). - governance_artifacts/oscal/oscal_conformance.py (new): for every control in every catalog under oscal/, runs 8 falsifiable checks — C1 OSCAL 1.1.2 structure (catalog/metadata/groups, id + statement part) C2 feasibility-tier in {A,B,C,D} C3 freshness-sla is a valid ISO-8601 duration (or P.../P... retest pair) C4 tla-spec resolves to a real .tla module (handles Module::label) C5 rego-policy resolves to a real declared package C6 circuit logical id resolves via CIRCUIT_REGISTRY to a real .circom file C7 simulator path exists on disk C8 every internal #href resolves to a back-matter anchor (no dangling) --json for machine-readable output; exit non-zero on any failure. Result on repo catalogs: 43 passed, 0 failed across 2 catalogs. - Both catalogs: added back-matter resources defining each regime anchor (EU AI Act Art.12/14/15, DORA ICT-risk/resilience, Basel op-risk, NIST AI RMF MEASURE, SR 11-7, plus Tier-C/D design fixtures clearly remarked as speculative), so the previously-dangling regime links now resolve. - run_runnable_assurance.sh: renumbered to 12 steps; new step 12 runs the validator. Suite now 12/12 PASS. - tests/governance/test_governance_artifacts.py: +2 tests — positive (repo catalogs conform) and negative (a catalog with a dangling href / bad tla-spec / bad tier / bad SLA fails with exit 1), proving the check is not vacuous. - CI: unit-test job also runs the OSCAL pytest (-k oscal). - Docs synced to 12/12: RUNNABLE_ASSURANCE.md (new row 12), DECADAL plan (verification ledger + counts), pilot checklist P6-REPRO + README. Tier A (in-repo cross-reference integrity). Does NOT assert the named regimes are satisfied in production — only that the catalog's claims are internally consistent and anchored to real, runnable artifacts. Regression: assurance 12/12 PASS; pilot 6/6 automated PASS; governance pytest 12/12.
1 parent 15d6734 commit 13eb322

10 files changed

Lines changed: 447 additions & 22 deletions

.github/workflows/runnable-assurance.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,12 @@ jobs:
8686
circom circuits/src1_concentration_bound.circom --r1cs --wasm --sym --O0 -o circuits/
8787
circom circuits/src_fair1_reason_code_check.circom --r1cs --wasm --sym --O0 -o circuits/
8888
89-
- name: Unit tests (routing + PQC WORM + contract logic)
89+
- name: Unit tests (routing + PQC WORM + contract logic + OSCAL conformance)
9090
run: |
9191
pytest governance_artifacts/routing/test_sara_acr_router.py -q
9292
pytest governance_artifacts/kafka/test_pqc_worm_logger_v2.py -q
9393
pytest governance_blueprint/contracts/test_contract_logic.py -q
94+
pytest tests/governance/test_governance_artifacts.py -q -k oscal
9495
9596
- name: Run runnable assurance suite
9697
run: bash governance_artifacts/run_runnable_assurance.sh

governance_artifacts/RUNNABLE_ASSURANCE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ the master reference documents assert that a control "holds," the artifacts here
1717
bash governance_artifacts/run_runnable_assurance.sh
1818
```
1919

20-
Runs all eleven checks below and fails fast on any error.
20+
Runs all twelve checks below and fails fast on any error.
2121

2222
## What is proven, and against which control
2323

@@ -34,6 +34,7 @@ Runs all eleven checks below and fails fast on any error.
3434
| 9 | PQC WORM audit log — real CRYSTALS-Dilithium (ML-DSA-65) signatures + tamper-evident hash chain + S3 Object Lock retention | Python (`dilithium-py`) + pytest | `cry-02` | DORA, EU AI Act Art. 12 logging |
3535
| 10 | OmegaActual contract hardening — both contracts compile (0 warnings); 7 logic tests prove original exploitable & hardened blocks SEC-01..06 | solc 0.8.26 + pytest | `con-07` settlement | EU AI Act Art. 14, DORA |
3636
| 11 | Governance artifact schema validation | Python validator | manifest/schema integrity | OSCAL, evidence logging (EU AI Act Art. 12) |
37+
| 12 | OSCAL catalog conformance — every control's `tla-spec` / `rego-policy` / `circuit` / `simulator` prop resolves to a real in-repo artifact; every regime `#href` resolves to a back-matter anchor (no dangling references); `feasibility-tier ∈ {A,B,C,D}`; `freshness-sla` is a valid ISO-8601 duration (43 cross-reference checks, falsifiable) | Python (`oscal_conformance.py`) + pytest | all `con-*`, `cry-*`, `env-*`, `rte-*` | OSCAL 1.1.2 compliance-as-code integrity (EU AI Act Annex IV, NIST AI RMF, DORA, Basel, SR 11-7) |
3738

3839
### Companion reviews & plan (this iteration)
3940

governance_artifacts/oscal/catalog_sentinel_v24_env_rte.json

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,34 @@
8080
}
8181
]
8282
}
83-
]
83+
],
84+
"back-matter": {
85+
"resources": [
86+
{
87+
"uuid": "eu-ai-act-art-15-robustness",
88+
"title": "EU AI Act Article 15 — Accuracy, robustness and cybersecurity",
89+
"props": [{"name": "regime", "value": "EU AI Act"}, {"name": "anchor", "value": "eu-ai-act-art-15-robustness"}],
90+
"remarks": "Regulation (EU) 2024/1689, Art. 15. Accuracy/robustness/cybersecurity for high-risk AI systems. Feasibility Tier A."
91+
},
92+
{
93+
"uuid": "dora-ict-risk",
94+
"title": "DORA — ICT risk management framework",
95+
"props": [{"name": "regime", "value": "DORA"}, {"name": "anchor", "value": "dora-ict-risk"}],
96+
"remarks": "Regulation (EU) 2022/2554, Ch. II (Arts. 5-16). ICT risk management framework and controls. Feasibility Tier A."
97+
},
98+
{
99+
"uuid": "nist-ai-rmf-measure",
100+
"title": "NIST AI RMF 1.0 — MEASURE function",
101+
"props": [{"name": "regime", "value": "NIST AI RMF"}, {"name": "anchor", "value": "nist-ai-rmf-measure"}],
102+
"remarks": "NIST AI 100-1 (Jan 2023), MEASURE function (MEASURE 2.x assessment of trustworthiness characteristics). Feasibility Tier A."
103+
},
104+
{
105+
"uuid": "sr-11-7-model-risk",
106+
"title": "SR 11-7 — Supervisory guidance on model risk management",
107+
"props": [{"name": "regime", "value": "Federal Reserve SR 11-7"}, {"name": "anchor", "value": "sr-11-7-model-risk"}],
108+
"remarks": "Federal Reserve SR 11-7 / OCC 2011-12. Model development, validation, and governance. Feasibility Tier A."
109+
}
110+
]
111+
}
84112
}
85113
}

governance_artifacts/oscal/catalog_sentinel_v24_excerpt.json

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,58 @@
102102
}
103103
]
104104
}
105-
]
105+
],
106+
"back-matter": {
107+
"resources": [
108+
{
109+
"uuid": "eu-ai-act-art-14",
110+
"title": "EU AI Act Article 14 — Human oversight",
111+
"props": [{"name": "regime", "value": "EU AI Act"}, {"name": "anchor", "value": "eu-ai-act-art-14"}],
112+
"remarks": "Regulation (EU) 2024/1689, Art. 14. Human oversight measures for high-risk AI systems. Feasibility Tier A."
113+
},
114+
{
115+
"uuid": "eu-ai-act-art-12-logging",
116+
"title": "EU AI Act Article 12 — Record-keeping / automatic logging",
117+
"props": [{"name": "regime", "value": "EU AI Act"}, {"name": "anchor", "value": "eu-ai-act-art-12-logging"}],
118+
"remarks": "Regulation (EU) 2024/1689, Art. 12. Automatic recording of events (logs) over the system lifetime. Feasibility Tier A."
119+
},
120+
{
121+
"uuid": "dora-resilience-testing",
122+
"title": "DORA — Digital operational resilience testing",
123+
"props": [{"name": "regime", "value": "DORA"}, {"name": "anchor", "value": "dora-resilience-testing"}],
124+
"remarks": "Regulation (EU) 2022/2554, Ch. IV (Arts. 24-27). Advanced testing including TLPT for critical functions. Feasibility Tier A."
125+
},
126+
{
127+
"uuid": "dora-ict-risk",
128+
"title": "DORA — ICT risk management framework",
129+
"props": [{"name": "regime", "value": "DORA"}, {"name": "anchor", "value": "dora-ict-risk"}],
130+
"remarks": "Regulation (EU) 2022/2554, Ch. II (Arts. 5-16). ICT risk management framework and controls. Feasibility Tier A."
131+
},
132+
{
133+
"uuid": "basel-op-risk",
134+
"title": "Basel III/IV — Operational risk / SMA",
135+
"props": [{"name": "regime", "value": "Basel III/IV"}, {"name": "anchor", "value": "basel-op-risk"}],
136+
"remarks": "BCBS d424 / d457. Standardised Measurement Approach for operational risk capital, model-risk linkage. Feasibility Tier A."
137+
},
138+
{
139+
"uuid": "sr-26-2-scenario-killswitch",
140+
"title": "Supervisory scenario — kill-switch actuation (SR 26-2 style)",
141+
"props": [{"name": "regime", "value": "Supervisory scenario"}, {"name": "anchor", "value": "sr-26-2-scenario-killswitch"}],
142+
"remarks": "Design fixture: a supervisory-stress scenario exercising dual-path kill-switch actuation. Feasibility Tier C (anticipated supervisory expectation, not a current numbered rule)."
143+
},
144+
{
145+
"uuid": "gaira-systemic-telemetry",
146+
"title": "GAIRA systemic-telemetry attestation (design fixture)",
147+
"props": [{"name": "regime", "value": "GAIRA"}, {"name": "anchor", "value": "gaira-systemic-telemetry"}],
148+
"remarks": "Speculative future Global AI Risk Authority telemetry-attestation obligation. Feasibility Tier D (2030-2035 horizon)."
149+
},
150+
{
151+
"uuid": "icgc-gacp-level-2",
152+
"title": "ICGC/GACP containment assurance Level 2 (design fixture)",
153+
"props": [{"name": "regime", "value": "ICGC/GACP"}, {"name": "anchor", "value": "icgc-gacp-level-2"}],
154+
"remarks": "Speculative International Compute Governance Compact assurance level. Feasibility Tier D (2030-2035 horizon)."
155+
}
156+
]
157+
}
106158
}
107159
}
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
#!/usr/bin/env python3
2+
"""
3+
OSCAL catalog conformance validator — Sentinel v2.4 compliance-as-code integrity.
4+
5+
Compliance-as-code only delivers assurance if the catalog's machine-readable
6+
cross-references actually resolve. A catalog can be valid JSON and still rot:
7+
a `tla-spec` prop pointing at a TLA+ module that was renamed, a `rego-policy`
8+
pointing at a deleted package, a `circuit` logical name with no circom file,
9+
or an internal `#href` regime link that resolves to nothing. Each of those is a
10+
silent gap between "what the control claims is verified" and "what is actually
11+
in the repo".
12+
13+
This validator closes that gap. For every control in every OSCAL catalog under
14+
governance_artifacts/oscal/ it checks:
15+
16+
C1 Structural shape OSCAL 1.1.2 catalog/metadata/groups/controls,
17+
each control has id + statement part.
18+
C2 Feasibility tier vocab feasibility-tier prop in {A,B,C,D}.
19+
C3 Freshness-SLA format freshness-sla is an ISO-8601 duration, or a
20+
"periodic/retest" pair "P.../P..." .
21+
C4 tla-spec resolution prop value (module, optionally "Module::label")
22+
maps to an existing .tla file under tla/.
23+
C5 rego-policy resolution prop "sentinel.attestation"-style package maps
24+
to a real package declared in some .rego file.
25+
C6 circuit resolution logical circuit id (e.g. SRC-1) maps via the
26+
registry to an existing .circom file.
27+
C7 simulator resolution simulator path exists on disk.
28+
C8 internal href resolution every link href "#anchor" resolves to a
29+
back-matter resource uuid (no dangling regime
30+
references).
31+
32+
Exit non-zero if any check fails. `--json` emits a machine-readable report.
33+
34+
This is a Tier-A artifact: it verifies in-repo cross-reference integrity. It does
35+
NOT assert that the named regimes are satisfied in production — only that the
36+
catalog's claims are internally consistent and anchored to real artifacts.
37+
"""
38+
from __future__ import annotations
39+
40+
import argparse
41+
import json
42+
import re
43+
import sys
44+
from dataclasses import dataclass, field, asdict
45+
from pathlib import Path
46+
47+
# Resolve repo-relative directories from this file's location:
48+
# governance_artifacts/oscal/oscal_conformance.py
49+
OSCAL_DIR = Path(__file__).resolve().parent
50+
GA_DIR = OSCAL_DIR.parent # governance_artifacts/
51+
REPO_ROOT = GA_DIR.parent # repo root
52+
TLA_DIR = GA_DIR / "tla"
53+
REGO_DIR = GA_DIR / "rego"
54+
ZK_CIRCUITS = GA_DIR / "zk" / "circuits"
55+
56+
VALID_TIERS = {"A", "B", "C", "D"}
57+
58+
# Logical circuit-id -> circom file (relative to zk/circuits). Keeps catalogs
59+
# referring to stable logical names while the physical filename can evolve.
60+
CIRCUIT_REGISTRY = {
61+
"SRC-1": "src1_concentration_bound.circom",
62+
"SRC-FAIR-1": "src_fair1_reason_code_check.circom",
63+
}
64+
65+
# ISO-8601 duration (subset sufficient for SLAs): PnYnMnDTnHnMnS / PnW.
66+
_ISO_DUR = re.compile(
67+
r"^P(?:\d+W|(?:\d+Y)?(?:\d+M)?(?:\d+D)?(?:T(?:\d+H)?(?:\d+M)?(?:\d+S)?)?)$"
68+
)
69+
70+
71+
@dataclass
72+
class CheckResult:
73+
check: str
74+
catalog: str
75+
control: str
76+
ok: bool
77+
detail: str
78+
79+
80+
@dataclass
81+
class Report:
82+
results: list[CheckResult] = field(default_factory=list)
83+
84+
def add(self, check, catalog, control, ok, detail):
85+
self.results.append(CheckResult(check, catalog, control, ok, detail))
86+
87+
@property
88+
def failed(self):
89+
return [r for r in self.results if not r.ok]
90+
91+
@property
92+
def passed(self):
93+
return [r for r in self.results if r.ok]
94+
95+
96+
def _iso_duration_ok(value: str) -> bool:
97+
if value == "P":
98+
return False
99+
# Allow a "periodic/retest" pair like P1D/P90D.
100+
parts = value.split("/")
101+
return all(bool(_ISO_DUR.match(p)) for p in parts) and all(parts)
102+
103+
104+
def _props(control: dict) -> dict[str, str]:
105+
return {p["name"]: p["value"] for p in control.get("props", [])}
106+
107+
108+
def _iter_controls(catalog: dict):
109+
"""Yield (control_dict) walking nested groups."""
110+
def walk(groups):
111+
for g in groups:
112+
for c in g.get("controls", []):
113+
yield c
114+
yield from walk(g.get("groups", []))
115+
yield from walk(catalog.get("groups", []))
116+
117+
118+
def _tla_modules() -> set[str]:
119+
return {p.stem for p in TLA_DIR.rglob("*.tla")}
120+
121+
122+
def _rego_packages() -> set[str]:
123+
pkgs: set[str] = set()
124+
pat = re.compile(r"^\s*package\s+([A-Za-z0-9_.]+)", re.MULTILINE)
125+
for p in REGO_DIR.rglob("*.rego"):
126+
for m in pat.finditer(p.read_text(encoding="utf-8", errors="ignore")):
127+
pkgs.add(m.group(1))
128+
return pkgs
129+
130+
131+
def validate_catalog(path: Path, rep: Report,
132+
tla_mods: set[str], rego_pkgs: set[str]) -> None:
133+
name = path.name
134+
try:
135+
doc = json.loads(path.read_text(encoding="utf-8"))
136+
except json.JSONDecodeError as e:
137+
rep.add("C1-structure", name, "-", False, f"invalid JSON: {e}")
138+
return
139+
140+
cat = doc.get("catalog")
141+
if not isinstance(cat, dict):
142+
rep.add("C1-structure", name, "-", False, "missing top-level 'catalog'")
143+
return
144+
145+
md = cat.get("metadata", {})
146+
ov = md.get("oscal-version")
147+
rep.add("C1-structure", name, "-", ov == "1.1.2",
148+
f"oscal-version={ov!r} (expected 1.1.2)")
149+
150+
# Build back-matter anchor set (uuids + explicit 'anchor' props).
151+
anchors: set[str] = set()
152+
for res in cat.get("back-matter", {}).get("resources", []):
153+
if res.get("uuid"):
154+
anchors.add(res["uuid"])
155+
for pr in res.get("props", []):
156+
if pr.get("name") == "anchor":
157+
anchors.add(pr["value"])
158+
159+
controls = list(_iter_controls(cat))
160+
if not controls:
161+
rep.add("C1-structure", name, "-", False, "no controls found")
162+
return
163+
164+
for c in controls:
165+
cid = c.get("id", "<no-id>")
166+
167+
# C1: id + statement part
168+
has_stmt = any(p.get("name") == "statement" and p.get("prose")
169+
for p in c.get("parts", []))
170+
rep.add("C1-structure", name, cid, bool(c.get("id")) and has_stmt,
171+
"id+statement present" if has_stmt else "missing id or statement part")
172+
173+
props = _props(c)
174+
175+
# C2: feasibility tier vocabulary
176+
tier = props.get("feasibility-tier")
177+
if tier is not None:
178+
rep.add("C2-tier", name, cid, tier in VALID_TIERS,
179+
f"feasibility-tier={tier!r}")
180+
else:
181+
rep.add("C2-tier", name, cid, False, "missing feasibility-tier prop")
182+
183+
# C3: freshness-sla format (only if present)
184+
sla = props.get("freshness-sla")
185+
if sla is not None:
186+
rep.add("C3-sla", name, cid, _iso_duration_ok(sla),
187+
f"freshness-sla={sla!r}")
188+
189+
# C4: tla-spec resolution
190+
tla = props.get("tla-spec")
191+
if tla is not None:
192+
module = tla.split("::", 1)[0]
193+
rep.add("C4-tla", name, cid, module in tla_mods,
194+
f"tla-spec={tla!r} -> module {module!r} "
195+
+ ("found" if module in tla_mods else "MISSING"))
196+
197+
# C5: rego-policy resolution
198+
rego = props.get("rego-policy")
199+
if rego is not None:
200+
ok = rego in rego_pkgs
201+
rep.add("C5-rego", name, cid, ok,
202+
f"rego-policy={rego!r} "
203+
+ ("found" if ok else f"MISSING (known: {sorted(rego_pkgs)})"))
204+
205+
# C6: circuit resolution via registry
206+
circ = props.get("circuit")
207+
if circ is not None:
208+
fn = CIRCUIT_REGISTRY.get(circ)
209+
ok = bool(fn) and (ZK_CIRCUITS / fn).is_file()
210+
rep.add("C6-circuit", name, cid, ok,
211+
f"circuit={circ!r} -> "
212+
+ (f"{fn} found" if ok else "UNRESOLVED (not in registry or file missing)"))
213+
214+
# C7: simulator path resolution
215+
sim = props.get("simulator")
216+
if sim is not None:
217+
target = GA_DIR / sim
218+
rep.add("C7-simulator", name, cid, target.is_file(),
219+
f"simulator={sim!r} "
220+
+ ("found" if target.is_file() else "MISSING"))
221+
222+
# C8: internal href resolution
223+
for link in c.get("links", []):
224+
href = link.get("href", "")
225+
if href.startswith("#"):
226+
anchor = href[1:]
227+
rep.add("C8-href", name, cid, anchor in anchors,
228+
f"link {link.get('rel','?')} -> #{anchor} "
229+
+ ("resolves" if anchor in anchors else "DANGLING"))
230+
231+
232+
def main(argv=None) -> int:
233+
ap = argparse.ArgumentParser(description="OSCAL catalog conformance validator")
234+
ap.add_argument("--json", action="store_true", help="emit JSON report")
235+
ap.add_argument("--dir", default=str(OSCAL_DIR),
236+
help="directory of OSCAL catalog *.json files")
237+
args = ap.parse_args(argv)
238+
239+
oscal_dir = Path(args.dir)
240+
catalogs = sorted(p for p in oscal_dir.glob("*.json"))
241+
rep = Report()
242+
243+
if not catalogs:
244+
print(f"ERROR: no OSCAL catalog JSON files in {oscal_dir}", file=sys.stderr)
245+
return 2
246+
247+
tla_mods = _tla_modules()
248+
rego_pkgs = _rego_packages()
249+
250+
for path in catalogs:
251+
validate_catalog(path, rep, tla_mods, rego_pkgs)
252+
253+
if args.json:
254+
print(json.dumps({
255+
"passed": len(rep.passed),
256+
"failed": len(rep.failed),
257+
"results": [asdict(r) for r in rep.results],
258+
}, indent=2))
259+
else:
260+
for r in rep.results:
261+
mark = "PASS" if r.ok else "FAIL"
262+
print(f" [{mark}] {r.check:<14} {r.catalog} :: {r.control:<10} {r.detail}")
263+
print("-" * 70)
264+
print(f"OSCAL conformance: {len(rep.passed)} passed, {len(rep.failed)} failed "
265+
f"across {len(catalogs)} catalog(s)")
266+
267+
return 1 if rep.failed else 0
268+
269+
270+
if __name__ == "__main__":
271+
raise SystemExit(main())

0 commit comments

Comments
 (0)