@cleocode/core 2026.6.3 → 2026.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/export-document.js +626 -310
- package/dist/docs/export-document.js.map +3 -3
- package/dist/llm/catalog-cache.d.ts +3 -0
- package/dist/llm/catalog-cache.d.ts.map +1 -1
- package/dist/llm/catalog-cache.js.map +1 -1
- package/dist/llm/catalog-model-resolver.d.ts +89 -0
- package/dist/llm/catalog-model-resolver.d.ts.map +1 -0
- package/dist/llm/catalog-model-resolver.js +158 -0
- package/dist/llm/catalog-model-resolver.js.map +1 -0
- package/dist/llm/cli-ops.d.ts +14 -0
- package/dist/llm/cli-ops.d.ts.map +1 -1
- package/dist/llm/cli-ops.js +35 -0
- package/dist/llm/cli-ops.js.map +1 -1
- package/dist/llm/index.d.ts +3 -0
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +3 -0
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/oauth/pkce.js +27 -5
- package/dist/llm/oauth/pkce.js.map +1 -1
- package/dist/llm/plugin-facade.js +613 -325
- package/dist/llm/plugin-facade.js.map +3 -3
- package/dist/llm/provider-registry/builtin/openai.d.ts +11 -2
- package/dist/llm/provider-registry/builtin/openai.d.ts.map +1 -1
- package/dist/llm/provider-registry/builtin/openai.js +15 -3
- package/dist/llm/provider-registry/builtin/openai.js.map +1 -1
- package/dist/llm/system-resolver.d.ts +94 -0
- package/dist/llm/system-resolver.d.ts.map +1 -0
- package/dist/llm/system-resolver.js +165 -0
- package/dist/llm/system-resolver.js.map +1 -0
- package/dist/memory/dialectic-evaluator.d.ts +13 -6
- package/dist/memory/dialectic-evaluator.d.ts.map +1 -1
- package/dist/memory/dialectic-evaluator.js +18 -7
- package/dist/memory/dialectic-evaluator.js.map +1 -1
- package/dist/memory/llm-backend-resolver.d.ts +23 -3
- package/dist/memory/llm-backend-resolver.d.ts.map +1 -1
- package/dist/memory/llm-backend-resolver.js +135 -0
- package/dist/memory/llm-backend-resolver.js.map +1 -1
- package/dist/store/dual-scope-db.d.ts +20 -2
- package/dist/store/dual-scope-db.d.ts.map +1 -1
- package/dist/store/dual-scope-db.js +74 -7
- package/dist/store/dual-scope-db.js.map +1 -1
- package/dist/store/exodus/archive.d.ts +216 -0
- package/dist/store/exodus/archive.d.ts.map +1 -0
- package/dist/store/exodus/archive.js +314 -0
- package/dist/store/exodus/archive.js.map +1 -0
- package/dist/store/exodus/index.d.ts +1 -0
- package/dist/store/exodus/index.d.ts.map +1 -1
- package/dist/store/exodus/index.js +1 -0
- package/dist/store/exodus/index.js.map +1 -1
- package/dist/store/exodus/migrate.d.ts.map +1 -1
- package/dist/store/exodus/migrate.js +118 -24
- package/dist/store/exodus/migrate.js.map +1 -1
- package/dist/store/exodus/on-open.d.ts.map +1 -1
- package/dist/store/exodus/on-open.js +95 -34
- package/dist/store/exodus/on-open.js.map +1 -1
- package/dist/store/exodus/types.d.ts +10 -1
- package/dist/store/exodus/types.d.ts.map +1 -1
- package/dist/store/exodus/types.js.map +1 -1
- package/dist/store/exodus/verify-migration.d.ts.map +1 -1
- package/dist/store/exodus/verify-migration.js +12 -1
- package/dist/store/exodus/verify-migration.js.map +1 -1
- package/dist/store/sqlite.d.ts +16 -0
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +160 -39
- package/dist/store/sqlite.js.map +1 -1
- package/dist/validation/doctor/checks.d.ts +22 -0
- package/dist/validation/doctor/checks.d.ts.map +1 -1
- package/dist/validation/doctor/checks.js +67 -0
- package/dist/validation/doctor/checks.js.map +1 -1
- package/dist/validation/doctor/index.d.ts +1 -1
- package/dist/validation/doctor/index.d.ts.map +1 -1
- package/dist/validation/doctor/index.js +1 -1
- package/dist/validation/doctor/index.js.map +1 -1
- package/package.json +12 -12
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exodus source-DB ARCHIVE + completion-marker subsystem (T11777).
|
|
3
|
+
*
|
|
4
|
+
* ## Why this exists (stranded-residue corruption trigger)
|
|
5
|
+
*
|
|
6
|
+
* The exodus engine migrates from SIX legacy source DBs (project: `tasks.db`,
|
|
7
|
+
* `brain.db`, `conduit.db`; global: `nexus.db`, `signaldock.db`, `skills.db`)
|
|
8
|
+
* but, historically, NEVER retired them — `on-open.ts` literally notes "the file
|
|
9
|
+
* is never unlinked." Every cutover therefore STRANDS the legacy files, and each
|
|
10
|
+
* stranded file re-arms the `tasks_tasks=0` auto-recover / exodus-on-open
|
|
11
|
+
* corruption trigger (DHQ-052 · T11662): on the next open the consolidated DB
|
|
12
|
+
* looks empty-with-legacy-present and the hook re-fires.
|
|
13
|
+
*
|
|
14
|
+
* This module closes the loop. After a migration's lossless validation passes
|
|
15
|
+
* (row-count parity + integrity — `verifyMigration` / `isDataContinuityOk`), the
|
|
16
|
+
* consumed source DBs are ARCHIVED (moved, never deleted) into a per-scope
|
|
17
|
+
* `_archive/` directory, and a committed COMPLETION MARKER records the cutover.
|
|
18
|
+
* The marker becomes the durable "this scope is already migrated" signal so a
|
|
19
|
+
* re-appearing or stranded legacy file can never re-trigger exodus-on-open.
|
|
20
|
+
*
|
|
21
|
+
* ## Archive destinations (per scope, via the paths SSoT)
|
|
22
|
+
*
|
|
23
|
+
* - project sources → `<cleoDir>/_archive/` (e.g. `.cleo/_archive/`)
|
|
24
|
+
* - global sources → `<cleoHome>/_archive/` (e.g. `~/.local/share/cleo/_archive/`)
|
|
25
|
+
*
|
|
26
|
+
* Both are resolved through `resolveCleoDir(cwd)` / `getCleoHome()` — never a
|
|
27
|
+
* hardcoded `~/.local/share` (Paths SSoT · Gate 2 · D009).
|
|
28
|
+
*
|
|
29
|
+
* ## Reversibility + idempotency invariants
|
|
30
|
+
*
|
|
31
|
+
* - **Reversible** — archiving is an atomic `rename` (fallback copy+unlink
|
|
32
|
+
* across filesystems). Nothing is ever deleted; an operator can move a DB
|
|
33
|
+
* back out of `_archive/` to roll the cutover back.
|
|
34
|
+
* - **Idempotent** — a source that is already absent (already archived, or a
|
|
35
|
+
* fresh install that never had it) is a silent no-op. Re-running over an
|
|
36
|
+
* already-archived fleet does nothing and never throws.
|
|
37
|
+
* - **Never blind-move** — only sources the caller asserts were actually
|
|
38
|
+
* consumed + validated by the migration are archived. A source whose
|
|
39
|
+
* migration did not run is left untouched.
|
|
40
|
+
* - **Emergency-archive reconciliation** — this box was emergency-archived
|
|
41
|
+
* (`.cleo/_archive-legacy-postcutover-*` already holds `tasks.db` +
|
|
42
|
+
* `conduit.db`). When the canonical destination already contains a file with
|
|
43
|
+
* the same name, the incoming file is parked under a timestamped sibling name
|
|
44
|
+
* rather than clobbering the prior archive.
|
|
45
|
+
*
|
|
46
|
+
* @module
|
|
47
|
+
* @task T11777 (exodus archives all 6 legacy DBs post-validation + completion marker)
|
|
48
|
+
* @epic T11249 (E6)
|
|
49
|
+
* @saga T11242 (SG-DB-SUBSTRATE-V2)
|
|
50
|
+
* @see packages/core/src/store/exodus/on-open.ts — wires this into the validated success path
|
|
51
|
+
* @see packages/core/src/store/exodus/plan.ts — buildSourceDescriptors (the 6 sources)
|
|
52
|
+
*/
|
|
53
|
+
import { copyFileSync, existsSync, mkdirSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
54
|
+
import { basename, join } from 'node:path';
|
|
55
|
+
import { getLogger } from '../../logger.js';
|
|
56
|
+
import { getCleoHome, resolveCleoDir } from '../../paths.js';
|
|
57
|
+
import { getCleoVersion } from '../../scaffold/ensure-config.js';
|
|
58
|
+
const log = getLogger('exodus-archive');
|
|
59
|
+
/** Per-scope archive directory name (sibling of the migrated DBs). */
|
|
60
|
+
const ARCHIVE_DIR_NAME = '_archive';
|
|
61
|
+
/** Per-scope completion-marker filename written next to the consolidated cleo.db. */
|
|
62
|
+
const MARKER_FILENAME_BY_SCOPE = {
|
|
63
|
+
project: 'exodus-complete',
|
|
64
|
+
global: 'exodus-complete',
|
|
65
|
+
};
|
|
66
|
+
/** SQLite sidecar suffixes archived alongside the main DB file. */
|
|
67
|
+
const SIDECAR_SUFFIXES = ['-wal', '-shm'];
|
|
68
|
+
/**
|
|
69
|
+
* Resolve the directory that holds a scope's legacy source DBs (and therefore
|
|
70
|
+
* its archive + completion marker): project → `<cleoDir>`, global →
|
|
71
|
+
* `<cleoHome>`. Always via the paths SSoT.
|
|
72
|
+
*
|
|
73
|
+
* @param scope - Target scope.
|
|
74
|
+
* @param cwd - Working directory used to resolve the project `.cleo/` dir.
|
|
75
|
+
* @returns Absolute path to the scope's base directory.
|
|
76
|
+
*/
|
|
77
|
+
function scopeBaseDir(scope, cwd) {
|
|
78
|
+
return scope === 'project' ? resolveCleoDir(cwd) : getCleoHome();
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Absolute path to a scope's `_archive/` directory.
|
|
82
|
+
*
|
|
83
|
+
* @param scope - Target scope.
|
|
84
|
+
* @param cwd - Working directory used to resolve the project `.cleo/` dir.
|
|
85
|
+
* @returns Absolute path to `<scopeBase>/_archive/`.
|
|
86
|
+
*/
|
|
87
|
+
export function exodusArchiveDir(scope, cwd) {
|
|
88
|
+
return join(scopeBaseDir(scope, cwd), ARCHIVE_DIR_NAME);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Absolute path to a scope's exodus completion marker file.
|
|
92
|
+
*
|
|
93
|
+
* @param scope - Target scope.
|
|
94
|
+
* @param cwd - Working directory used to resolve the project `.cleo/` dir.
|
|
95
|
+
* @returns Absolute path to `<scopeBase>/exodus-complete`.
|
|
96
|
+
*/
|
|
97
|
+
export function exodusMarkerPath(scope, cwd) {
|
|
98
|
+
return join(scopeBaseDir(scope, cwd), MARKER_FILENAME_BY_SCOPE[scope]);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Return `true` if a scope's exodus completion marker exists on disk.
|
|
102
|
+
*
|
|
103
|
+
* Resolution-safe: when the project `.cleo/` dir cannot be resolved (e.g. `cwd`
|
|
104
|
+
* is not inside a CLEO project — `resolveCleoDir` throws), this returns `false`
|
|
105
|
+
* (no marker) rather than propagating, so the on-open trigger gate degrades to
|
|
106
|
+
* the source-file path safely instead of crashing the open.
|
|
107
|
+
*
|
|
108
|
+
* @param scope - Target scope.
|
|
109
|
+
* @param cwd - Working directory used to resolve the project `.cleo/` dir.
|
|
110
|
+
* @returns Whether `<scopeBase>/exodus-complete` exists.
|
|
111
|
+
*/
|
|
112
|
+
export function hasExodusCompleteMarker(scope, cwd) {
|
|
113
|
+
try {
|
|
114
|
+
return existsSync(exodusMarkerPath(scope, cwd));
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Write a scope's exodus completion marker atomically (write-then-rename).
|
|
122
|
+
*
|
|
123
|
+
* Idempotent: re-writing simply refreshes the marker (same path). The marker is
|
|
124
|
+
* the SSoT trigger-gate consulted by {@link maybeRunExodusOnOpen} — once present,
|
|
125
|
+
* a stranded/re-appearing legacy file cannot re-arm the auto-migration.
|
|
126
|
+
*
|
|
127
|
+
* @param scope - Scope being certified as migrated.
|
|
128
|
+
* @param archivedSources - Logical names of the sources archived for this scope.
|
|
129
|
+
* @param cwd - Working directory used to resolve the project dir.
|
|
130
|
+
* @returns The marker's absolute path.
|
|
131
|
+
*
|
|
132
|
+
* @task T11777
|
|
133
|
+
*/
|
|
134
|
+
export function writeExodusCompleteMarker(scope, archivedSources, cwd) {
|
|
135
|
+
const markerPath = exodusMarkerPath(scope, cwd);
|
|
136
|
+
const baseDir = scopeBaseDir(scope, cwd);
|
|
137
|
+
mkdirSync(baseDir, { recursive: true });
|
|
138
|
+
const marker = {
|
|
139
|
+
version: 1,
|
|
140
|
+
scope,
|
|
141
|
+
cleoVersion: getCleoVersion(),
|
|
142
|
+
completedAt: new Date().toISOString(),
|
|
143
|
+
archivedSources: [...archivedSources],
|
|
144
|
+
};
|
|
145
|
+
const tmpPath = `${markerPath}.tmp`;
|
|
146
|
+
writeFileSync(tmpPath, JSON.stringify(marker, null, 2) + '\n', 'utf8');
|
|
147
|
+
renameSync(tmpPath, markerPath);
|
|
148
|
+
log.info({ scope, markerPath, archivedSources }, 'exodus: wrote completion marker');
|
|
149
|
+
return markerPath;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Move a single file to `destDir`, atomically when possible.
|
|
153
|
+
*
|
|
154
|
+
* Uses `rename`; on a cross-filesystem `EXDEV` (or any rename failure) falls back
|
|
155
|
+
* to copy-then-unlink so the move still completes. When `destDir` already holds a
|
|
156
|
+
* file with the same name (e.g. a prior emergency archive), the incoming file is
|
|
157
|
+
* parked under a timestamped sibling name so the prior archive is never clobbered.
|
|
158
|
+
*
|
|
159
|
+
* @param srcPath - Absolute source file path (assumed to exist).
|
|
160
|
+
* @param destDir - Absolute archive directory (created if missing).
|
|
161
|
+
* @returns The absolute destination path the file landed at.
|
|
162
|
+
*/
|
|
163
|
+
function moveFileInto(srcPath, destDir) {
|
|
164
|
+
mkdirSync(destDir, { recursive: true });
|
|
165
|
+
let dest = join(destDir, basename(srcPath));
|
|
166
|
+
if (existsSync(dest)) {
|
|
167
|
+
// Do not clobber a prior archive (e.g. emergency-archived tasks.db). Park
|
|
168
|
+
// the incoming file under a timestamped sibling name instead.
|
|
169
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '').replace(/Z$/, 'Z');
|
|
170
|
+
dest = join(destDir, `${basename(srcPath)}.${stamp}`);
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
renameSync(srcPath, dest);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// Cross-filesystem (EXDEV) or other rename failure → copy + unlink fallback.
|
|
177
|
+
copyFileSync(srcPath, dest);
|
|
178
|
+
unlinkSync(srcPath);
|
|
179
|
+
}
|
|
180
|
+
return dest;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Archive ONE legacy source DB (and its `-wal` / `-shm` sidecars) into the
|
|
184
|
+
* scope's `_archive/` directory.
|
|
185
|
+
*
|
|
186
|
+
* Idempotent: if the main DB file is already absent, this is a no-op (the file
|
|
187
|
+
* was already archived, or never existed). Sidecars are archived best-effort and
|
|
188
|
+
* only when the main DB is present.
|
|
189
|
+
*
|
|
190
|
+
* @param source - The descriptor for the source DB to archive.
|
|
191
|
+
* @param cwd - Working directory used to resolve the project dir.
|
|
192
|
+
* @returns A {@link ArchivedSourceResult} describing what happened.
|
|
193
|
+
*
|
|
194
|
+
* @task T11777
|
|
195
|
+
*/
|
|
196
|
+
export function archiveSourceDb(source, cwd) {
|
|
197
|
+
// Idempotent no-op: nothing to archive (already archived or fresh install).
|
|
198
|
+
if (!existsSync(source.path)) {
|
|
199
|
+
return { name: source.name, sourcePath: source.path, archivedTo: null, action: 'absent' };
|
|
200
|
+
}
|
|
201
|
+
const destDir = exodusArchiveDir(source.targetScope, cwd);
|
|
202
|
+
const archivedTo = moveFileInto(source.path, destDir);
|
|
203
|
+
// Archive sidecars alongside the DB (best-effort — they may not exist).
|
|
204
|
+
for (const suffix of SIDECAR_SUFFIXES) {
|
|
205
|
+
const sidecar = `${source.path}${suffix}`;
|
|
206
|
+
if (existsSync(sidecar)) {
|
|
207
|
+
try {
|
|
208
|
+
moveFileInto(sidecar, destDir);
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
log.warn({ err, sidecar, sourceName: source.name }, 'exodus-archive: failed to archive sidecar (non-fatal)');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
log.info({ sourceName: source.name, sourcePath: source.path, archivedTo, scope: source.targetScope }, 'exodus-archive: archived legacy source DB');
|
|
216
|
+
return {
|
|
217
|
+
name: source.name,
|
|
218
|
+
sourcePath: source.path,
|
|
219
|
+
archivedTo,
|
|
220
|
+
action: 'archived',
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Archive every consumed legacy source DB AFTER a validated cutover and write a
|
|
225
|
+
* per-scope completion marker.
|
|
226
|
+
*
|
|
227
|
+
* **Never blind-moves**: only the descriptors passed in `consumed` are archived
|
|
228
|
+
* — the caller (the validated migrate/on-open success path) passes exactly the
|
|
229
|
+
* sources whose migration actually ran and passed parity. A completion marker is
|
|
230
|
+
* written for every scope represented in `consumed`, even if some of that scope's
|
|
231
|
+
* sources were already absent (already archived) — the marker certifies "this
|
|
232
|
+
* scope's cutover is done", which is true once parity passed.
|
|
233
|
+
*
|
|
234
|
+
* Idempotent + reversible (see module docs). Safe to call repeatedly.
|
|
235
|
+
*
|
|
236
|
+
* @param consumed - Source descriptors the migration consumed + validated.
|
|
237
|
+
* @param cwd - Working directory used to resolve the project dir.
|
|
238
|
+
* @returns A {@link ArchiveMigratedSourcesResult} with per-source + per-scope outcomes.
|
|
239
|
+
*
|
|
240
|
+
* @task T11777
|
|
241
|
+
*/
|
|
242
|
+
export function archiveMigratedSources(consumed, cwd) {
|
|
243
|
+
const results = [];
|
|
244
|
+
const scopes = new Set();
|
|
245
|
+
for (const source of consumed) {
|
|
246
|
+
scopes.add(source.targetScope);
|
|
247
|
+
results.push(archiveSourceDb(source, cwd));
|
|
248
|
+
}
|
|
249
|
+
const markersWritten = [];
|
|
250
|
+
for (const scope of scopes) {
|
|
251
|
+
const archivedForScope = consumed.filter((s) => s.targetScope === scope).map((s) => s.name);
|
|
252
|
+
writeExodusCompleteMarker(scope, archivedForScope, cwd);
|
|
253
|
+
markersWritten.push(scope);
|
|
254
|
+
}
|
|
255
|
+
return { sources: results, markersWritten };
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Detect stranded legacy source DBs: any of the six sources still present on
|
|
259
|
+
* disk for a scope whose exodus completion marker exists.
|
|
260
|
+
*
|
|
261
|
+
* Returns an empty array when no marker exists for either scope (a pre-cutover
|
|
262
|
+
* install where legacy DBs are still the live source of truth — NOT residue) or
|
|
263
|
+
* when every source for a marked scope has been archived.
|
|
264
|
+
*
|
|
265
|
+
* @param sources - The full legacy source descriptor list (from `buildExodusPlan`).
|
|
266
|
+
* @param cwd - Working directory used to resolve the project dir + markers.
|
|
267
|
+
* @returns The stranded entries (empty when clean).
|
|
268
|
+
*
|
|
269
|
+
* @task T11777
|
|
270
|
+
*/
|
|
271
|
+
export function detectStrandedResidue(sources, cwd) {
|
|
272
|
+
const markedScopes = new Set();
|
|
273
|
+
for (const scope of ['project', 'global']) {
|
|
274
|
+
if (hasExodusCompleteMarker(scope, cwd))
|
|
275
|
+
markedScopes.add(scope);
|
|
276
|
+
}
|
|
277
|
+
if (markedScopes.size === 0)
|
|
278
|
+
return [];
|
|
279
|
+
const stranded = [];
|
|
280
|
+
for (const source of sources) {
|
|
281
|
+
if (!markedScopes.has(source.targetScope))
|
|
282
|
+
continue;
|
|
283
|
+
if (existsSync(source.path)) {
|
|
284
|
+
stranded.push({ name: source.name, path: source.path, scope: source.targetScope });
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return stranded;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Archive stranded residue detected by {@link detectStrandedResidue}.
|
|
291
|
+
*
|
|
292
|
+
* This is the `--fix` action for the `cleo doctor exodus-residue` check. It reuses
|
|
293
|
+
* {@link archiveSourceDb} so the on-open success path and the doctor fix share one
|
|
294
|
+
* archive routine. Reversible (move, never delete) and idempotent.
|
|
295
|
+
*
|
|
296
|
+
* @param stranded - The stranded entries to archive.
|
|
297
|
+
* @param sources - The full source descriptor list (to map name → descriptor).
|
|
298
|
+
* @param cwd - Working directory used to resolve the project dir.
|
|
299
|
+
* @returns Per-source archive outcomes.
|
|
300
|
+
*
|
|
301
|
+
* @task T11777
|
|
302
|
+
*/
|
|
303
|
+
export function archiveStrandedResidue(stranded, sources, cwd) {
|
|
304
|
+
const byName = new Map(sources.map((s) => [s.name, s]));
|
|
305
|
+
const results = [];
|
|
306
|
+
for (const entry of stranded) {
|
|
307
|
+
const descriptor = byName.get(entry.name);
|
|
308
|
+
if (descriptor === undefined)
|
|
309
|
+
continue;
|
|
310
|
+
results.push(archiveSourceDb(descriptor, cwd));
|
|
311
|
+
}
|
|
312
|
+
return results;
|
|
313
|
+
}
|
|
314
|
+
//# sourceMappingURL=archive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive.js","sourceRoot":"","sources":["../../../src/store/exodus/archive.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @task T11248 (E5 · SG-DB-SUBSTRATE-V2)
|
|
9
9
|
* @saga T11242
|
|
10
10
|
*/
|
|
11
|
+
export { type ArchivedSourceResult, type ArchiveMigratedSourcesResult, archiveMigratedSources, archiveSourceDb, archiveStrandedResidue, detectStrandedResidue, type ExodusCompleteMarker, exodusArchiveDir, exodusMarkerPath, hasExodusCompleteMarker, type StrandedResidueEntry, writeExodusCompleteMarker, } from './archive.js';
|
|
11
12
|
export { clearExodusJournal, runExodusMigrate } from './migrate.js';
|
|
12
13
|
export { buildExodusPlan, deriveStagingDirName, sourcesPresent } from './plan.js';
|
|
13
14
|
export { runExodusStatus } from './status.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/store/exodus/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAClF,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,wBAAwB,EACxB,4BAA4B,EAC5B,aAAa,EACb,KAAK,mBAAmB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,4BAA4B,EAC5B,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,GACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/store/exodus/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,4BAA4B,EACjC,sBAAsB,EACtB,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,oBAAoB,EACzB,gBAAgB,EAChB,gBAAgB,EAChB,uBAAuB,EACvB,KAAK,oBAAoB,EACzB,yBAAyB,GAC1B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAClF,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,wBAAwB,EACxB,4BAA4B,EAC5B,aAAa,EACb,KAAK,mBAAmB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,4BAA4B,EAC5B,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,GACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @task T11248 (E5 · SG-DB-SUBSTRATE-V2)
|
|
9
9
|
* @saga T11242
|
|
10
10
|
*/
|
|
11
|
+
export { archiveMigratedSources, archiveSourceDb, archiveStrandedResidue, detectStrandedResidue, exodusArchiveDir, exodusMarkerPath, hasExodusCompleteMarker, writeExodusCompleteMarker, } from './archive.js';
|
|
11
12
|
export { clearExodusJournal, runExodusMigrate } from './migrate.js';
|
|
12
13
|
export { buildExodusPlan, deriveStagingDirName, sourcesPresent } from './plan.js';
|
|
13
14
|
export { runExodusStatus } from './status.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/store/exodus/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAClF,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,wBAAwB,EACxB,4BAA4B,EAC5B,aAAa,GAEd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,4BAA4B,GAY7B,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/store/exodus/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAGL,sBAAsB,EACtB,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EAErB,gBAAgB,EAChB,gBAAgB,EAChB,uBAAuB,EAEvB,yBAAyB,GAC1B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAClF,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,wBAAwB,EACxB,4BAA4B,EAC5B,aAAa,GAEd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,4BAA4B,GAY7B,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../../src/store/exodus/migrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgHG;
|
|
1
|
+
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../../src/store/exodus/migrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgHG;AAmBH,OAAO,KAAK,EAEV,mBAAmB,EACnB,UAAU,EAMX,MAAM,YAAY,CAAC;AAiEpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAiB9D;AAw9BD;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,UAAU,EAChB,iBAAiB,UAAQ,EACzB,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GACjC,OAAO,CAAC,mBAAmB,CAAC,CAoK9B"}
|
|
@@ -115,7 +115,7 @@ import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSy
|
|
|
115
115
|
import { join } from 'node:path';
|
|
116
116
|
import { getLogger } from '../../logger.js';
|
|
117
117
|
import { getCleoVersion } from '../../scaffold/ensure-config.js';
|
|
118
|
-
import {
|
|
118
|
+
import { openDualScopeDbAtPath } from '../dual-scope-db.js';
|
|
119
119
|
import { openCleoDbSnapshot } from '../open-cleo-db.js';
|
|
120
120
|
import { resolveConsolidatedTableName, resolveTableTargetScope } from './table-name-map.js';
|
|
121
121
|
import { EXODUS_TARGET_SCHEMA_VERSION } from './types.js';
|
|
@@ -419,6 +419,35 @@ function enumNormExpr(targetTableName, col, srcRef) {
|
|
|
419
419
|
const fn = ENUM_NORMALIZATIONS.get(key);
|
|
420
420
|
return fn ? fn(srcRef) : null;
|
|
421
421
|
}
|
|
422
|
+
const NUMERIC_CLAMPS = new Map([
|
|
423
|
+
// --- brain_weight_history.delta_weight (T11782) -------------------------
|
|
424
|
+
// +Inf → 1.0 (max canonical reinforcement), -Inf → -1.0 (max canonical
|
|
425
|
+
// depression), NaN → 0.0 (no-op delta). Finite values pass through.
|
|
426
|
+
[
|
|
427
|
+
'brain_weight_history.delta_weight',
|
|
428
|
+
(src) => `CASE` +
|
|
429
|
+
` WHEN ${src} = 9e999 THEN 1.0` +
|
|
430
|
+
` WHEN ${src} = -9e999 THEN -1.0` +
|
|
431
|
+
` WHEN ${src} != ${src} THEN 0.0` +
|
|
432
|
+
` ELSE ${src}` +
|
|
433
|
+
` END`,
|
|
434
|
+
],
|
|
435
|
+
]);
|
|
436
|
+
/**
|
|
437
|
+
* Return a SQL CASE expression that clamps non-finite legacy REAL values for
|
|
438
|
+
* `col` in `targetTableName` to a finite in-range value, or `null` when no
|
|
439
|
+
* clamp rule exists for this (table, column).
|
|
440
|
+
*
|
|
441
|
+
* @param targetTableName - Physical consolidated target table name.
|
|
442
|
+
* @param col - Column name.
|
|
443
|
+
* @param srcRef - SQL expression referencing the source column.
|
|
444
|
+
* @returns A SQL CASE expression string, or `null` if no rule applies.
|
|
445
|
+
*/
|
|
446
|
+
function numericClampExpr(targetTableName, col, srcRef) {
|
|
447
|
+
const key = `${targetTableName}.${col}`;
|
|
448
|
+
const fn = NUMERIC_CLAMPS.get(key);
|
|
449
|
+
return fn ? fn(srcRef) : null;
|
|
450
|
+
}
|
|
422
451
|
// ---------------------------------------------------------------------------
|
|
423
452
|
// Epoch-to-ISO coercion layer (ROOT CAUSE 1 fix — T11546)
|
|
424
453
|
// ---------------------------------------------------------------------------
|
|
@@ -558,12 +587,15 @@ function detectIsoGlobColumns(db, tableName, targetSchema = 'main') {
|
|
|
558
587
|
*
|
|
559
588
|
* 1. **Epoch→ISO-8601 coercion** (T11546): when the target has an ISO GLOB CHECK
|
|
560
589
|
* and the source column is INTEGER-typed.
|
|
561
|
-
* 2. **
|
|
590
|
+
* 2. **Non-finite numeric clamp** (T11782): when `NUMERIC_CLAMPS` has an entry
|
|
591
|
+
* for `(targetTableName, col)`, mapping `Inf`/`-Inf`/`NaN` to a finite in-range
|
|
592
|
+
* value so the row is not silently dropped.
|
|
593
|
+
* 3. **Enum-value normalization** (T11547): when `ENUM_NORMALIZATIONS` has an
|
|
562
594
|
* entry for `(targetTableName, col)`, producing a SQL CASE expression that
|
|
563
595
|
* maps legacy values to canonical enum members without losing semantics.
|
|
564
|
-
*
|
|
596
|
+
* 4. **NOT NULL coalesce** (T11533): for non-epoch, non-normalized columns whose
|
|
565
597
|
* target is NOT NULL with no schema default.
|
|
566
|
-
*
|
|
598
|
+
* 5. **Plain column reference** otherwise.
|
|
567
599
|
*
|
|
568
600
|
* The epoch→ISO conversion uses `buildEpochToIsoExpr` which detects the epoch
|
|
569
601
|
* scale (seconds vs milliseconds) per-row using a magnitude heuristic: values
|
|
@@ -605,7 +637,20 @@ function buildSelectExpr(attachAlias, legacyTable, targetTableName, col, srcType
|
|
|
605
637
|
}
|
|
606
638
|
return `${isoExpr} AS "${col}"`;
|
|
607
639
|
}
|
|
608
|
-
// Priority 2:
|
|
640
|
+
// Priority 2: Non-finite numeric clamp (T11782) — coerce Inf/-Inf/NaN legacy
|
|
641
|
+
// REAL values to a finite in-range value so the row is not silently dropped.
|
|
642
|
+
// (delta_weight is NOT NULL without a default — but the clamp always produces
|
|
643
|
+
// a non-NULL finite value for non-NULL input, so no extra COALESCE is needed;
|
|
644
|
+
// a genuinely NULL source still flows to the NOT NULL coalesce below.)
|
|
645
|
+
const clampExpr = numericClampExpr(targetTableName, col, srcRef);
|
|
646
|
+
if (clampExpr !== null) {
|
|
647
|
+
if (isNotNullWithoutDefault) {
|
|
648
|
+
const defLiteral = typeDefaultLiteral(tgtInfo.type);
|
|
649
|
+
return `COALESCE(${clampExpr}, ${defLiteral}) AS "${col}"`;
|
|
650
|
+
}
|
|
651
|
+
return `${clampExpr} AS "${col}"`;
|
|
652
|
+
}
|
|
653
|
+
// Priority 3: Enum-value normalization (T11547) — maps legacy enum values to
|
|
609
654
|
// canonical members so CHECK constraints accept them.
|
|
610
655
|
const normExpr = enumNormExpr(targetTableName, col, srcRef);
|
|
611
656
|
if (normExpr !== null) {
|
|
@@ -617,8 +662,8 @@ function buildSelectExpr(attachAlias, legacyTable, targetTableName, col, srcType
|
|
|
617
662
|
}
|
|
618
663
|
return `${normExpr} AS "${col}"`;
|
|
619
664
|
}
|
|
620
|
-
// Priority
|
|
621
|
-
// (T11533 fix preserved).
|
|
665
|
+
// Priority 4: Standard NOT NULL coalesce for non-epoch, non-clamped,
|
|
666
|
+
// non-normalized columns (T11533 fix preserved).
|
|
622
667
|
if (isNotNullWithoutDefault) {
|
|
623
668
|
const defLiteral = typeDefaultLiteral(tgtInfo.type);
|
|
624
669
|
return `COALESCE(${srcRef}, ${defLiteral}) AS "${col}"`;
|
|
@@ -767,13 +812,23 @@ function copyTableFromAttached(targetNativeDb, srcNativeDb, attachAlias, legacyT
|
|
|
767
812
|
if (normalizedCols.length > 0) {
|
|
768
813
|
log.info({ legacyTableName, targetTableName, sourceName, normalizedCols }, `Exodus: applying enum-value normalization for ${normalizedCols.length} column(s) (T11547)`);
|
|
769
814
|
}
|
|
815
|
+
// --- Step 5d: Detect non-finite numeric-clamp columns (T11782) -----------
|
|
816
|
+
//
|
|
817
|
+
// Log which columns have a numeric clamp rule (Inf/-Inf/NaN → finite) so the
|
|
818
|
+
// recovery of otherwise-dropped rows (e.g. brain_weight_history.delta_weight)
|
|
819
|
+
// is traceable in the migration journal.
|
|
820
|
+
const clampedCols = sharedColumns.filter((col) => NUMERIC_CLAMPS.has(`${targetTableName}.${col}`));
|
|
821
|
+
if (clampedCols.length > 0) {
|
|
822
|
+
log.info({ legacyTableName, targetTableName, sourceName, clampedCols }, `Exodus: applying non-finite numeric clamp for ${clampedCols.length} column(s) (T11782)`);
|
|
823
|
+
}
|
|
770
824
|
// --- Step 6: Build the SELECT expression list ---
|
|
771
825
|
//
|
|
772
826
|
// For each shared column, `buildSelectExpr` handles (priority order):
|
|
773
827
|
// 1. Epoch→ISO coercion when target has ISO GLOB CHECK and source is INTEGER (T11546)
|
|
774
|
-
// 2.
|
|
775
|
-
// 3.
|
|
776
|
-
// 4.
|
|
828
|
+
// 2. Non-finite numeric clamp (Inf/-Inf/NaN → finite in-range) (T11782)
|
|
829
|
+
// 3. Enum-value normalization for legacy values not in the consolidated CHECK (T11547)
|
|
830
|
+
// 4. COALESCE for NOT NULL target columns without schema defaults (T11533)
|
|
831
|
+
// 5. Plain column reference otherwise
|
|
777
832
|
const selectExprs = sharedColumns.map((col) => {
|
|
778
833
|
const srcType = srcTypeMap.get(col) ?? '';
|
|
779
834
|
const tgtInfo = tgtColMap.get(col);
|
|
@@ -875,7 +930,7 @@ function checkSchemaVersion(journal, forceCrossVersion) {
|
|
|
875
930
|
* @task T11531 (P0 attach-leak fix)
|
|
876
931
|
*/
|
|
877
932
|
export async function runExodusMigrate(plan, forceCrossVersion = false, onProgress) {
|
|
878
|
-
const { sources, stagingDir, diskPreflight, projectDbPath } = plan;
|
|
933
|
+
const { sources, stagingDir, diskPreflight, projectDbPath, globalDbPath } = plan;
|
|
879
934
|
// AC8: disk pre-flight
|
|
880
935
|
if (!diskPreflight) {
|
|
881
936
|
return {
|
|
@@ -919,6 +974,17 @@ export async function runExodusMigrate(plan, forceCrossVersion = false, onProgre
|
|
|
919
974
|
const backupPaths = [];
|
|
920
975
|
const allTableResults = [];
|
|
921
976
|
const lockedPaths = [];
|
|
977
|
+
// FIX D (T11782): the migrate engine opens the consolidated TARGET DBs on a
|
|
978
|
+
// DEDICATED, NON-cached connection (a second SQLite handle to the same file —
|
|
979
|
+
// WAL allows it) rather than the cached caller handle. The copy AND the
|
|
980
|
+
// parity-abort rollback operate ONLY on these dedicated connections, so a
|
|
981
|
+
// concurrent caller INSERT (`tasks.add`) on its OWN cached connection is
|
|
982
|
+
// physically OUTSIDE this migration's transaction and CANNOT be rolled back
|
|
983
|
+
// with an aborted migration. The handles are closed in the `finally` below to
|
|
984
|
+
// avoid a file-descriptor leak (a dedicated handle is never evicted by the
|
|
985
|
+
// singleton cache).
|
|
986
|
+
let projectHandle = null;
|
|
987
|
+
let globalHandle = null;
|
|
922
988
|
try {
|
|
923
989
|
// 1. Back up existing source DBs into staging dir and acquire advisory locks
|
|
924
990
|
for (const src of sources) {
|
|
@@ -934,13 +1000,17 @@ export async function runExodusMigrate(plan, forceCrossVersion = false, onProgre
|
|
|
934
1000
|
acquireAdvisoryLock(src.path);
|
|
935
1001
|
lockedPaths.push(src.path);
|
|
936
1002
|
}
|
|
937
|
-
// 2. Open
|
|
938
|
-
// This runs Drizzle migrations to create the target
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1003
|
+
// 2. Open the consolidated target DBs on a DEDICATED connection via the
|
|
1004
|
+
// chokepoint (FIX D). This runs Drizzle migrations to create the target
|
|
1005
|
+
// schema on an isolated handle, NOT the cached caller handle.
|
|
1006
|
+
onProgress?.('Opening DEDICATED project-scope cleo.db connection (running migrations)…');
|
|
1007
|
+
projectHandle = await openDualScopeDbAtPath('project', projectDbPath, undefined, {
|
|
1008
|
+
dedicated: true,
|
|
1009
|
+
});
|
|
1010
|
+
onProgress?.('Opening DEDICATED global-scope cleo.db connection (running migrations)…');
|
|
1011
|
+
globalHandle = await openDualScopeDbAtPath('global', globalDbPath, undefined, {
|
|
1012
|
+
dedicated: true,
|
|
1013
|
+
});
|
|
944
1014
|
// Extract the raw DatabaseSync from the Drizzle wrapper ($client pattern).
|
|
945
1015
|
function extractNativeDb(handle) {
|
|
946
1016
|
const drizzleHandle = handle.db;
|
|
@@ -969,8 +1039,6 @@ export async function runExodusMigrate(plan, forceCrossVersion = false, onProgre
|
|
|
969
1039
|
// Final journal update
|
|
970
1040
|
journal.updatedAt = new Date().toISOString();
|
|
971
1041
|
writeJournal(stagingDir, journal);
|
|
972
|
-
projectHandle.close();
|
|
973
|
-
globalHandle.close();
|
|
974
1042
|
return { ok: true, tables: allTableResults, stagingDir, backupPaths };
|
|
975
1043
|
}
|
|
976
1044
|
catch (err) {
|
|
@@ -979,6 +1047,21 @@ export async function runExodusMigrate(plan, forceCrossVersion = false, onProgre
|
|
|
979
1047
|
return { ok: false, tables: allTableResults, stagingDir, backupPaths, error };
|
|
980
1048
|
}
|
|
981
1049
|
finally {
|
|
1050
|
+
// FIX D (T11782): always close the DEDICATED migrate connections (success OR
|
|
1051
|
+
// failure) so the second SQLite handle does not leak a file descriptor. A
|
|
1052
|
+
// dedicated handle is never cached, so this is the only thing that closes it.
|
|
1053
|
+
try {
|
|
1054
|
+
projectHandle?.close();
|
|
1055
|
+
}
|
|
1056
|
+
catch {
|
|
1057
|
+
// ignore double-close
|
|
1058
|
+
}
|
|
1059
|
+
try {
|
|
1060
|
+
globalHandle?.close();
|
|
1061
|
+
}
|
|
1062
|
+
catch {
|
|
1063
|
+
// ignore double-close
|
|
1064
|
+
}
|
|
982
1065
|
// Release advisory locks
|
|
983
1066
|
for (const p of lockedPaths) {
|
|
984
1067
|
releaseAdvisoryLock(p);
|
|
@@ -1105,10 +1188,21 @@ async function migrateScope(scope, sources, targetNativeDb, journal, stagingDir,
|
|
|
1105
1188
|
skipped = true;
|
|
1106
1189
|
}
|
|
1107
1190
|
else if (copyResult.reason) {
|
|
1108
|
-
// No-swallow error:
|
|
1109
|
-
//
|
|
1110
|
-
//
|
|
1111
|
-
|
|
1191
|
+
// No-swallow error: rows dropped by a constraint (T11546). The
|
|
1192
|
+
// copy WAS attempted (skipped stays false) and the deficit MUST be
|
|
1193
|
+
// surfaced — never a silent 0-row "done". (T11782 · FIX C.)
|
|
1194
|
+
//
|
|
1195
|
+
// Record `partial` rather than `skipped`: the table is neither
|
|
1196
|
+
// intentionally excluded nor cleanly complete. `partial` keeps the
|
|
1197
|
+
// journal honest (a resume re-copies; it never masquerades as
|
|
1198
|
+
// `done`) WITHOUT, by itself, tripping a scope-wide rollback at
|
|
1199
|
+
// this layer. A genuine deficit on a data-bearing BASE table still
|
|
1200
|
+
// ABORTS the cutover downstream: the parity gate
|
|
1201
|
+
// (`isDataContinuityOk` via `verifyMigration`) compares row counts
|
|
1202
|
+
// and fails on any `targetCount < sourceCount` deficit regardless
|
|
1203
|
+
// of journal status. With FIX B (Inf clamp) this branch should
|
|
1204
|
+
// rarely fire — it is belt-and-suspenders.
|
|
1205
|
+
status = 'partial';
|
|
1112
1206
|
errorMsg = copyResult.reason;
|
|
1113
1207
|
// skipped stays false — the distinction is the reason field (data loss vs intentional skip)
|
|
1114
1208
|
}
|