@transitrix/diagrams 1.0.0 → 1.1.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 -21
- package/README.md +47 -47
- package/dist/__tests__/edge-path.test.js +8 -0
- package/dist/activities/cpm.js +11 -2
- package/dist/activities/gantt.js +6 -3
- package/dist/activities/types.d.ts +1 -0
- package/dist/activities/validate.js +3 -3
- package/dist/activity-card/__tests__/example.test.js +20 -1
- package/dist/activity-card/__tests__/layout.test.js +102 -11
- package/dist/activity-card/__tests__/resolver.test.js +161 -7
- package/dist/activity-card/index.d.ts +1 -1
- package/dist/activity-card/layout.d.ts +1 -1
- package/dist/activity-card/layout.js +220 -103
- package/dist/activity-card/resolver.js +115 -28
- package/dist/activity-card/types.d.ts +150 -27
- package/dist/activity-card/validate.js +3 -0
- package/dist/actor/__tests__/validate.test.js +63 -31
- package/dist/actor/index.d.ts +1 -1
- package/dist/actor/index.js +1 -1
- package/dist/actor/types.d.ts +7 -3
- package/dist/actor/types.js +17 -3
- package/dist/actor/validate.js +55 -26
- package/dist/assertion/types.d.ts +3 -1
- package/dist/assertion/types.js +1 -1
- package/dist/change/__tests__/validate.test.js +40 -34
- package/dist/change/index.d.ts +1 -0
- package/dist/change/types.d.ts +1 -1
- package/dist/change/types.js +2 -2
- package/dist/change/validate.d.ts +6 -1
- package/dist/change/validate.js +68 -41
- package/dist/compliance/__tests__/classify.test.js +34 -0
- package/dist/compliance/__tests__/coverage-metric.test.js +146 -46
- package/dist/compliance/__tests__/html.test.js +2 -2
- package/dist/compliance/__tests__/impact.test.js +509 -6
- package/dist/compliance/classify.d.ts +6 -0
- package/dist/compliance/classify.js +9 -1
- package/dist/compliance/coverage-metric.d.ts +31 -11
- package/dist/compliance/coverage-metric.js +149 -39
- package/dist/compliance/html.js +78 -81
- package/dist/compliance/impact.d.ts +100 -16
- package/dist/compliance/impact.js +272 -28
- package/dist/compliance/index.d.ts +2 -2
- package/dist/compliance/index.js +1 -1
- package/dist/compliance/markdown.js +1 -0
- package/dist/compliance/types.d.ts +25 -6
- package/dist/edge-path.d.ts +8 -4
- package/dist/edge-path.js +11 -6
- package/dist/factor/__tests__/validate.test.js +75 -46
- package/dist/factor/index.d.ts +3 -2
- package/dist/factor/index.js +1 -1
- package/dist/factor/types.d.ts +5 -8
- package/dist/factor/types.js +10 -5
- package/dist/factor/validate.d.ts +6 -1
- package/dist/factor/validate.js +68 -50
- package/dist/fgca/__tests__/chain-table.test.js +2 -2
- package/dist/fgca/__tests__/layout.test.js +11 -11
- package/dist/fgca/__tests__/preview-layout.test.js +7 -7
- package/dist/fgca/__tests__/resolver.test.js +185 -0
- package/dist/fgca/chain-table.d.ts +1 -1
- package/dist/fgca/chain-table.js +4 -4
- package/dist/fgca/index.d.ts +4 -2
- package/dist/fgca/index.js +2 -1
- package/dist/fgca/layout.d.ts +2 -2
- package/dist/fgca/layout.js +9 -9
- package/dist/fgca/nodeTypes.d.ts +1 -1
- package/dist/fgca/nodeTypes.js +2 -2
- package/dist/fgca/nodes/FGCAFactorNode.d.ts +3 -3
- package/dist/fgca/nodes/FGCAFactorNode.js +5 -5
- package/dist/fgca/parse-canonical.js +15 -10
- package/dist/fgca/preview-layout.d.ts +1 -1
- package/dist/fgca/preview-layout.js +4 -4
- package/dist/fgca/resolver.d.ts +49 -0
- package/dist/fgca/resolver.js +126 -0
- package/dist/fgca/types.d.ts +11 -11
- package/dist/fgca/types.js +2 -2
- package/dist/fgca/validate.d.ts +2 -2
- package/dist/fgca/validate.js +17 -14
- package/dist/goals/index.d.ts +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/process-blueprint/__tests__/layout.test.js +37 -0
- package/dist/process-blueprint/index.d.ts +1 -1
- package/dist/process-blueprint/layout.js +66 -35
- package/dist/process-blueprint/types.d.ts +36 -1
- package/dist/scenarios/index.d.ts +1 -1
- package/dist/scenarios/types.d.ts +4 -4
- package/dist/stakeholder/__tests__/validate.test.js +49 -47
- package/dist/stakeholder/index.d.ts +1 -0
- package/dist/stakeholder/types.d.ts +3 -4
- package/dist/stakeholder/types.js +2 -3
- package/dist/stakeholder/validate.d.ts +6 -1
- package/dist/stakeholder/validate.js +71 -50
- package/dist/target-state/__tests__/validate.test.js +43 -27
- package/dist/target-state/index.d.ts +2 -0
- package/dist/target-state/index.js +1 -0
- package/dist/target-state/types.d.ts +6 -1
- package/dist/target-state/types.js +8 -7
- package/dist/target-state/validate.d.ts +6 -1
- package/dist/target-state/validate.js +69 -45
- package/dist/theme/themes.js +71 -69
- package/dist/theme/tokens.d.ts +7 -1
- package/dist/theme/tokens.js +3 -1
- package/dist/webview/__tests__/entry-notations.test.js +2 -0
- package/dist/webview/__tests__/entry.test.js +11 -11
- package/dist/webview/__tests__/render-process.test.js +463 -0
- package/dist/webview/__tests__/render-util.test.d.ts +1 -0
- package/dist/webview/__tests__/render-util.test.js +54 -0
- package/dist/webview/entry.d.ts +1 -1
- package/dist/webview/entry.js +9 -5
- package/dist/webview/render-activities.d.ts +24 -2
- package/dist/webview/render-activities.js +92 -57
- package/dist/webview/render-activity-card.d.ts +11 -1
- package/dist/webview/render-activity-card.js +115 -79
- package/dist/webview/render-blocks.d.ts +24 -1
- package/dist/webview/render-blocks.js +58 -24
- package/dist/webview/render-fgca.d.ts +31 -0
- package/dist/webview/render-fgca.js +30 -19
- package/dist/webview/render-goals.d.ts +27 -1
- package/dist/webview/render-goals.js +42 -32
- package/dist/webview/render-process-blueprint.d.ts +11 -1
- package/dist/webview/render-process-blueprint.js +118 -53
- package/dist/webview/render-process.d.ts +95 -0
- package/dist/webview/render-process.js +392 -0
- package/dist/webview/render-scenarios.js +2 -2
- package/dist/webview/render-util.d.ts +19 -6
- package/dist/webview/render-util.js +23 -15
- package/package.json +54 -40
- package/src/__tests__/edge-path.test.ts +58 -46
- package/src/__tests__/scope.test.ts +27 -27
- package/src/__tests__/yaml-normalize.test.ts +158 -158
- package/src/activities/__tests__/cpm.test.ts +164 -164
- package/src/activities/__tests__/gantt.test.ts +245 -245
- package/src/activities/__tests__/layout.test.ts +112 -112
- package/src/activities/__tests__/validate.test.ts +409 -409
- package/src/activities/cpm.ts +107 -95
- package/src/activities/gantt.ts +277 -276
- package/src/activities/index.ts +28 -28
- package/src/activities/layout.ts +112 -112
- package/src/activities/types.ts +149 -148
- package/src/activities/validate.ts +357 -357
- package/src/activity-card/__tests__/example.test.ts +85 -66
- package/src/activity-card/__tests__/layout.test.ts +169 -63
- package/src/activity-card/__tests__/resolver.test.ts +330 -161
- package/src/activity-card/__tests__/validate.test.ts +131 -131
- package/src/activity-card/index.ts +38 -32
- package/src/activity-card/layout.ts +324 -194
- package/src/activity-card/resolver.ts +422 -337
- package/src/activity-card/types.ts +376 -237
- package/src/activity-card/validate.ts +162 -158
- package/src/actor/__tests__/validate.test.ts +70 -31
- package/src/actor/index.ts +1 -1
- package/src/actor/types.ts +21 -7
- package/src/actor/validate.ts +59 -27
- package/src/applications/__tests__/validate.test.ts +256 -256
- package/src/applications/index.ts +11 -11
- package/src/applications/types.ts +43 -43
- package/src/applications/validate.ts +131 -131
- package/src/assertion/__tests__/example.test.ts +37 -37
- package/src/assertion/__tests__/validate.test.ts +142 -142
- package/src/assertion/index.ts +12 -12
- package/src/assertion/types.ts +60 -58
- package/src/assertion/validate.ts +145 -145
- package/src/blocks/__tests__/layout.test.ts +169 -169
- package/src/blocks/__tests__/validate.test.ts +260 -260
- package/src/blocks/index.ts +17 -17
- package/src/blocks/layout.ts +186 -186
- package/src/blocks/types.ts +62 -62
- package/src/blocks/validate.ts +206 -206
- package/src/capability-map/__tests__/validate.test.ts +238 -238
- package/src/capability-map/index.ts +8 -8
- package/src/capability-map/types.ts +29 -29
- package/src/capability-map/validate.ts +127 -127
- package/src/change/__tests__/validate.test.ts +43 -34
- package/src/change/index.ts +1 -0
- package/src/change/types.ts +5 -3
- package/src/change/validate.ts +75 -43
- package/src/compliance/__tests__/classify.test.ts +64 -27
- package/src/compliance/__tests__/confidence.test.ts +98 -98
- package/src/compliance/__tests__/coverage-metric.test.ts +300 -188
- package/src/compliance/__tests__/demo-scenario.test.ts +273 -273
- package/src/compliance/__tests__/example.test.ts +57 -57
- package/src/compliance/__tests__/gap-report.test.ts +128 -128
- package/src/compliance/__tests__/html.test.ts +141 -141
- package/src/compliance/__tests__/impact.test.ts +1059 -481
- package/src/compliance/__tests__/markdown.test.ts +94 -94
- package/src/compliance/__tests__/views.test.ts +64 -64
- package/src/compliance/classify.ts +100 -84
- package/src/compliance/confidence.ts +69 -69
- package/src/compliance/coverage-metric.ts +404 -263
- package/src/compliance/gap-report.ts +94 -94
- package/src/compliance/html.ts +291 -295
- package/src/compliance/impact.ts +979 -677
- package/src/compliance/index.ts +57 -56
- package/src/compliance/markdown.ts +136 -135
- package/src/compliance/reverse-index.ts +33 -33
- package/src/compliance/types.ts +136 -120
- package/src/compliance/views.ts +46 -46
- package/src/compliance-matrix/__tests__/build.test.ts +200 -200
- package/src/compliance-matrix/__tests__/example.test.ts +57 -57
- package/src/compliance-matrix/index.ts +11 -11
- package/src/compliance-matrix/types.ts +102 -102
- package/src/edge-path.ts +50 -44
- package/src/factor/__tests__/validate.test.ts +81 -46
- package/src/factor/index.ts +3 -7
- package/src/factor/types.ts +15 -19
- package/src/factor/validate.ts +77 -51
- package/src/fgca/__tests__/chain-table.test.ts +172 -172
- package/src/fgca/__tests__/layout.test.ts +179 -179
- package/src/fgca/__tests__/parse-canonical.test.ts +248 -248
- package/src/fgca/__tests__/preview-layout.test.ts +124 -124
- package/src/fgca/__tests__/resolver.test.ts +209 -0
- package/src/fgca/__tests__/validate.test.ts +142 -142
- package/src/fgca/chain-table.ts +225 -225
- package/src/fgca/index.ts +47 -45
- package/src/fgca/layout.ts +204 -204
- package/src/fgca/nodeTypes.ts +11 -11
- package/src/fgca/nodes/FGCAActivityNode.tsx +65 -65
- package/src/fgca/nodes/FGCAChangeNode.tsx +64 -64
- package/src/fgca/nodes/FGCAFactorNode.tsx +64 -64
- package/src/fgca/nodes/FGCAGoalNode.tsx +72 -72
- package/src/fgca/parse-canonical.ts +405 -400
- package/src/fgca/preview-layout.ts +217 -217
- package/src/fgca/resolver.ts +159 -0
- package/src/fgca/types.ts +50 -50
- package/src/fgca/validate.ts +129 -126
- package/src/geometry.ts +9 -9
- package/src/goals/__tests__/layout.test.ts +175 -175
- package/src/goals/__tests__/mutations.test.ts +111 -111
- package/src/goals/__tests__/parse-canonical.test.ts +145 -145
- package/src/goals/__tests__/validate.test.ts +202 -202
- package/src/goals/index.ts +20 -21
- package/src/goals/layout.ts +172 -172
- package/src/goals/mutations.ts +123 -123
- package/src/goals/parse-canonical.ts +286 -286
- package/src/goals/types.ts +85 -85
- package/src/goals/validate.ts +136 -136
- package/src/index.ts +28 -23
- package/src/process-blueprint/__tests__/compliance-lane.test.ts +320 -320
- package/src/process-blueprint/__tests__/layout.test.ts +240 -195
- package/src/process-blueprint/__tests__/validate.test.ts +310 -310
- package/src/process-blueprint/index.ts +32 -31
- package/src/process-blueprint/layout.ts +564 -531
- package/src/process-blueprint/types.ts +278 -242
- package/src/process-blueprint/validate.ts +181 -181
- package/src/process-map/__tests__/validate.test.ts +223 -223
- package/src/process-map/index.ts +10 -10
- package/src/process-map/types.ts +36 -36
- package/src/process-map/validate.ts +135 -135
- package/src/products/__tests__/validate.test.ts +239 -239
- package/src/products/index.ts +9 -9
- package/src/products/types.ts +31 -31
- package/src/products/validate.ts +112 -112
- package/src/repo-validate/__tests__/validate-repo.test.ts +149 -149
- package/src/repo-validate/index.ts +2 -2
- package/src/repo-validate/types.ts +50 -50
- package/src/repo-validate/validate-repo.ts +196 -196
- package/src/requirement/__tests__/example.test.ts +24 -24
- package/src/requirement/__tests__/validate.test.ts +96 -96
- package/src/requirement/index.ts +4 -4
- package/src/requirement/types.ts +35 -35
- package/src/requirement/validate.ts +92 -92
- package/src/scenarios/__tests__/validate.test.ts +209 -209
- package/src/scenarios/index.ts +15 -15
- package/src/scenarios/types.ts +37 -37
- package/src/scenarios/validate.ts +122 -122
- package/src/schema-version.ts +16 -16
- package/src/scope.ts +46 -46
- package/src/stakeholder/__tests__/validate.test.ts +51 -47
- package/src/stakeholder/index.ts +1 -0
- package/src/stakeholder/types.ts +7 -7
- package/src/stakeholder/validate.ts +79 -52
- package/src/target-state/__tests__/validate.test.ts +48 -27
- package/src/target-state/index.ts +2 -0
- package/src/target-state/types.ts +11 -7
- package/src/target-state/validate.ts +76 -46
- package/src/theme/index.ts +4 -4
- package/src/theme/themes.ts +239 -237
- package/src/theme/tokens.ts +135 -133
- package/src/typed-id.ts +56 -56
- package/src/validation-types.ts +36 -36
- package/src/webview/__tests__/entry-notations.test.ts +79 -77
- package/src/webview/__tests__/entry.test.ts +64 -64
- package/src/webview/__tests__/render-goals.test.ts +72 -72
- package/src/webview/__tests__/render-process.test.ts +538 -0
- package/src/webview/__tests__/render-util.test.ts +66 -0
- package/src/webview/entry.ts +298 -292
- package/src/webview/render-activities.ts +111 -69
- package/src/webview/render-activity-card.ts +275 -234
- package/src/webview/render-blocks.ts +78 -28
- package/src/webview/render-fgca.ts +53 -28
- package/src/webview/render-goals.ts +133 -110
- package/src/webview/render-process-blueprint.ts +281 -207
- package/src/webview/render-process.ts +507 -0
- package/src/webview/render-scenarios.ts +3 -3
- package/src/webview/render-util.ts +27 -14
- package/src/webview/styles.css +322 -323
- package/src/yaml-normalize.ts +56 -56
- package/dist/element-envelope.d.ts +0 -15
- package/dist/element-envelope.js +0 -55
- package/dist/issues/__tests__/layout.test.js +0 -62
- package/dist/issues/__tests__/validate.test.js +0 -82
- package/dist/issues/index.d.ts +0 -4
- package/dist/issues/index.js +0 -2
- package/dist/issues/layout.d.ts +0 -10
- package/dist/issues/layout.js +0 -82
- package/dist/issues/types.d.ts +0 -61
- package/dist/issues/types.js +0 -6
- package/dist/issues/validate.d.ts +0 -3
- package/dist/issues/validate.js +0 -143
- package/dist/webview/transitrix-render.css +0 -323
- package/dist/webview/transitrix-render.js +0 -6452
- package/dist/webview/transitrix-render.js.map +0 -7
- package/src/element-envelope.ts +0 -76
- /package/dist/{issues/__tests__/layout.test.d.ts → fgca/__tests__/resolver.test.d.ts} +0 -0
- /package/dist/{issues/__tests__/validate.test.d.ts → webview/__tests__/render-process.test.d.ts} +0 -0
|
@@ -10,6 +10,7 @@ export interface ImpactObligationFilter {
|
|
|
10
10
|
export interface ImpactSubjects {
|
|
11
11
|
products?: string[];
|
|
12
12
|
processes?: string[];
|
|
13
|
+
capabilities?: string[];
|
|
13
14
|
}
|
|
14
15
|
export interface ImpactStatusDisplay {
|
|
15
16
|
show?: AssertionStatus[];
|
|
@@ -28,30 +29,56 @@ export interface ImpactEmptyCellLabels {
|
|
|
28
29
|
* single subject: `{ subjectId, label: subjectId }`.
|
|
29
30
|
*
|
|
30
31
|
* At `grouping.columns: 'product-stage'` a column represents one (product,
|
|
31
|
-
* stage) pair: `{ subjectId
|
|
32
|
+
* stage) pair: `{ subjectId, stageId, label: subjectId + ':' + stageId }`.
|
|
32
33
|
* A cell is populated when an ASSERTION has `subject === subjectId` and
|
|
33
34
|
* `stageId ∈ assertion.realised_via` (or `realised_via` is absent, meaning
|
|
34
35
|
* the assertion covers the entire subject — it then contributes to every
|
|
35
36
|
* stage column for that subject).
|
|
37
|
+
*
|
|
38
|
+
* At `grouping.columns: 'product-stage-task'` a column represents one
|
|
39
|
+
* (product, stage, task) triple: `{ subjectId, stageId, taskId,
|
|
40
|
+
* label: subjectId + ':' + stageId + ':' + taskId }`. A cell is populated
|
|
41
|
+
* when an ASSERTION's `realised_via` contains the `taskId` directly, OR
|
|
42
|
+
* contains the `stageId` (stage-level claim applies to all tasks in that stage),
|
|
43
|
+
* OR `realised_via` is absent (claim covers the entire subject).
|
|
36
44
|
*/
|
|
37
45
|
export interface ImpactColumn {
|
|
38
46
|
/** Subject (product / process / capability) the column belongs to. */
|
|
39
47
|
subjectId: string;
|
|
40
|
-
/** Stage ID — present
|
|
48
|
+
/** Stage ID — present at product-stage and product-stage-task grain. */
|
|
41
49
|
stageId?: string;
|
|
50
|
+
/** Task ID — present only at product-stage-task grain. */
|
|
51
|
+
taskId?: string;
|
|
42
52
|
/** Human-readable column header. */
|
|
43
53
|
label: string;
|
|
54
|
+
/** Display name for the subject (product/process name, not the id). */
|
|
55
|
+
subjectName?: string;
|
|
56
|
+
/** Which subjects pool this column derives from (product / process / capability).
|
|
57
|
+
* Used to enforce the subject type label invariant (§5.2, COMPIMP-010). */
|
|
58
|
+
subjectType: 'product' | 'process' | 'capability';
|
|
59
|
+
}
|
|
60
|
+
/** A structural validation finding emitted by the matrix builder. */
|
|
61
|
+
export interface ImpactFinding {
|
|
62
|
+
code: string;
|
|
63
|
+
severity: 'error' | 'warning';
|
|
64
|
+
message: string;
|
|
44
65
|
}
|
|
45
66
|
/**
|
|
46
67
|
* Grouping options for matrix columns.
|
|
47
68
|
*
|
|
48
|
-
* `columns` controls the column grain:
|
|
69
|
+
* `columns` controls the column grain (COMPIMP-004 validates the value set):
|
|
49
70
|
* - `'product'` (default) — one column per subject.
|
|
50
|
-
* - `'product-stage'` — one column per (subject, stage) pair
|
|
51
|
-
* from `
|
|
71
|
+
* - `'product-stage'` — one column per (subject, stage) pair; pass
|
|
72
|
+
* `objectDetails` (from `extractObjectDetails`) into `buildImpactMatrix`.
|
|
73
|
+
* - `'product-stage-task'` — one column per (subject, stage, task) triple;
|
|
74
|
+
* pass `objectDetails` with stages that carry `tasks[]` (built via
|
|
75
|
+
* `extractObjectDetails` + `extractProcessFlowTasks` + `mergeStageTaskDetails`).
|
|
76
|
+
* - `'process'` / `'process-stage'` / `'process-stage-task'` — process-centric
|
|
77
|
+
* equivalents used when `view.subjects.processes` is the sole entry point
|
|
78
|
+
* (§7.1 process compliance variant). Avoids COMPIMP-010 for process subjects.
|
|
52
79
|
*/
|
|
53
80
|
export interface ImpactGrouping {
|
|
54
|
-
columns: '
|
|
81
|
+
columns: 'product' | 'product-stage' | 'product-stage-task' | 'process' | 'process-stage' | 'process-stage-task';
|
|
55
82
|
}
|
|
56
83
|
/**
|
|
57
84
|
* Named, versioned view-config — the report *definition*.
|
|
@@ -69,6 +96,15 @@ export interface ImpactViewConfig {
|
|
|
69
96
|
id: string;
|
|
70
97
|
name: string;
|
|
71
98
|
description?: string;
|
|
99
|
+
/**
|
|
100
|
+
* Declares the subject scope of the view (§4, COMPIMP-011 enforcement).
|
|
101
|
+
* `product` — covers products only; `subjects.processes` MUST be absent.
|
|
102
|
+
* `process` — covers processes directly; `subjects.products` MUST be absent.
|
|
103
|
+
* `combined` — both subject types allowed; column headers carry explicit badges.
|
|
104
|
+
* Omitting the field preserves backward-compatible behaviour (auto-detected
|
|
105
|
+
* from `subjects.*` presence).
|
|
106
|
+
*/
|
|
107
|
+
report_type?: 'product' | 'process' | 'combined';
|
|
72
108
|
/**
|
|
73
109
|
* ISO 8601 date of the last report snapshot (YYYY-MM-DD).
|
|
74
110
|
* Populated automatically by the CLI on each run and stored back to the
|
|
@@ -99,7 +135,7 @@ export interface ImpactViewConfig {
|
|
|
99
135
|
* are auditable ("what I assumed").
|
|
100
136
|
*/
|
|
101
137
|
export declare const COMPLIANCE_IMPACT_DEFAULTS: {
|
|
102
|
-
/** Accept all
|
|
138
|
+
/** Accept all active statuses + n_a in cell aggregation. */
|
|
103
139
|
readonly status_display: {
|
|
104
140
|
readonly show: AssertionStatus[];
|
|
105
141
|
};
|
|
@@ -119,10 +155,11 @@ export declare const COMPLIANCE_IMPACT_DEFAULTS: {
|
|
|
119
155
|
readonly subjects: {
|
|
120
156
|
readonly products: string[];
|
|
121
157
|
readonly processes: string[];
|
|
158
|
+
readonly capabilities: string[];
|
|
122
159
|
};
|
|
123
|
-
/** Column grain: one column per
|
|
160
|
+
/** Column grain: one column per subject (no stage/task decomposition). */
|
|
124
161
|
readonly grouping: {
|
|
125
|
-
readonly columns: "
|
|
162
|
+
readonly columns: "product";
|
|
126
163
|
};
|
|
127
164
|
};
|
|
128
165
|
export type ParseImpactViewConfigResult = {
|
|
@@ -204,6 +241,8 @@ export interface ImpactMatrix {
|
|
|
204
241
|
* Empty array when no requirement in the column has a `derived_from`.
|
|
205
242
|
*/
|
|
206
243
|
obligationsLane: string[][];
|
|
244
|
+
/** Structural validation findings emitted during matrix build (COMPIMP-009, COMPIMP-010, etc.). */
|
|
245
|
+
findings: ImpactFinding[];
|
|
207
246
|
}
|
|
208
247
|
/**
|
|
209
248
|
* Classify a REQUIREMENT deadline relative to a reference date.
|
|
@@ -216,18 +255,26 @@ export declare function computeDeadlineStatus(deadline: string | undefined, toda
|
|
|
216
255
|
* Build the matrix per §5.2.
|
|
217
256
|
*
|
|
218
257
|
* At the default `grouping.columns: 'product'` grain, one column per subject.
|
|
219
|
-
* At `grouping.columns: 'product-stage'`, pass `objectDetails` to expand each
|
|
220
|
-
* subject into per-stage sub-columns (typically extracted from a
|
|
221
|
-
* process-blueprint document via `extractObjectDetails`). Subjects with no
|
|
222
|
-
* stage mapping fall back to a single product-grain column automatically.
|
|
223
258
|
*
|
|
224
|
-
*
|
|
225
|
-
* `
|
|
259
|
+
* At `grouping.columns: 'product-stage'`, pass `objectDetails` (from
|
|
260
|
+
* `extractObjectDetails` on a process-blueprint) to expand each subject into
|
|
261
|
+
* per-stage sub-columns. Subjects with no stage mapping fall back to a single
|
|
262
|
+
* product-grain column.
|
|
263
|
+
*
|
|
264
|
+
* At `grouping.columns: 'product-stage-task'`, pass `objectDetails` where each
|
|
265
|
+
* stage carries a `tasks[]` array (combine blueprint stages and process-flow
|
|
266
|
+
* tasks via `mergeStageTaskDetails`). Each task becomes a
|
|
267
|
+
* `(subject, stage, task)` column. Stages with no tasks fall back to
|
|
268
|
+
* stage-grain columns.
|
|
269
|
+
*
|
|
270
|
+
* Subjects are taken from `config.subjects.products`, `config.subjects.processes`,
|
|
271
|
+
* and `config.subjects.capabilities` (in that order). Subjects with no binding
|
|
226
272
|
* obligation still appear as columns so the gap is visible.
|
|
227
273
|
*/
|
|
228
274
|
export declare function buildImpactMatrix(canon: ComplianceCanon, config: ImpactViewConfig, objectDetails?: ObjectDetailInput[]): ImpactMatrix;
|
|
229
275
|
/**
|
|
230
|
-
* Extract `ObjectDetailInput` from a raw (YAML-parsed)
|
|
276
|
+
* Extract stage-level `ObjectDetailInput` from a raw (YAML-parsed)
|
|
277
|
+
* process-blueprint document.
|
|
231
278
|
*
|
|
232
279
|
* Accepts both the bare document object and the wrapped form
|
|
233
280
|
* (`{ process_blueprint: { id, stages: […] } }`).
|
|
@@ -236,8 +283,45 @@ export declare function buildImpactMatrix(canon: ComplianceCanon, config: Impact
|
|
|
236
283
|
*
|
|
237
284
|
* Pass the returned value into `buildImpactMatrix` as part of the
|
|
238
285
|
* `objectDetails` array when `grouping.columns: 'product-stage'` is configured.
|
|
286
|
+
* For `grouping.columns: 'product-stage-task'`, combine with
|
|
287
|
+
* `extractProcessFlowTasks` via `mergeStageTaskDetails`.
|
|
239
288
|
*/
|
|
240
289
|
export declare function extractObjectDetails(doc: unknown): ObjectDetailInput | null;
|
|
290
|
+
/**
|
|
291
|
+
* Extract task-type steps from a raw (YAML-parsed) PROCESS element document.
|
|
292
|
+
*
|
|
293
|
+
* Reads `notation: process` documents with an inline `flow.steps[]` array.
|
|
294
|
+
* Filters for task-type nodes (`task`, `userTask`, `serviceTask`) — start/end
|
|
295
|
+
* events and gateways are excluded. Returns a flat `ObjectDetailInput` where
|
|
296
|
+
* `details` are the extracted task steps in declaration order.
|
|
297
|
+
*
|
|
298
|
+
* Returns `null` when the document is not a recognisable process element or
|
|
299
|
+
* has no `id` / `flow.steps` array.
|
|
300
|
+
*
|
|
301
|
+
* Use the returned value with `mergeStageTaskDetails` to embed tasks into
|
|
302
|
+
* blueprint stages for `grouping.columns: product-stage-task`.
|
|
303
|
+
*/
|
|
304
|
+
export declare function extractProcessFlowTasks(doc: unknown): ObjectDetailInput | null;
|
|
305
|
+
/**
|
|
306
|
+
* Combine blueprint stage details (from `extractObjectDetails`) with
|
|
307
|
+
* process-flow task details (from `extractProcessFlowTasks`) into a
|
|
308
|
+
* stage-with-tasks hierarchy for `grouping.columns: product-stage-task`.
|
|
309
|
+
*
|
|
310
|
+
* All tasks from `taskInput.details` are distributed into their parent stage
|
|
311
|
+
* by matching the `stageOwner` map: `stageId → taskId[]`. If `stageOwner` is
|
|
312
|
+
* omitted (no stage-to-task mapping is available), all tasks are placed into
|
|
313
|
+
* every stage (`undefined` key `→` all tasks).
|
|
314
|
+
*
|
|
315
|
+
* When `taskInput` is `null` or its `objectId` does not match `stageInput`,
|
|
316
|
+
* returns `stageInput` unchanged (callers can safely chain the result into
|
|
317
|
+
* `buildImpactMatrix` — it will degrade to stage-grain columns).
|
|
318
|
+
*
|
|
319
|
+
* @param stageInput Stage-level detail (from `extractObjectDetails`).
|
|
320
|
+
* @param taskInput Task-level detail (from `extractProcessFlowTasks`).
|
|
321
|
+
* @param stageOwner Optional mapping `stageId → taskId[]`. When provided,
|
|
322
|
+
* only tasks listed for a stage are embedded in it.
|
|
323
|
+
*/
|
|
324
|
+
export declare function mergeStageTaskDetails(stageInput: ObjectDetailInput, taskInput: ObjectDetailInput | null, stageOwner?: Record<string, string[]>): ObjectDetailInput;
|
|
241
325
|
/**
|
|
242
326
|
* Render the matrix as a GitHub-flavoured markdown table.
|
|
243
327
|
*
|
|
@@ -16,9 +16,9 @@ import { buildComplianceIndex } from './reverse-index.js';
|
|
|
16
16
|
* are auditable ("what I assumed").
|
|
17
17
|
*/
|
|
18
18
|
export const COMPLIANCE_IMPACT_DEFAULTS = {
|
|
19
|
-
/** Accept all
|
|
19
|
+
/** Accept all active statuses + n_a in cell aggregation. */
|
|
20
20
|
status_display: {
|
|
21
|
-
show: ['compliant', 'partial', 'non_compliant', 'under_review', 'n_a'],
|
|
21
|
+
show: ['compliant', 'partial', 'non_compliant', 'under_review', 'pending_owner', 'n_a'],
|
|
22
22
|
},
|
|
23
23
|
/** Order rows by canonical REQUIREMENT ID (lexicographic). */
|
|
24
24
|
order_rows_by: 'id',
|
|
@@ -30,9 +30,9 @@ export const COMPLIANCE_IMPACT_DEFAULTS = {
|
|
|
30
30
|
/** Obligation scope: all known REQUIREMENTs in the canon (no filter). */
|
|
31
31
|
obligations: { include: undefined, filter: undefined },
|
|
32
32
|
/** Subjects: empty — must be supplied explicitly in the view config. */
|
|
33
|
-
subjects: { products: [], processes: [] },
|
|
34
|
-
/** Column grain: one column per
|
|
35
|
-
grouping: { columns: '
|
|
33
|
+
subjects: { products: [], processes: [], capabilities: [] },
|
|
34
|
+
/** Column grain: one column per subject (no stage/task decomposition). */
|
|
35
|
+
grouping: { columns: 'product' },
|
|
36
36
|
};
|
|
37
37
|
/**
|
|
38
38
|
* Validate and normalise a raw (YAML-parsed) value into an ImpactViewConfig.
|
|
@@ -81,10 +81,28 @@ export function parseImpactViewConfig(raw) {
|
|
|
81
81
|
const emptyCells = v.empty_cells && typeof v.empty_cells === 'object' && !Array.isArray(v.empty_cells)
|
|
82
82
|
? v.empty_cells
|
|
83
83
|
: {};
|
|
84
|
+
// Parse grouping.columns — accept spec-canonical values plus backward-compat aliases.
|
|
85
|
+
const rawGrouping = v.grouping && typeof v.grouping === 'object' && !Array.isArray(v.grouping)
|
|
86
|
+
? v.grouping
|
|
87
|
+
: null;
|
|
88
|
+
const rawColumns = typeof rawGrouping?.columns === 'string' ? rawGrouping.columns : undefined;
|
|
89
|
+
const VALID_COLUMNS = new Set([
|
|
90
|
+
'product', 'product-stage', 'product-stage-task',
|
|
91
|
+
'process', 'process-stage', 'process-stage-task',
|
|
92
|
+
]);
|
|
93
|
+
const normalizedColumns = rawColumns === 'object-details' ? 'product-stage' // backward-compat alias
|
|
94
|
+
: rawColumns === 'object' ? 'product' // backward-compat alias
|
|
95
|
+
: VALID_COLUMNS.has(rawColumns ?? '') ? rawColumns
|
|
96
|
+
: undefined;
|
|
97
|
+
const VALID_REPORT_TYPES = new Set(['product', 'process', 'combined']);
|
|
98
|
+
const rawReportType = typeof v.report_type === 'string' ? v.report_type : undefined;
|
|
84
99
|
const config = {
|
|
85
100
|
id: v.id,
|
|
86
101
|
name: v.name,
|
|
87
102
|
description: typeof v.description === 'string' ? v.description : undefined,
|
|
103
|
+
report_type: rawReportType && VALID_REPORT_TYPES.has(rawReportType)
|
|
104
|
+
? rawReportType
|
|
105
|
+
: undefined,
|
|
88
106
|
snapshot_at: typeof v.snapshot_at === 'string' ? v.snapshot_at : undefined,
|
|
89
107
|
subjects: {
|
|
90
108
|
products: Array.isArray(subjects.products)
|
|
@@ -93,6 +111,9 @@ export function parseImpactViewConfig(raw) {
|
|
|
93
111
|
processes: Array.isArray(subjects.processes)
|
|
94
112
|
? subjects.processes.filter((x) => typeof x === 'string')
|
|
95
113
|
: [...COMPLIANCE_IMPACT_DEFAULTS.subjects.processes],
|
|
114
|
+
capabilities: Array.isArray(subjects.capabilities)
|
|
115
|
+
? subjects.capabilities.filter((x) => typeof x === 'string')
|
|
116
|
+
: [...COMPLIANCE_IMPACT_DEFAULTS.subjects.capabilities],
|
|
96
117
|
},
|
|
97
118
|
obligations: {
|
|
98
119
|
include: Array.isArray(obligations.include)
|
|
@@ -120,6 +141,7 @@ export function parseImpactViewConfig(raw) {
|
|
|
120
141
|
: COMPLIANCE_IMPACT_DEFAULTS.empty_cells.no_obligation_applies_label,
|
|
121
142
|
},
|
|
122
143
|
order_rows_by: v.order_rows_by === 'name' ? 'name' : COMPLIANCE_IMPACT_DEFAULTS.order_rows_by,
|
|
144
|
+
grouping: normalizedColumns !== undefined ? { columns: normalizedColumns } : undefined,
|
|
123
145
|
};
|
|
124
146
|
return { ok: true, config };
|
|
125
147
|
}
|
|
@@ -182,9 +204,11 @@ function orderRows(rows, key) {
|
|
|
182
204
|
return sorted;
|
|
183
205
|
}
|
|
184
206
|
/** Aggregate multiple matching assertions into one cell value, per §5.2 step 4.
|
|
207
|
+
* Precedence: non_compliant > partial > pending_owner > under_review > compliant > n_a.
|
|
185
208
|
* Assumes at least one assertion matches an allowed status; caller filters. */
|
|
186
209
|
function aggregateStatus(assertions) {
|
|
187
210
|
let sawPartial = false;
|
|
211
|
+
let sawPendingOwner = false;
|
|
188
212
|
let sawUnderReview = false;
|
|
189
213
|
let sawCompliant = false;
|
|
190
214
|
for (const a of assertions) {
|
|
@@ -192,6 +216,8 @@ function aggregateStatus(assertions) {
|
|
|
192
216
|
return 'non_compliant';
|
|
193
217
|
if (a.status === 'partial')
|
|
194
218
|
sawPartial = true;
|
|
219
|
+
else if (a.status === 'pending_owner')
|
|
220
|
+
sawPendingOwner = true;
|
|
195
221
|
else if (a.status === 'under_review')
|
|
196
222
|
sawUnderReview = true;
|
|
197
223
|
else if (a.status === 'compliant')
|
|
@@ -199,6 +225,8 @@ function aggregateStatus(assertions) {
|
|
|
199
225
|
}
|
|
200
226
|
if (sawPartial)
|
|
201
227
|
return 'partial';
|
|
228
|
+
if (sawPendingOwner)
|
|
229
|
+
return 'pending_owner';
|
|
202
230
|
if (sawUnderReview)
|
|
203
231
|
return 'under_review';
|
|
204
232
|
if (sawCompliant)
|
|
@@ -206,22 +234,99 @@ function aggregateStatus(assertions) {
|
|
|
206
234
|
return 'n_a';
|
|
207
235
|
}
|
|
208
236
|
// ── Column builder helpers (CV-3a) ──────────────────────────────────────────
|
|
209
|
-
function buildProductColumns(config) {
|
|
210
|
-
const
|
|
211
|
-
|
|
237
|
+
function buildProductColumns(config, namedItems, isCombined) {
|
|
238
|
+
const nameMap = new Map(namedItems.map(p => [p.id, p.name]));
|
|
239
|
+
const cols = [];
|
|
240
|
+
for (const id of (config.subjects.products ?? [])) {
|
|
241
|
+
cols.push({
|
|
242
|
+
subjectId: id,
|
|
243
|
+
label: isCombined ? `[PRODUCT] ${id}` : id,
|
|
244
|
+
subjectName: nameMap.get(id),
|
|
245
|
+
subjectType: 'product',
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
for (const id of (config.subjects.processes ?? [])) {
|
|
249
|
+
cols.push({
|
|
250
|
+
subjectId: id,
|
|
251
|
+
label: isCombined ? `[PROCESS] ${id}` : id,
|
|
252
|
+
subjectName: nameMap.get(id),
|
|
253
|
+
subjectType: 'process',
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
for (const id of (config.subjects.capabilities ?? [])) {
|
|
257
|
+
cols.push({
|
|
258
|
+
subjectId: id,
|
|
259
|
+
label: id,
|
|
260
|
+
subjectName: nameMap.get(id),
|
|
261
|
+
subjectType: 'capability',
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
return cols;
|
|
212
265
|
}
|
|
213
|
-
function buildObjectDetailColumns(config, objectDetails) {
|
|
266
|
+
function buildObjectDetailColumns(config, objectDetails, isCombined) {
|
|
214
267
|
const detailMap = new Map(objectDetails.map(d => [d.objectId, d.details]));
|
|
215
|
-
const
|
|
268
|
+
const productSet = new Set(config.subjects.products ?? []);
|
|
269
|
+
const processSet = new Set(config.subjects.processes ?? []);
|
|
270
|
+
const subjects = [
|
|
271
|
+
...(config.subjects.products ?? []),
|
|
272
|
+
...(config.subjects.processes ?? []),
|
|
273
|
+
...(config.subjects.capabilities ?? []),
|
|
274
|
+
];
|
|
216
275
|
const cols = [];
|
|
217
276
|
for (const subjectId of subjects) {
|
|
277
|
+
const subjectType = productSet.has(subjectId) ? 'product' : processSet.has(subjectId) ? 'process' : 'capability';
|
|
278
|
+
const badge = isCombined && subjectType !== 'capability' ? `[${subjectType.toUpperCase()}] ` : '';
|
|
218
279
|
const details = detailMap.get(subjectId);
|
|
219
280
|
if (!details || details.length === 0) {
|
|
220
|
-
cols.push({ subjectId, label: subjectId });
|
|
281
|
+
cols.push({ subjectId, label: `${badge}${subjectId}`, subjectType });
|
|
221
282
|
}
|
|
222
283
|
else {
|
|
223
284
|
for (const detail of details) {
|
|
224
|
-
cols.push({ subjectId, stageId: detail.id, label: `${subjectId}:${detail.id}
|
|
285
|
+
cols.push({ subjectId, stageId: detail.id, label: `${badge}${subjectId}:${detail.id}`, subjectType });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return cols;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Build `product-stage-task` columns: one column per (subject, stage, task) triple.
|
|
293
|
+
* Each stage in `objectDetails` that carries a `tasks[]` array is expanded to
|
|
294
|
+
* per-task columns `{ subjectId, stageId, taskId, label: subjectId:stageId:taskId }`.
|
|
295
|
+
* Stages with no tasks fall back to a single stage-grain column.
|
|
296
|
+
*/
|
|
297
|
+
function buildTaskColumns(config, objectDetails, isCombined) {
|
|
298
|
+
const detailMap = new Map(objectDetails.map(d => [d.objectId, d.details]));
|
|
299
|
+
const productSet = new Set(config.subjects.products ?? []);
|
|
300
|
+
const processSet = new Set(config.subjects.processes ?? []);
|
|
301
|
+
const subjects = [
|
|
302
|
+
...(config.subjects.products ?? []),
|
|
303
|
+
...(config.subjects.processes ?? []),
|
|
304
|
+
...(config.subjects.capabilities ?? []),
|
|
305
|
+
];
|
|
306
|
+
const cols = [];
|
|
307
|
+
for (const subjectId of subjects) {
|
|
308
|
+
const subjectType = productSet.has(subjectId) ? 'product' : processSet.has(subjectId) ? 'process' : 'capability';
|
|
309
|
+
const badge = isCombined && subjectType !== 'capability' ? `[${subjectType.toUpperCase()}] ` : '';
|
|
310
|
+
const stages = detailMap.get(subjectId);
|
|
311
|
+
if (!stages || stages.length === 0) {
|
|
312
|
+
cols.push({ subjectId, label: `${badge}${subjectId}`, subjectType });
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
for (const stage of stages) {
|
|
316
|
+
if (!stage.tasks || stage.tasks.length === 0) {
|
|
317
|
+
cols.push({ subjectId, stageId: stage.id, label: `${badge}${subjectId}:${stage.id}`, subjectType });
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
for (const task of stage.tasks) {
|
|
321
|
+
cols.push({
|
|
322
|
+
subjectId,
|
|
323
|
+
stageId: stage.id,
|
|
324
|
+
taskId: task.id,
|
|
325
|
+
label: `${badge}${subjectId}:${stage.id}:${task.id}`,
|
|
326
|
+
subjectType,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
225
330
|
}
|
|
226
331
|
}
|
|
227
332
|
}
|
|
@@ -232,8 +337,12 @@ function buildObjectDetailColumns(config, objectDetails) {
|
|
|
232
337
|
*
|
|
233
338
|
* At `'product'` grain: `a.subject` must match `col.subjectId`.
|
|
234
339
|
* At `'product-stage'` grain: `a.subject` must match AND either
|
|
235
|
-
* `a.realised_via` is absent (claim covers the whole subject
|
|
236
|
-
*
|
|
340
|
+
* `a.realised_via` is absent (claim covers the whole subject) OR
|
|
341
|
+
* `col.stageId` ∈ `a.realised_via`.
|
|
342
|
+
* At `'product-stage-task'` grain: as above for stage, plus `col.taskId` ∈
|
|
343
|
+
* `a.realised_via` counts as a match (task-level claim covers this task column).
|
|
344
|
+
* A stage-level claim (`col.stageId` ∈ `a.realised_via`) covers all task columns
|
|
345
|
+
* in that stage.
|
|
237
346
|
*/
|
|
238
347
|
function assertionMatchesColumn(a, col) {
|
|
239
348
|
if (a.subject !== col.subjectId)
|
|
@@ -242,19 +351,30 @@ function assertionMatchesColumn(a, col) {
|
|
|
242
351
|
return true;
|
|
243
352
|
if (!a.realised_via || a.realised_via.length === 0)
|
|
244
353
|
return true;
|
|
354
|
+
if (col.taskId) {
|
|
355
|
+
// Task grain: match on taskId OR on stageId (stage claim covers all tasks).
|
|
356
|
+
return a.realised_via.includes(col.taskId) || a.realised_via.includes(col.stageId);
|
|
357
|
+
}
|
|
245
358
|
return a.realised_via.includes(col.stageId);
|
|
246
359
|
}
|
|
247
360
|
/**
|
|
248
361
|
* Build the matrix per §5.2.
|
|
249
362
|
*
|
|
250
363
|
* At the default `grouping.columns: 'product'` grain, one column per subject.
|
|
251
|
-
* At `grouping.columns: 'product-stage'`, pass `objectDetails` to expand each
|
|
252
|
-
* subject into per-stage sub-columns (typically extracted from a
|
|
253
|
-
* process-blueprint document via `extractObjectDetails`). Subjects with no
|
|
254
|
-
* stage mapping fall back to a single product-grain column automatically.
|
|
255
364
|
*
|
|
256
|
-
*
|
|
257
|
-
* `
|
|
365
|
+
* At `grouping.columns: 'product-stage'`, pass `objectDetails` (from
|
|
366
|
+
* `extractObjectDetails` on a process-blueprint) to expand each subject into
|
|
367
|
+
* per-stage sub-columns. Subjects with no stage mapping fall back to a single
|
|
368
|
+
* product-grain column.
|
|
369
|
+
*
|
|
370
|
+
* At `grouping.columns: 'product-stage-task'`, pass `objectDetails` where each
|
|
371
|
+
* stage carries a `tasks[]` array (combine blueprint stages and process-flow
|
|
372
|
+
* tasks via `mergeStageTaskDetails`). Each task becomes a
|
|
373
|
+
* `(subject, stage, task)` column. Stages with no tasks fall back to
|
|
374
|
+
* stage-grain columns.
|
|
375
|
+
*
|
|
376
|
+
* Subjects are taken from `config.subjects.products`, `config.subjects.processes`,
|
|
377
|
+
* and `config.subjects.capabilities` (in that order). Subjects with no binding
|
|
258
378
|
* obligation still appear as columns so the gap is visible.
|
|
259
379
|
*/
|
|
260
380
|
export function buildImpactMatrix(canon, config, objectDetails) {
|
|
@@ -263,11 +383,53 @@ export function buildImpactMatrix(canon, config, objectDetails) {
|
|
|
263
383
|
assertions: canon.assertions,
|
|
264
384
|
});
|
|
265
385
|
const obligations = orderRows(resolveObligations(config, index, canon.requirements), config.order_rows_by);
|
|
266
|
-
const
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
386
|
+
const grain = config.grouping?.columns ?? 'product';
|
|
387
|
+
const hasDetails = objectDetails !== undefined && objectDetails.length > 0;
|
|
388
|
+
const hasProducts = (config.subjects.products?.length ?? 0) > 0;
|
|
389
|
+
const hasProcesses = (config.subjects.processes?.length ?? 0) > 0;
|
|
390
|
+
// Combined = both products and processes are explicit entry points (§7.1).
|
|
391
|
+
const isCombined = hasProducts && hasProcesses;
|
|
392
|
+
let columns = (grain === 'product-stage-task' || grain === 'process-stage-task') && hasDetails
|
|
393
|
+
? buildTaskColumns(config, objectDetails, isCombined)
|
|
394
|
+
: (grain === 'product-stage' || grain === 'process-stage' || grain === 'object-details') && hasDetails
|
|
395
|
+
? buildObjectDetailColumns(config, objectDetails, isCombined)
|
|
396
|
+
: buildProductColumns(config, [...(canon.products ?? []), ...(canon.subjects ?? [])], isCombined);
|
|
397
|
+
// ── Structural validation (COMPIMP-009, COMPIMP-010, COMPIMP-011) ──────
|
|
398
|
+
const findings = [];
|
|
399
|
+
if (!hasProducts && hasProcesses &&
|
|
400
|
+
(grain === 'product' || grain === 'product-stage' || grain === 'product-stage-task')) {
|
|
401
|
+
findings.push({
|
|
402
|
+
code: 'COMPIMP-009',
|
|
403
|
+
severity: 'warning',
|
|
404
|
+
message: `view.grouping.columns is '${grain}' but view.subjects.products is absent and subjects.processes is the only entry point. Consider using '${grain.replace('product', 'process')}' for a process-centric view.`,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
if (hasProcesses && (grain === 'product-stage' || grain === 'product-stage-task')) {
|
|
408
|
+
findings.push({
|
|
409
|
+
code: 'COMPIMP-010',
|
|
410
|
+
severity: 'error',
|
|
411
|
+
message: `view.grouping.columns is '${grain}' but view.subjects.processes has entries — product-stage semantics applied to process columns violate the subject type label invariant (§5.2). Use '${grain.replace('product', 'process')}' for process columns.`,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
// COMPIMP-011: report_type contract — strip cross-type columns and emit an
|
|
415
|
+
// error when the declared scope contradicts the subjects actually present.
|
|
416
|
+
if (config.report_type === 'product' && hasProcesses) {
|
|
417
|
+
findings.push({
|
|
418
|
+
code: 'COMPIMP-011',
|
|
419
|
+
severity: 'error',
|
|
420
|
+
message: `view.report_type is 'product' but view.subjects.processes has entries — process columns are excluded. Remove subjects.processes or set report_type to 'combined'.`,
|
|
421
|
+
});
|
|
422
|
+
columns = columns.filter(c => c.subjectType !== 'process');
|
|
423
|
+
}
|
|
424
|
+
if (config.report_type === 'process' && hasProducts) {
|
|
425
|
+
findings.push({
|
|
426
|
+
code: 'COMPIMP-011',
|
|
427
|
+
severity: 'error',
|
|
428
|
+
message: `view.report_type is 'process' but view.subjects.products has entries — product columns are excluded. Remove subjects.products or set report_type to 'combined'.`,
|
|
429
|
+
});
|
|
430
|
+
columns = columns.filter(c => c.subjectType !== 'product');
|
|
431
|
+
}
|
|
432
|
+
const allowedStatuses = new Set(config.status_display?.show ?? ['compliant', 'partial', 'non_compliant', 'under_review', 'pending_owner', 'n_a']);
|
|
271
433
|
const rowIndex = new Set(obligations.map(r => r.id));
|
|
272
434
|
const cells = obligations.map(() => columns.map(() => emptyCell()));
|
|
273
435
|
for (let c = 0; c < columns.length; c++) {
|
|
@@ -347,6 +509,7 @@ export function buildImpactMatrix(canon, config, objectDetails) {
|
|
|
347
509
|
no_obligation_applies_label: config.empty_cells?.no_obligation_applies_label ?? DEFAULT_NO_OBLIGATION_APPLIES_LABEL,
|
|
348
510
|
},
|
|
349
511
|
obligationsLane,
|
|
512
|
+
findings,
|
|
350
513
|
};
|
|
351
514
|
}
|
|
352
515
|
function emptyCell() {
|
|
@@ -357,9 +520,10 @@ function emptyCell() {
|
|
|
357
520
|
decoration: { isNew: false, isUrgent: false, deadlineStatus: 'none' },
|
|
358
521
|
};
|
|
359
522
|
}
|
|
360
|
-
// ── Process-blueprint
|
|
523
|
+
// ── Process-blueprint / process-flow extractors (CV-3a) ─────────────────────
|
|
361
524
|
/**
|
|
362
|
-
* Extract `ObjectDetailInput` from a raw (YAML-parsed)
|
|
525
|
+
* Extract stage-level `ObjectDetailInput` from a raw (YAML-parsed)
|
|
526
|
+
* process-blueprint document.
|
|
363
527
|
*
|
|
364
528
|
* Accepts both the bare document object and the wrapped form
|
|
365
529
|
* (`{ process_blueprint: { id, stages: […] } }`).
|
|
@@ -368,6 +532,8 @@ function emptyCell() {
|
|
|
368
532
|
*
|
|
369
533
|
* Pass the returned value into `buildImpactMatrix` as part of the
|
|
370
534
|
* `objectDetails` array when `grouping.columns: 'product-stage'` is configured.
|
|
535
|
+
* For `grouping.columns: 'product-stage-task'`, combine with
|
|
536
|
+
* `extractProcessFlowTasks` via `mergeStageTaskDetails`.
|
|
371
537
|
*/
|
|
372
538
|
export function extractObjectDetails(doc) {
|
|
373
539
|
if (!doc || typeof doc !== 'object' || Array.isArray(doc))
|
|
@@ -396,12 +562,90 @@ export function extractObjectDetails(doc) {
|
|
|
396
562
|
}
|
|
397
563
|
return { objectId: id, details };
|
|
398
564
|
}
|
|
565
|
+
/**
|
|
566
|
+
* Extract task-type steps from a raw (YAML-parsed) PROCESS element document.
|
|
567
|
+
*
|
|
568
|
+
* Reads `notation: process` documents with an inline `flow.steps[]` array.
|
|
569
|
+
* Filters for task-type nodes (`task`, `userTask`, `serviceTask`) — start/end
|
|
570
|
+
* events and gateways are excluded. Returns a flat `ObjectDetailInput` where
|
|
571
|
+
* `details` are the extracted task steps in declaration order.
|
|
572
|
+
*
|
|
573
|
+
* Returns `null` when the document is not a recognisable process element or
|
|
574
|
+
* has no `id` / `flow.steps` array.
|
|
575
|
+
*
|
|
576
|
+
* Use the returned value with `mergeStageTaskDetails` to embed tasks into
|
|
577
|
+
* blueprint stages for `grouping.columns: product-stage-task`.
|
|
578
|
+
*/
|
|
579
|
+
export function extractProcessFlowTasks(doc) {
|
|
580
|
+
if (!doc || typeof doc !== 'object' || Array.isArray(doc))
|
|
581
|
+
return null;
|
|
582
|
+
const top = doc;
|
|
583
|
+
if (top.notation !== 'process')
|
|
584
|
+
return null;
|
|
585
|
+
const id = typeof top.id === 'string' ? top.id : null;
|
|
586
|
+
if (!id)
|
|
587
|
+
return null;
|
|
588
|
+
const flow = top.flow && typeof top.flow === 'object' && !Array.isArray(top.flow)
|
|
589
|
+
? top.flow
|
|
590
|
+
: null;
|
|
591
|
+
if (!flow || !Array.isArray(flow.steps))
|
|
592
|
+
return null;
|
|
593
|
+
const TASK_TYPES = new Set(['task', 'userTask', 'serviceTask', 'scriptTask',
|
|
594
|
+
'receiveTask', 'sendTask', 'manualTask', 'businessRuleTask', 'callActivity', 'subProcess']);
|
|
595
|
+
const details = [];
|
|
596
|
+
for (const step of flow.steps) {
|
|
597
|
+
if (!step || typeof step !== 'object' || Array.isArray(step))
|
|
598
|
+
continue;
|
|
599
|
+
const s = step;
|
|
600
|
+
const stepId = typeof s.id === 'string' ? s.id : null;
|
|
601
|
+
if (!stepId)
|
|
602
|
+
continue;
|
|
603
|
+
if (!TASK_TYPES.has(s.type))
|
|
604
|
+
continue;
|
|
605
|
+
details.push({ id: stepId, name: typeof s.name === 'string' ? s.name : stepId });
|
|
606
|
+
}
|
|
607
|
+
return details.length > 0 ? { objectId: id, details } : null;
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Combine blueprint stage details (from `extractObjectDetails`) with
|
|
611
|
+
* process-flow task details (from `extractProcessFlowTasks`) into a
|
|
612
|
+
* stage-with-tasks hierarchy for `grouping.columns: product-stage-task`.
|
|
613
|
+
*
|
|
614
|
+
* All tasks from `taskInput.details` are distributed into their parent stage
|
|
615
|
+
* by matching the `stageOwner` map: `stageId → taskId[]`. If `stageOwner` is
|
|
616
|
+
* omitted (no stage-to-task mapping is available), all tasks are placed into
|
|
617
|
+
* every stage (`undefined` key `→` all tasks).
|
|
618
|
+
*
|
|
619
|
+
* When `taskInput` is `null` or its `objectId` does not match `stageInput`,
|
|
620
|
+
* returns `stageInput` unchanged (callers can safely chain the result into
|
|
621
|
+
* `buildImpactMatrix` — it will degrade to stage-grain columns).
|
|
622
|
+
*
|
|
623
|
+
* @param stageInput Stage-level detail (from `extractObjectDetails`).
|
|
624
|
+
* @param taskInput Task-level detail (from `extractProcessFlowTasks`).
|
|
625
|
+
* @param stageOwner Optional mapping `stageId → taskId[]`. When provided,
|
|
626
|
+
* only tasks listed for a stage are embedded in it.
|
|
627
|
+
*/
|
|
628
|
+
export function mergeStageTaskDetails(stageInput, taskInput, stageOwner) {
|
|
629
|
+
if (!taskInput || taskInput.objectId !== stageInput.objectId)
|
|
630
|
+
return stageInput;
|
|
631
|
+
const allTasks = taskInput.details;
|
|
632
|
+
return {
|
|
633
|
+
objectId: stageInput.objectId,
|
|
634
|
+
details: stageInput.details.map(stage => {
|
|
635
|
+
const stageTasks = stageOwner
|
|
636
|
+
? allTasks.filter(t => (stageOwner[stage.id] ?? []).includes(t.id))
|
|
637
|
+
: allTasks;
|
|
638
|
+
return stageTasks.length > 0 ? { ...stage, tasks: stageTasks } : stage;
|
|
639
|
+
}),
|
|
640
|
+
};
|
|
641
|
+
}
|
|
399
642
|
// ── Markdown rendering ──────────────────────────────────────────────────────
|
|
400
643
|
const STATUS_GLYPH = {
|
|
401
644
|
compliant: 'OK',
|
|
402
645
|
partial: 'PARTIAL',
|
|
403
646
|
non_compliant: 'FAIL',
|
|
404
647
|
under_review: 'REVIEW',
|
|
648
|
+
pending_owner: 'PENDING',
|
|
405
649
|
n_a: 'N/A',
|
|
406
650
|
};
|
|
407
651
|
function escMd(s) {
|
|
@@ -450,7 +694,7 @@ export function renderImpactMarkdown(matrix) {
|
|
|
450
694
|
lines.push('');
|
|
451
695
|
lines.push('## Legend');
|
|
452
696
|
lines.push('');
|
|
453
|
-
lines.push('- **OK / PARTIAL / FAIL / REVIEW / N/A** — aggregated `ASSERTION.status` per §5.2.');
|
|
697
|
+
lines.push('- **OK / PARTIAL / FAIL / REVIEW / PENDING / N/A** — aggregated `ASSERTION.status` per §5.2.');
|
|
454
698
|
lines.push(`- **${escMd(matrix.emptyLabels.no_obligation_label)}** — modelling gap; no admitted ASSERTION binds this (obligation, subject) pair.`);
|
|
455
699
|
lines.push(`- **${escMd(matrix.emptyLabels.no_obligation_applies_label)}** — modelled fact; an admitted ASSERTION with status \`n_a\` excludes this pair.`);
|
|
456
700
|
return lines.join('\n') + '\n';
|
|
@@ -8,9 +8,9 @@ export { renderComplianceMarkdown } from './markdown.js';
|
|
|
8
8
|
export type { ReportScope, MarkdownOptions } from './markdown.js';
|
|
9
9
|
export { renderComplianceHtml, renderImpactMatrixHtml } from './html.js';
|
|
10
10
|
export type { HtmlOptions, ImpactMatrixHtmlOptions } from './html.js';
|
|
11
|
-
export { buildImpactMatrix, renderImpactMarkdown, parseImpactViewConfig, COMPLIANCE_IMPACT_DEFAULTS, extractObjectDetails, computeDeadlineStatus } from './impact.js';
|
|
11
|
+
export { buildImpactMatrix, renderImpactMarkdown, parseImpactViewConfig, COMPLIANCE_IMPACT_DEFAULTS, extractObjectDetails, extractProcessFlowTasks, mergeStageTaskDetails, computeDeadlineStatus } from './impact.js';
|
|
12
12
|
export type { ImpactCell, ImpactColumn, ImpactEmptyCellLabels, ImpactGrouping, ImpactMatrix, ImpactObligationFilter, ImpactStatusDisplay, ImpactSubjects, ImpactViewConfig, ParseImpactViewConfigResult, } from './impact.js';
|
|
13
13
|
export { assertionToScoringElement, complianceConfidenceHeader, requirementToScoringElement, scoreComplianceView, } from './confidence.js';
|
|
14
14
|
export { parseCoverageMetricConfig, buildCoverageMatrix, } from './coverage-metric.js';
|
|
15
|
-
export type { CoverageMetricConfig,
|
|
15
|
+
export type { CoverageMetricConfig, CoverageMetricRegimes, CoverageMetricRegimesFilter, CoverageMetricSubjects, CoverageMetricThresholds, CoverageMatrix, CoverageRow, ParseCoverageMetricResult, RagStatus, } from './coverage-metric.js';
|
|
16
16
|
export type { ComplianceIndex, ComplianceIndexInput, DeadlineStatus, IndexRequirement, IndexAssertion, LawTree, LawTreeRequirement, ProductView, ProductRequirementStatus, ObjectDetailDef, ObjectDetailInput, } from './types.js';
|
package/dist/compliance/index.js
CHANGED
|
@@ -4,6 +4,6 @@ export { buildGapReport } from './gap-report.js';
|
|
|
4
4
|
export { emptyCanon, ingestComplianceDoc } from './classify.js';
|
|
5
5
|
export { renderComplianceMarkdown } from './markdown.js';
|
|
6
6
|
export { renderComplianceHtml, renderImpactMatrixHtml } from './html.js';
|
|
7
|
-
export { buildImpactMatrix, renderImpactMarkdown, parseImpactViewConfig, COMPLIANCE_IMPACT_DEFAULTS, extractObjectDetails, computeDeadlineStatus } from './impact.js';
|
|
7
|
+
export { buildImpactMatrix, renderImpactMarkdown, parseImpactViewConfig, COMPLIANCE_IMPACT_DEFAULTS, extractObjectDetails, extractProcessFlowTasks, mergeStageTaskDetails, computeDeadlineStatus } from './impact.js';
|
|
8
8
|
export { assertionToScoringElement, complianceConfidenceHeader, requirementToScoringElement, scoreComplianceView, } from './confidence.js';
|
|
9
9
|
export { parseCoverageMetricConfig, buildCoverageMatrix, } from './coverage-metric.js';
|