@mmnto/cli 1.70.0 → 1.71.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/commands/spine-cert-corpus.d.ts +57 -0
- package/dist/commands/spine-cert-corpus.d.ts.map +1 -0
- package/dist/commands/spine-cert-corpus.js +51 -0
- package/dist/commands/spine-cert-corpus.js.map +1 -0
- package/dist/commands/spine-cert-corpus.test.d.ts +2 -0
- package/dist/commands/spine-cert-corpus.test.d.ts.map +1 -0
- package/dist/commands/spine-cert-corpus.test.js +112 -0
- package/dist/commands/spine-cert-corpus.test.js.map +1 -0
- package/dist/commands/spine-cert-e2e.test.d.ts +2 -0
- package/dist/commands/spine-cert-e2e.test.d.ts.map +1 -0
- package/dist/commands/spine-cert-e2e.test.js +145 -0
- package/dist/commands/spine-cert-e2e.test.js.map +1 -0
- package/dist/commands/spine-cert-persist.d.ts +63 -0
- package/dist/commands/spine-cert-persist.d.ts.map +1 -0
- package/dist/commands/spine-cert-persist.js +85 -0
- package/dist/commands/spine-cert-persist.js.map +1 -0
- package/dist/commands/spine-cert-persist.test.d.ts +2 -0
- package/dist/commands/spine-cert-persist.test.d.ts.map +1 -0
- package/dist/commands/spine-cert-persist.test.js +108 -0
- package/dist/commands/spine-cert-persist.test.js.map +1 -0
- package/dist/commands/spine-cert-record.d.ts +118 -0
- package/dist/commands/spine-cert-record.d.ts.map +1 -0
- package/dist/commands/spine-cert-record.js +194 -0
- package/dist/commands/spine-cert-record.js.map +1 -0
- package/dist/commands/spine-cert-record.test.d.ts +2 -0
- package/dist/commands/spine-cert-record.test.d.ts.map +1 -0
- package/dist/commands/spine-cert-record.test.js +129 -0
- package/dist/commands/spine-cert-record.test.js.map +1 -0
- package/dist/commands/spine-cert-run-corpus.d.ts +42 -0
- package/dist/commands/spine-cert-run-corpus.d.ts.map +1 -0
- package/dist/commands/spine-cert-run-corpus.js +133 -0
- package/dist/commands/spine-cert-run-corpus.js.map +1 -0
- package/dist/commands/spine-cert-run-corpus.test.d.ts +2 -0
- package/dist/commands/spine-cert-run-corpus.test.d.ts.map +1 -0
- package/dist/commands/spine-cert-run-corpus.test.js +171 -0
- package/dist/commands/spine-cert-run-corpus.test.js.map +1 -0
- package/dist/commands/spine-windtunnel.d.ts +65 -0
- package/dist/commands/spine-windtunnel.d.ts.map +1 -1
- package/dist/commands/spine-windtunnel.js +160 -7
- package/dist/commands/spine-windtunnel.js.map +1 -1
- package/dist/commands/spine-windtunnel.test.js +80 -1
- package/dist/commands/spine-windtunnel.test.js.map +1 -1
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { buildCertifyingCorpus } from './spine-cert-corpus.js';
|
|
5
|
+
import { buildReplayAdapters } from './spine-cert-record.js';
|
|
6
|
+
import { ReplayArtifactSchema } from './spine-llm-replay.js';
|
|
7
|
+
// ─── Fixture file names under the gate-1 dir ─────────
|
|
8
|
+
const SPLIT_FILE = 'split.json';
|
|
9
|
+
const REPLAY_FILE = 'llm-replay.v1.json';
|
|
10
|
+
const CONTENT_FILE = 'review-content.json';
|
|
11
|
+
const PR_DIFFS_FILE = 'pr-diffs.json';
|
|
12
|
+
const GROUND_TRUTH_FILE = 'ground-truth-labels.json';
|
|
13
|
+
const LEDGERS_FILE = 'miner-ledgers.json';
|
|
14
|
+
// ─── Fixture schemas (the committed cert-run inputs) ──
|
|
15
|
+
const ReviewThreadContentSchema = z.object({
|
|
16
|
+
pr: z.number().int().positive(),
|
|
17
|
+
mergeCommitSha: z.string().regex(/^[0-9a-f]{40}$/),
|
|
18
|
+
threads: z.array(z.object({
|
|
19
|
+
path: z.string(),
|
|
20
|
+
comments: z.array(z.object({ author: z.string(), body: z.string() })),
|
|
21
|
+
isResolved: z.boolean(),
|
|
22
|
+
isOutdated: z.boolean(),
|
|
23
|
+
})),
|
|
24
|
+
});
|
|
25
|
+
const ResolvedPrDiffSchema = z.object({
|
|
26
|
+
pr: z.number().int().positive(),
|
|
27
|
+
diff: z.string(),
|
|
28
|
+
controlKind: z.enum(['corpus', 'positive', 'negative']),
|
|
29
|
+
targetRuleId: z.string().optional(),
|
|
30
|
+
});
|
|
31
|
+
// firingLabelId → TP|FP
|
|
32
|
+
const GroundTruthSchema = z.record(z.enum(['TP', 'FP']));
|
|
33
|
+
/** A zero-network ReviewThreadSource backed by committed, frozen review content. */
|
|
34
|
+
function frozenSourceFrom(contents) {
|
|
35
|
+
const byPr = new Map(contents.map((c) => [c.pr, c]));
|
|
36
|
+
return {
|
|
37
|
+
async fetch(pr) {
|
|
38
|
+
const content = byPr.get(pr);
|
|
39
|
+
return content
|
|
40
|
+
? { kind: 'ok', content }
|
|
41
|
+
: { kind: 'unreachable', detail: `no frozen review content for pr ${pr}` };
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Load + validate the committed cert-run fixture inputs from the gate-1 dir.
|
|
47
|
+
* Async so the `@mmnto/totem` runtime values (schema + error class) are
|
|
48
|
+
* dynamically imported per the CLI lazy-import convention, not pulled in at
|
|
49
|
+
* module load.
|
|
50
|
+
*/
|
|
51
|
+
export async function loadCertRunFixtures(gate1Dir) {
|
|
52
|
+
const { SplitArtifactSchema, TotemError } = await import('@mmnto/totem');
|
|
53
|
+
const loadJson = (file) => {
|
|
54
|
+
let raw;
|
|
55
|
+
try {
|
|
56
|
+
raw = fs.readFileSync(file, 'utf-8');
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
throw new TotemError('CONFIG_INVALID', `Cert-run fixture missing: ${file}`, 'Ensure the gate-1 fixture set exists and is readable.', err);
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(raw);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
throw new TotemError('CONFIG_INVALID', `Cert-run fixture is not valid JSON (${file})`, 'Re-freeze the gate-1 fixtures with `spine windtunnel record`.', err);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const split = SplitArtifactSchema.parse(loadJson(path.join(gate1Dir, SPLIT_FILE)));
|
|
69
|
+
const artifact = ReplayArtifactSchema.parse(loadJson(path.join(gate1Dir, REPLAY_FILE)));
|
|
70
|
+
const content = z
|
|
71
|
+
.array(ReviewThreadContentSchema)
|
|
72
|
+
.parse(loadJson(path.join(gate1Dir, CONTENT_FILE)));
|
|
73
|
+
const prDiffs = z.array(ResolvedPrDiffSchema).parse(loadJson(path.join(gate1Dir, PR_DIFFS_FILE)));
|
|
74
|
+
const gtRecord = GroundTruthSchema.parse(loadJson(path.join(gate1Dir, GROUND_TRUTH_FILE)));
|
|
75
|
+
const groundTruth = new Map(Object.entries(gtRecord));
|
|
76
|
+
return { split, artifact, content, prDiffs, groundTruth };
|
|
77
|
+
}
|
|
78
|
+
/** Derive the SplitLedger from the loaded split + the lock's resolved corpus. */
|
|
79
|
+
function splitLedgerFrom(split, lock) {
|
|
80
|
+
return {
|
|
81
|
+
split,
|
|
82
|
+
corpus: lock.corpus.resolvedPrs.map((p) => p.pr),
|
|
83
|
+
corpusMergeCommits: lock.corpus.resolvedPrs.map((p) => ({
|
|
84
|
+
pr: p.pr,
|
|
85
|
+
mergeCommit: p.mergeCommit,
|
|
86
|
+
})),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Build the REPLAY-mode `CertifyingCorpusProvider` the certifying run injects.
|
|
91
|
+
*
|
|
92
|
+
* Loads the committed cert-run fixtures (split, frozen `llm-replay.v1` artifact,
|
|
93
|
+
* frozen review content, resolved-PR diffs, ground-truth labels) from the gate-1
|
|
94
|
+
* dir, constructs the zero-network replay adapters (gated on the lock's L2
|
|
95
|
+
* `llmReplaySha`) + a frozen review-thread source, then composes them through
|
|
96
|
+
* `buildCertifyingCorpus`. fold-I ledgers are emitted (default: written to
|
|
97
|
+
* `miner-ledgers.json`) for §7 observability. Throws loud if the lock lacks the
|
|
98
|
+
* L2 replay hash (no safe default for an integrity gate).
|
|
99
|
+
*/
|
|
100
|
+
export function buildReplayCorpusProvider(opts) {
|
|
101
|
+
return async (lock) => {
|
|
102
|
+
const { TotemError } = await import('@mmnto/totem');
|
|
103
|
+
const expectedHash = lock.controls.integrity.llmReplaySha;
|
|
104
|
+
if (!expectedHash) {
|
|
105
|
+
throw new TotemError('CONFIG_INVALID', 'Certifying run: lock is missing controls.integrity.llmReplaySha (L2) — the frozen ' +
|
|
106
|
+
'llm-replay fixture cannot be integrity-checked.', 'Re-freeze the lock after a `record` run.');
|
|
107
|
+
}
|
|
108
|
+
const { split, artifact, content, prDiffs, groundTruth } = await loadCertRunFixtures(opts.gate1Dir);
|
|
109
|
+
const { extractor, classifier } = buildReplayAdapters(artifact, expectedHash);
|
|
110
|
+
const { corpus, ledgers } = await buildCertifyingCorpus({
|
|
111
|
+
split,
|
|
112
|
+
splitLedger: splitLedgerFrom(split, lock),
|
|
113
|
+
source: frozenSourceFrom(content),
|
|
114
|
+
extractor,
|
|
115
|
+
classifier,
|
|
116
|
+
seedClassesProvided: opts.seedClassesProvided ?? false,
|
|
117
|
+
stage4: opts.stage4,
|
|
118
|
+
now: opts.now,
|
|
119
|
+
prDiffs,
|
|
120
|
+
groundTruth,
|
|
121
|
+
});
|
|
122
|
+
// fold-I (§7): emit the miner ledgers, observable beside the cert-run report.
|
|
123
|
+
if (opts.onLedgers) {
|
|
124
|
+
opts.onLedgers(ledgers);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
fs.mkdirSync(opts.gate1Dir, { recursive: true });
|
|
128
|
+
fs.writeFileSync(path.join(opts.gate1Dir, LEDGERS_FILE), JSON.stringify(ledgers, null, 2), 'utf-8');
|
|
129
|
+
}
|
|
130
|
+
return corpus;
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=spine-cert-run-corpus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spine-cert-run-corpus.js","sourceRoot":"","sources":["../../src/commands/spine-cert-run-corpus.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spine-cert-run-corpus.test.d.ts","sourceRoot":"","sources":["../../src/commands/spine-cert-run-corpus.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,171 @@
|
|
|
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 { recordReplayFixture } from './spine-cert-record.js';
|
|
6
|
+
import { buildReplayCorpusProvider } from './spine-cert-run-corpus.js';
|
|
7
|
+
import { serializeReplayArtifact } from './spine-llm-replay.js';
|
|
8
|
+
// ─── Fixtures ────────────────────────────────────────
|
|
9
|
+
const sha = (n) => String(n).padStart(40, '0');
|
|
10
|
+
const NOW = '2026-06-19T12:00:00.000Z';
|
|
11
|
+
const REGEX_DSL = [
|
|
12
|
+
'**Pattern:** `forbiddenCall\\(`',
|
|
13
|
+
'**Engine:** regex',
|
|
14
|
+
'**Severity:** warning',
|
|
15
|
+
'',
|
|
16
|
+
'### Bad Example',
|
|
17
|
+
'```ts',
|
|
18
|
+
'forbiddenCall()',
|
|
19
|
+
'```',
|
|
20
|
+
].join('\n');
|
|
21
|
+
const PROVENANCE = {
|
|
22
|
+
promptTemplateHash: sha(11),
|
|
23
|
+
systemPromptHash: sha(12),
|
|
24
|
+
provider: 'anthropic',
|
|
25
|
+
model: 'claude-test',
|
|
26
|
+
temperature: 0,
|
|
27
|
+
orchestratorVersion: '0.0.0-test',
|
|
28
|
+
adapterKind: 'extractor+classifier',
|
|
29
|
+
keyVersion: 'v1',
|
|
30
|
+
totemVersion: '0.0.0-test',
|
|
31
|
+
};
|
|
32
|
+
const SPLIT = {
|
|
33
|
+
asOfCommit: sha(100),
|
|
34
|
+
trainPrs: [1],
|
|
35
|
+
heldOutPrs: [],
|
|
36
|
+
excludedPrs: [],
|
|
37
|
+
positiveControlPrs: [],
|
|
38
|
+
negativeControlPrs: [],
|
|
39
|
+
splitRule: { predicate: 'code-touching non-bot', cutIndex: 1 },
|
|
40
|
+
};
|
|
41
|
+
const SPLIT_LEDGER = {
|
|
42
|
+
split: SPLIT,
|
|
43
|
+
corpus: [1],
|
|
44
|
+
corpusMergeCommits: [{ pr: 1, mergeCommit: sha(1) }],
|
|
45
|
+
};
|
|
46
|
+
function content(pr) {
|
|
47
|
+
return {
|
|
48
|
+
pr,
|
|
49
|
+
mergeCommitSha: sha(pr),
|
|
50
|
+
threads: [
|
|
51
|
+
{
|
|
52
|
+
path: 'src/a.ts',
|
|
53
|
+
comments: [{ author: 'Jane', body: 'note' }],
|
|
54
|
+
isResolved: false,
|
|
55
|
+
isOutdated: false,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function fakeSource() {
|
|
61
|
+
return {
|
|
62
|
+
async fetch(pr) {
|
|
63
|
+
return { kind: 'ok', content: content(pr) };
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function fakeExtractor() {
|
|
68
|
+
return {
|
|
69
|
+
async draft() {
|
|
70
|
+
return [REGEX_DSL];
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function fakeClassifier() {
|
|
75
|
+
return {
|
|
76
|
+
async classify() {
|
|
77
|
+
return { disposition: 'structural', dispositionSource: 'classified' };
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function stage4(files) {
|
|
82
|
+
return {
|
|
83
|
+
listFiles: () => Promise.resolve(Object.keys(files)),
|
|
84
|
+
readFile: (f) => f in files ? Promise.resolve(files[f]) : Promise.reject(new Error(`absent ${f}`)),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** A minimal lock partial — the provider reads only the L2 hash + resolved corpus. */
|
|
88
|
+
function lockWith(llmReplaySha) {
|
|
89
|
+
return {
|
|
90
|
+
controls: { integrity: { llmReplaySha } },
|
|
91
|
+
corpus: { resolvedPrs: [{ pr: 1, mergeCommit: sha(1) }] },
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
let tmpDir;
|
|
95
|
+
let gate1Dir;
|
|
96
|
+
beforeEach(async () => {
|
|
97
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-cert-run-'));
|
|
98
|
+
gate1Dir = path.join(tmpDir, 'gate-1');
|
|
99
|
+
fs.mkdirSync(gate1Dir, { recursive: true });
|
|
100
|
+
// Freeze a replay fixture from the live fakes, then lay down the committed
|
|
101
|
+
// cert-run inputs the run path loads.
|
|
102
|
+
const { artifact } = await recordReplayFixture({
|
|
103
|
+
split: SPLIT,
|
|
104
|
+
splitLedger: SPLIT_LEDGER,
|
|
105
|
+
source: fakeSource(),
|
|
106
|
+
liveExtractor: fakeExtractor(),
|
|
107
|
+
liveClassifier: fakeClassifier(),
|
|
108
|
+
seedClassesProvided: false,
|
|
109
|
+
provenance: PROVENANCE,
|
|
110
|
+
});
|
|
111
|
+
fs.writeFileSync(path.join(gate1Dir, 'split.json'), JSON.stringify(SPLIT), 'utf-8');
|
|
112
|
+
fs.writeFileSync(path.join(gate1Dir, 'llm-replay.v1.json'), serializeReplayArtifact(artifact), 'utf-8');
|
|
113
|
+
fs.writeFileSync(path.join(gate1Dir, 'review-content.json'), JSON.stringify([content(1)]), 'utf-8');
|
|
114
|
+
fs.writeFileSync(path.join(gate1Dir, 'pr-diffs.json'), JSON.stringify([]), 'utf-8');
|
|
115
|
+
fs.writeFileSync(path.join(gate1Dir, 'ground-truth-labels.json'), JSON.stringify({}), 'utf-8');
|
|
116
|
+
});
|
|
117
|
+
afterEach(() => {
|
|
118
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
119
|
+
});
|
|
120
|
+
/** Recompute the artifact hash the lock must carry (L2), from the written fixture. */
|
|
121
|
+
async function fixtureHash() {
|
|
122
|
+
const { hash } = await recordReplayFixture({
|
|
123
|
+
split: SPLIT,
|
|
124
|
+
splitLedger: SPLIT_LEDGER,
|
|
125
|
+
source: fakeSource(),
|
|
126
|
+
liveExtractor: fakeExtractor(),
|
|
127
|
+
liveClassifier: fakeClassifier(),
|
|
128
|
+
seedClassesProvided: false,
|
|
129
|
+
provenance: PROVENANCE,
|
|
130
|
+
});
|
|
131
|
+
return hash;
|
|
132
|
+
}
|
|
133
|
+
// ─── Tests ───────────────────────────────────────────
|
|
134
|
+
describe('buildReplayCorpusProvider (run-path)', () => {
|
|
135
|
+
it('loads the committed fixtures + replays them into a corpus (zero LLM/network)', async () => {
|
|
136
|
+
const hash = await fixtureHash();
|
|
137
|
+
let captured;
|
|
138
|
+
const provider = buildReplayCorpusProvider({
|
|
139
|
+
gate1Dir,
|
|
140
|
+
stage4: stage4({ 'src/a.ts': 'forbiddenCall()' }),
|
|
141
|
+
now: NOW,
|
|
142
|
+
onLedgers: (l) => {
|
|
143
|
+
captured = l;
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
const corpus = await provider(lockWith(hash));
|
|
147
|
+
expect(corpus.rules).toHaveLength(1);
|
|
148
|
+
expect(corpus.provenanceByRule.get(corpus.rules[0].lessonHash)?.mergedPr).toBe(1);
|
|
149
|
+
// fold-I ledgers emitted via the sink.
|
|
150
|
+
expect(captured?.apiUsage.heldOutFetchCount).toBe(0);
|
|
151
|
+
});
|
|
152
|
+
it('throws loud when the lock lacks the L2 llmReplaySha (no integrity gate)', async () => {
|
|
153
|
+
const provider = buildReplayCorpusProvider({
|
|
154
|
+
gate1Dir,
|
|
155
|
+
stage4: stage4({ 'src/a.ts': 'forbiddenCall()' }),
|
|
156
|
+
now: NOW,
|
|
157
|
+
});
|
|
158
|
+
await expect(provider(lockWith(undefined))).rejects.toThrow(/llmReplaySha/);
|
|
159
|
+
});
|
|
160
|
+
it('writes the fold-I miner ledgers to the gate-1 dir by default', async () => {
|
|
161
|
+
const hash = await fixtureHash();
|
|
162
|
+
const provider = buildReplayCorpusProvider({
|
|
163
|
+
gate1Dir,
|
|
164
|
+
stage4: stage4({ 'src/a.ts': 'forbiddenCall()' }),
|
|
165
|
+
now: NOW,
|
|
166
|
+
});
|
|
167
|
+
await provider(lockWith(hash));
|
|
168
|
+
expect(fs.existsSync(path.join(gate1Dir, 'miner-ledgers.json'))).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
//# sourceMappingURL=spine-cert-run-corpus.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spine-cert-run-corpus.test.js","sourceRoot":"","sources":["../../src/commands/spine-cert-run-corpus.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -20,6 +20,51 @@ export interface RunOptions {
|
|
|
20
20
|
lcDir?: string;
|
|
21
21
|
lockPath?: string;
|
|
22
22
|
phase?: string;
|
|
23
|
+
/**
|
|
24
|
+
* 5c-ii injection seam (out of 5c-i scope to populate): the certifying corpus
|
|
25
|
+
* — resolved-PR diffs (corpus + controls), the active compiled rules, and the
|
|
26
|
+
* frozen ground-truth labels. When omitted on a certifying run, the real
|
|
27
|
+
* engine path throws a structured "corpus provider not wired" error rather
|
|
28
|
+
* than silently scoring an empty set. 5c-ii (the orchestrator) supplies the
|
|
29
|
+
* live-recorded corpus here; 5c-i unit tests supply a deterministic fixture.
|
|
30
|
+
*/
|
|
31
|
+
certifyingCorpus?: CertifyingCorpusProvider;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The certifying corpus the real engine scores (5c-ii supplies this; 5c-i
|
|
35
|
+
* defines the seam + the deterministic engine that consumes it). Returns the
|
|
36
|
+
* active compiled rules (archived MUST be excluded — fold-F throws otherwise),
|
|
37
|
+
* the resolved-PR diffs (corpus + positive/negative controls), and the frozen
|
|
38
|
+
* ground-truth labels keyed by firingLabelId.
|
|
39
|
+
*/
|
|
40
|
+
export type CertifyingCorpusProvider = (lock: WindtunnelLock) => Promise<CertifyingCorpus> | CertifyingCorpus;
|
|
41
|
+
export interface CertifyingCorpus {
|
|
42
|
+
rules: import('@mmnto/totem').CompiledRule[];
|
|
43
|
+
prDiffs: import('@mmnto/totem').ResolvedPrDiff[];
|
|
44
|
+
groundTruth: Map<string, import('@mmnto/totem').GroundTruthLabel>;
|
|
45
|
+
/**
|
|
46
|
+
* Mining provenance per rule (lessonHash → provenance) — supplied by the
|
|
47
|
+
* orchestrator from the candidate/emission records. fold-B needs it to stamp
|
|
48
|
+
* legitimacy; a survivor without provenance is surfaced as a skip, never
|
|
49
|
+
* fabricated.
|
|
50
|
+
*/
|
|
51
|
+
provenanceByRule: Map<string, import('@mmnto/totem').ProvenanceRecord>;
|
|
52
|
+
}
|
|
53
|
+
/** Internal shape the run command's scorer + persist step consume (engine-agnostic). */
|
|
54
|
+
interface EngineResult {
|
|
55
|
+
mintedRuleIds: string[];
|
|
56
|
+
firings: import('@mmnto/totem').RuleFiring[];
|
|
57
|
+
groundTruth: Map<string, import('@mmnto/totem').GroundTruthLabel>;
|
|
58
|
+
positiveControlTargets: Array<{
|
|
59
|
+
pr: number;
|
|
60
|
+
targetRuleId: string;
|
|
61
|
+
}>;
|
|
62
|
+
/** C2 — real touched-file exposure (0 for the harness mock). */
|
|
63
|
+
filesTouchedInWindow: number;
|
|
64
|
+
/** Candidate rules eligible for fold-B stamping (empty for the harness mock). */
|
|
65
|
+
candidates: import('@mmnto/totem').CompiledRule[];
|
|
66
|
+
/** Mining provenance per rule for fold-B (empty for the harness mock). */
|
|
67
|
+
provenanceByRule: Map<string, import('@mmnto/totem').ProvenanceRecord>;
|
|
23
68
|
}
|
|
24
69
|
/**
|
|
25
70
|
* `totem spine windtunnel run`
|
|
@@ -92,5 +137,25 @@ export declare function enumeratePrMetas(asOfCommit: string, lcDir: string, safe
|
|
|
92
137
|
parseRevertSha: (body: string) => string | undefined;
|
|
93
138
|
isBotIdentity: (author: string) => boolean;
|
|
94
139
|
}): PrMeta[];
|
|
140
|
+
/**
|
|
141
|
+
* Run the REAL engine for the certifying phase (5c-i — #2189 item 1).
|
|
142
|
+
*
|
|
143
|
+
* Replaces the mock for `--phase certifying`: drives each resolved-PR diff
|
|
144
|
+
* through `buildFirings` (core), which runs `enrichWithAstContext` +
|
|
145
|
+
* `applyAstRulesToAdditions` with the shared post-image `readStrategy` (S1/C1)
|
|
146
|
+
* and maps every violation to a `RuleFiring` (content-based labelId). Then:
|
|
147
|
+
* - **fold-F**: `buildFirings` throws if any archived rule is in the scored set
|
|
148
|
+
* (the engine never runs on an archived rule).
|
|
149
|
+
* - **A1 (fold-D)**: `assertUniqueFiringLabels` hard-gates labelId uniqueness
|
|
150
|
+
* BEFORE scoring (throws on collision, surfacing the offending refs).
|
|
151
|
+
* - **C2**: `filesTouchedInWindow` is the real distinct-file exposure.
|
|
152
|
+
* - **fold-H**: neg-control firings flow through as `controlKind:'negative'`;
|
|
153
|
+
* unlabeled firings route to needsAdjudication via the scorer.
|
|
154
|
+
*
|
|
155
|
+
* The corpus itself (resolved-PR diffs + active rules + frozen ground truth) is
|
|
156
|
+
* supplied by the 5c-ii orchestrator via the `certifyingCorpus` seam. 5c-i owns
|
|
157
|
+
* the deterministic engine; it does NOT fetch/compile live data (out of scope).
|
|
158
|
+
*/
|
|
159
|
+
export declare function runCertifyingEngine(lock: WindtunnelLock, readStrategy: (file: string) => Promise<string | null>, corpusProvider?: CertifyingCorpusProvider): Promise<EngineResult>;
|
|
95
160
|
export {};
|
|
96
161
|
//# sourceMappingURL=spine-windtunnel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spine-windtunnel.d.ts","sourceRoot":"","sources":["../../src/commands/spine-windtunnel.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAuB,cAAc,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"spine-windtunnel.d.ts","sourceRoot":"","sources":["../../src/commands/spine-windtunnel.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAuB,cAAc,EAAE,MAAM,cAAc,CAAC;AAYhF,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAoFtE;AAID,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,wBAAwB,CAAC;CAC7C;AAED;;;;;;GAMG;AACH,MAAM,MAAM,wBAAwB,GAAG,CACrC,IAAI,EAAE,cAAc,KACjB,OAAO,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAC;AAElD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,cAAc,EAAE,YAAY,EAAE,CAAC;IAC7C,OAAO,EAAE,OAAO,cAAc,EAAE,cAAc,EAAE,CAAC;IACjD,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,cAAc,EAAE,gBAAgB,CAAC,CAAC;IAClE;;;;;OAKG;IACH,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,cAAc,EAAE,gBAAgB,CAAC,CAAC;CACxE;AAED,wFAAwF;AACxF,UAAU,YAAY;IACpB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,OAAO,EAAE,OAAO,cAAc,EAAE,UAAU,EAAE,CAAC;IAC7C,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,cAAc,EAAE,gBAAgB,CAAC,CAAC;IAClE,sBAAsB,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpE,gEAAgE;IAChE,oBAAoB,EAAE,MAAM,CAAC;IAC7B,iFAAiF;IACjF,UAAU,EAAE,OAAO,cAAc,EAAE,YAAY,EAAE,CAAC;IAClD,0EAA0E;IAC1E,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,cAAc,EAAE,gBAAgB,CAAC,CAAC;CACxE;AAED;;;;;;;;;GASG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAyOhE;AAID,KAAK,UAAU,GAAG,cAAc,cAAc,EAAE,QAAQ,CAAC;AAEzD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,UAAU,GACnB,OAAO,CAWT;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,UAAU,GACnB,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkB1C;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,GAAG,IAAI,CA0DhG;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,EAAE,EACrB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,UAAU,GACnB,MAAM,GAAG,IAAI,CA+Cf;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EAAE,EACrB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,UAAU,GACnB,IAAI,CAmBN;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,UAAU,GACnB,OAAO,CAAC,IAAI,CAAC,CAqGf;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,UAAU,EACpB,OAAO,EAAE;IACP,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IAClD,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACrD,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;CAC5C,GACA,MAAM,EAAE,CA+DV;AA2DD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,cAAc,EACpB,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,EACtD,cAAc,CAAC,EAAE,wBAAwB,GACxC,OAAO,CAAC,YAAY,CAAC,CAiFvB"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
|
+
import { persistCertifyingOutcome } from './spine-cert-persist.js';
|
|
4
|
+
import { buildReplayCorpusProvider } from './spine-cert-run-corpus.js';
|
|
3
5
|
// ─── Named constants ─────────────────────────────────
|
|
4
6
|
const LOCK_REL_PATH = '.totem/spine/gate-1/windtunnel.lock.json';
|
|
5
7
|
const COMMIT_SHA_REGEX = /^[0-9a-f]{40}$/;
|
|
@@ -149,12 +151,48 @@ export async function runCommand(opts) {
|
|
|
149
151
|
// simple null-returning strategy (all files → skip classification = fail-open).
|
|
150
152
|
// When lcDir is provided, resolve post-image blobs from the lc clone.
|
|
151
153
|
const readStrategy = buildReadStrategy(lcDir, lock.corpus.selectionRule.asOfCommit, safeExec);
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
|
|
154
|
+
// Run the engine. Harness phase → mock engine (no real rules yet). Certifying
|
|
155
|
+
// phase → the REAL engine path (5c-i): build additions from each resolved-PR
|
|
156
|
+
// diff → enrichWithAstContext + applyAstRulesToAdditions with the shared
|
|
157
|
+
// post-image readStrategy → RuleFiring[] → A1 unique-label hard-gate → score.
|
|
158
|
+
// C2: filesTouchedInWindow is the real exposure computed from the diffs (no
|
|
159
|
+
// longer the hard-coded 0).
|
|
160
|
+
// A single wall-clock stamp for the whole certifying run — threaded into both
|
|
161
|
+
// the replay-corpus provider (compile-stage timestamp) and the persist step
|
|
162
|
+
// (report generatedAt / filename slug) so the two artifacts share one moment.
|
|
163
|
+
const runNowIso = new Date().toISOString();
|
|
164
|
+
// Certifying phase: use the injected corpus (tests) or build the REPLAY-mode
|
|
165
|
+
// provider from the committed cert-run fixtures under the gate-1 dir (5c-ii).
|
|
166
|
+
let corpusProvider = opts.certifyingCorpus;
|
|
167
|
+
if (lock.phase === 'certifying' && !corpusProvider) {
|
|
168
|
+
const gate1Dir = path.dirname(lockPath);
|
|
169
|
+
const asOf = lock.corpus.selectionRule.asOfCommit;
|
|
170
|
+
const stage4 = lcDir
|
|
171
|
+
? {
|
|
172
|
+
listFiles: async () => safeExec('git', ['ls-tree', '-r', '--name-only', asOf], { cwd: lcDir })
|
|
173
|
+
.split('\n')
|
|
174
|
+
.filter(Boolean),
|
|
175
|
+
readFile: async (f) => safeExec('git', ['show', `${asOf}:${f.replace(/\\/g, '/')}`], { cwd: lcDir }),
|
|
176
|
+
workingDirectory: lcDir,
|
|
177
|
+
}
|
|
178
|
+
: {
|
|
179
|
+
// No lc clone → Stage-4 sees no files (rules read as 'no-matches' /
|
|
180
|
+
// untested, NOT archived) — the wind-tunnel firing/scoring still runs.
|
|
181
|
+
listFiles: async () => [],
|
|
182
|
+
readFile: async (f) => {
|
|
183
|
+
throw new TotemError('CONFIG_INVALID', `Cert run: no lc clone (--lc-dir) — cannot read ${f} for Stage-4.`, 'Provide the lc clone via --lc-dir or the TOTEM_LC_DIR environment variable.');
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
corpusProvider = buildReplayCorpusProvider({
|
|
187
|
+
gate1Dir,
|
|
188
|
+
stage4,
|
|
189
|
+
now: runNowIso,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
const engineResult = lock.phase === 'certifying'
|
|
193
|
+
? await runCertifyingEngine(lock, readStrategy, corpusProvider)
|
|
194
|
+
: await runMockEngineAdapter(lock, readStrategy);
|
|
195
|
+
const { mintedRuleIds, firings, groundTruth, positiveControlTargets, filesTouchedInWindow } = engineResult;
|
|
158
196
|
// Score
|
|
159
197
|
const verdict = scoreWindtunnel({
|
|
160
198
|
firings,
|
|
@@ -169,7 +207,7 @@ export async function runCommand(opts) {
|
|
|
169
207
|
},
|
|
170
208
|
actualExposure: {
|
|
171
209
|
activeRulesEvaluated: mintedRuleIds.length,
|
|
172
|
-
filesTouchedInWindow
|
|
210
|
+
filesTouchedInWindow,
|
|
173
211
|
positiveControlsExercised: positiveControlTargets.length,
|
|
174
212
|
},
|
|
175
213
|
});
|
|
@@ -201,6 +239,31 @@ export async function runCommand(opts) {
|
|
|
201
239
|
console.log(` • ${id}`);
|
|
202
240
|
}
|
|
203
241
|
}
|
|
242
|
+
// 5c-ii: certifying-phase persistence — fold-B project → fold-C parse-before-write
|
|
243
|
+
// persist (PASS-survivors-only) + the transient cert-run report (§6 L3). The
|
|
244
|
+
// repo's live `.totem/compiled-rules.json` is NEVER touched here; survivors land
|
|
245
|
+
// in the gate-1 cert output, which strategy#516 promotes to the live corpus.
|
|
246
|
+
if (lock.phase === 'certifying') {
|
|
247
|
+
// Persist beside the same gate-1 dir the fixtures were loaded from (line ~288),
|
|
248
|
+
// not a hardcoded path — keeps cert output co-located under a non-default --lock-path.
|
|
249
|
+
const gate1Dir = path.dirname(lockPath);
|
|
250
|
+
const persistResult = await persistCertifyingOutcome({
|
|
251
|
+
verdict,
|
|
252
|
+
firings,
|
|
253
|
+
mintedRuleIds,
|
|
254
|
+
positiveControlTargets,
|
|
255
|
+
candidates: engineResult.candidates,
|
|
256
|
+
provenanceByRule: engineResult.provenanceByRule,
|
|
257
|
+
certifiedRulesOutPath: path.join(gate1Dir, 'compiled-rules.json'),
|
|
258
|
+
reportDir: path.join(gate1Dir, 'run-reports'),
|
|
259
|
+
nowIso: runNowIso,
|
|
260
|
+
asOfCommit: lock.corpus.selectionRule.asOfCommit,
|
|
261
|
+
});
|
|
262
|
+
console.error(`[WindtunnelRun] Cert-run report: ${persistResult.reportPath}` +
|
|
263
|
+
(persistResult.persisted
|
|
264
|
+
? ` — ${persistResult.stampedCount} survivor(s) stamped → ${persistResult.certifiedRulesPath}`
|
|
265
|
+
: ` — no rules persisted (verdict ${verdict.verdict}); live corpus untouched`));
|
|
266
|
+
}
|
|
204
267
|
// Exit non-zero on FAIL / HONEST-NEGATIVE / needs-adjudication
|
|
205
268
|
if (verdict.verdict !== 'PASS' || verdict.needsAdjudication.length > 0) {
|
|
206
269
|
process.exitCode = 1;
|
|
@@ -531,4 +594,94 @@ async function runMockEngine(lock, _readStrategy) {
|
|
|
531
594
|
}
|
|
532
595
|
return { mintedRuleIds, firings, groundTruth, positiveControlTargets };
|
|
533
596
|
}
|
|
597
|
+
/** Adapt the harness mock engine to the EngineResult shape (filesTouched = 0). */
|
|
598
|
+
async function runMockEngineAdapter(lock, readStrategy) {
|
|
599
|
+
const mock = await runMockEngine(lock, readStrategy);
|
|
600
|
+
return {
|
|
601
|
+
...mock,
|
|
602
|
+
filesTouchedInWindow: 0,
|
|
603
|
+
candidates: [],
|
|
604
|
+
provenanceByRule: new Map(),
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
// ─── Real engine (certifying phase, 5c-i) ────────────
|
|
608
|
+
/**
|
|
609
|
+
* Run the REAL engine for the certifying phase (5c-i — #2189 item 1).
|
|
610
|
+
*
|
|
611
|
+
* Replaces the mock for `--phase certifying`: drives each resolved-PR diff
|
|
612
|
+
* through `buildFirings` (core), which runs `enrichWithAstContext` +
|
|
613
|
+
* `applyAstRulesToAdditions` with the shared post-image `readStrategy` (S1/C1)
|
|
614
|
+
* and maps every violation to a `RuleFiring` (content-based labelId). Then:
|
|
615
|
+
* - **fold-F**: `buildFirings` throws if any archived rule is in the scored set
|
|
616
|
+
* (the engine never runs on an archived rule).
|
|
617
|
+
* - **A1 (fold-D)**: `assertUniqueFiringLabels` hard-gates labelId uniqueness
|
|
618
|
+
* BEFORE scoring (throws on collision, surfacing the offending refs).
|
|
619
|
+
* - **C2**: `filesTouchedInWindow` is the real distinct-file exposure.
|
|
620
|
+
* - **fold-H**: neg-control firings flow through as `controlKind:'negative'`;
|
|
621
|
+
* unlabeled firings route to needsAdjudication via the scorer.
|
|
622
|
+
*
|
|
623
|
+
* The corpus itself (resolved-PR diffs + active rules + frozen ground truth) is
|
|
624
|
+
* supplied by the 5c-ii orchestrator via the `certifyingCorpus` seam. 5c-i owns
|
|
625
|
+
* the deterministic engine; it does NOT fetch/compile live data (out of scope).
|
|
626
|
+
*/
|
|
627
|
+
export async function runCertifyingEngine(lock, readStrategy, corpusProvider) {
|
|
628
|
+
const { buildFirings, assertUniqueFiringLabels, resolveGitRoot, TotemError, FiringLabelCollisionError, } = await import('@mmnto/totem');
|
|
629
|
+
if (!corpusProvider) {
|
|
630
|
+
// No silent empty-set scoring: a certifying run with no corpus provider is a
|
|
631
|
+
// wiring error (5c-ii supplies it). Fail loud + actionable (Tenet 4).
|
|
632
|
+
throw new TotemError('CONFIG_INVALID', 'Wind-tunnel certifying run: no certifying-corpus provider wired (5c-ii orchestration).', 'The deterministic real-engine firing path (5c-i) is in place, but the corpus ' +
|
|
633
|
+
'(resolved-PR diffs + compiled rules + ground truth) is supplied by the 5c-ii ' +
|
|
634
|
+
'orchestrator. Run via the certifying orchestrator once it lands.');
|
|
635
|
+
}
|
|
636
|
+
const corpus = await corpusProvider(lock);
|
|
637
|
+
const cwd = resolveGitRoot(process.cwd()) ?? process.cwd();
|
|
638
|
+
const ruleEngineCtx = {
|
|
639
|
+
logger: { warn: (msg) => console.error(`[WindtunnelRun] ${msg}`) },
|
|
640
|
+
state: { hasWarnedShieldContext: false },
|
|
641
|
+
};
|
|
642
|
+
// buildFirings runs fold-F (archived assert) internally before the engine; its
|
|
643
|
+
// only throw is ArchivedRuleInScopeError, which propagates. A1 (labelId
|
|
644
|
+
// collision) is deliberately NOT raised here — it is the caller's pre-score gate
|
|
645
|
+
// below (assertUniqueFiringLabels), so the structured per-collision report is
|
|
646
|
+
// threaded there rather than swallowed at construction. (greptile #2215 P2.)
|
|
647
|
+
const built = await buildFirings({
|
|
648
|
+
rules: corpus.rules,
|
|
649
|
+
prDiffs: corpus.prDiffs,
|
|
650
|
+
cwd,
|
|
651
|
+
readStrategy,
|
|
652
|
+
ruleEngineCtx,
|
|
653
|
+
onWarn: (msg) => console.error(`[WindtunnelRun] ${msg}`),
|
|
654
|
+
});
|
|
655
|
+
// A1 (fold-D): post-dedup uniqueness INVARIANT before scoring (Tenet 4).
|
|
656
|
+
// `buildFirings` now collapses same-labelId matches (fold-D dedup), so this can
|
|
657
|
+
// no longer fire on an honest multi-match line — a collision here signals a
|
|
658
|
+
// dedup BUG, not corpus data, so it fails loud as an internal invariant.
|
|
659
|
+
try {
|
|
660
|
+
assertUniqueFiringLabels(built.firings);
|
|
661
|
+
}
|
|
662
|
+
catch (err) {
|
|
663
|
+
if (err instanceof FiringLabelCollisionError) {
|
|
664
|
+
console.error(`[WindtunnelRun] A1 post-dedup invariant violated (fold-D):`);
|
|
665
|
+
for (const c of err.collisions) {
|
|
666
|
+
console.error(` • ${c.labelId.slice(0, 12)}… ×${c.evidenceRefs.length}`);
|
|
667
|
+
}
|
|
668
|
+
throw new TotemError('CONFIG_INVALID', err.message, 'A post-dedup firing-label collision is an internal invariant violation: buildFirings ' +
|
|
669
|
+
'should have collapsed same-labelId matches. This indicates a dedup defect, not a corpus issue.', err);
|
|
670
|
+
}
|
|
671
|
+
throw err;
|
|
672
|
+
}
|
|
673
|
+
const mintedRuleIds = corpus.rules.map((r) => r.lessonHash);
|
|
674
|
+
console.error(`[WindtunnelRun] Certifying engine: ${mintedRuleIds.length} rule(s), ` +
|
|
675
|
+
`${corpus.prDiffs.length} PR diff(s), ${built.firings.length} firing(s), ` +
|
|
676
|
+
`${built.filesTouchedInWindow} file(s) touched.`);
|
|
677
|
+
return {
|
|
678
|
+
mintedRuleIds,
|
|
679
|
+
firings: built.firings,
|
|
680
|
+
groundTruth: corpus.groundTruth,
|
|
681
|
+
positiveControlTargets: built.positiveControlTargets,
|
|
682
|
+
filesTouchedInWindow: built.filesTouchedInWindow,
|
|
683
|
+
candidates: corpus.rules,
|
|
684
|
+
provenanceByRule: corpus.provenanceByRule,
|
|
685
|
+
};
|
|
686
|
+
}
|
|
534
687
|
//# sourceMappingURL=spine-windtunnel.js.map
|