@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.
Files changed (71) hide show
  1. package/dist/activities/resolver.d.ts +3 -0
  2. package/dist/activities/resolver.js +39 -18
  3. package/dist/activities/validate.js +67 -4
  4. package/dist/canon-resolver-utils.d.ts +3 -0
  5. package/dist/canon-resolver-utils.js +16 -0
  6. package/dist/compliance/classify.js +1 -0
  7. package/dist/compliance/impact.d.ts +3 -1
  8. package/dist/compliance/impact.js +41 -13
  9. package/dist/compliance/types.d.ts +6 -0
  10. package/dist/compliance-verification-matrix/build.d.ts +17 -0
  11. package/dist/compliance-verification-matrix/build.js +65 -0
  12. package/dist/compliance-verification-matrix/csv.d.ts +6 -0
  13. package/dist/compliance-verification-matrix/csv.js +41 -0
  14. package/dist/compliance-verification-matrix/index.d.ts +3 -0
  15. package/dist/compliance-verification-matrix/index.js +2 -0
  16. package/dist/compliance-verification-matrix/types.d.ts +75 -0
  17. package/dist/compliance-verification-matrix/types.js +4 -0
  18. package/dist/fgca/action-change-ids.d.ts +7 -0
  19. package/dist/fgca/action-change-ids.js +11 -0
  20. package/dist/fgca/parse-canonical.d.ts +17 -3
  21. package/dist/fgca/parse-canonical.js +44 -16
  22. package/dist/fgca/resolver.d.ts +6 -2
  23. package/dist/fgca/resolver.js +28 -6
  24. package/dist/fgca/validate.d.ts +2 -0
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.js +1 -0
  27. package/dist/process-blueprint/index.d.ts +3 -1
  28. package/dist/process-blueprint/index.js +1 -0
  29. package/dist/process-blueprint/layout.js +33 -29
  30. package/dist/process-blueprint/resolve-columns.d.ts +52 -0
  31. package/dist/process-blueprint/resolve-columns.js +138 -0
  32. package/dist/process-blueprint/types.d.ts +23 -5
  33. package/dist/process-blueprint/validate.d.ts +19 -1
  34. package/dist/process-blueprint/validate.js +73 -21
  35. package/dist/repo-validate/check-process-parent.d.ts +8 -0
  36. package/dist/repo-validate/check-process-parent.js +172 -0
  37. package/dist/repo-validate/index.d.ts +1 -0
  38. package/dist/repo-validate/index.js +1 -0
  39. package/dist/repo-validate/validate-repo.js +3 -0
  40. package/dist/webview/entry.js +26 -4
  41. package/dist/webview/render-fgca.d.ts +2 -0
  42. package/dist/webview/render-fgca.js +9 -3
  43. package/dist/webview/render-process.d.ts +19 -1
  44. package/dist/webview/render-process.js +75 -42
  45. package/package.json +3 -3
  46. package/src/activities/resolver.ts +40 -20
  47. package/src/activities/validate.ts +66 -4
  48. package/src/canon-resolver-utils.ts +19 -0
  49. package/src/compliance/classify.ts +1 -0
  50. package/src/compliance/impact.ts +41 -12
  51. package/src/compliance/types.ts +6 -0
  52. package/src/compliance-verification-matrix/build.ts +84 -0
  53. package/src/compliance-verification-matrix/csv.ts +47 -0
  54. package/src/compliance-verification-matrix/index.ts +7 -0
  55. package/src/compliance-verification-matrix/types.ts +85 -0
  56. package/src/fgca/action-change-ids.ts +13 -0
  57. package/src/fgca/parse-canonical.ts +56 -15
  58. package/src/fgca/resolver.ts +39 -6
  59. package/src/fgca/validate.ts +2 -0
  60. package/src/index.ts +1 -0
  61. package/src/process-blueprint/index.ts +11 -0
  62. package/src/process-blueprint/layout.ts +48 -29
  63. package/src/process-blueprint/resolve-columns.ts +147 -0
  64. package/src/process-blueprint/types.ts +19 -5
  65. package/src/process-blueprint/validate.ts +107 -21
  66. package/src/repo-validate/check-process-parent.ts +184 -0
  67. package/src/repo-validate/index.ts +5 -0
  68. package/src/repo-validate/validate-repo.ts +3 -0
  69. package/src/webview/entry.ts +26 -4
  70. package/src/webview/render-fgca.ts +13 -2
  71. 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`, `activity.changes`, `activity.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`, `activity.changes`, `activity.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`) may omit the changes layer. */
47
- function isChangesLayerOff(raw) {
48
- const vc = raw['view_config'];
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 && isChangesLayerOff(raw)) {
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 activity reference (via
216
- // canonical activity.changes)? Used to populate the internal `change.activity_ids`.
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 refIds = checkRefArray(el, 'changes', changeIds, CHANGE_ID_RE, 'FGCA-010', `activities[${i}]`);
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
  }
@@ -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.changes[]`) are carried from the canon elements unchanged; the
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>;
@@ -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.changes[]`) are carried from the canon elements unchanged; the
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 activitiesConf = isObject(vc['activities']) ? vc['activities'] : {};
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 activities: derived = reference ≥1 selected change or goal (degenerate FGA link)
116
+ // 4. Select actions: derived = delivers ≥1 selected change, or (DGA) a selected goal
112
117
  let selectedActivities;
113
- if (str(activitiesConf['surface']) === 'all') {
118
+ if (str(actionsConf['surface']) === 'all') {
114
119
  selectedActivities = [...activityElems.values()];
115
120
  }
116
121
  else {
117
- selectedActivities = [...activityElems.values()].filter((a) => strArray(a['changes']).some((cid) => selectedChangeIds.has(cid)) ||
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
  }
@@ -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 idx = stageIndexById.get(stageRef.trim());
185
- if (idx === undefined)
185
+ const idxs = columnIndexesForRealisedVia(stageRef, stageIndexById, opts.stepHomeProcess);
186
+ if (idxs.length === 0)
186
187
  continue;
187
- for (const lawId of lawIds) {
188
- // Apply jurisdiction filter.
189
- if (filterJurisdictions) {
190
- const jur = input.codexJurisdictions?.[lawId];
191
- if (!jur || !filterJurisdictions.has(jur))
192
- continue;
193
- }
194
- if (!stageAccum.has(idx))
195
- stageAccum.set(idx, new Map());
196
- const lawMap = stageAccum.get(idx);
197
- const existing = lawMap.get(lawId);
198
- const isGap = a.status === 'non_compliant' || a.status === 'partial';
199
- if (!existing) {
200
- lawMap.set(lawId, { gap: isGap, deadline: req.deadline });
201
- }
202
- else {
203
- existing.gap = existing.gap || isGap;
204
- // Keep the earlier deadline (more urgent).
205
- if (req.deadline && (!existing.deadline || req.deadline < existing.deadline)) {
206
- existing.deadline = req.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: s?.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 = stages.map(s => wrap(s?.goal ?? ''));
284
- const resultLines = stages.map(s => wrap(s?.result ?? ''));
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(...stages.map((s, i) => ({
294
- stageIndex: i, text: s?.goal ?? '', lines: goalLines[i],
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(...stages.map((s, i) => ({
304
- stageIndex: i, text: s?.result ?? '', lines: resultLines[i],
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
+ }