@cleocode/core 2026.6.5 → 2026.6.7

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 (156) hide show
  1. package/dist/dispatch/contracts/output-contracts.d.ts +36 -0
  2. package/dist/dispatch/contracts/output-contracts.d.ts.map +1 -0
  3. package/dist/dispatch/contracts/output-contracts.js +38 -0
  4. package/dist/dispatch/contracts/output-contracts.js.map +1 -0
  5. package/dist/dispatch/describe-operation.d.ts +98 -0
  6. package/dist/dispatch/describe-operation.d.ts.map +1 -0
  7. package/dist/dispatch/describe-operation.js +101 -0
  8. package/dist/dispatch/describe-operation.js.map +1 -0
  9. package/dist/docs/export-document.js +933 -489
  10. package/dist/docs/export-document.js.map +3 -3
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +5 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/internal.d.ts +2 -0
  16. package/dist/internal.d.ts.map +1 -1
  17. package/dist/internal.js +6 -0
  18. package/dist/internal.js.map +1 -1
  19. package/dist/llm/api-mode.d.ts +64 -0
  20. package/dist/llm/api-mode.d.ts.map +1 -0
  21. package/dist/llm/api-mode.js +72 -0
  22. package/dist/llm/api-mode.js.map +1 -0
  23. package/dist/llm/api.d.ts.map +1 -1
  24. package/dist/llm/api.js +6 -37
  25. package/dist/llm/api.js.map +1 -1
  26. package/dist/llm/auxiliary-fallback.d.ts.map +1 -1
  27. package/dist/llm/auxiliary-fallback.js +24 -38
  28. package/dist/llm/auxiliary-fallback.js.map +1 -1
  29. package/dist/llm/index.d.ts +1 -3
  30. package/dist/llm/index.d.ts.map +1 -1
  31. package/dist/llm/index.js +1 -2
  32. package/dist/llm/index.js.map +1 -1
  33. package/dist/llm/model-metadata.d.ts +14 -0
  34. package/dist/llm/model-metadata.d.ts.map +1 -1
  35. package/dist/llm/model-metadata.js +23 -0
  36. package/dist/llm/model-metadata.js.map +1 -1
  37. package/dist/llm/model-runner.d.ts +127 -0
  38. package/dist/llm/model-runner.d.ts.map +1 -0
  39. package/dist/llm/model-runner.js +312 -0
  40. package/dist/llm/model-runner.js.map +1 -0
  41. package/dist/llm/plugin-facade.js +30084 -1748
  42. package/dist/llm/plugin-facade.js.map +3 -3
  43. package/dist/llm/provider-registry/builtin/anthropic.d.ts.map +1 -1
  44. package/dist/llm/provider-registry/builtin/anthropic.js +4 -0
  45. package/dist/llm/provider-registry/builtin/anthropic.js.map +1 -1
  46. package/dist/llm/provider-registry/builtin/gemini.d.ts.map +1 -1
  47. package/dist/llm/provider-registry/builtin/gemini.js +4 -0
  48. package/dist/llm/provider-registry/builtin/gemini.js.map +1 -1
  49. package/dist/llm/provider-registry/builtin/ollama.d.ts.map +1 -1
  50. package/dist/llm/provider-registry/builtin/ollama.js +4 -0
  51. package/dist/llm/provider-registry/builtin/ollama.js.map +1 -1
  52. package/dist/llm/provider-registry/builtin/openai.d.ts.map +1 -1
  53. package/dist/llm/provider-registry/builtin/openai.js +6 -0
  54. package/dist/llm/provider-registry/builtin/openai.js.map +1 -1
  55. package/dist/llm/role-executor.d.ts +6 -5
  56. package/dist/llm/role-executor.d.ts.map +1 -1
  57. package/dist/llm/role-executor.js +40 -86
  58. package/dist/llm/role-executor.js.map +1 -1
  59. package/dist/llm/role-resolver.d.ts +28 -1
  60. package/dist/llm/role-resolver.d.ts.map +1 -1
  61. package/dist/llm/role-resolver.js +10 -0
  62. package/dist/llm/role-resolver.js.map +1 -1
  63. package/dist/llm/session-factory.d.ts +4 -6
  64. package/dist/llm/session-factory.d.ts.map +1 -1
  65. package/dist/llm/session-factory.js +6 -72
  66. package/dist/llm/session-factory.js.map +1 -1
  67. package/dist/llm/system-resolver.d.ts.map +1 -1
  68. package/dist/llm/system-resolver.js +6 -0
  69. package/dist/llm/system-resolver.js.map +1 -1
  70. package/dist/llm/tool-loop.d.ts.map +1 -1
  71. package/dist/llm/tool-loop.js +9 -32
  72. package/dist/llm/tool-loop.js.map +1 -1
  73. package/dist/llm/transports/index.d.ts +5 -3
  74. package/dist/llm/transports/index.d.ts.map +1 -1
  75. package/dist/llm/transports/index.js +5 -2
  76. package/dist/llm/transports/index.js.map +1 -1
  77. package/dist/reconciliation/reconciliation-engine.d.ts.map +1 -1
  78. package/dist/reconciliation/reconciliation-engine.js +3 -0
  79. package/dist/reconciliation/reconciliation-engine.js.map +1 -1
  80. package/dist/release/plan.d.ts +27 -0
  81. package/dist/release/plan.d.ts.map +1 -1
  82. package/dist/release/plan.js +36 -2
  83. package/dist/release/plan.js.map +1 -1
  84. package/dist/release/provenance-fk.d.ts +74 -0
  85. package/dist/release/provenance-fk.d.ts.map +1 -0
  86. package/dist/release/provenance-fk.js +122 -0
  87. package/dist/release/provenance-fk.js.map +1 -0
  88. package/dist/release/reconcile.d.ts +10 -0
  89. package/dist/release/reconcile.d.ts.map +1 -1
  90. package/dist/release/reconcile.js +107 -2
  91. package/dist/release/reconcile.js.map +1 -1
  92. package/dist/sticky/convert.d.ts.map +1 -1
  93. package/dist/sticky/convert.js +3 -0
  94. package/dist/sticky/convert.js.map +1 -1
  95. package/dist/store/exodus/column-transforms.d.ts +275 -0
  96. package/dist/store/exodus/column-transforms.d.ts.map +1 -0
  97. package/dist/store/exodus/column-transforms.js +478 -0
  98. package/dist/store/exodus/column-transforms.js.map +1 -0
  99. package/dist/store/exodus/count-parity.d.ts +71 -0
  100. package/dist/store/exodus/count-parity.d.ts.map +1 -0
  101. package/dist/store/exodus/count-parity.js +124 -0
  102. package/dist/store/exodus/count-parity.js.map +1 -0
  103. package/dist/store/exodus/health.d.ts +70 -0
  104. package/dist/store/exodus/health.d.ts.map +1 -0
  105. package/dist/store/exodus/health.js +130 -0
  106. package/dist/store/exodus/health.js.map +1 -0
  107. package/dist/store/exodus/index.d.ts +3 -0
  108. package/dist/store/exodus/index.d.ts.map +1 -1
  109. package/dist/store/exodus/index.js +3 -0
  110. package/dist/store/exodus/index.js.map +1 -1
  111. package/dist/store/exodus/migrate.d.ts.map +1 -1
  112. package/dist/store/exodus/migrate.js +103 -298
  113. package/dist/store/exodus/migrate.js.map +1 -1
  114. package/dist/store/exodus/plan.d.ts +48 -4
  115. package/dist/store/exodus/plan.d.ts.map +1 -1
  116. package/dist/store/exodus/plan.js +67 -9
  117. package/dist/store/exodus/plan.js.map +1 -1
  118. package/dist/store/exodus/seal.d.ts +69 -0
  119. package/dist/store/exodus/seal.d.ts.map +1 -0
  120. package/dist/store/exodus/seal.js +73 -0
  121. package/dist/store/exodus/seal.js.map +1 -0
  122. package/dist/store/exodus/types.d.ts +24 -1
  123. package/dist/store/exodus/types.d.ts.map +1 -1
  124. package/dist/store/exodus/types.js.map +1 -1
  125. package/dist/store/exodus/verify-migration.d.ts.map +1 -1
  126. package/dist/store/exodus/verify-migration.js +109 -24
  127. package/dist/store/exodus/verify-migration.js.map +1 -1
  128. package/dist/tasks/add.d.ts +13 -0
  129. package/dist/tasks/add.d.ts.map +1 -1
  130. package/dist/tasks/add.js +50 -18
  131. package/dist/tasks/add.js.map +1 -1
  132. package/dist/tasks/archive.d.ts.map +1 -1
  133. package/dist/tasks/archive.js +12 -7
  134. package/dist/tasks/archive.js.map +1 -1
  135. package/dist/tasks/child-disposition.d.ts +66 -0
  136. package/dist/tasks/child-disposition.d.ts.map +1 -0
  137. package/dist/tasks/child-disposition.js +80 -0
  138. package/dist/tasks/child-disposition.js.map +1 -0
  139. package/dist/tasks/delete-preview.js +1 -1
  140. package/dist/tasks/delete-preview.js.map +1 -1
  141. package/dist/tasks/deletion-strategy.d.ts +21 -3
  142. package/dist/tasks/deletion-strategy.d.ts.map +1 -1
  143. package/dist/tasks/deletion-strategy.js +61 -15
  144. package/dist/tasks/deletion-strategy.js.map +1 -1
  145. package/dist/tasks/engine-wrap.d.ts +8 -0
  146. package/dist/tasks/engine-wrap.d.ts.map +1 -1
  147. package/dist/tasks/engine-wrap.js +22 -9
  148. package/dist/tasks/engine-wrap.js.map +1 -1
  149. package/dist/tasks/update.d.ts.map +1 -1
  150. package/dist/tasks/update.js +12 -0
  151. package/dist/tasks/update.js.map +1 -1
  152. package/package.json +12 -12
  153. package/dist/llm/transports/openai.d.ts +0 -181
  154. package/dist/llm/transports/openai.d.ts.map +0 -1
  155. package/dist/llm/transports/openai.js +0 -645
  156. package/dist/llm/transports/openai.js.map +0 -1
