@transitrix/diagrams 1.0.0 → 1.0.1

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 (267) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +47 -47
  3. package/dist/__tests__/edge-path.test.js +8 -0
  4. package/dist/activity-card/__tests__/example.test.js +20 -1
  5. package/dist/activity-card/__tests__/layout.test.js +102 -11
  6. package/dist/activity-card/__tests__/resolver.test.js +139 -6
  7. package/dist/activity-card/index.d.ts +1 -1
  8. package/dist/activity-card/layout.d.ts +1 -1
  9. package/dist/activity-card/layout.js +220 -103
  10. package/dist/activity-card/resolver.js +107 -26
  11. package/dist/activity-card/types.d.ts +150 -27
  12. package/dist/activity-card/validate.js +3 -0
  13. package/dist/actor/__tests__/validate.test.js +63 -31
  14. package/dist/actor/index.d.ts +1 -1
  15. package/dist/actor/index.js +1 -1
  16. package/dist/actor/types.d.ts +7 -3
  17. package/dist/actor/types.js +17 -3
  18. package/dist/actor/validate.js +55 -26
  19. package/dist/assertion/types.d.ts +3 -1
  20. package/dist/assertion/types.js +1 -1
  21. package/dist/change/__tests__/validate.test.js +40 -34
  22. package/dist/change/index.d.ts +1 -0
  23. package/dist/change/types.d.ts +1 -1
  24. package/dist/change/types.js +2 -2
  25. package/dist/change/validate.d.ts +6 -1
  26. package/dist/change/validate.js +68 -41
  27. package/dist/compliance/__tests__/classify.test.js +34 -0
  28. package/dist/compliance/__tests__/coverage-metric.test.js +146 -46
  29. package/dist/compliance/__tests__/html.test.js +2 -2
  30. package/dist/compliance/__tests__/impact.test.js +311 -6
  31. package/dist/compliance/classify.d.ts +6 -0
  32. package/dist/compliance/classify.js +9 -1
  33. package/dist/compliance/coverage-metric.d.ts +31 -11
  34. package/dist/compliance/coverage-metric.js +149 -39
  35. package/dist/compliance/html.js +77 -74
  36. package/dist/compliance/impact.d.ts +77 -16
  37. package/dist/compliance/impact.js +193 -25
  38. package/dist/compliance/index.d.ts +2 -2
  39. package/dist/compliance/index.js +1 -1
  40. package/dist/compliance/markdown.js +1 -0
  41. package/dist/compliance/types.d.ts +25 -6
  42. package/dist/edge-path.d.ts +8 -4
  43. package/dist/edge-path.js +11 -6
  44. package/dist/factor/__tests__/validate.test.js +74 -45
  45. package/dist/factor/index.d.ts +3 -2
  46. package/dist/factor/index.js +1 -1
  47. package/dist/factor/types.d.ts +4 -7
  48. package/dist/factor/types.js +10 -5
  49. package/dist/factor/validate.d.ts +6 -1
  50. package/dist/factor/validate.js +68 -50
  51. package/dist/fgca/__tests__/resolver.test.js +185 -0
  52. package/dist/fgca/index.d.ts +2 -0
  53. package/dist/fgca/index.js +1 -0
  54. package/dist/fgca/resolver.d.ts +49 -0
  55. package/dist/fgca/resolver.js +124 -0
  56. package/dist/goals/index.d.ts +1 -1
  57. package/dist/index.d.ts +5 -0
  58. package/dist/index.js +5 -0
  59. package/dist/process-blueprint/__tests__/layout.test.js +37 -0
  60. package/dist/process-blueprint/layout.js +5 -1
  61. package/dist/process-blueprint/types.d.ts +22 -1
  62. package/dist/stakeholder/__tests__/validate.test.js +49 -47
  63. package/dist/stakeholder/index.d.ts +1 -0
  64. package/dist/stakeholder/types.d.ts +3 -4
  65. package/dist/stakeholder/types.js +2 -3
  66. package/dist/stakeholder/validate.d.ts +6 -1
  67. package/dist/stakeholder/validate.js +71 -50
  68. package/dist/target-state/__tests__/validate.test.js +43 -27
  69. package/dist/target-state/index.d.ts +2 -0
  70. package/dist/target-state/index.js +1 -0
  71. package/dist/target-state/types.d.ts +6 -1
  72. package/dist/target-state/types.js +8 -7
  73. package/dist/target-state/validate.d.ts +6 -1
  74. package/dist/target-state/validate.js +69 -45
  75. package/dist/theme/themes.js +69 -69
  76. package/dist/webview/__tests__/entry.test.js +11 -11
  77. package/dist/webview/render-activities.d.ts +3 -1
  78. package/dist/webview/render-activities.js +2 -2
  79. package/dist/webview/render-activity-card.js +104 -77
  80. package/dist/webview/render-fgca.d.ts +4 -0
  81. package/dist/webview/render-fgca.js +2 -5
  82. package/dist/webview/render-goals.d.ts +1 -0
  83. package/dist/webview/render-goals.js +15 -15
  84. package/dist/webview/render-process-blueprint.js +10 -10
  85. package/package.json +40 -40
  86. package/src/__tests__/edge-path.test.ts +58 -46
  87. package/src/__tests__/scope.test.ts +27 -27
  88. package/src/__tests__/yaml-normalize.test.ts +158 -158
  89. package/src/activities/__tests__/cpm.test.ts +164 -164
  90. package/src/activities/__tests__/gantt.test.ts +245 -245
  91. package/src/activities/__tests__/layout.test.ts +112 -112
  92. package/src/activities/__tests__/validate.test.ts +409 -409
  93. package/src/activities/cpm.ts +95 -95
  94. package/src/activities/gantt.ts +276 -276
  95. package/src/activities/index.ts +28 -28
  96. package/src/activities/layout.ts +112 -112
  97. package/src/activities/types.ts +148 -148
  98. package/src/activities/validate.ts +357 -357
  99. package/src/activity-card/__tests__/example.test.ts +85 -66
  100. package/src/activity-card/__tests__/layout.test.ts +169 -63
  101. package/src/activity-card/__tests__/resolver.test.ts +307 -161
  102. package/src/activity-card/__tests__/validate.test.ts +131 -131
  103. package/src/activity-card/index.ts +38 -32
  104. package/src/activity-card/layout.ts +324 -194
  105. package/src/activity-card/resolver.ts +415 -337
  106. package/src/activity-card/types.ts +376 -237
  107. package/src/activity-card/validate.ts +162 -158
  108. package/src/actor/__tests__/validate.test.ts +70 -31
  109. package/src/actor/index.ts +1 -1
  110. package/src/actor/types.ts +21 -7
  111. package/src/actor/validate.ts +59 -27
  112. package/src/applications/__tests__/validate.test.ts +256 -256
  113. package/src/applications/index.ts +11 -11
  114. package/src/applications/types.ts +43 -43
  115. package/src/applications/validate.ts +131 -131
  116. package/src/assertion/__tests__/example.test.ts +37 -37
  117. package/src/assertion/__tests__/validate.test.ts +142 -142
  118. package/src/assertion/index.ts +12 -12
  119. package/src/assertion/types.ts +60 -58
  120. package/src/assertion/validate.ts +145 -145
  121. package/src/blocks/__tests__/layout.test.ts +169 -169
  122. package/src/blocks/__tests__/validate.test.ts +260 -260
  123. package/src/blocks/index.ts +17 -17
  124. package/src/blocks/layout.ts +186 -186
  125. package/src/blocks/types.ts +62 -62
  126. package/src/blocks/validate.ts +206 -206
  127. package/src/capability-map/__tests__/validate.test.ts +238 -238
  128. package/src/capability-map/index.ts +8 -8
  129. package/src/capability-map/types.ts +29 -29
  130. package/src/capability-map/validate.ts +127 -127
  131. package/src/change/__tests__/validate.test.ts +43 -34
  132. package/src/change/index.ts +1 -0
  133. package/src/change/types.ts +5 -3
  134. package/src/change/validate.ts +75 -43
  135. package/src/compliance/__tests__/classify.test.ts +64 -27
  136. package/src/compliance/__tests__/confidence.test.ts +98 -98
  137. package/src/compliance/__tests__/coverage-metric.test.ts +300 -188
  138. package/src/compliance/__tests__/demo-scenario.test.ts +273 -273
  139. package/src/compliance/__tests__/example.test.ts +57 -57
  140. package/src/compliance/__tests__/gap-report.test.ts +128 -128
  141. package/src/compliance/__tests__/html.test.ts +141 -141
  142. package/src/compliance/__tests__/impact.test.ts +840 -481
  143. package/src/compliance/__tests__/markdown.test.ts +94 -94
  144. package/src/compliance/__tests__/views.test.ts +64 -64
  145. package/src/compliance/classify.ts +100 -84
  146. package/src/compliance/confidence.ts +69 -69
  147. package/src/compliance/coverage-metric.ts +404 -263
  148. package/src/compliance/gap-report.ts +94 -94
  149. package/src/compliance/html.ts +298 -295
  150. package/src/compliance/impact.ts +861 -677
  151. package/src/compliance/index.ts +57 -56
  152. package/src/compliance/markdown.ts +136 -135
  153. package/src/compliance/reverse-index.ts +33 -33
  154. package/src/compliance/types.ts +136 -120
  155. package/src/compliance/views.ts +46 -46
  156. package/src/compliance-matrix/__tests__/build.test.ts +200 -200
  157. package/src/compliance-matrix/__tests__/example.test.ts +57 -57
  158. package/src/compliance-matrix/index.ts +11 -11
  159. package/src/compliance-matrix/types.ts +102 -102
  160. package/src/edge-path.ts +50 -44
  161. package/src/factor/__tests__/validate.test.ts +80 -45
  162. package/src/factor/index.ts +3 -7
  163. package/src/factor/types.ts +14 -18
  164. package/src/factor/validate.ts +77 -51
  165. package/src/fgca/__tests__/chain-table.test.ts +172 -172
  166. package/src/fgca/__tests__/layout.test.ts +179 -179
  167. package/src/fgca/__tests__/parse-canonical.test.ts +248 -248
  168. package/src/fgca/__tests__/preview-layout.test.ts +124 -124
  169. package/src/fgca/__tests__/resolver.test.ts +209 -0
  170. package/src/fgca/__tests__/validate.test.ts +142 -142
  171. package/src/fgca/chain-table.ts +225 -225
  172. package/src/fgca/index.ts +47 -45
  173. package/src/fgca/layout.ts +204 -204
  174. package/src/fgca/nodeTypes.ts +11 -11
  175. package/src/fgca/nodes/FGCAActivityNode.tsx +65 -65
  176. package/src/fgca/nodes/FGCAChangeNode.tsx +64 -64
  177. package/src/fgca/nodes/FGCAFactorNode.tsx +64 -64
  178. package/src/fgca/nodes/FGCAGoalNode.tsx +72 -72
  179. package/src/fgca/parse-canonical.ts +400 -400
  180. package/src/fgca/preview-layout.ts +217 -217
  181. package/src/fgca/resolver.ts +157 -0
  182. package/src/fgca/types.ts +50 -50
  183. package/src/fgca/validate.ts +126 -126
  184. package/src/geometry.ts +9 -9
  185. package/src/goals/__tests__/layout.test.ts +175 -175
  186. package/src/goals/__tests__/mutations.test.ts +111 -111
  187. package/src/goals/__tests__/parse-canonical.test.ts +145 -145
  188. package/src/goals/__tests__/validate.test.ts +202 -202
  189. package/src/goals/index.ts +20 -21
  190. package/src/goals/layout.ts +172 -172
  191. package/src/goals/mutations.ts +123 -123
  192. package/src/goals/parse-canonical.ts +286 -286
  193. package/src/goals/types.ts +85 -85
  194. package/src/goals/validate.ts +136 -136
  195. package/src/index.ts +28 -23
  196. package/src/process-blueprint/__tests__/compliance-lane.test.ts +320 -320
  197. package/src/process-blueprint/__tests__/layout.test.ts +240 -195
  198. package/src/process-blueprint/__tests__/validate.test.ts +310 -310
  199. package/src/process-blueprint/index.ts +31 -31
  200. package/src/process-blueprint/layout.ts +535 -531
  201. package/src/process-blueprint/types.ts +263 -242
  202. package/src/process-blueprint/validate.ts +181 -181
  203. package/src/process-map/__tests__/validate.test.ts +223 -223
  204. package/src/process-map/index.ts +10 -10
  205. package/src/process-map/types.ts +36 -36
  206. package/src/process-map/validate.ts +135 -135
  207. package/src/products/__tests__/validate.test.ts +239 -239
  208. package/src/products/index.ts +9 -9
  209. package/src/products/types.ts +31 -31
  210. package/src/products/validate.ts +112 -112
  211. package/src/repo-validate/__tests__/validate-repo.test.ts +149 -149
  212. package/src/repo-validate/index.ts +2 -2
  213. package/src/repo-validate/types.ts +50 -50
  214. package/src/repo-validate/validate-repo.ts +196 -196
  215. package/src/requirement/__tests__/example.test.ts +24 -24
  216. package/src/requirement/__tests__/validate.test.ts +96 -96
  217. package/src/requirement/index.ts +4 -4
  218. package/src/requirement/types.ts +35 -35
  219. package/src/requirement/validate.ts +92 -92
  220. package/src/scenarios/__tests__/validate.test.ts +209 -209
  221. package/src/scenarios/index.ts +15 -15
  222. package/src/scenarios/types.ts +37 -37
  223. package/src/scenarios/validate.ts +122 -122
  224. package/src/schema-version.ts +16 -16
  225. package/src/scope.ts +46 -46
  226. package/src/stakeholder/__tests__/validate.test.ts +51 -47
  227. package/src/stakeholder/index.ts +1 -0
  228. package/src/stakeholder/types.ts +7 -7
  229. package/src/stakeholder/validate.ts +79 -52
  230. package/src/target-state/__tests__/validate.test.ts +48 -27
  231. package/src/target-state/index.ts +2 -0
  232. package/src/target-state/types.ts +11 -7
  233. package/src/target-state/validate.ts +76 -46
  234. package/src/theme/index.ts +4 -4
  235. package/src/theme/themes.ts +237 -237
  236. package/src/theme/tokens.ts +133 -133
  237. package/src/typed-id.ts +56 -56
  238. package/src/validation-types.ts +36 -36
  239. package/src/webview/__tests__/entry-notations.test.ts +77 -77
  240. package/src/webview/__tests__/entry.test.ts +64 -64
  241. package/src/webview/__tests__/render-goals.test.ts +72 -72
  242. package/src/webview/entry.ts +292 -292
  243. package/src/webview/render-activities.ts +5 -3
  244. package/src/webview/render-activity-card.ts +266 -234
  245. package/src/webview/render-fgca.ts +6 -5
  246. package/src/webview/render-goals.ts +111 -110
  247. package/src/webview/render-process-blueprint.ts +207 -207
  248. package/src/webview/styles.css +322 -323
  249. package/src/yaml-normalize.ts +56 -56
  250. package/dist/element-envelope.d.ts +0 -15
  251. package/dist/element-envelope.js +0 -55
  252. package/dist/issues/__tests__/layout.test.js +0 -62
  253. package/dist/issues/__tests__/validate.test.d.ts +0 -1
  254. package/dist/issues/__tests__/validate.test.js +0 -82
  255. package/dist/issues/index.d.ts +0 -4
  256. package/dist/issues/index.js +0 -2
  257. package/dist/issues/layout.d.ts +0 -10
  258. package/dist/issues/layout.js +0 -82
  259. package/dist/issues/types.d.ts +0 -61
  260. package/dist/issues/types.js +0 -6
  261. package/dist/issues/validate.d.ts +0 -3
  262. package/dist/issues/validate.js +0 -143
  263. package/dist/webview/transitrix-render.css +0 -323
  264. package/dist/webview/transitrix-render.js +0 -6452
  265. package/dist/webview/transitrix-render.js.map +0 -7
  266. package/src/element-envelope.ts +0 -76
  267. /package/dist/{issues/__tests__/layout.test.d.ts → fgca/__tests__/resolver.test.d.ts} +0 -0
