# p0151g: preserve scanner anchors in a structured top-N alongside the prose summary

- [Architecture] `ScannerTopFindings` as a record in `Contracts/Models` carrying per-scanner arrays (`Nuclei`, `Zap`, `Spectral`). Stored on the pipeline context under `ContextKeys.ScannerTopFindings` alongside the existing `ApiScanFindingsSummary` (prose) and `ApiScanFindingsByCategory` (slices). Both consumer shapes coexist: recon skills that need quick orientation read the prose summary; specialist skills (the scanner-correlation-judge in p0151f) read the structured top-N and cite specific template_ids / matched URLs.
- [Architecture] Per-scanner selectors as separate sealed services (`NucleiTopSelector`, `ZapTopSelector`, `SpectralTopSelector`) injected into `CompressApiScanFindingsHandler`. Each owns one scanner's sort + cap policy in isolation. A combined selector would have mixed orthogonal concerns (different severity vocabularies, different tie-break rules, different cap sizes); per-scanner classes keep each policy obvious and testable.
- [Architecture] Deterministic selection — not LLM-curated. A reproducible scan must select the same top-N every run for the same input. The selectors are pure functions over the finding lists, sorted by severity rank then by stable tie-breaker fields (matched URL + template id for Nuclei; URL + alertRef for ZAP; path + line within rule clusters for Spectral). No randomness, no LLM call.
- [Architecture] Per-scanner caps reserve slots — 20 Nuclei, 30 ZAP (chosen for headroom because ZAP typically emits ≤ 10), 30 Spectral. A single global cap of 50 would let a chatty scanner (Spectral often emits thousands) crowd out everything else. Per-scanner caps guarantee each evidence source contributes.
- [Architecture] Spectral selector groups by rule code before taking instances. The reference baseline produced ~1k warnings dominated by a handful of repeating rules (`missing-response-code` etc.). Grouping by rule first means a single chatty rule contributes at most 3 representative instances, leaving slots for rarer rules that signal more interesting drift.
- [Implementation] Spectral selector filters to errors only — `severity == "error"`. Warnings are too noisy at API scale and are already visible in the prose summary headline counts. If a future skill specifically needs warnings, it can read the raw `SpectralResult` from the pipeline context.
- [Implementation] Severity ranking is local to each selector. Nuclei uses critical/high/medium/low/info; ZAP uses high/medium/low/informational. Mapping to a single ordinal per selector keeps the comparison logic on numbers; unknown labels fall to rank 0 (last) so a typo or new severity does not crash the sort.
- [Implementation] `CompressApiScanFindingsHandler` ctor gains three new selector dependencies plus its existing logger. DI registration in `PipelineHandlersExtensions` adds the three selectors as transients. The selectors themselves are stateless so transient registration is purely cosmetic — singleton would also work, but transient matches the surrounding handler-collaborator pattern.
- [TradeOff] Prompt-strategy projection of `ScannerTopFindings` is deferred to p0151f (skill catalog rewrite). p0151g only writes the structured data into the pipeline context. The new scanner-correlation-judge skill in p0151f will be the primary consumer; adding an unconditional "Scanner Top Findings" section to every api-security skill's prompt before p0151f would inflate token cost for skills that do not need it.
- [TradeOff] Existing tests touching `CompressApiScanFindingsHandler` directly: zero. The handler is exercised through the pipeline composition; the new top-N selectors are unit-tested in isolation. Integration test coverage (handler-end-to-end) is implicit via the smoke harness coming in p0151h.
