@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,57 @@
|
|
|
1
|
+
import type { DraftClassifier, DraftExtractor, GroundTruthLabel, MinerLedgers, ResolvedPrDiff, ReviewThreadSource, SplitArtifact, SplitLedger, Stage4VerifierDeps } from '@mmnto/totem';
|
|
2
|
+
import type { CertifyingCorpus } from './spine-windtunnel.js';
|
|
3
|
+
export interface BuildCertifyingCorpusDeps {
|
|
4
|
+
/** The frozen train/held-out/control split. */
|
|
5
|
+
split: SplitArtifact;
|
|
6
|
+
/** SplitLedger (split + corpusMergeCommits) for the classify integrity re-check. */
|
|
7
|
+
splitLedger: SplitLedger;
|
|
8
|
+
/**
|
|
9
|
+
* Review-thread source. REPLAY (certifying run): a FROZEN source that reads
|
|
10
|
+
* committed fixture content (zero network, fold-K). LIVE (record): the gh-CLI
|
|
11
|
+
* adapter. The extract stage calls `source.fetch(pr)` to build the content the
|
|
12
|
+
* replay extractor keys on, so the content must be reproducible either way.
|
|
13
|
+
*/
|
|
14
|
+
source: ReviewThreadSource;
|
|
15
|
+
/** Draft extractor. REPLAY: `ReplayDraftExtractor`; RECORD: `RecordingDraftExtractor`(Live). */
|
|
16
|
+
extractor: DraftExtractor;
|
|
17
|
+
/** Draft classifier. REPLAY: `ReplayDraftClassifier`; RECORD: `RecordingDraftClassifier`(Live). */
|
|
18
|
+
classifier: DraftClassifier;
|
|
19
|
+
/** Seed-blindness attestation (fold-I §7) — surfaced into the emission ledger. */
|
|
20
|
+
seedClassesProvided: boolean;
|
|
21
|
+
/** Stage-4 verifier deps (listFiles/readFile over the frozen post-image). */
|
|
22
|
+
stage4: Stage4VerifierDeps;
|
|
23
|
+
/** Injected timestamp (Tenet 15 determinism — no `new Date()` in the pipeline). */
|
|
24
|
+
now: string;
|
|
25
|
+
/** Resolved-PR diffs (corpus + positive/negative controls) for firing/scoring. */
|
|
26
|
+
prDiffs: ResolvedPrDiff[];
|
|
27
|
+
/** Frozen ground-truth labels keyed by firingLabelId (TP/FP). */
|
|
28
|
+
groundTruth: Map<string, GroundTruthLabel>;
|
|
29
|
+
}
|
|
30
|
+
export interface CertifyingCorpusBuildResult {
|
|
31
|
+
/** The corpus `runCertifyingEngine` scores (rules, prDiffs, groundTruth, provenanceByRule). */
|
|
32
|
+
corpus: CertifyingCorpus;
|
|
33
|
+
/** fold-I miner ledgers (emission/drop/classifier/split/apiUsage), emitted + CI-observable. */
|
|
34
|
+
ledgers: MinerLedgers;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Compose the shipped slice-2/3/4 stages into the certifying corpus (5c-ii).
|
|
38
|
+
*
|
|
39
|
+
* Runs Extract → Classify → Compile over the train slice via the injected ports
|
|
40
|
+
* (REPLAY for the certifying run — zero LLM, zero network; RECORD wraps the live
|
|
41
|
+
* adapters), then assembles the `CertifyingCorpus`:
|
|
42
|
+
* - **rules + provenanceByRule** from the compiled candidates;
|
|
43
|
+
* - **binding-2 (carried)**: archived (Stage-4 out-of-scope) rules are EXCLUDED
|
|
44
|
+
* from the scored set (archived ≠ wind-tunnel FP — `buildFirings`/fold-F would
|
|
45
|
+
* throw if one leaked); their provenance is dropped with them.
|
|
46
|
+
* - **fold-I (FM-h)**: the API-usage ledger's held-out-fetch-count MUST be 0 —
|
|
47
|
+
* a non-zero count means mining touched the held-out slice, which voids the
|
|
48
|
+
* run, so we fail loud here.
|
|
49
|
+
* - **prDiffs + groundTruth** are passed through from the (fixture or resolved)
|
|
50
|
+
* inputs; firing/scoring is the deterministic 5c-i engine's job downstream.
|
|
51
|
+
*
|
|
52
|
+
* Returns the corpus + the fold-I ledgers (emitted for §7 observability). Pure of
|
|
53
|
+
* its own IO — all IO is behind the injected ports/deps, so it is fully testable
|
|
54
|
+
* with fakes (no LLM, no network, no real git).
|
|
55
|
+
*/
|
|
56
|
+
export declare function buildCertifyingCorpus(deps: BuildCertifyingCorpusDeps): Promise<CertifyingCorpusBuildResult>;
|
|
57
|
+
//# sourceMappingURL=spine-cert-corpus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spine-cert-corpus.d.ts","sourceRoot":"","sources":["../../src/commands/spine-cert-corpus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,YAAY,EAEZ,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,WAAW,EACX,kBAAkB,EACnB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAI9D,MAAM,WAAW,yBAAyB;IACxC,+CAA+C;IAC/C,KAAK,EAAE,aAAa,CAAC;IACrB,oFAAoF;IACpF,WAAW,EAAE,WAAW,CAAC;IACzB;;;;;OAKG;IACH,MAAM,EAAE,kBAAkB,CAAC;IAC3B,gGAAgG;IAChG,SAAS,EAAE,cAAc,CAAC;IAC1B,mGAAmG;IACnG,UAAU,EAAE,eAAe,CAAC;IAC5B,kFAAkF;IAClF,mBAAmB,EAAE,OAAO,CAAC;IAC7B,6EAA6E;IAC7E,MAAM,EAAE,kBAAkB,CAAC;IAC3B,mFAAmF;IACnF,GAAG,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,iEAAiE;IACjE,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,2BAA2B;IAC1C,+FAA+F;IAC/F,MAAM,EAAE,gBAAgB,CAAC;IACzB,+FAA+F;IAC/F,OAAO,EAAE,YAAY,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,yBAAyB,GAC9B,OAAO,CAAC,2BAA2B,CAAC,CA0CtC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compose the shipped slice-2/3/4 stages into the certifying corpus (5c-ii).
|
|
3
|
+
*
|
|
4
|
+
* Runs Extract → Classify → Compile over the train slice via the injected ports
|
|
5
|
+
* (REPLAY for the certifying run — zero LLM, zero network; RECORD wraps the live
|
|
6
|
+
* adapters), then assembles the `CertifyingCorpus`:
|
|
7
|
+
* - **rules + provenanceByRule** from the compiled candidates;
|
|
8
|
+
* - **binding-2 (carried)**: archived (Stage-4 out-of-scope) rules are EXCLUDED
|
|
9
|
+
* from the scored set (archived ≠ wind-tunnel FP — `buildFirings`/fold-F would
|
|
10
|
+
* throw if one leaked); their provenance is dropped with them.
|
|
11
|
+
* - **fold-I (FM-h)**: the API-usage ledger's held-out-fetch-count MUST be 0 —
|
|
12
|
+
* a non-zero count means mining touched the held-out slice, which voids the
|
|
13
|
+
* run, so we fail loud here.
|
|
14
|
+
* - **prDiffs + groundTruth** are passed through from the (fixture or resolved)
|
|
15
|
+
* inputs; firing/scoring is the deterministic 5c-i engine's job downstream.
|
|
16
|
+
*
|
|
17
|
+
* Returns the corpus + the fold-I ledgers (emitted for §7 observability). Pure of
|
|
18
|
+
* its own IO — all IO is behind the injected ports/deps, so it is fully testable
|
|
19
|
+
* with fakes (no LLM, no network, no real git).
|
|
20
|
+
*/
|
|
21
|
+
export async function buildCertifyingCorpus(deps) {
|
|
22
|
+
const { runExtractStage, runClassifyStage, runCompileStage, assembleMinerLedgers, TotemError } = await import('@mmnto/totem');
|
|
23
|
+
// Stage 1 — Extract (REPLAY or LIVE via the injected ports).
|
|
24
|
+
const extract = await runExtractStage(deps.split, {
|
|
25
|
+
source: deps.source,
|
|
26
|
+
extractor: deps.extractor,
|
|
27
|
+
seedClassesProvided: deps.seedClassesProvided,
|
|
28
|
+
});
|
|
29
|
+
// fold-I (FM-h): the held-out slice must NEVER be fetched during mining.
|
|
30
|
+
if (extract.apiUsageLedger.heldOutFetchCount !== 0) {
|
|
31
|
+
throw new TotemError('CONFIG_INVALID', `Held-out fetch count is ${extract.apiUsageLedger.heldOutFetchCount}, must be 0 ` +
|
|
32
|
+
`(fold-I / FM-h): mining touched the held-out slice, which voids the certifying run.`, 'The extract stage fetched a held-out PR. Verify the split + fetch loop honor the train-only boundary.');
|
|
33
|
+
}
|
|
34
|
+
// Stage 2 — Classify; Stage 3/4 — Compile + Stage-4 verify.
|
|
35
|
+
const classify = await runClassifyStage(extract, deps.splitLedger, {
|
|
36
|
+
classifier: deps.classifier,
|
|
37
|
+
});
|
|
38
|
+
const compileResult = await runCompileStage(classify, { stage4: deps.stage4, now: deps.now });
|
|
39
|
+
// binding-2 (carried): exclude Stage-4 out-of-scope (archived) rules from the
|
|
40
|
+
// scored set — archived ≠ wind-tunnel FP; fold-F throws if one reaches firing.
|
|
41
|
+
const scored = compileResult.compiled.filter((c) => c.rule.status !== 'archived');
|
|
42
|
+
const rules = scored.map((c) => c.rule);
|
|
43
|
+
const provenanceByRule = new Map(scored.map((c) => [c.rule.lessonHash, c.provenance]));
|
|
44
|
+
// fold-I ledgers — emitted + CI-observable (§7), never asserted-on beyond FM-h.
|
|
45
|
+
const ledgers = assembleMinerLedgers(deps.splitLedger, extract, classify);
|
|
46
|
+
return {
|
|
47
|
+
corpus: { rules, prDiffs: deps.prDiffs, groundTruth: deps.groundTruth, provenanceByRule },
|
|
48
|
+
ledgers,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=spine-cert-corpus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spine-cert-corpus.js","sourceRoot":"","sources":["../../src/commands/spine-cert-corpus.ts"],"names":[],"mappings":"AAoDA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,IAA+B;IAE/B,MAAM,EAAE,eAAe,EAAE,gBAAgB,EAAE,eAAe,EAAE,oBAAoB,EAAE,UAAU,EAAE,GAC5F,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;IAE/B,6DAA6D;IAC7D,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,KAAK,EAAE;QAChD,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,mBAAmB,EAAE,IAAI,CAAC,mBAAmB;KAC9C,CAAC,CAAC;IAEH,yEAAyE;IACzE,IAAI,OAAO,CAAC,cAAc,CAAC,iBAAiB,KAAK,CAAC,EAAE,CAAC;QACnD,MAAM,IAAI,UAAU,CAClB,gBAAgB,EAChB,2BAA2B,OAAO,CAAC,cAAc,CAAC,iBAAiB,cAAc;YAC/E,qFAAqF,EACvF,uGAAuG,CACxG,CAAC;IACJ,CAAC;IAED,4DAA4D;IAC5D,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE;QACjE,UAAU,EAAE,IAAI,CAAC,UAAU;KAC5B,CAAC,CAAC;IACH,MAAM,aAAa,GAAG,MAAM,eAAe,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAE9F,8EAA8E;IAC9E,+EAA+E;IAC/E,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC;IAClF,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAC9B,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CACrD,CAAC;IAEF,gFAAgF;IAChF,MAAM,OAAO,GAAG,oBAAoB,CAAC,IAAI,CAAC,WAAW,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAE1E,OAAO;QACL,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,gBAAgB,EAAE;QACzF,OAAO;KACR,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spine-cert-corpus.test.d.ts","sourceRoot":"","sources":["../../src/commands/spine-cert-corpus.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { buildCertifyingCorpus } from './spine-cert-corpus.js';
|
|
3
|
+
// ─── Fixtures (mirrors the slice-2/3/4 stage tests) ───
|
|
4
|
+
const sha = (n) => String(n).padStart(40, '0');
|
|
5
|
+
const NOW = '2026-06-19T12:00:00.000Z';
|
|
6
|
+
const REGEX_DSL = [
|
|
7
|
+
'**Pattern:** `forbiddenCall\\(`',
|
|
8
|
+
'**Engine:** regex',
|
|
9
|
+
'**Severity:** warning',
|
|
10
|
+
'',
|
|
11
|
+
'### Bad Example',
|
|
12
|
+
'```ts',
|
|
13
|
+
'forbiddenCall()',
|
|
14
|
+
'```',
|
|
15
|
+
].join('\n');
|
|
16
|
+
function content(pr) {
|
|
17
|
+
return {
|
|
18
|
+
pr,
|
|
19
|
+
mergeCommitSha: sha(pr),
|
|
20
|
+
threads: [
|
|
21
|
+
{
|
|
22
|
+
path: 'src/a.ts',
|
|
23
|
+
comments: [{ author: 'Jane', body: 'note' }],
|
|
24
|
+
isResolved: false,
|
|
25
|
+
isOutdated: false,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function fakeSource() {
|
|
31
|
+
return {
|
|
32
|
+
async fetch(pr) {
|
|
33
|
+
return { kind: 'ok', content: content(pr) };
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function fakeExtractor(dsls) {
|
|
38
|
+
return {
|
|
39
|
+
async draft() {
|
|
40
|
+
return dsls;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function fakeClassifier(disposition = 'structural') {
|
|
45
|
+
return {
|
|
46
|
+
async classify() {
|
|
47
|
+
return { disposition, dispositionSource: 'classified' };
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const SPLIT = {
|
|
52
|
+
asOfCommit: sha(100),
|
|
53
|
+
trainPrs: [1],
|
|
54
|
+
heldOutPrs: [],
|
|
55
|
+
excludedPrs: [],
|
|
56
|
+
positiveControlPrs: [],
|
|
57
|
+
negativeControlPrs: [],
|
|
58
|
+
splitRule: { predicate: 'code-touching non-bot', cutIndex: 1 },
|
|
59
|
+
};
|
|
60
|
+
const SPLIT_LEDGER = {
|
|
61
|
+
split: SPLIT,
|
|
62
|
+
corpus: [1],
|
|
63
|
+
corpusMergeCommits: [{ pr: 1, mergeCommit: sha(1) }],
|
|
64
|
+
};
|
|
65
|
+
function stage4(files) {
|
|
66
|
+
return {
|
|
67
|
+
listFiles: () => Promise.resolve(Object.keys(files)),
|
|
68
|
+
readFile: (f) => f in files ? Promise.resolve(files[f]) : Promise.reject(new Error(`absent ${f}`)),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function baseDeps(overrides = {}) {
|
|
72
|
+
return {
|
|
73
|
+
split: SPLIT,
|
|
74
|
+
splitLedger: SPLIT_LEDGER,
|
|
75
|
+
source: fakeSource(),
|
|
76
|
+
extractor: fakeExtractor([REGEX_DSL]),
|
|
77
|
+
classifier: fakeClassifier(),
|
|
78
|
+
seedClassesProvided: false,
|
|
79
|
+
stage4: stage4({ 'src/a.ts': 'forbiddenCall()' }),
|
|
80
|
+
now: NOW,
|
|
81
|
+
prDiffs: [],
|
|
82
|
+
groundTruth: new Map(),
|
|
83
|
+
...overrides,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// ─── Tests ───────────────────────────────────────────
|
|
87
|
+
describe('buildCertifyingCorpus', () => {
|
|
88
|
+
it('composes extract→classify→compile into a corpus (rules + provenanceByRule + fold-I ledgers)', async () => {
|
|
89
|
+
const { corpus, ledgers } = await buildCertifyingCorpus(baseDeps());
|
|
90
|
+
expect(corpus.rules).toHaveLength(1);
|
|
91
|
+
const rule = corpus.rules[0];
|
|
92
|
+
// provenanceByRule maps the survivor to its mining provenance (train PR 1).
|
|
93
|
+
const prov = corpus.provenanceByRule.get(rule.lessonHash);
|
|
94
|
+
expect(prov).toBeDefined();
|
|
95
|
+
expect(prov.mergedPr).toBe(1);
|
|
96
|
+
// fold-I: held-out fetch count is 0 (FM-h) + seed-blindness surfaced.
|
|
97
|
+
expect(ledgers.apiUsage.heldOutFetchCount).toBe(0);
|
|
98
|
+
expect(ledgers.emission.extractionInputsAttestation.seedClassesProvided).toBe(false);
|
|
99
|
+
});
|
|
100
|
+
it('binding-2: a Stage-4 out-of-scope (archived) rule is EXCLUDED from the scored set', async () => {
|
|
101
|
+
// The rule fires only on a TEST file → Stage-4 out-of-scope → status archived →
|
|
102
|
+
// must not reach the scored set (archived ≠ wind-tunnel FP; fold-F would throw).
|
|
103
|
+
const { corpus } = await buildCertifyingCorpus(baseDeps({ stage4: stage4({ 'src/a.test.ts': 'forbiddenCall()' }) }));
|
|
104
|
+
expect(corpus.rules).toHaveLength(0);
|
|
105
|
+
expect(corpus.provenanceByRule.size).toBe(0);
|
|
106
|
+
});
|
|
107
|
+
it('a behavioral candidate is routed rag-only (never compiled into the scored set)', async () => {
|
|
108
|
+
const { corpus } = await buildCertifyingCorpus(baseDeps({ classifier: fakeClassifier('behavioral') }));
|
|
109
|
+
expect(corpus.rules).toHaveLength(0);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
//# sourceMappingURL=spine-cert-corpus.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spine-cert-corpus.test.js","sourceRoot":"","sources":["../../src/commands/spine-cert-corpus.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spine-cert-e2e.test.d.ts","sourceRoot":"","sources":["../../src/commands/spine-cert-e2e.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import * as net from 'node:net';
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { buildCertifyingCorpus } from './spine-cert-corpus.js';
|
|
4
|
+
import { buildReplayAdapters, recordReplayFixture } from './spine-cert-record.js';
|
|
5
|
+
// ─── Fixtures ────────────────────────────────────────
|
|
6
|
+
const sha = (n) => String(n).padStart(40, '0');
|
|
7
|
+
const NOW = '2026-06-19T12:00:00.000Z';
|
|
8
|
+
const REGEX_DSL = [
|
|
9
|
+
'**Pattern:** `forbiddenCall\\(`',
|
|
10
|
+
'**Engine:** regex',
|
|
11
|
+
'**Severity:** warning',
|
|
12
|
+
'',
|
|
13
|
+
'### Bad Example',
|
|
14
|
+
'```ts',
|
|
15
|
+
'forbiddenCall()',
|
|
16
|
+
'```',
|
|
17
|
+
].join('\n');
|
|
18
|
+
const PROVENANCE = {
|
|
19
|
+
promptTemplateHash: sha(11),
|
|
20
|
+
systemPromptHash: sha(12),
|
|
21
|
+
provider: 'anthropic',
|
|
22
|
+
model: 'claude-test',
|
|
23
|
+
temperature: 0,
|
|
24
|
+
orchestratorVersion: '0.0.0-test',
|
|
25
|
+
adapterKind: 'extractor+classifier',
|
|
26
|
+
keyVersion: 'v1',
|
|
27
|
+
totemVersion: '0.0.0-test',
|
|
28
|
+
};
|
|
29
|
+
function content(pr) {
|
|
30
|
+
return {
|
|
31
|
+
pr,
|
|
32
|
+
mergeCommitSha: sha(pr),
|
|
33
|
+
threads: [
|
|
34
|
+
{
|
|
35
|
+
path: 'src/a.ts',
|
|
36
|
+
comments: [{ author: 'Jane', body: 'note' }],
|
|
37
|
+
isResolved: false,
|
|
38
|
+
isOutdated: false,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function frozenSource() {
|
|
44
|
+
return {
|
|
45
|
+
async fetch(pr) {
|
|
46
|
+
return { kind: 'ok', content: content(pr) };
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function liveExtractor() {
|
|
51
|
+
return {
|
|
52
|
+
async draft() {
|
|
53
|
+
return [REGEX_DSL];
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function liveClassifier() {
|
|
58
|
+
return {
|
|
59
|
+
async classify() {
|
|
60
|
+
return { disposition: 'structural', dispositionSource: 'classified' };
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const SPLIT = {
|
|
65
|
+
asOfCommit: sha(100),
|
|
66
|
+
trainPrs: [1],
|
|
67
|
+
heldOutPrs: [],
|
|
68
|
+
excludedPrs: [],
|
|
69
|
+
positiveControlPrs: [],
|
|
70
|
+
negativeControlPrs: [],
|
|
71
|
+
splitRule: { predicate: 'code-touching non-bot', cutIndex: 1 },
|
|
72
|
+
};
|
|
73
|
+
const SPLIT_LEDGER = {
|
|
74
|
+
split: SPLIT,
|
|
75
|
+
corpus: [1],
|
|
76
|
+
corpusMergeCommits: [{ pr: 1, mergeCommit: sha(1) }],
|
|
77
|
+
};
|
|
78
|
+
function stage4() {
|
|
79
|
+
const files = { 'src/a.ts': 'forbiddenCall()' };
|
|
80
|
+
return {
|
|
81
|
+
listFiles: () => Promise.resolve(Object.keys(files)),
|
|
82
|
+
readFile: (f) => f in files ? Promise.resolve(files[f]) : Promise.reject(new Error(`absent ${f}`)),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** Record a replay fixture from the live fakes (the artifact the cert run replays). */
|
|
86
|
+
async function freezeFixture() {
|
|
87
|
+
return recordReplayFixture({
|
|
88
|
+
split: SPLIT,
|
|
89
|
+
splitLedger: SPLIT_LEDGER,
|
|
90
|
+
source: frozenSource(),
|
|
91
|
+
liveExtractor: liveExtractor(),
|
|
92
|
+
liveClassifier: liveClassifier(),
|
|
93
|
+
seedClassesProvided: false,
|
|
94
|
+
provenance: PROVENANCE,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function replayCorpus(artifact, hash) {
|
|
98
|
+
const { extractor, classifier } = buildReplayAdapters(artifact, hash);
|
|
99
|
+
return buildCertifyingCorpus({
|
|
100
|
+
split: SPLIT,
|
|
101
|
+
splitLedger: SPLIT_LEDGER,
|
|
102
|
+
source: frozenSource(),
|
|
103
|
+
extractor,
|
|
104
|
+
classifier,
|
|
105
|
+
seedClassesProvided: false,
|
|
106
|
+
stage4: stage4(),
|
|
107
|
+
now: NOW,
|
|
108
|
+
prDiffs: [],
|
|
109
|
+
groundTruth: new Map(),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
// ─── fold-K: E2E replay round-trip under network isolation ───
|
|
113
|
+
describe('5c-ii cert run — replay round-trip + TCP isolation (fold-K)', () => {
|
|
114
|
+
afterEach(() => {
|
|
115
|
+
vi.restoreAllMocks();
|
|
116
|
+
});
|
|
117
|
+
it('replays the recorded fixture into a faithful corpus (record→freeze→replay round-trip)', async () => {
|
|
118
|
+
const { artifact, hash } = await freezeFixture();
|
|
119
|
+
const { corpus } = await replayCorpus(artifact, hash);
|
|
120
|
+
expect(corpus.rules).toHaveLength(1);
|
|
121
|
+
const rule = corpus.rules[0];
|
|
122
|
+
expect(corpus.provenanceByRule.get(rule.lessonHash)?.mergedPr).toBe(1);
|
|
123
|
+
});
|
|
124
|
+
it('the replay path makes ZERO outgoing network calls (fold-K TCP block)', async () => {
|
|
125
|
+
const { artifact, hash } = await freezeFixture();
|
|
126
|
+
// Block every outgoing TCP/fetch — the replay corpus build must complete
|
|
127
|
+
// without tripping either guard (zero-LLM, zero-network by construction).
|
|
128
|
+
const netConnect = vi.spyOn(net.Socket.prototype, 'connect').mockImplementation((() => {
|
|
129
|
+
throw new Error('fold-K violation: outgoing TCP connect attempted during replay');
|
|
130
|
+
}));
|
|
131
|
+
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation((() => {
|
|
132
|
+
throw new Error('fold-K violation: fetch attempted during replay');
|
|
133
|
+
}));
|
|
134
|
+
const { corpus } = await replayCorpus(artifact, hash);
|
|
135
|
+
expect(corpus.rules).toHaveLength(1);
|
|
136
|
+
expect(netConnect).not.toHaveBeenCalled();
|
|
137
|
+
expect(fetchSpy).not.toHaveBeenCalled();
|
|
138
|
+
});
|
|
139
|
+
it('a wrong expected-hash trips the integrity gate before any replay (loud fixture failure)', async () => {
|
|
140
|
+
const { artifact } = await freezeFixture();
|
|
141
|
+
// L2: the lock-wired hash must match the artifact; a stale/tampered one fails loud.
|
|
142
|
+
expect(() => buildReplayAdapters(artifact, sha(99))).toThrow();
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
//# sourceMappingURL=spine-cert-e2e.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spine-cert-e2e.test.js","sourceRoot":"","sources":["../../src/commands/spine-cert-e2e.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { CompiledRule, LegitimacyProjectionSkip, ProvenanceRecord, RuleFiring, WindtunnelVerdict } from '@mmnto/totem';
|
|
2
|
+
export interface CertPersistInput {
|
|
3
|
+
/** The scorer verdict for this run. */
|
|
4
|
+
verdict: WindtunnelVerdict;
|
|
5
|
+
/** Firings the verdict was computed over (for the C1 control map + report). */
|
|
6
|
+
firings: RuleFiring[];
|
|
7
|
+
/** Minted (active) rule ids the scorer evaluated. */
|
|
8
|
+
mintedRuleIds: string[];
|
|
9
|
+
/** Positive-control targets (for the per-rule control map). */
|
|
10
|
+
positiveControlTargets: Array<{
|
|
11
|
+
pr: number;
|
|
12
|
+
targetRuleId: string;
|
|
13
|
+
}>;
|
|
14
|
+
/** Candidate compiled rules eligible for stamping (the corpus rules). */
|
|
15
|
+
candidates: CompiledRule[];
|
|
16
|
+
/** Mining provenance per rule (lessonHash → provenance). */
|
|
17
|
+
provenanceByRule: Map<string, ProvenanceRecord>;
|
|
18
|
+
/**
|
|
19
|
+
* Where PASS-survivors are written (the cert OUTPUT compiled-rules file under
|
|
20
|
+
* the gate-1 dir — NOT the repo's live `.totem/compiled-rules.json`, which the
|
|
21
|
+
* strategy#516 populator owns). Required: no default to the live corpus, so a
|
|
22
|
+
* cert run can never clobber it.
|
|
23
|
+
*/
|
|
24
|
+
certifiedRulesOutPath: string;
|
|
25
|
+
/** Directory for the transient cert-run report (§6 L3). */
|
|
26
|
+
reportDir: string;
|
|
27
|
+
/** Injected ISO timestamp (report filename + body) — keeps the call testable. */
|
|
28
|
+
nowIso: string;
|
|
29
|
+
/** Corpus identity for the report (the lock's asOfCommit). */
|
|
30
|
+
asOfCommit: string;
|
|
31
|
+
}
|
|
32
|
+
export interface CertPersistResult {
|
|
33
|
+
/** True iff PASS-survivors were written to the cert output (PASS only). */
|
|
34
|
+
persisted: boolean;
|
|
35
|
+
/** Number of survivor rules stamped + written. */
|
|
36
|
+
stampedCount: number;
|
|
37
|
+
/** Absolute path of the transient cert-run report. */
|
|
38
|
+
reportPath: string;
|
|
39
|
+
/** Path of the written cert rules file (only when persisted). */
|
|
40
|
+
certifiedRulesPath?: string;
|
|
41
|
+
/** Projection skips (non-survivors / missing-provenance / verdict-not-pass). */
|
|
42
|
+
skips: LegitimacyProjectionSkip[];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The certifying-run persistence boundary (§6 L3 + bindings 1+4).
|
|
46
|
+
*
|
|
47
|
+
* 1. Computes the C1 per-rule control map from the firings (survivor-only).
|
|
48
|
+
* 2. fold-B `projectLegitimacy`: stamps survivors PASS-only, from their OWN
|
|
49
|
+
* control results (never the global nonVacuity); non-PASS stamps nothing.
|
|
50
|
+
* 3. fold-C `buildCertifiedRulesFile`: parses the payload BEFORE any write, so a
|
|
51
|
+
* half-stamp fails loud pre-disk. On PASS with ≥1 survivor, the validated
|
|
52
|
+
* file is written to `certifiedRulesOutPath` (the cert OUTPUT, never the live
|
|
53
|
+
* corpus — strategy#516 promotes from here).
|
|
54
|
+
* 4. Always writes a transient cert-run report (verdict + skips + cull ledger +
|
|
55
|
+
* needs-adjudication + exposure) under `reportDir` — including for non-PASS
|
|
56
|
+
* runs, which write a report but NO rules (L3: non-terminals never reach the
|
|
57
|
+
* corpus).
|
|
58
|
+
*
|
|
59
|
+
* The clock + paths are injected so the whole step is deterministically testable
|
|
60
|
+
* with a tmpdir, no real lock required.
|
|
61
|
+
*/
|
|
62
|
+
export declare function persistCertifyingOutcome(input: CertPersistInput): Promise<CertPersistResult>;
|
|
63
|
+
//# sourceMappingURL=spine-cert-persist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spine-cert-persist.d.ts","sourceRoot":"","sources":["../../src/commands/spine-cert-persist.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,YAAY,EACZ,wBAAwB,EACxB,gBAAgB,EAChB,UAAU,EACV,iBAAiB,EAClB,MAAM,cAAc,CAAC;AAItB,MAAM,WAAW,gBAAgB;IAC/B,uCAAuC;IACvC,OAAO,EAAE,iBAAiB,CAAC;IAC3B,+EAA+E;IAC/E,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,qDAAqD;IACrD,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,+DAA+D;IAC/D,sBAAsB,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpE,yEAAyE;IACzE,UAAU,EAAE,YAAY,EAAE,CAAC;IAC3B,4DAA4D;IAC5D,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAChD;;;;;OAKG;IACH,qBAAqB,EAAE,MAAM,CAAC;IAC9B,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,2EAA2E;IAC3E,SAAS,EAAE,OAAO,CAAC;IACnB,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,sDAAsD;IACtD,UAAU,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gFAAgF;IAChF,KAAK,EAAE,wBAAwB,EAAE,CAAC;CACnC;AAUD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,wBAAwB,CAC5C,KAAK,EAAE,gBAAgB,GACtB,OAAO,CAAC,iBAAiB,CAAC,CAsE5B"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
/** Filesystem-safe slug for an ISO timestamp (`2026-06-20T23:59:59.000Z` → `20260620T235959`). */
|
|
5
|
+
function timestampSlug(nowIso) {
|
|
6
|
+
return nowIso
|
|
7
|
+
.replace(/[-:]/g, '')
|
|
8
|
+
.replace(/\.\d+Z$/, '')
|
|
9
|
+
.replace('Z', '');
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The certifying-run persistence boundary (§6 L3 + bindings 1+4).
|
|
13
|
+
*
|
|
14
|
+
* 1. Computes the C1 per-rule control map from the firings (survivor-only).
|
|
15
|
+
* 2. fold-B `projectLegitimacy`: stamps survivors PASS-only, from their OWN
|
|
16
|
+
* control results (never the global nonVacuity); non-PASS stamps nothing.
|
|
17
|
+
* 3. fold-C `buildCertifiedRulesFile`: parses the payload BEFORE any write, so a
|
|
18
|
+
* half-stamp fails loud pre-disk. On PASS with ≥1 survivor, the validated
|
|
19
|
+
* file is written to `certifiedRulesOutPath` (the cert OUTPUT, never the live
|
|
20
|
+
* corpus — strategy#516 promotes from here).
|
|
21
|
+
* 4. Always writes a transient cert-run report (verdict + skips + cull ledger +
|
|
22
|
+
* needs-adjudication + exposure) under `reportDir` — including for non-PASS
|
|
23
|
+
* runs, which write a report but NO rules (L3: non-terminals never reach the
|
|
24
|
+
* corpus).
|
|
25
|
+
*
|
|
26
|
+
* The clock + paths are injected so the whole step is deterministically testable
|
|
27
|
+
* with a tmpdir, no real lock required.
|
|
28
|
+
*/
|
|
29
|
+
export async function persistCertifyingOutcome(input) {
|
|
30
|
+
const { computePerRuleControlResults, projectLegitimacy, buildCertifiedRulesFile, saveCompiledRulesFile, } = await import('@mmnto/totem');
|
|
31
|
+
const perRuleControls = computePerRuleControlResults({
|
|
32
|
+
firings: input.firings,
|
|
33
|
+
mintedRuleIds: input.mintedRuleIds,
|
|
34
|
+
positiveControlTargets: input.positiveControlTargets,
|
|
35
|
+
});
|
|
36
|
+
// fold-B — survivor-only, PASS-only legitimacy projection.
|
|
37
|
+
const projection = projectLegitimacy({
|
|
38
|
+
verdict: input.verdict,
|
|
39
|
+
perRuleControls,
|
|
40
|
+
candidates: input.candidates,
|
|
41
|
+
provenanceByRule: input.provenanceByRule,
|
|
42
|
+
});
|
|
43
|
+
let persisted = false;
|
|
44
|
+
let certifiedRulesPath;
|
|
45
|
+
if (input.verdict.verdict === 'PASS' && projection.stamped.length > 0) {
|
|
46
|
+
// fold-C — parse-before-write net (throws loud on any half-stamp pre-disk).
|
|
47
|
+
const file = buildCertifiedRulesFile(projection.stamped);
|
|
48
|
+
fs.mkdirSync(path.dirname(input.certifiedRulesOutPath), { recursive: true });
|
|
49
|
+
saveCompiledRulesFile(input.certifiedRulesOutPath, file);
|
|
50
|
+
persisted = true;
|
|
51
|
+
certifiedRulesPath = input.certifiedRulesOutPath;
|
|
52
|
+
}
|
|
53
|
+
// Transient cert-run report (§6 L3) — never the live corpus.
|
|
54
|
+
const report = {
|
|
55
|
+
kind: 'windtunnel-cert-run.v1',
|
|
56
|
+
generatedAt: input.nowIso,
|
|
57
|
+
asOfCommit: input.asOfCommit,
|
|
58
|
+
verdict: input.verdict,
|
|
59
|
+
persisted,
|
|
60
|
+
stampedRuleIds: projection.stamped.map((r) => r.lessonHash),
|
|
61
|
+
skips: projection.skips,
|
|
62
|
+
firingCount: input.firings.length,
|
|
63
|
+
};
|
|
64
|
+
// Content hash over the run identity (NOT the timestamp) so the same run is
|
|
65
|
+
// recognizable across re-runs; the timestamp disambiguates the filename.
|
|
66
|
+
const reportHash = createHash('sha256')
|
|
67
|
+
.update(JSON.stringify({
|
|
68
|
+
asOfCommit: input.asOfCommit,
|
|
69
|
+
verdict: input.verdict.verdict,
|
|
70
|
+
labelIds: input.firings.map((f) => f.labelId).sort(),
|
|
71
|
+
}))
|
|
72
|
+
.digest('hex')
|
|
73
|
+
.slice(0, 12);
|
|
74
|
+
const reportPath = path.join(input.reportDir, `run-${timestampSlug(input.nowIso)}-${reportHash}.json`);
|
|
75
|
+
fs.mkdirSync(input.reportDir, { recursive: true });
|
|
76
|
+
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2), 'utf-8');
|
|
77
|
+
return {
|
|
78
|
+
persisted,
|
|
79
|
+
stampedCount: projection.stamped.length,
|
|
80
|
+
reportPath,
|
|
81
|
+
certifiedRulesPath,
|
|
82
|
+
skips: projection.skips,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=spine-cert-persist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spine-cert-persist.js","sourceRoot":"","sources":["../../src/commands/spine-cert-persist.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spine-cert-persist.test.d.ts","sourceRoot":"","sources":["../../src/commands/spine-cert-persist.test.ts"],"names":[],"mappings":""}
|