# Implementing a detector to the contract

audience: AI coding agents first. Imperative, BLUF-ordered.

**Core principle: a detector ships ONLY when its conformance suite is green — flags every canary, ignores every anti-canary, survives every discriminator. No green suite, no ship.**

## When to use
Invoke to turn a harvested candidate into a shipping detector.

## The detector contract (spec §4 — author ALL fields)
- `id` · `domain` · `band` · `severity` · `category`
- the check: a **declarative rule** (regex / ast-grep, runs on the Rust engine) OR a **procedural `fn(target) -> Finding[]`** (TS for type-aware, e.g. the oracle).
- `appliesTo(target)` — which files it runs on.
- `canary` — input it MUST flag (RED-on-vuln).
- `antiCanary[]` — inputs it MUST NOT flag (GREEN-on-safe).
- `discriminators` — rename / cosmetic / polarity / null-mutant variants it MUST survive (anti-overfit — slopgate lacked these; we require them).
- `Finding`: `{id, domain, severity, file, line, text, resolution, band, rollsFound?}`.

## Language by BOTTLENECK (spec §5) — pick by the WORK, never preference
- CPU-bound + syntactic → declarative rule on the Rust engine. Authoring = a YAML/JSON rule file, NOT writing Rust.
- Semantic / type-aware (needs types, cross-file symbol resolution) → TS. // reject: porting the oracle to ast-grep — loses the type info that is its whole value.
- Lockfile / CVE / config → the native tool (`pnpm audit`, a config parser).
- LLM band / orchestrator glue → python. // reject: Rust-ifying the orchestrator — it is LLM-latency-bound.

## Admission gate (imperative — run before ship)
1. Write canary + every anti-canary + every discriminator as corpus cells/fixtures (use `create-tests`).
2. Run the detector over them. Green = flags all canaries, ignores all anti-canaries, survives all discriminators.
3. **delete-test-vs-LLM:** if deleting this detector loses NO catch the LLM bands can't recover, DO NOT ship it (YAGNI). [MEASURED] oracle/deps/headers pass; SQLi/IDOR/SSRF fail (LLM catches 3/3).
4. Register by CONVENTION (file in the domain's `detectors/`), NOT a registry.

## DO NOT
- DO NOT ship a detector without discriminators — a canary-only suite overfits to one phrasing.
- DO NOT add a band-3 detector for an LLM-caught class (delete-test).
- DO NOT tune-and-test on the same cell (overfit). Validate on a HELD-OUT cell.

## Pointers
Contract + admission gate + delete-test: `docs/specs/2026-06-17-security-gate-design.md` §4–§5. Conformance idiom is Semgrep `.test.yaml` / CodeQL `.expected` / ESLint RuleTester — best practice, not novel.

## Learned Rules

### ledger-coverage-column-admission-gate | fired:1 | 2026-06-20
New ledger coverage grade derived from a manifest-declared string (`resolution_grade` trusted `rung`) → wrong: a `solution.json` with no `solve.py`/no conformance still grades `suggested` and `check.sh` stays green = a false-coverage claim in the new column (exactly the blind spot the ledger exists to prevent).
Prevent: admission-gate EVERY new ledger coverage column at `ledger.py --check` — a non-`none` grade REQUIRES a discoverable conformance artifact (solution ships exec + `cells/*.json`, mirroring detector `ships_safe`), else a `solution-no-conformance` gap must fail `--check`. Never grade coverage from a manifest string alone. A green suite cannot see this — the advisor caught it, not the tests.