@@ -1,122 +1,122 @@
1
- import type { ScenarioStatus, FactorRelevance } from './types.js';
2
- import type { ValidationError, ValidationWarning, ValidationResult } from '../validation-types.js';
3
-
4
- export type { ValidationError, ValidationWarning, ValidationResult };
5
-
6
- const VALID_STATUSES = new Set<ScenarioStatus>(['Draft', 'Active', 'Archived']);
7
- const VALID_RELEVANCE = new Set<FactorRelevance>(['High', 'Medium', 'Low']);
8
- const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
9
-
10
- interface RefSpec {
11
- field: string;
12
- idField: string;
13
- errorCode: string;
14
- }
15
-
16
- const REF_SPECS: RefSpec[] = [
17
- { field: 'goals', idField: 'goal_id', errorCode: 'SCN-007' },
18
- { field: 'capabilities', idField: 'capability_id', errorCode: 'SCN-008' },
19
- { field: 'activities', idField: 'activity_id', errorCode: 'SCN-009' },
20
- { field: 'products', idField: 'product_id', errorCode: 'SCN-010' },
21
- { field: 'processes', idField: 'process_id', errorCode: 'SCN-011' },
22
- { field: 'applications', idField: 'app_id', errorCode: 'SCN-012' },
23
- ];
24
-
25
- export function validateScenario(input: unknown): ValidationResult {
26
- const errors: ValidationError[] = [];
27
- const warnings: ValidationWarning[] = [];
28
-
29
- if (!input || typeof input !== 'object') {
30
- return { valid: false, errors: [{ code: 'SCN-001', message: 'Input must be an object' }], warnings };
31
- }
32
- const raw = input as Record<string, unknown>;
33
-
34
- if (!('notation' in raw)) {
35
- errors.push({ code: 'SCN-001', message: 'Missing required field: notation' });
36
- } else if (raw['notation'] !== 'scenarios') {
37
- errors.push({ code: 'SCN-001', message: `notation must be "scenarios", got "${raw['notation']}"` });
38
- }
39
- if (errors.length > 0) return { valid: false, errors, warnings };
40
-
41
- const scn = raw['scenario'];
42
- if (!scn || typeof scn !== 'object') {
43
- errors.push({ code: 'SCN-002', message: 'Missing required field: scenario' });
44
- return { valid: false, errors, warnings };
45
- }
46
- const s = scn as Record<string, unknown>;
47
-
48
- if (!s['id'] || typeof s['id'] !== 'string' || !(s['id'] as string).trim())
49
- errors.push({ code: 'SCN-002', message: 'scenario.id is required' });
50
- if (!s['name'] || typeof s['name'] !== 'string' || !(s['name'] as string).trim())
51
- errors.push({ code: 'SCN-002', message: 'scenario.name is required' });
52
- if (!s['status']) {
53
- errors.push({ code: 'SCN-002', message: 'scenario.status is required' });
54
- } else if (!VALID_STATUSES.has(s['status'] as ScenarioStatus)) {
55
- errors.push({ code: 'SCN-003', message: `scenario.status "${s['status']}" must be one of: Draft, Active, Archived` });
56
- }
57
-
58
- if (errors.length > 0) return { valid: false, errors, warnings };
59
-
60
- if (s['created_at'] !== undefined) {
61
- if (typeof s['created_at'] !== 'string' || !DATE_RE.test(s['created_at'] as string)) {
62
- errors.push({ code: 'SCN-004', message: `scenario.created_at must be YYYY-MM-DD, got "${s['created_at']}"` });
63
- }
64
- }
65
-
66
- if (s['factors_view'] !== undefined) {
67
- if (!Array.isArray(s['factors_view'])) {
68
- errors.push({ code: 'SCN-005', message: 'scenario.factors_view must be an array' });
69
- } else {
70
- const seen = new Set<string>();
71
- const fv = s['factors_view'] as unknown[];
72
- for (let i = 0; i < fv.length; i++) {
73
- const rawFactor = fv[i];
74
- const idx = `factors_view[${i}]`;
75
- if (!rawFactor || typeof rawFactor !== 'object') {
76
- errors.push({ code: 'SCN-005', message: `${idx} must be an object` });
77
- continue;
78
- }
79
- const f = rawFactor as Record<string, unknown>;
80
- if (!f['factor_id'] || typeof f['factor_id'] !== 'string' || !(f['factor_id'] as string).trim()) {
81
- errors.push({ code: 'SCN-005', message: `${idx}: factor_id is required` });
82
- } else {
83
- const fid = f['factor_id'] as string;
84
- if (seen.has(fid)) errors.push({ code: 'SCN-013', message: `Duplicate factor_id: "${fid}"` });
85
- seen.add(fid);
86
- }
87
- if (f['relevance'] !== undefined && !VALID_RELEVANCE.has(f['relevance'] as FactorRelevance)) {
88
- errors.push({ code: 'SCN-006', message: `${idx}: relevance "${f['relevance']}" must be one of: High, Medium, Low` });
89
- }
90
- }
91
- }
92
- }
93
-
94
- for (const spec of REF_SPECS) {
95
- const list = s[spec.field];
96
- if (list === undefined) continue;
97
- if (!Array.isArray(list)) {
98
- errors.push({ code: spec.errorCode, message: `scenario.${spec.field} must be an array` });
99
- continue;
100
- }
101
- const seen = new Set<string>();
102
- for (let i = 0; i < list.length; i++) {
103
- const rawItem = list[i];
104
- const idx = `${spec.field}[${i}]`;
105
- if (!rawItem || typeof rawItem !== 'object') {
106
- errors.push({ code: spec.errorCode, message: `${idx} must be an object` });
107
- continue;
108
- }
109
- const item = rawItem as Record<string, unknown>;
110
- const id = item[spec.idField];
111
- if (!id || typeof id !== 'string' || !(id as string).trim()) {
112
- errors.push({ code: spec.errorCode, message: `${idx}: ${spec.idField} is required` });
113
- } else {
114
- const sid = id as string;
115
- if (seen.has(sid)) errors.push({ code: spec.errorCode, message: `${idx}: duplicate ${spec.idField} "${sid}"` });
116
- seen.add(sid);
117
- }
118
- }
119
- }
120
-
121
- return { valid: errors.length === 0, errors, warnings };
122
- }
1
+ import type { ScenarioStatus, FactorRelevance } from './types.js';
2
+ import type { ValidationError, ValidationWarning, ValidationResult } from '../validation-types.js';
3
+
4
+ export type { ValidationError, ValidationWarning, ValidationResult };
5
+
6
+ const VALID_STATUSES = new Set<ScenarioStatus>(['Draft', 'Active', 'Archived']);
7
+ const VALID_RELEVANCE = new Set<FactorRelevance>(['High', 'Medium', 'Low']);
8
+ const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
9
+
10
+ interface RefSpec {
11
+ field: string;
12
+ idField: string;
13
+ errorCode: string;
14
+ }
15
+
16
+ const REF_SPECS: RefSpec[] = [
17
+ { field: 'goals', idField: 'goal_id', errorCode: 'SCN-007' },
18
+ { field: 'capabilities', idField: 'capability_id', errorCode: 'SCN-008' },
19
+ { field: 'activities', idField: 'activity_id', errorCode: 'SCN-009' },
20
+ { field: 'products', idField: 'product_id', errorCode: 'SCN-010' },
21
+ { field: 'processes', idField: 'process_id', errorCode: 'SCN-011' },
22
+ { field: 'applications', idField: 'app_id', errorCode: 'SCN-012' },
23
+ ];
24
+
25
+ export function validateScenario(input: unknown): ValidationResult {
26
+ const errors: ValidationError[] = [];
27
+ const warnings: ValidationWarning[] = [];
28
+
29
+ if (!input || typeof input !== 'object') {
30
+ return { valid: false, errors: [{ code: 'SCN-001', message: 'Input must be an object' }], warnings };
31
+ }
32
+ const raw = input as Record<string, unknown>;
33
+
34
+ if (!('notation' in raw)) {
35
+ errors.push({ code: 'SCN-001', message: 'Missing required field: notation' });
36
+ } else if (raw['notation'] !== 'scenarios') {
37
+ errors.push({ code: 'SCN-001', message: `notation must be "scenarios", got "${raw['notation']}"` });
38
+ }
39
+ if (errors.length > 0) return { valid: false, errors, warnings };
40
+
41
+ const scn = raw['scenario'];
42
+ if (!scn || typeof scn !== 'object') {
43
+ errors.push({ code: 'SCN-002', message: 'Missing required field: scenario' });
44
+ return { valid: false, errors, warnings };
45
+ }
46
+ const s = scn as Record<string, unknown>;
47
+
48
+ if (!s['id'] || typeof s['id'] !== 'string' || !(s['id'] as string).trim())
49
+ errors.push({ code: 'SCN-002', message: 'scenario.id is required' });
50
+ if (!s['name'] || typeof s['name'] !== 'string' || !(s['name'] as string).trim())
51
+ errors.push({ code: 'SCN-002', message: 'scenario.name is required' });
52
+ if (!s['status']) {
53
+ errors.push({ code: 'SCN-002', message: 'scenario.status is required' });
54
+ } else if (!VALID_STATUSES.has(s['status'] as ScenarioStatus)) {
55
+ errors.push({ code: 'SCN-003', message: `scenario.status "${s['status']}" must be one of: Draft, Active, Archived` });
56
+ }
57
+
58
+ if (errors.length > 0) return { valid: false, errors, warnings };
59
+
60
+ if (s['created_at'] !== undefined) {
61
+ if (typeof s['created_at'] !== 'string' || !DATE_RE.test(s['created_at'] as string)) {
62
+ errors.push({ code: 'SCN-004', message: `scenario.created_at must be YYYY-MM-DD, got "${s['created_at']}"` });
63
+ }
64
+ }
65
+
66
+ if (s['factors_view'] !== undefined) {
67
+ if (!Array.isArray(s['factors_view'])) {
68
+ errors.push({ code: 'SCN-005', message: 'scenario.factors_view must be an array' });
69
+ } else {
70
+ const seen = new Set<string>();
71
+ const fv = s['factors_view'] as unknown[];
72
+ for (let i = 0; i < fv.length; i++) {
73
+ const rawFactor = fv[i];
74
+ const idx = `factors_view[${i}]`;
75
+ if (!rawFactor || typeof rawFactor !== 'object') {
76
+ errors.push({ code: 'SCN-005', message: `${idx} must be an object` });
77
+ continue;
78
+ }
79
+ const f = rawFactor as Record<string, unknown>;
80
+ if (!f['factor_id'] || typeof f['factor_id'] !== 'string' || !(f['factor_id'] as string).trim()) {
81
+ errors.push({ code: 'SCN-005', message: `${idx}: factor_id is required` });
82
+ } else {
83
+ const fid = f['factor_id'] as string;
84
+ if (seen.has(fid)) errors.push({ code: 'SCN-013', message: `Duplicate factor_id: "${fid}"` });
85
+ seen.add(fid);
86
+ }
87
+ if (f['relevance'] !== undefined && !VALID_RELEVANCE.has(f['relevance'] as FactorRelevance)) {
88
+ errors.push({ code: 'SCN-006', message: `${idx}: relevance "${f['relevance']}" must be one of: High, Medium, Low` });
89
+ }
90
+ }
91
+ }
92
+ }
93
+
94
+ for (const spec of REF_SPECS) {
95
+ const list = s[spec.field];
96
+ if (list === undefined) continue;
97
+ if (!Array.isArray(list)) {
98
+ errors.push({ code: spec.errorCode, message: `scenario.${spec.field} must be an array` });
99
+ continue;
100
+ }
101
+ const seen = new Set<string>();
102
+ for (let i = 0; i < list.length; i++) {
103
+ const rawItem = list[i];
104
+ const idx = `${spec.field}[${i}]`;
105
+ if (!rawItem || typeof rawItem !== 'object') {
106
+ errors.push({ code: spec.errorCode, message: `${idx} must be an object` });
107
+ continue;
108
+ }
109
+ const item = rawItem as Record<string, unknown>;
110
+ const id = item[spec.idField];
111
+ if (!id || typeof id !== 'string' || !(id as string).trim()) {
112
+ errors.push({ code: spec.errorCode, message: `${idx}: ${spec.idField} is required` });
113
+ } else {
114
+ const sid = id as string;
115
+ if (seen.has(sid)) errors.push({ code: spec.errorCode, message: `${idx}: duplicate ${spec.idField} "${sid}"` });
116
+ seen.add(sid);
117
+ }
118
+ }
119
+ }
120
+
121
+ return { valid: errors.length === 0, errors, warnings };
122
+ }
@@ -1,16 +1,16 @@
1
- /**
2
- * SCHEMA_VERSION — the methodology release whose notation schemas and validation
3
- * rules this `@transitrix/diagrams` build conforms to (SV-1).
4
- *
5
- * Source of truth: the methodology `methodology_version`, declared in an adopter
6
- * repository's `transitrix.yaml` per the methodology `notations/MANIFEST.md`
7
- * (single source of truth for a repo's conformance). The current methodology
8
- * release is **0.5.0**.
9
- *
10
- * This constant is kept in lockstep with the project manifest's
11
- * `transitrix.methodologyVersion` (`package.json`); the
12
- * `tests/schema-version.test.ts` unit test asserts the two are equal. A CI guard
13
- * that additionally asserts both equal the methodology's published SoT lands
14
- * separately (SV-1 PR2) — do not bundle it here.
15
- */
16
- export const SCHEMA_VERSION = '0.5.0';
1
+ /**
2
+ * SCHEMA_VERSION — the methodology release whose notation schemas and validation
3
+ * rules this `@transitrix/diagrams` build conforms to (SV-1).
4
+ *
5
+ * Source of truth: the methodology `methodology_version`, declared in an adopter
6
+ * repository's `transitrix.yaml` per the methodology `notations/MANIFEST.md`
7
+ * (single source of truth for a repo's conformance). The current methodology
8
+ * release is **0.5.0**.
9
+ *
10
+ * This constant is kept in lockstep with the project manifest's
11
+ * `transitrix.methodologyVersion` (`package.json`); the
12
+ * `tests/schema-version.test.ts` unit test asserts the two are equal. A CI guard
13
+ * that additionally asserts both equal the methodology's published SoT lands
14
+ * separately (SV-1 PR2) — do not bundle it here.
15
+ */
16
+ export const SCHEMA_VERSION = '0.5.0';
package/src/scope.ts CHANGED
@@ -1,46 +1,46 @@
1
- // Scope filtering for hierarchical / relational previews (Goals, FGA, FGCA).
2
- //
3
- // A `Scope` trims what a preview renders to part of the hierarchy:
4
- // - 'all' → everything (default, today's behaviour)
5
- // - 'level' → only goals at or below a level cap, plus the factors /
6
- // changes / activities that touch a visible goal
7
- // - 'root' → only a chosen goal's subtree (Goals: descendants via
8
- // parent_id; FGA/FGCA goals are flat, so the subtree is the
9
- // single root goal), plus its connected factors / changes /
10
- // activities
11
- //
12
- // This is intentionally a small, pure rendering primitive: a role's view in
13
- // a future access-control layer (DSM) can be expressed as a level cap or a
14
- // subtree root and reuse exactly this filter. No RBAC here — just the filter.
15
-
16
- import type { ValidationWarning } from './validation-types.js';
17
-
18
- export type Scope =
19
- | { mode: 'all' }
20
- | { mode: 'level'; maxLevel: number }
21
- | { mode: 'root'; rootGoalId: string };
22
-
23
- /** Default scope — render everything. */
24
- export const SCOPE_ALL: Scope = { mode: 'all' };
25
-
26
- /** Warning code emitted when a root-mode scope names a goal that isn't in the document. */
27
- export const SCOPE_MISSING_ROOT_CODE = 'SCOPE-001';
28
-
29
- /**
30
- * Returns the SCOPE-001 warning when `scope` is root-mode and `rootGoalId` is
31
- * not among the document's goal ids; otherwise null. Goal ids are compared as
32
- * strings so numeric (FGCA/Goals) and string ids both work.
33
- *
34
- * Previews call this to surface the warning in their panel — the layout
35
- * functions independently return an empty layout in the same situation.
36
- */
37
- export function checkScopeRoot(scope: Scope, goalIds: Iterable<string | number>): ValidationWarning | null {
38
- if (scope.mode !== 'root') return null;
39
- for (const id of goalIds) {
40
- if (String(id) === scope.rootGoalId) return null;
41
- }
42
- return {
43
- code: SCOPE_MISSING_ROOT_CODE,
44
- message: `Scope root goal "${scope.rootGoalId}" was not found in this document — nothing to show. Clear the scope or pick a goal that exists.`,
45
- };
46
- }
1
+ // Scope filtering for hierarchical / relational previews (Goals, FGA, FGCA).
2
+ //
3
+ // A `Scope` trims what a preview renders to part of the hierarchy:
4
+ // - 'all' → everything (default, today's behaviour)
5
+ // - 'level' → only goals at or below a level cap, plus the factors /
6
+ // changes / activities that touch a visible goal
7
+ // - 'root' → only a chosen goal's subtree (Goals: descendants via
8
+ // parent_id; FGA/FGCA goals are flat, so the subtree is the
9
+ // single root goal), plus its connected factors / changes /
10
+ // activities
11
+ //
12
+ // This is intentionally a small, pure rendering primitive: a role's view in
13
+ // a future access-control layer (DSM) can be expressed as a level cap or a
14
+ // subtree root and reuse exactly this filter. No RBAC here — just the filter.
15
+
16
+ import type { ValidationWarning } from './validation-types.js';
17
+
18
+ export type Scope =
19
+ | { mode: 'all' }
20
+ | { mode: 'level'; maxLevel: number }
21
+ | { mode: 'root'; rootGoalId: string };
22
+
23
+ /** Default scope — render everything. */
24
+ export const SCOPE_ALL: Scope = { mode: 'all' };
25
+
26
+ /** Warning code emitted when a root-mode scope names a goal that isn't in the document. */
27
+ export const SCOPE_MISSING_ROOT_CODE = 'SCOPE-001';
28
+
29
+ /**
30
+ * Returns the SCOPE-001 warning when `scope` is root-mode and `rootGoalId` is
31
+ * not among the document's goal ids; otherwise null. Goal ids are compared as
32
+ * strings so numeric (FGCA/Goals) and string ids both work.
33
+ *
34
+ * Previews call this to surface the warning in their panel — the layout
35
+ * functions independently return an empty layout in the same situation.
36
+ */
37
+ export function checkScopeRoot(scope: Scope, goalIds: Iterable<string | number>): ValidationWarning | null {
38
+ if (scope.mode !== 'root') return null;
39
+ for (const id of goalIds) {
40
+ if (String(id) === scope.rootGoalId) return null;
41
+ }
42
+ return {
43
+ code: SCOPE_MISSING_ROOT_CODE,
44
+ message: `Scope root goal "${scope.rootGoalId}" was not found in this document — nothing to show. Clear the scope or pick a goal that exists.`,
45
+ };
46
+ }
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import { validateStakeholder } from '../validate.js';
3
+ import type { CanonCatalog } from '../../typed-id.js';
3
4
 
