@transitrix/diagrams 1.10.0 → 1.12.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/dist/activities/resolver.d.ts +3 -0
- package/dist/activities/resolver.js +39 -18
- package/dist/activities/validate.js +67 -4
- package/dist/canon-resolver-utils.d.ts +3 -0
- package/dist/canon-resolver-utils.js +16 -0
- package/dist/compliance/classify.js +1 -0
- package/dist/compliance/impact.d.ts +3 -1
- package/dist/compliance/impact.js +41 -13
- package/dist/compliance/types.d.ts +6 -0
- package/dist/compliance-verification-matrix/build.d.ts +17 -0
- package/dist/compliance-verification-matrix/build.js +65 -0
- package/dist/compliance-verification-matrix/csv.d.ts +6 -0
- package/dist/compliance-verification-matrix/csv.js +41 -0
- package/dist/compliance-verification-matrix/index.d.ts +3 -0
- package/dist/compliance-verification-matrix/index.js +2 -0
- package/dist/compliance-verification-matrix/types.d.ts +75 -0
- package/dist/compliance-verification-matrix/types.js +4 -0
- package/dist/fgca/action-change-ids.d.ts +7 -0
- package/dist/fgca/action-change-ids.js +11 -0
- package/dist/fgca/parse-canonical.d.ts +17 -3
- package/dist/fgca/parse-canonical.js +44 -16
- package/dist/fgca/resolver.d.ts +6 -2
- package/dist/fgca/resolver.js +28 -6
- package/dist/fgca/validate.d.ts +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/process-blueprint/index.d.ts +3 -1
- package/dist/process-blueprint/index.js +1 -0
- package/dist/process-blueprint/layout.js +33 -29
- package/dist/process-blueprint/resolve-columns.d.ts +52 -0
- package/dist/process-blueprint/resolve-columns.js +138 -0
- package/dist/process-blueprint/types.d.ts +23 -5
- package/dist/process-blueprint/validate.d.ts +19 -1
- package/dist/process-blueprint/validate.js +73 -21
- package/dist/repo-validate/check-process-parent.d.ts +8 -0
- package/dist/repo-validate/check-process-parent.js +172 -0
- package/dist/repo-validate/index.d.ts +1 -0
- package/dist/repo-validate/index.js +1 -0
- package/dist/repo-validate/validate-repo.js +3 -0
- package/dist/webview/entry.js +26 -4
- package/dist/webview/render-fgca.d.ts +2 -0
- package/dist/webview/render-fgca.js +9 -3
- package/dist/webview/render-process.d.ts +19 -1
- package/dist/webview/render-process.js +75 -42
- package/package.json +3 -3
- package/src/activities/resolver.ts +40 -20
- package/src/activities/validate.ts +66 -4
- package/src/canon-resolver-utils.ts +19 -0
- package/src/compliance/classify.ts +1 -0
- package/src/compliance/impact.ts +41 -12
- package/src/compliance/types.ts +6 -0
- package/src/compliance-verification-matrix/build.ts +84 -0
- package/src/compliance-verification-matrix/csv.ts +47 -0
- package/src/compliance-verification-matrix/index.ts +7 -0
- package/src/compliance-verification-matrix/types.ts +85 -0
- package/src/fgca/action-change-ids.ts +13 -0
- package/src/fgca/parse-canonical.ts +56 -15
- package/src/fgca/resolver.ts +39 -6
- package/src/fgca/validate.ts +2 -0
- package/src/index.ts +1 -0
- package/src/process-blueprint/index.ts +11 -0
- package/src/process-blueprint/layout.ts +48 -29
- package/src/process-blueprint/resolve-columns.ts +147 -0
- package/src/process-blueprint/types.ts +19 -5
- package/src/process-blueprint/validate.ts +107 -21
- package/src/repo-validate/check-process-parent.ts +184 -0
- package/src/repo-validate/index.ts +5 -0
- package/src/repo-validate/validate-repo.ts +3 -0
- package/src/webview/entry.ts +26 -4
- package/src/webview/render-fgca.ts +13 -2
- package/src/webview/render-process.ts +94 -49
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Typed string IDs per `IDS_AND_REFERENCES.md` (`FACTOR-1`,
|
|
9
9
|
* `GOAL-RET-1`, `CHANGE-1`, `ACTION-ONBOARD-1` (legacy `ACTIVITY-*` accepted).
|
|
10
10
|
* - Plural canonical-direction cross-references: `goal.factors`,
|
|
11
|
-
* `change.goals`, `
|
|
11
|
+
* `change.goals`, `action.delivers_changes`, `action.goals`.
|
|
12
12
|
* - Per-notation validation codes `FGCA-001 … FGCA-015`.
|
|
13
13
|
*
|
|
14
14
|
* Returns the validation result plus, on success, an internal `FGCADoc`
|
|
@@ -28,15 +28,29 @@ import type { ValidationResult } from '../validation-types.js';
|
|
|
28
28
|
export interface CanonicalFGCAResult extends ValidationResult {
|
|
29
29
|
/** On success, the internal-form FGCADoc derived from the canonical input. */
|
|
30
30
|
parsed?: FGCADoc;
|
|
31
|
+
/** True when a goal-scoped projection should show the scope caption. */
|
|
32
|
+
scopeCaption?: boolean;
|
|
31
33
|
}
|
|
34
|
+
/** DGA mode (`view_config.layers.changes: off`) omits the Changes column. */
|
|
35
|
+
export declare function isDgcaChangesLayerOff(input: unknown): boolean;
|
|
32
36
|
/**
|
|
33
37
|
* Validate canonical FGCA YAML and, on success, return an internal-form
|
|
34
38
|
* `FGCADoc` ready for `buildFGCALayout`.
|
|
39
|
+
*
|
|
40
|
+
* @param input The FGCA document to parse
|
|
41
|
+
* @param outOfScopeGoalIds When a goal-scoped projection omits goals that exist
|
|
42
|
+
* in the catalogue, pass their IDs here to suppress FGCA-011/FGCA-009 errors
|
|
43
|
+
* for references to out-of-scope goals. When provided and non-empty, and at least
|
|
44
|
+
* one action or change references an out-of-scope goal, `scopeCaption` is set true.
|
|
45
|
+
* @param outOfScopeFactorIds When a goal-scoped projection omits factors that exist
|
|
46
|
+
* in the catalogue, pass their IDs here to suppress FGCA-008 errors.
|
|
35
47
|
*/
|
|
36
|
-
export declare function parseCanonicalFGCA(input: unknown): CanonicalFGCAResult;
|
|
48
|
+
export declare function parseCanonicalFGCA(input: unknown, outOfScopeGoalIds?: Set<string> | null, outOfScopeFactorIds?: Set<string> | null): CanonicalFGCAResult;
|
|
37
49
|
/** On success, the internal-form FGCADoc derived from canonical FGA input. */
|
|
38
50
|
export interface CanonicalFGAResult extends ValidationResult {
|
|
39
51
|
parsed?: FGCADoc;
|
|
52
|
+
/** True when a goal-scoped projection should show the scope caption. */
|
|
53
|
+
scopeCaption?: boolean;
|
|
40
54
|
}
|
|
41
55
|
/**
|
|
42
56
|
* Canonical-form FGA parser + validator.
|
|
@@ -55,4 +69,4 @@ export interface CanonicalFGAResult extends ValidationResult {
|
|
|
55
69
|
* the canonical FGA path is unit-testable and the library owns the single
|
|
56
70
|
* canon shape — flat form only, no `fga:` wrapper.
|
|
57
71
|
*/
|
|
58
|
-
export declare function parseCanonicalFGA(input: unknown): CanonicalFGAResult;
|
|
72
|
+
export declare function parseCanonicalFGA(input: unknown, outOfScopeGoalIds?: Set<string> | null, outOfScopeFactorIds?: Set<string> | null): CanonicalFGAResult;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Typed string IDs per `IDS_AND_REFERENCES.md` (`FACTOR-1`,
|
|
9
9
|
* `GOAL-RET-1`, `CHANGE-1`, `ACTION-ONBOARD-1` (legacy `ACTIVITY-*` accepted).
|
|
10
10
|
* - Plural canonical-direction cross-references: `goal.factors`,
|
|
11
|
-
* `change.goals`, `
|
|
11
|
+
* `change.goals`, `action.delivers_changes`, `action.goals`.
|
|
12
12
|
* - Per-notation validation codes `FGCA-001 … FGCA-015`.
|
|
13
13
|
*
|
|
14
14
|
* Returns the validation result plus, on success, an internal `FGCADoc`
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* unify the internal types with the canonical form across all sister
|
|
24
24
|
* modules.
|
|
25
25
|
*/
|
|
26
|
+
import { actionChangeLinkField } from './action-change-ids.js';
|
|
26
27
|
// Canonical ID grammars per IDS_AND_REFERENCES.md §1. The TYPE prefix
|
|
27
28
|
// is fixed per layer; middle segments are optional; the terminal is a
|
|
28
29
|
// positive integer with no leading zeros.
|
|
@@ -43,9 +44,11 @@ function asObjectArray(v) {
|
|
|
43
44
|
return null;
|
|
44
45
|
return v.map((el) => (el && typeof el === 'object' ? el : null));
|
|
45
46
|
}
|
|
46
|
-
/** DGA mode (`view_config.layers.changes: off`)
|
|
47
|
-
function
|
|
48
|
-
|
|
47
|
+
/** DGA mode (`view_config.layers.changes: off`) omits the Changes column. */
|
|
48
|
+
export function isDgcaChangesLayerOff(input) {
|
|
49
|
+
if (!input || typeof input !== 'object')
|
|
50
|
+
return false;
|
|
51
|
+
const vc = input['view_config'];
|
|
49
52
|
if (!vc || typeof vc !== 'object')
|
|
50
53
|
return false;
|
|
51
54
|
const layers = vc['layers'];
|
|
@@ -56,8 +59,16 @@ function isChangesLayerOff(raw) {
|
|
|
56
59
|
/**
|
|
57
60
|
* Validate canonical FGCA YAML and, on success, return an internal-form
|
|
58
61
|
* `FGCADoc` ready for `buildFGCALayout`.
|
|
62
|
+
*
|
|
63
|
+
* @param input The FGCA document to parse
|
|
64
|
+
* @param outOfScopeGoalIds When a goal-scoped projection omits goals that exist
|
|
65
|
+
* in the catalogue, pass their IDs here to suppress FGCA-011/FGCA-009 errors
|
|
66
|
+
* for references to out-of-scope goals. When provided and non-empty, and at least
|
|
67
|
+
* one action or change references an out-of-scope goal, `scopeCaption` is set true.
|
|
68
|
+
* @param outOfScopeFactorIds When a goal-scoped projection omits factors that exist
|
|
69
|
+
* in the catalogue, pass their IDs here to suppress FGCA-008 errors.
|
|
59
70
|
*/
|
|
60
|
-
export function parseCanonicalFGCA(input) {
|
|
71
|
+
export function parseCanonicalFGCA(input, outOfScopeGoalIds, outOfScopeFactorIds) {
|
|
61
72
|
const errors = [];
|
|
62
73
|
const warnings = [];
|
|
63
74
|
if (!input || typeof input !== 'object') {
|
|
@@ -99,7 +110,7 @@ export function parseCanonicalFGCA(input) {
|
|
|
99
110
|
const factorsRaw = asObjectArray(raw['factors']);
|
|
100
111
|
const goalsRaw = asObjectArray(raw['goals']);
|
|
101
112
|
let changesRaw = asObjectArray(raw['changes']);
|
|
102
|
-
if (changesRaw === null && raw['changes'] === undefined &&
|
|
113
|
+
if (changesRaw === null && raw['changes'] === undefined && isDgcaChangesLayerOff(raw)) {
|
|
103
114
|
changesRaw = [];
|
|
104
115
|
}
|
|
105
116
|
const activitiesRaw = asObjectArray(raw['actions']);
|
|
@@ -170,8 +181,10 @@ export function parseCanonicalFGCA(input) {
|
|
|
170
181
|
activities.forEach((el, i) => checkLayerElement(el, `activities[${i}]`, ACTION_OR_ACTIVITY_ID_RE, activityIds));
|
|
171
182
|
if (errors.length > 0)
|
|
172
183
|
return { valid: false, errors, warnings };
|
|
184
|
+
// Track if any out-of-scope goals/factors are referenced (for scope caption).
|
|
185
|
+
let hasOutOfScopeRef = false;
|
|
173
186
|
// Cross-refs.
|
|
174
|
-
function checkRefArray(el, field, targets, refRe, code, path) {
|
|
187
|
+
function checkRefArray(el, field, targets, refRe, code, path, outOfScope) {
|
|
175
188
|
const ref = el[field];
|
|
176
189
|
if (ref === undefined || ref === null)
|
|
177
190
|
return [];
|
|
@@ -189,6 +202,12 @@ export function parseCanonicalFGCA(input) {
|
|
|
189
202
|
errors.push({ code: 'FGCA-007', message: `${path}.${field}[] entry "${r}" does not match the expected grammar`, path });
|
|
190
203
|
continue;
|
|
191
204
|
}
|
|
205
|
+
if (outOfScope?.has(r)) {
|
|
206
|
+
// Reference is out-of-scope: suppress error and mark that we found an out-of-scope reference (for caption).
|
|
207
|
+
hasOutOfScopeRef = true;
|
|
208
|
+
out.push(r);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
192
211
|
if (!targets.has(r)) {
|
|
193
212
|
errors.push({ code, message: `${path}.${field}[] references undeclared element "${r}"`, path });
|
|
194
213
|
continue;
|
|
@@ -205,18 +224,19 @@ export function parseCanonicalFGCA(input) {
|
|
|
205
224
|
name: String(el['name'] ?? ''),
|
|
206
225
|
}));
|
|
207
226
|
const internalGoals = goals.map((el, i) => {
|
|
208
|
-
const refIds = checkRefArray(el, 'factors', factorIds, FACTOR_OR_DRIVER_ID_RE, 'FGCA-008', `goals[${i}]
|
|
227
|
+
const refIds = checkRefArray(el, 'factors', factorIds, FACTOR_OR_DRIVER_ID_RE, 'FGCA-008', `goals[${i}]`, outOfScopeFactorIds);
|
|
209
228
|
return {
|
|
210
229
|
id: String(el['id']),
|
|
211
230
|
name: String(el['name'] ?? ''),
|
|
212
231
|
factor: refIds.map((r) => ({ id: r })),
|
|
213
232
|
};
|
|
214
233
|
});
|
|
215
|
-
// Build a reverse mapping: which changes does each
|
|
216
|
-
// canonical
|
|
234
|
+
// Build a reverse mapping: which changes does each action deliver (via
|
|
235
|
+
// canonical `delivers_changes`, or the pre-rename `changes` alias)?
|
|
217
236
|
const activityChangesByCanonicalActivityId = new Map();
|
|
218
237
|
activities.forEach((el, i) => {
|
|
219
|
-
const
|
|
238
|
+
const field = actionChangeLinkField(el);
|
|
239
|
+
const refIds = checkRefArray(el, field, changeIds, CHANGE_ID_RE, 'FGCA-010', `activities[${i}]`);
|
|
220
240
|
activityChangesByCanonicalActivityId.set(String(el['id']), refIds);
|
|
221
241
|
});
|
|
222
242
|
// For each change, find the activities that reference it.
|
|
@@ -229,7 +249,7 @@ export function parseCanonicalFGCA(input) {
|
|
|
229
249
|
}
|
|
230
250
|
}
|
|
231
251
|
const internalChanges = changes.map((el, i) => {
|
|
232
|
-
const goalRefs = checkRefArray(el, 'goals', goalIds, GOAL_ID_RE, 'FGCA-009', `changes[${i}]
|
|
252
|
+
const goalRefs = checkRefArray(el, 'goals', goalIds, GOAL_ID_RE, 'FGCA-009', `changes[${i}]`, outOfScopeGoalIds);
|
|
233
253
|
// Internal `change.goal_id` is singular; canonical `change.goals` is plural.
|
|
234
254
|
// First goal wins for the singular field; warn (not error) if multiple.
|
|
235
255
|
if (goalRefs.length > 1) {
|
|
@@ -249,7 +269,7 @@ export function parseCanonicalFGCA(input) {
|
|
|
249
269
|
};
|
|
250
270
|
});
|
|
251
271
|
const internalActivities = activities.map((el, i) => {
|
|
252
|
-
const goalRefs = checkRefArray(el, 'goals', goalIds, GOAL_ID_RE, 'FGCA-011', `actions[${i}]
|
|
272
|
+
const goalRefs = checkRefArray(el, 'goals', goalIds, GOAL_ID_RE, 'FGCA-011', `actions[${i}]`, outOfScopeGoalIds);
|
|
253
273
|
const typeVal = typeof el['type'] === 'string' ? el['type'] : undefined;
|
|
254
274
|
return {
|
|
255
275
|
id: String(el['id']),
|
|
@@ -286,8 +306,15 @@ export function parseCanonicalFGCA(input) {
|
|
|
286
306
|
goals: internalGoals,
|
|
287
307
|
changes: internalChanges,
|
|
288
308
|
activities: internalActivities,
|
|
309
|
+
hideChanges: isDgcaChangesLayerOff(raw),
|
|
310
|
+
};
|
|
311
|
+
return {
|
|
312
|
+
valid: true,
|
|
313
|
+
errors,
|
|
314
|
+
warnings,
|
|
315
|
+
parsed,
|
|
316
|
+
scopeCaption: (outOfScopeGoalIds?.size ?? 0) > 0 && hasOutOfScopeRef,
|
|
289
317
|
};
|
|
290
|
-
return { valid: true, errors, warnings, parsed };
|
|
291
318
|
}
|
|
292
319
|
const FGA_DOC_ID_RE = /^(FGA|DGA)(-[A-Z0-9][A-Z0-9_]*)*-\d+$/;
|
|
293
320
|
/**
|
|
@@ -307,7 +334,7 @@ const FGA_DOC_ID_RE = /^(FGA|DGA)(-[A-Z0-9][A-Z0-9_]*)*-\d+$/;
|
|
|
307
334
|
* the canonical FGA path is unit-testable and the library owns the single
|
|
308
335
|
* canon shape — flat form only, no `fga:` wrapper.
|
|
309
336
|
*/
|
|
310
|
-
export function parseCanonicalFGA(input) {
|
|
337
|
+
export function parseCanonicalFGA(input, outOfScopeGoalIds, outOfScopeFactorIds) {
|
|
311
338
|
if (!input || typeof input !== 'object') {
|
|
312
339
|
return { valid: false, errors: [{ code: 'FGA-001', message: 'document root is not an object' }], warnings: [] };
|
|
313
340
|
}
|
|
@@ -334,7 +361,7 @@ export function parseCanonicalFGA(input) {
|
|
|
334
361
|
// codes are remapped on the way out. FGCA-009 / 010 / 014 (changes-related)
|
|
335
362
|
// are unreachable here because `changes` is empty.
|
|
336
363
|
const synth = { ...raw, notation: 'dgca', id: 'DGCA-FROM-DGA-1', changes: [] };
|
|
337
|
-
const r = parseCanonicalFGCA(synth);
|
|
364
|
+
const r = parseCanonicalFGCA(synth, outOfScopeGoalIds, outOfScopeFactorIds);
|
|
338
365
|
const remap = {
|
|
339
366
|
'FGCA-001': 'FGA-001',
|
|
340
367
|
'FGCA-002': 'FGA-002',
|
|
@@ -354,5 +381,6 @@ export function parseCanonicalFGA(input) {
|
|
|
354
381
|
errors: r.errors.map((e) => ({ ...e, code: remapCode(e.code) })),
|
|
355
382
|
warnings: r.warnings.map((w) => ({ ...w, code: remapCode(w.code) })),
|
|
356
383
|
parsed: r.parsed,
|
|
384
|
+
scopeCaption: r.scopeCaption,
|
|
357
385
|
};
|
|
358
386
|
}
|
package/dist/fgca/resolver.d.ts
CHANGED
|
@@ -18,6 +18,10 @@ export interface FGCAViewConfig {
|
|
|
18
18
|
changes?: {
|
|
19
19
|
surface?: 'derived' | 'all';
|
|
20
20
|
};
|
|
21
|
+
/** Canonical projection key. `activities` remains a deprecated alias. */
|
|
22
|
+
actions?: {
|
|
23
|
+
surface?: 'derived' | 'all';
|
|
24
|
+
};
|
|
21
25
|
activities?: {
|
|
22
26
|
surface?: 'derived' | 'all';
|
|
23
27
|
};
|
|
@@ -43,7 +47,7 @@ export declare function isFGCAViewDoc(parsed: unknown): boolean;
|
|
|
43
47
|
* to pass to `parseCanonicalFGCA`.
|
|
44
48
|
*
|
|
45
49
|
* Cross-reference fields (`goal.factors[]`, `change.goals[]`,
|
|
46
|
-
* `action.
|
|
47
|
-
* resolver decides which elements to include, not how they are shaped.
|
|
50
|
+
* `action.delivers_changes[]`) are carried from the canon elements unchanged;
|
|
51
|
+
* the resolver decides which elements to include, not how they are shaped.
|
|
48
52
|
*/
|
|
49
53
|
export declare function resolveFGCA(viewDoc: unknown, sources: FGCACanonSources): Record<string, unknown>;
|
package/dist/fgca/resolver.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* top-level `factors[]`, `goals[]`, `changes[]`, `actions[]` with typed
|
|
7
7
|
* string IDs. Filesystem-free — callable from unit tests without vscode.
|
|
8
8
|
*/
|
|
9
|
+
import { actionChangeLinkField } from './action-change-ids.js';
|
|
9
10
|
function isObject(v) {
|
|
10
11
|
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
11
12
|
}
|
|
@@ -49,8 +50,8 @@ export function isFGCAViewDoc(parsed) {
|
|
|
49
50
|
* to pass to `parseCanonicalFGCA`.
|
|
50
51
|
*
|
|
51
52
|
* Cross-reference fields (`goal.factors[]`, `change.goals[]`,
|
|
52
|
-
* `action.
|
|
53
|
-
* resolver decides which elements to include, not how they are shaped.
|
|
53
|
+
* `action.delivers_changes[]`) are carried from the canon elements unchanged;
|
|
54
|
+
* the resolver decides which elements to include, not how they are shaped.
|
|
54
55
|
*/
|
|
55
56
|
export function resolveFGCA(viewDoc, sources) {
|
|
56
57
|
if (!isObject(viewDoc)) {
|
|
@@ -60,7 +61,11 @@ export function resolveFGCA(viewDoc, sources) {
|
|
|
60
61
|
const goalsConf = isObject(vc['goals']) ? vc['goals'] : {};
|
|
61
62
|
const factorsConf = isObject(vc['factors']) ? vc['factors'] : {};
|
|
62
63
|
const changesConf = isObject(vc['changes']) ? vc['changes'] : {};
|
|
63
|
-
const
|
|
64
|
+
const actionsConf = isObject(vc['actions'])
|
|
65
|
+
? vc['actions']
|
|
66
|
+
: isObject(vc['activities'])
|
|
67
|
+
? vc['activities']
|
|
68
|
+
: {};
|
|
64
69
|
const factorElemsLegacy = collectByNotation(sources.elements, 'factor');
|
|
65
70
|
const factorElemsNew = collectByNotation(sources.elements, 'driver');
|
|
66
71
|
const factorElems = new Map([...factorElemsLegacy, ...factorElemsNew]);
|
|
@@ -108,15 +113,29 @@ export function resolveFGCA(viewDoc, sources) {
|
|
|
108
113
|
selectedChanges = [...changeElems.values()].filter((c) => strArray(c['goals']).some((gid) => selectedGoalIds.has(gid)));
|
|
109
114
|
}
|
|
110
115
|
const selectedChangeIds = new Set(selectedChanges.map((c) => str(c['id'])).filter((x) => x !== undefined));
|
|
111
|
-
// 4. Select
|
|
116
|
+
// 4. Select actions: derived = delivers ≥1 selected change, or (DGA) a selected goal
|
|
112
117
|
let selectedActivities;
|
|
113
|
-
if (str(
|
|
118
|
+
if (str(actionsConf['surface']) === 'all') {
|
|
114
119
|
selectedActivities = [...activityElems.values()];
|
|
115
120
|
}
|
|
116
121
|
else {
|
|
117
|
-
selectedActivities = [...activityElems.values()].filter((a) => strArray(a[
|
|
122
|
+
selectedActivities = [...activityElems.values()].filter((a) => strArray(a[actionChangeLinkField(a)]).some((cid) => selectedChangeIds.has(cid)) ||
|
|
118
123
|
strArray(a['goals']).some((gid) => selectedGoalIds.has(gid)));
|
|
119
124
|
}
|
|
125
|
+
// Collect out-of-scope goals (exist in catalogue but not in selected set).
|
|
126
|
+
// Used by the validator to suppress errors for references to goals that exist
|
|
127
|
+
// but are outside the scope.
|
|
128
|
+
let outOfScopeGoalIds;
|
|
129
|
+
if (goalsFilter !== 'all') {
|
|
130
|
+
// Goal scope is restricted; collect goals that exist but aren't selected.
|
|
131
|
+
outOfScopeGoalIds = new Set([...goalElems.keys()].filter((id) => !selectedGoalIds.has(id)));
|
|
132
|
+
}
|
|
133
|
+
// Collect out-of-scope factors (exist in catalogue but not in selected set).
|
|
134
|
+
const selectedFactorIds = new Set(selectedFactors.map((f) => str(f['id'])).filter((x) => x !== undefined));
|
|
135
|
+
let outOfScopeFactorIds;
|
|
136
|
+
if (goalsFilter !== 'all') {
|
|
137
|
+
outOfScopeFactorIds = new Set([...factorElems.keys()].filter((id) => !selectedFactorIds.has(id)));
|
|
138
|
+
}
|
|
120
139
|
return {
|
|
121
140
|
notation: 'dgca',
|
|
122
141
|
id: viewDoc['id'],
|
|
@@ -126,5 +145,8 @@ export function resolveFGCA(viewDoc, sources) {
|
|
|
126
145
|
goals: selectedGoals,
|
|
127
146
|
changes: selectedChanges,
|
|
128
147
|
actions: selectedActivities,
|
|
148
|
+
view_config: viewDoc['view_config'],
|
|
149
|
+
__outOfScopeGoalIds: outOfScopeGoalIds,
|
|
150
|
+
__outOfScopeFactorIds: outOfScopeFactorIds,
|
|
129
151
|
};
|
|
130
152
|
}
|
package/dist/fgca/validate.d.ts
CHANGED
|
@@ -10,5 +10,7 @@ export interface FGCADoc {
|
|
|
10
10
|
goals: GoalItem[];
|
|
11
11
|
changes: BdnChangeWithActivities[];
|
|
12
12
|
activities: ActivityItem[];
|
|
13
|
+
/** DGA mode: omit the Changes column (`view_config.layers.changes: off`). */
|
|
14
|
+
hideChanges?: boolean;
|
|
13
15
|
}
|
|
14
16
|
export declare function validateFGCADoc(input: unknown): FGCAValidationResult;
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export { validateCapabilityMap, layoutCapabilityTree, TREE_NODE_WIDTH, TREE_NODE
|
|
|
11
11
|
export * from "./change/index";
|
|
12
12
|
export * from "./compliance/index";
|
|
13
13
|
export * from "./compliance-matrix/index";
|
|
14
|
+
export * from "./compliance-verification-matrix/index";
|
|
14
15
|
export * from "./confidence/index";
|
|
15
16
|
export * from "./factor/index";
|
|
16
17
|
export * from "./fgca/index";
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ export { validateCapabilityMap, layoutCapabilityTree, TREE_NODE_WIDTH, TREE_NODE
|
|
|
10
10
|
export * from "./change/index.js";
|
|
11
11
|
export * from "./compliance/index.js";
|
|
12
12
|
export * from "./compliance-matrix/index.js";
|
|
13
|
+
export * from "./compliance-verification-matrix/index.js";
|
|
13
14
|
export * from "./confidence/index.js";
|
|
14
15
|
export * from "./factor/index.js";
|
|
15
16
|
export * from "./fgca/index.js";
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export type { AspectCategory, RowId, Stage, AspectEntry, LaneConfig, ProcessBlueprintHeader, ProcessBlueprintFile, ProcessBlueprintLayoutOptions, LegendCell, StageHeaderCell, StageTextCell, AspectPill, AspectRow, ProcessBlueprintLayout, ComplianceDecoration, ComplianceChip, ComplianceRow, ComplianceLaneConfig, ComplianceLaneAssertion, ComplianceLaneRequirement, ComplianceLaneInput, } from './types.js';
|
|
2
2
|
export { validateProcessBlueprint } from './validate.js';
|
|
3
|
-
export type { ValidationError as ProcessBlueprintValidationError, ValidationWarning as ProcessBlueprintValidationWarning, ValidationResult as ProcessBlueprintValidationResult, } from './validate.js';
|
|
3
|
+
export type { ValidationError as ProcessBlueprintValidationError, ValidationWarning as ProcessBlueprintValidationWarning, ValidationResult as ProcessBlueprintValidationResult, ProcessBlueprintValidateOptions, ProcessParentEdge, } from './validate.js';
|
|
4
4
|
export { layoutProcessBlueprint } from './layout.js';
|
|
5
|
+
export { collectProcessColumnRecords, collectStepHomeProcess, columnIndexesForRealisedVia, isProcessColumnId, isStageColumnId, resolveColumnDisplay, } from './resolve-columns.js';
|
|
6
|
+
export type { ProcessColumnRecord } from './resolve-columns.js';
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { validateProcessBlueprint } from './validate.js';
|
|
2
2
|
export { layoutProcessBlueprint } from './layout.js';
|
|
3
|
+
export { collectProcessColumnRecords, collectStepHomeProcess, columnIndexesForRealisedVia, isProcessColumnId, isStageColumnId, resolveColumnDisplay, } from './resolve-columns.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { wrapTextLines } from '../webview/entity-text-layout.js';
|
|
2
|
+
import { columnIndexesForRealisedVia, resolveColumnDisplay } from './resolve-columns.js';
|
|
2
3
|
const ASPECT_CATEGORIES = ['systems', 'actors', 'equipment', 'information_entities'];
|
|
3
4
|
const ASPECT_LABEL = {
|
|
4
5
|
systems: 'Systems',
|
|
@@ -181,29 +182,31 @@ function deriveComplianceRow(stages, stageIndexById, input, opts, yStart) {
|
|
|
181
182
|
for (const stageRef of realisedVia) {
|
|
182
183
|
if (typeof stageRef !== 'string')
|
|
183
184
|
continue;
|
|
184
|
-
const
|
|
185
|
-
if (
|
|
185
|
+
const idxs = columnIndexesForRealisedVia(stageRef, stageIndexById, opts.stepHomeProcess);
|
|
186
|
+
if (idxs.length === 0)
|
|
186
187
|
continue;
|
|
187
|
-
for (const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
stageAccum.
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
existing.deadline
|
|
188
|
+
for (const idx of idxs) {
|
|
189
|
+
for (const lawId of lawIds) {
|
|
190
|
+
// Apply jurisdiction filter.
|
|
191
|
+
if (filterJurisdictions) {
|
|
192
|
+
const jur = input.codexJurisdictions?.[lawId];
|
|
193
|
+
if (!jur || !filterJurisdictions.has(jur))
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (!stageAccum.has(idx))
|
|
197
|
+
stageAccum.set(idx, new Map());
|
|
198
|
+
const lawMap = stageAccum.get(idx);
|
|
199
|
+
const existing = lawMap.get(lawId);
|
|
200
|
+
const isGap = a.status === 'non_compliant' || a.status === 'partial';
|
|
201
|
+
if (!existing) {
|
|
202
|
+
lawMap.set(lawId, { gap: isGap, deadline: req.deadline });
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
existing.gap = existing.gap || isGap;
|
|
206
|
+
// Keep the earlier deadline (more urgent).
|
|
207
|
+
if (req.deadline && (!existing.deadline || req.deadline < existing.deadline)) {
|
|
208
|
+
existing.deadline = req.deadline;
|
|
209
|
+
}
|
|
207
210
|
}
|
|
208
211
|
}
|
|
209
212
|
}
|
|
@@ -253,6 +256,7 @@ export function layoutProcessBlueprint(file, options) {
|
|
|
253
256
|
? allStages.filter(s => s?.id != null && opts.visibleStages.includes(s.id))
|
|
254
257
|
: allStages;
|
|
255
258
|
const stageIndexById = buildStageIndex(stages);
|
|
259
|
+
const display = stages.map(s => resolveColumnDisplay(s, opts.processCatalog));
|
|
256
260
|
// Row visibility from visibleRows
|
|
257
261
|
const visRows = opts.visibleRows;
|
|
258
262
|
const showGoal = !visRows || visRows.includes('goal');
|
|
@@ -266,7 +270,7 @@ export function layoutProcessBlueprint(file, options) {
|
|
|
266
270
|
const stageHeaders = stages.map((s, i) => ({
|
|
267
271
|
stageIndex: i,
|
|
268
272
|
id: s?.id ?? '',
|
|
269
|
-
name:
|
|
273
|
+
name: display[i].name,
|
|
270
274
|
x: opts.legendColumnWidth + i * opts.stageColumnWidth,
|
|
271
275
|
y: 0,
|
|
272
276
|
width: opts.stageColumnWidth,
|
|
@@ -280,8 +284,8 @@ export function layoutProcessBlueprint(file, options) {
|
|
|
280
284
|
const maxLines = Math.max(1, ...lineCounts);
|
|
281
285
|
return Math.max(minHeight, maxLines * opts.textLineHeight + 2 * opts.cellTextPadY);
|
|
282
286
|
};
|
|
283
|
-
const goalLines =
|
|
284
|
-
const resultLines =
|
|
287
|
+
const goalLines = display.map(d => wrap(d.goal));
|
|
288
|
+
const resultLines = display.map(d => wrap(d.result));
|
|
285
289
|
let cursorY = opts.stageHeaderHeight;
|
|
286
290
|
let goalRowY = 0, goalRowHeight = 0;
|
|
287
291
|
let resultRowY = 0, resultRowHeight = 0;
|
|
@@ -290,8 +294,8 @@ export function layoutProcessBlueprint(file, options) {
|
|
|
290
294
|
if (showGoal) {
|
|
291
295
|
goalRowY = cursorY;
|
|
292
296
|
goalRowHeight = rowHeightFor(goalLines.map(l => l.length), opts.goalRowHeight);
|
|
293
|
-
goalCells.push(...
|
|
294
|
-
stageIndex: i, text:
|
|
297
|
+
goalCells.push(...display.map((d, i) => ({
|
|
298
|
+
stageIndex: i, text: d.goal, lines: goalLines[i],
|
|
295
299
|
x: opts.legendColumnWidth + i * opts.stageColumnWidth, y: goalRowY,
|
|
296
300
|
width: opts.stageColumnWidth, height: goalRowHeight,
|
|
297
301
|
})));
|
|
@@ -300,8 +304,8 @@ export function layoutProcessBlueprint(file, options) {
|
|
|
300
304
|
if (showResult) {
|
|
301
305
|
resultRowY = cursorY;
|
|
302
306
|
resultRowHeight = rowHeightFor(resultLines.map(l => l.length), opts.resultRowHeight);
|
|
303
|
-
resultCells.push(...
|
|
304
|
-
stageIndex: i, text:
|
|
307
|
+
resultCells.push(...display.map((d, i) => ({
|
|
308
|
+
stageIndex: i, text: d.result, lines: resultLines[i],
|
|
305
309
|
x: opts.legendColumnWidth + i * opts.stageColumnWidth, y: resultRowY,
|
|
306
310
|
width: opts.stageColumnWidth, height: resultRowHeight,
|
|
307
311
|
})));
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render-time resolution of Process Blueprint column headers and the
|
|
3
|
+
* compliance-lane join key.
|
|
4
|
+
*
|
|
5
|
+
* A `PROCESS-…` column is the process: `name` / `goal` / `result` come from
|
|
6
|
+
* the child PROCESS element (ELEMENT_PRIMITIVES §7.5), never from restated
|
|
7
|
+
* view fields. A `STAGE-…` column keeps the authored sketch copy.
|
|
8
|
+
*
|
|
9
|
+
* Pure: no I/O. The host (VS Code preview, JCEF, CLI) already owns loading
|
|
10
|
+
* canon/elements/** — this module only knows how to pick PROCESS / STEP
|
|
11
|
+
* records out of that pool.
|
|
12
|
+
*
|
|
13
|
+
* This is a read model, never storage. Callers render an ephemeral display
|
|
14
|
+
* copy and must never serialise it back onto the view.
|
|
15
|
+
*/
|
|
16
|
+
import type { Stage } from './types.js';
|
|
17
|
+
export declare function isProcessColumnId(id: string): boolean;
|
|
18
|
+
export declare function isStageColumnId(id: string): boolean;
|
|
19
|
+
/** Display fields taken from a `notation: process` element. */
|
|
20
|
+
export interface ProcessColumnRecord {
|
|
21
|
+
name: string;
|
|
22
|
+
goal?: string;
|
|
23
|
+
result?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Collect admitted PROCESS elements from a raw canon/elements/** doc pool,
|
|
27
|
+
* keyed by id. Malformed documents are skipped.
|
|
28
|
+
*/
|
|
29
|
+
export declare function collectProcessColumnRecords(docs: readonly unknown[]): Map<string, ProcessColumnRecord>;
|
|
30
|
+
/**
|
|
31
|
+
* Map each STEP id onto the PROCESS that contains it — either inline in
|
|
32
|
+
* `PROCESS.flow.steps[]` or a promoted `{ notation: step, process }` element.
|
|
33
|
+
* First writer wins; a later duplicate is ignored.
|
|
34
|
+
*/
|
|
35
|
+
export declare function collectStepHomeProcess(docs: readonly unknown[]): Map<string, string>;
|
|
36
|
+
/**
|
|
37
|
+
* Display copy for one column. PROCESS- columns ignore any restated view
|
|
38
|
+
* fields; missing catalogue falls back to the id (header) and empty
|
|
39
|
+
* goal/result. STAGE- columns keep authored name/goal/result.
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveColumnDisplay(stage: Stage, catalog?: ReadonlyMap<string, ProcessColumnRecord>): {
|
|
42
|
+
name: string;
|
|
43
|
+
goal: string;
|
|
44
|
+
result: string;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Column indexes a `realised_via` token pins on this blueprint.
|
|
48
|
+
*
|
|
49
|
+
* `STAGE-…` is never a join key (ASSERT-004). A `PROCESS-…` token pins that
|
|
50
|
+
* column. A STEP whose home process is a column pins that column.
|
|
51
|
+
*/
|
|
52
|
+
export declare function columnIndexesForRealisedVia(ref: string, stageIndexById: ReadonlyMap<string, number>, stepHomeProcess?: ReadonlyMap<string, string>): number[];
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render-time resolution of Process Blueprint column headers and the
|
|
3
|
+
* compliance-lane join key.
|
|
4
|
+
*
|
|
5
|
+
* A `PROCESS-…` column is the process: `name` / `goal` / `result` come from
|
|
6
|
+
* the child PROCESS element (ELEMENT_PRIMITIVES §7.5), never from restated
|
|
7
|
+
* view fields. A `STAGE-…` column keeps the authored sketch copy.
|
|
8
|
+
*
|
|
9
|
+
* Pure: no I/O. The host (VS Code preview, JCEF, CLI) already owns loading
|
|
10
|
+
* canon/elements/** — this module only knows how to pick PROCESS / STEP
|
|
11
|
+
* records out of that pool.
|
|
12
|
+
*
|
|
13
|
+
* This is a read model, never storage. Callers render an ephemeral display
|
|
14
|
+
* copy and must never serialise it back onto the view.
|
|
15
|
+
*/
|
|
16
|
+
const PROCESS_ID_RE = /^PROCESS(-[A-Z0-9][A-Z0-9_]*)*-\d+$/;
|
|
17
|
+
const STAGE_ID_RE = /^STAGE(-[A-Z0-9][A-Z0-9_]*)*-\d+$/;
|
|
18
|
+
export function isProcessColumnId(id) {
|
|
19
|
+
return PROCESS_ID_RE.test(id);
|
|
20
|
+
}
|
|
21
|
+
export function isStageColumnId(id) {
|
|
22
|
+
return STAGE_ID_RE.test(id);
|
|
23
|
+
}
|
|
24
|
+
function isRecord(v) {
|
|
25
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
26
|
+
}
|
|
27
|
+
function optionalString(v) {
|
|
28
|
+
return typeof v === 'string' && v.trim().length > 0 ? v.trim() : undefined;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Collect admitted PROCESS elements from a raw canon/elements/** doc pool,
|
|
32
|
+
* keyed by id. Malformed documents are skipped.
|
|
33
|
+
*/
|
|
34
|
+
export function collectProcessColumnRecords(docs) {
|
|
35
|
+
const out = new Map();
|
|
36
|
+
for (const doc of docs) {
|
|
37
|
+
if (!isRecord(doc))
|
|
38
|
+
continue;
|
|
39
|
+
if (doc['notation'] !== 'process')
|
|
40
|
+
continue;
|
|
41
|
+
const id = optionalString(doc['id']);
|
|
42
|
+
if (!id || !isProcessColumnId(id))
|
|
43
|
+
continue;
|
|
44
|
+
const name = optionalString(doc['name']);
|
|
45
|
+
if (!name)
|
|
46
|
+
continue;
|
|
47
|
+
const rec = { name };
|
|
48
|
+
const goal = optionalString(doc['goal']);
|
|
49
|
+
const result = optionalString(doc['result']);
|
|
50
|
+
if (goal)
|
|
51
|
+
rec.goal = goal;
|
|
52
|
+
if (result)
|
|
53
|
+
rec.result = result;
|
|
54
|
+
out.set(id, rec);
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Map each STEP id onto the PROCESS that contains it — either inline in
|
|
60
|
+
* `PROCESS.flow.steps[]` or a promoted `{ notation: step, process }` element.
|
|
61
|
+
* First writer wins; a later duplicate is ignored.
|
|
62
|
+
*/
|
|
63
|
+
export function collectStepHomeProcess(docs) {
|
|
64
|
+
const out = new Map();
|
|
65
|
+
for (const doc of docs) {
|
|
66
|
+
if (!isRecord(doc))
|
|
67
|
+
continue;
|
|
68
|
+
if (doc['notation'] === 'process') {
|
|
69
|
+
const processId = optionalString(doc['id']);
|
|
70
|
+
if (!processId || !isProcessColumnId(processId))
|
|
71
|
+
continue;
|
|
72
|
+
const flow = isRecord(doc['flow']) ? doc['flow'] : undefined;
|
|
73
|
+
const steps = flow && Array.isArray(flow['steps']) ? flow['steps'] : [];
|
|
74
|
+
for (const step of steps) {
|
|
75
|
+
if (!isRecord(step))
|
|
76
|
+
continue;
|
|
77
|
+
const stepId = optionalString(step['id']);
|
|
78
|
+
if (!stepId || out.has(stepId))
|
|
79
|
+
continue;
|
|
80
|
+
out.set(stepId, processId);
|
|
81
|
+
}
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (doc['notation'] === 'step') {
|
|
85
|
+
const stepId = optionalString(doc['id']);
|
|
86
|
+
const processId = optionalString(doc['process']);
|
|
87
|
+
if (!stepId || !processId || !isProcessColumnId(processId))
|
|
88
|
+
continue;
|
|
89
|
+
if (!out.has(stepId))
|
|
90
|
+
out.set(stepId, processId);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Display copy for one column. PROCESS- columns ignore any restated view
|
|
97
|
+
* fields; missing catalogue falls back to the id (header) and empty
|
|
98
|
+
* goal/result. STAGE- columns keep authored name/goal/result.
|
|
99
|
+
*/
|
|
100
|
+
export function resolveColumnDisplay(stage, catalog) {
|
|
101
|
+
const id = typeof stage.id === 'string' ? stage.id.trim() : '';
|
|
102
|
+
if (isProcessColumnId(id)) {
|
|
103
|
+
const rec = catalog?.get(id);
|
|
104
|
+
return {
|
|
105
|
+
name: rec?.name ?? id,
|
|
106
|
+
goal: rec?.goal ?? '',
|
|
107
|
+
result: rec?.result ?? '',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
name: typeof stage.name === 'string' ? stage.name : '',
|
|
112
|
+
goal: typeof stage.goal === 'string' ? stage.goal : '',
|
|
113
|
+
result: typeof stage.result === 'string' ? stage.result : '',
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Column indexes a `realised_via` token pins on this blueprint.
|
|
118
|
+
*
|
|
119
|
+
* `STAGE-…` is never a join key (ASSERT-004). A `PROCESS-…` token pins that
|
|
120
|
+
* column. A STEP whose home process is a column pins that column.
|
|
121
|
+
*/
|
|
122
|
+
export function columnIndexesForRealisedVia(ref, stageIndexById, stepHomeProcess) {
|
|
123
|
+
const token = ref.trim();
|
|
124
|
+
if (token.length === 0)
|
|
125
|
+
return [];
|
|
126
|
+
if (isStageColumnId(token))
|
|
127
|
+
return [];
|
|
128
|
+
const direct = stageIndexById.get(token);
|
|
129
|
+
if (direct !== undefined && isProcessColumnId(token))
|
|
130
|
+
return [direct];
|
|
131
|
+
const home = stepHomeProcess?.get(token);
|
|
132
|
+
if (home && isProcessColumnId(home)) {
|
|
133
|
+
const idx = stageIndexById.get(home);
|
|
134
|
+
if (idx !== undefined)
|
|
135
|
+
return [idx];
|
|
136
|
+
}
|
|
137
|
+
return [];
|
|
138
|
+
}
|