New GAP finding CA-GAP-028: authored subagents exist and not one of them names `model:` or `effort:`, so every delegated task runs on the main conversation's model (`model` defaults to `inherit`). Cites BP-MODEL-001/002, landed in C1. `whats-active` and `manifest` now carry `model`/`effort` per agent. Shipped as a conditional LEVER rather than a 25th dimension, and the choice was made by measurement: as a t3 dimension the agent-less marketplace-medium fixture would count it vacuously-present, moving the denominators 41->42 and utilization 44->45 — which flips `segment` "Developing"->"Competent" in the frozen v5.0.0 posture baseline, a field strip-retired-gap.mjs does not mask. A lever never enters those denominators. The general rule is now an invariant in CLAUDE.md. One check across both axes, not one per axis: it fires only when neither is used anywhere, so a deliberate everything-on-one-model policy stays silent. Cost is recall, chosen for precision. Found by dogfooding, fixed red-first: `model: inherit` is the documented default spelled out, so it must not count as routing — otherwise a config opts out of the opportunity without changing anything real. Two pre-existing defects surfaced and closed on the way: - The humanizer guard asserted TRANSLATIONS.GAP.static EQUALS the dimension titles, which forbade humanizing any lever — all three existing levers fell through to the generic "feature opportunity" default, wrong for a budget lever. Guard now requires coverage of every emittable title, seen red against those three before the entries were written. - Two hand-written copies of the lever list (finding-codes guard, humanizer guard) merged into one exported LEVERS registry carrying code AND title. - suppression-validation pinned CA-GAP-028 as an unoccupied number; C4 claimed it. Fixed structurally with a derived first-free id, not by picking a new literal — same class as #60's "bump this again". Suite 1596/0. Frozen v5.0.0 snapshots untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pq3nye21RVYk4pZLeT8pGz
63 lines
2.6 KiB
JavaScript
63 lines
2.6 KiB
JavaScript
/**
|
|
* A suppression that names no known check must be reported, not silently
|
|
* ignored (M-BUG-28, prediction 8).
|
|
*
|
|
* This is what makes the ID-semantics change safe to ship: pins written against
|
|
* the old positional numbering either still name a real check, or they now name
|
|
* nothing — and "nothing" has to be visible. A silently-dead suppression is the
|
|
* same failure the old scheme had, just in the other direction.
|
|
*/
|
|
|
|
import { describe, it } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { parseIgnoreFile, unknownSuppressions } from '../../scanners/lib/suppression.mjs';
|
|
import { FINDING_CODES } from '../../scanners/lib/finding-codes.mjs';
|
|
|
|
/**
|
|
* The next number no check in `scanner` occupies, DERIVED rather than written
|
|
* down. An unoccupied number is a moving target — every added check claims one —
|
|
* so a literal here expires the moment the registry grows, which is what C4
|
|
* (claiming CA-GAP-028) demonstrated. Derived, the input is unoccupied by
|
|
* construction; the assertion it feeds is unchanged.
|
|
*/
|
|
function firstFreeId(scanner) {
|
|
const n = Math.max(...Object.values(FINDING_CODES[scanner])) + 1;
|
|
return `CA-${scanner}-${String(n).padStart(3, '0')}`;
|
|
}
|
|
|
|
describe('unknownSuppressions', () => {
|
|
it('accepts an exact ID that names a declared check', () => {
|
|
const s = parseIgnoreFile('CA-SKL-003\n');
|
|
assert.deepEqual(unknownSuppressions(s), []);
|
|
});
|
|
|
|
it('reports an exact ID that names no declared check', () => {
|
|
// CA-GAP-099 has never existed; the PLH one is past the end of PLH's range.
|
|
const pastEnd = firstFreeId('PLH');
|
|
const s = parseIgnoreFile(`CA-GAP-099\n${pastEnd}\n`);
|
|
assert.deepEqual(unknownSuppressions(s), ['CA-GAP-099', pastEnd]);
|
|
});
|
|
|
|
it('reports an ID whose number no check occupies rather than pretending it matches', () => {
|
|
// The registry never reissues a retired key's number, so an ID can name a
|
|
// hole. Any unoccupied number exercises the same path.
|
|
const free = firstFreeId('GAP');
|
|
const s = parseIgnoreFile(`${free}\n`);
|
|
assert.deepEqual(unknownSuppressions(s), [free]);
|
|
});
|
|
|
|
it('accepts a scanner-wide glob for a real scanner', () => {
|
|
const s = parseIgnoreFile('CA-GAP-*\nCA-PLH-*\n');
|
|
assert.deepEqual(unknownSuppressions(s), []);
|
|
});
|
|
|
|
it('reports a glob for a scanner that does not exist', () => {
|
|
const s = parseIgnoreFile('CA-XYZ-*\n');
|
|
assert.deepEqual(unknownSuppressions(s), ['CA-XYZ-*']);
|
|
});
|
|
|
|
it('stays quiet on an empty ignore file', () => {
|
|
assert.deepEqual(unknownSuppressions(parseIgnoreFile('# just a comment\n')), []);
|
|
assert.deepEqual(unknownSuppressions([]), []);
|
|
});
|
|
});
|