@transitrix/diagrams 1.0.0 → 1.0.1

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.
Files changed (267) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +47 -47
  3. package/dist/__tests__/edge-path.test.js +8 -0
  4. package/dist/activity-card/__tests__/example.test.js +20 -1
  5. package/dist/activity-card/__tests__/layout.test.js +102 -11
  6. package/dist/activity-card/__tests__/resolver.test.js +139 -6
  7. package/dist/activity-card/index.d.ts +1 -1
  8. package/dist/activity-card/layout.d.ts +1 -1
  9. package/dist/activity-card/layout.js +220 -103
  10. package/dist/activity-card/resolver.js +107 -26
  11. package/dist/activity-card/types.d.ts +150 -27
  12. package/dist/activity-card/validate.js +3 -0
  13. package/dist/actor/__tests__/validate.test.js +63 -31
  14. package/dist/actor/index.d.ts +1 -1
  15. package/dist/actor/index.js +1 -1
  16. package/dist/actor/types.d.ts +7 -3
  17. package/dist/actor/types.js +17 -3
  18. package/dist/actor/validate.js +55 -26
  19. package/dist/assertion/types.d.ts +3 -1
  20. package/dist/assertion/types.js +1 -1
  21. package/dist/change/__tests__/validate.test.js +40 -34
  22. package/dist/change/index.d.ts +1 -0
  23. package/dist/change/types.d.ts +1 -1
  24. package/dist/change/types.js +2 -2
  25. package/dist/change/validate.d.ts +6 -1
  26. package/dist/change/validate.js +68 -41
  27. package/dist/compliance/__tests__/classify.test.js +34 -0
  28. package/dist/compliance/__tests__/coverage-metric.test.js +146 -46
  29. package/dist/compliance/__tests__/html.test.js +2 -2
  30. package/dist/compliance/__tests__/impact.test.js +311 -6
  31. package/dist/compliance/classify.d.ts +6 -0
  32. package/dist/compliance/classify.js +9 -1
  33. package/dist/compliance/coverage-metric.d.ts +31 -11
  34. package/dist/compliance/coverage-metric.js +149 -39
  35. package/dist/compliance/html.js +77 -74
  36. package/dist/compliance/impact.d.ts +77 -16
  37. package/dist/compliance/impact.js +193 -25
  38. package/dist/compliance/index.d.ts +2 -2
  39. package/dist/compliance/index.js +1 -1
  40. package/dist/compliance/markdown.js +1 -0
  41. package/dist/compliance/types.d.ts +25 -6
  42. package/dist/edge-path.d.ts +8 -4
  43. package/dist/edge-path.js +11 -6
  44. package/dist/factor/__tests__/validate.test.js +74 -45
  45. package/dist/factor/index.d.ts +3 -2
  46. package/dist/factor/index.js +1 -1
  47. package/dist/factor/types.d.ts +4 -7
  48. package/dist/factor/types.js +10 -5
  49. package/dist/factor/validate.d.ts +6 -1
  50. package/dist/factor/validate.js +68 -50
  51. package/dist/fgca/__tests__/resolver.test.js +185 -0
  52. package/dist/fgca/index.d.ts +2 -0
  53. package/dist/fgca/index.js +1 -0
  54. package/dist/fgca/resolver.d.ts +49 -0
  55. package/dist/fgca/resolver.js +124 -0
  56. package/dist/goals/index.d.ts +1 -1
  57. package/dist/index.d.ts +5 -0
  58. package/dist/index.js +5 -0
  59. package/dist/process-blueprint/__tests__/layout.test.js +37 -0
  60. package/dist/process-blueprint/layout.js +5 -1
  61. package/dist/process-blueprint/types.d.ts +22 -1
  62. package/dist/stakeholder/__tests__/validate.test.js +49 -47
  63. package/dist/stakeholder/index.d.ts +1 -0
  64. package/dist/stakeholder/types.d.ts +3 -4
  65. package/dist/stakeholder/types.js +2 -3
  66. package/dist/stakeholder/validate.d.ts +6 -1
  67. package/dist/stakeholder/validate.js +71 -50
  68. package/dist/target-state/__tests__/validate.test.js +43 -27
  69. package/dist/target-state/index.d.ts +2 -0
  70. package/dist/target-state/index.js +1 -0
  71. package/dist/target-state/types.d.ts +6 -1
  72. package/dist/target-state/types.js +8 -7
  73. package/dist/target-state/validate.d.ts +6 -1
  74. package/dist/target-state/validate.js +69 -45
  75. package/dist/theme/themes.js +69 -69
  76. package/dist/webview/__tests__/entry.test.js +11 -11
  77. package/dist/webview/render-activities.d.ts +3 -1
  78. package/dist/webview/render-activities.js +2 -2
  79. package/dist/webview/render-activity-card.js +104 -77
  80. package/dist/webview/render-fgca.d.ts +4 -0
  81. package/dist/webview/render-fgca.js +2 -5
  82. package/dist/webview/render-goals.d.ts +1 -0
  83. package/dist/webview/render-goals.js +15 -15
  84. package/dist/webview/render-process-blueprint.js +10 -10
  85. package/package.json +40 -40
  86. package/src/__tests__/edge-path.test.ts +58 -46
  87. package/src/__tests__/scope.test.ts +27 -27
  88. package/src/__tests__/yaml-normalize.test.ts +158 -158
  89. package/src/activities/__tests__/cpm.test.ts +164 -164
  90. package/src/activities/__tests__/gantt.test.ts +245 -245
  91. package/src/activities/__tests__/layout.test.ts +112 -112
  92. package/src/activities/__tests__/validate.test.ts +409 -409
  93. package/src/activities/cpm.ts +95 -95
  94. package/src/activities/gantt.ts +276 -276
  95. package/src/activities/index.ts +28 -28
  96. package/src/activities/layout.ts +112 -112
  97. package/src/activities/types.ts +148 -148
  98. package/src/activities/validate.ts +357 -357
  99. package/src/activity-card/__tests__/example.test.ts +85 -66
  100. package/src/activity-card/__tests__/layout.test.ts +169 -63
  101. package/src/activity-card/__tests__/resolver.test.ts +307 -161
  102. package/src/activity-card/__tests__/validate.test.ts +131 -131
  103. package/src/activity-card/index.ts +38 -32
  104. package/src/activity-card/layout.ts +324 -194
  105. package/src/activity-card/resolver.ts +415 -337
  106. package/src/activity-card/types.ts +376 -237
  107. package/src/activity-card/validate.ts +162 -158
  108. package/src/actor/__tests__/validate.test.ts +70 -31
  109. package/src/actor/index.ts +1 -1
  110. package/src/actor/types.ts +21 -7
  111. package/src/actor/validate.ts +59 -27
  112. package/src/applications/__tests__/validate.test.ts +256 -256
  113. package/src/applications/index.ts +11 -11
  114. package/src/applications/types.ts +43 -43
  115. package/src/applications/validate.ts +131 -131
  116. package/src/assertion/__tests__/example.test.ts +37 -37
  117. package/src/assertion/__tests__/validate.test.ts +142 -142
  118. package/src/assertion/index.ts +12 -12
  119. package/src/assertion/types.ts +60 -58
  120. package/src/assertion/validate.ts +145 -145
  121. package/src/blocks/__tests__/layout.test.ts +169 -169
  122. package/src/blocks/__tests__/validate.test.ts +260 -260
  123. package/src/blocks/index.ts +17 -17
  124. package/src/blocks/layout.ts +186 -186
  125. package/src/blocks/types.ts +62 -62
  126. package/src/blocks/validate.ts +206 -206
  127. package/src/capability-map/__tests__/validate.test.ts +238 -238
  128. package/src/capability-map/index.ts +8 -8
  129. package/src/capability-map/types.ts +29 -29
  130. package/src/capability-map/validate.ts +127 -127
  131. package/src/change/__tests__/validate.test.ts +43 -34
  132. package/src/change/index.ts +1 -0
  133. package/src/change/types.ts +5 -3
  134. package/src/change/validate.ts +75 -43
  135. package/src/compliance/__tests__/classify.test.ts +64 -27
  136. package/src/compliance/__tests__/confidence.test.ts +98 -98
  137. package/src/compliance/__tests__/coverage-metric.test.ts +300 -188
  138. package/src/compliance/__tests__/demo-scenario.test.ts +273 -273
  139. package/src/compliance/__tests__/example.test.ts +57 -57
  140. package/src/compliance/__tests__/gap-report.test.ts +128 -128
  141. package/src/compliance/__tests__/html.test.ts +141 -141
  142. package/src/compliance/__tests__/impact.test.ts +840 -481
  143. package/src/compliance/__tests__/markdown.test.ts +94 -94
  144. package/src/compliance/__tests__/views.test.ts +64 -64
  145. package/src/compliance/classify.ts +100 -84
  146. package/src/compliance/confidence.ts +69 -69
  147. package/src/compliance/coverage-metric.ts +404 -263
  148. package/src/compliance/gap-report.ts +94 -94
  149. package/src/compliance/html.ts +298 -295
  150. package/src/compliance/impact.ts +861 -677
  151. package/src/compliance/index.ts +57 -56
  152. package/src/compliance/markdown.ts +136 -135
  153. package/src/compliance/reverse-index.ts +33 -33
  154. package/src/compliance/types.ts +136 -120
  155. package/src/compliance/views.ts +46 -46
  156. package/src/compliance-matrix/__tests__/build.test.ts +200 -200
  157. package/src/compliance-matrix/__tests__/example.test.ts +57 -57
  158. package/src/compliance-matrix/index.ts +11 -11
  159. package/src/compliance-matrix/types.ts +102 -102
  160. package/src/edge-path.ts +50 -44
  161. package/src/factor/__tests__/validate.test.ts +80 -45
  162. package/src/factor/index.ts +3 -7
  163. package/src/factor/types.ts +14 -18
  164. package/src/factor/validate.ts +77 -51
  165. package/src/fgca/__tests__/chain-table.test.ts +172 -172
  166. package/src/fgca/__tests__/layout.test.ts +179 -179
  167. package/src/fgca/__tests__/parse-canonical.test.ts +248 -248
  168. package/src/fgca/__tests__/preview-layout.test.ts +124 -124
  169. package/src/fgca/__tests__/resolver.test.ts +209 -0
  170. package/src/fgca/__tests__/validate.test.ts +142 -142
  171. package/src/fgca/chain-table.ts +225 -225
  172. package/src/fgca/index.ts +47 -45
  173. package/src/fgca/layout.ts +204 -204
  174. package/src/fgca/nodeTypes.ts +11 -11
  175. package/src/fgca/nodes/FGCAActivityNode.tsx +65 -65
  176. package/src/fgca/nodes/FGCAChangeNode.tsx +64 -64
  177. package/src/fgca/nodes/FGCAFactorNode.tsx +64 -64
  178. package/src/fgca/nodes/FGCAGoalNode.tsx +72 -72
  179. package/src/fgca/parse-canonical.ts +400 -400
  180. package/src/fgca/preview-layout.ts +217 -217
  181. package/src/fgca/resolver.ts +157 -0
  182. package/src/fgca/types.ts +50 -50
  183. package/src/fgca/validate.ts +126 -126
  184. package/src/geometry.ts +9 -9
  185. package/src/goals/__tests__/layout.test.ts +175 -175
  186. package/src/goals/__tests__/mutations.test.ts +111 -111
  187. package/src/goals/__tests__/parse-canonical.test.ts +145 -145
  188. package/src/goals/__tests__/validate.test.ts +202 -202
  189. package/src/goals/index.ts +20 -21
  190. package/src/goals/layout.ts +172 -172
  191. package/src/goals/mutations.ts +123 -123
  192. package/src/goals/parse-canonical.ts +286 -286
  193. package/src/goals/types.ts +85 -85
  194. package/src/goals/validate.ts +136 -136
  195. package/src/index.ts +28 -23
  196. package/src/process-blueprint/__tests__/compliance-lane.test.ts +320 -320
  197. package/src/process-blueprint/__tests__/layout.test.ts +240 -195
  198. package/src/process-blueprint/__tests__/validate.test.ts +310 -310
  199. package/src/process-blueprint/index.ts +31 -31
  200. package/src/process-blueprint/layout.ts +535 -531
  201. package/src/process-blueprint/types.ts +263 -242
  202. package/src/process-blueprint/validate.ts +181 -181
  203. package/src/process-map/__tests__/validate.test.ts +223 -223
  204. package/src/process-map/index.ts +10 -10
  205. package/src/process-map/types.ts +36 -36
  206. package/src/process-map/validate.ts +135 -135
  207. package/src/products/__tests__/validate.test.ts +239 -239
  208. package/src/products/index.ts +9 -9
  209. package/src/products/types.ts +31 -31
  210. package/src/products/validate.ts +112 -112
  211. package/src/repo-validate/__tests__/validate-repo.test.ts +149 -149
  212. package/src/repo-validate/index.ts +2 -2
  213. package/src/repo-validate/types.ts +50 -50
  214. package/src/repo-validate/validate-repo.ts +196 -196
  215. package/src/requirement/__tests__/example.test.ts +24 -24
  216. package/src/requirement/__tests__/validate.test.ts +96 -96
  217. package/src/requirement/index.ts +4 -4
  218. package/src/requirement/types.ts +35 -35
  219. package/src/requirement/validate.ts +92 -92
  220. package/src/scenarios/__tests__/validate.test.ts +209 -209
  221. package/src/scenarios/index.ts +15 -15
  222. package/src/scenarios/types.ts +37 -37
  223. package/src/scenarios/validate.ts +122 -122
  224. package/src/schema-version.ts +16 -16
  225. package/src/scope.ts +46 -46
  226. package/src/stakeholder/__tests__/validate.test.ts +51 -47
  227. package/src/stakeholder/index.ts +1 -0
  228. package/src/stakeholder/types.ts +7 -7
  229. package/src/stakeholder/validate.ts +79 -52
  230. package/src/target-state/__tests__/validate.test.ts +48 -27
  231. package/src/target-state/index.ts +2 -0
  232. package/src/target-state/types.ts +11 -7
  233. package/src/target-state/validate.ts +76 -46
  234. package/src/theme/index.ts +4 -4
  235. package/src/theme/themes.ts +237 -237
  236. package/src/theme/tokens.ts +133 -133
  237. package/src/typed-id.ts +56 -56
  238. package/src/validation-types.ts +36 -36
  239. package/src/webview/__tests__/entry-notations.test.ts +77 -77
  240. package/src/webview/__tests__/entry.test.ts +64 -64
  241. package/src/webview/__tests__/render-goals.test.ts +72 -72
  242. package/src/webview/entry.ts +292 -292
  243. package/src/webview/render-activities.ts +5 -3
  244. package/src/webview/render-activity-card.ts +266 -234
  245. package/src/webview/render-fgca.ts +6 -5
  246. package/src/webview/render-goals.ts +111 -110
  247. package/src/webview/render-process-blueprint.ts +207 -207
  248. package/src/webview/styles.css +322 -323
  249. package/src/yaml-normalize.ts +56 -56
  250. package/dist/element-envelope.d.ts +0 -15
  251. package/dist/element-envelope.js +0 -55
  252. package/dist/issues/__tests__/layout.test.js +0 -62
  253. package/dist/issues/__tests__/validate.test.d.ts +0 -1
  254. package/dist/issues/__tests__/validate.test.js +0 -82
  255. package/dist/issues/index.d.ts +0 -4
  256. package/dist/issues/index.js +0 -2
  257. package/dist/issues/layout.d.ts +0 -10
  258. package/dist/issues/layout.js +0 -82
  259. package/dist/issues/types.d.ts +0 -61
  260. package/dist/issues/types.js +0 -6
  261. package/dist/issues/validate.d.ts +0 -3
  262. package/dist/issues/validate.js +0 -143
  263. package/dist/webview/transitrix-render.css +0 -323
  264. package/dist/webview/transitrix-render.js +0 -6452
  265. package/dist/webview/transitrix-render.js.map +0 -7
  266. package/src/element-envelope.ts +0 -76
  267. /package/dist/{issues/__tests__/layout.test.d.ts → fgca/__tests__/resolver.test.d.ts} +0 -0