@@ -5,15 +5,58 @@
5
5
  * combined size, free-disk availability, staging directory — BEFORE any
6
6
  * writes occur. A `--dry-run` caller can print the plan and exit early.
7
7
  *
8
- * ## Disk pre-flight (AC8)
8
+ * ## Disk pre-flight (AC8 · right-sized T11838)
9
9
  *
10
- * `availableBytes >= 3 * totalSourceBytes` must hold before migration begins.
11
- * The check uses `statvfs` via `node:fs.statfsSync()` (Node 18+).
10
+ * The original gate required `availableBytes >= 3 * totalSourceBytes` an
11
+ * over-estimate that blocked large-fleet migrations even with ample headroom:
12
+ * exodus never holds 3× the SUM of every source on disk at once. It copies one
13
+ * source into staging at a time (lock released before the next) and writes a
14
+ * single consolidated cleo.db whose size approximates the sum of source ROW data.
15
+ *
16
+ * The right-sized requirement is therefore
17
+ * `STAGING_HEADROOM_FACTOR * largestSourceBytes + consolidatedEstimate`, where
18
+ * `consolidatedEstimate ≈ totalSourceBytes` ({@link computeRequiredBytes}). The
19
+ * check uses `statvfs` via `node:fs.statfsSync()` (Node 18+).
12
20
  *
