@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.
- package/LICENSE +21 -21
- package/README.md +47 -47
- package/dist/__tests__/edge-path.test.js +8 -0
- 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 +139 -6
- 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 +107 -26
- 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 +311 -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 +77 -74
- package/dist/compliance/impact.d.ts +77 -16
- package/dist/compliance/impact.js +193 -25
- 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 +74 -45
- package/dist/factor/index.d.ts +3 -2
- package/dist/factor/index.js +1 -1
- package/dist/factor/types.d.ts +4 -7
- 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__/resolver.test.js +185 -0
- package/dist/fgca/index.d.ts +2 -0
- package/dist/fgca/index.js +1 -0
- package/dist/fgca/resolver.d.ts +49 -0
- package/dist/fgca/resolver.js +124 -0
- 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/layout.js +5 -1
- package/dist/process-blueprint/types.d.ts +22 -1
- 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 +69 -69
- package/dist/webview/__tests__/entry.test.js +11 -11
- package/dist/webview/render-activities.d.ts +3 -1
- package/dist/webview/render-activities.js +2 -2
- package/dist/webview/render-activity-card.js +104 -77
- package/dist/webview/render-fgca.d.ts +4 -0
- package/dist/webview/render-fgca.js +2 -5
- package/dist/webview/render-goals.d.ts +1 -0
- package/dist/webview/render-goals.js +15 -15
- package/dist/webview/render-process-blueprint.js +10 -10
- package/package.json +40 -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 +95 -95
- package/src/activities/gantt.ts +276 -276
- package/src/activities/index.ts +28 -28
- package/src/activities/layout.ts +112 -112
- package/src/activities/types.ts +148 -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 +307 -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 +415 -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 +840 -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 +298 -295
- package/src/compliance/impact.ts +861 -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 +80 -45
- package/src/factor/index.ts +3 -7
- package/src/factor/types.ts +14 -18
- 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 +400 -400
- package/src/fgca/preview-layout.ts +217 -217
- package/src/fgca/resolver.ts +157 -0
- package/src/fgca/types.ts +50 -50
- package/src/fgca/validate.ts +126 -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 +31 -31
- package/src/process-blueprint/layout.ts +535 -531
- package/src/process-blueprint/types.ts +263 -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 +237 -237
- package/src/theme/tokens.ts +133 -133
- package/src/typed-id.ts +56 -56
- package/src/validation-types.ts +36 -36
- package/src/webview/__tests__/entry-notations.test.ts +77 -77
- package/src/webview/__tests__/entry.test.ts +64 -64
- package/src/webview/__tests__/render-goals.test.ts +72 -72
- package/src/webview/entry.ts +292 -292
- package/src/webview/render-activities.ts +5 -3
- package/src/webview/render-activity-card.ts +266 -234
- package/src/webview/render-fgca.ts +6 -5
- package/src/webview/render-goals.ts +111 -110
- package/src/webview/render-process-blueprint.ts +207 -207
- 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.d.ts +0 -1
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import { emptyCanon } from '../classify.js';
|
|
3
|
-
import { buildImpactMatrix, renderImpactMarkdown, parseImpactViewConfig, COMPLIANCE_IMPACT_DEFAULTS, extractObjectDetails, computeDeadlineStatus, } from '../impact.js';
|
|
3
|
+
import { buildImpactMatrix, renderImpactMarkdown, parseImpactViewConfig, COMPLIANCE_IMPACT_DEFAULTS, extractObjectDetails, extractProcessFlowTasks, mergeStageTaskDetails, computeDeadlineStatus, } from '../impact.js';
|
|
4
4
|
// Industry- and regime-agnostic synthetic fixture: opaque ids only, no real
|
|
5
5
|
// regulator / product names, per #166 acceptance ("Output is aggregates only —
|
|
6
6
|
// no subject/obligation names tied to any real corpus").
|
|
@@ -160,6 +160,94 @@ describe('renderImpactMarkdown', () => {
|
|
|
160
160
|
const md = renderImpactMarkdown(matrix);
|
|
161
161
|
expect(md).toContain('Report snapshot: 2026-06-09');
|
|
162
162
|
});
|
|
163
|
+
it('resolves capability subject names from canon.subjects', () => {
|
|
164
|
+
const canon = emptyCanon();
|
|
165
|
+
canon.subjects.push({ id: 'CAPABILITY-CRM-1', name: 'CRM Platform' });
|
|
166
|
+
canon.requirements.push({ id: 'REQUIREMENT-FOO-1', name: 'Foo' });
|
|
167
|
+
canon.assertions.push({
|
|
168
|
+
id: 'ASSERTION-1',
|
|
169
|
+
about: 'REQUIREMENT-FOO-1',
|
|
170
|
+
subject: 'CAPABILITY-CRM-1',
|
|
171
|
+
status: 'compliant',
|
|
172
|
+
});
|
|
173
|
+
const matrix = buildImpactMatrix(canon, {
|
|
174
|
+
id: 'V-1', name: 'Test',
|
|
175
|
+
subjects: { capabilities: ['CAPABILITY-CRM-1'] },
|
|
176
|
+
obligations: {},
|
|
177
|
+
});
|
|
178
|
+
expect(matrix.columns[0].subjectId).toBe('CAPABILITY-CRM-1');
|
|
179
|
+
expect(matrix.columns[0].subjectName).toBe('CRM Platform');
|
|
180
|
+
expect(matrix.cells[0][0].status).toBe('compliant');
|
|
181
|
+
});
|
|
182
|
+
it('resolves process subject names from canon.subjects', () => {
|
|
183
|
+
const canon = emptyCanon();
|
|
184
|
+
canon.subjects.push({ id: 'PROCESS-CS-1', name: 'Customer Support' });
|
|
185
|
+
canon.requirements.push({ id: 'REQUIREMENT-BAR-1', name: 'Bar' });
|
|
186
|
+
canon.assertions.push({
|
|
187
|
+
id: 'ASSERTION-P1',
|
|
188
|
+
about: 'REQUIREMENT-BAR-1',
|
|
189
|
+
subject: 'PROCESS-CS-1',
|
|
190
|
+
status: 'partial',
|
|
191
|
+
});
|
|
192
|
+
const matrix = buildImpactMatrix(canon, {
|
|
193
|
+
id: 'V-2', name: 'Test',
|
|
194
|
+
subjects: { processes: ['PROCESS-CS-1'] },
|
|
195
|
+
obligations: {},
|
|
196
|
+
});
|
|
197
|
+
expect(matrix.columns[0].subjectName).toBe('Customer Support');
|
|
198
|
+
expect(matrix.cells[0][0].status).toBe('partial');
|
|
199
|
+
});
|
|
200
|
+
it('mixed product + capability columns — names resolved from respective buckets', () => {
|
|
201
|
+
const canon = buildCanon();
|
|
202
|
+
canon.subjects.push({ id: 'CAPABILITY-CRM-1', name: 'CRM' });
|
|
203
|
+
canon.requirements.push({ id: 'REQUIREMENT-CRM-1', name: 'CRM req' });
|
|
204
|
+
canon.assertions.push({
|
|
205
|
+
id: 'ASSERTION-CRM-1',
|
|
206
|
+
about: 'REQUIREMENT-CRM-1',
|
|
207
|
+
subject: 'CAPABILITY-CRM-1',
|
|
208
|
+
status: 'under_review',
|
|
209
|
+
});
|
|
210
|
+
const matrix = buildImpactMatrix(canon, {
|
|
211
|
+
id: 'V-3', name: 'Mixed',
|
|
212
|
+
subjects: { products: ['PRODUCT-A-1'], capabilities: ['CAPABILITY-CRM-1'] },
|
|
213
|
+
obligations: {},
|
|
214
|
+
});
|
|
215
|
+
expect(matrix.columns.map(c => c.subjectId)).toEqual(['PRODUCT-A-1', 'CAPABILITY-CRM-1']);
|
|
216
|
+
expect(matrix.columns[0].subjectName).toBe('Product A');
|
|
217
|
+
expect(matrix.columns[1].subjectName).toBe('CRM');
|
|
218
|
+
});
|
|
219
|
+
it('pending_owner cell renders as bound with PENDING status', () => {
|
|
220
|
+
const canon = emptyCanon();
|
|
221
|
+
canon.products.push({ id: 'PRODUCT-A-1', name: 'Product A' });
|
|
222
|
+
canon.requirements.push({ id: 'REQUIREMENT-PO-1', name: 'PO req' });
|
|
223
|
+
canon.assertions.push({
|
|
224
|
+
id: 'ASSERTION-PO-1',
|
|
225
|
+
about: 'REQUIREMENT-PO-1',
|
|
226
|
+
subject: 'PRODUCT-A-1',
|
|
227
|
+
status: 'pending_owner',
|
|
228
|
+
owner_to_confirm: 'alice@example.com',
|
|
229
|
+
});
|
|
230
|
+
const matrix = buildImpactMatrix(canon, {
|
|
231
|
+
id: 'V-PO', name: 'PO Test',
|
|
232
|
+
subjects: { products: ['PRODUCT-A-1'] },
|
|
233
|
+
obligations: {},
|
|
234
|
+
});
|
|
235
|
+
expect(matrix.cells[0][0].kind).toBe('bound');
|
|
236
|
+
expect(matrix.cells[0][0].status).toBe('pending_owner');
|
|
237
|
+
expect(matrix.cells[0][0].assertions[0].owner_to_confirm).toBe('alice@example.com');
|
|
238
|
+
});
|
|
239
|
+
it('pending_owner loses to partial in aggregation', () => {
|
|
240
|
+
const canon = emptyCanon();
|
|
241
|
+
canon.products.push({ id: 'PRODUCT-A-1', name: 'Product A' });
|
|
242
|
+
canon.requirements.push({ id: 'REQUIREMENT-PO-2', name: 'PO req 2' });
|
|
243
|
+
canon.assertions.push({ id: 'ASSERTION-PO-2a', about: 'REQUIREMENT-PO-2', subject: 'PRODUCT-A-1', status: 'partial' }, { id: 'ASSERTION-PO-2b', about: 'REQUIREMENT-PO-2', subject: 'PRODUCT-A-1', status: 'pending_owner' });
|
|
244
|
+
const matrix = buildImpactMatrix(canon, {
|
|
245
|
+
id: 'V-PO2', name: 'PO Agg',
|
|
246
|
+
subjects: { products: ['PRODUCT-A-1'] },
|
|
247
|
+
obligations: {},
|
|
248
|
+
});
|
|
249
|
+
expect(matrix.cells[0][0].status).toBe('partial');
|
|
250
|
+
});
|
|
163
251
|
});
|
|
164
252
|
// ── parseImpactViewConfig ───────────────────────────────────────────────────
|
|
165
253
|
describe('parseImpactViewConfig', () => {
|
|
@@ -193,6 +281,7 @@ describe('parseImpactViewConfig', () => {
|
|
|
193
281
|
const c = r.config;
|
|
194
282
|
expect(c.subjects.products).toEqual([]);
|
|
195
283
|
expect(c.subjects.processes).toEqual([]);
|
|
284
|
+
expect(c.subjects.capabilities).toEqual([]);
|
|
196
285
|
expect(c.status_display?.show).toEqual(COMPLIANCE_IMPACT_DEFAULTS.status_display.show);
|
|
197
286
|
expect(c.order_rows_by).toBe('id');
|
|
198
287
|
expect(c.empty_cells?.no_obligation_label).toBe(COMPLIANCE_IMPACT_DEFAULTS.empty_cells.no_obligation_label);
|
|
@@ -239,7 +328,7 @@ describe('CV-3a — stage grouping (ImpactColumn, extractObjectDetails, buildImp
|
|
|
239
328
|
});
|
|
240
329
|
it('product-stage grain: expands columns by stage, label is subjectId:stageId', () => {
|
|
241
330
|
const canon = buildCanon();
|
|
242
|
-
const matrix = buildImpactMatrix(canon, { ...baseConfig, subjects: { products: ['PRODUCT-A-1'] }, grouping: { columns: '
|
|
331
|
+
const matrix = buildImpactMatrix(canon, { ...baseConfig, subjects: { products: ['PRODUCT-A-1'] }, grouping: { columns: 'product-stage' } }, [{ objectId: 'PRODUCT-A-1', details: [{ id: 'STAGE-1', name: 'Stage One' }, { id: 'STAGE-2', name: 'Stage Two' }] }]);
|
|
243
332
|
expect(matrix.columns.map(c => c.label)).toEqual(['PRODUCT-A-1:STAGE-1', 'PRODUCT-A-1:STAGE-2']);
|
|
244
333
|
expect(matrix.columns.map(c => c.stageId)).toEqual(['STAGE-1', 'STAGE-2']);
|
|
245
334
|
});
|
|
@@ -252,7 +341,7 @@ describe('CV-3a — stage grouping (ImpactColumn, extractObjectDetails, buildImp
|
|
|
252
341
|
id: 'V', name: 'V',
|
|
253
342
|
subjects: { products: ['PROD-1'] },
|
|
254
343
|
obligations: {},
|
|
255
|
-
grouping: { columns: '
|
|
344
|
+
grouping: { columns: 'product-stage' },
|
|
256
345
|
}, [{ objectId: 'PROD-1', details: [{ id: 'S1', name: 's1' }, { id: 'S2', name: 's2' }] }]);
|
|
257
346
|
// Both stage columns should be bound (no realised_via means all stages inherit).
|
|
258
347
|
expect(matrix.cells[0][0].kind).toBe('bound');
|
|
@@ -270,7 +359,7 @@ describe('CV-3a — stage grouping (ImpactColumn, extractObjectDetails, buildImp
|
|
|
270
359
|
id: 'V', name: 'V',
|
|
271
360
|
subjects: { products: ['PROD-1'] },
|
|
272
361
|
obligations: {},
|
|
273
|
-
grouping: { columns: '
|
|
362
|
+
grouping: { columns: 'product-stage' },
|
|
274
363
|
}, [{ objectId: 'PROD-1', details: [{ id: 'STAGE-A', name: 'a' }, { id: 'STAGE-B', name: 'b' }] }]);
|
|
275
364
|
expect(matrix.cells[0][0].kind).toBe('bound'); // STAGE-A: covered
|
|
276
365
|
expect(matrix.cells[0][1].kind).toBe('gap'); // STAGE-B: gap
|
|
@@ -280,7 +369,7 @@ describe('CV-3a — stage grouping (ImpactColumn, extractObjectDetails, buildImp
|
|
|
280
369
|
const matrix = buildImpactMatrix(canon, {
|
|
281
370
|
...baseConfig,
|
|
282
371
|
subjects: { products: ['PRODUCT-A-1', 'PRODUCT-B-1'] },
|
|
283
|
-
grouping: { columns: '
|
|
372
|
+
grouping: { columns: 'product-stage' },
|
|
284
373
|
},
|
|
285
374
|
// Only provide stages for A — B falls back to a single column.
|
|
286
375
|
[{ objectId: 'PRODUCT-A-1', details: [{ id: 'S1', name: 's1' }] }]);
|
|
@@ -308,7 +397,7 @@ describe('CV-3a — stage grouping (ImpactColumn, extractObjectDetails, buildImp
|
|
|
308
397
|
id: 'V', name: 'Stage Report',
|
|
309
398
|
subjects: { products: ['PROD-1'] },
|
|
310
399
|
obligations: {},
|
|
311
|
-
grouping: { columns: '
|
|
400
|
+
grouping: { columns: 'product-stage' },
|
|
312
401
|
}, [{ objectId: 'PROD-1', details: [{ id: 'S1', name: 'Stage One' }] }]);
|
|
313
402
|
const md = renderImpactMarkdown(matrix);
|
|
314
403
|
expect(md).toContain('PROD-1:S1');
|
|
@@ -408,3 +497,219 @@ describe('computeDeadlineStatus', () => {
|
|
|
408
497
|
expect(computeDeadlineStatus('2026-12-31', '2026-06-01')).toBe('upcoming');
|
|
409
498
|
});
|
|
410
499
|
});
|
|
500
|
+
// ── parseImpactViewConfig — grouping field ────────────────────────────────────
|
|
501
|
+
describe('parseImpactViewConfig — grouping field', () => {
|
|
502
|
+
it('parses grouping.columns: product-stage', () => {
|
|
503
|
+
const r = parseImpactViewConfig({ id: 'V', name: 'V', subjects: {}, grouping: { columns: 'product-stage' } });
|
|
504
|
+
expect(r.ok).toBe(true);
|
|
505
|
+
if (r.ok)
|
|
506
|
+
expect(r.config.grouping?.columns).toBe('product-stage');
|
|
507
|
+
});
|
|
508
|
+
it('parses grouping.columns: product-stage-task', () => {
|
|
509
|
+
const r = parseImpactViewConfig({ id: 'V', name: 'V', subjects: {}, grouping: { columns: 'product-stage-task' } });
|
|
510
|
+
expect(r.ok).toBe(true);
|
|
511
|
+
if (r.ok)
|
|
512
|
+
expect(r.config.grouping?.columns).toBe('product-stage-task');
|
|
513
|
+
});
|
|
514
|
+
it('parses grouping.columns: product (coarsest grain)', () => {
|
|
515
|
+
const r = parseImpactViewConfig({ id: 'V', name: 'V', subjects: {}, grouping: { columns: 'product' } });
|
|
516
|
+
expect(r.ok).toBe(true);
|
|
517
|
+
if (r.ok)
|
|
518
|
+
expect(r.config.grouping?.columns).toBe('product');
|
|
519
|
+
});
|
|
520
|
+
it('normalises backward-compat alias object-details → product-stage', () => {
|
|
521
|
+
const r = parseImpactViewConfig({ id: 'V', name: 'V', subjects: {}, grouping: { columns: 'object-details' } });
|
|
522
|
+
expect(r.ok).toBe(true);
|
|
523
|
+
if (r.ok)
|
|
524
|
+
expect(r.config.grouping?.columns).toBe('product-stage');
|
|
525
|
+
});
|
|
526
|
+
it('normalises backward-compat alias object → product', () => {
|
|
527
|
+
const r = parseImpactViewConfig({ id: 'V', name: 'V', subjects: {}, grouping: { columns: 'object' } });
|
|
528
|
+
expect(r.ok).toBe(true);
|
|
529
|
+
if (r.ok)
|
|
530
|
+
expect(r.config.grouping?.columns).toBe('product');
|
|
531
|
+
});
|
|
532
|
+
it('leaves grouping undefined when the field is absent', () => {
|
|
533
|
+
const r = parseImpactViewConfig({ id: 'V', name: 'V', subjects: {} });
|
|
534
|
+
expect(r.ok).toBe(true);
|
|
535
|
+
if (r.ok)
|
|
536
|
+
expect(r.config.grouping).toBeUndefined();
|
|
537
|
+
});
|
|
538
|
+
it('leaves grouping undefined for an unrecognised columns value', () => {
|
|
539
|
+
const r = parseImpactViewConfig({ id: 'V', name: 'V', subjects: {}, grouping: { columns: 'unknown-grain' } });
|
|
540
|
+
expect(r.ok).toBe(true);
|
|
541
|
+
if (r.ok)
|
|
542
|
+
expect(r.config.grouping).toBeUndefined();
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
// ── CV-3a — product-stage-task grain ─────────────────────────────────────────
|
|
546
|
+
describe('CV-3a — product-stage-task grain (buildImpactMatrix, buildTaskColumns)', () => {
|
|
547
|
+
it('expands columns to (subject, stage, task) triples; label is subjectId:stageId:taskId', () => {
|
|
548
|
+
const canon = emptyCanon();
|
|
549
|
+
canon.requirements.push({ id: 'REQ-1', name: 'R1' });
|
|
550
|
+
canon.assertions.push({ id: 'ASS-1', about: 'REQ-1', subject: 'PROD-1', status: 'compliant' });
|
|
551
|
+
const matrix = buildImpactMatrix(canon, {
|
|
552
|
+
id: 'V', name: 'V',
|
|
553
|
+
subjects: { products: ['PROD-1'] },
|
|
554
|
+
obligations: {},
|
|
555
|
+
grouping: { columns: 'product-stage-task' },
|
|
556
|
+
}, [{
|
|
557
|
+
objectId: 'PROD-1',
|
|
558
|
+
details: [
|
|
559
|
+
{ id: 'STAGE-A', name: 'Stage A', tasks: [{ id: 'TASK-1', name: 'Task 1' }, { id: 'TASK-2', name: 'Task 2' }] },
|
|
560
|
+
],
|
|
561
|
+
}]);
|
|
562
|
+
expect(matrix.columns.map(c => c.label)).toEqual(['PROD-1:STAGE-A:TASK-1', 'PROD-1:STAGE-A:TASK-2']);
|
|
563
|
+
expect(matrix.columns[0]).toMatchObject({ subjectId: 'PROD-1', stageId: 'STAGE-A', taskId: 'TASK-1' });
|
|
564
|
+
expect(matrix.columns[1]).toMatchObject({ subjectId: 'PROD-1', stageId: 'STAGE-A', taskId: 'TASK-2' });
|
|
565
|
+
});
|
|
566
|
+
it('assertion without realised_via fills every task column for its subject', () => {
|
|
567
|
+
const canon = emptyCanon();
|
|
568
|
+
canon.requirements.push({ id: 'REQ-1', name: 'R1' });
|
|
569
|
+
canon.assertions.push({ id: 'ASS-1', about: 'REQ-1', subject: 'PROD-1', status: 'compliant' });
|
|
570
|
+
const matrix = buildImpactMatrix(canon, {
|
|
571
|
+
id: 'V', name: 'V',
|
|
572
|
+
subjects: { products: ['PROD-1'] },
|
|
573
|
+
obligations: {},
|
|
574
|
+
grouping: { columns: 'product-stage-task' },
|
|
575
|
+
}, [{ objectId: 'PROD-1', details: [{ id: 'S1', name: 's1', tasks: [{ id: 'T1', name: 't1' }, { id: 'T2', name: 't2' }] }] }]);
|
|
576
|
+
expect(matrix.cells[0][0].kind).toBe('bound');
|
|
577
|
+
expect(matrix.cells[0][1].kind).toBe('bound');
|
|
578
|
+
});
|
|
579
|
+
it('assertion with realised_via: [taskId] fills only the matching task column', () => {
|
|
580
|
+
const canon = emptyCanon();
|
|
581
|
+
canon.requirements.push({ id: 'REQ-1', name: 'R1' });
|
|
582
|
+
canon.assertions.push({ id: 'ASS-1', about: 'REQ-1', subject: 'PROD-1', status: 'compliant', realised_via: ['T1'] });
|
|
583
|
+
const matrix = buildImpactMatrix(canon, {
|
|
584
|
+
id: 'V', name: 'V',
|
|
585
|
+
subjects: { products: ['PROD-1'] },
|
|
586
|
+
obligations: {},
|
|
587
|
+
grouping: { columns: 'product-stage-task' },
|
|
588
|
+
}, [{ objectId: 'PROD-1', details: [{ id: 'S1', name: 's1', tasks: [{ id: 'T1', name: 't1' }, { id: 'T2', name: 't2' }] }] }]);
|
|
589
|
+
expect(matrix.cells[0][0].kind).toBe('bound'); // T1: covered
|
|
590
|
+
expect(matrix.cells[0][1].kind).toBe('gap'); // T2: not covered
|
|
591
|
+
});
|
|
592
|
+
it('assertion with realised_via: [stageId] fills all task columns in that stage', () => {
|
|
593
|
+
const canon = emptyCanon();
|
|
594
|
+
canon.requirements.push({ id: 'REQ-1', name: 'R1' });
|
|
595
|
+
canon.assertions.push({ id: 'ASS-1', about: 'REQ-1', subject: 'PROD-1', status: 'compliant', realised_via: ['S1'] });
|
|
596
|
+
const matrix = buildImpactMatrix(canon, {
|
|
597
|
+
id: 'V', name: 'V',
|
|
598
|
+
subjects: { products: ['PROD-1'] },
|
|
599
|
+
obligations: {},
|
|
600
|
+
grouping: { columns: 'product-stage-task' },
|
|
601
|
+
}, [{ objectId: 'PROD-1', details: [{ id: 'S1', name: 's1', tasks: [{ id: 'T1', name: 't1' }, { id: 'T2', name: 't2' }] }] }]);
|
|
602
|
+
expect(matrix.cells[0][0].kind).toBe('bound');
|
|
603
|
+
expect(matrix.cells[0][1].kind).toBe('bound');
|
|
604
|
+
});
|
|
605
|
+
it('stage with no tasks falls back to stage-grain column', () => {
|
|
606
|
+
const canon = emptyCanon();
|
|
607
|
+
canon.requirements.push({ id: 'REQ-1', name: 'R1' });
|
|
608
|
+
const matrix = buildImpactMatrix(canon, {
|
|
609
|
+
id: 'V', name: 'V',
|
|
610
|
+
subjects: { products: ['PROD-1'] },
|
|
611
|
+
obligations: {},
|
|
612
|
+
grouping: { columns: 'product-stage-task' },
|
|
613
|
+
}, [{ objectId: 'PROD-1', details: [{ id: 'S1', name: 's1' }] }]);
|
|
614
|
+
expect(matrix.columns.map(c => c.label)).toEqual(['PROD-1:S1']);
|
|
615
|
+
expect(matrix.columns[0].taskId).toBeUndefined();
|
|
616
|
+
expect(matrix.columns[0].stageId).toBe('S1');
|
|
617
|
+
});
|
|
618
|
+
it('subject with no objectDetails falls back to product-grain column', () => {
|
|
619
|
+
const canon = emptyCanon();
|
|
620
|
+
canon.requirements.push({ id: 'REQ-1', name: 'R1' });
|
|
621
|
+
const matrix = buildImpactMatrix(canon, {
|
|
622
|
+
id: 'V', name: 'V',
|
|
623
|
+
subjects: { products: ['PROD-1'] },
|
|
624
|
+
obligations: {},
|
|
625
|
+
grouping: { columns: 'product-stage-task' },
|
|
626
|
+
});
|
|
627
|
+
expect(matrix.columns.map(c => c.label)).toEqual(['PROD-1']);
|
|
628
|
+
expect(matrix.columns[0].stageId).toBeUndefined();
|
|
629
|
+
});
|
|
630
|
+
});
|
|
631
|
+
// ── extractProcessFlowTasks ───────────────────────────────────────────────────
|
|
632
|
+
describe('extractProcessFlowTasks', () => {
|
|
633
|
+
it('returns null for non-process documents', () => {
|
|
634
|
+
expect(extractProcessFlowTasks(null)).toBeNull();
|
|
635
|
+
expect(extractProcessFlowTasks({ notation: 'requirement', id: 'R1' })).toBeNull();
|
|
636
|
+
expect(extractProcessFlowTasks({ notation: 'process_blueprint', id: 'BP-1' })).toBeNull();
|
|
637
|
+
});
|
|
638
|
+
it('returns null when process has no flow.steps', () => {
|
|
639
|
+
expect(extractProcessFlowTasks({ notation: 'process', id: 'PROC-1' })).toBeNull();
|
|
640
|
+
expect(extractProcessFlowTasks({ notation: 'process', id: 'PROC-1', flow: { sequence: [] } })).toBeNull();
|
|
641
|
+
});
|
|
642
|
+
it('returns null when no task-type steps exist', () => {
|
|
643
|
+
const doc = {
|
|
644
|
+
notation: 'process', id: 'PROC-1',
|
|
645
|
+
flow: { steps: [
|
|
646
|
+
{ id: 'S-1', type: 'startEvent' },
|
|
647
|
+
{ id: 'G-1', type: 'exclusiveGateway' },
|
|
648
|
+
{ id: 'E-1', type: 'endEvent' },
|
|
649
|
+
] },
|
|
650
|
+
};
|
|
651
|
+
expect(extractProcessFlowTasks(doc)).toBeNull();
|
|
652
|
+
});
|
|
653
|
+
it('extracts task-type steps and skips events/gateways', () => {
|
|
654
|
+
const doc = {
|
|
655
|
+
notation: 'process', id: 'PROC-1',
|
|
656
|
+
flow: { steps: [
|
|
657
|
+
{ id: 'S-1', type: 'startEvent' },
|
|
658
|
+
{ id: 'STEP-T1', type: 'task', name: 'Task One' },
|
|
659
|
+
{ id: 'G-1', type: 'exclusiveGateway', name: 'Branch?' },
|
|
660
|
+
{ id: 'STEP-T2', type: 'userTask', name: 'User Task' },
|
|
661
|
+
{ id: 'E-1', type: 'endEvent' },
|
|
662
|
+
] },
|
|
663
|
+
};
|
|
664
|
+
const result = extractProcessFlowTasks(doc);
|
|
665
|
+
expect(result).not.toBeNull();
|
|
666
|
+
expect(result.objectId).toBe('PROC-1');
|
|
667
|
+
expect(result.details.map(d => d.id)).toEqual(['STEP-T1', 'STEP-T2']);
|
|
668
|
+
expect(result.details[0].name).toBe('Task One');
|
|
669
|
+
});
|
|
670
|
+
it('falls back to step id as name when name is absent', () => {
|
|
671
|
+
const doc = {
|
|
672
|
+
notation: 'process', id: 'PROC-1',
|
|
673
|
+
flow: { steps: [{ id: 'STEP-T1', type: 'serviceTask' }] },
|
|
674
|
+
};
|
|
675
|
+
const result = extractProcessFlowTasks(doc);
|
|
676
|
+
expect(result.details[0].name).toBe('STEP-T1');
|
|
677
|
+
});
|
|
678
|
+
});
|
|
679
|
+
// ── mergeStageTaskDetails ─────────────────────────────────────────────────────
|
|
680
|
+
describe('mergeStageTaskDetails', () => {
|
|
681
|
+
const stages = {
|
|
682
|
+
objectId: 'PROC-1',
|
|
683
|
+
details: [
|
|
684
|
+
{ id: 'S1', name: 'Stage 1' },
|
|
685
|
+
{ id: 'S2', name: 'Stage 2' },
|
|
686
|
+
],
|
|
687
|
+
};
|
|
688
|
+
it('returns stageInput unchanged when taskInput is null', () => {
|
|
689
|
+
expect(mergeStageTaskDetails(stages, null)).toBe(stages);
|
|
690
|
+
});
|
|
691
|
+
it('returns stageInput unchanged when objectIds do not match', () => {
|
|
692
|
+
const other = { objectId: 'PROC-99', details: [{ id: 'T1', name: 't1' }] };
|
|
693
|
+
expect(mergeStageTaskDetails(stages, other)).toBe(stages);
|
|
694
|
+
});
|
|
695
|
+
it('embeds all tasks into every stage when no stageOwner map is provided', () => {
|
|
696
|
+
const tasks = { objectId: 'PROC-1', details: [{ id: 'T1', name: 't1' }, { id: 'T2', name: 't2' }] };
|
|
697
|
+
const merged = mergeStageTaskDetails(stages, tasks);
|
|
698
|
+
expect(merged.objectId).toBe('PROC-1');
|
|
699
|
+
expect(merged.details[0].tasks?.map(t => t.id)).toEqual(['T1', 'T2']);
|
|
700
|
+
expect(merged.details[1].tasks?.map(t => t.id)).toEqual(['T1', 'T2']);
|
|
701
|
+
});
|
|
702
|
+
it('distributes tasks by stageOwner map', () => {
|
|
703
|
+
const tasks = { objectId: 'PROC-1', details: [{ id: 'T1', name: 't1' }, { id: 'T2', name: 't2' }] };
|
|
704
|
+
const merged = mergeStageTaskDetails(stages, tasks, { S1: ['T1'], S2: ['T2'] });
|
|
705
|
+
expect(merged.details[0].tasks?.map(t => t.id)).toEqual(['T1']);
|
|
706
|
+
expect(merged.details[1].tasks?.map(t => t.id)).toEqual(['T2']);
|
|
707
|
+
});
|
|
708
|
+
it('leaves stages without matching stageOwner entries task-free', () => {
|
|
709
|
+
const tasks = { objectId: 'PROC-1', details: [{ id: 'T1', name: 't1' }] };
|
|
710
|
+
const merged = mergeStageTaskDetails(stages, tasks, { S1: ['T1'], S2: [] });
|
|
711
|
+
expect(merged.details[0].tasks?.map(t => t.id)).toEqual(['T1']);
|
|
712
|
+
// S2 has no tasks in stageOwner, filter returns [] → no tasks embedded
|
|
713
|
+
expect(merged.details[1].tasks).toBeUndefined();
|
|
714
|
+
});
|
|
715
|
+
});
|
|
@@ -15,6 +15,12 @@ export interface ComplianceCanon {
|
|
|
15
15
|
requirements: IndexRequirement[];
|
|
16
16
|
assertions: IndexAssertion[];
|
|
17
17
|
codex: ComplianceCodexDoc[];
|
|
18
|
+
/**
|
|
19
|
+
* Named elements that can serve as assertion subjects beyond `products`:
|
|
20
|
+
* capabilities, processes, applications, and systems. Populated by
|
|
21
|
+
* `ingestComplianceDoc` for the corresponding notation values.
|
|
22
|
+
*/
|
|
23
|
+
subjects: ComplianceProduct[];
|
|
18
24
|
}
|
|
19
25
|
export declare function emptyCanon(): ComplianceCanon;
|
|
20
26
|
/**
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// (webview previews) and the CLI scan (`export-compliance`), so the recognition
|
|
4
4
|
// rules live once. Pure: takes a parsed YAML document, no IO.
|
|
5
5
|
export function emptyCanon() {
|
|
6
|
-
return { products: [], requirements: [], assertions: [], codex: [] };
|
|
6
|
+
return { products: [], requirements: [], assertions: [], codex: [], subjects: [] };
|
|
7
7
|
}
|
|
8
8
|
const str = (v) => (typeof v === 'string' ? v : undefined);
|
|
9
9
|
const strArray = (v) => Array.isArray(v) ? v.filter((x) => typeof x === 'string') : undefined;
|
|
@@ -25,6 +25,13 @@ export function ingestComplianceDoc(canon, doc) {
|
|
|
25
25
|
canon.products.push({ id, name: str(d.name) ?? id });
|
|
26
26
|
return id;
|
|
27
27
|
}
|
|
28
|
+
if (d.notation === 'capability' ||
|
|
29
|
+
d.notation === 'process' ||
|
|
30
|
+
d.notation === 'application' ||
|
|
31
|
+
d.notation === 'system') {
|
|
32
|
+
canon.subjects.push({ id, name: str(d.name) ?? id });
|
|
33
|
+
return id;
|
|
34
|
+
}
|
|
28
35
|
if (d.notation === 'requirement') {
|
|
29
36
|
canon.requirements.push({
|
|
30
37
|
id,
|
|
@@ -49,6 +56,7 @@ export function ingestComplianceDoc(canon, doc) {
|
|
|
49
56
|
evidenceCount: Array.isArray(d.evidence) ? d.evidence.length : 0,
|
|
50
57
|
admitted_at: str(d.admitted_at),
|
|
51
58
|
realised_via: strArray(d.realised_via),
|
|
59
|
+
owner_to_confirm: str(d.owner_to_confirm),
|
|
52
60
|
});
|
|
53
61
|
return id;
|
|
54
62
|
}
|
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import type { ComplianceCanon } from './classify.js';
|
|
2
|
+
/** Declarative filter resolved at build time from the canon's codex catalogue. */
|
|
3
|
+
export interface CoverageMetricRegimesFilter {
|
|
4
|
+
jurisdiction?: string[];
|
|
5
|
+
codex_type?: string[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Regime (codex) selection for the coverage view.
|
|
9
|
+
* - `include`: explicit list of codex IDs — takes precedence over `filter`.
|
|
10
|
+
* - `filter`: resolved against the workspace canon at render time.
|
|
11
|
+
* - Omit entirely → all codex artefacts in the canon become regime columns.
|
|
12
|
+
*/
|
|
13
|
+
export interface CoverageMetricRegimes {
|
|
14
|
+
include?: string[];
|
|
15
|
+
filter?: CoverageMetricRegimesFilter;
|
|
16
|
+
}
|
|
2
17
|
export interface CoverageMetricSubjects {
|
|
3
18
|
products?: string[];
|
|
4
19
|
}
|
|
5
|
-
export interface CoverageMetricScope {
|
|
6
|
-
jurisdictions?: string[];
|
|
7
|
-
codex?: string[];
|
|
8
|
-
subjects?: CoverageMetricSubjects;
|
|
9
|
-
}
|
|
10
20
|
export interface CoverageMetricThresholds {
|
|
11
21
|
/** Coverage fraction (0–1) at or above which the status is green. */
|
|
12
22
|
green: number;
|
|
@@ -19,8 +29,13 @@ export interface CoverageMetricConfig {
|
|
|
19
29
|
description?: string;
|
|
20
30
|
date?: string;
|
|
21
31
|
version?: string;
|
|
22
|
-
|
|
32
|
+
/** Regime (codex) selection. Absent = all canon codex entries. */
|
|
33
|
+
regimes?: CoverageMetricRegimes;
|
|
34
|
+
/** Products to scope assertions to. Absent = all products. */
|
|
35
|
+
subjects?: CoverageMetricSubjects;
|
|
23
36
|
thresholds: CoverageMetricThresholds;
|
|
37
|
+
/** Non-fatal parse warnings (e.g. deprecated keys). */
|
|
38
|
+
warnings?: string[];
|
|
24
39
|
}
|
|
25
40
|
export type ParseCoverageMetricResult = {
|
|
26
41
|
ok: true;
|
|
@@ -39,6 +54,8 @@ export interface CoverageRow {
|
|
|
39
54
|
partial: number;
|
|
40
55
|
non_compliant: number;
|
|
41
56
|
under_review: number;
|
|
57
|
+
/** Requirements with at least one pending-owner assertion and no stronger binding. */
|
|
58
|
+
pending_owner: number;
|
|
42
59
|
/** Requirements with no admitted assertion for the scoped products. */
|
|
43
60
|
gap: number;
|
|
44
61
|
/** compliant + partial. */
|
|
@@ -57,17 +74,20 @@ export interface CoverageMatrix {
|
|
|
57
74
|
thresholds: CoverageMetricThresholds;
|
|
58
75
|
}
|
|
59
76
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
77
|
+
* Parse and validate a raw (YAML-parsed) document into a `CoverageMetricConfig`.
|
|
78
|
+
*
|
|
79
|
+
* Accepts:
|
|
80
|
+
* - `{ view: { id, name, regimes, subjects, thresholds } }` — canonical (new spec)
|
|
81
|
+
* - `{ coverage_metric: { … } }` — deprecated; `scope.codex` → `regimes.include`
|
|
82
|
+
* - Bare top-level object — same as deprecated wrapper, no wrapper warning
|
|
63
83
|
*/
|
|
64
84
|
export declare function parseCoverageMetricConfig(raw: unknown): ParseCoverageMetricResult;
|
|
65
85
|
/**
|
|
66
86
|
* Compute per-codex coverage statistics.
|
|
67
87
|
*
|
|
68
|
-
* For each law (codex ID)
|
|
88
|
+
* For each law (codex ID) resolved from `config.regimes`:
|
|
69
89
|
* - Collect requirements whose `derived_from` includes that law.
|
|
70
|
-
* - Optionally restrict to assertions for `config.
|
|
90
|
+
* - Optionally restrict to assertions for `config.subjects.products`.
|
|
71
91
|
* - Compute compliant / partial / non_compliant / under_review / gap counts.
|
|
72
92
|
* - Compute coverage % = (compliant + partial) / total and apply RAG.
|
|
73
93
|
*/
|