@ttsc/metro 0.30.4 → 0.31.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 (62) hide show
  1. package/README.md +11 -6
  2. package/lib/core/SnapshotReadOperations.d.ts +119 -0
  3. package/lib/core/SnapshotReadOperations.js +3 -0
  4. package/lib/core/SnapshotReadOperations.js.map +1 -0
  5. package/lib/core/SnapshotReadOperations.mjs +2 -0
  6. package/lib/core/SnapshotReadOperations.mjs.map +1 -0
  7. package/lib/core/TtscMetroOptions.d.ts +119 -0
  8. package/lib/core/TtscMetroOptions.js +3 -0
  9. package/lib/core/TtscMetroOptions.js.map +1 -0
  10. package/lib/core/TtscMetroOptions.mjs +2 -0
  11. package/lib/core/TtscMetroOptions.mjs.map +1 -0
  12. package/lib/core/TtscMetroProjectView.d.ts +64 -0
  13. package/lib/core/TtscMetroProjectView.js +3 -0
  14. package/lib/core/TtscMetroProjectView.js.map +1 -0
  15. package/lib/core/TtscMetroProjectView.mjs +2 -0
  16. package/lib/core/TtscMetroProjectView.mjs.map +1 -0
  17. package/lib/core/UpstreamTransformer.d.ts +127 -0
  18. package/lib/core/UpstreamTransformer.js +3 -0
  19. package/lib/core/UpstreamTransformer.js.map +1 -0
  20. package/lib/core/UpstreamTransformer.mjs +2 -0
  21. package/lib/core/UpstreamTransformer.mjs.map +1 -0
  22. package/lib/core/fingerprint.d.ts +442 -97
  23. package/lib/core/fingerprint.js +838 -83
  24. package/lib/core/fingerprint.js.map +1 -1
  25. package/lib/core/fingerprint.mjs +839 -84
  26. package/lib/core/fingerprint.mjs.map +1 -1
  27. package/lib/core/options.d.ts +81 -58
  28. package/lib/core/options.js +81 -4
  29. package/lib/core/options.js.map +1 -1
  30. package/lib/core/options.mjs +81 -4
  31. package/lib/core/options.mjs.map +1 -1
  32. package/lib/core/remapAstLocations.d.ts +76 -0
  33. package/lib/core/remapAstLocations.js +205 -0
  34. package/lib/core/remapAstLocations.js.map +1 -0
  35. package/lib/core/remapAstLocations.mjs +203 -0
  36. package/lib/core/remapAstLocations.mjs.map +1 -0
  37. package/lib/core/upstream.d.ts +97 -27
  38. package/lib/core/upstream.js +109 -8
  39. package/lib/core/upstream.js.map +1 -1
  40. package/lib/core/upstream.mjs +109 -9
  41. package/lib/core/upstream.mjs.map +1 -1
  42. package/lib/index.d.ts +50 -2
  43. package/lib/index.js +88 -24
  44. package/lib/index.js.map +1 -1
  45. package/lib/index.mjs +89 -25
  46. package/lib/index.mjs.map +1 -1
  47. package/lib/transformer.d.ts +183 -6
  48. package/lib/transformer.js +209 -14
  49. package/lib/transformer.js.map +1 -1
  50. package/lib/transformer.mjs +211 -16
  51. package/lib/transformer.mjs.map +1 -1
  52. package/package.json +7 -18
  53. package/src/core/SnapshotReadOperations.ts +121 -0
  54. package/src/core/TtscMetroOptions.ts +127 -0
  55. package/src/core/TtscMetroProjectView.ts +74 -0
  56. package/src/core/UpstreamTransformer.ts +126 -0
  57. package/src/core/fingerprint.ts +987 -109
  58. package/src/core/options.ts +85 -62
  59. package/src/core/remapAstLocations.ts +219 -0
  60. package/src/core/upstream.ts +110 -26
  61. package/src/index.ts +99 -28
  62. package/src/transformer.ts +214 -17
