@transitrix/diagrams 1.9.18 → 1.11.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 (87) 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 +59 -0
  4. package/dist/canon-resolver-utils.d.ts +3 -0
  5. package/dist/canon-resolver-utils.js +16 -0
  6. package/dist/codex/index.d.ts +1 -1
  7. package/dist/codex/index.js +1 -1
  8. package/dist/codex/types.d.ts +9 -2
  9. package/dist/codex/types.js +10 -3
  10. package/dist/codex/validate.js +29 -3
  11. package/dist/compliance/classify.d.ts +11 -1
  12. package/dist/compliance/classify.js +31 -17
  13. package/dist/compliance/impact.d.ts +3 -1
  14. package/dist/compliance/impact.js +41 -13
  15. package/dist/compliance/types.d.ts +6 -0
  16. package/dist/compliance-matrix/build.js +0 -0
  17. package/dist/compliance-verification-matrix/build.d.ts +17 -0
  18. package/dist/compliance-verification-matrix/build.js +65 -0
  19. package/dist/compliance-verification-matrix/csv.d.ts +6 -0
  20. package/dist/compliance-verification-matrix/csv.js +41 -0
  21. package/dist/compliance-verification-matrix/index.d.ts +3 -0
  22. package/dist/compliance-verification-matrix/index.js +2 -0
  23. package/dist/compliance-verification-matrix/types.d.ts +75 -0
  24. package/dist/compliance-verification-matrix/types.js +4 -0
  25. package/dist/fgca/action-change-ids.d.ts +7 -0
  26. package/dist/fgca/action-change-ids.js +11 -0
  27. package/dist/fgca/parse-canonical.d.ts +3 -1
  28. package/dist/fgca/parse-canonical.js +13 -8
  29. package/dist/fgca/resolver.d.ts +6 -2
  30. package/dist/fgca/resolver.js +12 -6
  31. package/dist/fgca/validate.d.ts +2 -0
  32. package/dist/index.d.ts +1 -0
  33. package/dist/index.js +1 -0
  34. package/dist/process-blueprint/index.d.ts +3 -1
  35. package/dist/process-blueprint/index.js +1 -0
  36. package/dist/process-blueprint/layout.js +33 -29
  37. package/dist/process-blueprint/resolve-columns.d.ts +52 -0
  38. package/dist/process-blueprint/resolve-columns.js +138 -0
  39. package/dist/process-blueprint/types.d.ts +23 -5
  40. package/dist/process-blueprint/validate.d.ts +19 -1
  41. package/dist/process-blueprint/validate.js +73 -21
  42. package/dist/repo-validate/check-candidate-fields.d.ts +6 -0
  43. package/dist/repo-validate/check-candidate-fields.js +87 -0
  44. package/dist/repo-validate/check-process-parent.d.ts +8 -0
  45. package/dist/repo-validate/check-process-parent.js +172 -0
  46. package/dist/repo-validate/index.d.ts +1 -0
  47. package/dist/repo-validate/index.js +1 -0
  48. package/dist/repo-validate/validate-repo.js +7 -0
  49. package/dist/requirement/types.d.ts +1 -1
  50. package/dist/requirement/types.js +1 -1
  51. package/dist/webview/entry.js +11 -4
  52. package/dist/webview/render-fgca.js +1 -1
  53. package/dist/webview/render-process.d.ts +19 -1
  54. package/dist/webview/render-process.js +75 -42
  55. package/package.json +1 -1
  56. package/src/activities/resolver.ts +40 -20
  57. package/src/activities/validate.ts +58 -0
  58. package/src/canon-resolver-utils.ts +19 -0
  59. package/src/codex/index.ts +1 -1
  60. package/src/codex/types.ts +11 -3
  61. package/src/codex/validate.ts +30 -2
  62. package/src/compliance/classify.ts +40 -17
  63. package/src/compliance/impact.ts +41 -12
  64. package/src/compliance/types.ts +6 -0
  65. package/src/compliance-matrix/build.ts +0 -0
  66. package/src/compliance-verification-matrix/build.ts +84 -0
  67. package/src/compliance-verification-matrix/csv.ts +47 -0
  68. package/src/compliance-verification-matrix/index.ts +7 -0
  69. package/src/compliance-verification-matrix/types.ts +85 -0
  70. package/src/fgca/action-change-ids.ts +13 -0
  71. package/src/fgca/parse-canonical.ts +12 -8
  72. package/src/fgca/resolver.ts +15 -6
  73. package/src/fgca/validate.ts +2 -0
  74. package/src/index.ts +1 -0
  75. package/src/process-blueprint/index.ts +11 -0
  76. package/src/process-blueprint/layout.ts +48 -29
  77. package/src/process-blueprint/resolve-columns.ts +147 -0
  78. package/src/process-blueprint/types.ts +19 -5
  79. package/src/process-blueprint/validate.ts +107 -21
  80. package/src/repo-validate/check-candidate-fields.ts +93 -0
  81. package/src/repo-validate/check-process-parent.ts +184 -0
  82. package/src/repo-validate/index.ts +5 -0
  83. package/src/repo-validate/validate-repo.ts +7 -0
  84. package/src/requirement/types.ts +1 -1
  85. package/src/webview/entry.ts +13 -6
  86. package/src/webview/render-fgca.ts +1 -1
  87. package/src/webview/render-process.ts +94 -49