@@ -6,38 +6,99 @@
6
6
  // thresholds.
7
7
  import { buildComplianceIndex } from './reverse-index.js';
8
8
  // ── Parser ───────────────────────────────────────────────────────────────────
9
+ function parseRegimes(raw) {
10
+ const warnings = [];
11
+ const rawR = raw.regimes;
12
+ if (!rawR || typeof rawR !== 'object' || Array.isArray(rawR)) {
13
+ return { regimes: undefined, warnings };
14
+ }
15
+ const r = rawR;
16
+ const include = Array.isArray(r.include)
17
+ ? r.include.filter((x) => typeof x === 'string')
18
+ : undefined;
19
+ const rawF = r.filter;
20
+ const filter = rawF && typeof rawF === 'object' && !Array.isArray(rawF)
21
+ ? (() => {
22
+ const f = rawF;
23
+ return {
24
+ jurisdiction: Array.isArray(f.jurisdiction)
25
+ ? f.jurisdiction.filter((x) => typeof x === 'string')
26
+ : undefined,
27
+ codex_type: Array.isArray(f.codex_type)
28
+ ? f.codex_type.filter((x) => typeof x === 'string')
29
+ : undefined,
30
+ };
31
+ })()
32
+ : undefined;
33
+ if (include?.length && filter) {
34
+ warnings.push('COVMET-007: both regimes.include and regimes.filter are set; include takes precedence');
35
+ }
36
+ return { regimes: { include, filter }, warnings };
37
+ }
38
+ function parseSubjects(raw) {
39
+ const s = raw.subjects;
40
+ if (!s || typeof s !== 'object' || Array.isArray(s))
41
+ return undefined;
42
+ const obj = s;
43
+ return {
44
+ products: Array.isArray(obj.products)
45
+ ? obj.products.filter((x) => typeof x === 'string')
46
+ : undefined,
47
+ };
48
+ }
9
49
  /**
10
- * Validate and normalise a raw (YAML-parsed) document into a
11
- * `CoverageMetricConfig`. Accepts both the bare top-level object and the
12
- * wrapped form (`{ coverage_metric: { … } }`).
50
+ * Parse and validate a raw (YAML-parsed) document into a `CoverageMetricConfig`.
51
+ *
52
+ * Accepts:
53
+ * - `{ view: { id, name, regimes, subjects, thresholds } }` — canonical (new spec)
54
+ * - `{ coverage_metric: { … } }` — deprecated; `scope.codex` → `regimes.include`
55
+ * - Bare top-level object — same as deprecated wrapper, no wrapper warning
13
56
  */