@@ -1,60 +1,23 @@
1
- /**
2
- * Project fingerprint and reference-graph snapshot for `@ttsc/metro`.
3
- *
4
- * Metro's transform cache keys each file on its own content plus one static
5
- * transformer key computed once per run (`getCacheKey`, called on the main
6
- * process at `Transformer` construction). A ttsc transform's output can depend
7
- * on inputs Metro never keys: other project sources reached through type-only
8
- * edges, `node_modules` declarations, monorepo sibling sources, and the
9
- * tsconfig `extends` ancestry. This module folds all of them into the static
10
- * key so the cache key incorporates every input that can influence a
11
- * transform's output:
12
- *
13
- * - **Project walk.** Every input file under the fingerprint roots (Metro's
14
- * `projectRoot` plus the resolved tsconfig's directory when it lies outside),
15
- * hashed with the exact walk universe the `@ttsc/unplugin` transform core
16
- * validates its own cache against.
17
- * - **Recorded transform inputs.** The host-owned reference graph
18
- * (samchon/ttsc#718) reports each transform's derived inputs. Workers retain
19
- * them under `node_modules/.cache/ttsc-metro`, compare their generation state
20
- * with the exact main-process key baseline, and batch one durable write per
21
- * delivered module.
22
- *
23
- * Snapshot layout: one main file carrying a random epoch id plus per-worker
24
- * files with unique names, so concurrent workers never race a shared write.
25
- * `withTtsc` compacts worker files into the main file before its workers exist,
26
- * under a process-shared lock for builds using the same project cache. Readers
27
- * take the union of every file, reading the worker files strictly before the
28
- * main file: the compactor renames the merged main into place strictly before
29
- * deleting a worker file, so a worker file that disappears mid-read is always
30
- * already merged into the main the reader loads afterwards.
31
- *
32
- * Sound degradations, by design:
33
- *
34
- * - No readable snapshot (first run, wiped cache dir, unwritable filesystem)
35
- * folds a random nonce: that run shares no cache entries with any other run.
36
- * - A failed snapshot write persists the pending observation beside the snapshot
37
- * directory. While any such recovery document exists, readers fold a nonce;
38
- * successful compaction merges it and mints a fresh epoch.
39
- * - A recreated snapshot carries a fresh epoch id, so it can never alias a key
40
- * from an older epoch whose recorded set is unknown.
41
- * - A plugin-declared volatile output (non-file inputs; unrepresentable in any
42
- * file fingerprint) marks the snapshot volatile, which also folds a nonce
43
- * until a later run records the volatile declaration gone.
44
- * - A recorded input that disappears hashes as a stable `missing` marker, so
45
- * deletion and reappearance both move the key.
46
- * - A worker state that differs from the static key's run baseline taints the
47
- * observation; compaction rotates the epoch so even A -> B -> A cannot reuse
48
- * output stored under the earlier A key.
49
- */
50
- import { readProjectMembershipPolicy } from "@ttsc/unplugin/api";
51
- import type { TtscProjectDiscoveryFilesystem, TtscProjectTreeDiscoveryFilesystem, TtscWatchInput } from "@ttsc/unplugin/api";
1
+ import type { TtscProjectDiscoveryFilesystem, TtscProjectTreeDiscoveryFilesystem, TtscWatchInput, TtscWatchInputBaseline } from "@ttsc/unplugin/api";
2
+ import type { SnapshotReadOperations } from "./SnapshotReadOperations";
3
+ import type { TtscMetroProjectView } from "./TtscMetroProjectView";
4
+ /** Requested native codecs, separate from compiler-accessible entry listings. */
5
+ type NativeInputKind = keyof NonNullable<TtscWatchInputBaseline["nativePredicates"]>;
52
6
  /** Union of the snapshot state readable on disk. */
