@transitrix/diagrams 1.3.0 → 1.5.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 (129) hide show
  1. package/dist/__tests__/yaml-normalize.test.js +9 -9
  2. package/dist/activities/__tests__/gantt.test.js +39 -0
  3. package/dist/activities/__tests__/validate.test.js +77 -74
  4. package/dist/activities/validate.js +5 -12
  5. package/dist/activity-card/__tests__/example.test.js +2 -2
  6. package/dist/activity-card/__tests__/validate.test.js +29 -18
  7. package/dist/activity-card/validate.js +3 -10
  8. package/dist/actor/__tests__/validate.test.js +6 -0
  9. package/dist/actor/types.d.ts +1 -1
  10. package/dist/actor/types.js +2 -0
  11. package/dist/applications/types.d.ts +7 -0
  12. package/dist/applications/validate.js +18 -0
  13. package/dist/blocks/__tests__/layout.test.js +1 -1
  14. package/dist/blocks/layout.js +1 -1
  15. package/dist/business-service/__tests__/validate.test.d.ts +1 -0
  16. package/dist/business-service/__tests__/validate.test.js +135 -0
  17. package/dist/business-service/index.d.ts +3 -0
  18. package/dist/business-service/index.js +2 -0
  19. package/dist/business-service/types.d.ts +20 -0
  20. package/dist/business-service/types.js +3 -0
  21. package/dist/business-service/validate.d.ts +2 -0
  22. package/dist/business-service/validate.js +74 -0
  23. package/dist/fgca/__tests__/parse-canonical.test.js +10 -10
  24. package/dist/fgca/__tests__/resolver.test.js +7 -7
  25. package/dist/fgca/__tests__/validate.test.js +11 -11
  26. package/dist/fgca/parse-canonical.js +7 -11
  27. package/dist/fgca/resolver.d.ts +2 -2
  28. package/dist/fgca/resolver.js +5 -5
  29. package/dist/fgca/validate.js +4 -13
  30. package/dist/index.d.ts +5 -0
  31. package/dist/index.js +5 -0
  32. package/dist/integration/__tests__/validate.test.d.ts +1 -0
  33. package/dist/integration/__tests__/validate.test.js +169 -0
  34. package/dist/integration/index.d.ts +2 -0
  35. package/dist/integration/index.js +1 -0
  36. package/dist/integration/types.d.ts +27 -0
  37. package/dist/integration/types.js +5 -0
  38. package/dist/integration/validate.d.ts +2 -0
  39. package/dist/integration/validate.js +94 -0
  40. package/dist/location/__tests__/validate.test.d.ts +1 -0
  41. package/dist/location/__tests__/validate.test.js +107 -0
  42. package/dist/location/index.d.ts +3 -0
  43. package/dist/location/index.js +2 -0
  44. package/dist/location/types.d.ts +20 -0
  45. package/dist/location/types.js +3 -0
  46. package/dist/location/validate.d.ts +2 -0
  47. package/dist/location/validate.js +63 -0
  48. package/dist/node/__tests__/validate.test.d.ts +1 -0
  49. package/dist/node/__tests__/validate.test.js +100 -0
  50. package/dist/node/index.d.ts +2 -0
  51. package/dist/node/index.js +1 -0
  52. package/dist/node/types.d.ts +18 -0
  53. package/dist/node/types.js +3 -0
  54. package/dist/node/validate.d.ts +2 -0
  55. package/dist/node/validate.js +53 -0
  56. package/dist/repo-validate/__tests__/validate-repo.test.js +444 -0
  57. package/dist/repo-validate/validate-repo.js +224 -10
  58. package/dist/technology-service/__tests__/validate.test.d.ts +1 -0
  59. package/dist/technology-service/__tests__/validate.test.js +121 -0
  60. package/dist/technology-service/index.d.ts +2 -0
  61. package/dist/technology-service/index.js +1 -0
  62. package/dist/technology-service/types.d.ts +18 -0
  63. package/dist/technology-service/types.js +3 -0
  64. package/dist/technology-service/validate.d.ts +2 -0
  65. package/dist/technology-service/validate.js +64 -0
  66. package/dist/webview/__tests__/entry-notations.test.js +2 -4
  67. package/dist/webview/__tests__/render-activities.test.d.ts +1 -0
  68. package/dist/webview/__tests__/render-activities.test.js +91 -0
  69. package/dist/webview/__tests__/render-blocks.test.d.ts +1 -0
  70. package/dist/webview/__tests__/render-blocks.test.js +178 -0
  71. package/dist/webview/entry.d.ts +1 -1
  72. package/dist/webview/entry.js +8 -15
  73. package/dist/webview/notation-style.d.ts +11 -0
  74. package/dist/webview/notation-style.js +11 -0
  75. package/dist/webview/render-activities.d.ts +12 -0
  76. package/dist/webview/render-activities.js +9 -3
  77. package/dist/webview/render-blocks.js +102 -19
  78. package/dist/webview/render-goals.js +2 -1
  79. package/package.json +1 -1
  80. package/src/__tests__/yaml-normalize.test.ts +11 -11
  81. package/src/activities/__tests__/gantt.test.ts +45 -0
  82. package/src/activities/__tests__/validate.test.ts +77 -74
  83. package/src/activities/validate.ts +5 -12
  84. package/src/activity-card/__tests__/example.test.ts +2 -2
  85. package/src/activity-card/__tests__/validate.test.ts +30 -18
  86. package/src/activity-card/validate.ts +3 -9
  87. package/src/actor/__tests__/validate.test.ts +8 -0
  88. package/src/actor/types.ts +2 -0
  89. package/src/applications/types.ts +8 -0
  90. package/src/applications/validate.ts +20 -0
  91. package/src/blocks/__tests__/layout.test.ts +1 -1
  92. package/src/blocks/layout.ts +1 -1
  93. package/src/business-service/__tests__/validate.test.ts +160 -0
  94. package/src/business-service/index.ts +3 -0
  95. package/src/business-service/types.ts +29 -0
  96. package/src/business-service/validate.ts +82 -0
  97. package/src/fgca/__tests__/parse-canonical.test.ts +10 -10
  98. package/src/fgca/__tests__/resolver.test.ts +7 -7
  99. package/src/fgca/__tests__/validate.test.ts +11 -11
  100. package/src/fgca/parse-canonical.ts +7 -11
  101. package/src/fgca/resolver.ts +5 -5
  102. package/src/fgca/validate.ts +4 -12
  103. package/src/index.ts +5 -0
  104. package/src/integration/__tests__/validate.test.ts +203 -0
  105. package/src/integration/index.ts +2 -0
  106. package/src/integration/types.ts +38 -0
  107. package/src/integration/validate.ts +104 -0
  108. package/src/location/__tests__/validate.test.ts +127 -0
  109. package/src/location/index.ts +3 -0
  110. package/src/location/types.ts +29 -0
  111. package/src/location/validate.ts +70 -0
  112. package/src/node/__tests__/validate.test.ts +118 -0
  113. package/src/node/index.ts +2 -0
  114. package/src/node/types.ts +27 -0
  115. package/src/node/validate.ts +59 -0
  116. package/src/repo-validate/__tests__/validate-repo.test.ts +550 -0
  117. package/src/repo-validate/validate-repo.ts +233 -10
  118. package/src/technology-service/__tests__/validate.test.ts +143 -0
  119. package/src/technology-service/index.ts +2 -0
  120. package/src/technology-service/types.ts +27 -0
  121. package/src/technology-service/validate.ts +71 -0
  122. package/src/webview/__tests__/entry-notations.test.ts +2 -4
  123. package/src/webview/__tests__/render-activities.test.ts +100 -0
  124. package/src/webview/__tests__/render-blocks.test.ts +209 -0
  125. package/src/webview/entry.ts +10 -19
  126. package/src/webview/notation-style.ts +12 -0
  127. package/src/webview/render-activities.ts +29 -4
  128. package/src/webview/render-blocks.ts +106 -21
  129. package/src/webview/render-goals.ts +2 -1
