@transitrix/diagrams 1.3.0 → 1.4.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 (122) hide show
  1. package/dist/__tests__/yaml-normalize.test.js +9 -9
  2. package/dist/activities/__tests__/validate.test.js +77 -74
  3. package/dist/activities/validate.js +5 -12
  4. package/dist/activity-card/__tests__/example.test.js +2 -2
  5. package/dist/activity-card/__tests__/validate.test.js +29 -18
  6. package/dist/activity-card/validate.js +3 -10
  7. package/dist/actor/__tests__/validate.test.js +6 -0
  8. package/dist/actor/types.d.ts +1 -1
  9. package/dist/actor/types.js +2 -0
  10. package/dist/applications/types.d.ts +7 -0
  11. package/dist/applications/validate.js +18 -0
  12. package/dist/blocks/__tests__/layout.test.js +1 -1
  13. package/dist/blocks/layout.js +1 -1
  14. package/dist/business-service/__tests__/validate.test.d.ts +1 -0
  15. package/dist/business-service/__tests__/validate.test.js +135 -0
  16. package/dist/business-service/index.d.ts +3 -0
  17. package/dist/business-service/index.js +2 -0
  18. package/dist/business-service/types.d.ts +20 -0
  19. package/dist/business-service/types.js +3 -0
  20. package/dist/business-service/validate.d.ts +2 -0
  21. package/dist/business-service/validate.js +74 -0
  22. package/dist/fgca/__tests__/parse-canonical.test.js +10 -10
  23. package/dist/fgca/__tests__/resolver.test.js +7 -7
  24. package/dist/fgca/__tests__/validate.test.js +11 -11
  25. package/dist/fgca/parse-canonical.js +7 -11
  26. package/dist/fgca/resolver.d.ts +2 -2
  27. package/dist/fgca/resolver.js +5 -5
  28. package/dist/fgca/validate.js +4 -13
  29. package/dist/index.d.ts +5 -0
  30. package/dist/index.js +5 -0
  31. package/dist/integration/__tests__/validate.test.d.ts +1 -0
  32. package/dist/integration/__tests__/validate.test.js +169 -0
  33. package/dist/integration/index.d.ts +2 -0
  34. package/dist/integration/index.js +1 -0
  35. package/dist/integration/types.d.ts +27 -0
  36. package/dist/integration/types.js +5 -0
  37. package/dist/integration/validate.d.ts +2 -0
  38. package/dist/integration/validate.js +94 -0
  39. package/dist/location/__tests__/validate.test.d.ts +1 -0
  40. package/dist/location/__tests__/validate.test.js +107 -0
  41. package/dist/location/index.d.ts +3 -0
  42. package/dist/location/index.js +2 -0
  43. package/dist/location/types.d.ts +20 -0
  44. package/dist/location/types.js +3 -0
  45. package/dist/location/validate.d.ts +2 -0
  46. package/dist/location/validate.js +63 -0
  47. package/dist/node/__tests__/validate.test.d.ts +1 -0
  48. package/dist/node/__tests__/validate.test.js +100 -0
  49. package/dist/node/index.d.ts +2 -0
  50. package/dist/node/index.js +1 -0
  51. package/dist/node/types.d.ts +18 -0
  52. package/dist/node/types.js +3 -0
  53. package/dist/node/validate.d.ts +2 -0
  54. package/dist/node/validate.js +53 -0
  55. package/dist/repo-validate/__tests__/validate-repo.test.js +444 -0
  56. package/dist/repo-validate/validate-repo.js +224 -10
  57. package/dist/technology-service/__tests__/validate.test.d.ts +1 -0
  58. package/dist/technology-service/__tests__/validate.test.js +121 -0
  59. package/dist/technology-service/index.d.ts +2 -0
  60. package/dist/technology-service/index.js +1 -0
  61. package/dist/technology-service/types.d.ts +18 -0
  62. package/dist/technology-service/types.js +3 -0
  63. package/dist/technology-service/validate.d.ts +2 -0
  64. package/dist/technology-service/validate.js +64 -0
  65. package/dist/webview/__tests__/entry-notations.test.js +2 -4
  66. package/dist/webview/__tests__/render-activities.test.d.ts +1 -0
  67. package/dist/webview/__tests__/render-activities.test.js +91 -0
  68. package/dist/webview/__tests__/render-blocks.test.d.ts +1 -0
  69. package/dist/webview/__tests__/render-blocks.test.js +130 -0
  70. package/dist/webview/entry.d.ts +1 -1
  71. package/dist/webview/entry.js +8 -15
  72. package/dist/webview/render-activities.d.ts +12 -0
  73. package/dist/webview/render-activities.js +9 -3
  74. package/dist/webview/render-blocks.js +97 -18
  75. package/package.json +1 -1
  76. package/src/__tests__/yaml-normalize.test.ts +11 -11
  77. package/src/activities/__tests__/validate.test.ts +77 -74
  78. package/src/activities/validate.ts +5 -12
  79. package/src/activity-card/__tests__/example.test.ts +2 -2
  80. package/src/activity-card/__tests__/validate.test.ts +30 -18
  81. package/src/activity-card/validate.ts +3 -9
  82. package/src/actor/__tests__/validate.test.ts +8 -0
  83. package/src/actor/types.ts +2 -0
  84. package/src/applications/types.ts +8 -0
  85. package/src/applications/validate.ts +20 -0
  86. package/src/blocks/__tests__/layout.test.ts +1 -1
  87. package/src/blocks/layout.ts +1 -1
  88. package/src/business-service/__tests__/validate.test.ts +160 -0
  89. package/src/business-service/index.ts +3 -0
  90. package/src/business-service/types.ts +29 -0
  91. package/src/business-service/validate.ts +82 -0
  92. package/src/fgca/__tests__/parse-canonical.test.ts +10 -10
  93. package/src/fgca/__tests__/resolver.test.ts +7 -7
  94. package/src/fgca/__tests__/validate.test.ts +11 -11
  95. package/src/fgca/parse-canonical.ts +7 -11
  96. package/src/fgca/resolver.ts +5 -5
  97. package/src/fgca/validate.ts +4 -12
  98. package/src/index.ts +5 -0
  99. package/src/integration/__tests__/validate.test.ts +203 -0
  100. package/src/integration/index.ts +2 -0
  101. package/src/integration/types.ts +38 -0
  102. package/src/integration/validate.ts +104 -0
  103. package/src/location/__tests__/validate.test.ts +127 -0
  104. package/src/location/index.ts +3 -0
  105. package/src/location/types.ts +29 -0
  106. package/src/location/validate.ts +70 -0
  107. package/src/node/__tests__/validate.test.ts +118 -0
  108. package/src/node/index.ts +2 -0
  109. package/src/node/types.ts +27 -0
  110. package/src/node/validate.ts +59 -0
  111. package/src/repo-validate/__tests__/validate-repo.test.ts +550 -0
  112. package/src/repo-validate/validate-repo.ts +233 -10
  113. package/src/technology-service/__tests__/validate.test.ts +143 -0
  114. package/src/technology-service/index.ts +2 -0
  115. package/src/technology-service/types.ts +27 -0
  116. package/src/technology-service/validate.ts +71 -0
  117. package/src/webview/__tests__/entry-notations.test.ts +2 -4
  118. package/src/webview/__tests__/render-activities.test.ts +100 -0
  119. package/src/webview/__tests__/render-blocks.test.ts +155 -0
  120. package/src/webview/entry.ts +10 -19
  121. package/src/webview/render-activities.ts +29 -4
  122. package/src/webview/render-blocks.ts +101 -20