53
7
  interface SnapshotState {
8
+ /** Native version-one kinds to observe again for each recorded path. */
9
+ nativePredicates: Record<string, NativeInputKind[]>;
10
+ /** Paths whose compiler-visible immediate entry listings affect output. */
11
+ accessibleEntries: string[];
54
12
  /** Random epoch id minted when the main snapshot was created. */
55
13
  id: string;
56
14
  /** Absolute paths of every recorded derived transform input. */
57
15
  files: string[];
16
+ /**
17
+ * The recorded inputs that are plugin source directories, each also in
18
+ * `files`, whose state the static key carries (samchon/ttsc#1487).
19
+ */
20
+ trees: string[];
58
21
  /** Whether any recorded transform declared volatile output. */
59
22
  volatile: boolean;
60
23
  /** Whether a transform observed state different from its run's static key. */
@@ -66,52 +29,146 @@ interface SnapshotState {
66
29
  * from Metro's cache-key options, the transformer from each file's transform
67
30
  * options — all the same value in a real Metro run), else the working directory
68
31
  * Metro was launched from.
32
+ *
33
+ * @evidence contracts/common.md#principled-implementation
34
+ * Node path.resolve provides one agreed base for config preparation, static
35
+ * keying and worker recording. A nonempty projectRoot wins; absent or empty
36
+ * input uses cwd.
37
+ *
38
+ * @evidence contracts/common.md#clear-and-simple-design
39
+ * One helper defines the shared base fallback for preparation, keying and
40
+ * worker recording instead of giving each boundary its own anchor policy.
41
+ *
42
+ * @evidenceExclude contracts/performance.md#efficient-algorithms
43
+ * This operation delegates one path resolution and chooses a fallback; it
44
+ * owns no collection-processing strategy.
45
+ *
46
+ * @evidenceExclude contracts/performance.md#reuse-equivalent-work
47
+ * The base depends on caller input and current cwd; this pure mapping
48
+ * coordinates no shared computation.
49
+ *
50
+ * @evidenceExclude contracts/performance.md#bound-retention-and-release-resources
51
+ * It returns a directory string and owns no retained state or handle.
52
+ *
53
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts
54
+ * It computes a directory value without changing the filesystem, foreign
55
+ * APIs or consumer-specific behavior.
56
+ *
57
+ * @evidence contracts/portability.md#os-neutral-implementation
58
+ * Node path.resolve and process.cwd handle native path roots and separators
59
+ * on supported OSes. No URL conversion or process spawning occurs.
60
+ *
61
+ * @evidence contracts/common.md#meaningful-documentation
62
+ * The native JSDoc explains shared base ownership and the absent/empty
63
+ * projectRoot fallback. Checked against the documentation skill: separate
64
+ * paragraphs state the contract and why its nonobvious boundary matters;
65
+ * field comments retain their own useful facts.
69
66
  */
70
67
  export declare function resolveFingerprintBase(projectRoot: string | undefined): string;
71
68
  /**
72
- * The directories whose walk universes the fingerprint hashes: the base
73
- * directory, plus the resolved tsconfig's directory when the tsconfig is not
74
- * already inside the base walk (an explicit out-of-root `project`, or a
75
- * monorepo-root tsconfig discovered above the app). Matching the transform
76
- * core's own validation universe keeps the invariant simple: everything the
77
- * core treats as an input is fingerprinted by the walk, the recorded snapshot,
78
- * or both.
69
+ * The directories whose walk universes the fingerprint hashes. Implicit
70
+ * selection searches from the base upward, so a config at the base uses that
71
+ * walk and a config above it replaces the base walk with its own directory,
72
+ * which contains the base. An explicit project inside the base uses the base
73
+ * walk; one above the base replaces it; one elsewhere adds its directory. The
74
+ * separate per-file project view can select a nested config below the base.
75
+ * Matching the transform core's validation universe keeps the invariant simple:
76
+ * everything it treats as an input is fingerprinted by the walk, the recorded
77
+ * snapshot, or both.
78
+ *
79
+ * @evidence contracts/common.md#principled-implementation
80
+ * The shared project resolver owns tsconfig selection; lexical path
81
+ * containment is separate from program membership.
82
+ * Blank explicit options mean implicit discovery. An in-root config avoids a
83
+ * duplicate whole-tree walk; a config above the base walks its own directory,
84
+ * which contains the base, instead of hashing the base subtree twice; an
85
+ * unrelated out-of-root config adds its directory because it can supply
86
+ * transform inputs.
87
+ *
88
+ * @evidence contracts/common.md#clear-and-simple-design
89
+ * Project selection delegates to the shared resolver and containment to
90
+ * projectViewRoots, separating config ownership from walk-root choice.
91
+ *
92
+ * @evidenceExclude contracts/performance.md#efficient-algorithms
93
+ * It delegates shared project discovery and computes at most two roots;
94
+ * traversal cost belongs to that resolver and the fingerprint operation.
95
+ *
96
+ * @evidenceExclude contracts/performance.md#reuse-equivalent-work
97
+ * This mapping retains no discovery result between calls and coordinates
98
+ * no completed or in-flight computation.
99
+ *
100
+ * @evidenceExclude contracts/performance.md#bound-retention-and-release-resources
101
+ * Roots transfer to the caller; no retained state or handle is acquired.
102
+ *
103
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts
104
+ * No fixture-specific roots or patched filesystem APIs are used.
105
+ *
106
+ * @evidence contracts/portability.md#os-neutral-implementation
107
+ * Node resolve and dirname handle Windows drives and POSIX roots. Lexical
108
+ * containment compares complete path components with only volume-root case
109
+ * normalization; it does not merge distinct native names by OS assumption.
110
+ *
111
+ * @evidence contracts/common.md#meaningful-documentation
112
+ * The native JSDoc explains walk roots, implicit selection and why outside
113
+ * configs extend coverage. Checked against the documentation skill:
114
+ * separate paragraphs state the contract and why its nonobvious boundary
115
+ * matters; field comments retain their own useful facts.
79
116
  */
80
117
  export declare function fingerprintRoots(base: string, explicitProject: string | undefined): string[];
81
- /**
82
- * One project, and the membership policy that describes it.
83
- *
84
- * The recorder's question is whether the project walk already covers an input,
85
- * so it needs both the walk's roots and the policy that walk used, and it is
86
- * wrong exactly when those two describe different projects. Passing them
87
- * separately made that mismatch expressible — the policy for one project
88
- * alongside the root of another — and passing the policy alone made it
89
- * expressible in a quieter way still, since a recorder that resolved its own
90
- * could describe a different program than the walk hashed. Both halves travel
91
- * together so neither can be supplied without the other (samchon/ttsc#1316).
92
- */
93
- export interface TtscMetroProjectView {
94
- /** The base directory both fingerprint sides agree on. */
95
- readonly base: string;
96
- /** Config candidates observed while selecting this transform's project. */
97
- readonly discoveryInputs: readonly TtscWatchInput[];
98
- /** The caller's explicit `project`, if any. */
99
- readonly explicitProject: string | undefined;
100
- /** The membership policy resolved for that project. */
101
- readonly policy: ReturnType<typeof readProjectMembershipPolicy>;
102
- /** The policy used by the routed static walk. */
103
- readonly walkPolicy: ReturnType<typeof readProjectMembershipPolicy>;
104
- /** Lexical roots whose fingerprint uses this project's policy. */
105
- readonly roots: readonly string[];
106
- /** The exact config selected for this project. */
107
- readonly tsconfig: string;
108
- }
109
118
  /**
110
119
  * Resolve one transform's project view, once, for every watch input it reports.
111
120
  *
112
- * Exported because the recorder is asked once per input while the answer is a
113
- * property of the project, and validating the memo means stat-ing the whole
114
- * `extends` chain (samchon/ttsc#1316).
121
+ * Discovery, referenced-project selection and membership policy are resolved
122
+ * once per delivered module and shared by its recorder batch. The policy is
123
+ * read from source rather than trusting a cached mtime/size observation, which
124
+ * could miss same-stamp edits.
125
+ *
126
+ * @evidence contracts/common.md#principled-implementation
127
+ * Shared project discovery, reference selection and membership-policy APIs
128
+ * select the same config as the transform core, retaining positive and
129
+ * negative discovery predicates. Consulted routing configs retain current
130
+ * content, since their references or admission rules can change the owner.
131
+ * Compiler-option overlays are merged through that owner.
132
+ *
133
+ * @evidence contracts/common.md#clear-and-simple-design
134
+ * Selection records the predicates that chose the project, then one
135
+ * createProjectView helper assembles its policy and roots for every input
136
+ * in the module's recorder batch.
137
+ *
138
+ * @evidence contracts/performance.md#efficient-algorithms
139
+ * Discovery follows the ancestor candidates and reference graph; the
140
+ * resulting policy and roots are assembled once for the delivered module.
141
+ * Candidate predicates and consulted config content are captured once for
142
+ * this view, with a lexical-key map merging their overlapping paths in
143
+ * linear map operations. Capturing content performs the shared baseline's
144
+ * two observations and scales with the consulted config bytes.
145
+ *
146
+ * @evidence contracts/performance.md#reuse-equivalent-work
147
+ * The returned view is reused by the module's recorder batch; its selected
148
+ * tsconfig is passed to the transform options. Config sources are reread for
149
+ * a later module because matching mtime and size cannot establish continued
150
+ * equivalence.
151
+ *
152
+ * @evidenceExclude contracts/performance.md#bound-retention-and-release-resources
153
+ * Returned project data transfers to the caller. This operation retains no
154
+ * process-wide state or handle after discovery completes.
155
+ *
156
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts
157
+ * The view is resolved once per module and shared with its batch, avoiding
158
+ * independent recorder discovery without hardcoded project or test names.
159
+ *
160
+ * @evidence contracts/portability.md#os-neutral-implementation
161
+ * Node absolute paths and host discovery predicates retain native
162
+ * filesystem identity. The injected discovery filesystem is an explicit
163
+ * observation boundary, and path-relative helpers respect native roots; no
164
+ * shell command or global filesystem patch is used.
165
+ *
166
+ * @evidence contracts/common.md#meaningful-documentation
167
+ * The native JSDoc explains selection inputs, returned coupled view,
168
+ * discovery evidence and per-module reuse. Checked against the
169
+ * documentation skill: separate paragraphs state the contract and why its
170
+ * nonobvious boundary matters; field comments retain their own useful
171
+ * facts.
115
172
  */
116
173
  export declare function resolveProjectView(props: {
117
174
  compilerOptions?: Record<string, unknown>;
@@ -124,6 +181,63 @@ export declare function resolveProjectView(props: {
124
181
  * Compute the fingerprint `getCacheKey` folds into Metro's static transformer
125
182
  * key. Never throws: any failure degrades to a nonce, which soundly disables
126
183
  * cross-run cache reuse for this run instead of serving stale output.
184
+ *
185
+ * A successful call makes two complete observations of the routed projects,
186
+ * config sources and recorded inputs. Work scales with their files and bytes;
187
+ * it is paid at Metro's static-key boundary, not once per delivered module. The
188
+ * run baseline retains exact hashes, identities and discovery predicates so a
189
+ * worker can prove that its generation matches the state keyed here.
190
+ *
191
+ * @evidence contracts/common.md#principled-implementation
192
+ * The shared project walker/policy and input-state APIs supply real file,
193
+ * config, identity and recorded-input observations. Two complete
194
+ * observations must agree before Node sha256 publishes the key and immutable
195
+ * run baseline. Missing, volatile, incomplete or changing state yields
196
+ * randomBytes nonce, a required withdrawal of reuse rather than a successful
197
+ * stale fallback. Injected discovery changes observation only, not
198
+ * production results.
199
+ *
200
+ * @evidence contracts/common.md#clear-and-simple-design
201
+ * The entry point compares two observations and publishes their baseline;
202
+ * observeProjectFingerprint owns collecting the complete input universe.
203
+ * Nonreusable state has one nonce exit rather than partial-success keys.
204
+ *
205
+ * @evidence contracts/performance.md#efficient-algorithms
206
+ * Two observations scan project files and recorded inputs, with time driven
207
+ * by their bytes and config graph size. Overlapping lexical project walks
208
+ * are excluded by routed roots; maps deduplicate config sources. Exact
209
+ * content observations are necessary because timestamps do not prove state.
210
+ *
211
+ * @evidence contracts/performance.md#reuse-equivalent-work
212
+ * The static key shares transforms only for equal options, config contents,
213
+ * file hashes, identities, recorded generation state and requested exact
214
+ * compiler-accessible entry listings. Volatile, tainted
215
+ * or unstable observations take a fresh nonce; a run baseline must be
216
+ * immutable before workers can claim its coverage.
217
+ *
218
+ * @evidence contracts/performance.md#bound-retention-and-release-resources
219
+ * Observation maps are local to key computation and are released on return.
220
+ * The persisted run baseline is owned by snapshot preparation, which sweeps
221
+ * old baselines. A long-running worker can retain its loaded baseline until
222
+ * worker termination; it owns no open handle here.
223
+ *
224
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts
225
+ * Decision values come from the documented inputs and product protocol
226
+ * rather than expected test answers. No compensating path is introduced to
227
+ * make a known example pass.
228
+ *
229
+ * @evidence contracts/portability.md#os-neutral-implementation
230
+ * Node paths/fs preserve native roots and physical identities, including
231
+ * symlink-aware discovery. Sorted key representation is deterministic; file
232
+ * bytes and host predicates remain inputs. Filesystem failure withdraws
233
+ * reuse consistently instead of becoming an OS-specific successful key.
234
+ *
235
+ * @evidence contracts/common.md#meaningful-documentation
236
+ * The native JSDoc explains hex digest versus nonce, repeated observation,
237
+ * private baseline publication and failure policy. Checked against the
238
+ * documentation skill: separate paragraphs state the contract and why its
239
+ * nonobvious boundary matters; field comments retain their own useful
240
+ * facts.
127
241
  */
128
242
  export declare function computeProjectFingerprint(props: {
129
243
  compilerOptions?: Record<string, unknown>;
@@ -148,24 +262,208 @@ export declare function computeProjectFingerprint(props: {
148
262
  * token crosses bundle and process boundaries, so `getCacheKey` degrades to a
149
263
  * nonce even when neither snapshot location can persist the failure and an old
150
264
  * main later reappears.
265
+ *
266
+ * Compaction reads the accumulated worker and recovery documents and unions
267
+ * their recorded paths. Workers have unique names; completed observations are
268
+ * published before removal. Locked claimed files remain until a later retry.
269
+ * Old temporary files and run baselines are swept, but recorded input paths
270
+ * accumulate in the main snapshot and dead-owner election directories remain to
271
+ * prevent delayed contenders from moving a newer lock. Those populations are
272
+ * not bounded by a fixed retention budget in this implementation.
273
+ *
274
+ * @evidence contracts/common.md#principled-implementation
275
+ * Node filesystem APIs atomically publish complete snapshot documents and a
276
+ * token-owned compaction lock. Worker claims are merged before deletion;
277
+ * retained claims are identified in the main document to prevent replay.
278
+ * Definite dead-PID recovery preserves an election record; contention or
279
+ * failed persistence returns a private nonreusable token.
280
+ *
281
+ * @evidence contracts/common.md#clear-and-simple-design
282
+ * Preparation owns one lock-protected main rewrite. Helpers separate worker
283
+ * claims, document parsing, atomic publication and recovery storage, so
284
+ * concurrent readers can use the same persisted protocol.
285
+ *
286
+ * @evidence contracts/performance.md#efficient-algorithms
287
+ * Sets union recorded paths in time proportional to document paths, then
288
+ * sorting gives deterministic output. Each claimed worker is merged once;
289
+ * readdir and document parsing remain necessary to find pending publishers.
290
+ *
291
+ * @evidence contracts/performance.md#reuse-equivalent-work
292
+ * The main document records claimed worker names before removal, preventing
293
+ * locked files from replaying completed compaction. The epoch survives only
294
+ * trusted, untainted observations; corrupt or lost evidence rotates it.
295
+ *
296
+ * @evidence contracts/performance.md#bound-retention-and-release-resources
297
+ * Preparation owns its compaction lock and retires it in finally. Claimed
298
+ * workers and recovery files are removed after publication; failed removals
299
+ * stay represented for retry. Temporary files and baselines have age sweeps,
300
+ * but cumulative input paths and stale-owner election records have no fixed
301
+ * bound. Removing these without a generation protocol could erase evidence.
302
+ *
303
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts
304
+ * Retries serve locked-file/recovery contracts rather than masking an
305
+ * uncorrected successful-state assumption.
306
+ *
307
+ * @evidence contracts/portability.md#os-neutral-implementation
308
+ * Node join, mkdir, rename and rm implement native cache paths.
309
+ * Windows-locked files remain represented for a later removal attempt; only
310
+ * a definite process-query absence permits dead-owner recovery. No shell
311
+ * process checks or patched fs methods are used.
312
+ *
313
+ * @evidence contracts/common.md#meaningful-documentation
314
+ * The native JSDoc explains new-run preparation, contention, epoch
315
+ * rotation, recovery storage and nonreusable tokens. Checked against the
316
+ * documentation skill: separate paragraphs state the contract and why its
317
+ * nonobvious boundary matters; field comments retain their own useful
318
+ * facts.
151
319
  */
152
320
  export declare function prepareSnapshot(projectRoot: string | undefined): string;
153
321
  /**
154
322
  * Read the unioned snapshot state, or `undefined` when the main snapshot is
155
- * missing or any snapshot file is corrupt (a torn or foreign write means the
156
- * recorded set cannot be trusted, so the caller degrades to a nonce).
323
+ * missing, any snapshot file is corrupt (a torn or foreign write means the
324
+ * recorded set cannot be trusted, so the caller degrades to a nonce), or a
325
+ * concurrent compaction keeps renaming worker files so the listing never
326
+ * settles or a compaction lock stays held.
327
+ *
328
+ * `operations` is the read boundary (existence, listing, file text); the
329
+ * default reads the real filesystem, and a test passes its own to interleave a
330
+ * compaction with a listing. A pass is retried at most
331
+ * {@link SNAPSHOT_LISTING_ATTEMPTS} (8) times with a 5 ms pause between passes,
332
+ * so a held lock costs a call at most about 40 ms before the state is reported
333
+ * untrusted; the retry is bounded and holds no resource between passes.
334
+ *
335
+ * The result contains sorted absolute file and tree paths, the epoch identity,
336
+ * and tainted/volatile flags. It reads persisted evidence without revalidating
337
+ * current file bytes; the fingerprint operation owns that validation. Reading
338
+ * costs the number and size of outstanding documents plus their unioned paths.
339
+ *
340
+ * @evidence contracts/common.md#principled-implementation
341
+ * Strict snapshot parsing and Set union read worker documents before the
342
+ * main publication, retaining file/tree/volatile/tainted state without
343
+ * replaying already compacted claims. This is the owned persisted-input
344
+ * protocol, not a separate compiler dependency model.
345
+ * A pass is accepted only when no compaction lock existed before or after
346
+ * it and the main file is unchanged, because a listing concurrent with a
347
+ * rename can return the renamed entry under neither name.
348
+ * The read boundary is an optional parameter, so the interleaving can be
349
+ * driven deterministically while production reads the real filesystem.
350
+ *
351
+ * @evidence contracts/common.md#clear-and-simple-design
352
+ * This reader validates persisted documents and unions their paths without
353
+ * recomputing file state. The fingerprint owner handles live validation;
354
+ * both reader and compactor share uncompacted-worker classification.
355
+ *
356
+ * @evidence contracts/performance.md#efficient-algorithms
357
+ * Reading costs document bytes plus Set insertion and final path sorting.
358
+ * The compacted-name Set avoids replay and avoids a linear search per worker.
359
+ *
360
+ * @evidenceExclude contracts/performance.md#reuse-equivalent-work
361
+ * This read must observe new worker publications; it coordinates no cache
362
+ * between calls. Cross-run reuse belongs to computeProjectFingerprint.
363
+ *
364
+ * @evidenceExclude contracts/performance.md#bound-retention-and-release-resources
365
+ * Returned snapshot data transfers to the caller. This reader retains no
366
+ * state or handle after synchronous reads finish.
367
+ *
368
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts
369
+ * Unknown, corrupt or recovery-pending state returns undefined, never an
370
+ * invented complete empty set.
371
+ *
372
+ * @evidence contracts/portability.md#os-neutral-implementation
373
+ * Node path and fs read native snapshot locations. Locked or unreadable
374
+ * directory/documents invalidate the proof on all supported OSes; sorted
375
+ * lexical paths remain values, not shell arguments.
376
+ *
377
+ * @evidence contracts/common.md#meaningful-documentation
378
+ * The native JSDoc explains unioned snapshot fields, read ordering and the
379
+ * undefined result for untrusted state. Checked against the documentation
380
+ * skill: separate paragraphs state the contract and why its nonobvious
381
+ * boundary matters; field comments retain their own useful facts.
157
382
  */
158
- export declare function readSnapshotState(base: string): SnapshotState | undefined;
383
+ export declare function readSnapshotState(base: string, operations?: SnapshotReadOperations): SnapshotState | undefined;
159
384
  /**
160
385
  * Recorder held by each Metro worker. It persists every derived watch input and
161
386
  * any volatile declaration, compares compiler-generation evidence with the
162
387
  * matching main-process run baseline, and marks any temporal mismatch tainted.
163
388
  * A clean transform also writes a document so it can clear a volatile
164
- * declaration from an earlier run. One cumulative document is flushed per
165
- * delivered module; the unique name makes worker writes race-free, and
166
- * `withTtsc` compacts the files on the next run.
389
+ * declaration from an earlier run. The first delivery, a delivery that adds a
390
+ * path, and every delivery once the state is tainted flush one cumulative
391
+ * document; the unique name makes worker writes race-free, and `withTtsc`
392
+ * compacts the files on the next run.
393
+ *
394
+ * `record` accepts one lexical path without generation evidence; `recordMany`
395
+ * accepts a module's generation evidence; `recordVolatile` withdraws the file
396
+ * proof for non-file inputs. Without a run identity every input is retained;
397
+ * production passes the identity whose immutable baseline was keyed in the main
398
+ * process. An unknown or mismatching baseline marks the snapshot tainted, and
399
+ * since `record` carries no evidence, a run identity always reads it as a
400
+ * mismatch. Listing predicates retain their paths for the next run's key
401
+ * observation; native kind requests survive the same persistence path without
402
+ * becoming compiler listings or gaining watch scope. The worker never adds a
403
+ * new disk read to its earlier immutable baseline.
404
+ *
405
+ * Sets and baseline maps live for the recorder's worker lifetime and grow with
406
+ * observed projects and distinct inputs. A flushing delivery serializes the
407
+ * cumulative recorded set once, not once per input, but a tainted state flushes
408
+ * on every delivery, so its cost grows with the recorded set for each module. A
409
+ * failed write remains dirty for retry and tries recovery storage. If both
410
+ * stores fail, a reusable run throws rather than publishing output backed by
411
+ * lost evidence.
412
+ *
413
+ * @evidence contracts/common.md#principled-implementation
414
+ * One worker closure retains derived inputs and compares their
415
+ * compiler-generation evidence with its immutable run baseline through
416
+ * shared validation APIs. Static coverage is omitted only after a matching
417
+ * proof; unknown/volatile inputs persist taint.
418
+ * Listing and plugin-tree paths stay in the retained file universe even
419
+ * when a static proof already covers them, so optional payloads survive
420
+ * subsequent compaction and key capture.
421
+ * A first or changed module batch publishes one cumulative atomic document,
422
+ * with recovery storage and AggregateError when a reusable generation cannot
423
+ * persist either record.
424
+ *
425
+ * @evidence contracts/common.md#clear-and-simple-design
426
+ * One worker closure owns recorder state; shared recordMany owns evidence
427
+ * comparison and batch publication for single and multiple inputs. flush
428
+ * owns durability, so volatile observations use the same failure policy.
429
+ *
430
+ * @evidence contracts/performance.md#efficient-algorithms
431
+ * Sets insert distinct inputs and a cached static-input Set checks coverage
432
+ * without searching the whole baseline. A flushing module serializes the
433
+ * cumulative paths once, costing their count and encoded bytes rather than
434
+ * one cumulative serialization for every input. A tainted state flushes on
435
+ * every module, so that cost recurs per delivery until the worker ends.
436
+ *
437
+ * @evidence contracts/performance.md#reuse-equivalent-work
438
+ * Each base loads its immutable run baseline once and shares it across
439
+ * module batches. Only matching generation evidence authorizes omission of
440
+ * static paths; missing or differing evidence records taint instead.
441
+ *
442
+ * @evidence contracts/performance.md#bound-retention-and-release-resources
443
+ * The worker owns maps of bases, baseline bytes and distinct recorded paths
444
+ * until termination. Population grows with projects and inputs observed in
445
+ * that worker; there is no fixed eviction budget. Persisted files transfer
446
+ * to the next preparation's compactor. No native handle remains open.
447
+ *
448
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts
449
+ * The legacy no-run handshake records all paths without claiming static
450
+ * coverage.
451
+ *
452
+ * @evidence contracts/portability.md#os-neutral-implementation
453
+ * Node resolve/join and atomic rename use native paths; unique PID/random
454
+ * worker names separate publishers. Windows-locked claims remain readable
455
+ * until later compaction, and persistence failures follow the same reuse
456
+ * withdrawal contract. No foreign filesystem or process method is replaced.
457
+ *
458
+ * @evidence contracts/common.md#meaningful-documentation
459
+ * The native JSDoc explains record, batch and volatile operations,
460
+ * run-baseline matching, cumulative persistence and failure ownership.
461
+ * Checked against the documentation skill: separate paragraphs state the
462
+ * contract and why its nonobvious boundary matters; field comments retain
463
+ * their own useful facts.
167
464
  */
168
465
  export declare function createSnapshotRecorder(runId?: string): {
466
+ /** Record one lexical input without claiming compiler-generation evidence. */
169
467
  record: (props: {
170
468
  input: string;
171
469
  /**
@@ -185,18 +483,65 @@ export declare function createSnapshotRecorder(runId?: string): {
185
483
  */
186
484
  project: TtscMetroProjectView;
187
485
  }) => void;
486
+ /** Record one module's input evidence and publish its cumulative snapshot. */
188
487
  recordMany: (props: {
189
488
  inputs: readonly TtscWatchInput[];
190
489
  project: TtscMetroProjectView;
191
490
  }) => void;
491
+ /** Persist that a project's output depends on inputs a file key cannot prove. */
192
492
  recordVolatile: (props: {
193
493
  project: TtscMetroProjectView;
194
494
  }) => void;
195
495
  };
196
496
  /**
197
- * JSON-serialise with object keys sorted recursively, so two semantically equal
198
- * records always hash to the same fingerprint regardless of property order.
497
+ * Represent acyclic JSON-like records with recursively sorted keys for hashing.
498
+ * Arrays preserve order and JSON primitives use JSON escaping. This is an
499
+ * internal key representation, not JSON for interchange.
500
+ *
501
+ * Undefined uses an unquoted marker and sparse array slots use a separate
502
+ * unquoted hole marker, preserving cardinality and the distinction from null
503
+ * and literal strings. Non-finite numbers, functions, symbols, BigInt and
504
+ * cyclic structures are unsupported and throw rather than supplying a key.
199
505
  * Shared with the transformer's option digest.
506
+ *
507
+ * @evidence contracts/common.md#principled-implementation
508
+ * Recursive Object.entries sorting and JSON primitive escaping provide a
509
+ * stable internal key representation, not another transport parser. The
510
+ * undefined and hole markers cannot collide with quoted string values.
511
+ * Array.from visits each slot, preserving length and explicit absence.
512
+ * Unsupported primitive values fail instead of aliasing null or absence.
513
+ *
514
+ * @evidence contracts/common.md#clear-and-simple-design
515
+ * One recursive encoder owns array, record and primitive representations;
516
+ * sorted record entries keep ordering policy in that one boundary.
517
+ *
518
+ * @evidence contracts/performance.md#efficient-algorithms
519
+ * Each visited value is encoded once. Each record sorts its own keys in
520
+ * O(k log k), and encoding space follows output bytes plus recursion depth.
521
+ * Sorting is necessary for insertion-order-independent record keys.
522
+ *
523
+ * @evidenceExclude contracts/performance.md#reuse-equivalent-work
524
+ * This encoder accepts potentially mutable caller records and coordinates
525
+ * no cross-call computation; consumers own whether encoded values can recur.
526
+ *
527
+ * @evidenceExclude contracts/performance.md#bound-retention-and-release-resources
528
+ * The encoder returns its owned string and retains no caller data or handle
529
+ * after return; temporary recursion state is computation space.
530
+ *
531
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts
532
+ * Absence is represented at its owning serializer rather than patched in
533
+ * individual key consumers. Unsupported primitives fail explicitly.
534
+ *
535
+ * @evidenceExclude contracts/portability.md#os-neutral-implementation
536
+ * This operation sorts and encodes caller values for a key; it has no
537
+ * native path, filesystem or process boundary.
538
+ *
539
+ * @evidence contracts/common.md#meaningful-documentation
540
+ * The native JSDoc explains sorted keys, the acyclic value domain,
541
+ * unsupported JSON interchange and explicit absence markers. Checked
542
+ * against the documentation skill: separate paragraphs state the contract
543
+ * and why its nonobvious boundary matters; field comments retain their own
544
+ * useful facts.
200
545
  */
201
546
  export declare function stableStringify(value: unknown): string;
202
547
  export {};