@cleocode/core 2026.5.134 → 2026.6.1
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/binaries/README.md +49 -27
- package/dist/agents/{agent-registry.d.ts → agent-capacity-tracker.d.ts} +2 -2
- package/dist/agents/agent-capacity-tracker.d.ts.map +1 -0
- package/dist/agents/{agent-registry.js → agent-capacity-tracker.js} +2 -2
- package/dist/agents/agent-capacity-tracker.js.map +1 -0
- package/dist/agents/index.d.ts +1 -1
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +4 -2
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/seed-install.d.ts +1 -1
- package/dist/agents/seed-install.d.ts.map +1 -1
- package/dist/agents/seed-install.js +42 -36
- package/dist/agents/seed-install.js.map +1 -1
- package/dist/caamp-export.d.ts +18 -0
- package/dist/caamp-export.d.ts.map +1 -0
- package/dist/caamp-export.js +18 -0
- package/dist/caamp-export.js.map +1 -0
- package/dist/conduit/http-transport.d.ts +7 -13
- package/dist/conduit/http-transport.d.ts.map +1 -1
- package/dist/conduit/http-transport.js +12 -59
- package/dist/conduit/http-transport.js.map +1 -1
- package/dist/conduit/local-transport.d.ts +1 -1
- package/dist/conduit/local-transport.d.ts.map +1 -1
- package/dist/conduit/local-transport.js +69 -43
- package/dist/conduit/local-transport.js.map +1 -1
- package/dist/dispatch/mutate-projection.d.ts.map +1 -1
- package/dist/dispatch/mutate-projection.js +11 -0
- package/dist/dispatch/mutate-projection.js.map +1 -1
- package/dist/docs/docs-read-model.d.ts +7 -0
- package/dist/docs/docs-read-model.d.ts.map +1 -1
- package/dist/docs/docs-read-model.js +5 -0
- package/dist/docs/docs-read-model.js.map +1 -1
- package/dist/docs/export-document.js +40526 -281
- package/dist/docs/export-document.js.map +6 -1
- package/dist/docs/supersede.d.ts.map +1 -1
- package/dist/docs/supersede.js +12 -7
- package/dist/docs/supersede.js.map +1 -1
- package/dist/doctor/db-substrate.d.ts.map +1 -1
- package/dist/doctor/db-substrate.js +10 -9
- package/dist/doctor/db-substrate.js.map +1 -1
- package/dist/git-shim-export.d.ts +18 -0
- package/dist/git-shim-export.d.ts.map +1 -0
- package/dist/git-shim-export.js +18 -0
- package/dist/git-shim-export.js.map +1 -0
- package/dist/hooks/payload-schemas.d.ts +2 -2
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +39 -32
- package/dist/init.js.map +1 -1
- package/dist/internal.d.ts +11 -3
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +14 -5
- package/dist/internal.js.map +1 -1
- package/dist/lafs-export.d.ts +18 -0
- package/dist/lafs-export.d.ts.map +1 -0
- package/dist/lafs-export.js +18 -0
- package/dist/lafs-export.js.map +1 -0
- package/dist/lifecycle/effective-stage.js +1 -1
- package/dist/lifecycle/index.js +1 -1
- package/dist/lifecycle/index.js.map +1 -1
- package/dist/lifecycle/rollup.js +1 -1
- package/dist/llm/credential-pool.d.ts +17 -0
- package/dist/llm/credential-pool.d.ts.map +1 -1
- package/dist/llm/credential-pool.js +40 -1
- package/dist/llm/credential-pool.js.map +1 -1
- package/dist/llm/plugin-facade.d.ts.map +1 -1
- package/dist/llm/plugin-facade.js +43392 -354
- package/dist/llm/plugin-facade.js.map +6 -1
- package/dist/llm/role-executor.d.ts +8 -0
- package/dist/llm/role-executor.d.ts.map +1 -1
- package/dist/llm/role-executor.js +96 -4
- package/dist/llm/role-executor.js.map +1 -1
- package/dist/llm/role-resolver.d.ts.map +1 -1
- package/dist/llm/role-resolver.js +56 -1
- package/dist/llm/role-resolver.js.map +1 -1
- package/dist/llm/transports/codex-oauth-headers.d.ts +51 -0
- package/dist/llm/transports/codex-oauth-headers.d.ts.map +1 -0
- package/dist/llm/transports/codex-oauth-headers.js +89 -0
- package/dist/llm/transports/codex-oauth-headers.js.map +1 -0
- package/dist/memory/claude-mem-migration.d.ts.map +1 -1
- package/dist/memory/claude-mem-migration.js +1 -3
- package/dist/memory/claude-mem-migration.js.map +1 -1
- package/dist/memory/decisions.d.ts.map +1 -1
- package/dist/memory/decisions.js +77 -23
- package/dist/memory/decisions.js.map +1 -1
- package/dist/memory/graph-memory-bridge.d.ts.map +1 -1
- package/dist/memory/graph-memory-bridge.js +12 -6
- package/dist/memory/graph-memory-bridge.js.map +1 -1
- package/dist/memory/learnings.d.ts +2 -2
- package/dist/memory/nexus-plasticity.d.ts +21 -9
- package/dist/memory/nexus-plasticity.d.ts.map +1 -1
- package/dist/memory/nexus-plasticity.js +44 -22
- package/dist/memory/nexus-plasticity.js.map +1 -1
- package/dist/memory/patterns.d.ts +2 -2
- package/dist/memory/redaction.d.ts +19 -3
- package/dist/memory/redaction.d.ts.map +1 -1
- package/dist/memory/redaction.js +64 -129
- package/dist/memory/redaction.js.map +6 -1
- package/dist/metrics/token-service.d.ts +8 -2
- package/dist/metrics/token-service.d.ts.map +1 -1
- package/dist/metrics/token-service.js +1 -1
- package/dist/metrics/token-service.js.map +1 -1
- package/dist/nexus/analyze-orchestrator.d.ts.map +1 -1
- package/dist/nexus/analyze-orchestrator.js +6 -8
- package/dist/nexus/analyze-orchestrator.js.map +1 -1
- package/dist/nexus/api-extractors/http-extractor.d.ts.map +1 -1
- package/dist/nexus/api-extractors/http-extractor.js +3 -3
- package/dist/nexus/api-extractors/http-extractor.js.map +1 -1
- package/dist/nexus/clusters.d.ts.map +1 -1
- package/dist/nexus/clusters.js +3 -2
- package/dist/nexus/clusters.js.map +1 -1
- package/dist/nexus/context.d.ts.map +1 -1
- package/dist/nexus/context.js +10 -16
- package/dist/nexus/context.js.map +1 -1
- package/dist/nexus/diff.d.ts.map +1 -1
- package/dist/nexus/diff.js +6 -4
- package/dist/nexus/diff.js.map +1 -1
- package/dist/nexus/export.d.ts.map +1 -1
- package/dist/nexus/export.js +7 -4
- package/dist/nexus/export.js.map +1 -1
- package/dist/nexus/flows.d.ts.map +1 -1
- package/dist/nexus/flows.js +3 -1
- package/dist/nexus/flows.js.map +1 -1
- package/dist/nexus/impact.d.ts +1 -1
- package/dist/nexus/impact.d.ts.map +1 -1
- package/dist/nexus/impact.js +31 -17
- package/dist/nexus/impact.js.map +1 -1
- package/dist/nexus/living-brain.d.ts.map +1 -1
- package/dist/nexus/living-brain.js +27 -15
- package/dist/nexus/living-brain.js.map +1 -1
- package/dist/nexus/nexus-bridge.d.ts.map +1 -1
- package/dist/nexus/nexus-bridge.js +28 -29
- package/dist/nexus/nexus-bridge.js.map +1 -1
- package/dist/nexus/plasticity-queries.d.ts +4 -2
- package/dist/nexus/plasticity-queries.d.ts.map +1 -1
- package/dist/nexus/plasticity-queries.js +27 -15
- package/dist/nexus/plasticity-queries.js.map +1 -1
- package/dist/nexus/query.d.ts.map +1 -1
- package/dist/nexus/query.js +6 -2
- package/dist/nexus/query.js.map +1 -1
- package/dist/nexus/registry.d.ts.map +1 -1
- package/dist/nexus/registry.js +65 -30
- package/dist/nexus/registry.js.map +1 -1
- package/dist/nexus/route-analysis.d.ts +3 -2
- package/dist/nexus/route-analysis.d.ts.map +1 -1
- package/dist/nexus/route-analysis.js +11 -10
- package/dist/nexus/route-analysis.js.map +1 -1
- package/dist/nexus/sigil.d.ts.map +1 -1
- package/dist/nexus/sigil.js +60 -13
- package/dist/nexus/sigil.js.map +1 -1
- package/dist/nexus/user-profile.d.ts +2 -1
- package/dist/nexus/user-profile.d.ts.map +1 -1
- package/dist/nexus/user-profile.js +8 -4
- package/dist/nexus/user-profile.js.map +1 -1
- package/dist/orchestrate/index.d.ts +1 -1
- package/dist/orchestrate/index.d.ts.map +1 -1
- package/dist/orchestrate/index.js +1 -1
- package/dist/orchestrate/index.js.map +1 -1
- package/dist/orchestrate/plan.d.ts +3 -3
- package/dist/orchestrate/plan.d.ts.map +1 -1
- package/dist/orchestrate/plan.js +7 -7
- package/dist/orchestrate/plan.js.map +1 -1
- package/dist/orchestrate/spawn-ops.js +2 -2
- package/dist/orchestrate/spawn-ops.js.map +1 -1
- package/dist/orchestration/classify.d.ts +2 -2
- package/dist/orchestration/classify.js +3 -3
- package/dist/orchestration/classify.js.map +1 -1
- package/dist/orchestration/validate-spawn.d.ts.map +1 -1
- package/dist/orchestration/validate-spawn.js +5 -5
- package/dist/orchestration/validate-spawn.js.map +1 -1
- package/dist/paths-export.d.ts +18 -0
- package/dist/paths-export.d.ts.map +1 -0
- package/dist/paths-export.js +18 -0
- package/dist/paths-export.js.map +1 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +24 -11
- package/dist/paths.js.map +1 -1
- package/dist/playbooks/index.d.ts +1 -0
- package/dist/playbooks/index.d.ts.map +1 -1
- package/dist/playbooks/index.js +4 -0
- package/dist/playbooks/index.js.map +1 -1
- package/dist/playbooks/skill-node-executor.d.ts +155 -0
- package/dist/playbooks/skill-node-executor.d.ts.map +1 -0
- package/dist/playbooks/skill-node-executor.js +156 -0
- package/dist/playbooks/skill-node-executor.js.map +1 -0
- package/dist/repair.d.ts +3 -7
- package/dist/repair.d.ts.map +1 -1
- package/dist/repair.js +5 -43
- package/dist/repair.js.map +1 -1
- package/dist/sagas/migrate-containment.js +7 -7
- package/dist/sagas/migrate-containment.js.map +1 -1
- package/dist/scaffold/ensure-dirs.d.ts +8 -2
- package/dist/scaffold/ensure-dirs.d.ts.map +1 -1
- package/dist/scaffold/ensure-dirs.js +24 -11
- package/dist/scaffold/ensure-dirs.js.map +1 -1
- package/dist/scaffold/project-detection.d.ts +5 -1
- package/dist/scaffold/project-detection.d.ts.map +1 -1
- package/dist/scaffold/project-detection.js +9 -5
- package/dist/scaffold/project-detection.js.map +1 -1
- package/dist/sentient/hygiene-scan.js +6 -6
- package/dist/sentient/hygiene-scan.js.map +1 -1
- package/dist/sentient/proposal-dedup.js +2 -2
- package/dist/sentient/proposal-rate-limiter.js +1 -1
- package/dist/sentient/propose-tick.js +5 -5
- package/dist/sentient/propose-tick.js.map +1 -1
- package/dist/sentient/stage-drift-tick.js +3 -3
- package/dist/sentient/stage-drift-tick.js.map +1 -1
- package/dist/sequence/index.d.ts.map +1 -1
- package/dist/sequence/index.js +6 -2
- package/dist/sequence/index.js.map +1 -1
- package/dist/sessions/briefing.d.ts.map +1 -1
- package/dist/sessions/briefing.js +18 -4
- package/dist/sessions/briefing.js.map +1 -1
- package/dist/setup/sections/verification.js +2 -2
- package/dist/setup/sections/verification.js.map +1 -1
- package/dist/shutdown.d.ts +91 -0
- package/dist/shutdown.d.ts.map +1 -0
- package/dist/shutdown.js +120 -0
- package/dist/shutdown.js.map +1 -0
- package/dist/skills/index.d.ts +2 -0
- package/dist/skills/index.d.ts.map +1 -1
- package/dist/skills/index.js +1 -0
- package/dist/skills/index.js.map +1 -1
- package/dist/skills/skill-executor-adapter.d.ts +136 -0
- package/dist/skills/skill-executor-adapter.d.ts.map +1 -0
- package/dist/skills/skill-executor-adapter.js +137 -0
- package/dist/skills/skill-executor-adapter.js.map +1 -0
- package/dist/skills/usage-recorder.d.ts.map +1 -1
- package/dist/skills/usage-recorder.js +30 -0
- package/dist/skills/usage-recorder.js.map +1 -1
- package/dist/skills-export.d.ts +23 -0
- package/dist/skills-export.d.ts.map +1 -0
- package/dist/skills-export.js +23 -0
- package/dist/skills-export.js.map +1 -0
- package/dist/stats/index.d.ts.map +1 -1
- package/dist/stats/index.js +8 -3
- package/dist/stats/index.js.map +1 -1
- package/dist/store/agent-doctor.d.ts +3 -3
- package/dist/store/agent-doctor.d.ts.map +1 -1
- package/dist/store/agent-doctor.js +18 -13
- package/dist/store/agent-doctor.js.map +1 -1
- package/dist/store/agent-install.d.ts +6 -5
- package/dist/store/agent-install.d.ts.map +1 -1
- package/dist/store/agent-install.js +20 -16
- package/dist/store/agent-install.js.map +1 -1
- package/dist/store/agent-registry-accessor.d.ts +66 -28
- package/dist/store/agent-registry-accessor.d.ts.map +1 -1
- package/dist/store/agent-registry-accessor.js +248 -167
- package/dist/store/agent-registry-accessor.js.map +1 -1
- package/dist/store/agent-registry-store.d.ts +242 -0
- package/dist/store/agent-registry-store.d.ts.map +1 -0
- package/dist/store/agent-registry-store.js +501 -0
- package/dist/store/agent-registry-store.js.map +1 -0
- package/dist/store/agent-resolver.d.ts +8 -8
- package/dist/store/agent-resolver.d.ts.map +1 -1
- package/dist/store/agent-resolver.js +19 -17
- package/dist/store/agent-resolver.js.map +1 -1
- package/dist/store/backup-pack.d.ts.map +1 -1
- package/dist/store/backup-pack.js +24 -8
- package/dist/store/backup-pack.js.map +1 -1
- package/dist/store/conduit-sqlite.d.ts +181 -74
- package/dist/store/conduit-sqlite.d.ts.map +1 -1
- package/dist/store/conduit-sqlite.js +307 -528
- package/dist/store/conduit-sqlite.js.map +1 -1
- package/dist/store/cross-db-cleanup.d.ts +5 -5
- package/dist/store/cross-db-cleanup.d.ts.map +1 -1
- package/dist/store/cross-db-cleanup.js +12 -10
- package/dist/store/cross-db-cleanup.js.map +1 -1
- package/dist/store/data-accessor.d.ts +4 -4
- package/dist/store/data-accessor.js +5 -5
- package/dist/store/data-accessor.js.map +1 -1
- package/dist/store/data-safety-central.d.ts +83 -1
- package/dist/store/data-safety-central.d.ts.map +1 -1
- package/dist/store/data-safety-central.js +257 -0
- package/dist/store/data-safety-central.js.map +1 -1
- package/dist/store/db-helpers.d.ts +8 -2
- package/dist/store/db-helpers.d.ts.map +1 -1
- package/dist/store/db-helpers.js +6 -2
- package/dist/store/db-helpers.js.map +1 -1
- package/dist/store/dual-scope-db.d.ts +46 -4
- package/dist/store/dual-scope-db.d.ts.map +1 -1
- package/dist/store/dual-scope-db.js +103 -9
- package/dist/store/dual-scope-db.js.map +1 -1
- package/dist/store/exodus/__fixtures__/representative-fixture.d.ts +116 -0
- package/dist/store/exodus/__fixtures__/representative-fixture.d.ts.map +1 -0
- package/dist/store/exodus/__fixtures__/representative-fixture.js +274 -0
- package/dist/store/exodus/__fixtures__/representative-fixture.js.map +1 -0
- package/dist/store/exodus/index.d.ts +3 -1
- package/dist/store/exodus/index.d.ts.map +1 -1
- package/dist/store/exodus/index.js +3 -1
- package/dist/store/exodus/index.js.map +1 -1
- package/dist/store/exodus/migrate.d.ts +120 -1
- package/dist/store/exodus/migrate.d.ts.map +1 -1
- package/dist/store/exodus/migrate.js +923 -119
- package/dist/store/exodus/migrate.js.map +1 -1
- package/dist/store/exodus/on-open.d.ts +189 -0
- package/dist/store/exodus/on-open.d.ts.map +1 -0
- package/dist/store/exodus/on-open.js +464 -0
- package/dist/store/exodus/on-open.js.map +1 -0
- package/dist/store/exodus/table-name-map.d.ts +173 -0
- package/dist/store/exodus/table-name-map.d.ts.map +1 -0
- package/dist/store/exodus/table-name-map.js +660 -0
- package/dist/store/exodus/table-name-map.js.map +1 -0
- package/dist/store/exodus/verify-migration.d.ts +72 -0
- package/dist/store/exodus/verify-migration.d.ts.map +1 -0
- package/dist/store/exodus/verify-migration.js +678 -0
- package/dist/store/exodus/verify-migration.js.map +1 -0
- package/dist/store/exodus/verify.d.ts +32 -8
- package/dist/store/exodus/verify.d.ts.map +1 -1
- package/dist/store/exodus/verify.js +48 -142
- package/dist/store/exodus/verify.js.map +1 -1
- package/dist/store/index.d.ts +2 -3
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +2 -3
- package/dist/store/index.js.map +1 -1
- package/dist/store/memory-accessor.d.ts +31 -0
- package/dist/store/memory-accessor.d.ts.map +1 -1
- package/dist/store/memory-accessor.js +38 -0
- package/dist/store/memory-accessor.js.map +1 -1
- package/dist/store/memory-sqlite.d.ts +86 -13
- package/dist/store/memory-sqlite.d.ts.map +1 -1
- package/dist/store/memory-sqlite.js +326 -528
- package/dist/store/memory-sqlite.js.map +1 -1
- package/dist/store/migrate-signaldock-to-conduit.d.ts +1 -1
- package/dist/store/migrate-signaldock-to-conduit.d.ts.map +1 -1
- package/dist/store/migrate-signaldock-to-conduit.js +126 -35
- package/dist/store/migrate-signaldock-to-conduit.js.map +1 -1
- package/dist/store/migration-manager.d.ts +49 -0
- package/dist/store/migration-manager.d.ts.map +1 -1
- package/dist/store/migration-manager.js +167 -67
- package/dist/store/migration-manager.js.map +1 -1
- package/dist/store/migration-sqlite.d.ts +1 -1
- package/dist/store/migration-sqlite.d.ts.map +1 -1
- package/dist/store/migration-sqlite.js +32 -3
- package/dist/store/migration-sqlite.js.map +1 -1
- package/dist/store/nexus-sqlite.d.ts +152 -29
- package/dist/store/nexus-sqlite.d.ts.map +1 -1
- package/dist/store/nexus-sqlite.js +496 -177
- package/dist/store/nexus-sqlite.js.map +1 -1
- package/dist/store/nexus-validation-schemas.d.ts +32 -32
- package/dist/store/open-cleo-db.d.ts +37 -40
- package/dist/store/open-cleo-db.d.ts.map +1 -1
- package/dist/store/open-cleo-db.js +76 -153
- package/dist/store/open-cleo-db.js.map +1 -1
- package/dist/store/role-accessors-impl.d.ts +4 -4
- package/dist/store/role-accessors-impl.d.ts.map +1 -1
- package/dist/store/role-accessors-impl.js +18 -15
- package/dist/store/role-accessors-impl.js.map +1 -1
- package/dist/store/schema/{signaldock-schema.d.ts → agent-registry-schema.d.ts} +15 -5
- package/dist/store/schema/agent-registry-schema.d.ts.map +1 -0
- package/dist/store/schema/{signaldock-schema.js → agent-registry-schema.js} +15 -5
- package/dist/store/schema/agent-registry-schema.js.map +1 -0
- package/dist/store/schema/agent-schema.d.ts +1 -1
- package/dist/store/schema/agent-schema.js +4 -4
- package/dist/store/schema/agent-schema.js.map +1 -1
- package/dist/store/schema/attachments.d.ts +1 -1
- package/dist/store/schema/audit.d.ts +15 -5
- package/dist/store/schema/audit.d.ts.map +1 -1
- package/dist/store/schema/audit.js +12 -2
- package/dist/store/schema/audit.js.map +1 -1
- package/dist/store/schema/background-jobs.d.ts +1 -1
- package/dist/store/schema/cleo-global/{signaldock.d.ts → agent-registry.d.ts} +277 -271
- package/dist/store/schema/cleo-global/agent-registry.d.ts.map +1 -0
- package/dist/store/schema/cleo-global/{signaldock.js → agent-registry.js} +136 -125
- package/dist/store/schema/cleo-global/agent-registry.js.map +1 -0
- package/dist/store/schema/cleo-global/index.d.ts +29 -22
- package/dist/store/schema/cleo-global/index.d.ts.map +1 -1
- package/dist/store/schema/cleo-global/index.js +29 -22
- package/dist/store/schema/cleo-global/index.js.map +1 -1
- package/dist/store/schema/cleo-global/nexus.d.ts +36 -1034
- package/dist/store/schema/cleo-global/nexus.d.ts.map +1 -1
- package/dist/store/schema/cleo-global/nexus.js +32 -337
- package/dist/store/schema/cleo-global/nexus.js.map +1 -1
- package/dist/store/schema/cleo-global/skills.d.ts +16 -0
- package/dist/store/schema/cleo-global/skills.d.ts.map +1 -1
- package/dist/store/schema/cleo-global/skills.js +11 -0
- package/dist/store/schema/cleo-global/skills.js.map +1 -1
- package/dist/store/schema/{cleo-project → cleo-global}/telemetry.d.ts +33 -17
- package/dist/store/schema/cleo-global/telemetry.d.ts.map +1 -0
- package/dist/store/schema/{cleo-project → cleo-global}/telemetry.js +30 -18
- package/dist/store/schema/cleo-global/telemetry.js.map +1 -0
- package/dist/store/schema/cleo-project/audit.d.ts +8 -8
- package/dist/store/schema/cleo-project/audit.d.ts.map +1 -1
- package/dist/store/schema/cleo-project/audit.js +2 -6
- package/dist/store/schema/cleo-project/audit.js.map +1 -1
- package/dist/store/schema/cleo-project/docs.d.ts +1 -1
- package/dist/store/schema/cleo-project/index.d.ts +29 -12
- package/dist/store/schema/cleo-project/index.d.ts.map +1 -1
- package/dist/store/schema/cleo-project/index.js +29 -12
- package/dist/store/schema/cleo-project/index.js.map +1 -1
- package/dist/store/schema/cleo-project/lifecycle.d.ts +2 -2
- package/dist/store/schema/cleo-project/nexus-graph.d.ts +1067 -0
- package/dist/store/schema/cleo-project/nexus-graph.d.ts.map +1 -0
- package/dist/store/schema/cleo-project/nexus-graph.js +407 -0
- package/dist/store/schema/cleo-project/nexus-graph.js.map +1 -0
- package/dist/store/schema/cleo-project/provenance-orphans.d.ts +385 -0
- package/dist/store/schema/cleo-project/provenance-orphans.d.ts.map +1 -0
- package/dist/store/schema/cleo-project/provenance-orphans.js +142 -0
- package/dist/store/schema/cleo-project/provenance-orphans.js.map +1 -0
- package/dist/store/schema/cleo-project/provenance-rest.d.ts +1 -1
- package/dist/store/schema/cleo-project/runtime.d.ts +1 -1
- package/dist/store/schema/cleo-project/tasks-core-batch2.d.ts +1 -1
- package/dist/store/schema/cleo-project/tasks-core.d.ts +3 -3
- package/dist/store/schema/cleo-shared/brain.d.ts +711 -494
- package/dist/store/schema/cleo-shared/brain.d.ts.map +1 -1
- package/dist/store/schema/cleo-shared/brain.js +215 -134
- package/dist/store/schema/cleo-shared/brain.js.map +1 -1
- package/dist/store/schema/conduit-schema.d.ts +63 -51
- package/dist/store/schema/conduit-schema.d.ts.map +1 -1
- package/dist/store/schema/conduit-schema.js +23 -11
- package/dist/store/schema/conduit-schema.js.map +1 -1
- package/dist/store/schema/goal.d.ts +3 -2
- package/dist/store/schema/goal.d.ts.map +1 -1
- package/dist/store/schema/goal.js +3 -2
- package/dist/store/schema/goal.js.map +1 -1
- package/dist/store/schema/index.d.ts +1 -0
- package/dist/store/schema/index.d.ts.map +1 -1
- package/dist/store/schema/index.js +1 -0
- package/dist/store/schema/index.js.map +1 -1
- package/dist/store/schema/lifecycle.d.ts +2 -2
- package/dist/store/schema/memory-schema.d.ts +2 -2
- package/dist/store/schema/nexus-schema.d.ts +174 -115
- package/dist/store/schema/nexus-schema.d.ts.map +1 -1
- package/dist/store/schema/nexus-schema.js +175 -55
- package/dist/store/schema/nexus-schema.js.map +1 -1
- package/dist/store/schema/provenance/releases.d.ts +1 -1
- package/dist/store/schema/schema-utils.d.ts +78 -0
- package/dist/store/schema/schema-utils.d.ts.map +1 -0
- package/dist/store/schema/schema-utils.js +49 -0
- package/dist/store/schema/schema-utils.js.map +1 -0
- package/dist/store/schema/skills-schema.d.ts +81 -44
- package/dist/store/schema/skills-schema.d.ts.map +1 -1
- package/dist/store/schema/skills-schema.js +49 -16
- package/dist/store/schema/skills-schema.js.map +1 -1
- package/dist/store/schema/tasks.d.ts +3 -3
- package/dist/store/skills-db.d.ts +90 -50
- package/dist/store/skills-db.d.ts.map +1 -1
- package/dist/store/skills-db.js +132 -146
- package/dist/store/skills-db.js.map +1 -1
- package/dist/store/sqlite-backup.d.ts +2 -2
- package/dist/store/sqlite-backup.d.ts.map +1 -1
- package/dist/store/sqlite-backup.js +11 -10
- package/dist/store/sqlite-backup.js.map +1 -1
- package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
- package/dist/store/sqlite-data-accessor.js +25 -18
- package/dist/store/sqlite-data-accessor.js.map +1 -1
- package/dist/store/sqlite.d.ts +72 -12
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +153 -89
- package/dist/store/sqlite.js.map +1 -1
- package/dist/store/tasks-schema.d.ts +4 -0
- package/dist/store/tasks-schema.d.ts.map +1 -1
- package/dist/store/tasks-schema.js +60 -0
- package/dist/store/tasks-schema.js.map +1 -1
- package/dist/store/tasks-sqlite.d.ts +2 -2
- package/dist/store/tasks-sqlite.d.ts.map +1 -1
- package/dist/store/tasks-sqlite.js +10 -5
- package/dist/store/tasks-sqlite.js.map +1 -1
- package/dist/store/umbrella-data-accessor.d.ts +17 -6
- package/dist/store/umbrella-data-accessor.d.ts.map +1 -1
- package/dist/store/umbrella-data-accessor.js +8 -8
- package/dist/store/umbrella-data-accessor.js.map +1 -1
- package/dist/store/validation-schemas.d.ts +241 -208
- package/dist/store/validation-schemas.d.ts.map +1 -1
- package/dist/system/health.d.ts.map +1 -1
- package/dist/system/health.js +11 -6
- package/dist/system/health.js.map +1 -1
- package/dist/system/project-health.d.ts.map +1 -1
- package/dist/system/project-health.js +58 -12
- package/dist/system/project-health.js.map +1 -1
- package/dist/tasks/add.d.ts +8 -0
- package/dist/tasks/add.d.ts.map +1 -1
- package/dist/tasks/add.js +101 -0
- package/dist/tasks/add.js.map +1 -1
- package/dist/tasks/cancelled-child-waiver-audit.d.ts +47 -0
- package/dist/tasks/cancelled-child-waiver-audit.d.ts.map +1 -0
- package/dist/tasks/cancelled-child-waiver-audit.js +34 -0
- package/dist/tasks/cancelled-child-waiver-audit.js.map +1 -0
- package/dist/tasks/complete.d.ts +22 -2
- package/dist/tasks/complete.d.ts.map +1 -1
- package/dist/tasks/complete.js +71 -6
- package/dist/tasks/complete.js.map +1 -1
- package/dist/tasks/compute-task-view.js +1 -1
- package/dist/tasks/session-scope.d.ts +5 -0
- package/dist/tasks/session-scope.d.ts.map +1 -1
- package/dist/tasks/session-scope.js +4 -0
- package/dist/tasks/session-scope.js.map +1 -1
- package/dist/tools/guard.d.ts +71 -1
- package/dist/tools/guard.d.ts.map +1 -1
- package/dist/tools/guard.js +73 -1
- package/dist/tools/guard.js.map +1 -1
- package/dist/tools/index.d.ts +21 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +25 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +22 -13
- package/dist/upgrade.js.map +1 -1
- package/dist/workgraph/containment.js +18 -18
- package/dist/workgraph/relations.js +2 -2
- package/dist/worktree/list.d.ts +1 -1
- package/dist/worktree/list.d.ts.map +1 -1
- package/dist/worktree/list.js +19 -21
- package/dist/worktree/list.js.map +1 -1
- package/dist/worktree-export.d.ts +18 -0
- package/dist/worktree-export.d.ts.map +1 -0
- package/dist/worktree-export.js +18 -0
- package/dist/worktree-export.js.map +1 -0
- package/migrations/drizzle-agent-registry/20260412000000_initial-global-agent-registry/migration.sql +29 -0
- package/migrations/drizzle-brain/20260601000001_t11522-brain-task-observations/migration.sql +28 -0
- package/migrations/drizzle-brain/20260601000002_t11522-inline-only-brain-tables/migration.sql +75 -0
- package/migrations/drizzle-cleo-global/20260531000001_t11363-consolidation-cleo-global/migration.sql +49 -144
- package/migrations/drizzle-cleo-global/20260531000001_t11363-consolidation-cleo-global/snapshot.json +8 -8
- package/migrations/drizzle-cleo-global/20260531000002_t11546-brain-usage-log/migration.sql +16 -0
- package/migrations/drizzle-cleo-global/20260601000001_t11544-skills-usage-project-id/migration.sql +12 -0
- package/migrations/drizzle-cleo-global/20260602000001_t11622-agent-registry-rename/migration.sql +80 -0
- package/migrations/drizzle-cleo-project/20260531000001_t11363-consolidation-cleo-project/migration.sql +26 -167
- package/migrations/drizzle-cleo-project/20260531000001_t11363-consolidation-cleo-project/snapshot.json +8 -8
- package/migrations/drizzle-cleo-project/20260531000002_t11546-brain-usage-log/migration.sql +21 -0
- package/migrations/drizzle-cleo-project/20260601000001_t11549-agent-credentials-brain-release-links/migration.sql +49 -0
- package/migrations/drizzle-cleo-project/20260601000002_t11538-project-nexus-graph/migration.sql +140 -0
- package/migrations/drizzle-cleo-project/20260602000002_t11649-token-usage-transport-mcp/migration.sql +146 -0
- package/migrations/drizzle-conduit/20260601000003_t11523-conduit-inline-schema/migration.sql +82 -0
- package/migrations/drizzle-nexus/20260421200001_t1165-baseline-reset/migration.sql +26 -8
- package/migrations/drizzle-nexus/20260601000001_t11545-nexus-relation-weights-partition/migration.sql +97 -0
- package/package.json +45 -13
- package/scripts/install-supervisor-binary.mjs +50 -201
- package/scripts/napi-binary-picker.mjs +267 -0
- package/dist/agents/agent-registry.d.ts.map +0 -1
- package/dist/agents/agent-registry.js.map +0 -1
- package/dist/store/data-safety.d.ts +0 -92
- package/dist/store/data-safety.d.ts.map +0 -1
- package/dist/store/data-safety.js +0 -274
- package/dist/store/data-safety.js.map +0 -1
- package/dist/store/schema/cleo-global/signaldock.d.ts.map +0 -1
- package/dist/store/schema/cleo-global/signaldock.js.map +0 -1
- package/dist/store/schema/cleo-project/telemetry.d.ts.map +0 -1
- package/dist/store/schema/cleo-project/telemetry.js.map +0 -1
- package/dist/store/schema/signaldock-schema.d.ts.map +0 -1
- package/dist/store/schema/signaldock-schema.js.map +0 -1
- package/dist/store/signaldock-sqlite.d.ts +0 -173
- package/dist/store/signaldock-sqlite.d.ts.map +0 -1
- package/dist/store/signaldock-sqlite.js +0 -445
- package/dist/store/signaldock-sqlite.js.map +0 -1
- package/migrations/drizzle-nexus/20260318205558_initial/migration.sql +0 -46
- package/migrations/drizzle-nexus/20260412000001_t529-nexus-graph-tables/migration.sql +0 -49
- package/migrations/drizzle-nexus/20260415000001_t622-project-registry-paths/migration.sql +0 -12
- package/migrations/drizzle-nexus/20260419000001_t998-nexus-plasticity/migration.sql +0 -13
- package/migrations/drizzle-nexus/20260423052640_t1077-add-user-profile-table/migration.sql +0 -16
- package/migrations/drizzle-nexus/20260423052640_t1077-add-user-profile-table/snapshot.json +0 -1531
- package/migrations/drizzle-nexus/20260424140538_t1148-add-sigils-table/migration.sql +0 -13
- package/migrations/drizzle-nexus/20260424140538_t1148-add-sigils-table/snapshot.json +0 -1652
- package/migrations/drizzle-nexus/20260504000001_t1839-fts5-nexus-symbols/migration.sql +0 -68
- package/migrations/drizzle-nexus/20260507135519_t9163-nexus-is-external/migration.sql +0 -20
- package/migrations/drizzle-nexus/20260507135519_t9163-nexus-is-external/snapshot.json +0 -1652
- package/migrations/drizzle-nexus/20260526222449_t11025-project-id-aliases/migration.sql +0 -16
- package/migrations/drizzle-signaldock/20260412000000_initial-global-signaldock/migration.sql +0 -209
- package/migrations/drizzle-signaldock/20260412000000_initial-global-signaldock/snapshot.json +0 -2060
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reusable CORE migration-parity primitive — `verifyMigration()`.
|
|
3
|
+
*
|
|
4
|
+
* This is the durable guard (T11551 · DHQ-045) that prevents the exodus
|
|
5
|
+
* dual-DB consolidation from silently losing rows. It is intentionally generic:
|
|
6
|
+
* given a set of legacy source DBs and the consolidated target DB paths, it
|
|
7
|
+
* returns a typed {@link VerifyMigrationResult} covering FOUR failure classes:
|
|
8
|
+
*
|
|
9
|
+
* 1. **Per-table row-count parity** — every data-bearing source table's
|
|
10
|
+
* consolidated counterpart MUST NOT have FEWER rows than the source (a
|
|
11
|
+
* DEFICIT = data loss → failure). A SURPLUS (target > source — e.g.
|
|
12
|
+
* `nexus_audit_log` gaining the migration's own audit writes during the
|
|
13
|
+
* migrating open) is NOT loss and is tolerated with a WARN (T11577). The
|
|
14
|
+
* per-table `countMatch` field stays a strict `source === target`
|
|
15
|
+
* diagnostic; only a deficit contributes a failure.
|
|
16
|
+
* 2. **`PRAGMA foreign_key_check`** — genuine referential orphans on the
|
|
17
|
+
* consolidated target surface as failures (not copy-order artifacts).
|
|
18
|
+
* 3. **Content checksum** — an ordered canonical-JSON SHA-256 digest over the
|
|
19
|
+
* sorted intersection of source/target columns catches content drift even
|
|
20
|
+
* when counts match.
|
|
21
|
+
* 4. **Enum/type-drift report** — source values outside the target column's
|
|
22
|
+
* CHECK enum. This is the EXACT class that `INSERT OR IGNORE` used to drop
|
|
23
|
+
* silently (the root cause of the ~805K-row exodus loss).
|
|
24
|
+
*
|
|
25
|
+
* ## Relationship to `runExodusVerify`
|
|
26
|
+
*
|
|
27
|
+
* `runExodusVerify` ({@link ./verify.ts}) is the exodus-specific entry point.
|
|
28
|
+
* It now DELEGATES the row-count + checksum parity to this primitive (DRY —
|
|
29
|
+
* T11551 AC2) and additionally surfaces the FK + enum-drift checks this module
|
|
30
|
+
* adds. The digest, name-mapping, and rowid-safe ordering logic that the exodus
|
|
31
|
+
* campaign hardened (T11531/32/33) live here as the single implementation.
|
|
32
|
+
*
|
|
33
|
+
* ## False-pass guard (T11531, preserved)
|
|
34
|
+
*
|
|
35
|
+
* When `ok === false`, `error` is ALWAYS populated with a human-readable
|
|
36
|
+
* failure summary so a caller that only checks `result.error` cannot mistake a
|
|
37
|
+
* silent loss for success.
|
|
38
|
+
*
|
|
39
|
+
* @task T11551 (DHQ-045 — exodus zero-loss durable guard)
|
|
40
|
+
* @epic T10878
|
|
41
|
+
* @saga T11242
|
|
42
|
+
*/
|
|
43
|
+
import { existsSync } from 'node:fs';
|
|
44
|
+
import { createRequire } from 'node:module';
|
|
45
|
+
import { MIGRATION_ENUM_DRIFT_SAMPLE_LIMIT } from '@cleocode/contracts';
|
|
46
|
+
import { getLogger } from '../../logger.js';
|
|
47
|
+
import { openCleoDbSnapshot } from '../open-cleo-db.js';
|
|
48
|
+
import { resolveConsolidatedTableName, resolveTableTargetScope } from './table-name-map.js';
|
|
49
|
+
const log = getLogger('verify-migration');
|
|
50
|
+
const _require = createRequire(import.meta.url);
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Digest helpers (rowid-safe ORDER BY + column-intersection digest)
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
/**
|
|
55
|
+
* Determine a deterministic ORDER BY clause for a table.
|
|
56
|
+
*
|
|
57
|
+
* Uses the table's declared primary-key columns (from `PRAGMA table_info`
|
|
58
|
+
* where `pk > 0`) so ordering is stable for both WITH ROWID and WITHOUT ROWID
|
|
59
|
+
* tables. Falls back to `rowid` only for ordinary tables that declare no
|
|
60
|
+
* explicit primary key. Avoids the `no such column: rowid` crash on virtual /
|
|
61
|
+
* WITHOUT ROWID tables (T11532 ROOT CAUSE 3).
|
|
62
|
+
*
|
|
63
|
+
* @param db - Database handle to introspect.
|
|
64
|
+
* @param tableName - Physical table name.
|
|
65
|
+
* @returns A SQL ORDER BY column list.
|
|
66
|
+
*/
|
|
67
|
+
function orderByClause(db, tableName) {
|
|
68
|
+
try {
|
|
69
|
+
const pragma = db.prepare(`PRAGMA table_info("${tableName}")`).all();
|
|
70
|
+
const pkCols = pragma
|
|
71
|
+
.filter((r) => r.pk > 0)
|
|
72
|
+
.sort((a, b) => a.pk - b.pk)
|
|
73
|
+
.map((r) => `"${r.name}"`);
|
|
74
|
+
if (pkCols.length > 0) {
|
|
75
|
+
return pkCols.join(', ');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Ignore — fall through to rowid
|
|
80
|
+
}
|
|
81
|
+
return 'rowid';
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Compute an ordered canonical-JSON SHA-256 digest (32 hex chars) for all rows
|
|
85
|
+
* in a table, restricted to the given column list.
|
|
86
|
+
*
|
|
87
|
+
* When the caller passes the SORTED INTERSECTION of source and target columns,
|
|
88
|
+
* both sides produce identically-structured JSON rows, eliminating spurious
|
|
89
|
+
* hash mismatches from schema-definition column reordering (T11533 ROOT CAUSE
|
|
90
|
+
* 4). Returns `{ count: 0, hash: '' }` for virtual tables that cannot be
|
|
91
|
+
* selected from, rather than throwing.
|
|
92
|
+
*
|
|
93
|
+
* @param db - Database handle to query.
|
|
94
|
+
* @param tableName - Physical table name.
|
|
95
|
+
* @param columns - Explicit column list in canonical order, or `null` for
|
|
96
|
+
* `SELECT *` (used when there is no counterpart table to intersect with).
|
|
97
|
+
* @returns `{ count, hash }` for the table.
|
|
98
|
+
*/
|
|
99
|
+
function computeTableDigest(db, tableName, columns) {
|
|
100
|
+
const { createHash } = _require('node:crypto');
|
|
101
|
+
const hasher = createHash('sha256');
|
|
102
|
+
const orderBy = orderByClause(db, tableName);
|
|
103
|
+
const selectClause = columns !== null && columns.length > 0 ? columns.map((c) => `"${c}"`).join(', ') : '*';
|
|
104
|
+
let rows;
|
|
105
|
+
try {
|
|
106
|
+
rows = db
|
|
107
|
+
.prepare(`SELECT ${selectClause} FROM "${tableName}" ORDER BY ${orderBy}`)
|
|
108
|
+
.all();
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
112
|
+
log.warn({ tableName, err: msg }, 'computeTableDigest: SELECT failed (possibly a virtual/FTS table) — treating as 0 rows');
|
|
113
|
+
return { count: 0, hash: '' };
|
|
114
|
+
}
|
|
115
|
+
for (const row of rows) {
|
|
116
|
+
hasher.update(JSON.stringify(row));
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
count: rows.length,
|
|
120
|
+
hash: hasher.digest('hex').slice(0, 32),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Return the sorted intersection of column names present in both the source and
|
|
125
|
+
* target tables, for use as the canonical column ordering in
|
|
126
|
+
* {@link computeTableDigest}. Returns `null` when either side has no columns
|
|
127
|
+
* (virtual/FTS-table fallback).
|
|
128
|
+
*
|
|
129
|
+
* @param srcDb - Source database handle.
|
|
130
|
+
* @param srcTable - Physical table name in the source DB.
|
|
131
|
+
* @param tgtDb - Target database handle.
|
|
132
|
+
* @param tgtTable - Physical table name in the target DB.
|
|
133
|
+
*/
|
|
134
|
+
function sharedColumnsSorted(srcDb, srcTable, tgtDb, tgtTable) {
|
|
135
|
+
try {
|
|
136
|
+
const srcCols = srcDb.prepare(`PRAGMA table_info("${srcTable}")`).all().map((r) => r.name);
|
|
137
|
+
const tgtColSet = new Set(tgtDb.prepare(`PRAGMA table_info("${tgtTable}")`).all().map((r) => r.name));
|
|
138
|
+
if (srcCols.length === 0 || tgtColSet.size === 0)
|
|
139
|
+
return null;
|
|
140
|
+
return srcCols.filter((c) => tgtColSet.has(c)).sort();
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* List user tables in a DB (excluding SQLite internals + Drizzle journal).
|
|
148
|
+
*/
|
|
149
|
+
function listTables(db) {
|
|
150
|
+
const rows = db
|
|
151
|
+
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '__drizzle_%' ORDER BY name")
|
|
152
|
+
.all();
|
|
153
|
+
return rows.map((r) => r.name);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Return `true` if `tableName` exists in `db`. Used to route an FK-orphan row to
|
|
157
|
+
* the scope DB that actually declares its child table (T11572).
|
|
158
|
+
*/
|
|
159
|
+
function tableExists(db, tableName) {
|
|
160
|
+
try {
|
|
161
|
+
const escaped = tableName.replace(/'/g, "''");
|
|
162
|
+
return (db.prepare(`SELECT 1 FROM sqlite_master WHERE type='table' AND name='${escaped}'`).get() !==
|
|
163
|
+
undefined);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// Enum/type-drift detection
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
/**
|
|
173
|
+
* Regex that extracts a single-column `IN (...)` CHECK enum from a table's DDL.
|
|
174
|
+
*
|
|
175
|
+
* Matches the canonical Drizzle-generated form:
|
|
176
|
+
* `CHECK ("col" IN ('a', 'b', 'c'))`
|
|
177
|
+
* and the NULL-tolerant variant:
|
|
178
|
+
* `CHECK ("col" IS NULL OR "col" IN ('a', 'b'))`
|
|
179
|
+
*
|
|
180
|
+
* Capture group 1 is the column name; group 2 is the raw `'a', 'b', …` list.
|
|
181
|
+
* The regex is intentionally conservative — it only recognises the
|
|
182
|
+
* string-literal `IN` enum shape (the one that drops rows on drift), not GLOB
|
|
183
|
+
* or arithmetic CHECKs.
|
|
184
|
+
*/
|
|
185
|
+
const CHECK_ENUM_REGEX = /CHECK\s*\(\s*"([^"]+)"\s+(?:IS\s+NULL\s+OR\s+"[^"]+"\s+)?IN\s*\(([^)]*)\)\s*\)/gi;
|
|
186
|
+
/**
|
|
187
|
+
* Parse the `'a', 'b', 'c'` body of an `IN (...)` clause into the set of
|
|
188
|
+
* canonical enum members (single-quoted SQL string literals, `''` un-escaped).
|
|
189
|
+
*/
|
|
190
|
+
function parseEnumMembers(body) {
|
|
191
|
+
const members = [];
|
|
192
|
+
// Match single-quoted literals, handling the SQL `''` escape for a quote.
|
|
193
|
+
for (const m of body.matchAll(/'((?:[^']|'')*)'/g)) {
|
|
194
|
+
members.push(m[1].replace(/''/g, "'"));
|
|
195
|
+
}
|
|
196
|
+
return members;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Read a target table's DDL and return a map of `column → allowed enum members`
|
|
200
|
+
* for every single-column string-literal CHECK enum on that table.
|
|
201
|
+
*
|
|
202
|
+
* @param db - Target DB with the consolidated schema.
|
|
203
|
+
* @param tableName - Physical consolidated table name.
|
|
204
|
+
* @returns Map from column name to its allowed enum members. Empty when the
|
|
205
|
+
* table declares no recognised CHECK enums.
|
|
206
|
+
*/
|
|
207
|
+
function detectCheckEnums(db, tableName) {
|
|
208
|
+
const escaped = tableName.replace(/'/g, "''");
|
|
209
|
+
const row = db
|
|
210
|
+
.prepare(`SELECT sql FROM sqlite_master WHERE type='table' AND name='${escaped}'`)
|
|
211
|
+
.get();
|
|
212
|
+
const out = new Map();
|
|
213
|
+
if (!row?.sql)
|
|
214
|
+
return out;
|
|
215
|
+
CHECK_ENUM_REGEX.lastIndex = 0;
|
|
216
|
+
for (const match of row.sql.matchAll(CHECK_ENUM_REGEX)) {
|
|
217
|
+
const col = match[1];
|
|
218
|
+
const members = parseEnumMembers(match[2]);
|
|
219
|
+
if (members.length > 0)
|
|
220
|
+
out.set(col, members);
|
|
221
|
+
}
|
|
222
|
+
return out;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Detect enum/type drift for one source→target table pair: source values in an
|
|
226
|
+
* enum-constrained column that are NOT members of the target CHECK enum.
|
|
227
|
+
*
|
|
228
|
+
* Reads the DISTINCT non-null values of each enum column from the source table
|
|
229
|
+
* and compares them against the target's allowed members. Only columns present
|
|
230
|
+
* in BOTH source and target are inspected. The check is purely diagnostic — it
|
|
231
|
+
* reports raw source drift; the migration layer is responsible for normalising
|
|
232
|
+
* known aliases before insert.
|
|
233
|
+
*
|
|
234
|
+
* @param srcDb - Source DB handle.
|
|
235
|
+
* @param srcTable - Physical source table name.
|
|
236
|
+
* @param tgtDb - Target DB handle.
|
|
237
|
+
* @param tgtTable - Physical consolidated target table name.
|
|
238
|
+
* @returns Drift findings for this table (empty when fully canonical).
|
|
239
|
+
*/
|
|
240
|
+
function detectTableEnumDrift(srcDb, srcTable, tgtDb, tgtTable) {
|
|
241
|
+
const enums = detectCheckEnums(tgtDb, tgtTable);
|
|
242
|
+
if (enums.size === 0)
|
|
243
|
+
return [];
|
|
244
|
+
let srcCols;
|
|
245
|
+
try {
|
|
246
|
+
srcCols = new Set(srcDb.prepare(`PRAGMA table_info("${srcTable}")`).all().map((r) => r.name));
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return [];
|
|
250
|
+
}
|
|
251
|
+
const findings = [];
|
|
252
|
+
for (const [col, allowed] of enums) {
|
|
253
|
+
if (!srcCols.has(col))
|
|
254
|
+
continue;
|
|
255
|
+
const allowedSet = new Set(allowed);
|
|
256
|
+
let rows;
|
|
257
|
+
try {
|
|
258
|
+
rows = srcDb
|
|
259
|
+
.prepare(`SELECT "${col}" AS v, COUNT(*) AS c FROM "${srcTable}" WHERE "${col}" IS NOT NULL GROUP BY "${col}"`)
|
|
260
|
+
.all();
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const offending = [];
|
|
266
|
+
let driftCount = 0;
|
|
267
|
+
for (const r of rows) {
|
|
268
|
+
const value = String(r.v);
|
|
269
|
+
if (!allowedSet.has(value)) {
|
|
270
|
+
driftCount += r.c;
|
|
271
|
+
if (offending.length < MIGRATION_ENUM_DRIFT_SAMPLE_LIMIT)
|
|
272
|
+
offending.push(value);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (driftCount > 0) {
|
|
276
|
+
findings.push({
|
|
277
|
+
targetTable: tgtTable,
|
|
278
|
+
column: col,
|
|
279
|
+
offendingValues: offending,
|
|
280
|
+
allowedValues: allowed,
|
|
281
|
+
driftCount,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return findings;
|
|
286
|
+
}
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
// Foreign-key integrity
|
|
289
|
+
// ---------------------------------------------------------------------------
|
|
290
|
+
/**
|
|
291
|
+
* Run `PRAGMA foreign_key_check` on a target DB and return any orphan rows.
|
|
292
|
+
*
|
|
293
|
+
* @param db - Target DB handle (consolidated cleo.db).
|
|
294
|
+
* @param scope - Scope label, attached to log context only.
|
|
295
|
+
* @returns The list of FK violations (empty when referential integrity holds).
|
|
296
|
+
*/
|
|
297
|
+
function foreignKeyCheck(db, scope) {
|
|
298
|
+
try {
|
|
299
|
+
const rows = db.prepare('PRAGMA foreign_key_check').all();
|
|
300
|
+
if (rows.length > 0) {
|
|
301
|
+
log.warn({ scope, count: rows.length, sample: rows.slice(0, 5) }, `verifyMigration: PRAGMA foreign_key_check found ${rows.length} orphan row(s)`);
|
|
302
|
+
}
|
|
303
|
+
return rows.map((r) => ({
|
|
304
|
+
table: r.table,
|
|
305
|
+
rowid: r.rowid ?? null,
|
|
306
|
+
parent: r.parent,
|
|
307
|
+
fkid: r.fkid,
|
|
308
|
+
}));
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
log.warn({ scope, err }, 'verifyMigration: PRAGMA foreign_key_check failed (non-fatal)');
|
|
312
|
+
return [];
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Compute a **migration-stable, name-mapping-stable signature** for one FK
|
|
317
|
+
* orphan so the same dangling row can be matched between the legacy SOURCE and
|
|
318
|
+
* the consolidated TARGET — despite (a) a different `rowid` after the copy AND
|
|
319
|
+
* (b) the legacy→consolidated table RENAME (`task_relations`→`tasks_task_relations`,
|
|
320
|
+
* parent `tasks`→`tasks_tasks`).
|
|
321
|
+
*
|
|
322
|
+
* `PRAGMA foreign_key_check` returns `{ table, rowid, parent, fkid }`. We key the
|
|
323
|
+
* orphan on:
|
|
324
|
+
* - the CONSOLIDATED child-table name (source names are mapped via
|
|
325
|
+
* {@link resolveConsolidatedTableName} so both sides agree),
|
|
326
|
+
* - the CONSOLIDATED parent-table name (same mapping),
|
|
327
|
+
* - the actual VALUES of the foreign-key child columns for that row (read via
|
|
328
|
+
* `PRAGMA foreign_key_list` + the row at `rowid`) — these travel with the row
|
|
329
|
+
* unchanged, so the dangling reference is identical on both sides.
|
|
330
|
+
*
|
|
331
|
+
* `fkid` is intentionally EXCLUDED — the consolidated table may declare its FKs
|
|
332
|
+
* in a different order than the legacy one, so the per-table fkid is not stable.
|
|
333
|
+
* The (consolidated child, consolidated parent, dangling FK column values) triple
|
|
334
|
+
* uniquely identifies the orphan reference.
|
|
335
|
+
*
|
|
336
|
+
* Falls back to a column-name-only shape if the child columns can't be read
|
|
337
|
+
* (best-effort — a fallback only ever makes two orphans compare as *different*,
|
|
338
|
+
* which biases toward treating an orphan as "introduced", the SAFE/strict
|
|
339
|
+
* direction).
|
|
340
|
+
*
|
|
341
|
+
* @param db - DB handle the orphan was found in.
|
|
342
|
+
* @param v - One `PRAGMA foreign_key_check` row.
|
|
343
|
+
* @param sourceName - When set, the orphan is on the SOURCE side and its
|
|
344
|
+
* `table`/`parent` names are mapped to their consolidated equivalents so the
|
|
345
|
+
* signature matches the target side. Omit for the already-consolidated target.
|
|
346
|
+
* @returns A stable string signature.
|
|
347
|
+
*/
|
|
348
|
+
function orphanSignature(db, v, sourceName) {
|
|
349
|
+
// Map both child + parent table names to their CONSOLIDATED form so the source
|
|
350
|
+
// and target signatures agree across the legacy→consolidated rename.
|
|
351
|
+
const mapName = (name) => {
|
|
352
|
+
if (sourceName === undefined)
|
|
353
|
+
return name; // already consolidated (target side)
|
|
354
|
+
const res = resolveConsolidatedTableName(sourceName, name);
|
|
355
|
+
return res.kind === 'skip' ? name : res.targetName;
|
|
356
|
+
};
|
|
357
|
+
const childTable = mapName(v.table);
|
|
358
|
+
const parentTable = mapName(v.parent);
|
|
359
|
+
// Resolve the child→parent column mapping for this specific FK (matched by
|
|
360
|
+
// fkid via PRAGMA foreign_key_list ordering).
|
|
361
|
+
let childCols = [];
|
|
362
|
+
try {
|
|
363
|
+
const fkList = db.prepare(`PRAGMA foreign_key_list("${v.table}")`).all();
|
|
364
|
+
childCols = fkList.filter((r) => r.id === v.fkid).map((r) => r.from);
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
// ignore — fall through to rowid-based fallback
|
|
368
|
+
}
|
|
369
|
+
if (v.rowid !== null && childCols.length > 0) {
|
|
370
|
+
try {
|
|
371
|
+
const sel = childCols.map((c) => `"${c}"`).join(', ');
|
|
372
|
+
const row = db.prepare(`SELECT ${sel} FROM "${v.table}" WHERE rowid = ?`).get(v.rowid);
|
|
373
|
+
if (row) {
|
|
374
|
+
// Order the FK column values deterministically by column name.
|
|
375
|
+
const vals = childCols
|
|
376
|
+
.slice()
|
|
377
|
+
.sort()
|
|
378
|
+
.map((c) => `${c}=${JSON.stringify(row[c])}`)
|
|
379
|
+
.join('&');
|
|
380
|
+
return `${childTable}|${parentTable}|${vals}`;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
// ignore — fall through to rowid-based fallback
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
// Fallback: rowid-qualified signature (only collides with itself).
|
|
388
|
+
return `${childTable}|${parentTable}|rowid:${v.rowid ?? '?'}`;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Collect the set of {@link orphanSignature}s for every FK orphan in a SOURCE DB.
|
|
392
|
+
*
|
|
393
|
+
* Used to compute SOURCE-side orphan signatures so the parity gate can subtract
|
|
394
|
+
* pre-existing orphans from the target's orphan set and fail only on the orphans
|
|
395
|
+
* the migration INTRODUCED (T11572). The signatures are mapped to consolidated
|
|
396
|
+
* table names so they compare equal to the target-side signatures.
|
|
397
|
+
*
|
|
398
|
+
* @param db - SOURCE DB handle to scan.
|
|
399
|
+
* @param sourceName - `LegacyDbDescriptor.name` (for table-name mapping).
|
|
400
|
+
* @param scope - Scope label for diagnostics.
|
|
401
|
+
* @returns A set of name-mapping-stable orphan signatures.
|
|
402
|
+
*/
|
|
403
|
+
function sourceOrphanSignatures(db, sourceName, scope) {
|
|
404
|
+
const sigs = new Set();
|
|
405
|
+
try {
|
|
406
|
+
const rows = db.prepare('PRAGMA foreign_key_check').all();
|
|
407
|
+
for (const r of rows)
|
|
408
|
+
sigs.add(orphanSignature(db, r, sourceName));
|
|
409
|
+
if (rows.length > 0) {
|
|
410
|
+
log.warn({ scope, count: rows.length, sample: rows.slice(0, 5) }, `verifyMigration: source already has ${rows.length} pre-existing FK orphan(s) — these are tolerated (carried forward losslessly, flagged for data-hygiene)`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
catch (err) {
|
|
414
|
+
log.warn({ scope, err }, 'verifyMigration: source PRAGMA foreign_key_check failed (non-fatal)');
|
|
415
|
+
}
|
|
416
|
+
return sigs;
|
|
417
|
+
}
|
|
418
|
+
// ---------------------------------------------------------------------------
|
|
419
|
+
// Public primitive
|
|
420
|
+
// ---------------------------------------------------------------------------
|
|
421
|
+
/**
|
|
422
|
+
* Verify that a source→target SQLite migration preserved every row, referential
|
|
423
|
+
* integrity, content, and enum validity.
|
|
424
|
+
*
|
|
425
|
+
* Opens all source DBs and the consolidated target DBs **read-only**, then for
|
|
426
|
+
* each legacy source table:
|
|
427
|
+
*
|
|
428
|
+
* 1. Resolves the consolidated target name via {@link resolveConsolidatedTableName}.
|
|
429
|
+
* 2. Compares row counts (parity gate).
|
|
430
|
+
* 3. Computes a column-intersection content digest on both sides.
|
|
431
|
+
* 4. Records any enum/type drift (source values outside the target CHECK enum).
|
|
432
|
+
*
|
|
433
|
+
* After all tables, it runs `PRAGMA foreign_key_check` on each distinct target
|
|
434
|
+
* DB and folds the orphan rows into the result.
|
|
435
|
+
*
|
|
436
|
+
* @param sources - Legacy source descriptors (from `buildExodusPlan()`).
|
|
437
|
+
* @param projectDbPath - Absolute path to the consolidated project `cleo.db`.
|
|
438
|
+
* @param globalDbPath - Absolute path to the consolidated global `cleo.db`.
|
|
439
|
+
* @param onProgress - Optional progress callback.
|
|
440
|
+
*
|
|
441
|
+
* @returns A {@link VerifyMigrationResult}. `ok === false` (with `error`
|
|
442
|
+
* populated) on any count mismatch, content mismatch, FK orphan, or enum
|
|
443
|
+
* drift.
|
|
444
|
+
*
|
|
445
|
+
* @task T11551 (DHQ-045 — exodus zero-loss durable guard · AC1)
|
|
446
|
+
*/
|
|
447
|
+
export function verifyMigration(sources, projectDbPath, globalDbPath, onProgress) {
|
|
448
|
+
const tables = [];
|
|
449
|
+
const enumDrift = [];
|
|
450
|
+
const foreignKeyViolations = [];
|
|
451
|
+
const introducedForeignKeyViolations = [];
|
|
452
|
+
const preExistingForeignKeyViolations = [];
|
|
453
|
+
const failureLines = [];
|
|
454
|
+
/**
|
|
455
|
+
* Signatures of FK orphans ALREADY present in the legacy SOURCE DBs (T11572).
|
|
456
|
+
* Target orphans whose signature is in this set are pre-existing — carried
|
|
457
|
+
* forward losslessly — and do NOT fail the parity gate.
|
|
458
|
+
*/
|
|
459
|
+
const sourceOrphanSigs = new Set();
|
|
460
|
+
if (!existsSync(projectDbPath)) {
|
|
461
|
+
return {
|
|
462
|
+
ok: false,
|
|
463
|
+
tables: [],
|
|
464
|
+
foreignKeyViolations: [],
|
|
465
|
+
introducedForeignKeyViolations: [],
|
|
466
|
+
preExistingForeignKeyViolations: [],
|
|
467
|
+
enumDrift: [],
|
|
468
|
+
error: `Consolidated project cleo.db not found at ${projectDbPath}. Run 'cleo exodus migrate' first.`,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
if (!existsSync(globalDbPath)) {
|
|
472
|
+
return {
|
|
473
|
+
ok: false,
|
|
474
|
+
tables: [],
|
|
475
|
+
foreignKeyViolations: [],
|
|
476
|
+
introducedForeignKeyViolations: [],
|
|
477
|
+
preExistingForeignKeyViolations: [],
|
|
478
|
+
enumDrift: [],
|
|
479
|
+
error: `Consolidated global cleo.db not found at ${globalDbPath}. Run 'cleo exodus migrate' first.`,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
const projectSnap = openCleoDbSnapshot(projectDbPath, { readOnly: true });
|
|
483
|
+
const globalSnap = openCleoDbSnapshot(globalDbPath, { readOnly: true });
|
|
484
|
+
try {
|
|
485
|
+
for (const src of sources) {
|
|
486
|
+
if (!existsSync(src.path)) {
|
|
487
|
+
onProgress?.(`Skipping ${src.name} (not present)`);
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
const srcSnap = openCleoDbSnapshot(src.path, { readOnly: true });
|
|
491
|
+
try {
|
|
492
|
+
// T11572: record FK orphans that already exist in THIS source DB so the
|
|
493
|
+
// gate can distinguish pre-existing orphans (tolerated, zero-loss) from
|
|
494
|
+
// orphans the migration introduces (genuine loss → abort). Done while the
|
|
495
|
+
// source snapshot is open; signatures are migration-stable (content, not
|
|
496
|
+
// rowid). FK enforcement on the source is irrelevant — foreign_key_check
|
|
497
|
+
// scans regardless.
|
|
498
|
+
for (const sig of sourceOrphanSignatures(srcSnap.db, src.name, `source:${src.name}`)) {
|
|
499
|
+
sourceOrphanSigs.add(sig);
|
|
500
|
+
}
|
|
501
|
+
const sourceTables = listTables(srcSnap.db);
|
|
502
|
+
// Pre-compute the table set for each consolidated scope once; the
|
|
503
|
+
// per-table scope override (ADR-090 nexus graph residency, T11539) means
|
|
504
|
+
// a single source can verify against BOTH scope DBs.
|
|
505
|
+
const projectTables = new Set(listTables(projectSnap.db));
|
|
506
|
+
const globalTables = new Set(listTables(globalSnap.db));
|
|
507
|
+
for (const legacyTableName of sourceTables) {
|
|
508
|
+
onProgress?.(`Verifying ${src.name}.${legacyTableName}…`);
|
|
509
|
+
const resolution = resolveConsolidatedTableName(src.name, legacyTableName);
|
|
510
|
+
if (resolution.kind === 'skip') {
|
|
511
|
+
onProgress?.(` [skip] ${src.name}.${legacyTableName} — ${resolution.reason}`);
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
const targetTableName = resolution.targetName;
|
|
515
|
+
// Per-table scope override (ADR-090 · T11539): the four nexus graph
|
|
516
|
+
// tables come from the GLOBAL `nexus.db` source but land in PROJECT
|
|
517
|
+
// scope. Pick the verify target DB by the effective per-table scope.
|
|
518
|
+
const scope = resolveTableTargetScope(src.name, legacyTableName, src.targetScope);
|
|
519
|
+
const targetSnap = scope === 'project' ? projectSnap : globalSnap;
|
|
520
|
+
const targetTables = scope === 'project' ? projectTables : globalTables;
|
|
521
|
+
// --- Enum/type-drift report (diagnostic, only when target exists) ---
|
|
522
|
+
if (targetTables.has(targetTableName)) {
|
|
523
|
+
const drift = detectTableEnumDrift(srcSnap.db, legacyTableName, targetSnap.db, targetTableName);
|
|
524
|
+
if (drift.length > 0) {
|
|
525
|
+
enumDrift.push(...drift);
|
|
526
|
+
for (const d of drift) {
|
|
527
|
+
failureLines.push(`[${scope}] ${targetTableName}.${d.column}: ${d.driftCount} row(s) with value(s) ` +
|
|
528
|
+
`outside enum {${d.allowedValues.join(', ')}} — e.g. ${d.offendingValues
|
|
529
|
+
.map((v) => `'${v}'`)
|
|
530
|
+
.join(', ')}`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
// --- Row-count + content-checksum parity ---
|
|
535
|
+
if (!targetTables.has(targetTableName)) {
|
|
536
|
+
const srcResult = computeTableDigest(srcSnap.db, legacyTableName, null);
|
|
537
|
+
const countMatch = srcResult.count === 0;
|
|
538
|
+
tables.push({
|
|
539
|
+
sourceTable: legacyTableName,
|
|
540
|
+
targetTable: targetTableName,
|
|
541
|
+
scope,
|
|
542
|
+
sourceCount: srcResult.count,
|
|
543
|
+
targetCount: 0,
|
|
544
|
+
sourceHash: srcResult.hash,
|
|
545
|
+
targetHash: '',
|
|
546
|
+
hashMatch: countMatch,
|
|
547
|
+
countMatch,
|
|
548
|
+
});
|
|
549
|
+
if (!countMatch) {
|
|
550
|
+
failureLines.push(`[${scope}] ${src.name}.${legacyTableName} → ${targetTableName}: ` +
|
|
551
|
+
`missing from target (source has ${srcResult.count} rows)`);
|
|
552
|
+
}
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
const cols = sharedColumnsSorted(srcSnap.db, legacyTableName, targetSnap.db, targetTableName);
|
|
556
|
+
const srcDigest = computeTableDigest(srcSnap.db, legacyTableName, cols);
|
|
557
|
+
const tgtDigest = computeTableDigest(targetSnap.db, targetTableName, cols);
|
|
558
|
+
const countMatch = srcDigest.count === tgtDigest.count;
|
|
559
|
+
const hashMatch = srcDigest.hash === tgtDigest.hash;
|
|
560
|
+
// T11577: only a row DEFICIT (target < source) is genuine data loss.
|
|
561
|
+
// A SURPLUS (target > source) — e.g. nexus_audit_log gaining the
|
|
562
|
+
// migration's OWN audit writes during the migrating open — is NOT loss
|
|
563
|
+
// and must not fail the gate. When a surplus exists the content digest
|
|
564
|
+
// necessarily differs (more rows), so the hash difference is expected
|
|
565
|
+
// and is NOT counted as a failure. Surplus is logged as a WARN so the
|
|
566
|
+
// table + delta stay visible (a surplus on a non-append table could
|
|
567
|
+
// hint at a double-copy worth an operator's attention).
|
|
568
|
+
if (tgtDigest.count < srcDigest.count) {
|
|
569
|
+
failureLines.push(`[${scope}] ${src.name}.${legacyTableName} → ${targetTableName}: ` +
|
|
570
|
+
`DEFICIT — source=${srcDigest.count} rows, target=${tgtDigest.count} rows (${srcDigest.count - tgtDigest.count} missing), hashMatch=${hashMatch}`);
|
|
571
|
+
}
|
|
572
|
+
else if (tgtDigest.count > srcDigest.count) {
|
|
573
|
+
log.warn({
|
|
574
|
+
scope,
|
|
575
|
+
source: src.name,
|
|
576
|
+
table: targetTableName,
|
|
577
|
+
sourceCount: srcDigest.count,
|
|
578
|
+
targetCount: tgtDigest.count,
|
|
579
|
+
delta: tgtDigest.count - srcDigest.count,
|
|
580
|
+
}, `verifyMigration: ${targetTableName} has ${tgtDigest.count - srcDigest.count} MORE row(s) in target than source (surplus — NOT data loss, tolerated; ` +
|
|
581
|
+
`e.g. migration-time audit writes). Verify it is not an unexpected double-copy.`);
|
|
582
|
+
}
|
|
583
|
+
else if (!hashMatch) {
|
|
584
|
+
// Exact count parity but content drift — a genuine corruption class.
|
|
585
|
+
failureLines.push(`[${scope}] ${src.name}.${legacyTableName} → ${targetTableName}: ` +
|
|
586
|
+
`source=${srcDigest.count} rows, target=${tgtDigest.count} rows, hashMatch=${hashMatch}`);
|
|
587
|
+
}
|
|
588
|
+
tables.push({
|
|
589
|
+
sourceTable: legacyTableName,
|
|
590
|
+
targetTable: targetTableName,
|
|
591
|
+
scope,
|
|
592
|
+
sourceCount: srcDigest.count,
|
|
593
|
+
targetCount: tgtDigest.count,
|
|
594
|
+
sourceHash: srcDigest.hash,
|
|
595
|
+
targetHash: tgtDigest.hash,
|
|
596
|
+
hashMatch,
|
|
597
|
+
countMatch,
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
finally {
|
|
602
|
+
srcSnap.close();
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
// --- Foreign-key integrity on each distinct target DB ---
|
|
606
|
+
// Collect ALL target orphans, then partition into pre-existing (already
|
|
607
|
+
// orphaned in the source — tolerated, zero-loss) vs migration-introduced
|
|
608
|
+
// (genuine loss → gate failure). Match by migration-stable signature so a
|
|
609
|
+
// faithfully-copied pre-existing orphan is recognised despite a new rowid.
|
|
610
|
+
const targetOrphans = [];
|
|
611
|
+
targetOrphans.push(...foreignKeyCheck(projectSnap.db, 'project'));
|
|
612
|
+
targetOrphans.push(...foreignKeyCheck(globalSnap.db, 'global'));
|
|
613
|
+
foreignKeyViolations.push(...targetOrphans);
|
|
614
|
+
for (const fk of targetOrphans) {
|
|
615
|
+
// Recompute the orphan signature on the TARGET side (same content key as
|
|
616
|
+
// the source side) to test membership in the pre-existing source set. The
|
|
617
|
+
// orphan lives in whichever scope DB declares the child table.
|
|
618
|
+
const orphanDb = tableExists(projectSnap.db, fk.table) ? projectSnap.db : globalSnap.db;
|
|
619
|
+
const sig = orphanSignature(orphanDb, fk);
|
|
620
|
+
const preExisting = sourceOrphanSigs.has(sig);
|
|
621
|
+
if (preExisting) {
|
|
622
|
+
preExistingForeignKeyViolations.push(fk);
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
introducedForeignKeyViolations.push(fk);
|
|
626
|
+
// ONLY migration-INTRODUCED orphans fail the gate (T11572).
|
|
627
|
+
failureLines.push(`[fk] ${fk.table}.rowid=${fk.rowid ?? '?'} references missing ${fk.parent} (fkid=${fk.fkid}) — INTRODUCED by migration`);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
if (preExistingForeignKeyViolations.length > 0) {
|
|
631
|
+
log.warn({
|
|
632
|
+
count: preExistingForeignKeyViolations.length,
|
|
633
|
+
sample: preExistingForeignKeyViolations.slice(0, 5),
|
|
634
|
+
}, `verifyMigration: ${preExistingForeignKeyViolations.length} pre-existing source FK orphan(s) carried forward losslessly — tolerated (flag for data-hygiene follow-up, NOT a migration failure)`);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
catch (err) {
|
|
638
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
639
|
+
log.error({ err }, 'verifyMigration failed');
|
|
640
|
+
return {
|
|
641
|
+
ok: false,
|
|
642
|
+
tables,
|
|
643
|
+
foreignKeyViolations,
|
|
644
|
+
introducedForeignKeyViolations,
|
|
645
|
+
preExistingForeignKeyViolations,
|
|
646
|
+
enumDrift,
|
|
647
|
+
error,
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
finally {
|
|
651
|
+
projectSnap.close();
|
|
652
|
+
globalSnap.close();
|
|
653
|
+
}
|
|
654
|
+
if (failureLines.length > 0) {
|
|
655
|
+
const error = `verifyMigration FAILED: ${failureLines.length} issue(s):\n${failureLines
|
|
656
|
+
.map((l) => ` • ${l}`)
|
|
657
|
+
.join('\n')}`;
|
|
658
|
+
log.error({ failureCount: failureLines.length }, error);
|
|
659
|
+
return {
|
|
660
|
+
ok: false,
|
|
661
|
+
tables,
|
|
662
|
+
foreignKeyViolations,
|
|
663
|
+
introducedForeignKeyViolations,
|
|
664
|
+
preExistingForeignKeyViolations,
|
|
665
|
+
enumDrift,
|
|
666
|
+
error,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
return {
|
|
670
|
+
ok: true,
|
|
671
|
+
tables,
|
|
672
|
+
foreignKeyViolations,
|
|
673
|
+
introducedForeignKeyViolations,
|
|
674
|
+
preExistingForeignKeyViolations,
|
|
675
|
+
enumDrift,
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
//# sourceMappingURL=verify-migration.js.map
|