@mmnto/totem 1.44.0 → 1.45.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/compile-cache.d.ts +223 -0
- package/dist/compile-cache.d.ts.map +1 -0
- package/dist/compile-cache.js +325 -0
- package/dist/compile-cache.js.map +1 -0
- package/dist/compile-cache.test.d.ts +9 -0
- package/dist/compile-cache.test.d.ts.map +1 -0
- package/dist/compile-cache.test.js +654 -0
- package/dist/compile-cache.test.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/ledger.d.ts +8 -3
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +6 -0
- package/dist/ledger.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-lesson compile cache (Proposal 281 — Per-Lesson Hash Stability).
|
|
3
|
+
*
|
|
4
|
+
* Short-circuits `totem lesson compile` for lessons whose source content is
|
|
5
|
+
* unchanged across compile runs. Eliminates the per-lesson hash rotation on
|
|
6
|
+
* unrelated lessons when a single lesson is added or modified.
|
|
7
|
+
*
|
|
8
|
+
* Cache key is layered (not composite):
|
|
9
|
+
* 1. `stableId` (when present — reserved for P280; P281 never writes it)
|
|
10
|
+
* 2. `sourceHash` — sha256 of normalized lesson source content
|
|
11
|
+
* 3. `fingerprint` — compile_worker_fingerprint from Proposal 278; mismatch
|
|
12
|
+
* invalidates the entry (e.g., model bump rotates every entry once)
|
|
13
|
+
*
|
|
14
|
+
* `cli_version` is intentionally NOT part of the cache key — including it
|
|
15
|
+
* would invalidate every cohort bump, defeating the purpose. Per the impl
|
|
16
|
+
* contract § Cache key composition.
|
|
17
|
+
*
|
|
18
|
+
* Storage: `.totem/cache/compile-lesson/<sourceHash-first-16-chars>.json`,
|
|
19
|
+
* one entry per file, flat directory for v1 (see follow-on for fan-out
|
|
20
|
+
* cutover at ~1000 lessons).
|
|
21
|
+
*
|
|
22
|
+
* Emergency escape: set `TOTEM_DISABLE_COMPILE_CACHE=1` to bypass the
|
|
23
|
+
* cache entirely (lookup always returns null; write becomes a no-op).
|
|
24
|
+
* Emergency only — not a long-term flag.
|
|
25
|
+
*/
|
|
26
|
+
import { z } from 'zod';
|
|
27
|
+
import type { CompileLessonResult } from './compile-lesson.js';
|
|
28
|
+
/**
|
|
29
|
+
* Cache entry for one lesson's compile output.
|
|
30
|
+
*
|
|
31
|
+
* `stableId` is reserved for P280 (Wind-Tunnel Decoupling). P281 does not
|
|
32
|
+
* populate or query it. Reserving the slot from day one avoids a full cache
|
|
33
|
+
* re-key event when P280 lands; with the reservation, P280's integration is
|
|
34
|
+
* an additive lookup extension, not a refactor. Per
|
|
35
|
+
* `mmnto-ai/totem-strategy#387` § Dependencies (load-bearing).
|
|
36
|
+
*/
|
|
37
|
+
export declare const CacheEntrySchema: z.ZodObject<{
|
|
38
|
+
sourceHash: z.ZodString;
|
|
39
|
+
stableId: z.ZodOptional<z.ZodString>;
|
|
40
|
+
fingerprint: z.ZodString;
|
|
41
|
+
output: z.ZodDiscriminatedUnion<"status", [z.ZodObject<{
|
|
42
|
+
status: z.ZodLiteral<"compiled">;
|
|
43
|
+
rule: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
44
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
45
|
+
status: z.ZodLiteral<"compiled">;
|
|
46
|
+
rule: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
47
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
48
|
+
status: z.ZodLiteral<"compiled">;
|
|
49
|
+
rule: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
50
|
+
}, z.ZodTypeAny, "passthrough">>, z.ZodObject<{
|
|
51
|
+
status: z.ZodLiteral<"skipped">;
|
|
52
|
+
hash: z.ZodString;
|
|
53
|
+
reasonCode: z.ZodString;
|
|
54
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
55
|
+
status: z.ZodLiteral<"skipped">;
|
|
56
|
+
hash: z.ZodString;
|
|
57
|
+
reasonCode: z.ZodString;
|
|
58
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
59
|
+
status: z.ZodLiteral<"skipped">;
|
|
60
|
+
hash: z.ZodString;
|
|
61
|
+
reasonCode: z.ZodString;
|
|
62
|
+
}, z.ZodTypeAny, "passthrough">>, z.ZodObject<{
|
|
63
|
+
status: z.ZodLiteral<"failed">;
|
|
64
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
65
|
+
status: z.ZodLiteral<"failed">;
|
|
66
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
67
|
+
status: z.ZodLiteral<"failed">;
|
|
68
|
+
}, z.ZodTypeAny, "passthrough">>, z.ZodObject<{
|
|
69
|
+
status: z.ZodLiteral<"noop">;
|
|
70
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
71
|
+
status: z.ZodLiteral<"noop">;
|
|
72
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
73
|
+
status: z.ZodLiteral<"noop">;
|
|
74
|
+
}, z.ZodTypeAny, "passthrough">>]>;
|
|
75
|
+
compiledAt: z.ZodString;
|
|
76
|
+
}, "strip", z.ZodTypeAny, {
|
|
77
|
+
compiledAt: string;
|
|
78
|
+
sourceHash: string;
|
|
79
|
+
fingerprint: string;
|
|
80
|
+
output: z.objectOutputType<{
|
|
81
|
+
status: z.ZodLiteral<"compiled">;
|
|
82
|
+
rule: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
83
|
+
}, z.ZodTypeAny, "passthrough"> | z.objectOutputType<{
|
|
84
|
+
status: z.ZodLiteral<"skipped">;
|
|
85
|
+
hash: z.ZodString;
|
|
86
|
+
reasonCode: z.ZodString;
|
|
87
|
+
}, z.ZodTypeAny, "passthrough"> | z.objectOutputType<{
|
|
88
|
+
status: z.ZodLiteral<"failed">;
|
|
89
|
+
}, z.ZodTypeAny, "passthrough"> | z.objectOutputType<{
|
|
90
|
+
status: z.ZodLiteral<"noop">;
|
|
91
|
+
}, z.ZodTypeAny, "passthrough">;
|
|
92
|
+
stableId?: string | undefined;
|
|
93
|
+
}, {
|
|
94
|
+
compiledAt: string;
|
|
95
|
+
sourceHash: string;
|
|
96
|
+
fingerprint: string;
|
|
97
|
+
output: z.objectInputType<{
|
|
98
|
+
status: z.ZodLiteral<"compiled">;
|
|
99
|
+
rule: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
100
|
+
}, z.ZodTypeAny, "passthrough"> | z.objectInputType<{
|
|
101
|
+
status: z.ZodLiteral<"skipped">;
|
|
102
|
+
hash: z.ZodString;
|
|
103
|
+
reasonCode: z.ZodString;
|
|
104
|
+
}, z.ZodTypeAny, "passthrough"> | z.objectInputType<{
|
|
105
|
+
status: z.ZodLiteral<"failed">;
|
|
106
|
+
}, z.ZodTypeAny, "passthrough"> | z.objectInputType<{
|
|
107
|
+
status: z.ZodLiteral<"noop">;
|
|
108
|
+
}, z.ZodTypeAny, "passthrough">;
|
|
109
|
+
stableId?: string | undefined;
|
|
110
|
+
}>;
|
|
111
|
+
export type CacheEntry = Omit<z.infer<typeof CacheEntrySchema>, 'output'> & {
|
|
112
|
+
output: CompileLessonResult;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Discrete cache decisions, emitted per lesson per compile run as
|
|
116
|
+
* telemetry. Maps to the `compile_cache_decision` ledger event's
|
|
117
|
+
* `activity_name` field.
|
|
118
|
+
*/
|
|
119
|
+
export type CacheDecision = 'cache_hit' | 'cache_miss_source_changed' | 'cache_miss_fingerprint_changed' | 'cache_miss_force' | 'cache_miss_no_prior_record';
|
|
120
|
+
/**
|
|
121
|
+
* Compute the cache key for a lesson source. SHA-256 of the content with
|
|
122
|
+
* line endings normalized to `\n` — same normalization as
|
|
123
|
+
* `generateInputHash` in compile-manifest.ts so both surfaces produce
|
|
124
|
+
* identical hashes for identical inputs.
|
|
125
|
+
*/
|
|
126
|
+
export declare function computeLessonSourceHash(lessonSource: string): string;
|
|
127
|
+
/**
|
|
128
|
+
* Compose the hashable lesson source from a parsed lesson's heading and body.
|
|
129
|
+
* Use this from any call site that has a parsed `LessonInput`-shaped object to
|
|
130
|
+
* ensure runtime and migration paths produce identical hashes for the same
|
|
131
|
+
* lesson. Without a shared composition helper, the runtime hash (computed from
|
|
132
|
+
* `lesson.heading` + `lesson.body`) and a migration hash (computed from raw
|
|
133
|
+
* file content with `## Lesson — ` framing) would diverge — exactly the bug
|
|
134
|
+
* GCA R2 surfaced on `#1983`.
|
|
135
|
+
*/
|
|
136
|
+
export declare function composeLessonSourceForHash(heading: string, body: string): string;
|
|
137
|
+
/**
|
|
138
|
+
* Resolve the on-disk cache file path for a given source hash. Pure;
|
|
139
|
+
* does not check existence. Callers handle missing files.
|
|
140
|
+
*/
|
|
141
|
+
export declare function cacheEntryPath(totemDir: string, sourceHash: string): string;
|
|
142
|
+
interface LookupResult {
|
|
143
|
+
entry: CacheEntry | null;
|
|
144
|
+
decision: CacheDecision;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Look up a cache entry by `(sourceHash, fingerprint)`. Returns the entry
|
|
148
|
+
* with `decision: 'cache_hit'` on a clean hit, or `null` with a
|
|
149
|
+
* decision discriminating the miss reason.
|
|
150
|
+
*
|
|
151
|
+
* `stableId` parameter is reserved for P280 wiring — when present, the
|
|
152
|
+
* lookup tries the stable-id-indexed entry first, falling back to
|
|
153
|
+
* `sourceHash` on miss. v1 (this PR) never receives a non-undefined
|
|
154
|
+
* value here.
|
|
155
|
+
*/
|
|
156
|
+
export declare function lookupCacheEntry(totemDir: string, sourceHash: string, fingerprint: string, options?: {
|
|
157
|
+
force?: boolean;
|
|
158
|
+
stableId?: string;
|
|
159
|
+
}): LookupResult;
|
|
160
|
+
/**
|
|
161
|
+
* Persist a cache entry after a successful compile. Idempotent — writing
|
|
162
|
+
* the same `(sourceHash, fingerprint, output)` twice is a no-op-shaped
|
|
163
|
+
* overwrite. Fire-and-forget: I/O failures are surfaced via `onWarn` and
|
|
164
|
+
* never propagate (a failed cache write should not crash a compile).
|
|
165
|
+
*
|
|
166
|
+
* Returns `true` when the entry was successfully persisted, `false` on any
|
|
167
|
+
* I/O failure or when the cache is disabled via env var. Callers that need
|
|
168
|
+
* to track migration outcomes (e.g., `migrateFromCompiledRules`) inspect
|
|
169
|
+
* the return; callers that don't care can ignore it.
|
|
170
|
+
*/
|
|
171
|
+
export declare function writeCacheEntry(totemDir: string, entry: CacheEntry, onWarn?: (msg: string) => void): boolean;
|
|
172
|
+
/**
|
|
173
|
+
* Construct a `CacheEntry` from the inputs of a fresh compile. Pure;
|
|
174
|
+
* callers persist via `writeCacheEntry`.
|
|
175
|
+
*/
|
|
176
|
+
export declare function buildCacheEntry(sourceHash: string, fingerprint: string, output: CompileLessonResult): CacheEntry;
|
|
177
|
+
interface MigrationSeedInput {
|
|
178
|
+
/** Canonical lesson hash (rotation-prone). Carried for diagnostics only. */
|
|
179
|
+
lessonHash: string;
|
|
180
|
+
/**
|
|
181
|
+
* Parsed lesson heading — the `## Lesson — …` heading-text minus the
|
|
182
|
+
* leading `##` markdown. MUST match what `readAllLessons` (or equivalent
|
|
183
|
+
* parser) provides at runtime to the compile path. Composed with `body`
|
|
184
|
+
* via `composeLessonSourceForHash` to produce the same sourceHash the
|
|
185
|
+
* runtime cache lookup will compute.
|
|
186
|
+
*/
|
|
187
|
+
heading: string;
|
|
188
|
+
/**
|
|
189
|
+
* Parsed lesson body — the content between the heading and the next
|
|
190
|
+
* lesson heading. MUST match what `readAllLessons` provides at runtime.
|
|
191
|
+
*/
|
|
192
|
+
body: string;
|
|
193
|
+
/** The compile output to seed into the cache. */
|
|
194
|
+
output: CompileLessonResult;
|
|
195
|
+
}
|
|
196
|
+
interface MigrationResult {
|
|
197
|
+
seeded: number;
|
|
198
|
+
skipped: number;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* One-shot seed migration: walk an existing compiled-rules.json + lesson
|
|
202
|
+
* sources, materialize the cache entries that would have been produced
|
|
203
|
+
* if the cache had existed from day one. After this step, the first
|
|
204
|
+
* post-migration compile run produces 100% cache hits and `compiled-
|
|
205
|
+
* rules.json` byte-for-byte matches its prior state.
|
|
206
|
+
*
|
|
207
|
+
* Idempotent — running twice with the same inputs writes the same entries
|
|
208
|
+
* a second time (overwrite). Per the impl contract § Migration sequence.
|
|
209
|
+
*
|
|
210
|
+
* Heading + body are taken separately (rather than a single raw-source
|
|
211
|
+
* string) to enforce the canonical `composeLessonSourceForHash` call shape.
|
|
212
|
+
* If migration accepted a free-form `lessonSource` string, the migration
|
|
213
|
+
* and runtime hash paths could diverge (per GCA R2 critical on `#1983`).
|
|
214
|
+
*/
|
|
215
|
+
export declare function migrateFromCompiledRules(totemDir: string, fingerprint: string, inputs: MigrationSeedInput[], onWarn?: (msg: string) => void): MigrationResult;
|
|
216
|
+
/**
|
|
217
|
+
* List all cache-entry filenames currently on disk. Returns the file basenames
|
|
218
|
+
* (not full paths). Useful for `totem cache --prune-orphans` (out of scope for
|
|
219
|
+
* v1) and for test isolation cleanup.
|
|
220
|
+
*/
|
|
221
|
+
export declare function listCacheEntries(totemDir: string): string[];
|
|
222
|
+
export {};
|
|
223
|
+
//# sourceMappingURL=compile-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile-cache.d.ts","sourceRoot":"","sources":["../src/compile-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAMH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAsF/D;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAM3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,EAAE,QAAQ,CAAC,GAAG;IAC1E,MAAM,EAAE,mBAAmB,CAAC;CAC7B,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,aAAa,GACrB,WAAW,GACX,2BAA2B,GAC3B,gCAAgC,GAChC,kBAAkB,GAClB,4BAA4B,CAAC;AAIjC;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAWpE;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhF;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAc3E;AAED,UAAU,YAAY;IACpB,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,aAAa,CAAC;CACzB;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GACnD,YAAY,CA4Cd;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,UAAU,EACjB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC7B,OAAO,CAiBT;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,mBAAmB,GAC1B,UAAU,CAOZ;AAED,UAAU,kBAAkB;IAC1B,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,MAAM,EAAE,mBAAmB,CAAC;CAC7B;AAED,UAAU,eAAe;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,kBAAkB,EAAE,EAC5B,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC7B,eAAe,CA8BjB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAM3D"}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-lesson compile cache (Proposal 281 — Per-Lesson Hash Stability).
|
|
3
|
+
*
|
|
4
|
+
* Short-circuits `totem lesson compile` for lessons whose source content is
|
|
5
|
+
* unchanged across compile runs. Eliminates the per-lesson hash rotation on
|
|
6
|
+
* unrelated lessons when a single lesson is added or modified.
|
|
7
|
+
*
|
|
8
|
+
* Cache key is layered (not composite):
|
|
9
|
+
* 1. `stableId` (when present — reserved for P280; P281 never writes it)
|
|
10
|
+
* 2. `sourceHash` — sha256 of normalized lesson source content
|
|
11
|
+
* 3. `fingerprint` — compile_worker_fingerprint from Proposal 278; mismatch
|
|
12
|
+
* invalidates the entry (e.g., model bump rotates every entry once)
|
|
13
|
+
*
|
|
14
|
+
* `cli_version` is intentionally NOT part of the cache key — including it
|
|
15
|
+
* would invalidate every cohort bump, defeating the purpose. Per the impl
|
|
16
|
+
* contract § Cache key composition.
|
|
17
|
+
*
|
|
18
|
+
* Storage: `.totem/cache/compile-lesson/<sourceHash-first-16-chars>.json`,
|
|
19
|
+
* one entry per file, flat directory for v1 (see follow-on for fan-out
|
|
20
|
+
* cutover at ~1000 lessons).
|
|
21
|
+
*
|
|
22
|
+
* Emergency escape: set `TOTEM_DISABLE_COMPILE_CACHE=1` to bypass the
|
|
23
|
+
* cache entirely (lookup always returns null; write becomes a no-op).
|
|
24
|
+
* Emergency only — not a long-term flag.
|
|
25
|
+
*/
|
|
26
|
+
import * as crypto from 'node:crypto';
|
|
27
|
+
import * as fs from 'node:fs';
|
|
28
|
+
import * as path from 'node:path';
|
|
29
|
+
import { z } from 'zod';
|
|
30
|
+
// ─── Constants ──────────────────────────────────────
|
|
31
|
+
// CACHE_DIR is resolved relative to `totemDir` (the already-configured
|
|
32
|
+
// `.totem` location). Including `.totem` in this constant duplicated the
|
|
33
|
+
// prefix and landed cache files at `<totemDir>/.totem/cache/...` instead of
|
|
34
|
+
// `<totemDir>/cache/...`. Per CR R3 Major on `mmnto-ai/totem#1983`.
|
|
35
|
+
const CACHE_DIR = path.join('cache', 'compile-lesson');
|
|
36
|
+
const HASH_FILENAME_PREFIX_LENGTH = 16;
|
|
37
|
+
const DISABLE_ENV_VAR = 'TOTEM_DISABLE_COMPILE_CACHE';
|
|
38
|
+
/**
|
|
39
|
+
* Strict sha256-hex shape for `sourceHash`. Used to gate `cacheEntryPath`
|
|
40
|
+
* against path-traversal inputs (`/`, `..`, etc.) per CR R4 Major on
|
|
41
|
+
* `mmnto-ai/totem#1983`. Matches the digest computeLessonSourceHash emits.
|
|
42
|
+
*/
|
|
43
|
+
const SOURCE_HASH_RE = /^[a-f0-9]{64}$/;
|
|
44
|
+
/**
|
|
45
|
+
* Best-effort wrapper around an optional warning callback. The cache module's
|
|
46
|
+
* non-throwing contract requires that a caller-supplied `onWarn` cannot
|
|
47
|
+
* propagate exceptions out of cache write/seed paths — even an `onWarn` that
|
|
48
|
+
* throws must not abort compile. Per CR R4 Major on `mmnto-ai/totem#1983`.
|
|
49
|
+
*/
|
|
50
|
+
function safeOnWarn(onWarn, message) {
|
|
51
|
+
if (onWarn === undefined)
|
|
52
|
+
return;
|
|
53
|
+
try {
|
|
54
|
+
onWarn(message);
|
|
55
|
+
// totem-context: intentional cleanup — onWarn must never propagate exceptions per the cache's non-throwing contract; swallow any caller-thrown errors silently.
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
// totem-context: intentional cleanup — onWarn must never propagate exceptions per the cache's non-throwing contract; swallow any caller-thrown errors silently.
|
|
59
|
+
void err;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// ─── Schema ─────────────────────────────────────────
|
|
63
|
+
/**
|
|
64
|
+
* Validator for the `output` field. CompileLessonResult is a discriminated
|
|
65
|
+
* union (status: 'compiled' | 'skipped' | 'failed' | 'noop'); each variant
|
|
66
|
+
* carries required fields that downstream consumers dereference unconditionally
|
|
67
|
+
* (e.g., the cache-hit path reads `result.rule` for compiled outputs and
|
|
68
|
+
* `result.hash` / `result.reasonCode` for skipped outputs). A minimal
|
|
69
|
+
* status-only schema would accept truncated cache files like
|
|
70
|
+
* `{ output: { status: 'compiled' } }` and break the file's own "bad cache
|
|
71
|
+
* entry → miss, not crash" contract on the next lookup.
|
|
72
|
+
*
|
|
73
|
+
* Per CR R3 Major on `mmnto-ai/totem#1983`: validate the required-per-variant
|
|
74
|
+
* shape so structurally incomplete payloads are rejected upfront. `rule` is
|
|
75
|
+
* `z.unknown()` to avoid coupling this module to the full `CompiledRule`
|
|
76
|
+
* shape (changes to that schema must not invalidate every cohort's cache);
|
|
77
|
+
* variant-required fields like `hash` and `reasonCode` are typed concretely
|
|
78
|
+
* since they're the dereferences that would crash.
|
|
79
|
+
*/
|
|
80
|
+
const CompileLessonResultMinimalSchema = z.discriminatedUnion('status', [
|
|
81
|
+
z
|
|
82
|
+
.object({
|
|
83
|
+
status: z.literal('compiled'),
|
|
84
|
+
// `rule` MUST be a non-null object (the CompiledRule payload). z.unknown()
|
|
85
|
+
// would accept missing fields too, which defeats the contract — a payload
|
|
86
|
+
// like `{ status: 'compiled' }` would survive validation and crash the
|
|
87
|
+
// cache-hit path on `result.rule` dereference. `z.record(z.unknown())`
|
|
88
|
+
// requires an object without coupling to CompiledRule's exact shape.
|
|
89
|
+
rule: z.record(z.unknown()),
|
|
90
|
+
})
|
|
91
|
+
.passthrough(),
|
|
92
|
+
z
|
|
93
|
+
.object({
|
|
94
|
+
status: z.literal('skipped'),
|
|
95
|
+
hash: z.string(),
|
|
96
|
+
reasonCode: z.string(),
|
|
97
|
+
})
|
|
98
|
+
.passthrough(),
|
|
99
|
+
z
|
|
100
|
+
.object({
|
|
101
|
+
status: z.literal('failed'),
|
|
102
|
+
})
|
|
103
|
+
.passthrough(),
|
|
104
|
+
z
|
|
105
|
+
.object({
|
|
106
|
+
status: z.literal('noop'),
|
|
107
|
+
})
|
|
108
|
+
.passthrough(),
|
|
109
|
+
]);
|
|
110
|
+
/**
|
|
111
|
+
* Cache entry for one lesson's compile output.
|
|
112
|
+
*
|
|
113
|
+
* `stableId` is reserved for P280 (Wind-Tunnel Decoupling). P281 does not
|
|
114
|
+
* populate or query it. Reserving the slot from day one avoids a full cache
|
|
115
|
+
* re-key event when P280 lands; with the reservation, P280's integration is
|
|
116
|
+
* an additive lookup extension, not a refactor. Per
|
|
117
|
+
* `mmnto-ai/totem-strategy#387` § Dependencies (load-bearing).
|
|
118
|
+
*/
|
|
119
|
+
export const CacheEntrySchema = z.object({
|
|
120
|
+
sourceHash: z.string(),
|
|
121
|
+
stableId: z.string().optional(),
|
|
122
|
+
fingerprint: z.string(),
|
|
123
|
+
output: CompileLessonResultMinimalSchema,
|
|
124
|
+
compiledAt: z.string(),
|
|
125
|
+
});
|
|
126
|
+
// ─── Public API ─────────────────────────────────────
|
|
127
|
+
/**
|
|
128
|
+
* Compute the cache key for a lesson source. SHA-256 of the content with
|
|
129
|
+
* line endings normalized to `\n` — same normalization as
|
|
130
|
+
* `generateInputHash` in compile-manifest.ts so both surfaces produce
|
|
131
|
+
* identical hashes for identical inputs.
|
|
132
|
+
*/
|
|
133
|
+
export function computeLessonSourceHash(lessonSource) {
|
|
134
|
+
// Normalization: collapse CRLF → LF ONLY. No trailing-whitespace trim — the
|
|
135
|
+
// canonical `generateInputHash` (`compile-manifest.ts`) and `lessonHash` (per
|
|
136
|
+
// `.gemini/styleguide.md` line 142) are both trailing-whitespace-sensitive.
|
|
137
|
+
// Trimming here would desynchronize the cache key from the canonical hashes:
|
|
138
|
+
// a whitespace-only edit would hit the cache (return stale lessonHash output)
|
|
139
|
+
// while the manifest pipeline would compute a fresh lessonHash for the same
|
|
140
|
+
// edit, breaking the deterministic link between lessons and rules.
|
|
141
|
+
// Reverses an over-correction from R1; per GCA R2 critical on `#1983`.
|
|
142
|
+
const normalized = lessonSource.replace(/\r\n/g, '\n');
|
|
143
|
+
return crypto.createHash('sha256').update(normalized).digest('hex');
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Compose the hashable lesson source from a parsed lesson's heading and body.
|
|
147
|
+
* Use this from any call site that has a parsed `LessonInput`-shaped object to
|
|
148
|
+
* ensure runtime and migration paths produce identical hashes for the same
|
|
149
|
+
* lesson. Without a shared composition helper, the runtime hash (computed from
|
|
150
|
+
* `lesson.heading` + `lesson.body`) and a migration hash (computed from raw
|
|
151
|
+
* file content with `## Lesson — ` framing) would diverge — exactly the bug
|
|
152
|
+
* GCA R2 surfaced on `#1983`.
|
|
153
|
+
*/
|
|
154
|
+
export function composeLessonSourceForHash(heading, body) {
|
|
155
|
+
return `${heading}\n${body}`;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Resolve the on-disk cache file path for a given source hash. Pure;
|
|
159
|
+
* does not check existence. Callers handle missing files.
|
|
160
|
+
*/
|
|
161
|
+
export function cacheEntryPath(totemDir, sourceHash) {
|
|
162
|
+
// Reject non-SHA inputs at the boundary (CR R4 Major on `mmnto-ai/totem#1983`).
|
|
163
|
+
// Without this, a sourceHash carrying `/` or `..` would flow through
|
|
164
|
+
// path.join() and escape `<totemDir>/cache/compile-lesson`, letting lookup or
|
|
165
|
+
// write touch arbitrary files. computeLessonSourceHash always emits the
|
|
166
|
+
// matching 64-char hex shape; this catches misuse where a caller hands us
|
|
167
|
+
// unvalidated input.
|
|
168
|
+
if (!SOURCE_HASH_RE.test(sourceHash)) {
|
|
169
|
+
throw new Error(`[Totem Error] Invalid sourceHash: expected 64-char sha256 hex digest, got ${JSON.stringify(sourceHash.slice(0, 80))}`);
|
|
170
|
+
}
|
|
171
|
+
const filename = `${sourceHash.slice(0, HASH_FILENAME_PREFIX_LENGTH)}.json`;
|
|
172
|
+
return path.join(totemDir, CACHE_DIR, filename);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Look up a cache entry by `(sourceHash, fingerprint)`. Returns the entry
|
|
176
|
+
* with `decision: 'cache_hit'` on a clean hit, or `null` with a
|
|
177
|
+
* decision discriminating the miss reason.
|
|
178
|
+
*
|
|
179
|
+
* `stableId` parameter is reserved for P280 wiring — when present, the
|
|
180
|
+
* lookup tries the stable-id-indexed entry first, falling back to
|
|
181
|
+
* `sourceHash` on miss. v1 (this PR) never receives a non-undefined
|
|
182
|
+
* value here.
|
|
183
|
+
*/
|
|
184
|
+
export function lookupCacheEntry(totemDir, sourceHash, fingerprint, options = {}) {
|
|
185
|
+
if (options.force === true) {
|
|
186
|
+
return { entry: null, decision: 'cache_miss_force' };
|
|
187
|
+
}
|
|
188
|
+
if (process.env[DISABLE_ENV_VAR] === '1') {
|
|
189
|
+
// totem-context: intentional cleanup — emergency escape hatch reverts
|
|
190
|
+
// cache to no-op without throwing.
|
|
191
|
+
return { entry: null, decision: 'cache_miss_no_prior_record' };
|
|
192
|
+
}
|
|
193
|
+
const filePath = cacheEntryPath(totemDir, sourceHash);
|
|
194
|
+
if (!fs.existsSync(filePath)) {
|
|
195
|
+
return { entry: null, decision: 'cache_miss_no_prior_record' };
|
|
196
|
+
}
|
|
197
|
+
let raw;
|
|
198
|
+
try {
|
|
199
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
200
|
+
raw = JSON.parse(content);
|
|
201
|
+
// totem-context: intentional cleanup — a bad cache entry must downgrade to a miss, never propagate; the cache is a side-channel optimization, not a load-bearing data path.
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
// totem-context: intentional cleanup — a bad cache entry must downgrade to a miss, never propagate; the cache is a side-channel optimization, not a load-bearing data path.
|
|
205
|
+
void err;
|
|
206
|
+
return { entry: null, decision: 'cache_miss_no_prior_record' };
|
|
207
|
+
}
|
|
208
|
+
const parsed = CacheEntrySchema.safeParse(raw);
|
|
209
|
+
if (!parsed.success) {
|
|
210
|
+
return { entry: null, decision: 'cache_miss_no_prior_record' };
|
|
211
|
+
}
|
|
212
|
+
// CacheEntrySchema validates the discriminator only; downstream casts back
|
|
213
|
+
// to the rich CompileLessonResult union type. See COMPILE_LESSON_STATUS.
|
|
214
|
+
const entry = parsed.data;
|
|
215
|
+
if (entry.sourceHash !== sourceHash) {
|
|
216
|
+
// File was named by hash but its content disagrees — corrupted state,
|
|
217
|
+
// treat as miss. Defensive against manual cache edits.
|
|
218
|
+
return { entry: null, decision: 'cache_miss_source_changed' };
|
|
219
|
+
}
|
|
220
|
+
if (entry.fingerprint !== fingerprint) {
|
|
221
|
+
return { entry: null, decision: 'cache_miss_fingerprint_changed' };
|
|
222
|
+
}
|
|
223
|
+
return { entry, decision: 'cache_hit' };
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Persist a cache entry after a successful compile. Idempotent — writing
|
|
227
|
+
* the same `(sourceHash, fingerprint, output)` twice is a no-op-shaped
|
|
228
|
+
* overwrite. Fire-and-forget: I/O failures are surfaced via `onWarn` and
|
|
229
|
+
* never propagate (a failed cache write should not crash a compile).
|
|
230
|
+
*
|
|
231
|
+
* Returns `true` when the entry was successfully persisted, `false` on any
|
|
232
|
+
* I/O failure or when the cache is disabled via env var. Callers that need
|
|
233
|
+
* to track migration outcomes (e.g., `migrateFromCompiledRules`) inspect
|
|
234
|
+
* the return; callers that don't care can ignore it.
|
|
235
|
+
*/
|
|
236
|
+
export function writeCacheEntry(totemDir, entry, onWarn) {
|
|
237
|
+
if (process.env[DISABLE_ENV_VAR] === '1') {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
const filePath = cacheEntryPath(totemDir, entry.sourceHash);
|
|
242
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
243
|
+
fs.writeFileSync(filePath, JSON.stringify(entry, null, 2) + '\n', 'utf-8');
|
|
244
|
+
return true;
|
|
245
|
+
// totem-context: intentional cleanup — cache writes are fire-and-forget; a failed write degrades to "no cache entry exists" on next lookup, never blocks compile.
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
// totem-context: intentional cleanup — cache writes are fire-and-forget; a failed write degrades to "no cache entry exists" on next lookup, never blocks compile.
|
|
249
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
250
|
+
safeOnWarn(onWarn, `Compile cache write failed: ${msg}`);
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Construct a `CacheEntry` from the inputs of a fresh compile. Pure;
|
|
256
|
+
* callers persist via `writeCacheEntry`.
|
|
257
|
+
*/
|
|
258
|
+
export function buildCacheEntry(sourceHash, fingerprint, output) {
|
|
259
|
+
return {
|
|
260
|
+
sourceHash,
|
|
261
|
+
fingerprint,
|
|
262
|
+
output,
|
|
263
|
+
compiledAt: new Date().toISOString(),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* One-shot seed migration: walk an existing compiled-rules.json + lesson
|
|
268
|
+
* sources, materialize the cache entries that would have been produced
|
|
269
|
+
* if the cache had existed from day one. After this step, the first
|
|
270
|
+
* post-migration compile run produces 100% cache hits and `compiled-
|
|
271
|
+
* rules.json` byte-for-byte matches its prior state.
|
|
272
|
+
*
|
|
273
|
+
* Idempotent — running twice with the same inputs writes the same entries
|
|
274
|
+
* a second time (overwrite). Per the impl contract § Migration sequence.
|
|
275
|
+
*
|
|
276
|
+
* Heading + body are taken separately (rather than a single raw-source
|
|
277
|
+
* string) to enforce the canonical `composeLessonSourceForHash` call shape.
|
|
278
|
+
* If migration accepted a free-form `lessonSource` string, the migration
|
|
279
|
+
* and runtime hash paths could diverge (per GCA R2 critical on `#1983`).
|
|
280
|
+
*/
|
|
281
|
+
export function migrateFromCompiledRules(totemDir, fingerprint, inputs, onWarn) {
|
|
282
|
+
if (process.env[DISABLE_ENV_VAR] === '1') {
|
|
283
|
+
return { seeded: 0, skipped: inputs.length };
|
|
284
|
+
}
|
|
285
|
+
let seeded = 0;
|
|
286
|
+
let skipped = 0;
|
|
287
|
+
for (const input of inputs) {
|
|
288
|
+
try {
|
|
289
|
+
const sourceForHash = composeLessonSourceForHash(input.heading, input.body);
|
|
290
|
+
const sourceHash = computeLessonSourceHash(sourceForHash);
|
|
291
|
+
const entry = buildCacheEntry(sourceHash, fingerprint, input.output);
|
|
292
|
+
// writeCacheEntry returns false on I/O failure or when the cache is
|
|
293
|
+
// disabled. Both count as "skipped" — seeded reflects entries actually
|
|
294
|
+
// persisted to disk, matching the operator's mental model of the
|
|
295
|
+
// post-migration cache hit rate.
|
|
296
|
+
if (writeCacheEntry(totemDir, entry, onWarn)) {
|
|
297
|
+
seeded += 1;
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
skipped += 1;
|
|
301
|
+
}
|
|
302
|
+
// totem-context: intentional cleanup — migration is best-effort per-input; one bad lesson source must not abort seeding the rest. Failed inputs accumulate in `skipped` count and surface via safeOnWarn for operator visibility.
|
|
303
|
+
}
|
|
304
|
+
catch (err) {
|
|
305
|
+
// totem-context: intentional cleanup — migration is best-effort per-input; one bad lesson source must not abort seeding the rest. Failed inputs accumulate in `skipped` count and surface via safeOnWarn for operator visibility.
|
|
306
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
307
|
+
safeOnWarn(onWarn, `Compile cache seed skipped for ${input.lessonHash}: ${msg}`);
|
|
308
|
+
skipped += 1;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return { seeded, skipped };
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* List all cache-entry filenames currently on disk. Returns the file basenames
|
|
315
|
+
* (not full paths). Useful for `totem cache --prune-orphans` (out of scope for
|
|
316
|
+
* v1) and for test isolation cleanup.
|
|
317
|
+
*/
|
|
318
|
+
export function listCacheEntries(totemDir) {
|
|
319
|
+
const dir = path.join(totemDir, CACHE_DIR);
|
|
320
|
+
if (!fs.existsSync(dir)) {
|
|
321
|
+
return [];
|
|
322
|
+
}
|
|
323
|
+
return fs.readdirSync(dir).filter((name) => name.endsWith('.json'));
|
|
324
|
+
}
|
|
325
|
+
//# sourceMappingURL=compile-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile-cache.js","sourceRoot":"","sources":["../src/compile-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,uDAAuD;AAEvD,uEAAuE;AACvE,yEAAyE;AACzE,4EAA4E;AAC5E,oEAAoE;AACpE,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;AACvD,MAAM,2BAA2B,GAAG,EAAE,CAAC;AACvC,MAAM,eAAe,GAAG,6BAA6B,CAAC;AAEtD;;;;GAIG;AACH,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAExC;;;;;GAKG;AACH,SAAS,UAAU,CAAC,MAA2C,EAAE,OAAe;IAC9E,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO;IACjC,IAAI,CAAC;QACH,MAAM,CAAC,OAAO,CAAC,CAAC;QAChB,gKAAgK;IAClK,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,gKAAgK;QAChK,KAAK,GAAG,CAAC;IACX,CAAC;AACH,CAAC;AAED,uDAAuD;AAEvD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,gCAAgC,GAAG,CAAC,CAAC,kBAAkB,CAAC,QAAQ,EAAE;IACtE,CAAC;SACE,MAAM,CAAC;QACN,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC;QAC7B,2EAA2E;QAC3E,0EAA0E;QAC1E,uEAAuE;QACvE,uEAAuE;QACvE,qEAAqE;QACrE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;KAC5B,CAAC;SACD,WAAW,EAAE;IAChB,CAAC;SACE,MAAM,CAAC;QACN,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;QAC5B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;KACvB,CAAC;SACD,WAAW,EAAE;IAChB,CAAC;SACE,MAAM,CAAC;QACN,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;KAC5B,CAAC;SACD,WAAW,EAAE;IAChB,CAAC;SACE,MAAM,CAAC;QACN,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;KAC1B,CAAC;SACD,WAAW,EAAE;CACjB,CAAC,CAAC;AAEH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,MAAM,EAAE,gCAAgC;IACxC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;CACvB,CAAC,CAAC;AAkBH,uDAAuD;AAEvD;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,YAAoB;IAC1D,4EAA4E;IAC5E,8EAA8E;IAC9E,4EAA4E;IAC5E,6EAA6E;IAC7E,8EAA8E;IAC9E,4EAA4E;IAC5E,mEAAmE;IACnE,uEAAuE;IACvE,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACvD,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACtE,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,0BAA0B,CAAC,OAAe,EAAE,IAAY;IACtE,OAAO,GAAG,OAAO,KAAK,IAAI,EAAE,CAAC;AAC/B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,UAAkB;IACjE,gFAAgF;IAChF,qEAAqE;IACrE,8EAA8E;IAC9E,wEAAwE;IACxE,0EAA0E;IAC1E,qBAAqB;IACrB,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CACb,6EAA6E,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CACvH,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,2BAA2B,CAAC,OAAO,CAAC;IAC5E,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AAClD,CAAC;AAOD;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAAgB,EAChB,UAAkB,EAClB,WAAmB,EACnB,UAAkD,EAAE;IAEpD,IAAI,OAAO,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;QAC3B,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IACvD,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,KAAK,GAAG,EAAE,CAAC;QACzC,sEAAsE;QACtE,mCAAmC;QACnC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,4BAA4B,EAAE,CAAC;IACjE,CAAC;IAED,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IACtD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,4BAA4B,EAAE,CAAC;IACjE,CAAC;IAED,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACnD,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC1B,4KAA4K;IAC9K,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,4KAA4K;QAC5K,KAAK,GAAG,CAAC;QACT,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,4BAA4B,EAAE,CAAC;IACjE,CAAC;IAED,MAAM,MAAM,GAAG,gBAAgB,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAC/C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,4BAA4B,EAAE,CAAC;IACjE,CAAC;IAED,2EAA2E;IAC3E,yEAAyE;IACzE,MAAM,KAAK,GAAG,MAAM,CAAC,IAAkB,CAAC;IACxC,IAAI,KAAK,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;QACpC,sEAAsE;QACtE,uDAAuD;QACvD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,2BAA2B,EAAE,CAAC;IAChE,CAAC;IACD,IAAI,KAAK,CAAC,WAAW,KAAK,WAAW,EAAE,CAAC;QACtC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,gCAAgC,EAAE,CAAC;IACrE,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAC7B,QAAgB,EAChB,KAAiB,EACjB,MAA8B;IAE9B,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,KAAK,GAAG,EAAE,CAAC;QACzC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;QAC5D,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1D,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;QAC3E,OAAO,IAAI,CAAC;QACZ,kKAAkK;IACpK,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,kKAAkK;QAClK,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,UAAU,CAAC,MAAM,EAAE,+BAA+B,GAAG,EAAE,CAAC,CAAC;QACzD,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,UAAkB,EAClB,WAAmB,EACnB,MAA2B;IAE3B,OAAO;QACL,UAAU;QACV,WAAW;QACX,MAAM;QACN,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACrC,CAAC;AACJ,CAAC;AA2BD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,wBAAwB,CACtC,QAAgB,EAChB,WAAmB,EACnB,MAA4B,EAC5B,MAA8B;IAE9B,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,KAAK,GAAG,EAAE,CAAC;QACzC,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;IAC/C,CAAC;IAED,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,aAAa,GAAG,0BAA0B,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAC5E,MAAM,UAAU,GAAG,uBAAuB,CAAC,aAAa,CAAC,CAAC;YAC1D,MAAM,KAAK,GAAG,eAAe,CAAC,UAAU,EAAE,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;YACrE,oEAAoE;YACpE,uEAAuE;YACvE,iEAAiE;YACjE,iCAAiC;YACjC,IAAI,eAAe,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC;gBAC7C,MAAM,IAAI,CAAC,CAAC;YACd,CAAC;iBAAM,CAAC;gBACN,OAAO,IAAI,CAAC,CAAC;YACf,CAAC;YACD,kOAAkO;QACpO,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,kOAAkO;YAClO,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,UAAU,CAAC,MAAM,EAAE,kCAAkC,KAAK,CAAC,UAAU,KAAK,GAAG,EAAE,CAAC,CAAC;YACjF,OAAO,IAAI,CAAC,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC7B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAC3C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;AACtE,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the per-lesson compile cache (Proposal 281).
|
|
3
|
+
*
|
|
4
|
+
* Falsifying-metric test is `partial_mutation_invariant` — exercises the
|
|
5
|
+
* load-bearing claim that a +1-lesson PR produces a 1-row compiled-rules
|
|
6
|
+
* delta, not a 130-row rotation.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=compile-cache.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile-cache.test.d.ts","sourceRoot":"","sources":["../src/compile-cache.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|