@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.
Files changed (45) hide show
  1. package/dist/commands/spine-cert-corpus.d.ts +57 -0
  2. package/dist/commands/spine-cert-corpus.d.ts.map +1 -0
  3. package/dist/commands/spine-cert-corpus.js +51 -0
  4. package/dist/commands/spine-cert-corpus.js.map +1 -0
  5. package/dist/commands/spine-cert-corpus.test.d.ts +2 -0
  6. package/dist/commands/spine-cert-corpus.test.d.ts.map +1 -0
  7. package/dist/commands/spine-cert-corpus.test.js +112 -0
  8. package/dist/commands/spine-cert-corpus.test.js.map +1 -0
  9. package/dist/commands/spine-cert-e2e.test.d.ts +2 -0
  10. package/dist/commands/spine-cert-e2e.test.d.ts.map +1 -0
  11. package/dist/commands/spine-cert-e2e.test.js +145 -0
  12. package/dist/commands/spine-cert-e2e.test.js.map +1 -0
  13. package/dist/commands/spine-cert-persist.d.ts +63 -0
  14. package/dist/commands/spine-cert-persist.d.ts.map +1 -0
  15. package/dist/commands/spine-cert-persist.js +85 -0
  16. package/dist/commands/spine-cert-persist.js.map +1 -0
  17. package/dist/commands/spine-cert-persist.test.d.ts +2 -0
  18. package/dist/commands/spine-cert-persist.test.d.ts.map +1 -0
  19. package/dist/commands/spine-cert-persist.test.js +108 -0
  20. package/dist/commands/spine-cert-persist.test.js.map +1 -0
  21. package/dist/commands/spine-cert-record.d.ts +118 -0
  22. package/dist/commands/spine-cert-record.d.ts.map +1 -0
  23. package/dist/commands/spine-cert-record.js +194 -0
  24. package/dist/commands/spine-cert-record.js.map +1 -0
  25. package/dist/commands/spine-cert-record.test.d.ts +2 -0
  26. package/dist/commands/spine-cert-record.test.d.ts.map +1 -0
  27. package/dist/commands/spine-cert-record.test.js +129 -0
  28. package/dist/commands/spine-cert-record.test.js.map +1 -0
  29. package/dist/commands/spine-cert-run-corpus.d.ts +42 -0
  30. package/dist/commands/spine-cert-run-corpus.d.ts.map +1 -0
  31. package/dist/commands/spine-cert-run-corpus.js +133 -0
  32. package/dist/commands/spine-cert-run-corpus.js.map +1 -0
  33. package/dist/commands/spine-cert-run-corpus.test.d.ts +2 -0
  34. package/dist/commands/spine-cert-run-corpus.test.d.ts.map +1 -0
  35. package/dist/commands/spine-cert-run-corpus.test.js +171 -0
  36. package/dist/commands/spine-cert-run-corpus.test.js.map +1 -0
  37. package/dist/commands/spine-windtunnel.d.ts +65 -0
  38. package/dist/commands/spine-windtunnel.d.ts.map +1 -1
  39. package/dist/commands/spine-windtunnel.js +160 -7
  40. package/dist/commands/spine-windtunnel.js.map +1 -1
  41. package/dist/commands/spine-windtunnel.test.js +80 -1
  42. package/dist/commands/spine-windtunnel.test.js.map +1 -1
  43. package/dist/index.js +32 -0
  44. package/dist/index.js.map +1 -1
  45. 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=spine-cert-run-corpus.test.d.ts.map
@@ -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;AAShF,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;CAChB;AAED;;;;;;;;;GASG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAmKhE;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"}
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
- // Enrich with AST context for any additions (harness: no diff, so no additions).
153
- // In the real certifying run, the caller would build additions from PR diffs
154
- // and pass them through enrichWithAstContext + applyAstRulesToAdditions with
155
- // the shared readStrategy (S1/C1 same content for regex astContext + AST).
156
- // Run the engine harness phase uses mock engines.
157
- const { mintedRuleIds, firings, groundTruth, positiveControlTargets } = await runMockEngine(lock, readStrategy);
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: 0,
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