14
57
  export function parseCoverageMetricConfig(raw) {
15
58
  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
16
59
  return { ok: false, errors: ['coverage-metric: expected an object at the document root'] };
17
60
  }
18
61
  const top = raw;
19
- // Unwrap optional `coverage_metric:` wrapper.
20
- const v = 'coverage_metric' in top &&
62
+ const errors = [];
63
+ const warnings = [];
64
+ let v;
65
+ let regimes;
66
+ let subjects;
67
+ if ('view' in top && top.view && typeof top.view === 'object' && !Array.isArray(top.view)) {
68
+ // Canonical format: view: { id, name, regimes, subjects, thresholds }
69
+ v = top.view;
70
+ const parsed = parseRegimes(v);
71
+ regimes = parsed.regimes;
72
+ warnings.push(...parsed.warnings);
73
+ subjects = parseSubjects(v);
74
+ }
75
+ else if ('coverage_metric' in top &&
21
76
  top.coverage_metric &&
22
77
  typeof top.coverage_metric === 'object' &&
23
- !Array.isArray(top.coverage_metric)
24
- ? top.coverage_metric
25
- : top;
26
- const errors = [];
78
+ !Array.isArray(top.coverage_metric)) {
79
+ // Deprecated wrapper: coverage_metric: { scope: { codex, jurisdictions, subjects }, … }
80
+ v = top.coverage_metric;
81
+ warnings.push('COVMET-DEPRECATED: coverage_metric: wrapper is deprecated; migrate to view:');
82
+ const r = migrateLegacyScope(v);
83
+ regimes = r.regimes;
84
+ subjects = r.subjects;
85
+ }
86
+ else {
87
+ // Bare object — accept as deprecated shape without extra warning
88
+ v = top;
89
+ const r = migrateLegacyScope(v);
90
+ regimes = r.regimes;
91
+ subjects = r.subjects;
92
+ }
27
93
  if (!v.id || typeof v.id !== 'string')