13
21
  * @task T11248 (E5 · SG-DB-SUBSTRATE-V2)
22
+ * @task T11838 (right-sized preflight + optional staging copy for large sources)
14
23
  * @saga T11242
15
24
  */
16
25
  import type { ExodusPlan, LegacyDbDescriptor } from './types.js';
26
+ /**
27
+ * Headroom multiplier applied to the LARGEST single source when sizing the
28
+ * staging-copy footprint. The staging dir only ever holds ONE source backup at a
29
+ * time (its advisory lock is released before the next source is touched), so the
30
+ * peak staging footprint is the largest source plus a small slack for the
31
+ * write-then-rename journal + SQLite sidecars — hence `1.2×`, not `3×` of the SUM.
32
+ *
33
+ * @task T11838
34
+ */
35
+ export declare const STAGING_HEADROOM_FACTOR: 1.2;
36
+ /**
37
+ * Per-source byte threshold above which the full staging `copyFileSync` backup
38
+ * is skipped (the source is archived, not deleted, on success). Sized at 256 MiB
39
+ * — comfortably above the small project DBs (tasks/conduit/skills/signaldock are
40
+ * sub-MB to low-MB) but below the large global stores (a multi-GB `brain.db` or
41
+ * `nexus.db`) whose redundant full-file copy is the costly case T11838 removes.
42
+ *
43
+ * @task T11838
44
+ */
45
+ export declare const STAGING_COPY_SKIP_THRESHOLD_BYTES: number;
46
+ /**
47
+ * Compute the right-sized free-disk requirement for an exodus run (T11838).
48
+ *
49
+ * `STAGING_HEADROOM_FACTOR * largestSourceBytes` covers the peak staging-copy
50
+ * footprint (one source at a time, plus slack), and `totalSourceBytes` is the
51
+ * consolidated-cleo.db estimate (every source's row data lands there). The two
52
+ * are additive because staging and the consolidated DB coexist on disk until the
53
+ * sources are archived.
54
+ *
55
+ * @param totalSourceBytes - Combined size of all source DB files in bytes.
56
+ * @param largestSourceBytes - Size of the single largest source DB in bytes.
57
+ * @returns The minimum free bytes the target filesystem must have.
58
+ */
59
+ export declare function computeRequiredBytes(totalSourceBytes: number, largestSourceBytes: number): number;
17
60
  /**
18
61
  * Derive the staging directory name from the current ISO-8601 timestamp.
19
62
  *
@@ -31,7 +74,8 @@ export declare function deriveStagingDirName(): string;
31
74
  * `process.cwd()`.
32
75
  * @returns {@link ExodusPlan} describing sources, disk availability, and paths.
33
76
  *
34
- * @task T11248 (AC8 — disk pre-flight)
77
+ * @task T11248 (AC8 — disk pre-flight)
78
+ * @task T11838 (right-sized: largest-source factor + consolidated estimate)
35
79
  */
36
80
  export declare function buildExodusPlan(cwd?: string): ExodusPlan;
37
81
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/store/exodus/plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AA8EjE;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAM7C;AA0BD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,UAAU,CA+BxD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,kBAAkB,EAAE,GAAG,OAAO,CAErE"}
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/store/exodus/plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAMjE;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,EAAG,GAAY,CAAC;AAEpD;;;;;;;;GAQG;AACH,eAAO,MAAM,iCAAiC,QAAoB,CAAC;AAEnE;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,gBAAgB,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,GAAG,MAAM,CAEjG;AA8ED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAM7C;AA0BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,UAAU,CAwCxD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,kBAAkB,EAAE,GAAG,OAAO,CAErE"}
@@ -5,12 +5,21 @@
5
5
  * combined size, free-disk availability, staging directory — BEFORE any
6
6
  * writes occur. A `--dry-run` caller can print the plan and exit early.
7
7
  *
8
- * ## Disk pre-flight (AC8)
8
+ * ## Disk pre-flight (AC8 · right-sized T11838)
9
9
  *
10
- * `availableBytes >= 3 * totalSourceBytes` must hold before migration begins.
11
- * The check uses `statvfs` via `node:fs.statfsSync()` (Node 18+).
10
+ * The original gate required `availableBytes >= 3 * totalSourceBytes` an
11
+ * over-estimate that blocked large-fleet migrations even with ample headroom:
12
+ * exodus never holds 3× the SUM of every source on disk at once. It copies one
13
+ * source into staging at a time (lock released before the next) and writes a
14
+ * single consolidated cleo.db whose size approximates the sum of source ROW data.
15
+ *
16
+ * The right-sized requirement is therefore
17
+ * `STAGING_HEADROOM_FACTOR * largestSourceBytes + consolidatedEstimate`, where
18
+ * `consolidatedEstimate ≈ totalSourceBytes` ({@link computeRequiredBytes}). The
19
+ * check uses `statvfs` via `node:fs.statfsSync()` (Node 18+).
12
20
  *
13
21
  * @task T11248 (E5 · SG-DB-SUBSTRATE-V2)
22
+ * @task T11838 (right-sized preflight + optional staging copy for large sources)
14
23
  * @saga T11242
15
24
  */
16
25
  import { existsSync, readdirSync, statfsSync, statSync } from 'node:fs';
@@ -18,6 +27,45 @@ import { join } from 'node:path';
18
27
  import { getCleoHome, resolveCleoDir } from '../../paths.js';
19
28
  import { resolveDualScopeDbPath } from '../dual-scope-db.js';
20
29
  // ---------------------------------------------------------------------------
