@mmnto/cli 1.72.1 → 1.73.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 (35) hide show
  1. package/dist/commands/spine-cert-run-corpus.d.ts +43 -11
  2. package/dist/commands/spine-cert-run-corpus.d.ts.map +1 -1
  3. package/dist/commands/spine-cert-run-corpus.js +96 -41
  4. package/dist/commands/spine-cert-run-corpus.js.map +1 -1
  5. package/dist/commands/spine-corpus-disposition-source.d.ts +179 -0
  6. package/dist/commands/spine-corpus-disposition-source.d.ts.map +1 -0
  7. package/dist/commands/spine-corpus-disposition-source.js +237 -0
  8. package/dist/commands/spine-corpus-disposition-source.js.map +1 -0
  9. package/dist/commands/spine-corpus-disposition-source.test.d.ts +2 -0
  10. package/dist/commands/spine-corpus-disposition-source.test.d.ts.map +1 -0
  11. package/dist/commands/spine-corpus-disposition-source.test.js +170 -0
  12. package/dist/commands/spine-corpus-disposition-source.test.js.map +1 -0
  13. package/dist/commands/spine-derive-labels.d.ts +12 -0
  14. package/dist/commands/spine-derive-labels.d.ts.map +1 -0
  15. package/dist/commands/spine-derive-labels.js +142 -0
  16. package/dist/commands/spine-derive-labels.js.map +1 -0
  17. package/dist/commands/spine-derive-labels.test.d.ts +2 -0
  18. package/dist/commands/spine-derive-labels.test.d.ts.map +1 -0
  19. package/dist/commands/spine-derive-labels.test.js +157 -0
  20. package/dist/commands/spine-derive-labels.test.js.map +1 -0
  21. package/dist/commands/spine-fetch-dispositions.d.ts +27 -0
  22. package/dist/commands/spine-fetch-dispositions.d.ts.map +1 -0
  23. package/dist/commands/spine-fetch-dispositions.js +104 -0
  24. package/dist/commands/spine-fetch-dispositions.js.map +1 -0
  25. package/dist/commands/spine-fetch-dispositions.test.d.ts +2 -0
  26. package/dist/commands/spine-fetch-dispositions.test.d.ts.map +1 -0
  27. package/dist/commands/spine-fetch-dispositions.test.js +155 -0
  28. package/dist/commands/spine-fetch-dispositions.test.js.map +1 -0
  29. package/dist/commands/spine-windtunnel.d.ts +19 -0
  30. package/dist/commands/spine-windtunnel.d.ts.map +1 -1
  31. package/dist/commands/spine-windtunnel.js +84 -50
  32. package/dist/commands/spine-windtunnel.js.map +1 -1
  33. package/dist/index.js +35 -0
  34. package/dist/index.js.map +1 -1
  35. package/package.json +2 -2
