@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,191 @@
|
|
|
1
|
+
// ─── Types ──────────────────────────────────────────
|
|
2
|
+
// ─── Pure scorer ─────────────────────────────────────
|
|
3
|
+
/**
|
|
4
|
+
* Score a wind-tunnel run. Pure function: no IO, no clock, no randomness.
|
|
5
|
+
* Implements ADR-110 §4/§5 done-criterion exactly per spec invariants.
|
|
6
|
+
*
|
|
7
|
+
* Verdict ordering (highest precedence first) — #2189 ruling:
|
|
8
|
+
* 1. Any firing labeled FP → FAIL (confirmed FP is a claim; precision = breaching value)
|
|
9
|
+
* 2. Positive control does not fire its target → FAIL (vacuous pass; precision = null)
|
|
10
|
+
* 3. Exposure floor below minimum → HONEST-NEGATIVE (masquerade guard; precision = null)
|
|
11
|
+
* 4. Cull rate exceeds threshold → HONEST-NEGATIVE (cull-laundering guard; precision = null)
|
|
12
|
+
* 5. Any unlabeled firing → HONEST-NEGATIVE (needs adjudication, not PASS; precision = null)
|
|
13
|
+
* 6. All labeled TP → PASS (precision = 1.0)
|
|
14
|
+
*
|
|
15
|
+
* The FAIL tier (1–2) outranks the masquerade guards (3–4): a guard may only
|
|
16
|
+
* DEMOTE a would-be PASS, never UPGRADE a FAIL. survivorPrecision (diagnostics)
|
|
17
|
+
* carries the informative survivor ratio distinct from the certifying precision.
|
|
18
|
+
*/
|
|
19
|
+
export function scoreWindtunnel(input) {
|
|
20
|
+
const { firings, groundTruth, positiveControlTargets, mintedRuleIds, cullRateThreshold, exposureFloors, actualExposure, } = input;
|
|
21
|
+
const mintedRuleCount = mintedRuleIds.length;
|
|
22
|
+
const cullLedger = [];
|
|
23
|
+
const needsAdjudication = [];
|
|
24
|
+
// Step 1: Cull rules that fire on negative controls (S2/C5).
|
|
25
|
+
// A rule firing on ANY negative-control item is culled + recorded in
|
|
26
|
+
// cullLedger. Never silently dropped.
|
|
27
|
+
const culledRuleIds = new Set();
|
|
28
|
+
for (const firing of firings) {
|
|
29
|
+
if (firing.controlKind === 'negative') {
|
|
30
|
+
culledRuleIds.add(firing.ruleId);
|
|
31
|
+
cullLedger.push({
|
|
32
|
+
ruleId: firing.ruleId,
|
|
33
|
+
pr: firing.pr,
|
|
34
|
+
filePath: firing.filePath.replace(/\\/g, '/'),
|
|
35
|
+
matchedLine: firing.matchedLine,
|
|
36
|
+
reason: 'negative-control-fired',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const culledCount = culledRuleIds.size;
|
|
41
|
+
const survivingRuleCount = mintedRuleCount - culledCount;
|
|
42
|
+
// Exposure tuple: always a 3-tuple, never collapsed (spec invariant).
|
|
43
|
+
const exposureTuple = [
|
|
44
|
+
actualExposure.activeRulesEvaluated,
|
|
45
|
+
actualExposure.filesTouchedInWindow,
|
|
46
|
+
actualExposure.positiveControlsExercised,
|
|
47
|
+
];
|
|
48
|
+
// Step 2: Label surviving (non-negative-control, non-culled) firings. Computed
|
|
49
|
+
// BEFORE the masquerade guards because the FAIL tier outranks them (#2189) — we
|
|
50
|
+
// must know hasFp/nonVacuity before deciding whether a guard may short-circuit.
|
|
51
|
+
let hasFp = false;
|
|
52
|
+
let tpCount = 0;
|
|
53
|
+
let labeledCount = 0;
|
|
54
|
+
for (const firing of firings) {
|
|
55
|
+
if (firing.controlKind === 'negative')
|
|
56
|
+
continue;
|
|
57
|
+
if (culledRuleIds.has(firing.ruleId))
|
|
58
|
+
continue;
|
|
59
|
+
const label = groundTruth.get(firing.labelId);
|
|
60
|
+
if (label === undefined) {
|
|
61
|
+
needsAdjudication.push(firing.labelId);
|
|
62
|
+
}
|
|
63
|
+
else if (label === 'FP') {
|
|
64
|
+
hasFp = true;
|
|
65
|
+
labeledCount++;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
tpCount++;
|
|
69
|
+
labeledCount++;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// survivorPrecision (diagnostic, descriptive): TP/(TP+FP) over labeled surviving
|
|
73
|
+
// firings — informative even on no-claim verdicts, NEVER the gate decision.
|
|
74
|
+
const survivorPrecision = labeledCount > 0 ? tpCount / labeledCount : null;
|
|
75
|
+
const diagnostics = { survivorPrecision };
|
|
76
|
+
// Step 3: Positive control non-vacuity check. Every positive control target
|
|
77
|
+
// must have its targetRuleId fire on an un-culled rule. A vacuous pass → FAIL.
|
|
78
|
+
let nonVacuity = true;
|
|
79
|
+
for (const target of positiveControlTargets) {
|
|
80
|
+
const fired = firings.some((f) => f.controlKind === 'positive' &&
|
|
81
|
+
f.pr === target.pr &&
|
|
82
|
+
f.ruleId === target.targetRuleId &&
|
|
83
|
+
!culledRuleIds.has(f.ruleId));
|
|
84
|
+
if (!fired) {
|
|
85
|
+
nonVacuity = false;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// ── FAIL tier (outranks the masquerade guards, #2189) ──
|
|
90
|
+
// Step 4a: Confirmed FP → FAIL. precision = the breaching value (the evidence,
|
|
91
|
+
// must be reported). A 0 here is a REAL all-FP measurement, never a sentinel.
|
|
92
|
+
// labeledCount ≥ 1 whenever hasFp, so the ratio is always defined.
|
|
93
|
+
if (hasFp) {
|
|
94
|
+
return {
|
|
95
|
+
verdict: 'FAIL',
|
|
96
|
+
precision: tpCount / labeledCount,
|
|
97
|
+
mintedRuleCount,
|
|
98
|
+
culledCount,
|
|
99
|
+
survivingRuleCount,
|
|
100
|
+
exposureTuple,
|
|
101
|
+
cullLedger,
|
|
102
|
+
nonVacuity,
|
|
103
|
+
needsAdjudication,
|
|
104
|
+
diagnostics,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
// Step 4b: Vacuous positive control → FAIL. precision = null — a structural
|
|
108
|
+
// failure makes no precision claim (#2189 Q-A); 0 would falsely read as all-FP.
|
|
109
|
+
if (!nonVacuity) {
|
|
110
|
+
return {
|
|
111
|
+
verdict: 'FAIL',
|
|
112
|
+
precision: null,
|
|
113
|
+
mintedRuleCount,
|
|
114
|
+
culledCount,
|
|
115
|
+
survivingRuleCount,
|
|
116
|
+
exposureTuple,
|
|
117
|
+
cullLedger,
|
|
118
|
+
nonVacuity: false,
|
|
119
|
+
needsAdjudication,
|
|
120
|
+
diagnostics,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
// ── Masquerade guards (may only DEMOTE a would-be PASS) ──
|
|
124
|
+
// Step 5: Exposure floor (P2). activeRules/positiveControls below floor →
|
|
125
|
+
// HONEST-NEGATIVE (no claim → precision null). Ranked above needs-adjudication
|
|
126
|
+
// (Step 7): labeling won't rescue a sub-floor run (strategy-claude tie-break).
|
|
127
|
+
if (actualExposure.activeRulesEvaluated < exposureFloors.activeRulesEvaluated ||
|
|
128
|
+
actualExposure.positiveControlsExercised < exposureFloors.positiveControlsExercised) {
|
|
129
|
+
return {
|
|
130
|
+
verdict: 'HONEST-NEGATIVE',
|
|
131
|
+
precision: null,
|
|
132
|
+
mintedRuleCount,
|
|
133
|
+
culledCount,
|
|
134
|
+
survivingRuleCount,
|
|
135
|
+
exposureTuple,
|
|
136
|
+
cullLedger,
|
|
137
|
+
nonVacuity,
|
|
138
|
+
needsAdjudication,
|
|
139
|
+
diagnostics,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
// Step 6: Cull-rate guard (C5/S2). culledCount / mintedRuleCount > threshold →
|
|
143
|
+
// HONEST-NEGATIVE. Guard applies only when mintedRuleCount > 0 (avoids division
|
|
144
|
+
// by zero at the harness phase). No claim → precision null.
|
|
145
|
+
if (mintedRuleCount > 0 && culledCount / mintedRuleCount > cullRateThreshold) {
|
|
146
|
+
return {
|
|
147
|
+
verdict: 'HONEST-NEGATIVE',
|
|
148
|
+
precision: null,
|
|
149
|
+
mintedRuleCount,
|
|
150
|
+
culledCount,
|
|
151
|
+
survivingRuleCount,
|
|
152
|
+
exposureTuple,
|
|
153
|
+
cullLedger,
|
|
154
|
+
nonVacuity,
|
|
155
|
+
needsAdjudication,
|
|
156
|
+
diagnostics,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
// Step 7: Unlabeled firings → not PASS (operator must adjudicate first). No
|
|
160
|
+
// claim → precision null.
|
|
161
|
+
if (needsAdjudication.length > 0) {
|
|
162
|
+
return {
|
|
163
|
+
verdict: 'HONEST-NEGATIVE',
|
|
164
|
+
precision: null,
|
|
165
|
+
mintedRuleCount,
|
|
166
|
+
culledCount,
|
|
167
|
+
survivingRuleCount,
|
|
168
|
+
exposureTuple,
|
|
169
|
+
cullLedger,
|
|
170
|
+
nonVacuity,
|
|
171
|
+
needsAdjudication,
|
|
172
|
+
diagnostics,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// Step 8: All firings labeled TP, exposure floors met, positive controls
|
|
176
|
+
// verified → PASS. precision = 1.0 (tpCount === labeledCount here; vacuously
|
|
177
|
+
// 1.0 when no firings — the harness phase).
|
|
178
|
+
return {
|
|
179
|
+
verdict: 'PASS',
|
|
180
|
+
precision: labeledCount > 0 ? tpCount / labeledCount : 1.0,
|
|
181
|
+
mintedRuleCount,
|
|
182
|
+
culledCount,
|
|
183
|
+
survivingRuleCount,
|
|
184
|
+
exposureTuple,
|
|
185
|
+
cullLedger,
|
|
186
|
+
nonVacuity,
|
|
187
|
+
needsAdjudication,
|
|
188
|
+
diagnostics,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=windtunnel-scorer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windtunnel-scorer.js","sourceRoot":"","sources":["../../src/spine/windtunnel-scorer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windtunnel-scorer.test.d.ts","sourceRoot":"","sources":["../../src/spine/windtunnel-scorer.test.ts"],"names":[],"mappings":""}
|