30
+ // Disk pre-flight sizing constants (right-sized — T11838)
31
+ // ---------------------------------------------------------------------------
32
+ /**
33
+ * Headroom multiplier applied to the LARGEST single source when sizing the
34
+ * staging-copy footprint. The staging dir only ever holds ONE source backup at a
35
+ * time (its advisory lock is released before the next source is touched), so the
36
+ * peak staging footprint is the largest source plus a small slack for the
37
+ * write-then-rename journal + SQLite sidecars — hence `1.2×`, not `3×` of the SUM.
38
+ *
39
+ * @task T11838
40
+ */
41
+ export const STAGING_HEADROOM_FACTOR = 1.2;
42
+ /**
43
+ * Per-source byte threshold above which the full staging `copyFileSync` backup
44
+ * is skipped (the source is archived, not deleted, on success). Sized at 256 MiB
45
+ * — comfortably above the small project DBs (tasks/conduit/skills/signaldock are
46
+ * sub-MB to low-MB) but below the large global stores (a multi-GB `brain.db` or
47
+ * `nexus.db`) whose redundant full-file copy is the costly case T11838 removes.
48
+ *
49
+ * @task T11838
50
+ */
51
+ export const STAGING_COPY_SKIP_THRESHOLD_BYTES = 256 * 1024 * 1024; // 256 MiB
52
+ /**
53
+ * Compute the right-sized free-disk requirement for an exodus run (T11838).
54
+ *
55
+ * `STAGING_HEADROOM_FACTOR * largestSourceBytes` covers the peak staging-copy
56
+ * footprint (one source at a time, plus slack), and `totalSourceBytes` is the
57
+ * consolidated-cleo.db estimate (every source's row data lands there). The two
58
+ * are additive because staging and the consolidated DB coexist on disk until the
59
+ * sources are archived.
60
+ *
61
+ * @param totalSourceBytes - Combined size of all source DB files in bytes.
62
+ * @param largestSourceBytes - Size of the single largest source DB in bytes.
63
+ * @returns The minimum free bytes the target filesystem must have.
64
+ */
65
+ export function computeRequiredBytes(totalSourceBytes, largestSourceBytes) {
66
+ return Math.ceil(STAGING_HEADROOM_FACTOR * largestSourceBytes) + totalSourceBytes;
67
+ }
68
+ // ---------------------------------------------------------------------------
21
69
  // Legacy DB descriptors (AC2 — 6 per-machine source DBs mapped to 2 targets)
22
70
  // ---------------------------------------------------------------------------
23
71
  /**
@@ -137,17 +185,24 @@ function findExistingStaging(cleoDir) {
137
185
  * `process.cwd()`.
138
186
  * @returns {@link ExodusPlan} describing sources, disk availability, and paths.
139
187
  *
140
- * @task T11248 (AC8 — disk pre-flight)
188
+ * @task T11248 (AC8 — disk pre-flight)
189
+ * @task T11838 (right-sized: largest-source factor + consolidated estimate)
141
190
  */
