# security-gate — project doctrine

audience: AI coding agents first. Imperative, BLUF-ordered. This file POINTS to the source of truth; it never re-inlines it.

## What this is
One standing GATE that catches defects across test domains — **security first** — across all of the user's repos.
A defect that ships is the exact failure the gate exists to prevent: **coverage is the product.**
Source of truth: `docs/specs/2026-06-17-security-gate-design.md`. Read it before changing anything structural.
Architecture SoT (named pattern, keystone contract, day-1 primitive set + the ≥2-consumer gate): `docs/ARCHITECTURE.md`. Read before adding any primitive/abstraction.

## Vocabulary — testing-native ONLY (spec §1)
Use: detector, domain, detector contract, corpus, cell, fixture, canary, anti-canary, discriminator, bench, band, delete-test.
NEVER use (banned — software-architecture vocabulary): module, package, layer, subpath, tier, seam, port-adapter, north-star, semver, changeset.

## The 3-band detector ladder (spec §3)
1. **baseline-LLM** — one always-on, domain-agnostic pass (`domains/security/detectors/baseline/baseline.prompt.txt`, covers S1–S11/OWASP breadth). The `finance` sibling (`detectors/finance/`) adds money-depth (S9; payments is a submodule). Select by id: `gate.py --detector <id>` (default `baseline`). A detector = a drop-in dir + `detector.json` manifest.
2. **orchestrated-LLM** — cross-file scope + k≥3 union + sub-prompts (`orchestrator/gate.py`). Carries hard-but-not-decidable classes.
3. **deterministic** — narrow decidable invariants (`domains/security/detectors/oracle/` TS, `deps/` pnpm-audit, header/config).
ROUTING: a bench-found miss is routed to a band by JUDGMENT. Most hard misses → band 2 (better orchestration), NOT a new band-3 detector. Reserve band 3 for the narrow decidable residue.

## Non-negotiable validation discipline (spec §2, §6)
- **LLM bands are measured STATISTICALLY: k≥3 rolls, recall as a rate. NEVER a single-run claim.** Deterministic detectors are pass/fail.
- **no-false-clean:** a detector on degraded input (unresolved imports) reports UNRELIABLE, never clean.
- **no-false-coverage-claim:** a taxonomy class with 0 cells / unmeasured recall is a flagged BLIND SPOT (`docs/taxonomy/security.md`), never "covered".
- **Ground truth = git FIX COMMITS.** vuln cell = `git show <fix>~1:<path>`, safe cell = `git show <fix>:<path>`.
- **Reconstruct cells with RAW git only** — `command git --no-pager show` (RTK silently truncates routed git show). Verify clean end + canonical symbol present.
- **Blind catch-test discipline:** detectors/agents under test never see audit docs / fix commits / ground truth; the orchestrator (judging) may.
- **Every detector ships only if its conformance suite is green** — flags every canary, ignores every anti-canary, survives every discriminator (spec §4 admission gate).
- **delete-test-vs-LLM:** build a band-3 deterministic detector ONLY if deleting it loses a catch the LLM bands cannot recover (spec §4). Building SQLi/IDOR/SSRF detectors is YAGNI — the LLM catches them.

