@lwelliott/cortex-cli 1.0.3
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/LICENSE +21 -0
- package/README.md +159 -0
- package/dist/cli/audit.d.ts +3 -0
- package/dist/cli/audit.d.ts.map +1 -0
- package/dist/cli/audit.js +78 -0
- package/dist/cli/audit.js.map +1 -0
- package/dist/cli/doc.d.ts +3 -0
- package/dist/cli/doc.d.ts.map +1 -0
- package/dist/cli/doc.js +291 -0
- package/dist/cli/doc.js.map +1 -0
- package/dist/cli/entity.d.ts +3 -0
- package/dist/cli/entity.d.ts.map +1 -0
- package/dist/cli/entity.js +135 -0
- package/dist/cli/entity.js.map +1 -0
- package/dist/cli/format-selector.d.ts +30 -0
- package/dist/cli/format-selector.d.ts.map +1 -0
- package/dist/cli/format-selector.js +101 -0
- package/dist/cli/format-selector.js.map +1 -0
- package/dist/cli/formatters/audit-formatter.d.ts +33 -0
- package/dist/cli/formatters/audit-formatter.d.ts.map +1 -0
- package/dist/cli/formatters/audit-formatter.js +204 -0
- package/dist/cli/formatters/audit-formatter.js.map +1 -0
- package/dist/cli/formatters/json.d.ts +10 -0
- package/dist/cli/formatters/json.d.ts.map +1 -0
- package/dist/cli/formatters/json.js +31 -0
- package/dist/cli/formatters/json.js.map +1 -0
- package/dist/cli/formatters/markdown.d.ts +12 -0
- package/dist/cli/formatters/markdown.d.ts.map +1 -0
- package/dist/cli/formatters/markdown.js +341 -0
- package/dist/cli/formatters/markdown.js.map +1 -0
- package/dist/cli/formatters/quiet.d.ts +15 -0
- package/dist/cli/formatters/quiet.d.ts.map +1 -0
- package/dist/cli/formatters/quiet.js +75 -0
- package/dist/cli/formatters/quiet.js.map +1 -0
- package/dist/cli/formatters/table.d.ts +11 -0
- package/dist/cli/formatters/table.d.ts.map +1 -0
- package/dist/cli/formatters/table.js +726 -0
- package/dist/cli/formatters/table.js.map +1 -0
- package/dist/cli/graph.d.ts +3 -0
- package/dist/cli/graph.d.ts.map +1 -0
- package/dist/cli/graph.js +209 -0
- package/dist/cli/graph.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +144 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/lesson.d.ts +3 -0
- package/dist/cli/lesson.d.ts.map +1 -0
- package/dist/cli/lesson.js +85 -0
- package/dist/cli/lesson.js.map +1 -0
- package/dist/cli/project-resolver.d.ts +17 -0
- package/dist/cli/project-resolver.d.ts.map +1 -0
- package/dist/cli/project-resolver.js +25 -0
- package/dist/cli/project-resolver.js.map +1 -0
- package/dist/cli/project.d.ts +12 -0
- package/dist/cli/project.d.ts.map +1 -0
- package/dist/cli/project.js +169 -0
- package/dist/cli/project.js.map +1 -0
- package/dist/cli/relation.d.ts +3 -0
- package/dist/cli/relation.d.ts.map +1 -0
- package/dist/cli/relation.js +128 -0
- package/dist/cli/relation.js.map +1 -0
- package/dist/cli/review.d.ts +3 -0
- package/dist/cli/review.d.ts.map +1 -0
- package/dist/cli/review.js +131 -0
- package/dist/cli/review.js.map +1 -0
- package/dist/cli/router.d.ts +38 -0
- package/dist/cli/router.d.ts.map +1 -0
- package/dist/cli/router.js +49 -0
- package/dist/cli/router.js.map +1 -0
- package/dist/cli/schema.d.ts +3 -0
- package/dist/cli/schema.d.ts.map +1 -0
- package/dist/cli/schema.js +94 -0
- package/dist/cli/schema.js.map +1 -0
- package/dist/cli/sprint.d.ts +12 -0
- package/dist/cli/sprint.d.ts.map +1 -0
- package/dist/cli/sprint.js +200 -0
- package/dist/cli/sprint.js.map +1 -0
- package/dist/cli/task.d.ts +12 -0
- package/dist/cli/task.d.ts.map +1 -0
- package/dist/cli/task.js +252 -0
- package/dist/cli/task.js.map +1 -0
- package/dist/cli/test-report.d.ts +3 -0
- package/dist/cli/test-report.d.ts.map +1 -0
- package/dist/cli/test-report.js +134 -0
- package/dist/cli/test-report.js.map +1 -0
- package/dist/cli/validate.d.ts +8 -0
- package/dist/cli/validate.d.ts.map +1 -0
- package/dist/cli/validate.js +86 -0
- package/dist/cli/validate.js.map +1 -0
- package/dist/config/config.d.ts +18 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +70 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/registry.d.ts +52 -0
- package/dist/config/registry.d.ts.map +1 -0
- package/dist/config/registry.js +193 -0
- package/dist/config/registry.js.map +1 -0
- package/dist/config/resolver.d.ts +26 -0
- package/dist/config/resolver.d.ts.map +1 -0
- package/dist/config/resolver.js +87 -0
- package/dist/config/resolver.js.map +1 -0
- package/dist/db/builtin-schema-constants.d.ts +109 -0
- package/dist/db/builtin-schema-constants.d.ts.map +1 -0
- package/dist/db/builtin-schema-constants.js +32 -0
- package/dist/db/builtin-schema-constants.js.map +1 -0
- package/dist/db/connection.d.ts +34 -0
- package/dist/db/connection.d.ts.map +1 -0
- package/dist/db/connection.js +140 -0
- package/dist/db/connection.js.map +1 -0
- package/dist/db/generated/schema-types.d.ts +134 -0
- package/dist/db/generated/schema-types.d.ts.map +1 -0
- package/dist/db/generated/schema-types.js +31 -0
- package/dist/db/generated/schema-types.js.map +1 -0
- package/dist/db/locks.d.ts +106 -0
- package/dist/db/locks.d.ts.map +1 -0
- package/dist/db/locks.js +211 -0
- package/dist/db/locks.js.map +1 -0
- package/dist/db/migration.d.ts +65 -0
- package/dist/db/migration.d.ts.map +1 -0
- package/dist/db/migration.js +233 -0
- package/dist/db/migration.js.map +1 -0
- package/dist/db/retry.d.ts +26 -0
- package/dist/db/retry.d.ts.map +1 -0
- package/dist/db/retry.js +71 -0
- package/dist/db/retry.js.map +1 -0
- package/dist/db/schema.d.ts +21 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +830 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/models/common.d.ts +27 -0
- package/dist/models/common.d.ts.map +1 -0
- package/dist/models/common.js +4 -0
- package/dist/models/common.js.map +1 -0
- package/dist/models/document.d.ts +89 -0
- package/dist/models/document.d.ts.map +1 -0
- package/dist/models/document.js +5 -0
- package/dist/models/document.js.map +1 -0
- package/dist/models/entity-model.d.ts +30 -0
- package/dist/models/entity-model.d.ts.map +1 -0
- package/dist/models/entity-model.js +99 -0
- package/dist/models/entity-model.js.map +1 -0
- package/dist/models/entity.d.ts +88 -0
- package/dist/models/entity.d.ts.map +1 -0
- package/dist/models/entity.js +5 -0
- package/dist/models/entity.js.map +1 -0
- package/dist/models/graph-schema.d.ts +43 -0
- package/dist/models/graph-schema.d.ts.map +1 -0
- package/dist/models/graph-schema.js +76 -0
- package/dist/models/graph-schema.js.map +1 -0
- package/dist/models/project.d.ts +49 -0
- package/dist/models/project.d.ts.map +1 -0
- package/dist/models/project.js +18 -0
- package/dist/models/project.js.map +1 -0
- package/dist/models/relation.d.ts +35 -0
- package/dist/models/relation.d.ts.map +1 -0
- package/dist/models/relation.js +212 -0
- package/dist/models/relation.js.map +1 -0
- package/dist/models/review.d.ts +86 -0
- package/dist/models/review.d.ts.map +1 -0
- package/dist/models/review.js +5 -0
- package/dist/models/review.js.map +1 -0
- package/dist/models/sprint.d.ts +38 -0
- package/dist/models/sprint.d.ts.map +1 -0
- package/dist/models/sprint.js +4 -0
- package/dist/models/sprint.js.map +1 -0
- package/dist/models/task.d.ts +75 -0
- package/dist/models/task.d.ts.map +1 -0
- package/dist/models/task.js +4 -0
- package/dist/models/task.js.map +1 -0
- package/dist/models/validation.d.ts +25 -0
- package/dist/models/validation.d.ts.map +1 -0
- package/dist/models/validation.js +4 -0
- package/dist/models/validation.js.map +1 -0
- package/dist/services/audit-check-service.d.ts +16 -0
- package/dist/services/audit-check-service.d.ts.map +1 -0
- package/dist/services/audit-check-service.js +137 -0
- package/dist/services/audit-check-service.js.map +1 -0
- package/dist/services/audit-checks/dangling-relations-check.d.ts +7 -0
- package/dist/services/audit-checks/dangling-relations-check.d.ts.map +1 -0
- package/dist/services/audit-checks/dangling-relations-check.js +52 -0
- package/dist/services/audit-checks/dangling-relations-check.js.map +1 -0
- package/dist/services/audit-checks/duplicate-ids-check.d.ts +4 -0
- package/dist/services/audit-checks/duplicate-ids-check.d.ts.map +1 -0
- package/dist/services/audit-checks/duplicate-ids-check.js +49 -0
- package/dist/services/audit-checks/duplicate-ids-check.js.map +1 -0
- package/dist/services/audit-checks/fix-engine.d.ts +24 -0
- package/dist/services/audit-checks/fix-engine.d.ts.map +1 -0
- package/dist/services/audit-checks/fix-engine.js +114 -0
- package/dist/services/audit-checks/fix-engine.js.map +1 -0
- package/dist/services/audit-checks/fr-nfr-to-fs-nfs-check.d.ts +9 -0
- package/dist/services/audit-checks/fr-nfr-to-fs-nfs-check.d.ts.map +1 -0
- package/dist/services/audit-checks/fr-nfr-to-fs-nfs-check.js +56 -0
- package/dist/services/audit-checks/fr-nfr-to-fs-nfs-check.js.map +1 -0
- package/dist/services/audit-checks/fs-nfs-to-sad-ad-qa-check.d.ts +10 -0
- package/dist/services/audit-checks/fs-nfs-to-sad-ad-qa-check.d.ts.map +1 -0
- package/dist/services/audit-checks/fs-nfs-to-sad-ad-qa-check.js +57 -0
- package/dist/services/audit-checks/fs-nfs-to-sad-ad-qa-check.js.map +1 -0
- package/dist/services/audit-checks/fs-nfs-to-ts-check.d.ts +9 -0
- package/dist/services/audit-checks/fs-nfs-to-ts-check.d.ts.map +1 -0
- package/dist/services/audit-checks/fs-nfs-to-ts-check.js +56 -0
- package/dist/services/audit-checks/fs-nfs-to-ts-check.js.map +1 -0
- package/dist/services/audit-checks/inverse-consistency-check.d.ts +7 -0
- package/dist/services/audit-checks/inverse-consistency-check.d.ts.map +1 -0
- package/dist/services/audit-checks/inverse-consistency-check.js +54 -0
- package/dist/services/audit-checks/inverse-consistency-check.js.map +1 -0
- package/dist/services/audit-checks/mapping-check.d.ts +4 -0
- package/dist/services/audit-checks/mapping-check.d.ts.map +1 -0
- package/dist/services/audit-checks/mapping-check.js +74 -0
- package/dist/services/audit-checks/mapping-check.js.map +1 -0
- package/dist/services/audit-checks/orphan-documents-check.d.ts +9 -0
- package/dist/services/audit-checks/orphan-documents-check.d.ts.map +1 -0
- package/dist/services/audit-checks/orphan-documents-check.js +54 -0
- package/dist/services/audit-checks/orphan-documents-check.js.map +1 -0
- package/dist/services/audit-checks/orphan-entities-check.d.ts +4 -0
- package/dist/services/audit-checks/orphan-entities-check.d.ts.map +1 -0
- package/dist/services/audit-checks/orphan-entities-check.js +39 -0
- package/dist/services/audit-checks/orphan-entities-check.js.map +1 -0
- package/dist/services/audit-checks/sad-ad-qa-tc-to-src-check.d.ts +9 -0
- package/dist/services/audit-checks/sad-ad-qa-tc-to-src-check.d.ts.map +1 -0
- package/dist/services/audit-checks/sad-ad-qa-tc-to-src-check.js +56 -0
- package/dist/services/audit-checks/sad-ad-qa-tc-to-src-check.js.map +1 -0
- package/dist/services/audit-checks/sprint-consistency-check.d.ts +4 -0
- package/dist/services/audit-checks/sprint-consistency-check.d.ts.map +1 -0
- package/dist/services/audit-checks/sprint-consistency-check.js +80 -0
- package/dist/services/audit-checks/sprint-consistency-check.js.map +1 -0
- package/dist/services/audit-checks/src-filesystem-only-check.d.ts +10 -0
- package/dist/services/audit-checks/src-filesystem-only-check.d.ts.map +1 -0
- package/dist/services/audit-checks/src-filesystem-only-check.js +106 -0
- package/dist/services/audit-checks/src-filesystem-only-check.js.map +1 -0
- package/dist/services/audit-checks/ts-to-tc-check.d.ts +10 -0
- package/dist/services/audit-checks/ts-to-tc-check.d.ts.map +1 -0
- package/dist/services/audit-checks/ts-to-tc-check.js +58 -0
- package/dist/services/audit-checks/ts-to-tc-check.js.map +1 -0
- package/dist/services/audit-checks/type-mismatch-check.d.ts +4 -0
- package/dist/services/audit-checks/type-mismatch-check.d.ts.map +1 -0
- package/dist/services/audit-checks/type-mismatch-check.js +55 -0
- package/dist/services/audit-checks/type-mismatch-check.js.map +1 -0
- package/dist/services/audit-checks/types.d.ts +66 -0
- package/dist/services/audit-checks/types.d.ts.map +1 -0
- package/dist/services/audit-checks/types.js +30 -0
- package/dist/services/audit-checks/types.js.map +1 -0
- package/dist/services/audit-checks/us-to-fr-nfr-check.d.ts +9 -0
- package/dist/services/audit-checks/us-to-fr-nfr-check.d.ts.map +1 -0
- package/dist/services/audit-checks/us-to-fr-nfr-check.js +56 -0
- package/dist/services/audit-checks/us-to-fr-nfr-check.js.map +1 -0
- package/dist/services/doc-entity-mapping.d.ts +19 -0
- package/dist/services/doc-entity-mapping.d.ts.map +1 -0
- package/dist/services/doc-entity-mapping.js +35 -0
- package/dist/services/doc-entity-mapping.js.map +1 -0
- package/dist/services/document-service.d.ts +58 -0
- package/dist/services/document-service.d.ts.map +1 -0
- package/dist/services/document-service.js +409 -0
- package/dist/services/document-service.js.map +1 -0
- package/dist/services/graph-export-service.d.ts +12 -0
- package/dist/services/graph-export-service.d.ts.map +1 -0
- package/dist/services/graph-export-service.js +112 -0
- package/dist/services/graph-export-service.js.map +1 -0
- package/dist/services/graph-service.d.ts +21 -0
- package/dist/services/graph-service.d.ts.map +1 -0
- package/dist/services/graph-service.js +49 -0
- package/dist/services/graph-service.js.map +1 -0
- package/dist/services/graph-validation-service.d.ts +13 -0
- package/dist/services/graph-validation-service.d.ts.map +1 -0
- package/dist/services/graph-validation-service.js +171 -0
- package/dist/services/graph-validation-service.js.map +1 -0
- package/dist/services/project.d.ts +80 -0
- package/dist/services/project.d.ts.map +1 -0
- package/dist/services/project.js +256 -0
- package/dist/services/project.js.map +1 -0
- package/dist/services/review-service.d.ts +81 -0
- package/dist/services/review-service.d.ts.map +1 -0
- package/dist/services/review-service.js +242 -0
- package/dist/services/review-service.js.map +1 -0
- package/dist/services/schema-migration-service.d.ts +44 -0
- package/dist/services/schema-migration-service.d.ts.map +1 -0
- package/dist/services/schema-migration-service.js +894 -0
- package/dist/services/schema-migration-service.js.map +1 -0
- package/dist/services/sprint.d.ts +71 -0
- package/dist/services/sprint.d.ts.map +1 -0
- package/dist/services/sprint.js +238 -0
- package/dist/services/sprint.js.map +1 -0
- package/dist/services/stage-validator.d.ts +36 -0
- package/dist/services/stage-validator.d.ts.map +1 -0
- package/dist/services/stage-validator.js +78 -0
- package/dist/services/stage-validator.js.map +1 -0
- package/dist/services/task.d.ts +75 -0
- package/dist/services/task.d.ts.map +1 -0
- package/dist/services/task.js +487 -0
- package/dist/services/task.js.map +1 -0
- package/dist/services/validation.d.ts +24 -0
- package/dist/services/validation.d.ts.map +1 -0
- package/dist/services/validation.js +53 -0
- package/dist/services/validation.js.map +1 -0
- package/dist/state-machines/sprint-state.d.ts +25 -0
- package/dist/state-machines/sprint-state.d.ts.map +1 -0
- package/dist/state-machines/sprint-state.js +64 -0
- package/dist/state-machines/sprint-state.js.map +1 -0
- package/dist/state-machines/task-state.d.ts +31 -0
- package/dist/state-machines/task-state.d.ts.map +1 -0
- package/dist/state-machines/task-state.js +83 -0
- package/dist/state-machines/task-state.js.map +1 -0
- package/dist/utils/errors.d.ts +673 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +1447 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/format.d.ts +52 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +65 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/validators/board-integrity.d.ts +25 -0
- package/dist/validators/board-integrity.d.ts.map +1 -0
- package/dist/validators/board-integrity.js +102 -0
- package/dist/validators/board-integrity.js.map +1 -0
- package/dist/validators/board-shape.d.ts +13 -0
- package/dist/validators/board-shape.d.ts.map +1 -0
- package/dist/validators/board-shape.js +74 -0
- package/dist/validators/board-shape.js.map +1 -0
- package/dist/validators/owner-validator.d.ts +30 -0
- package/dist/validators/owner-validator.d.ts.map +1 -0
- package/dist/validators/owner-validator.js +70 -0
- package/dist/validators/owner-validator.js.map +1 -0
- package/dist/validators/spawn-legality.d.ts +19 -0
- package/dist/validators/spawn-legality.d.ts.map +1 -0
- package/dist/validators/spawn-legality.js +77 -0
- package/dist/validators/spawn-legality.js.map +1 -0
- package/dist/validators/state-field-checker.d.ts +8 -0
- package/dist/validators/state-field-checker.d.ts.map +1 -0
- package/dist/validators/state-field-checker.js +47 -0
- package/dist/validators/state-field-checker.js.map +1 -0
- package/dist/validators/state-field-recorder.d.ts +39 -0
- package/dist/validators/state-field-recorder.d.ts.map +1 -0
- package/dist/validators/state-field-recorder.js +62 -0
- package/dist/validators/state-field-recorder.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,894 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Schema Migration Service — Sprint 31: 9-Phase Graph Schema Overhaul
|
|
3
|
+
// Implements SAD-0033: Schema Finalization — Drop entity_types Table, 18 Relation Types
|
|
4
|
+
// Implements SAD-0012: Schema-to-Graph CLI Migration (extended for Sprint 31)
|
|
5
|
+
// Implements FS-SS-031-0001 through FS-SS-031-0008 (all 8 Sprint 31 migration specs)
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.getAvailablePhases = getAvailablePhases;
|
|
8
|
+
exports.runPhase = runPhase;
|
|
9
|
+
exports.runMigration = runMigration;
|
|
10
|
+
exports.validatePreMigration = validatePreMigration;
|
|
11
|
+
exports.validatePostMigration = validatePostMigration;
|
|
12
|
+
const fs_1 = require("fs");
|
|
13
|
+
const path_1 = require("path");
|
|
14
|
+
const child_process_1 = require("child_process");
|
|
15
|
+
const retry_1 = require("../db/retry");
|
|
16
|
+
const errors_1 = require("../utils/errors");
|
|
17
|
+
// ─── Utility Functions ───
|
|
18
|
+
function measureElapsed(fn) {
|
|
19
|
+
const start = process.hrtime.bigint();
|
|
20
|
+
fn();
|
|
21
|
+
const end = process.hrtime.bigint();
|
|
22
|
+
return Number((end - start) / 1000000n);
|
|
23
|
+
}
|
|
24
|
+
// ─── Canonical Schema Definitions (post-Sprint-31 / builtin:3.0) ───
|
|
25
|
+
/** 18 canonical relation types (SAD-0033 §3, plan/project-schema.yaml) */
|
|
26
|
+
const NEW_RELATION_TYPES = {
|
|
27
|
+
// 8 NEW types (Phase 4)
|
|
28
|
+
decomposed_to: { from: ['SAD'], to: ['AD', 'QA'], cardinality: 'one_to_many' },
|
|
29
|
+
decomposes: { from: ['AD', 'QA'], to: ['SAD'], cardinality: 'many_to_one' },
|
|
30
|
+
divided_from: { from: ['TC'], to: ['TS'], cardinality: 'many_to_one' },
|
|
31
|
+
divided_into: { from: ['TC'], to: ['TS'], cardinality: 'many_to_one' },
|
|
32
|
+
fulfilled_by: { from: ['US'], to: ['FR', 'NFR'], cardinality: 'many_to_many' },
|
|
33
|
+
fulfills: { from: ['FR', 'NFR'], to: ['US'], cardinality: 'many_to_many' },
|
|
34
|
+
realized_by: { from: ['FR'], to: ['FS'], cardinality: 'many_to_many' },
|
|
35
|
+
realizes: { from: ['FS'], to: ['FR'], cardinality: 'many_to_many' },
|
|
36
|
+
};
|
|
37
|
+
/** 9 surviving relation types with UPDATED definitions (Phase 5) */
|
|
38
|
+
const SURVIVING_UPDATES = {
|
|
39
|
+
contained_in: { from: ['US', 'FR', 'NFR'], to: ['MDL'], cardinality: 'many_to_one' },
|
|
40
|
+
contains: { from: ['MDL'], to: ['US', 'FR', 'NFR'], cardinality: 'one_to_many' },
|
|
41
|
+
covered_by: { from: ['FS', 'NFS'], to: ['SAD', 'AD', 'QA'], cardinality: 'many_to_many' },
|
|
42
|
+
covers: { from: ['SAD', 'AD', 'QA'], to: ['FS', 'NFS'], cardinality: 'many_to_many' },
|
|
43
|
+
implemented_by: { from: ['SAD', 'AD', 'QA'], to: ['SRC'], cardinality: 'many_to_many' },
|
|
44
|
+
implements: { from: ['SRC'], to: ['SAD', 'AD', 'QA'], cardinality: 'many_to_many' },
|
|
45
|
+
verified_by: { from: ['FS', 'NFS'], to: ['TS'], cardinality: 'many_to_many' },
|
|
46
|
+
verifies: { from: ['TS'], to: ['FS', 'NFS'], cardinality: 'many_to_many' },
|
|
47
|
+
// depends_on: unchanged [MDL] → [MDL]; no update applied
|
|
48
|
+
};
|
|
49
|
+
/** depends_on + depended_on_by: unchanged from builtin:2.0 but listed for completeness */
|
|
50
|
+
const UNCHANGED_SURVIVORS = ['depends_on', 'depended_on_by'];
|
|
51
|
+
/** 17 relation types to DELETE in Phase 7 (SAD-0033 §3) */
|
|
52
|
+
const REMOVED_RELATION_TYPES = [
|
|
53
|
+
'affected_by', 'affects', 'belongs_to', 'belongs_to_suite',
|
|
54
|
+
'decided_by', 'decides', 'decomposed_from', 'decomposes_to',
|
|
55
|
+
'governed_by', 'governs_inv', 'has_member', 'reviewed_by',
|
|
56
|
+
'reviews', 'traced_from', 'traces_to', 'validated_by', 'validates',
|
|
57
|
+
];
|
|
58
|
+
/** All 18 final relation type names */
|
|
59
|
+
const FINAL_RELATION_TYPE_NAMES = [
|
|
60
|
+
...Object.keys(NEW_RELATION_TYPES),
|
|
61
|
+
...Object.keys(SURVIVING_UPDATES),
|
|
62
|
+
...UNCHANGED_SURVIVORS,
|
|
63
|
+
];
|
|
64
|
+
// ─── Phase 1: Drop entity_types Table ───
|
|
65
|
+
function phase1_execute(db) {
|
|
66
|
+
const details = { entityTypesTableDropped: 0, entitiesTableRecreated: 0 };
|
|
67
|
+
// Check if entity_types table exists
|
|
68
|
+
const tableExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entity_types'").get();
|
|
69
|
+
if (tableExists) {
|
|
70
|
+
// SQLite does not support ALTER TABLE DROP CONSTRAINT.
|
|
71
|
+
// The entities table has a FK: type_name REFERENCES entity_types(name).
|
|
72
|
+
// We must recreate entities without the FK before dropping entity_types.
|
|
73
|
+
// (FK enforcement is already OFF for the entire migration.)
|
|
74
|
+
db.exec(`
|
|
75
|
+
CREATE TABLE entities_new (
|
|
76
|
+
id TEXT PRIMARY KEY,
|
|
77
|
+
type_name TEXT NOT NULL,
|
|
78
|
+
properties TEXT NOT NULL DEFAULT '{}',
|
|
79
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
80
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
81
|
+
);
|
|
82
|
+
INSERT INTO entities_new SELECT id, type_name, properties, created_at, updated_at FROM entities;
|
|
83
|
+
DROP TABLE entities;
|
|
84
|
+
ALTER TABLE entities_new RENAME TO entities;
|
|
85
|
+
CREATE INDEX idx_entities_type ON entities(type_name);
|
|
86
|
+
`);
|
|
87
|
+
details.entitiesTableRecreated = 1;
|
|
88
|
+
db.exec('DROP TABLE IF EXISTS entity_types');
|
|
89
|
+
details.entityTypesTableDropped = 1;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
details.entityTypesTableDropped = 0;
|
|
93
|
+
details.entitiesTableRecreated = 0;
|
|
94
|
+
}
|
|
95
|
+
return details;
|
|
96
|
+
}
|
|
97
|
+
function phase1_validatePre(db) {
|
|
98
|
+
const tableExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entity_types'").get();
|
|
99
|
+
return !!tableExists;
|
|
100
|
+
}
|
|
101
|
+
function phase1_validatePost(db) {
|
|
102
|
+
const tableExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entity_types'").get();
|
|
103
|
+
return !tableExists;
|
|
104
|
+
}
|
|
105
|
+
// ─── Phase 2: Delete TR Graph Entities and Relations ───
|
|
106
|
+
function phase2_execute(db) {
|
|
107
|
+
const details = { trEntitiesDeleted: 0, trRelationsDeleted: 0, testReportRowsPreserved: 0 };
|
|
108
|
+
// Count TR entities
|
|
109
|
+
const trEntityCount = db.prepare("SELECT COUNT(*) as cnt FROM entities WHERE type_name = 'TR'").get().cnt;
|
|
110
|
+
if (trEntityCount === 0) {
|
|
111
|
+
return details; // Nothing to do — idempotent
|
|
112
|
+
}
|
|
113
|
+
// Delete all relations referencing TR entities
|
|
114
|
+
const relResult1 = db.prepare("DELETE FROM relations WHERE from_id IN (SELECT id FROM entities WHERE type_name = 'TR') OR to_id IN (SELECT id FROM entities WHERE type_name = 'TR')").run();
|
|
115
|
+
details.trRelationsDeleted = relResult1.changes;
|
|
116
|
+
// Also delete any relations referencing TR entities by ID
|
|
117
|
+
const trEntityIds = db.prepare("SELECT id FROM entities WHERE type_name = 'TR'").all();
|
|
118
|
+
for (const e of trEntityIds) {
|
|
119
|
+
db.prepare('DELETE FROM relations WHERE from_id = ? OR to_id = ?').run(e.id, e.id);
|
|
120
|
+
}
|
|
121
|
+
// Delete TR entities
|
|
122
|
+
const entityResult = db.prepare("DELETE FROM entities WHERE type_name = 'TR'").run();
|
|
123
|
+
details.trEntitiesDeleted = entityResult.changes;
|
|
124
|
+
// Count preserved test_reports rows
|
|
125
|
+
const testReportCount = db.prepare('SELECT COUNT(*) as cnt FROM test_reports').get().cnt;
|
|
126
|
+
details.testReportsRowsPreserved = testReportCount;
|
|
127
|
+
return details;
|
|
128
|
+
}
|
|
129
|
+
function phase2_validatePre(db) {
|
|
130
|
+
const trCount = db.prepare("SELECT COUNT(*) as cnt FROM entities WHERE type_name = 'TR'").get().cnt;
|
|
131
|
+
return trCount > 0;
|
|
132
|
+
}
|
|
133
|
+
function phase2_validatePost(db) {
|
|
134
|
+
const trCount = db.prepare("SELECT COUNT(*) as cnt FROM entities WHERE type_name = 'TR'").get().cnt;
|
|
135
|
+
return trCount === 0;
|
|
136
|
+
}
|
|
137
|
+
// ─── Phase 3: Migrate URD Documents to US/FR/NFR ───
|
|
138
|
+
function determineUrdReplacementType(title, content) {
|
|
139
|
+
const titleLower = (title || '').toLowerCase();
|
|
140
|
+
const contentLower = (content || '').toLowerCase();
|
|
141
|
+
// NFR pattern: non-functional, performance, NFR prefix
|
|
142
|
+
if (titleLower.includes('non-functional') || titleLower.includes('non functional') ||
|
|
143
|
+
titleLower.startsWith('nfr-') || titleLower.includes('nfr ') ||
|
|
144
|
+
contentLower.includes('non-functional requirement') ||
|
|
145
|
+
titleLower.includes('performance') ||
|
|
146
|
+
titleLower.includes('latency') || titleLower.includes('throughput') ||
|
|
147
|
+
titleLower.includes('concurrency')) {
|
|
148
|
+
return 'NFR';
|
|
149
|
+
}
|
|
150
|
+
// FR pattern: functional requirement, FR prefix
|
|
151
|
+
if (titleLower.includes('functional requirement') ||
|
|
152
|
+
titleLower.startsWith('fr-') || titleLower.includes('fr ') ||
|
|
153
|
+
contentLower.includes('functional requirement') ||
|
|
154
|
+
titleLower.includes(' bug ') || titleLower.includes('fix') ||
|
|
155
|
+
titleLower.includes('error') || titleLower.includes('validation') ||
|
|
156
|
+
titleLower.includes('command') || titleLower.includes('search')) {
|
|
157
|
+
return 'FR';
|
|
158
|
+
}
|
|
159
|
+
// US pattern: user story, US prefix
|
|
160
|
+
if (titleLower.includes('user story') || titleLower.includes('userstory') ||
|
|
161
|
+
titleLower.startsWith('us-') || titleLower.includes('us ')) {
|
|
162
|
+
return 'US';
|
|
163
|
+
}
|
|
164
|
+
// Default: US as catch-all (most URD docs are user story level)
|
|
165
|
+
return 'US';
|
|
166
|
+
}
|
|
167
|
+
function phase3_execute(db, _projectRoot) {
|
|
168
|
+
const details = {
|
|
169
|
+
urdDocsMigrated: 0,
|
|
170
|
+
migratedToUS: 0,
|
|
171
|
+
migratedToFR: 0,
|
|
172
|
+
migratedToNFR: 0,
|
|
173
|
+
};
|
|
174
|
+
// Find all URD documents
|
|
175
|
+
const urdDocs = db.prepare("SELECT d.id, d.title, d.type FROM documents d WHERE d.type = 'URD'").all();
|
|
176
|
+
if (urdDocs.length === 0) {
|
|
177
|
+
return details; // Nothing to do — idempotent
|
|
178
|
+
}
|
|
179
|
+
// Delete any existing URD entities (cascading relations)
|
|
180
|
+
db.prepare("DELETE FROM relations WHERE from_id IN (SELECT id FROM entities WHERE type_name = 'URD') OR to_id IN (SELECT id FROM entities WHERE type_name = 'URD')").run();
|
|
181
|
+
const urdEntities = db.prepare("SELECT id FROM entities WHERE type_name = 'URD'").all();
|
|
182
|
+
for (const e of urdEntities) {
|
|
183
|
+
db.prepare('DELETE FROM relations WHERE from_id = ? OR to_id = ?').run(e.id, e.id);
|
|
184
|
+
}
|
|
185
|
+
db.prepare("DELETE FROM entities WHERE type_name = 'URD'").run();
|
|
186
|
+
// Get content for each URD doc and determine replacement type
|
|
187
|
+
for (const doc of urdDocs) {
|
|
188
|
+
const contentRow = db.prepare('SELECT content FROM document_contents WHERE document_id = ?').get(doc.id);
|
|
189
|
+
const replacementType = determineUrdReplacementType(doc.title, contentRow?.content ?? null);
|
|
190
|
+
// Generate new ID: TYPE-NNNN where NNNN is next sequential number
|
|
191
|
+
const maxIdRow = db.prepare("SELECT MAX(CAST(SUBSTR(id, INSTR(id, '-') + 1) AS INTEGER)) as maxNum FROM documents WHERE type = ?").get(replacementType);
|
|
192
|
+
const nextNum = ((maxIdRow?.maxNum ?? 0) + 1);
|
|
193
|
+
const newId = `${replacementType}-${String(nextNum).padStart(4, '0')}`;
|
|
194
|
+
// Update document type and ID
|
|
195
|
+
db.prepare('UPDATE documents SET type = ?, id = ? WHERE id = ?').run(replacementType, newId, doc.id);
|
|
196
|
+
// Update content FK
|
|
197
|
+
db.prepare('UPDATE document_contents SET document_id = ? WHERE document_id = ?').run(newId, doc.id);
|
|
198
|
+
details.urdDocsMigrated++;
|
|
199
|
+
if (replacementType === 'US')
|
|
200
|
+
details.migratedToUS++;
|
|
201
|
+
else if (replacementType === 'FR')
|
|
202
|
+
details.migratedToFR++;
|
|
203
|
+
else
|
|
204
|
+
details.migratedToNFR++;
|
|
205
|
+
}
|
|
206
|
+
return details;
|
|
207
|
+
}
|
|
208
|
+
function phase3_validatePre(db) {
|
|
209
|
+
const urdCount = db.prepare("SELECT COUNT(*) as cnt FROM documents WHERE type = 'URD'").get().cnt;
|
|
210
|
+
return urdCount > 0;
|
|
211
|
+
}
|
|
212
|
+
function phase3_validatePost(db) {
|
|
213
|
+
const urdCount = db.prepare("SELECT COUNT(*) as cnt FROM documents WHERE type = 'URD'").get().cnt;
|
|
214
|
+
return urdCount === 0;
|
|
215
|
+
}
|
|
216
|
+
// ─── Phase 4: Add 8 New Relation Type Definitions ───
|
|
217
|
+
function phase4_execute(db) {
|
|
218
|
+
const details = { newRelationTypesAdded: 0, alreadyExisted: 0 };
|
|
219
|
+
const now = new Date().toISOString();
|
|
220
|
+
for (const [name, def] of Object.entries(NEW_RELATION_TYPES)) {
|
|
221
|
+
const existing = db.prepare('SELECT name FROM relation_types WHERE name = ?').get(name);
|
|
222
|
+
if (existing) {
|
|
223
|
+
details.alreadyExisted++;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
db.prepare(`
|
|
227
|
+
INSERT INTO relation_types (name, from_types, to_types, cardinality, inverse_of, description, created_at, updated_at)
|
|
228
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
229
|
+
`).run(name, JSON.stringify(def.from), JSON.stringify(def.to), def.cardinality, null, '', now, now);
|
|
230
|
+
details.newRelationTypesAdded++;
|
|
231
|
+
}
|
|
232
|
+
return details;
|
|
233
|
+
}
|
|
234
|
+
function phase4_validatePre(db) {
|
|
235
|
+
// Valid if not all 8 new types exist yet
|
|
236
|
+
let missing = 0;
|
|
237
|
+
for (const name of Object.keys(NEW_RELATION_TYPES)) {
|
|
238
|
+
const exists = db.prepare('SELECT name FROM relation_types WHERE name = ?').get(name);
|
|
239
|
+
if (!exists)
|
|
240
|
+
missing++;
|
|
241
|
+
}
|
|
242
|
+
return missing > 0;
|
|
243
|
+
}
|
|
244
|
+
function phase4_validatePost(db) {
|
|
245
|
+
let allPresent = true;
|
|
246
|
+
for (const name of Object.keys(NEW_RELATION_TYPES)) {
|
|
247
|
+
const exists = db.prepare('SELECT name FROM relation_types WHERE name = ?').get(name);
|
|
248
|
+
if (!exists)
|
|
249
|
+
allPresent = false;
|
|
250
|
+
}
|
|
251
|
+
return allPresent;
|
|
252
|
+
}
|
|
253
|
+
// ─── Phase 5: Update 9 Surviving Relation Type Definitions ───
|
|
254
|
+
function phase5_execute(db) {
|
|
255
|
+
const details = { relationTypesUpdated: 0 };
|
|
256
|
+
for (const [name, def] of Object.entries(SURVIVING_UPDATES)) {
|
|
257
|
+
const existing = db.prepare('SELECT name, from_types, to_types, cardinality FROM relation_types WHERE name = ?').get(name);
|
|
258
|
+
if (!existing)
|
|
259
|
+
continue;
|
|
260
|
+
const newFrom = JSON.stringify(def.from);
|
|
261
|
+
const newTo = JSON.stringify(def.to);
|
|
262
|
+
if (existing.from_types !== newFrom || existing.to_types !== newTo || existing.cardinality !== def.cardinality) {
|
|
263
|
+
db.prepare('UPDATE relation_types SET from_types = ?, to_types = ?, cardinality = ? WHERE name = ?').run(newFrom, newTo, def.cardinality, name);
|
|
264
|
+
details.relationTypesUpdated++;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return details;
|
|
268
|
+
}
|
|
269
|
+
function phase5_validatePre(_db) {
|
|
270
|
+
// Always allowed; idempotent
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
function phase5_validatePost(db) {
|
|
274
|
+
// Verify all surviving types match expected definitions
|
|
275
|
+
for (const [name, def] of Object.entries(SURVIVING_UPDATES)) {
|
|
276
|
+
const row = db.prepare('SELECT from_types, to_types FROM relation_types WHERE name = ?').get(name);
|
|
277
|
+
if (!row)
|
|
278
|
+
return false;
|
|
279
|
+
if (row.from_types !== JSON.stringify(def.from))
|
|
280
|
+
return false;
|
|
281
|
+
if (row.to_types !== JSON.stringify(def.to))
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
// ─── Phase 6: Post-Migration Traceability Rebuild ───
|
|
287
|
+
function phase6_execute(db) {
|
|
288
|
+
const details = {
|
|
289
|
+
fulfilledByCreated: 0,
|
|
290
|
+
fulfillsCreated: 0,
|
|
291
|
+
realizedByCreated: 0,
|
|
292
|
+
realizesCreated: 0,
|
|
293
|
+
oldEdgesRead: 0,
|
|
294
|
+
};
|
|
295
|
+
const now = new Date().toISOString();
|
|
296
|
+
// Step 1: Read old decomposed_from / decomposes_to edges (these will be deleted in Phase 7)
|
|
297
|
+
// These represent: US → decomposes_to → FR/NFR
|
|
298
|
+
const oldDecomposeEdges = db.prepare(`
|
|
299
|
+
SELECT DISTINCT r.from_id, r.to_id,
|
|
300
|
+
e_from.type_name as from_type, e_to.type_name as to_type
|
|
301
|
+
FROM relations r
|
|
302
|
+
JOIN entities e_from ON r.from_id = e_from.id
|
|
303
|
+
JOIN entities e_to ON r.to_id = e_to.id
|
|
304
|
+
WHERE r.rel_type IN ('decomposed_from', 'decomposes_to')
|
|
305
|
+
`).all();
|
|
306
|
+
details.oldEdgesRead += oldDecomposeEdges.length;
|
|
307
|
+
// Build fulfilled_by/fulfills edges from decomposition data
|
|
308
|
+
const fulfilledPairs = new Set();
|
|
309
|
+
for (const edge of oldDecomposeEdges) {
|
|
310
|
+
let usId = null;
|
|
311
|
+
let frNfrId = null;
|
|
312
|
+
if (edge.from_type === 'US' && (edge.to_type === 'FR' || edge.to_type === 'NFR')) {
|
|
313
|
+
usId = edge.from_id;
|
|
314
|
+
frNfrId = edge.to_id;
|
|
315
|
+
}
|
|
316
|
+
else if (edge.to_type === 'US' && (edge.from_type === 'FR' || edge.from_type === 'NFR')) {
|
|
317
|
+
usId = edge.to_id;
|
|
318
|
+
frNfrId = edge.from_id;
|
|
319
|
+
}
|
|
320
|
+
if (usId && frNfrId) {
|
|
321
|
+
const pairKey = `fulfilled:${usId}->${frNfrId}`;
|
|
322
|
+
if (!fulfilledPairs.has(pairKey)) {
|
|
323
|
+
fulfilledPairs.add(pairKey);
|
|
324
|
+
// Check if fulfilled_by already exists
|
|
325
|
+
const existingFb = db.prepare("SELECT id FROM relations WHERE rel_type = 'fulfilled_by' AND from_id = ? AND to_id = ?").get(usId, frNfrId);
|
|
326
|
+
if (!existingFb) {
|
|
327
|
+
db.prepare(`
|
|
328
|
+
INSERT INTO relations (rel_type, from_id, to_id, properties, created_at)
|
|
329
|
+
VALUES (?, ?, ?, ?, ?)
|
|
330
|
+
`).run('fulfilled_by', usId, frNfrId, '{}', now);
|
|
331
|
+
details.fulfilledByCreated++;
|
|
332
|
+
}
|
|
333
|
+
// Check if fulfills already exists
|
|
334
|
+
const existingFl = db.prepare("SELECT id FROM relations WHERE rel_type = 'fulfills' AND from_id = ? AND to_id = ?").get(frNfrId, usId);
|
|
335
|
+
if (!existingFl) {
|
|
336
|
+
db.prepare(`
|
|
337
|
+
INSERT INTO relations (rel_type, from_id, to_id, properties, created_at)
|
|
338
|
+
VALUES (?, ?, ?, ?, ?)
|
|
339
|
+
`).run('fulfills', frNfrId, usId, '{}', now);
|
|
340
|
+
details.fulfillsCreated++;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// Step 2: Read old traced_from / traces_to edges
|
|
346
|
+
// These represent: FR → traced_from → FS (FR requirement traces from FS specification)
|
|
347
|
+
const oldTraceEdges = db.prepare(`
|
|
348
|
+
SELECT DISTINCT r.from_id, r.to_id,
|
|
349
|
+
e_from.type_name as from_type, e_to.type_name as to_type
|
|
350
|
+
FROM relations r
|
|
351
|
+
JOIN entities e_from ON r.from_id = e_from.id
|
|
352
|
+
JOIN entities e_to ON r.to_id = e_to.id
|
|
353
|
+
WHERE r.rel_type IN ('traced_from', 'traces_to')
|
|
354
|
+
`).all();
|
|
355
|
+
details.oldEdgesRead += oldTraceEdges.length;
|
|
356
|
+
// Build realized_by/realizes edges from traceability data
|
|
357
|
+
const realizedPairs = new Set();
|
|
358
|
+
for (const edge of oldTraceEdges) {
|
|
359
|
+
let frId = null;
|
|
360
|
+
let fsId = null;
|
|
361
|
+
if (edge.from_type === 'FR' && edge.to_type === 'FS') {
|
|
362
|
+
frId = edge.from_id;
|
|
363
|
+
fsId = edge.to_id;
|
|
364
|
+
}
|
|
365
|
+
else if (edge.to_type === 'FR' && edge.from_type === 'FS') {
|
|
366
|
+
frId = edge.to_id;
|
|
367
|
+
fsId = edge.from_id;
|
|
368
|
+
}
|
|
369
|
+
else if (edge.from_type === 'FR' && (edge.to_type === 'FS' || edge.to_type === 'NFS')) {
|
|
370
|
+
frId = edge.from_id;
|
|
371
|
+
fsId = edge.to_id;
|
|
372
|
+
}
|
|
373
|
+
else if (edge.to_type === 'FR' && (edge.from_type === 'FS' || edge.from_type === 'NFS')) {
|
|
374
|
+
frId = edge.to_id;
|
|
375
|
+
fsId = edge.from_id;
|
|
376
|
+
}
|
|
377
|
+
if (frId && fsId) {
|
|
378
|
+
const pairKey = `realized:${frId}->${fsId}`;
|
|
379
|
+
if (!realizedPairs.has(pairKey)) {
|
|
380
|
+
realizedPairs.add(pairKey);
|
|
381
|
+
// Check if realized_by already exists
|
|
382
|
+
const existingRb = db.prepare("SELECT id FROM relations WHERE rel_type = 'realized_by' AND from_id = ? AND to_id = ?").get(frId, fsId);
|
|
383
|
+
if (!existingRb) {
|
|
384
|
+
db.prepare(`
|
|
385
|
+
INSERT INTO relations (rel_type, from_id, to_id, properties, created_at)
|
|
386
|
+
VALUES (?, ?, ?, ?, ?)
|
|
387
|
+
`).run('realized_by', frId, fsId, '{}', now);
|
|
388
|
+
details.realizedByCreated++;
|
|
389
|
+
}
|
|
390
|
+
// Check if realizes already exists
|
|
391
|
+
const existingRl = db.prepare("SELECT id FROM relations WHERE rel_type = 'realizes' AND from_id = ? AND to_id = ?").get(fsId, frId);
|
|
392
|
+
if (!existingRl) {
|
|
393
|
+
db.prepare(`
|
|
394
|
+
INSERT INTO relations (rel_type, from_id, to_id, properties, created_at)
|
|
395
|
+
VALUES (?, ?, ?, ?, ?)
|
|
396
|
+
`).run('realizes', fsId, frId, '{}', now);
|
|
397
|
+
details.realizesCreated++;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return details;
|
|
403
|
+
}
|
|
404
|
+
function phase6_validatePre(db) {
|
|
405
|
+
// Check if all 8 new relation types exist (Phase 4 must have been run)
|
|
406
|
+
for (const name of Object.keys(NEW_RELATION_TYPES)) {
|
|
407
|
+
const exists = db.prepare('SELECT name FROM relation_types WHERE name = ?').get(name);
|
|
408
|
+
if (!exists)
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
return true;
|
|
412
|
+
}
|
|
413
|
+
function phase6_validatePost() {
|
|
414
|
+
// Success if no errors thrown
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
// ─── Phase 7: Remove 17 Old Relation Types ───
|
|
418
|
+
function phase7_execute(db) {
|
|
419
|
+
const details = { relationTypesRemoved: 0, relationInstancesDeleted: 0, violatingRelationsDeleted: 0 };
|
|
420
|
+
// Step 0: Delete relation instances that violate the new schema type constraints.
|
|
421
|
+
// After Phase 5 updates the relation type definitions, some existing relation
|
|
422
|
+
// instances may have entity types that are no longer allowed.
|
|
423
|
+
const currentRelationTypes = db.prepare('SELECT name, from_types, to_types FROM relation_types').all();
|
|
424
|
+
for (const rt of currentRelationTypes) {
|
|
425
|
+
const allowedFrom = JSON.parse(rt.from_types);
|
|
426
|
+
const allowedTo = JSON.parse(rt.to_types);
|
|
427
|
+
// Delete relations where from_entity type is not in allowedFrom
|
|
428
|
+
const violatingFrom = db.prepare(`
|
|
429
|
+
DELETE FROM relations WHERE rel_type = ?
|
|
430
|
+
AND from_id IN (SELECT id FROM entities WHERE type_name NOT IN (${allowedFrom.map(() => '?').join(',')}))
|
|
431
|
+
`).run(rt.name, ...allowedFrom);
|
|
432
|
+
details.violatingRelationsDeleted += violatingFrom.changes;
|
|
433
|
+
// Delete relations where to_entity type is not in allowedTo
|
|
434
|
+
const violatingTo = db.prepare(`
|
|
435
|
+
DELETE FROM relations WHERE rel_type = ?
|
|
436
|
+
AND to_id IN (SELECT id FROM entities WHERE type_name NOT IN (${allowedTo.map(() => '?').join(',')}))
|
|
437
|
+
`).run(rt.name, ...allowedTo);
|
|
438
|
+
details.violatingRelationsDeleted += violatingTo.changes;
|
|
439
|
+
}
|
|
440
|
+
for (const typeName of REMOVED_RELATION_TYPES) {
|
|
441
|
+
// Step 1: Delete all relation instances of this type
|
|
442
|
+
const instResult = db.prepare('DELETE FROM relations WHERE rel_type = ?').run(typeName);
|
|
443
|
+
details.relationInstancesDeleted += instResult.changes;
|
|
444
|
+
// Step 2: Delete the relation type definition
|
|
445
|
+
const typeResult = db.prepare('DELETE FROM relation_types WHERE name = ?').run(typeName);
|
|
446
|
+
if (typeResult.changes > 0) {
|
|
447
|
+
details.relationTypesRemoved++;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return details;
|
|
451
|
+
}
|
|
452
|
+
function phase7_validatePre(db) {
|
|
453
|
+
let remaining = 0;
|
|
454
|
+
for (const name of REMOVED_RELATION_TYPES) {
|
|
455
|
+
const exists = db.prepare('SELECT name FROM relation_types WHERE name = ?').get(name);
|
|
456
|
+
if (exists)
|
|
457
|
+
remaining++;
|
|
458
|
+
}
|
|
459
|
+
return remaining > 0;
|
|
460
|
+
}
|
|
461
|
+
function phase7_validatePost(db) {
|
|
462
|
+
for (const name of REMOVED_RELATION_TYPES) {
|
|
463
|
+
const exists = db.prepare('SELECT name FROM relation_types WHERE name = ?').get(name);
|
|
464
|
+
if (exists)
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
// ─── Phase 8: YAML Finalization ───
|
|
470
|
+
// generateYamlContent removed — YAML is now source-of-truth, read-only by migration
|
|
471
|
+
function phase8_execute(db, projectRoot) {
|
|
472
|
+
const details = {};
|
|
473
|
+
// Step 1: Verify relation type count
|
|
474
|
+
const rtCount = db.prepare('SELECT COUNT(*) as cnt FROM relation_types').get().cnt;
|
|
475
|
+
details.relationTypeCount = rtCount;
|
|
476
|
+
// Step 2: Read plan/project-schema.yaml as source-of-truth
|
|
477
|
+
const planPath = (0, path_1.resolve)(projectRoot, 'plan', 'project-schema.yaml');
|
|
478
|
+
if (!(0, fs_1.existsSync)(planPath)) {
|
|
479
|
+
throw new errors_1.MigrationYamlWriteError('plan/project-schema.yaml', 'YAML schema file not found — cannot sync DB from YAML');
|
|
480
|
+
}
|
|
481
|
+
const yamlContent = (0, fs_1.readFileSync)(planPath, 'utf-8');
|
|
482
|
+
details.planYamlRead = planPath;
|
|
483
|
+
details.planYamlBytes = yamlContent.length;
|
|
484
|
+
// Step 3: Parse YAML and sync to DB (YAML is source-of-truth)
|
|
485
|
+
const yaml = require('yaml');
|
|
486
|
+
const schema = yaml.parse(yamlContent);
|
|
487
|
+
if (!schema || !schema.relation_types) {
|
|
488
|
+
throw new errors_1.MigrationValidationError('YAML schema missing relation_types key');
|
|
489
|
+
}
|
|
490
|
+
const updateStmt = db.prepare(`UPDATE relation_types SET from_types = ?, to_types = ?, cardinality = ?, updated_at = datetime('now') WHERE name = ?`);
|
|
491
|
+
let syncedCount = 0;
|
|
492
|
+
for (const [name, def] of Object.entries(schema.relation_types)) {
|
|
493
|
+
const fromTypes = JSON.stringify(def?.from ?? []);
|
|
494
|
+
const toTypes = JSON.stringify(def?.to ?? []);
|
|
495
|
+
const cardinality = def?.cardinality ?? 'many_to_many';
|
|
496
|
+
const result = updateStmt.run(fromTypes, toTypes, cardinality, name);
|
|
497
|
+
if (result.changes > 0)
|
|
498
|
+
syncedCount++;
|
|
499
|
+
}
|
|
500
|
+
details.relationTypesSynced = syncedCount;
|
|
501
|
+
// Step 4: Re-generate schema-types.ts (optional — skip if script unavailable)
|
|
502
|
+
const scriptPath = (0, path_1.resolve)(projectRoot, 'scripts', 'generate-schema-types.js');
|
|
503
|
+
if ((0, fs_1.existsSync)(scriptPath)) {
|
|
504
|
+
try {
|
|
505
|
+
const output = (0, child_process_1.execSync)(`node ${scriptPath}`, {
|
|
506
|
+
cwd: projectRoot,
|
|
507
|
+
encoding: 'utf-8',
|
|
508
|
+
timeout: 30_000,
|
|
509
|
+
});
|
|
510
|
+
details.scriptOutput = output.trim();
|
|
511
|
+
}
|
|
512
|
+
catch (err) {
|
|
513
|
+
// Script execution failure is non-fatal for yaml-finalization
|
|
514
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
515
|
+
details.scriptOutput = `Warning: generate-schema-types.js failed: ${msg}`;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
details.scriptOutput = 'generate-schema-types.js not found — skipped';
|
|
520
|
+
}
|
|
521
|
+
// Step 5: Update graph_schema metadata to builtin:3.0
|
|
522
|
+
const now = new Date().toISOString();
|
|
523
|
+
db.prepare('DELETE FROM graph_schema').run();
|
|
524
|
+
db.prepare('INSERT INTO graph_schema (source_file, version, bootstrapped_at, checksum) VALUES (?, ?, ?, ?)').run('builtin:3.0', 'builtin:3.0', now, null);
|
|
525
|
+
details.schemaVersion = 'builtin:3.0';
|
|
526
|
+
return details;
|
|
527
|
+
}
|
|
528
|
+
function phase8_validatePre(db) {
|
|
529
|
+
const rtCount = db.prepare('SELECT COUNT(*) as cnt FROM relation_types').get().cnt;
|
|
530
|
+
return rtCount === 18;
|
|
531
|
+
}
|
|
532
|
+
function phase8_validatePost(_db) {
|
|
533
|
+
return true; // Verified by Phase 9
|
|
534
|
+
}
|
|
535
|
+
// ─── Phase 9: Verification ───
|
|
536
|
+
function phase9_execute(db) {
|
|
537
|
+
const details = {};
|
|
538
|
+
// Re-enable FK enforcement — it was disabled at the start of the migration
|
|
539
|
+
db.exec('PRAGMA foreign_keys = ON');
|
|
540
|
+
// Check 1: Relation type count
|
|
541
|
+
const rtCount = db.prepare('SELECT COUNT(*) as cnt FROM relation_types').get().cnt;
|
|
542
|
+
details.relationTypeCount = rtCount;
|
|
543
|
+
// Check 2: Verify no entity_types table
|
|
544
|
+
const etTable = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entity_types'").get();
|
|
545
|
+
details.entityTypesTableExists = etTable ? 'YES' : 'NO';
|
|
546
|
+
// Check 3: Count remaining relation instances
|
|
547
|
+
const relCount = db.prepare('SELECT COUNT(*) as cnt FROM relations').get().cnt;
|
|
548
|
+
details.remainingRelationInstances = relCount;
|
|
549
|
+
// Check 4: Verify no removed relation types exist
|
|
550
|
+
let removedStillPresent = 0;
|
|
551
|
+
for (const name of REMOVED_RELATION_TYPES) {
|
|
552
|
+
const exists = db.prepare('SELECT name FROM relation_types WHERE name = ?').get(name);
|
|
553
|
+
if (exists)
|
|
554
|
+
removedStillPresent++;
|
|
555
|
+
}
|
|
556
|
+
details.removedTypesStillPresent = removedStillPresent;
|
|
557
|
+
// Check 5: Verify all final types exist
|
|
558
|
+
let finalTypesPresent = 0;
|
|
559
|
+
for (const name of FINAL_RELATION_TYPE_NAMES) {
|
|
560
|
+
const exists = db.prepare('SELECT name FROM relation_types WHERE name = ?').get(name);
|
|
561
|
+
if (exists)
|
|
562
|
+
finalTypesPresent++;
|
|
563
|
+
}
|
|
564
|
+
details.finalTypesPresent = finalTypesPresent;
|
|
565
|
+
// Check 6: Count URD documents
|
|
566
|
+
const urdCount = db.prepare("SELECT COUNT(*) as cnt FROM documents WHERE type = 'URD'").get().cnt;
|
|
567
|
+
details.urdDocsRemaining = urdCount;
|
|
568
|
+
// Check 7: Count TR entities
|
|
569
|
+
const trCount = db.prepare("SELECT COUNT(*) as cnt FROM entities WHERE type_name = 'TR'").get().cnt;
|
|
570
|
+
details.trEntitiesRemaining = trCount;
|
|
571
|
+
// Check 8: Dangling relations
|
|
572
|
+
const danglingCount = db.prepare(`
|
|
573
|
+
SELECT COUNT(*) as cnt FROM relations r
|
|
574
|
+
LEFT JOIN entities e1 ON r.from_id = e1.id
|
|
575
|
+
LEFT JOIN entities e2 ON r.to_id = e2.id
|
|
576
|
+
WHERE e1.id IS NULL OR e2.id IS NULL
|
|
577
|
+
`).get().cnt;
|
|
578
|
+
details.danglingRelations = danglingCount;
|
|
579
|
+
// Overall pass/fail
|
|
580
|
+
const failed = [];
|
|
581
|
+
if (rtCount !== 18)
|
|
582
|
+
failed.push(`Relation type count ${rtCount} != 18`);
|
|
583
|
+
if (etTable)
|
|
584
|
+
failed.push('entity_types table still exists');
|
|
585
|
+
if (removedStillPresent > 0)
|
|
586
|
+
failed.push(`${removedStillPresent} removed types still present`);
|
|
587
|
+
if (finalTypesPresent !== 18)
|
|
588
|
+
failed.push(`Only ${finalTypesPresent}/18 final types present`);
|
|
589
|
+
if (urdCount > 0)
|
|
590
|
+
failed.push(`${urdCount} URD docs remaining`);
|
|
591
|
+
if (trCount > 0)
|
|
592
|
+
failed.push(`${trCount} TR entities remaining`);
|
|
593
|
+
if (danglingCount > 0)
|
|
594
|
+
failed.push(`${danglingCount} dangling relations`);
|
|
595
|
+
if (failed.length > 0) {
|
|
596
|
+
throw new errors_1.MigrationValidationError(`Verification failed: ${failed.join('; ')}`);
|
|
597
|
+
}
|
|
598
|
+
details.verdict = 'PASS';
|
|
599
|
+
return details;
|
|
600
|
+
}
|
|
601
|
+
function phase9_validatePre(_db) {
|
|
602
|
+
return true; // Always runnable
|
|
603
|
+
}
|
|
604
|
+
function phase9_validatePost(_db) {
|
|
605
|
+
return true;
|
|
606
|
+
}
|
|
607
|
+
// ─── Phase Registry ───
|
|
608
|
+
const PHASES = [
|
|
609
|
+
{
|
|
610
|
+
name: 'entity-types-drop',
|
|
611
|
+
execute: (db, _) => {
|
|
612
|
+
const result = { phase: 'entity-types-drop', elapsedMs: 0, details: {} };
|
|
613
|
+
try {
|
|
614
|
+
result.elapsedMs = measureElapsed(() => {
|
|
615
|
+
result.details = phase1_execute(db);
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
catch (err) {
|
|
619
|
+
result.error = err instanceof Error ? err.message : String(err);
|
|
620
|
+
}
|
|
621
|
+
return result;
|
|
622
|
+
},
|
|
623
|
+
validatePre: phase1_validatePre,
|
|
624
|
+
validatePost: phase1_validatePost,
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
name: 'tr-relations-cleanup',
|
|
628
|
+
execute: (db, _) => {
|
|
629
|
+
const result = { phase: 'tr-relations-cleanup', elapsedMs: 0, details: {} };
|
|
630
|
+
try {
|
|
631
|
+
result.elapsedMs = measureElapsed(() => {
|
|
632
|
+
result.details = phase2_execute(db);
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
catch (err) {
|
|
636
|
+
result.error = err instanceof Error ? err.message : String(err);
|
|
637
|
+
}
|
|
638
|
+
return result;
|
|
639
|
+
},
|
|
640
|
+
validatePre: phase2_validatePre,
|
|
641
|
+
validatePost: phase2_validatePost,
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
name: 'urd-migration',
|
|
645
|
+
execute: (db, projectRoot) => {
|
|
646
|
+
const result = { phase: 'urd-migration', elapsedMs: 0, details: {} };
|
|
647
|
+
try {
|
|
648
|
+
result.elapsedMs = measureElapsed(() => {
|
|
649
|
+
result.details = phase3_execute(db, projectRoot);
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
catch (err) {
|
|
653
|
+
result.error = err instanceof Error ? err.message : String(err);
|
|
654
|
+
}
|
|
655
|
+
return result;
|
|
656
|
+
},
|
|
657
|
+
validatePre: phase3_validatePre,
|
|
658
|
+
validatePost: phase3_validatePost,
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
name: 'add-new-relations',
|
|
662
|
+
execute: (db, _) => {
|
|
663
|
+
const result = { phase: 'add-new-relations', elapsedMs: 0, details: {} };
|
|
664
|
+
try {
|
|
665
|
+
result.elapsedMs = measureElapsed(() => {
|
|
666
|
+
result.details = phase4_execute(db);
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
catch (err) {
|
|
670
|
+
result.error = err instanceof Error ? err.message : String(err);
|
|
671
|
+
}
|
|
672
|
+
return result;
|
|
673
|
+
},
|
|
674
|
+
validatePre: phase4_validatePre,
|
|
675
|
+
validatePost: phase4_validatePost,
|
|
676
|
+
},
|
|
677
|
+
{
|
|
678
|
+
name: 'update-surviving-relations',
|
|
679
|
+
execute: (db, _) => {
|
|
680
|
+
const result = { phase: 'update-surviving-relations', elapsedMs: 0, details: {} };
|
|
681
|
+
try {
|
|
682
|
+
result.elapsedMs = measureElapsed(() => {
|
|
683
|
+
result.details = phase5_execute(db);
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
catch (err) {
|
|
687
|
+
result.error = err instanceof Error ? err.message : String(err);
|
|
688
|
+
}
|
|
689
|
+
return result;
|
|
690
|
+
},
|
|
691
|
+
validatePre: phase5_validatePre,
|
|
692
|
+
validatePost: phase5_validatePost,
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
name: 'traceability-rebuild',
|
|
696
|
+
execute: (db, _) => {
|
|
697
|
+
const result = { phase: 'traceability-rebuild', elapsedMs: 0, details: {} };
|
|
698
|
+
try {
|
|
699
|
+
result.elapsedMs = measureElapsed(() => {
|
|
700
|
+
result.details = phase6_execute(db);
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
catch (err) {
|
|
704
|
+
result.error = err instanceof Error ? err.message : String(err);
|
|
705
|
+
}
|
|
706
|
+
return result;
|
|
707
|
+
},
|
|
708
|
+
validatePre: phase6_validatePre,
|
|
709
|
+
validatePost: (_db) => phase6_validatePost(),
|
|
710
|
+
},
|
|
711
|
+
{
|
|
712
|
+
name: 'remove-old-relations',
|
|
713
|
+
execute: (db, _) => {
|
|
714
|
+
const result = { phase: 'remove-old-relations', elapsedMs: 0, details: {} };
|
|
715
|
+
try {
|
|
716
|
+
result.elapsedMs = measureElapsed(() => {
|
|
717
|
+
result.details = phase7_execute(db);
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
catch (err) {
|
|
721
|
+
result.error = err instanceof Error ? err.message : String(err);
|
|
722
|
+
}
|
|
723
|
+
return result;
|
|
724
|
+
},
|
|
725
|
+
validatePre: phase7_validatePre,
|
|
726
|
+
validatePost: phase7_validatePost,
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
name: 'yaml-finalization',
|
|
730
|
+
execute: (db, projectRoot) => {
|
|
731
|
+
const result = { phase: 'yaml-finalization', elapsedMs: 0, details: {} };
|
|
732
|
+
try {
|
|
733
|
+
result.elapsedMs = measureElapsed(() => {
|
|
734
|
+
result.details = phase8_execute(db, projectRoot);
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
catch (err) {
|
|
738
|
+
result.error = err instanceof Error ? err.message : String(err);
|
|
739
|
+
}
|
|
740
|
+
return result;
|
|
741
|
+
},
|
|
742
|
+
validatePre: phase8_validatePre,
|
|
743
|
+
validatePost: phase8_validatePost,
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
name: 'verification',
|
|
747
|
+
execute: (db, _) => {
|
|
748
|
+
const result = { phase: 'verification', elapsedMs: 0, details: {} };
|
|
749
|
+
try {
|
|
750
|
+
result.elapsedMs = measureElapsed(() => {
|
|
751
|
+
result.details = phase9_execute(db);
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
catch (err) {
|
|
755
|
+
result.error = err instanceof Error ? err.message : String(err);
|
|
756
|
+
}
|
|
757
|
+
return result;
|
|
758
|
+
},
|
|
759
|
+
validatePre: phase9_validatePre,
|
|
760
|
+
validatePost: phase9_validatePost,
|
|
761
|
+
},
|
|
762
|
+
];
|
|
763
|
+
const PHASE_NAME_MAP = new Map(PHASES.map(p => [p.name, p]));
|
|
764
|
+
// ─── Public API ───
|
|
765
|
+
function getAvailablePhases() {
|
|
766
|
+
return PHASES.map(p => p.name);
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Run a single named phase. Throws if phase name is unknown.
|
|
770
|
+
*/
|
|
771
|
+
function runPhase(db, phaseName, projectRoot) {
|
|
772
|
+
const phase = PHASE_NAME_MAP.get(phaseName);
|
|
773
|
+
if (!phase) {
|
|
774
|
+
throw new Error(`Unknown migration phase: ${phaseName}. Valid phases: ${getAvailablePhases().join(', ')}`);
|
|
775
|
+
}
|
|
776
|
+
return phase.execute(db, projectRoot);
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Run the full 9-phase Sprint 31 migration on the given database.
|
|
780
|
+
* Phases execute in order. Each phase runs in its own transaction.
|
|
781
|
+
* Idempotent: phases skip work when preconditions are already met.
|
|
782
|
+
*
|
|
783
|
+
* @param db - Database connection
|
|
784
|
+
* @param projectRoot - Project root directory (for YAML/script paths in Phase 8)
|
|
785
|
+
* @returns MigrationResult with phase details and elapsed times
|
|
786
|
+
*/
|
|
787
|
+
function runMigration(db, projectRoot) {
|
|
788
|
+
const phases = [];
|
|
789
|
+
const errors = [];
|
|
790
|
+
const totalStart = process.hrtime.bigint();
|
|
791
|
+
// SQLite schema migrations require FK enforcement to be OFF during the
|
|
792
|
+
// entire migration because we recreate tables, change IDs, and drop
|
|
793
|
+
// referenced tables. FK is re-enabled in Phase 9 (verification).
|
|
794
|
+
db.exec('PRAGMA foreign_keys = OFF');
|
|
795
|
+
// Phase ordering enforcement: check each phase's preconditions
|
|
796
|
+
// and skip if already applied
|
|
797
|
+
for (const phase of PHASES) {
|
|
798
|
+
const phaseName = phase.name;
|
|
799
|
+
// Check if phase needs to run (or is already applied)
|
|
800
|
+
// For destructive phases, validatePre checks if work needs to be done
|
|
801
|
+
// For additive phases, we check the post-condition instead
|
|
802
|
+
try {
|
|
803
|
+
// Run the phase in a transaction
|
|
804
|
+
(0, retry_1.withTransaction)(db, () => {
|
|
805
|
+
const result = phase.execute(db, projectRoot);
|
|
806
|
+
result.phase = phaseName; // Ensure phase name is set
|
|
807
|
+
phases.push(result);
|
|
808
|
+
if (result.error) {
|
|
809
|
+
errors.push(`${phaseName}: ${result.error}`);
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
console.log(`Phase ${phaseName}: ${result.elapsedMs}ms`);
|
|
813
|
+
}
|
|
814
|
+
// Post-validation
|
|
815
|
+
if (!result.error && !phase.validatePost(db)) {
|
|
816
|
+
const msg = `Post-validation failed for phase ${phaseName}`;
|
|
817
|
+
result.error = msg;
|
|
818
|
+
errors.push(msg);
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
catch (err) {
|
|
823
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
824
|
+
phases.push({
|
|
825
|
+
phase: phaseName,
|
|
826
|
+
elapsedMs: 0,
|
|
827
|
+
details: {},
|
|
828
|
+
error: msg,
|
|
829
|
+
});
|
|
830
|
+
errors.push(`${phaseName}: ${msg}`);
|
|
831
|
+
console.error(`Phase ${phaseName} failed: ${msg}`);
|
|
832
|
+
}
|
|
833
|
+
// If a phase failed, stop further phases
|
|
834
|
+
if (phases[phases.length - 1].error) {
|
|
835
|
+
const totalElapsed = Number((process.hrtime.bigint() - totalStart) / 1000000n);
|
|
836
|
+
return { success: false, phases, totalElapsedMs: totalElapsed, errors };
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
const totalElapsed = Number((process.hrtime.bigint() - totalStart) / 1000000n);
|
|
840
|
+
console.log(`Total migration time: ${totalElapsed}ms`);
|
|
841
|
+
return {
|
|
842
|
+
success: errors.length === 0,
|
|
843
|
+
phases,
|
|
844
|
+
totalElapsedMs: totalElapsed,
|
|
845
|
+
errors,
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Pre-migration validation: check that the database has the expected pre-migration state.
|
|
850
|
+
* (builtin:2.0 characteristics — entity_types table, 27+ relation types)
|
|
851
|
+
*/
|
|
852
|
+
function validatePreMigration(db) {
|
|
853
|
+
const entityTypeTable = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entity_types'").get();
|
|
854
|
+
if (entityTypeTable) {
|
|
855
|
+
const entityTypeCount = db.prepare('SELECT COUNT(*) as cnt FROM entity_types').get().cnt;
|
|
856
|
+
console.log(`Pre-migration: entity_types table present with ${entityTypeCount} types`);
|
|
857
|
+
}
|
|
858
|
+
else {
|
|
859
|
+
console.log('Pre-migration: entity_types table already dropped');
|
|
860
|
+
}
|
|
861
|
+
const relationTypeCount = db.prepare('SELECT COUNT(*) as cnt FROM relation_types').get().cnt;
|
|
862
|
+
console.log(`Pre-migration: ${relationTypeCount} relation types present`);
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Post-migration validation: verify the database is in builtin:3.0 state.
|
|
866
|
+
*/
|
|
867
|
+
function validatePostMigration(db) {
|
|
868
|
+
// Verify entity_types table is dropped
|
|
869
|
+
const etTable = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entity_types'").get();
|
|
870
|
+
if (etTable) {
|
|
871
|
+
throw new errors_1.MigrationValidationError('Post-migration validation failed: entity_types table still exists');
|
|
872
|
+
}
|
|
873
|
+
// Verify relation type count
|
|
874
|
+
const rtCount = db.prepare('SELECT COUNT(*) as cnt FROM relation_types').get().cnt;
|
|
875
|
+
if (rtCount !== 18) {
|
|
876
|
+
throw new errors_1.MigrationValidationError(`Post-migration validation failed: expected 18 relation types, found ${rtCount}`);
|
|
877
|
+
}
|
|
878
|
+
// Verify no removed relation types exist
|
|
879
|
+
for (const removed of REMOVED_RELATION_TYPES) {
|
|
880
|
+
const exists = db.prepare('SELECT name FROM relation_types WHERE name = ?').get(removed);
|
|
881
|
+
if (exists) {
|
|
882
|
+
throw new errors_1.MigrationValidationError(`Post-migration validation failed: removed relation type "${removed}" still exists`);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
// Verify all final types exist
|
|
886
|
+
for (const name of FINAL_RELATION_TYPE_NAMES) {
|
|
887
|
+
const exists = db.prepare('SELECT name FROM relation_types WHERE name = ?').get(name);
|
|
888
|
+
if (!exists) {
|
|
889
|
+
throw new errors_1.MigrationValidationError(`Post-migration validation failed: required relation type "${name}" not found`);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
console.log('Post-migration validation: PASSED');
|
|
893
|
+
}
|
|
894
|
+
//# sourceMappingURL=schema-migration-service.js.map
|