@documentation-robotics/cli 0.1.0 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +466 -693
- package/dist/analysis/data-model-analyzer.js +189 -0
- package/dist/analysis/data-model-analyzer.js.map +7 -0
- package/dist/analysis/index.js +3 -0
- package/dist/analysis/index.js.map +7 -0
- package/dist/analysis/relationship-classifier.js +181 -0
- package/dist/analysis/relationship-classifier.js.map +7 -0
- package/dist/analyzers/base-analyzer.js +1 -0
- package/dist/analyzers/base-analyzer.js.map +7 -0
- package/dist/analyzers/call-graph-utils.js +39 -0
- package/dist/analyzers/call-graph-utils.js.map +7 -0
- package/dist/analyzers/cbm-analyzer.js +1205 -0
- package/dist/analyzers/cbm-analyzer.js.map +7 -0
- package/dist/analyzers/heuristic-utils.js +55 -0
- package/dist/analyzers/heuristic-utils.js.map +7 -0
- package/dist/analyzers/mapping-loader.js +146 -0
- package/dist/analyzers/mapping-loader.js.map +7 -0
- package/dist/analyzers/registry.js +124 -0
- package/dist/analyzers/registry.js.map +7 -0
- package/dist/analyzers/session-state.js +176 -0
- package/dist/analyzers/session-state.js.map +7 -0
- package/dist/analyzers/stdio-client.js +208 -0
- package/dist/analyzers/stdio-client.js.map +7 -0
- package/dist/analyzers/types.js +15 -0
- package/dist/analyzers/types.js.map +7 -0
- package/dist/analyzers/verify-engine.js +209 -0
- package/dist/analyzers/verify-engine.js.map +7 -0
- package/dist/analyzers/verify-ignore.js +130 -0
- package/dist/analyzers/verify-ignore.js.map +7 -0
- package/dist/analyzers/version.js +8 -0
- package/dist/analyzers/version.js.map +7 -0
- package/dist/audit/ai/runner.js +41 -0
- package/dist/audit/ai/runner.js.map +7 -0
- package/dist/audit/differential-analyzer.js +183 -0
- package/dist/audit/differential-analyzer.js.map +7 -0
- package/dist/audit/index.js +32 -0
- package/dist/audit/index.js.map +7 -0
- package/dist/audit/nodes/ai/evaluator.js +49 -0
- package/dist/audit/nodes/ai/evaluator.js.map +7 -0
- package/dist/audit/nodes/ai/parser.js +55 -0
- package/dist/audit/nodes/ai/parser.js.map +7 -0
- package/dist/audit/nodes/ai/prompts.js +103 -0
- package/dist/audit/nodes/ai/prompts.js.map +7 -0
- package/dist/audit/nodes/analysis/node-completeness-checker.js +32 -0
- package/dist/audit/nodes/analysis/node-completeness-checker.js.map +7 -0
- package/dist/audit/nodes/analysis/node-definition-analyzer.js +47 -0
- package/dist/audit/nodes/analysis/node-definition-analyzer.js.map +7 -0
- package/dist/audit/nodes/analysis/node-overlap-detector.js +79 -0
- package/dist/audit/nodes/analysis/node-overlap-detector.js.map +7 -0
- package/dist/audit/nodes/model/orchestrator.js +82 -0
- package/dist/audit/nodes/model/orchestrator.js.map +7 -0
- package/dist/audit/nodes/spec/loader.js +116 -0
- package/dist/audit/nodes/spec/loader.js.map +7 -0
- package/dist/audit/nodes/spec/orchestrator.js +68 -0
- package/dist/audit/nodes/spec/orchestrator.js.map +7 -0
- package/dist/audit/nodes/types.js +1 -0
- package/dist/audit/nodes/types.js.map +7 -0
- package/dist/audit/pipeline/index.js +5 -0
- package/dist/audit/pipeline/index.js.map +7 -0
- package/dist/audit/pipeline/pipeline-orchestrator.js +218 -0
- package/dist/audit/pipeline/pipeline-orchestrator.js.map +7 -0
- package/dist/audit/relationships/ai/evaluator.js +235 -0
- package/dist/audit/relationships/ai/evaluator.js.map +7 -0
- package/dist/audit/relationships/ai/parser.js +197 -0
- package/dist/audit/relationships/ai/parser.js.map +7 -0
- package/dist/audit/relationships/ai/prompts.js +274 -0
- package/dist/audit/relationships/ai/prompts.js.map +7 -0
- package/dist/audit/relationships/analysis/balance-assessor.js +115 -0
- package/dist/audit/relationships/analysis/balance-assessor.js.map +7 -0
- package/dist/audit/relationships/analysis/coverage-analyzer.js +99 -0
- package/dist/audit/relationships/analysis/coverage-analyzer.js.map +7 -0
- package/dist/audit/relationships/analysis/duplicate-detector.js +95 -0
- package/dist/audit/relationships/analysis/duplicate-detector.js.map +7 -0
- package/dist/audit/relationships/analysis/gap-analyzer.js +99 -0
- package/dist/audit/relationships/analysis/gap-analyzer.js.map +7 -0
- package/dist/audit/relationships/analysis/layer-templates.js +294 -0
- package/dist/audit/relationships/analysis/layer-templates.js.map +7 -0
- package/dist/audit/relationships/graph/connectivity.js +129 -0
- package/dist/audit/relationships/graph/connectivity.js.map +7 -0
- package/dist/audit/relationships/graph/relationship-graph.js +179 -0
- package/dist/audit/relationships/graph/relationship-graph.js.map +7 -0
- package/dist/audit/relationships/model/balance-assessor.js +49 -0
- package/dist/audit/relationships/model/balance-assessor.js.map +7 -0
- package/dist/audit/relationships/model/coverage-analyzer.js +34 -0
- package/dist/audit/relationships/model/coverage-analyzer.js.map +7 -0
- package/dist/audit/relationships/model/duplicate-detector.js +68 -0
- package/dist/audit/relationships/model/duplicate-detector.js.map +7 -0
- package/dist/audit/relationships/model/gap-analyzer.js +41 -0
- package/dist/audit/relationships/model/gap-analyzer.js.map +7 -0
- package/dist/audit/relationships/model/orchestrator.js +126 -0
- package/dist/audit/relationships/model/orchestrator.js.map +7 -0
- package/dist/audit/relationships/spec/orchestrator.js +157 -0
- package/dist/audit/relationships/spec/orchestrator.js.map +7 -0
- package/dist/audit/reports/index.js +5 -0
- package/dist/audit/reports/index.js.map +7 -0
- package/dist/audit/reports/report-generator.js +118 -0
- package/dist/audit/reports/report-generator.js.map +7 -0
- package/dist/audit/snapshot-storage.js +163 -0
- package/dist/audit/snapshot-storage.js.map +7 -0
- package/dist/audit/types.js +21 -0
- package/dist/audit/types.js.map +7 -0
- package/dist/cli.js +223 -92
- package/dist/cli.js.map +3 -3
- package/dist/coding-agents/agents/claude-code.js +147 -0
- package/dist/coding-agents/agents/claude-code.js.map +7 -0
- package/dist/coding-agents/agents/index.js +5 -0
- package/dist/coding-agents/agents/index.js.map +7 -0
- package/dist/coding-agents/agents/types.js +1 -0
- package/dist/coding-agents/agents/types.js.map +7 -0
- package/dist/coding-agents/base-chat-client.js +45 -0
- package/dist/coding-agents/base-chat-client.js.map +7 -0
- package/dist/coding-agents/chat-utils.js +23 -0
- package/dist/coding-agents/chat-utils.js.map +7 -0
- package/dist/coding-agents/claude-code-client.js +246 -0
- package/dist/coding-agents/claude-code-client.js.map +7 -0
- package/dist/coding-agents/copilot-client.js +209 -0
- package/dist/coding-agents/copilot-client.js.map +7 -0
- package/dist/commands/add.js +95 -33
- package/dist/commands/add.js.map +2 -2
- package/dist/commands/analyzer.js +511 -0
- package/dist/commands/analyzer.js.map +7 -0
- package/dist/commands/audit-diff.js +49 -0
- package/dist/commands/audit-diff.js.map +7 -0
- package/dist/commands/audit-snapshots.js +62 -0
- package/dist/commands/audit-snapshots.js.map +7 -0
- package/dist/commands/audit.js +81 -0
- package/dist/commands/audit.js.map +7 -0
- package/dist/commands/catalog.js +248 -0
- package/dist/commands/catalog.js.map +7 -0
- package/dist/commands/changeset.js +577 -66
- package/dist/commands/changeset.js.map +2 -2
- package/dist/commands/chat.js +106 -83
- package/dist/commands/chat.js.map +2 -2
- package/dist/commands/claude.js +74 -0
- package/dist/commands/claude.js.map +7 -0
- package/dist/commands/conformance.js +71 -68
- package/dist/commands/conformance.js.map +2 -2
- package/dist/commands/copilot.js +75 -0
- package/dist/commands/copilot.js.map +7 -0
- package/dist/commands/delete.js +125 -11
- package/dist/commands/delete.js.map +3 -3
- package/dist/commands/discover-logic.js +44 -0
- package/dist/commands/discover-logic.js.map +7 -0
- package/dist/commands/docs.js +87 -0
- package/dist/commands/docs.js.map +7 -0
- package/dist/commands/element.js +14 -20
- package/dist/commands/element.js.map +2 -2
- package/dist/commands/export.js +13 -3
- package/dist/commands/export.js.map +2 -2
- package/dist/commands/import.js +51 -0
- package/dist/commands/import.js.map +7 -0
- package/dist/commands/info.js +30 -24
- package/dist/commands/info.js.map +2 -2
- package/dist/commands/init.js +27 -11
- package/dist/commands/init.js.map +2 -2
- package/dist/commands/list.js +63 -13
- package/dist/commands/list.js.map +3 -3
- package/dist/commands/relationship.js +225 -37
- package/dist/commands/relationship.js.map +2 -2
- package/dist/commands/report.js +54 -0
- package/dist/commands/report.js.map +7 -0
- package/dist/commands/reports.js +45 -0
- package/dist/commands/reports.js.map +7 -0
- package/dist/commands/schema.js +135 -0
- package/dist/commands/schema.js.map +7 -0
- package/dist/commands/search.js +40 -12
- package/dist/commands/search.js.map +2 -2
- package/dist/commands/show.js +58 -27
- package/dist/commands/show.js.map +2 -2
- package/dist/commands/stats.js +35 -0
- package/dist/commands/stats.js.map +7 -0
- package/dist/commands/trace.js +39 -16
- package/dist/commands/trace.js.map +2 -2
- package/dist/commands/update.js +141 -14
- package/dist/commands/update.js.map +2 -2
- package/dist/commands/upgrade.js +210 -46
- package/dist/commands/upgrade.js.map +3 -3
- package/dist/commands/validate.js +142 -23
- package/dist/commands/validate.js.map +3 -3
- package/dist/commands/version.js +8 -12
- package/dist/commands/version.js.map +2 -2
- package/dist/commands/visualize.js +162 -25
- package/dist/commands/visualize.js.map +2 -2
- package/dist/core/base-snapshot-manager.js +84 -0
- package/dist/core/base-snapshot-manager.js.map +7 -0
- package/dist/core/changeset-exporter.js +305 -0
- package/dist/core/changeset-exporter.js.map +7 -0
- package/dist/core/changeset-validator.js +41 -0
- package/dist/core/changeset-validator.js.map +7 -0
- package/dist/core/changeset.js +88 -173
- package/dist/core/changeset.js.map +2 -2
- package/dist/core/dependency-tracker.js +4 -2
- package/dist/core/dependency-tracker.js.map +2 -2
- package/dist/core/element.js +98 -27
- package/dist/core/element.js.map +2 -2
- package/dist/core/golden-copy-cache.js +290 -0
- package/dist/core/golden-copy-cache.js.map +7 -0
- package/dist/core/graph-model.js +266 -0
- package/dist/core/graph-model.js.map +7 -0
- package/dist/core/layer.js +159 -16
- package/dist/core/layer.js.map +2 -2
- package/dist/core/layers.js +32 -0
- package/dist/core/layers.js.map +7 -0
- package/dist/core/manifest.js +32 -11
- package/dist/core/manifest.js.map +2 -2
- package/dist/core/migration-registry.js +56 -1
- package/dist/core/migration-registry.js.map +2 -2
- package/dist/core/model.js +424 -217
- package/dist/core/model.js.map +3 -3
- package/dist/core/mutation-handler.js +179 -0
- package/dist/core/mutation-handler.js.map +7 -0
- package/dist/core/reference-registry.js +29 -21
- package/dist/core/reference-registry.js.map +2 -2
- package/dist/core/relationship-catalog.js +189 -0
- package/dist/core/relationship-catalog.js.map +7 -0
- package/dist/core/relationship-registry.js.map +2 -2
- package/dist/core/relationships.js.map +1 -1
- package/dist/core/report-data-model.js +285 -0
- package/dist/core/report-data-model.js.map +7 -0
- package/dist/core/spec-data-service.js +185 -0
- package/dist/core/spec-data-service.js.map +7 -0
- package/dist/core/spec-loader-types.js +1 -0
- package/dist/core/spec-loader-types.js.map +7 -0
- package/dist/core/spec-loader.js +234 -0
- package/dist/core/spec-loader.js.map +7 -0
- package/dist/core/staged-changeset-storage.js +282 -0
- package/dist/core/staged-changeset-storage.js.map +7 -0
- package/dist/core/staging-area.js +1002 -0
- package/dist/core/staging-area.js.map +7 -0
- package/dist/core/stats-collector.js +169 -0
- package/dist/core/stats-collector.js.map +7 -0
- package/dist/core/virtual-projection.js +443 -0
- package/dist/core/virtual-projection.js.map +7 -0
- package/dist/export/archimate-exporter.js +99 -60
- package/dist/export/archimate-exporter.js.map +2 -2
- package/dist/export/audit-formatters.js +479 -0
- package/dist/export/audit-formatters.js.map +7 -0
- package/dist/export/diff-formatters.js +99 -0
- package/dist/export/diff-formatters.js.map +7 -0
- package/dist/export/export-manager.js.map +2 -2
- package/dist/export/graphml-exporter.js +44 -27
- package/dist/export/graphml-exporter.js.map +2 -2
- package/dist/export/index.js +4 -0
- package/dist/export/index.js.map +2 -2
- package/dist/export/json-schema-exporter.js +55 -42
- package/dist/export/json-schema-exporter.js.map +2 -2
- package/dist/export/markdown-exporter.js +92 -70
- package/dist/export/markdown-exporter.js.map +2 -2
- package/dist/export/markdown-generator.js +248 -0
- package/dist/export/markdown-generator.js.map +7 -0
- package/dist/export/markdown-utils.js +30 -0
- package/dist/export/markdown-utils.js.map +7 -0
- package/dist/export/mermaid-markdown-exporter.js +37 -0
- package/dist/export/mermaid-markdown-exporter.js.map +7 -0
- package/dist/export/openapi-exporter.js +119 -71
- package/dist/export/openapi-exporter.js.map +2 -2
- package/dist/export/plantuml-exporter.js +104 -39
- package/dist/export/plantuml-exporter.js.map +2 -2
- package/dist/export/report-formatters.js +111 -0
- package/dist/export/report-formatters.js.map +7 -0
- package/dist/export/stats-formatters.js +98 -0
- package/dist/export/stats-formatters.js.map +7 -0
- package/dist/export/types.js +2 -14
- package/dist/export/types.js.map +2 -2
- package/dist/export/verify-formatters.js +153 -0
- package/dist/export/verify-formatters.js.map +7 -0
- package/dist/generated/compiled-validators.js +17 -0
- package/dist/generated/compiled-validators.js.map +7 -0
- package/dist/generated/index.js +31 -0
- package/dist/generated/index.js.map +7 -0
- package/dist/generated/layer-registry.js +190 -0
- package/dist/generated/layer-registry.js.map +7 -0
- package/dist/generated/layer-types.js +7 -0
- package/dist/generated/layer-types.js.map +7 -0
- package/dist/generated/node-types.js +1719 -0
- package/dist/generated/node-types.js.map +7 -0
- package/dist/generated/relationship-index.js +14552 -0
- package/dist/generated/relationship-index.js.map +7 -0
- package/dist/import/archimate-importer.js +166 -0
- package/dist/import/archimate-importer.js.map +7 -0
- package/dist/import/index.js +47 -0
- package/dist/import/index.js.map +7 -0
- package/dist/import/openapi-importer.js +143 -0
- package/dist/import/openapi-importer.js.map +7 -0
- package/dist/import/types.js +1 -0
- package/dist/import/types.js.map +7 -0
- package/dist/integrations/base-manager.js +311 -0
- package/dist/integrations/base-manager.js.map +7 -0
- package/dist/integrations/claude-manager.js +360 -0
- package/dist/integrations/claude-manager.js.map +7 -0
- package/dist/integrations/claude_code/agents/dr-advisor.md +253 -0
- package/dist/integrations/claude_code/agents/dr-architect.md +278 -0
- package/dist/integrations/claude_code/agents/dr-extractor.md +286 -0
- package/dist/integrations/claude_code/commands/dr-changeset.md +657 -0
- package/dist/integrations/claude_code/commands/dr-design.md +373 -0
- package/dist/integrations/claude_code/commands/dr-info.md +132 -0
- package/dist/integrations/claude_code/commands/dr-init.md +251 -0
- package/dist/integrations/claude_code/commands/dr-map.md +936 -0
- package/dist/integrations/claude_code/commands/dr-model.md +493 -0
- package/dist/integrations/claude_code/commands/dr-relate.md +814 -0
- package/dist/integrations/claude_code/commands/dr-sync.md +279 -0
- package/dist/integrations/claude_code/commands/dr-validate.md +654 -0
- package/dist/integrations/claude_code/commands/dr-verify.md +559 -0
- package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +903 -0
- package/dist/integrations/claude_code/docs/QUICK_START.md +271 -0
- package/dist/integrations/claude_code/docs/README.md +347 -0
- package/dist/integrations/claude_code/skills/dr_01_motivation_layer/SKILL.md +550 -0
- package/dist/integrations/claude_code/skills/dr_02_business_layer/SKILL.md +620 -0
- package/dist/integrations/claude_code/skills/dr_03_security_layer/SKILL.md +642 -0
- package/dist/integrations/claude_code/skills/dr_04_application_layer/SKILL.md +911 -0
- package/dist/integrations/claude_code/skills/dr_05_technology_layer/SKILL.md +836 -0
- package/dist/integrations/claude_code/skills/dr_06_api_layer/SKILL.md +933 -0
- package/dist/integrations/claude_code/skills/dr_07_data_model_layer/SKILL.md +210 -0
- package/dist/integrations/claude_code/skills/dr_08_dataset_layer/SKILL.md +319 -0
- package/dist/integrations/claude_code/skills/dr_09_ux_layer/SKILL.md +319 -0
- package/dist/integrations/claude_code/skills/dr_10_navigation_layer/SKILL.md +318 -0
- package/dist/integrations/claude_code/skills/dr_11_apm_layer/SKILL.md +273 -0
- package/dist/integrations/claude_code/skills/dr_12_testing_layer/SKILL.md +273 -0
- package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +187 -0
- package/dist/integrations/claude_code/skills/dr_codebase_memory/SKILL.md +590 -0
- package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +295 -0
- package/dist/integrations/copilot-manager.js +328 -0
- package/dist/integrations/copilot-manager.js.map +7 -0
- package/dist/integrations/github_copilot/agents/dr-advisor.md +243 -0
- package/dist/integrations/github_copilot/agents/dr-architect.md +1567 -0
- package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +491 -0
- package/dist/integrations/github_copilot/skills/dr_02_business_layer/SKILL.md +539 -0
- package/dist/integrations/github_copilot/skills/dr_03_security_layer/SKILL.md +667 -0
- package/dist/integrations/github_copilot/skills/dr_04_application_layer/SKILL.md +670 -0
- package/dist/integrations/github_copilot/skills/dr_05_technology_layer/SKILL.md +660 -0
- package/dist/integrations/github_copilot/skills/dr_06_api_layer/SKILL.md +855 -0
- package/dist/integrations/github_copilot/skills/dr_07_data_model_layer/SKILL.md +171 -0
- package/dist/integrations/github_copilot/skills/dr_08_dataset_layer/SKILL.md +216 -0
- package/dist/integrations/github_copilot/skills/dr_09_ux_layer/SKILL.md +230 -0
- package/dist/integrations/github_copilot/skills/dr_10_navigation_layer/SKILL.md +228 -0
- package/dist/integrations/github_copilot/skills/dr_11_apm_layer/SKILL.md +234 -0
- package/dist/integrations/github_copilot/skills/dr_12_testing_layer/SKILL.md +230 -0
- package/dist/integrations/github_copilot/skills/dr_changeset_reviewer/SKILL.md +187 -0
- package/dist/integrations/hash-utils.js +51 -0
- package/dist/integrations/hash-utils.js.map +7 -0
- package/dist/integrations/types.js +16 -0
- package/dist/integrations/types.js.map +7 -0
- package/dist/reports/model-layer-report-generator.js +278 -0
- package/dist/reports/model-layer-report-generator.js.map +7 -0
- package/dist/reports/model-report-data.js +57 -0
- package/dist/reports/model-report-data.js.map +7 -0
- package/dist/reports/model-report-orchestrator.js +103 -0
- package/dist/reports/model-report-orchestrator.js.map +7 -0
- package/dist/schemas/bundled/analyzers/cbm.json +595 -0
- package/dist/schemas/bundled/analyzers/manifest.json +9 -0
- package/dist/schemas/bundled/api.json +3411 -0
- package/dist/schemas/bundled/apm.json +2371 -0
- package/dist/schemas/bundled/application.json +1253 -0
- package/dist/schemas/bundled/base.json +1622 -0
- package/dist/schemas/bundled/business.json +1279 -0
- package/dist/schemas/bundled/data-model.json +1298 -0
- package/dist/schemas/bundled/data-store.json +2025 -0
- package/dist/schemas/bundled/manifest.json +93 -0
- package/dist/schemas/bundled/motivation.json +1360 -0
- package/dist/schemas/bundled/navigation.json +1874 -0
- package/dist/schemas/bundled/security.json +3488 -0
- package/dist/schemas/bundled/technology.json +2067 -0
- package/dist/schemas/bundled/testing.json +3120 -0
- package/dist/schemas/bundled/ux.json +3592 -0
- package/dist/server/schemas.js +202 -0
- package/dist/server/schemas.js.map +7 -0
- package/dist/server/server.js +1336 -252
- package/dist/server/server.js.map +3 -3
- package/dist/server/telemetry-middleware.js +22 -15
- package/dist/server/telemetry-middleware.js.map +2 -2
- package/dist/server-entry.js +65 -0
- package/dist/server-entry.js.map +7 -0
- package/dist/telemetry/config.js +89 -0
- package/dist/telemetry/config.js.map +7 -0
- package/dist/telemetry/console-interceptor.js.map +1 -1
- package/dist/telemetry/detect.js +24 -0
- package/dist/telemetry/detect.js.map +7 -0
- package/dist/telemetry/fetch-otlp-exporter.js +112 -0
- package/dist/telemetry/fetch-otlp-exporter.js.map +7 -0
- package/dist/telemetry/index.js +153 -3
- package/dist/telemetry/index.js.map +2 -2
- package/dist/telemetry/resilient-exporter.js +22 -8
- package/dist/telemetry/resilient-exporter.js.map +2 -2
- package/dist/telemetry/resilient-log-exporter.js +17 -8
- package/dist/telemetry/resilient-log-exporter.js.map +2 -2
- package/dist/telemetry/test-instrumentation.js.map +2 -2
- package/dist/types/index.js +84 -0
- package/dist/types/index.js.map +3 -3
- package/dist/types/source-reference.js +1 -0
- package/dist/types/source-reference.js.map +7 -0
- package/dist/utils/chat-logger.js +292 -0
- package/dist/utils/chat-logger.js.map +7 -0
- package/dist/utils/claude-stream.js +8 -0
- package/dist/utils/claude-stream.js.map +7 -0
- package/dist/utils/date-utils.js +7 -0
- package/dist/utils/date-utils.js.map +7 -0
- package/dist/utils/element-utils.js.map +1 -1
- package/dist/utils/errors.js +88 -15
- package/dist/utils/errors.js.map +3 -3
- package/dist/utils/export-python-annotations.js +82 -0
- package/dist/utils/export-python-annotations.js.map +7 -0
- package/dist/utils/file-io.js +7 -2
- package/dist/utils/file-io.js.map +2 -2
- package/dist/utils/file-lock.js +156 -0
- package/dist/utils/file-lock.js.map +7 -0
- package/dist/utils/id-generator.js +18 -0
- package/dist/utils/id-generator.js.map +7 -0
- package/dist/utils/layer-name-formatter.js +16 -0
- package/dist/utils/layer-name-formatter.js.map +7 -0
- package/dist/utils/markdown-anchor.js +7 -0
- package/dist/utils/markdown-anchor.js.map +7 -0
- package/dist/utils/markdown-link.js +12 -0
- package/dist/utils/markdown-link.js.map +7 -0
- package/dist/utils/markdown-table.js +17 -0
- package/dist/utils/markdown-table.js.map +7 -0
- package/dist/utils/mermaid-utils.js +11 -0
- package/dist/utils/mermaid-utils.js.map +7 -0
- package/dist/utils/project-paths.js.map +2 -2
- package/dist/utils/source-reference.js +52 -0
- package/dist/utils/source-reference.js.map +7 -0
- package/dist/utils/spec-installer.js +40 -53
- package/dist/utils/spec-installer.js.map +2 -2
- package/dist/utils/spec-version.js +5 -1
- package/dist/utils/spec-version.js.map +2 -2
- package/dist/utils/string-utils.js +12 -0
- package/dist/utils/string-utils.js.map +7 -0
- package/dist/utils/table-formatting.js +19 -0
- package/dist/utils/table-formatting.js.map +7 -0
- package/dist/validators/index.js +4 -0
- package/dist/validators/index.js.map +2 -2
- package/dist/validators/naming-validator.js +20 -23
- package/dist/validators/naming-validator.js.map +2 -2
- package/dist/validators/reference-validator.js +23 -38
- package/dist/validators/reference-validator.js.map +2 -2
- package/dist/validators/relationship-schema-validator.js +243 -0
- package/dist/validators/relationship-schema-validator.js.map +7 -0
- package/dist/validators/schema-validator.js +168 -68
- package/dist/validators/schema-validator.js.map +2 -2
- package/dist/validators/semantic-validator.js +36 -60
- package/dist/validators/semantic-validator.js.map +2 -2
- package/dist/validators/types.js.map +2 -2
- package/dist/validators/validation-formatter.js +159 -0
- package/dist/validators/validation-formatter.js.map +7 -0
- package/dist/validators/validator.js +24 -38
- package/dist/validators/validator.js.map +2 -2
- package/dist/viewer/README.md +139 -0
- package/dist/viewer/assets/main-CY0Xufuj.css +1 -0
- package/dist/viewer/assets/main-DtgErnwF.js +81 -0
- package/dist/viewer/assets/vendor-CxKvDp2x.js +17 -0
- package/dist/viewer/favicon.svg +11 -0
- package/dist/viewer/fonts/inter/Inter-Black.woff2 +0 -0
- package/dist/viewer/fonts/inter/Inter-Bold.woff2 +0 -0
- package/dist/viewer/fonts/inter/Inter-ExtraBold.woff2 +0 -0
- package/dist/viewer/fonts/inter/Inter-Light.woff2 +0 -0
- package/dist/viewer/fonts/inter/Inter-Medium.woff2 +0 -0
- package/dist/viewer/fonts/inter/Inter-Regular.woff2 +0 -0
- package/dist/viewer/fonts/inter/Inter-SemiBold.woff2 +0 -0
- package/dist/viewer/fonts/jetbrains-mono/JetBrainsMono-Medium.woff2 +0 -0
- package/dist/viewer/fonts/jetbrains-mono/JetBrainsMono-Regular.woff2 +0 -0
- package/dist/viewer/fonts/jetbrains-mono/JetBrainsMono-SemiBold.woff2 +0 -0
- package/dist/viewer/index.html +15 -0
- package/dist/viewer/manifest.json +99 -0
- package/dist/viewer/version.json +1 -0
- package/package.json +42 -16
- package/PYTHON_CLI_PARITY.md +0 -265
- package/PYTHON_CLI_STRUCTURE_UPDATE.md +0 -246
- package/biome.json +0 -42
- package/bunfig.toml +0 -3
- package/dist/ai/claude-client.js +0 -71
- package/dist/ai/claude-client.js.map +0 -7
- package/dist/ai/context-provider.js +0 -129
- package/dist/ai/context-provider.js.map +0 -7
- package/dist/ai/tools.js +0 -231
- package/dist/ai/tools.js.map +0 -7
- package/dist/commands/migrate.js +0 -36
- package/dist/commands/migrate.js.map +0 -7
- package/dist/commands/project.js +0 -74
- package/dist/commands/project.js.map +0 -7
- package/dist/core/active-changeset.js +0 -52
- package/dist/core/active-changeset.js.map +0 -7
- package/dist/core/projection-engine.js +0 -288
- package/dist/core/projection-engine.js.map +0 -7
- package/dist/schemas/bundled/01-motivation-layer.schema.json +0 -942
- package/dist/schemas/bundled/02-business-layer.schema.json +0 -1293
- package/dist/schemas/bundled/03-security-layer.schema.json +0 -2177
- package/dist/schemas/bundled/04-application-layer.schema.json +0 -1328
- package/dist/schemas/bundled/05-technology-layer.schema.json +0 -1465
- package/dist/schemas/bundled/06-api-layer.schema.json +0 -1743
- package/dist/schemas/bundled/07-data-model-layer.schema.json +0 -1253
- package/dist/schemas/bundled/08-datastore-layer.schema.json +0 -892
- package/dist/schemas/bundled/09-ux-layer.schema.json +0 -1506
- package/dist/schemas/bundled/10-navigation-layer.schema.json +0 -843
- package/dist/schemas/bundled/11-apm-observability-layer.schema.json +0 -1430
- package/dist/schemas/bundled/12-testing-layer.schema.json +0 -1091
- package/dist/schemas/bundled/common/layer-extensions.schema.json +0 -266
- package/dist/schemas/bundled/common/predicates.schema.json +0 -493
- package/dist/schemas/bundled/common/relationships.schema.json +0 -791
- package/dist/schemas/bundled/common/source-references.schema.json +0 -71
- package/dist/schemas/bundled/link-registry.json +0 -1839
- package/dist/schemas/bundled/link-registry.schema.json +0 -339
- package/dist/schemas/bundled/relationship-catalog.json +0 -1302
- package/dist/schemas/bundled/relationship-type.schema.json +0 -221
- package/dist/utils/model-path.js +0 -22
- package/dist/utils/model-path.js.map +0 -7
- package/docs/TEST-INSTRUMENTATION.md +0 -405
- package/docs/telemetry.md +0 -427
- package/test-debug.js +0 -33
- package/test-special.mjs +0 -46
- package/test_runner.ts +0 -7
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { ValidationResult } from "./types.js";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { readFile } from "../utils/file-io.js";
|
|
5
|
+
import { existsSync } from "fs";
|
|
6
|
+
import { startActiveSpan } from "../telemetry/index.js";
|
|
7
|
+
const isTelemetryEnabled = !1;
|
|
8
|
+
class RelationshipValidator {
|
|
9
|
+
schemasDir;
|
|
10
|
+
relationshipSchemas = /* @__PURE__ */ new Map();
|
|
11
|
+
constructor() {
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
this.schemasDir = path.join(__dirname, "..", "schemas", "bundled");
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Initialize and load relationship schemas
|
|
17
|
+
*/
|
|
18
|
+
async initialize() {
|
|
19
|
+
await this.loadRelationshipSchemas();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Load relationship schemas from compiled bundled spec (manifest.json + per-layer JSON files)
|
|
23
|
+
*/
|
|
24
|
+
async loadRelationshipSchemas() {
|
|
25
|
+
const manifestPath = path.join(this.schemasDir, "manifest.json");
|
|
26
|
+
if (!existsSync(manifestPath))
|
|
27
|
+
throw new Error(`Bundled manifest not found at ${manifestPath}`);
|
|
28
|
+
const manifestContent = await readFile(manifestPath), manifest = JSON.parse(manifestContent);
|
|
29
|
+
for (const { id: layerId } of manifest.layers) {
|
|
30
|
+
const layerPath = path.join(this.schemasDir, `${layerId}.json`);
|
|
31
|
+
if (existsSync(layerPath))
|
|
32
|
+
try {
|
|
33
|
+
const layerContent = await readFile(layerPath), layerData = JSON.parse(layerContent);
|
|
34
|
+
for (const [relId, relSchema] of Object.entries(layerData.relationshipSchemas || {}))
|
|
35
|
+
this.relationshipSchemas.set(relId, {
|
|
36
|
+
id: relSchema.id || relId,
|
|
37
|
+
source_spec_node_id: relSchema.source_spec_node_id || "",
|
|
38
|
+
source_layer: relSchema.source_layer || "",
|
|
39
|
+
destination_spec_node_id: relSchema.destination_spec_node_id || "",
|
|
40
|
+
destination_layer: relSchema.destination_layer || "",
|
|
41
|
+
predicate: relSchema.predicate || "",
|
|
42
|
+
cardinality: relSchema.cardinality || "many-to-many",
|
|
43
|
+
strength: relSchema.strength || "medium",
|
|
44
|
+
required: relSchema.required || !1,
|
|
45
|
+
attributes: relSchema.attributes
|
|
46
|
+
});
|
|
47
|
+
} catch (error) {
|
|
48
|
+
throw new Error(`Failed to load relationship schemas for layer '${layerId}': ${error.message}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Validate relationships in a model with cardinality enforcement
|
|
54
|
+
*/
|
|
55
|
+
async validateModel(model) {
|
|
56
|
+
return startActiveSpan("relationship-schema.validate-model", async (span) => {
|
|
57
|
+
if (this.relationshipSchemas.size === 0)
|
|
58
|
+
try {
|
|
59
|
+
await this.initialize();
|
|
60
|
+
} catch (error) {
|
|
61
|
+
const result2 = new ValidationResult();
|
|
62
|
+
return result2.addError({
|
|
63
|
+
layer: "system",
|
|
64
|
+
message: `${error.message} Relationship validation is not available.`
|
|
65
|
+
}), result2;
|
|
66
|
+
}
|
|
67
|
+
if (this.relationshipSchemas.size === 0) {
|
|
68
|
+
const result2 = new ValidationResult();
|
|
69
|
+
return result2.addError({
|
|
70
|
+
layer: "system",
|
|
71
|
+
message: `No relationship schemas loaded from ${path.join(this.schemasDir, "manifest.json")}. Relationship validation is not available.`
|
|
72
|
+
}), result2;
|
|
73
|
+
}
|
|
74
|
+
const result = new ValidationResult(), allRelationships = model.relationships.getAll();
|
|
75
|
+
if (allRelationships.length === 0)
|
|
76
|
+
return result;
|
|
77
|
+
const relationshipsBySource = this.groupRelationshipsBySourceTarget(allRelationships, "source"), relationshipsByTarget = this.groupRelationshipsBySourceTarget(allRelationships, "target");
|
|
78
|
+
for (const relationship of allRelationships) {
|
|
79
|
+
const relErrors = await this.validateRelationship(relationship, model, relationshipsBySource, relationshipsByTarget);
|
|
80
|
+
if (relErrors.length > 0)
|
|
81
|
+
for (const error of relErrors)
|
|
82
|
+
result.addError(error);
|
|
83
|
+
}
|
|
84
|
+
return isTelemetryEnabled && (span.setAttribute("relationship.error_count", result.errors.length), span.setAttribute("relationship.warning_count", result.warnings.length), span.setAttribute("relationship.validated_count", allRelationships.length)), result;
|
|
85
|
+
}, { "relationship.layer_count": model.layers.size });
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Validate a single relationship
|
|
89
|
+
*/
|
|
90
|
+
async validateRelationship(relationship, model, relationshipsBySource, relationshipsByTarget) {
|
|
91
|
+
const errors = [], sourceElement = this.findElementInModel(model, relationship.source), targetElement = this.findElementInModel(model, relationship.target);
|
|
92
|
+
if (!sourceElement)
|
|
93
|
+
return errors.push({
|
|
94
|
+
layer: relationship.layer,
|
|
95
|
+
elementId: relationship.source,
|
|
96
|
+
message: `Relationship source element '${relationship.source}' not found`
|
|
97
|
+
}), errors;
|
|
98
|
+
if (!targetElement)
|
|
99
|
+
return errors.push({
|
|
100
|
+
layer: relationship.layer,
|
|
101
|
+
elementId: relationship.target,
|
|
102
|
+
message: `Relationship target element '${relationship.target}' not found`
|
|
103
|
+
}), errors;
|
|
104
|
+
const sourceLayer = sourceElement.layer_id || sourceElement.layer || relationship.layer, targetLayer = targetElement.layer_id || targetElement.layer || relationship.layer, sourceSpecId = sourceElement.spec_node_id ? sourceElement.spec_node_id : `${sourceLayer}.${sourceElement.type}`, targetSpecId = targetElement.spec_node_id ? targetElement.spec_node_id : `${targetLayer}.${targetElement.type}`, schemaKey = this.findRelationshipSchemaKeyBySpecId(sourceSpecId, targetSpecId, relationship.predicate);
|
|
105
|
+
if (!schemaKey) {
|
|
106
|
+
const sourceType = sourceSpecId.includes(".") ? sourceSpecId.split(".")[1] : sourceElement.type, targetType = targetSpecId.includes(".") ? targetSpecId.split(".")[1] : targetElement.type;
|
|
107
|
+
return errors.push({
|
|
108
|
+
layer: relationship.layer,
|
|
109
|
+
elementId: relationship.source,
|
|
110
|
+
message: `No schema found for relationship: ${sourceLayer}.${sourceType} --[${relationship.predicate}]--> ${targetLayer}.${targetType}`
|
|
111
|
+
}), errors;
|
|
112
|
+
}
|
|
113
|
+
const schema = this.relationshipSchemas.get(schemaKey);
|
|
114
|
+
if (!schema)
|
|
115
|
+
return errors.push({
|
|
116
|
+
layer: relationship.layer,
|
|
117
|
+
elementId: relationship.source,
|
|
118
|
+
message: `Failed to load schema for relationship: ${schemaKey}`
|
|
119
|
+
}), errors;
|
|
120
|
+
const cardinalityErrors = this.validateCardinality(relationship, schema, relationshipsBySource, relationshipsByTarget);
|
|
121
|
+
if (errors.push(...cardinalityErrors), relationship.properties && schema.attributes) {
|
|
122
|
+
const attrErrors = this.validateRelationshipAttributes(relationship, schema);
|
|
123
|
+
errors.push(...attrErrors);
|
|
124
|
+
}
|
|
125
|
+
return errors;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Validate cardinality constraints for a relationship
|
|
129
|
+
*
|
|
130
|
+
* Cardinality constraint semantics:
|
|
131
|
+
* - "one-to-one": Each source can have at most 1 such relationship, and each target can be involved in at most 1
|
|
132
|
+
* - "one-to-many": Each target can receive at most 1 such relationship (target is the "one"); source is unconstrained
|
|
133
|
+
* - "many-to-one": Each source can be involved in at most 1 such relationship (maps to one target); multiple sources may share the same target
|
|
134
|
+
* - "many-to-many": No cardinality constraints
|
|
135
|
+
*/
|
|
136
|
+
validateCardinality(relationship, schema, relationshipsBySource, relationshipsByTarget) {
|
|
137
|
+
const errors = [], cardinality = schema.cardinality, sourceRelCount = relationshipsBySource.get(relationship.source)?.filter(
|
|
138
|
+
(r) => r.predicate === relationship.predicate && r.target !== relationship.target
|
|
139
|
+
// Exclude current relationship
|
|
140
|
+
).length || 0, targetRelCount = relationshipsByTarget.get(relationship.target)?.filter(
|
|
141
|
+
(r) => r.predicate === relationship.predicate && r.source !== relationship.source
|
|
142
|
+
// Exclude current relationship
|
|
143
|
+
).length || 0;
|
|
144
|
+
switch (cardinality) {
|
|
145
|
+
case "one-to-one":
|
|
146
|
+
sourceRelCount > 0 && errors.push({
|
|
147
|
+
layer: relationship.layer,
|
|
148
|
+
elementId: relationship.source,
|
|
149
|
+
message: `Cardinality violation: '${relationship.predicate}' has cardinality 'one-to-one', but source '${relationship.source}' already has this relationship`
|
|
150
|
+
}), targetRelCount > 0 && errors.push({
|
|
151
|
+
layer: relationship.layer,
|
|
152
|
+
elementId: relationship.target,
|
|
153
|
+
message: `Cardinality violation: '${relationship.predicate}' has cardinality 'one-to-one', but target '${relationship.target}' is already involved in this relationship`
|
|
154
|
+
});
|
|
155
|
+
break;
|
|
156
|
+
case "one-to-many":
|
|
157
|
+
targetRelCount > 0 && errors.push({
|
|
158
|
+
layer: relationship.layer,
|
|
159
|
+
elementId: relationship.target,
|
|
160
|
+
message: `Cardinality violation: '${relationship.predicate}' has cardinality 'one-to-many', but target '${relationship.target}' already has this relationship`
|
|
161
|
+
});
|
|
162
|
+
break;
|
|
163
|
+
case "many-to-one":
|
|
164
|
+
sourceRelCount > 0 && errors.push({
|
|
165
|
+
layer: relationship.layer,
|
|
166
|
+
elementId: relationship.source,
|
|
167
|
+
message: `Cardinality violation: '${relationship.predicate}' has cardinality 'many-to-one', but source '${relationship.source}' already has this relationship`
|
|
168
|
+
});
|
|
169
|
+
break;
|
|
170
|
+
case "many-to-many":
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
return errors;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Validate relationship attributes against schema
|
|
177
|
+
*/
|
|
178
|
+
validateRelationshipAttributes(relationship, schema) {
|
|
179
|
+
const errors = [];
|
|
180
|
+
if (!schema.attributes || !relationship.properties)
|
|
181
|
+
return errors;
|
|
182
|
+
for (const key of Object.keys(relationship.properties))
|
|
183
|
+
key in schema.attributes || errors.push({
|
|
184
|
+
layer: relationship.layer,
|
|
185
|
+
elementId: relationship.source,
|
|
186
|
+
message: `Relationship property '${key}' not defined in schema for predicate '${relationship.predicate}'`
|
|
187
|
+
});
|
|
188
|
+
return errors;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Find relationship schema ID by source/dest spec node IDs and predicate
|
|
192
|
+
*
|
|
193
|
+
* Matches relationship schemas by:
|
|
194
|
+
* 1. Predicate (must match exactly)
|
|
195
|
+
* 2. Spec node ID pair (source and destination must match exactly)
|
|
196
|
+
*
|
|
197
|
+
* Spec node IDs are in the format "layer.type" (e.g., "application.applicationservice")
|
|
198
|
+
* and uniquely identify an element type across the model.
|
|
199
|
+
*
|
|
200
|
+
* Supports both intra-layer (motivation -> motivation) and cross-layer
|
|
201
|
+
* (motivation -> business) relationships.
|
|
202
|
+
*/
|
|
203
|
+
findRelationshipSchemaKeyBySpecId(sourceSpecId, destSpecId, predicate) {
|
|
204
|
+
for (const [schemaId, schema] of this.relationshipSchemas.entries())
|
|
205
|
+
if (schema.predicate === predicate && schema.source_spec_node_id === sourceSpecId && schema.destination_spec_node_id === destSpecId)
|
|
206
|
+
return schemaId;
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Group relationships by source or target element
|
|
211
|
+
*/
|
|
212
|
+
groupRelationshipsBySourceTarget(relationships, by) {
|
|
213
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
214
|
+
for (const rel of relationships) {
|
|
215
|
+
const key = by === "source" ? rel.source : rel.target;
|
|
216
|
+
grouped.has(key) || grouped.set(key, []), grouped.get(key).push(rel);
|
|
217
|
+
}
|
|
218
|
+
return grouped;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Find an element in the model by ID
|
|
222
|
+
*
|
|
223
|
+
* Performs element lookup using Layer.getElement(), which supports multiple lookup strategies:
|
|
224
|
+
* - Direct lookup by graph key (path)
|
|
225
|
+
* - UUID lookup
|
|
226
|
+
* - Semantic ID lookup (for legacy format elements converted to UUID)
|
|
227
|
+
* - Element ID lookup
|
|
228
|
+
*
|
|
229
|
+
* This comprehensive lookup ensures elements are found regardless of how they were stored.
|
|
230
|
+
*/
|
|
231
|
+
findElementInModel(model, elementId) {
|
|
232
|
+
for (const layer of model.layers.values()) {
|
|
233
|
+
const element = layer.getElement(elementId);
|
|
234
|
+
if (element)
|
|
235
|
+
return element;
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
export {
|
|
241
|
+
RelationshipValidator
|
|
242
|
+
};
|
|
243
|
+
//# sourceMappingURL=relationship-schema-validator.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/validators/relationship-schema-validator.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Relationship Schema Validation with Cardinality Enforcement\n *\n * Validates relationships against their spec relationship schemas, including:\n * - Schema compliance (source/destination node types)\n * - Cardinality constraints (one-to-one, one-to-many, many-to-one, many-to-many)\n * - Predicate validity\n * - Relationship attributes against schema\n */\n\nimport { ValidationResult } from \"./types.js\";\nimport type { Model } from \"../core/model.js\";\nimport type { Relationship } from \"../core/relationships.js\";\nimport { fileURLToPath } from \"url\";\nimport path from \"path\";\nimport { readFile } from \"../utils/file-io.js\";\nimport { existsSync } from \"fs\";\nimport { startActiveSpan } from \"../telemetry/index.js\";\n\ninterface ManifestDistFile {\n layers: Array<{ id: string }>;\n}\n\ninterface LayerDistFile {\n relationshipSchemas: Record<string, {\n id: string;\n source_spec_node_id: string;\n source_layer: string;\n destination_spec_node_id: string;\n destination_layer: string;\n predicate: string;\n cardinality: \"one-to-one\" | \"one-to-many\" | \"many-to-one\" | \"many-to-many\";\n strength: string;\n required?: boolean;\n attributes?: Record<string, unknown>;\n }>;\n}\n\ndeclare const TELEMETRY_ENABLED: boolean | undefined;\nconst isTelemetryEnabled = typeof TELEMETRY_ENABLED !== \"undefined\" ? TELEMETRY_ENABLED : false;\n\nexport interface RelationshipSchema {\n id: string;\n source_spec_node_id: string;\n source_layer: string;\n destination_spec_node_id: string;\n destination_layer: string;\n predicate: string;\n cardinality: \"one-to-one\" | \"one-to-many\" | \"many-to-one\" | \"many-to-many\";\n strength: string;\n required?: boolean;\n attributes?: Record<string, unknown>;\n}\n\n/**\n * Validator for relationship schema compliance and cardinality constraints\n *\n * Validates that relationships:\n * 1. Conform to their spec relationship schemas\n * 2. Respect cardinality constraints (e.g., one-to-one means max 1 relationship of that type)\n * 3. Reference valid predicates\n * 4. Have valid source/destination element types\n */\nexport class RelationshipValidator {\n private schemasDir: string;\n private relationshipSchemas: Map<string, RelationshipSchema> = new Map();\n\n constructor() {\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n this.schemasDir = path.join(__dirname, \"..\", \"schemas\", \"bundled\");\n }\n\n /**\n * Initialize and load relationship schemas\n */\n async initialize(): Promise<void> {\n await this.loadRelationshipSchemas();\n }\n\n /**\n * Load relationship schemas from compiled bundled spec (manifest.json + per-layer JSON files)\n */\n private async loadRelationshipSchemas(): Promise<void> {\n const manifestPath = path.join(this.schemasDir, \"manifest.json\");\n\n if (!existsSync(manifestPath)) {\n throw new Error(`Bundled manifest not found at ${manifestPath}`);\n }\n\n const manifestContent = await readFile(manifestPath);\n const manifest = JSON.parse(manifestContent) as ManifestDistFile;\n\n for (const { id: layerId } of manifest.layers) {\n const layerPath = path.join(this.schemasDir, `${layerId}.json`);\n if (!existsSync(layerPath)) {\n continue;\n }\n\n try {\n const layerContent = await readFile(layerPath);\n const layerData = JSON.parse(layerContent) as LayerDistFile;\n\n for (const [relId, relSchema] of Object.entries(layerData.relationshipSchemas || {})) {\n this.relationshipSchemas.set(relId, {\n id: relSchema.id || relId,\n source_spec_node_id: relSchema.source_spec_node_id || \"\",\n source_layer: relSchema.source_layer || \"\",\n destination_spec_node_id: relSchema.destination_spec_node_id || \"\",\n destination_layer: relSchema.destination_layer || \"\",\n predicate: relSchema.predicate || \"\",\n cardinality: relSchema.cardinality || \"many-to-many\",\n strength: relSchema.strength || \"medium\",\n required: relSchema.required || false,\n attributes: relSchema.attributes,\n });\n }\n } catch (error: any) {\n throw new Error(`Failed to load relationship schemas for layer '${layerId}': ${error.message}`);\n }\n }\n }\n\n /**\n * Validate relationships in a model with cardinality enforcement\n */\n async validateModel(model: Model): Promise<ValidationResult> {\n return startActiveSpan(\n \"relationship-schema.validate-model\",\n async (span) => {\n // Ensure schemas are loaded\n if (this.relationshipSchemas.size === 0) {\n try {\n await this.initialize();\n } catch (error: any) {\n const result = new ValidationResult();\n result.addError({\n layer: \"system\",\n message: `${error.message} Relationship validation is not available.`,\n });\n return result;\n }\n }\n\n // Verify schemas were loaded. This covers two cases:\n // 1. initialize() succeeded but the manifest has no layers with relationship schemas\n // 2. Edge case where manifest exists but is empty\n // In both cases, relationship validation cannot proceed.\n if (this.relationshipSchemas.size === 0) {\n const result = new ValidationResult();\n result.addError({\n layer: \"system\",\n message: `No relationship schemas loaded from ${path.join(this.schemasDir, \"manifest.json\")}. Relationship validation is not available.`,\n });\n return result;\n }\n\n const result = new ValidationResult();\n const allRelationships = model.relationships.getAll();\n\n if (allRelationships.length === 0) {\n return result;\n }\n\n // Group relationships for cardinality validation\n const relationshipsBySource = this.groupRelationshipsBySourceTarget(\n allRelationships,\n \"source\"\n );\n const relationshipsByTarget = this.groupRelationshipsBySourceTarget(\n allRelationships,\n \"target\"\n );\n\n // Validate each relationship\n for (const relationship of allRelationships) {\n const relErrors = await this.validateRelationship(\n relationship,\n model,\n relationshipsBySource,\n relationshipsByTarget\n );\n\n if (relErrors.length > 0) {\n for (const error of relErrors) {\n result.addError(error);\n }\n }\n }\n\n if (isTelemetryEnabled) {\n span.setAttribute(\"relationship.error_count\", result.errors.length);\n span.setAttribute(\"relationship.warning_count\", result.warnings.length);\n span.setAttribute(\"relationship.validated_count\", allRelationships.length);\n }\n\n return result;\n },\n { \"relationship.layer_count\": model.layers.size }\n );\n }\n\n /**\n * Validate a single relationship\n */\n private async validateRelationship(\n relationship: Relationship,\n model: Model,\n relationshipsBySource: Map<string, Relationship[]>,\n relationshipsByTarget: Map<string, Relationship[]>\n ): Promise<Array<{ layer: string; message: string; elementId?: string }>> {\n const errors: Array<{ layer: string; message: string; elementId?: string }> = [];\n\n // Find source and destination elements\n const sourceElement = this.findElementInModel(model, relationship.source);\n const targetElement = this.findElementInModel(model, relationship.target);\n\n if (!sourceElement) {\n errors.push({\n layer: relationship.layer,\n elementId: relationship.source,\n message: `Relationship source element '${relationship.source}' not found`,\n });\n return errors;\n }\n\n if (!targetElement) {\n errors.push({\n layer: relationship.layer,\n elementId: relationship.target,\n message: `Relationship target element '${relationship.target}' not found`,\n });\n return errors;\n }\n\n // Extract element types AND layers (CRITICAL FIX for cross-layer relationships)\n // The source and target elements may be in different layers\n const sourceLayer = sourceElement.layer_id || sourceElement.layer || relationship.layer;\n const targetLayer = targetElement.layer_id || targetElement.layer || relationship.layer;\n\n // Use spec_node_id if present, otherwise construct from layer and type\n // spec_node_id contains the canonical type (e.g., \"application.applicationservice\")\n // Guard against empty spec_node_id from older CLI versions or manually authored elements\n const sourceSpecId = sourceElement.spec_node_id\n ? sourceElement.spec_node_id\n : `${sourceLayer}.${sourceElement.type}`;\n const targetSpecId = targetElement.spec_node_id\n ? targetElement.spec_node_id\n : `${targetLayer}.${targetElement.type}`;\n\n // Find applicable relationship schema using actual source/target layers and spec node IDs\n // (not relationship.layer for both, which assumes intra-layer relationships)\n const schemaKey = this.findRelationshipSchemaKeyBySpecId(\n sourceSpecId,\n targetSpecId,\n relationship.predicate\n );\n\n if (!schemaKey) {\n // Extract type names from spec_node_id for error message (e.g., \"applicationservice\" from \"application.applicationservice\")\n const sourceType = sourceSpecId.includes(\".\") ? sourceSpecId.split(\".\")[1] : sourceElement.type;\n const targetType = targetSpecId.includes(\".\") ? targetSpecId.split(\".\")[1] : targetElement.type;\n errors.push({\n layer: relationship.layer,\n elementId: relationship.source,\n message: `No schema found for relationship: ${sourceLayer}.${sourceType} --[${relationship.predicate}]--> ${targetLayer}.${targetType}`,\n });\n return errors;\n }\n\n const schema = this.relationshipSchemas.get(schemaKey);\n if (!schema) {\n errors.push({\n layer: relationship.layer,\n elementId: relationship.source,\n message: `Failed to load schema for relationship: ${schemaKey}`,\n });\n return errors;\n }\n\n // Validate cardinality constraints\n const cardinalityErrors = this.validateCardinality(\n relationship,\n schema,\n relationshipsBySource,\n relationshipsByTarget\n );\n\n errors.push(...cardinalityErrors);\n\n // Validate relationship attributes if schema specifies them\n if (relationship.properties && schema.attributes) {\n const attrErrors = this.validateRelationshipAttributes(\n relationship,\n schema\n );\n errors.push(...attrErrors);\n }\n\n return errors;\n }\n\n /**\n * Validate cardinality constraints for a relationship\n *\n * Cardinality constraint semantics:\n * - \"one-to-one\": Each source can have at most 1 such relationship, and each target can be involved in at most 1\n * - \"one-to-many\": Each target can receive at most 1 such relationship (target is the \"one\"); source is unconstrained\n * - \"many-to-one\": Each source can be involved in at most 1 such relationship (maps to one target); multiple sources may share the same target\n * - \"many-to-many\": No cardinality constraints\n */\n private validateCardinality(\n relationship: Relationship,\n schema: RelationshipSchema,\n relationshipsBySource: Map<string, Relationship[]>,\n relationshipsByTarget: Map<string, Relationship[]>\n ): Array<{ layer: string; message: string; elementId?: string }> {\n const errors: Array<{ layer: string; message: string; elementId?: string }> = [];\n const cardinality = schema.cardinality;\n\n // Count OTHER relationships for this predicate from source and to target\n // (excluding the current relationship to get accurate violation detection)\n const sourceRelCount =\n relationshipsBySource\n .get(relationship.source)\n ?.filter(\n (r) =>\n r.predicate === relationship.predicate &&\n r.target !== relationship.target // Exclude current relationship\n ).length || 0;\n\n const targetRelCount =\n relationshipsByTarget\n .get(relationship.target)\n ?.filter(\n (r) =>\n r.predicate === relationship.predicate &&\n r.source !== relationship.source // Exclude current relationship\n ).length || 0;\n\n // Validate based on cardinality type\n switch (cardinality) {\n case \"one-to-one\":\n // Source can have at most 1 such relationship\n if (sourceRelCount > 0) {\n errors.push({\n layer: relationship.layer,\n elementId: relationship.source,\n message: `Cardinality violation: '${relationship.predicate}' has cardinality 'one-to-one', but source '${relationship.source}' already has this relationship`,\n });\n }\n // Target can be involved in at most 1 such relationship\n if (targetRelCount > 0) {\n errors.push({\n layer: relationship.layer,\n elementId: relationship.target,\n message: `Cardinality violation: '${relationship.predicate}' has cardinality 'one-to-one', but target '${relationship.target}' is already involved in this relationship`,\n });\n }\n break;\n\n case \"one-to-many\":\n // Standard ER one-to-many: the \"one\" is at the target end.\n // Each target can receive at most 1 incoming relationship of this type.\n // The source is unconstrained \u2014 it may have many outgoing of this type\n // to different targets.\n if (targetRelCount > 0) {\n errors.push({\n layer: relationship.layer,\n elementId: relationship.target,\n message: `Cardinality violation: '${relationship.predicate}' has cardinality 'one-to-many', but target '${relationship.target}' already has this relationship`,\n });\n }\n break;\n\n case \"many-to-one\":\n // Standard ER many-to-one: the \"one\" is at the source end.\n // Each source can be involved in at most 1 relationship of this type\n // (it maps to exactly one target). Multiple sources CAN share the same\n // target \u2014 fan-in is allowed and expected for shared services.\n if (sourceRelCount > 0) {\n errors.push({\n layer: relationship.layer,\n elementId: relationship.source,\n message: `Cardinality violation: '${relationship.predicate}' has cardinality 'many-to-one', but source '${relationship.source}' already has this relationship`,\n });\n }\n break;\n\n case \"many-to-many\":\n // No cardinality constraints\n break;\n }\n\n return errors;\n }\n\n /**\n * Validate relationship attributes against schema\n */\n private validateRelationshipAttributes(\n relationship: Relationship,\n schema: RelationshipSchema\n ): Array<{ layer: string; message: string; elementId?: string }> {\n const errors: Array<{ layer: string; message: string; elementId?: string }> = [];\n\n if (!schema.attributes || !relationship.properties) {\n return errors;\n }\n\n // Simple validation: check that properties match schema keys\n for (const key of Object.keys(relationship.properties)) {\n if (!(key in schema.attributes)) {\n errors.push({\n layer: relationship.layer,\n elementId: relationship.source,\n message: `Relationship property '${key}' not defined in schema for predicate '${relationship.predicate}'`,\n });\n }\n }\n\n return errors;\n }\n\n /**\n * Find relationship schema ID by source/dest spec node IDs and predicate\n *\n * Matches relationship schemas by:\n * 1. Predicate (must match exactly)\n * 2. Spec node ID pair (source and destination must match exactly)\n *\n * Spec node IDs are in the format \"layer.type\" (e.g., \"application.applicationservice\")\n * and uniquely identify an element type across the model.\n *\n * Supports both intra-layer (motivation -> motivation) and cross-layer\n * (motivation -> business) relationships.\n */\n private findRelationshipSchemaKeyBySpecId(\n sourceSpecId: string,\n destSpecId: string,\n predicate: string\n ): string | null {\n // Search for matching schema\n for (const [schemaId, schema] of this.relationshipSchemas.entries()) {\n // First filter: predicate must match exactly\n if (schema.predicate !== predicate) {\n continue;\n }\n\n // Second filter: spec node ID pair must match exactly\n // This directly compares canonical forms like \"application.applicationservice\"\n if (\n schema.source_spec_node_id === sourceSpecId &&\n schema.destination_spec_node_id === destSpecId\n ) {\n return schemaId;\n }\n }\n\n return null;\n }\n\n /**\n * Group relationships by source or target element\n */\n private groupRelationshipsBySourceTarget(\n relationships: Relationship[],\n by: \"source\" | \"target\"\n ): Map<string, Relationship[]> {\n const grouped = new Map<string, Relationship[]>();\n\n for (const rel of relationships) {\n const key = by === \"source\" ? rel.source : rel.target;\n if (!grouped.has(key)) {\n grouped.set(key, []);\n }\n grouped.get(key)!.push(rel);\n }\n\n return grouped;\n }\n\n /**\n * Find an element in the model by ID\n *\n * Performs element lookup using Layer.getElement(), which supports multiple lookup strategies:\n * - Direct lookup by graph key (path)\n * - UUID lookup\n * - Semantic ID lookup (for legacy format elements converted to UUID)\n * - Element ID lookup\n *\n * This comprehensive lookup ensures elements are found regardless of how they were stored.\n */\n private findElementInModel(model: Model, elementId: string) {\n for (const layer of model.layers.values()) {\n const element = layer.getElement(elementId);\n if (element) {\n return element;\n }\n }\n return null;\n }\n\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["result"]
|
|
7
|
+
}
|
|
@@ -4,95 +4,177 @@ import { ValidationResult } from "./types.js";
|
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
import path from "path";
|
|
6
6
|
import { readFile } from "../utils/file-io.js";
|
|
7
|
-
import {
|
|
7
|
+
import { startActiveSpan } from "../telemetry/index.js";
|
|
8
|
+
import { existsSync } from "fs";
|
|
9
|
+
import { validateSpecNode, validateSpecNodeRelationship, validateSourceReference, validateAttributeSpec } from "../generated/compiled-validators.js";
|
|
8
10
|
const isTelemetryEnabled = !1;
|
|
9
11
|
class SchemaValidator {
|
|
12
|
+
// Static shared AJV instance to avoid schema registry conflicts
|
|
13
|
+
// When multiple SchemaValidator instances are created, they all use the same
|
|
14
|
+
// AJV instance which maintains a single schema registry. This prevents
|
|
15
|
+
// "schema with key or id already exists" errors when loading the same schemas.
|
|
16
|
+
static sharedAjv = null;
|
|
17
|
+
static baseSchemaLoadedInSharedInstance = !1;
|
|
18
|
+
// Track schema IDs already registered in the shared AJV instance to avoid re-registering
|
|
19
|
+
static registeredSchemaIds = /* @__PURE__ */ new Set();
|
|
20
|
+
// Layer-level cache: layer ID → nodeSchemas map (avoids re-reading layer dist files)
|
|
21
|
+
static layerCache = /* @__PURE__ */ new Map();
|
|
10
22
|
ajv;
|
|
11
23
|
compiledSchemas = /* @__PURE__ */ new Map();
|
|
12
|
-
|
|
24
|
+
// Key: layer.type
|
|
25
|
+
bundledDir;
|
|
13
26
|
constructor() {
|
|
14
|
-
|
|
27
|
+
SchemaValidator.sharedAjv || (SchemaValidator.sharedAjv = new Ajv({
|
|
15
28
|
allErrors: !0,
|
|
16
29
|
strict: !1,
|
|
17
|
-
validateFormats: !0
|
|
18
|
-
|
|
30
|
+
validateFormats: !0,
|
|
31
|
+
verbose: !0
|
|
32
|
+
}), addFormats(SchemaValidator.sharedAjv)), this.ajv = SchemaValidator.sharedAjv;
|
|
33
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
34
|
+
this.bundledDir = path.join(__dirname, "..", "schemas", "bundled");
|
|
19
35
|
}
|
|
20
36
|
/**
|
|
21
|
-
*
|
|
37
|
+
* Extract error message from unknown error type with proper type checking
|
|
22
38
|
*/
|
|
23
|
-
|
|
24
|
-
|
|
39
|
+
static getErrorMessage(error) {
|
|
40
|
+
if (error instanceof Error)
|
|
41
|
+
return error.message;
|
|
42
|
+
if (typeof error == "object" && error !== null && "message" in error) {
|
|
43
|
+
const msg = error.message;
|
|
44
|
+
if (typeof msg == "string")
|
|
45
|
+
return msg;
|
|
46
|
+
}
|
|
47
|
+
return "Unknown error";
|
|
25
48
|
}
|
|
26
49
|
/**
|
|
27
|
-
*
|
|
50
|
+
* Ensure base schemas are pre-compiled validators are initialized
|
|
51
|
+
* Loads base schemas from base.json and registers them with AJV for reference resolution.
|
|
52
|
+
* This allows per-type schemas to resolve $ref to base schemas during compilation.
|
|
28
53
|
*/
|
|
29
|
-
async
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"common/predicates.schema.json",
|
|
46
|
-
"common/relationships.schema.json",
|
|
47
|
-
"common/layer-extensions.schema.json"
|
|
48
|
-
];
|
|
49
|
-
for (const commonSchemaFile of commonSchemas)
|
|
50
|
-
try {
|
|
51
|
-
const schemaPath = path.join(schemasDir, commonSchemaFile), schemaContent = await readFile(schemaPath), schema = JSON.parse(schemaContent);
|
|
52
|
-
this.ajv.addSchema(schema);
|
|
53
|
-
} catch (error) {
|
|
54
|
-
console.warn(`Warning: Failed to load common schema ${commonSchemaFile}:`, error);
|
|
54
|
+
async ensureBaseSchemaLoaded() {
|
|
55
|
+
if (SchemaValidator.baseSchemaLoadedInSharedInstance)
|
|
56
|
+
return;
|
|
57
|
+
this.compiledSchemas.set("spec-node", validateSpecNode), this.compiledSchemas.set("spec-node-relationship", validateSpecNodeRelationship), this.compiledSchemas.set("source-references", validateSourceReference), this.compiledSchemas.set("attribute-spec", validateAttributeSpec);
|
|
58
|
+
const baseJsonPath = path.join(this.bundledDir, "base.json");
|
|
59
|
+
try {
|
|
60
|
+
const baseContent = await readFile(baseJsonPath), baseData = JSON.parse(baseContent);
|
|
61
|
+
for (const [key, schema] of Object.entries(baseData.schemas)) {
|
|
62
|
+
const s = schema, schemaId = s.$id || `urn:dr:spec:base:${key}`;
|
|
63
|
+
if (!SchemaValidator.registeredSchemaIds.has(schemaId))
|
|
64
|
+
try {
|
|
65
|
+
this.ajv.addSchema(s, schemaId), SchemaValidator.registeredSchemaIds.add(schemaId);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
const errorMsg = SchemaValidator.getErrorMessage(error);
|
|
68
|
+
/schema with key or id [^ ]+ already exists/.test(errorMsg) ? SchemaValidator.registeredSchemaIds.add(schemaId) : console.warn(`[SCHEMA-LOAD-001] Failed to register base schema '${key}': ${errorMsg}`);
|
|
69
|
+
}
|
|
55
70
|
}
|
|
56
|
-
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.warn(`[SCHEMA-LOAD-001] Failed to load base.json from ${baseJsonPath}: ${SchemaValidator.getErrorMessage(error)}`);
|
|
73
|
+
}
|
|
74
|
+
SchemaValidator.baseSchemaLoadedInSharedInstance = !0;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Load the nodeSchemas map for a layer, using a static layer-level cache.
|
|
78
|
+
* Reads bundled/{layer}.json once per layer and caches the nodeSchemas object.
|
|
79
|
+
*/
|
|
80
|
+
async loadLayerNodeSchemas(layer) {
|
|
81
|
+
if (SchemaValidator.layerCache.has(layer))
|
|
82
|
+
return SchemaValidator.layerCache.get(layer);
|
|
83
|
+
const layerPath = path.join(this.bundledDir, `${layer}.json`);
|
|
84
|
+
if (!existsSync(layerPath))
|
|
85
|
+
return null;
|
|
86
|
+
try {
|
|
87
|
+
const content = await readFile(layerPath), nodeSchemas = JSON.parse(content).nodeSchemas || {};
|
|
88
|
+
return SchemaValidator.layerCache.set(layer, nodeSchemas), nodeSchemas;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
return console.warn(`[SCHEMA-LOAD-002] Failed to load layer file for '${layer}': ${SchemaValidator.getErrorMessage(error)}`), null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Load and compile a spec node schema for a specific element type
|
|
95
|
+
*/
|
|
96
|
+
async loadSpecNodeSchema(layer, type) {
|
|
97
|
+
const cacheKey = `${layer}.${type}`;
|
|
98
|
+
if (this.compiledSchemas.has(cacheKey))
|
|
99
|
+
return this.compiledSchemas.get(cacheKey);
|
|
100
|
+
await this.ensureBaseSchemaLoaded();
|
|
101
|
+
try {
|
|
102
|
+
const nodeSchemas = await this.loadLayerNodeSchemas(layer);
|
|
103
|
+
if (!nodeSchemas || !nodeSchemas[type])
|
|
104
|
+
return null;
|
|
105
|
+
const schema = nodeSchemas[type], schemaId = schema.$id || `urn:dr:spec:node:${layer}.${type}`;
|
|
106
|
+
if (SchemaValidator.registeredSchemaIds.has(schemaId))
|
|
107
|
+
return this.compiledSchemas.get(cacheKey) || null;
|
|
57
108
|
try {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const schema = JSON.parse(schemaContent), validate = this.ajv.compile(schema);
|
|
65
|
-
this.compiledSchemas.set(layerName, validate);
|
|
66
|
-
} catch (error) {
|
|
67
|
-
console.warn(`Failed to load schema for layer ${layerName}:`, error);
|
|
109
|
+
const validate = this.ajv.compile(schema);
|
|
110
|
+
return this.compiledSchemas.set(cacheKey, validate), SchemaValidator.registeredSchemaIds.add(schemaId), validate;
|
|
111
|
+
} catch (registrationError) {
|
|
112
|
+
if (SchemaValidator.getErrorMessage(registrationError).includes("already exists"))
|
|
113
|
+
return SchemaValidator.registeredSchemaIds.add(schemaId), null;
|
|
114
|
+
throw registrationError;
|
|
68
115
|
}
|
|
116
|
+
} catch (error) {
|
|
117
|
+
const errorMsg = SchemaValidator.getErrorMessage(error);
|
|
118
|
+
return errorMsg.includes("can't resolve reference") || console.warn(`[SCHEMA-LOAD-002] Failed to load spec node schema for ${layer}.${type}: ${errorMsg}`), null;
|
|
119
|
+
}
|
|
69
120
|
}
|
|
70
121
|
/**
|
|
71
|
-
* Validate a layer against its
|
|
122
|
+
* Validate a layer against its spec node schemas
|
|
123
|
+
*
|
|
124
|
+
* This validates each element in the layer against its corresponding spec node schema.
|
|
72
125
|
*/
|
|
73
126
|
async validateLayer(layer) {
|
|
74
|
-
|
|
75
|
-
"schema.layer": layer.name
|
|
76
|
-
}) : null;
|
|
77
|
-
try {
|
|
127
|
+
return startActiveSpan("schema.validate", async (span) => {
|
|
78
128
|
const result = new ValidationResult();
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
129
|
+
let validatedCount = 0, skippedCount = 0;
|
|
130
|
+
for (const element of layer.elements.values()) {
|
|
131
|
+
const elementResult = await this.validateElement(element, layer.name);
|
|
132
|
+
if (elementResult.hasErrors)
|
|
133
|
+
for (const error of elementResult.errors)
|
|
134
|
+
result.addError({
|
|
135
|
+
layer: layer.name,
|
|
136
|
+
elementId: element.path || element.id,
|
|
137
|
+
message: error.message,
|
|
138
|
+
location: error.location,
|
|
139
|
+
fixSuggestion: error.fixSuggestion
|
|
140
|
+
});
|
|
141
|
+
elementResult.validated ? validatedCount++ : skippedCount++;
|
|
142
|
+
}
|
|
143
|
+
return isTelemetryEnabled && (span.setAttribute("schema.valid", result.isValid()), span.setAttribute("schema.error_count", result.errors.length), span.setAttribute("schema.validated_count", validatedCount), span.setAttribute("schema.skipped_count", skippedCount)), result;
|
|
144
|
+
}, { "schema.layer": layer.name });
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Validate a single element against its spec node schema
|
|
148
|
+
*/
|
|
149
|
+
async validateElement(element, layerName) {
|
|
150
|
+
const result = {
|
|
151
|
+
validated: !1,
|
|
152
|
+
hasErrors: !1,
|
|
153
|
+
errors: []
|
|
154
|
+
}, specNode = element.toSpecNode();
|
|
155
|
+
if (await this.ensureBaseSchemaLoaded(), !validateSpecNode(specNode) && validateSpecNode.errors) {
|
|
156
|
+
result.hasErrors = !0, result.validated = !0;
|
|
157
|
+
for (const error of validateSpecNode.errors)
|
|
158
|
+
result.errors.push({
|
|
159
|
+
message: `Element '${element.path || element.id}': ${this.formatAjvError(error)}`,
|
|
160
|
+
location: error.instancePath || "/",
|
|
161
|
+
fixSuggestion: this.generateFixSuggestion(error)
|
|
162
|
+
});
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
const validate = await this.loadSpecNodeSchema(layerName, element.type);
|
|
166
|
+
if (!validate)
|
|
167
|
+
return result.validated = !0, result;
|
|
168
|
+
if (result.validated = !0, !validate(specNode) && validate.errors) {
|
|
169
|
+
result.hasErrors = !0;
|
|
170
|
+
for (const error of validate.errors)
|
|
171
|
+
result.errors.push({
|
|
172
|
+
message: `Element '${element.path || element.id}': ${this.formatAjvError(error)}`,
|
|
173
|
+
location: error.instancePath || "/",
|
|
174
|
+
fixSuggestion: this.generateFixSuggestion(error)
|
|
175
|
+
});
|
|
95
176
|
}
|
|
177
|
+
return result;
|
|
96
178
|
}
|
|
97
179
|
/**
|
|
98
180
|
* Format AJV error message
|
|
@@ -101,7 +183,7 @@ class SchemaValidator {
|
|
|
101
183
|
const keyword = error.keyword, dataPath = error.instancePath || "/";
|
|
102
184
|
switch (keyword) {
|
|
103
185
|
case "type":
|
|
104
|
-
return `At ${dataPath}: expected ${error.params?.type}, got ${typeof error.data}`;
|
|
186
|
+
return `At ${dataPath}: expected ${error.params?.type}, got ${error.data === null ? "null" : typeof error.data}`;
|
|
105
187
|
case "required":
|
|
106
188
|
return `At ${dataPath}: missing required property '${error.params?.missingProperty}'`;
|
|
107
189
|
case "enum":
|
|
@@ -118,6 +200,8 @@ class SchemaValidator {
|
|
|
118
200
|
return `At ${dataPath}: value must be <= ${error.params?.limit}`;
|
|
119
201
|
case "additionalProperties":
|
|
120
202
|
return `At ${dataPath}: unexpected property '${error.params?.additionalProperty}'`;
|
|
203
|
+
case "const":
|
|
204
|
+
return `At ${dataPath}: value must be ${JSON.stringify(error.params?.allowedValue)}`;
|
|
121
205
|
default:
|
|
122
206
|
return error.message || "Validation failed";
|
|
123
207
|
}
|
|
@@ -137,10 +221,26 @@ class SchemaValidator {
|
|
|
137
221
|
return `Value must match pattern: ${error.params?.pattern}`;
|
|
138
222
|
case "additionalProperties":
|
|
139
223
|
return `Remove unexpected property: ${error.params?.additionalProperty}`;
|
|
224
|
+
case "const":
|
|
225
|
+
return `Value must be exactly: ${JSON.stringify(error.params?.allowedValue)}`;
|
|
140
226
|
default:
|
|
141
227
|
return;
|
|
142
228
|
}
|
|
143
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* Reset static state for test isolation
|
|
232
|
+
*
|
|
233
|
+
* Clears the shared AJV instance and schema registry, allowing tests
|
|
234
|
+
* to load schemas from a clean state without conflicts.
|
|
235
|
+
*
|
|
236
|
+
* Usage (in test setup):
|
|
237
|
+
* beforeEach(() => {
|
|
238
|
+
* SchemaValidator.reset();
|
|
239
|
+
* });
|
|
240
|
+
*/
|
|
241
|
+
static reset() {
|
|
242
|
+
SchemaValidator.sharedAjv = null, SchemaValidator.baseSchemaLoadedInSharedInstance = !1, SchemaValidator.registeredSchemaIds.clear(), SchemaValidator.layerCache.clear();
|
|
243
|
+
}
|
|
144
244
|
}
|
|
145
245
|
export {
|
|
146
246
|
SchemaValidator
|