@cleocode/core 2026.6.7 → 2026.6.9
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/db/index.d.ts +5 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +5 -1
- package/dist/db/index.js.map +1 -1
- package/dist/docs/build-provenance-graph.d.ts +12 -0
- package/dist/docs/build-provenance-graph.d.ts.map +1 -1
- package/dist/docs/build-provenance-graph.js +52 -0
- package/dist/docs/build-provenance-graph.js.map +1 -1
- package/dist/docs/display-alias.d.ts +97 -0
- package/dist/docs/display-alias.d.ts.map +1 -0
- package/dist/docs/display-alias.js +136 -0
- package/dist/docs/display-alias.js.map +1 -0
- package/dist/docs/docs-read-model.d.ts +47 -0
- package/dist/docs/docs-read-model.d.ts.map +1 -1
- package/dist/docs/docs-read-model.js +40 -2
- package/dist/docs/docs-read-model.js.map +1 -1
- package/dist/docs/export-document.js +929 -732
- package/dist/docs/export-document.js.map +3 -3
- package/dist/docs/index.d.ts +6 -0
- package/dist/docs/index.d.ts.map +1 -1
- package/dist/docs/index.js +3 -0
- package/dist/docs/index.js.map +1 -1
- package/dist/docs/numbering.d.ts +29 -0
- package/dist/docs/numbering.d.ts.map +1 -1
- package/dist/docs/numbering.js +41 -0
- package/dist/docs/numbering.js.map +1 -1
- package/dist/docs/read-doc.d.ts +60 -0
- package/dist/docs/read-doc.d.ts.map +1 -0
- package/dist/docs/read-doc.js +188 -0
- package/dist/docs/read-doc.js.map +1 -0
- package/dist/docs/wikilinks.d.ts +119 -0
- package/dist/docs/wikilinks.d.ts.map +1 -0
- package/dist/docs/wikilinks.js +217 -0
- package/dist/docs/wikilinks.js.map +1 -0
- package/dist/internal.d.ts +3 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +2 -1
- package/dist/internal.js.map +1 -1
- package/dist/llm/plugin-facade.js +973 -778
- package/dist/llm/plugin-facade.js.map +3 -3
- package/dist/store/attachment-store.d.ts +5 -0
- package/dist/store/attachment-store.d.ts.map +1 -1
- package/dist/store/attachment-store.js +7 -1
- package/dist/store/attachment-store.js.map +1 -1
- package/dist/store/dual-scope-db.d.ts +83 -0
- package/dist/store/dual-scope-db.d.ts.map +1 -1
- package/dist/store/dual-scope-db.js +135 -6
- package/dist/store/dual-scope-db.js.map +1 -1
- package/dist/store/exodus/abort-events.d.ts +116 -0
- package/dist/store/exodus/abort-events.d.ts.map +1 -0
- package/dist/store/exodus/abort-events.js +130 -0
- package/dist/store/exodus/abort-events.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/repair-malformed-dbs.d.ts +87 -0
- package/dist/store/repair-malformed-dbs.d.ts.map +1 -0
- package/dist/store/repair-malformed-dbs.js +188 -0
- package/dist/store/repair-malformed-dbs.js.map +1 -0
- package/dist/store/schema/attachments.d.ts +149 -0
- package/dist/store/schema/attachments.d.ts.map +1 -1
- package/dist/store/schema/attachments.js +93 -0
- package/dist/store/schema/attachments.js.map +1 -1
- package/migrations/drizzle-tasks/20260605000001_t11826-docs-wikilinks/migration.sql +110 -0
- package/migrations/drizzle-tasks/20260606000001_t11875-attachments-display-alias/migration.sql +46 -0
- package/package.json +12 -12
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed, process-local event channel for exodus-on-open ABORTS (T11828 · DHQ-059).
|
|
3
|
+
*
|
|
4
|
+
* ## Why this exists
|
|
5
|
+
*
|
|
6
|
+
* The exodus-on-open data-continuity gate ({@link maybeRunExodusOnOpen}) can
|
|
7
|
+
* ABORT a first-open auto-migration when the parity verify fails or the copy
|
|
8
|
+
* errors mid-flight. On abort the consolidated `cleo.db` is rolled back to EMPTY
|
|
9
|
+
* and the legacy fleet is kept as the source of truth — so the handle the
|
|
10
|
+
* chokepoint hands back is live and success-shaped, but the data the caller
|
|
11
|
+
* expected is NOT in it.
|
|
12
|
+
*
|
|
13
|
+
* Before T11828 that abort surfaced ONLY as a `log.warn(...)` inside the open
|
|
14
|
+
* chokepoint. A MUTATING caller (e.g. `tasks.add`) therefore had no programmatic
|
|
15
|
+
* signal that its write was about to land in a consolidated DB that does not
|
|
16
|
+
* contain the user's real data — i.e. the write may not be durable against the
|
|
17
|
+
* source of truth. This module is the out-of-band surface: every abort is
|
|
18
|
+
* broadcast here so daemon/session/diagnostic subscribers can react, AND the
|
|
19
|
+
* abort detail is stamped onto the returned {@link DualScopeDbHandle} so a
|
|
20
|
+
* mutation path can detect it synchronously via {@link assertWriteDurable}.
|
|
21
|
+
*
|
|
22
|
+
* Read-only callers ignore the marker entirely — they get a valid handle and the
|
|
23
|
+
* empty-but-consistent consolidated DB, exactly as before.
|
|
24
|
+
*
|
|
25
|
+
* @module
|
|
26
|
+
* @task T11828 (DHQ-059 — surface exodus-on-open abort to mutating callers)
|
|
27
|
+
* @epic T11833
|
|
28
|
+
* @saga T11242 (SG-DB-SUBSTRATE-V2)
|
|
29
|
+
* @see packages/core/src/store/exodus/on-open.ts — where the abort originates
|
|
30
|
+
* @see packages/core/src/store/dual-scope-db.ts — where the marker is stamped + assertWriteDurable
|
|
31
|
+
*/
|
|
32
|
+
import { EventEmitter } from 'node:events';
|
|
33
|
+
import type { DualScope } from '../dual-scope-db.js';
|
|
34
|
+
/**
|
|
35
|
+
* Structured detail of an exodus-on-open abort, stamped onto the returned
|
|
36
|
+
* {@link DualScopeDbHandle} and broadcast on the {@link exodusAbortEvents}
|
|
37
|
+
* channel.
|
|
38
|
+
*
|
|
39
|
+
* @task T11828
|
|
40
|
+
* @public
|
|
41
|
+
*/
|
|
42
|
+
export interface ExodusAbortDetail {
|
|
43
|
+
/** The scope whose first-open auto-migration aborted. */
|
|
44
|
+
readonly scope: DualScope;
|
|
45
|
+
/** Absolute path to the consolidated `cleo.db` for that scope. */
|
|
46
|
+
readonly dbPath: string;
|
|
47
|
+
/** Human-readable abort reason (parity deficit, mid-copy failure, …). */
|
|
48
|
+
readonly reason: string;
|
|
49
|
+
/** Epoch-ms timestamp the abort was observed. */
|
|
50
|
+
readonly at: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Map of event name → listener argument tuple for the exodus-abort channel.
|
|
54
|
+
*
|
|
55
|
+
* @task T11828
|
|
56
|
+
*/
|
|
57
|
+
interface ExodusAbortEventMap {
|
|
58
|
+
/** Emitted once per exodus-on-open abort, after rollback completes. */
|
|
59
|
+
abort: [detail: ExodusAbortDetail];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Process-local emitter broadcasting every exodus-on-open ABORT.
|
|
63
|
+
*
|
|
64
|
+
* Subscribers (daemon liveness, session lifecycle, `cleo doctor exodus-health`)
|
|
65
|
+
* MAY listen for `'abort'` to react to a degraded first-open without coupling to
|
|
66
|
+
* the store chokepoint. Emission is best-effort and never throws into the open
|
|
67
|
+
* path — listener errors are swallowed by {@link emitExodusAbort}.
|
|
68
|
+
*
|
|
69
|
+
* @task T11828
|
|
70
|
+
* @public
|
|
71
|
+
*/
|
|
72
|
+
export declare const exodusAbortEvents: EventEmitter<ExodusAbortEventMap>;
|
|
73
|
+
/**
|
|
74
|
+
* Broadcast an exodus-on-open abort on the {@link exodusAbortEvents} channel and
|
|
75
|
+
* record it in the process-local per-scope registry.
|
|
76
|
+
*
|
|
77
|
+
* Best-effort: a throwing/synchronous listener must NOT propagate into the DB
|
|
78
|
+
* open path, so emission is wrapped. Returns `true` if at least one listener was
|
|
79
|
+
* notified (matching `EventEmitter.emit` semantics) — informational only.
|
|
80
|
+
*
|
|
81
|
+
* @param detail - The structured abort detail to broadcast.
|
|
82
|
+
* @returns `true` when the event had listeners; `false` otherwise.
|
|
83
|
+
*
|
|
84
|
+
* @task T11828
|
|
85
|
+
* @public
|
|
86
|
+
*/
|
|
87
|
+
export declare function emitExodusAbort(detail: ExodusAbortDetail): boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Return the recorded abort detail for `scope`, or — when `scope` is omitted —
|
|
90
|
+
* the most-recent abort across any scope. `undefined` when no abort is recorded.
|
|
91
|
+
*
|
|
92
|
+
* Used by the write-side guard to reject a mutation that would land in a
|
|
93
|
+
* consolidated DB the exodus-on-open gate left empty.
|
|
94
|
+
*
|
|
95
|
+
* @param scope - Optional scope filter; when omitted, returns any recorded abort.
|
|
96
|
+
* @returns The {@link ExodusAbortDetail}, or `undefined`.
|
|
97
|
+
*
|
|
98
|
+
* @task T11828
|
|
99
|
+
* @public
|
|
100
|
+
*/
|
|
101
|
+
export declare function getRecordedExodusAbort(scope?: DualScope): ExodusAbortDetail | undefined;
|
|
102
|
+
/**
|
|
103
|
+
* Clear recorded aborts — all scopes, or a single `scope`.
|
|
104
|
+
*
|
|
105
|
+
* Call after the aborted migration is resolved (a subsequent successful
|
|
106
|
+
* `cleo exodus migrate` / `cleo doctor repair`) so writes are no longer rejected,
|
|
107
|
+
* and in test teardown to isolate cases.
|
|
108
|
+
*
|
|
109
|
+
* @param scope - Optional scope to clear; when omitted, clears every scope.
|
|
110
|
+
*
|
|
111
|
+
* @task T11828
|
|
112
|
+
* @public
|
|
113
|
+
*/
|
|
114
|
+
export declare function clearExodusAborts(scope?: DualScope): void;
|
|
115
|
+
export {};
|
|
116
|
+
//# sourceMappingURL=abort-events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abort-events.d.ts","sourceRoot":"","sources":["../../../src/store/exodus/abort-events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,yDAAyD;IACzD,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,kEAAkE;IAClE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,UAAU,mBAAmB;IAC3B,uEAAuE;IACvE,KAAK,EAAE,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;CACpC;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,mCAA0C,CAAC;AAsBzE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAQlE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,CAAC,EAAE,SAAS,GAAG,iBAAiB,GAAG,SAAS,CAQvF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI,CAMzD"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed, process-local event channel for exodus-on-open ABORTS (T11828 · DHQ-059).
|
|
3
|
+
*
|
|
4
|
+
* ## Why this exists
|
|
5
|
+
*
|
|
6
|
+
* The exodus-on-open data-continuity gate ({@link maybeRunExodusOnOpen}) can
|
|
7
|
+
* ABORT a first-open auto-migration when the parity verify fails or the copy
|
|
8
|
+
* errors mid-flight. On abort the consolidated `cleo.db` is rolled back to EMPTY
|
|
9
|
+
* and the legacy fleet is kept as the source of truth — so the handle the
|
|
10
|
+
* chokepoint hands back is live and success-shaped, but the data the caller
|
|
11
|
+
* expected is NOT in it.
|
|
12
|
+
*
|
|
13
|
+
* Before T11828 that abort surfaced ONLY as a `log.warn(...)` inside the open
|
|
14
|
+
* chokepoint. A MUTATING caller (e.g. `tasks.add`) therefore had no programmatic
|
|
15
|
+
* signal that its write was about to land in a consolidated DB that does not
|
|
16
|
+
* contain the user's real data — i.e. the write may not be durable against the
|
|
17
|
+
* source of truth. This module is the out-of-band surface: every abort is
|
|
18
|
+
* broadcast here so daemon/session/diagnostic subscribers can react, AND the
|
|
19
|
+
* abort detail is stamped onto the returned {@link DualScopeDbHandle} so a
|
|
20
|
+
* mutation path can detect it synchronously via {@link assertWriteDurable}.
|
|
21
|
+
*
|
|
22
|
+
* Read-only callers ignore the marker entirely — they get a valid handle and the
|
|
23
|
+
* empty-but-consistent consolidated DB, exactly as before.
|
|
24
|
+
*
|
|
25
|
+
* @module
|
|
26
|
+
* @task T11828 (DHQ-059 — surface exodus-on-open abort to mutating callers)
|
|
27
|
+
* @epic T11833
|
|
28
|
+
* @saga T11242 (SG-DB-SUBSTRATE-V2)
|
|
29
|
+
* @see packages/core/src/store/exodus/on-open.ts — where the abort originates
|
|
30
|
+
* @see packages/core/src/store/dual-scope-db.ts — where the marker is stamped + assertWriteDurable
|
|
31
|
+
*/
|
|
32
|
+
import { EventEmitter } from 'node:events';
|
|
33
|
+
/**
|
|
34
|
+
* Process-local emitter broadcasting every exodus-on-open ABORT.
|
|
35
|
+
*
|
|
36
|
+
* Subscribers (daemon liveness, session lifecycle, `cleo doctor exodus-health`)
|
|
37
|
+
* MAY listen for `'abort'` to react to a degraded first-open without coupling to
|
|
38
|
+
* the store chokepoint. Emission is best-effort and never throws into the open
|
|
39
|
+
* path — listener errors are swallowed by {@link emitExodusAbort}.
|
|
40
|
+
*
|
|
41
|
+
* @task T11828
|
|
42
|
+
* @public
|
|
43
|
+
*/
|
|
44
|
+
export const exodusAbortEvents = new EventEmitter();
|
|
45
|
+
// An aborted first-open install can legitimately have many domain opens fire in
|
|
46
|
+
// one process (tasks, brain, conduit, …); each would re-broadcast. Raise the cap
|
|
47
|
+
// modestly above the default 10 so a busy session does not emit a spurious
|
|
48
|
+
// MaxListenersExceededWarning, while still flagging a genuine listener leak.
|
|
49
|
+
exodusAbortEvents.setMaxListeners(50);
|
|
50
|
+
/**
|
|
51
|
+
/**
|
|
52
|
+
* Process-local registry of the most recent abort detail per scope.
|
|
53
|
+
*
|
|
54
|
+
* Recorded on every {@link emitExodusAbort} so the write-side guard
|
|
55
|
+
* (`assertWriteDurable` via {@link insertIdempotent} / {@link upsertIdempotent})
|
|
56
|
+
* can detect a degraded first-open even when the caller no longer holds the
|
|
57
|
+
* original {@link DualScopeDbHandle} (e.g. domain modules that extract the native
|
|
58
|
+
* handle and discard the wrapper). Cleared by {@link clearExodusAborts} once the
|
|
59
|
+
* underlying migration is resolved (successful `cleo exodus migrate` / recovery)
|
|
60
|
+
* or in test teardown.
|
|
61
|
+
*/
|
|
62
|
+
const _abortedScopes = new Map();
|
|
63
|
+
/**
|
|
64
|
+
* Broadcast an exodus-on-open abort on the {@link exodusAbortEvents} channel and
|
|
65
|
+
* record it in the process-local per-scope registry.
|
|
66
|
+
*
|
|
67
|
+
* Best-effort: a throwing/synchronous listener must NOT propagate into the DB
|
|
68
|
+
* open path, so emission is wrapped. Returns `true` if at least one listener was
|
|
69
|
+
* notified (matching `EventEmitter.emit` semantics) — informational only.
|
|
70
|
+
*
|
|
71
|
+
* @param detail - The structured abort detail to broadcast.
|
|
72
|
+
* @returns `true` when the event had listeners; `false` otherwise.
|
|
73
|
+
*
|
|
74
|
+
* @task T11828
|
|
75
|
+
* @public
|
|
76
|
+
*/
|
|
77
|
+
export function emitExodusAbort(detail) {
|
|
78
|
+
_abortedScopes.set(detail.scope, detail);
|
|
79
|
+
try {
|
|
80
|
+
return exodusAbortEvents.emit('abort', detail);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// A misbehaving listener must never break the open path.
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Return the recorded abort detail for `scope`, or — when `scope` is omitted —
|
|
89
|
+
* the most-recent abort across any scope. `undefined` when no abort is recorded.
|
|
90
|
+
*
|
|
91
|
+
* Used by the write-side guard to reject a mutation that would land in a
|
|
92
|
+
* consolidated DB the exodus-on-open gate left empty.
|
|
93
|
+
*
|
|
94
|
+
* @param scope - Optional scope filter; when omitted, returns any recorded abort.
|
|
95
|
+
* @returns The {@link ExodusAbortDetail}, or `undefined`.
|
|
96
|
+
*
|
|
97
|
+
* @task T11828
|
|
98
|
+
* @public
|
|
99
|
+
*/
|
|
100
|
+
export function getRecordedExodusAbort(scope) {
|
|
101
|
+
if (scope !== undefined)
|
|
102
|
+
return _abortedScopes.get(scope);
|
|
103
|
+
// Most-recent across scopes (Map preserves insertion order; emit overwrites).
|
|
104
|
+
let latest;
|
|
105
|
+
for (const detail of _abortedScopes.values()) {
|
|
106
|
+
if (!latest || detail.at >= latest.at)
|
|
107
|
+
latest = detail;
|
|
108
|
+
}
|
|
109
|
+
return latest;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Clear recorded aborts — all scopes, or a single `scope`.
|
|
113
|
+
*
|
|
114
|
+
* Call after the aborted migration is resolved (a subsequent successful
|
|
115
|
+
* `cleo exodus migrate` / `cleo doctor repair`) so writes are no longer rejected,
|
|
116
|
+
* and in test teardown to isolate cases.
|
|
117
|
+
*
|
|
118
|
+
* @param scope - Optional scope to clear; when omitted, clears every scope.
|
|
119
|
+
*
|
|
120
|
+
* @task T11828
|
|
121
|
+
* @public
|
|
122
|
+
*/
|
|
123
|
+
export function clearExodusAborts(scope) {
|
|
124
|
+
if (scope !== undefined) {
|
|
125
|
+
_abortedScopes.delete(scope);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
_abortedScopes.clear();
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=abort-events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abort-events.js","sourceRoot":"","sources":["../../../src/store/exodus/abort-events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAgC3C;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,YAAY,EAAuB,CAAC;AAEzE,gFAAgF;AAChF,iFAAiF;AACjF,2EAA2E;AAC3E,6EAA6E;AAC7E,iBAAiB,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;AAEtC;;;;;;;;;;;GAWG;AACH,MAAM,cAAc,GAAG,IAAI,GAAG,EAAgC,CAAC;AAE/D;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,eAAe,CAAC,MAAyB;IACvD,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACzC,IAAI,CAAC;QACH,OAAO,iBAAiB,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,yDAAyD;QACzD,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAiB;IACtD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC1D,8EAA8E;IAC9E,IAAI,MAAqC,CAAC;IAC1C,KAAK,MAAM,MAAM,IAAI,cAAc,CAAC,MAAM,EAAE,EAAE,CAAC;QAC7C,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,EAAE;YAAE,MAAM,GAAG,MAAM,CAAC;IACzD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAiB;IACjD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC7B,OAAO;IACT,CAAC;IACD,cAAc,CAAC,KAAK,EAAE,CAAC;AACzB,CAAC"}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @task T11248 (E5 · SG-DB-SUBSTRATE-V2)
|
|
9
9
|
* @saga T11242
|
|
10
10
|
*/
|
|
11
|
+
export { clearExodusAborts, type ExodusAbortDetail, emitExodusAbort, exodusAbortEvents, getRecordedExodusAbort, } from './abort-events.js';
|
|
11
12
|
export { type ArchivedSourceResult, type ArchiveMigratedSourcesResult, archiveMigratedSources, archiveSourceDb, archiveStrandedResidue, detectStrandedResidue, type ExodusCompleteMarker, exodusArchiveDir, exodusMarkerPath, hasExodusCompleteMarker, type StrandedResidueEntry, writeExodusCompleteMarker, } from './archive.js';
|
|
12
13
|
export { type CountParityEntry, type CountParityResult, computeCountParity, } from './count-parity.js';
|
|
13
14
|
export { buildExodusHealth, type ExodusHealth, type ExodusScopeHealth, type ExodusScopeState, type ExodusSourceHealth, } from './health.js';
|
|
@@ -1 +1 @@
|
|
|
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,EACL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,iBAAiB,EACjB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAClF,OAAO,EACL,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,UAAU,GACX,MAAM,WAAW,CAAC;AACnB,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,iBAAiB,EACjB,KAAK,iBAAiB,EACtB,eAAe,EACf,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAC3B,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,EACL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,iBAAiB,EACjB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAClF,OAAO,EACL,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,UAAU,GACX,MAAM,WAAW,CAAC;AACnB,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 { clearExodusAborts, emitExodusAbort, exodusAbortEvents, getRecordedExodusAbort, } from './abort-events.js';
|
|
11
12
|
export { archiveMigratedSources, archiveSourceDb, archiveStrandedResidue, detectStrandedResidue, exodusArchiveDir, exodusMarkerPath, hasExodusCompleteMarker, writeExodusCompleteMarker, } from './archive.js';
|
|
12
13
|
export { computeCountParity, } from './count-parity.js';
|
|
13
14
|
export { buildExodusHealth, } from './health.js';
|
|
@@ -1 +1 @@
|
|
|
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,EAGL,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,iBAAiB,GAKlB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAClF,OAAO,EAIL,UAAU,GACX,MAAM,WAAW,CAAC;AACnB,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,EACL,iBAAiB,EAEjB,eAAe,EACf,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAGL,sBAAsB,EACtB,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EAErB,gBAAgB,EAChB,gBAAgB,EAChB,uBAAuB,EAEvB,yBAAyB,GAC1B,MAAM,cAAc,CAAC;AACtB,OAAO,EAGL,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,iBAAiB,GAKlB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAClF,OAAO,EAIL,UAAU,GACX,MAAM,WAAW,CAAC;AACnB,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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cleo doctor repair` orchestrator — detect malformed CLEO databases and
|
|
3
|
+
* restore each from its freshest validated snapshot (T11829 · DHQ-060).
|
|
4
|
+
*
|
|
5
|
+
* ## Why this exists
|
|
6
|
+
*
|
|
7
|
+
* The corruption-resilience pipeline ALREADY existed before T11829:
|
|
8
|
+
*
|
|
9
|
+
* - {@link recoverMalformedDb} (quarantine → snapshot-restore → `quick_check`),
|
|
10
|
+
* - `autoRecoverFromBackup` (catches `"database disk image is malformed"` at
|
|
11
|
+
* open and restores the freshest VACUUM snapshot), and
|
|
12
|
+
* - the operator verb `cleo backup recover <role>` ({@link runBackupRecover}).
|
|
13
|
+
*
|
|
14
|
+
* The ONLY gap vs the DHQ-060 ask was a discoverable `cleo doctor` entry point
|
|
15
|
+
* that DETECTS corruption across the fleet and repairs only what is broken. This
|
|
16
|
+
* module is that orchestrator — it adds NO new recovery logic. It probes each
|
|
17
|
+
* role's live DB with `PRAGMA quick_check` (the same probe the pipeline runs on
|
|
18
|
+
* snapshot candidates, via {@link probeSnapshot}) and delegates every actual
|
|
19
|
+
* repair to {@link runBackupRecover} → {@link recoverMalformedDb}.
|
|
20
|
+
*
|
|
21
|
+
* ## WAL-specific corruption is covered
|
|
22
|
+
*
|
|
23
|
+
* A torn WAL frame surfaces as `"database disk image is malformed"` and makes
|
|
24
|
+
* `PRAGMA quick_check` return a non-`ok` result — so {@link probeSnapshot}
|
|
25
|
+
* detects it. The quarantine step ({@link quarantineCorruptDb}) already moves the
|
|
26
|
+
* `-wal`/`-shm` sidecars alongside the main file, so the WAL case is fully
|
|
27
|
+
* handled by the existing pipeline; this orchestrator does not need to special-case it.
|
|
28
|
+
*
|
|
29
|
+
* @module
|
|
30
|
+
* @task T11829 (DHQ-060 — `cleo doctor repair` entry point over the recovery pipeline)
|
|
31
|
+
* @epic T11833
|
|
32
|
+
* @saga T11242 (SG-DB-SUBSTRATE-V2)
|
|
33
|
+
* @see packages/core/src/store/recover-malformed-db.ts — the recovery pipeline
|
|
34
|
+
* @see packages/core/src/store/backup-recover.ts — `cleo backup recover <role>` wrapper
|
|
35
|
+
*/
|
|
36
|
+
import { type DbRole, type DoctorRepairResult } from '@cleocode/contracts';
|
|
37
|
+
import { type RecoveryLogger } from './recover-malformed-db.js';
|
|
38
|
+
/**
|
|
39
|
+
* Options accepted by {@link repairMalformedDbs}.
|
|
40
|
+
*
|
|
41
|
+
* @task T11829
|
|
42
|
+
* @public
|
|
43
|
+
*/
|
|
44
|
+
export interface RepairMalformedDbsOptions {
|
|
45
|
+
/** Absolute path to the project root (resolves `<projectRoot>` inventory tokens). */
|
|
46
|
+
projectRoot: string;
|
|
47
|
+
/**
|
|
48
|
+
* Roles to inspect. When omitted, EVERY role in {@link DB_INVENTORY} whose live
|
|
49
|
+
* file exists is probed. An explicit single-role list (from `--role`) is probed
|
|
50
|
+
* even when absent (so the operator gets a clear "not present" report).
|
|
51
|
+
*/
|
|
52
|
+
roles?: DbRole[];
|
|
53
|
+
/**
|
|
54
|
+
* When `true`, detect + plan only — corruption is reported with
|
|
55
|
+
* `action: 'would-repair'` but NO quarantine/restore is performed.
|
|
56
|
+
*
|
|
57
|
+
* @default false
|
|
58
|
+
*/
|
|
59
|
+
dryRun?: boolean;
|
|
60
|
+
/** Pino-shaped logger for recovery announcements. */
|
|
61
|
+
logger: RecoveryLogger;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Detect and repair malformed CLEO databases across the fleet (T11829 · DHQ-060).
|
|
65
|
+
*
|
|
66
|
+
* For each requested role (or every present role in {@link DB_INVENTORY} when none
|
|
67
|
+
* are specified) this probes the live DB with `PRAGMA quick_check` and, when
|
|
68
|
+
* corruption is found, restores the freshest validated snapshot via the existing
|
|
69
|
+
* {@link runBackupRecover} pipeline. No new recovery logic is introduced — this is
|
|
70
|
+
* the discoverable `cleo doctor repair` entry point requested by DHQ-060.
|
|
71
|
+
*
|
|
72
|
+
* @param opts - Repair inputs (project root, optional role filter, dry-run, logger).
|
|
73
|
+
* @returns A {@link DoctorRepairResult} aggregate with per-role outcomes.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* const report = repairMalformedDbs({ projectRoot: '/repo', logger });
|
|
78
|
+
* if (report.failedCount > 0) process.exitCode = 1;
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* @task T11829
|
|
82
|
+
* @epic T11833
|
|
83
|
+
* @saga T11242
|
|
84
|
+
* @public
|
|
85
|
+
*/
|
|
86
|
+
export declare function repairMalformedDbs(opts: RepairMalformedDbsOptions): DoctorRepairResult;
|
|
87
|
+
//# sourceMappingURL=repair-malformed-dbs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repair-malformed-dbs.d.ts","sourceRoot":"","sources":["../../src/store/repair-malformed-dbs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAGH,OAAO,EAAgB,KAAK,MAAM,EAAE,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzF,OAAO,EAAiB,KAAK,cAAc,EAAqB,MAAM,2BAA2B,CAAC;AAElG;;;;;GAKG;AACH,MAAM,WAAW,yBAAyB;IACxC,qFAAqF;IACrF,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,qDAAqD;IACrD,MAAM,EAAE,cAAc,CAAC;CACxB;AA2HD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,yBAAyB,GAAG,kBAAkB,CAiBtF"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cleo doctor repair` orchestrator — detect malformed CLEO databases and
|
|
3
|
+
* restore each from its freshest validated snapshot (T11829 · DHQ-060).
|
|
4
|
+
*
|
|
5
|
+
* ## Why this exists
|
|
6
|
+
*
|
|
7
|
+
* The corruption-resilience pipeline ALREADY existed before T11829:
|
|
8
|
+
*
|
|
9
|
+
* - {@link recoverMalformedDb} (quarantine → snapshot-restore → `quick_check`),
|
|
10
|
+
* - `autoRecoverFromBackup` (catches `"database disk image is malformed"` at
|
|
11
|
+
* open and restores the freshest VACUUM snapshot), and
|
|
12
|
+
* - the operator verb `cleo backup recover <role>` ({@link runBackupRecover}).
|
|
13
|
+
*
|
|
14
|
+
* The ONLY gap vs the DHQ-060 ask was a discoverable `cleo doctor` entry point
|
|
15
|
+
* that DETECTS corruption across the fleet and repairs only what is broken. This
|
|
16
|
+
* module is that orchestrator — it adds NO new recovery logic. It probes each
|
|
17
|
+
* role's live DB with `PRAGMA quick_check` (the same probe the pipeline runs on
|
|
18
|
+
* snapshot candidates, via {@link probeSnapshot}) and delegates every actual
|
|
19
|
+
* repair to {@link runBackupRecover} → {@link recoverMalformedDb}.
|
|
20
|
+
*
|
|
21
|
+
* ## WAL-specific corruption is covered
|
|
22
|
+
*
|
|
23
|
+
* A torn WAL frame surfaces as `"database disk image is malformed"` and makes
|
|
24
|
+
* `PRAGMA quick_check` return a non-`ok` result — so {@link probeSnapshot}
|
|
25
|
+
* detects it. The quarantine step ({@link quarantineCorruptDb}) already moves the
|
|
26
|
+
* `-wal`/`-shm` sidecars alongside the main file, so the WAL case is fully
|
|
27
|
+
* handled by the existing pipeline; this orchestrator does not need to special-case it.
|
|
28
|
+
*
|
|
29
|
+
* @module
|
|
30
|
+
* @task T11829 (DHQ-060 — `cleo doctor repair` entry point over the recovery pipeline)
|
|
31
|
+
* @epic T11833
|
|
32
|
+
* @saga T11242 (SG-DB-SUBSTRATE-V2)
|
|
33
|
+
* @see packages/core/src/store/recover-malformed-db.ts — the recovery pipeline
|
|
34
|
+
* @see packages/core/src/store/backup-recover.ts — `cleo backup recover <role>` wrapper
|
|
35
|
+
*/
|
|
36
|
+
import { existsSync } from 'node:fs';
|
|
37
|
+
import { DB_INVENTORY } from '@cleocode/contracts';
|
|
38
|
+
import { BackupRecoverError, runBackupRecover } from './backup-recover.js';
|
|
39
|
+
import { probeSnapshot, resolveRoleDbPath } from './recover-malformed-db.js';
|
|
40
|
+
/**
|
|
41
|
+
* Probe a single role's live DB and, when malformed, repair it from snapshot.
|
|
42
|
+
*
|
|
43
|
+
* Pure delegation: detection via {@link probeSnapshot} (`PRAGMA quick_check`),
|
|
44
|
+
* repair via {@link runBackupRecover}. Never throws — a recovery failure is
|
|
45
|
+
* captured in the returned record's `action: 'failed'`.
|
|
46
|
+
*
|
|
47
|
+
* @internal
|
|
48
|
+
*/
|
|
49
|
+
function repairOneRole(role, opts) {
|
|
50
|
+
const dbPath = resolveRoleDbPath(role, { projectRoot: opts.projectRoot });
|
|
51
|
+
const present = existsSync(dbPath);
|
|
52
|
+
if (!present) {
|
|
53
|
+
return {
|
|
54
|
+
role,
|
|
55
|
+
dbPath,
|
|
56
|
+
present: false,
|
|
57
|
+
healthy: true,
|
|
58
|
+
action: 'skipped',
|
|
59
|
+
restoredFrom: null,
|
|
60
|
+
quarantinedTo: null,
|
|
61
|
+
dataLossWindowHours: null,
|
|
62
|
+
detail: 'live DB file not present on disk (nothing to repair)',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// DETECT — the same `PRAGMA quick_check` the pipeline runs on snapshots. A
|
|
66
|
+
// torn WAL frame ("database disk image is malformed") fails this probe.
|
|
67
|
+
const probe = probeSnapshot(dbPath);
|
|
68
|
+
if (probe.ok) {
|
|
69
|
+
return {
|
|
70
|
+
role,
|
|
71
|
+
dbPath,
|
|
72
|
+
present: true,
|
|
73
|
+
healthy: true,
|
|
74
|
+
action: 'skipped',
|
|
75
|
+
restoredFrom: null,
|
|
76
|
+
quarantinedTo: null,
|
|
77
|
+
dataLossWindowHours: null,
|
|
78
|
+
detail: 'PRAGMA quick_check passed — DB is healthy',
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// MALFORMED — plan or repair.
|
|
82
|
+
if (opts.dryRun === true) {
|
|
83
|
+
try {
|
|
84
|
+
// Dry-run delegates to the SAME pipeline (no mutation) for an honest plan.
|
|
85
|
+
const plan = runBackupRecover({
|
|
86
|
+
role,
|
|
87
|
+
projectRoot: opts.projectRoot,
|
|
88
|
+
logger: opts.logger,
|
|
89
|
+
dryRun: true,
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
role,
|
|
93
|
+
dbPath,
|
|
94
|
+
present: true,
|
|
95
|
+
healthy: false,
|
|
96
|
+
action: 'would-repair',
|
|
97
|
+
restoredFrom: plan.restoredFrom || null,
|
|
98
|
+
quarantinedTo: null,
|
|
99
|
+
dataLossWindowHours: plan.dataLossWindowHours,
|
|
100
|
+
detail: `malformed — would restore from ${plan.restoredFrom} (re-run without --dry-run to repair)`,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
return {
|
|
105
|
+
role,
|
|
106
|
+
dbPath,
|
|
107
|
+
present: true,
|
|
108
|
+
healthy: false,
|
|
109
|
+
action: 'failed',
|
|
110
|
+
restoredFrom: null,
|
|
111
|
+
quarantinedTo: null,
|
|
112
|
+
dataLossWindowHours: null,
|
|
113
|
+
detail: `malformed but NO valid snapshot to restore from: ${err instanceof BackupRecoverError ? err.message : String(err)}`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// EXECUTE — quarantine + restore + verify via the existing pipeline.
|
|
118
|
+
try {
|
|
119
|
+
const result = runBackupRecover({
|
|
120
|
+
role,
|
|
121
|
+
projectRoot: opts.projectRoot,
|
|
122
|
+
logger: opts.logger,
|
|
123
|
+
dryRun: false,
|
|
124
|
+
});
|
|
125
|
+
return {
|
|
126
|
+
role,
|
|
127
|
+
dbPath,
|
|
128
|
+
present: true,
|
|
129
|
+
healthy: false,
|
|
130
|
+
action: 'repaired',
|
|
131
|
+
restoredFrom: result.restoredFrom || null,
|
|
132
|
+
quarantinedTo: result.quarantinedTo || null,
|
|
133
|
+
dataLossWindowHours: result.dataLossWindowHours,
|
|
134
|
+
detail: `repaired — restored from ${result.restoredFrom}; corrupt DB quarantined at ${result.quarantinedTo}`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
return {
|
|
139
|
+
role,
|
|
140
|
+
dbPath,
|
|
141
|
+
present: true,
|
|
142
|
+
healthy: false,
|
|
143
|
+
action: 'failed',
|
|
144
|
+
restoredFrom: null,
|
|
145
|
+
quarantinedTo: null,
|
|
146
|
+
dataLossWindowHours: null,
|
|
147
|
+
detail: `malformed — recovery FAILED: ${err instanceof BackupRecoverError ? err.message : String(err)}`,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Detect and repair malformed CLEO databases across the fleet (T11829 · DHQ-060).
|
|
153
|
+
*
|
|
154
|
+
* For each requested role (or every present role in {@link DB_INVENTORY} when none
|
|
155
|
+
* are specified) this probes the live DB with `PRAGMA quick_check` and, when
|
|
156
|
+
* corruption is found, restores the freshest validated snapshot via the existing
|
|
157
|
+
* {@link runBackupRecover} pipeline. No new recovery logic is introduced — this is
|
|
158
|
+
* the discoverable `cleo doctor repair` entry point requested by DHQ-060.
|
|
159
|
+
*
|
|
160
|
+
* @param opts - Repair inputs (project root, optional role filter, dry-run, logger).
|
|
161
|
+
* @returns A {@link DoctorRepairResult} aggregate with per-role outcomes.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```ts
|
|
165
|
+
* const report = repairMalformedDbs({ projectRoot: '/repo', logger });
|
|
166
|
+
* if (report.failedCount > 0) process.exitCode = 1;
|
|
167
|
+
* ```
|
|
168
|
+
*
|
|
169
|
+
* @task T11829
|
|
170
|
+
* @epic T11833
|
|
171
|
+
* @saga T11242
|
|
172
|
+
* @public
|
|
173
|
+
*/
|
|
174
|
+
export function repairMalformedDbs(opts) {
|
|
175
|
+
const explicit = opts.roles !== undefined && opts.roles.length > 0;
|
|
176
|
+
const candidateRoles = explicit
|
|
177
|
+
? opts.roles
|
|
178
|
+
: DB_INVENTORY.map((e) => e.role).filter((role) => existsSync(resolveRoleDbPath(role, { projectRoot: opts.projectRoot })));
|
|
179
|
+
const roles = candidateRoles.map((role) => repairOneRole(role, opts));
|
|
180
|
+
return {
|
|
181
|
+
dryRun: opts.dryRun === true,
|
|
182
|
+
roles,
|
|
183
|
+
malformedCount: roles.filter((r) => !r.healthy).length,
|
|
184
|
+
repairedCount: roles.filter((r) => r.action === 'repaired').length,
|
|
185
|
+
failedCount: roles.filter((r) => r.action === 'failed').length,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=repair-malformed-dbs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repair-malformed-dbs.js","sourceRoot":"","sources":["../../src/store/repair-malformed-dbs.ts"],"names":[],"mappings":"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"}
|