@documentation-robotics/cli 0.2.0 → 0.3.0
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/README.md +36 -2
- package/dist/analysis/relationship-classifier.js +2 -9
- package/dist/analysis/relationship-classifier.js.map +2 -2
- package/dist/analyzers/call-graph-utils.js +2 -2
- package/dist/analyzers/call-graph-utils.js.map +2 -2
- package/dist/analyzers/cards/build.js +171 -0
- package/dist/analyzers/cards/build.js.map +7 -0
- package/dist/analyzers/cards/classify.js +42 -0
- package/dist/analyzers/cards/classify.js.map +7 -0
- package/dist/analyzers/cards/edge-index.js +44 -0
- package/dist/analyzers/cards/edge-index.js.map +7 -0
- package/dist/analyzers/cards/facts.js +46 -0
- package/dist/analyzers/cards/facts.js.map +7 -0
- package/dist/analyzers/cards/graph.js +42 -0
- package/dist/analyzers/cards/graph.js.map +7 -0
- package/dist/analyzers/cards/index.js +8 -0
- package/dist/analyzers/cards/index.js.map +7 -0
- package/dist/analyzers/cards/tier0.js +18 -0
- package/dist/analyzers/cards/tier0.js.map +7 -0
- package/dist/analyzers/cards/types.js +1 -0
- package/dist/analyzers/cards/types.js.map +7 -0
- package/dist/analyzers/cbm-analyzer.js +195 -171
- package/dist/analyzers/cbm-analyzer.js.map +2 -2
- package/dist/analyzers/cbm-responses.js +336 -0
- package/dist/analyzers/cbm-responses.js.map +7 -0
- package/dist/analyzers/edge-relationships.js +45 -0
- package/dist/analyzers/edge-relationships.js.map +7 -0
- package/dist/analyzers/mapping-loader.js +9 -0
- package/dist/analyzers/mapping-loader.js.map +2 -2
- package/dist/analyzers/tier0-rules.js +153 -0
- package/dist/analyzers/tier0-rules.js.map +7 -0
- package/dist/analyzers/types.js.map +1 -1
- package/dist/audit/relationships/ai/prompts.js +10 -10
- package/dist/audit/relationships/ai/prompts.js.map +2 -2
- package/dist/audit/relationships/analysis/layer-templates.js +52 -52
- package/dist/audit/relationships/analysis/layer-templates.js.map +1 -1
- package/dist/cli.js +12 -2
- package/dist/cli.js.map +2 -2
- package/dist/coding-agents/default-permissions.js +31 -1
- package/dist/coding-agents/default-permissions.js.map +2 -2
- package/dist/coding-agents/drbot-mcp.js +167 -0
- package/dist/coding-agents/drbot-mcp.js.map +7 -0
- package/dist/commands/add.js +3 -2
- package/dist/commands/add.js.map +2 -2
- package/dist/commands/analyzer.js +117 -14
- package/dist/commands/analyzer.js.map +2 -2
- package/dist/commands/changeset-verify.js +55 -0
- package/dist/commands/changeset-verify.js.map +7 -0
- package/dist/commands/changeset.js +43 -22
- package/dist/commands/changeset.js.map +2 -2
- package/dist/commands/delete.js +10 -4
- package/dist/commands/delete.js.map +2 -2
- package/dist/commands/farm.js +176 -60
- package/dist/commands/farm.js.map +2 -2
- package/dist/commands/mcp.js +19 -4
- package/dist/commands/mcp.js.map +2 -2
- package/dist/commands/relationship.js +207 -26
- package/dist/commands/relationship.js.map +2 -2
- package/dist/commands/update.js +42 -23
- package/dist/commands/update.js.map +2 -2
- package/dist/commands/upgrade.js +99 -11
- package/dist/commands/upgrade.js.map +3 -3
- package/dist/commands/version.js +1 -1
- package/dist/commands/visualize.js +4 -3
- package/dist/commands/visualize.js.map +2 -2
- package/dist/core/catalog-migration.js +33 -0
- package/dist/core/catalog-migration.js.map +7 -0
- package/dist/core/changeset-analysis.js +421 -0
- package/dist/core/changeset-analysis.js.map +7 -0
- package/dist/core/changeset-base-index.js +153 -0
- package/dist/core/changeset-base-index.js.map +7 -0
- package/dist/core/changeset-brief-delta.js +102 -0
- package/dist/core/changeset-brief-delta.js.map +7 -0
- package/dist/core/changeset-graph.js +281 -0
- package/dist/core/changeset-graph.js.map +7 -0
- package/dist/core/changeset-only-guard.js +32 -0
- package/dist/core/changeset-only-guard.js.map +7 -0
- package/dist/core/changeset-summary.js +68 -0
- package/dist/core/changeset-summary.js.map +7 -0
- package/dist/core/changeset-validator.js +66 -20
- package/dist/core/changeset-validator.js.map +2 -2
- package/dist/core/changeset-verify.js +518 -0
- package/dist/core/changeset-verify.js.map +7 -0
- package/dist/core/changeset.js +56 -9
- package/dist/core/changeset.js.map +2 -2
- package/dist/core/element-type-migration.js +145 -0
- package/dist/core/element-type-migration.js.map +7 -0
- package/dist/core/farm-manifest.js +12 -6
- package/dist/core/farm-manifest.js.map +2 -2
- package/dist/core/farm-sync-engine.js +1 -1
- package/dist/core/farm-sync-engine.js.map +2 -2
- package/dist/core/farm-sync-state.js +10 -2
- package/dist/core/farm-sync-state.js.map +2 -2
- package/dist/core/migration-registry.js +75 -46
- package/dist/core/migration-registry.js.map +2 -2
- package/dist/core/model-upgrade.js +102 -0
- package/dist/core/model-upgrade.js.map +7 -0
- package/dist/core/model.js +82 -30
- package/dist/core/model.js.map +2 -2
- package/dist/core/mutation-handler.js +71 -6
- package/dist/core/mutation-handler.js.map +2 -2
- package/dist/core/relationship-migration-table.js +6639 -0
- package/dist/core/relationship-migration-table.js.map +7 -0
- package/dist/core/relationship-migration.js +272 -0
- package/dist/core/relationship-migration.js.map +7 -0
- package/dist/core/relationship-prune.js +49 -0
- package/dist/core/relationship-prune.js.map +7 -0
- package/dist/core/relationship-staging.js +68 -0
- package/dist/core/relationship-staging.js.map +7 -0
- package/dist/core/relationships.js +26 -4
- package/dist/core/relationships.js.map +2 -2
- package/dist/core/report-data-model.js +7 -13
- package/dist/core/report-data-model.js.map +2 -2
- package/dist/core/staged-changeset-storage.js +64 -17
- package/dist/core/staged-changeset-storage.js.map +2 -2
- package/dist/core/staged-source-reference.js +28 -0
- package/dist/core/staged-source-reference.js.map +7 -0
- package/dist/core/staging-area.js +83 -18
- package/dist/core/staging-area.js.map +2 -2
- package/dist/core/virtual-projection.js +13 -5
- package/dist/core/virtual-projection.js.map +2 -2
- package/dist/export/archimate-exporter.js +1 -0
- package/dist/export/archimate-exporter.js.map +2 -2
- package/dist/generated/compiled-validators.js +2 -2
- package/dist/generated/compiled-validators.js.map +2 -2
- package/dist/generated/layer-registry.js +16 -16
- package/dist/generated/layer-registry.js.map +2 -2
- package/dist/generated/node-types.js +186 -168
- package/dist/generated/node-types.js.map +2 -2
- package/dist/generated/relationship-index.js +2236 -7790
- package/dist/generated/relationship-index.js.map +2 -2
- package/dist/integrations/claude_code/agents/dr-architect.md +16 -3
- package/dist/integrations/claude_code/agents/dr-extractor.md +1 -1
- package/dist/integrations/claude_code/commands/dr-analyzer.md +565 -0
- package/dist/integrations/claude_code/commands/dr-map.md +13 -0
- package/dist/integrations/claude_code/commands/dr-plan.md +40 -0
- package/dist/integrations/claude_code/commands/dr-relate.md +106 -96
- package/dist/integrations/claude_code/commands/dr-verify.md +45 -2
- package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +14 -0
- package/dist/integrations/claude_code/docs/MCP_SERVER.md +26 -2
- package/dist/integrations/claude_code/docs/QUICK_START.md +37 -8
- package/dist/integrations/claude_code/docs/README.md +48 -1
- package/dist/integrations/claude_code/skills/dr_01_motivation_layer/SKILL.md +93 -72
- package/dist/integrations/claude_code/skills/dr_02_business_layer/SKILL.md +163 -126
- package/dist/integrations/claude_code/skills/dr_03_product_layer/SKILL.md +161 -126
- package/dist/integrations/claude_code/skills/dr_04_security_layer/SKILL.md +153 -133
- package/dist/integrations/claude_code/skills/dr_05_application_layer/SKILL.md +129 -100
- package/dist/integrations/claude_code/skills/dr_06_technology_layer/SKILL.md +111 -94
- package/dist/integrations/claude_code/skills/dr_07_api_layer/SKILL.md +143 -157
- package/dist/integrations/claude_code/skills/dr_08_data_model_layer/SKILL.md +117 -33
- package/dist/integrations/claude_code/skills/dr_09_dataset_layer/SKILL.md +74 -25
- package/dist/integrations/claude_code/skills/dr_10_ux_layer/SKILL.md +88 -12
- package/dist/integrations/claude_code/skills/dr_11_navigation_layer/SKILL.md +83 -24
- package/dist/integrations/claude_code/skills/dr_12_apm_layer/SKILL.md +57 -12
- package/dist/integrations/claude_code/skills/dr_13_testing_layer/SKILL.md +92 -15
- package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +119 -0
- package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +3 -0
- package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +1 -1
- package/dist/integrations/github_copilot/agents/dr-architect.md +1 -1
- package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +182 -102
- package/dist/integrations/github_copilot/skills/dr_02_business_layer/SKILL.md +266 -148
- package/dist/integrations/github_copilot/skills/dr_03_product_layer/SKILL.md +547 -86
- package/dist/integrations/github_copilot/skills/dr_04_security_layer/SKILL.md +164 -169
- package/dist/integrations/github_copilot/skills/dr_05_application_layer/SKILL.md +420 -150
- package/dist/integrations/github_copilot/skills/dr_06_technology_layer/SKILL.md +331 -138
- package/dist/integrations/github_copilot/skills/dr_07_api_layer/SKILL.md +265 -201
- package/dist/integrations/github_copilot/skills/dr_08_data_model_layer/SKILL.md +181 -58
- package/dist/integrations/github_copilot/skills/dr_09_dataset_layer/SKILL.md +209 -57
- package/dist/integrations/github_copilot/skills/dr_10_ux_layer/SKILL.md +277 -112
- package/dist/integrations/github_copilot/skills/dr_11_navigation_layer/SKILL.md +219 -70
- package/dist/integrations/github_copilot/skills/dr_12_apm_layer/SKILL.md +189 -105
- package/dist/integrations/github_copilot/skills/dr_13_testing_layer/SKILL.md +222 -102
- package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +119 -0
- package/dist/integrations/github_copilot/skills/dr_changeset_reviewer/SKILL.md +3 -0
- package/dist/integrations/hash-utils.js.map +2 -2
- package/dist/mcp/api-key-manager.js +14 -1
- package/dist/mcp/api-key-manager.js.map +2 -2
- package/dist/mcp/changeset-only.js +34 -0
- package/dist/mcp/changeset-only.js.map +7 -0
- package/dist/mcp/tool-registry.js +19 -3
- package/dist/mcp/tool-registry.js.map +2 -2
- package/dist/mcp/tools/changeset-analysis-tools.js +78 -0
- package/dist/mcp/tools/changeset-analysis-tools.js.map +7 -0
- package/dist/mcp/tools/changeset-editing.js +184 -0
- package/dist/mcp/tools/changeset-editing.js.map +7 -0
- package/dist/mcp/tools/changeset-lifecycle.js +83 -0
- package/dist/mcp/tools/changeset-lifecycle.js.map +7 -0
- package/dist/mcp/tools/changeset-shared.js +31 -0
- package/dist/mcp/tools/changeset-shared.js.map +7 -0
- package/dist/mcp/tools/changeset-show.js +17 -1
- package/dist/mcp/tools/changeset-show.js.map +2 -2
- package/dist/mcp/tools/changeset-verify.js +43 -0
- package/dist/mcp/tools/changeset-verify.js.map +7 -0
- package/dist/mcp/tools/index.js +19 -1
- package/dist/mcp/tools/index.js.map +2 -2
- package/dist/mcp/tools/model-add.js +6 -5
- package/dist/mcp/tools/model-add.js.map +2 -2
- package/dist/mcp/tools/model-delete.js +2 -2
- package/dist/mcp/tools/model-delete.js.map +2 -2
- package/dist/mcp/tools/model-search.js +14 -0
- package/dist/mcp/tools/model-search.js.map +2 -2
- package/dist/mcp/tools/model-show.js +29 -2
- package/dist/mcp/tools/model-show.js.map +2 -2
- package/dist/mcp/tools/model-trace.js +93 -30
- package/dist/mcp/tools/model-trace.js.map +2 -2
- package/dist/mcp/tools/model-update.js +35 -19
- package/dist/mcp/tools/model-update.js.map +2 -2
- package/dist/mcp/tools/relationship-tools.js +98 -0
- package/dist/mcp/tools/relationship-tools.js.map +7 -0
- package/dist/mcp/tools/shared.js +25 -2
- package/dist/mcp/tools/shared.js.map +2 -2
- package/dist/mcp/tools/spec-type.js +90 -0
- package/dist/mcp/tools/spec-type.js.map +7 -0
- package/dist/schemas/bundled/analyzers/cbm.json +1186 -68
- package/dist/schemas/bundled/analyzers/manifest.json +2 -2
- package/dist/schemas/bundled/api.json +1029 -1658
- package/dist/schemas/bundled/apm.json +499 -734
- package/dist/schemas/bundled/application.json +735 -371
- package/dist/schemas/bundled/base.json +3 -220
- package/dist/schemas/bundled/business.json +452 -366
- package/dist/schemas/bundled/data-model.json +395 -411
- package/dist/schemas/bundled/data-store.json +334 -558
- package/dist/schemas/bundled/manifest.json +18 -18
- package/dist/schemas/bundled/motivation.json +349 -368
- package/dist/schemas/bundled/navigation.json +415 -731
- package/dist/schemas/bundled/product.json +325 -159
- package/dist/schemas/bundled/security.json +864 -1232
- package/dist/schemas/bundled/technology.json +574 -703
- package/dist/schemas/bundled/testing.json +718 -1643
- package/dist/schemas/bundled/ux.json +739 -1352
- package/dist/server/schemas.js +8 -0
- package/dist/server/schemas.js.map +2 -2
- package/dist/server/server.js +218 -34
- package/dist/server/server.js.map +2 -2
- package/dist/server-entry.js +2 -1
- package/dist/server-entry.js.map +2 -2
- package/dist/telemetry/index.js +1 -1
- package/dist/utils/ansi-suppressor.js +6 -8
- package/dist/utils/ansi-suppressor.js.map +2 -2
- package/dist/utils/attribute-patch.js +41 -0
- package/dist/utils/attribute-patch.js.map +7 -0
- package/dist/utils/error-hints.js +29 -0
- package/dist/utils/error-hints.js.map +7 -0
- package/dist/utils/errors.js +9 -3
- package/dist/utils/errors.js.map +2 -2
- package/dist/utils/project-paths.js +26 -1
- package/dist/utils/project-paths.js.map +2 -2
- package/dist/utils/source-reference.js +14 -12
- package/dist/utils/source-reference.js.map +2 -2
- package/dist/utils/spec-version.js +2 -2
- package/dist/utils/spec-version.js.map +1 -1
- package/dist/validators/composed-reference-validator.js +5 -27
- package/dist/validators/composed-reference-validator.js.map +2 -2
- package/dist/validators/reference-validator.js +0 -7
- package/dist/validators/reference-validator.js.map +2 -2
- package/dist/validators/relationship-schema-validator.js +3 -3
- package/dist/validators/relationship-schema-validator.js.map +2 -2
- package/dist/validators/schema-validator.js +27 -9
- package/dist/validators/schema-validator.js.map +2 -2
- package/dist/validators/types.js.map +1 -1
- package/dist/viewer/README.md +6 -6
- package/dist/viewer/assets/main-CxzcuU_o.js +84 -0
- package/dist/viewer/assets/main-j-7ULYpm.css +1 -0
- package/dist/viewer/index.html +2 -2
- package/dist/viewer/manifest.json +10 -10
- package/dist/viewer/version.json +1 -1
- package/package.json +2 -1
- package/dist/viewer/assets/main-B7AOQXXX.js +0 -81
- package/dist/viewer/assets/main-vdrFrG7q.css +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/core/staging-area.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * StagingAreaManager - Manages staged changesets and interception logic\n *\n * Provides:\n * - Changeset lifecycle management (create, load, list, delete)\n * - Staging operations (stage, unstage, discard)\n * - Commit operations with drift detection and validation\n * - Base snapshot tracking for drift detection\n */\n\nimport path from \"path\";\nimport ansis from \"ansis\";\nimport { StagedChangesetStorage } from \"./staged-changeset-storage.js\";\nimport { BaseSnapshotManager } from \"./base-snapshot-manager.js\";\nimport { Changeset, StagedChange } from \"./changeset.js\";\nimport { ChangesetValidator } from \"./changeset-validator.js\";\nimport { fileExists } from \"../utils/file-io.js\";\nimport type { Model } from \"./model.js\";\nimport type { VirtualProjectionEngine } from \"./virtual-projection.js\";\nimport { createSha256Hash } from \"../types/index.js\";\nimport type { BackupManifest } from \"../types/index.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\nimport { Layer } from \"./layer.js\";\nimport { isValidLayerName } from \"./layers.js\";\nimport {\n isTelemetryEnabled,\n startSpan,\n startActiveSpan,\n endSpan,\n emitLog,\n SeverityNumber,\n} from \"../telemetry/index.js\";\nimport { ModelReportOrchestrator } from \"../reports/model-report-orchestrator.js\";\n\n/**\n * Options for commit operation\n */\nexport interface CommitOptions {\n force?: boolean; // Ignore drift warnings\n validate?: boolean; // Run validation (default: true)\n dryRun?: boolean; // Preview without applying\n}\n\n/**\n * Drift report for change detection\n *\n * NOTE: Detects whether the base model has drifted (changed) since the changeset was created.\n * Current implementation only identifies that drift occurred (isDrifted flag).\n * Element-level change tracking is not implemented; detailed diff detection would require\n * storing complete snapshot data, not just hashes.\n */\nexport interface DriftReport {\n isDrifted: boolean;\n baseSnapshotId: string;\n currentSnapshotId: string;\n warnings: string[];\n}\n\n/**\n * Result of commit operation\n */\nexport interface CommitResult {\n changeset: string;\n committed: number;\n skipped?: number;\n skippedDetails?: string[];\n failed: number;\n validation: {\n passed: boolean;\n errors: string[];\n };\n driftWarning?: DriftReport;\n}\n\n/**\n * StagingAreaManager provides changeset and staging operations\n */\nexport class StagingAreaManager {\n private storage: StagedChangesetStorage;\n private snapshotManager: BaseSnapshotManager;\n private validator: ChangesetValidator;\n private projectionEngine: VirtualProjectionEngine | null = null;\n private rootPath: string;\n private model: Model | null = null;\n\n constructor(rootPath: string, model?: Model) {\n this.rootPath = rootPath;\n this.model = model || null;\n this.storage = new StagedChangesetStorage(rootPath);\n this.snapshotManager = new BaseSnapshotManager();\n this.validator = new ChangesetValidator(rootPath);\n this.projectionEngine = null; // Lazy loaded on first call to getProjectionEngine()\n }\n\n /**\n * Validate changeset ID format and content\n * Ensures ID is safe and provides early validation before file operations\n *\n * Validation rules:\n * - Must be a non-empty string\n * - Cannot contain path separators (.., /, \\)\n * - Cannot contain special characters (<>:\"|?*)\n * - During storage, the ID will be normalized: lowercase, spaces\u2192hyphens, non-alphanumeric\u2192stripped\n *\n * @param changesetId - The changeset ID to validate\n * @throws Error if ID contains invalid characters or structure\n */\n private validateChangesetId(changesetId: string): void {\n if (!changesetId || typeof changesetId !== \"string\") {\n throw new Error(\"Changeset ID must be a non-empty string\");\n }\n\n if (changesetId.trim() === \"\") {\n throw new Error(\"Changeset ID cannot be empty or whitespace-only\");\n }\n\n // Check for path traversal attempts\n if (changesetId.includes(\"..\") || changesetId.includes(\"/\") || changesetId.includes(\"\\\\\")) {\n throw new Error(\"Changeset ID cannot contain path separators or traversal sequences (..)\");\n }\n\n // Check for special characters that could cause issues\n // Note: sanitizeId() will normalize the ID by converting to lowercase, replacing spaces with hyphens,\n // and removing all non-alphanumeric characters (except hyphens)\n if (/[<>:\"|?*]/.test(changesetId)) {\n throw new Error(\n 'Changeset ID contains invalid special characters (<>:\"|?*). ' +\n \"Only alphanumeric characters and hyphens are preserved. \" +\n \"Spaces are converted to hyphens, and other characters are removed during storage.\"\n );\n }\n }\n\n /**\n * Get or create projection engine instance (lazy initialization).\n * Deferred initialization avoids circular dependency issues at module load time.\n * @returns VirtualProjectionEngine instance for computing merged model views\n */\n private async getProjectionEngine(): Promise<VirtualProjectionEngine> {\n if (!this.projectionEngine) {\n const { VirtualProjectionEngine } = await import(\"./virtual-projection.js\");\n this.projectionEngine = new VirtualProjectionEngine(this.rootPath);\n }\n return this.projectionEngine;\n }\n\n /**\n * Create a new staged changeset\n */\n async create(name: string, description?: string): Promise<Changeset> {\n if (!this.model) {\n throw new Error(\"Model required for creating changesets\");\n }\n const id = this.generateChangesetId();\n const baseSnapshotId = await this.snapshotManager.captureSnapshot(this.model);\n\n const changeset = await this.storage.create(id, name, description, baseSnapshotId);\n changeset.id = id;\n\n // Mark as 'staged' so it can accept changes immediately\n changeset.markStaged();\n await this.storage.save(changeset);\n\n return changeset;\n }\n\n /**\n * Load a changeset by name or ID\n */\n async load(name: string): Promise<Changeset | null> {\n // Try loading directly as ID first\n let changeset = await this.storage.load(name);\n if (changeset) {\n return changeset;\n }\n\n // If not found by ID, search by name in all changesets\n const all = await this.storage.list();\n return all.find((cs) => cs.name === name) || null;\n }\n\n /**\n * List all staged changesets\n */\n async list(): Promise<Changeset[]> {\n return this.storage.list();\n }\n\n /**\n * Delete a changeset\n * If the changeset being deleted is the currently active changeset,\n * this method will also clear the active changeset marker.\n */\n async delete(name: string): Promise<void> {\n const changeset = await this.load(name);\n if (!changeset) {\n throw new Error(`Changeset '${name}' not found`);\n }\n\n if (!changeset.id) {\n throw new Error(`Changeset '${name}' has no ID and cannot be deleted`);\n }\n\n // Check if this changeset is the active one and clear it if so\n const activeChangeset = await this.getActive();\n if (activeChangeset && activeChangeset.id === changeset.id) {\n await this.clearActive();\n }\n\n await this.storage.delete(changeset.id);\n }\n\n /**\n * Stage a change in the changeset.\n * Records the change in the staging area without applying to the base model.\n * The changeset must be in 'staged' status to accept new changes.\n *\n * @param changesetId - ID of the changeset to stage the change in\n * @param change - The change to stage (type, elementId, layerName, before/after snapshots)\n * @throws Error if changeset ID is invalid, changeset not found, or not in 'staged' status\n */\n async stage(changesetId: string, change: Omit<StagedChange, \"sequenceNumber\">): Promise<void> {\n this.validateChangesetId(changesetId);\n\n const changeset = await this.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n // Only allow staging when status is 'staged' (not 'committed' or 'discarded')\n if (changeset.status !== \"staged\") {\n throw new Error(\n `Cannot stage changes on changeset with status '${changeset.status}'. ` +\n `Changeset must have status 'staged' to accept new changes.`\n );\n }\n\n const stagedChange: Omit<StagedChange, \"sequenceNumber\"> = {\n ...change,\n timestamp: change.timestamp || new Date().toISOString(),\n };\n\n await this.storage.addChange(changesetId, stagedChange);\n\n // Invalidate projection cache for this layer\n const engine = await this.getProjectionEngine();\n engine.invalidateOnStage(changesetId, change.layerName);\n }\n\n /**\n * Unstage a specific element from the changeset.\n * Removes all changes associated with the element.\n * The changeset must be in 'staged' status.\n *\n * @param changesetId - ID of the changeset to unstage from\n * @param elementId - ID of the element to remove all changes for\n * @throws Error if changeset ID is invalid, changeset not found, or not in 'staged' status\n */\n async unstage(changesetId: string, elementId: string): Promise<void> {\n this.validateChangesetId(changesetId);\n\n const changeset = await this.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n if (changeset.status !== \"staged\") {\n throw new Error(\n `Cannot unstage changes on changeset with status '${changeset.status}'. ` +\n `Changeset must have status 'staged'.`\n );\n }\n\n // Remove the element's changes and resequence remaining changes\n await this.storage.removeChange(changesetId, elementId);\n\n // Invalidate projection cache for the removed element's layers\n const engine = await this.getProjectionEngine();\n await engine.invalidateOnUnstage(changesetId, elementId);\n }\n\n /**\n * Discard all changes in the changeset.\n * Clears all staged changes and marks the changeset as 'discarded'.\n * This operation is irreversible for the changeset but does not affect the base model.\n *\n * @param changesetId - ID of the changeset to discard\n * @throws Error if changeset ID is invalid or changeset not found\n */\n async discard(changesetId: string): Promise<void> {\n this.validateChangesetId(changesetId);\n\n const changeset = await this.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n // Clear all staged changes and update status\n changeset.changes = [];\n changeset.status = \"discarded\";\n // Note: stats are auto-computed from changes array (will be 0/0/0 after clearing)\n changeset.updateModified();\n\n await this.storage.save(changeset);\n\n // Invalidate all projection cache entries for this changeset\n const engine = await this.getProjectionEngine();\n engine.invalidateOnDiscard(changesetId);\n }\n\n /**\n * Capture a base snapshot of the current model state for drift detection.\n * Stores a hash of the model that can be compared later to detect changes.\n *\n * @param changesetId - ID of the changeset to capture snapshot for\n * @returns SHA256 hash of the current model state\n * @throws Error if changeset not found or model not available\n */\n async captureBaseSnapshot(changesetId: string): Promise<string> {\n this.validateChangesetId(changesetId);\n\n if (!this.model) {\n throw new Error(\"Model required for capturing base snapshot\");\n }\n const changeset = await this.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n const snapshotId = await this.snapshotManager.captureSnapshot(this.model);\n changeset.baseSnapshot = snapshotId;\n changeset.updateModified();\n await this.storage.save(changeset);\n\n return snapshotId;\n }\n\n /**\n * Detect drift by comparing base snapshot hash to current model state.\n * Identifies whether the base model has changed since the changeset was created.\n *\n * @param changesetId - ID of the changeset to check for drift\n * @returns DriftReport with isDrifted flag, snapshot hashes, and warnings\n * @throws Error if changeset ID is invalid, changeset not found, or model not available\n *\n * @remarks\n * Drift detection uses hash-based comparison of the entire model snapshot.\n * Element-level change tracking is not implemented - only the isDrifted flag\n * indicates if the model has changed. Detailed diff detection would require\n * storing complete snapshot data, not just hashes.\n * Returns isDrifted=false and warnings if no base snapshot was captured.\n */\n async detectDrift(changesetId: string): Promise<DriftReport> {\n this.validateChangesetId(changesetId);\n\n if (!this.model) {\n throw new Error(\"Model required for drift detection\");\n }\n const changeset = await this.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n if (!changeset.baseSnapshot) {\n return {\n isDrifted: false,\n baseSnapshotId: \"unknown\",\n currentSnapshotId: \"unknown\",\n warnings: [\"No base snapshot available for drift detection\"],\n };\n }\n\n // Capture current model snapshot to compare against base\n const currentSnapshotId = await this.snapshotManager.captureSnapshot(this.model);\n\n // Compare snapshot hashes to detect drift\n // isDrifted=true indicates base model has changed since changeset creation\n const isDrifted = changeset.baseSnapshot !== currentSnapshotId;\n\n const warnings: string[] = [];\n\n if (isDrifted) {\n warnings.push(\n `Model has drifted since changeset creation (base snapshot hash differs from current)`\n );\n }\n\n return {\n isDrifted,\n baseSnapshotId: changeset.baseSnapshot,\n currentSnapshotId: currentSnapshotId,\n warnings,\n };\n }\n\n /**\n * Commit a changeset to the base model with atomic guarantee.\n * Applies all staged changes and persists them to the base model.\n *\n * Implements atomic commit with the following steps:\n * 1. Detect drift in base model (fail unless force=true)\n * 2. Validate projected model (schema, references, semantics)\n * 3. Create backup of current model state\n * 4. Apply all changes in sequence order\n * 5. Save all modified layers to disk\n * 6. Update changeset status and manifest history\n * 7. Cleanup backup on success, restore on failure\n *\n * @param model - The model to commit changes to\n * @param changesetId - ID of the changeset to commit\n * @param options - Commit options (force, validate, dryRun)\n * @returns CommitResult with count of committed/failed changes and validation status\n * @throws Error if validation fails, drift detected without --force, or commit fails\n *\n * @remarks\n * Validation errors always block commit (force flag does not override).\n * Drift warnings can be bypassed with --force flag.\n * Dry-run mode performs all checks but does not persist changes.\n * On commit failure, model state is restored from backup; rollback failure triggers CRITICAL error.\n */\n async commit(\n model: Model,\n changesetId: string,\n options: CommitOptions = {}\n ): Promise<CommitResult> {\n return startActiveSpan(\"staging.commit\", async (span) => {\n this.validateChangesetId(changesetId);\n\n const changeset = await this.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n (span as any).setAttribute(\"changeset.name\", changeset.name);\n (span as any).setAttribute(\"changeset.changeCount\", changeset.changes.length);\n\n const isDryRun = options.dryRun === true;\n const shouldValidate = options.validate !== false;\n\n // Step 1: Detect drift and throw if drifted without --force\n const driftSpan = isTelemetryEnabled ? startSpan(\"staging.detectDrift\") : null;\n const drift = await this.detectDrift(changesetId);\n if (isTelemetryEnabled && driftSpan) {\n (driftSpan as any).setAttribute(\"drift.detected\", drift.isDrifted);\n (driftSpan as any).setAttribute(\"drift.baseSnapshotId\", drift.baseSnapshotId);\n (driftSpan as any).setAttribute(\"drift.currentSnapshotId\", drift.currentSnapshotId);\n (driftSpan as any).setStatus({ code: 0 });\n }\n endSpan(driftSpan);\n\n if (drift.isDrifted) {\n (span as any).setAttribute(\"commit.driftDetected\", true);\n if (options.force) {\n emitLog(SeverityNumber.WARN, \"Drift detected but overridden with --force\", {\n \"drift.baseSnapshot\": drift.baseSnapshotId,\n \"drift.currentSnapshot\": drift.currentSnapshotId,\n });\n } else {\n emitLog(SeverityNumber.ERROR, \"Drift detected, commit blocked\", {\n \"drift.baseSnapshot\": drift.baseSnapshotId,\n \"drift.currentSnapshot\": drift.currentSnapshotId,\n });\n throw new Error(\n `Base model has drifted since changeset creation. ` + `Use --force to commit anyway.`\n );\n }\n }\n\n const result: CommitResult = {\n changeset: changeset.name,\n committed: 0,\n skipped: 0,\n skippedDetails: [],\n failed: 0,\n validation: {\n passed: true,\n errors: [],\n },\n };\n\n // Step 2: Validate against projected model (force cannot override validation)\n if (shouldValidate) {\n await startActiveSpan(\"staging.validate\", async (validationSpan) => {\n const validation = await this.validator.validateChangeset(model, changesetId);\n\n (validationSpan as any).setAttribute(\"validation.passed\", validation.isValid());\n (validationSpan as any).setAttribute(\"validation.errorCount\", validation.errors.length);\n (validationSpan as any).setStatus({ code: 0 });\n\n if (!validation.isValid()) {\n result.validation.passed = false;\n result.validation.errors = validation.errors.map((e) => e.message);\n\n emitLog(SeverityNumber.ERROR, \"Validation failed\", {\n \"validation.errorCount\": result.validation.errors.length,\n \"validation.errors\": result.validation.errors.slice(0, 10), // First 10 errors\n });\n\n throw new Error(\n `Validation failed with ${result.validation.errors.length} error(s):\\n` +\n result.validation.errors.map((e) => ` - ${e}`).join(\"\\n\") +\n `\\n\\nFix these validation errors before committing.`\n );\n }\n });\n }\n\n // Step 3: Dry run early exit (before making any changes)\n if (isDryRun) {\n result.committed = changeset.changes.length;\n if (drift.isDrifted) {\n result.driftWarning = drift;\n }\n return result;\n }\n\n // Step 4: Backup model state for atomic rollback\n let backup: string;\n try {\n backup = await this.backupModel(model);\n } catch (backupError) {\n // Backup creation failed - cannot proceed safely\n throw new Error(\n `Failed to create backup before commit. No changes have been applied. ` +\n `${backupError instanceof Error ? backupError.message : String(backupError)}`\n );\n }\n\n // Step 5: Apply all changes sequentially by sequence number\n const sortedChanges = [...(changeset.changes as StagedChange[])].sort(\n (a, b) => a.sequenceNumber - b.sequenceNumber\n );\n\n try {\n const applySpan = isTelemetryEnabled\n ? startSpan(\"staging.apply\", {\n \"apply.changeCount\": changeset.changes.length,\n })\n : null;\n\n try {\n for (const change of sortedChanges) {\n try {\n // Use getBaseLayer() so commit operates on the actual mutable base layer,\n // not a projected clone. getLayer() would return a projected (read-only clone)\n // which cannot be persisted via saveLayer().\n let layer = await model.getBaseLayer(change.layerName);\n if (!layer) {\n // Layer directory doesn't exist yet \u2014 this changeset introduces\n // the first elements for this layer. Create an empty layer and\n // register it so saveLayer() will create the directory on disk.\n layer = new Layer(change.layerName);\n model.addLayer(layer);\n }\n\n if (change.type === \"relationship-add\" && change.after) {\n // Relationship-add: the relationship was already injected into model.relationships\n // during Model.load() for transparent read-path visibility. Here we just need\n // saveRelationships() to persist it. Skip adding if it already exists.\n const rel = change.after as {\n source: string;\n target: string;\n predicate: string;\n layer: string;\n targetLayer?: string;\n category?: \"structural\" | \"behavioral\";\n properties?: Record<string, unknown>;\n };\n const existing = model.relationships.find(rel.source, rel.target, rel.predicate);\n if (existing.length === 0) {\n model.relationships.add({\n source: rel.source,\n target: rel.target,\n predicate: rel.predicate,\n layer: rel.layer,\n ...(rel.targetLayer ? { targetLayer: rel.targetLayer } : {}),\n category: rel.category ?? \"structural\",\n ...(rel.properties ? { properties: rel.properties } : {}),\n });\n }\n result.committed++;\n continue;\n }\n\n if (change.type === \"relationship-delete\" && change.before) {\n // Relationship-delete: the relationship was already removed from model.relationships\n // during Model.load(). Just ensure it's gone and count it.\n const rel = change.before as {\n source: string;\n target: string;\n predicate?: string;\n };\n model.relationships.delete(rel.source, rel.target, rel.predicate);\n result.committed++;\n continue;\n }\n\n if (change.type === \"add\" && change.after) {\n const { Element } = await import(\"./element.js\");\n const elementData = change.after as Record<string, unknown>;\n const elementId = elementData.id as string;\n\n // Check if element already exists using the UUID from elementData\n const elements = layer.listElements();\n const elementExists = elements.some((e) => e.id === elementId);\n\n if (!elementExists) {\n // Pass the entire elementData to Element constructor - it already has all fields from toJSON()\n const element = new Element(elementData);\n layer.addElement(element);\n result.committed++;\n } else {\n result.skipped = (result.skipped ?? 0) + 1;\n result.skippedDetails = result.skippedDetails ?? [];\n result.skippedDetails.push(\n `'${elementId}' in '${change.layerName}' already exists \u2014 deduplicated`\n );\n emitLog(SeverityNumber.WARN, \"Element deduplicated during commit\", {\n \"change.elementId\": change.elementId,\n \"change.layer\": change.layerName,\n });\n }\n } else if (change.type === \"update\" && change.after) {\n const element = layer.getElement(change.elementId);\n if (element) {\n const elementData = change.after as Record<string, unknown>;\n Object.assign(element, elementData);\n // Persist the mutation back to the underlying graph node.\n // getElement() returns a new wrapper each time, so Object.assign\n // alone is discarded; updateElement() writes through to the graph.\n layer.updateElement(element);\n result.committed++;\n } else {\n result.failed++;\n result.validation.passed = false;\n result.validation.errors.push(\n `Cannot update '${change.elementId}' in '${change.layerName}': element not found in base model`\n );\n }\n } else if (change.type === \"delete\") {\n const element = layer.getElement(change.elementId);\n if (element) {\n layer.deleteElement(element.path || element.id);\n result.committed++;\n } else {\n // Element was already removed \u2014 count as skipped, not failed\n result.skipped = (result.skipped ?? 0) + 1;\n result.skippedDetails = result.skippedDetails ?? [];\n result.skippedDetails.push(\n `'${change.elementId}' in '${change.layerName}' not found \u2014 already deleted`\n );\n }\n } else {\n // Handles: unknown change types, and known types with missing payloads\n // (e.g. \"add\" with no change.after, \"relationship-add\" with no change.after).\n result.failed++;\n result.validation.passed = false;\n result.validation.errors.push(\n `Cannot apply change '${change.type}' for '${change.elementId}': missing payload or unrecognised type`\n );\n }\n } catch (error) {\n result.failed++;\n result.validation.passed = false;\n result.validation.errors.push(\n `Failed to apply change to ${change.elementId}: ${getErrorMessage(error)}`\n );\n\n emitLog(SeverityNumber.ERROR, \"Failed to apply change\", {\n \"change.elementId\": change.elementId,\n \"change.type\": change.type,\n \"change.layer\": change.layerName,\n \"error.message\": getErrorMessage(error),\n });\n\n throw new Error(\n `Atomic commit failed at change ${sortedChanges.indexOf(change) + 1}/${sortedChanges.length}: ` +\n `${getErrorMessage(error)}`\n );\n }\n }\n\n if (isTelemetryEnabled && applySpan) {\n (applySpan as any).setAttribute(\"apply.committed\", result.committed);\n (applySpan as any).setAttribute(\"apply.failed\", result.failed);\n (applySpan as any).setStatus({ code: 0 });\n }\n } finally {\n endSpan(applySpan);\n }\n\n // Step 6: Save all modified layers and relationships atomically\n const modifiedLayers = new Set<string>();\n let hasRelationshipChanges = false;\n for (const change of sortedChanges) {\n if (change.type === \"relationship-add\" || change.type === \"relationship-delete\") {\n hasRelationshipChanges = true;\n } else {\n modifiedLayers.add(change.layerName);\n }\n }\n\n for (const layerName of modifiedLayers) {\n try {\n await model.saveLayer(layerName);\n } catch (error) {\n throw new Error(\n `Failed to save layer '${layerName}': ${getErrorMessage(error)}`\n );\n }\n }\n\n // Persist staged relationship changes to relationships.yaml\n if (hasRelationshipChanges) {\n try {\n await model.saveRelationships();\n } catch (error) {\n throw new Error(\n `Failed to save relationships after commit: ${getErrorMessage(error)}`\n );\n }\n }\n\n // Step 7: Update changeset status to committed ONLY AFTER successful saves\n changeset.status = \"committed\";\n changeset.updateModified();\n\n try {\n await this.storage.save(changeset);\n } catch (error) {\n // Changeset status is critical metadata - must be persisted with model changes\n throw new Error(\n `Failed to save changeset status after commit: ${getErrorMessage(error)}. ` +\n `This is a critical error that requires rollback to maintain consistency between model state and changeset metadata.`\n );\n }\n\n // Step 8: Add changeset to manifest history\n if (!model.manifest.changeset_history) {\n model.manifest.changeset_history = [];\n }\n model.manifest.changeset_history.push({\n name: changeset.name,\n committed_at: new Date().toISOString(),\n action: \"committed\",\n });\n\n // Step 9: Save manifest with updated changeset history\n try {\n await model.saveManifest();\n } catch (manifestError) {\n // Manifest save failed - this is CRITICAL and will trigger rollback\n // Don't log here - let the rollback handler provide comprehensive error output\n throw new Error(\n `Failed to save manifest with changeset history: ${manifestError instanceof Error ? manifestError.message : String(manifestError)}. ` +\n `Model will be rolled back to maintain consistency.`\n );\n }\n\n // Step 9.5: Regenerate reports for all layers modified by this changeset\n try {\n const orchestrator = new ModelReportOrchestrator(model, model.rootPath);\n const affectedLayers = new Set<string>();\n for (const change of sortedChanges) {\n if (isValidLayerName(change.layerName)) {\n for (const layer of orchestrator.computeAffectedLayers(change.layerName)) {\n affectedLayers.add(layer);\n }\n }\n }\n await orchestrator.regenerate(affectedLayers);\n } catch (error) {\n emitLog(\n SeverityNumber.WARN,\n \"Failed to regenerate layer reports after changeset commit\",\n {\n \"changeset.id\": changesetId,\n \"changeset.name\": changeset.name,\n \"changeset.changeCount\": sortedChanges.length,\n \"error.message\": getErrorMessage(error),\n }\n );\n }\n\n // Step 10: Clean up backup directory after successful commit\n // Note: cleanupBackup handles its own errors and logs them with ERROR severity\n await this.cleanupBackup(backup);\n\n (span as any).setAttribute(\"commit.success\", true);\n (span as any).setAttribute(\"commit.committed\", result.committed);\n if (drift.isDrifted) {\n (span as any).setAttribute(\"commit.driftOverridden\", true);\n }\n (span as any).setStatus({ code: 0 });\n\n return result;\n } catch (commitError) {\n // Attempt rollback on any failure - restore from backup\n let rollbackSucceeded = false;\n let rollbackError: Error | null = null;\n\n const rollbackSpan = isTelemetryEnabled ? startSpan(\"staging.rollback\") : null;\n\n try {\n emitLog(SeverityNumber.WARN, \"Commit failed, attempting rollback\", {\n \"commit.error\":\n commitError instanceof Error ? commitError.message : String(commitError),\n });\n\n await this.restoreModel(model, backup);\n rollbackSucceeded = true;\n\n if (isTelemetryEnabled && rollbackSpan) {\n (rollbackSpan as any).setAttribute(\"rollback.success\", true);\n (rollbackSpan as any).setStatus({ code: 0 });\n }\n\n emitLog(SeverityNumber.INFO, \"Rollback succeeded, model restored\", {\n \"backup.path\": backup,\n });\n } catch (err) {\n rollbackError = err instanceof Error ? err : new Error(String(err));\n\n if (isTelemetryEnabled && rollbackSpan) {\n (rollbackSpan as any).setAttribute(\"rollback.success\", false);\n (rollbackSpan as any).recordException(rollbackError);\n (rollbackSpan as any).setStatus({ code: 2, message: rollbackError.message });\n }\n\n emitLog(SeverityNumber.ERROR, \"Rollback failed, model may be corrupted\", {\n \"rollback.error\": rollbackError.message,\n \"backup.path\": backup,\n });\n } finally {\n endSpan(rollbackSpan);\n }\n\n // CRITICAL: Validate backup integrity BEFORE suggesting manual restoration\n let backupHealth: { isValid: boolean; filesChecked: number; errors: string[] };\n let backupValidationError: string | null = null;\n let validationProcessFailed = false;\n\n try {\n backupHealth = await this.validateBackupIntegrity(backup);\n } catch (validationErr) {\n validationProcessFailed = true;\n backupValidationError =\n validationErr instanceof Error ? validationErr.message : String(validationErr);\n backupHealth = {\n isValid: false,\n filesChecked: 0,\n errors: [\n `Validation process failed: ${backupValidationError}`,\n `This indicates a critical issue with the backup infrastructure, not the backup content.`,\n `Contact support before attempting manual restoration.`,\n ],\n };\n }\n\n // Build comprehensive error message\n let compositeMessage: string;\n const suggestions: string[] = [];\n\n if (rollbackSucceeded) {\n // Rollback succeeded - throw original commit error\n compositeMessage = `Commit failed but rollback succeeded. Model restored to pre-commit state.\\n\\n`;\n compositeMessage += `Error during commit:\\n`;\n compositeMessage += ` ${commitError instanceof Error ? commitError.message : String(commitError)}\\n\\n`;\n compositeMessage += `Backup location (for reference): ${backup}`;\n\n // Clean up backup after successful rollback\n // Note: cleanupBackup handles its own errors and logs them with ERROR severity\n await this.cleanupBackup(backup);\n\n (span as any).setAttribute(\"commit.success\", false);\n (span as any).setAttribute(\"commit.rollbackSuccess\", true);\n (span as any).recordException(commitError as Error);\n (span as any).setStatus({\n code: 2,\n message: commitError instanceof Error ? commitError.message : String(commitError),\n });\n\n throw new Error(compositeMessage);\n } else {\n // Rollback failed - model may be corrupted\n compositeMessage =\n `Commit failed AND rollback failed. Model may be in corrupted state.\\n` +\n `\\n` +\n `Original commit error:\\n` +\n ` ${commitError instanceof Error ? commitError.message : String(commitError)}\\n` +\n `\\n` +\n `Rollback error:\\n` +\n ` ${rollbackError ? rollbackError.message : \"Unknown error\"}\\n` +\n `\\n` +\n `Backup location: ${backup}\\n`;\n\n // Add backup health status (always show header for consistency with tests)\n compositeMessage += `Backup integrity check:\\n`;\n if (validationProcessFailed) {\n compositeMessage += ` \u2717 Backup integrity issues found:\\n`;\n compositeMessage += ` - Validation process failed: ${backupValidationError}\\n`;\n compositeMessage += `\\n\u26A0 CRITICAL: Backup validation process crashed.\\n`;\n compositeMessage += `This indicates a system-level issue (filesystem, memory, permissions).\\n`;\n compositeMessage += `DO NOT attempt manual restoration until this is resolved.\\n`;\n } else if (backupHealth.isValid) {\n compositeMessage += ` \u2713 Backup is valid (${backupHealth.filesChecked} files checked)\\n`;\n } else {\n compositeMessage += ` \u2717 Backup integrity issues found:\\n`;\n for (const error of backupHealth.errors) {\n compositeMessage += ` - ${error}\\n`;\n }\n }\n\n compositeMessage += `\\n`;\n\n if (backupHealth.isValid) {\n suggestions.push(\n `Backup is valid. Manually restore from backup: ${backup}`,\n `Copy manifest.json/manifest.yaml from backup to ${path.join(model.rootPath, \"documentation-robotics\")}`,\n `Copy layer files from ${path.join(backup, \"layers\")} to ${path.join(model.rootPath, \"documentation-robotics\", \"layers\")}`,\n `Run 'dr validate' after manual restoration`,\n `Contact support if issue persists`\n );\n } else {\n suggestions.push(\n `\u26A0 Backup integrity is compromised. Do NOT use this backup for recovery.`,\n `Check if backup files exist at: ${backup}`,\n `Verify disk space and file permissions`,\n `Contact support immediately - manual data recovery may be required`,\n `Do not attempt manual restoration with a corrupted backup`\n );\n }\n\n compositeMessage += `Manual recovery may be required.`;\n\n (span as any).setAttribute(\"commit.success\", false);\n (span as any).setAttribute(\"commit.rollbackSuccess\", false);\n (span as any).recordException(commitError as Error);\n (span as any).setStatus({ code: 2, message: \"Commit and rollback both failed\" });\n\n const { CLIError } = await import(\"../utils/errors.js\");\n throw new CLIError(compositeMessage, 1, suggestions);\n }\n }\n }, {\n \"changeset.id\": changesetId,\n \"commit.validate\": options.validate !== false,\n \"commit.force\": options.force === true,\n \"commit.dryRun\": options.dryRun === true,\n });\n }\n\n /**\n * Check if a changeset is currently active.\n * Active changesets intercept mutations to stage changes instead of applying to base model.\n *\n * @param changesetId - ID of the changeset to check\n * @returns true if changeset is marked as active, false otherwise\n * @throws Error if changeset ID is invalid\n */\n async isActive(changesetId: string): Promise<boolean> {\n this.validateChangesetId(changesetId);\n\n const activePath = path.join(this.rootPath, \"documentation-robotics\", \"changesets\", \".active\");\n if (!(await fileExists(activePath))) {\n return false;\n }\n\n const { readFile } = await import(\"../utils/file-io.js\");\n const activeId = await readFile(activePath);\n return activeId.trim() === changesetId;\n }\n\n /**\n * Activate a changeset to intercept model mutations.\n * Marks the changeset as active and transitions status to 'staged'.\n * Only one changeset can be active at a time.\n *\n * @param changesetId - ID of the changeset to activate\n * @throws Error if changeset ID is invalid or changeset not found\n *\n * @remarks\n * When a changeset is active, add/update/delete commands will stage changes\n * instead of applying them directly to the base model.\n */\n async setActive(changesetId: string): Promise<void> {\n this.validateChangesetId(changesetId);\n\n const changeset = await this.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n // Transition to 'staged' status to enable interception\n if (changeset.status !== \"staged\") {\n changeset.markStaged();\n await this.storage.save(changeset);\n }\n\n const { writeFile, ensureDir } = await import(\"../utils/file-io.js\");\n const changesetDir = path.join(this.rootPath, \"documentation-robotics\", \"changesets\");\n await ensureDir(changesetDir);\n\n const activePath = path.join(changesetDir, \".active\");\n // Write the actual changeset ID (not the user-provided name)\n if (!changeset.id) {\n throw new Error(`Changeset has no ID, cannot activate`);\n }\n await writeFile(activePath, changeset.id);\n }\n\n /**\n * Clear the active changeset marker.\n * Allows a new changeset to be activated or model mutations to proceed normally.\n */\n async clearActive(): Promise<void> {\n const { writeFile } = await import(\"../utils/file-io.js\");\n const activePath = path.join(this.rootPath, \"documentation-robotics\", \"changesets\", \".active\");\n\n if (await fileExists(activePath)) {\n await writeFile(activePath, \"\");\n }\n }\n\n /**\n * Get the currently active changeset.\n * Returns null if no changeset is active.\n *\n * @returns The active changeset or null if none is active\n */\n async getActive(): Promise<Changeset | null> {\n const activePath = path.join(this.rootPath, \"documentation-robotics\", \"changesets\", \".active\");\n\n if (!(await fileExists(activePath))) {\n return null;\n }\n\n const { readFile } = await import(\"../utils/file-io.js\");\n const activeId = await readFile(activePath);\n const id = activeId.trim();\n\n if (!id) {\n return null;\n }\n\n return this.storage.load(id);\n }\n\n /**\n * Get the ID of the active changeset without loading the full changeset object.\n * Useful for status display and quick active changeset checks.\n *\n * @returns The active changeset ID or null if no active changeset exists\n */\n async getActiveId(): Promise<string | null> {\n const activePath = path.join(this.rootPath, \"documentation-robotics\", \"changesets\", \".active\");\n\n if (!(await fileExists(activePath))) {\n return null;\n }\n\n const { readFile } = await import(\"../utils/file-io.js\");\n const activeId = await readFile(activePath);\n const id = activeId.trim();\n\n return id || null;\n }\n\n /**\n * Apply a changeset to the base model.\n * Alias for commit() to match legacy command terminology.\n *\n * @param model - The model to apply changes to\n * @param changesetId - ID of the changeset to apply\n * @param options - Commit options (force, validate, dryRun)\n * @returns CommitResult with count of applied changes and validation status\n * @throws Error if validation fails, drift detected without --force, or apply fails\n *\n * @remarks\n * This method provides backward compatibility with changeset commands that use\n * \"apply\" instead of \"commit\" terminology. All logic is delegated to commit().\n */\n async apply(\n model: Model,\n changesetId: string,\n options: CommitOptions = {}\n ): Promise<CommitResult> {\n return this.commit(model, changesetId, options);\n }\n\n /**\n * Revert a changeset without applying changes.\n * Alias for discard() to match legacy command terminology.\n *\n * @param changesetId - ID of the changeset to revert\n * @throws Error if changeset ID is invalid or changeset not found\n *\n * @remarks\n * This method provides backward compatibility with changeset commands that use\n * \"revert\" instead of \"discard\" terminology. All logic is delegated to discard().\n */\n async revert(changesetId: string): Promise<void> {\n return this.discard(changesetId);\n }\n\n /**\n * Backup the current model state for atomic rollback on commit failure.\n * Creates a temporary backup directory with copies of all layer files and manifest.\n * Validates backup completeness and calculates checksums for integrity verification.\n *\n * @param model - The model to backup\n * @returns Path to the backup directory\n * @throws Error if backup creation or validation fails\n *\n * @remarks\n * Backup includes:\n * - manifest.yaml or manifest.json\n * - All layer YAML files\n * - Backup manifest with checksums and metadata\n * Backup is removed after successful commit; retained if commit fails for recovery.\n */\n private async backupModel(model: Model): Promise<string> {\n const backupDir = path.join(\n this.rootPath,\n \"documentation-robotics\",\n \".backups\",\n `backup-${Date.now()}`\n );\n\n const { ensureDir, readFile, atomicWrite } = await import(\"../utils/file-io.js\");\n const { createHash } = await import(\"crypto\");\n\n await ensureDir(backupDir);\n\n // Track what we're backing up for validation\n // Use mutable array during construction, then make readonly when creating BackupManifest\n const backupFiles: Array<{\n path: string;\n checksum: ReturnType<typeof createSha256Hash>;\n size: number;\n }> = [];\n const timestamp = new Date().toISOString();\n\n try {\n // Backup manifest - try both .yaml and .json formats\n let manifestPath = path.join(\n model.rootPath,\n \"documentation-robotics\",\n \"model\",\n \"manifest.yaml\"\n );\n if (!(await fileExists(manifestPath))) {\n manifestPath = path.join(model.rootPath, \"documentation-robotics\", \"manifest.json\");\n }\n if (!(await fileExists(manifestPath))) {\n throw new Error(\"Manifest file not found in expected locations\");\n }\n\n const manifestContent = await readFile(manifestPath);\n const manifestChecksum = createSha256Hash(\n createHash(\"sha256\").update(manifestContent).digest(\"hex\")\n );\n const manifestFilename = path.basename(manifestPath);\n\n await atomicWrite(path.join(backupDir, manifestFilename), manifestContent);\n backupFiles.push({\n path: manifestFilename,\n checksum: manifestChecksum,\n size: manifestContent.length,\n });\n\n // Backup all layers - handle multiple YAML files per layer\n const layersDir = path.join(backupDir, \"layers\");\n await ensureDir(layersDir);\n\n const fs = await import(\"fs/promises\");\n\n for (const layer of model.layers.values()) {\n const layerBackupDir = path.join(layersDir, layer.name);\n await ensureDir(layerBackupDir);\n\n // Find the layer directory in the model\n const layerDirPath = await this.findLayerDirectory(model.rootPath, layer.name);\n if (!layerDirPath) {\n continue;\n }\n\n // Backup all YAML files in the layer directory\n const files = await fs.readdir(layerDirPath);\n for (const file of files) {\n if (file.endsWith(\".yaml\") || file.endsWith(\".yml\")) {\n const filePath = path.join(layerDirPath, file);\n const fileContent = await readFile(filePath);\n const fileChecksum = createSha256Hash(\n createHash(\"sha256\").update(fileContent).digest(\"hex\")\n );\n\n await atomicWrite(path.join(layerBackupDir, file), fileContent);\n backupFiles.push({\n path: `layers/${layer.name}/${file}`,\n checksum: fileChecksum,\n size: fileContent.length,\n });\n }\n }\n }\n\n // Create the final immutable BackupManifest\n const backupManifest: BackupManifest = {\n files: backupFiles as ReadonlyArray<(typeof backupFiles)[0]>,\n timestamp,\n };\n\n // Write backup manifest for validation\n await atomicWrite(\n path.join(backupDir, \".backup-manifest.json\"),\n JSON.stringify(backupManifest, null, 2)\n );\n\n // Validate backup completeness\n await this.validateBackup(backupDir, backupManifest);\n\n return backupDir;\n } catch (error) {\n // Clean up incomplete backup with detailed error handling\n const cleanupError = await this.forceRemoveBackupDir(backupDir);\n\n if (cleanupError) {\n // This is CRITICAL - orphaned backups accumulate\n throw new Error(\n `Backup creation failed AND cleanup failed.\\n` +\n `Original error: ${getErrorMessage(error)}\\n` +\n `Cleanup error: ${cleanupError}\\n\\n` +\n `Orphaned backup directory: ${backupDir}\\n` +\n `This directory will accumulate and consume disk space.\\n\\n` +\n `REQUIRED ACTIONS:\\n` +\n ` 1. Manually remove: rm -rf \"${backupDir}\"\\n` +\n ` 2. Check disk space: df -h\\n` +\n ` 3. Check permissions: ls -la \"${path.dirname(backupDir)}\"\\n\\n` +\n `Cannot proceed with commit until this is resolved.`\n );\n }\n\n throw new Error(\n `Failed to create backup: ${getErrorMessage(error)}`\n );\n }\n }\n\n /**\n * Validate backup completeness and file integrity.\n * Verifies that all files in the manifest exist in the backup directory\n * with matching checksums and sizes.\n *\n * @param backupDir - Path to the backup directory\n * @param manifest - Backup manifest with file list and checksums\n * @throws Error if any file is missing, checksum mismatches, or size differs\n */\n private async validateBackup(backupDir: string, manifest: BackupManifest): Promise<void> {\n const { readFile, fileExists } = await import(\"../utils/file-io.js\");\n const { createHash } = await import(\"crypto\");\n\n for (const file of manifest.files) {\n const backupFilePath = path.join(backupDir, file.path);\n\n // Verify file exists\n if (!(await fileExists(backupFilePath))) {\n throw new Error(`Backup validation failed: ${file.path} not found in backup`);\n }\n\n // Verify checksum matches\n const content = await readFile(backupFilePath);\n const checksum = createHash(\"sha256\").update(content).digest(\"hex\");\n\n if (checksum !== file.checksum) {\n throw new Error(\n `Backup validation failed: ${file.path} checksum mismatch (expected ${file.checksum}, got ${checksum})`\n );\n }\n\n // Verify size matches\n if (content.length !== file.size) {\n throw new Error(\n `Backup validation failed: ${file.path} size mismatch (expected ${file.size}, got ${content.length})`\n );\n }\n }\n }\n\n /**\n * Validate backup integrity by checking manifest and file checksums.\n * This is called before suggesting manual restoration to ensure the backup is usable.\n * @param backupDir - Path to the backup directory\n * @returns Promise resolving to backup health status or throwing error with details\n * @throws Error with details about any integrity issues found\n */\n async validateBackupIntegrity(backupDir: string): Promise<{\n isValid: boolean;\n filesChecked: number;\n errors: string[];\n }> {\n const { readFile, fileExists } = await import(\"../utils/file-io.js\");\n const { createHash } = await import(\"crypto\");\n\n const errors: string[] = [];\n let filesChecked = 0;\n\n try {\n // Check if backup directory exists\n if (!(await fileExists(backupDir))) {\n throw new Error(`Backup directory not found: ${backupDir}`);\n }\n\n // Load and validate backup manifest\n const manifestPath = path.join(backupDir, \".backup-manifest.json\");\n if (!(await fileExists(manifestPath))) {\n throw new Error(`Backup manifest not found at ${manifestPath}`);\n }\n\n // Read manifest content first (can throw for parse errors)\n let rawManifest;\n try {\n const manifestContent = await readFile(manifestPath);\n if (!manifestContent || manifestContent.length === 0) {\n throw new Error(\"Backup manifest is empty\");\n }\n rawManifest = JSON.parse(manifestContent.toString());\n } catch (err) {\n // JSON parse errors should throw - these are critical errors\n throw new Error(\n `Failed to read backup manifest: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n\n // Validate manifest structure (can throw for missing required arrays)\n if (!rawManifest || typeof rawManifest !== \"object\") {\n throw new Error(\"Backup manifest must be an object\");\n }\n\n if (!rawManifest.files || !Array.isArray(rawManifest.files)) {\n throw new Error(\"Backup manifest must have a files array\");\n }\n\n // Validate each file entry in the manifest and collect errors gracefully\n for (let index = 0; index < rawManifest.files.length; index++) {\n const file = rawManifest.files[index];\n filesChecked++;\n\n // Validate file entry structure\n if (!file || typeof file !== \"object\") {\n errors.push(`Backup manifest file entry ${index} must be an object`);\n continue;\n }\n\n if (typeof file.path !== \"string\" || !file.path) {\n errors.push(`Backup manifest file entry ${index} must have a non-empty path string`);\n continue;\n }\n\n // Validate required metadata fields - these are validation errors, not critical\n if (typeof file.checksum !== \"string\") {\n errors.push(`Backup manifest file entry ${index} must have a checksum string`);\n continue;\n }\n\n if (typeof file.size !== \"number\" || file.size < 0) {\n errors.push(`Backup manifest file entry ${index} must have a non-negative size number`);\n continue;\n }\n\n // File validation: check file exists and integrity\n const backupFilePath = path.join(backupDir, file.path);\n\n // Check file exists\n if (!(await fileExists(backupFilePath))) {\n errors.push(`Missing: ${file.path}`);\n continue;\n }\n\n // Check checksum\n try {\n const content = await readFile(backupFilePath);\n const checksum = createHash(\"sha256\").update(content).digest(\"hex\");\n\n if (checksum !== file.checksum) {\n errors.push(\n `Checksum mismatch: ${file.path} (expected ${file.checksum}, got ${checksum})`\n );\n continue;\n }\n\n // Check size\n if (content.length !== file.size) {\n errors.push(\n `Size mismatch: ${file.path} (expected ${file.size}, got ${content.length})`\n );\n }\n } catch (err) {\n errors.push(\n `Failed to validate: ${file.path} (${err instanceof Error ? err.message : String(err)})`\n );\n }\n }\n\n return {\n isValid: errors.length === 0,\n filesChecked,\n errors,\n };\n } catch (err) {\n throw new Error(\n `Backup integrity check failed: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n /**\n * Clean up a backup directory after successful commit.\n * Removes the backup directory and all its contents to prevent disk space accumulation.\n *\n * @param backupDir - Path to the backup directory to remove\n *\n * @remarks\n * Failures during cleanup are logged with ERROR severity but do not throw.\n * Successful commits should not fail due to cleanup errors, but users need to\n * know about cleanup failures to avoid disk space accumulation.\n * Includes actionable guidance for common failure scenarios.\n */\n private async cleanupBackup(backupDir: string): Promise<void> {\n try {\n const { rm } = await import(\"node:fs/promises\");\n await rm(backupDir, { recursive: true, force: true });\n } catch (error) {\n // Log cleanup failures with ERROR severity - these mask real problems\n const errorMsg = getErrorMessage(error);\n const errorCode = (error as NodeJS.ErrnoException)?.code;\n\n let suggestions = \"\";\n\n // Handle ENOENT specially - backup already deleted is not an error\n if (errorCode === \"ENOENT\") {\n // Backup was already deleted - directory removal goal is achieved\n return;\n }\n\n // ENOSPC is CRITICAL - orphaned backups will accumulate and fill disk\n if (errorCode === \"ENOSPC\") {\n throw new Error(\n `Backup cleanup failed due to disk full.\\n` +\n `Original error: ${errorMsg}\\n\\n` +\n `Orphaned backup directory: ${backupDir}\\n` +\n `This directory will accumulate and consume disk space.\\n\\n` +\n `REQUIRED ACTIONS:\\n` +\n ` 1. Manually remove: rm -rf \"${backupDir}\"\\n` +\n ` 2. Free up disk space: df -h\\n` +\n ` 3. Check disk usage: du -sh ${path.dirname(backupDir)}/*\\n\\n` +\n `Cannot proceed with future commits until disk space is freed.`\n );\n }\n\n // Provide specific guidance based on error type\n if (errorCode === \"EACCES\") {\n suggestions =\n \"\\n\\n[PERMISSION ERROR] Cannot delete backup directory.\\n\" +\n `Directory: ${backupDir}\\n` +\n \"Check file permissions or try manual cleanup:\\n\" +\n `sudo rm -rf \"${backupDir}\"`;\n } else {\n suggestions =\n \"\\n\\n[ACTION REQUIRED] Backup cleanup failed due to file system error.\\n\" +\n `Directory: ${backupDir}\\n` +\n `Manual cleanup: rm -rf \"${backupDir}\"\\n` +\n \"Note: Backup directories accumulate over time and can fill disk space.\";\n }\n\n console.error(\n `[ERROR] Failed to clean up backup directory after commit: ${errorMsg}` + suggestions\n );\n }\n }\n\n /**\n * Force remove a backup directory with enhanced error handling.\n * Attempts multiple strategies to ensure removal even with permission issues.\n *\n * @param backupDir - Path to the backup directory to remove\n * @returns Error message if removal failed, null if successful\n *\n * @remarks\n * This is used during backup creation failures to ensure cleanup of partial state.\n * Returns error details instead of throwing so caller can log and continue.\n */\n private async forceRemoveBackupDir(backupDir: string): Promise<string | null> {\n const fs = await import(\"fs/promises\");\n\n try {\n // Try standard removal first\n await fs.rm(backupDir, { recursive: true, force: true });\n return null;\n } catch (error) {\n // If standard removal fails, try more aggressive approach\n try {\n // Accumulate removal failures for detailed error reporting\n const removalFailures: Array<{ path: string; error: string }> = [];\n\n // List directory contents and try to remove each item\n const entries = await fs.readdir(backupDir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = path.join(backupDir, entry.name);\n try {\n if (entry.isDirectory()) {\n await fs.rm(fullPath, { recursive: true, force: true });\n } else {\n await fs.unlink(fullPath);\n }\n } catch (itemError) {\n removalFailures.push({\n path: fullPath,\n error: itemError instanceof Error ? itemError.message : String(itemError),\n });\n }\n }\n\n // Try to remove the directory itself\n try {\n await fs.rmdir(backupDir);\n return null;\n } catch {\n // Directory may not be empty due to removal failures\n if (removalFailures.length > 0) {\n const failureDetails = removalFailures\n .map((f) => ` - ${f.path}: ${f.error}`)\n .join(\"\\n\");\n\n return (\n `Could not remove ${removalFailures.length} items from backup directory:\\n` +\n failureDetails +\n `\\n\\nManual cleanup required: rm -rf \"${backupDir}\"`\n );\n }\n\n return `Could not remove directory ${backupDir} - may contain leftover files`;\n }\n } catch (aggressiveError) {\n return `Backup cleanup failed: ${aggressiveError instanceof Error ? aggressiveError.message : String(aggressiveError)}`;\n }\n }\n }\n\n /**\n * Restore model state from backup after failed commit.\n * Restores manifest first (critical), then all layer files with defensive error handling.\n * Provides detailed reporting of restoration progress and failures.\n *\n * @param model - The model to restore\n * @param backupDir - Path to the backup directory\n * @throws Error with detailed restoration summary if restoration fails\n *\n * @remarks\n * Restoration strategy:\n * 1. Restore manifest first (critical for model integrity)\n * 2. Restore all layer files with per-file error handling\n * 3. Continue restoring remaining files even if some fail\n * 4. Throw detailed error if critical files fail to restore\n * Both manifest and layer restoration failures are critical and will throw errors.\n */\n private async restoreModel(model: Model, backupDir: string): Promise<void> {\n const { readFile, fileExists, atomicWrite } = await import(\"../utils/file-io.js\");\n\n const restoredFiles: string[] = [];\n const failedFiles: Array<{ path: string; error: string; isCritical: boolean }> = [];\n const criticalLayers = new Set<string>(); // Track critical layer failures\n\n try {\n // Restore manifest first (most critical) - try both .yaml and .json\n let backupManifestFile = path.join(backupDir, \"manifest.yaml\");\n let manifestDestPath = path.join(\n model.rootPath,\n \"documentation-robotics\",\n \"model\",\n \"manifest.yaml\"\n );\n\n if (!(await fileExists(backupManifestFile))) {\n backupManifestFile = path.join(backupDir, \"manifest.json\");\n manifestDestPath = path.join(model.rootPath, \"documentation-robotics\", \"manifest.json\");\n }\n\n try {\n if (!(await fileExists(backupManifestFile))) {\n throw new Error(\"Backup manifest file not found (tried .yaml and .json)\");\n }\n\n const manifestContent = await readFile(backupManifestFile);\n await atomicWrite(manifestDestPath, manifestContent);\n restoredFiles.push(path.basename(backupManifestFile));\n } catch (error) {\n failedFiles.push({\n path: path.basename(backupManifestFile),\n error: getErrorMessage(error),\n isCritical: true,\n });\n // Manifest is critical - throw immediately\n throw new Error(\n `Failed to restore ${path.basename(backupManifestFile)}: ${getErrorMessage(error)}`\n );\n }\n\n // Restore all layer files with defensive error handling\n const backupLayersDir = path.join(backupDir, \"layers\");\n const fs = await import(\"fs/promises\");\n\n for (const layer of model.layers.values()) {\n const backupLayerDir = path.join(backupLayersDir, layer.name);\n\n try {\n if (!(await fileExists(backupLayerDir))) {\n // CRITICAL: Layer directory missing in backup - this is a data loss scenario\n const errorMsg = `Layer '${layer.name}' not found in backup (backup incomplete or corrupted)`;\n failedFiles.push({\n path: `layers/${layer.name}`,\n error: errorMsg,\n isCritical: true,\n });\n criticalLayers.add(layer.name);\n continue;\n }\n\n // Find the correct destination directory for this layer\n const layerDirPath = await this.findLayerDirectory(model.rootPath, layer.name);\n\n if (!layerDirPath) {\n // CRITICAL: Cannot find destination for layer - filesystem issue or permission problem\n const errorMsg = `Destination directory not found for layer '${layer.name}' (permissions issue or directory structure changed)`;\n failedFiles.push({\n path: `layers/${layer.name}`,\n error: errorMsg,\n isCritical: true,\n });\n criticalLayers.add(layer.name);\n continue;\n }\n\n // Restore all YAML files from backup\n const backupFiles = await fs.readdir(backupLayerDir);\n let filesRestoredInLayer = 0;\n\n for (const file of backupFiles) {\n if (file.endsWith(\".yaml\") || file.endsWith(\".yml\")) {\n const backupFilePath = path.join(backupLayerDir, file);\n const destFilePath = path.join(layerDirPath, file);\n try {\n const fileContent = await readFile(backupFilePath);\n await atomicWrite(destFilePath, fileContent);\n restoredFiles.push(`layers/${layer.name}/${file}`);\n filesRestoredInLayer++;\n } catch (fileError) {\n // Individual file restoration failure - still critical for data integrity\n const errorMsg = fileError instanceof Error ? fileError.message : String(fileError);\n\n // Log immediately with ERROR severity\n console.error(ansis.red(`\u2717 Failed to restore ${layer.name}/${file}: ${errorMsg}`));\n\n failedFiles.push({\n path: `layers/${layer.name}/${file}`,\n error: errorMsg,\n isCritical: true,\n });\n criticalLayers.add(layer.name);\n }\n }\n }\n } catch (error) {\n // Unexpected error during layer restoration - treat as critical\n const errorMessage = getErrorMessage(error);\n failedFiles.push({\n path: `layers/${layer.name}`,\n error: errorMessage,\n isCritical: true,\n });\n criticalLayers.add(layer.name);\n }\n }\n\n // Reload model layers to reflect restored state\n model.layers.clear();\n\n // CRITICAL: If any critical layers failed to restore, throw error immediately\n if (criticalLayers.size > 0) {\n const failedLayersList = Array.from(criticalLayers).join(\", \");\n const detailedErrors = failedFiles.map((f) => ` - ${f.path}: ${f.error}`).join(\"\\n\");\n\n const errorSummary =\n `Model restoration FAILED - ${failedFiles.length} critical layer(s) could not be restored:\\n` +\n ` Failed layers: ${failedLayersList}\\n` +\n `\\n` +\n `Detailed errors:\\n${detailedErrors}\\n` +\n `\\n` +\n `Successfully restored: ${restoredFiles.length} files\\n` +\n `Failed to restore: ${failedFiles.length} files\\n` +\n `\\n` +\n `Possible causes:\\n` +\n ` 1. Backup directory corrupted or incomplete\\n` +\n ` 2. Insufficient permissions to write to model directory\\n` +\n ` 3. Disk full during restoration\\n` +\n ` 4. Layer directory structure changed since backup\\n` +\n `\\n` +\n `Backup location: ${backupDir}\\n` +\n `\\n` +\n `ACTION REQUIRED: Manually restore from backup or contact support.`;\n\n throw new Error(errorSummary);\n }\n } catch (error) {\n // Critical failure during restore\n const errorMessage = getErrorMessage(error);\n const summary =\n `Model restoration failed: ${errorMessage}\\n` +\n `\\n` +\n `Restoration progress:\\n` +\n ` Successfully restored: ${restoredFiles.length} files\\n` +\n ` Failed to restore: ${failedFiles.length} files (all critical)\\n` +\n `\\n` +\n `Restored files:\\n${restoredFiles.map((f) => ` \u2713 ${f}`).join(\"\\n\")}\\n` +\n `\\n` +\n `Failed files:\\n${failedFiles.map((f) => ` ${f.isCritical ? \"\u2717 [CRITICAL]\" : \"\u26A0 [WARNING]\"} ${f.path}: ${f.error}`).join(\"\\n\")}\\n` +\n `\\n` +\n `Backup location: ${backupDir}`;\n\n throw new Error(summary);\n }\n }\n\n /**\n * Find the directory path for a layer in the model.\n * Uses same discovery logic as Model.loadLayer to locate layer directories.\n * Handles naming convention: `{PREFIX}_{layerName}` where PREFIX is numeric (01_motivation, etc).\n *\n * @param rootPath - Root path of the model\n * @param layerName - Name of the layer to find\n * @returns Path to the layer directory or null if not found\n */\n private async findLayerDirectory(rootPath: string, layerName: string): Promise<string | null> {\n const fs = await import(\"fs/promises\");\n const modelDir = path.join(rootPath, \"documentation-robotics\", \"model\");\n\n try {\n const entries = await fs.readdir(modelDir, { withFileTypes: true });\n const layerDir = entries.find(\n (e) =>\n e.isDirectory() && e.name.match(/^\\d{2}_/) && e.name.replace(/^\\d{2}_/, \"\") === layerName\n );\n\n if (layerDir) {\n return path.join(modelDir, layerDir.name);\n }\n\n // Layer directory legitimately not found\n return null;\n } catch (error) {\n const errorCode = (error as NodeJS.ErrnoException)?.code;\n\n if (errorCode === \"ENOENT\") {\n // Model directory doesn't exist - this might be OK during restore\n return null;\n }\n\n // All other errors are CRITICAL during restore operations\n throw new Error(\n `Cannot access model directory for layer '${layerName}': ${getErrorMessage(error)}\\n` +\n `Location: ${modelDir}\\n` +\n `Error code: ${errorCode}\\n` +\n `This indicates a filesystem or permissions issue. Check:\\n` +\n ` 1. Directory permissions: ls -la ${path.dirname(modelDir)}\\n` +\n ` 2. Disk health: df -h\\n` +\n ` 3. Filesystem errors: dmesg | tail`\n );\n }\n }\n\n /**\n * Generate a unique changeset ID.\n * Combines timestamp and random suffix to ensure uniqueness across concurrent operations.\n *\n * @returns A unique changeset ID in format `changeset-{timestamp}-{random}`\n */\n private generateChangesetId(): string {\n return `changeset-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * StagingAreaManager - Manages staged changesets and interception logic\n *\n * Provides:\n * - Changeset lifecycle management (create, load, list, delete)\n * - Staging operations (stage, unstage, discard)\n * - Commit operations with drift detection and validation\n * - Base snapshot tracking for drift detection\n */\n\nimport path from \"path\";\nimport ansis from \"ansis\";\nimport { StagedChangesetStorage } from \"./staged-changeset-storage.js\";\nimport { BaseSnapshotManager } from \"./base-snapshot-manager.js\";\nimport { Changeset, StagedChange, withSequenceNumbers } from \"./changeset.js\";\nimport { ChangesetValidator } from \"./changeset-validator.js\";\nimport { fileExists } from \"../utils/file-io.js\";\nimport type { Model } from \"./model.js\";\nimport type { VirtualProjectionEngine } from \"./virtual-projection.js\";\nimport { createSha256Hash } from \"../types/index.js\";\nimport type { BackupManifest } from \"../types/index.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\nimport { Layer } from \"./layer.js\";\nimport { canonicalStagedSnapshot } from \"./staged-source-reference.js\";\nimport { elementIdentifiers } from \"./relationships.js\";\nimport { isValidLayerName } from \"./layers.js\";\nimport {\n isTelemetryEnabled,\n startSpan,\n startActiveSpan,\n endSpan,\n emitLog,\n SeverityNumber,\n} from \"../telemetry/index.js\";\nimport { ModelReportOrchestrator } from \"../reports/model-report-orchestrator.js\";\n\n/**\n * Options for commit operation\n */\nexport interface CommitOptions {\n force?: boolean; // Ignore drift warnings\n validate?: boolean; // Run validation (default: true)\n dryRun?: boolean; // Preview without applying\n}\n\n/**\n * Drift report for change detection\n *\n * NOTE: Detects whether the base model has drifted (changed) since the changeset was created.\n * Current implementation only identifies that drift occurred (isDrifted flag).\n * Element-level change tracking is not implemented; detailed diff detection would require\n * storing complete snapshot data, not just hashes.\n */\nexport interface DriftReport {\n isDrifted: boolean;\n baseSnapshotId: string;\n currentSnapshotId: string;\n warnings: string[];\n}\n\n/**\n * Result of commit operation\n */\nexport interface CommitResult {\n changeset: string;\n committed: number;\n skipped?: number;\n skippedDetails?: string[];\n failed: number;\n validation: {\n passed: boolean;\n errors: string[];\n /** Whether validation ran (false when the caller passed validate: false). */\n ran?: boolean;\n /**\n * Errors the base model already had that the changeset leaves in place. They are\n * reported, not blocking: commit judges the change, not the base.\n */\n preExistingErrors?: number;\n /** A sample of the pre-existing errors' messages (first 10). */\n preExistingSample?: string[];\n /** Set when the base model could not be validated, so every error blocked. */\n baseValidationFailure?: string;\n };\n driftWarning?: DriftReport;\n}\n\n/**\n * StagingAreaManager provides changeset and staging operations\n */\nexport class StagingAreaManager {\n private storage: StagedChangesetStorage;\n private snapshotManager: BaseSnapshotManager;\n private validator: ChangesetValidator;\n private projectionEngine: VirtualProjectionEngine | null = null;\n private rootPath: string;\n private model: Model | null = null;\n\n constructor(rootPath: string, model?: Model) {\n this.rootPath = rootPath;\n this.model = model || null;\n this.storage = new StagedChangesetStorage(rootPath);\n this.snapshotManager = new BaseSnapshotManager();\n this.validator = new ChangesetValidator(rootPath);\n this.projectionEngine = null; // Lazy loaded on first call to getProjectionEngine()\n }\n\n /**\n * Validate changeset ID format and content\n * Ensures ID is safe and provides early validation before file operations\n *\n * Validation rules:\n * - Must be a non-empty string\n * - Cannot contain path separators (.., /, \\)\n * - Cannot contain special characters (<>:\"|?*)\n * - During storage, the ID will be normalized: lowercase, spaces\u2192hyphens, non-alphanumeric\u2192stripped\n *\n * @param changesetId - The changeset ID to validate\n * @throws Error if ID contains invalid characters or structure\n */\n private validateChangesetId(changesetId: string): void {\n if (!changesetId || typeof changesetId !== \"string\") {\n throw new Error(\"Changeset ID must be a non-empty string\");\n }\n\n if (changesetId.trim() === \"\") {\n throw new Error(\"Changeset ID cannot be empty or whitespace-only\");\n }\n\n // Check for path traversal attempts\n if (changesetId.includes(\"..\") || changesetId.includes(\"/\") || changesetId.includes(\"\\\\\")) {\n throw new Error(\"Changeset ID cannot contain path separators or traversal sequences (..)\");\n }\n\n // Check for special characters that could cause issues\n // Note: sanitizeId() will normalize the ID by converting to lowercase, replacing spaces with hyphens,\n // and removing all non-alphanumeric characters (except hyphens)\n if (/[<>:\"|?*]/.test(changesetId)) {\n throw new Error(\n 'Changeset ID contains invalid special characters (<>:\"|?*). ' +\n \"Only alphanumeric characters and hyphens are preserved. \" +\n \"Spaces are converted to hyphens, and other characters are removed during storage.\"\n );\n }\n }\n\n /**\n * Get or create projection engine instance (lazy initialization).\n * Deferred initialization avoids circular dependency issues at module load time.\n * @returns VirtualProjectionEngine instance for computing merged model views\n */\n private async getProjectionEngine(): Promise<VirtualProjectionEngine> {\n // Share the model's engine when there is one: its cache is the one `model.getLayer()`\n // reads, so staging must invalidate that cache, not a private copy. Otherwise a model\n // held across calls (the MCP server's) serves pre-stage layers until the cache expires.\n if (!this.projectionEngine && typeof this.model?.getVirtualProjectionEngine === \"function\") {\n this.projectionEngine = this.model.getVirtualProjectionEngine();\n }\n if (!this.projectionEngine) {\n const { VirtualProjectionEngine } = await import(\"./virtual-projection.js\");\n this.projectionEngine = new VirtualProjectionEngine(this.rootPath);\n }\n return this.projectionEngine;\n }\n\n /**\n * Create a new staged changeset\n */\n async create(name: string, description?: string): Promise<Changeset> {\n if (!this.model) {\n throw new Error(\"Model required for creating changesets\");\n }\n const id = this.generateChangesetId();\n const baseSnapshotId = await this.snapshotManager.captureSnapshot(this.model);\n\n const changeset = await this.storage.create(id, name, description, baseSnapshotId);\n changeset.id = id;\n\n // Mark as 'staged' so it can accept changes immediately\n changeset.markStaged();\n await this.storage.save(changeset);\n\n // Record which elements and relationships the base has now, so verify and brief can tell what\n // predates the plan. Optional: without it they fall back and say so, so a failure is reported,\n // not fatal.\n try {\n const { captureBaseIndex, writeBaseIndex } = await import(\"./changeset-base-index.js\");\n await writeBaseIndex(this.rootPath, changeset.id, await captureBaseIndex(this.model));\n } catch (error) {\n console.warn(\n `Warning: could not record the base index for changeset '${name}': ${getErrorMessage(error)}. ` +\n \"Verify and brief will fall back to what they can tell without it.\"\n );\n }\n\n return changeset;\n }\n\n /**\n * Load a changeset by name or ID\n */\n async load(name: string): Promise<Changeset | null> {\n // Try loading directly as ID first\n let changeset = await this.storage.load(name);\n if (changeset) {\n return changeset;\n }\n\n // If not found by ID, search by name in all changesets\n const all = await this.storage.list();\n return all.find((cs) => cs.name === name) || null;\n }\n\n /**\n * List all staged changesets\n */\n async list(): Promise<Changeset[]> {\n return this.storage.list();\n }\n\n /**\n * Delete a changeset\n * If the changeset being deleted is the currently active changeset,\n * this method will also clear the active changeset marker.\n */\n async delete(name: string): Promise<void> {\n const changeset = await this.load(name);\n if (!changeset) {\n throw new Error(`Changeset '${name}' not found`);\n }\n\n if (!changeset.id) {\n throw new Error(`Changeset '${name}' has no ID and cannot be deleted`);\n }\n\n // Check if this changeset is the active one and clear it if so\n const activeChangeset = await this.getActive();\n if (activeChangeset && activeChangeset.id === changeset.id) {\n await this.clearActive();\n }\n\n await this.storage.delete(changeset.id);\n }\n\n /**\n * Rename a changeset and/or change its description (its stated intent). Staged changes,\n * status and the active marker are untouched. Names stay unique: renaming to a name another\n * changeset already has is refused.\n *\n * @param nameOrId - Changeset ID or current name\n * @param patch - Fields to change; an empty description clears it\n * @throws Error if the changeset is not found, or the new name is taken\n */\n async update(nameOrId: string, patch: { name?: string; description?: string }): Promise<Changeset> {\n const changeset = await this.load(nameOrId);\n if (!changeset || !changeset.id) {\n throw new Error(`Changeset '${nameOrId}' not found`);\n }\n if (patch.name !== undefined) {\n const name = patch.name.trim();\n if (!name) {\n throw new Error(\"Changeset name cannot be empty\");\n }\n const clash = (await this.storage.list()).find((cs) => cs.name === name && cs.id !== changeset.id);\n if (clash) {\n throw new Error(`Another changeset (${clash.id}) is already named '${name}'`);\n }\n patch = { ...patch, name };\n }\n return this.storage.updateMetadata(changeset.id, patch);\n }\n\n /**\n * Stage a change in the changeset.\n * Records the change in the staging area without applying to the base model.\n * The changeset must be in 'staged' status to accept new changes.\n *\n * @param changesetId - ID of the changeset to stage the change in\n * @param change - The change to stage (type, elementId, layerName, before/after snapshots)\n * @throws Error if changeset ID is invalid, changeset not found, or not in 'staged' status\n */\n async stage(changesetId: string, change: Omit<StagedChange, \"sequenceNumber\">): Promise<void> {\n this.validateChangesetId(changesetId);\n\n const changeset = await this.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n // Only allow staging when status is 'staged' (not 'committed' or 'discarded')\n if (changeset.status !== \"staged\") {\n throw new Error(\n `Cannot stage changes on changeset with status '${changeset.status}'. ` +\n `Changeset must have status 'staged' to accept new changes.`\n );\n }\n\n const stagedChange: Omit<StagedChange, \"sequenceNumber\"> = {\n ...change,\n timestamp: change.timestamp || new Date().toISOString(),\n };\n\n await this.storage.addChange(changesetId, stagedChange);\n\n // A model held across calls (the MCP server's) lists its layers from what was staged when it\n // loaded: tell it about an element change in a layer it may not have, so lookups find it.\n if (!change.type.startsWith(\"relationship-\") && typeof this.model?.noteStagedLayer === \"function\") {\n this.model.noteStagedLayer(changesetId, change.layerName);\n }\n\n // Invalidate projection cache for this layer\n const engine = await this.getProjectionEngine();\n engine.invalidateOnStage(changesetId, change.layerName);\n }\n\n /**\n * Rewrite a staged change in place, keeping its sequence number: e.g. to fold an update of an\n * element into the changeset's add of it. The changeset must be in 'staged' status.\n *\n * @param elementId - The key (`elementId`) of the change to rewrite\n * @throws Error if the changeset is not found, not staged, or has no change with that key\n */\n async restage(\n changesetId: string,\n elementId: string,\n change: Omit<StagedChange, \"sequenceNumber\">\n ): Promise<void> {\n this.validateChangesetId(changesetId);\n\n const changeset = await this.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n if (changeset.status !== \"staged\") {\n throw new Error(\n `Cannot change staged changes on changeset with status '${changeset.status}'. ` +\n `Changeset must have status 'staged'.`\n );\n }\n\n await this.storage.updateChange(changesetId, elementId, {\n ...change,\n timestamp: change.timestamp || new Date().toISOString(),\n });\n\n const engine = await this.getProjectionEngine();\n engine.invalidateOnStage(changesetId, change.layerName);\n }\n\n /**\n * Unstage a specific element from the changeset.\n * Removes all changes associated with the element.\n * The changeset must be in 'staged' status.\n *\n * @param changesetId - ID of the changeset to unstage from\n * @param elementId - ID of the element to remove all changes for\n * @throws Error if changeset ID is invalid, changeset not found, or not in 'staged' status\n */\n async unstage(changesetId: string, elementId: string): Promise<void> {\n this.validateChangesetId(changesetId);\n\n const changeset = await this.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n if (changeset.status !== \"staged\") {\n throw new Error(\n `Cannot unstage changes on changeset with status '${changeset.status}'. ` +\n `Changeset must have status 'staged'.`\n );\n }\n\n // Remove the element's changes and resequence remaining changes\n await this.storage.removeChange(changesetId, elementId);\n\n // Invalidate projection cache for the removed element's layers\n const engine = await this.getProjectionEngine();\n await engine.invalidateOnUnstage(changesetId, elementId);\n }\n\n /**\n * Discard all changes in the changeset.\n * Clears all staged changes and marks the changeset as 'discarded'.\n * This operation is irreversible for the changeset but does not affect the base model.\n *\n * @param changesetId - ID of the changeset to discard\n * @throws Error if changeset ID is invalid or changeset not found\n */\n async discard(changesetId: string): Promise<void> {\n this.validateChangesetId(changesetId);\n\n const changeset = await this.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n // Clear all staged changes and update status\n changeset.changes = [];\n // Discard is for good: the unstage tombstones go with the changes.\n changeset.unstaged = [];\n changeset.status = \"discarded\";\n // Note: stats are auto-computed from changes array (will be 0/0/0 after clearing)\n changeset.updateModified();\n\n await this.storage.save(changeset);\n\n // Invalidate all projection cache entries for this changeset\n const engine = await this.getProjectionEngine();\n engine.invalidateOnDiscard(changesetId);\n }\n\n /**\n * Capture a base snapshot of the current model state for drift detection.\n * Stores a hash of the model that can be compared later to detect changes.\n *\n * @param changesetId - ID of the changeset to capture snapshot for\n * @returns SHA256 hash of the current model state\n * @throws Error if changeset not found or model not available\n */\n async captureBaseSnapshot(changesetId: string): Promise<string> {\n this.validateChangesetId(changesetId);\n\n if (!this.model) {\n throw new Error(\"Model required for capturing base snapshot\");\n }\n const changeset = await this.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n const snapshotId = await this.snapshotManager.captureSnapshot(this.model);\n changeset.baseSnapshot = snapshotId;\n changeset.updateModified();\n await this.storage.save(changeset);\n\n return snapshotId;\n }\n\n /**\n * Detect drift by comparing base snapshot hash to current model state.\n * Identifies whether the base model has changed since the changeset was created.\n *\n * @param changesetId - ID of the changeset to check for drift\n * @returns DriftReport with isDrifted flag, snapshot hashes, and warnings\n * @throws Error if changeset ID is invalid, changeset not found, or model not available\n *\n * @remarks\n * Drift detection uses hash-based comparison of the entire model snapshot.\n * Element-level change tracking is not implemented - only the isDrifted flag\n * indicates if the model has changed. Detailed diff detection would require\n * storing complete snapshot data, not just hashes.\n * Returns isDrifted=false and warnings if no base snapshot was captured.\n */\n async detectDrift(changesetId: string): Promise<DriftReport> {\n this.validateChangesetId(changesetId);\n\n if (!this.model) {\n throw new Error(\"Model required for drift detection\");\n }\n const changeset = await this.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n if (!changeset.baseSnapshot) {\n return {\n isDrifted: false,\n baseSnapshotId: \"unknown\",\n currentSnapshotId: \"unknown\",\n warnings: [\"No base snapshot available for drift detection\"],\n };\n }\n\n // Capture current model snapshot to compare against base\n const currentSnapshotId = await this.snapshotManager.captureSnapshot(this.model);\n\n // Compare snapshot hashes to detect drift\n // isDrifted=true indicates base model has changed since changeset creation\n const isDrifted = changeset.baseSnapshot !== currentSnapshotId;\n\n const warnings: string[] = [];\n\n if (isDrifted) {\n warnings.push(\n `Model has drifted since changeset creation (base snapshot hash differs from current)`\n );\n }\n\n return {\n isDrifted,\n baseSnapshotId: changeset.baseSnapshot,\n currentSnapshotId: currentSnapshotId,\n warnings,\n };\n }\n\n /**\n * Commit a changeset to the base model with atomic guarantee.\n * Applies all staged changes and persists them to the base model.\n *\n * Implements atomic commit with the following steps:\n * 1. Detect drift in base model (fail unless force=true)\n * 2. Validate projected model (schema, references, semantics)\n * 3. Create backup of current model state\n * 4. Apply all changes in sequence order\n * 5. Save all modified layers to disk\n * 6. Update changeset status and manifest history\n * 7. Cleanup backup on success, restore on failure\n *\n * @param model - The model to commit changes to\n * @param changesetId - ID of the changeset to commit\n * @param options - Commit options (force, validate, dryRun)\n * @returns CommitResult with count of committed/failed changes and validation status\n * @throws Error if validation fails, drift detected without --force, or commit fails\n *\n * @remarks\n * Validation errors always block commit (force flag does not override).\n * Drift warnings can be bypassed with --force flag.\n * Dry-run mode performs all checks but does not persist changes.\n * On commit failure, model state is restored from backup; rollback failure triggers CRITICAL error.\n */\n async commit(\n model: Model,\n changesetId: string,\n options: CommitOptions = {}\n ): Promise<CommitResult> {\n return startActiveSpan(\"staging.commit\", async (span) => {\n this.validateChangesetId(changesetId);\n\n const changeset = await this.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n (span as any).setAttribute(\"changeset.name\", changeset.name);\n (span as any).setAttribute(\"changeset.changeCount\", changeset.changes.length);\n\n const isDryRun = options.dryRun === true;\n const shouldValidate = options.validate !== false;\n\n // Step 1: Detect drift and throw if drifted without --force\n const driftSpan = isTelemetryEnabled ? startSpan(\"staging.detectDrift\") : null;\n const drift = await this.detectDrift(changesetId);\n if (isTelemetryEnabled && driftSpan) {\n (driftSpan as any).setAttribute(\"drift.detected\", drift.isDrifted);\n (driftSpan as any).setAttribute(\"drift.baseSnapshotId\", drift.baseSnapshotId);\n (driftSpan as any).setAttribute(\"drift.currentSnapshotId\", drift.currentSnapshotId);\n (driftSpan as any).setStatus({ code: 0 });\n }\n endSpan(driftSpan);\n\n if (drift.isDrifted) {\n (span as any).setAttribute(\"commit.driftDetected\", true);\n if (options.force) {\n emitLog(SeverityNumber.WARN, \"Drift detected but overridden with --force\", {\n \"drift.baseSnapshot\": drift.baseSnapshotId,\n \"drift.currentSnapshot\": drift.currentSnapshotId,\n });\n } else {\n emitLog(SeverityNumber.ERROR, \"Drift detected, commit blocked\", {\n \"drift.baseSnapshot\": drift.baseSnapshotId,\n \"drift.currentSnapshot\": drift.currentSnapshotId,\n });\n throw new Error(\n `Base model has drifted since changeset creation. ` + `Use --force to commit anyway.`\n );\n }\n }\n\n const result: CommitResult = {\n changeset: changeset.name,\n committed: 0,\n skipped: 0,\n skippedDetails: [],\n failed: 0,\n validation: {\n passed: true,\n errors: [],\n ran: shouldValidate,\n },\n };\n\n // Step 2: Validate the change (force cannot override validation). Only errors the\n // changeset introduces block; errors already in the base model are counted and\n // reported, so a model with old problems can still take a change.\n if (shouldValidate) {\n await startActiveSpan(\"staging.validate\", async (validationSpan) => {\n const report = await this.validator.validateChangesetReport(model, changesetId);\n const validation = report.result;\n result.validation.preExistingErrors = report.preExistingErrors.length;\n if (report.preExistingErrors.length > 0) {\n result.validation.preExistingSample = report.preExistingErrors\n .slice(0, 10)\n .map((e) => e.message);\n emitLog(SeverityNumber.WARN, \"Pre-existing validation errors not counted against the changeset\", {\n \"validation.preExistingErrorCount\": report.preExistingErrors.length,\n });\n }\n if (report.baseValidationFailure) {\n result.validation.baseValidationFailure = report.baseValidationFailure;\n emitLog(SeverityNumber.WARN, \"Base model could not be validated; every error counts\", {\n \"validation.baseValidationFailure\": report.baseValidationFailure,\n });\n }\n\n (validationSpan as any).setAttribute(\"validation.passed\", validation.isValid());\n (validationSpan as any).setAttribute(\"validation.errorCount\", validation.errors.length);\n (validationSpan as any).setAttribute(\n \"validation.preExistingErrorCount\",\n report.preExistingErrors.length\n );\n (validationSpan as any).setStatus({ code: 0 });\n\n if (!validation.isValid()) {\n result.validation.passed = false;\n result.validation.errors = validation.errors.map((e) => e.message);\n\n emitLog(SeverityNumber.ERROR, \"Validation failed\", {\n \"validation.errorCount\": result.validation.errors.length,\n \"validation.errors\": result.validation.errors.slice(0, 10), // First 10 errors\n });\n\n const notes: string[] = [];\n if (report.preExistingErrors.length > 0) {\n notes.push(\n `${report.preExistingErrors.length} pre-existing error(s) in the base model were not counted against this changeset.`\n );\n }\n if (report.baseValidationFailure) {\n notes.push(\n `The base model could not be validated (${report.baseValidationFailure}), so every error was counted.`\n );\n }\n throw new Error(\n `Validation failed with ${result.validation.errors.length} error(s) introduced by this changeset:\\n` +\n result.validation.errors.map((e) => ` - ${e}`).join(\"\\n\") +\n (notes.length > 0 ? `\\n\\n${notes.join(\"\\n\")}` : \"\") +\n `\\n\\nFix these validation errors before committing, or skip validation with --no-validate.`\n );\n }\n });\n }\n\n // Step 3: Dry run early exit (before making any changes)\n if (isDryRun) {\n result.committed = changeset.changes.length;\n if (drift.isDrifted) {\n result.driftWarning = drift;\n }\n return result;\n }\n\n // Step 4: Backup model state for atomic rollback\n let backup: string;\n try {\n backup = await this.backupModel(model);\n } catch (backupError) {\n // Backup creation failed - cannot proceed safely\n throw new Error(\n `Failed to create backup before commit. No changes have been applied. ` +\n `${backupError instanceof Error ? backupError.message : String(backupError)}`\n );\n }\n\n // Step 5: Apply all changes sequentially by sequence number (numbers may have gaps where\n // changes were unstaged; unstage tombstones are never applied)\n const sortedChanges = withSequenceNumbers(changeset.changes).sort(\n (a, b) => a.sequenceNumber - b.sequenceNumber\n );\n // Set when applying an element delete removed rows from relationships.yaml\n let relationshipsPurged = false;\n\n try {\n const applySpan = isTelemetryEnabled\n ? startSpan(\"staging.apply\", {\n \"apply.changeCount\": changeset.changes.length,\n })\n : null;\n\n try {\n for (const change of sortedChanges) {\n try {\n // Use getBaseLayer() so commit operates on the actual mutable base layer,\n // not a projected clone. getLayer() would return a projected (read-only clone)\n // which cannot be persisted via saveLayer().\n let layer = await model.getBaseLayer(change.layerName);\n if (!layer) {\n // Layer directory doesn't exist yet \u2014 this changeset introduces\n // the first elements for this layer. Create an empty layer and\n // register it so saveLayer() will create the directory on disk.\n layer = new Layer(change.layerName);\n model.addLayer(layer);\n }\n\n if (change.type === \"relationship-add\" && change.after) {\n // Relationship-add: the relationship was already injected into model.relationships\n // during Model.load() for transparent read-path visibility. Here we just need\n // saveRelationships() to persist it. Skip adding if it already exists.\n const rel = change.after as {\n source: string;\n target: string;\n predicate: string;\n layer: string;\n targetLayer?: string;\n category?: \"structural\" | \"behavioral\";\n properties?: Record<string, unknown>;\n };\n const existing = model.relationships.find(rel.source, rel.target, rel.predicate);\n if (existing.length === 0) {\n model.relationships.add({\n source: rel.source,\n target: rel.target,\n predicate: rel.predicate,\n layer: rel.layer,\n ...(rel.targetLayer ? { targetLayer: rel.targetLayer } : {}),\n category: rel.category ?? \"structural\",\n ...(rel.properties ? { properties: rel.properties } : {}),\n });\n }\n result.committed++;\n continue;\n }\n\n if (change.type === \"relationship-delete\" && change.before) {\n // Relationship-delete: the relationship was already removed from model.relationships\n // during Model.load(). Just ensure it's gone and count it.\n const rel = change.before as {\n source: string;\n target: string;\n predicate?: string;\n };\n model.relationships.delete(rel.source, rel.target, rel.predicate);\n result.committed++;\n continue;\n }\n\n if (change.type === \"add\" && change.after) {\n const { Element } = await import(\"./element.js\");\n const elementData = change.after as Record<string, unknown>;\n const elementId = elementData.id as string;\n\n // Check if element already exists using the UUID from elementData\n const elements = layer.listElements();\n const elementExists = elements.some((e) => e.id === elementId);\n\n if (!elementExists) {\n // Pass the entire elementData to Element constructor - it already has all fields from toJSON()\n const element = new Element(canonicalStagedSnapshot(elementData).data);\n layer.addElement(element);\n result.committed++;\n } else {\n result.skipped = (result.skipped ?? 0) + 1;\n result.skippedDetails = result.skippedDetails ?? [];\n result.skippedDetails.push(\n `'${elementId}' in '${change.layerName}' already exists \u2014 deduplicated`\n );\n emitLog(SeverityNumber.WARN, \"Element deduplicated during commit\", {\n \"change.elementId\": change.elementId,\n \"change.layer\": change.layerName,\n });\n }\n } else if (change.type === \"update\" && change.after) {\n const element = layer.getElement(change.elementId);\n if (element) {\n const { data: elementData, sourceReference } = canonicalStagedSnapshot(\n change.after as Record<string, unknown>\n );\n Object.assign(element, elementData);\n if (sourceReference.present) element.setSourceReference(sourceReference.value);\n // Persist the mutation back to the underlying graph node.\n // getElement() returns a new wrapper each time, so Object.assign\n // alone is discarded; updateElement() writes through to the graph.\n layer.updateElement(element);\n result.committed++;\n } else {\n result.failed++;\n result.validation.passed = false;\n result.validation.errors.push(\n `Cannot update '${change.elementId}' in '${change.layerName}': element not found in base model`\n );\n }\n } else if (change.type === \"delete\") {\n const element = layer.getElement(change.elementId);\n // Purge relationships that name the element by UUID or by path.\n // Deletes staged before relationship purging existed carry no\n // relationship-delete changes, so this is the safety net at commit.\n const before = (change.before ?? {}) as { id?: unknown; path?: unknown };\n const purged = model.relationships.deleteForElement(\n elementIdentifiers(element ?? before, before.id as string | undefined,\n before.path as string | undefined, change.elementId)\n );\n if (purged > 0) {\n relationshipsPurged = true;\n }\n if (element) {\n layer.deleteElement(element.path || element.id);\n result.committed++;\n } else {\n // Element was already removed \u2014 count as skipped, not failed\n result.skipped = (result.skipped ?? 0) + 1;\n result.skippedDetails = result.skippedDetails ?? [];\n result.skippedDetails.push(\n `'${change.elementId}' in '${change.layerName}' not found \u2014 already deleted`\n );\n }\n } else {\n // Handles: unknown change types, and known types with missing payloads\n // (e.g. \"add\" with no change.after, \"relationship-add\" with no change.after).\n result.failed++;\n result.validation.passed = false;\n result.validation.errors.push(\n `Cannot apply change '${change.type}' for '${change.elementId}': missing payload or unrecognised type`\n );\n }\n } catch (error) {\n result.failed++;\n result.validation.passed = false;\n result.validation.errors.push(\n `Failed to apply change to ${change.elementId}: ${getErrorMessage(error)}`\n );\n\n emitLog(SeverityNumber.ERROR, \"Failed to apply change\", {\n \"change.elementId\": change.elementId,\n \"change.type\": change.type,\n \"change.layer\": change.layerName,\n \"error.message\": getErrorMessage(error),\n });\n\n throw new Error(\n `Atomic commit failed at change ${sortedChanges.indexOf(change) + 1}/${sortedChanges.length}: ` +\n `${getErrorMessage(error)}`\n );\n }\n }\n\n if (isTelemetryEnabled && applySpan) {\n (applySpan as any).setAttribute(\"apply.committed\", result.committed);\n (applySpan as any).setAttribute(\"apply.failed\", result.failed);\n (applySpan as any).setStatus({ code: 0 });\n }\n } finally {\n endSpan(applySpan);\n }\n\n // Step 6: Save all modified layers and relationships atomically\n const modifiedLayers = new Set<string>();\n let hasRelationshipChanges = false;\n for (const change of sortedChanges) {\n if (change.type === \"relationship-add\" || change.type === \"relationship-delete\") {\n hasRelationshipChanges = true;\n } else {\n modifiedLayers.add(change.layerName);\n }\n }\n\n for (const layerName of modifiedLayers) {\n try {\n await model.saveLayer(layerName);\n } catch (error) {\n throw new Error(\n `Failed to save layer '${layerName}': ${getErrorMessage(error)}`\n );\n }\n }\n\n // Persist staged relationship changes to relationships.yaml\n if (hasRelationshipChanges || relationshipsPurged) {\n try {\n await model.saveRelationships();\n } catch (error) {\n throw new Error(\n `Failed to save relationships after commit: ${getErrorMessage(error)}`\n );\n }\n }\n\n // Step 7: Update changeset status to committed ONLY AFTER successful saves\n changeset.status = \"committed\";\n changeset.updateModified();\n\n try {\n await this.storage.save(changeset);\n } catch (error) {\n // Changeset status is critical metadata - must be persisted with model changes\n throw new Error(\n `Failed to save changeset status after commit: ${getErrorMessage(error)}. ` +\n `This is a critical error that requires rollback to maintain consistency between model state and changeset metadata.`\n );\n }\n\n // Step 8: Add changeset to manifest history\n if (!model.manifest.changeset_history) {\n model.manifest.changeset_history = [];\n }\n model.manifest.changeset_history.push({\n name: changeset.name,\n committed_at: new Date().toISOString(),\n action: \"committed\",\n });\n\n // Step 9: Save manifest with updated changeset history\n try {\n await model.saveManifest();\n } catch (manifestError) {\n // Manifest save failed - this is CRITICAL and will trigger rollback\n // Don't log here - let the rollback handler provide comprehensive error output\n throw new Error(\n `Failed to save manifest with changeset history: ${manifestError instanceof Error ? manifestError.message : String(manifestError)}. ` +\n `Model will be rolled back to maintain consistency.`\n );\n }\n\n // Step 9.5: Regenerate reports for all layers modified by this changeset\n try {\n const orchestrator = new ModelReportOrchestrator(model, model.rootPath);\n const affectedLayers = new Set<string>();\n for (const change of sortedChanges) {\n if (isValidLayerName(change.layerName)) {\n for (const layer of orchestrator.computeAffectedLayers(change.layerName)) {\n affectedLayers.add(layer);\n }\n }\n }\n await orchestrator.regenerate(affectedLayers);\n } catch (error) {\n emitLog(\n SeverityNumber.WARN,\n \"Failed to regenerate layer reports after changeset commit\",\n {\n \"changeset.id\": changesetId,\n \"changeset.name\": changeset.name,\n \"changeset.changeCount\": sortedChanges.length,\n \"error.message\": getErrorMessage(error),\n }\n );\n }\n\n // Step 10: Clean up backup directory after successful commit\n // Note: cleanupBackup handles its own errors and logs them with ERROR severity\n await this.cleanupBackup(backup);\n\n (span as any).setAttribute(\"commit.success\", true);\n (span as any).setAttribute(\"commit.committed\", result.committed);\n if (drift.isDrifted) {\n (span as any).setAttribute(\"commit.driftOverridden\", true);\n }\n (span as any).setStatus({ code: 0 });\n\n return result;\n } catch (commitError) {\n // Attempt rollback on any failure - restore from backup\n let rollbackSucceeded = false;\n let rollbackError: Error | null = null;\n\n const rollbackSpan = isTelemetryEnabled ? startSpan(\"staging.rollback\") : null;\n\n try {\n emitLog(SeverityNumber.WARN, \"Commit failed, attempting rollback\", {\n \"commit.error\":\n commitError instanceof Error ? commitError.message : String(commitError),\n });\n\n await this.restoreModel(model, backup);\n rollbackSucceeded = true;\n\n if (isTelemetryEnabled && rollbackSpan) {\n (rollbackSpan as any).setAttribute(\"rollback.success\", true);\n (rollbackSpan as any).setStatus({ code: 0 });\n }\n\n emitLog(SeverityNumber.INFO, \"Rollback succeeded, model restored\", {\n \"backup.path\": backup,\n });\n } catch (err) {\n rollbackError = err instanceof Error ? err : new Error(String(err));\n\n if (isTelemetryEnabled && rollbackSpan) {\n (rollbackSpan as any).setAttribute(\"rollback.success\", false);\n (rollbackSpan as any).recordException(rollbackError);\n (rollbackSpan as any).setStatus({ code: 2, message: rollbackError.message });\n }\n\n emitLog(SeverityNumber.ERROR, \"Rollback failed, model may be corrupted\", {\n \"rollback.error\": rollbackError.message,\n \"backup.path\": backup,\n });\n } finally {\n endSpan(rollbackSpan);\n }\n\n // CRITICAL: Validate backup integrity BEFORE suggesting manual restoration\n let backupHealth: { isValid: boolean; filesChecked: number; errors: string[] };\n let backupValidationError: string | null = null;\n let validationProcessFailed = false;\n\n try {\n backupHealth = await this.validateBackupIntegrity(backup);\n } catch (validationErr) {\n validationProcessFailed = true;\n backupValidationError =\n validationErr instanceof Error ? validationErr.message : String(validationErr);\n backupHealth = {\n isValid: false,\n filesChecked: 0,\n errors: [\n `Validation process failed: ${backupValidationError}`,\n `This indicates a critical issue with the backup infrastructure, not the backup content.`,\n `Contact support before attempting manual restoration.`,\n ],\n };\n }\n\n // Build comprehensive error message\n let compositeMessage: string;\n const suggestions: string[] = [];\n\n if (rollbackSucceeded) {\n // Rollback succeeded - throw original commit error\n compositeMessage = `Commit failed but rollback succeeded. Model restored to pre-commit state.\\n\\n`;\n compositeMessage += `Error during commit:\\n`;\n compositeMessage += ` ${commitError instanceof Error ? commitError.message : String(commitError)}\\n\\n`;\n compositeMessage += `Backup location (for reference): ${backup}`;\n\n // Clean up backup after successful rollback\n // Note: cleanupBackup handles its own errors and logs them with ERROR severity\n await this.cleanupBackup(backup);\n\n (span as any).setAttribute(\"commit.success\", false);\n (span as any).setAttribute(\"commit.rollbackSuccess\", true);\n (span as any).recordException(commitError as Error);\n (span as any).setStatus({\n code: 2,\n message: commitError instanceof Error ? commitError.message : String(commitError),\n });\n\n throw new Error(compositeMessage);\n } else {\n // Rollback failed - model may be corrupted\n compositeMessage =\n `Commit failed AND rollback failed. Model may be in corrupted state.\\n` +\n `\\n` +\n `Original commit error:\\n` +\n ` ${commitError instanceof Error ? commitError.message : String(commitError)}\\n` +\n `\\n` +\n `Rollback error:\\n` +\n ` ${rollbackError ? rollbackError.message : \"Unknown error\"}\\n` +\n `\\n` +\n `Backup location: ${backup}\\n`;\n\n // Add backup health status (always show header for consistency with tests)\n compositeMessage += `Backup integrity check:\\n`;\n if (validationProcessFailed) {\n compositeMessage += ` \u2717 Backup integrity issues found:\\n`;\n compositeMessage += ` - Validation process failed: ${backupValidationError}\\n`;\n compositeMessage += `\\n\u26A0 CRITICAL: Backup validation process crashed.\\n`;\n compositeMessage += `This indicates a system-level issue (filesystem, memory, permissions).\\n`;\n compositeMessage += `DO NOT attempt manual restoration until this is resolved.\\n`;\n } else if (backupHealth.isValid) {\n compositeMessage += ` \u2713 Backup is valid (${backupHealth.filesChecked} files checked)\\n`;\n } else {\n compositeMessage += ` \u2717 Backup integrity issues found:\\n`;\n for (const error of backupHealth.errors) {\n compositeMessage += ` - ${error}\\n`;\n }\n }\n\n compositeMessage += `\\n`;\n\n if (backupHealth.isValid) {\n suggestions.push(\n `Backup is valid. Manually restore from backup: ${backup}`,\n `Copy manifest.json/manifest.yaml from backup to ${path.join(model.rootPath, \"documentation-robotics\")}`,\n `Copy layer files from ${path.join(backup, \"layers\")} to ${path.join(model.rootPath, \"documentation-robotics\", \"layers\")}`,\n `Run 'dr validate' after manual restoration`,\n `Contact support if issue persists`\n );\n } else {\n suggestions.push(\n `\u26A0 Backup integrity is compromised. Do NOT use this backup for recovery.`,\n `Check if backup files exist at: ${backup}`,\n `Verify disk space and file permissions`,\n `Contact support immediately - manual data recovery may be required`,\n `Do not attempt manual restoration with a corrupted backup`\n );\n }\n\n compositeMessage += `Manual recovery may be required.`;\n\n (span as any).setAttribute(\"commit.success\", false);\n (span as any).setAttribute(\"commit.rollbackSuccess\", false);\n (span as any).recordException(commitError as Error);\n (span as any).setStatus({ code: 2, message: \"Commit and rollback both failed\" });\n\n const { CLIError } = await import(\"../utils/errors.js\");\n throw new CLIError(compositeMessage, 1, suggestions);\n }\n }\n }, {\n \"changeset.id\": changesetId,\n \"commit.validate\": options.validate !== false,\n \"commit.force\": options.force === true,\n \"commit.dryRun\": options.dryRun === true,\n });\n }\n\n /**\n * Check if a changeset is currently active.\n * Active changesets intercept mutations to stage changes instead of applying to base model.\n *\n * @param changesetId - ID of the changeset to check\n * @returns true if changeset is marked as active, false otherwise\n * @throws Error if changeset ID is invalid\n */\n async isActive(changesetId: string): Promise<boolean> {\n this.validateChangesetId(changesetId);\n\n const activePath = path.join(this.rootPath, \"documentation-robotics\", \"changesets\", \".active\");\n if (!(await fileExists(activePath))) {\n return false;\n }\n\n const { readFile } = await import(\"../utils/file-io.js\");\n const activeId = await readFile(activePath);\n return activeId.trim() === changesetId;\n }\n\n /**\n * Activate a changeset to intercept model mutations.\n * Marks the changeset as active and transitions status to 'staged'.\n * Only one changeset can be active at a time.\n *\n * @param changesetId - ID of the changeset to activate\n * @throws Error if changeset ID is invalid or changeset not found\n *\n * @remarks\n * When a changeset is active, add/update/delete commands will stage changes\n * instead of applying them directly to the base model.\n */\n async setActive(changesetId: string): Promise<void> {\n this.validateChangesetId(changesetId);\n\n const changeset = await this.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n // Transition to 'staged' status to enable interception\n if (changeset.status !== \"staged\") {\n changeset.markStaged();\n await this.storage.save(changeset);\n }\n\n const { writeFile, ensureDir } = await import(\"../utils/file-io.js\");\n const changesetDir = path.join(this.rootPath, \"documentation-robotics\", \"changesets\");\n await ensureDir(changesetDir);\n\n const activePath = path.join(changesetDir, \".active\");\n // Write the actual changeset ID (not the user-provided name)\n if (!changeset.id) {\n throw new Error(`Changeset has no ID, cannot activate`);\n }\n await writeFile(activePath, changeset.id);\n }\n\n /**\n * Clear the active changeset marker.\n * Allows a new changeset to be activated or model mutations to proceed normally.\n */\n async clearActive(): Promise<void> {\n const { writeFile } = await import(\"../utils/file-io.js\");\n const activePath = path.join(this.rootPath, \"documentation-robotics\", \"changesets\", \".active\");\n\n if (await fileExists(activePath)) {\n await writeFile(activePath, \"\");\n }\n }\n\n /**\n * Get the currently active changeset.\n * Returns null if no changeset is active.\n *\n * @returns The active changeset or null if none is active\n */\n async getActive(): Promise<Changeset | null> {\n const activePath = path.join(this.rootPath, \"documentation-robotics\", \"changesets\", \".active\");\n\n if (!(await fileExists(activePath))) {\n return null;\n }\n\n const { readFile } = await import(\"../utils/file-io.js\");\n const activeId = await readFile(activePath);\n const id = activeId.trim();\n\n if (!id) {\n return null;\n }\n\n return this.storage.load(id);\n }\n\n /**\n * Get the ID of the active changeset without loading the full changeset object.\n * Useful for status display and quick active changeset checks.\n *\n * @returns The active changeset ID or null if no active changeset exists\n */\n async getActiveId(): Promise<string | null> {\n const activePath = path.join(this.rootPath, \"documentation-robotics\", \"changesets\", \".active\");\n\n if (!(await fileExists(activePath))) {\n return null;\n }\n\n const { readFile } = await import(\"../utils/file-io.js\");\n const activeId = await readFile(activePath);\n const id = activeId.trim();\n\n return id || null;\n }\n\n /**\n * Apply a changeset to the base model.\n * Alias for commit() to match legacy command terminology.\n *\n * @param model - The model to apply changes to\n * @param changesetId - ID of the changeset to apply\n * @param options - Commit options (force, validate, dryRun)\n * @returns CommitResult with count of applied changes and validation status\n * @throws Error if validation fails, drift detected without --force, or apply fails\n *\n * @remarks\n * This method provides backward compatibility with changeset commands that use\n * \"apply\" instead of \"commit\" terminology. All logic is delegated to commit().\n */\n async apply(\n model: Model,\n changesetId: string,\n options: CommitOptions = {}\n ): Promise<CommitResult> {\n return this.commit(model, changesetId, options);\n }\n\n /**\n * Revert a changeset without applying changes.\n * Alias for discard() to match legacy command terminology.\n *\n * @param changesetId - ID of the changeset to revert\n * @throws Error if changeset ID is invalid or changeset not found\n *\n * @remarks\n * This method provides backward compatibility with changeset commands that use\n * \"revert\" instead of \"discard\" terminology. All logic is delegated to discard().\n */\n async revert(changesetId: string): Promise<void> {\n return this.discard(changesetId);\n }\n\n /**\n * Backup the current model state for atomic rollback on commit failure.\n * Creates a temporary backup directory with copies of all layer files and manifest.\n * Validates backup completeness and calculates checksums for integrity verification.\n *\n * @param model - The model to backup\n * @returns Path to the backup directory\n * @throws Error if backup creation or validation fails\n *\n * @remarks\n * Backup includes:\n * - manifest.yaml or manifest.json\n * - All layer YAML files\n * - Backup manifest with checksums and metadata\n * Backup is removed after successful commit; retained if commit fails for recovery.\n */\n private async backupModel(model: Model): Promise<string> {\n const backupDir = path.join(\n this.rootPath,\n \"documentation-robotics\",\n \".backups\",\n `backup-${Date.now()}`\n );\n\n const { ensureDir, readFile, atomicWrite } = await import(\"../utils/file-io.js\");\n const { createHash } = await import(\"crypto\");\n\n await ensureDir(backupDir);\n\n // Track what we're backing up for validation\n // Use mutable array during construction, then make readonly when creating BackupManifest\n const backupFiles: Array<{\n path: string;\n checksum: ReturnType<typeof createSha256Hash>;\n size: number;\n }> = [];\n const timestamp = new Date().toISOString();\n\n try {\n // Backup manifest - try both .yaml and .json formats\n let manifestPath = path.join(\n model.rootPath,\n \"documentation-robotics\",\n \"model\",\n \"manifest.yaml\"\n );\n if (!(await fileExists(manifestPath))) {\n manifestPath = path.join(model.rootPath, \"documentation-robotics\", \"manifest.json\");\n }\n if (!(await fileExists(manifestPath))) {\n throw new Error(\"Manifest file not found in expected locations\");\n }\n\n const manifestContent = await readFile(manifestPath);\n const manifestChecksum = createSha256Hash(\n createHash(\"sha256\").update(manifestContent).digest(\"hex\")\n );\n const manifestFilename = path.basename(manifestPath);\n\n await atomicWrite(path.join(backupDir, manifestFilename), manifestContent);\n backupFiles.push({\n path: manifestFilename,\n checksum: manifestChecksum,\n size: manifestContent.length,\n });\n\n // Backup all layers - handle multiple YAML files per layer\n const layersDir = path.join(backupDir, \"layers\");\n await ensureDir(layersDir);\n\n const fs = await import(\"fs/promises\");\n\n for (const layer of model.layers.values()) {\n const layerBackupDir = path.join(layersDir, layer.name);\n await ensureDir(layerBackupDir);\n\n // Find the layer directory in the model\n const layerDirPath = await this.findLayerDirectory(model.rootPath, layer.name);\n if (!layerDirPath) {\n continue;\n }\n\n // Backup all YAML files in the layer directory\n const files = await fs.readdir(layerDirPath);\n for (const file of files) {\n if (file.endsWith(\".yaml\") || file.endsWith(\".yml\")) {\n const filePath = path.join(layerDirPath, file);\n const fileContent = await readFile(filePath);\n const fileChecksum = createSha256Hash(\n createHash(\"sha256\").update(fileContent).digest(\"hex\")\n );\n\n await atomicWrite(path.join(layerBackupDir, file), fileContent);\n backupFiles.push({\n path: `layers/${layer.name}/${file}`,\n checksum: fileChecksum,\n size: fileContent.length,\n });\n }\n }\n }\n\n // Create the final immutable BackupManifest\n const backupManifest: BackupManifest = {\n files: backupFiles as ReadonlyArray<(typeof backupFiles)[0]>,\n timestamp,\n };\n\n // Write backup manifest for validation\n await atomicWrite(\n path.join(backupDir, \".backup-manifest.json\"),\n JSON.stringify(backupManifest, null, 2)\n );\n\n // Validate backup completeness\n await this.validateBackup(backupDir, backupManifest);\n\n return backupDir;\n } catch (error) {\n // Clean up incomplete backup with detailed error handling\n const cleanupError = await this.forceRemoveBackupDir(backupDir);\n\n if (cleanupError) {\n // This is CRITICAL - orphaned backups accumulate\n throw new Error(\n `Backup creation failed AND cleanup failed.\\n` +\n `Original error: ${getErrorMessage(error)}\\n` +\n `Cleanup error: ${cleanupError}\\n\\n` +\n `Orphaned backup directory: ${backupDir}\\n` +\n `This directory will accumulate and consume disk space.\\n\\n` +\n `REQUIRED ACTIONS:\\n` +\n ` 1. Manually remove: rm -rf \"${backupDir}\"\\n` +\n ` 2. Check disk space: df -h\\n` +\n ` 3. Check permissions: ls -la \"${path.dirname(backupDir)}\"\\n\\n` +\n `Cannot proceed with commit until this is resolved.`\n );\n }\n\n throw new Error(\n `Failed to create backup: ${getErrorMessage(error)}`\n );\n }\n }\n\n /**\n * Validate backup completeness and file integrity.\n * Verifies that all files in the manifest exist in the backup directory\n * with matching checksums and sizes.\n *\n * @param backupDir - Path to the backup directory\n * @param manifest - Backup manifest with file list and checksums\n * @throws Error if any file is missing, checksum mismatches, or size differs\n */\n private async validateBackup(backupDir: string, manifest: BackupManifest): Promise<void> {\n const { readFile, fileExists } = await import(\"../utils/file-io.js\");\n const { createHash } = await import(\"crypto\");\n\n for (const file of manifest.files) {\n const backupFilePath = path.join(backupDir, file.path);\n\n // Verify file exists\n if (!(await fileExists(backupFilePath))) {\n throw new Error(`Backup validation failed: ${file.path} not found in backup`);\n }\n\n // Verify checksum matches\n const content = await readFile(backupFilePath);\n const checksum = createHash(\"sha256\").update(content).digest(\"hex\");\n\n if (checksum !== file.checksum) {\n throw new Error(\n `Backup validation failed: ${file.path} checksum mismatch (expected ${file.checksum}, got ${checksum})`\n );\n }\n\n // Verify size matches\n if (content.length !== file.size) {\n throw new Error(\n `Backup validation failed: ${file.path} size mismatch (expected ${file.size}, got ${content.length})`\n );\n }\n }\n }\n\n /**\n * Validate backup integrity by checking manifest and file checksums.\n * This is called before suggesting manual restoration to ensure the backup is usable.\n * @param backupDir - Path to the backup directory\n * @returns Promise resolving to backup health status or throwing error with details\n * @throws Error with details about any integrity issues found\n */\n async validateBackupIntegrity(backupDir: string): Promise<{\n isValid: boolean;\n filesChecked: number;\n errors: string[];\n }> {\n const { readFile, fileExists } = await import(\"../utils/file-io.js\");\n const { createHash } = await import(\"crypto\");\n\n const errors: string[] = [];\n let filesChecked = 0;\n\n try {\n // Check if backup directory exists\n if (!(await fileExists(backupDir))) {\n throw new Error(`Backup directory not found: ${backupDir}`);\n }\n\n // Load and validate backup manifest\n const manifestPath = path.join(backupDir, \".backup-manifest.json\");\n if (!(await fileExists(manifestPath))) {\n throw new Error(`Backup manifest not found at ${manifestPath}`);\n }\n\n // Read manifest content first (can throw for parse errors)\n let rawManifest;\n try {\n const manifestContent = await readFile(manifestPath);\n if (!manifestContent || manifestContent.length === 0) {\n throw new Error(\"Backup manifest is empty\");\n }\n rawManifest = JSON.parse(manifestContent.toString());\n } catch (err) {\n // JSON parse errors should throw - these are critical errors\n throw new Error(\n `Failed to read backup manifest: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n\n // Validate manifest structure (can throw for missing required arrays)\n if (!rawManifest || typeof rawManifest !== \"object\") {\n throw new Error(\"Backup manifest must be an object\");\n }\n\n if (!rawManifest.files || !Array.isArray(rawManifest.files)) {\n throw new Error(\"Backup manifest must have a files array\");\n }\n\n // Validate each file entry in the manifest and collect errors gracefully\n for (let index = 0; index < rawManifest.files.length; index++) {\n const file = rawManifest.files[index];\n filesChecked++;\n\n // Validate file entry structure\n if (!file || typeof file !== \"object\") {\n errors.push(`Backup manifest file entry ${index} must be an object`);\n continue;\n }\n\n if (typeof file.path !== \"string\" || !file.path) {\n errors.push(`Backup manifest file entry ${index} must have a non-empty path string`);\n continue;\n }\n\n // Validate required metadata fields - these are validation errors, not critical\n if (typeof file.checksum !== \"string\") {\n errors.push(`Backup manifest file entry ${index} must have a checksum string`);\n continue;\n }\n\n if (typeof file.size !== \"number\" || file.size < 0) {\n errors.push(`Backup manifest file entry ${index} must have a non-negative size number`);\n continue;\n }\n\n // File validation: check file exists and integrity\n const backupFilePath = path.join(backupDir, file.path);\n\n // Check file exists\n if (!(await fileExists(backupFilePath))) {\n errors.push(`Missing: ${file.path}`);\n continue;\n }\n\n // Check checksum\n try {\n const content = await readFile(backupFilePath);\n const checksum = createHash(\"sha256\").update(content).digest(\"hex\");\n\n if (checksum !== file.checksum) {\n errors.push(\n `Checksum mismatch: ${file.path} (expected ${file.checksum}, got ${checksum})`\n );\n continue;\n }\n\n // Check size\n if (content.length !== file.size) {\n errors.push(\n `Size mismatch: ${file.path} (expected ${file.size}, got ${content.length})`\n );\n }\n } catch (err) {\n errors.push(\n `Failed to validate: ${file.path} (${err instanceof Error ? err.message : String(err)})`\n );\n }\n }\n\n return {\n isValid: errors.length === 0,\n filesChecked,\n errors,\n };\n } catch (err) {\n throw new Error(\n `Backup integrity check failed: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n /**\n * Clean up a backup directory after successful commit.\n * Removes the backup directory and all its contents to prevent disk space accumulation.\n *\n * @param backupDir - Path to the backup directory to remove\n *\n * @remarks\n * Failures during cleanup are logged with ERROR severity but do not throw.\n * Successful commits should not fail due to cleanup errors, but users need to\n * know about cleanup failures to avoid disk space accumulation.\n * Includes actionable guidance for common failure scenarios.\n */\n private async cleanupBackup(backupDir: string): Promise<void> {\n try {\n const { rm } = await import(\"node:fs/promises\");\n await rm(backupDir, { recursive: true, force: true });\n } catch (error) {\n // Log cleanup failures with ERROR severity - these mask real problems\n const errorMsg = getErrorMessage(error);\n const errorCode = (error as NodeJS.ErrnoException)?.code;\n\n let suggestions = \"\";\n\n // Handle ENOENT specially - backup already deleted is not an error\n if (errorCode === \"ENOENT\") {\n // Backup was already deleted - directory removal goal is achieved\n return;\n }\n\n // ENOSPC is CRITICAL - orphaned backups will accumulate and fill disk\n if (errorCode === \"ENOSPC\") {\n throw new Error(\n `Backup cleanup failed due to disk full.\\n` +\n `Original error: ${errorMsg}\\n\\n` +\n `Orphaned backup directory: ${backupDir}\\n` +\n `This directory will accumulate and consume disk space.\\n\\n` +\n `REQUIRED ACTIONS:\\n` +\n ` 1. Manually remove: rm -rf \"${backupDir}\"\\n` +\n ` 2. Free up disk space: df -h\\n` +\n ` 3. Check disk usage: du -sh ${path.dirname(backupDir)}/*\\n\\n` +\n `Cannot proceed with future commits until disk space is freed.`\n );\n }\n\n // Provide specific guidance based on error type\n if (errorCode === \"EACCES\") {\n suggestions =\n \"\\n\\n[PERMISSION ERROR] Cannot delete backup directory.\\n\" +\n `Directory: ${backupDir}\\n` +\n \"Check file permissions or try manual cleanup:\\n\" +\n `sudo rm -rf \"${backupDir}\"`;\n } else {\n suggestions =\n \"\\n\\n[ACTION REQUIRED] Backup cleanup failed due to file system error.\\n\" +\n `Directory: ${backupDir}\\n` +\n `Manual cleanup: rm -rf \"${backupDir}\"\\n` +\n \"Note: Backup directories accumulate over time and can fill disk space.\";\n }\n\n console.error(\n `[ERROR] Failed to clean up backup directory after commit: ${errorMsg}` + suggestions\n );\n }\n }\n\n /**\n * Force remove a backup directory with enhanced error handling.\n * Attempts multiple strategies to ensure removal even with permission issues.\n *\n * @param backupDir - Path to the backup directory to remove\n * @returns Error message if removal failed, null if successful\n *\n * @remarks\n * This is used during backup creation failures to ensure cleanup of partial state.\n * Returns error details instead of throwing so caller can log and continue.\n */\n private async forceRemoveBackupDir(backupDir: string): Promise<string | null> {\n const fs = await import(\"fs/promises\");\n\n try {\n // Try standard removal first\n await fs.rm(backupDir, { recursive: true, force: true });\n return null;\n } catch (error) {\n // If standard removal fails, try more aggressive approach\n try {\n // Accumulate removal failures for detailed error reporting\n const removalFailures: Array<{ path: string; error: string }> = [];\n\n // List directory contents and try to remove each item\n const entries = await fs.readdir(backupDir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = path.join(backupDir, entry.name);\n try {\n if (entry.isDirectory()) {\n await fs.rm(fullPath, { recursive: true, force: true });\n } else {\n await fs.unlink(fullPath);\n }\n } catch (itemError) {\n removalFailures.push({\n path: fullPath,\n error: itemError instanceof Error ? itemError.message : String(itemError),\n });\n }\n }\n\n // Try to remove the directory itself\n try {\n await fs.rmdir(backupDir);\n return null;\n } catch {\n // Directory may not be empty due to removal failures\n if (removalFailures.length > 0) {\n const failureDetails = removalFailures\n .map((f) => ` - ${f.path}: ${f.error}`)\n .join(\"\\n\");\n\n return (\n `Could not remove ${removalFailures.length} items from backup directory:\\n` +\n failureDetails +\n `\\n\\nManual cleanup required: rm -rf \"${backupDir}\"`\n );\n }\n\n return `Could not remove directory ${backupDir} - may contain leftover files`;\n }\n } catch (aggressiveError) {\n return `Backup cleanup failed: ${aggressiveError instanceof Error ? aggressiveError.message : String(aggressiveError)}`;\n }\n }\n }\n\n /**\n * Restore model state from backup after failed commit.\n * Restores manifest first (critical), then all layer files with defensive error handling.\n * Provides detailed reporting of restoration progress and failures.\n *\n * @param model - The model to restore\n * @param backupDir - Path to the backup directory\n * @throws Error with detailed restoration summary if restoration fails\n *\n * @remarks\n * Restoration strategy:\n * 1. Restore manifest first (critical for model integrity)\n * 2. Restore all layer files with per-file error handling\n * 3. Continue restoring remaining files even if some fail\n * 4. Throw detailed error if critical files fail to restore\n * Both manifest and layer restoration failures are critical and will throw errors.\n */\n private async restoreModel(model: Model, backupDir: string): Promise<void> {\n const { readFile, fileExists, atomicWrite } = await import(\"../utils/file-io.js\");\n\n const restoredFiles: string[] = [];\n const failedFiles: Array<{ path: string; error: string; isCritical: boolean }> = [];\n const criticalLayers = new Set<string>(); // Track critical layer failures\n\n try {\n // Restore manifest first (most critical) - try both .yaml and .json\n let backupManifestFile = path.join(backupDir, \"manifest.yaml\");\n let manifestDestPath = path.join(\n model.rootPath,\n \"documentation-robotics\",\n \"model\",\n \"manifest.yaml\"\n );\n\n if (!(await fileExists(backupManifestFile))) {\n backupManifestFile = path.join(backupDir, \"manifest.json\");\n manifestDestPath = path.join(model.rootPath, \"documentation-robotics\", \"manifest.json\");\n }\n\n try {\n if (!(await fileExists(backupManifestFile))) {\n throw new Error(\"Backup manifest file not found (tried .yaml and .json)\");\n }\n\n const manifestContent = await readFile(backupManifestFile);\n await atomicWrite(manifestDestPath, manifestContent);\n restoredFiles.push(path.basename(backupManifestFile));\n } catch (error) {\n failedFiles.push({\n path: path.basename(backupManifestFile),\n error: getErrorMessage(error),\n isCritical: true,\n });\n // Manifest is critical - throw immediately\n throw new Error(\n `Failed to restore ${path.basename(backupManifestFile)}: ${getErrorMessage(error)}`\n );\n }\n\n // Restore all layer files with defensive error handling\n const backupLayersDir = path.join(backupDir, \"layers\");\n const fs = await import(\"fs/promises\");\n\n for (const layer of model.layers.values()) {\n const backupLayerDir = path.join(backupLayersDir, layer.name);\n\n try {\n if (!(await fileExists(backupLayerDir))) {\n // CRITICAL: Layer directory missing in backup - this is a data loss scenario\n const errorMsg = `Layer '${layer.name}' not found in backup (backup incomplete or corrupted)`;\n failedFiles.push({\n path: `layers/${layer.name}`,\n error: errorMsg,\n isCritical: true,\n });\n criticalLayers.add(layer.name);\n continue;\n }\n\n // Find the correct destination directory for this layer\n const layerDirPath = await this.findLayerDirectory(model.rootPath, layer.name);\n\n if (!layerDirPath) {\n // CRITICAL: Cannot find destination for layer - filesystem issue or permission problem\n const errorMsg = `Destination directory not found for layer '${layer.name}' (permissions issue or directory structure changed)`;\n failedFiles.push({\n path: `layers/${layer.name}`,\n error: errorMsg,\n isCritical: true,\n });\n criticalLayers.add(layer.name);\n continue;\n }\n\n // Restore all YAML files from backup\n const backupFiles = await fs.readdir(backupLayerDir);\n let filesRestoredInLayer = 0;\n\n for (const file of backupFiles) {\n if (file.endsWith(\".yaml\") || file.endsWith(\".yml\")) {\n const backupFilePath = path.join(backupLayerDir, file);\n const destFilePath = path.join(layerDirPath, file);\n try {\n const fileContent = await readFile(backupFilePath);\n await atomicWrite(destFilePath, fileContent);\n restoredFiles.push(`layers/${layer.name}/${file}`);\n filesRestoredInLayer++;\n } catch (fileError) {\n // Individual file restoration failure - still critical for data integrity\n const errorMsg = fileError instanceof Error ? fileError.message : String(fileError);\n\n // Log immediately with ERROR severity\n console.error(ansis.red(`\u2717 Failed to restore ${layer.name}/${file}: ${errorMsg}`));\n\n failedFiles.push({\n path: `layers/${layer.name}/${file}`,\n error: errorMsg,\n isCritical: true,\n });\n criticalLayers.add(layer.name);\n }\n }\n }\n } catch (error) {\n // Unexpected error during layer restoration - treat as critical\n const errorMessage = getErrorMessage(error);\n failedFiles.push({\n path: `layers/${layer.name}`,\n error: errorMessage,\n isCritical: true,\n });\n criticalLayers.add(layer.name);\n }\n }\n\n // Reload model layers to reflect restored state\n model.layers.clear();\n\n // CRITICAL: If any critical layers failed to restore, throw error immediately\n if (criticalLayers.size > 0) {\n const failedLayersList = Array.from(criticalLayers).join(\", \");\n const detailedErrors = failedFiles.map((f) => ` - ${f.path}: ${f.error}`).join(\"\\n\");\n\n const errorSummary =\n `Model restoration FAILED - ${failedFiles.length} critical layer(s) could not be restored:\\n` +\n ` Failed layers: ${failedLayersList}\\n` +\n `\\n` +\n `Detailed errors:\\n${detailedErrors}\\n` +\n `\\n` +\n `Successfully restored: ${restoredFiles.length} files\\n` +\n `Failed to restore: ${failedFiles.length} files\\n` +\n `\\n` +\n `Possible causes:\\n` +\n ` 1. Backup directory corrupted or incomplete\\n` +\n ` 2. Insufficient permissions to write to model directory\\n` +\n ` 3. Disk full during restoration\\n` +\n ` 4. Layer directory structure changed since backup\\n` +\n `\\n` +\n `Backup location: ${backupDir}\\n` +\n `\\n` +\n `ACTION REQUIRED: Manually restore from backup or contact support.`;\n\n throw new Error(errorSummary);\n }\n } catch (error) {\n // Critical failure during restore\n const errorMessage = getErrorMessage(error);\n const summary =\n `Model restoration failed: ${errorMessage}\\n` +\n `\\n` +\n `Restoration progress:\\n` +\n ` Successfully restored: ${restoredFiles.length} files\\n` +\n ` Failed to restore: ${failedFiles.length} files (all critical)\\n` +\n `\\n` +\n `Restored files:\\n${restoredFiles.map((f) => ` \u2713 ${f}`).join(\"\\n\")}\\n` +\n `\\n` +\n `Failed files:\\n${failedFiles.map((f) => ` ${f.isCritical ? \"\u2717 [CRITICAL]\" : \"\u26A0 [WARNING]\"} ${f.path}: ${f.error}`).join(\"\\n\")}\\n` +\n `\\n` +\n `Backup location: ${backupDir}`;\n\n throw new Error(summary);\n }\n }\n\n /**\n * Find the directory path for a layer in the model.\n * Uses same discovery logic as Model.loadLayer to locate layer directories.\n * Handles naming convention: `{PREFIX}_{layerName}` where PREFIX is numeric (01_motivation, etc).\n *\n * @param rootPath - Root path of the model\n * @param layerName - Name of the layer to find\n * @returns Path to the layer directory or null if not found\n */\n private async findLayerDirectory(rootPath: string, layerName: string): Promise<string | null> {\n const fs = await import(\"fs/promises\");\n const modelDir = path.join(rootPath, \"documentation-robotics\", \"model\");\n\n try {\n const entries = await fs.readdir(modelDir, { withFileTypes: true });\n const layerDir = entries.find(\n (e) =>\n e.isDirectory() && e.name.match(/^\\d{2}_/) && e.name.replace(/^\\d{2}_/, \"\") === layerName\n );\n\n if (layerDir) {\n return path.join(modelDir, layerDir.name);\n }\n\n // Layer directory legitimately not found\n return null;\n } catch (error) {\n const errorCode = (error as NodeJS.ErrnoException)?.code;\n\n if (errorCode === \"ENOENT\") {\n // Model directory doesn't exist - this might be OK during restore\n return null;\n }\n\n // All other errors are CRITICAL during restore operations\n throw new Error(\n `Cannot access model directory for layer '${layerName}': ${getErrorMessage(error)}\\n` +\n `Location: ${modelDir}\\n` +\n `Error code: ${errorCode}\\n` +\n `This indicates a filesystem or permissions issue. Check:\\n` +\n ` 1. Directory permissions: ls -la ${path.dirname(modelDir)}\\n` +\n ` 2. Disk health: df -h\\n` +\n ` 3. Filesystem errors: dmesg | tail`\n );\n }\n }\n\n /**\n * Generate a unique changeset ID.\n * Combines timestamp and random suffix to ensure uniqueness across concurrent operations.\n *\n * @returns A unique changeset ID in format `changeset-{timestamp}-{random}`\n */\n private generateChangesetId(): string {\n return `changeset-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["fileExists"]
|
|
7
7
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { readStagedSourceReference, stagedSourceReference } from "./staged-source-reference.js";
|
|
1
2
|
import { Layer as LayerClass } from "./layer.js";
|
|
2
3
|
import { Element as ElementClass } from "./element.js";
|
|
4
|
+
import { withSequenceNumbers } from "./changeset.js";
|
|
3
5
|
import { StagedChangesetStorage } from "./staged-changeset-storage.js";
|
|
4
6
|
class VirtualProjectionEngine {
|
|
5
7
|
stagingAreaManager;
|
|
@@ -60,7 +62,10 @@ class VirtualProjectionEngine {
|
|
|
60
62
|
},
|
|
61
63
|
references: Array.isArray(projectedData.references) ? projectedData.references : baseElement.references,
|
|
62
64
|
relationships: Array.isArray(projectedData.relationships) ? projectedData.relationships : baseElement.relationships,
|
|
63
|
-
source_reference:
|
|
65
|
+
source_reference: (() => {
|
|
66
|
+
const staged = readStagedSourceReference(projectedData);
|
|
67
|
+
return staged.present ? staged.value : baseElement.source_reference;
|
|
68
|
+
})(),
|
|
64
69
|
metadata: baseElement.metadata
|
|
65
70
|
};
|
|
66
71
|
return new ElementClass(mergedData);
|
|
@@ -76,7 +81,7 @@ class VirtualProjectionEngine {
|
|
|
76
81
|
attributes: typeof projectedData.attributes == "object" ? projectedData.attributes : {},
|
|
77
82
|
references: Array.isArray(projectedData.references) ? projectedData.references : [],
|
|
78
83
|
relationships: Array.isArray(projectedData.relationships) ? projectedData.relationships : [],
|
|
79
|
-
source_reference: projectedData
|
|
84
|
+
source_reference: stagedSourceReference(projectedData),
|
|
80
85
|
metadata: projectedData.metadata,
|
|
81
86
|
layer: lastChange.layerName
|
|
82
87
|
});
|
|
@@ -112,7 +117,7 @@ class VirtualProjectionEngine {
|
|
|
112
117
|
const baseLayer = await baseModel.getBaseLayer(layerName), projectedLayer = baseLayer ? this.cloneLayer(baseLayer) : new LayerClass(layerName), changeset = await this.stagingAreaManager.load(changesetId);
|
|
113
118
|
if (!changeset)
|
|
114
119
|
throw new Error(`Changeset '${changesetId}' not found`);
|
|
115
|
-
const layerChanges = changeset.changes.filter((c) => c.layerName === layerName).sort((a, b) => a.sequenceNumber - b.sequenceNumber);
|
|
120
|
+
const layerChanges = withSequenceNumbers(changeset.changes).filter((c) => c.layerName === layerName).sort((a, b) => a.sequenceNumber - b.sequenceNumber);
|
|
116
121
|
for (const change of layerChanges)
|
|
117
122
|
switch (change.type) {
|
|
118
123
|
case "add":
|
|
@@ -128,7 +133,7 @@ class VirtualProjectionEngine {
|
|
|
128
133
|
attributes: typeof change.after.attributes == "object" ? change.after.attributes : {},
|
|
129
134
|
references: Array.isArray(change.after.references) ? change.after.references : [],
|
|
130
135
|
relationships: Array.isArray(change.after.relationships) ? change.after.relationships : [],
|
|
131
|
-
source_reference: change.after
|
|
136
|
+
source_reference: stagedSourceReference(change.after),
|
|
132
137
|
metadata: change.after.metadata,
|
|
133
138
|
layer: layerName
|
|
134
139
|
});
|
|
@@ -154,7 +159,10 @@ class VirtualProjectionEngine {
|
|
|
154
159
|
references: Array.isArray(change.after.references) ? change.after.references : existing.references,
|
|
155
160
|
relationships: Array.isArray(change.after.relationships) ? change.after.relationships : existing.relationships,
|
|
156
161
|
layer: layerName,
|
|
157
|
-
source_reference:
|
|
162
|
+
source_reference: (() => {
|
|
163
|
+
const staged = readStagedSourceReference(change.after);
|
|
164
|
+
return staged.present ? staged.value : existing.source_reference;
|
|
165
|
+
})(),
|
|
158
166
|
metadata: existing.metadata
|
|
159
167
|
});
|
|
160
168
|
projectedLayer.updateElement(updatedElement);
|