@@ -0,0 +1,65 @@
1
+ // Requirement -> verification matrix builder.
2
+ import { buildGapReport } from '../compliance/gap-report.js';
3
+ function byId(a, b) {
4
+ return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
5
+ }
6
+ /**
7
+ * Builds the repository-wide requirement -> verification matrix.
8
+ *
9
+ * Pure projection over an already-built `ComplianceIndex`
10
+ * (`compliance/reverse-index.ts`) — no I/O, no revalidation. Reuses
11
+ * `buildGapReport`'s `REQ-VERIF-COVERAGE-001`/`-002` verdicts verbatim rather
12
+ * than recomputing a competing coverage status.
13
+ *
14
+ * Ordering: requirements sorted by id; within a requirement, its verification
15
+ * rows sorted by verification id. Both are explicit sorts over the index's
16
+ * values, so the result is independent of the Map's insertion order (which —
17
+ * for a caller that built the index from a filesystem walk — would otherwise
18
+ * track directory enumeration order).
19
+ */
20
+ export function buildRequirementVerificationMatrix(index, options = {}) {
21
+ const gapReport = buildGapReport(index, options);
22
+ const noVerification = new Set(gapReport.requirementsWithoutVerification.map(r => r.id));
23
+ const unresolvedVerification = new Set(gapReport.requirementsWithUnresolvedVerification.map(r => r.id));
24
+ const requirements = [...index.requirementById.values()].sort(byId);
25
+ const rows = [];
26
+ let verificationRowCount = 0;
27
+ let gapRequirementCount = 0;
28
+ for (const requirement of requirements) {
29
+ const parentId = requirement.parent;
30
+ const parentLabel = parentId ? index.requirementById.get(parentId)?.name : undefined;
31
+ const base = {
32
+ requirementId: requirement.id,
33
+ requirementLabel: requirement.name,
34
+ ...(parentId !== undefined ? { parentId } : {}),
35
+ ...(parentLabel !== undefined ? { parentLabel } : {}),
36
+ };
37
+ if (noVerification.has(requirement.id)) {
38
+ gapRequirementCount++;
39
+ rows.push({ ...base, coverageGap: 'REQ-VERIF-COVERAGE-001' });
40
+ continue;
41
+ }
42
+ const gap = unresolvedVerification.has(requirement.id) ? 'REQ-VERIF-COVERAGE-002' : undefined;
43
+ if (gap)
44
+ gapRequirementCount++;
45
+ const verifications = [...(index.verificationsByRequirement.get(requirement.id) ?? [])].sort(byId);
46
+ for (const verification of verifications) {
47
+ verificationRowCount++;
48
+ rows.push({
49
+ ...base,
50
+ verificationId: verification.id,
51
+ verificationLabel: verification.protocol ?? verification.id,
52
+ verificationOutcome: verification.outcome,
53
+ ...(gap ? { coverageGap: gap } : {}),
54
+ });
55
+ }
56
+ }
57
+ return {
58
+ rows,
59
+ summary: {
60
+ requirements: requirements.length,
61
+ verifications: verificationRowCount,
62
+ gaps: gapRequirementCount,
63
+ },
64
+ };
65
+ }
@@ -0,0 +1,6 @@
1
+ import type { RequirementVerificationMatrix } from './types.js';
2
+ /**
3
+ * Serialise the matrix to CSV. Identical input yields byte-identical output;
4
+ * the row order is the builder's stable order.
5
+ */
6
+ export declare function renderRequirementVerificationMatrixCsv(matrix: RequirementVerificationMatrix): string;
@@ -0,0 +1,41 @@
1
+ // Deterministic CSV serialiser for the requirement-verification matrix.
2
+ // Same row order as `buildRequirementVerificationMatrix` (requirement id,
3
+ // then verification id). CRLF line endings, RFC-4180 quoting — the same
4
+ // convention as the Gap Dashboard export.
5
+ const HEADER = [
6
+ 'requirement_id',
7
+ 'requirement_label',
8
+ 'parent_id',
9
+ 'parent_label',
10
+ 'related_test_result_id',
11
+ 'related_test_result_label',
12
+ 'outcome',
13
+ 'coverage_gap',
14
+ ];
15
+ function csvCell(value) {
16
+ const v = value ?? '';
17
+ return `"${v.replace(/"/g, '""')}"`;
18
+ }
19
+ function rowCells(row) {
20
+ return [
21
+ row.requirementId,
22
+ row.requirementLabel,
23
+ row.parentId ?? '',
24
+ row.parentLabel ?? '',
25
+ row.verificationId ?? '',
26
+ row.verificationLabel ?? '',
27
+ row.verificationOutcome ?? '',
28
+ row.coverageGap ?? '',
29
+ ];
30
+ }
31
+ /**
32
+ * Serialise the matrix to CSV. Identical input yields byte-identical output;
33
+ * the row order is the builder's stable order.
34
+ */
35
+ export function renderRequirementVerificationMatrixCsv(matrix) {
36
+ const lines = [HEADER.map(h => csvCell(h)).join(',')];
37
+ for (const row of matrix.rows) {
38
+ lines.push(rowCells(row).map(csvCell).join(','));
39
+ }
40
+ return lines.join('\r\n') + '\r\n';
41
+ }
@@ -0,0 +1,3 @@
1
+ export { buildRequirementVerificationMatrix } from './build.js';
2
+ export { renderRequirementVerificationMatrixCsv } from './csv.js';
3
+ export type { RequirementVerificationMatrix, RequirementVerificationMatrixOptions, RequirementVerificationRow, } from './types.js';
@@ -0,0 +1,2 @@
1
+ export { buildRequirementVerificationMatrix } from './build.js';
2
+ export { renderRequirementVerificationMatrixCsv } from './csv.js';
@@ -0,0 +1,75 @@
1
+ import type { VerificationOutcome } from '../verification/types.js';
2
+ /**
3
+ * One row of the requirement-verification matrix.
4
+ *
5
+ * A requirement with N admitted VERIFICATION records produces N rows, one per
6
+ * verification. A requirement with none still produces exactly one row,
7
+ * carrying the `REQ-VERIF-COVERAGE-001` gap. A requirement whose
8
+ * verifications exist but never closed (pass/fail) produces one row per
9
+ * verification — each carrying the `REQ-VERIF-COVERAGE-002` gap alongside its
10
+ * (still-open) outcome; the open verification is real data and is never
11
+ * replaced by a placeholder gap row.
12
+ *
13
+ * Coverage comes only from `VERIFICATION.verifies` (27-verification.md §2). A
14
+ * child requirement's verification never covers its `parent`
15
+ * (15-requirement.md §2.4 — `parent` records structure only, no roll-up).
16
+ */
17
+ export interface RequirementVerificationRow {
18
+ requirementId: string;
19
+ requirementLabel: string;
20
+ /**
21
+ * Direct `REQUIREMENT.parent` id, verbatim, present whenever the field is
22
+ * set — whether or not it resolves. `parent` resolution is not currently a
23
+ * validator concern (15-requirement.md §2.4); a dangling parent must still
24
+ * be visible on the row, not silently dropped.
25
+ */
26
+ parentId?: string;
27
+ /**
28
+ * The resolved parent's `name`, present only when `parentId` names an
29
+ * admitted requirement in the index. Absent when `parentId` is set but
30
+ * unresolved (dangling), or when the requirement has no parent.
31
+ */
32
+ parentLabel?: string;
33
+ /** The verification filling this row, when one exists. */
34
+ verificationId?: string;
35
+ /**
36
+ * VERIFICATION carries no `name`/label field of its own
37
+ * (27-verification.md §2); `protocol` — the procedure description — is the
38
+ * closest human-readable text and is what this row shows as its label.
39
+ */
40
+ verificationLabel?: string;
41
+ verificationOutcome?: VerificationOutcome;
42
+ /**
43
+ * `REQ-VERIF-COVERAGE-001` / `-002` (`compliance/gap-report.ts`), reused
44
+ * verbatim — this builder never recomputes a competing coverage verdict.
45
+ * Absent once the requirement has at least one closed (pass/fail)
46
+ * verification.
47
+ */
48
+ coverageGap?: 'REQ-VERIF-COVERAGE-001' | 'REQ-VERIF-COVERAGE-002';
49
+ }
50
+ export interface RequirementVerificationMatrixOptions {
51
+ /**
52
+ * Forwarded to `buildGapReport`. Neither coverage gap this matrix surfaces
53
+ * (`REQ-VERIF-COVERAGE-001`/`-002`) depends on `today` — accepted only for
54
+ * call-site symmetry with the other compliance-view builders.
55
+ */
56
+ today?: string;
57
+ }
58
+ export interface RequirementVerificationMatrix {
59
+ /**
60
+ * One row per (requirement, verification) pair, plus one gap row per
61
+ * requirement with zero verifications.
62
+ *
63
+ * Ordering (stable, independent of filesystem/Map enumeration order):
64
+ * requirements sorted by id; within a requirement, its verification rows
65
+ * sorted by verification id.
66
+ */
67
+ rows: RequirementVerificationRow[];
68
+ summary: {
69
+ requirements: number;
70
+ /** Rows carrying a real verification (excludes -001 gap-only rows). */
71
+ verifications: number;
72
+ /** Requirements carrying a REQ-VERIF-COVERAGE-001 or -002 gap. */
73
+ gaps: number;
74
+ };
75
+ }
@@ -0,0 +1,4 @@
1
+ // Requirement -> verification matrix — deterministic, repository-wide
2
+ // projection over the shared compliance reverse-index. Pure data; the
3
+ // consumer (a VS Code table, an export) renders it.
4
+ export {};
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Action → Change linkage on an element record.
3
+ *
4
+ * Canonical field is `delivers_changes` (Action element spec). `changes` is
5
+ * the pre-rename alias still present on older fixtures and inline docs.
6
+ */
7
+ export declare function actionChangeLinkField(el: Record<string, unknown>): 'delivers_changes' | 'changes';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Action → Change linkage on an element record.
3
+ *
4
+ * Canonical field is `delivers_changes` (Action element spec). `changes` is
5
+ * the pre-rename alias still present on older fixtures and inline docs.
6
+ */
7
+ export function actionChangeLinkField(el) {
8
+ return Object.prototype.hasOwnProperty.call(el, 'delivers_changes')
9
+ ? 'delivers_changes'
10
+ : 'changes';
11
+ }
@@ -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`
@@ -29,6 +29,8 @@ export interface CanonicalFGCAResult extends ValidationResult {
29
29
  /** On success, the internal-form FGCADoc derived from the canonical input. */
30
30
  parsed?: FGCADoc;
31
31
  }
32
+ /** DGA mode (`view_config.layers.changes: off`) omits the Changes column. */
33
+ export declare function isDgcaChangesLayerOff(input: unknown): boolean;
32
34
  /**
33
35
  * Validate canonical FGCA YAML and, on success, return an internal-form
34
36
  * `FGCADoc` ready for `buildFGCALayout`.
@@ -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'];
@@ -99,7 +102,7 @@ export function parseCanonicalFGCA(input) {
99
102
  const factorsRaw = asObjectArray(raw['factors']);
100
103
  const goalsRaw = asObjectArray(raw['goals']);
101
104
  let changesRaw = asObjectArray(raw['changes']);
102
- if (changesRaw === null && raw['changes'] === undefined && isChangesLayerOff(raw)) {
105
+ if (changesRaw === null && raw['changes'] === undefined && isDgcaChangesLayerOff(raw)) {
103
106
  changesRaw = [];
104
107
  }
105
108
  const activitiesRaw = asObjectArray(raw['actions']);
@@ -212,11 +215,12 @@ export function parseCanonicalFGCA(input) {
212
215
  factor: refIds.map((r) => ({ id: r })),
213
216
  };
214
217
  });
215
- // Build a reverse mapping: which changes does each activity reference (via
216
- // canonical activity.changes)? Used to populate the internal `change.activity_ids`.
218
+ // Build a reverse mapping: which changes does each action deliver (via
219
+ // canonical `delivers_changes`, or the pre-rename `changes` alias)?
217
220
  const activityChangesByCanonicalActivityId = new Map();
218
221
  activities.forEach((el, i) => {
219
- const refIds = checkRefArray(el, 'changes', changeIds, CHANGE_ID_RE, 'FGCA-010', `activities[${i}]`);
222
+ const field = actionChangeLinkField(el);
223
+ const refIds = checkRefArray(el, field, changeIds, CHANGE_ID_RE, 'FGCA-010', `activities[${i}]`);
220
224
  activityChangesByCanonicalActivityId.set(String(el['id']), refIds);
221
225
  });
222
226
  // For each change, find the activities that reference it.
@@ -286,6 +290,7 @@ export function parseCanonicalFGCA(input) {
286
290
  goals: internalGoals,
287
291
  changes: internalChanges,
288
292
  activities: internalActivities,
293
+ hideChanges: isDgcaChangesLayerOff(raw),
289
294
  };
290
295
  return { valid: true, errors, warnings, parsed };
291
296
  }
@@ -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,13 +113,13 @@ 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
  }
120
125
  return {
@@ -126,5 +131,6 @@ export function resolveFGCA(viewDoc, sources) {
126
131
  goals: selectedGoals,
127
132
  changes: selectedChanges,
128
133
  actions: selectedActivities,
134
+ view_config: viewDoc['view_config'],
129
135
  };
130
136
  }
@@ -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[];