@cleocode/core 2026.8.1 → 2026.8.2
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/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +57 -27
- package/dist/bootstrap.js.map +1 -1
- package/dist/db/index.d.ts +13 -0
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +13 -0
- package/dist/db/index.js.map +1 -1
- package/dist/docs/display-alias.js +1 -1
- package/dist/docs/display-alias.js.map +1 -1
- package/dist/docs/export-document.js +434 -365
- package/dist/docs/export-document.js.map +3 -3
- package/dist/docs/numbering.js +1 -1
- package/dist/docs/numbering.js.map +1 -1
- package/dist/docs/supersede.js +1 -1
- package/dist/docs/supersede.js.map +1 -1
- package/dist/injection.d.ts.map +1 -1
- package/dist/injection.js +44 -3
- package/dist/injection.js.map +1 -1
- package/dist/lifecycle/index.js +1 -1
- package/dist/lifecycle/index.js.map +1 -1
- package/dist/llm/plugin-facade.js +232 -96
- package/dist/llm/plugin-facade.js.map +3 -3
- package/dist/memory/brain-backfill.js +3 -3
- package/dist/memory/brain-backfill.js.map +1 -1
- package/dist/memory/brain-consolidator.js +2 -2
- package/dist/memory/brain-consolidator.js.map +1 -1
- package/dist/memory/brain-doctor.js +1 -1
- package/dist/memory/brain-doctor.js.map +1 -1
- package/dist/memory/brain-health-dashboard.js +1 -1
- package/dist/memory/brain-health-dashboard.js.map +1 -1
- package/dist/memory/brain-lifecycle.js +10 -10
- package/dist/memory/brain-lifecycle.js.map +1 -1
- package/dist/memory/brain-maintenance.js +1 -1
- package/dist/memory/brain-maintenance.js.map +1 -1
- package/dist/memory/brain-page-nodes.js +4 -4
- package/dist/memory/brain-page-nodes.js.map +1 -1
- package/dist/memory/brain-reasoning.js +1 -1
- package/dist/memory/brain-reasoning.js.map +1 -1
- package/dist/memory/brain-search.js +1 -1
- package/dist/memory/brain-search.js.map +1 -1
- package/dist/memory/brain-similarity.js +1 -1
- package/dist/memory/brain-similarity.js.map +1 -1
- package/dist/memory/brain-stdp.js +6 -6
- package/dist/memory/brain-stdp.js.map +1 -1
- package/dist/memory/brain-sweep-executor.js +1 -1
- package/dist/memory/brain-sweep-executor.js.map +1 -1
- package/dist/memory/brain-writer-handlers.js +2 -2
- package/dist/memory/brain-writer-handlers.js.map +1 -1
- package/dist/memory/claude-mem-migration.d.ts.map +1 -1
- package/dist/memory/claude-mem-migration.js +2 -2
- package/dist/memory/claude-mem-migration.js.map +1 -1
- package/dist/memory/engine-compat.js +4 -4
- package/dist/memory/engine-compat.js.map +1 -1
- package/dist/memory/extraction-gate.js +3 -3
- package/dist/memory/extraction-gate.js.map +1 -1
- package/dist/memory/graph-memory-bridge.js +9 -9
- package/dist/memory/graph-memory-bridge.js.map +1 -1
- package/dist/memory/graph-queries.js +4 -4
- package/dist/memory/graph-queries.js.map +1 -1
- package/dist/memory/llm-extraction.js +1 -1
- package/dist/memory/llm-extraction.js.map +1 -1
- package/dist/memory/memory-bridge.js +1 -1
- package/dist/memory/memory-bridge.js.map +1 -1
- package/dist/memory/nexus-plasticity.js +1 -1
- package/dist/memory/nexus-plasticity.js.map +1 -1
- package/dist/memory/observer-reflector.js +1 -1
- package/dist/memory/observer-reflector.js.map +1 -1
- package/dist/memory/pipeline-manifest-sqlite.d.ts.map +1 -1
- package/dist/memory/pipeline-manifest-sqlite.js +13 -3
- package/dist/memory/pipeline-manifest-sqlite.js.map +1 -1
- package/dist/memory/quality-feedback.js +4 -4
- package/dist/memory/quality-feedback.js.map +1 -1
- package/dist/memory/retrieval/increment-citation-counts.js +1 -1
- package/dist/memory/retrieval/increment-citation-counts.js.map +1 -1
- package/dist/memory/retrieval/log-retrieval.js +1 -1
- package/dist/memory/retrieval/log-retrieval.js.map +1 -1
- package/dist/memory/retrieval/observe.js +1 -1
- package/dist/memory/retrieval/observe.js.map +1 -1
- package/dist/memory/retrieval/search.js +1 -1
- package/dist/memory/retrieval/search.js.map +1 -1
- package/dist/memory/retrieval/timeline.js +1 -1
- package/dist/memory/retrieval/timeline.js.map +1 -1
- package/dist/memory/sleep-consolidation.js +4 -4
- package/dist/memory/sleep-consolidation.js.map +1 -1
- package/dist/memory/temporal-supersession.js +5 -5
- package/dist/memory/temporal-supersession.js.map +1 -1
- package/dist/memory/transcript-extractor.js +1 -1
- package/dist/memory/transcript-extractor.js.map +1 -1
- package/dist/memory/transcript-ingestor.js +1 -1
- package/dist/memory/transcript-ingestor.js.map +1 -1
- package/dist/memory/transcript-scanner.js +1 -1
- package/dist/memory/transcript-scanner.js.map +1 -1
- package/dist/nexus/living-brain.js +7 -7
- package/dist/nexus/living-brain.js.map +1 -1
- package/dist/nexus/tasks-bridge.js +4 -4
- package/dist/nexus/tasks-bridge.js.map +1 -1
- package/dist/release/reconcile.d.ts.map +1 -1
- package/dist/release/reconcile.js +5 -1
- package/dist/release/reconcile.js.map +1 -1
- package/dist/sagas/migrate-containment.d.ts.map +1 -1
- package/dist/sagas/migrate-containment.js +4 -2
- package/dist/sagas/migrate-containment.js.map +1 -1
- package/dist/scaffold/ensure-dirs.js +1 -1
- package/dist/scaffold/ensure-dirs.js.map +1 -1
- package/dist/selfimprove/fix-gen.js +235 -99
- package/dist/selfimprove/fix-gen.js.map +3 -3
- package/dist/sentient/dream-cycle.js +1 -1
- package/dist/sentient/dream-cycle.js.map +1 -1
- package/dist/sentient/hygiene-scan.js +1 -1
- package/dist/sentient/hygiene-scan.js.map +1 -1
- package/dist/sentient/propose-tick.js +3 -3
- package/dist/sentient/propose-tick.js.map +1 -1
- package/dist/sentient/stage-drift-tick.js +1 -1
- package/dist/sentient/stage-drift-tick.js.map +1 -1
- package/dist/sequence/index.js +1 -1
- package/dist/sequence/index.js.map +1 -1
- package/dist/store/agent-registry-store.d.ts.map +1 -1
- package/dist/store/agent-registry-store.js +71 -28
- package/dist/store/agent-registry-store.js.map +1 -1
- package/dist/store/attachment-store.js +3 -3
- package/dist/store/attachment-store.js.map +1 -1
- package/dist/store/conduit-sqlite.d.ts +21 -1
- package/dist/store/conduit-sqlite.d.ts.map +1 -1
- package/dist/store/conduit-sqlite.js +42 -56
- package/dist/store/conduit-sqlite.js.map +1 -1
- package/dist/store/dual-scope-db.d.ts +39 -0
- package/dist/store/dual-scope-db.d.ts.map +1 -1
- package/dist/store/dual-scope-db.js +26 -8
- package/dist/store/dual-scope-db.js.map +1 -1
- package/dist/store/memory-sqlite.d.ts +57 -3
- package/dist/store/memory-sqlite.d.ts.map +1 -1
- package/dist/store/memory-sqlite.js +70 -89
- package/dist/store/memory-sqlite.js.map +1 -1
- package/dist/store/nexus-sqlite.d.ts +42 -0
- package/dist/store/nexus-sqlite.d.ts.map +1 -1
- package/dist/store/nexus-sqlite.js +79 -115
- package/dist/store/nexus-sqlite.js.map +1 -1
- package/dist/store/ports/domain-binding.d.ts +233 -0
- package/dist/store/ports/domain-binding.d.ts.map +1 -0
- package/dist/store/ports/domain-binding.js +500 -0
- package/dist/store/ports/domain-binding.js.map +1 -0
- package/dist/store/ports/fk-integrity.d.ts +155 -0
- package/dist/store/ports/fk-integrity.d.ts.map +1 -0
- package/dist/store/ports/fk-integrity.js +182 -0
- package/dist/store/ports/fk-integrity.js.map +1 -0
- package/dist/store/role-accessors-impl.js +2 -2
- package/dist/store/role-accessors-impl.js.map +1 -1
- package/dist/store/skills-db.d.ts +19 -0
- package/dist/store/skills-db.d.ts.map +1 -1
- package/dist/store/skills-db.js +80 -72
- package/dist/store/skills-db.js.map +1 -1
- package/dist/store/sqlite-backup.js +2 -2
- package/dist/store/sqlite-backup.js.map +1 -1
- package/dist/store/sqlite-data-accessor.js +1 -1
- package/dist/store/sqlite-data-accessor.js.map +1 -1
- package/dist/store/sqlite.d.ts +72 -10
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +173 -160
- package/dist/store/sqlite.js.map +1 -1
- package/dist/store/tasks-sqlite.js +2 -2
- package/dist/store/tasks-sqlite.js.map +1 -1
- package/dist/system/health.d.ts +0 -4
- package/dist/system/health.d.ts.map +1 -1
- package/dist/system/health.js +58 -2
- package/dist/system/health.js.map +1 -1
- package/dist/upgrade.js +2 -2
- package/dist/upgrade.js.map +1 -1
- package/dist/validation/doctor/checks.d.ts +23 -1
- package/dist/validation/doctor/checks.d.ts.map +1 -1
- package/dist/validation/doctor/checks.js +79 -19
- package/dist/validation/doctor/checks.js.map +1 -1
- package/dist/worktree/list.js +1 -1
- package/dist/worktree/list.js.map +1 -1
- package/package.json +22 -12
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path-keyed domain binding registry — the single owner of every per-domain
|
|
3
|
+
* Drizzle wrapper in the CLEO runtime.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this module exists (E6-L13 · T12037)
|
|
6
|
+
*
|
|
7
|
+
* Before this leaf, each store facade (`sqlite.ts`, `memory-sqlite.ts`,
|
|
8
|
+
* `conduit-sqlite.ts`, `nexus-sqlite.ts`, `skills-db.ts`) owned its OWN
|
|
9
|
+
* process-global singleton quartet:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* let _db: NodeSQLiteDatabase | null = null;
|
|
13
|
+
* let _nativeDb: DatabaseSync | null = null;
|
|
14
|
+
* let _dbPath: string | null = null;
|
|
15
|
+
* let _initPromise: Promise<NodeSQLiteDatabase> | null = null;
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* Five independent caches over ONE shared `DatabaseSync` produced a documented
|
|
19
|
+
* class of defects:
|
|
20
|
+
*
|
|
21
|
+
* - **Last-project-wins** — `_dbPath` held a single project. Touching project B
|
|
22
|
+
* reset the singleton, so an interleaved project-A query re-opened and
|
|
23
|
+
* re-migrated on every alternation (thrash, not just slowness).
|
|
24
|
+
* - **Cross-domain staleness** — the tasks singleton could reference a
|
|
25
|
+
* `DatabaseSync` that the brain domain had already closed (T12019/T12020),
|
|
26
|
+
* surfacing as `database is not open` or, worse, a silently-nulled
|
|
27
|
+
* `sourceSessionId`.
|
|
28
|
+
* - **Band-aid retry loops** — both `getDb` and `getBrainDb` grew bounded
|
|
29
|
+
* re-acquisition loops (T12035) purely to paper over the above.
|
|
30
|
+
*
|
|
31
|
+
* This module replaces all five with ONE registry keyed by
|
|
32
|
+
* `${scope}::${canonical dbPath}::${domain}`, layered on the
|
|
33
|
+
* {@link CleoRuntime} store registry (T12036). A binding is valid only while
|
|
34
|
+
* the {@link ProjectStore} / {@link GlobalStore} instance it was established
|
|
35
|
+
* against is still the one the runtime hands out AND its native handle is
|
|
36
|
+
* open. Any eviction — a `close()`, a `_resetDualScopeDbCache`, an external
|
|
37
|
+
* teardown — yields a fresh store object, which invalidates every binding
|
|
38
|
+
* derived from it by identity comparison. No liveness polling, no retry loop,
|
|
39
|
+
* no "last opened project".
|
|
40
|
+
*
|
|
41
|
+
* ## What a domain owns after this leaf
|
|
42
|
+
*
|
|
43
|
+
* A domain owns its `establish` function — the schema reconciliation it must
|
|
44
|
+
* run against a native handle (legacy Drizzle wrapping, `runMigrations`,
|
|
45
|
+
* vec0 extension loading, seed rows). It no longer owns caching, path
|
|
46
|
+
* resolution, single-flight, or liveness. Those are this module's job.
|
|
47
|
+
*
|
|
48
|
+
* @packageDocumentation
|
|
49
|
+
* @task T12037 (E6-L13)
|
|
50
|
+
* @epic T11249 (E6)
|
|
51
|
+
* @saga T11242 (SG-DB-SUBSTRATE-V2)
|
|
52
|
+
*/
|
|
53
|
+
import { resolve } from 'node:path';
|
|
54
|
+
import { createCleoRuntime, resolveDualScopeDbPath, } from '../dual-scope-db.js';
|
|
55
|
+
/**
|
|
56
|
+
* The process-wide {@link CleoRuntime} instance backing every domain port.
|
|
57
|
+
*
|
|
58
|
+
* A single runtime is correct here: the runtime registry is itself path-keyed,
|
|
59
|
+
* so one instance serves every project the process touches. It is lazily
|
|
60
|
+
* constructed so importing this module has no side effects (a hard constraint
|
|
61
|
+
* — the Gate-13 chokepoint audit rejects import-time DB work).
|
|
62
|
+
*/
|
|
63
|
+
let _runtime = null;
|
|
64
|
+
/**
|
|
65
|
+
* Get (or lazily create) the process-wide {@link CleoRuntime}.
|
|
66
|
+
*
|
|
67
|
+
* @returns The shared runtime store registry.
|
|
68
|
+
*/
|
|
69
|
+
export function getCleoRuntime() {
|
|
70
|
+
if (!_runtime) {
|
|
71
|
+
_runtime = createCleoRuntime();
|
|
72
|
+
}
|
|
73
|
+
return _runtime;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Tear down the process-wide runtime: close every registry entry and drop
|
|
77
|
+
* every domain binding. The next {@link getCleoRuntime} call builds a fresh
|
|
78
|
+
* registry.
|
|
79
|
+
*
|
|
80
|
+
* Used by full-teardown paths (`closeAllDatabases`) and by tests that need a
|
|
81
|
+
* pristine process state between cases.
|
|
82
|
+
*/
|
|
83
|
+
export function resetCleoRuntime() {
|
|
84
|
+
releaseAllDomainBindings();
|
|
85
|
+
if (_runtime) {
|
|
86
|
+
try {
|
|
87
|
+
_runtime.closeAll();
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// Best-effort teardown — the registry is discarded either way.
|
|
91
|
+
}
|
|
92
|
+
_runtime = null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Established bindings, keyed `${scope}::${canonical dbPath}::${domain}`.
|
|
97
|
+
* Type-erased; {@link bindProjectDomain} re-applies the caller's type at the
|
|
98
|
+
* boundary, which is sound because the key includes the domain id and each
|
|
99
|
+
* domain passes exactly one `establish` shape.
|
|
100
|
+
*/
|
|
101
|
+
const _bindings = new Map();
|
|
102
|
+
/**
|
|
103
|
+
* In-flight `establish` calls, same keying as {@link _bindings}. Concurrent
|
|
104
|
+
* binders for one key share a single establishment (single-flight).
|
|
105
|
+
*/
|
|
106
|
+
const _inflight = new Map();
|
|
107
|
+
/**
|
|
108
|
+
* Build the composite registry key for a domain binding.
|
|
109
|
+
*
|
|
110
|
+
* @param scope - The dual-scope discriminator.
|
|
111
|
+
* @param dbPath - Canonical (already `path.resolve`d) database path.
|
|
112
|
+
* @param domain - Stable domain id (e.g. `'tasks'`, `'brain'`).
|
|
113
|
+
*/
|
|
114
|
+
function bindingKey(scope, dbPath, domain) {
|
|
115
|
+
return `${scope}::${dbPath}::${domain}`;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Extract the underlying `DatabaseSync` from a store's Drizzle handle.
|
|
119
|
+
*
|
|
120
|
+
* Drizzle's `node:sqlite` driver exposes the native connection as `$client`.
|
|
121
|
+
* This is the ONLY place that reach-through happens — domains receive the
|
|
122
|
+
* already-extracted handle.
|
|
123
|
+
*
|
|
124
|
+
* @param store - The runtime store to unwrap.
|
|
125
|
+
* @returns The live native connection.
|
|
126
|
+
* @throws If the Drizzle handle exposes no `$client`.
|
|
127
|
+
*/
|
|
128
|
+
function nativeOf(store) {
|
|
129
|
+
const native = store.db.$client;
|
|
130
|
+
if (!native) {
|
|
131
|
+
throw new Error(`T12037: CleoRuntime store for ${store.scope}::${store.dbPath} exposed no $client — ` +
|
|
132
|
+
'cannot bind a domain schema to it.');
|
|
133
|
+
}
|
|
134
|
+
return native;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Reuse the cached binding for `key` if it is still valid.
|
|
138
|
+
*
|
|
139
|
+
* A binding is valid iff it was established against the SAME store instance
|
|
140
|
+
* the runtime just returned (identity, not path — a reopened store is a new
|
|
141
|
+
* object) and that store's native connection is still open.
|
|
142
|
+
*
|
|
143
|
+
* @param key - Composite registry key.
|
|
144
|
+
* @param store - The store the runtime returned for this open.
|
|
145
|
+
* @returns The still-valid row, or `undefined` if it must be re-established.
|
|
146
|
+
*/
|
|
147
|
+
function reuseValid(key, store) {
|
|
148
|
+
const row = _bindings.get(key);
|
|
149
|
+
if (!row)
|
|
150
|
+
return undefined;
|
|
151
|
+
if (row.store !== store || !store.isOpen || !row.native.isOpen) {
|
|
152
|
+
_bindings.delete(key);
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
return row;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Establish (or reuse) `domain`'s binding against a store, single-flighted.
|
|
159
|
+
*
|
|
160
|
+
* @param key - Composite registry key.
|
|
161
|
+
* @param store - The store to bind against.
|
|
162
|
+
* @param establish - Domain schema reconciliation.
|
|
163
|
+
*/
|
|
164
|
+
async function bindAgainst(key, store, establish) {
|
|
165
|
+
const cached = reuseValid(key, store);
|
|
166
|
+
if (cached)
|
|
167
|
+
return cached;
|
|
168
|
+
const pending = _inflight.get(key);
|
|
169
|
+
if (pending) {
|
|
170
|
+
const row = await pending;
|
|
171
|
+
// The in-flight establishment may have raced an eviction; re-validate.
|
|
172
|
+
if (row.store === store && row.native.isOpen)
|
|
173
|
+
return row;
|
|
174
|
+
return bindAgainst(key, store, establish);
|
|
175
|
+
}
|
|
176
|
+
const native = nativeOf(store);
|
|
177
|
+
const flight = (async () => {
|
|
178
|
+
const db = await establish(native, store);
|
|
179
|
+
const row = { store, native, db };
|
|
180
|
+
// Publish only if this store is still the live one for the key. A close
|
|
181
|
+
// that landed during `establish` must not resurrect a dead binding.
|
|
182
|
+
if (store.isOpen && native.isOpen) {
|
|
183
|
+
_bindings.set(key, row);
|
|
184
|
+
}
|
|
185
|
+
return row;
|
|
186
|
+
})();
|
|
187
|
+
_inflight.set(key, flight);
|
|
188
|
+
try {
|
|
189
|
+
return await flight;
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
if (_inflight.get(key) === flight) {
|
|
193
|
+
_inflight.delete(key);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Maximum consecutive attempts to obtain a binding over a LIVE connection.
|
|
199
|
+
*
|
|
200
|
+
* The shared consolidated `DatabaseSync` can be closed in the microtask gap
|
|
201
|
+
* between the runtime resolving a store and `establish` finishing against it
|
|
202
|
+
* (another domain's `closeDb()`, a `_resetDualScopeDbCache`, a fire-and-forget
|
|
203
|
+
* brain write crossing a test boundary). One reacquisition is enough: closing
|
|
204
|
+
* the dead store evicts it from BOTH the runtime registry and the dual-scope
|
|
205
|
+
* chokepoint cache, so the retry necessarily opens a fresh connection.
|
|
206
|
+
*
|
|
207
|
+
* Before this module each facade carried its own copy of this loop
|
|
208
|
+
* (T12020 · T12035). It lives here now — one implementation, every domain.
|
|
209
|
+
*/
|
|
210
|
+
const MAX_LIVENESS_ATTEMPTS = 2;
|
|
211
|
+
/**
|
|
212
|
+
* Obtain a binding whose connection is guaranteed live, with bounded
|
|
213
|
+
* reacquisition.
|
|
214
|
+
*
|
|
215
|
+
* @param scope - Dual-scope discriminator, for the eviction filter.
|
|
216
|
+
* @param key - Composite registry key.
|
|
217
|
+
* @param dbPath - Canonical database path, for the eviction filter.
|
|
218
|
+
* @param openStore - Opens (or reuses) the store for this key.
|
|
219
|
+
* @param establish - Domain schema reconciliation.
|
|
220
|
+
* @returns A row whose `native` was open at publication.
|
|
221
|
+
* @throws When every attempt yielded a dead connection.
|
|
222
|
+
*/
|
|
223
|
+
async function bindLive(scope, key, dbPath, openStore, establish) {
|
|
224
|
+
let lastError;
|
|
225
|
+
for (let attempt = 0; attempt < MAX_LIVENESS_ATTEMPTS; attempt += 1) {
|
|
226
|
+
let store;
|
|
227
|
+
try {
|
|
228
|
+
store = await openStore();
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
// The runtime's OWN bounded reacquisition can exhaust when a sibling
|
|
232
|
+
// keeps closing the shared handle across a microtask boundary (a
|
|
233
|
+
// fire-and-forget brain write crossing a test teardown is the usual
|
|
234
|
+
// culprit). That is transient, not terminal: drop our bindings for the
|
|
235
|
+
// path so the chokepoint cache is not holding a corpse, and take one
|
|
236
|
+
// more pass. A genuinely broken open fails again and rethrows.
|
|
237
|
+
lastError = err;
|
|
238
|
+
releaseDomainBindings({ scope, dbPath });
|
|
239
|
+
if (attempt + 1 >= MAX_LIVENESS_ATTEMPTS)
|
|
240
|
+
throw err;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
const row = await bindAgainst(key, store, establish);
|
|
245
|
+
if (row.store.isOpen && row.native.isOpen)
|
|
246
|
+
return row;
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
// A connection closed mid-`establish` surfaces as "database is not
|
|
250
|
+
// open" from the migration/DDL path. Retry once against a fresh
|
|
251
|
+
// handle; a genuine schema error will fail again and rethrow below.
|
|
252
|
+
lastError = err;
|
|
253
|
+
if (store.isOpen)
|
|
254
|
+
throw err;
|
|
255
|
+
}
|
|
256
|
+
// Dead handle. Closing the store evicts the runtime entry AND the
|
|
257
|
+
// chokepoint cache entry, so the next attempt opens a fresh connection.
|
|
258
|
+
releaseDomainBindings({ scope, dbPath });
|
|
259
|
+
try {
|
|
260
|
+
store.close();
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
// Already closed — the eviction is what matters.
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (lastError !== undefined)
|
|
267
|
+
throw lastError;
|
|
268
|
+
throw new Error(`T12037: could not bind ${key} over a live connection after ` +
|
|
269
|
+
`${MAX_LIVENESS_ATTEMPTS} attempts — the consolidated handle was closed each time.`);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Bind a project-scope domain schema to the {@link ProjectStore} for `cwd`.
|
|
273
|
+
*
|
|
274
|
+
* This is the replacement for every `getXxxDb(cwd)` project-domain facade.
|
|
275
|
+
* The returned binding carries the store itself, so callers that need a
|
|
276
|
+
* cross-domain transaction hold an explicit handle rather than reaching for a
|
|
277
|
+
* process global.
|
|
278
|
+
*
|
|
279
|
+
* @typeParam TDb - The domain's Drizzle handle type.
|
|
280
|
+
* @param domain - Stable domain id; part of the registry key.
|
|
281
|
+
* @param cwd - Project working directory. Resolved to the canonical
|
|
282
|
+
* `cleo.db` path and forwarded as `exodusCwd` so a port open arms the
|
|
283
|
+
* exodus-on-open auto-migration exactly like the facade it replaces.
|
|
284
|
+
* @param establish - Reconcile the domain's schema against the native handle
|
|
285
|
+
* and return the domain-typed Drizzle instance. Called once per
|
|
286
|
+
* (path, domain, store instance); MUST be idempotent because a store
|
|
287
|
+
* eviction re-runs it.
|
|
288
|
+
* @returns The live {@link DomainBinding}.
|
|
289
|
+
*/
|
|
290
|
+
export async function bindProjectDomain(domain, cwd, establish) {
|
|
291
|
+
const dbPath = resolve(resolveDualScopeDbPath('project', cwd));
|
|
292
|
+
const row = await bindLive('project', bindingKey('project', dbPath, domain), dbPath, () => getCleoRuntime().openProject(dbPath, { exodusCwd: cwd }), establish);
|
|
293
|
+
return row;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Bind a project-scope domain schema to an EXPLICIT database path.
|
|
297
|
+
*
|
|
298
|
+
* Used by domains whose legacy lifecycle API accepted an on-disk path (test
|
|
299
|
+
* fixtures, snapshot inspection). Never arms exodus-on-open — an explicit
|
|
300
|
+
* path must not auto-migrate a legacy fleet into a fixture.
|
|
301
|
+
*
|
|
302
|
+
* @typeParam TDb - The domain's Drizzle handle type.
|
|
303
|
+
* @param domain - Stable domain id.
|
|
304
|
+
* @param dbPath - Absolute path to a consolidated `cleo.db`.
|
|
305
|
+
* @param establish - See {@link bindProjectDomain}.
|
|
306
|
+
*/
|
|
307
|
+
export async function bindProjectDomainAtPath(domain, dbPath, establish) {
|
|
308
|
+
const normalized = resolve(dbPath);
|
|
309
|
+
const row = await bindLive('project', bindingKey('project', normalized, domain), normalized, () => getCleoRuntime().openProject(normalized), establish);
|
|
310
|
+
return row;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Bind a global-scope domain schema to the {@link GlobalStore}.
|
|
314
|
+
*
|
|
315
|
+
* This is the replacement for every `getXxxDb()` global-domain facade
|
|
316
|
+
* (nexus, skills, agents, telemetry).
|
|
317
|
+
*
|
|
318
|
+
* @typeParam TDb - The domain's Drizzle handle type.
|
|
319
|
+
* @param domain - Stable domain id; part of the registry key.
|
|
320
|
+
* @param establish - See {@link bindProjectDomain}.
|
|
321
|
+
* @param cwd - Optional cwd forwarded as `exodusCwd` for the global scope's
|
|
322
|
+
* own legacy-fleet auto-migration.
|
|
323
|
+
*/
|
|
324
|
+
export async function bindGlobalDomain(domain, establish, cwd) {
|
|
325
|
+
const dbPath = resolve(resolveDualScopeDbPath('global'));
|
|
326
|
+
const row = await bindLive('global', bindingKey('global', dbPath, domain), dbPath, () => getCleoRuntime().openGlobal({ exodusCwd: cwd }), establish);
|
|
327
|
+
return row;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Bind a global-scope domain schema to an EXPLICIT database path.
|
|
331
|
+
*
|
|
332
|
+
* The path-aware sibling of {@link bindGlobalDomain}, for domains whose
|
|
333
|
+
* lifecycle API accepts an on-disk path — notably the skills registry's
|
|
334
|
+
* test-sandbox `{ path }` override. Never arms exodus-on-open.
|
|
335
|
+
*
|
|
336
|
+
* @typeParam TDb - The domain's Drizzle handle type.
|
|
337
|
+
* @param domain - Stable domain id.
|
|
338
|
+
* @param dbPath - Absolute path to a global-scope `cleo.db`.
|
|
339
|
+
* @param establish - See {@link bindProjectDomain}.
|
|
340
|
+
*/
|
|
341
|
+
export async function bindGlobalDomainAtPath(domain, dbPath, establish) {
|
|
342
|
+
const normalized = resolve(dbPath);
|
|
343
|
+
const row = await bindLive('global', bindingKey('global', normalized, domain), normalized, () => getCleoRuntime().openGlobalAt(normalized), establish);
|
|
344
|
+
return row;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Synchronously read an ALREADY-established binding at an EXPLICIT path.
|
|
348
|
+
*
|
|
349
|
+
* @typeParam TDb - The domain's Drizzle handle type.
|
|
350
|
+
* @param scope - Dual-scope discriminator.
|
|
351
|
+
* @param dbPath - Absolute database path.
|
|
352
|
+
* @param domain - Stable domain id.
|
|
353
|
+
*/
|
|
354
|
+
export function peekDomainAtPath(scope, dbPath, domain) {
|
|
355
|
+
const row = _bindings.get(bindingKey(scope, resolve(dbPath), domain));
|
|
356
|
+
if (!row?.native.isOpen)
|
|
357
|
+
return null;
|
|
358
|
+
return row;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Drop cached bindings.
|
|
362
|
+
*
|
|
363
|
+
* Dropping a binding does NOT close any connection — the runtime registry and
|
|
364
|
+
* the dual-scope chokepoint own connection lifecycle. This only forces the
|
|
365
|
+
* next bind to re-run the domain's `establish`.
|
|
366
|
+
*
|
|
367
|
+
* @param filter - Restrict the drop. Omit any field to match all values of it.
|
|
368
|
+
* `{ scope: 'project' }` drops every project-domain binding;
|
|
369
|
+
* `{ domain: 'brain' }` drops the brain binding for every project.
|
|
370
|
+
*/
|
|
371
|
+
export function releaseDomainBindings(filter) {
|
|
372
|
+
if (!filter) {
|
|
373
|
+
_bindings.clear();
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const wantPath = filter.dbPath === undefined ? undefined : resolve(filter.dbPath);
|
|
377
|
+
for (const key of [..._bindings.keys()]) {
|
|
378
|
+
const sep = key.indexOf('::');
|
|
379
|
+
const lastSep = key.lastIndexOf('::');
|
|
380
|
+
const scope = key.slice(0, sep);
|
|
381
|
+
const dbPath = key.slice(sep + 2, lastSep);
|
|
382
|
+
const domain = key.slice(lastSep + 2);
|
|
383
|
+
if (filter.scope !== undefined && scope !== filter.scope)
|
|
384
|
+
continue;
|
|
385
|
+
if (wantPath !== undefined && dbPath !== wantPath)
|
|
386
|
+
continue;
|
|
387
|
+
if (filter.domain !== undefined && domain !== filter.domain)
|
|
388
|
+
continue;
|
|
389
|
+
_bindings.delete(key);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Drop every cached binding in every scope. Equivalent to
|
|
394
|
+
* `releaseDomainBindings()` with no filter; named for call-site clarity in
|
|
395
|
+
* teardown paths.
|
|
396
|
+
*/
|
|
397
|
+
export function releaseAllDomainBindings() {
|
|
398
|
+
_bindings.clear();
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Snapshot of currently-established binding keys.
|
|
402
|
+
*
|
|
403
|
+
* Read-only diagnostic surface for `cleo health` / architecture tests that
|
|
404
|
+
* assert a process holds exactly one binding per (path, domain).
|
|
405
|
+
*/
|
|
406
|
+
export function boundDomainKeys() {
|
|
407
|
+
return new Set(_bindings.keys());
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Synchronously look up the native `DatabaseSync` of an ALREADY-established
|
|
411
|
+
* project-domain binding.
|
|
412
|
+
*
|
|
413
|
+
* This exists solely to keep the legacy synchronous native getters
|
|
414
|
+
* (`getNativeDb`, `getNativeTasksDb`, `getBrainNativeDb`) working while their
|
|
415
|
+
* call sites migrate to the async binding. Unlike the module-global they
|
|
416
|
+
* replace, this lookup is **path-keyed** — passing the caller's `cwd` returns
|
|
417
|
+
* that project's handle rather than "whichever project was opened last".
|
|
418
|
+
*
|
|
419
|
+
* Returns `null` when nothing is bound yet or the connection has since
|
|
420
|
+
* closed; callers must have awaited the domain's async binder first.
|
|
421
|
+
*
|
|
422
|
+
* @param domain - Stable domain id.
|
|
423
|
+
* @param cwd - Project working directory; resolved to the canonical path.
|
|
424
|
+
* @returns The live native connection, or `null`.
|
|
425
|
+
*
|
|
426
|
+
* @deprecated Read `.native` from the {@link DomainBinding} instead. Removed
|
|
427
|
+
* when the last synchronous native getter is retired (T12040 · E6-L16).
|
|
428
|
+
*/
|
|
429
|
+
export function boundProjectNative(domain, cwd) {
|
|
430
|
+
return peekProjectDomain(domain, cwd)?.native ?? null;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Synchronously read an ALREADY-established project-domain binding without
|
|
434
|
+
* opening anything.
|
|
435
|
+
*
|
|
436
|
+
* Returns `null` when the domain is unbound for that project or its
|
|
437
|
+
* connection has closed. Unlike {@link bindProjectDomain} this never triggers
|
|
438
|
+
* an open, so it is safe on synchronous code paths (accessor predicates,
|
|
439
|
+
* health probes) that must not force a migration.
|
|
440
|
+
*
|
|
441
|
+
* @typeParam TDb - The domain's Drizzle handle type, as passed to
|
|
442
|
+
* {@link bindProjectDomain}. Unchecked at runtime — the caller is
|
|
443
|
+
* responsible for using the same type it bound with.
|
|
444
|
+
* @param domain - Stable domain id.
|
|
445
|
+
* @param cwd - Project working directory.
|
|
446
|
+
*/
|
|
447
|
+
export function peekProjectDomain(domain, cwd) {
|
|
448
|
+
const dbPath = resolve(resolveDualScopeDbPath('project', cwd));
|
|
449
|
+
const row = _bindings.get(bindingKey('project', dbPath, domain));
|
|
450
|
+
if (row?.native.isOpen)
|
|
451
|
+
return row;
|
|
452
|
+
// An EXPLICIT `cwd` is a precise request: if that project is not bound, say
|
|
453
|
+
// so. Never silently answer with a different project's connection — that is
|
|
454
|
+
// the cross-project hazard this cutover exists to remove.
|
|
455
|
+
if (cwd !== undefined)
|
|
456
|
+
return null;
|
|
457
|
+
// No `cwd` means "the project". Ambient resolution can still miss when the
|
|
458
|
+
// caller bound an EXPLICIT project root that differs from the ambient one
|
|
459
|
+
// (a test fixture, a tool operating on another checkout). Fall back ONLY
|
|
460
|
+
// when the answer is unambiguous — exactly one project bound for this
|
|
461
|
+
// domain. With two or more we return null, forcing the caller to pass a
|
|
462
|
+
// `cwd`, which is precisely the situation where it must.
|
|
463
|
+
const suffix = `::${domain}`;
|
|
464
|
+
let sole;
|
|
465
|
+
for (const [key, candidate] of _bindings) {
|
|
466
|
+
if (!key.startsWith('project::') || !key.endsWith(suffix))
|
|
467
|
+
continue;
|
|
468
|
+
if (!candidate.native.isOpen)
|
|
469
|
+
continue;
|
|
470
|
+
if (sole)
|
|
471
|
+
return null; // ambiguous — the caller must disambiguate
|
|
472
|
+
sole = candidate;
|
|
473
|
+
}
|
|
474
|
+
return sole ?? null;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Synchronous sibling of {@link peekProjectDomain} for the global scope.
|
|
478
|
+
*
|
|
479
|
+
* @typeParam TDb - The domain's Drizzle handle type.
|
|
480
|
+
* @param domain - Stable domain id.
|
|
481
|
+
*/
|
|
482
|
+
export function peekGlobalDomain(domain) {
|
|
483
|
+
const dbPath = resolve(resolveDualScopeDbPath('global'));
|
|
484
|
+
const row = _bindings.get(bindingKey('global', dbPath, domain));
|
|
485
|
+
if (!row || !row.native.isOpen)
|
|
486
|
+
return null;
|
|
487
|
+
return row;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Synchronous sibling of {@link boundProjectNative} for the global scope.
|
|
491
|
+
*
|
|
492
|
+
* @param domain - Stable domain id.
|
|
493
|
+
* @returns The live native connection, or `null`.
|
|
494
|
+
*
|
|
495
|
+
* @deprecated See {@link boundProjectNative}.
|
|
496
|
+
*/
|
|
497
|
+
export function boundGlobalNative(domain) {
|
|
498
|
+
return peekGlobalDomain(domain)?.native ?? null;
|
|
499
|
+
}
|
|
500
|
+
//# sourceMappingURL=domain-binding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain-binding.js","sourceRoot":"","sources":["../../../src/store/ports/domain-binding.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Foreign-key integrity verification for the consolidated dual-scope
|
|
3
|
+
* `cleo.db` files.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this exists (E6-L10 · T11530)
|
|
6
|
+
*
|
|
7
|
+
* The E6 cutover consolidated eight standalone databases into two files
|
|
8
|
+
* (project `cleo.db` and global `cleo.db`) and re-pointed every cross-domain
|
|
9
|
+
* foreign key at the PREFIXED consolidated tables. Two properties must hold
|
|
10
|
+
* afterwards and neither is self-evident:
|
|
11
|
+
*
|
|
12
|
+
* 1. **Referential integrity survived the move.** Exodus copies tables in
|
|
13
|
+
* dependency order, but a partially-applied migration, a hand-edited row,
|
|
14
|
+
* or a legacy DB restored from a pre-consolidation backup can leave an
|
|
15
|
+
* orphan whose parent row never made the trip.
|
|
16
|
+
* 2. **The FKs point at the right tables.** SQLite resolves a `REFERENCES`
|
|
17
|
+
* clause lazily, so an FK naming a table that no longer exists is not an
|
|
18
|
+
* error at DDL time — it fails only when a write touches it. A schema that
|
|
19
|
+
* still references the retired bare `tasks` table looks fine until
|
|
20
|
+
* something inserts.
|
|
21
|
+
*
|
|
22
|
+
* `PRAGMA foreign_key_check` answers both: it walks every FK in the schema and
|
|
23
|
+
* returns one row per violation, including one whose `parent` names a table
|
|
24
|
+
* that does not exist. So a structural defect and a row-level orphan surface
|
|
25
|
+
* through the same path.
|
|
26
|
+
*
|
|
27
|
+
* The verification runs against the LIVE consolidated files, not a fixture —
|
|
28
|
+
* this is the check that proves a real installation is sound.
|
|
29
|
+
*
|
|
30
|
+
* @packageDocumentation
|
|
31
|
+
* @task T11530 (E6-L10)
|
|
32
|
+
* @epic T11249 (E6)
|
|
33
|
+
* @saga T11242 (SG-DB-SUBSTRATE-V2)
|
|
34
|
+
*/
|
|
35
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
36
|
+
import type { DualScope } from '../dual-scope-db.js';
|
|
37
|
+
/**
|
|
38
|
+
* One referential-integrity violation.
|
|
39
|
+
*
|
|
40
|
+
* Mirrors a `PRAGMA foreign_key_check` row, plus the scope it was found in so
|
|
41
|
+
* a combined project+global report stays attributable.
|
|
42
|
+
*
|
|
43
|
+
* @task T11530
|
|
44
|
+
*/
|
|
45
|
+
export interface ForeignKeyViolation {
|
|
46
|
+
/** Which consolidated `cleo.db` the violation was found in. */
|
|
47
|
+
readonly scope: DualScope;
|
|
48
|
+
/** The child table holding the orphan row. */
|
|
49
|
+
readonly table: string;
|
|
50
|
+
/**
|
|
51
|
+
* `rowid` of the orphan row, or `null` when the violation is structural
|
|
52
|
+
* (the referenced parent TABLE does not exist, so there is no single row).
|
|
53
|
+
*/
|
|
54
|
+
readonly rowid: number | null;
|
|
55
|
+
/** The parent table the foreign key references. */
|
|
56
|
+
readonly parent: string;
|
|
57
|
+
/**
|
|
58
|
+
* Index of the failing foreign key within the child table's FK list, as
|
|
59
|
+
* reported by `PRAGMA foreign_key_list`. `-1` for structural violations.
|
|
60
|
+
*/
|
|
61
|
+
readonly fkid: number;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Result of verifying one or both consolidated scopes.
|
|
65
|
+
*
|
|
66
|
+
* @task T11530
|
|
67
|
+
*/
|
|
68
|
+
export interface ForeignKeyIntegrityReport {
|
|
69
|
+
/** `true` when {@link violations} is empty. */
|
|
70
|
+
readonly ok: boolean;
|
|
71
|
+
/** The scopes actually checked. */
|
|
72
|
+
readonly scopes: readonly DualScope[];
|
|
73
|
+
/** Every violation found, in scope order. */
|
|
74
|
+
readonly violations: readonly ForeignKeyViolation[];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Run `PRAGMA foreign_key_check` against one open connection.
|
|
78
|
+
*
|
|
79
|
+
* @param native - An open consolidated `cleo.db` connection.
|
|
80
|
+
* @param scope - The scope label to attribute violations to.
|
|
81
|
+
* @returns Every violation the pragma reported.
|
|
82
|
+
*/
|
|
83
|
+
export declare function checkForeignKeys(native: DatabaseSync, scope: DualScope): readonly ForeignKeyViolation[];
|
|
84
|
+
/**
|
|
85
|
+
* Verify foreign-key integrity across the consolidated dual-scope databases.
|
|
86
|
+
*
|
|
87
|
+
* Opens each requested scope through the ProjectStore/GlobalStore ports — the
|
|
88
|
+
* same chokepoint the runtime uses — so the check runs against exactly the
|
|
89
|
+
* connection (and therefore the schema and pragmas) production code sees.
|
|
90
|
+
*
|
|
91
|
+
* The `establish` callbacks are deliberately inert: this is a read-only probe
|
|
92
|
+
* and must not reconcile or migrate anything as a side effect of checking.
|
|
93
|
+
*
|
|
94
|
+
* @param options - Which scopes to check and which project.
|
|
95
|
+
* @returns A report; `ok` is `true` only when ZERO violations were found.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* const report = await verifyForeignKeyIntegrity({ cwd: projectRoot });
|
|
100
|
+
* if (!report.ok) {
|
|
101
|
+
* for (const v of report.violations) {
|
|
102
|
+
* console.error(`${v.scope}: ${v.table} row ${v.rowid} → missing ${v.parent}`);
|
|
103
|
+
* }
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* @task T11530
|
|
108
|
+
*/
|
|
109
|
+
export declare function verifyForeignKeyIntegrity(options?: {
|
|
110
|
+
/** Project working directory. Defaults to the ambient project. */
|
|
111
|
+
readonly cwd?: string;
|
|
112
|
+
/** Scopes to check. Defaults to BOTH — the AC requires project AND global. */
|
|
113
|
+
readonly scopes?: readonly DualScope[];
|
|
114
|
+
}): Promise<ForeignKeyIntegrityReport>;
|
|
115
|
+
/**
|
|
116
|
+
* Outcome of a repair pass.
|
|
117
|
+
*
|
|
118
|
+
* @task T11530
|
|
119
|
+
*/
|
|
120
|
+
export interface ForeignKeyRepairReport {
|
|
121
|
+
/** Violations found before the repair ran. */
|
|
122
|
+
readonly found: readonly ForeignKeyViolation[];
|
|
123
|
+
/** Violations the repair deleted (parent FK declared `ON DELETE CASCADE`). */
|
|
124
|
+
readonly repaired: readonly ForeignKeyViolation[];
|
|
125
|
+
/**
|
|
126
|
+
* Violations left in place because the schema does NOT authorise deleting
|
|
127
|
+
* the child — the FK's `ON DELETE` is not `CASCADE`, so removing the row is
|
|
128
|
+
* a judgement call an operator must make.
|
|
129
|
+
*/
|
|
130
|
+
readonly skipped: readonly ForeignKeyViolation[];
|
|
131
|
+
/** `true` when a re-check after the repair reported zero violations. */
|
|
132
|
+
readonly ok: boolean;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Delete orphan rows whose foreign key declares `ON DELETE CASCADE`.
|
|
136
|
+
*
|
|
137
|
+
* These rows are the residue of a parent deletion that happened while FK
|
|
138
|
+
* enforcement was off — most commonly a pre-consolidation legacy database that
|
|
139
|
+
* exodus then copied verbatim. The schema already says they should not exist;
|
|
140
|
+
* this restores that invariant.
|
|
141
|
+
*
|
|
142
|
+
* Rows whose FK is NOT `CASCADE` are reported in `skipped` and left untouched.
|
|
143
|
+
*
|
|
144
|
+
* @param options - Which scopes to repair and which project.
|
|
145
|
+
* @returns What was found, repaired, and deliberately skipped.
|
|
146
|
+
*
|
|
147
|
+
* @task T11530
|
|
148
|
+
*/
|
|
149
|
+
export declare function repairForeignKeyViolations(options?: {
|
|
150
|
+
/** Project working directory. Defaults to the ambient project. */
|
|
151
|
+
readonly cwd?: string;
|
|
152
|
+
/** Scopes to repair. Defaults to BOTH. */
|
|
153
|
+
readonly scopes?: readonly DualScope[];
|
|
154
|
+
}): Promise<ForeignKeyRepairReport>;
|
|
155
|
+
//# sourceMappingURL=fk-integrity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fk-integrity.d.ts","sourceRoot":"","sources":["../../../src/store/ports/fk-integrity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGrD;;;;;;;GAOG;AACH,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,mDAAmD;IACnD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,MAAM,WAAW,yBAAyB;IACxC,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,mCAAmC;IACnC,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IACtC,6CAA6C;IAC7C,QAAQ,CAAC,UAAU,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACrD;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,SAAS,GACf,SAAS,mBAAmB,EAAE,CA6BhC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,yBAAyB,CAAC,OAAO,CAAC,EAAE;IACxD,kEAAkE;IAClE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;CACxC,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAcrC;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAClD;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACjD,wEAAwE;IACxE,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;CACtB;AA6BD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,0BAA0B,CAAC,OAAO,CAAC,EAAE;IACzD,kEAAkE;IAClE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;CACxC,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAuClC"}
|