@@ -0,0 +1,237 @@
1
+ /**
2
+ * #709 ground-truth deriver — slice 5d-ii: the live held-out disposition adapter.
3
+ *
4
+ * Fetches a HELD-OUT corpus PR's review threads WITH span anchoring (the comment
5
+ * `diffHunk` + the thread line) — the data slice 5d-iii binds a `RuleFiring`'s
6
+ * matched line to a disposition by content/invariant. Distinct from the mining
7
+ * `ReviewThreadSourceAdapter` (which fetches TRAIN PRs and carries no span): the
8
+ * answer key labels HELD-OUT firings, so it needs the held-out PRs' threads,
9
+ * span-anchored, and a richer `CorpusDisposition` payload (core schema).
10
+ *
11
+ * Like the mining adapter, this SURFACES `isResolved`/`isOutdated` and never
12
+ * filters on them (the #2201 "surface, don't filter" discipline; the taxonomy +
13
+ * span-bind decide, 5d-i/5d-iii). Unlike it, a fetch failure FAILS LOUD: the
14
+ * disposition freeze is a by-hand, all-or-nothing producer step (a silent skip
15
+ * would under-populate the answer-key provenance), so a network/parse/pagination
16
+ * fault throws a structured TotemError rather than a discriminated skip.
17
+ *
18
+ * CI HARD-GATE (agy panel): a live GitHub fetch must NEVER run in CI — the
19
+ * certifying run is zero-network and reads only the FROZEN `corpus-dispositions.json`.
20
+ * `fetch` throws if `CI` is set without an explicit `ALLOW_LIVE_FETCH` escape, so
21
+ * an accidental test/CI invocation fails loud instead of hitting the network.
22
+ *
23
+ * Reuses the established `gh` CLI exec pattern (Tenet-21 — no bespoke GraphQL
24
+ * client). The `exec` seam is injectable so the query-spy + mapping + CI-gate
25
+ * tests run fully offline.
26
+ */
27
+ import { z } from 'zod';
28
+ // ─── Named constants ─────────────────────────────────
29
+ const GH_TIMEOUT_MS = 60_000;
30
+ const GH_MAX_BUFFER = 10 * 1024 * 1024;
31
+ /** Single page; a payload past one page fails LOUD rather than silently shrinking the provenance. */
32
+ const PAGE_SIZE = 100;
33
+ // ─── GraphQL response schema (Zod at the IO boundary only) ───────────────────
34
+ const PageInfoSchema = z.object({ hasNextPage: z.boolean() });
35
+ const GqlCommentSchema = z.object({
36
+ // databaseId is the audit-anchor the evidence-ref points at (5d-iii). Nullable for system rows.
37
+ databaseId: z.number().int().nullable(),
38
+ // diffHunk is the content span the firing binds to; '' for an outdated/synthetic comment.
39
+ diffHunk: z.string().nullable(),
40
+ author: z.object({ login: z.string() }).nullable(),
41
+ body: z.string(),
42
+ });
43
+ const GqlThreadSchema = z.object({
44
+ id: z.string(),
45
+ isResolved: z.boolean(),
46
+ isOutdated: z.boolean(),
47
+ path: z.string(),
48
+ line: z.number().int().nullable(),
49
+ originalLine: z.number().int().nullable(),
50
+ comments: z.object({
51
+ pageInfo: PageInfoSchema,
52
+ nodes: z.array(GqlCommentSchema),
53
+ }),
54
+ });
55
+ const GqlResponseSchema = z.object({
56
+ data: z.object({
57
+ repository: z
58
+ .object({
59
+ pullRequest: z
60
+ .object({
61
+ mergeCommit: z.object({ oid: z.string() }).nullable(),
62
+ reviewThreads: z.object({
63
+ pageInfo: PageInfoSchema,
64
+ nodes: z.array(GqlThreadSchema),
65
+ }),
66
+ })
67
+ .nullable(),
68
+ })
69
+ .nullable(),
70
+ }),
71
+ });
72
+ // ─── Query construction ──────────────────────────────
73
+ /**
74
+ * Build the held-out `reviewThreads` GraphQL query. ADDITIVELY requests the span
75
+ * anchors (`line`, `originalLine`, comment `diffHunk`) + the audit ids
76
+ * (`thread.id`, comment `databaseId`) on top of the mining query's
77
+ * `isResolved`/`isOutdated`/`path`/`author`/`body`. It deliberately does NOT add
78
+ * an `isResolved: false` server filter (the "surface, don't filter" ruling).
79
+ * Exported for the query-spy test.
80
+ */
81
+ export function buildCorpusDispositionsQuery(owner, name, pr) {
82
+ return `query {
83
+ repository(owner: ${JSON.stringify(owner)}, name: ${JSON.stringify(name)}) {
84
+ pullRequest(number: ${pr}) {
85
+ mergeCommit { oid }
86
+ reviewThreads(first: ${PAGE_SIZE}) {
87
+ pageInfo { hasNextPage }
88
+ nodes {
89
+ id
90
+ isResolved
91
+ isOutdated
92
+ path
93
+ line
94
+ originalLine
95
+ comments(first: ${PAGE_SIZE}) {
96
+ pageInfo { hasNextPage }
97
+ nodes {
98
+ databaseId
99
+ diffHunk
100
+ author { login }
101
+ body
102
+ }
103
+ }
104
+ }
105
+ }
106
+ }
107
+ }
108
+ }`;
109
+ }
110
+ // ─── Mapping ─────────────────────────────────────────
111
+ /**
112
+ * Map validated GraphQL thread nodes to `CorpusDispositionThread[]`. The thread's
113
+ * span source `diffHunk` is the ROOT (first) comment's hunk — the finding's
114
+ * anchor; a thread with no comments carries ''. A null author (deleted/ghost)
115
+ * coerces to '' (a non-bot empty author core's logic handles). Pure — exported
116
+ * for the mapping test.
117
+ */
118
+ export function mapDispositionThreads(nodes) {
119
+ return nodes.map((t) => ({
120
+ threadId: t.id,
121
+ path: t.path,
122
+ line: t.line,
123
+ originalLine: t.originalLine,
124
+ // The root comment's hunk is the finding's span; '' when absent (outdated/empty thread).
125
+ diffHunk: t.comments.nodes[0]?.diffHunk ?? '',
126
+ isResolved: t.isResolved,
127
+ isOutdated: t.isOutdated,
128
+ comments: t.comments.nodes.map((c) => ({
129
+ ...(c.databaseId !== null ? { commentId: c.databaseId } : {}),
130
+ author: c.author?.login ?? '',
131
+ body: c.body,
132
+ })),
133
+ }));
134
+ }
135
+ /**
136
+ * The live held-out `corpus-dispositions` source. By-hand producer-time only
137
+ * (never CI — the hard-gate enforces it). `fetch` throws loud on any fault.
138
+ */
139
+ export class CorpusDispositionSourceAdapter {
140
+ owner;
141
+ name;
142
+ cwd;
143
+ env;
144
+ injectedExec;
145
+ execPromise;
146
+ constructor(opts) {
147
+ this.owner = opts.owner;
148
+ this.name = opts.name;
149
+ this.cwd = opts.cwd ?? process.cwd();
150
+ this.env = opts.env ?? process.env;
151
+ this.injectedExec = opts.exec;
152
+ }
153
+ async resolveExec() {
154
+ if (this.injectedExec)
155
+ return this.injectedExec;
156
+ this.execPromise ??= (async () => {
157
+ const { safeExec } = await import('@mmnto/totem');
158
+ return (command, args) => safeExec(command, args, {
159
+ cwd: this.cwd,
160
+ timeout: GH_TIMEOUT_MS,
161
+ maxBuffer: GH_MAX_BUFFER,
162
+ // Honor the injected env end-to-end (GCA #2231): `this.env` defaults to
163
+ // process.env live, so no behavior change there, but a fully-injected env
164
+ // now controls the subprocess too, not just the CI hard-gate check.
165
+ env: { ...this.env, GH_PROMPT_DISABLED: '1' },
166
+ });
167
+ })();
168
+ return this.execPromise;
169
+ }
170
+ async fetch(pr) {
171
+ const { TotemError } = await import('@mmnto/totem');
172
+ // CI hard-gate (agy): a live fetch in CI is a contract violation — the cert run
173
+ // is zero-network and reads only the frozen fixture. Fail loud, never the network.
174
+ if (this.env['CI'] && !this.env['ALLOW_LIVE_FETCH']) {
175
+ throw new TotemError('CONFIG_INVALID', 'corpus-disposition fetch: live GitHub fetch attempted in a CI context.', 'fetch-dispositions is a by-hand producer step; the cert run reads the frozen ' +
176
+ 'corpus-dispositions.json. Set ALLOW_LIVE_FETCH=1 only for a deliberate live freeze.');
177
+ }
178
+ const query = buildCorpusDispositionsQuery(this.owner, this.name, pr);
179
+ const exec = await this.resolveExec();
180
+ let raw;
181
+ try {
182
+ raw = exec('gh', ['api', 'graphql', '-f', `query=${query}`]);
183
+ }
184
+ catch (err) {
185
+ throw new TotemError('CONFIG_INVALID', `corpus-disposition fetch: gh graphql failed for held-out PR #${pr}.`, 'Verify gh auth + repo access (the freeze is all-or-nothing — no silent skip).', err);
186
+ }
187
+ let json;
188
+ try {
189
+ json = JSON.parse(raw);
190
+ }
191
+ catch (err) {
192
+ throw new TotemError('CONFIG_INVALID', `corpus-disposition fetch: gh returned non-JSON for held-out PR #${pr}.`, 'The gh GraphQL response was not valid JSON.', err);
193
+ }
194
+ // GitHub GraphQL returns HTTP 200 with `{ errors: [...] }` on auth/scope/query
195
+ // faults — JSON.parse succeeds but `data` is absent, so a bare schema-parse would
196
+ // throw a misleading "unparseable". Surface the REAL gh error first so a by-hand
197
+ // freeze diagnoses fast (greptile #2231 P2).
198
+ if (json !== null &&
199
+ typeof json === 'object' &&
200
+ Array.isArray(json.errors)) {
201
+ const errs = json.errors;
202
+ const first = errs[0]?.message ?? 'unknown GraphQL error';
203
+ throw new TotemError('CONFIG_INVALID', `corpus-disposition fetch: GitHub GraphQL error for held-out PR #${pr}: ${first}`, 'Check gh auth + token scope for the repo (the freeze is all-or-nothing — no silent skip).');
204
+ }
205
+ let parsed;
206
+ try {
207
+ parsed = GqlResponseSchema.parse(json);
208
+ }
209
+ catch (err) {
210
+ throw new TotemError('CONFIG_INVALID', `corpus-disposition fetch: payload unparseable for held-out PR #${pr}.`, 'The gh GraphQL response did not match the expected shape.', err);
211
+ }
212
+ const repo = parsed.data.repository;
213
+ if (!repo) {
214
+ throw new TotemError('CONFIG_INVALID', `corpus-disposition fetch: repository ${this.owner}/${this.name} not found or inaccessible (PR #${pr}).`, 'Check owner/name + token scope.');
215
+ }
216
+ const pull = repo.pullRequest;
217
+ if (!pull) {
218
+ throw new TotemError('CONFIG_INVALID', `corpus-disposition fetch: held-out PR #${pr} not found in ${this.owner}/${this.name}.`, 'Verify the split heldOut set matches the lc repo.');
219
+ }
220
+ const mergeCommitSha = pull.mergeCommit?.oid?.toLowerCase();
221
+ if (!mergeCommitSha) {
222
+ throw new TotemError('CONFIG_INVALID', `corpus-disposition fetch: held-out PR #${pr} has no merge commit oid.`, 'A held-out corpus PR must be merged (lc is squash-merge).');
223
+ }
224
+ // No-silent-shrink (§6): a paginated payload fails loud rather than freezing a
225
+ // partial provenance set (the integrity digest must cover the COMPLETE threads).
226
+ if (pull.reviewThreads.pageInfo.hasNextPage) {
227
+ throw new TotemError('CONFIG_INVALID', `corpus-disposition fetch: held-out PR #${pr} has more than ${PAGE_SIZE} review threads (pagination unsupported).`, 'Increase PAGE_SIZE or add pagination (TODO #2199) before freezing this corpus.');
228
+ }
229
+ for (const t of pull.reviewThreads.nodes) {
230
+ if (t.comments.pageInfo.hasNextPage) {
231
+ throw new TotemError('CONFIG_INVALID', `corpus-disposition fetch: PR #${pr} thread ${t.id} has more than ${PAGE_SIZE} comments (pagination unsupported).`, 'Increase PAGE_SIZE or add pagination before freezing this corpus.');
232
+ }
233
+ }
234
+ return { pr, mergeCommitSha, threads: mapDispositionThreads(pull.reviewThreads.nodes) };
235
+ }
236
+ }
237
+ //# sourceMappingURL=spine-corpus-disposition-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spine-corpus-disposition-source.js","sourceRoot":"","sources":["../../src/commands/spine-corpus-disposition-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB,wDAAwD;AAExD,MAAM,aAAa,GAAG,MAAM,CAAC;AAC7B,MAAM,aAAa,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AACvC,qGAAqG;AACrG,MAAM,SAAS,GAAG,GAAG,CAAC;AAEtB,gFAAgF;AAEhF,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAE9D,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChC,gGAAgG;IAChG,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACvC,0FAA0F;IAC1F,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,QAAQ,EAAE;IAClD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;CACjB,CAAC,CAAC;AAEH,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/B,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE;IACvB,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE;IACvB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACjC,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACzC,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC;QACjB,QAAQ,EAAE,cAAc;QACxB,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC;KACjC,CAAC;CACH,CAAC,CAAC;AAEH,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACjC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;QACb,UAAU,EAAE,CAAC;aACV,MAAM,CAAC;YACN,WAAW,EAAE,CAAC;iBACX,MAAM,CAAC;gBACN,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,QAAQ,EAAE;gBACrD,aAAa,EAAE,CAAC,CAAC,MAAM,CAAC;oBACtB,QAAQ,EAAE,cAAc;oBACxB,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC;iBAChC,CAAC;aACH,CAAC;iBACD,QAAQ,EAAE;SACd,CAAC;aACD,QAAQ,EAAE;KACd,CAAC;CACH,CAAC,CAAC;AAEH,wDAAwD;AAExD;;;;;;;GAOG;AACH,MAAM,UAAU,4BAA4B,CAAC,KAAa,EAAE,IAAY,EAAE,EAAU;IAClF,OAAO;sBACa,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,WAAW,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;0BAChD,EAAE;;6BAEC,SAAS;;;;;;;;;4BASV,SAAS;;;;;;;;;;;;;EAanC,CAAC;AACH,CAAC;AAED,wDAAwD;AAExD;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CACnC,KAAwC;IAExC,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACvB,QAAQ,EAAE,CAAC,CAAC,EAAE;QACd,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,YAAY,EAAE,CAAC,CAAC,YAAY;QAC5B,yFAAyF;QACzF,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,IAAI,EAAE;QAC7C,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACrC,GAAG,CAAC,CAAC,CAAC,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7D,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE;YAC7B,IAAI,EAAE,CAAC,CAAC,IAAI;SACb,CAAC,CAAC;KACJ,CAAC,CAAC,CAAC;AACN,CAAC;AAcD;;;GAGG;AACH,MAAM,OAAO,8BAA8B;IACxB,KAAK,CAAS;IACd,IAAI,CAAS;IACb,GAAG,CAAS;IACZ,GAAG,CAAoB;IACvB,YAAY,CAAqB;IAC1C,WAAW,CAA8B;IAEjD,YAAY,IAAoC;QAC9C,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QACrC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;QACnC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC;IAChC,CAAC;IAEO,KAAK,CAAC,WAAW;QACvB,IAAI,IAAI,CAAC,YAAY;YAAE,OAAO,IAAI,CAAC,YAAY,CAAC;QAChD,IAAI,CAAC,WAAW,KAAK,CAAC,KAAK,IAAI,EAAE;YAC/B,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;YAClD,OAAO,CAAC,OAAe,EAAE,IAAc,EAAE,EAAE,CACzC,QAAQ,CAAC,OAAO,EAAE,IAAI,EAAE;gBACtB,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,OAAO,EAAE,aAAa;gBACtB,SAAS,EAAE,aAAa;gBACxB,wEAAwE;gBACxE,0EAA0E;gBAC1E,oEAAoE;gBACpE,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE;aAC9C,CAAC,CAAC;QACP,CAAC,CAAC,EAAE,CAAC;QACL,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,EAAU;QACpB,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;QAEpD,gFAAgF;QAChF,mFAAmF;QACnF,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,kBAAkB,CAAC,EAAE,CAAC;YACpD,MAAM,IAAI,UAAU,CAClB,gBAAgB,EAChB,wEAAwE,EACxE,+EAA+E;gBAC7E,qFAAqF,CACxF,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,4BAA4B,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACtE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;QAEtC,IAAI,GAAW,CAAC;QAChB,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,KAAK,EAAE,CAAC,CAAC,CAAC;QAC/D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,UAAU,CAClB,gBAAgB,EAChB,gEAAgE,EAAE,GAAG,EACrE,+EAA+E,EAC/E,GAAG,CACJ,CAAC;QACJ,CAAC;QAED,IAAI,IAAa,CAAC;QAClB,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACzB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,UAAU,CAClB,gBAAgB,EAChB,mEAAmE,EAAE,GAAG,EACxE,6CAA6C,EAC7C,GAAG,CACJ,CAAC;QACJ,CAAC;QACD,+EAA+E;QAC/E,kFAAkF;QAClF,iFAAiF;QACjF,6CAA6C;QAC7C,IACE,IAAI,KAAK,IAAI;YACb,OAAO,IAAI,KAAK,QAAQ;YACxB,KAAK,CAAC,OAAO,CAAE,IAA6B,CAAC,MAAM,CAAC,EACpD,CAAC;YACD,MAAM,IAAI,GAAI,IAAgD,CAAC,MAAM,CAAC;YACtE,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,uBAAuB,CAAC;YAC1D,MAAM,IAAI,UAAU,CAClB,gBAAgB,EAChB,mEAAmE,EAAE,KAAK,KAAK,EAAE,EACjF,2FAA2F,CAC5F,CAAC;QACJ,CAAC;QACD,IAAI,MAAyC,CAAC;QAC9C,IAAI,CAAC;YACH,MAAM,GAAG,iBAAiB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,UAAU,CAClB,gBAAgB,EAChB,kEAAkE,EAAE,GAAG,EACvE,2DAA2D,EAC3D,GAAG,CACJ,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;QACpC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,UAAU,CAClB,gBAAgB,EAChB,wCAAwC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,IAAI,mCAAmC,EAAE,IAAI,EACxG,iCAAiC,CAClC,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC;QAC9B,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,UAAU,CAClB,gBAAgB,EAChB,0CAA0C,EAAE,iBAAiB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,IAAI,GAAG,EACvF,mDAAmD,CACpD,CAAC;QACJ,CAAC;QACD,MAAM,cAAc,GAAG,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC;QAC5D,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,MAAM,IAAI,UAAU,CAClB,gBAAgB,EAChB,0CAA0C,EAAE,2BAA2B,EACvE,2DAA2D,CAC5D,CAAC;QACJ,CAAC;QAED,+EAA+E;QAC/E,iFAAiF;QACjF,IAAI,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;YAC5C,MAAM,IAAI,UAAU,CAClB,gBAAgB,EAChB,0CAA0C,EAAE,kBAAkB,SAAS,2CAA2C,EAClH,gFAAgF,CACjF,CAAC;QACJ,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;YACzC,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;gBACpC,MAAM,IAAI,UAAU,CAClB,gBAAgB,EAChB,iCAAiC,EAAE,WAAW,CAAC,CAAC,EAAE,kBAAkB,SAAS,qCAAqC,EAClH,mEAAmE,CACpE,CAAC;YACJ,CAAC;QACH,CAAC;QAED,OAAO,EAAE,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,qBAAqB,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;IAC1F,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=spine-corpus-disposition-source.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spine-corpus-disposition-source.test.d.ts","sourceRoot":"","sources":["../../src/commands/spine-corpus-disposition-source.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,170 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { buildCorpusDispositionsQuery, CorpusDispositionSourceAdapter, mapDispositionThreads, } from './spine-corpus-disposition-source.js';
3
+ const OWNER = 'mmnto-ai';
4
+ const NAME = 'liquid-city';
5
+ const MERGE_OID = 'a'.repeat(40);
6
+ function gqlThread(t) {
7
+ return {
8
+ id: t.id ?? 'PRRT_1',
9
+ isResolved: t.isResolved ?? false,
10
+ isOutdated: t.isOutdated ?? false,
11
+ path: t.path ?? 'src/a.ts',
12
+ line: t.line === undefined ? 40 : t.line,
13
+ originalLine: t.originalLine === undefined ? 38 : t.originalLine,
14
+ comments: {
15
+ pageInfo: { hasNextPage: t.commentsHasNext ?? false },
16
+ nodes: (t.comments ?? [{ login: 'jane', body: 'a note' }]).map((c) => ({
17
+ databaseId: c.databaseId === undefined ? 1001 : c.databaseId,
18
+ diffHunk: c.diffHunk === undefined ? '@@ -1 +1 @@\n+x' : c.diffHunk,
19
+ author: c.login === null ? null : { login: c.login },
20
+ body: c.body,
21
+ })),
22
+ },
23
+ };
24
+ }
25
+ function okPayload(opts) {
26
+ return JSON.stringify({
27
+ data: {
28
+ repository: {
29
+ pullRequest: {
30
+ mergeCommit: opts?.mergeOid === null ? null : { oid: opts?.mergeOid ?? MERGE_OID },
31
+ reviewThreads: {
32
+ pageInfo: { hasNextPage: opts?.threadsHasNext ?? false },
33
+ nodes: (opts?.threads ?? []).map(gqlThread),
34
+ },
35
+ },
36
+ },
37
+ },
38
+ });
39
+ }
40
+ function stubExec(payload) {
41
+ const calls = [];
42
+ const fn = ((_command, args) => {
43
+ calls.push(args);
44
+ return payload;
45
+ });
46
+ fn.calls = calls;
47
+ return fn;
48
+ }
49
+ describe('buildCorpusDispositionsQuery', () => {
50
+ it('REQUESTS the span anchors + audit ids that the mining query lacks', () => {
51
+ const q = buildCorpusDispositionsQuery(OWNER, NAME, 42);
52
+ for (const field of ['diffHunk', 'line', 'originalLine', 'databaseId', 'id']) {
53
+ expect(q).toContain(field);
54
+ }
55
+ });
56
+ it('does NOT add an isResolved:false server filter (surface, don’t filter)', () => {
57
+ const q = buildCorpusDispositionsQuery(OWNER, NAME, 42);
58
+ expect(q).toContain('isResolved');
59
+ expect(q).not.toMatch(/isResolved\s*:/); // no argument form
60
+ });
61
+ });
62
+ describe('mapDispositionThreads', () => {
63
+ it('lifts the root comment diffHunk to the thread span + preserves ids/flags', () => {
64
+ const [mapped] = mapDispositionThreads([
65
+ gqlThread({
66
+ id: 'PRRT_9',
67
+ isResolved: true,
68
+ isOutdated: false,
69
+ path: 'src/foo.ts',
70
+ line: 12,
71
+ originalLine: 10,
72
+ comments: [
73
+ {
74
+ databaseId: 7,
75
+ diffHunk: '@@ root @@\n+rootline',
76
+ login: 'coderabbitai[bot]',
77
+ body: 'finding',
78
+ },
79
+ { databaseId: 8, diffHunk: '@@ reply @@', login: 'jane', body: 'fixed' },
80
+ ],
81
+ }),
82
+ ]);
83
+ expect(mapped).toEqual({
84
+ threadId: 'PRRT_9',
85
+ path: 'src/foo.ts',
86
+ line: 12,
87
+ originalLine: 10,
88
+ diffHunk: '@@ root @@\n+rootline',
89
+ isResolved: true,
90
+ isOutdated: false,
91
+ comments: [
92
+ { commentId: 7, author: 'coderabbitai[bot]', body: 'finding' },
93
+ { commentId: 8, author: 'jane', body: 'fixed' },
94
+ ],
95
+ });
96
+ });
97
+ it('coerces a null author to "" and omits a null commentId / empty hunk', () => {
98
+ const [mapped] = mapDispositionThreads([
99
+ gqlThread({
100
+ line: null,
101
+ originalLine: null,
102
+ comments: [{ databaseId: null, diffHunk: null, login: null, body: 'x' }],
103
+ }),
104
+ ]);
105
+ expect(mapped?.diffHunk).toBe('');
106
+ expect(mapped?.line).toBeNull();
107
+ expect(mapped?.comments[0]).toEqual({ author: '', body: 'x' });
108
+ });
109
+ });
110
+ describe('CorpusDispositionSourceAdapter.fetch', () => {
111
+ it('CI hard-gate: throws on a live fetch in CI without ALLOW_LIVE_FETCH', async () => {
112
+ const adapter = new CorpusDispositionSourceAdapter({
113
+ owner: OWNER,
114
+ name: NAME,
115
+ exec: stubExec(okPayload()),
116
+ env: { CI: '1' },
117
+ });
118
+ await expect(adapter.fetch(42)).rejects.toThrow(/CI context/i);
119
+ });
120
+ it('CI hard-gate: ALLOW_LIVE_FETCH escapes the gate', async () => {
121
+ const adapter = new CorpusDispositionSourceAdapter({
122
+ owner: OWNER,
123
+ name: NAME,
124
+ exec: stubExec(okPayload({ threads: [{}] })),
125
+ env: { CI: '1', ALLOW_LIVE_FETCH: '1' },
126
+ });
127
+ const res = await adapter.fetch(42);
128
+ expect(res.pr).toBe(42);
129
+ expect(res.threads).toHaveLength(1);
130
+ });
131
+ it('maps a held-out PR to a span-anchored CorpusDisposition', async () => {
132
+ const adapter = new CorpusDispositionSourceAdapter({
133
+ owner: OWNER,
134
+ name: NAME,
135
+ exec: stubExec(okPayload({ threads: [{ path: 'src/z.ts' }] })),
136
+ env: {},
137
+ });
138
+ const res = await adapter.fetch(7);
139
+ expect(res).toMatchObject({ pr: 7, mergeCommitSha: MERGE_OID });
140
+ expect(res.threads[0]?.path).toBe('src/z.ts');
141
+ });
142
+ it('fails loud on a paginated thread set (no silent shrink)', async () => {
143
+ const adapter = new CorpusDispositionSourceAdapter({
144
+ owner: OWNER,
145
+ name: NAME,
146
+ exec: stubExec(okPayload({ threads: [{}], threadsHasNext: true })),
147
+ env: {},
148
+ });
149
+ await expect(adapter.fetch(7)).rejects.toThrow(/pagination unsupported/i);
150
+ });
151
+ it('fails loud when a held-out PR has no merge commit', async () => {
152
+ const adapter = new CorpusDispositionSourceAdapter({
153
+ owner: OWNER,
154
+ name: NAME,
155
+ exec: stubExec(okPayload({ mergeOid: null })),
156
+ env: {},
157
+ });
158
+ await expect(adapter.fetch(7)).rejects.toThrow(/no merge commit/i);
159
+ });
160
+ it('surfaces a GitHub GraphQL error message, not a misleading "unparseable" (greptile #2231)', async () => {
161
+ const adapter = new CorpusDispositionSourceAdapter({
162
+ owner: OWNER,
163
+ name: NAME,
164
+ exec: stubExec(JSON.stringify({ errors: [{ message: 'Bad credentials' }] })),
165
+ env: {},
166
+ });
167
+ await expect(adapter.fetch(7)).rejects.toThrow(/GraphQL error.*Bad credentials/i);
168
+ });
169
+ });
170
+ //# sourceMappingURL=spine-corpus-disposition-source.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spine-corpus-disposition-source.test.js","sourceRoot":"","sources":["../../src/commands/spine-corpus-disposition-source.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export interface DeriveLabelsOptions {
2
+ /** Path to the wind-tunnel lock (default `.totem/spine/gate-1/windtunnel.lock.json`). */
3
+ lockPath?: string;
4
+ /** Path to the lc clone (env: TOTEM_LC_DIR) — the post-image blob source the firings read. */
5
+ lcDir?: string;
6
+ /** Gate-1 output dir (default: the lock's dir). */
7
+ outputDir?: string;
8
+ /** Working dir (default `process.cwd()`; injected for tests). */
9
+ cwd?: string;
10
+ }
11
+ export declare function deriveLabelsCommand(opts: DeriveLabelsOptions): Promise<void>;
12
+ //# sourceMappingURL=spine-derive-labels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spine-derive-labels.d.ts","sourceRoot":"","sources":["../../src/commands/spine-derive-labels.ts"],"names":[],"mappings":"AA8BA,MAAM,WAAW,mBAAmB;IAClC,yFAAyF;IACzF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8FAA8F;IAC9F,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgLlF"}
@@ -0,0 +1,142 @@
1
+ // ─── #709 ground-truth deriver — slice 5d-iii-i: the derive-labels command ────
2
+ //
3
+ // `totem spine windtunnel derive-labels [--lock-path] [--lc-dir] [--output-dir]`
4
+ // produces `ground-truth-labels.json` — the cert-run ANSWER KEY (firingLabelId →
5
+ // TP|FP) — by enumerating firings byte-identically to the certifying run (the
6
+ // SHARED firing-setup) and joining each against the frozen held-out
7
+ // `corpus-dispositions.json` (5d-ii) through the closed 5d-i taxonomy. HARD-GATES
8
+ // `controls.integrity.corpusDispositionsSha` before deriving (a tampered
9
+ // disposition would silently flip a label — the #2224/#2225 lesson) and stamps
10
+ // `controls.integrity.groundTruthSha` over the emitted answer key. A by-hand
11
+ // producer step (like `materialize` / `fetch-dispositions`): the certifying RUN
12
+ // consumes the frozen labels (5d-iii-ii) and never re-derives — that, plus the
13
+ // digest, is what makes "the run graded against the key the deriver emitted"
14
+ // verifiable. The deriver MUST NOT call `loadCertRunFixtures` (it reads the file
15
+ // we emit — circularity); it uses `assembleCertifyingCorpus({ skipGroundTruth })`.
16
+ import { createHash } from 'node:crypto';
17
+ import * as fs from 'node:fs';
18
+ import * as path from 'node:path';
19
+ import { assembleCertifyingCorpus, buildGate1Stage4Deps, CORPUS_DISPOSITIONS_FILE, GROUND_TRUTH_FILE, } from './spine-cert-run-corpus.js';
20
+ import { buildCertifyingFirings, buildReadStrategy } from './spine-windtunnel.js';
21
+ const sha256Hex = (s) => createHash('sha256').update(s, 'utf-8').digest('hex');
22
+ export async function deriveLabelsCommand(opts) {
23
+ const { WindtunnelLockSchema, CorpusDispositionsSchema, deriveLabelsFromDispositions, canonicalStringify, safeExec, TotemError, } = await import('@mmnto/totem');
24
+ const cwd = opts.cwd ?? process.cwd();
25
+ const lockPath = opts.lockPath
26
+ ? path.resolve(cwd, opts.lockPath)
27
+ : path.resolve(cwd, '.totem/spine/gate-1/windtunnel.lock.json');
28
+ const gate1Dir = opts.outputDir ? path.resolve(cwd, opts.outputDir) : path.dirname(lockPath);
29
+ const lcDir = opts.lcDir ?? process.env['TOTEM_LC_DIR'];
30
+ // The deriver enumerates REAL firings over the post-image, so it needs the lc
31
+ // clone — without it `readStrategy` returns null for every file and NO rule
32
+ // fires, silently minting an empty answer key. Fail loud (Tenet 4).
33
+ if (!lcDir) {
34
+ throw new TotemError('CONFIG_INVALID', 'derive-labels: no lc clone (--lc-dir / TOTEM_LC_DIR) — cannot read the post-image to ' +
35
+ 'enumerate firings, so the answer key would be silently empty.', 'Provide the lc clone via --lc-dir or the TOTEM_LC_DIR environment variable.');
36
+ }
37
+ const readRaw = (p, hint) => {
38
+ try {
39
+ return fs.readFileSync(p, 'utf-8');
40
+ }
41
+ catch (err) {
42
+ throw new TotemError('CONFIG_INVALID', `derive-labels: required file not found at ${p}`, hint, err);
43
+ }
44
+ };
45
+ // Wrap JSON.parse so a malformed fixture surfaces a structured CLI error instead of a
46
+ // raw SyntaxError (GCA) — `TotemError('CONFIG_INVALID')` to match the CLI-layer
47
+ // convention in `loadCertRunFixtures` / `fetch-dispositions` (NOT TotemParseError,
48
+ // which the styleguide reserves for the core compile/parse layer).
49
+ const parseJson = (raw, p, hint) => {
50
+ try {
51
+ return JSON.parse(raw);
52
+ }
53
+ catch (err) {
54
+ throw new TotemError('CONFIG_INVALID', `derive-labels: ${p} is not valid JSON`, hint, err);
55
+ }
56
+ };
57
+ const lockHint = 'Run `spine windtunnel materialize` first.';
58
+ const lock = WindtunnelLockSchema.parse(parseJson(readRaw(lockPath, lockHint), lockPath, lockHint));
59
+ // ── corpusDispositionsSha derive-time HARD-GATE (must LAND — the #2224 trap
60
+ // #2225 closed for prDiffsSha). Verify-then-parse on a SINGLE read (no check/use
61
+ // split), CRLF→LF normalized to match the 5d-ii stamp + freeze-warn. A tampered
62
+ // disposition would silently flip a label, so a missing OR mismatched digest is a
63
+ // hard fail BEFORE any firing enumeration or label emission. ──
64
+ const expectedDispositionsSha = lock.controls.integrity.corpusDispositionsSha;
65
+ if (!expectedDispositionsSha) {
66
+ throw new TotemError('CONFIG_INVALID', 'derive-labels: lock is missing controls.integrity.corpusDispositionsSha — the held-out ' +
67
+ 'disposition provenance cannot be integrity-checked, so the answer key cannot be trusted.', 'Run `spine windtunnel fetch-dispositions` first (5d-ii stamps the digest).');
68
+ }
69
+ const dispositionsPath = path.join(gate1Dir, CORPUS_DISPOSITIONS_FILE);
70
+ const dispositionsRaw = readRaw(dispositionsPath, 'Run `spine windtunnel fetch-dispositions` first (it writes corpus-dispositions.json).');
71
+ const actualDispositionsSha = sha256Hex(dispositionsRaw.replace(/\r\n/g, '\n'));
72
+ if (actualDispositionsSha !== expectedDispositionsSha) {
73
+ throw new TotemError('CONFIG_INVALID', `derive-labels: corpus-dispositions.json integrity FAILED — expected ${expectedDispositionsSha}, ` +
74
+ `got ${actualDispositionsSha} (the frozen disposition provenance was tampered or re-serialized).`, 'Restore the frozen corpus-dispositions.json or re-run `spine windtunnel fetch-dispositions`.');
75
+ }
76
+ const dispositions = CorpusDispositionsSchema.parse(parseJson(dispositionsRaw, dispositionsPath, 'Restore the frozen corpus-dispositions.json or re-run `spine windtunnel fetch-dispositions`.'));
77
+ // ── Enumerate firings via the SHARED path — byte-identical to the certifying
78
+ // run (assembleCertifyingCorpus + buildCertifyingFirings). skipGroundTruth: the
79
+ // deriver PRODUCES the answer key, so it must not read it (circularity guard). ──
80
+ const asOf = lock.corpus.selectionRule.asOfCommit;
81
+ const readStrategy = buildReadStrategy(lcDir, asOf, safeExec);
82
+ const stage4 = buildGate1Stage4Deps(lcDir, asOf, safeExec);
83
+ // `now` feeds the corpus compile-stage timestamp only; it does NOT enter the
84
+ // content-based firing labelId, so the answer key is deterministic regardless.
85
+ const now = new Date().toISOString();
86
+ // `seedClassesProvided` is intentionally omitted (defaults false), matching the run's
87
+ // `buildReplayCorpusProvider` call which also leaves it false (greptile). It is safe to
88
+ // pin: it is a fold-I §7 ledger ATTESTATION threaded only into the extract-stage ledger
89
+ // — it does not enter rule compilation or the content-based labelId — so it cannot break
90
+ // the byte-identical guarantee even if a future run set it. Thread it through both the
91
+ // run and the deriver together if it ever becomes a real RunOption.
92
+ const { corpus } = await assembleCertifyingCorpus({ gate1Dir, stage4, now, skipGroundTruth: true }, lock);
93
+ const built = await buildCertifyingFirings({
94
+ rules: corpus.rules,
95
+ prDiffs: corpus.prDiffs,
96
+ readStrategy,
97
+ logPrefix: '[DeriveLabels]',
98
+ });
99
+ // ── Derive the answer key (pure, zero-LLM) ──
100
+ const { labels, diagnostics, evidence } = deriveLabelsFromDispositions(built.firings, dispositions);
101
+ // ── Write the answer key canonically (sorted-key, LF + trailing newline) so the
102
+ // groundTruthSha covers the exact on-disk bytes a run/freeze enforcer re-hashes. ──
103
+ fs.mkdirSync(gate1Dir, { recursive: true });
104
+ const gtPath = path.join(gate1Dir, GROUND_TRUTH_FILE);
105
+ const text = `${canonicalStringify(labels, 2)}\n`;
106
+ const gtTmp = `${gtPath}.tmp`;
107
+ fs.writeFileSync(gtTmp, text, 'utf-8');
108
+ fs.renameSync(gtTmp, gtPath);
109
+ const groundTruthSha = sha256Hex(text);
110
+ // ── Stamp groundTruthSha into the lock (read-modify-write canonical), mirroring
111
+ // how `fetch-dispositions` stamps corpusDispositionsSha. Idempotent. The run-side
112
+ // verify + freeze-warn land in 5d-iii-ii (producer → enforcement split). ──
113
+ const stampedLock = {
114
+ ...lock,
115
+ controls: {
116
+ ...lock.controls,
117
+ integrity: { ...lock.controls.integrity, groundTruthSha },
118
+ },
119
+ };
120
+ const lockText = `${canonicalStringify(stampedLock, 2)}\n`;
121
+ const lockTmp = `${lockPath}.tmp`;
122
+ fs.writeFileSync(lockTmp, lockText, 'utf-8');
123
+ fs.renameSync(lockTmp, lockPath);
124
+ // ── Diagnostic report (gemini: deriver reports DATA QUALITY). Deterministic +
125
+ // zero-LLM. A sparse answer key is HONEST-NEGATIVE territory — contract-working,
126
+ // not failure — so surface the coverage + why, never densify-to-PASS. ──
127
+ const d = diagnostics;
128
+ const labeled = d.labelCounts.TP + d.labelCounts.FP;
129
+ console.error(`[DeriveLabels] gate1Dir: ${gate1Dir}`);
130
+ console.error(` firings: ${d.totalFirings} (corpus ${d.corpusFirings} · positive ${d.positiveFirings} · negative ${d.negativeFirings})`);
131
+ console.error(` labeled: ${labeled} (TP ${d.labelCounts.TP} · FP ${d.labelCounts.FP})`);
132
+ console.error(` disposition density: ${(d.dispositionDensity * 100).toFixed(1)}% ` +
133
+ `(${d.boundCorpusFirings}/${d.corpusFirings} corpus firings bound a disposition)`);
134
+ console.error(` unlabeled: ${d.unlabeledFirings} (${(d.unlabeledRate * 100).toFixed(1)}% of scored) — ` +
135
+ `no-match ${d.unlabeledByReason['no-matching-disposition']} · ` +
136
+ `ambiguous ${d.unlabeledByReason['ambiguous-multiple-dispositions']} · ` +
137
+ `unlabeled-class ${d.unlabeledByReason['unlabeled-class']} · ` +
138
+ `incidental-positive ${d.unlabeledByReason['incidental-positive']}`);
139
+ console.error(` groundTruthSha: ${groundTruthSha} (stamped into ${path.basename(lockPath)})`);
140
+ console.error(` Wrote ${GROUND_TRUTH_FILE} (${evidence.length} labeled firing(s) with disposition provenance).`);
141
+ }
142
+ //# sourceMappingURL=spine-derive-labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spine-derive-labels.js","sourceRoot":"","sources":["../../src/commands/spine-derive-labels.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=spine-derive-labels.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spine-derive-labels.test.d.ts","sourceRoot":"","sources":["../../src/commands/spine-derive-labels.test.ts"],"names":[],"mappings":""}