28
94
  errors.push('coverage_metric.id: required string');
29
95
  if (!v.name || typeof v.name !== 'string')
30
96
  errors.push('coverage_metric.name: required string');
31
97
  if (errors.length)
32
98
  return { ok: false, errors };
33
- const rawScope = v.scope && typeof v.scope === 'object' && !Array.isArray(v.scope)
34
- ? v.scope
35
- : {};
36
- const rawSubjects = rawScope.subjects && typeof rawScope.subjects === 'object' && !Array.isArray(rawScope.subjects)
37
- ? rawScope.subjects
38
- : {};
39
- const rawThresholds = v.thresholds && typeof v.thresholds === 'object' && !Array.isArray(v.thresholds)
40
- ? v.thresholds
99
+ const rawT = v.thresholds;
100
+ const rawThresholds = rawT && typeof rawT === 'object' && !Array.isArray(rawT)
101
+ ? rawT
41
102
  : {};
42
103
  const greenRaw = typeof rawThresholds.green === 'number' ? rawThresholds.green : 0.8;
43
104
  const amberRaw = typeof rawThresholds.amber === 'number' ? rawThresholds.amber : 0.5;
@@ -47,31 +108,51 @@ export function parseCoverageMetricConfig(raw) {
47
108
  description: typeof v.description === 'string' ? v.description : undefined,
48
109
  date: typeof v.date === 'string' ? v.date : undefined,
49
110
  version: typeof v.version === 'string' ? v.version : undefined,
50
- scope: {
51
- jurisdictions: Array.isArray(rawScope.jurisdictions)
52
- ? rawScope.jurisdictions.filter((x) => typeof x === 'string')
53
- : undefined,
54
- codex: Array.isArray(rawScope.codex)
55
- ? rawScope.codex.filter((x) => typeof x === 'string')
56
- : undefined,
57
- subjects: {
58
- products: Array.isArray(rawSubjects.products)
59
- ? rawSubjects.products.filter((x) => typeof x === 'string')
60
- : undefined,
61
- },
62
- },
111
+ regimes,
112
+ subjects,
63
113
  thresholds: {
64
114
  green: Math.max(0, Math.min(1, greenRaw)),
65
115
  amber: Math.max(0, Math.min(1, amberRaw)),
66
116
  },
117
+ warnings: warnings.length > 0 ? warnings : undefined,
67
118
  };
68
119
  return { ok: true, config };
69
120
  }
121
+ /** Translate old `scope.codex` / `scope.jurisdictions` / `scope.subjects` to new shape. */
122
+ function migrateLegacyScope(v) {
123
+ const rawScope = v.scope && typeof v.scope === 'object' && !Array.isArray(v.scope)
124
+ ? v.scope
125
+ : {};
126
+ const codexList = Array.isArray(rawScope.codex)
127
+ ? rawScope.codex.filter((x) => typeof x === 'string')
128
+ : undefined;
129
+ const jurisdictionList = Array.isArray(rawScope.jurisdictions)
130
+ ? rawScope.jurisdictions.filter((x) => typeof x === 'string')
131
+ : undefined;
132
+ let regimes;
133
+ if (codexList && codexList.length > 0) {
134
+ regimes = { include: codexList };
135
+ }
136
+ else if (jurisdictionList && jurisdictionList.length > 0) {
137
+ regimes = { filter: { jurisdiction: jurisdictionList } };
138
+ }
139
+ const rawSubjectsObj = rawScope.subjects && typeof rawScope.subjects === 'object' && !Array.isArray(rawScope.subjects)
140
+ ? rawScope.subjects
141
+ : {};
142
+ const subjects = {
143
+ products: Array.isArray(rawSubjectsObj.products)
144
+ ? rawSubjectsObj.products.filter((x) => typeof x === 'string')
145
+ : undefined,
146
+ };
147
+ return { regimes, subjects };
148
+ }
70
149
  // ── Builder ───────────────────────────────────────────────────────────────────