## Measured state (do not re-derive — see docs)
- 7/11 S1–S11 classes MEASURED, all CAUGHT single-file by band-1 (`docs/validation/2026-06-17-recall-spike-*`).
- baseline (de-financed generalist, #16) catches 17/17 LLM corpus cells right-reason AND passes the safe.ts discriminator 17/17 (canonical NOT re-raised on the fixed file — right-reason, not trigger-happy) **at the k=3 union FLAKINESS-AUDIT config** (10 cells 3/3, 7 cells 2/3), no regression vs the finance prompt, lifts the `assignee_id` sub-shape 1/3→2/3 (`docs/validation/2026-06-18-baseline-generalization-recall.md`). S4 SQLi + S6 SSRF added as real cells (RAW git `9b0c485`/`bec8bc8`) — every S1–S11 class now has a measured detector. **k=1 PRODUCTION recall ≈86%, NOT 99%** (production = k=1 per target, the unbuilt Opus orchestrator; k=3 union is the audit — SoT: modular-hierarchy design §"Strategy placement"). Open: the 7×2/3 cells are the k=1 flaky tail (~14%, orchestrator-addressable) — distinct from the deferred elusive ~1% (cross-model dice + band-2 cross-file precision); S4/S6 are n=1 point-estimates (n≥3 for a RATE).
- **Comment-FP cluster CLOSED** (band-1 prompt fix + navigable-emit citation-spine). The live-fire zync 0/8 precision miss (all 8 findings inside comment blocks of stub files) is resolved by the scoped executes-only rule in `baseline.prompt.txt` (flag only code that EXECUTES; absence-class bugs still required, never withheld) paired with the citation-spine (`gate.py`/`semantic_merge.py`: capture quoted snippet → resolve real line from the target file → emit a dedicated `code` field). MEASURED GREEN (`docs/validation/2026-06-19-citation-prompt-recall.md`): recall NO-REGRESSION 17/17 right-reason (16/17 ≥ bar floor; S2-role 3/9 under BOTH prompts is a pre-existing flaky cell, NOT moved by this change — ship holds on no-regression, not absolute-floor; absence-class cells hold, controlled OLD==NEW k=9 per-roll union on the suspect cells), discriminator 17/17, precision-hold 0 comment-construct FPs n≥3 on the real zync stubs. Band-3 comment-stripper verifier DEFERRED per delete-test (the prompt fix recovers the catch). OPEN non-goals: residual executable-stub over-flagging + cross-file UNRELIABLE confidence (live-fire defect #2).
- Cross-file Shape-B (imported insecure default) is a band-1 single-file MISS, a 3/3 CATCH once resolved. **Band-2 DELIVERY is SHIPPED** (resolver #36 pulls ALL first-party called imports — `CRITICAL` demoted to ordering-only; budget invariant E2E-tested, `tests/test_gate_emit.py::test_over_budget_fanin_drops_real_and_emit_is_degraded`; trigger gap CLOSED, `docs/validation/2026-06-17-band2-trigger-gap-shipped.md` §RESOLVED). The RESIDUAL is the **n≥3 Shape-B recall RATE** across non-payments domains — corpus-scarcity-blocked (Shape-B n=1/85; synthesis FORBIDDEN), an n=1 point estimate until real fix commits are harvested. The open item is corpus harvest, NOT the trigger/resolver.

## Run it
- Gate one file: `python3 orchestrator/gate.py <file> --k 3 --config-dir /tmp/sg_cfg --report <out>.md` (clean unseeded config; refresh creds first).
- **Coverage registry (the derived blind-spot oracle, ARCHITECTURE §5):** `python3 ledger.py` (graded projection) · `python3 ledger.py --check` (CI gate: exit 1 on any structural gap — class with 0 cells, class/cell with no detector, inert ratchet). This is the DERIVED authority; `bench.py --inventory` is a curated convenience that must agree with it. NOT YET auto-enforced — wire `ledger.py --check` into pre-push/CI.
- Recall over orchestrator findings: `python3 bench.py --findings <f>.json --require <cell-id>`.
- **Routing gate (ARCHITECTURE §10, ADD-only — no SUBTRACT):** `python3 bench.py --routing` (exit 1 on a specialist-routing MISS). Asserts each cell's INTENDED specialist routes (baseline EXCLUDED — disable-specialist→RED), guards `applies_to` drift. Baseline 2026-06-19: recall 1.0 over 13 routable cells; 5 BLIND_SPOT (S4/S5/S6/S7/S10 = baseline-floor only), 1 REPO_SCOPE (deps), 1 XFAIL (headers dead). The `sink_signal` SUBTRACT was measured redundant and dropped (collapses into `applies_to.signal`); cost control is ADD-only (file enumeration + opt-in specialists).
- **Aggregated dev gate:** `./check.sh` runs all six deterministic gates (pytest + `ledger.py --check` + `bench.py --routing` + `bench.py --solutions` + recall-gate (`recall_gate.py`) + precision-gate (`recall_gate.py --records domains/security/recall/precision_records.json`)), exit 1 on any failure. Wire as a local pre-push hook (`ln -sf ../../check.sh .git/hooks/pre-push`) — no git remote yet, so CI is moot. This gates security-gate's OWN development; it is NOT the PREVENT gate that runs on the user's app repos (`prevent/prevent.py`, hook-ready, installer + live-repo wiring still a go-live DECISION).

## v1 scope (spec §8)
SECURITY domain only, to the bar: catch ALL fable (zync S1–S11) bugs in 1 round. The Rust `engine/` is DEFERRED (placeholder) — no v1 detector needs syntactic scanning. Other domains follow only after security hits the bar.

## Operating skills (`skills/`)
harvest · implement · investigate · create-tests · coding-standards. Invoke the matching skill before its task.

## When authoring any doc/skill here
Apply the `agent-doc-authoring` discipline: audience-first, imperative + scoped + BLUF, before/after examples with anti-patterns tagged DO NOT, recognized principle names only, point to the SoT (never copy it).