142
191
  export function buildExodusPlan(cwd) {
143
192
  const cleoDir = resolveCleoDir(cwd);
144
193
  const sources = buildSourceDescriptors(cwd);
145
- // Compute total source size (only existing files count toward the check)
146
- const totalSourceBytes = sources.reduce((sum, s) => sum + safeFileBytes(s.path), 0);
147
- // Disk pre-flight: check against the directory that will hold the staging data
148
- // (the .cleo/ dir, which is where both the backup and staging live).
194
+ // Per-source sizes (only existing files contribute) drive both the total
195
+ // and the largest-single-source factor of the right-sized preflight (T11838).
196
+ const sourceBytes = sources.map((s) => safeFileBytes(s.path));
197
+ const totalSourceBytes = sourceBytes.reduce((sum, b) => sum + b, 0);
198
+ const largestSourceBytes = sourceBytes.reduce((max, b) => Math.max(max, b), 0);
199
+ // Right-sized disk pre-flight (T11838): exodus never holds 3× the SUM of all
200
+ // sources at once — it stages ONE source at a time and writes one consolidated
201
+ // cleo.db (≈ totalSourceBytes). Check against the directory that will hold the
202
+ // staging data (the .cleo/ dir, where both backup + staging live).
203
+ const requiredBytes = computeRequiredBytes(totalSourceBytes, largestSourceBytes);
149
204
  const availableBytes = getAvailableBytes(cleoDir);
150
- const diskPreflight = totalSourceBytes === 0 || availableBytes >= 3 * totalSourceBytes;
205
+ const diskPreflight = totalSourceBytes === 0 || availableBytes >= requiredBytes;
151
206
  // Staging directory — resume if a previous one exists
152
207
  const existingStaging = findExistingStaging(cleoDir);
153
208
  const stagingDir = existingStaging ?? join(cleoDir, deriveStagingDirName());
@@ -158,8 +213,11 @@ export function buildExodusPlan(cwd) {
158
213
  return {
159
214
  sources,
160
215
  totalSourceBytes,
216
+ largestSourceBytes,
217
+ requiredBytes,
161
218
  availableBytes,
162
219
  diskPreflight,
220
+ stagingCopyThresholdBytes: STAGING_COPY_SKIP_THRESHOLD_BYTES,
163
221
  stagingDir,
164
222
  resumeFromStaging,
165
223
  projectDbPath,
@@ -1 +1 @@
1
- {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../src/store/exodus/plan.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../src/store/exodus/plan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * `cleo exodus seal` core — certify an already-migrated install (T11837).
3
+ *
4
+ * Some installs were cut over to the consolidated `cleo.db` BEFORE the archive +
5
+ * completion-marker subsystem (T11777) existed: their data is fully in `cleo.db`
6
+ * but the legacy source DBs were never archived and no `exodus-complete` marker
7
+ * was written, so `exodus-on-open` keeps re-arming (the ~34s-per-command tax)
8
+ * unless muzzled by `CLEO_DISABLE_EXODUS_ON_OPEN`. `cleo exodus migrate` would
9
+ * complete the archival — but it routes through the heavy `verifyMigration`
10
+ * digest, which OOMs on a 1.7 GB-class legacy `brain.db`.
11
+ *
12
+ * `sealExodus` closes the loop WITHOUT a destructive re-migrate and WITHOUT the
13
+ * OOM digest: it gates on the memory-safe COUNT(*) deficit check
14
+ * ({@link computeCountParity}) — the SAME gate (`target >= source`) the archive
15
+ * path enforces — and, only when no rows are missing, archives the consumed
16
+ * legacy sources (reversible move) and writes the per-scope completion marker.
17
+ *
18
+ * @task T11837 (fleet-flow surface — seal an already-migrated install)
19
+ * @epic T11833 (EP-EXODUS-FLEET-HARDENING)
20
+ * @saga T11242 (SG-DB-SUBSTRATE-V2)
21
+ */
22
+ import { type CountParityResult } from './count-parity.js';
23
+ import type { ExodusPlan, ExodusScope } from './types.js';
24
+ /** Scope selector for {@link sealExodus}. */
25
+ export type SealScopeArg = ExodusScope | 'both';
26
+ /** Per-scope outcome of a seal. */
27
+ export interface SealScopeOutcome {
28
+ /** Scope certified. */
29
+ readonly scope: ExodusScope;
30
+ /** `true` if a completion marker already existed (re-seal is idempotent). */
31
+ readonly alreadySealed: boolean;
32
+ /** Per-source archive outcomes for this scope. */
33
+ readonly archived: ReadonlyArray<{
34
+ readonly name: string;
35
+ readonly action: 'archived' | 'absent';
36
+ readonly archivedTo: string | null;
37
+ }>;
38
+ /** Absolute path of the completion marker written. */
39
+ readonly markerPath: string;
40
+ }
41
+ /** Result of {@link sealExodus}. */
42
+ export interface SealResult {
43
+ /** `true` when the seal proceeded; `false` when refused on a parity deficit. */
44
+ readonly ok: boolean;
45
+ /** Populated when `ok === false`: why the seal was refused. */
46
+ readonly refusedReason?: string;
47
+ /** The COUNT(*)-only parity sweep that gated the seal. */
48
+ readonly parity: CountParityResult;
49
+ /** Per-scope outcomes (empty when refused). */
50
+ readonly scopes: readonly SealScopeOutcome[];
51
+ }
52
+ /**
53
+ * Seal one or more scopes of an already-migrated install: gate on COUNT(*) parity
54
+ * (no digest), then archive the consumed legacy sources + write the completion
55
+ * marker. Refuses (archives nothing) if ANY table has a deficit — the data is not
56
+ * fully in `cleo.db` and the operator must run `cleo exodus migrate` first.
57
+ *
58
+ * Idempotent + reversible: archiving is a `rename` (never delete) and a re-seal
59
+ * over an already-sealed scope simply refreshes the marker.
60
+ *
61
+ * @param plan - The exodus plan (`buildExodusPlan(cwd)`).
62
+ * @param scopeArg - Which scope(s) to seal.
63
+ * @param cwd - Working directory used to resolve the project dir.
64
+ * @returns A {@link SealResult}.
65
+ *
66
+ * @task T11837
67
+ */
68
+ export declare function sealExodus(plan: ExodusPlan, scopeArg: SealScopeArg, cwd: string | undefined): SealResult;
69
+ //# sourceMappingURL=seal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seal.d.ts","sourceRoot":"","sources":["../../../src/store/exodus/seal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,EAAE,KAAK,iBAAiB,EAAsB,MAAM,mBAAmB,CAAC;AAC/E,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAI1D,6CAA6C;AAC7C,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,MAAM,CAAC;AAEhD,mCAAmC;AACnC,MAAM,WAAW,gBAAgB;IAC/B,uBAAuB;IACvB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,6EAA6E;IAC7E,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,kDAAkD;IAClD,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;QAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,QAAQ,CAAC;QACvC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;KACpC,CAAC,CAAC;IACH,sDAAsD;IACtD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,oCAAoC;AACpC,MAAM,WAAW,UAAU;IACzB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,+DAA+D;IAC/D,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAC9C;AAOD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,YAAY,EACtB,GAAG,EAAE,MAAM,GAAG,SAAS,GACtB,UAAU,CAqCZ"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * `cleo exodus seal` core — certify an already-migrated install (T11837).
3
+ *
4
+ * Some installs were cut over to the consolidated `cleo.db` BEFORE the archive +
5
+ * completion-marker subsystem (T11777) existed: their data is fully in `cleo.db`
6
+ * but the legacy source DBs were never archived and no `exodus-complete` marker
7
+ * was written, so `exodus-on-open` keeps re-arming (the ~34s-per-command tax)
8
+ * unless muzzled by `CLEO_DISABLE_EXODUS_ON_OPEN`. `cleo exodus migrate` would
9
+ * complete the archival — but it routes through the heavy `verifyMigration`
10
+ * digest, which OOMs on a 1.7 GB-class legacy `brain.db`.
11
+ *
12
+ * `sealExodus` closes the loop WITHOUT a destructive re-migrate and WITHOUT the
13
+ * OOM digest: it gates on the memory-safe COUNT(*) deficit check
14
+ * ({@link computeCountParity}) — the SAME gate (`target >= source`) the archive
15
+ * path enforces — and, only when no rows are missing, archives the consumed
16
+ * legacy sources (reversible move) and writes the per-scope completion marker.
17
+ *
18
+ * @task T11837 (fleet-flow surface — seal an already-migrated install)
19
+ * @epic T11833 (EP-EXODUS-FLEET-HARDENING)
20
+ * @saga T11242 (SG-DB-SUBSTRATE-V2)
21
+ */
22
+ import { getLogger } from '../../logger.js';
23
+ import { archiveSourceDb, hasExodusCompleteMarker, writeExodusCompleteMarker } from './archive.js';
24
+ import { computeCountParity } from './count-parity.js';
25
+ const log = getLogger('exodus-seal');
26
+ /** Resolve a {@link SealScopeArg} to the concrete scopes it covers. */
27
+ function resolveScopes(arg) {
28
+ return arg === 'both' ? ['project', 'global'] : [arg];
29
+ }
30
+ /**
31
+ * Seal one or more scopes of an already-migrated install: gate on COUNT(*) parity
32
+ * (no digest), then archive the consumed legacy sources + write the completion
33
+ * marker. Refuses (archives nothing) if ANY table has a deficit — the data is not
34
+ * fully in `cleo.db` and the operator must run `cleo exodus migrate` first.
35
+ *
36
+ * Idempotent + reversible: archiving is a `rename` (never delete) and a re-seal
37
+ * over an already-sealed scope simply refreshes the marker.
38
+ *
39
+ * @param plan - The exodus plan (`buildExodusPlan(cwd)`).
40
+ * @param scopeArg - Which scope(s) to seal.
41
+ * @param cwd - Working directory used to resolve the project dir.
42
+ * @returns A {@link SealResult}.
43
+ *
44
+ * @task T11837
45
+ */
46
+ export function sealExodus(plan, scopeArg, cwd) {
47
+ // Memory-safe gate — NEVER the heavy verifyMigration digest (this whole epic
48
+ // exists to avoid OOMing it on a 1.7 GB-class legacy brain.db).
49
+ const parity = computeCountParity(plan.sources, plan.projectDbPath, plan.globalDbPath);
50
+ if (!parity.ok) {
51
+ const refusedReason = `Refusing to seal: ${parity.deficits.length} table(s) have FEWER rows in the ` +
52
+ `consolidated cleo.db than the legacy source — the data is NOT fully migrated. Run ` +
53
+ `\`cleo exodus migrate\` first. Deficits: ${parity.deficits
54
+ .map((d) => `${d.targetTable}(${d.sourceCount}→${d.targetCount})`)
55
+ .join(', ')}`;
56
+ log.error({ deficits: parity.deficits.length }, `exodus seal refused — ${refusedReason}`);
57
+ return { ok: false, refusedReason, parity, scopes: [] };
58
+ }
59
+ const outcomes = [];
60
+ for (const scope of resolveScopes(scopeArg)) {
61
+ const alreadySealed = hasExodusCompleteMarker(scope, cwd);
62
+ const scopeSources = plan.sources.filter((s) => s.targetScope === scope);
63
+ const archived = scopeSources.map((s) => {
64
+ const r = archiveSourceDb(s, cwd);
65
+ return { name: r.name, action: r.action, archivedTo: r.archivedTo };
66
+ });
67
+ const markerPath = writeExodusCompleteMarker(scope, scopeSources.map((s) => s.name), cwd);
68
+ log.info({ scope, alreadySealed, archived: archived.filter((a) => a.action === 'archived').length }, `exodus seal: scope '${scope}' certified (count-parity verified, ${parity.checked} tables)`);
69
+ outcomes.push({ scope, alreadySealed, archived, markerPath });
70
+ }
71
+ return { ok: true, parity, scopes: outcomes };
72
+ }
73
+ //# sourceMappingURL=seal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seal.js","sourceRoot":"","sources":["../../../src/store/exodus/seal.ts"],"names":[],"mappings":"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"}
@@ -97,10 +97,33 @@ export interface ExodusPlan {
97
97
  readonly sources: LegacyDbDescriptor[];
98
98
  /** Combined size of all source DB files in bytes. */
99
99
  readonly totalSourceBytes: number;
100
+ /**
101
+ * Size of the SINGLE largest source DB file in bytes. The right-sized disk
102
+ * preflight is driven by this (the staging copy only ever holds one source at
103
+ * a time before its lock is released), NOT by {@link totalSourceBytes} (T11838).
104
+ */
105
+ readonly largestSourceBytes: number;
106
+ /**
107
+ * Right-sized free-disk requirement in bytes:
108
+ * `STAGING_HEADROOM_FACTOR * largestSourceBytes + consolidatedEstimate`, where
109
+ * `consolidatedEstimate ≈ totalSourceBytes` (all source rows land in the
110
+ * consolidated cleo.db). Replaces the historical `3 * totalSourceBytes`
111
+ * over-estimate that blocked large-fleet migrations (T11838).
112
+ */
113
+ readonly requiredBytes: number;
100
114
  /** Available disk bytes on the target filesystem. */
101
115
  readonly availableBytes: number;
102
- /** Whether the free-disk pre-flight passes. */
116
+ /** Whether the right-sized free-disk pre-flight passes. */
103
117
  readonly diskPreflight: boolean;
118
+ /**
119
+ * Per-source byte threshold above which the full {@link LegacyDbDescriptor}
120
+ * staging `copyFileSync` backup is SKIPPED (T11838). The legacy source is
121
+ * archived (renamed into `_archive/`), not deleted, on success — so a redundant
122
+ * full-file backup of a large source only doubles its disk + I/O cost. Sources
123
+ * at or below this threshold still get the staging backup (cheap rollback
124
+ * safety for the common small-fleet case).
125
+ */
126
+ readonly stagingCopyThresholdBytes: number;
104
127
  /** Absolute path to the staging directory. */
105
128
  readonly stagingDir: string;
106
129
  /** Whether a staging directory from a previous run was found (resume mode). */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/store/exodus/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/store/exodus/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/store/exodus/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAqKH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,wCAAiD,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/store/exodus/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA4LH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,wCAAiD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"verify-migration.d.ts","sourceRoot":"","sources":["../../../src/store/exodus/verify-migration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAKH,OAAO,KAAK,EAIV,qBAAqB,EACtB,MAAM,qBAAqB,CAAC;AAK7B,OAAO,KAAK,EAAe,kBAAkB,EAAE,MAAM,YAAY,CAAC;AA2clE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,kBAAkB,EAAE,EAC7B,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,MAAM,EACpB,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GACjC,qBAAqB,CAqRvB"}
1
+ {"version":3,"file":"verify-migration.d.ts","sourceRoot":"","sources":["../../../src/store/exodus/verify-migration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAKH,OAAO,KAAK,EAIV,qBAAqB,EACtB,MAAM,qBAAqB,CAAC;AAU7B,OAAO,KAAK,EAAe,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAolBlE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,kBAAkB,EAAE,EAC7B,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,MAAM,EACpB,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GACjC,qBAAqB,CA+RvB"}
@@ -45,6 +45,7 @@ import { createRequire } from 'node:module';
45
45
  import { MIGRATION_ENUM_DRIFT_SAMPLE_LIMIT } from '@cleocode/contracts';
46
46
  import { getLogger } from '../../logger.js';
47
47
  import { openCleoDbSnapshot } from '../open-cleo-db.js';
48
+ import { buildDigestExpr, detectIsoGlobColumns, } from './column-transforms.js';
48
49
  import { resolveConsolidatedTableName, resolveTableTargetScope } from './table-name-map.js';
49
50
  const log = getLogger('verify-migration');
50
51
  const _require = createRequire(import.meta.url);
@@ -90,44 +91,89 @@ function orderByClause(db, tableName) {
90
91
  * 4). Returns `{ count: 0, hash: '' }` for virtual tables that cannot be
91
92
  * selected from, rather than throwing.
92
93
  *
94
+ * ## Source-side coercion (T11809 · AC2)
95
+ *
96
+ * When `transform` is provided (SOURCE side only), each column is SELECTed
97
+ * through {@link buildDigestExpr} — the SAME epoch→ISO / enum-normalize /
98
+ * non-finite-clamp transforms the migration applied — and aliased back to its
99
+ * original name so the canonical-key serialisation matches the (already
100
+ * canonical) target row. The TARGET side passes `transform === undefined` and
101
+ * digests its stored values raw.
102
+ *
93
103
  * @param db - Database handle to query.
94
104
  * @param tableName - Physical table name.
95
105
  * @param columns - Explicit column list in canonical order, or `null` for
96
106
  * `SELECT *` (used when there is no counterpart table to intersect with).
107
+ * @param transform - Source-side transform spec, or `undefined` for the raw
108
+ * (target-side) digest.
97
109
  * @returns `{ count, hash }` for the table.
98
110
  */
99
- function computeTableDigest(db, tableName, columns) {
100
- const { createHash } = _require('node:crypto');
101
- const hasher = createHash('sha256');
102
- const orderBy = orderByClause(db, tableName);
103
- const selectClause = columns !== null && columns.length > 0 ? columns.map((c) => `"${c}"`).join(', ') : '*';
104
- let rows;
111
+ function computeTableDigest(db, tableName, columns, transform) {
112
+ // Row count a cheap, set-based `COUNT(*)` that never materialises rows.
113
+ //
114
+ // This is the value the data-continuity gate ({@link isDataContinuityOk})
115
+ // consumes to decide deficit/surplus, so it MUST stay independent of the heavy
116
+ // content digest below: the migrate-time verify can then pass/fail on counts
117
+ // alone even when the digest is large or streaming-slow (T11834).
118
+ let count = 0;
105
119
  try {
106
- rows = db
107
- .prepare(`SELECT ${selectClause} FROM "${tableName}" ORDER BY ${orderBy}`)
108
- .all();
120
+ const row = db.prepare(`SELECT COUNT(*) AS c FROM "${tableName}"`).get();
121
+ count = Number(row?.c ?? 0);
109
122
  }
110
123
  catch (err) {
111
124
  const msg = err instanceof Error ? err.message : String(err);
112
- log.warn({ tableName, err: msg }, 'computeTableDigest: SELECT failed (possibly a virtual/FTS table) — treating as 0 rows');
125
+ log.warn({ tableName, err: msg }, 'computeTableDigest: COUNT(*) failed (possibly a virtual/FTS table) — treating as 0 rows');
113
126
  return { count: 0, hash: '' };
114
127
  }
115
- // Canonicalise each row's property order before hashing (T11782 · FIX A).
128
+ const { createHash } = _require('node:crypto');
129
+ const hasher = createHash('sha256');
130
+ const orderBy = orderByClause(db, tableName);
131
+ let selectClause;
132
+ if (columns !== null && columns.length > 0) {
133
+ selectClause = columns
134
+ .map((c) => {
135
+ if (transform === undefined)
136
+ return `"${c}"`;
137
+ // SOURCE side: route the raw value through the SAME transform migrate
138
+ // applied, aliased back to `c` so the row key matches the target side.
139
+ const srcType = transform.srcTypeByCol.get(c) ?? '';
140
+ const tgtCol = transform.tgtColByCol.get(c);
141
+ const expr = buildDigestExpr(transform.targetTableName, c, srcType, transform.isoGlobCols, tgtCol);
142
+ return `${expr} AS "${c}"`;
143
+ })
144
+ .join(', ');
145
+ }
146
+ else {
147
+ selectClause = '*';
148
+ }
149
+ // Content digest — STREAMED row-by-row through the statement iterator so peak
150
+ // heap stays bounded by ONE row regardless of table size (T11834).
151
+ //
152
+ // The prior implementation called `.all()`, materialising the ENTIRE table
153
+ // (SOURCE *and* TARGET) into a JS array — a multi-hundred-MB-to-GB allocation
154
+ // on a 697K-row / 1.7 GB-class table (e.g. `brain_weight_history`) that OOM'd
155
+ // the migrate-time verify and rolled back an otherwise-lossless cutover. The
156
+ // digest is a NON-FATAL diagnostic; the gate is driven by the `COUNT(*)` parity
157
+ // above + introduced-FK orphans, never this hash.
116
158
  //
117
- // `JSON.stringify(row)` serialises object keys in the SQLite driver's row
118
- // property-insertion order, which is NOT guaranteed identical between the
119
- // SOURCE snapshot and the TARGET snapshot for the same logical row (the two
120
- // handles may materialise columns in a different order). Identical data would
121
- // then digest to a DIFFERENT hash, producing a false `hashMatch === false`
122
- // and historically — false-negative aborts. Passing the SORTED key array as
123
- // `JSON.stringify`'s `replacer` forces a canonical, order-independent
124
- // serialisation so identical rows always digest identically.
125
- for (const row of rows) {
126
- const rowObj = row;
127
- hasher.update(JSON.stringify(rowObj, Object.keys(rowObj).sort()));
159
+ // Canonicalise each row's property order before hashing (T11782 · FIX A): pass
160
+ // the SORTED key array as `JSON.stringify`'s replacer so identical rows digest
161
+ // identically regardless of the driver's column-materialisation order on the
162
+ // two snapshots (otherwise a false `hashMatch === false`).
163
+ try {
164
+ const stmt = db.prepare(`SELECT ${selectClause} FROM "${tableName}" ORDER BY ${orderBy}`);
165
+ for (const row of stmt.iterate()) {
166
+ const rowObj = row;
167
+ hasher.update(JSON.stringify(rowObj, Object.keys(rowObj).sort()));
168
+ }
169
+ }
170
+ catch (err) {
171
+ const msg = err instanceof Error ? err.message : String(err);
172
+ log.warn({ tableName, err: msg }, 'computeTableDigest: streamed SELECT failed (possibly a virtual/FTS table) — digest skipped (COUNT(*) parity still enforced)');
173
+ return { count, hash: '' };
128
174
  }
129
175
  return {
130
- count: rows.length,
176
+ count,
131
177
  hash: hasher.digest('hex').slice(0, 32),
132
178
  };
133
179
  }
@@ -154,6 +200,40 @@ function sharedColumnsSorted(srcDb, srcTable, tgtDb, tgtTable) {
154
200
  return null;
155
201
  }
156
202
  }
203
+ /**
204
+ * Build the source-side {@link DigestTransformSpec} for a source→target table
205
+ * pair: the source column type affinities, the target's ISO-GLOB columns, and
206
+ * the target column metadata (NOT-NULL flag + default + affinity), keyed by the
207
+ * consolidated target name (the transform lookup key). The target metadata
208
+ * drives the NULL→NOT-NULL-default `COALESCE` mirroring (T11836).
209
+ *
210
+ * Returns `undefined` (raw digest, no transform) when the source `PRAGMA
211
+ * table_info` cannot be read — best-effort, biased to surfacing a real
212
+ * difference rather than masking one.
213
+ *
214
+ * @param srcDb - Source database handle.
215
+ * @param srcTable - Physical legacy source table name.
216
+ * @param tgtDb - Target database handle (consolidated cleo.db).
217
+ * @param targetTableName - Physical consolidated target table name.
218
+ * @returns A transform spec, or `undefined` when source metadata is unavailable.
219
+ */
220
+ function buildSourceDigestTransform(srcDb, srcTable, tgtDb, targetTableName) {
221
+ try {
222
+ const srcTypeByCol = new Map(srcDb.prepare(`PRAGMA table_info("${srcTable}")`).all().map((r) => [r.name, r.type]));
223
+ const isoGlobCols = detectIsoGlobColumns(tgtDb, targetTableName);
224
+ // Target column metadata (notnull + dflt_value + type) — drives the
225
+ // NULL→NOT-NULL-default COALESCE migrate applies, so a stored type default
226
+ // for a NULL source value digests as a MATCH (T11836).
227
+ const tgtColByCol = new Map(tgtDb.prepare(`PRAGMA table_info("${targetTableName}")`).all().map((r) => [
228
+ r.name,
229
+ { notnull: r.notnull, dflt_value: r.dflt_value, type: r.type },
230
+ ]));
231
+ return { targetTableName, srcTypeByCol, isoGlobCols, tgtColByCol };
232
+ }
233
+ catch {
234
+ return undefined;
235
+ }
236
+ }
157
237
  /**
158
238
  * List user tables in a DB (excluding SQLite internals + Drizzle journal).
159
239
  */
@@ -564,7 +644,12 @@ export function verifyMigration(sources, projectDbPath, globalDbPath, onProgress
564
644
  continue;
565
645
  }
566
646
  const cols = sharedColumnsSorted(srcSnap.db, legacyTableName, targetSnap.db, targetTableName);
567
- const srcDigest = computeTableDigest(srcSnap.db, legacyTableName, cols);
647
+ // Source-side coercion spec (T11809 · AC2): digest the SOURCE through
648
+ // the SAME transforms migrate applied (epoch→ISO, enum-normalize,
649
+ // non-finite clamp) so equal logical data digests EQUAL. The target
650
+ // side already holds canonical values and is digested raw.
651
+ const srcTransform = buildSourceDigestTransform(srcSnap.db, legacyTableName, targetSnap.db, targetTableName);
652
+ const srcDigest = computeTableDigest(srcSnap.db, legacyTableName, cols, srcTransform);
568
653
  const tgtDigest = computeTableDigest(targetSnap.db, targetTableName, cols);
569
654
  const countMatch = srcDigest.count === tgtDigest.count;
570
655
  const hashMatch = srcDigest.hash === tgtDigest.hash;