@prisma-next/migration-tools 0.5.0-dev.62 → 0.5.0-dev.63

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 (67) hide show
  1. package/dist/{constants-BQEHsaEx.mjs → constants-B87kJAGj.mjs} +1 -1
  2. package/dist/{constants-BQEHsaEx.mjs.map → constants-B87kJAGj.mjs.map} +1 -1
  3. package/dist/{errors-CfmjBeK0.mjs → errors-DQsXvidG.mjs} +22 -2
  4. package/dist/errors-DQsXvidG.mjs.map +1 -0
  5. package/dist/exports/constants.mjs +1 -1
  6. package/dist/exports/errors.d.mts.map +1 -1
  7. package/dist/exports/errors.mjs +1 -1
  8. package/dist/exports/graph.d.mts +1 -1
  9. package/dist/exports/hash.d.mts +3 -3
  10. package/dist/exports/hash.d.mts.map +1 -1
  11. package/dist/exports/hash.mjs +1 -1
  12. package/dist/exports/invariants.d.mts +1 -1
  13. package/dist/exports/invariants.mjs +2 -2
  14. package/dist/exports/io.d.mts +40 -5
  15. package/dist/exports/io.d.mts.map +1 -1
  16. package/dist/exports/io.mjs +4 -162
  17. package/dist/exports/metadata.d.mts +1 -1
  18. package/dist/exports/migration-graph.d.mts +3 -3
  19. package/dist/exports/migration-graph.d.mts.map +1 -1
  20. package/dist/exports/migration-graph.mjs +2 -2
  21. package/dist/exports/migration-graph.mjs.map +1 -1
  22. package/dist/exports/migration.d.mts +3 -3
  23. package/dist/exports/migration.d.mts.map +1 -1
  24. package/dist/exports/migration.mjs +4 -4
  25. package/dist/exports/package.d.mts +3 -2
  26. package/dist/exports/refs.mjs +1 -1
  27. package/dist/exports/spaces.d.mts +447 -0
  28. package/dist/exports/spaces.d.mts.map +1 -0
  29. package/dist/exports/spaces.mjs +433 -0
  30. package/dist/exports/spaces.mjs.map +1 -0
  31. package/dist/{graph-BHPv-9Gl.d.mts → graph-Czaj8O2q.d.mts} +1 -1
  32. package/dist/{graph-BHPv-9Gl.d.mts.map → graph-Czaj8O2q.d.mts.map} +1 -1
  33. package/dist/{hash-BARZdVgW.mjs → hash-G0bAfIGh.mjs} +2 -2
  34. package/dist/hash-G0bAfIGh.mjs.map +1 -0
  35. package/dist/{invariants-30VA65sB.mjs → invariants-4Avb_Yhy.mjs} +2 -2
  36. package/dist/{invariants-30VA65sB.mjs.map → invariants-4Avb_Yhy.mjs.map} +1 -1
  37. package/dist/io-CDJaWGbt.mjs +207 -0
  38. package/dist/io-CDJaWGbt.mjs.map +1 -0
  39. package/dist/metadata-CSjwljJx.d.mts +2 -0
  40. package/dist/{op-schema-DZKFua46.mjs → op-schema-BiF1ZYqH.mjs} +1 -1
  41. package/dist/{op-schema-DZKFua46.mjs.map → op-schema-BiF1ZYqH.mjs.map} +1 -1
  42. package/dist/package-B3Yl6DTr.d.mts +21 -0
  43. package/dist/package-B3Yl6DTr.d.mts.map +1 -0
  44. package/package.json +8 -4
  45. package/src/concatenate-space-apply-inputs.ts +90 -0
  46. package/src/detect-space-contract-drift.ts +95 -0
  47. package/src/emit-pinned-space-artefacts.ts +89 -0
  48. package/src/errors.ts +35 -0
  49. package/src/exports/io.ts +1 -0
  50. package/src/exports/package.ts +2 -1
  51. package/src/exports/spaces.ts +36 -0
  52. package/src/hash.ts +2 -2
  53. package/src/io.ts +71 -16
  54. package/src/metadata.ts +1 -41
  55. package/src/migration-graph.ts +2 -2
  56. package/src/package.ts +14 -11
  57. package/src/plan-all-spaces.ts +80 -0
  58. package/src/read-pinned-contract-hash.ts +77 -0
  59. package/src/space-layout.ts +55 -0
  60. package/src/verify-contract-spaces.ts +276 -0
  61. package/dist/errors-CfmjBeK0.mjs.map +0 -1
  62. package/dist/exports/io.mjs.map +0 -1
  63. package/dist/hash-BARZdVgW.mjs.map +0 -1
  64. package/dist/metadata-BP1cmU7Z.d.mts +0 -50
  65. package/dist/metadata-BP1cmU7Z.d.mts.map +0 -1
  66. package/dist/package-5HCCg0z-.d.mts +0 -21
  67. package/dist/package-5HCCg0z-.d.mts.map +0 -1