@@ -4,43 +4,41 @@ import * as path from 'node:path';
4
4
  import yaml from 'js-yaml';
5
5
  import { validateActivities } from '../validate.js';
6
6
 
7
- const EXAMPLES_DIR = path.resolve(process.cwd(), '..', '..', 'tests', 'fixtures', 'notation-corpus', 'activities');
7
+ const EXAMPLES_DIR = path.resolve(process.cwd(), '..', '..', 'tests', 'fixtures', 'notation-corpus', 'action');
8
8
 
9
9
  const minimalValid = {
10
- notation: 'activities',
11
- activities: [
10
+ notation: 'action',
11
+ actions: [
12
12
  { id: 'A-001', name: 'Start', duration: 3 },
13
13
  { id: 'A-002', name: 'End', duration: 2, predecessors: ['A-001'] },
14
14
  ],
15
15
  };
16
16
 
17
17
  describe('validateActivities', () => {
18
- it('ACT-001 — accepts notation: activities (deprecated)', () => {
18
+ it('ACT-001 — accepts notation: action (canonical)', () => {
19
19
  const r = validateActivities(minimalValid);
20
20
  expect(r.valid).toBe(true);
21
- // deprecated notation emits a warning, not an error
22
- expect(r.warnings.some(w => w.code === 'DEPRECATED_NOTATION')).toBe(true);
21
+ expect(r.warnings.some(w => w.code === 'DEPRECATED_NOTATION')).toBe(false);
23
22
  });
24
23
 
25
- it('ACT-001 — accepts notation: action (canonical)', () => {
24
+ it('ACT-001 — rejects notation: activities (no longer accepted)', () => {
26
25
  const r = validateActivities({
27
- notation: 'action',
28
- actions: [
29
- { id: 'ACTION-001', name: 'Start', duration: 3 },
30
- { id: 'ACTION-002', name: 'End', duration: 2, predecessors: ['ACTION-001'] },
26
+ notation: 'activities',
27
+ activities: [
28
+ { id: 'A-001', name: 'Start', duration: 3 },
31
29
  ],
32
30
  });
33
- expect(r.valid).toBe(true);
34
- expect(r.warnings.some(w => w.code === 'DEPRECATED_NOTATION')).toBe(false);
31
+ expect(r.valid).toBe(false);
32
+ expect(r.errors.some(e => e.code === 'ACT-001')).toBe(true);
35
33
  });
36
34
 
37
- it('ACT-001 — accepts notation: action with deprecated activities: key (emits field warning)', () => {
35
+ it('ACT-001 — rejects activities: root key (only actions: accepted)', () => {
38
36
  const r = validateActivities({
39
37
  notation: 'action',
40
38
  activities: [{ id: 'ACTION-001', name: 'Foo', duration: 1 }],
41
39
  });
42
- expect(r.valid).toBe(true);
43
- expect(r.warnings.some(w => w.code === 'DEPRECATED_FIELD')).toBe(true);
40
+ expect(r.valid).toBe(false);
41
+ expect(r.errors.some(e => e.code === 'SCHEMA_INVALID')).toBe(true);
44
42
  });
45
43
 
46
44
  it('ACT-001 — normalises action_type to activity_type for downstream layout', () => {
@@ -69,8 +67,8 @@ describe('validateActivities', () => {
69
67
 
70
68
  it('ACT-002 — rejects activity with missing id', () => {
71
69
  const r = validateActivities({
72
- notation: 'activities',
73
- activities: [{ name: 'No ID', duration: 1 }],
70
+ notation: 'action',
71
+ actions: [{ name: 'No ID', duration: 1 }],
74
72
  });
75
73
  expect(r.valid).toBe(false);
76
74
  expect(r.errors.some(e => e.code === 'ACT-002')).toBe(true);
@@ -78,8 +76,8 @@ describe('validateActivities', () => {
78
76
 
79
77
  it('ACT-002 — rejects activity with empty id', () => {
80
78
  const r = validateActivities({
81
- notation: 'activities',
82
- activities: [{ id: ' ', name: 'Empty ID', duration: 1 }],
79
+ notation: 'action',
80
+ actions: [{ id: ' ', name: 'Empty ID', duration: 1 }],
83
81
  });
84
82
  expect(r.valid).toBe(false);
85
83
  expect(r.errors.some(e => e.code === 'ACT-002')).toBe(true);
@@ -87,8 +85,8 @@ describe('validateActivities', () => {
87
85
 
88
86
  it('ACT-003 — rejects activity with missing name', () => {
89
87
  const r = validateActivities({
90
- notation: 'activities',
91
- activities: [{ id: 'A-001', duration: 1 }],
88
+ notation: 'action',
89
+ actions: [{ id: 'A-001', duration: 1 }],
92
90
  });
93
91
  expect(r.valid).toBe(false);
94
92
  expect(r.errors.some(e => e.code === 'ACT-003')).toBe(true);
@@ -96,8 +94,8 @@ describe('validateActivities', () => {
96
94
 
97
95
  it('ACT-004 — rejects duplicate ids', () => {
98
96
  const r = validateActivities({
99
- notation: 'activities',
100
- activities: [
97
+ notation: 'action',
98
+ actions: [
101
99
  { id: 'A-001', name: 'First', duration: 1 },
102
100
  { id: 'A-001', name: 'Duplicate', duration: 2 },
103
101
  ],
@@ -108,8 +106,8 @@ describe('validateActivities', () => {
108
106
 
109
107
  it('ACT-005 — rejects unknown predecessor reference', () => {
110
108
  const r = validateActivities({
111
- notation: 'activities',
112
- activities: [{ id: 'A-001', name: 'Task', duration: 1, predecessors: ['MISSING'] }],
109
+ notation: 'action',
110
+ actions: [{ id: 'A-001', name: 'Task', duration: 1, predecessors: ['MISSING'] }],
113
111
  });
114
112
  expect(r.valid).toBe(false);
115
113
  expect(r.errors.some(e => e.code === 'ACT-005')).toBe(true);
@@ -123,8 +121,8 @@ describe('validateActivities', () => {
123
121
 
124
122
  it('ACT-006 — rejects cyclic dependency', () => {
125
123
  const r = validateActivities({
126
- notation: 'activities',
127
- activities: [
124
+ notation: 'action',
125
+ actions: [
128
126
  { id: 'A-001', name: 'Task A', duration: 1, predecessors: ['A-002'] },
129
127
  { id: 'A-002', name: 'Task B', duration: 1, predecessors: ['A-001'] },
130
128
  ],
@@ -135,8 +133,8 @@ describe('validateActivities', () => {
135
133
 
136
134
  it('ACT-007 — rejects self-loop', () => {
137
135
  const r = validateActivities({
138
- notation: 'activities',
139
- activities: [{ id: 'A-001', name: 'Task', duration: 1, predecessors: ['A-001'] }],
136
+ notation: 'action',
137
+ actions: [{ id: 'A-001', name: 'Task', duration: 1, predecessors: ['A-001'] }],
140
138
  });
141
139
  expect(r.valid).toBe(false);
142
140
  expect(r.errors.some(e => e.code === 'ACT-007')).toBe(true);
@@ -144,8 +142,8 @@ describe('validateActivities', () => {
144
142
 
145
143
  it('ACT-008 — rejects end_date before start_date', () => {
146
144
  const r = validateActivities({
147
- notation: 'activities',
148
- activities: [
145
+ notation: 'action',
146
+ actions: [
149
147
  { id: 'A-001', name: 'Task', duration: 5, start_date: '2026-06-10', end_date: '2026-06-01' },
150
148
  ],
151
149
  });
@@ -155,8 +153,8 @@ describe('validateActivities', () => {
155
153
 
156
154
  it('ACT-008 — accepts end_date equal to start_date', () => {
157
155
  const r = validateActivities({
158
- notation: 'activities',
159
- activities: [
156
+ notation: 'action',
157
+ actions: [
160
158
  { id: 'A-001', name: 'Task', duration: 0, start_date: '2026-06-01', end_date: '2026-06-01' },
161
159
  ],
162
160
  });
@@ -165,8 +163,8 @@ describe('validateActivities', () => {
165
163
 
166
164
  it('ACT-008 — rejects non-ISO start_date format', () => {
167
165
  const r = validateActivities({
168
- notation: 'activities',
169
- activities: [
166
+ notation: 'action',
167
+ actions: [
170
168
  { id: 'A-001', name: 'Task', duration: 5, start_date: 'June 1 2026' },
171
169
  ],
172
170
  });
@@ -176,8 +174,8 @@ describe('validateActivities', () => {
176
174
 
177
175
  it('ACT-008 — rejects non-ISO end_date format', () => {
178
176
  const r = validateActivities({
179
- notation: 'activities',
180
- activities: [
177
+ notation: 'action',
178
+ actions: [
181
179
  { id: 'A-001', name: 'Task', duration: 5, end_date: '2026/06/30' },
182
180
  ],
183
181
  });
@@ -191,8 +189,8 @@ describe('validateActivities', () => {
191
189
  // on top would be misleading because the lexicographic compare is
192
190
  // meaningless on non-ISO strings.
193
191
  const r = validateActivities({
194
- notation: 'activities',
195
- activities: [
192
+ notation: 'action',
193
+ actions: [
196
194
  { id: 'A-001', name: 'Task', duration: 5, start_date: 'tomorrow', end_date: 'yesterday' },
197
195
  ],
198
196
  });
@@ -202,8 +200,8 @@ describe('validateActivities', () => {
202
200
 
203
201
  it('ACT-009 — rejects negative duration', () => {
204
202
  const r = validateActivities({
205
- notation: 'activities',
206
- activities: [{ id: 'A-001', name: 'Task', duration: -1 }],
203
+ notation: 'action',
204
+ actions: [{ id: 'A-001', name: 'Task', duration: -1 }],
207
205
  });
208
206
  expect(r.valid).toBe(false);
209
207
  expect(r.errors.some(e => e.code === 'ACT-009')).toBe(true);
@@ -211,16 +209,16 @@ describe('validateActivities', () => {
211
209
 
212
210
  it('ACT-009 — accepts zero duration', () => {
213
211
  const r = validateActivities({
214
- notation: 'activities',
215
- activities: [{ id: 'A-001', name: 'Milestone', duration: 0 }],
212
+ notation: 'action',
213
+ actions: [{ id: 'A-001', name: 'Milestone', duration: 0 }],
216
214
  });
217
215
  expect(r.errors.some(e => e.code === 'ACT-009')).toBe(false);
218
216
  });
219
217
 
220
218
  it('ACT-010 — rejects singular "goal:" field', () => {
221
219
  const r = validateActivities({
222
- notation: 'activities',
223
- activities: [{ id: 'A-001', name: 'Task', duration: 1, goal: 'GOAL-001' } as any],
220
+ notation: 'action',
221
+ actions: [{ id: 'A-001', name: 'Task', duration: 1, goal: 'GOAL-001' } as any],
224
222
  });
225
223
  expect(r.valid).toBe(false);
226
224
  expect(r.errors.some(e => e.code === 'ACT-010')).toBe(true);
@@ -228,8 +226,8 @@ describe('validateActivities', () => {
228
226
 
229
227
  it('ACT-010 — rejects singular "predecessor:" field', () => {
230
228
  const r = validateActivities({
231
- notation: 'activities',
232
- activities: [
229
+ notation: 'action',
230
+ actions: [
233
231
  { id: 'A-001', name: 'First', duration: 1 },
234
232
  { id: 'A-002', name: 'Task', duration: 1, predecessor: 'A-001' } as any,
235
233
  ],
@@ -240,8 +238,8 @@ describe('validateActivities', () => {
240
238
 
241
239
  it('ACT-010 — rejects singular "tag:" field', () => {
242
240
  const r = validateActivities({
243
- notation: 'activities',
244
- activities: [{ id: 'A-001', name: 'Task', duration: 1, tag: 'q3' } as any],
241
+ notation: 'action',
242
+ actions: [{ id: 'A-001', name: 'Task', duration: 1, tag: 'q3' } as any],
245
243
  });
246
244
  expect(r.valid).toBe(false);
247
245
  expect(r.errors.some(e => e.code === 'ACT-010')).toBe(true);
@@ -249,8 +247,8 @@ describe('validateActivities', () => {
249
247
 
250
248
  it('ACT-011 — warns when duration is absent', () => {
251
249
  const r = validateActivities({
252
- notation: 'activities',
253
- activities: [{ id: 'A-001', name: 'No duration' }],
250
+ notation: 'action',
251
+ actions: [{ id: 'A-001', name: 'No duration' }],
254
252
  });
255
253
  expect(r.valid).toBe(true);
256
254
  expect(r.warnings.some(w => w.code === 'ACT-011')).toBe(true);
@@ -258,8 +256,8 @@ describe('validateActivities', () => {
258
256
 
259
257
  it('ACT-013 — warns on structurally orphan activity', () => {
260
258
  const r = validateActivities({
261
- notation: 'activities',
262
- activities: [
259
+ notation: 'action',
260
+ actions: [
263
261
  { id: 'A-001', name: 'Connected', duration: 1 },
264
262
  { id: 'A-002', name: 'Orphan', duration: 1 },
265
263
  { id: 'A-003', name: 'End', duration: 1, predecessors: ['A-001'] },
@@ -272,9 +270,9 @@ describe('validateActivities', () => {
272
270
 
273
271
  it('full valid example passes with no errors', () => {
274
272
  const r = validateActivities({
275
- notation: 'activities',
273
+ notation: 'action',
276
274
  title: 'Test Project',
277
- activities: [
275
+ actions: [
278
276
  { id: 'A', name: 'Start', duration: 3 },
279
277
  { id: 'B', name: 'Middle', duration: 5, predecessors: ['A'] },
280
278
  { id: 'C', name: 'End', duration: 2, predecessors: ['B'] },
@@ -333,8 +331,8 @@ describe('validateActivities', () => {
333
331
 
334
332
  it('ACT-016 — rejects milestone with mismatched start/end dates', () => {
335
333
  const r = validateActivities({
336
- notation: 'activities',
337
- activities: [
334
+ notation: 'action',
335
+ actions: [
338
336
  { id: 'M-1', name: 'Launch', duration: 0, start_date: '2026-06-01', end_date: '2026-06-02' },
339
337
  ],
340
338
  });
@@ -344,8 +342,8 @@ describe('validateActivities', () => {
344
342
 
345
343
  it('ACT-016 — accepts milestone with equal start/end dates', () => {
346
344
  const r = validateActivities({
347
- notation: 'activities',
348
- activities: [
345
+ notation: 'action',
346
+ actions: [
349
347
  { id: 'M-1', name: 'Launch', duration: 0, start_date: '2026-06-01', end_date: '2026-06-01' },
350
348
  ],
351
349
  });
@@ -356,8 +354,8 @@ describe('validateActivities', () => {
356
354
 
357
355
  it('ACT-017 — warns when a referenced-as-parent activity carries its own duration', () => {
358
356
  const r = validateActivities({
359
- notation: 'activities',
360
- activities: [
357
+ notation: 'action',
358
+ actions: [
361
359
  { id: 'PHASE-1', name: 'Design', duration: 5 },
362
360
  { id: 'A-1', name: 'Child', duration: 3, parent: 'PHASE-1' },
363
361
  ],
@@ -368,8 +366,8 @@ describe('validateActivities', () => {
368
366
 
369
367
  it('ACT-017 — no warning when phase omits duration/dates and rolls up from children', () => {
370
368
  const r = validateActivities({
371
- notation: 'activities',
372
- activities: [
369
+ notation: 'action',
370
+ actions: [
373
371
  { id: 'PHASE-1', name: 'Design' },
374
372
  { id: 'A-1', name: 'Child', duration: 3, parent: 'PHASE-1' },
375
373
  ],
@@ -379,8 +377,8 @@ describe('validateActivities', () => {
379
377
 
380
378
  it('ACT-018 — warns when an activity with no duration is not referenced as parent', () => {
381
379
  const r = validateActivities({
382
- notation: 'activities',
383
- activities: [
380
+ notation: 'action',
381
+ actions: [
384
382
  { id: 'A-1', name: 'Has duration', duration: 3 },
385
383
  { id: 'P-EMPTY', name: 'Looks like phase but no children' },
386
384
  ],
@@ -390,8 +388,8 @@ describe('validateActivities', () => {
390
388
 
391
389
  it('ACT-018 — no warning when the no-duration activity has at least one child', () => {
392
390
  const r = validateActivities({
393
- notation: 'activities',
394
- activities: [
391
+ notation: 'action',
392
+ actions: [
395
393
  { id: 'PHASE-1', name: 'Phase' },
396
394
  { id: 'A-1', name: 'Child', duration: 3, parent: 'PHASE-1' },
397
395
  ],
@@ -413,26 +411,31 @@ describe('validateActivities', () => {
413
411
 
414
412
  it('ACT-019 — no warning when every activity has pinned dates', () => {
415
413
  const r = validateActivities({
416
- notation: 'activities',
417
- activities: [
414
+ notation: 'action',
415
+ actions: [
418
416
  { id: 'A-1', name: 'A', duration: 3, start_date: '2026-06-01', end_date: '2026-06-04' },
419
417
  ],
420
418
  });
421
419
  expect(r.warnings.some(w => w.code === 'ACT-019')).toBe(false);
422
420
  });
423
421
 
424
- it('[blocker] tolerates a null element in activities[] without throwing', () => {
425
- const r = validateActivities({ ...minimalValid, activities: [null] });
422
+ it('[blocker] tolerates a null element in actions[] without throwing', () => {
423
+ // Do NOT spread minimalValid here: validateActivities mutates its input
424
+ // to normalise raw.activities, so a previously-called test that passed
425
+ // minimalValid directly would have set minimalValid.activities. Spreading
426
+ // that mutated object would cause the actions:[null] override to be masked
427
+ // by the pre-existing activities array.
428
+ const r = validateActivities({ notation: 'action', actions: [null] });
426
429
  expect(r.valid).toBe(false);
427
430
  expect(r.errors.some(e => e.code === 'SCHEMA_INVALID')).toBe(true);
428
431
  });
429
432
  });
430
433
 
431
- describe('activities examples (regression)', () => {
434
+ describe('action examples (regression)', () => {
432
435
  const files = fs.readdirSync(EXAMPLES_DIR).filter(f => f.endsWith('.yaml'));
433
436
  expect(files.length).toBeGreaterThan(0);
434
437
  for (const file of files) {
435
- it(`validates tests/fixtures/notation-corpus/activities/${file}`, () => {
438
+ it(`validates tests/fixtures/notation-corpus/action/${file}`, () => {
436
439
  const text = fs.readFileSync(path.join(EXAMPLES_DIR, file), 'utf8');
437
440
  const parsed = yaml.load(text);
438
441
  const r = validateActivities(parsed);
@@ -19,27 +19,20 @@ export function validateActivities(input: unknown): ActivityValidationResult {
19
19
 
20
20
  const raw = input as Record<string, unknown>;
21
21
 
22
- // ACT-001: notation must be "action" (canonical) or "activities" (deprecated)
22
+ // ACT-001: notation must be "action"
23
23
  if (raw.notation === undefined) {
24
24
  errors.push({ code: 'ACT-001', message: 'notation field is required' });
25
25
  return { valid: false, errors, warnings };
26
26
  }
27
- if (raw.notation !== 'action' && raw.notation !== 'activities') {
27
+ if (raw.notation !== 'action') {
28
28
  errors.push({ code: 'ACT-001', message: `notation must be "action", got "${String(raw.notation)}"` });
29
29
  return { valid: false, errors, warnings };
30
30
  }
31
- if (raw.notation === 'activities') {
32
- warnings.push({ code: 'DEPRECATED_NOTATION', message: 'notation: "activities" is deprecated — migrate to notation: "action"' });
33
- }
34
31
 
35
- // Accept "actions:" (canonical) or "activities:" (deprecated)
36
- const rawArray = Array.isArray(raw.actions) ? raw.actions : (Array.isArray(raw.activities) ? raw.activities : undefined);
37
- if (Array.isArray(raw.activities) && !Array.isArray(raw.actions)) {
38
- warnings.push({ code: 'DEPRECATED_FIELD', message: 'Root key "activities:" is deprecated — rename to "actions:"' });
39
- }
32
+ // Only "actions:" root key is accepted.
33
+ const rawArray = Array.isArray(raw.actions) ? raw.actions : undefined;
40
34
  if (!rawArray) {
41
- const canonKey = raw.notation === 'action' ? 'actions' : 'activities';
42
- errors.push({ code: 'SCHEMA_INVALID', message: `${canonKey} must be an array`, path: canonKey });
35
+ errors.push({ code: 'SCHEMA_INVALID', message: 'actions must be an array', path: 'actions' });
43
36
  return { valid: false, errors, warnings };
44
37
  }
45
38
  // Normalise onto raw.activities for the rest of this function (avoids touching downstream code)
@@ -15,7 +15,7 @@ import type { ActivityCardDoc } from '../types.js';
15
15
 
16
16
  const dir = path.resolve(
17
17
  path.dirname(fileURLToPath(import.meta.url)),
18
- '../../../../../tests/fixtures/notation-corpus/activity-card',
18
+ '../../../../../tests/fixtures/notation-corpus/action-card',
19
19
  );
20
20
  const load = (f: string): unknown => yaml.load(readFileSync(path.join(dir, f), 'utf-8'));
21
21
 
@@ -33,7 +33,7 @@ function loadYamlTree(root: string): unknown[] {
33
33
 
34
34
  describe('activity-card worked example (eu-programme)', () => {
35
35
  it('validates, resolves, and lays out the success-signal card', () => {
36
- const card = load('eu-programme.activity-card.transitrix.yaml');
36
+ const card = load('eu-programme.action-card.transitrix.yaml');
37
37
  const elements = loadYamlTree(path.join(dir, 'canon', 'elements'));
38
38
  const relations = loadYamlTree(path.join(dir, 'canon', 'relations'));
39
39
 
@@ -2,9 +2,9 @@ import { describe, it, expect } from 'vitest';
2
2
  import { validateActivityCard } from '../validate.js';
3
3
 
4
4
  const VALID = {
5
- notation: 'activity-card',
5
+ notation: 'action-card',
6
6
  spec_version: '0.1',
7
- activity_card: {
7
+ action_card: {
8
8
  id: 'ACTIVITY_CARD-EU-PROGRAMME-1',
9
9
  project: 'ACTIVITY-EU-PROGRAMME-1',
10
10
  description: 'Executive summary.',
@@ -20,11 +20,23 @@ const VALID = {
20
20
  };
21
21
 
22
22
  describe('validateActivityCard', () => {
23
- it('accepts a well-formed card (deprecated notation: activity-card)', () => {
23
+ it('accepts a well-formed card (notation: action-card)', () => {
24
24
  const r = validateActivityCard(VALID);
25
25
  expect(r.valid).toBe(true);
26
26
  expect(r.errors).toHaveLength(0);
27
- expect(r.warnings.some(w => w.code === 'DEPRECATED_NOTATION')).toBe(true);
27
+ expect(r.warnings.some(w => w.code === 'DEPRECATED_NOTATION')).toBe(false);
28
+ });
29
+
30
+ it('rejects deprecated notation: activity-card', () => {
31
+ const r = validateActivityCard({
32
+ notation: 'activity-card',
33
+ activity_card: {
34
+ id: 'ACTIVITY_CARD-EU-PROGRAMME-1',
35
+ project: 'ACTIVITY-EU-PROGRAMME-1',
36
+ },
37
+ });
38
+ expect(r.valid).toBe(false);
39
+ expect(r.errors.some(e => e.code === 'HDR-002')).toBe(true);
28
40
  });
29
41
 
30
42
  it('accepts notation: action-card with action_card block (canonical)', () => {
@@ -67,21 +79,21 @@ describe('validateActivityCard', () => {
67
79
  expect(r.errors.some((e) => e.code === 'HDR-002')).toBe(true);
68
80
  });
69
81
 
70
- it('AC-001 on missing activity_card block', () => {
71
- const r = validateActivityCard({ notation: 'activity-card' });
82
+ it('AC-001 on missing action_card block', () => {
83
+ const r = validateActivityCard({ notation: 'action-card' });
72
84
  expect(r.valid).toBe(false);
73
85
  expect(r.errors.some((e) => e.code === 'AC-001')).toBe(true);
74
86
  });
75
87
 
76
88
  it('AC-002 on malformed card id', () => {
77
- const r = validateActivityCard({ ...VALID, activity_card: { ...VALID.activity_card, id: 'CARD-1' } });
89
+ const r = validateActivityCard({ ...VALID, action_card: { ...VALID.action_card, id: 'CARD-1' } });
78
90
  expect(r.errors.some((e) => e.code === 'AC-002')).toBe(true);
79
91
  });
80
92
 
81
93
  it('PC-001 on missing project', () => {
82
94
  const r = validateActivityCard({
83
95
  ...VALID,
84
- activity_card: { ...VALID.activity_card, project: undefined },
96
+ action_card: { ...VALID.action_card, project: undefined },
85
97
  });
86
98
  expect(r.errors.some((e) => e.code === 'PC-001')).toBe(true);
87
99
  });
@@ -89,7 +101,7 @@ describe('validateActivityCard', () => {
89
101
  it('PC-001 on malformed project id', () => {
90
102
  const r = validateActivityCard({
91
103
  ...VALID,
92
- activity_card: { ...VALID.activity_card, project: 'GOAL-1' },
104
+ action_card: { ...VALID.action_card, project: 'GOAL-1' },
93
105
  });
94
106
  expect(r.errors.some((e) => e.code === 'PC-001')).toBe(true);
95
107
  });
@@ -97,7 +109,7 @@ describe('validateActivityCard', () => {
97
109
  it('AC-003 on milestone missing required fields', () => {
98
110
  const r = validateActivityCard({
99
111
  ...VALID,
100
- activity_card: { ...VALID.activity_card, milestones: [{ id: 'MILESTONE-X-1' }] },
112
+ action_card: { ...VALID.action_card, milestones: [{ id: 'MILESTONE-X-1' }] },
101
113
  });
102
114
  expect(r.valid).toBe(false);
103
115
  expect(r.errors.some((e) => e.code === 'AC-003')).toBe(true);
@@ -106,8 +118,8 @@ describe('validateActivityCard', () => {
106
118
  it('AC-004 on malformed milestone id', () => {
107
119
  const r = validateActivityCard({
108
120
  ...VALID,
109
- activity_card: {
110
- ...VALID.activity_card,
121
+ action_card: {
122
+ ...VALID.action_card,
111
123
  milestones: [{ id: 'MS-1', name: 'x', date: '2027-01-01' }],
112
124
  },
113
125
  });
@@ -118,7 +130,7 @@ describe('validateActivityCard', () => {
118
130
  const m = { id: 'MILESTONE-X-1', name: 'x', date: '2027-01-01' };
119
131
  const r = validateActivityCard({
120
132
  ...VALID,
121
- activity_card: { ...VALID.activity_card, milestones: [m, { ...m }] },
133
+ action_card: { ...VALID.action_card, milestones: [m, { ...m }] },
122
134
  });
123
135
  expect(r.errors.some((e) => e.code === 'AC-004' && /Duplicate/.test(e.message))).toBe(true);
124
136
  });
@@ -126,8 +138,8 @@ describe('validateActivityCard', () => {
126
138
  it('AC-005 on bad milestone date format', () => {
127
139
  const r = validateActivityCard({
128
140
  ...VALID,
129
- activity_card: {
130
- ...VALID.activity_card,
141
+ action_card: {
142
+ ...VALID.action_card,
131
143
  milestones: [{ id: 'MILESTONE-X-1', name: 'x', date: '31/01/2027' }],
132
144
  },
133
145
  });
@@ -137,8 +149,8 @@ describe('validateActivityCard', () => {
137
149
  it('AC-006 on malformed delivers_changes entry', () => {
138
150
  const r = validateActivityCard({
139
151
  ...VALID,
140
- activity_card: {
141
- ...VALID.activity_card,
152
+ action_card: {
153
+ ...VALID.action_card,
142
154
  milestones: [{ id: 'MILESTONE-X-1', name: 'x', date: '2027-01-01', delivers_changes: ['GOAL-1'] }],
143
155
  },
144
156
  });
@@ -148,7 +160,7 @@ describe('validateActivityCard', () => {
148
160
  it('accepts a card with no milestones', () => {
149
161
  const r = validateActivityCard({
150
162
  ...VALID,
151
- activity_card: { id: 'ACTIVITY_CARD-X-1', project: 'ACTIVITY-X-1' },
163
+ action_card: { id: 'ACTIVITY_CARD-X-1', project: 'ACTIVITY-X-1' },
152
164
  });
153
165
  expect(r.valid).toBe(true);
154
166
  });
@@ -49,21 +49,15 @@ export function validateActivityCard(input: unknown): ValidationResult {
49
49
  // ── Header (CONTRACT.md §2) ────────────────────────────────────────────────
50
50
  if (!('notation' in raw)) {
51
51
  errors.push({ code: 'HDR-001', message: 'notation field is required' });
52
- } else if (raw['notation'] !== 'action-card' && raw['notation'] !== 'activity-card') {
52
+ } else if (raw['notation'] !== 'action-card') {
53
53
  errors.push({
54
54
  code: 'HDR-002',
55
55
  message: `notation must be "action-card", got "${String(raw['notation'])}"`,
56
56
  });
57
- } else if (raw['notation'] === 'activity-card') {
58
- warnings.push({ code: 'DEPRECATED_NOTATION', message: 'notation: "activity-card" is deprecated — migrate to notation: "action-card"' });
59
57
  }
60
58
 
61
- // ── action_card / activity_card block ───────────────────────────────────────
62
- // "action_card:" is canonical; "activity_card:" is accepted for compat.
63
- const rawBlock = raw['action_card'] ?? raw['activity_card'];
64
- if (raw['activity_card'] !== undefined && raw['action_card'] === undefined) {
65
- warnings.push({ code: 'DEPRECATED_FIELD', message: 'Root key "activity_card:" is deprecated — rename to "action_card:"' });
66
- }
59
+ // ── action_card block ────────────────────────────────────────────────────────
60
+ const rawBlock = raw['action_card'];
67
61
  const block = rawBlock;
68
62
  if (!block || typeof block !== 'object' || Array.isArray(block)) {
69
63
  errors.push({ code: 'AC-001', message: 'Missing required root key: action_card (object)' });
@@ -103,4 +103,12 @@ describe('validateActor — ACTOR-003 (engagement fields forbidden inline)', ()
103
103
  it('flags inline owner', () => {
104
104
  expect(codes({ ...valid(), owner: 'ACTOR-OPS-1' })).toContain('ACTOR-003');
105
105
  });
106
+
107
+ it('flags inline located_at (location belongs in a REL file)', () => {
108
+ expect(codes({ ...valid(), located_at: 'LOCATION-TBILISI-1' })).toContain('ACTOR-003');
109
+ });
110
+
111
+ it('flags inline unit_located_at (location belongs in a REL file)', () => {
112
+ expect(codes({ ...valid(), unit_located_at: 'LOCATION-TBILISI-1' })).toContain('ACTOR-003');
113
+ });
106
114
  });
@@ -18,6 +18,8 @@ export const ACTOR_FORBIDDEN_INLINE_FIELDS = [
18
18
  'community_membership',
19
19
  'contracting',
20
20
  'unit_parent',
21
+ 'located_at',
22
+ 'unit_located_at',
21
23
  'roles',
22
24
  'owner',
23
25
  ] as const;
@@ -2,11 +2,19 @@ export type ApplicationType = 'application' | 'integration' | 'platform' | 'data
2
2
  export type ApplicationStatus = 'Draft' | 'Active' | 'Deprecated' | 'Decommissioning';
3
3
  export type IntegrationDirection = 'inbound' | 'outbound' | 'bidirectional';
4
4
 
5
+ export type IntegrationSensitivity = 'public' | 'internal' | 'confidential' | 'restricted';
6
+ export type IntegrationDirectionality = 'producer' | 'consumer' | 'request_reply' | 'bidirectional_stream';
7
+
5
8
  export interface ApplicationIntegration {
6
9
  target?: string;
10
+ source?: string;
7
11
  direction?: IntegrationDirection;
8
12
  protocol?: string;
9
13
  description?: string;
14
+ interface_semantics?: boolean;
15
+ payload_class?: string;
16
+ sensitivity?: IntegrationSensitivity;
17
+ directionality?: IntegrationDirectionality;
10
18
  }
11
19
 
12
20
  export interface Application {