@@ -6,8 +6,12 @@ export type { ValidationError, ValidationWarning, ValidationResult };
6
6
  const VALID_TYPES = new Set<ApplicationType>(['application', 'integration', 'platform', 'data_store']);
7
7
  const VALID_STATUSES = new Set<ApplicationStatus>(['Draft', 'Active', 'Deprecated', 'Decommissioning']);
8
8
  const VALID_DIRECTIONS = new Set<IntegrationDirection>(['inbound', 'outbound', 'bidirectional']);
9
+ const VALID_SENSITIVITY = new Set(['public', 'internal', 'confidential', 'restricted']);
10
+ const VALID_DIRECTIONALITY = new Set(['producer', 'consumer', 'request_reply', 'bidirectional_stream']);
9
11
  const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
10
12
 
13
+ const IFACE_CONDITIONAL = ['protocol', 'payload_class', 'sensitivity', 'directionality'] as const;
14
+
11
15
  export function validateApplicationsCatalogue(input: unknown): ValidationResult {
12
16
  const errors: ValidationError[] = [];
13
17
  const warnings: ValidationWarning[] = [];
@@ -123,6 +127,22 @@ export function validateApplicationsCatalogue(input: unknown): ValidationResult
123
127
  if (intg['direction'] !== undefined && !VALID_DIRECTIONS.has(intg['direction'] as IntegrationDirection)) {
124
128
  errors.push({ code: 'APP-009', message: `${idx}.integrations[${j}]: direction "${intg['direction']}" must be one of: inbound, outbound, bidirectional` });
125
129
  }
130
+
131
+ // INT-001 — when interface_semantics: true, four fields become required.
132
+ if (intg['interface_semantics'] === true) {
133
+ for (const field of IFACE_CONDITIONAL) {
134
+ const v = intg[field];
135
+ if (typeof v !== 'string' || (v as string).trim() === '') {
136
+ errors.push({ code: 'INT-001', message: `${idx}.integrations[${j}]: ${field} is required when interface_semantics is true.`, path: `${idx}.integrations[${j}].${field}` });
137
+ }
138
+ }
139
+ if (typeof intg['sensitivity'] === 'string' && !VALID_SENSITIVITY.has(intg['sensitivity'])) {
140
+ errors.push({ code: 'INT-001', message: `${idx}.integrations[${j}]: sensitivity "${intg['sensitivity']}" must be one of public, internal, confidential, restricted.`, path: `${idx}.integrations[${j}].sensitivity` });
141
+ }
142
+ if (typeof intg['directionality'] === 'string' && !VALID_DIRECTIONALITY.has(intg['directionality'])) {
143
+ errors.push({ code: 'INT-001', message: `${idx}.integrations[${j}]: directionality "${intg['directionality']}" must be one of producer, consumer, request_reply, bidirectional_stream.`, path: `${idx}.integrations[${j}].directionality` });
144
+ }
145
+ }
126
146
  }
127
147
  }
128
148
  }
@@ -119,7 +119,7 @@ describe('layoutNestedBlocks', () => {
119
119
  });
120
120
  const a = layout.blocks[0];
121
121
  expect(a.width).toBe(160);
122
- expect(a.height).toBe(60);
122
+ expect(a.height).toBe(72);
123
123
  });
124
124
 