71
- const ADMITTED = new Set(['compliant', 'partial', 'non_compliant', 'under_review', 'n_a']);
72
- /** Worst-case aggregate across multiple assertions (excludes n_a when others exist). */
150
+ const ADMITTED = new Set(['compliant', 'partial', 'non_compliant', 'under_review', 'pending_owner', 'n_a']);
151
+ /** Worst-case aggregate across multiple assertions (excludes n_a when others exist).
152
+ * Precedence: non_compliant > partial > pending_owner > under_review > compliant > n_a. */
73
153
  function aggregateStatus(assertions) {
74
154
  let sawPartial = false;
155
+ let sawPendingOwner = false;
75
156
  let sawUnderReview = false;
76
157
  let sawCompliant = false;
77
158
  for (const a of assertions) {
@@ -79,6 +160,8 @@ function aggregateStatus(assertions) {
79
160
  return 'non_compliant';
80
161
  if (a.status === 'partial')
81
162
  sawPartial = true;
163
+ else if (a.status === 'pending_owner')
164
+ sawPendingOwner = true;
82
165
  else if (a.status === 'under_review')
83
166
  sawUnderReview = true;
84
167
  else if (a.status === 'compliant')
@@ -86,6 +169,8 @@ function aggregateStatus(assertions) {
86
169
  }
87
170
  if (sawPartial)
88
171
  return 'partial';
172
+ if (sawPendingOwner)
173
+ return 'pending_owner';
89
174
  if (sawUnderReview)
90
175
  return 'under_review';
91
176
  if (sawCompliant)
@@ -101,12 +186,35 @@ function ragFromPct(pct, thresholds, hasData) {
101
186
  return 'amber';
102
187
  return 'red';
103
188
  }
189
+ /**
190
+ * Resolve the ordered list of codex IDs from the config's `regimes` field:
191
+ * - `regimes.include` — explicit list, takes precedence.
192
+ * - `regimes.filter` — resolved against the canon's codex catalogue.
193
+ * - No `regimes` — all codex entries in the canon.
194
+ */
195
+ function resolveCodexList(regimes, canon) {
196
+ if (regimes?.include && regimes.include.length > 0)
197
+ return regimes.include;
198
+ if (regimes?.filter) {
199
+ let docs = [...canon.codex];
200
+ if (regimes.filter.jurisdiction?.length) {
201
+ const jurs = regimes.filter.jurisdiction;
202
+ docs = docs.filter(c => c.jurisdiction != null && jurs.includes(c.jurisdiction));
203
+ }
204
+ if (regimes.filter.codex_type?.length) {
205
+ const types = regimes.filter.codex_type;
206
+ docs = docs.filter(c => c.type != null && types.includes(c.type));
207
+ }
208
+ return docs.map(c => c.id);
209
+ }
210
+ return canon.codex.map(c => c.id);
211
+ }
104
212
  /**
105
213
  * Compute per-codex coverage statistics.
106
214
  *
107
- * For each law (codex ID) in `config.scope.codex`:
215
+ * For each law (codex ID) resolved from `config.regimes`:
108
216
  * - Collect requirements whose `derived_from` includes that law.
109
- * - Optionally restrict to assertions for `config.scope.subjects.products`.
217
+ * - Optionally restrict to assertions for `config.subjects.products`.
110
218
  * - Compute compliant / partial / non_compliant / under_review / gap counts.
111
219
  * - Compute coverage % = (compliant + partial) / total and apply RAG.
112
220
  */
@@ -115,8 +223,8 @@ export function buildCoverageMatrix(canon, config) {
115
223
  requirements: canon.requirements,
116
224
  assertions: canon.assertions,
117
225
  });
118
- const codexList = config.scope.codex ?? [];
119
- const productList = config.scope.subjects?.products ?? [];
226
+ const codexList = resolveCodexList(config.regimes, canon);
227
+ const productList = config.subjects?.products ?? [];
120
228
  const rows = [];
121
229
  for (const codexId of codexList) {
122
230
  const reqs = index.requirementsByLaw.get(codexId) ?? [];
@@ -126,14 +234,13 @@ export function buildCoverageMatrix(canon, config) {
126
234
  let partial = 0;
127
235
  let non_compliant = 0;
128
236
  let under_review = 0;
237
+ let pending_owner = 0;
129
238
  let gap = 0;
130
239
  for (const req of reqs) {
131
240
  const allAssertions = index.assertionsByRequirement.get(req.id) ?? [];
132
241
  // Filter to admitted statuses, then optionally to scoped products.
133
242
  const admitted = allAssertions.filter(a => ADMITTED.has(a.status));
134
- const scoped = productList.length > 0
135
- ? admitted.filter(a => productList.includes(a.subject))
136
- : admitted;
243
+ const scoped = productList.length > 0 ? admitted.filter(a => productList.includes(a.subject)) : admitted;
137
244
  // Exclude pure n_a assertions from counting as coverage.
138
245
  const active = scoped.filter(a => a.status !== 'n_a');
139
246
  if (active.length === 0) {
@@ -149,6 +256,8 @@ export function buildCoverageMatrix(canon, config) {
149
256
  non_compliant++;
150
257
  else if (status === 'under_review')
151
258
  under_review++;
259
+ else if (status === 'pending_owner')
260
+ pending_owner++;
152
261
  else
153
262
  gap++;
154
263
  }
@@ -163,6 +272,7 @@ export function buildCoverageMatrix(canon, config) {
163
272
  partial,
164
273
  non_compliant,
165
274
  under_review,
275
+ pending_owner,
166
276
  gap,
167
277
  coveredCount,
168
278
  coveragePct,
@@ -16,6 +16,7 @@ const STATUS_LABELS = {
16
16
  partial: 'Partial',
17
17
  non_compliant: 'Non-compliant',
18
18
  under_review: 'Under review',
19
+ pending_owner: 'Pending owner',
19
20
  n_a: 'N/A',
20
21
  };
21
22
  function escHtml(s) {
@@ -31,51 +32,52 @@ function badge(status) {
31
32
  // A4 portrait with 18mm margins — matches the WeasyPrint one-pager template
32
33
  // the site uses. Brand colour `#004d67` mirrors `--ts-brand-primary` from
33
34
  // extension/src/compliance-render.ts so the PDF and the Studio preview agree.
34
- const CSS = `
35
- @page {
36
- size: A4 portrait;
37
- margin: 18mm 16mm 22mm;
38
- @bottom-left { content: "Transitrix — compliance report"; font: 9pt "Helvetica", "Arial", sans-serif; color: #475569; }
39
- @bottom-right { content: "Page " counter(page) " / " counter(pages); font: 9pt "Helvetica", "Arial", sans-serif; color: #475569; }
40
- }
41
- html, body { margin: 0; padding: 0; }
42
- body { font-family: "Helvetica", "Arial", sans-serif; font-size: 10pt; color: #0f172a; line-height: 1.4; }
43
- .cmp-header { border-bottom: 2px solid #004d67; padding-bottom: 6pt; margin-bottom: 14pt; }
44
- .cmp-header .cmp-eyebrow { font-size: 8.5pt; letter-spacing: 0.08em; text-transform: uppercase; color: #004d67; font-weight: 700; }
45
- .cmp-header h1 { font-size: 17pt; margin: 2pt 0 4pt; color: #0f172a; }
46
- .cmp-header .cmp-stamp { font-size: 9pt; color: #475569; }
47
- h2 { font-size: 12pt; margin: 16pt 0 6pt; color: #0f172a; page-break-after: avoid; }
48
- h2 .cmp-count { color: #64748b; font-weight: 400; font-size: 10pt; }
49
- p.cmp-empty { color: #64748b; font-style: italic; }
50
- table { border-collapse: collapse; width: 100%; font-size: 9.5pt; margin-bottom: 10pt; }
51
- th, td { border: 0.5pt solid #cbd5e1; padding: 4pt 6pt; text-align: left; vertical-align: top; }
52
- th { background: #f1f5f9; font-weight: 700; color: #0f172a; }
53
- table.cmp-matrix th:first-child, table.cmp-matrix td:first-child { background: #f8fafc; font-weight: 600; }
54
- code, .cmp-id { font-family: "Menlo", "Consolas", monospace; font-size: 9pt; color: #475569; }
55
- .cmp-badge { display: inline-block; padding: 1pt 6pt; border-radius: 8pt; font-size: 8.5pt; font-weight: 700; }
56
- .cmp-compliant { background: #d1fae5; color: #065f46; }
57
- .cmp-partial { background: #fef9c3; color: #854d0e; }
58
- .cmp-non_compliant { background: #fee2e2; color: #991b1b; }
59
- .cmp-under_review { background: #e0f2fe; color: #0c4a6e; }
60
- .cmp-n_a { background: #f1f5f9; color: #64748b; }
61
- .cmp-cell-gap { color: #94a3b8; text-align: center; }
62
- .cmp-req { border: 0.5pt solid #cbd5e1; border-radius: 3pt; margin-bottom: 8pt; page-break-inside: avoid; }
63
- .cmp-req-head { background: #f1f5f9; padding: 5pt 8pt; }
64
- .cmp-req-head .cmp-req-name { font-weight: 700; color: #0f172a; }
65
- .cmp-req-head .cmp-req-id { margin-left: 6pt; color: #475569; font-size: 9pt; font-family: "Menlo", "Consolas", monospace; }
66
- .cmp-sev { font-size: 8pt; text-transform: uppercase; letter-spacing: 0.06em; margin-left: 8pt; }
67
- .cmp-sev-high { color: #b91c1c; }
68
- .cmp-sev-medium { color: #b45309; }
69
- .cmp-sev-low { color: #2563eb; }
70
- .cmp-assertions { list-style: none; margin: 0; padding: 0; }
71
- .cmp-assertions li { padding: 4pt 8pt 4pt 16pt; border-top: 0.5pt solid #e2e8f0; }
72
- .cmp-assertions li .cmp-meta { color: #475569; font-size: 8.5pt; margin-left: 6pt; }
73
- .cmp-section { margin-bottom: 12pt; }
74
- .cmp-rows { list-style: none; margin: 0; padding: 0; }
75
- .cmp-rows li { padding: 3pt 0 3pt 14pt; border-bottom: 0.5pt solid #e2e8f0; text-indent: -10pt; }
76
- .cmp-rows li::before { content: "☐"; color: #94a3b8; margin-right: 6pt; }
77
- .cmp-ok { color: #065f46; font-weight: 600; }
78
- .cmp-summary { font-size: 9pt; color: #475569; }
35
+ const CSS = `
36
+ @page {
37
+ size: A4 portrait;
38
+ margin: 18mm 16mm 22mm;
39
+ @bottom-left { content: "Transitrix — compliance report"; font: 9pt "Helvetica", "Arial", sans-serif; color: #475569; }
40
+ @bottom-right { content: "Page " counter(page) " / " counter(pages); font: 9pt "Helvetica", "Arial", sans-serif; color: #475569; }
41
+ }
42
+ html, body { margin: 0; padding: 0; }
43
+ body { font-family: "Helvetica", "Arial", sans-serif; font-size: 10pt; color: #0f172a; line-height: 1.4; }
44
+ .cmp-header { border-bottom: 2px solid #004d67; padding-bottom: 6pt; margin-bottom: 14pt; }
45
+ .cmp-header .cmp-eyebrow { font-size: 8.5pt; letter-spacing: 0.08em; text-transform: uppercase; color: #004d67; font-weight: 700; }
46
+ .cmp-header h1 { font-size: 17pt; margin: 2pt 0 4pt; color: #0f172a; }
47
+ .cmp-header .cmp-stamp { font-size: 9pt; color: #475569; }
48
+ h2 { font-size: 12pt; margin: 16pt 0 6pt; color: #0f172a; page-break-after: avoid; }
49
+ h2 .cmp-count { color: #64748b; font-weight: 400; font-size: 10pt; }
50
+ p.cmp-empty { color: #64748b; }
51
+ table { border-collapse: collapse; width: 100%; font-size: 9.5pt; margin-bottom: 10pt; }
52
+ th, td { border: 0.5pt solid #cbd5e1; padding: 4pt 6pt; text-align: left; vertical-align: top; }
53
+ th { background: #f1f5f9; font-weight: 700; color: #0f172a; }
54
+ table.cmp-matrix th:first-child, table.cmp-matrix td:first-child { background: #f8fafc; font-weight: 600; }
55
+ code, .cmp-id { font-family: "Menlo", "Consolas", monospace; font-size: 9pt; color: #475569; }
56
+ .cmp-badge { display: inline-block; padding: 1pt 6pt; border-radius: 8pt; font-size: 8.5pt; font-weight: 700; }
57
+ .cmp-compliant { background: #d1fae5; color: #065f46; }
58
+ .cmp-partial { background: #fef9c3; color: #854d0e; }
59
+ .cmp-non_compliant { background: #fee2e2; color: #991b1b; }
60
+ .cmp-under_review { background: #e0f2fe; color: #0c4a6e; }
61
+ .cmp-pending_owner { background: #f3e8ff; color: #6b21a8; }
62
+ .cmp-n_a { background: #f1f5f9; color: #64748b; }
63
+ .cmp-cell-gap { color: #94a3b8; text-align: center; }
64
+ .cmp-req { border: 0.5pt solid #cbd5e1; border-radius: 3pt; margin-bottom: 8pt; page-break-inside: avoid; }
65
+ .cmp-req-head { background: #f1f5f9; padding: 5pt 8pt; }
66
+ .cmp-req-head .cmp-req-name { font-weight: 700; color: #0f172a; }
67
+ .cmp-req-head .cmp-req-id { margin-left: 6pt; color: #475569; font-size: 9pt; font-family: "Menlo", "Consolas", monospace; }
68
+ .cmp-sev { font-size: 8pt; text-transform: uppercase; letter-spacing: 0.06em; margin-left: 8pt; }
69
+ .cmp-sev-high { color: #b91c1c; }
70
+ .cmp-sev-medium { color: #b45309; }
71
+ .cmp-sev-low { color: #2563eb; }
72
+ .cmp-assertions { list-style: none; margin: 0; padding: 0; }
73
+ .cmp-assertions li { padding: 4pt 8pt 4pt 16pt; border-top: 0.5pt solid #e2e8f0; }
74
+ .cmp-assertions li .cmp-meta { color: #475569; font-size: 8.5pt; margin-left: 6pt; }
75
+ .cmp-section { margin-bottom: 12pt; }
76
+ .cmp-rows { list-style: none; margin: 0; padding: 0; }
77
+ .cmp-rows li { padding: 3pt 0 3pt 14pt; border-bottom: 0.5pt solid #e2e8f0; text-indent: -10pt; }
78
+ .cmp-rows li::before { content: "☐"; color: #94a3b8; margin-right: 6pt; }
79
+ .cmp-ok { color: #065f46; font-weight: 600; }
80
+ .cmp-summary { font-size: 9pt; color: #475569; }
79
81
  `;
80
82
  function htmlDoc(title, today, body) {
81
83
  const stamp = today ? `Generated ${escHtml(today)}` : '';
@@ -203,7 +205,7 @@ export function renderComplianceHtml(canon, scope, options = {}) {
203
205
  export function renderImpactMatrixHtml(matrix, options = {}) {
204
206
  const STATUS_GLYPH = {
205
207
  compliant: 'OK', partial: 'PARTIAL', non_compliant: 'FAIL',
206
- under_review: 'REVIEW', n_a: 'N/A',
208
+ under_review: 'REVIEW', pending_owner: 'PENDING', n_a: 'N/A',
207
209
  };
208
210
  const statusClass = (s) => s ?? 'gap';
209
211
  const colHeaders = matrix.columns.map(c => `<th>${escHtml(c.label)}</th>`).join('');
@@ -223,34 +225,35 @@ export function renderImpactMatrixHtml(matrix, options = {}) {
223
225
  }).join('');
224
226
  const snapshotLine = matrix.snapshotAt ? `<span>Snapshot: ${escHtml(matrix.snapshotAt)}</span>` : '';
225
227
  const desc = matrix.description ? `<p>${escHtml(matrix.description)}</p>` : '';
226
- const extraCss = `
227
- table { border-collapse: collapse; width: 100%; font-size: 9pt; }
228
- th, td { border: 1px solid #cbd5e1; padding: 3pt 5pt; text-align: center; vertical-align: middle; }
229
- td.cmp-row-label { text-align: left; font-size: 8.5pt; max-width: 180pt; white-space: normal; }
230
- .cmp-compliant { background: #d1fae5; color: #065f46; font-weight: 700; }
231
- .cmp-partial { background: #fef9c3; color: #854d0e; font-weight: 700; }
232
- .cmp-non_compliant { background: #fee2e2; color: #991b1b; font-weight: 700; }
233
- .cmp-under_review { background: #e0f2fe; color: #0c4a6e; }
234
- .cmp-gap { background: #f8fafc; color: #94a3b8; font-size: 8pt; }
235
- .cmp-na { background: #f1f5f9; color: #94a3b8; font-size: 8pt; }
228
+ const extraCss = `
229
+ table { border-collapse: collapse; width: 100%; font-size: 9pt; }
230
+ th, td { border: 1px solid #cbd5e1; padding: 3pt 5pt; text-align: center; vertical-align: middle; }
231
+ td.cmp-row-label { text-align: left; font-size: 8.5pt; max-width: 180pt; white-space: normal; }
232
+ .cmp-compliant { background: #d1fae5; color: #065f46; font-weight: 700; }
233
+ .cmp-partial { background: #fef9c3; color: #854d0e; font-weight: 700; }
234
+ .cmp-non_compliant { background: #fee2e2; color: #991b1b; font-weight: 700; }
235
+ .cmp-under_review { background: #e0f2fe; color: #0c4a6e; }
236
+ .cmp-pending_owner { background: #f3e8ff; color: #6b21a8; }
237
+ .cmp-gap { background: #f8fafc; color: #94a3b8; font-size: 8pt; }
238
+ .cmp-na { background: #f1f5f9; color: #94a3b8; font-size: 8pt; }
236
239
  .cmp-dl { color: #b45309; font-size: 8pt; }`;
237
- return `<!DOCTYPE html>
238
- <html lang="en">
239
- <head>
240
- <meta charset="UTF-8">
241
- <style>
242
- ${CSS}
243
- ${extraCss}
244
- </style>
245
- </head>
246
- <body>
247
- <div class="cmp-header">
248
- <div class="cmp-eyebrow">Compliance Impact Matrix</div>
249
- <h1>${escHtml(matrix.viewName)}</h1>
250
- <div class="cmp-stamp">View: <code>${escHtml(matrix.viewId)}</code> ${snapshotLine}${options.today ? ` · Generated: ${escHtml(options.today)}` : ''}</div>
251
- </div>
252
- ${desc}
253
- <table><thead>${head}</thead><tbody>${rows}</tbody></table>
254
- </body>
240
+ return `<!DOCTYPE html>
241
+ <html lang="en">
242
+ <head>
243
+ <meta charset="UTF-8">
244
+ <style>
245
+ ${CSS}
246
+ ${extraCss}
247
+ </style>
248
+ </head>
249
+ <body>
250
+ <div class="cmp-header">
251
+ <div class="cmp-eyebrow">Compliance Impact Matrix</div>
252
+ <h1>${escHtml(matrix.viewName)}</h1>
253
+ <div class="cmp-stamp">View: <code>${escHtml(matrix.viewId)}</code> ${snapshotLine}${options.today ? ` · Generated: ${escHtml(options.today)}` : ''}</div>
254
+ </div>
255
+ ${desc}
256
+ <table><thead>${head}</thead><tbody>${rows}</tbody></table>
257
+ </body>
255
258
  </html>`;
256
259
  }
@@ -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,44 @@ 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: productId, stageId, label: productId + ':' + stageId }`.
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 only when grouping.columns is not 'product'. */
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;
44
56
  }
45
57
  /**
46
58
  * Grouping options for matrix columns.
47
59
  *
48
- * `columns` controls the column grain:
60
+ * `columns` controls the column grain (COMPIMP-004 validates the value set):
49
61
  * - `'product'` (default) — one column per subject.
50
- * - `'product-stage'` — one column per (subject, stage) pair derived
51
- * from `objectDetails` passed to `buildImpactMatrix`.
62
+ * - `'product-stage'` — one column per (subject, stage) pair; pass
63
+ * `objectDetails` (from `extractObjectDetails`) into `buildImpactMatrix`.
64
+ * - `'product-stage-task'` — one column per (subject, stage, task) triple;
65
+ * pass `objectDetails` with stages that carry `tasks[]` (built via
66
+ * `extractObjectDetails` + `extractProcessFlowTasks` + `mergeStageTaskDetails`).
52
67
  */
53
68
  export interface ImpactGrouping {
54
- columns: 'object' | 'object-details';
69
+ columns: 'product' | 'product-stage' | 'product-stage-task';
55
70
  }
56
71
  /**
57
72
  * Named, versioned view-config — the report *definition*.
@@ -99,7 +114,7 @@ export interface ImpactViewConfig {
99
114
  * are auditable ("what I assumed").
100
115
  */
101
116
  export declare const COMPLIANCE_IMPACT_DEFAULTS: {
102
- /** Accept all four active statuses + n_a in cell aggregation. */
117
+ /** Accept all active statuses + n_a in cell aggregation. */
103
118
  readonly status_display: {
104
119
  readonly show: AssertionStatus[];
105
120
  };
@@ -119,10 +134,11 @@ export declare const COMPLIANCE_IMPACT_DEFAULTS: {
119
134
  readonly subjects: {
120
135
  readonly products: string[];
121
136
  readonly processes: string[];
137
+ readonly capabilities: string[];
122
138
  };
123
- /** Column grain: one column per business object (no stage decomposition). */
139
+ /** Column grain: one column per subject (no stage/task decomposition). */
124
140
  readonly grouping: {
125
- readonly columns: "object";
141
+ readonly columns: "product";
126
142
  };
127
143
  };
128
144
  export type ParseImpactViewConfigResult = {
@@ -216,18 +232,26 @@ export declare function computeDeadlineStatus(deadline: string | undefined, toda
216
232
  * Build the matrix per §5.2.
217
233
  *
218
234
  * 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
235
  *
224
- * Subjects are taken from `config.subjects.products` and
225
- * `config.subjects.processes` (in that order). Subjects with no binding
236
+ * At `grouping.columns: 'product-stage'`, pass `objectDetails` (from
237
+ * `extractObjectDetails` on a process-blueprint) to expand each subject into
238
+ * per-stage sub-columns. Subjects with no stage mapping fall back to a single
239
+ * product-grain column.
240
+ *
241
+ * At `grouping.columns: 'product-stage-task'`, pass `objectDetails` where each
242
+ * stage carries a `tasks[]` array (combine blueprint stages and process-flow
243
+ * tasks via `mergeStageTaskDetails`). Each task becomes a
244
+ * `(subject, stage, task)` column. Stages with no tasks fall back to
245
+ * stage-grain columns.
246
+ *
247
+ * Subjects are taken from `config.subjects.products`, `config.subjects.processes`,
248
+ * and `config.subjects.capabilities` (in that order). Subjects with no binding
226
249
  * obligation still appear as columns so the gap is visible.
227
250
  */
228
251
  export declare function buildImpactMatrix(canon: ComplianceCanon, config: ImpactViewConfig, objectDetails?: ObjectDetailInput[]): ImpactMatrix;
229
252
  /**
230
- * Extract `ObjectDetailInput` from a raw (YAML-parsed) process-blueprint document.
253
+ * Extract stage-level `ObjectDetailInput` from a raw (YAML-parsed)
254
+ * process-blueprint document.
231
255
  *
232
256
  * Accepts both the bare document object and the wrapped form
233
257
  * (`{ process_blueprint: { id, stages: […] } }`).
@@ -236,8 +260,45 @@ export declare function buildImpactMatrix(canon: ComplianceCanon, config: Impact
236
260
  *
237
261
  * Pass the returned value into `buildImpactMatrix` as part of the
238
262
  * `objectDetails` array when `grouping.columns: 'product-stage'` is configured.
263
+ * For `grouping.columns: 'product-stage-task'`, combine with
264
+ * `extractProcessFlowTasks` via `mergeStageTaskDetails`.
239
265
  */
240
266
  export declare function extractObjectDetails(doc: unknown): ObjectDetailInput | null;
267
+ /**
268
+ * Extract task-type steps from a raw (YAML-parsed) PROCESS element document.
269
+ *
270
+ * Reads `notation: process` documents with an inline `flow.steps[]` array.
271
+ * Filters for task-type nodes (`task`, `userTask`, `serviceTask`) — start/end
272
+ * events and gateways are excluded. Returns a flat `ObjectDetailInput` where
273
+ * `details` are the extracted task steps in declaration order.
274
+ *
275
+ * Returns `null` when the document is not a recognisable process element or
276
+ * has no `id` / `flow.steps` array.
277
+ *
278
+ * Use the returned value with `mergeStageTaskDetails` to embed tasks into
279
+ * blueprint stages for `grouping.columns: product-stage-task`.
280
+ */
281
+ export declare function extractProcessFlowTasks(doc: unknown): ObjectDetailInput | null;
282
+ /**
283
+ * Combine blueprint stage details (from `extractObjectDetails`) with
284
+ * process-flow task details (from `extractProcessFlowTasks`) into a
285
+ * stage-with-tasks hierarchy for `grouping.columns: product-stage-task`.
286
+ *
287
+ * All tasks from `taskInput.details` are distributed into their parent stage
288
+ * by matching the `stageOwner` map: `stageId → taskId[]`. If `stageOwner` is
289
+ * omitted (no stage-to-task mapping is available), all tasks are placed into
290
+ * every stage (`undefined` key `→` all tasks).
291
+ *
292
+ * When `taskInput` is `null` or its `objectId` does not match `stageInput`,
293
+ * returns `stageInput` unchanged (callers can safely chain the result into
294
+ * `buildImpactMatrix` — it will degrade to stage-grain columns).
295
+ *
296
+ * @param stageInput Stage-level detail (from `extractObjectDetails`).
297
+ * @param taskInput Task-level detail (from `extractProcessFlowTasks`).
298
+ * @param stageOwner Optional mapping `stageId → taskId[]`. When provided,
299
+ * only tasks listed for a stage are embedded in it.
300
+ */
301
+ export declare function mergeStageTaskDetails(stageInput: ObjectDetailInput, taskInput: ObjectDetailInput | null, stageOwner?: Record<string, string[]>): ObjectDetailInput;
241
302
  /**
242
303
  * Render the matrix as a GitHub-flavoured markdown table.
243
304
  *