@cleocode/core 2026.6.5 → 2026.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dispatch/contracts/output-contracts.d.ts +36 -0
- package/dist/dispatch/contracts/output-contracts.d.ts.map +1 -0
- package/dist/dispatch/contracts/output-contracts.js +38 -0
- package/dist/dispatch/contracts/output-contracts.js.map +1 -0
- package/dist/dispatch/describe-operation.d.ts +98 -0
- package/dist/dispatch/describe-operation.d.ts.map +1 -0
- package/dist/dispatch/describe-operation.js +101 -0
- package/dist/dispatch/describe-operation.js.map +1 -0
- package/dist/docs/export-document.js +933 -489
- package/dist/docs/export-document.js.map +3 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +2 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +6 -0
- package/dist/internal.js.map +1 -1
- package/dist/llm/api-mode.d.ts +64 -0
- package/dist/llm/api-mode.d.ts.map +1 -0
- package/dist/llm/api-mode.js +72 -0
- package/dist/llm/api-mode.js.map +1 -0
- package/dist/llm/api.d.ts.map +1 -1
- package/dist/llm/api.js +6 -37
- package/dist/llm/api.js.map +1 -1
- package/dist/llm/auxiliary-fallback.d.ts.map +1 -1
- package/dist/llm/auxiliary-fallback.js +24 -38
- package/dist/llm/auxiliary-fallback.js.map +1 -1
- package/dist/llm/index.d.ts +1 -3
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +1 -2
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/model-metadata.d.ts +14 -0
- package/dist/llm/model-metadata.d.ts.map +1 -1
- package/dist/llm/model-metadata.js +23 -0
- package/dist/llm/model-metadata.js.map +1 -1
- package/dist/llm/model-runner.d.ts +127 -0
- package/dist/llm/model-runner.d.ts.map +1 -0
- package/dist/llm/model-runner.js +312 -0
- package/dist/llm/model-runner.js.map +1 -0
- package/dist/llm/plugin-facade.js +30084 -1748
- package/dist/llm/plugin-facade.js.map +3 -3
- package/dist/llm/provider-registry/builtin/anthropic.d.ts.map +1 -1
- package/dist/llm/provider-registry/builtin/anthropic.js +4 -0
- package/dist/llm/provider-registry/builtin/anthropic.js.map +1 -1
- package/dist/llm/provider-registry/builtin/gemini.d.ts.map +1 -1
- package/dist/llm/provider-registry/builtin/gemini.js +4 -0
- package/dist/llm/provider-registry/builtin/gemini.js.map +1 -1
- package/dist/llm/provider-registry/builtin/ollama.d.ts.map +1 -1
- package/dist/llm/provider-registry/builtin/ollama.js +4 -0
- package/dist/llm/provider-registry/builtin/ollama.js.map +1 -1
- package/dist/llm/provider-registry/builtin/openai.d.ts.map +1 -1
- package/dist/llm/provider-registry/builtin/openai.js +6 -0
- package/dist/llm/provider-registry/builtin/openai.js.map +1 -1
- package/dist/llm/role-executor.d.ts +6 -5
- package/dist/llm/role-executor.d.ts.map +1 -1
- package/dist/llm/role-executor.js +40 -86
- package/dist/llm/role-executor.js.map +1 -1
- package/dist/llm/role-resolver.d.ts +28 -1
- package/dist/llm/role-resolver.d.ts.map +1 -1
- package/dist/llm/role-resolver.js +10 -0
- package/dist/llm/role-resolver.js.map +1 -1
- package/dist/llm/session-factory.d.ts +4 -6
- package/dist/llm/session-factory.d.ts.map +1 -1
- package/dist/llm/session-factory.js +6 -72
- package/dist/llm/session-factory.js.map +1 -1
- package/dist/llm/system-resolver.d.ts.map +1 -1
- package/dist/llm/system-resolver.js +6 -0
- package/dist/llm/system-resolver.js.map +1 -1
- package/dist/llm/tool-loop.d.ts.map +1 -1
- package/dist/llm/tool-loop.js +9 -32
- package/dist/llm/tool-loop.js.map +1 -1
- package/dist/llm/transports/index.d.ts +5 -3
- package/dist/llm/transports/index.d.ts.map +1 -1
- package/dist/llm/transports/index.js +5 -2
- package/dist/llm/transports/index.js.map +1 -1
- package/dist/reconciliation/reconciliation-engine.d.ts.map +1 -1
- package/dist/reconciliation/reconciliation-engine.js +3 -0
- package/dist/reconciliation/reconciliation-engine.js.map +1 -1
- package/dist/release/plan.d.ts +27 -0
- package/dist/release/plan.d.ts.map +1 -1
- package/dist/release/plan.js +36 -2
- package/dist/release/plan.js.map +1 -1
- package/dist/release/provenance-fk.d.ts +74 -0
- package/dist/release/provenance-fk.d.ts.map +1 -0
- package/dist/release/provenance-fk.js +122 -0
- package/dist/release/provenance-fk.js.map +1 -0
- package/dist/release/reconcile.d.ts +10 -0
- package/dist/release/reconcile.d.ts.map +1 -1
- package/dist/release/reconcile.js +107 -2
- package/dist/release/reconcile.js.map +1 -1
- package/dist/sticky/convert.d.ts.map +1 -1
- package/dist/sticky/convert.js +3 -0
- package/dist/sticky/convert.js.map +1 -1
- package/dist/store/exodus/column-transforms.d.ts +275 -0
- package/dist/store/exodus/column-transforms.d.ts.map +1 -0
- package/dist/store/exodus/column-transforms.js +478 -0
- package/dist/store/exodus/column-transforms.js.map +1 -0
- package/dist/store/exodus/count-parity.d.ts +71 -0
- package/dist/store/exodus/count-parity.d.ts.map +1 -0
- package/dist/store/exodus/count-parity.js +124 -0
- package/dist/store/exodus/count-parity.js.map +1 -0
- package/dist/store/exodus/health.d.ts +70 -0
- package/dist/store/exodus/health.d.ts.map +1 -0
- package/dist/store/exodus/health.js +130 -0
- package/dist/store/exodus/health.js.map +1 -0
- package/dist/store/exodus/index.d.ts +3 -0
- package/dist/store/exodus/index.d.ts.map +1 -1
- package/dist/store/exodus/index.js +3 -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 +103 -298
- package/dist/store/exodus/migrate.js.map +1 -1
- package/dist/store/exodus/plan.d.ts +48 -4
- package/dist/store/exodus/plan.d.ts.map +1 -1
- package/dist/store/exodus/plan.js +67 -9
- package/dist/store/exodus/plan.js.map +1 -1
- package/dist/store/exodus/seal.d.ts +69 -0
- package/dist/store/exodus/seal.d.ts.map +1 -0
- package/dist/store/exodus/seal.js +73 -0
- package/dist/store/exodus/seal.js.map +1 -0
- package/dist/store/exodus/types.d.ts +24 -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 +109 -24
- package/dist/store/exodus/verify-migration.js.map +1 -1
- package/dist/tasks/add.d.ts +13 -0
- package/dist/tasks/add.d.ts.map +1 -1
- package/dist/tasks/add.js +50 -18
- package/dist/tasks/add.js.map +1 -1
- package/dist/tasks/archive.d.ts.map +1 -1
- package/dist/tasks/archive.js +12 -7
- package/dist/tasks/archive.js.map +1 -1
- package/dist/tasks/child-disposition.d.ts +66 -0
- package/dist/tasks/child-disposition.d.ts.map +1 -0
- package/dist/tasks/child-disposition.js +80 -0
- package/dist/tasks/child-disposition.js.map +1 -0
- package/dist/tasks/delete-preview.js +1 -1
- package/dist/tasks/delete-preview.js.map +1 -1
- package/dist/tasks/deletion-strategy.d.ts +21 -3
- package/dist/tasks/deletion-strategy.d.ts.map +1 -1
- package/dist/tasks/deletion-strategy.js +61 -15
- package/dist/tasks/deletion-strategy.js.map +1 -1
- package/dist/tasks/engine-wrap.d.ts +8 -0
- package/dist/tasks/engine-wrap.d.ts.map +1 -1
- package/dist/tasks/engine-wrap.js +22 -9
- package/dist/tasks/engine-wrap.js.map +1 -1
- package/dist/tasks/update.d.ts.map +1 -1
- package/dist/tasks/update.js +12 -0
- package/dist/tasks/update.js.map +1 -1
- package/package.json +12 -12
- package/dist/llm/transports/openai.d.ts +0 -181
- package/dist/llm/transports/openai.d.ts.map +0 -1
- package/dist/llm/transports/openai.js +0 -645
- package/dist/llm/transports/openai.js.map +0 -1
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared column-value transform layer for the exodus migration.
|
|
3
|
+
*
|
|
4
|
+
* The exodus copy (`migrate.ts`) does not move source values byte-for-byte into
|
|
5
|
+
* the consolidated `cleo.db`: a handful of columns are TRANSFORMED on the way in
|
|
6
|
+
* so they satisfy the consolidated schema's CHECK constraints (which the legacy
|
|
7
|
+
* runtime DBs did not carry). Three transform classes exist:
|
|
8
|
+
*
|
|
9
|
+
* 1. **Epoch INTEGER → ISO-8601 TEXT** ({@link buildEpochToIsoExpr}) — for any
|
|
10
|
+
* target column carrying an ISO-GLOB CHECK ({@link detectIsoGlobColumns}).
|
|
11
|
+
* A per-row magnitude heuristic ({@link EPOCH_SECONDS_THRESHOLD}) classifies
|
|
12
|
+
* seconds vs milliseconds.
|
|
13
|
+
* 2. **Legacy enum → canonical member** ({@link ENUM_NORMALIZATIONS} +
|
|
14
|
+
* {@link enumNormExpr}) — maps pre-tightening enum aliases (e.g.
|
|
15
|
+
* `tasks_commits.conventional_type` `'style'`/`'merge'` → `'chore'`).
|
|
16
|
+
* 3. **Non-finite REAL → finite** ({@link NUMERIC_CLAMPS} +
|
|
17
|
+
* {@link numericClampExpr}) — `Inf`/`-Inf`/`NaN` → a finite in-range value
|
|
18
|
+
* (`brain_weight_history.delta_weight`).
|
|
19
|
+
*
|
|
20
|
+
* ## Why this module exists (T11809 · AC2)
|
|
21
|
+
*
|
|
22
|
+
* Before T11809 these transforms lived ONLY in `migrate.ts`. The parity verifier
|
|
23
|
+
* (`verify-migration.ts`) digested RAW source values against the TRANSFORMED
|
|
24
|
+
* target values, so every coerced column (epoch INTEGER vs ISO TEXT, legacy enum
|
|
25
|
+
* vs canonical, Inf vs clamped) produced a `hashMatch === false` even on a
|
|
26
|
+
* perfectly lossless migration — a false-negative that aborted the cutover and
|
|
27
|
+
* lost the batch writes accumulated during the migrating open.
|
|
28
|
+
*
|
|
29
|
+
* Extracting the transform logic into ONE shared module lets the verifier digest
|
|
30
|
+
* the SOURCE side **through the same transforms migrate applied**
|
|
31
|
+
* ({@link buildDigestExpr}), so equal logical data digests equal. `migrate.ts`
|
|
32
|
+
* re-imports these primitives so its copy behaviour stays byte-identical; the
|
|
33
|
+
* verifier imports {@link buildDigestExpr} for the digest-oriented variant.
|
|
34
|
+
*
|
|
35
|
+
* @task T11809 (exodus verify applies source-side coercion — hashMatch on equal data)
|
|
36
|
+
* @task T11546 (epoch→ISO coercion — original)
|
|
37
|
+
* @task T11547 (enum normalization — original)
|
|
38
|
+
* @task T11782 (non-finite numeric clamp — original)
|
|
39
|
+
* @epic T11249 (E6)
|
|
40
|
+
* @saga T11242
|
|
41
|
+
*/
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// NOT NULL default-literal helper
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
/**
|
|
46
|
+
* Determine a safe SQL literal default for a NOT NULL column with no schema
|
|
47
|
+
* default, given its SQLite type affinity.
|
|
48
|
+
*
|
|
49
|
+
* Used by `migrate.ts` to coalesce NULL source values for target-only NOT NULL
|
|
50
|
+
* columns so that rows are not silently dropped by `INSERT OR IGNORE` when a
|
|
51
|
+
* source value is NULL (T11533 ROOT CAUSE 2 fix).
|
|
52
|
+
*
|
|
53
|
+
* @param colType - Raw `type` string from `PRAGMA table_info` (e.g. `"INTEGER"`,
|
|
54
|
+
* `"TEXT"`, `"REAL"`, `"BLOB"`, or compound forms like `"text NOT NULL"`).
|
|
55
|
+
* @returns A SQL literal string suitable for embedding in a `COALESCE()` call.
|
|
56
|
+
*/
|
|
57
|
+
export function typeDefaultLiteral(colType) {
|
|
58
|
+
const upper = colType.toUpperCase();
|
|
59
|
+
if (upper.includes('INT'))
|
|
60
|
+
return '0';
|
|
61
|
+
if (upper.includes('REAL') || upper.includes('FLOAT') || upper.includes('DOUBLE'))
|
|
62
|
+
return '0.0';
|
|
63
|
+
if (upper.includes('BLOB'))
|
|
64
|
+
return "x''";
|
|
65
|
+
// TEXT and any other affinity (SQLite permissive) → empty string
|
|
66
|
+
return "''";
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Per-(targetTable, column) normalization rules that map legacy enum values to
|
|
70
|
+
* the canonical enum accepted by the consolidated schema CHECK constraints.
|
|
71
|
+
*
|
|
72
|
+
* Each entry is a function that, given the `srcRef` SQL expression for the
|
|
73
|
+
* column, returns a SQL CASE expression that produces the canonical value.
|
|
74
|
+
* Rows with already-canonical values pass through unchanged (the ELSE branch).
|
|
75
|
+
*
|
|
76
|
+
* ## Brain enum normalizations REMOVED (T11647)
|
|
77
|
+
*
|
|
78
|
+
* The brain memory family no longer participates in enum normalization. Its
|
|
79
|
+
* consolidated exodus target now matches the LEGACY RUNTIME shape, which carries
|
|
80
|
+
* NO SQL CHECK constraints (the `text({ enum })` unions are enforced at the
|
|
81
|
+
* application layer only). With no CHECK to satisfy, coercing brain enum values
|
|
82
|
+
* would be data corruption, not a fix — so every brain enum value is copied
|
|
83
|
+
* VERBATIM. The historical brain rules (`brain_observations.{source_type,type}`,
|
|
84
|
+
* `brain_decisions.{confirmation_state,decision_category,confidence,outcome,
|
|
85
|
+
* decided_by}`) were deleted. The TASKS-domain rules below remain because those
|
|
86
|
+
* consolidated tables keep their CHECK constraints.
|
|
87
|
+
*
|
|
88
|
+
* ## nexus/signaldock enum-drift audit (T11809 · AC1)
|
|
89
|
+
*
|
|
90
|
+
* A real-data audit of `nexus.db` (106 MB) and `signaldock.db` (280 KB) against
|
|
91
|
+
* the consolidated CHECK enums found ZERO out-of-enum values for every
|
|
92
|
+
* CHECK-constrained column that has source data: `nexus_nodes.kind` (24,482
|
|
93
|
+
* rows), `nexus_relations.type` (39,163 rows), `nexus_contracts.type` (0 rows),
|
|
94
|
+
* `nexus_sigils.role` (8 rows), `agent_registry_agents.status` (19 rows),
|
|
95
|
+
* `agent_registry_users.role` (0 rows). No new normalization entry was required.
|
|
96
|
+
* The reported "nexus/signaldock drop rows" symptom was in fact the AC2 verify
|
|
97
|
+
* false-negative (epoch→ISO coercion makes the source digest differ from the
|
|
98
|
+
* target digest), which fixing {@link buildDigestExpr} resolves — NOT a CHECK
|
|
99
|
+
* drop. See the T11809 return notes for the full per-column row counts.
|
|
100
|
+
*
|
|
101
|
+
* Lookup key: `${targetTable}.${column}` (lowercase, dotted).
|
|
102
|
+
*
|
|
103
|
+
* @since T11547 (P0 data-loss fix)
|
|
104
|
+
* @since T11548 (P0 final enum coverage)
|
|
105
|
+
* @since T11647 (brain target = runtime shape — brain enum coercions removed)
|
|
106
|
+
*/
|
|
107
|
+
export const ENUM_NORMALIZATIONS = new Map([
|
|
108
|
+
// --- task_commits.link_source -------------------------------------------
|
|
109
|
+
// 'commit-message' → 'commit-subject' (pre-T9506 legacy value)
|
|
110
|
+
[
|
|
111
|
+
'tasks_task_commits.link_source',
|
|
112
|
+
(src) => `CASE ${src} WHEN 'commit-message' THEN 'commit-subject' ELSE ${src} END`,
|
|
113
|
+
],
|
|
114
|
+
// --- architecture_decisions.status (case + date-suffix normalization) ----
|
|
115
|
+
// 'Accepted', 'ACCEPTED', 'approved', 'Accepted (2026-04-18)', … → 'accepted'
|
|
116
|
+
// 'Proposed', 'PROPOSED' → 'proposed'
|
|
117
|
+
// 'Superseded', 'SUPERSEDED' → 'superseded'
|
|
118
|
+
[
|
|
119
|
+
'tasks_architecture_decisions.status',
|
|
120
|
+
(src) => `CASE` +
|
|
121
|
+
` WHEN lower(${src}) = 'accepted' OR lower(${src}) LIKE 'accepted %' OR lower(${src}) = 'approved' THEN 'accepted'` +
|
|
122
|
+
` WHEN lower(${src}) = 'proposed' THEN 'proposed'` +
|
|
123
|
+
` WHEN lower(${src}) = 'superseded' THEN 'superseded'` +
|
|
124
|
+
` WHEN lower(${src}) = 'deprecated' THEN 'deprecated'` +
|
|
125
|
+
` ELSE ${src}` +
|
|
126
|
+
` END`,
|
|
127
|
+
],
|
|
128
|
+
// --- brain_* enum normalizations REMOVED (T11647) -----------------------
|
|
129
|
+
// The brain memory family now lands in the consolidated cleo.db in its LEGACY
|
|
130
|
+
// RUNTIME shape — INTEGER epoch timestamps and, critically, NO SQL CHECK
|
|
131
|
+
// constraints (the `text({ enum })` unions are enforced only at the
|
|
132
|
+
// application layer, exactly as the runtime `drizzle-brain` tables are). With
|
|
133
|
+
// no brain CHECK constraint to satisfy, exodus MUST copy every brain enum
|
|
134
|
+
// value VERBATIM — coercing them (e.g. source_type 'observer-compressed'/
|
|
135
|
+
// 'sleep-consolidation' → 'agent', type 'observation'/'proposal'/'pattern' →
|
|
136
|
+
// nearest) would now be unnecessary data CORRUPTION, not a constraint fix.
|
|
137
|
+
// The previous brain entries (brain_observations.{source_type,type},
|
|
138
|
+
// brain_decisions.{confirmation_state,decision_category,confidence,outcome,
|
|
139
|
+
// decided_by}) are therefore deleted. The non-brain entries below still apply
|
|
140
|
+
// because those consolidated tables retain their CHECK constraints.
|
|
141
|
+
// --- tasks_token_usage.transport (T11548 → REMOVED T11649) ---------------
|
|
142
|
+
// NO normalization. 'mcp' is a first-class transport origin (MCP-gateway
|
|
143
|
+
// requests) and is preserved verbatim. The consolidated CHECK enum was WIDENED
|
|
144
|
+
// to include 'mcp' (canonical TOKEN_USAGE_TRANSPORTS SSoT + forward migration
|
|
145
|
+
// 20260602000002_t11649-token-usage-transport-mcp), so the value lands without
|
|
146
|
+
// coercion. The earlier 'mcp' → 'agent' mapping was a silent semantic alteration
|
|
147
|
+
// of ~194 rows (count-preserving, NOT integrity-preserving) — see T11649.
|
|
148
|
+
// (brain_decisions.{decision_category,confidence} normalizations removed —
|
|
149
|
+
// T11647: brain target = runtime shape with no CHECK; copy values verbatim.)
|
|
150
|
+
// --- tasks_commits.conventional_type (T11548 + T11578) -------------------
|
|
151
|
+
// The consolidated CHECK enum is feat/fix/chore/docs/refactor/test/build/ci/
|
|
152
|
+
// perf/revert/breaking. Real git history carries non-conventional subjects:
|
|
153
|
+
// - 'style' → 'chore' (pre-T11548 mapping; no 'style' in enum).
|
|
154
|
+
// - 'merge'/'release' → 'chore' (T11578): merge + release commits are
|
|
155
|
+
// maintenance-class; the precise semantic is preserved by the dedicated
|
|
156
|
+
// `is_merge_commit` / `is_release_commit` boolean columns, so collapsing
|
|
157
|
+
// `conventional_type` to the maintenance catch-all 'chore' is lossless at
|
|
158
|
+
// the row grain. Without this the 'merge'/'release' rows violate the CHECK,
|
|
159
|
+
// `INSERT OR IGNORE` drops the WHOLE commits table, and the exodus-on-open
|
|
160
|
+
// data-continuity gate aborts the cutover (T11578 CI regression).
|
|
161
|
+
// - any OTHER out-of-enum value → 'chore' (defensive: future non-conventional
|
|
162
|
+
// subjects must never re-break the zero-deficit gate; the boolean flags and
|
|
163
|
+
// raw subject text remain the precise provenance).
|
|
164
|
+
[
|
|
165
|
+
'tasks_commits.conventional_type',
|
|
166
|
+
(src) => `CASE` +
|
|
167
|
+
` WHEN ${src} IS NULL THEN NULL` +
|
|
168
|
+
` WHEN ${src} IN ('feat', 'fix', 'chore', 'docs', 'refactor', 'test', 'build', 'ci', 'perf', 'revert', 'breaking') THEN ${src}` +
|
|
169
|
+
` ELSE 'chore'` +
|
|
170
|
+
` END`,
|
|
171
|
+
],
|
|
172
|
+
// --- tasks_task_relations.relation_type (T11548) -------------------------
|
|
173
|
+
// 'grouped-by' → 'groups' (enum: related/blocks/duplicates/absorbs/fixes/extends/
|
|
174
|
+
// supersedes/groups). 4 rows.
|
|
175
|
+
[
|
|
176
|
+
'tasks_task_relations.relation_type',
|
|
177
|
+
(src) => `CASE ${src} WHEN 'grouped-by' THEN 'groups' ELSE ${src} END`,
|
|
178
|
+
],
|
|
179
|
+
// --- tasks_lifecycle_stages.stage_name (T11548) --------------------------
|
|
180
|
+
// Legacy camelCase / past-tense values → canonical snake_case stage names.
|
|
181
|
+
// 'implemented' → 'implementation', 'qaPassed' → 'validation',
|
|
182
|
+
// 'testsPassed' → 'testing'. 3 rows.
|
|
183
|
+
[
|
|
184
|
+
'tasks_lifecycle_stages.stage_name',
|
|
185
|
+
(src) => `CASE ${src}` +
|
|
186
|
+
` WHEN 'implemented' THEN 'implementation'` +
|
|
187
|
+
` WHEN 'qaPassed' THEN 'validation'` +
|
|
188
|
+
` WHEN 'testsPassed' THEN 'testing'` +
|
|
189
|
+
` ELSE ${src}` +
|
|
190
|
+
` END`,
|
|
191
|
+
],
|
|
192
|
+
// --- tasks_architecture_decisions.gate_status (T11548) ------------------
|
|
193
|
+
// 'passed (T5313 consensus)' → 'passed', 'approved' → 'passed'
|
|
194
|
+
// (enum: pending/passed/failed/waived). 2 rows.
|
|
195
|
+
[
|
|
196
|
+
'tasks_architecture_decisions.gate_status',
|
|
197
|
+
(src) => `CASE` +
|
|
198
|
+
` WHEN ${src} LIKE 'passed%' THEN 'passed'` +
|
|
199
|
+
` WHEN ${src} = 'approved' THEN 'passed'` +
|
|
200
|
+
` ELSE ${src}` +
|
|
201
|
+
` END`,
|
|
202
|
+
],
|
|
203
|
+
// --- tasks_evidence_ac_bindings.binding_type (T11548) -------------------
|
|
204
|
+
// Values with a 'validator:...' prefix → 'direct'
|
|
205
|
+
// (enum: direct/satisfies/coverage). 3 rows.
|
|
206
|
+
// Strip the namespace prefix introduced before the enum was tightened.
|
|
207
|
+
[
|
|
208
|
+
'tasks_evidence_ac_bindings.binding_type',
|
|
209
|
+
(src) => `CASE` + ` WHEN ${src} LIKE 'validator:%' THEN 'direct'` + ` ELSE ${src}` + ` END`,
|
|
210
|
+
],
|
|
211
|
+
// (brain_decisions.{outcome,decided_by} normalizations removed — T11647:
|
|
212
|
+
// brain target = runtime shape with no CHECK; legacy values like 'accepted',
|
|
213
|
+
// 'rejected', 'prime' now survive VERBATIM instead of being coerced.)
|
|
214
|
+
]);
|
|
215
|
+
/**
|
|
216
|
+
* Return a SQL CASE expression that normalises legacy enum values for `col` in
|
|
217
|
+
* `targetTableName` to the canonical values accepted by the consolidated CHECK,
|
|
218
|
+
* or return `null` when no normalization rule exists for this (table, column).
|
|
219
|
+
*
|
|
220
|
+
* @param targetTableName - Physical consolidated target table name.
|
|
221
|
+
* @param col - Column name.
|
|
222
|
+
* @param srcRef - SQL expression referencing the source column.
|
|
223
|
+
* @returns A SQL CASE expression string, or `null` if no rule applies.
|
|
224
|
+
*/
|
|
225
|
+
export function enumNormExpr(targetTableName, col, srcRef) {
|
|
226
|
+
const key = `${targetTableName}.${col}`;
|
|
227
|
+
const fn = ENUM_NORMALIZATIONS.get(key);
|
|
228
|
+
return fn ? fn(srcRef) : null;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Per-(targetTable, column) numeric-clamp rules that coerce non-finite legacy
|
|
232
|
+
* REAL values (`Inf` / `-Inf` / `NaN`) to a finite in-range value so the row is
|
|
233
|
+
* NOT silently dropped by `INSERT OR IGNORE`.
|
|
234
|
+
*
|
|
235
|
+
* ## Why this exists (T11782)
|
|
236
|
+
*
|
|
237
|
+
* 188,926 of 697,780 legacy `brain_weight_history` rows carry
|
|
238
|
+
* `delta_weight = Inf`/`-Inf` (the R-STDP plasticity writer saturated the delta
|
|
239
|
+
* before the value was clamped at write time). SQLite stores ±Inf as the IEEE-754
|
|
240
|
+
* float, but the consolidated `brain_weight_history.delta_weight` column is a
|
|
241
|
+
* plain `real NOT NULL` with NO CHECK — so a verbatim copy would land the Inf
|
|
242
|
+
* value. The historical deficit, however, was that those rows tripped a constraint
|
|
243
|
+
* elsewhere in the copy chain and `INSERT OR IGNORE` dropped them, yielding a
|
|
244
|
+
* deficit that fired the parity-gate abort. Clamping the non-finite value to a
|
|
245
|
+
* finite member of the column's domain guarantees every row lands.
|
|
246
|
+
*
|
|
247
|
+
* The clamp mirrors the {@link ENUM_NORMALIZATIONS} shape: each entry is a
|
|
248
|
+
* function `(srcRef) => CASE …`. Finite values pass through unchanged via the
|
|
249
|
+
* ELSE branch. The `col != col` self-comparison is the canonical SQL NaN guard
|
|
250
|
+
* (NaN is the only value not equal to itself); `9e999` is the SQLite literal that
|
|
251
|
+
* evaluates to `+Infinity` (and `-9e999` to `-Infinity`).
|
|
252
|
+
*
|
|
253
|
+
* Lookup key: `${targetTable}.${column}` (lowercase, dotted).
|
|
254
|
+
*
|
|
255
|
+
* @since T11782 (P0 — brain_weight_history Inf recovery)
|
|
256
|
+
*/
|
|
257
|
+
export const NUMERIC_CLAMPS = new Map([
|
|
258
|
+
// --- brain_weight_history.delta_weight (T11782) -------------------------
|
|
259
|
+
// +Inf → 1.0 (max canonical reinforcement), -Inf → -1.0 (max canonical
|
|
260
|
+
// depression), NaN → 0.0 (no-op delta). Finite values pass through.
|
|
261
|
+
[
|
|
262
|
+
'brain_weight_history.delta_weight',
|
|
263
|
+
(src) => `CASE` +
|
|
264
|
+
` WHEN ${src} = 9e999 THEN 1.0` +
|
|
265
|
+
` WHEN ${src} = -9e999 THEN -1.0` +
|
|
266
|
+
` WHEN ${src} != ${src} THEN 0.0` +
|
|
267
|
+
` ELSE ${src}` +
|
|
268
|
+
` END`,
|
|
269
|
+
],
|
|
270
|
+
]);
|
|
271
|
+
/**
|
|
272
|
+
* Return a SQL CASE expression that clamps non-finite legacy REAL values for
|
|
273
|
+
* `col` in `targetTableName` to a finite in-range value, or `null` when no
|
|
274
|
+
* clamp rule exists for this (table, column).
|
|
275
|
+
*
|
|
276
|
+
* @param targetTableName - Physical consolidated target table name.
|
|
277
|
+
* @param col - Column name.
|
|
278
|
+
* @param srcRef - SQL expression referencing the source column.
|
|
279
|
+
* @returns A SQL CASE expression string, or `null` if no rule applies.
|
|
280
|
+
*/
|
|
281
|
+
export function numericClampExpr(targetTableName, col, srcRef) {
|
|
282
|
+
const key = `${targetTableName}.${col}`;
|
|
283
|
+
const fn = NUMERIC_CLAMPS.get(key);
|
|
284
|
+
return fn ? fn(srcRef) : null;
|
|
285
|
+
}
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
// Epoch-to-ISO coercion layer (ROOT CAUSE 1 fix — T11546)
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
/**
|
|
290
|
+
* Regex to detect ISO GLOB CHECK constraints in DDL SQL.
|
|
291
|
+
* Matches: `CHECK ("colname" IS NULL OR "colname" GLOB '[0-9]...')`
|
|
292
|
+
* Uses `\[0-9` to match the literal `[0-9` at the start of the GLOB pattern.
|
|
293
|
+
*/
|
|
294
|
+
const ISO_CHECK_REGEX = /CHECK\s*\(\s*"([^"]+)"\s+IS\s+NULL\s+OR\s+"[^"]+"\s+GLOB\s+'\[0-9/gi;
|
|
295
|
+
/**
|
|
296
|
+
* Magnitude threshold distinguishing epoch SECONDS from epoch MILLISECONDS.
|
|
297
|
+
*
|
|
298
|
+
* A Unix epoch value for years 2020–2100 is roughly 1.6e9 – 4.1e9 seconds,
|
|
299
|
+
* or 1.6e12 – 4.1e12 milliseconds. The safe boundary is 1e11 (100 billion):
|
|
300
|
+
* any value BELOW 1e11 is in seconds (even year 2100 seconds ≈ 4.1e9 < 1e11);
|
|
301
|
+
* any value AT OR ABOVE 1e11 is in milliseconds (year 2020 ms ≈ 1.6e12 > 1e11).
|
|
302
|
+
*
|
|
303
|
+
* This constant is embedded directly in the generated SQL CASE expression so
|
|
304
|
+
* it is evaluated per-row — each row's epoch is classified independently.
|
|
305
|
+
*/
|
|
306
|
+
export const EPOCH_SECONDS_THRESHOLD = 100_000_000_000; // 1e11
|
|
307
|
+
/**
|
|
308
|
+
* Build a SQL expression that converts an INTEGER epoch column to ISO-8601 TEXT,
|
|
309
|
+
* automatically detecting whether the stored value is in seconds or milliseconds
|
|
310
|
+
* using a magnitude heuristic (T11549 correctness fix).
|
|
311
|
+
*
|
|
312
|
+
* ## Heuristic
|
|
313
|
+
*
|
|
314
|
+
* A per-row CASE checks whether the column value is below {@link EPOCH_SECONDS_THRESHOLD}
|
|
315
|
+
* (100 billion). If so, the value is treated as seconds and passed directly to
|
|
316
|
+
* `strftime(..., 'unixepoch')`. If at or above the threshold, it is divided by
|
|
317
|
+
* 1000.0 first (milliseconds → seconds).
|
|
318
|
+
*
|
|
319
|
+
* This replaces the previous per-source heuristic which failed when individual
|
|
320
|
+
* columns within a source DB used a different epoch unit than the majority of that
|
|
321
|
+
* source's columns. The specific bug: `nexus.user_profile.{first_observed_at,
|
|
322
|
+
* last_reinforced_at}` stores SECONDS (value ≈ 1.78e9) but the nexus source was
|
|
323
|
+
* labeled `milliseconds`, causing these values to be divided by 1000 and converted
|
|
324
|
+
* to a 1970 date.
|
|
325
|
+
*
|
|
326
|
+
* ## NULL handling
|
|
327
|
+
*
|
|
328
|
+
* A NULL source value is preserved as NULL so it passes the `IS NULL` branch of
|
|
329
|
+
* the ISO GLOB CHECK constraint on the target column.
|
|
330
|
+
*
|
|
331
|
+
* @param srcRef - SQL expression referencing the source column value.
|
|
332
|
+
* @returns A SQL CASE expression producing an ISO-8601 TEXT timestamp.
|
|
333
|
+
*/
|
|
334
|
+
export function buildEpochToIsoExpr(srcRef) {
|
|
335
|
+
return (`CASE` +
|
|
336
|
+
` WHEN ${srcRef} IS NULL THEN NULL` +
|
|
337
|
+
` WHEN ${srcRef} < ${EPOCH_SECONDS_THRESHOLD}` +
|
|
338
|
+
` THEN strftime('%Y-%m-%dT%H:%M:%fZ', ${srcRef}, 'unixepoch')` +
|
|
339
|
+
` ELSE strftime('%Y-%m-%dT%H:%M:%fZ', ${srcRef}/1000.0, 'unixepoch')` +
|
|
340
|
+
` END`);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Parse the DDL for a given table from `sqlite_master` and return the set of
|
|
344
|
+
* column names that have an ISO GLOB CHECK constraint.
|
|
345
|
+
*
|
|
346
|
+
* Reads the raw DDL text and uses a regex to extract column names appearing in
|
|
347
|
+
* `CHECK ("colname" IS NULL OR "colname" GLOB '[0-9]...')` patterns. This is
|
|
348
|
+
* robust to Drizzle's generated CHECK format (all CHECK constraints generated
|
|
349
|
+
* by T11363 follow this exact pattern).
|
|
350
|
+
*
|
|
351
|
+
* @param db - Target DB with the consolidated schema.
|
|
352
|
+
* @param tableName - Physical table name (consolidated, e.g. `conduit_messages`).
|
|
353
|
+
* @param targetSchema - Schema name the target table lives in (`'main'`, or an
|
|
354
|
+
* ATTACH alias for cross-scope routing — ADR-090 nexus graph residency, T11539).
|
|
355
|
+
* @returns Set of column names that require ISO GLOB validation.
|
|
356
|
+
*/
|
|
357
|
+
export function detectIsoGlobColumns(db, tableName, targetSchema = 'main') {
|
|
358
|
+
const escapedTable = tableName.replace(/'/g, "''");
|
|
359
|
+
const row = db
|
|
360
|
+
.prepare(`SELECT sql FROM "${targetSchema}".sqlite_master WHERE type='table' AND name='${escapedTable}'`)
|
|
361
|
+
.get();
|
|
362
|
+
if (!row?.sql)
|
|
363
|
+
return new Set();
|
|
364
|
+
const isoColumns = new Set();
|
|
365
|
+
// Pattern: CHECK ("colname" IS NULL OR "colname" GLOB '[0-9]...')
|
|
366
|
+
// The column name appears TWICE — we capture the first occurrence.
|
|
367
|
+
// Use matchAll to avoid the biome no-assign-in-expressions rule.
|
|
368
|
+
ISO_CHECK_REGEX.lastIndex = 0; // reset before reuse (global regex stateful)
|
|
369
|
+
for (const match of row.sql.matchAll(ISO_CHECK_REGEX)) {
|
|
370
|
+
isoColumns.add(match[1]);
|
|
371
|
+
}
|
|
372
|
+
return isoColumns;
|
|
373
|
+
}
|
|
374
|
+
// ---------------------------------------------------------------------------
|
|
375
|
+
// Digest-oriented transform expression (T11809 · AC2)
|
|
376
|
+
// ---------------------------------------------------------------------------
|
|
377
|
+
/**
|
|
378
|
+
* Return `true` when the source column's type affinity is INTEGER-like, so an
|
|
379
|
+
* epoch→ISO coercion applies when the matching target column carries an ISO GLOB
|
|
380
|
+
* CHECK. Empty affinity and `NUMERIC` are treated as integer-like (matching the
|
|
381
|
+
* historical `buildSelectExpr` behaviour — legacy epoch columns sometimes carry
|
|
382
|
+
* no declared type or a `NUMERIC` affinity).
|
|
383
|
+
*
|
|
384
|
+
* @param srcType - Raw `type` string from the source `PRAGMA table_info`.
|
|
385
|
+
* @returns `true` if the source column should be considered an INTEGER epoch.
|
|
386
|
+
*/
|
|
387
|
+
function isIntegerSourceType(srcType) {
|
|
388
|
+
const upper = srcType.toUpperCase();
|
|
389
|
+
return upper.includes('INT') || upper === '' || upper === 'NUMERIC';
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Wrap a digest value expression in the SAME `COALESCE(expr, type_default)`
|
|
393
|
+
* substitution migrate's `buildSelectExpr` applies, when (and only when) the
|
|
394
|
+
* matching TARGET column is NOT NULL without a schema default (T11836).
|
|
395
|
+
*
|
|
396
|
+
* On a faithful FRESH migration a NULL source value in such a column is stored
|
|
397
|
+
* as the type default (`''`/`0`/`0.0`/`x''`) by migrate; digesting the source
|
|
398
|
+
* raw would read NULL and diverge from the stored default — a false
|
|
399
|
+
* `hashMatch === false` at equal row counts. Mirroring the COALESCE here makes
|
|
400
|
+
* the source digest reflect what migrate actually stored, so the substitution is
|
|
401
|
+
* a NON-FATAL diagnostic class rather than a content-corruption failure.
|
|
402
|
+
*
|
|
403
|
+
* @param expr - The value expression produced by an upstream transform branch.
|
|
404
|
+
* @param tgtCol - Target column metadata, or `undefined` when unavailable (no
|
|
405
|
+
* COALESCE applied — best-effort, biased to surfacing a real difference).
|
|
406
|
+
* @returns The expression, COALESCE-wrapped when the target is NOT-NULL-without-default.
|
|
407
|
+
*/
|
|
408
|
+
function maybeCoalesceNotNull(expr, tgtCol) {
|
|
409
|
+
if (tgtCol === undefined)
|
|
410
|
+
return expr;
|
|
411
|
+
const isNotNullWithoutDefault = tgtCol.notnull === 1 && tgtCol.dflt_value === null;
|
|
412
|
+
if (!isNotNullWithoutDefault)
|
|
413
|
+
return expr;
|
|
414
|
+
return `COALESCE(${expr}, ${typeDefaultLiteral(tgtCol.type)})`;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Build the SQL expression a column's SOURCE value must pass through so it
|
|
418
|
+
* matches the canonical value the migration STORES in the target — for use by
|
|
419
|
+
* the parity verifier's content digest (T11809 · AC2).
|
|
420
|
+
*
|
|
421
|
+
* This is the digest-oriented sibling of migrate's `buildSelectExpr`. It applies
|
|
422
|
+
* the SAME value transforms the migration actually performs — and ONLY those:
|
|
423
|
+
*
|
|
424
|
+
* 1. **Epoch→ISO-8601** ({@link buildEpochToIsoExpr}) — when the target has an
|
|
425
|
+
* ISO GLOB CHECK and the source column is INTEGER-typed.
|
|
426
|
+
* 2. **Non-finite numeric clamp** ({@link numericClampExpr}).
|
|
427
|
+
* 3. **Enum-value normalization** ({@link enumNormExpr}).
|
|
428
|
+
* 4. **Plain column reference** otherwise.
|
|
429
|
+
*
|
|
430
|
+
* ## NOT-NULL default substitution mirroring (T11836)
|
|
431
|
+
*
|
|
432
|
+
* After the value transform, the result is wrapped in the SAME
|
|
433
|
+
* `COALESCE(expr, type_default)` substitution migrate's `buildSelectExpr` uses
|
|
434
|
+
* for a TARGET column that is NOT NULL without a schema default (see
|
|
435
|
+
* {@link maybeCoalesceNotNull}). migrate stores the type default (`''`/`0`/
|
|
436
|
+
* `0.0`/`x''`) for a NULL source value in such a column; without mirroring it
|
|
437
|
+
* here the source digest would read NULL and diverge from the stored default — a
|
|
438
|
+
* false `hashMatch === false` on a faithful FRESH migration (the exact T11836
|
|
439
|
+
* symptom). Mirroring it makes a NULL→NOT-NULL-default substitution a NON-FATAL
|
|
440
|
+
* diagnostic class rather than a hash failure. The target metadata is supplied
|
|
441
|
+
* via the `tgtColByCol` map; when it is absent (best-effort) no COALESCE is
|
|
442
|
+
* applied and a genuine NULL→'' divergence remains visible as before.
|
|
443
|
+
*
|
|
444
|
+
* The returned expression is a bare SQL value expression (no `AS "col"` alias)
|
|
445
|
+
* suitable for embedding directly in a `SELECT <expr> ... ORDER BY ...` digest
|
|
446
|
+
* query. When no transform applies, a plain quoted column reference is returned.
|
|
447
|
+
*
|
|
448
|
+
* @param targetTableName - Physical consolidated target table name (transform
|
|
449
|
+
* lookup key).
|
|
450
|
+
* @param col - Column name (present in BOTH source and target).
|
|
451
|
+
* @param srcType - Raw `type` string from the source `PRAGMA table_info`.
|
|
452
|
+
* @param isoGlobCols - Set of target columns carrying an ISO GLOB CHECK.
|
|
453
|
+
* @param tgtCol - Target column metadata (NOT-NULL flag + default +
|
|
454
|
+
* affinity) for `col`, or `undefined` when unavailable.
|
|
455
|
+
* @returns A SQL value expression that maps the raw source value to the canonical
|
|
456
|
+
* value the target stores.
|
|
457
|
+
*/
|
|
458
|
+
export function buildDigestExpr(targetTableName, col, srcType, isoGlobCols, tgtCol) {
|
|
459
|
+
const srcRef = `"${col}"`;
|
|
460
|
+
// Priority 1: Epoch→ISO coercion — applies when the target has an ISO GLOB
|
|
461
|
+
// CHECK and the source column is INTEGER (epoch) typed. Mirrors migrate's
|
|
462
|
+
// per-row magnitude heuristic exactly.
|
|
463
|
+
if (isoGlobCols.has(col) && isIntegerSourceType(srcType)) {
|
|
464
|
+
return maybeCoalesceNotNull(buildEpochToIsoExpr(srcRef), tgtCol);
|
|
465
|
+
}
|
|
466
|
+
// Priority 2: Non-finite numeric clamp (Inf/-Inf/NaN → finite in-range).
|
|
467
|
+
const clampExpr = numericClampExpr(targetTableName, col, srcRef);
|
|
468
|
+
if (clampExpr !== null)
|
|
469
|
+
return maybeCoalesceNotNull(clampExpr, tgtCol);
|
|
470
|
+
// Priority 3: Enum-value normalization (legacy value → canonical member).
|
|
471
|
+
const normExpr = enumNormExpr(targetTableName, col, srcRef);
|
|
472
|
+
if (normExpr !== null)
|
|
473
|
+
return maybeCoalesceNotNull(normExpr, tgtCol);
|
|
474
|
+
// Priority 4: plain column reference, COALESCE-wrapped when the target is
|
|
475
|
+
// NOT NULL without a default (mirrors migrate's T11533 substitution — T11836).
|
|
476
|
+
return maybeCoalesceNotNull(srcRef, tgtCol);
|
|
477
|
+
}
|
|
478
|
+
//# sourceMappingURL=column-transforms.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"column-transforms.js","sourceRoot":"","sources":["../../../src/store/exodus/column-transforms.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAIH,8EAA8E;AAC9E,kCAAkC;AAClC,8EAA8E;AAE9E;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IACpC,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC;IACtC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAChG,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IACzC,iEAAiE;IACjE,OAAO,IAAI,CAAC;AACd,CAAC;AAaD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAqC,IAAI,GAAG,CAAC;IAC3E,2EAA2E;IAC3E,+DAA+D;IAC/D;QACE,gCAAgC;QAChC,CAAC,GAAW,EAAE,EAAE,CAAC,QAAQ,GAAG,qDAAqD,GAAG,MAAM;KAC3F;IAED,4EAA4E;IAC5E,8EAA8E;IAC9E,sCAAsC;IACtC,4CAA4C;IAC5C;QACE,qCAAqC;QACrC,CAAC,GAAW,EAAE,EAAE,CACd,MAAM;YACN,eAAe,GAAG,2BAA2B,GAAG,gCAAgC,GAAG,gCAAgC;YACnH,eAAe,GAAG,gCAAgC;YAClD,eAAe,GAAG,oCAAoC;YACtD,eAAe,GAAG,oCAAoC;YACtD,SAAS,GAAG,EAAE;YACd,MAAM;KACT;IAED,2EAA2E;IAC3E,8EAA8E;IAC9E,yEAAyE;IACzE,oEAAoE;IACpE,8EAA8E;IAC9E,0EAA0E;IAC1E,0EAA0E;IAC1E,6EAA6E;IAC7E,2EAA2E;IAC3E,qEAAqE;IACrE,4EAA4E;IAC5E,8EAA8E;IAC9E,oEAAoE;IAEpE,4EAA4E;IAC5E,yEAAyE;IACzE,+EAA+E;IAC/E,8EAA8E;IAC9E,+EAA+E;IAC/E,iFAAiF;IACjF,0EAA0E;IAE1E,2EAA2E;IAC3E,8EAA8E;IAE9E,4EAA4E;IAC5E,6EAA6E;IAC7E,4EAA4E;IAC5E,4EAA4E;IAC5E,wEAAwE;IACxE,4EAA4E;IAC5E,6EAA6E;IAC7E,8EAA8E;IAC9E,gFAAgF;IAChF,+EAA+E;IAC/E,sEAAsE;IACtE,gFAAgF;IAChF,gFAAgF;IAChF,uDAAuD;IACvD;QACE,iCAAiC;QACjC,CAAC,GAAW,EAAE,EAAE,CACd,MAAM;YACN,SAAS,GAAG,oBAAoB;YAChC,SAAS,GAAG,8GAA8G,GAAG,EAAE;YAC/H,eAAe;YACf,MAAM;KACT;IAED,4EAA4E;IAC5E,kFAAkF;IAClF,8BAA8B;IAC9B;QACE,oCAAoC;QACpC,CAAC,GAAW,EAAE,EAAE,CAAC,QAAQ,GAAG,yCAAyC,GAAG,MAAM;KAC/E;IAED,4EAA4E;IAC5E,2EAA2E;IAC3E,+DAA+D;IAC/D,qCAAqC;IACrC;QACE,mCAAmC;QACnC,CAAC,GAAW,EAAE,EAAE,CACd,QAAQ,GAAG,EAAE;YACb,2CAA2C;YAC3C,oCAAoC;YACpC,oCAAoC;YACpC,SAAS,GAAG,EAAE;YACd,MAAM;KACT;IAED,2EAA2E;IAC3E,+DAA+D;IAC/D,gDAAgD;IAChD;QACE,0CAA0C;QAC1C,CAAC,GAAW,EAAE,EAAE,CACd,MAAM;YACN,SAAS,GAAG,+BAA+B;YAC3C,SAAS,GAAG,6BAA6B;YACzC,SAAS,GAAG,EAAE;YACd,MAAM;KACT;IAED,2EAA2E;IAC3E,kDAAkD;IAClD,6CAA6C;IAC7C,uEAAuE;IACvE;QACE,yCAAyC;QACzC,CAAC,GAAW,EAAE,EAAE,CACd,MAAM,GAAG,SAAS,GAAG,mCAAmC,GAAG,SAAS,GAAG,EAAE,GAAG,MAAM;KACrF;IAED,yEAAyE;IACzE,8EAA8E;IAC9E,uEAAuE;CACxE,CAAC,CAAC;AAEH;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,eAAuB,EAAE,GAAW,EAAE,MAAc;IAC/E,MAAM,GAAG,GAAG,GAAG,eAAe,IAAI,GAAG,EAAE,CAAC;IACxC,MAAM,EAAE,GAAG,mBAAmB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACxC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAChC,CAAC;AAYD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,MAAM,cAAc,GAAwC,IAAI,GAAG,CAAC;IACzE,2EAA2E;IAC3E,uEAAuE;IACvE,oEAAoE;IACpE;QACE,mCAAmC;QACnC,CAAC,GAAW,EAAE,EAAE,CACd,MAAM;YACN,SAAS,GAAG,mBAAmB;YAC/B,SAAS,GAAG,qBAAqB;YACjC,SAAS,GAAG,OAAO,GAAG,WAAW;YACjC,SAAS,GAAG,EAAE;YACd,MAAM;KACT;CACF,CAAC,CAAC;AAEH;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAC9B,eAAuB,EACvB,GAAW,EACX,MAAc;IAEd,MAAM,GAAG,GAAG,GAAG,eAAe,IAAI,GAAG,EAAE,CAAC;IACxC,MAAM,EAAE,GAAG,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAChC,CAAC;AAED,8EAA8E;AAC9E,0DAA0D;AAC1D,8EAA8E;AAE9E;;;;GAIG;AACH,MAAM,eAAe,GAAG,qEAAqE,CAAC;AAE9F;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,eAAwB,CAAC,CAAC,OAAO;AAExE;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAc;IAChD,OAAO,CACL,MAAM;QACN,SAAS,MAAM,oBAAoB;QACnC,SAAS,MAAM,MAAM,uBAAuB,EAAE;QAC9C,wCAAwC,MAAM,gBAAgB;QAC9D,wCAAwC,MAAM,uBAAuB;QACrE,MAAM,CACP,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,oBAAoB,CAClC,EAAgB,EAChB,SAAiB,EACjB,YAAY,GAAG,MAAM;IAErB,MAAM,YAAY,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACnD,MAAM,GAAG,GAAG,EAAE;SACX,OAAO,CACN,oBAAoB,YAAY,gDAAgD,YAAY,GAAG,CAChG;SACA,GAAG,EAA4B,CAAC;IAEnC,IAAI,CAAC,GAAG,EAAE,GAAG;QAAE,OAAO,IAAI,GAAG,EAAE,CAAC;IAEhC,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IACrC,kEAAkE;IAClE,mEAAmE;IACnE,iEAAiE;IACjE,eAAe,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,6CAA6C;IAC5E,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;QACtD,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,8EAA8E;AAC9E,sDAAsD;AACtD,8EAA8E;AAE9E;;;;;;;;;GASG;AACH,SAAS,mBAAmB,CAAC,OAAe;IAC1C,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IACpC,OAAO,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,KAAK,EAAE,IAAI,KAAK,KAAK,SAAS,CAAC;AACtE,CAAC;AAsBD;;;;;;;;;;;;;;;;GAgBG;AACH,SAAS,oBAAoB,CAAC,IAAY,EAAE,MAAoC;IAC9E,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,uBAAuB,GAAG,MAAM,CAAC,OAAO,KAAK,CAAC,IAAI,MAAM,CAAC,UAAU,KAAK,IAAI,CAAC;IACnF,IAAI,CAAC,uBAAuB;QAAE,OAAO,IAAI,CAAC;IAC1C,OAAO,YAAY,IAAI,KAAK,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;AACjE,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,MAAM,UAAU,eAAe,CAC7B,eAAuB,EACvB,GAAW,EACX,OAAe,EACf,WAAgC,EAChC,MAAyB;IAEzB,MAAM,MAAM,GAAG,IAAI,GAAG,GAAG,CAAC;IAE1B,2EAA2E;IAC3E,0EAA0E;IAC1E,uCAAuC;IACvC,IAAI,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,mBAAmB,CAAC,OAAO,CAAC,EAAE,CAAC;QACzD,OAAO,oBAAoB,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IACnE,CAAC;IAED,yEAAyE;IACzE,MAAM,SAAS,GAAG,gBAAgB,CAAC,eAAe,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;IACjE,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,oBAAoB,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAEvE,0EAA0E;IAC1E,MAAM,QAAQ,GAAG,YAAY,CAAC,eAAe,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;IAC5D,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,oBAAoB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAErE,0EAA0E;IAC1E,+EAA+E;IAC/E,OAAO,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAC9C,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory-safe COUNT(*)-only exodus parity — the deficit gate WITHOUT the content
|
|
3
|
+
* digest (T11837).
|
|
4
|
+
*
|
|
5
|
+
* `verifyMigration` is the full parity guard (row counts + content digest + FK +
|
|
6
|
+
* enum drift), but its content digest streams every row of every table. For the
|
|
7
|
+
* fleet-flow surface — `cleo exodus seal` (certify an already-migrated install)
|
|
8
|
+
* and `cleo doctor exodus` (health report) — we only need the DEFICIT gate that
|
|
9
|
+
* `isDataContinuityOk` actually enforces: per-table `target COUNT(*) >= source
|
|
10
|
+
* COUNT(*)`. That is a set-based query SQLite answers without materialising a
|
|
11
|
+
* single row, so it is safe to run against a 1.7 GB-class legacy `brain.db`
|
|
12
|
+
* (where the digest would be expensive). A SURPLUS (target > source — the live
|
|
13
|
+
* consolidated DB has moved ahead of the frozen legacy snapshot) is NOT loss and
|
|
14
|
+
* is tolerated, exactly as in `isDataContinuityOk`.
|
|
15
|
+
*
|
|
16
|
+
* This is intentionally a SEPARATE primitive from `verifyMigration` — sealing an
|
|
17
|
+
* already-migrated install must never re-run the heavy digest that this whole
|
|
18
|
+
* fleet-hardening epic (T11833) exists to avoid.
|
|
19
|
+
*
|
|
20
|
+
* @task T11837 (fleet-flow surface — count-only parity for seal + health)
|
|
21
|
+
* @epic T11833 (EP-EXODUS-FLEET-HARDENING)
|
|
22
|
+
* @saga T11242 (SG-DB-SUBSTRATE-V2)
|
|
23
|
+
*/
|
|
24
|
+
import type { ExodusScope, LegacyDbDescriptor } from './types.js';
|
|
25
|
+
/** Per-table COUNT(*) comparison between a legacy source table and its target. */
|
|
26
|
+
export interface CountParityEntry {
|
|
27
|
+
/** Logical source DB name (`LegacyDbDescriptor.name`). */
|
|
28
|
+
readonly sourceDb: string;
|
|
29
|
+
/** Physical legacy source table name. */
|
|
30
|
+
readonly sourceTable: string;
|
|
31
|
+
/** Consolidated target table name. */
|
|
32
|
+
readonly targetTable: string;
|
|
33
|
+
/** Effective target scope (per-table override included — ADR-090 nexus graph). */
|
|
34
|
+
readonly scope: ExodusScope;
|
|
35
|
+
/** Source row count. */
|
|
36
|
+
readonly sourceCount: number;
|
|
37
|
+
/** Consolidated target row count (0 when the target table is absent). */
|
|
38
|
+
readonly targetCount: number;
|
|
39
|
+
/** `sourceCount - targetCount` when positive (rows MISSING in target); else 0. */
|
|
40
|
+
readonly deficit: number;
|
|
41
|
+
}
|
|
42
|
+
/** Result of a COUNT(*)-only parity sweep across all source tables. */
|
|
43
|
+
export interface CountParityResult {
|
|
44
|
+
/** `true` when NO data-bearing table has a deficit. */
|
|
45
|
+
readonly ok: boolean;
|
|
46
|
+
/** Every compared table (parity, surplus, and deficit). */
|
|
47
|
+
readonly entries: readonly CountParityEntry[];
|
|
48
|
+
/** The subset with a genuine deficit (`targetCount < sourceCount`). */
|
|
49
|
+
readonly deficits: readonly CountParityEntry[];
|
|
50
|
+
/** Count of tables compared. */
|
|
51
|
+
readonly checked: number;
|
|
52
|
+
/** Count of skipped tables (derived/FTS/internal or virtual that cannot be counted). */
|
|
53
|
+
readonly skipped: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Compute COUNT(*)-only parity for every legacy source table against the
|
|
57
|
+
* consolidated dual-scope target — the deficit gate, never the heavy digest.
|
|
58
|
+
*
|
|
59
|
+
* Opens all DBs read-only. A table whose consolidated counterpart is ABSENT with
|
|
60
|
+
* source rows is a deficit (`targetCount: 0`). Derived/FTS/internal tables that
|
|
61
|
+
* map to `skip`, and virtual tables that cannot be counted, are skipped.
|
|
62
|
+
*
|
|
63
|
+
* @param sources - Legacy source descriptors (from `buildExodusPlan()`).
|
|
64
|
+
* @param projectDbPath - Absolute path to the consolidated project `cleo.db`.
|
|
65
|
+
* @param globalDbPath - Absolute path to the consolidated global `cleo.db`.
|
|
66
|
+
* @returns A {@link CountParityResult}; `ok === false` when any table has a deficit.
|
|
67
|
+
*
|
|
68
|
+
* @task T11837
|
|
69
|
+
*/
|
|
70
|
+
export declare function computeCountParity(sources: readonly LegacyDbDescriptor[], projectDbPath: string, globalDbPath: string): CountParityResult;
|
|
71
|
+
//# sourceMappingURL=count-parity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"count-parity.d.ts","sourceRoot":"","sources":["../../../src/store/exodus/count-parity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAOH,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAIlE,kFAAkF;AAClF,MAAM,WAAW,gBAAgB;IAC/B,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yCAAyC;IACzC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,sCAAsC;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,wBAAwB;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,yEAAyE;IACzE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,uEAAuE;AACvE,MAAM,WAAW,iBAAiB;IAChC,uDAAuD;IACvD,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC9C,uEAAuE;IACvE,QAAQ,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC/C,gCAAgC;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,wFAAwF;IACxF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAkCD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,SAAS,kBAAkB,EAAE,EACtC,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,MAAM,GACnB,iBAAiB,CA8DnB"}
|