Heuristic Calibration as a Hard Delivery Gate¶
Problem¶
Pattern-matching heuristics in workflow gates can fire on well-formed inputs, eroding user trust in the gate over time. If the false-positive rate is not validated before shipping, the gate becomes noise — users learn to dismiss it.
Discovered when building gray-area-heuristic.py for SPEC-127: the calibration
task was initially advisory ("run and note the results"), but the false-positive
risk warranted elevating it to a ship-blocker.
Solution¶
Include a mandatory calibration task in plan.md that:
- Runs the heuristic against a real corpus (5–10 existing spec/PRD files that are known-good — i.e., already shaped or clearly well-formed)
- Records the false-positive count and rate
- Requires that rate to be acceptable before the spec can be marked complete
The calibration task should be listed in tasks.md as type: gate — not
type: task — so it cannot be skipped without an explicit gate bypass.
Implementation Example (SPEC-127)¶
# tasks.md excerpt
- id: T11
title: Calibration gate — 0 FP on well-formed corpus
type: gate
acceptance: |
Run gray-area-heuristic.py against specs/[N]-*/spec.md files with
status=completed. Verify 0 false positives (no gray-area prompts
for well-formed specs). If FPs found, tighten patterns before ship.
The T11 anchor in the test suite ties acceptance criteria directly to calibration results — the suite fails if the calibration corpus produces FPs.
Prevention¶
Any future spec introducing a heuristic-based detection mechanism should:
- Add a calibration task as a hard gate (not advisory) in the plan
- Write a test fixture for the calibration check (T-last in the suite)
- Document the corpus used and the acceptable FP threshold in
decisions.md
Why Hard, Not Advisory¶
An advisory calibration produces a number but doesn't block. Developers under time pressure skip advisory steps. Making it a gate ensures the number is seen and acted on — and the test suite keeps it honest across future changes.
Calibration Outcome: Length-Gate the Strictest Sub-Rule (SPEC-162)¶
The gate tells you the FP rate; it does not tell you how to fix an over-firing heuristic. SPEC-162's FR-2a description-quality screen is the worked example.
The first heuristic required a positive signal — an explicit trigger/when-clause
token — in every skill description. Against the real 127-skill corpus that hit
55% false positives: well-authored, specific descriptions (vt-d-activate,
vt-d-2-plan, vt-d-spec-from-requirements) were flagged simply for not spelling
out a "when" word they didn't need.
The wrong fix is to loosen the rule uniformly (that reintroduces false negatives). The right fix is to length-gate the strictest sub-rule — demand the positive signal only where its absence is genuinely ambiguous, and let unambiguous items pass without it:
- Fail on the unconditionally-bad shapes (empty, first-person, vague stem like "helps with X") regardless of length.
- Require the explicit when-clause only when the description is short (< 80 chars) — a terse description with no trigger is ambiguous; a long, specific one carries its own signal and passes without the token.
Result on the SPEC-162 corpus: 0 FP on the stratified known-good set, and the library flag count dropped from 70 to 15/127 — the 15 being genuine terse/vague descriptions, not specific-but-tokenless ones.
Generalizable rule: when a heuristic requires a positive signal in every item and the calibration corpus shows it punishing specific-but-terse content, gate the strictest sub-rule on the axis that distinguishes "ambiguous" from "self-evident" (here, length) rather than dropping the sub-rule. Stratify the calibration corpus across the real population's sub-shapes (SPEC-162: English-workflow / non-English / persona-domain) so the FP budget measures the distribution the heuristic will actually see, not a convenient monoculture. Document the residual FP posture (long specific descriptions pass without an explicit trigger) as an accepted trade-off, not a bug.