@transitrix/diagrams 1.0.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/LICENSE +21 -0
- package/README.md +47 -0
- package/dist/__tests__/edge-path.test.d.ts +1 -0
- package/dist/__tests__/edge-path.test.js +39 -0
- package/dist/__tests__/scope.test.d.ts +1 -0
- package/dist/__tests__/scope.test.js +23 -0
- package/dist/__tests__/yaml-normalize.test.d.ts +1 -0
- package/dist/__tests__/yaml-normalize.test.js +148 -0
- package/dist/activities/__tests__/cpm.test.d.ts +1 -0
- package/dist/activities/__tests__/cpm.test.js +146 -0
- package/dist/activities/__tests__/gantt.test.d.ts +1 -0
- package/dist/activities/__tests__/gantt.test.js +224 -0
- package/dist/activities/__tests__/layout.test.d.ts +1 -0
- package/dist/activities/__tests__/layout.test.js +98 -0
- package/dist/activities/__tests__/validate.test.d.ts +1 -0
- package/dist/activities/__tests__/validate.test.js +363 -0
- package/dist/activities/cpm.d.ts +2 -0
- package/dist/activities/cpm.js +86 -0
- package/dist/activities/gantt.d.ts +6 -0
- package/dist/activities/gantt.js +250 -0
- package/dist/activities/index.d.ts +5 -0
- package/dist/activities/index.js +4 -0
- package/dist/activities/layout.d.ts +2 -0
- package/dist/activities/layout.js +103 -0
- package/dist/activities/types.d.ts +125 -0
- package/dist/activities/types.js +1 -0
- package/dist/activities/validate.d.ts +2 -0
- package/dist/activities/validate.js +326 -0
- package/dist/activity-card/__tests__/example.test.d.ts +1 -0
- package/dist/activity-card/__tests__/example.test.js +56 -0
- package/dist/activity-card/__tests__/layout.test.d.ts +1 -0
- package/dist/activity-card/__tests__/layout.test.js +57 -0
- package/dist/activity-card/__tests__/resolver.test.d.ts +1 -0
- package/dist/activity-card/__tests__/resolver.test.js +146 -0
- package/dist/activity-card/__tests__/validate.test.d.ts +1 -0
- package/dist/activity-card/__tests__/validate.test.js +116 -0
- package/dist/activity-card/index.d.ts +5 -0
- package/dist/activity-card/index.js +3 -0
- package/dist/activity-card/layout.d.ts +7 -0
- package/dist/activity-card/layout.js +167 -0
- package/dist/activity-card/resolver.d.ts +7 -0
- package/dist/activity-card/resolver.js +310 -0
- package/dist/activity-card/types.d.ts +175 -0
- package/dist/activity-card/types.js +20 -0
- package/dist/activity-card/validate.d.ts +3 -0
- package/dist/activity-card/validate.js +145 -0
- package/dist/actor/__tests__/validate.test.d.ts +1 -0
- package/dist/actor/__tests__/validate.test.js +57 -0
- package/dist/actor/index.d.ts +3 -0
- package/dist/actor/index.js +2 -0
- package/dist/actor/types.d.ts +20 -0
- package/dist/actor/types.js +4 -0
- package/dist/actor/validate.d.ts +2 -0
- package/dist/actor/validate.js +34 -0
- package/dist/applications/__tests__/validate.test.d.ts +1 -0
- package/dist/applications/__tests__/validate.test.js +223 -0
- package/dist/applications/index.d.ts +2 -0
- package/dist/applications/index.js +1 -0
- package/dist/applications/types.d.ts +39 -0
- package/dist/applications/types.js +1 -0
- package/dist/applications/validate.d.ts +3 -0
- package/dist/applications/validate.js +109 -0
- package/dist/assertion/__tests__/example.test.d.ts +1 -0
- package/dist/assertion/__tests__/example.test.js +32 -0
- package/dist/assertion/__tests__/validate.test.d.ts +1 -0
- package/dist/assertion/__tests__/validate.test.js +130 -0
- package/dist/assertion/index.d.ts +4 -0
- package/dist/assertion/index.js +2 -0
- package/dist/assertion/types.d.ts +43 -0
- package/dist/assertion/types.js +7 -0
- package/dist/assertion/validate.d.ts +9 -0
- package/dist/assertion/validate.js +137 -0
- package/dist/blocks/__tests__/layout.test.d.ts +1 -0
- package/dist/blocks/__tests__/layout.test.js +153 -0
- package/dist/blocks/__tests__/validate.test.d.ts +1 -0
- package/dist/blocks/__tests__/validate.test.js +235 -0
- package/dist/blocks/index.d.ts +4 -0
- package/dist/blocks/index.js +2 -0
- package/dist/blocks/layout.d.ts +4 -0
- package/dist/blocks/layout.js +145 -0
- package/dist/blocks/types.d.ts +56 -0
- package/dist/blocks/types.js +1 -0
- package/dist/blocks/validate.d.ts +9 -0
- package/dist/blocks/validate.js +186 -0
- package/dist/capability-map/__tests__/validate.test.d.ts +1 -0
- package/dist/capability-map/__tests__/validate.test.js +212 -0
- package/dist/capability-map/index.d.ts +2 -0
- package/dist/capability-map/index.js +1 -0
- package/dist/capability-map/types.d.ts +26 -0
- package/dist/capability-map/types.js +1 -0
- package/dist/capability-map/validate.d.ts +3 -0
- package/dist/capability-map/validate.js +103 -0
- package/dist/change/__tests__/validate.test.d.ts +1 -0
- package/dist/change/__tests__/validate.test.js +73 -0
- package/dist/change/index.d.ts +2 -0
- package/dist/change/index.js +1 -0
- package/dist/change/types.d.ts +17 -0
- package/dist/change/types.js +3 -0
- package/dist/change/validate.d.ts +2 -0
- package/dist/change/validate.js +57 -0
- package/dist/compliance/__tests__/classify.test.d.ts +1 -0
- package/dist/compliance/__tests__/classify.test.js +24 -0
- package/dist/compliance/__tests__/confidence.test.d.ts +1 -0
- package/dist/compliance/__tests__/confidence.test.js +78 -0
- package/dist/compliance/__tests__/coverage-metric.test.d.ts +1 -0
- package/dist/compliance/__tests__/coverage-metric.test.js +164 -0
- package/dist/compliance/__tests__/demo-scenario.test.d.ts +1 -0
- package/dist/compliance/__tests__/demo-scenario.test.js +226 -0
- package/dist/compliance/__tests__/example.test.d.ts +1 -0
- package/dist/compliance/__tests__/example.test.js +51 -0
- package/dist/compliance/__tests__/gap-report.test.d.ts +1 -0
- package/dist/compliance/__tests__/gap-report.test.js +113 -0
- package/dist/compliance/__tests__/html.test.d.ts +1 -0
- package/dist/compliance/__tests__/html.test.js +122 -0
- package/dist/compliance/__tests__/impact.test.d.ts +1 -0
- package/dist/compliance/__tests__/impact.test.js +410 -0
- package/dist/compliance/__tests__/markdown.test.d.ts +1 -0
- package/dist/compliance/__tests__/markdown.test.js +85 -0
- package/dist/compliance/__tests__/views.test.d.ts +1 -0
- package/dist/compliance/__tests__/views.test.js +59 -0
- package/dist/compliance/classify.d.ts +27 -0
- package/dist/compliance/classify.js +60 -0
- package/dist/compliance/confidence.d.ts +27 -0
- package/dist/compliance/confidence.js +43 -0
- package/dist/compliance/coverage-metric.d.ts +74 -0
- package/dist/compliance/coverage-metric.js +181 -0
- package/dist/compliance/gap-report.d.ts +20 -0
- package/dist/compliance/gap-report.js +64 -0
- package/dist/compliance/html.d.ts +22 -0
- package/dist/compliance/html.js +256 -0
- package/dist/compliance/impact.d.ts +251 -0
- package/dist/compliance/impact.js +464 -0
- package/dist/compliance/index.d.ts +16 -0
- package/dist/compliance/index.js +9 -0
- package/dist/compliance/markdown.d.ts +18 -0
- package/dist/compliance/markdown.js +129 -0
- package/dist/compliance/reverse-index.d.ts +8 -0
- package/dist/compliance/reverse-index.js +30 -0
- package/dist/compliance/types.d.ts +98 -0
- package/dist/compliance/types.js +5 -0
- package/dist/compliance/views.d.ts +14 -0
- package/dist/compliance/views.js +35 -0
- package/dist/compliance-matrix/__tests__/build.test.d.ts +1 -0
- package/dist/compliance-matrix/__tests__/build.test.js +183 -0
- package/dist/compliance-matrix/__tests__/example.test.d.ts +1 -0
- package/dist/compliance-matrix/__tests__/example.test.js +49 -0
- package/dist/compliance-matrix/build.d.ts +18 -0
- package/dist/compliance-matrix/build.js +0 -0
- package/dist/compliance-matrix/index.d.ts +2 -0
- package/dist/compliance-matrix/index.js +1 -0
- package/dist/compliance-matrix/types.d.ts +86 -0
- package/dist/compliance-matrix/types.js +8 -0
- package/dist/confidence/__tests__/score.test.d.ts +1 -0
- package/dist/confidence/__tests__/score.test.js +188 -0
- package/dist/confidence/index.d.ts +2 -0
- package/dist/confidence/index.js +1 -0
- package/dist/confidence/score.d.ts +62 -0
- package/dist/confidence/score.js +215 -0
- package/dist/confidence/types.d.ts +79 -0
- package/dist/confidence/types.js +6 -0
- package/dist/edge-path.d.ts +18 -0
- package/dist/edge-path.js +35 -0
- package/dist/element-envelope.d.ts +15 -0
- package/dist/element-envelope.js +55 -0
- package/dist/factor/__tests__/validate.test.d.ts +1 -0
- package/dist/factor/__tests__/validate.test.js +83 -0
- package/dist/factor/index.d.ts +3 -0
- package/dist/factor/index.js +2 -0
- package/dist/factor/types.d.ts +23 -0
- package/dist/factor/types.js +6 -0
- package/dist/factor/validate.d.ts +2 -0
- package/dist/factor/validate.js +69 -0
- package/dist/fgca/__tests__/chain-table.test.d.ts +1 -0
- package/dist/fgca/__tests__/chain-table.test.js +158 -0
- package/dist/fgca/__tests__/layout.test.d.ts +1 -0
- package/dist/fgca/__tests__/layout.test.js +140 -0
- package/dist/fgca/__tests__/parse-canonical.test.d.ts +1 -0
- package/dist/fgca/__tests__/parse-canonical.test.js +224 -0
- package/dist/fgca/__tests__/preview-layout.test.d.ts +1 -0
- package/dist/fgca/__tests__/preview-layout.test.js +106 -0
- package/dist/fgca/__tests__/validate.test.d.ts +1 -0
- package/dist/fgca/__tests__/validate.test.js +125 -0
- package/dist/fgca/chain-table.d.ts +36 -0
- package/dist/fgca/chain-table.js +177 -0
- package/dist/fgca/index.d.ts +16 -0
- package/dist/fgca/index.js +11 -0
- package/dist/fgca/layout.d.ts +27 -0
- package/dist/fgca/layout.js +156 -0
- package/dist/fgca/nodeTypes.d.ts +6 -0
- package/dist/fgca/nodeTypes.js +10 -0
- package/dist/fgca/nodes/FGCAActivityNode.d.ts +13 -0
- package/dist/fgca/nodes/FGCAActivityNode.js +35 -0
- package/dist/fgca/nodes/FGCAChangeNode.d.ts +12 -0
- package/dist/fgca/nodes/FGCAChangeNode.js +35 -0
- package/dist/fgca/nodes/FGCAFactorNode.d.ts +12 -0
- package/dist/fgca/nodes/FGCAFactorNode.js +35 -0
- package/dist/fgca/nodes/FGCAGoalNode.d.ts +14 -0
- package/dist/fgca/nodes/FGCAGoalNode.js +40 -0
- package/dist/fgca/parse-canonical.d.ts +58 -0
- package/dist/fgca/parse-canonical.js +339 -0
- package/dist/fgca/preview-layout.d.ts +87 -0
- package/dist/fgca/preview-layout.js +122 -0
- package/dist/fgca/types.d.ts +39 -0
- package/dist/fgca/types.js +7 -0
- package/dist/fgca/validate.d.ts +14 -0
- package/dist/fgca/validate.js +108 -0
- package/dist/geometry.d.ts +7 -0
- package/dist/geometry.js +2 -0
- package/dist/goals/__tests__/layout.test.d.ts +1 -0
- package/dist/goals/__tests__/layout.test.js +153 -0
- package/dist/goals/__tests__/mutations.test.d.ts +1 -0
- package/dist/goals/__tests__/mutations.test.js +95 -0
- package/dist/goals/__tests__/parse-canonical.test.d.ts +1 -0
- package/dist/goals/__tests__/parse-canonical.test.js +129 -0
- package/dist/goals/__tests__/validate.test.d.ts +1 -0
- package/dist/goals/__tests__/validate.test.js +183 -0
- package/dist/goals/index.d.ts +6 -0
- package/dist/goals/index.js +4 -0
- package/dist/goals/layout.d.ts +11 -0
- package/dist/goals/layout.js +147 -0
- package/dist/goals/mutations.d.ts +6 -0
- package/dist/goals/mutations.js +116 -0
- package/dist/goals/parse-canonical.d.ts +29 -0
- package/dist/goals/parse-canonical.js +257 -0
- package/dist/goals/types.d.ts +71 -0
- package/dist/goals/types.js +1 -0
- package/dist/goals/validate.d.ts +2 -0
- package/dist/goals/validate.js +122 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +23 -0
- package/dist/issues/__tests__/layout.test.d.ts +1 -0
- package/dist/issues/__tests__/layout.test.js +62 -0
- package/dist/issues/__tests__/validate.test.d.ts +1 -0
- package/dist/issues/__tests__/validate.test.js +82 -0
- package/dist/issues/index.d.ts +4 -0
- package/dist/issues/index.js +2 -0
- package/dist/issues/layout.d.ts +10 -0
- package/dist/issues/layout.js +82 -0
- package/dist/issues/types.d.ts +61 -0
- package/dist/issues/types.js +6 -0
- package/dist/issues/validate.d.ts +3 -0
- package/dist/issues/validate.js +143 -0
- package/dist/process-blueprint/__tests__/compliance-lane.test.d.ts +1 -0
- package/dist/process-blueprint/__tests__/compliance-lane.test.js +272 -0
- package/dist/process-blueprint/__tests__/layout.test.d.ts +1 -0
- package/dist/process-blueprint/__tests__/layout.test.js +159 -0
- package/dist/process-blueprint/__tests__/validate.test.d.ts +1 -0
- package/dist/process-blueprint/__tests__/validate.test.js +280 -0
- package/dist/process-blueprint/index.d.ts +4 -0
- package/dist/process-blueprint/index.js +2 -0
- package/dist/process-blueprint/layout.d.ts +2 -0
- package/dist/process-blueprint/layout.js +416 -0
- package/dist/process-blueprint/types.d.ts +220 -0
- package/dist/process-blueprint/types.js +1 -0
- package/dist/process-blueprint/validate.d.ts +3 -0
- package/dist/process-blueprint/validate.js +165 -0
- package/dist/process-map/__tests__/validate.test.d.ts +1 -0
- package/dist/process-map/__tests__/validate.test.js +199 -0
- package/dist/process-map/index.d.ts +2 -0
- package/dist/process-map/index.js +1 -0
- package/dist/process-map/types.d.ts +32 -0
- package/dist/process-map/types.js +1 -0
- package/dist/process-map/validate.d.ts +3 -0
- package/dist/process-map/validate.js +115 -0
- package/dist/products/__tests__/validate.test.d.ts +1 -0
- package/dist/products/__tests__/validate.test.js +210 -0
- package/dist/products/index.d.ts +2 -0
- package/dist/products/index.js +1 -0
- package/dist/products/types.d.ts +28 -0
- package/dist/products/types.js +1 -0
- package/dist/products/validate.d.ts +3 -0
- package/dist/products/validate.js +93 -0
- package/dist/repo-validate/__tests__/validate-repo.test.d.ts +1 -0
- package/dist/repo-validate/__tests__/validate-repo.test.js +132 -0
- package/dist/repo-validate/index.d.ts +2 -0
- package/dist/repo-validate/index.js +1 -0
- package/dist/repo-validate/types.d.ts +34 -0
- package/dist/repo-validate/types.js +14 -0
- package/dist/repo-validate/validate-repo.d.ts +8 -0
- package/dist/repo-validate/validate-repo.js +189 -0
- package/dist/requirement/__tests__/example.test.d.ts +1 -0
- package/dist/requirement/__tests__/example.test.js +21 -0
- package/dist/requirement/__tests__/validate.test.d.ts +1 -0
- package/dist/requirement/__tests__/validate.test.js +80 -0
- package/dist/requirement/index.d.ts +4 -0
- package/dist/requirement/index.js +2 -0
- package/dist/requirement/types.d.ts +25 -0
- package/dist/requirement/types.js +4 -0
- package/dist/requirement/validate.d.ts +7 -0
- package/dist/requirement/validate.js +81 -0
- package/dist/scenarios/__tests__/validate.test.d.ts +1 -0
- package/dist/scenarios/__tests__/validate.test.js +183 -0
- package/dist/scenarios/index.d.ts +2 -0
- package/dist/scenarios/index.js +1 -0
- package/dist/scenarios/types.d.ts +45 -0
- package/dist/scenarios/types.js +1 -0
- package/dist/scenarios/validate.d.ts +3 -0
- package/dist/scenarios/validate.js +110 -0
- package/dist/schema-version.d.ts +16 -0
- package/dist/schema-version.js +16 -0
- package/dist/scope.d.ts +23 -0
- package/dist/scope.js +38 -0
- package/dist/stakeholder/__tests__/validate.test.d.ts +1 -0
- package/dist/stakeholder/__tests__/validate.test.js +86 -0
- package/dist/stakeholder/index.d.ts +3 -0
- package/dist/stakeholder/index.js +2 -0
- package/dist/stakeholder/types.d.ts +24 -0
- package/dist/stakeholder/types.js +5 -0
- package/dist/stakeholder/validate.d.ts +2 -0
- package/dist/stakeholder/validate.js +58 -0
- package/dist/target-state/__tests__/validate.test.d.ts +1 -0
- package/dist/target-state/__tests__/validate.test.js +62 -0
- package/dist/target-state/index.d.ts +2 -0
- package/dist/target-state/index.js +1 -0
- package/dist/target-state/types.d.ts +18 -0
- package/dist/target-state/types.js +7 -0
- package/dist/target-state/validate.d.ts +2 -0
- package/dist/target-state/validate.js +61 -0
- package/dist/theme/index.d.ts +4 -0
- package/dist/theme/index.js +2 -0
- package/dist/theme/themes.d.ts +16 -0
- package/dist/theme/themes.js +213 -0
- package/dist/theme/tokens.d.ts +158 -0
- package/dist/theme/tokens.js +105 -0
- package/dist/typed-id.d.ts +20 -0
- package/dist/typed-id.js +38 -0
- package/dist/validation-types.d.ts +34 -0
- package/dist/validation-types.js +1 -0
- package/dist/webview/__tests__/entry-notations.test.d.ts +1 -0
- package/dist/webview/__tests__/entry-notations.test.js +62 -0
- package/dist/webview/__tests__/entry.test.d.ts +1 -0
- package/dist/webview/__tests__/entry.test.js +56 -0
- package/dist/webview/__tests__/render-goals.test.d.ts +1 -0
- package/dist/webview/__tests__/render-goals.test.js +65 -0
- package/dist/webview/entry.d.ts +38 -0
- package/dist/webview/entry.js +182 -0
- package/dist/webview/render-activities.d.ts +24 -0
- package/dist/webview/render-activities.js +127 -0
- package/dist/webview/render-activity-card.d.ts +5 -0
- package/dist/webview/render-activity-card.js +168 -0
- package/dist/webview/render-applications.d.ts +11 -0
- package/dist/webview/render-applications.js +105 -0
- package/dist/webview/render-blocks.d.ts +5 -0
- package/dist/webview/render-blocks.js +71 -0
- package/dist/webview/render-capability-map.d.ts +7 -0
- package/dist/webview/render-capability-map.js +88 -0
- package/dist/webview/render-fgca.d.ts +8 -0
- package/dist/webview/render-fgca.js +94 -0
- package/dist/webview/render-goals.d.ts +6 -0
- package/dist/webview/render-goals.js +92 -0
- package/dist/webview/render-process-blueprint.d.ts +5 -0
- package/dist/webview/render-process-blueprint.js +135 -0
- package/dist/webview/render-process-map.d.ts +7 -0
- package/dist/webview/render-process-map.js +82 -0
- package/dist/webview/render-products.d.ts +7 -0
- package/dist/webview/render-products.js +81 -0
- package/dist/webview/render-scenarios.d.ts +7 -0
- package/dist/webview/render-scenarios.js +75 -0
- package/dist/webview/render-util.d.ts +16 -0
- package/dist/webview/render-util.js +31 -0
- package/dist/webview/transitrix-render.css +323 -0
- package/dist/webview/transitrix-render.js +6452 -0
- package/dist/webview/transitrix-render.js.map +7 -0
- package/dist/yaml-normalize.d.ts +20 -0
- package/dist/yaml-normalize.js +57 -0
- package/package.json +40 -0
- package/src/__tests__/edge-path.test.ts +46 -0
- package/src/__tests__/scope.test.ts +27 -0
- package/src/__tests__/yaml-normalize.test.ts +158 -0
- package/src/activities/__tests__/cpm.test.ts +164 -0
- package/src/activities/__tests__/gantt.test.ts +245 -0
- package/src/activities/__tests__/layout.test.ts +112 -0
- package/src/activities/__tests__/validate.test.ts +409 -0
- package/src/activities/cpm.ts +95 -0
- package/src/activities/gantt.ts +276 -0
- package/src/activities/index.ts +28 -0
- package/src/activities/layout.ts +112 -0
- package/src/activities/types.ts +148 -0
- package/src/activities/validate.ts +357 -0
- package/src/activity-card/__tests__/example.test.ts +66 -0
- package/src/activity-card/__tests__/layout.test.ts +63 -0
- package/src/activity-card/__tests__/resolver.test.ts +161 -0
- package/src/activity-card/__tests__/validate.test.ts +131 -0
- package/src/activity-card/index.ts +32 -0
- package/src/activity-card/layout.ts +194 -0
- package/src/activity-card/resolver.ts +337 -0
- package/src/activity-card/types.ts +237 -0
- package/src/activity-card/validate.ts +158 -0
- package/src/actor/__tests__/validate.test.ts +67 -0
- package/src/actor/index.ts +3 -0
- package/src/actor/types.ts +29 -0
- package/src/actor/validate.ts +38 -0
- package/src/applications/__tests__/validate.test.ts +256 -0
- package/src/applications/index.ts +11 -0
- package/src/applications/types.ts +43 -0
- package/src/applications/validate.ts +131 -0
- package/src/assertion/__tests__/example.test.ts +37 -0
- package/src/assertion/__tests__/validate.test.ts +142 -0
- package/src/assertion/index.ts +12 -0
- package/src/assertion/types.ts +58 -0
- package/src/assertion/validate.ts +145 -0
- package/src/blocks/__tests__/layout.test.ts +169 -0
- package/src/blocks/__tests__/validate.test.ts +260 -0
- package/src/blocks/index.ts +17 -0
- package/src/blocks/layout.ts +186 -0
- package/src/blocks/types.ts +62 -0
- package/src/blocks/validate.ts +206 -0
- package/src/capability-map/__tests__/validate.test.ts +238 -0
- package/src/capability-map/index.ts +8 -0
- package/src/capability-map/types.ts +29 -0
- package/src/capability-map/validate.ts +127 -0
- package/src/change/__tests__/validate.test.ts +88 -0
- package/src/change/index.ts +2 -0
- package/src/change/types.ts +23 -0
- package/src/change/validate.ts +61 -0
- package/src/compliance/__tests__/classify.test.ts +27 -0
- package/src/compliance/__tests__/confidence.test.ts +98 -0
- package/src/compliance/__tests__/coverage-metric.test.ts +188 -0
- package/src/compliance/__tests__/demo-scenario.test.ts +273 -0
- package/src/compliance/__tests__/example.test.ts +57 -0
- package/src/compliance/__tests__/gap-report.test.ts +129 -0
- package/src/compliance/__tests__/html.test.ts +142 -0
- package/src/compliance/__tests__/impact.test.ts +482 -0
- package/src/compliance/__tests__/markdown.test.ts +95 -0
- package/src/compliance/__tests__/views.test.ts +64 -0
- package/src/compliance/classify.ts +84 -0
- package/src/compliance/confidence.ts +69 -0
- package/src/compliance/coverage-metric.ts +263 -0
- package/src/compliance/gap-report.ts +94 -0
- package/src/compliance/html.ts +295 -0
- package/src/compliance/impact.ts +677 -0
- package/src/compliance/index.ts +56 -0
- package/src/compliance/markdown.ts +135 -0
- package/src/compliance/reverse-index.ts +33 -0
- package/src/compliance/types.ts +120 -0
- package/src/compliance/views.ts +46 -0
- package/src/compliance-matrix/__tests__/build.test.ts +200 -0
- package/src/compliance-matrix/__tests__/example.test.ts +57 -0
- package/src/compliance-matrix/build.ts +0 -0
- package/src/compliance-matrix/index.ts +11 -0
- package/src/compliance-matrix/types.ts +102 -0
- package/src/confidence/__tests__/score.test.ts +254 -0
- package/src/confidence/index.ts +21 -0
- package/src/confidence/score.ts +243 -0
- package/src/confidence/types.ts +96 -0
- package/src/edge-path.ts +44 -0
- package/src/element-envelope.ts +76 -0
- package/src/factor/__tests__/validate.test.ts +98 -0
- package/src/factor/index.ts +8 -0
- package/src/factor/types.ts +41 -0
- package/src/factor/validate.ts +74 -0
- package/src/fgca/__tests__/chain-table.test.ts +172 -0
- package/src/fgca/__tests__/layout.test.ts +179 -0
- package/src/fgca/__tests__/parse-canonical.test.ts +248 -0
- package/src/fgca/__tests__/preview-layout.test.ts +124 -0
- package/src/fgca/__tests__/validate.test.ts +142 -0
- package/src/fgca/chain-table.ts +225 -0
- package/src/fgca/index.ts +45 -0
- package/src/fgca/layout.ts +204 -0
- package/src/fgca/nodeTypes.ts +11 -0
- package/src/fgca/nodes/FGCAActivityNode.tsx +65 -0
- package/src/fgca/nodes/FGCAChangeNode.tsx +64 -0
- package/src/fgca/nodes/FGCAFactorNode.tsx +64 -0
- package/src/fgca/nodes/FGCAGoalNode.tsx +72 -0
- package/src/fgca/parse-canonical.ts +400 -0
- package/src/fgca/preview-layout.ts +217 -0
- package/src/fgca/types.ts +50 -0
- package/src/fgca/validate.ts +126 -0
- package/src/geometry.ts +9 -0
- package/src/goals/__tests__/layout.test.ts +175 -0
- package/src/goals/__tests__/mutations.test.ts +111 -0
- package/src/goals/__tests__/parse-canonical.test.ts +145 -0
- package/src/goals/__tests__/validate.test.ts +202 -0
- package/src/goals/index.ts +21 -0
- package/src/goals/layout.ts +172 -0
- package/src/goals/mutations.ts +123 -0
- package/src/goals/parse-canonical.ts +286 -0
- package/src/goals/types.ts +85 -0
- package/src/goals/validate.ts +136 -0
- package/src/index.ts +23 -0
- package/src/process-blueprint/__tests__/compliance-lane.test.ts +320 -0
- package/src/process-blueprint/__tests__/layout.test.ts +195 -0
- package/src/process-blueprint/__tests__/validate.test.ts +310 -0
- package/src/process-blueprint/index.ts +31 -0
- package/src/process-blueprint/layout.ts +531 -0
- package/src/process-blueprint/types.ts +242 -0
- package/src/process-blueprint/validate.ts +181 -0
- package/src/process-map/__tests__/validate.test.ts +223 -0
- package/src/process-map/index.ts +10 -0
- package/src/process-map/types.ts +36 -0
- package/src/process-map/validate.ts +135 -0
- package/src/products/__tests__/validate.test.ts +239 -0
- package/src/products/index.ts +9 -0
- package/src/products/types.ts +31 -0
- package/src/products/validate.ts +112 -0
- package/src/repo-validate/__tests__/validate-repo.test.ts +149 -0
- package/src/repo-validate/index.ts +2 -0
- package/src/repo-validate/types.ts +50 -0
- package/src/repo-validate/validate-repo.ts +196 -0
- package/src/requirement/__tests__/example.test.ts +24 -0
- package/src/requirement/__tests__/validate.test.ts +96 -0
- package/src/requirement/index.ts +4 -0
- package/src/requirement/types.ts +35 -0
- package/src/requirement/validate.ts +92 -0
- package/src/scenarios/__tests__/validate.test.ts +209 -0
- package/src/scenarios/index.ts +15 -0
- package/src/scenarios/types.ts +37 -0
- package/src/scenarios/validate.ts +122 -0
- package/src/schema-version.ts +16 -0
- package/src/scope.ts +46 -0
- package/src/stakeholder/__tests__/validate.test.ts +101 -0
- package/src/stakeholder/index.ts +3 -0
- package/src/stakeholder/types.ts +33 -0
- package/src/stakeholder/validate.ts +63 -0
- package/src/target-state/__tests__/validate.test.ts +74 -0
- package/src/target-state/index.ts +2 -0
- package/src/target-state/types.ts +28 -0
- package/src/target-state/validate.ts +67 -0
- package/src/theme/index.ts +4 -0
- package/src/theme/themes.ts +237 -0
- package/src/theme/tokens.ts +133 -0
- package/src/typed-id.ts +56 -0
- package/src/validation-types.ts +36 -0
- package/src/webview/__tests__/entry-notations.test.ts +77 -0
- package/src/webview/__tests__/entry.test.ts +64 -0
- package/src/webview/__tests__/render-goals.test.ts +72 -0
- package/src/webview/entry.ts +292 -0
- package/src/webview/render-activities.ts +156 -0
- package/src/webview/render-activity-card.ts +234 -0
- package/src/webview/render-applications.ts +123 -0
- package/src/webview/render-blocks.ts +91 -0
- package/src/webview/render-capability-map.ts +98 -0
- package/src/webview/render-fgca.ts +122 -0
- package/src/webview/render-goals.ts +110 -0
- package/src/webview/render-process-blueprint.ts +207 -0
- package/src/webview/render-process-map.ts +93 -0
- package/src/webview/render-products.ts +91 -0
- package/src/webview/render-scenarios.ts +90 -0
- package/src/webview/render-util.ts +34 -0
- package/src/webview/styles.css +323 -0
- package/src/yaml-normalize.ts +56 -0
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { emptyCanon } from '../classify.js';
|
|
3
|
+
import {
|
|
4
|
+
buildImpactMatrix,
|
|
5
|
+
renderImpactMarkdown,
|
|
6
|
+
parseImpactViewConfig,
|
|
7
|
+
COMPLIANCE_IMPACT_DEFAULTS,
|
|
8
|
+
extractObjectDetails,
|
|
9
|
+
computeDeadlineStatus,
|
|
10
|
+
type ImpactViewConfig,
|
|
11
|
+
} from '../impact.js';
|
|
12
|
+
import type { ComplianceCanon } from '../classify.js';
|
|
13
|
+
|
|
14
|
+
// Industry- and regime-agnostic synthetic fixture: opaque ids only, no real
|
|
15
|
+
// regulator / product names, per #166 acceptance ("Output is aggregates only —
|
|
16
|
+
// no subject/obligation names tied to any real corpus").
|
|
17
|
+
function buildCanon(): ComplianceCanon {
|
|
18
|
+
const canon = emptyCanon();
|
|
19
|
+
canon.products.push({ id: 'PRODUCT-A-1', name: 'Product A' });
|
|
20
|
+
canon.products.push({ id: 'PRODUCT-B-1', name: 'Product B' });
|
|
21
|
+
canon.requirements.push(
|
|
22
|
+
{ id: 'REQUIREMENT-FOO-1', name: 'Foo', derived_from: ['LAW-X-1'] },
|
|
23
|
+
{ id: 'REQUIREMENT-BAR-1', name: 'Bar', derived_from: ['LAW-X-1', 'REGULATION-Y-1'] },
|
|
24
|
+
{ id: 'REQUIREMENT-BAZ-1', name: 'Baz', derived_from: ['REGULATION-Y-1'] },
|
|
25
|
+
// Internal-only requirement, no codex derivation:
|
|
26
|
+
{ id: 'REQUIREMENT-INT-1', name: 'Internal-only' },
|
|
27
|
+
);
|
|
28
|
+
canon.assertions.push(
|
|
29
|
+
// (PRODUCT-A-1, FOO) — two assertions; partial wins over compliant per §5.2 step 4.
|
|
30
|
+
{ id: 'ASSERTION-1', about: 'REQUIREMENT-FOO-1', subject: 'PRODUCT-A-1', status: 'compliant' },
|
|
31
|
+
{ id: 'ASSERTION-2', about: 'REQUIREMENT-FOO-1', subject: 'PRODUCT-A-1', status: 'partial' },
|
|
32
|
+
// (PRODUCT-A-1, BAR) — single n_a → modelled fact ("No obligation applies").
|
|
33
|
+
{ id: 'ASSERTION-3', about: 'REQUIREMENT-BAR-1', subject: 'PRODUCT-A-1', status: 'n_a' },
|
|
34
|
+
// (PRODUCT-B-1, BAR) — non_compliant.
|
|
35
|
+
{ id: 'ASSERTION-4', about: 'REQUIREMENT-BAR-1', subject: 'PRODUCT-B-1', status: 'non_compliant' },
|
|
36
|
+
// (PRODUCT-B-1, BAZ) — under_review beats compliant.
|
|
37
|
+
{ id: 'ASSERTION-5', about: 'REQUIREMENT-BAZ-1', subject: 'PRODUCT-B-1', status: 'under_review' },
|
|
38
|
+
{ id: 'ASSERTION-6', about: 'REQUIREMENT-BAZ-1', subject: 'PRODUCT-B-1', status: 'compliant' },
|
|
39
|
+
);
|
|
40
|
+
return canon;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const baseConfig: ImpactViewConfig = {
|
|
44
|
+
id: 'COMPLIANCE_IMPACT-FIXTURE-1',
|
|
45
|
+
name: 'Fixture matrix',
|
|
46
|
+
subjects: { products: ['PRODUCT-A-1', 'PRODUCT-B-1'] },
|
|
47
|
+
obligations: {},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
describe('buildImpactMatrix', () => {
|
|
51
|
+
it('aggregates statuses per §5.2 step 4 — non_compliant > partial > under_review > compliant', () => {
|
|
52
|
+
const canon = buildCanon();
|
|
53
|
+
const matrix = buildImpactMatrix(canon, {
|
|
54
|
+
...baseConfig,
|
|
55
|
+
obligations: { include: ['REQUIREMENT-FOO-1', 'REQUIREMENT-BAR-1', 'REQUIREMENT-BAZ-1'] },
|
|
56
|
+
});
|
|
57
|
+
expect(matrix.columns.map(c => c.label)).toEqual(['PRODUCT-A-1', 'PRODUCT-B-1']);
|
|
58
|
+
expect(matrix.rows.map(r => r.id)).toEqual(['REQUIREMENT-BAR-1', 'REQUIREMENT-BAZ-1', 'REQUIREMENT-FOO-1']);
|
|
59
|
+
|
|
60
|
+
const cellFor = (req: string, subject: string) => {
|
|
61
|
+
const r = matrix.rows.findIndex(x => x.id === req);
|
|
62
|
+
const c = matrix.columns.findIndex(col => col.label === subject);
|
|
63
|
+
return matrix.cells[r][c];
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
expect(cellFor('REQUIREMENT-FOO-1', 'PRODUCT-A-1').status).toBe('partial');
|
|
67
|
+
expect(cellFor('REQUIREMENT-FOO-1', 'PRODUCT-A-1').kind).toBe('bound');
|
|
68
|
+
expect(cellFor('REQUIREMENT-BAR-1', 'PRODUCT-A-1').kind).toBe('n_a_only');
|
|
69
|
+
expect(cellFor('REQUIREMENT-BAR-1', 'PRODUCT-B-1').status).toBe('non_compliant');
|
|
70
|
+
expect(cellFor('REQUIREMENT-BAZ-1', 'PRODUCT-B-1').status).toBe('under_review');
|
|
71
|
+
expect(cellFor('REQUIREMENT-FOO-1', 'PRODUCT-B-1').kind).toBe('gap');
|
|
72
|
+
expect(cellFor('REQUIREMENT-BAZ-1', 'PRODUCT-A-1').kind).toBe('gap');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('selects obligations by `filter.derived_from_codex`', () => {
|
|
76
|
+
const canon = buildCanon();
|
|
77
|
+
const matrix = buildImpactMatrix(canon, {
|
|
78
|
+
...baseConfig,
|
|
79
|
+
obligations: { filter: { derived_from_codex: ['REGULATION-Y-1'] } },
|
|
80
|
+
});
|
|
81
|
+
expect(matrix.rows.map(r => r.id)).toEqual(['REQUIREMENT-BAR-1', 'REQUIREMENT-BAZ-1']);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('`include` wins when both `include` and `filter` are present (COMPIMP-007)', () => {
|
|
85
|
+
const canon = buildCanon();
|
|
86
|
+
const matrix = buildImpactMatrix(canon, {
|
|
87
|
+
...baseConfig,
|
|
88
|
+
obligations: {
|
|
89
|
+
include: ['REQUIREMENT-INT-1'],
|
|
90
|
+
filter: { derived_from_codex: ['LAW-X-1'] },
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
expect(matrix.rows.map(r => r.id)).toEqual(['REQUIREMENT-INT-1']);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('distinguishes modelling gap from n_a-only per §5.3', () => {
|
|
97
|
+
const canon = buildCanon();
|
|
98
|
+
const matrix = buildImpactMatrix(canon, {
|
|
99
|
+
...baseConfig,
|
|
100
|
+
obligations: { include: ['REQUIREMENT-BAR-1'] },
|
|
101
|
+
});
|
|
102
|
+
const aCell = matrix.cells[0][matrix.columns.findIndex(c => c.label === 'PRODUCT-A-1')];
|
|
103
|
+
const bCell = matrix.cells[0][matrix.columns.findIndex(c => c.label === 'PRODUCT-B-1')];
|
|
104
|
+
expect(aCell.kind).toBe('n_a_only');
|
|
105
|
+
expect(bCell.kind).toBe('bound');
|
|
106
|
+
expect(bCell.status).toBe('non_compliant');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('respects `status_display.show` — filtered-out statuses leave the cell as a gap', () => {
|
|
110
|
+
const canon = buildCanon();
|
|
111
|
+
const matrix = buildImpactMatrix(canon, {
|
|
112
|
+
...baseConfig,
|
|
113
|
+
obligations: { include: ['REQUIREMENT-BAZ-1'] },
|
|
114
|
+
status_display: { show: ['non_compliant'] }, // under_review + compliant filtered out
|
|
115
|
+
});
|
|
116
|
+
const cell = matrix.cells[0][matrix.columns.findIndex(c => c.label === 'PRODUCT-B-1')];
|
|
117
|
+
expect(cell.kind).toBe('gap');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('keeps subjects with no binding obligation as columns so the gap is visible', () => {
|
|
121
|
+
const canon = buildCanon();
|
|
122
|
+
const matrix = buildImpactMatrix(canon, {
|
|
123
|
+
...baseConfig,
|
|
124
|
+
subjects: { products: ['PRODUCT-A-1', 'PRODUCT-B-1', 'PRODUCT-NONE-1'] },
|
|
125
|
+
obligations: { include: ['REQUIREMENT-FOO-1'] },
|
|
126
|
+
});
|
|
127
|
+
expect(matrix.columns.map(c => c.label)).toContain('PRODUCT-NONE-1');
|
|
128
|
+
const cell = matrix.cells[0][matrix.columns.findIndex(c => c.label === 'PRODUCT-NONE-1')];
|
|
129
|
+
expect(cell.kind).toBe('gap');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('orders rows alphabetically by name when `order_rows_by: name`', () => {
|
|
133
|
+
const canon = buildCanon();
|
|
134
|
+
const matrix = buildImpactMatrix(canon, {
|
|
135
|
+
...baseConfig,
|
|
136
|
+
obligations: { include: ['REQUIREMENT-FOO-1', 'REQUIREMENT-BAR-1', 'REQUIREMENT-BAZ-1'] },
|
|
137
|
+
order_rows_by: 'name',
|
|
138
|
+
});
|
|
139
|
+
expect(matrix.rows.map(r => r.name)).toEqual(['Bar', 'Baz', 'Foo']);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('renderImpactMarkdown', () => {
|
|
144
|
+
it('renders a header, table, and §5.3 legend', () => {
|
|
145
|
+
const canon = buildCanon();
|
|
146
|
+
const matrix = buildImpactMatrix(canon, {
|
|
147
|
+
...baseConfig,
|
|
148
|
+
obligations: { include: ['REQUIREMENT-FOO-1', 'REQUIREMENT-BAR-1'] },
|
|
149
|
+
});
|
|
150
|
+
const md = renderImpactMarkdown(matrix);
|
|
151
|
+
expect(md).toContain('# Fixture matrix');
|
|
152
|
+
expect(md).toContain('View ID: `COMPLIANCE_IMPACT-FIXTURE-1`');
|
|
153
|
+
expect(md).toContain('| Obligation | PRODUCT-A-1 | PRODUCT-B-1 |');
|
|
154
|
+
expect(md).toContain('PARTIAL');
|
|
155
|
+
expect(md).toContain('FAIL');
|
|
156
|
+
expect(md).toContain('No mapped obligation (current model)');
|
|
157
|
+
expect(md).toContain('No obligation applies');
|
|
158
|
+
expect(md).toContain('## Legend');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('honours custom empty-cell labels', () => {
|
|
162
|
+
const canon = buildCanon();
|
|
163
|
+
const matrix = buildImpactMatrix(canon, {
|
|
164
|
+
...baseConfig,
|
|
165
|
+
obligations: { include: ['REQUIREMENT-FOO-1', 'REQUIREMENT-BAR-1'] },
|
|
166
|
+
empty_cells: {
|
|
167
|
+
no_obligation_label: 'GAP',
|
|
168
|
+
no_obligation_applies_label: 'EXCLUDED',
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
const md = renderImpactMarkdown(matrix);
|
|
172
|
+
expect(md).toContain('GAP');
|
|
173
|
+
expect(md).toContain('EXCLUDED');
|
|
174
|
+
expect(md).not.toContain('No mapped obligation (current model)');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('renders a placeholder when zero obligations are selected', () => {
|
|
178
|
+
const canon = buildCanon();
|
|
179
|
+
const matrix = buildImpactMatrix(canon, {
|
|
180
|
+
...baseConfig,
|
|
181
|
+
obligations: { include: ['REQUIREMENT-DOES-NOT-EXIST-1'] },
|
|
182
|
+
});
|
|
183
|
+
const md = renderImpactMarkdown(matrix);
|
|
184
|
+
expect(md).toContain('No obligations in scope');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('propagates snapshot_at into the matrix and renders it', () => {
|
|
188
|
+
const canon = buildCanon();
|
|
189
|
+
const matrix = buildImpactMatrix(canon, { ...baseConfig, snapshot_at: '2026-06-09' });
|
|
190
|
+
expect(matrix.snapshotAt).toBe('2026-06-09');
|
|
191
|
+
const md = renderImpactMarkdown(matrix);
|
|
192
|
+
expect(md).toContain('Report snapshot: 2026-06-09');
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// ── parseImpactViewConfig ───────────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
describe('parseImpactViewConfig', () => {
|
|
199
|
+
it('returns ok:false for a non-object', () => {
|
|
200
|
+
const r = parseImpactViewConfig(null);
|
|
201
|
+
expect(r.ok).toBe(false);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('returns ok:false when id is missing', () => {
|
|
205
|
+
const r = parseImpactViewConfig({ name: 'X', subjects: {} });
|
|
206
|
+
expect(r.ok).toBe(false);
|
|
207
|
+
if (!r.ok) expect(r.errors.some(e => e.includes('id'))).toBe(true);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('returns ok:false when name is missing', () => {
|
|
211
|
+
const r = parseImpactViewConfig({ id: 'X', subjects: {} });
|
|
212
|
+
expect(r.ok).toBe(false);
|
|
213
|
+
if (!r.ok) expect(r.errors.some(e => e.includes('name'))).toBe(true);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('unwraps the top-level view: wrapper', () => {
|
|
217
|
+
const r = parseImpactViewConfig({ view: { id: 'V-1', name: 'My view', subjects: {} } });
|
|
218
|
+
expect(r.ok).toBe(true);
|
|
219
|
+
if (r.ok) expect(r.config.id).toBe('V-1');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('fills all optional fields from COMPLIANCE_IMPACT_DEFAULTS', () => {
|
|
223
|
+
const r = parseImpactViewConfig({ id: 'V-1', name: 'My view', subjects: {} });
|
|
224
|
+
expect(r.ok).toBe(true);
|
|
225
|
+
if (!r.ok) return;
|
|
226
|
+
const c = r.config;
|
|
227
|
+
expect(c.subjects.products).toEqual([]);
|
|
228
|
+
expect(c.subjects.processes).toEqual([]);
|
|
229
|
+
expect(c.status_display?.show).toEqual(COMPLIANCE_IMPACT_DEFAULTS.status_display.show);
|
|
230
|
+
expect(c.order_rows_by).toBe('id');
|
|
231
|
+
expect(c.empty_cells?.no_obligation_label).toBe(
|
|
232
|
+
COMPLIANCE_IMPACT_DEFAULTS.empty_cells.no_obligation_label,
|
|
233
|
+
);
|
|
234
|
+
expect(c.empty_cells?.no_obligation_applies_label).toBe(
|
|
235
|
+
COMPLIANCE_IMPACT_DEFAULTS.empty_cells.no_obligation_applies_label,
|
|
236
|
+
);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('preserves explicit subject lists and snapshot_at', () => {
|
|
240
|
+
const r = parseImpactViewConfig({
|
|
241
|
+
id: 'V-2',
|
|
242
|
+
name: 'View 2',
|
|
243
|
+
snapshot_at: '2026-01-01',
|
|
244
|
+
subjects: { products: ['PRODUCT-A-1', 'PRODUCT-B-1'] },
|
|
245
|
+
});
|
|
246
|
+
expect(r.ok).toBe(true);
|
|
247
|
+
if (!r.ok) return;
|
|
248
|
+
expect(r.config.subjects.products).toEqual(['PRODUCT-A-1', 'PRODUCT-B-1']);
|
|
249
|
+
expect(r.config.snapshot_at).toBe('2026-01-01');
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('applies custom order_rows_by:name', () => {
|
|
253
|
+
const r = parseImpactViewConfig({ id: 'V-3', name: 'V', subjects: {}, order_rows_by: 'name' });
|
|
254
|
+
expect(r.ok).toBe(true);
|
|
255
|
+
if (r.ok) expect(r.config.order_rows_by).toBe('name');
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('parses obligation filter correctly', () => {
|
|
259
|
+
const r = parseImpactViewConfig({
|
|
260
|
+
id: 'V-4',
|
|
261
|
+
name: 'V',
|
|
262
|
+
subjects: {},
|
|
263
|
+
obligations: { filter: { derived_from_codex: ['LAW-X-1'] } },
|
|
264
|
+
});
|
|
265
|
+
expect(r.ok).toBe(true);
|
|
266
|
+
if (r.ok) expect(r.config.obligations.filter?.derived_from_codex).toEqual(['LAW-X-1']);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe('CV-3a — stage grouping (ImpactColumn, extractObjectDetails, buildImpactMatrix stage grain)', () => {
|
|
271
|
+
it('default product grain: columns are plain ImpactColumn with label=subjectId', () => {
|
|
272
|
+
const canon = buildCanon();
|
|
273
|
+
const matrix = buildImpactMatrix(canon, baseConfig);
|
|
274
|
+
expect(matrix.columns.map(c => c.subjectId)).toEqual(['PRODUCT-A-1', 'PRODUCT-B-1']);
|
|
275
|
+
expect(matrix.columns.map(c => c.label)).toEqual(['PRODUCT-A-1', 'PRODUCT-B-1']);
|
|
276
|
+
expect(matrix.columns.every(c => !c.stageId)).toBe(true);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('product-stage grain: expands columns by stage, label is subjectId:stageId', () => {
|
|
280
|
+
const canon = buildCanon();
|
|
281
|
+
const matrix = buildImpactMatrix(
|
|
282
|
+
canon,
|
|
283
|
+
{ ...baseConfig, subjects: { products: ['PRODUCT-A-1'] }, grouping: { columns: 'object-details' } },
|
|
284
|
+
[{ objectId: 'PRODUCT-A-1', details: [{ id: 'STAGE-1', name: 'Stage One' }, { id: 'STAGE-2', name: 'Stage Two' }] }],
|
|
285
|
+
);
|
|
286
|
+
expect(matrix.columns.map(c => c.label)).toEqual(['PRODUCT-A-1:STAGE-1', 'PRODUCT-A-1:STAGE-2']);
|
|
287
|
+
expect(matrix.columns.map(c => c.stageId)).toEqual(['STAGE-1', 'STAGE-2']);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('assertion without realised_via fills every stage column for its subject', () => {
|
|
291
|
+
const canon = emptyCanon();
|
|
292
|
+
canon.requirements.push({ id: 'REQ-1', name: 'R1' });
|
|
293
|
+
// Assertion with no realised_via — covers the whole subject.
|
|
294
|
+
canon.assertions.push({ id: 'ASS-1', about: 'REQ-1', subject: 'PROD-1', status: 'compliant' });
|
|
295
|
+
const matrix = buildImpactMatrix(
|
|
296
|
+
canon,
|
|
297
|
+
{
|
|
298
|
+
id: 'V', name: 'V',
|
|
299
|
+
subjects: { products: ['PROD-1'] },
|
|
300
|
+
obligations: {},
|
|
301
|
+
grouping: { columns: 'object-details' },
|
|
302
|
+
},
|
|
303
|
+
[{ objectId: 'PROD-1', details: [{ id: 'S1', name: 's1' }, { id: 'S2', name: 's2' }] }],
|
|
304
|
+
);
|
|
305
|
+
// Both stage columns should be bound (no realised_via means all stages inherit).
|
|
306
|
+
expect(matrix.cells[0][0].kind).toBe('bound');
|
|
307
|
+
expect(matrix.cells[0][1].kind).toBe('bound');
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('assertion with realised_via fills only the matching stage column', () => {
|
|
311
|
+
const canon = emptyCanon();
|
|
312
|
+
canon.requirements.push({ id: 'REQ-1', name: 'R1' });
|
|
313
|
+
// Assertion only covers STAGE-A.
|
|
314
|
+
canon.assertions.push({
|
|
315
|
+
id: 'ASS-1', about: 'REQ-1', subject: 'PROD-1', status: 'compliant',
|
|
316
|
+
realised_via: ['STAGE-A'],
|
|
317
|
+
});
|
|
318
|
+
const matrix = buildImpactMatrix(
|
|
319
|
+
canon,
|
|
320
|
+
{
|
|
321
|
+
id: 'V', name: 'V',
|
|
322
|
+
subjects: { products: ['PROD-1'] },
|
|
323
|
+
obligations: {},
|
|
324
|
+
grouping: { columns: 'object-details' },
|
|
325
|
+
},
|
|
326
|
+
[{ objectId: 'PROD-1', details: [{ id: 'STAGE-A', name: 'a' }, { id: 'STAGE-B', name: 'b' }] }],
|
|
327
|
+
);
|
|
328
|
+
expect(matrix.cells[0][0].kind).toBe('bound'); // STAGE-A: covered
|
|
329
|
+
expect(matrix.cells[0][1].kind).toBe('gap'); // STAGE-B: gap
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('subject with no stage mapping falls back to single product-grain column', () => {
|
|
333
|
+
const canon = buildCanon();
|
|
334
|
+
const matrix = buildImpactMatrix(
|
|
335
|
+
canon,
|
|
336
|
+
{
|
|
337
|
+
...baseConfig,
|
|
338
|
+
subjects: { products: ['PRODUCT-A-1', 'PRODUCT-B-1'] },
|
|
339
|
+
grouping: { columns: 'object-details' },
|
|
340
|
+
},
|
|
341
|
+
// Only provide stages for A — B falls back to a single column.
|
|
342
|
+
[{ objectId: 'PRODUCT-A-1', details: [{ id: 'S1', name: 's1' }] }],
|
|
343
|
+
);
|
|
344
|
+
expect(matrix.columns.map(c => c.label)).toEqual(['PRODUCT-A-1:S1', 'PRODUCT-B-1']);
|
|
345
|
+
// PRODUCT-B-1 fallback column: no stageId.
|
|
346
|
+
expect(matrix.columns[1].stageId).toBeUndefined();
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('extractObjectDetails — returns null for non-blueprint docs', () => {
|
|
350
|
+
expect(extractObjectDetails(null)).toBeNull();
|
|
351
|
+
expect(extractObjectDetails({ notation: 'requirement', id: 'R1' })).toBeNull();
|
|
352
|
+
expect(extractObjectDetails({ process_blueprint: { details: [{ id: 'S1' }] } })).toBeNull(); // missing id
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('extractObjectDetails — extracts subjectId and stages from process_blueprint wrapper', () => {
|
|
356
|
+
const doc = { process_blueprint: { id: 'PROC-1', notation: 'process_blueprint', stages: [{ id: 'S1', name: 'Stage One' }, { id: 'S2', name: 'Stage Two' }] } };
|
|
357
|
+
const sg = extractObjectDetails(doc);
|
|
358
|
+
expect(sg).not.toBeNull();
|
|
359
|
+
expect(sg!.objectId).toBe('PROC-1');
|
|
360
|
+
expect(sg!.details.map((s: { id: string }) => s.id)).toEqual(['S1', 'S2']);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it('renderImpactMarkdown uses column labels in the table header', () => {
|
|
364
|
+
const canon = emptyCanon();
|
|
365
|
+
canon.requirements.push({ id: 'REQ-1', name: 'Req' });
|
|
366
|
+
canon.assertions.push({ id: 'ASS-1', about: 'REQ-1', subject: 'PROD-1', status: 'compliant' });
|
|
367
|
+
const matrix = buildImpactMatrix(
|
|
368
|
+
canon,
|
|
369
|
+
{
|
|
370
|
+
id: 'V', name: 'Stage Report',
|
|
371
|
+
subjects: { products: ['PROD-1'] },
|
|
372
|
+
obligations: {},
|
|
373
|
+
grouping: { columns: 'object-details' },
|
|
374
|
+
},
|
|
375
|
+
[{ objectId: 'PROD-1', details: [{ id: 'S1', name: 'Stage One' }] }],
|
|
376
|
+
);
|
|
377
|
+
const md = renderImpactMarkdown(matrix);
|
|
378
|
+
expect(md).toContain('PROD-1:S1');
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
describe('CV-3 -- blueprint-lane decorations + obligations lane', () => {
|
|
383
|
+
it('computeDeadlineStatus function is available', () => {
|
|
384
|
+
expect(typeof computeDeadlineStatus).toBe('function');
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it('cell.decoration.isNew = false when no snapshot_at', () => {
|
|
388
|
+
const canon = emptyCanon();
|
|
389
|
+
canon.requirements.push({ id: 'REQ-1', name: 'R1', admitted_at: '2026-01-01' });
|
|
390
|
+
canon.assertions.push({ id: 'ASS-1', about: 'REQ-1', subject: 'PROD-1', status: 'compliant' });
|
|
391
|
+
const matrix = buildImpactMatrix(canon, {
|
|
392
|
+
id: 'V', name: 'V',
|
|
393
|
+
subjects: { products: ['PROD-1'] },
|
|
394
|
+
obligations: {},
|
|
395
|
+
});
|
|
396
|
+
expect(matrix.cells[0][0].decoration.isNew).toBe(false);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('cell.decoration.isNew = true when req admitted after snapshot_at', () => {
|
|
400
|
+
const canon = emptyCanon();
|
|
401
|
+
canon.requirements.push({ id: 'REQ-1', name: 'R1', admitted_at: '2026-06-01' });
|
|
402
|
+
canon.assertions.push({ id: 'ASS-1', about: 'REQ-1', subject: 'PROD-1', status: 'compliant' });
|
|
403
|
+
const matrix = buildImpactMatrix(canon, {
|
|
404
|
+
id: 'V', name: 'V',
|
|
405
|
+
snapshot_at: '2026-05-01',
|
|
406
|
+
subjects: { products: ['PROD-1'] },
|
|
407
|
+
obligations: {},
|
|
408
|
+
});
|
|
409
|
+
// admitted_at (2026-06-01) > snapshot_at (2026-05-01) -> isNew
|
|
410
|
+
expect(matrix.cells[0][0].decoration.isNew).toBe(true);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('cell.decoration.isUrgent = true when gap + past_due deadline', () => {
|
|
414
|
+
const canon = emptyCanon();
|
|
415
|
+
canon.requirements.push({ id: 'REQ-1', name: 'R1', deadline: '2000-01-01' });
|
|
416
|
+
// No assertion -> gap cell
|
|
417
|
+
const matrix = buildImpactMatrix(canon, {
|
|
418
|
+
id: 'V', name: 'V',
|
|
419
|
+
subjects: { products: ['PROD-1'] },
|
|
420
|
+
obligations: {},
|
|
421
|
+
});
|
|
422
|
+
expect(matrix.cells[0][0].kind).toBe('gap');
|
|
423
|
+
expect(matrix.cells[0][0].decoration.isUrgent).toBe(true);
|
|
424
|
+
expect(matrix.cells[0][0].decoration.deadlineStatus).toBe('past_due');
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('cell.decoration.isUrgent = false on bound cell even with past deadline', () => {
|
|
428
|
+
const canon = emptyCanon();
|
|
429
|
+
canon.requirements.push({ id: 'REQ-1', name: 'R1', deadline: '2000-01-01' });
|
|
430
|
+
canon.assertions.push({ id: 'ASS-1', about: 'REQ-1', subject: 'PROD-1', status: 'compliant' });
|
|
431
|
+
const matrix = buildImpactMatrix(canon, {
|
|
432
|
+
id: 'V', name: 'V',
|
|
433
|
+
subjects: { products: ['PROD-1'] },
|
|
434
|
+
obligations: {},
|
|
435
|
+
});
|
|
436
|
+
expect(matrix.cells[0][0].kind).toBe('bound');
|
|
437
|
+
// Not urgent: cell is bound, not a gap
|
|
438
|
+
expect(matrix.cells[0][0].decoration.isUrgent).toBe(false);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it('obligationsLane lists derived_from codex IDs for non-gap columns', () => {
|
|
442
|
+
const canon = emptyCanon();
|
|
443
|
+
canon.requirements.push({ id: 'REQ-1', name: 'R1', derived_from: ['LAW-X', 'REG-Y'] });
|
|
444
|
+
canon.requirements.push({ id: 'REQ-2', name: 'R2', derived_from: ['LAW-X'] });
|
|
445
|
+
// Only REQ-1 is bound in PROD-1 column; REQ-2 is a gap
|
|
446
|
+
canon.assertions.push({ id: 'ASS-1', about: 'REQ-1', subject: 'PROD-1', status: 'compliant' });
|
|
447
|
+
const matrix = buildImpactMatrix(canon, {
|
|
448
|
+
id: 'V', name: 'V',
|
|
449
|
+
subjects: { products: ['PROD-1'] },
|
|
450
|
+
obligations: {},
|
|
451
|
+
});
|
|
452
|
+
// obligationsLane[0] (PROD-1 column): REQ-1 is bound -> derived_from -> LAW-X, REG-Y sorted
|
|
453
|
+
expect(matrix.obligationsLane[0]).toEqual(['LAW-X', 'REG-Y']);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it('obligationsLane is empty for columns with only gap cells', () => {
|
|
457
|
+
const canon = emptyCanon();
|
|
458
|
+
canon.requirements.push({ id: 'REQ-1', name: 'R1', derived_from: ['LAW-X'] });
|
|
459
|
+
// No assertions -> all gaps
|
|
460
|
+
const matrix = buildImpactMatrix(canon, {
|
|
461
|
+
id: 'V', name: 'V',
|
|
462
|
+
subjects: { products: ['PROD-1'] },
|
|
463
|
+
obligations: {},
|
|
464
|
+
});
|
|
465
|
+
expect(matrix.obligationsLane[0]).toEqual([]);
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
describe('computeDeadlineStatus', () => {
|
|
470
|
+
it('returns none for missing deadline', () => {
|
|
471
|
+
expect(computeDeadlineStatus(undefined, '2026-06-01')).toBe('none');
|
|
472
|
+
});
|
|
473
|
+
it('returns past_due when deadline < today', () => {
|
|
474
|
+
expect(computeDeadlineStatus('2026-01-01', '2026-06-01')).toBe('past_due');
|
|
475
|
+
});
|
|
476
|
+
it('returns in_force within 30 days', () => {
|
|
477
|
+
expect(computeDeadlineStatus('2026-06-20', '2026-06-01')).toBe('in_force');
|
|
478
|
+
});
|
|
479
|
+
it('returns upcoming more than 30 days away', () => {
|
|
480
|
+
expect(computeDeadlineStatus('2026-12-31', '2026-06-01')).toBe('upcoming');
|
|
481
|
+
});
|
|
482
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import yaml from 'js-yaml';
|
|
6
|
+
import { renderComplianceMarkdown } from '../markdown.js';
|
|
7
|
+
import { emptyCanon, ingestComplianceDoc, type ComplianceCanon } from '../classify.js';
|
|
8
|
+
|
|
9
|
+
function synthetic(): ComplianceCanon {
|
|
10
|
+
const canon = emptyCanon();
|
|
11
|
+
ingestComplianceDoc(canon, { notation: 'product', id: 'PRODUCT-M-1', name: 'Mobile App' });
|
|
12
|
+
ingestComplianceDoc(canon, { notation: 'requirement', id: 'REQUIREMENT-ERASURE-1', name: 'Erasure', severity: 'high', derived_from: ['LAW-X-1'] });
|
|
13
|
+
ingestComplianceDoc(canon, { notation: 'requirement', id: 'REQUIREMENT-LOG-1', name: 'Logs', severity: 'low' });
|
|
14
|
+
ingestComplianceDoc(canon, { notation: 'assertion', id: 'ASSERTION-1', about: 'REQUIREMENT-ERASURE-1', subject: 'PRODUCT-M-1', status: 'compliant', evidence: [{ kind: 'note', text: 'x' }], next_review_at: '2026-09-01' });
|
|
15
|
+
ingestComplianceDoc(canon, { id: 'LAW-X-1', name: 'Privacy Law', zone: 'codex', type: 'LAW' });
|
|
16
|
+
return canon;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('renderComplianceMarkdown — matrix', () => {
|
|
20
|
+
it('renders a table with status cells and gap dashes', () => {
|
|
21
|
+
const md = renderComplianceMarkdown(synthetic(), { mode: 'matrix' });
|
|
22
|
+
expect(md).toContain('# Compliance Matrix');
|
|
23
|
+
expect(md).toContain('| Product \\ Requirement | Erasure | Logs |');
|
|
24
|
+
expect(md).toMatch(/\| Mobile App \| Compliant \| — \|/);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('renderComplianceMarkdown — law', () => {
|
|
29
|
+
it('renders the law name and its requirement with the assertion bullet', () => {
|
|
30
|
+
const md = renderComplianceMarkdown(synthetic(), { mode: 'law', id: 'LAW-X-1' });
|
|
31
|
+
expect(md).toContain('# Compliance — Privacy Law');
|
|
32
|
+
expect(md).toContain('## Erasure (`REQUIREMENT-ERASURE-1`) — severity: high');
|
|
33
|
+
expect(md).toContain('- **Compliant** — `ASSERTION-1`');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('renderComplianceMarkdown — product', () => {
|
|
38
|
+
it('renders the product table of asserted requirements', () => {
|
|
39
|
+
const md = renderComplianceMarkdown(synthetic(), { mode: 'product', id: 'PRODUCT-M-1' });
|
|
40
|
+
expect(md).toContain('# Compliance — Mobile App');
|
|
41
|
+
expect(md).toContain('| Requirement | Status | Assertion | Next review |');
|
|
42
|
+
expect(md).toMatch(/\| Erasure \(`REQUIREMENT-ERASURE-1`\) \| Compliant \| `ASSERTION-1` \| 2026-09-01 \|/);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('renderComplianceMarkdown — gap', () => {
|
|
47
|
+
it('lists the un-asserted requirement as a checklist item', () => {
|
|
48
|
+
const md = renderComplianceMarkdown(synthetic(), { mode: 'gap' }, { today: '2026-06-02' });
|
|
49
|
+
expect(md).toContain('## Requirements without assertions (1)');
|
|
50
|
+
expect(md).toContain('- [ ] `REQUIREMENT-LOG-1` Logs — severity: low');
|
|
51
|
+
expect(md).toContain('## Assertions without evidence — ASSERT-007 (0)');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// ── Conformance: build canon by ingesting the acme example files ────────────
|
|
56
|
+
|
|
57
|
+
const examples = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../../tests/fixtures/notation-corpus');
|
|
58
|
+
function ingestDir(canon: ComplianceCanon, dir: string): void {
|
|
59
|
+
const full = path.join(examples, dir);
|
|
60
|
+
for (const f of readdirSync(full)) {
|
|
61
|
+
if (!f.endsWith('.yaml')) continue;
|
|
62
|
+
ingestComplianceDoc(canon, yaml.load(readFileSync(path.join(full, f), 'utf-8')));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
describe('renderComplianceMarkdown — acme conformance', () => {
|
|
67
|
+
const canon = emptyCanon();
|
|
68
|
+
ingestDir(canon, 'product');
|
|
69
|
+
ingestDir(canon, 'requirement');
|
|
70
|
+
ingestDir(canon, 'assertion');
|
|
71
|
+
|
|
72
|
+
it('matrix marks the dangling PRODUCT-MOBILE-1 row and shows compliant + gaps', () => {
|
|
73
|
+
const md = renderComplianceMarkdown(canon, { mode: 'matrix' });
|
|
74
|
+
expect(md).toContain('PRODUCT-MOBILE-1 ⚠');
|
|
75
|
+
expect(md).toContain('Compliant');
|
|
76
|
+
expect(md).toContain('—'); // at least one gap dash
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('gap report surfaces the un-asserted audit-log requirement', () => {
|
|
80
|
+
const md = renderComplianceMarkdown(canon, { mode: 'gap' }, { today: '2026-06-02' });
|
|
81
|
+
expect(md).toContain('`REQUIREMENT-AUDIT-LOG-RETENTION-1`');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('gapMarkdown CV-5 pastDeadlineRequirements section', () => {
|
|
86
|
+
it('includes the 4th section in output', () => {
|
|
87
|
+
const canon = emptyCanon();
|
|
88
|
+
ingestComplianceDoc(canon, { notation: 'requirement', id: 'REQ-PD-1', name: 'Past', deadline: '2020-01-01' });
|
|
89
|
+
const md = renderComplianceMarkdown(canon, { mode: 'gap' }, { today: '2026-06-09' });
|
|
90
|
+
expect(md).toContain('Past-deadline requirements');
|
|
91
|
+
expect(md).toContain('REQ-PD-1');
|
|
92
|
+
expect(md).toContain('2020-01-01');
|
|
93
|
+
expect(md).toContain('4 checks');
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { buildComplianceIndex } from '../reverse-index.js';
|
|
3
|
+
import { buildLawTree, buildProductView } from '../views.js';
|
|
4
|
+
import type { ComplianceIndexInput } from '../types.js';
|
|
5
|
+
|
|
6
|
+
const input: ComplianceIndexInput = {
|
|
7
|
+
requirements: [
|
|
8
|
+
{ id: 'REQUIREMENT-A-1', name: 'A', severity: 'high', derived_from: ['LAW-X-1'] },
|
|
9
|
+
{ id: 'REQUIREMENT-B-1', name: 'B', severity: 'low', derived_from: ['LAW-X-1', 'REGULATION-Y-1'] },
|
|
10
|
+
{ id: 'REQUIREMENT-C-1', name: 'C' }, // internal-only, no law
|
|
11
|
+
],
|
|
12
|
+
assertions: [
|
|
13
|
+
{ id: 'ASSERTION-2', about: 'REQUIREMENT-A-1', subject: 'PRODUCT-M-1', status: 'compliant' },
|
|
14
|
+
{ id: 'ASSERTION-1', about: 'REQUIREMENT-A-1', subject: 'CAPABILITY-V2', status: 'under_review' },
|
|
15
|
+
{ id: 'ASSERTION-3', about: 'REQUIREMENT-B-1', subject: 'PRODUCT-M-1', status: 'partial' },
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
describe('buildComplianceIndex', () => {
|
|
20
|
+
const idx = buildComplianceIndex(input);
|
|
21
|
+
it('indexes requirements by the laws they derive from', () => {
|
|
22
|
+
expect(idx.requirementsByLaw.get('LAW-X-1')?.map(r => r.id)).toEqual(['REQUIREMENT-A-1', 'REQUIREMENT-B-1']);
|
|
23
|
+
expect(idx.requirementsByLaw.get('REGULATION-Y-1')?.map(r => r.id)).toEqual(['REQUIREMENT-B-1']);
|
|
24
|
+
expect(idx.requirementsByLaw.has('LAW-NONE-1')).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
it('indexes assertions by requirement and by subject', () => {
|
|
27
|
+
expect(idx.assertionsByRequirement.get('REQUIREMENT-A-1')?.map(a => a.id).sort()).toEqual(['ASSERTION-1', 'ASSERTION-2']);
|
|
28
|
+
expect(idx.assertionsBySubject.get('PRODUCT-M-1')?.map(a => a.id).sort()).toEqual(['ASSERTION-2', 'ASSERTION-3']);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('buildLawTree', () => {
|
|
33
|
+
const idx = buildComplianceIndex(input);
|
|
34
|
+
it('lists requirements derived from the law, each with its id-sorted assertions', () => {
|
|
35
|
+
const tree = buildLawTree('LAW-X-1', idx);
|
|
36
|
+
expect(tree.requirements.map(r => r.requirement.id)).toEqual(['REQUIREMENT-A-1', 'REQUIREMENT-B-1']);
|
|
37
|
+
expect(tree.requirements[0].assertions.map(a => a.id)).toEqual(['ASSERTION-1', 'ASSERTION-2']);
|
|
38
|
+
expect(tree.requirements[0].assertions.map(a => a.status)).toEqual(['under_review', 'compliant']);
|
|
39
|
+
});
|
|
40
|
+
it('returns an empty tree for an unknown law', () => {
|
|
41
|
+
expect(buildLawTree('LAW-NONE-1', idx).requirements).toEqual([]);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('buildProductView', () => {
|
|
46
|
+
const idx = buildComplianceIndex(input);
|
|
47
|
+
it('lists the requirements the product is asserted against, with status, requirement-id-sorted', () => {
|
|
48
|
+
const view = buildProductView('PRODUCT-M-1', idx);
|
|
49
|
+
expect(view.requirements.map(r => r.requirement.id)).toEqual(['REQUIREMENT-A-1', 'REQUIREMENT-B-1']);
|
|
50
|
+
expect(view.requirements.map(r => r.assertion.status)).toEqual(['compliant', 'partial']);
|
|
51
|
+
expect(view.requirements[0].requirement.name).toBe('A');
|
|
52
|
+
});
|
|
53
|
+
it('falls back to the id as name when the requirement is not in the scan (dangling about)', () => {
|
|
54
|
+
const idx2 = buildComplianceIndex({
|
|
55
|
+
requirements: [],
|
|
56
|
+
assertions: [{ id: 'ASSERTION-9', about: 'REQUIREMENT-GONE-1', subject: 'PRODUCT-M-1', status: 'n_a' }],
|
|
57
|
+
});
|
|
58
|
+
const view = buildProductView('PRODUCT-M-1', idx2);
|
|
59
|
+
expect(view.requirements[0].requirement).toEqual({ id: 'REQUIREMENT-GONE-1', name: 'REQUIREMENT-GONE-1' });
|
|
60
|
+
});
|
|
61
|
+
it('returns an empty view for a product with no assertions', () => {
|
|
62
|
+
expect(buildProductView('PRODUCT-NONE-1', idx).requirements).toEqual([]);
|
|
63
|
+
});
|
|
64
|
+
});
|