@@ -0,0 +1,447 @@
1
+ import { APP_SPACE_ID } from "@prisma-next/framework-components/control";
2
+
3
+ //#region src/concatenate-space-apply-inputs.d.ts
4
+
5
+ /**
6
+ * Per-space input the runner consumes when applying a migration.
7
+ *
8
+ * The shape is target-agnostic: callers (today the SQL family; later
9
+ * any other family) bind `TOp` to their own per-target operation type
10
+ * (e.g. `SqlMigrationPlanOperation<TTargetDetails>` for the SQL family)
11
+ * and the helper preserves it through the concatenation.
12
+ *
13
+ * - `migrationDirectory` is the on-disk migration directory for the
14
+ * space — `<projectRoot>/migrations` for `'app'` and
15
+ * `<projectRoot>/migrations/<space-id>` for an extension space.
16
+ * - `currentMarkerHash` and `currentMarkerInvariants` are the values
17
+ * read from the `prisma_contract.marker` row keyed by `space = <space-id>`
18
+ * (T1.1). `null` hash = no marker row yet.
19
+ * - `path` is the per-space operation list resolved from
20
+ * `findPathWithDecision(currentMarker, ref.hash, effectiveRequired)`
21
+ * per ADR 208, materialised against the on-disk migration packages.
22
+ *
23
+ * @see specs/framework-mechanism.spec.md § 4 — Runner.
24
+ */
25
+ interface SpaceApplyInput<TOp> {
26
+ readonly spaceId: string;
27
+ readonly migrationDirectory: string;
28
+ readonly currentMarkerHash: string | null;
29
+ readonly currentMarkerInvariants: readonly string[];
30
+ readonly path: readonly TOp[];
31
+ }
32
+ /**
33
+ * Order a set of per-space apply inputs into the canonical cross-space
34
+ * sequence the runner applies under a single transaction.
35
+ *
36
+ * Cross-space ordering convention (sub-spec § 4):
37
+ *
38
+ * 1. **Extension spaces first**, alphabetically by `spaceId`.
39
+ * 2. **App space last** — only one `'app'` entry expected, at most.
40
+ *
41
+ * Rationale: extensions install their own structural objects (types,
42
+ * functions, helper tables) before the app's structural ops reference
43
+ * them. Putting app-space last lets app-space ops freely depend on any
44
+ * extension-space declaration in the same transaction.
45
+ *
46
+ * Determinism (NFR6): the output order is independent of the input
47
+ * order, so two callers with the same set of `extensionPacks` produce
48
+ * identical apply sequences.
49
+ *
50
+ * Atomicity: rejects duplicate `spaceId`s with
51
+ * `MIGRATION.DUPLICATE_SPACE_ID` before producing any output. This
52
+ * mirrors {@link import('./plan-all-spaces').planAllSpaces} so the
53
+ * planner-side and runner-side helpers reject malformed inputs the same
54
+ * way (callers don't need a separate dedup pass).
55
+ *
56
+ * Synchronous, pure, no I/O: callers resolve marker rows and `path`
57
+ * before invoking this helper. The actual DB application — driving the
58
+ * transaction, committing marker writes, recording the per-space marker
59
+ * rows — happens at the SQL-family consumption site (per the
60
+ * helper-location convention from R3).
61
+ */
62
+ declare function concatenateSpaceApplyInputs<TOp>(inputs: readonly SpaceApplyInput<TOp>[]): readonly SpaceApplyInput<TOp>[];
63
+ //#endregion
64
+ //#region src/detect-space-contract-drift.d.ts
65
+ /**
66
+ * Inputs for {@link detectSpaceContractDrift}.
67
+ *
68
+ * Both hashes are produced by the caller (the SQL-family wiring at the
69
+ * consumption site) using the canonical contract hashing pipeline.
70
+ * Keeping the helper pure lets `migration-tools` stay framework-neutral
71
+ * — the SQL family already speaks `Contract<SqlStorage>`, the Mongo
72
+ * family speaks its own contract type, and both reduce to a hash string
73
+ * before drift detection runs.
74
+ *
75
+ * `pinnedHash` is `null` when no pinned `contract.json` exists yet for
76
+ * the space (the descriptor declares an extension that has never been
77
+ * emitted into the user's repo). That's the "first emit" case — no
78
+ * drift to surface; the migrate emit will create the pinned files.
79
+ *
80
+ * @see specs/framework-mechanism.spec.md § 3 — Drift detection (T1.9).
81
+ */
82
+ interface DetectSpaceContractDriftInputs {
83
+ readonly descriptorHash: string;
84
+ readonly pinnedHash: string | null;
85
+ }
86
+ /**
87
+ * Result discriminant for {@link detectSpaceContractDrift}.
88
+ *
89
+ * - `noDrift`: descriptor hash and pinned hash agree byte-for-byte.
90
+ * The migrate emit can proceed with no warning.
91
+ * - `firstEmit`: no pinned `contract.json` on disk yet. The extension
92
+ * was just added to `extensionPacks`; this run will create the
93
+ * pinned files. No warning either — the user's intent is to install
94
+ * the extension, not to "drift" from a state they haven't pinned.
95
+ * - `drift`: descriptor hash differs from pinned hash. The caller
96
+ * surfaces a non-fatal warning naming the extension and the
97
+ * diff direction (descriptor → pinned). The migrate emit proceeds
98
+ * normally so the bump is materialised this run; the warning just
99
+ * confirms the bump is being captured.
100
+ *
101
+ * `spaceId`, `descriptorHash`, and `pinnedHash` are threaded through
102
+ * verbatim so the caller (logger / TerminalUI / strict-mode envelope)
103
+ * has everything it needs to format the warning message without
104
+ * re-reading the descriptor or the pinned file.
105
+ */
106
+ type SpaceContractDriftResult = {
107
+ readonly kind: 'noDrift' | 'firstEmit' | 'drift';
108
+ readonly spaceId: string;
109
+ readonly descriptorHash: string;
110
+ readonly pinnedHash: string | null;
111
+ };
112
+ /**
113
+ * Pure drift-detection primitive for a single contract space.
114
+ *
115
+ * Runs once per loaded extension space, just before computing the
116
+ * `priorContract` that feeds {@link import('./plan-all-spaces').planAllSpaces}.
117
+ * Hash equality is byte-for-byte (no normalisation) — both sides are
118
+ * already canonical hashes produced by the same pipeline, so any
119
+ * difference is meaningful drift.
120
+ *
121
+ * Synchronous, pure, no I/O. The caller (SQL family in M2 R1) reads
122
+ * the pinned `contract.json` and computes its hash, then invokes this
123
+ * helper alongside the descriptor's `headRef.hash`. Composes naturally
124
+ * with {@link import('./read-pinned-contract-hash').readPinnedContractHash}
125
+ * which provides the read-side primitive.
126
+ *
127
+ * @see specs/framework-mechanism.spec.md § 3 — Drift detection (T1.9).
128
+ * @see specs/framework-mechanism.spec.md AM7 — drift warning surfaces
129
+ * the extension name and the diff direction.
130
+ */
131
+ declare function detectSpaceContractDrift(spaceId: string, inputs: DetectSpaceContractDriftInputs): SpaceContractDriftResult;
132
+ //#endregion
133
+ //#region src/emit-pinned-space-artefacts.d.ts
134
+ /**
135
+ * Pinned head reference for a contract space — `(hash, invariants)`.
136
+ * Mirrors {@link import('./refs').RefEntry} but is redeclared locally so
137
+ * callers can construct the input without depending on the refs module.
138
+ */
139
+ interface PinnedSpaceHeadRef {
140
+ readonly hash: string;
141
+ readonly invariants: readonly string[];
142
+ }
143
+ /**
144
+ * Inputs for {@link emitPinnedSpaceArtefacts}.
145
+ *
146
+ * - `contract` is the canonical contract value the framework just emitted
147
+ * for the space; it is serialised through {@link canonicalizeJson}, so
148
+ * it must be a JSON-compatible value (objects / arrays / primitives).
149
+ * Typed as `unknown` rather than the SQL-family `Contract<SqlStorage>`
150
+ * to keep `migration-tools` framework-neutral; SQL-family callers pass
151
+ * their typed value through unchanged.
152
+ *
153
+ * - `contractDts` is the pre-rendered `.d.ts` text. Rendering happens in
154
+ * the SQL family (which owns the codec / typemap input the renderer
155
+ * needs), so this helper accepts the text verbatim and writes it out
156
+ * without further transformation.
157
+ *
158
+ * - `headRef` is the pinned head reference for the space.
159
+ * `invariants` are sorted alphabetically before serialisation so two
160
+ * callers passing the same set in different orders produce
161
+ * byte-identical `refs/head.json`.
162
+ */
163
+ interface PinnedSpaceArtefactInputs {
164
+ readonly contract: unknown;
165
+ readonly contractDts: string;
166
+ readonly headRef: PinnedSpaceHeadRef;
167
+ }
168
+ /**
169
+ * Emit the pinned per-space artefacts (`contract.json`, `contract.d.ts`,
170
+ * `refs/head.json`) under `<projectMigrationsDir>/<spaceId>/`.
171
+ *
172
+ * Always-overwrite: the framework owns these files; running `migrate`
173
+ * twice with the same inputs is a no-op observably (idempotent), but the
174
+ * helper does not check pre-existing contents — re-emit always wins.
175
+ *
176
+ * Path layout matches the convention in
177
+ * [`spaceMigrationDirectory`](./space-layout.ts), with two restrictions
178
+ * specific to pinned artefacts:
179
+ *
180
+ * - Rejects the app space (`spaceId === APP_SPACE_ID`): the app space's
181
+ * canonical `contract.json` lives at the project root, not under
182
+ * `migrations/`. Callers that want to emit it use the app-space
183
+ * contract emit pipeline.
184
+ * - Validates `spaceId` against `[a-z][a-z0-9_-]{0,63}` via
185
+ * {@link assertValidSpaceId} for the same filesystem-safety reasons.
186
+ *
187
+ * The migrations directory and space subdirectory are created if they
188
+ * do not yet exist (`mkdir { recursive: true }`).
189
+ *
190
+ * @see specs/framework-mechanism.spec.md § 3 — Pinned artefact emission (T1.8).
191
+ */
192
+ declare function emitPinnedSpaceArtefacts(projectMigrationsDir: string, spaceId: string, inputs: PinnedSpaceArtefactInputs): Promise<void>;
193
+ //#endregion
194
+ //#region src/plan-all-spaces.d.ts
195
+ /**
196
+ * Per-space input for {@link planAllSpaces}. One entry per loaded
197
+ * contract space (the application's `'app'` plus each extension that
198
+ * exposes a `contractSpace`).
199
+ *
200
+ * - `priorContract` is `null` for a space that has never been emitted
201
+ * (no `migrations/<space-id>/contract.json` on disk yet); otherwise it
202
+ * is the canonical contract value pinned for that space.
203
+ * - `newContract` is the canonical contract value the planner is about
204
+ * to emit for that space — for app-space, the just-emitted root
205
+ * `contract.json`; for an extension space, the descriptor's
206
+ * `contractSpace.contractJson`.
207
+ *
208
+ * @see specs/framework-mechanism.spec.md § 3.
209
+ */
210
+ interface SpacePlanInput<TContract> {
211
+ readonly spaceId: string;
212
+ readonly priorContract: TContract | null;
213
+ readonly newContract: TContract;
214
+ }
215
+ interface SpacePlanOutput<TPackage> {
216
+ readonly spaceId: string;
217
+ readonly migrationPackages: readonly TPackage[];
218
+ }
219
+ /**
220
+ * Iterate the per-space planner across a set of loaded contract spaces
221
+ * and return a deterministic shape regardless of declaration order.
222
+ *
223
+ * Behaviour:
224
+ *
225
+ * - The output is sorted alphabetically by `spaceId` (AM3). Two callers
226
+ * passing the same set of inputs in different orders observe
227
+ * byte-identical outputs.
228
+ * - The per-space planner (`planSpace`) is called exactly once per
229
+ * input, in alphabetical-by-spaceId order. Its return value is
230
+ * attached to the corresponding output entry verbatim.
231
+ * - Duplicate `spaceId`s in the input array throw
232
+ * `MIGRATION.DUPLICATE_SPACE_ID` before any `planSpace` call runs,
233
+ * keeping the planner pure when the input is malformed.
234
+ *
235
+ * The signature is generic over `TContract` and `TPackage` because the
236
+ * shape is framework-neutral (SQL family today, Mongo family
237
+ * eventually). Callers wire in whatever contract value and migration
238
+ * package shape their family already speaks.
239
+ *
240
+ * Synchronous: the underlying per-space planner (target's
241
+ * `MigrationPlanner.plan(...)`) is synchronous; callers that need to
242
+ * resolve async I/O (e.g. reading pinned `contract.json` from disk)
243
+ * resolve it before calling `planAllSpaces` and pass the materialised
244
+ * inputs through.
245
+ *
246
+ * @see specs/framework-mechanism.spec.md § 3 — Per-space planner (T1.3).
247
+ */
248
+ declare function planAllSpaces<TContract, TPackage>(inputs: readonly SpacePlanInput<TContract>[], planSpace: (input: SpacePlanInput<TContract>) => readonly TPackage[]): readonly SpacePlanOutput<TPackage>[];
249
+ //#endregion
250
+ //#region src/read-pinned-contract-hash.d.ts
251
+ /**
252
+ * Read the pinned head hash for an extension space.
253
+ *
254
+ * Returns the `hash` field of `<projectMigrationsDir>/<spaceId>/refs/head.json`
255
+ * — i.e. the canonical contract hash the framework wrote on the last
256
+ * `migrate` for this space. Returns `null` when the file does not exist
257
+ * (or the migrations directory is missing entirely), which is the
258
+ * "first emit" signal {@link import('./detect-space-contract-drift').detectSpaceContractDrift}
259
+ * uses to distinguish a brand-new extension from drift.
260
+ *
261
+ * Pure I/O (read + parse). The "comparison hash" is stored on disk by
262
+ * {@link import('./emit-pinned-space-artefacts').emitPinnedSpaceArtefacts}
263
+ * via the descriptor's `headRef.hash`, so reading it back here matches
264
+ * the descriptor's hashing pipeline by construction — neither side
265
+ * recomputes anything.
266
+ *
267
+ * Validation:
268
+ *
269
+ * - Rejects the app space — pinned head refs are an extension-space
270
+ * concept; the app space's contract-of-record lives at the project
271
+ * root, not under `migrations/`.
272
+ * - Validates the space id against the same `[a-z][a-z0-9_-]{0,63}`
273
+ * pattern as the rest of the per-space helpers.
274
+ * - Surfaces `MIGRATION.INVALID_JSON` / `MIGRATION.INVALID_REF_FILE`
275
+ * on a corrupt `refs/head.json` so callers can distinguish "no
276
+ * pinned file" (returns `null`) from "pinned file but unreadable"
277
+ * (throws).
278
+ *
279
+ * @see specs/framework-mechanism.spec.md § 3 — Drift detection (T1.9).
280
+ */
281
+ declare function readPinnedContractHash(projectMigrationsDir: string, spaceId: string): Promise<string | null>;
282
+ //#endregion
283
+ //#region src/space-layout.d.ts
284
+ /**
285
+ * Branded string carrying a compile-time guarantee that the value has
286
+ * been validated by {@link assertValidSpaceId}. Downstream filesystem
287
+ * helpers (e.g. {@link spaceMigrationDirectory}) accept this type to
288
+ * make "validated" tracking visible at the type level rather than
289
+ * relying purely on a runtime check.
290
+ */
291
+ type ValidSpaceId = string & {
292
+ readonly __brand: 'ValidSpaceId';
293
+ };
294
+ declare function isValidSpaceId(spaceId: string): spaceId is ValidSpaceId;
295
+ declare function assertValidSpaceId(spaceId: string): asserts spaceId is ValidSpaceId;
296
+ /**
297
+ * Resolve the migrations subdirectory for a given contract space.
298
+ *
299
+ * - **App space** (`spaceId === APP_SPACE_ID`) keeps today's layout: the
300
+ * project's `migrations/` directory is the migrations directory, no
301
+ * subdirectory.
302
+ * - **Extension space** lands under `<projectMigrationsDir>/<spaceId>/`.
303
+ * The space id is validated against {@link SPACE_ID_PATTERN} because
304
+ * it becomes a filesystem directory name verbatim.
305
+ *
306
+ * `projectMigrationsDir` is the project's top-level `migrations/`
307
+ * directory; the helper does not assume anything about its absolute /
308
+ * relative shape and is symmetric with `pathe.join`.
309
+ */
310
+ declare function spaceMigrationDirectory(projectMigrationsDir: string, spaceId: string): string;
311
+ //#endregion
312
+ //#region src/verify-contract-spaces.d.ts
313
+ /**
314
+ * List the per-space pinned subdirectories under
315
+ * `<projectRoot>/migrations/`. Returns space-id directory names (sorted
316
+ * alphabetically) — i.e. any non-dot-prefixed subdirectory whose root
317
+ * does **not** contain a `migration.json` manifest. The manifest is the
318
+ * structural marker of a user-authored migration directory (see
319
+ * `readMigrationsDir` in `./io`); directory names themselves belong to
320
+ * the user and are not part of the contract.
321
+ *
322
+ * Returns `[]` if the migrations directory does not exist (greenfield
323
+ * project).
324
+ *
325
+ * Reads only the user's repo. **No descriptor import.** The caller
326
+ * (verifier) feeds the result into {@link verifyContractSpaces} alongside
327
+ * the loaded-space set and the marker rows.
328
+ *
329
+ * @see specs/framework-mechanism.spec.md § 4 — Verifier (steps 5–6).
330
+ */
331
+ declare function listPinnedSpaceDirectories(projectMigrationsDir: string): Promise<readonly string[]>;
332
+ /**
333
+ * Pinned head value (`(hash, invariants)`) for one contract space.
334
+ * The verifier compares this against the marker row for the same space
335
+ * to detect drift between the user-emitted artefacts and the live DB
336
+ * marker.
337
+ */
338
+ interface SpacePinnedHashRecord {
339
+ readonly hash: string;
340
+ readonly invariants: readonly string[];
341
+ }
342
+ /**
343
+ * Marker row read from `prisma_contract.marker` (one per `space`).
344
+ * Caller resolves these via the family runtime's marker reader (T1.1)
345
+ * before invoking {@link verifyContractSpaces}.
346
+ */
347
+ interface SpaceMarkerRecord {
348
+ readonly hash: string;
349
+ readonly invariants: readonly string[];
350
+ }
351
+ interface VerifyContractSpacesInputs {
352
+ /**
353
+ * Set of contract spaces the project declares: `'app'` plus each
354
+ * extension space in `extensionPacks`. The caller's discovery path
355
+ * never reads the extension descriptor module — it walks the
356
+ * `extensionPacks` configuration in `prisma-next.config.ts` for the
357
+ * space ids.
358
+ */
359
+ readonly loadedSpaces: ReadonlySet<string>;
360
+ /**
361
+ * Pinned per-space subdirectories observed under
362
+ * `<projectRoot>/migrations/`. Resolved via
363
+ * {@link listPinnedSpaceDirectories}.
364
+ */
365
+ readonly pinnedDirsOnDisk: readonly string[];
366
+ /**
367
+ * Pinned head ref per space, keyed by space id. Caller reads
368
+ * `<projectRoot>/migrations/<space-id>/contract.json` and
369
+ * `refs/head.json` (or, for app-space if its pinned shape ever moves
370
+ * under `migrations/`, the equivalent files) to construct this map.
371
+ * Spaces with no pinned dir on disk simply omit a map entry.
372
+ */
373
+ readonly pinnedHashesBySpace: ReadonlyMap<string, SpacePinnedHashRecord>;
374
+ /**
375
+ * Marker rows keyed by `space`. Caller reads them from the
376
+ * `prisma_contract.marker` table.
377
+ */
378
+ readonly markerRowsBySpace: ReadonlyMap<string, SpaceMarkerRecord>;
379
+ }
380
+ type SpaceVerifierViolation = {
381
+ readonly kind: 'declaredButUnmigrated';
382
+ readonly spaceId: string;
383
+ readonly remediation: string;
384
+ } | {
385
+ readonly kind: 'orphanMarker';
386
+ readonly spaceId: string;
387
+ readonly remediation: string;
388
+ } | {
389
+ readonly kind: 'orphanPinnedDir';
390
+ readonly spaceId: string;
391
+ readonly remediation: string;
392
+ } | {
393
+ readonly kind: 'hashMismatch';
394
+ readonly spaceId: string;
395
+ readonly pinnedHash: string;
396
+ readonly markerHash: string;
397
+ readonly remediation: string;
398
+ } | {
399
+ readonly kind: 'invariantsMismatch';
400
+ readonly spaceId: string;
401
+ readonly pinnedInvariants: readonly string[];
402
+ readonly markerInvariants: readonly string[];
403
+ readonly remediation: string;
404
+ };
405
+ type VerifyContractSpacesResult = {
406
+ readonly ok: true;
407
+ } | {
408
+ readonly ok: false;
409
+ readonly violations: readonly SpaceVerifierViolation[];
410
+ };
411
+ /**
412
+ * Pure structural verifier for the per-space mechanism. Aggregates the
413
+ * three orphan / missing checks (FR6 cases a–c) plus per-space hash and
414
+ * invariant comparison.
415
+ *
416
+ * Algorithm (sub-spec § 4):
417
+ *
418
+ * - For every extension space declared in `loadedSpaces` (`'app'`
419
+ * excluded — its pinned `contract.json` lives at the project root):
420
+ * - If no pinned dir on disk → `declaredButUnmigrated`.
421
+ * - Else if `markerRowsBySpace` lacks an entry → no violation here;
422
+ * the live-DB compare in step 8 (out of scope of this helper) is
423
+ * where the absence shows up.
424
+ * - Else compare marker hash / invariants vs. pinned hash /
425
+ * invariants → `hashMismatch` / `invariantsMismatch` on drift.
426
+ * - For every pinned dir on disk that is not in `loadedSpaces` →
427
+ * `orphanPinnedDir`.
428
+ * - For every marker row whose `space` is not in `loadedSpaces` →
429
+ * `orphanMarker`. The app-space marker is always loaded (`'app'` is
430
+ * in `loadedSpaces` by definition).
431
+ *
432
+ * Output is deterministic (NFR6): violations are sorted first by `kind`
433
+ * (`declaredButUnmigrated` → `orphanMarker` → `orphanPinnedDir` →
434
+ * `hashMismatch` → `invariantsMismatch`) then by `spaceId`. Two callers
435
+ * passing equivalent inputs see byte-identical violation lists.
436
+ *
437
+ * Synchronous, pure, no I/O. **Does not import the extension descriptor**
438
+ * (the inputs are pre-resolved by the caller). This is the property
439
+ * AC-15 / AC-26 ("verifier reads only the user repo, not
440
+ * `node_modules`") locks in.
441
+ *
442
+ * @see specs/framework-mechanism.spec.md § 4 — Verifier (T1.5).
443
+ */
444
+ declare function verifyContractSpaces(inputs: VerifyContractSpacesInputs): VerifyContractSpacesResult;
445
+ //#endregion
446
+ export { APP_SPACE_ID, type DetectSpaceContractDriftInputs, type PinnedSpaceArtefactInputs, type PinnedSpaceHeadRef, type SpaceApplyInput, type SpaceContractDriftResult, type SpaceMarkerRecord, type SpacePinnedHashRecord, type SpacePlanInput, type SpacePlanOutput, type SpaceVerifierViolation, type ValidSpaceId, type VerifyContractSpacesInputs, type VerifyContractSpacesResult, assertValidSpaceId, concatenateSpaceApplyInputs, detectSpaceContractDrift, emitPinnedSpaceArtefacts, isValidSpaceId, listPinnedSpaceDirectories, planAllSpaces, readPinnedContractHash, spaceMigrationDirectory, verifyContractSpaces };
447
+ //# sourceMappingURL=spaces.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spaces.d.mts","names":[],"sources":["../../src/concatenate-space-apply-inputs.ts","../../src/detect-space-contract-drift.ts","../../src/emit-pinned-space-artefacts.ts","../../src/plan-all-spaces.ts","../../src/read-pinned-contract-hash.ts","../../src/space-layout.ts","../../src/verify-contract-spaces.ts"],"sourcesContent":[],"mappings":";;;;;;;AAuBA;AAsCA;;;;;;;;;AC5CA;AAyBA;AA0BA;;;;ACzDA;AAyBiB,UFbA,eEayB,CAAA,GAAA,CAAA,CAAA;EA8BpB,SAAA,OAAA,EAAA,MAAA;;;;ECjDL,SAAA,IAAA,EAAA,SHWS,GGXK,EAAA;AAM/B;AAkCA;;;;;;;;;;;;AClBA;;;;AC1BA;AAWA;AAIA;AAoBA;;;;ACrBA;AA2CA;AAUA;AAKA;;;;AA8BkD,iBNtDlC,2BMsDkC,CAAA,GAAA,CAAA,CAAA,MAAA,EAAA,SNrD/B,eMqD+B,CNrDf,GMqDe,CAAA,EAAA,CAAA,EAAA,SNpDtC,eMoDsC,CNpDtB,GMoDsB,CAAA,EAAA;;;;;;AN5FlD;AAsCA;;;;;;;;;AC5CA;AAyBA;AA0BA;;UAnDiB,8BAAA;;ECNA,SAAA,UAAA,EAAkB,MAAA,GAAA,IAAA;AAyBnC;AA8BA;;;;ACjDA;AAMA;AAkCA;;;;;;;;;;;;AClBA;;KHGY,wBAAA;;EI7BA,SAAA,OAAY,EAAA,MAAA;EAWR,SAAA,cAAc,EAAA,MAA8B;EAI5C,SAAA,UAAA,EAAkB,MAAA,GAAA,IAAsC;AAoBxE,CAAA;;;;ACrBA;AA2CA;AAUA;AAKA;;;;;;;AAiCA;AA+BA;AAqCA;;;;iBLtHgB,wBAAA,0BAEN,iCACP;;;;;;ADhDH;AAsCA;AACmC,UEnDlB,kBAAA,CFmDkB;EAAhB,SAAA,IAAA,EAAA,MAAA;EACS,SAAA,UAAA,EAAA,SAAA,MAAA,EAAA;;;;;;AC9C5B;AAyBA;AA0BA;;;;ACzDA;AAyBA;AA8BA;;;;ACjDA;AAMA;AAkCA;;AACmB,UDtBF,yBAAA,CCsBE;EACiB,SAAA,QAAA,EAAA,OAAA;EAAf,SAAA,WAAA,EAAA,MAAA;EAAuC,SAAA,OAAA,EDpBxC,kBCoBwC;;;;;;;ACpB5D;;;;AC1BA;AAWA;AAIA;AAoBA;;;;ACrBA;AA2CA;AAUA;AAKA;;;;;AA8B8B,iBJjDR,wBAAA,CIiDQ,oBAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,EAAA,MAAA,EJ9CpB,yBI8CoB,CAAA,EJ7C3B,OI6C2B,CAAA,IAAA,CAAA;;;;;;AN5F9B;AAsCA;;;;;;;;;AC5CA;AAyBA;AA0BgB,UEnDC,cFmDuB,CAAA,SAAA,CAAA,CAE9B;;0BEnDgB;wBACF;ADTxB;AAyBiB,UCbA,eDayB,CAAA,QAAA,CAAA,CAAA;EA8BpB,SAAA,OAAA,EAAA,MAAA;uCCzCiB;;;AARvC;AAMA;AAkCA;;;;;;;;;;;;AClBA;;;;AC1BA;AAWA;AAIA;AAoBA;;;;ACrBA;AA2CA;AAUA;AAKiB,iBH5BD,aG4B2B,CAAA,SAAA,EAAA,QAAA,CAAA,CAAA,MAAA,EAAA,SH3BxB,cG2BwB,CH3BT,SG2BS,CAAA,EAAA,EAAA,SAAA,EAAA,CAAA,KAAA,EH1BtB,cG0BsB,CH1BP,SG0BO,CAAA,EAAA,GAAA,SH1BiB,QG0BjB,EAAA,CAAA,EAAA,SHzB/B,eGyB+B,CHzBf,QGyBe,CAAA,EAAA;;;;;;AN9D3C;AAsCA;;;;;;;;;AC5CA;AAyBA;AA0BA;;;;ACzDA;AAyBA;AA8BA;;;;ACjDA;AAMA;AAkCA;;;AAEoC,iBCpBd,sBAAA,CDoBc,oBAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,CAAA,ECjBjC,ODiBiC,CAAA,MAAA,GAAA,IAAA,CAAA;;;;AHpCpC;AAsCA;;;;;AAE2B,KKlDf,YAAA,GLkDe,MAAA,GAAA;;;iBKvCX,cAAA,8BAA4C;AJP3C,iBIWD,kBAAA,CJX+B,OAAA,EAAA,MAAA,CAAA,EAAA,QAAA,OAAA,IIWyB,YJXzB;AAyB/C;AA0BA;;;;ACzDA;AAyBA;AA8BA;;;;ACjDA;AAMA;AAkCA;AACkC,iBEVlB,uBAAA,CFUkB,oBAAA,EAAA,MAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EAAA,MAAA;;;;;;AHnClC;AAsCA;;;;;;;;;AC5CA;AAyBA;AA0BA;;;iBKzCsB,0BAAA,gCAEnB;AJlBH;AAyBA;AA8BA;;;;ACjDiB,UGqDA,qBAAA,CHrDc;EAMd,SAAA,IAAA,EAAA,MAAe;EAkChB,SAAA,UAAa,EAAA,SAAA,MAAA,EAAA;;;;;;;AAGjB,UGoBK,iBAAA,CHpBL;EAAe,SAAA,IAAA,EAAA,MAAA;;;UGyBV,0BAAA;EF9CK;;;;AC1BtB;AAWA;AAIA;EAoBgB,SAAA,YAAA,EC6CS,WD7Cc,CAAA,MAAA,CAAA;;;;ACrBvC;AA2CA;EAUiB,SAAA,gBAAiB,EAAA,SAAA,MAAA,EAAA;EAKjB;;;;;;;EAiCL,SAAA,mBAAsB,EATF,WASE,CAAA,MAAA,EATkB,qBASlB,CAAA;EA+BtB;AAqCZ;;;8BAvE8B,oBAAoB;;KAGtC,sBAAA;;;;;;;;;;;;;;;;;;;;;;;;;KA+BA,0BAAA;;;;gCAE4C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAmCxC,oBAAA,SACN,6BACP"}