@mmnto/cli 1.67.1 → 1.69.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-llm-replay.d.ts +446 -0
- package/dist/commands/spine-llm-replay.d.ts.map +1 -0
- package/dist/commands/spine-llm-replay.js +492 -0
- package/dist/commands/spine-llm-replay.js.map +1 -0
- package/dist/commands/spine-llm-replay.test.d.ts +2 -0
- package/dist/commands/spine-llm-replay.test.d.ts.map +1 -0
- package/dist/commands/spine-llm-replay.test.js +372 -0
- package/dist/commands/spine-llm-replay.test.js.map +1 -0
- package/dist/commands/spine-review-thread-source.d.ts +165 -0
- package/dist/commands/spine-review-thread-source.d.ts.map +1 -0
- package/dist/commands/spine-review-thread-source.js +251 -0
- package/dist/commands/spine-review-thread-source.js.map +1 -0
- package/dist/commands/spine-review-thread-source.test.d.ts +2 -0
- package/dist/commands/spine-review-thread-source.test.d.ts.map +1 -0
- package/dist/commands/spine-review-thread-source.test.js +284 -0
- package/dist/commands/spine-review-thread-source.test.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-111 miner slice 5b-i — the deterministic LLM record/replay SCAFFOLD.
|
|
3
|
+
*
|
|
4
|
+
* The certifying run (slice 5) introduces the miner's first NON-deterministic,
|
|
5
|
+
* LLM-backed components (the live `DraftExtractor` / `DraftClassifier` adapters,
|
|
6
|
+
* slice 5b-ii). The cohort panel's central answer (consolidated fold A, 4/4) is
|
|
7
|
+
* that those live outputs must be FROZEN into a recorded replay artifact so the
|
|
8
|
+
* scorer + falsification harness can re-run the certifying experiment ZERO-LLM,
|
|
9
|
+
* byte-deterministically, and auditably — "arguably contract-MANDATED" by §6
|
|
10
|
+
* (fail-loud, no degrade) + §8 (every decision ledgered) + Tenet-15.
|
|
11
|
+
*
|
|
12
|
+
* THIS file is the deterministic record/replay scaffold proven in isolation with
|
|
13
|
+
* a STUB orchestrator: NO live LLM, NO network, NO prompts (those are slice
|
|
14
|
+
* 5b-ii, which populates the provenance block from the real adapter). It defines:
|
|
15
|
+
*
|
|
16
|
+
* - the `llm-replay.v1` Zod artifact (two typed record sections + a provenance
|
|
17
|
+
* block), CLI-side — the replay artifact is a CLI-layer concern; core stays
|
|
18
|
+
* unaware of it;
|
|
19
|
+
* - the deterministic `inputKey` digests for the extractor + classifier ports
|
|
20
|
+
* (mirroring core's `deriveClaimId` — `sha256("<prefix>" + canonicalJson(...))`
|
|
21
|
+
* over IDENTITY fields only);
|
|
22
|
+
* - record/replay decorators (generic over the port) that wrap the core ports;
|
|
23
|
+
* - the EXTERNAL-expected-hash integrity gate (fold B — a self-hash the artifact
|
|
24
|
+
* validates against itself is circular; a content+hash co-rewrite would pass).
|
|
25
|
+
*
|
|
26
|
+
* Determinism discipline: this module is `new Date()` / `Math.random()` -free. A
|
|
27
|
+
* recorded `[]` (extractor) or `{behavioral, error-default}` (classifier) is a
|
|
28
|
+
* REAL row — present in the map, distinguishable from a missing key (a replay
|
|
29
|
+
* MISS is a corpus-integrity failure, never a safe-default).
|
|
30
|
+
*
|
|
31
|
+
* Reuse (Tenet-21): the canonical key-sorted serializer (`canonicalStringify`),
|
|
32
|
+
* the full-digest hash (`calculateDeterministicHash` shape), the `deriveClaimId`
|
|
33
|
+
* identity-fields-only pattern, and the wind-tunnel's external-expected-hash
|
|
34
|
+
* integrity discipline (`verifyControlIntegrity`) are all reused, not reinvented.
|
|
35
|
+
*/
|
|
36
|
+
import { createHash } from 'node:crypto';
|
|
37
|
+
import { z } from 'zod';
|
|
38
|
+
// ─── Named constants ─────────────────────────────────
|
|
39
|
+
/**
|
|
40
|
+
* inputKey version prefixes (mirrors core's `CLAIM_ID_VERSION` discipline). The
|
|
41
|
+
* version is folded INTO the canonical identity payload (`keyVersion` field) AND
|
|
42
|
+
* prepended to the digest input, so the key space is partitioned by both port
|
|
43
|
+
* kind and schema version — a future identity-shape change re-keys cleanly
|
|
44
|
+
* instead of silently colliding with v1 keys.
|
|
45
|
+
*/
|
|
46
|
+
const EXTRACTOR_KEY_VERSION = 'extractor:v1';
|
|
47
|
+
const CLASSIFIER_KEY_VERSION = 'classifier:v1';
|
|
48
|
+
// ─── Errors (CLI-layer; loud-by-construction) ────────
|
|
49
|
+
/**
|
|
50
|
+
* A replay query hit an `inputKey` absent from the frozen records. This is a
|
|
51
|
+
* CORPUS-INTEGRITY failure, never a recoverable per-PR condition: the frozen
|
|
52
|
+
* experiment's premise is that every input the certifying run will ask for was
|
|
53
|
+
* recorded. A miss means the corpus drifted out from under the replay (a new
|
|
54
|
+
* input appeared, or a recorded one was dropped). Falling back to `[]` /
|
|
55
|
+
* `{behavioral, error-default}` would absorb that drift silently and let the
|
|
56
|
+
* certifying verdict diverge from the frozen one undetected — so we throw.
|
|
57
|
+
*/
|
|
58
|
+
export class ReplayMissError extends Error {
|
|
59
|
+
adapterKind;
|
|
60
|
+
inputKey;
|
|
61
|
+
constructor(adapterKind, inputKey) {
|
|
62
|
+
super(`${adapterKind} replay MISS: inputKey ${inputKey} is absent from the frozen records. ` +
|
|
63
|
+
`A miss is a corpus-integrity failure (the frozen-experiment premise is broken), never a safe-default — ` +
|
|
64
|
+
`re-record the replay fixture from the live adapter so it covers this input.`);
|
|
65
|
+
this.name = 'ReplayMissError';
|
|
66
|
+
this.adapterKind = adapterKind;
|
|
67
|
+
this.inputKey = inputKey;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The loaded replay fixture's content-hash does not match the EXTERNAL expected
|
|
72
|
+
* hash injected at construction (fold B). The expected hash is supplied by the
|
|
73
|
+
* caller (5c sources it from a committed lock), NOT embedded in the artifact —
|
|
74
|
+
* an embedded self-hash would be circular (a content+hash co-rewrite passes its
|
|
75
|
+
* own check). A mismatch means the fixture was tampered with or drifted; the
|
|
76
|
+
* replay must NOT proceed, so we throw AT CONSTRUCTION (before any query).
|
|
77
|
+
*/
|
|
78
|
+
export class FixtureIntegrityError extends Error {
|
|
79
|
+
expectedHash;
|
|
80
|
+
actualHash;
|
|
81
|
+
constructor(expectedHash, actualHash) {
|
|
82
|
+
super(`replay fixture integrity check failed — expected ${expectedHash} got ${actualHash}. ` +
|
|
83
|
+
`The frozen LLM record/replay artifact was altered (tampered or drifted). Revert the change or ` +
|
|
84
|
+
`re-freeze the lock with the updated content-hash.`);
|
|
85
|
+
this.name = 'FixtureIntegrityError';
|
|
86
|
+
this.expectedHash = expectedHash;
|
|
87
|
+
this.actualHash = actualHash;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Recording the same `(adapterKind, inputKey)` twice with (potentially)
|
|
92
|
+
* different outputs. The record sink is APPEND-ONCE: a duplicate is never
|
|
93
|
+
* last-write-wins (that would silently launder a non-deterministic adapter's
|
|
94
|
+
* second answer over its first). The recording run must be deterministic, so a
|
|
95
|
+
* duplicate key is a producer bug → throw.
|
|
96
|
+
*/
|
|
97
|
+
export class DuplicateRecordError extends Error {
|
|
98
|
+
adapterKind;
|
|
99
|
+
inputKey;
|
|
100
|
+
constructor(adapterKind, inputKey) {
|
|
101
|
+
super(`duplicate record for ${adapterKind} inputKey ${inputKey} — the record sink is append-once ` +
|
|
102
|
+
`(never last-write-wins). A second output for the same input is a non-determinism leak in the recording run.`);
|
|
103
|
+
this.name = 'DuplicateRecordError';
|
|
104
|
+
this.adapterKind = adapterKind;
|
|
105
|
+
this.inputKey = inputKey;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// ─── Canonical serialization (reused, not reinvented) ─
|
|
109
|
+
/**
|
|
110
|
+
* Recursively rebuild a value with object keys in SORTED order so the subsequent
|
|
111
|
+
* stringify is canonical (mirrors core's `canonicalize` in `artifacts/hash.ts`).
|
|
112
|
+
* Arrays keep element order (a reordered array is a different payload). Used for
|
|
113
|
+
* BOTH the inputKey digest payloads and the records-block content-hash, so the
|
|
114
|
+
* pure replay path has no git / IO dependency.
|
|
115
|
+
*/
|
|
116
|
+
function canonicalize(value) {
|
|
117
|
+
if (Array.isArray(value))
|
|
118
|
+
return value.map(canonicalize);
|
|
119
|
+
if (typeof value === 'object' && value !== null) {
|
|
120
|
+
const sorted = {};
|
|
121
|
+
for (const key of Object.keys(value).sort()) {
|
|
122
|
+
sorted[key] = canonicalize(value[key]);
|
|
123
|
+
}
|
|
124
|
+
return sorted;
|
|
125
|
+
}
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
/** Canonical (recursively key-sorted) minified JSON serialization of `payload`. */
|
|
129
|
+
function canonicalJson(payload) {
|
|
130
|
+
return JSON.stringify(canonicalize(payload));
|
|
131
|
+
}
|
|
132
|
+
/** Full sha256 hex (64 chars) — the digest IS an identity, never a truncation. */
|
|
133
|
+
function sha256Hex(input) {
|
|
134
|
+
return createHash('sha256').update(input, 'utf-8').digest('hex');
|
|
135
|
+
}
|
|
136
|
+
// ─── inputKey derivation (fold D — the `deriveClaimId` pattern) ───────────────
|
|
137
|
+
/**
|
|
138
|
+
* Normalize a single review thread to its STABLE identity: sort comments by
|
|
139
|
+
* (body, author) and carry only the resolution flags + path. Provider/array
|
|
140
|
+
* order must not change the key, so comments are sorted by a stable tuple before
|
|
141
|
+
* hashing. Resolved/outdated flags ARE part of the identity (the eligible-thread
|
|
142
|
+
* set the extractor was actually handed depends on them).
|
|
143
|
+
*/
|
|
144
|
+
function normalizeThread(thread) {
|
|
145
|
+
const comments = [...thread.comments].sort((a, b) => a.body !== b.body
|
|
146
|
+
? a.body < b.body
|
|
147
|
+
? -1
|
|
148
|
+
: 1
|
|
149
|
+
: a.author < b.author
|
|
150
|
+
? -1
|
|
151
|
+
: a.author > b.author
|
|
152
|
+
? 1
|
|
153
|
+
: 0);
|
|
154
|
+
return {
|
|
155
|
+
path: thread.path,
|
|
156
|
+
isResolved: thread.isResolved,
|
|
157
|
+
isOutdated: thread.isOutdated,
|
|
158
|
+
comments,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Normalize the eligible thread set: per-thread comment-normalize, then sort by the
|
|
163
|
+
* FULL canonical JSON of each normalized thread. Sorting by `(path, first-comment)`
|
|
164
|
+
* was NOT a total order — two threads on the SAME path sharing a first comment but
|
|
165
|
+
* differing in LATER comments compared equal, so their provider array order leaked
|
|
166
|
+
* into the canonical payload and the same logical input keyed differently (greptile
|
|
167
|
+
* P1 + CR, #2209; a PR commonly has multiple threads on one file). Sorting by the
|
|
168
|
+
* whole canonical thread IS a total order: two threads compare equal only when
|
|
169
|
+
* byte-identical, so neither thread nor comment order from the provider can shift
|
|
170
|
+
* the key. Stripped of any non-deterministic field — `ReviewThread` carries only
|
|
171
|
+
* `path`, resolution flags, and `{author, body}` comments, all stable.
|
|
172
|
+
*/
|
|
173
|
+
function normalizeThreads(threads) {
|
|
174
|
+
return threads.map(normalizeThread).sort((a, b) => {
|
|
175
|
+
const ka = canonicalJson(a);
|
|
176
|
+
const kb = canonicalJson(b);
|
|
177
|
+
return ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Filter to the EXACT eligible set the extractor is handed: non-resolved,
|
|
182
|
+
* non-outdated threads (mirrors core's `eligibleThreads`). The inputKey is a
|
|
183
|
+
* function of what the port ACTUALLY saw, so eligibility must be applied before
|
|
184
|
+
* normalizing — two contents that differ only in resolved/outdated threads
|
|
185
|
+
* (which the extractor never sees) still key identically.
|
|
186
|
+
*/
|
|
187
|
+
function eligibleThreads(threads) {
|
|
188
|
+
return threads.filter((t) => !t.isResolved && !t.isOutdated);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Deterministic extractor inputKey (fold D). `sha256(canonicalJson({ keyVersion,
|
|
192
|
+
* pr, mergeCommitSha, threads: <normalized eligible threads> }))`. MUST include
|
|
193
|
+
* `mergeCommitSha` (provenance identity). The eligible set is normalized so
|
|
194
|
+
* provider thread/comment order can't change the key; resolved/outdated threads
|
|
195
|
+
* are excluded (the port never sees them). Mirrors `deriveClaimId`: the version
|
|
196
|
+
* is BOTH a payload field and a digest-input prefix.
|
|
197
|
+
*/
|
|
198
|
+
export function extractorInputKey(content) {
|
|
199
|
+
const payload = {
|
|
200
|
+
keyVersion: EXTRACTOR_KEY_VERSION,
|
|
201
|
+
pr: content.pr,
|
|
202
|
+
mergeCommitSha: content.mergeCommitSha,
|
|
203
|
+
threads: normalizeThreads(eligibleThreads(content.threads)),
|
|
204
|
+
};
|
|
205
|
+
return sha256Hex(`${EXTRACTOR_KEY_VERSION}${canonicalJson(payload)}`);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Deterministic classifier inputKey (fold D). `sha256(canonicalJson({
|
|
209
|
+
* keyVersion, provenance, dslSource, draftRef }))`. The classifier does NOT
|
|
210
|
+
* dedupe drafts — TWO drafts from the SAME provenance must not collide to one
|
|
211
|
+
* key — so `draftRef` (a stable ordinal/ref the caller supplies, e.g. the slice-3
|
|
212
|
+
* per-(pr, ordinal) candidate ref) disambiguates them. Without it, N drafts with
|
|
213
|
+
* an identical body from one PR would map to one record and lose N-1 outputs.
|
|
214
|
+
*/
|
|
215
|
+
export function classifierInputKey(draft, draftRef) {
|
|
216
|
+
const payload = {
|
|
217
|
+
keyVersion: CLASSIFIER_KEY_VERSION,
|
|
218
|
+
provenance: draft.provenance,
|
|
219
|
+
dslSource: draft.dslSource,
|
|
220
|
+
draftRef,
|
|
221
|
+
};
|
|
222
|
+
return sha256Hex(`${CLASSIFIER_KEY_VERSION}${canonicalJson(payload)}`);
|
|
223
|
+
}
|
|
224
|
+
// ─── The `llm-replay.v1` artifact (Zod, CLI-side) ─────
|
|
225
|
+
/**
|
|
226
|
+
* Run-level provenance block. In 5b-i these fields are populated by the (stub)
|
|
227
|
+
* caller — the SCHEMA and the integrity gate covering them are what this slice
|
|
228
|
+
* builds; 5b-ii populates them from the live adapter. They pin the exact frozen
|
|
229
|
+
* experiment (prompt + model + adapter + key version + tool version) so a replay
|
|
230
|
+
* is reproducible and a provenance drift is detectable. Provenance lives OUTSIDE
|
|
231
|
+
* the records map (the records block is strictly `inputKey → output`).
|
|
232
|
+
*/
|
|
233
|
+
export const ReplayProvenanceSchema = z.object({
|
|
234
|
+
/** sha256 of the frozen draft/classify prompt TEMPLATE (the decaying-prompt pin). */
|
|
235
|
+
promptTemplateHash: z.string(),
|
|
236
|
+
/** sha256 of the frozen system prompt. */
|
|
237
|
+
systemPromptHash: z.string(),
|
|
238
|
+
/** LLM provider id (e.g. `anthropic` / `gemini` / `openai`). */
|
|
239
|
+
provider: z.string(),
|
|
240
|
+
/** Model id (e.g. a pinned model snapshot). */
|
|
241
|
+
model: z.string(),
|
|
242
|
+
/** Decode temperature the frozen outputs were produced at. */
|
|
243
|
+
temperature: z.number(),
|
|
244
|
+
/** The orchestrator build the live adapter ran under. */
|
|
245
|
+
orchestratorVersion: z.string(),
|
|
246
|
+
/** Which port adapter produced these records (`extractor` / `classifier` / a combined run). */
|
|
247
|
+
adapterKind: z.string(),
|
|
248
|
+
/** The inputKey schema version (so a key-shape change is recorded, not silent). */
|
|
249
|
+
keyVersion: z.string(),
|
|
250
|
+
/** The totem/CLI version that froze the artifact. */
|
|
251
|
+
totemVersion: z.string(),
|
|
252
|
+
});
|
|
253
|
+
/**
|
|
254
|
+
* The records block: STRICTLY `inputKey → the port's raw return value`. A
|
|
255
|
+
* recorded `[]` (extractor) / `{behavioral, error-default}` (classifier) is a
|
|
256
|
+
* REAL row, distinct from a missing key. NEVER write `durationMs` / `recordedAt`
|
|
257
|
+
* / local-user / run-id into a record — those are non-deterministic / identifying
|
|
258
|
+
* metadata that would corrupt the content-hash and break the frozen-experiment
|
|
259
|
+
* premise. The maps are plain `Record<inputKey, output>`; record keys are
|
|
260
|
+
* written SORTED by the canonical serializer (clean git diffs).
|
|
261
|
+
*/
|
|
262
|
+
/**
|
|
263
|
+
* CLI-side validation of a recorded `ClassifierResult` (GCA #2209 + the cohort
|
|
264
|
+
* panel's "replay artifact schema is a CLI-layer concern", gemini): a LOCAL Zod
|
|
265
|
+
* schema rather than a static runtime import of core's `ClassifierResultSchema`
|
|
266
|
+
* (which would pull the heavy `@mmnto/totem` barrel onto the CLI-startup path).
|
|
267
|
+
* Mirrors core's shape AND its refinement (`error-default` ⟹ `behavioral`, the
|
|
268
|
+
* low-privilege safe-default) so a recorded `{structural, error-default}` is
|
|
269
|
+
* rejected. A test asserts parity with core, so this duplication can't silently
|
|
270
|
+
* drift if core's `ClassifierResult` changes.
|
|
271
|
+
*/
|
|
272
|
+
export const ClassifierResultLocalSchema = z
|
|
273
|
+
.object({
|
|
274
|
+
disposition: z.enum(['structural', 'behavioral']),
|
|
275
|
+
dispositionSource: z.enum(['classified', 'error-default']),
|
|
276
|
+
})
|
|
277
|
+
.refine((v) => v.dispositionSource !== 'error-default' || v.disposition === 'behavioral', {
|
|
278
|
+
message: "dispositionSource 'error-default' requires disposition 'behavioral'",
|
|
279
|
+
});
|
|
280
|
+
export const ReplayRecordsSchema = z.object({
|
|
281
|
+
/** `inputKey → DraftExtractor.draft()` return (a `string[]`; `[]` is a real row). */
|
|
282
|
+
extractor: z.record(z.array(z.string())),
|
|
283
|
+
/** `inputKey → DraftClassifier.classify()` return (a `ClassifierResult`). */
|
|
284
|
+
classifier: z.record(ClassifierResultLocalSchema),
|
|
285
|
+
});
|
|
286
|
+
/** Stable artifact-format tag — bumped if the envelope shape changes. */
|
|
287
|
+
export const REPLAY_ARTIFACT_KIND = 'llm-replay.v1';
|
|
288
|
+
/** The full `llm-replay.v1` artifact: format tag + provenance + the two record sections. */
|
|
289
|
+
export const ReplayArtifactSchema = z.object({
|
|
290
|
+
kind: z.literal(REPLAY_ARTIFACT_KIND),
|
|
291
|
+
provenance: ReplayProvenanceSchema,
|
|
292
|
+
records: ReplayRecordsSchema,
|
|
293
|
+
});
|
|
294
|
+
/**
|
|
295
|
+
* Serialize an artifact to canonical, key-SORTED JSON, PRETTY-printed for a
|
|
296
|
+
* committable artifact + clean diffs. Record keys land sorted because the
|
|
297
|
+
* canonicalizer recursively sorts object keys — so re-freezing in a different
|
|
298
|
+
* insertion order is a no-op diff.
|
|
299
|
+
*
|
|
300
|
+
* NOTE (greptile/CR #2209): the content-hash (`computeArtifactHash`) is computed
|
|
301
|
+
* over the MINIFIED canonical form (`canonicalJson`), NOT these pretty-printed
|
|
302
|
+
* bytes. Both run through the same `canonicalize` (so they never drift on key
|
|
303
|
+
* order), but they are NOT byte-identical — to verify integrity always call
|
|
304
|
+
* `computeArtifactHash(loadedArtifact)`; never `sha256` the raw file bytes.
|
|
305
|
+
*/
|
|
306
|
+
export function serializeReplayArtifact(artifact) {
|
|
307
|
+
return JSON.stringify(canonicalize(ReplayArtifactSchema.parse(artifact)), null, 2);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* The artifact CONTENT-HASH (fold B + fold F): sha256 over the canonically-
|
|
311
|
+
* serialized WHOLE artifact (kind + provenance + records). Deterministic +
|
|
312
|
+
* git-independent (the pure replay path has no git dependency — contrast the
|
|
313
|
+
* wind-tunnel's `git hash-object`, which 5c may switch to for wind-tunnel
|
|
314
|
+
* consistency, out of scope here). Hashing the WHOLE artifact (not records-only)
|
|
315
|
+
* means the integrity gate ALSO COVERS the provenance block — so a prompt / model
|
|
316
|
+
* / key-version edit (e.g. `promptTemplateHash`) WITHOUT a re-record trips the
|
|
317
|
+
* gate (fold F: a prompt change must force a re-record, never silently serve
|
|
318
|
+
* stale outputs under a changed prompt). The expected hash is EXTERNAL (caller-
|
|
319
|
+
* injected; 5c sources it from a committed lock) — never embedded (a self-hash
|
|
320
|
+
* the artifact validates against itself is circular: a content+hash co-rewrite
|
|
321
|
+
* would pass its own check).
|
|
322
|
+
*/
|
|
323
|
+
export function computeArtifactHash(artifact) {
|
|
324
|
+
return hashParsedArtifact(ReplayArtifactSchema.parse(artifact));
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Hash an ALREADY-parsed artifact — no redundant re-parse (CR #2209). The Replay
|
|
328
|
+
* constructors parse the fixture once, then the integrity gate hashes that parsed
|
|
329
|
+
* value directly via this helper; `computeArtifactHash` (public) parses first for
|
|
330
|
+
* untrusted input. Both produce the same digest for a valid artifact.
|
|
331
|
+
*/
|
|
332
|
+
function hashParsedArtifact(parsed) {
|
|
333
|
+
return sha256Hex(canonicalJson(parsed));
|
|
334
|
+
}
|
|
335
|
+
// ─── Record sink ─────────────────────────────────────
|
|
336
|
+
/**
|
|
337
|
+
* The append-once record sink the `Recording*` decorators write into. Holds the
|
|
338
|
+
* in-progress records map; `freeze()` produces the immutable artifact. A
|
|
339
|
+
* duplicate `(adapterKind, inputKey)` throws `DuplicateRecordError` — never
|
|
340
|
+
* last-write-wins (a second output for the same input is a non-determinism leak
|
|
341
|
+
* the recording run must surface, not absorb).
|
|
342
|
+
*/
|
|
343
|
+
export class ReplayRecordSink {
|
|
344
|
+
extractor = new Map();
|
|
345
|
+
classifier = new Map();
|
|
346
|
+
recordExtractor(inputKey, output) {
|
|
347
|
+
if (this.extractor.has(inputKey))
|
|
348
|
+
throw new DuplicateRecordError('extractor', inputKey);
|
|
349
|
+
this.extractor.set(inputKey, output);
|
|
350
|
+
}
|
|
351
|
+
recordClassifier(inputKey, output) {
|
|
352
|
+
if (this.classifier.has(inputKey))
|
|
353
|
+
throw new DuplicateRecordError('classifier', inputKey);
|
|
354
|
+
this.classifier.set(inputKey, output);
|
|
355
|
+
}
|
|
356
|
+
/** Snapshot the records as a plain (Zod-validated) object — keys land sorted on serialize. */
|
|
357
|
+
records() {
|
|
358
|
+
return ReplayRecordsSchema.parse({
|
|
359
|
+
extractor: Object.fromEntries(this.extractor),
|
|
360
|
+
classifier: Object.fromEntries(this.classifier),
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
/** Assemble the full `llm-replay.v1` artifact from the recorded sink + a provenance block. */
|
|
364
|
+
freeze(provenance) {
|
|
365
|
+
return ReplayArtifactSchema.parse({
|
|
366
|
+
kind: REPLAY_ARTIFACT_KIND,
|
|
367
|
+
provenance,
|
|
368
|
+
records: this.records(),
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
// ─── Recording decorators (generic over the port) ─────
|
|
373
|
+
/**
|
|
374
|
+
* Records every `DraftExtractor.draft()` call's inputKey → raw `string[]` output
|
|
375
|
+
* into the sink, then passes the value THROUGH unchanged. The recorded value is
|
|
376
|
+
* the PORT's return (pre-core-funnel) — a recorded `[]` is a real row. A
|
|
377
|
+
* duplicate inputKey throws (via the sink).
|
|
378
|
+
*/
|
|
379
|
+
export class RecordingDraftExtractor {
|
|
380
|
+
wrapped;
|
|
381
|
+
sink;
|
|
382
|
+
constructor(wrapped, sink) {
|
|
383
|
+
this.wrapped = wrapped;
|
|
384
|
+
this.sink = sink;
|
|
385
|
+
}
|
|
386
|
+
async draft(content) {
|
|
387
|
+
const output = await this.wrapped.draft(content);
|
|
388
|
+
this.sink.recordExtractor(extractorInputKey(content), output);
|
|
389
|
+
return output;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Records every `DraftClassifier.classify()` call's inputKey → raw
|
|
394
|
+
* `ClassifierResult` output into the sink, then passes it through unchanged. The
|
|
395
|
+
* caller supplies a `draftRef` (the disambiguator for multiple drafts from one
|
|
396
|
+
* provenance — fold D). A duplicate inputKey throws (via the sink).
|
|
397
|
+
*
|
|
398
|
+
* `classify` does not take a `draftRef` in the core port signature, so the
|
|
399
|
+
* decorator binds it via a per-draft ref RESOLVER the caller supplies (e.g. the
|
|
400
|
+
* slice-3 per-(pr, ordinal) candidate ref). The resolver is pure + deterministic.
|
|
401
|
+
*/
|
|
402
|
+
export class RecordingDraftClassifier {
|
|
403
|
+
wrapped;
|
|
404
|
+
sink;
|
|
405
|
+
draftRef;
|
|
406
|
+
constructor(wrapped, sink, draftRef) {
|
|
407
|
+
this.wrapped = wrapped;
|
|
408
|
+
this.sink = sink;
|
|
409
|
+
this.draftRef = draftRef;
|
|
410
|
+
}
|
|
411
|
+
async classify(draft) {
|
|
412
|
+
const output = await this.wrapped.classify(draft);
|
|
413
|
+
this.sink.recordClassifier(classifierInputKey(draft, this.draftRef(draft)), output);
|
|
414
|
+
return output;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// ─── Replay decorators (PURE — zero live calls) ───────
|
|
418
|
+
/**
|
|
419
|
+
* Validate the loaded fixture's whole-artifact content-hash against the EXTERNAL
|
|
420
|
+
* expected hash (fold B + fold F — provenance is covered, so a prompt-hash edit
|
|
421
|
+
* trips the gate). Throws `FixtureIntegrityError` on mismatch. Shared by both
|
|
422
|
+
* replay decorators so the gate runs exactly once per construction with one
|
|
423
|
+
* implementation.
|
|
424
|
+
*/
|
|
425
|
+
function assertFixtureIntegrity(artifact, expectedHash) {
|
|
426
|
+
// `artifact` is already Zod-parsed by the Replay constructor — hash it directly,
|
|
427
|
+
// no redundant re-parse (CR #2209; matters for high-volume 5c runs).
|
|
428
|
+
const actualHash = hashParsedArtifact(artifact);
|
|
429
|
+
if (actualHash !== expectedHash)
|
|
430
|
+
throw new FixtureIntegrityError(expectedHash, actualHash);
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* PURE replay of `DraftExtractor` — zero live LLM / network calls. Computes the
|
|
434
|
+
* inputKey for the requested content, looks it up in the frozen records:
|
|
435
|
+
* - HIT → return the recorded `string[]` (including a recorded `[]` — a real row);
|
|
436
|
+
* - MISS → throw `ReplayMissError` (NEVER fall back to `[]`).
|
|
437
|
+
*
|
|
438
|
+
* Integrity (fold B + F): the constructor takes the EXTERNAL expected content-
|
|
439
|
+
* hash, computes the actual WHOLE-ARTIFACT hash (records + provenance), and throws
|
|
440
|
+
* `FixtureIntegrityError` AT CONSTRUCTION on mismatch — so a tampered/drifted
|
|
441
|
+
* fixture (records OR a prompt-provenance edit) can never serve a single replay.
|
|
442
|
+
*/
|
|
443
|
+
export class ReplayDraftExtractor {
|
|
444
|
+
records;
|
|
445
|
+
constructor(fixture, expectedHash) {
|
|
446
|
+
const parsed = ReplayArtifactSchema.parse(fixture);
|
|
447
|
+
assertFixtureIntegrity(parsed, expectedHash);
|
|
448
|
+
this.records = parsed.records.extractor;
|
|
449
|
+
}
|
|
450
|
+
// `async` so a MISS surfaces as a REJECTED promise (the port contract is
|
|
451
|
+
// `Promise<string[]>`; a consumer awaits it). A synchronous `throw` would
|
|
452
|
+
// escape an `await`-less caller's `.catch`, so the rejection path is uniform.
|
|
453
|
+
async draft(content) {
|
|
454
|
+
const inputKey = extractorInputKey(content);
|
|
455
|
+
// `Object.prototype.hasOwnProperty`-style presence check, NOT truthiness: a
|
|
456
|
+
// recorded `[]` is a real HIT (falsy-but-present), distinct from an absent
|
|
457
|
+
// key (a MISS).
|
|
458
|
+
if (!Object.prototype.hasOwnProperty.call(this.records, inputKey)) {
|
|
459
|
+
throw new ReplayMissError('extractor', inputKey);
|
|
460
|
+
}
|
|
461
|
+
return this.records[inputKey];
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* PURE replay of `DraftClassifier` — zero live calls. Computes the inputKey
|
|
466
|
+
* (using the caller's `draftRef` resolver), looks it up:
|
|
467
|
+
* - HIT → return the recorded `ClassifierResult` (including a recorded
|
|
468
|
+
* `{behavioral, error-default}` — a real row);
|
|
469
|
+
* - MISS → throw `ReplayMissError` (NEVER fall back to the safe-default).
|
|
470
|
+
*
|
|
471
|
+
* Same external-expected-hash integrity gate at construction (fold B).
|
|
472
|
+
*/
|
|
473
|
+
export class ReplayDraftClassifier {
|
|
474
|
+
records;
|
|
475
|
+
draftRef;
|
|
476
|
+
constructor(fixture, expectedHash, draftRef) {
|
|
477
|
+
const parsed = ReplayArtifactSchema.parse(fixture);
|
|
478
|
+
assertFixtureIntegrity(parsed, expectedHash);
|
|
479
|
+
this.records = parsed.records.classifier;
|
|
480
|
+
this.draftRef = draftRef;
|
|
481
|
+
}
|
|
482
|
+
// `async` so a MISS surfaces as a REJECTED promise (uniform with the port
|
|
483
|
+
// contract `Promise<ClassifierResult>`), as in `ReplayDraftExtractor.draft`.
|
|
484
|
+
async classify(draft) {
|
|
485
|
+
const inputKey = classifierInputKey(draft, this.draftRef(draft));
|
|
486
|
+
if (!Object.prototype.hasOwnProperty.call(this.records, inputKey)) {
|
|
487
|
+
throw new ReplayMissError('classifier', inputKey);
|
|
488
|
+
}
|
|
489
|
+
return this.records[inputKey];
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
//# sourceMappingURL=spine-llm-replay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spine-llm-replay.js","sourceRoot":"","sources":["../../src/commands/spine-llm-replay.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spine-llm-replay.test.d.ts","sourceRoot":"","sources":["../../src/commands/spine-llm-replay.test.ts"],"names":[],"mappings":""}
|