@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.
- package/dist/__tests__/yaml-normalize.test.js +9 -9
- package/dist/activities/__tests__/validate.test.js +77 -74
- package/dist/activities/validate.js +5 -12
- package/dist/activity-card/__tests__/example.test.js +2 -2
- package/dist/activity-card/__tests__/validate.test.js +29 -18
- package/dist/activity-card/validate.js +3 -10
- package/dist/actor/__tests__/validate.test.js +6 -0
- package/dist/actor/types.d.ts +1 -1
- package/dist/actor/types.js +2 -0
- package/dist/applications/types.d.ts +7 -0
- package/dist/applications/validate.js +18 -0
- package/dist/blocks/__tests__/layout.test.js +1 -1
- package/dist/blocks/layout.js +1 -1
- package/dist/business-service/__tests__/validate.test.d.ts +1 -0
- package/dist/business-service/__tests__/validate.test.js +135 -0
- package/dist/business-service/index.d.ts +3 -0
- package/dist/business-service/index.js +2 -0
- package/dist/business-service/types.d.ts +20 -0
- package/dist/business-service/types.js +3 -0
- package/dist/business-service/validate.d.ts +2 -0
- package/dist/business-service/validate.js +74 -0
- package/dist/fgca/__tests__/parse-canonical.test.js +10 -10
- package/dist/fgca/__tests__/resolver.test.js +7 -7
- package/dist/fgca/__tests__/validate.test.js +11 -11
- package/dist/fgca/parse-canonical.js +7 -11
- package/dist/fgca/resolver.d.ts +2 -2
- package/dist/fgca/resolver.js +5 -5
- package/dist/fgca/validate.js +4 -13
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/integration/__tests__/validate.test.d.ts +1 -0
- package/dist/integration/__tests__/validate.test.js +169 -0
- package/dist/integration/index.d.ts +2 -0
- package/dist/integration/index.js +1 -0
- package/dist/integration/types.d.ts +27 -0
- package/dist/integration/types.js +5 -0
- package/dist/integration/validate.d.ts +2 -0
- package/dist/integration/validate.js +94 -0
- package/dist/location/__tests__/validate.test.d.ts +1 -0
- package/dist/location/__tests__/validate.test.js +107 -0
- package/dist/location/index.d.ts +3 -0
- package/dist/location/index.js +2 -0
- package/dist/location/types.d.ts +20 -0
- package/dist/location/types.js +3 -0
- package/dist/location/validate.d.ts +2 -0
- package/dist/location/validate.js +63 -0
- package/dist/node/__tests__/validate.test.d.ts +1 -0
- package/dist/node/__tests__/validate.test.js +100 -0
- package/dist/node/index.d.ts +2 -0
- package/dist/node/index.js +1 -0
- package/dist/node/types.d.ts +18 -0
- package/dist/node/types.js +3 -0
- package/dist/node/validate.d.ts +2 -0
- package/dist/node/validate.js +53 -0
- package/dist/repo-validate/__tests__/validate-repo.test.js +444 -0
- package/dist/repo-validate/validate-repo.js +224 -10
- package/dist/technology-service/__tests__/validate.test.d.ts +1 -0
- package/dist/technology-service/__tests__/validate.test.js +121 -0
- package/dist/technology-service/index.d.ts +2 -0
- package/dist/technology-service/index.js +1 -0
- package/dist/technology-service/types.d.ts +18 -0
- package/dist/technology-service/types.js +3 -0
- package/dist/technology-service/validate.d.ts +2 -0
- package/dist/technology-service/validate.js +64 -0
- package/dist/webview/__tests__/entry-notations.test.js +2 -4
- package/dist/webview/__tests__/render-activities.test.d.ts +1 -0
- package/dist/webview/__tests__/render-activities.test.js +91 -0
- package/dist/webview/__tests__/render-blocks.test.d.ts +1 -0
- package/dist/webview/__tests__/render-blocks.test.js +130 -0
- package/dist/webview/entry.d.ts +1 -1
- package/dist/webview/entry.js +8 -15
- package/dist/webview/render-activities.d.ts +12 -0
- package/dist/webview/render-activities.js +9 -3
- package/dist/webview/render-blocks.js +97 -18
- package/package.json +1 -1
- package/src/__tests__/yaml-normalize.test.ts +11 -11
- package/src/activities/__tests__/validate.test.ts +77 -74
- package/src/activities/validate.ts +5 -12
- package/src/activity-card/__tests__/example.test.ts +2 -2
- package/src/activity-card/__tests__/validate.test.ts +30 -18
- package/src/activity-card/validate.ts +3 -9
- package/src/actor/__tests__/validate.test.ts +8 -0
- package/src/actor/types.ts +2 -0
- package/src/applications/types.ts +8 -0
- package/src/applications/validate.ts +20 -0
- package/src/blocks/__tests__/layout.test.ts +1 -1
- package/src/blocks/layout.ts +1 -1
- package/src/business-service/__tests__/validate.test.ts +160 -0
- package/src/business-service/index.ts +3 -0
- package/src/business-service/types.ts +29 -0
- package/src/business-service/validate.ts +82 -0
- package/src/fgca/__tests__/parse-canonical.test.ts +10 -10
- package/src/fgca/__tests__/resolver.test.ts +7 -7
- package/src/fgca/__tests__/validate.test.ts +11 -11
- package/src/fgca/parse-canonical.ts +7 -11
- package/src/fgca/resolver.ts +5 -5
- package/src/fgca/validate.ts +4 -12
- package/src/index.ts +5 -0
- package/src/integration/__tests__/validate.test.ts +203 -0
- package/src/integration/index.ts +2 -0
- package/src/integration/types.ts +38 -0
- package/src/integration/validate.ts +104 -0
- package/src/location/__tests__/validate.test.ts +127 -0
- package/src/location/index.ts +3 -0
- package/src/location/types.ts +29 -0
- package/src/location/validate.ts +70 -0
- package/src/node/__tests__/validate.test.ts +118 -0
- package/src/node/index.ts +2 -0
- package/src/node/types.ts +27 -0
- package/src/node/validate.ts +59 -0
- package/src/repo-validate/__tests__/validate-repo.test.ts +550 -0
- package/src/repo-validate/validate-repo.ts +233 -10
- package/src/technology-service/__tests__/validate.test.ts +143 -0
- package/src/technology-service/index.ts +2 -0
- package/src/technology-service/types.ts +27 -0
- package/src/technology-service/validate.ts +71 -0
- package/src/webview/__tests__/entry-notations.test.ts +2 -4
- package/src/webview/__tests__/render-activities.test.ts +100 -0
- package/src/webview/__tests__/render-blocks.test.ts +155 -0
- package/src/webview/entry.ts +10 -19
- package/src/webview/render-activities.ts +29 -4
- package/src/webview/render-blocks.ts +101 -20
|
@@ -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', '
|
|
7
|
+
const EXAMPLES_DIR = path.resolve(process.cwd(), '..', '..', 'tests', 'fixtures', 'notation-corpus', 'action');
|
|
8
8
|
|
|
9
9
|
const minimalValid = {
|
|
10
|
-
notation: '
|
|
11
|
-
|
|
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:
|
|
18
|
+
it('ACT-001 — accepts notation: action (canonical)', () => {
|
|
19
19
|
const r = validateActivities(minimalValid);
|
|
20
20
|
expect(r.valid).toBe(true);
|
|
21
|
-
|
|
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 —
|
|
24
|
+
it('ACT-001 — rejects notation: activities (no longer accepted)', () => {
|
|
26
25
|
const r = validateActivities({
|
|
27
|
-
notation: '
|
|
28
|
-
|
|
29
|
-
{ id: '
|
|
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(
|
|
34
|
-
expect(r.
|
|
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 —
|
|
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(
|
|
43
|
-
expect(r.
|
|
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: '
|
|
73
|
-
|
|
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: '
|
|
82
|
-
|
|
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: '
|
|
91
|
-
|
|
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: '
|
|
100
|
-
|
|
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: '
|
|
112
|
-
|
|
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: '
|
|
127
|
-
|
|
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: '
|
|
139
|
-
|
|
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: '
|
|
148
|
-
|
|
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: '
|
|
159
|
-
|
|
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: '
|
|
169
|
-
|
|
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: '
|
|
180
|
-
|
|
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: '
|
|
195
|
-
|
|
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: '
|
|
206
|
-
|
|
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: '
|
|
215
|
-
|
|
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: '
|
|
223
|
-
|
|
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: '
|
|
232
|
-
|
|
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: '
|
|
244
|
-
|
|
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: '
|
|
253
|
-
|
|
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: '
|
|
262
|
-
|
|
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: '
|
|
273
|
+
notation: 'action',
|
|
276
274
|
title: 'Test Project',
|
|
277
|
-
|
|
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: '
|
|
337
|
-
|
|
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: '
|
|
348
|
-
|
|
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: '
|
|
360
|
-
|
|
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: '
|
|
372
|
-
|
|
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: '
|
|
383
|
-
|
|
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: '
|
|
394
|
-
|
|
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: '
|
|
417
|
-
|
|
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
|
|
425
|
-
|
|
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('
|
|
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/
|
|
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"
|
|
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'
|
|
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
|
-
//
|
|
36
|
-
const rawArray = Array.isArray(raw.actions) ? raw.actions :
|
|
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
|
-
|
|
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/
|
|
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.
|
|
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: '
|
|
5
|
+
notation: 'action-card',
|
|
6
6
|
spec_version: '0.1',
|
|
7
|
-
|
|
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 (
|
|
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(
|
|
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
|
|
71
|
-
const r = validateActivityCard({ notation: '
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
110
|
-
...VALID.
|
|
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
|
-
|
|
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
|
-
|
|
130
|
-
...VALID.
|
|
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
|
-
|
|
141
|
-
...VALID.
|
|
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
|
-
|
|
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'
|
|
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
|
|
62
|
-
|
|
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
|
});
|
package/src/actor/types.ts
CHANGED
|
@@ -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 {
|