4
5
  function valid(): Record<string, unknown> {
5
6
  return {
@@ -8,7 +9,7 @@ function valid(): Record<string, unknown> {
8
9
  name: 'Data Protection Authority',
9
10
  type: 'external',
10
11
  actor: 'ACTOR-DPA-1',
11
- concern: 'Lawful processing and timely erasure of EU customer personal data.',
12
+ concern: 'Lawful processing and timely erasure of personal data.',
12
13
  interest: 'high',
13
14
  influence: 'high',
14
15
  description: 'External oversight stakeholder.',
@@ -21,81 +22,84 @@ function valid(): Record<string, unknown> {
21
22
  };
22
23
  }
23
24
 
24
- const codes = (input: unknown): string[] => validateStakeholder(input).errors.map(e => e.code);
25
+ const codes = (input: unknown, opts?: Parameters<typeof validateStakeholder>[1]): string[] =>
26
+ validateStakeholder(input, opts).errors.map(e => e.code);
25
27
 
26
28
  describe('validateStakeholder — positive', () => {
27
- it('accepts the worked-example STAKEHOLDER-DPA-1', () => {
29
+ it('accepts a well-formed external stakeholder', () => {
28
30
  const r = validateStakeholder(valid());
29
31
  expect(r.valid, JSON.stringify(r.errors)).toBe(true);
30
- expect(r.errors).toHaveLength(0);
31
32
  });
32
33
 
33
- it('accepts a minimal stakeholder (no concern / interest / influence)', () => {
34
- const r = valid();
35
- delete r.concern;
36
- delete r.interest;
37
- delete r.influence;
38
- expect(validateStakeholder(r).valid).toBe(true);
34
+ it('accepts a minimal internal stakeholder with no optional profile fields', () => {
35
+ const s = valid();
36
+ s.id = 'STAKEHOLDER-OPS-1';
37
+ s.name = 'Operations';
38
+ s.type = 'internal';
39
+ s.actor = 'ACTOR-OPS-1';
40
+ delete s.concern;
41
+ delete s.interest;
42
+ delete s.influence;
43
+ delete s.description;
44
+ expect(validateStakeholder(s).valid).toBe(true);
39
45
  });
40
- });
41
46
 
42
- describe('validateStakeholder — STAKEHOLDER-001 (envelope / shape)', () => {
43
- it('flags a non-object', () => {
44
- expect(codes(null)).toEqual(['STAKEHOLDER-001']);
47
+ it('accepts when the catalog resolves the actor', () => {
48
+ const catalog: CanonCatalog = { typeOf: id => (id === 'ACTOR-DPA-1' ? 'ACTOR' : undefined) };
49
+ expect(validateStakeholder(valid(), { catalog }).valid).toBe(true);
45
50
  });
51
+ });
46
52
 
47
- it('flags a wrong notation tag', () => {
48
- expect(codes({ ...valid(), notation: 'stake' })).toContain('STAKEHOLDER-001');
53
+ describe('validateStakeholder — STAKE-001 (shape / id grammar / levels)', () => {
54
+ it('flags a missing required envelope field', () => {
55
+ const s = valid();
56
+ delete s.name;
57
+ expect(codes(s)).toContain('STAKE-001');
49
58
  });
50
59
 
51
- it('flags an id with the wrong TYPE prefix', () => {
52
- expect(codes({ ...valid(), id: 'ACTOR-DPA-1' })).toContain('STAKEHOLDER-001');
60
+ it('flags an id that violates the grammar', () => {
61
+ expect(codes({ ...valid(), id: 'STAKEHOLDER-DPA-001' })).toContain('STAKE-001');
62
+ expect(codes({ ...valid(), id: 'STAKE-1' })).toContain('STAKE-001');
53
63
  });
54
- });
55
64
 
56
- describe('validateStakeholder — STAKEHOLDER-002 (type enum)', () => {
57
- it('flags a missing type', () => {
58
- const r = valid();
59
- delete r.type;
60
- expect(codes(r)).toContain('STAKEHOLDER-002');
65
+ it('flags a wrong notation tag', () => {
66
+ expect(codes({ ...valid(), notation: 'stakeholders' })).toContain('STAKE-001');
61
67
  });
62
68
 
63
- it('flags a type outside internal/external', () => {
64
- expect(codes({ ...valid(), type: 'regulator' })).toContain('STAKEHOLDER-002');
69
+ it('flags an interest / influence value outside {high, medium, low}', () => {
70
+ expect(codes({ ...valid(), interest: 'critical' })).toContain('STAKE-001');
71
+ expect(codes({ ...valid(), influence: 'none' })).toContain('STAKE-001');
65
72
  });
66
73
 
67
- it('accepts both internal and external', () => {
68
- for (const t of ['internal', 'external']) {
69
- expect(validateStakeholder({ ...valid(), type: t }).valid).toBe(true);
70
- }
74
+ it('rejects a non-object', () => {
75
+ expect(codes(null)).toEqual(['STAKE-001']);
71
76
  });
72
77
  });
73
78
 
74
- describe('validateStakeholder — STAKEHOLDER-003 (actor binding)', () => {
79
+ describe('validateStakeholder — STAKE-002 (actor binding)', () => {
75
80
  it('flags a missing actor', () => {
76
- const r = valid();
77
- delete r.actor;
78
- expect(codes(r)).toContain('STAKEHOLDER-003');
81
+ const s = valid();
82
+ delete s.actor;
83
+ expect(codes(s)).toContain('STAKE-002');
79
84
  });
80
85
 
81
- it('flags an actor that is not an ACTOR typed id', () => {
82
- expect(codes({ ...valid(), actor: 'ROLE-CEO-1' })).toContain('STAKEHOLDER-003');
83
- expect(codes({ ...valid(), actor: 'not an id' })).toContain('STAKEHOLDER-003');
86
+ it('flags an actor of the wrong TYPE', () => {
87
+ expect(codes({ ...valid(), actor: 'ROLE-OPS-1' })).toContain('STAKE-002');
84
88
  });
85
- });
86
89
 
87
- describe('validateStakeholder — STAKEHOLDER-004 (level enums)', () => {
88
- it('flags an interest outside high/medium/low', () => {
89
- expect(codes({ ...valid(), interest: 'extreme' })).toContain('STAKEHOLDER-004');
90
+ it('flags an actor that does not resolve when a catalog is supplied', () => {
91
+ const catalog: CanonCatalog = { typeOf: () => undefined };
92
+ expect(codes(valid(), { catalog })).toContain('STAKE-002');
90
93
  });
91
94
 
92
- it('flags an influence outside high/medium/low', () => {
93
- expect(codes({ ...valid(), influence: 'extreme' })).toContain('STAKEHOLDER-004');
95
+ it('flags an actor whose catalog TYPE is not ACTOR', () => {
96
+ const catalog: CanonCatalog = { typeOf: () => 'ROLE' };
97
+ expect(codes(valid(), { catalog })).toContain('STAKE-002');
94
98
  });
99
+ });
95
100
 
96
- it('accepts each permitted level', () => {
97
- for (const l of ['high', 'medium', 'low']) {
98
- expect(validateStakeholder({ ...valid(), interest: l, influence: l }).valid).toBe(true);
99
- }
101
+ describe('validateStakeholder — STAKE-003 (type enum)', () => {
102
+ it('flags a type outside {internal, external}', () => {
103
+ expect(codes({ ...valid(), type: 'partner' })).toContain('STAKE-003');
100
104
  });
101
105
  });
@@ -1,3 +1,4 @@
1
1
  export type { Stakeholder, StakeholderType, StakeholderLevel } from './types.js';
2
2
  export { STAKEHOLDER_TYPES, STAKEHOLDER_LEVELS } from './types.js';
3
3
  export { validateStakeholder } from './validate.js';
4
+ export type { StakeholderValidateOptions } from './validate.js';
@@ -1,33 +1,33 @@
1
- // STAKEHOLDER — motivation-layer interest primitive.
2
- // Schema: methodology notations/ELEMENT_PRIMITIVES.md §7.15 +
3
- // notations/elements/20-stakeholders.md + envelope §3.
1
+ // STAKEHOLDER — motivation-layer interest primitive (ArchiMate Stakeholder).
2
+ // Schema: methodology notations/elements/20-stakeholders.md + ELEMENT_PRIMITIVES.md §7.16.
4
3
 
5
4
  import type { GateChecks } from '../requirement/types.js';
6
5
 
7
- export type StakeholderType = 'internal' | 'external';
8
6
  export const STAKEHOLDER_TYPES = ['internal', 'external'] as const;
7
+ export type StakeholderType = typeof STAKEHOLDER_TYPES[number];
9
8
 
10
- export type StakeholderLevel = 'high' | 'medium' | 'low';
11
9
  export const STAKEHOLDER_LEVELS = ['high', 'medium', 'low'] as const;
10
+ export type StakeholderLevel = typeof STAKEHOLDER_LEVELS[number];
12
11
 
13
12
  export interface Stakeholder {
14
13
  notation: 'stakeholder';
15
14
  id: string;
16
15
  name: string;
17
- /** Required subtype — internal | external. */
18
16
  type: StakeholderType;
19
- /** Required ACTOR-… typed id — identity binding (never inline). */
17
+ /** ACTOR-… whose identity this stake attaches to (STAKE-002). */
20
18
  actor: string;
21
19
  concern?: string;
22
20
  interest?: StakeholderLevel;
23
21
  influence?: StakeholderLevel;
24
22
  description?: string;
25
23
 
24
+ // Admission record (CONTRACT.md §6).
26
25
  zone: 'canon';
27
26
  admitted_at: string;
28
27
  admitted_by: string;
29
28
  gate_checks: GateChecks;
30
29
 
30
+ // Primitive lifecycle (CONTRACT.md §7).
31
31
  valid_from: string;
32
32
  valid_to: string | null;
33
33
  }