125
125
  it('honours custom layout options', () => {
@@ -8,7 +8,7 @@ import type {
8
8
 
9
9
  const DEFAULTS: Required<BlocksLayoutOptions> = {
10
10
  leafWidth: 160,
11
- leafHeight: 60,
11
+ leafHeight: 72,
12
12
  padding: 12,
13
13
  headerHeight: 28,
14
14
  childGap: 12,
@@ -0,0 +1,160 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { validateBusinessService } from '../validate.js';
3
+
4
+ function valid(): Record<string, unknown> {
5
+ return {
6
+ notation: 'business-service',
7
+ id: 'BUSINESS_SERVICE-CRM-1',
8
+ name: 'CRM Service',
9
+ type: 'internal',
10
+ description: 'Customer relationship management service offering contact management and deal tracking.',
11
+ offering_unit: 'ACTOR-SALES-OPS-1',
12
+ capability: 'CAPABILITY-V2.1',
13
+ zone: 'canon',
14
+ admitted_at: '2026-06-28',
15
+ admitted_by: 'v.korobeinikov',
16
+ gate_checks: { uniqueness: 'pass', consistency: 'pass', completeness: 'pass' },
17
+ valid_from: '2024-01-01',
18
+ valid_to: null,
19
+ };
20
+ }
21
+
22
+ const codes = (input: unknown): string[] =>
23
+ validateBusinessService(input).errors.map(e => e.code);
24
+
25
+ describe('validateBusinessService — positive', () => {
26
+ it('accepts a well-formed internal service with all optional fields', () => {
27
+ const r = validateBusinessService(valid());
28
+ expect(r.valid, JSON.stringify(r.errors)).toBe(true);
29
+ });
30
+
31
+ it('accepts an external service with no optional fields', () => {
32
+ const a = valid();
33
+ a.id = 'BUSINESS_SERVICE-ONBOARDING-1';
34
+ a.name = 'Onboarding Service';
35
+ a.type = 'external';
36
+ delete a.description;
37
+ delete a.offering_unit;
38
+ delete a.capability;
39
+ expect(validateBusinessService(a).valid).toBe(true);
40
+ });
41
+
42
+ it('accepts a shared service offered by a ROLE', () => {
43
+ expect(validateBusinessService({ ...valid(), type: 'shared', offering_unit: 'ROLE-PLATFORM-OWNER-1' }).valid).toBe(true);
44
+ });
45
+
46
+ it('accepts valid_to as a date string', () => {
47
+ expect(validateBusinessService({ ...valid(), valid_to: '2026-12-31' }).valid).toBe(true);
48
+ });
49
+
50
+ it('accepts a service with no offering_unit or capability', () => {
51
+ const a = valid();
52
+ delete a.offering_unit;
53
+ delete a.capability;
54
+ expect(validateBusinessService(a).valid).toBe(true);
55
+ });
56
+ });
57
+
58
+ describe('validateBusinessService — BSV-001 (shape / id grammar)', () => {
59
+ it('flags a missing required envelope field (name)', () => {
60
+ const a = valid();
61
+ delete a.name;
62
+ expect(codes(a)).toContain('BSV-001');
63
+ });
64
+
65
+ it('flags an id that violates the grammar', () => {
66
+ expect(codes({ ...valid(), id: 'BUSINESS_SERVICE-001' })).toContain('BSV-001');
67
+ expect(codes({ ...valid(), id: 'BSV-1' })).toContain('BSV-001');
68
+ expect(codes({ ...valid(), id: 'business-service-1' })).toContain('BSV-001');
69
+ });
70
+
71
+ it('flags a wrong notation tag', () => {
72
+ expect(codes({ ...valid(), notation: 'service' })).toContain('BSV-001');
73
+ expect(codes({ ...valid(), notation: 'business_service' })).toContain('BSV-001');
74
+ });
75
+
76
+ it('flags a non-canon zone', () => {
77
+ expect(codes({ ...valid(), zone: 'sandbox' })).toContain('BSV-001');
78
+ });
79
+
80
+ it('flags a missing zone', () => {
81
+ const a = valid();
82
+ delete a.zone;
83
+ expect(codes(a)).toContain('BSV-001');
84
+ });
85
+
86
+ it('flags a missing type', () => {
87
+ const a = valid();
88
+ delete a.type;
89
+ expect(codes(a)).toContain('BSV-001');
90
+ });
91
+
92
+ it('flags a missing valid_to key', () => {
93
+ const a = valid();
94
+ delete a.valid_to;
95
+ expect(codes(a)).toContain('BSV-001');
96
+ });
97
+
98
+ it('flags missing gate_checks', () => {
99
+ const a = valid();
100
+ delete a.gate_checks;
101
+ expect(codes(a)).toContain('BSV-001');
102
+ });
103
+
104
+ it('rejects a non-object', () => {
105
+ expect(codes(null)).toEqual(['BSV-001']);
106
+ expect(codes('string')).toEqual(['BSV-001']);
107
+ });
108
+ });
109
+
110
+ describe('validateBusinessService — BSV-002 (type enum)', () => {
111
+ it('flags a type outside the allowed enum', () => {
112
+ expect(codes({ ...valid(), type: 'public' })).toContain('BSV-002');
113
+ expect(codes({ ...valid(), type: 'platform' })).toContain('BSV-002');
114
+ });
115
+
116
+ it('accepts all valid type values', () => {
117
+ for (const t of ['internal', 'external', 'shared']) {
118
+ expect(codes({ ...valid(), type: t })).not.toContain('BSV-002');
119
+ }
120
+ });
121
+ });
122
+
123
+ describe('validateBusinessService — BSV-003 (offering_unit grammar)', () => {
124
+ it('flags an offering_unit that is not an ACTOR-… or ROLE-… id', () => {
125
+ expect(codes({ ...valid(), offering_unit: 'PRODUCT-PLATFORM-1' })).toContain('BSV-003');
126
+ expect(codes({ ...valid(), offering_unit: 'not-an-id' })).toContain('BSV-003');
127
+ expect(codes({ ...valid(), offering_unit: 'BUSINESS_SERVICE-CRM-1' })).toContain('BSV-003');
128
+ });
129
+
130
+ it('accepts a null or absent offering_unit without error', () => {
131
+ expect(codes({ ...valid(), offering_unit: undefined })).not.toContain('BSV-003');
132
+ const a = valid();
133
+ delete a.offering_unit;
134
+ expect(codes(a)).not.toContain('BSV-003');
135
+ });
136
+
137
+ it('accepts ACTOR-… and ROLE-… ids', () => {
138
+ expect(codes({ ...valid(), offering_unit: 'ACTOR-OPS-1' })).not.toContain('BSV-003');
139
+ expect(codes({ ...valid(), offering_unit: 'ROLE-SERVICE-OWNER-1' })).not.toContain('BSV-003');
140
+ });
141
+ });
142
+
143
+ describe('validateBusinessService — BSV-004 (capability grammar)', () => {
144
+ it('flags a capability that is not a CAPABILITY-… id', () => {
145
+ expect(codes({ ...valid(), capability: 'PRODUCT-MOBILE-1' })).toContain('BSV-004');
146
+ expect(codes({ ...valid(), capability: 'not-a-capability' })).toContain('BSV-004');
147
+ });
148
+
149
+ it('accepts a null or absent capability without error', () => {
150
+ expect(codes({ ...valid(), capability: undefined })).not.toContain('BSV-004');
151
+ const a = valid();
152
+ delete a.capability;
153
+ expect(codes(a)).not.toContain('BSV-004');
154
+ });
155
+
156
+ it('accepts a CAPABILITY-… id', () => {
157
+ expect(codes({ ...valid(), capability: 'CAPABILITY-V2.1' })).not.toContain('BSV-004');
158
+ expect(codes({ ...valid(), capability: 'CAPABILITY-H3.1' })).not.toContain('BSV-004');
159
+ });
160
+ });
@@ -0,0 +1,3 @@
1
+ export type { BusinessService, BusinessServiceType } from './types.js';
2
+ export { BUSINESS_SERVICE_TYPES } from './types.js';
3
+ export { validateBusinessService } from './validate.js';
@@ -0,0 +1,29 @@
1
+ // BUSINESS_SERVICE — externally visible behaviour primitive (ArchiMate Business Service).
2
+ // Schema: methodology notations/elements/25-business-services.md
3
+
4
+ import type { GateChecks } from '../requirement/types.js';
5
+
6
+ export const BUSINESS_SERVICE_TYPES = ['internal', 'external', 'shared'] as const;
7
+ export type BusinessServiceType = typeof BUSINESS_SERVICE_TYPES[number];
8
+
9
+ export interface BusinessService {
10
+ notation: 'business-service';
11
+ id: string;
12
+ name: string;
13
+ type: BusinessServiceType;
14
+ description?: string;
15
+ offering_unit?: string;
16
+ capability?: string;
17
+ owner_role?: string;
18
+ status?: string;
19
+
20
+ // Admission record (CONTRACT.md §6).
21
+ zone: 'canon';
22
+ admitted_at: string;
23
+ admitted_by: string;
24
+ gate_checks: GateChecks;
25
+
26
+ // Primitive lifecycle (CONTRACT.md §7).
27
+ valid_from: string;
28
+ valid_to: string | null;
29
+ }
@@ -0,0 +1,82 @@
1
+ // BUSINESS_SERVICE validator — methodology notations/elements/25-business-services.md §5.
2
+ //
3
+ // Implements BSV-001..004. The shared HDR-/LIFECYCLE- rules are
4
+ // cross-cutting concerns (CONTRACT.md §8) and out of scope for the
5
+ // single-artefact validator.
6
+
7
+ import type { ValidationError, ValidationWarning, ValidationResult } from '../validation-types.js';
8
+ import { isCanonicalIdOfType, typeOfId } from '../typed-id.js';
9
+ import { BUSINESS_SERVICE_TYPES } from './types.js';
10
+
11
+ const REQUIRED_STRING_FIELDS = [
12
+ 'name', 'admitted_at', 'admitted_by', 'valid_from',
13
+ ] as const;
14
+
15
+ const TYPES: readonly string[] = BUSINESS_SERVICE_TYPES;
16
+
17
+ export function validateBusinessService(input: unknown): ValidationResult {
18
+ const errors: ValidationError[] = [];
19
+ const warnings: ValidationWarning[] = [];
20
+
21
+ if (input === null || typeof input !== 'object' || Array.isArray(input)) {
22
+ errors.push({ code: 'BSV-001', message: 'Business service must be a YAML mapping.' });
23
+ return { valid: false, errors, warnings };
24
+ }
25
+ const a = input as Record<string, unknown>;
26
+
27
+ // BSV-001 — id grammar + notation tag + envelope.
28
+ if (!isCanonicalIdOfType(a.id, 'BUSINESS_SERVICE')) {
29
+ errors.push({ code: 'BSV-001', message: `id "${String(a.id)}" must match BUSINESS_SERVICE-[<middle>-]<INTEGER>.`, path: 'id' });
30
+ }
31
+ if (a.notation !== 'business-service') {
32
+ errors.push({ code: 'BSV-001', message: 'notation must be the fixed value "business-service".', path: 'notation' });
33
+ }
34
+ if (a.zone !== undefined && a.zone !== 'canon') {
35
+ errors.push({ code: 'BSV-001', message: 'zone must be "canon" for a BUSINESS_SERVICE.', path: 'zone' });
36
+ } else if (a.zone === undefined) {
37
+ errors.push({ code: 'BSV-001', message: 'zone is required.', path: 'zone' });
38
+ }
39
+ for (const field of REQUIRED_STRING_FIELDS) {
40
+ const v = a[field];
41
+ if (typeof v !== 'string' || v.trim() === '') {
42
+ errors.push({ code: 'BSV-001', message: `${field} is required.`, path: field });
43
+ }
44
+ }
45
+ if (a.gate_checks === null || typeof a.gate_checks !== 'object') {
46
+ errors.push({ code: 'BSV-001', message: 'gate_checks is required and must be a mapping.', path: 'gate_checks' });
47
+ }
48
+ if (!('valid_to' in a) || !(typeof a.valid_to === 'string' || a.valid_to === null)) {
49
+ errors.push({ code: 'BSV-001', message: 'valid_to is required (an ISO date string or null).', path: 'valid_to' });
50
+ }
51
+ if (typeof a.type !== 'string' || a.type.trim() === '') {
52
+ errors.push({ code: 'BSV-001', message: 'type is required.', path: 'type' });
53
+ } else if (!TYPES.includes(a.type)) {
54
+ // BSV-002 — type enum.
55
+ errors.push({ code: 'BSV-002', message: `type "${a.type}" must be one of ${TYPES.join(', ')}.`, path: 'type' });
56
+ }
57
+
58
+ // BSV-003 — offering_unit must resolve to ACTOR(business_unit) or ROLE when present.
59
+ if ('offering_unit' in a && a.offering_unit !== undefined && a.offering_unit !== null) {
60
+ const t = typeOfId(a.offering_unit);
61
+ if (t !== 'ACTOR' && t !== 'ROLE') {
62
+ errors.push({
63
+ code: 'BSV-003',
64
+ message: `offering_unit "${String(a.offering_unit)}" must be an ACTOR-… or ROLE-… canonical id.`,
65
+ path: 'offering_unit',
66
+ });
67
+ }
68
+ }
69
+
70
+ // BSV-004 — capability must resolve to CAPABILITY when present.
71
+ if ('capability' in a && a.capability !== undefined && a.capability !== null) {
72
+ if (typeOfId(a.capability) !== 'CAPABILITY') {
73
+ errors.push({
74
+ code: 'BSV-004',
75
+ message: `capability "${String(a.capability)}" must be a CAPABILITY-… canonical id.`,
76
+ path: 'capability',
77
+ });
78
+ }
79
+ }
80
+
81
+ return { valid: errors.length === 0, errors, warnings };
82
+ }
@@ -5,7 +5,7 @@ import yaml from 'js-yaml';
5
5
  import { parseCanonicalFGCA, parseCanonicalFGA } from '../parse-canonical.js';
6
6
 
7
7
  const VALID = {
8
- notation: 'fgca',
8
+ notation: 'dgca',
9
9
  spec_version: '0.1',
10
10
  id: 'FGCA-SAMPLE-1',
11
11
  name: 'Sample FGCA chain',
@@ -18,7 +18,7 @@ const VALID = {
18
18
  changes: [
19
19
  { id: 'CHANGE-1', name: 'Transformation', goals: ['GOAL-1'] },
20
20
  ],
21
- activities: [
21
+ actions: [
22
22
  { id: 'ACTIVITY-1', name: 'Workstream', changes: ['CHANGE-1'] },
23
23
  ],
24
24
  };
@@ -108,7 +108,7 @@ describe('parseCanonicalFGCA', () => {
108
108
  it('FGCA-010: rejects activity.changes[] referencing undefined change', () => {
109
109
  const r = parseCanonicalFGCA({
110
110
  ...VALID,
111
- activities: [{ id: 'ACTIVITY-1', name: 'A', changes: ['CHANGE-99'] }],
111
+ actions: [{ id: 'ACTIVITY-1', name: 'A', changes: ['CHANGE-99'] }],
112
112
  });
113
113
  expect(r.valid).toBe(false);
114
114
  expect(r.errors.some((e) => e.code === 'FGCA-010')).toBe(true);
@@ -135,7 +135,7 @@ describe('parseCanonicalFGCA', () => {
135
135
  const r = parseCanonicalFGCA({
136
136
  ...VALID,
137
137
  changes: [{ id: 'CHANGE-1', name: 'C', goals: ['GOAL-1'] }],
138
- activities: [
138
+ actions: [
139
139
  { id: 'ACTIVITY-1', name: 'A1', changes: ['CHANGE-1'] },
140
140
  { id: 'ACTIVITY-2', name: 'A2', changes: ['CHANGE-1'] },
141
141
  ],
@@ -180,13 +180,13 @@ describe('parseCanonicalFGCA — edge-driving fields populated', () => {
180
180
 
181
181
  describe('parseCanonicalFGA', () => {
182
182
  const VALID_FGA = {
183
- notation: 'fga',
183
+ notation: 'dga',
184
184
  spec_version: '0.1',
185
185
  id: 'FGA-SAMPLE-1',
186
186
  name: 'Sample FGA chain',
187
187
  factors: [{ id: 'FACTOR-1', name: 'Driver' }],
188
188
  goals: [{ id: 'GOAL-1', name: 'Outcome', factors: ['FACTOR-1'] }],
189
- activities: [{ id: 'ACTIVITY-1', name: 'Workstream', goals: ['GOAL-1'] }],
189
+ actions: [{ id: 'ACTIVITY-1', name: 'Workstream', goals: ['GOAL-1'] }],
190
190
  };
191
191
 
192
192
  it('accepts a valid canonical FGA document (no changes layer)', () => {
@@ -229,12 +229,12 @@ describe('parseCanonicalFGA', () => {
229
229
  });
230
230
 
231
231
  describe('parseCanonicalFGCA — example file regression', () => {
232
- const EXAMPLES_DIR = path.resolve(process.cwd(), '..', '..', 'tests', 'fixtures', 'notation-corpus', 'fgca');
232
+ const EXAMPLES_DIR = path.resolve(process.cwd(), '..', '..', 'tests', 'fixtures', 'notation-corpus', 'dgca');
233
233
  const files = fs.existsSync(EXAMPLES_DIR)
234
234
  ? fs.readdirSync(EXAMPLES_DIR).filter((f) => f.endsWith('.transitrix.yaml'))
235
235
  : [];
236
236
  for (const file of files) {
237
- it(`accepts tests/fixtures/notation-corpus/fgca/${file}`, () => {
237
+ it(`accepts tests/fixtures/notation-corpus/dgca/${file}`, () => {
238
238
  const text = fs.readFileSync(path.join(EXAMPLES_DIR, file), 'utf8');
239
239
  const parsedYaml = yaml.load(text);
240
240
  const r = parseCanonicalFGCA(parsedYaml);
@@ -245,12 +245,12 @@ describe('parseCanonicalFGCA — example file regression', () => {
245
245
  });
246
246
 
247
247
  describe('parseCanonicalFGA — example file regression', () => {
248
- const EXAMPLES_DIR = path.resolve(process.cwd(), '..', '..', 'tests', 'fixtures', 'notation-corpus', 'fga');
248
+ const EXAMPLES_DIR = path.resolve(process.cwd(), '..', '..', 'tests', 'fixtures', 'notation-corpus', 'dga');
249
249
  const files = fs.existsSync(EXAMPLES_DIR)
250
250
  ? fs.readdirSync(EXAMPLES_DIR).filter((f) => f.endsWith('.transitrix.yaml'))
251
251
  : [];
252
252
  for (const file of files) {
253
- it(`accepts tests/fixtures/notation-corpus/fga/${file} and resolves every activity to a goal`, () => {
253
+ it(`accepts tests/fixtures/notation-corpus/dga/${file} and resolves every activity to a goal`, () => {
254
254
  const text = fs.readFileSync(path.join(EXAMPLES_DIR, file), 'utf8');
255
255
  const r = parseCanonicalFGA(yaml.load(text));
256
256
  expect(r.errors).toEqual([]);
@@ -26,7 +26,7 @@ const ELEMENTS = [
26
26
  const SOURCES = { elements: ELEMENTS, relations: [] };
27
27
 
28
28
  const VIEW_DOC = {
29
- notation: 'fgca',
29
+ notation: 'dgca',
30
30
  id: 'FGCA-STRAT-1',
31
31
  name: 'Strategy 2026 — FGCA chain',
32
32
  spec_version: '0.1',
@@ -47,7 +47,7 @@ describe('isFGCAViewDoc', () => {
47
47
 
48
48
  it('returns false for an inline doc with factors/goals arrays', () => {
49
49
  expect(isFGCAViewDoc({
50
- notation: 'fgca', id: 'FGCA-1', name: 'x',
50
+ notation: 'dgca', id: 'FGCA-1', name: 'x',
51
51
  factors: [], goals: [], changes: [], activities: [],
52
52
  })).toBe(false);
53
53
  });
@@ -66,7 +66,7 @@ describe('resolveFGCA — goals.filter=all', () => {
66
66
  const doc = resolveFGCA(VIEW_DOC, SOURCES);
67
67
  expect((doc['goals'] as unknown[]).length).toBe(3);
68
68
  expect((doc['changes'] as unknown[]).length).toBe(3);
69
- expect((doc['activities'] as unknown[]).length).toBe(5);
69
+ expect((doc['actions'] as unknown[]).length).toBe(5);
70
70
  });
71
71
 
72
72
  it('derives factors from all goal.factors[] refs', () => {
@@ -90,7 +90,7 @@ describe('resolveFGCA — goals.filter=all', () => {
90
90
 
91
91
  it('carries view doc metadata (id, name, spec_version)', () => {
92
92
  const doc = resolveFGCA(VIEW_DOC, SOURCES);
93
- expect(doc['notation']).toBe('fgca');
93
+ expect(doc['notation']).toBe('dgca');
94
94
  expect(doc['id']).toBe('FGCA-STRAT-1');
95
95
  expect(doc['name']).toBe('Strategy 2026 — FGCA chain');
96
96
  expect(doc['spec_version']).toBe('0.1');
@@ -110,7 +110,7 @@ describe('resolveFGCA — goals.filter=ids', () => {
110
110
  expect(goalIds).toEqual(['GOAL-1']);
111
111
  const changeIds = (doc['changes'] as Array<{ id: string }>).map((c) => c.id);
112
112
  expect(changeIds).toEqual(['CHANGE-1']);
113
- const actIds = (doc['activities'] as Array<{ id: string }>).map((a) => a.id).sort();
113
+ const actIds = (doc['actions'] as Array<{ id: string }>).map((a) => a.id).sort();
114
114
  expect(actIds).toEqual(['ACTIVITY-1', 'ACTIVITY-2']);
115
115
  const r = parseCanonicalFGCA(doc);
116
116
  expect(r.valid).toBe(true);
@@ -159,7 +159,7 @@ describe('resolveFGCA — empty sources', () => {
159
159
  expect((doc['goals'] as unknown[]).length).toBe(0);
160
160
  expect((doc['factors'] as unknown[]).length).toBe(0);
161
161
  expect((doc['changes'] as unknown[]).length).toBe(0);
162
- expect((doc['activities'] as unknown[]).length).toBe(0);
162
+ expect((doc['actions'] as unknown[]).length).toBe(0);
163
163
  });
164
164
 
165
165
  it('returns empty arrays for a non-object viewDoc', () => {
@@ -172,7 +172,7 @@ describe('resolveFGCA — empty sources', () => {
172
172
 
173
173
  describe('resolveFGCA — file-based regression against canon fixture', () => {
174
174
  const CANON_ROOT = path.resolve(
175
- process.cwd(), '..', '..', 'tests', 'fixtures', 'notation-corpus', 'fgca', 'canon',
175
+ process.cwd(), '..', '..', 'tests', 'fixtures', 'notation-corpus', 'dgca', 'canon',
176
176
  );
177
177
  const VIEW_DIR = path.join(CANON_ROOT, 'views');
178
178
  const ELEM_DIR = path.join(CANON_ROOT, 'elements');
@@ -2,11 +2,11 @@ import { describe, it, expect } from 'vitest';
2
2
  import { validateFGCADoc } from '../validate.js';
3
3
 
4
4
  const VALID_DOC = {
5
- notation: 'fgca',
5
+ notation: 'dgca',
6
6
  factors: [{ id: 1, name: 'Market pressure' }],
7
7
  goals: [{ id: 1, name: 'Grow revenue', factor: [{ id: 1 }] }],
8
8
  changes: [{ id: 1, name: 'Launch product', goal_id: 1, activity_ids: [1] }],
9
- activities: [{ id: 1, name: 'Market research', goal_id: 1 }],
9
+ actions: [{ id: 1, name: 'Market research', goal_id: 1 }],
10
10
  };
11
11
 
12
12
  describe('validateFGCADoc', () => {
@@ -37,28 +37,28 @@ describe('validateFGCADoc', () => {
37
37
 
38
38
  it('errors when factors array is missing', () => {
39
39
  const { factors: _, ...without } = VALID_DOC;
40
- const result = validateFGCADoc({ ...without, notation: 'fgca' });
40
+ const result = validateFGCADoc({ ...without, notation: 'dgca' });
41
41
  expect(result.valid).toBe(false);
42
42
  expect(result.errors.some(e => e.path === 'factors')).toBe(true);
43
43
  });
44
44
 
45
45
  it('errors when goals array is missing', () => {
46
46
  const { goals: _, ...without } = VALID_DOC;
47
- const result = validateFGCADoc({ ...without, notation: 'fgca' });
47
+ const result = validateFGCADoc({ ...without, notation: 'dgca' });
48
48
  expect(result.valid).toBe(false);
49
49
  expect(result.errors.some(e => e.path === 'goals')).toBe(true);
50
50
  });
51
51
 
52
52
  it('errors when changes array is missing', () => {
53
53
  const { changes: _, ...without } = VALID_DOC;
54
- const result = validateFGCADoc({ ...without, notation: 'fgca' });
54
+ const result = validateFGCADoc({ ...without, notation: 'dgca' });
55
55
  expect(result.valid).toBe(false);
56
56
  expect(result.errors.some(e => e.path === 'changes')).toBe(true);
57
57
  });
58
58
 
59
59
  it('errors when actions array is missing', () => {
60
- const { activities: _, ...without } = VALID_DOC;
61
- const result = validateFGCADoc({ ...without, notation: 'fgca' });
60
+ const { actions: _, ...without } = VALID_DOC;
61
+ const result = validateFGCADoc({ ...without, notation: 'dgca' });
62
62
  expect(result.valid).toBe(false);
63
63
  expect(result.errors.some(e => e.path === 'actions')).toBe(true);
64
64
  });
@@ -104,11 +104,11 @@ describe('validateFGCADoc', () => {
104
104
 
105
105
  it('accepts a document with empty arrays', () => {
106
106
  const result = validateFGCADoc({
107
- notation: 'fgca',
107
+ notation: 'dgca',
108
108
  factors: [],
109
109
  goals: [],
110
110
  changes: [],
111
- activities: [],
111
+ actions: [],
112
112
  });
113
113
  expect(result.valid).toBe(true);
114
114
  });
@@ -134,8 +134,8 @@ describe('validateFGCADoc', () => {
134
134
  expect(r.valid).toBe(false);
135
135
  expect(r.errors.some(e => e.code === 'SCHEMA_INVALID')).toBe(true);
136
136
  });
137
- it('[blocker] tolerates a null element in activities[] without throwing', () => {
138
- const r = validateFGCADoc({ ...VALID_DOC, activities: [null] });
137
+ it('[blocker] tolerates a null element in actions[] without throwing', () => {
138
+ const r = validateFGCADoc({ ...VALID_DOC, actions: [null] });
139
139
  expect(r.valid).toBe(false);
140
140
  expect(r.errors.some(e => e.code === 'SCHEMA_INVALID')).toBe(true);
141
141
  });
@@ -82,21 +82,18 @@ export function parseCanonicalFGCA(input: unknown): CanonicalFGCAResult {
82
82
 
83
83
  const raw = input as Record<string, unknown>;
84
84
 
85
- if ('notation' in raw && raw['notation'] !== 'fgca' && raw['notation'] !== 'dgca') {
85
+ if ('notation' in raw && raw['notation'] !== 'dgca') {
86
86
  return {
87
87
  valid: false,
88
88
  errors: [
89
89
  {
90
90
  code: 'FGCA-001',
91
- message: `notation must be "dgca" (or legacy "fgca"), got "${String(raw['notation'])}"`,
91
+ message: `notation must be "dgca", got "${String(raw['notation'])}"`,
92
92
  },
93
93
  ],
94
94
  warnings,
95
95
  };
96
96
  }
97
- if ('notation' in raw && raw['notation'] === 'fgca') {
98
- warnings.push({ code: 'FGCA-001', message: 'notation "fgca" is deprecated — rename to "dgca"' });
99
- }
100
97
 
101
98
  // FGCA-002 — document id (optional in v1.4 per the canonical spec? — spec
102
99
  // says required; absence is FGCA-002).
@@ -118,10 +115,9 @@ export function parseCanonicalFGCA(input: unknown): CanonicalFGCAResult {
118
115
  const factorsRaw = asObjectArray(raw['factors']);
119
116
  const goalsRaw = asObjectArray(raw['goals']);
120
117
  const changesRaw = asObjectArray(raw['changes']);
121
- // Accept canonical `actions:` or deprecated `activities:`; warn on the latter.
122
- const activitiesRaw = asObjectArray(raw['actions']) ?? asObjectArray(raw['activities']);
123
- if ('activities' in raw && !('actions' in raw)) {
124
- warnings.push({ code: 'FGCA-001', message: '"activities" key is deprecated — rename to "actions"' });
118
+ const activitiesRaw = asObjectArray(raw['actions']);
119
+ if (activitiesRaw === null && 'activities' in raw) {
120
+ errors.push({ code: 'FGCA-004', message: '"activities" key is not accepted — rename to "actions"', path: 'actions' });
125
121
  }
126
122
 
127
123
  if (factorsRaw === null) errors.push({ code: 'FGCA-004', message: 'factors must be an array', path: 'factors' });
@@ -348,10 +344,10 @@ export function parseCanonicalFGA(input: unknown): CanonicalFGAResult {
348
344
  return { valid: false, errors: [{ code: 'FGA-001', message: 'document root is not an object' }], warnings: [] };
349
345
  }
350
346
  const raw = input as Record<string, unknown>;
351
- if ('notation' in raw && raw['notation'] !== 'fga' && raw['notation'] !== 'dga') {
347
+ if ('notation' in raw && raw['notation'] !== 'dga') {
352
348
  return {
353
349
  valid: false,
354
- errors: [{ code: 'FGA-001', message: `notation must be "dga" (or legacy "fga"), got "${String(raw['notation'])}"` }],
350
+ errors: [{ code: 'FGA-001', message: `notation must be "dga", got "${String(raw['notation'])}"` }],
355
351
  warnings: [],
356
352
  };
357
353
  }
@@ -3,7 +3,7 @@
3
3
  * view_config projection and a canon element/relation store (VP-3).
4
4
  *
5
5
  * Output shape matches the flat form that `parseCanonicalFGCA` expects:
6
- * top-level `factors[]`, `goals[]`, `changes[]`, `activities[]` with typed
6
+ * top-level `factors[]`, `goals[]`, `changes[]`, `actions[]` with typed
7
7
  * string IDs. Filesystem-free — callable from unit tests without vscode.
8
8
  */
9
9
 
@@ -69,7 +69,7 @@ export function isFGCAViewDoc(parsed: unknown): boolean {
69
69
  * to pass to `parseCanonicalFGCA`.
70
70
  *
71
71
  * Cross-reference fields (`goal.factors[]`, `change.goals[]`,
72
- * `activity.changes[]`) are carried from the canon elements unchanged; the
72
+ * `action.changes[]`) are carried from the canon elements unchanged; the
73
73
  * resolver decides which elements to include, not how they are shaped.
74
74
  */
75
75
  export function resolveFGCA(
@@ -77,7 +77,7 @@ export function resolveFGCA(
77
77
  sources: FGCACanonSources,
78
78
  ): Record<string, unknown> {
79
79
  if (!isObject(viewDoc)) {
80
- return { notation: 'fgca', factors: [], goals: [], changes: [], activities: [] };
80
+ return { notation: 'dgca', factors: [], goals: [], changes: [], actions: [] };
81
81
  }
82
82
 
83
83
  const vc = isObject(viewDoc['view_config']) ? viewDoc['view_config'] : {};
@@ -147,13 +147,13 @@ export function resolveFGCA(
147
147
  }
148
148
 
149
149
  return {
150
- notation: 'fgca',
150
+ notation: 'dgca',
151
151
  id: viewDoc['id'],
152
152
  name: viewDoc['name'],
153
153
  spec_version: viewDoc['spec_version'],
154
154
  factors: selectedFactors,
155
155
  goals: selectedGoals,
156
156
  changes: selectedChanges,
157
- activities: selectedActivities,
157
+ actions: selectedActivities,
158
158
  };
159
159
  }