Run it yourself
make negative
The script breaks the thing each gate protects, asserts the gate refuses, and restores the tree. It refuses to run on a dirty working tree, because it edits files.
Negative tests — each gate must refuse:
refused FIN-02 float on a monetary field
refused DP-11 personal data in an error message
refused DP-11 logging outside audit.safe_log()
refused SEC-API-01 unauthenticated, unaudited POST
refused FRZ-01 edit to a frozen path
refused SOD-01 CODEOWNERS rule guarding a missing path
refused SOD-01 control layer with no named owner
refused CHG-04 artifact with no header
refused CHG-04 raw model name instead of a route
refused HUM-14 R3 change claiming A3 autonomy
refused HUM-14 material change declared R1
refused TRC-01 code changed with no plan claiming it
12 refused, 0 did not.
What each gate decides
| Control | Gate | Refuses |
|---|---|---|
FIN-02 |
check_money.py |
A float or a round() anywhere near a monetary value. Money is Decimal, or it is a rounding error waiting for volume. |
DP-11 |
check_pii.py |
A field classified personal reaching a log line or an error message, and any logging outside audit.safe_log(). |
SEC-API-01 |
check_endpoints.sh |
A state-changing route with no authentication, no audit event, or an untyped body. |
FRZ-01 |
check_frozen_paths.py |
An edit to a frozen path without a recorded exception at R3 with architect approval. |
SOD-01 |
check_codeowners.py |
A CODEOWNERS rule GitHub would silently ignore, and any part of the control layer left to the catch-all. |
CHG-04 |
check_artifact_header.py |
An artifact with no §6.2 header, a malformed one, a raw model name where a route belongs, or a gap in the chain. |
HUM-14 |
check_autonomy.py |
An autonomy tier above what the matrix permits for the risk class — including a material change self-declaring as routine. |
TRC-01 |
check_plan_conformance.py |
A code file in the diff that no plan’s ## Files section claims. |
The two things that make these gates unusual
They call GitHub’s own validator
A CODEOWNERS rule whose owner does not exist is silently ignored. The rule stays in the file, branch protection still says require review from Code Owners, and the requirement is satisfied by nobody. That is a control which reads as operating while it is not — precisely the failure mode this repository is about. So the gate asks GitHub, rather than trusting the file.
The risk class comes from the paths, not the header
path_floors:
- { pattern: "service/app/money.py", min_class: R3, reason: "monetary logic" }
- { pattern: "service/app/audit.py", min_class: R3, reason: "audit and PII controls" }
- { pattern: "service/app/models.py", min_class: R3, reason: "data classification map" }
- { pattern: "policy/**", min_class: R3, reason: "control layer" }
- { pattern: ".github/workflows/**", min_class: R3, reason: "control layer" }
A change that touches the classification map is material whatever its header says. You cannot self-declare your way past a gate.
No model in the gate
Models diagnose, propose, draft and review. The decision to allow or block is arithmetic over policy that lives in
policy/as version-controlled YAML — the same tables governance signed off.
A model that can block a merge is a control whose effectiveness you cannot evidence. Ask what its false-negative rate was last quarter and there is no answer, because there is no stable artefact to have measured. Arithmetic over a YAML table has an answer, and the table is reviewable by people who do not read Python.
Every gate writes a JSON record to evidence/ as it runs — control id, result, findings,
commit, actor, timestamp. Evidence is a by-product of the control operating, never a
reconstruction exercise at audit time.
python scripts/query_evidence.py --control SEC-API-01