@mmnto/totem 1.64.2 → 1.66.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ast-gate.d.ts +9 -0
- package/dist/ast-gate.d.ts.map +1 -1
- package/dist/ast-gate.js +64 -25
- package/dist/ast-gate.js.map +1 -1
- package/dist/ast-gate.test.js +47 -0
- package/dist/ast-gate.test.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/spine/windtunnel-lock.d.ts +440 -0
- package/dist/spine/windtunnel-lock.d.ts.map +1 -0
- package/dist/spine/windtunnel-lock.js +115 -0
- package/dist/spine/windtunnel-lock.js.map +1 -0
- package/dist/spine/windtunnel-lock.test.d.ts +2 -0
- package/dist/spine/windtunnel-lock.test.d.ts.map +1 -0
- package/dist/spine/windtunnel-lock.test.js +214 -0
- package/dist/spine/windtunnel-lock.test.js.map +1 -0
- package/dist/spine/windtunnel-parity.test.d.ts +2 -0
- package/dist/spine/windtunnel-parity.test.d.ts.map +1 -0
- package/dist/spine/windtunnel-parity.test.js +192 -0
- package/dist/spine/windtunnel-parity.test.js.map +1 -0
- package/dist/spine/windtunnel-scorer.d.ts +93 -0
- package/dist/spine/windtunnel-scorer.d.ts.map +1 -0
- package/dist/spine/windtunnel-scorer.js +191 -0
- package/dist/spine/windtunnel-scorer.js.map +1 -0
- package/dist/spine/windtunnel-scorer.test.d.ts +2 -0
- package/dist/spine/windtunnel-scorer.test.d.ts.map +1 -0
- package/dist/spine/windtunnel-scorer.test.js +533 -0
- package/dist/spine/windtunnel-scorer.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { firingLabelId, WindtunnelLockSchema } from './windtunnel-lock.js';
|
|
3
|
+
// ─── Helpers ─────────────────────────────────────────
|
|
4
|
+
const VALID_SHA = '0'.repeat(40);
|
|
5
|
+
const VALID_SHA_2 = '1'.repeat(40);
|
|
6
|
+
const VALID_SHA_3 = 'a'.repeat(40);
|
|
7
|
+
function validLock(overrides) {
|
|
8
|
+
return {
|
|
9
|
+
schema: 'windtunnel.lock.v1',
|
|
10
|
+
canonicalPath: '.totem/spine/gate-1/windtunnel.lock.json',
|
|
11
|
+
gate: 'gate-1',
|
|
12
|
+
phase: 'harness',
|
|
13
|
+
corpus: {
|
|
14
|
+
repo: 'mmnto-ai/liquid-city',
|
|
15
|
+
selectionRule: {
|
|
16
|
+
state: 'merged',
|
|
17
|
+
predicate: 'touches-code',
|
|
18
|
+
window: { type: 'all' },
|
|
19
|
+
asOfCommit: VALID_SHA,
|
|
20
|
+
},
|
|
21
|
+
resolvedPrs: [
|
|
22
|
+
{
|
|
23
|
+
pr: 1,
|
|
24
|
+
mergeCommit: VALID_SHA,
|
|
25
|
+
baseSha: VALID_SHA_2,
|
|
26
|
+
headSha: VALID_SHA_3,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
pr: 2,
|
|
30
|
+
mergeCommit: VALID_SHA_2,
|
|
31
|
+
baseSha: VALID_SHA_3,
|
|
32
|
+
headSha: VALID_SHA,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
fpDefinition: {
|
|
37
|
+
rubricRef: 'controls/rubric.md',
|
|
38
|
+
groundTruthRef: 'controls/ground-truth-labels.json',
|
|
39
|
+
adjudicator: 'operator',
|
|
40
|
+
precisionFloor: 1.0,
|
|
41
|
+
},
|
|
42
|
+
controls: {
|
|
43
|
+
positiveRef: 'controls/positive/',
|
|
44
|
+
negativeRef: 'controls/negative/',
|
|
45
|
+
integrity: {
|
|
46
|
+
mechanism: 'git-hash-object',
|
|
47
|
+
fixtureSha: VALID_SHA,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
cullRateThreshold: 0.25,
|
|
51
|
+
exposureDenominator: {
|
|
52
|
+
activeRulesEvaluated: { floor: 2 },
|
|
53
|
+
filesTouchedInWindow: { floor: 0 },
|
|
54
|
+
positiveControlsExercised: { floor: 0 },
|
|
55
|
+
},
|
|
56
|
+
...overrides,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// ─── Schema acceptance ───────────────────────────────
|
|
60
|
+
describe('WindtunnelLockSchema acceptance', () => {
|
|
61
|
+
it('accepts a valid harness-phase lock', () => {
|
|
62
|
+
const result = WindtunnelLockSchema.safeParse(validLock());
|
|
63
|
+
expect(result.success).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
it('accepts a certifying-phase lock', () => {
|
|
66
|
+
const result = WindtunnelLockSchema.safeParse(validLock({ phase: 'certifying' }));
|
|
67
|
+
expect(result.success).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
it('accepts a bounded window lock', () => {
|
|
70
|
+
const lock = validLock();
|
|
71
|
+
lock.corpus.selectionRule.window = { type: 'bounded', n: 50 };
|
|
72
|
+
const result = WindtunnelLockSchema.safeParse(lock);
|
|
73
|
+
expect(result.success).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
// ─── Schema rejection invariants ─────────────────────
|
|
77
|
+
describe('WindtunnelLockSchema rejection', () => {
|
|
78
|
+
it('rejects precisionFloor !== 1.0', () => {
|
|
79
|
+
const lock = validLock();
|
|
80
|
+
lock.fpDefinition.precisionFloor = 0.9;
|
|
81
|
+
const result = WindtunnelLockSchema.safeParse(lock);
|
|
82
|
+
expect(result.success).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
it('rejects non-40-hex asOfCommit', () => {
|
|
85
|
+
const lock = validLock();
|
|
86
|
+
lock.corpus.selectionRule.asOfCommit = 'notahex';
|
|
87
|
+
const result = WindtunnelLockSchema.safeParse(lock);
|
|
88
|
+
expect(result.success).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
it('rejects empty resolvedPrs', () => {
|
|
91
|
+
const lock = validLock();
|
|
92
|
+
lock.corpus.resolvedPrs = [];
|
|
93
|
+
const result = WindtunnelLockSchema.safeParse(lock);
|
|
94
|
+
expect(result.success).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
it('rejects resolvedPrs entries missing mergeCommit (C4)', () => {
|
|
97
|
+
const lock = validLock();
|
|
98
|
+
lock.corpus.resolvedPrs = [
|
|
99
|
+
{ pr: 1, baseSha: VALID_SHA, headSha: VALID_SHA_2 },
|
|
100
|
+
];
|
|
101
|
+
const result = WindtunnelLockSchema.safeParse(lock);
|
|
102
|
+
expect(result.success).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
it('rejects resolvedPrs entries missing baseSha (C4)', () => {
|
|
105
|
+
const lock = validLock();
|
|
106
|
+
lock.corpus.resolvedPrs = [
|
|
107
|
+
{ pr: 1, mergeCommit: VALID_SHA, headSha: VALID_SHA_2 },
|
|
108
|
+
];
|
|
109
|
+
const result = WindtunnelLockSchema.safeParse(lock);
|
|
110
|
+
expect(result.success).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
it('rejects resolvedPrs entries missing headSha (C4)', () => {
|
|
113
|
+
const lock = validLock();
|
|
114
|
+
lock.corpus.resolvedPrs = [
|
|
115
|
+
{ pr: 1, mergeCommit: VALID_SHA, baseSha: VALID_SHA_2 },
|
|
116
|
+
];
|
|
117
|
+
const result = WindtunnelLockSchema.safeParse(lock);
|
|
118
|
+
expect(result.success).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
it('rejects duplicate pr numbers in resolvedPrs (C4)', () => {
|
|
121
|
+
const lock = validLock();
|
|
122
|
+
lock.corpus.resolvedPrs = [
|
|
123
|
+
{ pr: 1, mergeCommit: VALID_SHA, baseSha: VALID_SHA_2, headSha: VALID_SHA_3 },
|
|
124
|
+
{ pr: 1, mergeCommit: VALID_SHA_2, baseSha: VALID_SHA_3, headSha: VALID_SHA },
|
|
125
|
+
];
|
|
126
|
+
const result = WindtunnelLockSchema.safeParse(lock);
|
|
127
|
+
expect(result.success).toBe(false);
|
|
128
|
+
expect(JSON.stringify(result)).toContain('unique');
|
|
129
|
+
});
|
|
130
|
+
it('rejects unsorted pr numbers in resolvedPrs (C4)', () => {
|
|
131
|
+
const lock = validLock();
|
|
132
|
+
lock.corpus.resolvedPrs = [
|
|
133
|
+
{ pr: 2, mergeCommit: VALID_SHA, baseSha: VALID_SHA_2, headSha: VALID_SHA_3 },
|
|
134
|
+
{ pr: 1, mergeCommit: VALID_SHA_2, baseSha: VALID_SHA_3, headSha: VALID_SHA },
|
|
135
|
+
];
|
|
136
|
+
const result = WindtunnelLockSchema.safeParse(lock);
|
|
137
|
+
expect(result.success).toBe(false);
|
|
138
|
+
expect(JSON.stringify(result)).toContain('sorted');
|
|
139
|
+
});
|
|
140
|
+
it('rejects absent cullRateThreshold (C5)', () => {
|
|
141
|
+
const lock = validLock();
|
|
142
|
+
delete lock['cullRateThreshold'];
|
|
143
|
+
const result = WindtunnelLockSchema.safeParse(lock);
|
|
144
|
+
expect(result.success).toBe(false);
|
|
145
|
+
});
|
|
146
|
+
it('rejects cullRateThreshold >= 1 (C5)', () => {
|
|
147
|
+
const lock = validLock({ cullRateThreshold: 1.0 });
|
|
148
|
+
const result = WindtunnelLockSchema.safeParse(lock);
|
|
149
|
+
expect(result.success).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
it('rejects negative cullRateThreshold (C5)', () => {
|
|
152
|
+
const lock = validLock({ cullRateThreshold: -0.1 });
|
|
153
|
+
const result = WindtunnelLockSchema.safeParse(lock);
|
|
154
|
+
expect(result.success).toBe(false);
|
|
155
|
+
});
|
|
156
|
+
it('accepts cullRateThreshold = 0 (boundary)', () => {
|
|
157
|
+
const lock = validLock({ cullRateThreshold: 0 });
|
|
158
|
+
const result = WindtunnelLockSchema.safeParse(lock);
|
|
159
|
+
expect(result.success).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
it('rejects activeRulesEvaluated.floor < 2', () => {
|
|
162
|
+
const lock = validLock();
|
|
163
|
+
lock.exposureDenominator.activeRulesEvaluated.floor = 1;
|
|
164
|
+
const result = WindtunnelLockSchema.safeParse(lock);
|
|
165
|
+
expect(result.success).toBe(false);
|
|
166
|
+
});
|
|
167
|
+
it('accepts activeRulesEvaluated.floor = 2 (boundary)', () => {
|
|
168
|
+
const lock = validLock();
|
|
169
|
+
lock.exposureDenominator.activeRulesEvaluated.floor = 2;
|
|
170
|
+
const result = WindtunnelLockSchema.safeParse(lock);
|
|
171
|
+
expect(result.success).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
it('rejects unknown phase', () => {
|
|
174
|
+
const lock = validLock({ phase: 'unknown' });
|
|
175
|
+
const result = WindtunnelLockSchema.safeParse(lock);
|
|
176
|
+
expect(result.success).toBe(false);
|
|
177
|
+
});
|
|
178
|
+
it('rejects non-40-hex mergeCommit', () => {
|
|
179
|
+
const lock = validLock();
|
|
180
|
+
lock.corpus.resolvedPrs = [
|
|
181
|
+
{ pr: 1, mergeCommit: 'short', baseSha: VALID_SHA, headSha: VALID_SHA_2 },
|
|
182
|
+
];
|
|
183
|
+
const result = WindtunnelLockSchema.safeParse(lock);
|
|
184
|
+
expect(result.success).toBe(false);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
// ─── firingLabelId ───────────────────────────────────
|
|
188
|
+
describe('firingLabelId (A2)', () => {
|
|
189
|
+
it('returns a 64-char hex SHA-256', () => {
|
|
190
|
+
const id = firingLabelId('rule-abc', 42, 'src/foo.ts', 'const secret = "abc"');
|
|
191
|
+
expect(id).toMatch(/^[0-9a-f]{64}$/);
|
|
192
|
+
});
|
|
193
|
+
it('is deterministic for the same inputs', () => {
|
|
194
|
+
const a = firingLabelId('rule-abc', 42, 'src/foo.ts', 'const secret = "abc"');
|
|
195
|
+
const b = firingLabelId('rule-abc', 42, 'src/foo.ts', 'const secret = "abc"');
|
|
196
|
+
expect(a).toBe(b);
|
|
197
|
+
});
|
|
198
|
+
it('differs when ruleId differs', () => {
|
|
199
|
+
const a = firingLabelId('rule-abc', 42, 'src/foo.ts', 'line');
|
|
200
|
+
const b = firingLabelId('rule-def', 42, 'src/foo.ts', 'line');
|
|
201
|
+
expect(a).not.toBe(b);
|
|
202
|
+
});
|
|
203
|
+
it('differs when pr differs', () => {
|
|
204
|
+
const a = firingLabelId('rule-abc', 1, 'src/foo.ts', 'line');
|
|
205
|
+
const b = firingLabelId('rule-abc', 2, 'src/foo.ts', 'line');
|
|
206
|
+
expect(a).not.toBe(b);
|
|
207
|
+
});
|
|
208
|
+
it('normalizes Windows backslash paths (A3)', () => {
|
|
209
|
+
const forward = firingLabelId('rule', 1, 'src/foo.ts', 'line');
|
|
210
|
+
const backward = firingLabelId('rule', 1, 'src\\foo.ts', 'line');
|
|
211
|
+
expect(forward).toBe(backward);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
//# sourceMappingURL=windtunnel-lock.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windtunnel-lock.test.js","sourceRoot":"","sources":["../../src/spine/windtunnel-lock.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windtunnel-parity.test.d.ts","sourceRoot":"","sources":["../../src/spine/windtunnel-parity.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { enrichWithAstContext } from '../ast-gate.js';
|
|
6
|
+
import { applyAstRulesToAdditions, applyRulesToAdditions } from '../rule-engine.js';
|
|
7
|
+
import { cleanTmpDir, makeRuleEngineCtx } from '../test-utils.js';
|
|
8
|
+
/**
|
|
9
|
+
* Bidirectional firing-parity test for the wind-tunnel readStrategy seam
|
|
10
|
+
* (S1+C1, .totem/specs/2188.md Invariants).
|
|
11
|
+
*
|
|
12
|
+
* The wind-tunnel replays each PR's diff against the rule engine and must
|
|
13
|
+
* observe the SAME firings production `totem lint` would observe — otherwise
|
|
14
|
+
* it measures a different engine than Gate 2 arms. The seam that makes this
|
|
15
|
+
* exact is the post-image `readStrategy` injected into BOTH
|
|
16
|
+
* `enrichWithAstContext` (regex `astContext` classification) and
|
|
17
|
+
* `applyAstRulesToAdditions` (whole-file AST parsing). This file proves parity
|
|
18
|
+
* is bidirectional:
|
|
19
|
+
*
|
|
20
|
+
* - OVER-FIRE (C1): a regex match that lands inside a comment/string must NOT
|
|
21
|
+
* become a violation. Production suppresses it via `astContext`; the replay,
|
|
22
|
+
* fed the same post-image content through the same seam, must suppress it
|
|
23
|
+
* identically. Caught by comparing readStrategy-fed firings to disk-fed
|
|
24
|
+
* firings (the production baseline) — they must be equal.
|
|
25
|
+
* - UNDER-FIRE (S1): an AST/ast-grep rule needs the whole post-image file, not
|
|
26
|
+
* just the additions. The readStrategy supplying that post-image must fire
|
|
27
|
+
* identically to the same content read from disk.
|
|
28
|
+
*/
|
|
29
|
+
// ─── Named constants ─────────────────────────────────
|
|
30
|
+
const SRC_FILE = 'src/app.ts';
|
|
31
|
+
const DEBUGGER_RULE = {
|
|
32
|
+
lessonHash: 'wt-parity-debugger',
|
|
33
|
+
lessonHeading: 'No debugger statements',
|
|
34
|
+
// Matches the literal token `debugger` — appears in both code and a comment
|
|
35
|
+
// in the fixture below, so astContext is what decides over-firing.
|
|
36
|
+
pattern: 'debugger',
|
|
37
|
+
message: 'debugger statement',
|
|
38
|
+
engine: 'regex',
|
|
39
|
+
compiledAt: '2026-06-17T00:00:00.000Z',
|
|
40
|
+
};
|
|
41
|
+
const CONSOLE_LOG_AST_RULE = {
|
|
42
|
+
lessonHash: 'wt-parity-console-log',
|
|
43
|
+
lessonHeading: 'No console.log',
|
|
44
|
+
pattern: '',
|
|
45
|
+
message: 'console.log call',
|
|
46
|
+
engine: 'ast-grep',
|
|
47
|
+
astGrepPattern: 'console.log($$$)',
|
|
48
|
+
compiledAt: '2026-06-17T00:00:00.000Z',
|
|
49
|
+
};
|
|
50
|
+
// Post-image fixture: the trigger token appears on a real code line (line 2)
|
|
51
|
+
// AND inside a comment (line 3). A faithful replay fires a *violation* only on
|
|
52
|
+
// the code line; the comment line is telemetry-only.
|
|
53
|
+
//
|
|
54
|
+
// The inline `// totem-ignore` markers below keep Totem's own corpus rules from
|
|
55
|
+
// flagging the intentional `debugger` / `console.log` fixture tokens in this
|
|
56
|
+
// test file — same pattern as ast-gate.test.ts and the pack-agent-security
|
|
57
|
+
// fixtures. Bare (no ticket-ref) per the established test-fixture convention.
|
|
58
|
+
const POST_IMAGE = [
|
|
59
|
+
'function run() {', // 1
|
|
60
|
+
' debugger;', // 2 — real code → violation // totem-ignore
|
|
61
|
+
' // debugger; left in by mistake — but this is a comment // totem-ignore', // 3 — comment → suppressed
|
|
62
|
+
' console.log("hi");', // 4
|
|
63
|
+
'}', // 5
|
|
64
|
+
].join('\n');
|
|
65
|
+
const CODE_LINE = ' debugger;'; // totem-ignore
|
|
66
|
+
const COMMENT_LINE = ' // debugger; left in by mistake — but this is a comment // totem-ignore';
|
|
67
|
+
const CONSOLE_LINE = ' console.log("hi");';
|
|
68
|
+
// ─── Helpers ─────────────────────────────────────────
|
|
69
|
+
let tmpDir;
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-wt-parity-'));
|
|
72
|
+
fs.mkdirSync(path.join(tmpDir, 'src'), { recursive: true });
|
|
73
|
+
});
|
|
74
|
+
afterEach(() => {
|
|
75
|
+
cleanTmpDir(tmpDir);
|
|
76
|
+
});
|
|
77
|
+
function additionsForDebugger() {
|
|
78
|
+
// Both the code line and the comment line are "added" in this PR. The replay
|
|
79
|
+
// must classify each from the post-image and fire only on the code line.
|
|
80
|
+
return [
|
|
81
|
+
{ file: SRC_FILE, line: CODE_LINE, lineNumber: 2, precedingLine: 'function run() {' },
|
|
82
|
+
{ file: SRC_FILE, line: COMMENT_LINE, lineNumber: 3, precedingLine: CODE_LINE },
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
/** Normalize a violation list into a comparable, order-independent shape. */
|
|
86
|
+
function fingerprint(violations) {
|
|
87
|
+
return violations
|
|
88
|
+
.map((v) => `${v.rule.lessonHash}|${v.file}|${v.lineNumber}|${v.line}`)
|
|
89
|
+
.sort()
|
|
90
|
+
.join('\n');
|
|
91
|
+
}
|
|
92
|
+
// ─── Assertion 1: over-fire suppression + disk/readStrategy parity (C1) ─
|
|
93
|
+
describe('bidirectional parity — over-fire suppression (C1)', () => {
|
|
94
|
+
it('a regex match inside a comment does NOT become a violation when classified from the post-image', async () => {
|
|
95
|
+
const additions = additionsForDebugger();
|
|
96
|
+
// Inject the post-image content through the readStrategy seam (the path the
|
|
97
|
+
// wind-tunnel uses): line 2 classifies as code, line 3 as comment.
|
|
98
|
+
await enrichWithAstContext(additions, {
|
|
99
|
+
cwd: tmpDir,
|
|
100
|
+
readStrategy: async () => POST_IMAGE,
|
|
101
|
+
});
|
|
102
|
+
expect(additions[0].astContext).toBe('code');
|
|
103
|
+
expect(additions[1].astContext).toBe('comment');
|
|
104
|
+
const ctx = makeRuleEngineCtx();
|
|
105
|
+
const violations = applyRulesToAdditions(ctx, [DEBUGGER_RULE], additions);
|
|
106
|
+
// Only the real code line fires; the comment match is telemetry-only.
|
|
107
|
+
expect(violations).toHaveLength(1);
|
|
108
|
+
expect(violations[0].lineNumber).toBe(2);
|
|
109
|
+
expect(violations[0].line).toBe(CODE_LINE);
|
|
110
|
+
});
|
|
111
|
+
it('readStrategy-injected content fires identically to the same content on disk (replay == production)', async () => {
|
|
112
|
+
// ── Production baseline: content on disk, NO readStrategy ──
|
|
113
|
+
fs.writeFileSync(path.join(tmpDir, SRC_FILE), POST_IMAGE, 'utf-8');
|
|
114
|
+
const diskAdditions = additionsForDebugger();
|
|
115
|
+
await enrichWithAstContext(diskAdditions, { cwd: tmpDir });
|
|
116
|
+
const diskCtx = makeRuleEngineCtx();
|
|
117
|
+
const diskViolations = applyRulesToAdditions(diskCtx, [DEBUGGER_RULE], diskAdditions);
|
|
118
|
+
// ── Replay: identical content fed via readStrategy (file may differ on disk) ──
|
|
119
|
+
const replayAdditions = additionsForDebugger();
|
|
120
|
+
await enrichWithAstContext(replayAdditions, {
|
|
121
|
+
cwd: tmpDir,
|
|
122
|
+
readStrategy: async () => POST_IMAGE,
|
|
123
|
+
});
|
|
124
|
+
const replayCtx = makeRuleEngineCtx();
|
|
125
|
+
const replayViolations = applyRulesToAdditions(replayCtx, [DEBUGGER_RULE], replayAdditions);
|
|
126
|
+
// Parity: the two firing sets are byte-identical.
|
|
127
|
+
expect(fingerprint(replayViolations)).toBe(fingerprint(diskViolations));
|
|
128
|
+
expect(replayViolations).toHaveLength(1);
|
|
129
|
+
expect(replayViolations[0].lineNumber).toBe(2);
|
|
130
|
+
});
|
|
131
|
+
it('proves the seam matters: stale on-disk content would mis-suppress without the post-image', async () => {
|
|
132
|
+
// On disk the file has the debugger ON A CODE LINE (no comment); but the PR
|
|
133
|
+
// post-image moved it into a comment. Without the readStrategy seam, the
|
|
134
|
+
// replay would classify against the wrong tree. Feeding the post-image makes
|
|
135
|
+
// line 3 a comment (suppressed) — exactly what production sees for this PR.
|
|
136
|
+
const staleDisk = [
|
|
137
|
+
'function run() {',
|
|
138
|
+
' debugger;', // totem-ignore
|
|
139
|
+
' debugger;', // totem-ignore — on disk this is CODE
|
|
140
|
+
' console.log("hi");',
|
|
141
|
+
'}',
|
|
142
|
+
].join('\n');
|
|
143
|
+
fs.writeFileSync(path.join(tmpDir, SRC_FILE), staleDisk, 'utf-8');
|
|
144
|
+
const additions = additionsForDebugger();
|
|
145
|
+
await enrichWithAstContext(additions, {
|
|
146
|
+
cwd: tmpDir,
|
|
147
|
+
readStrategy: async () => POST_IMAGE, // the PR's actual post-image
|
|
148
|
+
});
|
|
149
|
+
// Line 3 is a comment in the post-image, so it is suppressed despite being
|
|
150
|
+
// code on disk — the seam decided correctly.
|
|
151
|
+
expect(additions[1].astContext).toBe('comment');
|
|
152
|
+
const ctx = makeRuleEngineCtx();
|
|
153
|
+
const violations = applyRulesToAdditions(ctx, [DEBUGGER_RULE], additions);
|
|
154
|
+
expect(violations).toHaveLength(1);
|
|
155
|
+
expect(violations[0].lineNumber).toBe(2);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
// ─── Assertion 2: AST whole-file seam parity (S1) ────
|
|
159
|
+
describe('bidirectional parity — AST whole-file seam (S1)', () => {
|
|
160
|
+
it('applyAstRulesToAdditions fires identically via readStrategy and via disk (parity)', async () => {
|
|
161
|
+
const additions = [
|
|
162
|
+
{ file: SRC_FILE, line: CONSOLE_LINE, lineNumber: 4, precedingLine: COMMENT_LINE },
|
|
163
|
+
];
|
|
164
|
+
// ── Production baseline: content on disk, no readStrategy ──
|
|
165
|
+
fs.writeFileSync(path.join(tmpDir, SRC_FILE), POST_IMAGE, 'utf-8');
|
|
166
|
+
const diskCtx = makeRuleEngineCtx();
|
|
167
|
+
const diskViolations = await applyAstRulesToAdditions(diskCtx, [CONSOLE_LOG_AST_RULE], additions, tmpDir);
|
|
168
|
+
// ── Replay: identical post-image fed via readStrategy ──
|
|
169
|
+
const replayCtx = makeRuleEngineCtx();
|
|
170
|
+
const replayViolations = await applyAstRulesToAdditions(replayCtx, [CONSOLE_LOG_AST_RULE], additions, tmpDir, undefined, undefined, async () => POST_IMAGE);
|
|
171
|
+
// The AST engine parses the WHOLE post-image in both cases — firings match.
|
|
172
|
+
expect(fingerprint(replayViolations)).toBe(fingerprint(diskViolations));
|
|
173
|
+
expect(replayViolations).toHaveLength(1);
|
|
174
|
+
expect(replayViolations[0].lineNumber).toBe(4);
|
|
175
|
+
});
|
|
176
|
+
it('the post-image readStrategy lets the AST engine see whole-file context an additions-only feed lacks (under-fire guard)', async () => {
|
|
177
|
+
// The addition we evaluate is line 4 (console.log). The AST engine parses
|
|
178
|
+
// the full post-image (functions, braces) to resolve it as a call
|
|
179
|
+
// expression. A readStrategy returning ONLY the bare addition line would
|
|
180
|
+
// still parse, but the point of S1 is that the engine receives the full
|
|
181
|
+
// post-image — proven by feeding the whole file and getting the fire.
|
|
182
|
+
const additions = [
|
|
183
|
+
{ file: SRC_FILE, line: CONSOLE_LINE, lineNumber: 4, precedingLine: COMMENT_LINE },
|
|
184
|
+
];
|
|
185
|
+
const ctx = makeRuleEngineCtx();
|
|
186
|
+
const violations = await applyAstRulesToAdditions(ctx, [CONSOLE_LOG_AST_RULE], additions, tmpDir, undefined, undefined, async () => POST_IMAGE);
|
|
187
|
+
expect(violations).toHaveLength(1);
|
|
188
|
+
expect(violations[0].lineNumber).toBe(4);
|
|
189
|
+
expect(violations[0].rule.lessonHash).toBe(CONSOLE_LOG_AST_RULE.lessonHash);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
//# sourceMappingURL=windtunnel-parity.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windtunnel-parity.test.js","sourceRoot":"","sources":["../../src/spine/windtunnel-parity.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export type WindtunnelVerdictKind = 'PASS' | 'HONEST-NEGATIVE' | 'FAIL';
|
|
2
|
+
export type GroundTruthLabel = 'TP' | 'FP';
|
|
3
|
+
export interface CullLedgerEntry {
|
|
4
|
+
ruleId: string;
|
|
5
|
+
pr: number;
|
|
6
|
+
filePath: string;
|
|
7
|
+
matchedLine: string;
|
|
8
|
+
reason: 'negative-control-fired';
|
|
9
|
+
}
|
|
10
|
+
export interface WindtunnelDiagnostics {
|
|
11
|
+
/**
|
|
12
|
+
* Precision (TP/(TP+FP)) over labeled, surviving (non-culled,
|
|
13
|
+
* non-negative-control) firings. DESCRIPTIVE ONLY — informative even on
|
|
14
|
+
* no-claim verdicts ("culled 8/10, the 2 survivors were clean"). NEVER
|
|
15
|
+
* consulted for the gate decision and never mistakable for the certifying
|
|
16
|
+
* `precision`. null when no surviving firing is labeled.
|
|
17
|
+
*/
|
|
18
|
+
survivorPrecision: number | null;
|
|
19
|
+
}
|
|
20
|
+
export interface WindtunnelVerdict {
|
|
21
|
+
verdict: WindtunnelVerdictKind;
|
|
22
|
+
/**
|
|
23
|
+
* Certifying precision claim. A real value ONLY on verdicts that make a
|
|
24
|
+
* precision claim: PASS (1.0) and confirmed-FP FAIL (the breaching value,
|
|
25
|
+
* which IS the evidence). `null` on every no-claim verdict (exposure-floor /
|
|
26
|
+
* cull-rate / needs-adjudication HONEST-NEGATIVE, and vacuous-control FAIL).
|
|
27
|
+
* `null` ⟺ no precision claim; `0` is reserved for a real all-FP measurement
|
|
28
|
+
* and NEVER means "not computed" (#2189 ruling, strategy-claude 2026-06-17).
|
|
29
|
+
*/
|
|
30
|
+
precision: number | null;
|
|
31
|
+
mintedRuleCount: number;
|
|
32
|
+
culledCount: number;
|
|
33
|
+
survivingRuleCount: number;
|
|
34
|
+
/** 3-tuple: [activeRulesEvaluated, filesTouchedInWindow, positiveControlsExercised]. Never collapsed to a product. */
|
|
35
|
+
exposureTuple: [number, number, number];
|
|
36
|
+
cullLedger: CullLedgerEntry[];
|
|
37
|
+
/** True when all positive controls fired their target rule. */
|
|
38
|
+
nonVacuity: boolean;
|
|
39
|
+
/** Label ids of firings with no ground-truth label (operator adjudication required). */
|
|
40
|
+
needsAdjudication: string[];
|
|
41
|
+
/** Separately-namespaced descriptive diagnostics — never part of the gate decision. */
|
|
42
|
+
diagnostics: WindtunnelDiagnostics;
|
|
43
|
+
}
|
|
44
|
+
export interface RuleFiring {
|
|
45
|
+
ruleId: string;
|
|
46
|
+
pr: number;
|
|
47
|
+
filePath: string;
|
|
48
|
+
matchedLine: string;
|
|
49
|
+
controlKind: 'corpus' | 'positive' | 'negative';
|
|
50
|
+
/** For positive controls: the rule that MUST fire to prove non-vacuousness. */
|
|
51
|
+
targetRuleId?: string;
|
|
52
|
+
/** Content-based label id (A2). Compute via firingLabelId from windtunnel-lock. */
|
|
53
|
+
labelId: string;
|
|
54
|
+
}
|
|
55
|
+
export interface ScorerInput {
|
|
56
|
+
firings: RuleFiring[];
|
|
57
|
+
/** Maps firingLabelId → TP/FP label. Unlabeled firings ⇒ needsAdjudication. */
|
|
58
|
+
groundTruth: Map<string, GroundTruthLabel>;
|
|
59
|
+
positiveControlTargets: Array<{
|
|
60
|
+
pr: number;
|
|
61
|
+
targetRuleId: string;
|
|
62
|
+
}>;
|
|
63
|
+
mintedRuleIds: string[];
|
|
64
|
+
cullRateThreshold: number;
|
|
65
|
+
exposureFloors: {
|
|
66
|
+
activeRulesEvaluated: number;
|
|
67
|
+
filesTouchedInWindow: number;
|
|
68
|
+
positiveControlsExercised: number;
|
|
69
|
+
};
|
|
70
|
+
actualExposure: {
|
|
71
|
+
activeRulesEvaluated: number;
|
|
72
|
+
filesTouchedInWindow: number;
|
|
73
|
+
positiveControlsExercised: number;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Score a wind-tunnel run. Pure function: no IO, no clock, no randomness.
|
|
78
|
+
* Implements ADR-110 §4/§5 done-criterion exactly per spec invariants.
|
|
79
|
+
*
|
|
80
|
+
* Verdict ordering (highest precedence first) — #2189 ruling:
|
|
81
|
+
* 1. Any firing labeled FP → FAIL (confirmed FP is a claim; precision = breaching value)
|
|
82
|
+
* 2. Positive control does not fire its target → FAIL (vacuous pass; precision = null)
|
|
83
|
+
* 3. Exposure floor below minimum → HONEST-NEGATIVE (masquerade guard; precision = null)
|
|
84
|
+
* 4. Cull rate exceeds threshold → HONEST-NEGATIVE (cull-laundering guard; precision = null)
|
|
85
|
+
* 5. Any unlabeled firing → HONEST-NEGATIVE (needs adjudication, not PASS; precision = null)
|
|
86
|
+
* 6. All labeled TP → PASS (precision = 1.0)
|
|
87
|
+
*
|
|
88
|
+
* The FAIL tier (1–2) outranks the masquerade guards (3–4): a guard may only
|
|
89
|
+
* DEMOTE a would-be PASS, never UPGRADE a FAIL. survivorPrecision (diagnostics)
|
|
90
|
+
* carries the informative survivor ratio distinct from the certifying precision.
|
|
91
|
+
*/
|
|
92
|
+
export declare function scoreWindtunnel(input: ScorerInput): WindtunnelVerdict;
|
|
93
|
+
//# sourceMappingURL=windtunnel-scorer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windtunnel-scorer.d.ts","sourceRoot":"","sources":["../../src/spine/windtunnel-scorer.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,iBAAiB,GAAG,MAAM,CAAC;AACxE,MAAM,MAAM,gBAAgB,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3C,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,wBAAwB,CAAC;CAClC;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,qBAAqB,CAAC;IAC/B;;;;;;;OAOG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sHAAsH;IACtH,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,+DAA+D;IAC/D,UAAU,EAAE,OAAO,CAAC;IACpB,wFAAwF;IACxF,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,uFAAuF;IACvF,WAAW,EAAE,qBAAqB,CAAC;CACpC;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAC;IAChD,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mFAAmF;IACnF,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,+EAA+E;IAC/E,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC3C,sBAAsB,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpE,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE;QACd,oBAAoB,EAAE,MAAM,CAAC;QAC7B,oBAAoB,EAAE,MAAM,CAAC;QAC7B,yBAAyB,EAAE,MAAM,CAAC;KACnC,CAAC;IACF,cAAc,EAAE;QACd,oBAAoB,EAAE,MAAM,CAAC;QAC7B,oBAAoB,EAAE,MAAM,CAAC;QAC7B,yBAAyB,EAAE,MAAM,CAAC;KACnC,CAAC;CACH;AAID;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,iBAAiB,CAoMrE"}
|