@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.
- package/LICENSE +21 -21
- package/README.md +47 -47
- package/dist/__tests__/edge-path.test.js +8 -0
- package/dist/activity-card/__tests__/example.test.js +20 -1
- package/dist/activity-card/__tests__/layout.test.js +102 -11
- package/dist/activity-card/__tests__/resolver.test.js +139 -6
- package/dist/activity-card/index.d.ts +1 -1
- package/dist/activity-card/layout.d.ts +1 -1
- package/dist/activity-card/layout.js +220 -103
- package/dist/activity-card/resolver.js +107 -26
- package/dist/activity-card/types.d.ts +150 -27
- package/dist/activity-card/validate.js +3 -0
- package/dist/actor/__tests__/validate.test.js +63 -31
- package/dist/actor/index.d.ts +1 -1
- package/dist/actor/index.js +1 -1
- package/dist/actor/types.d.ts +7 -3
- package/dist/actor/types.js +17 -3
- package/dist/actor/validate.js +55 -26
- package/dist/assertion/types.d.ts +3 -1
- package/dist/assertion/types.js +1 -1
- package/dist/change/__tests__/validate.test.js +40 -34
- package/dist/change/index.d.ts +1 -0
- package/dist/change/types.d.ts +1 -1
- package/dist/change/types.js +2 -2
- package/dist/change/validate.d.ts +6 -1
- package/dist/change/validate.js +68 -41
- package/dist/compliance/__tests__/classify.test.js +34 -0
- package/dist/compliance/__tests__/coverage-metric.test.js +146 -46
- package/dist/compliance/__tests__/html.test.js +2 -2
- package/dist/compliance/__tests__/impact.test.js +311 -6
- package/dist/compliance/classify.d.ts +6 -0
- package/dist/compliance/classify.js +9 -1
- package/dist/compliance/coverage-metric.d.ts +31 -11
- package/dist/compliance/coverage-metric.js +149 -39
- package/dist/compliance/html.js +77 -74
- package/dist/compliance/impact.d.ts +77 -16
- package/dist/compliance/impact.js +193 -25
- package/dist/compliance/index.d.ts +2 -2
- package/dist/compliance/index.js +1 -1
- package/dist/compliance/markdown.js +1 -0
- package/dist/compliance/types.d.ts +25 -6
- package/dist/edge-path.d.ts +8 -4
- package/dist/edge-path.js +11 -6
- package/dist/factor/__tests__/validate.test.js +74 -45
- package/dist/factor/index.d.ts +3 -2
- package/dist/factor/index.js +1 -1
- package/dist/factor/types.d.ts +4 -7
- package/dist/factor/types.js +10 -5
- package/dist/factor/validate.d.ts +6 -1
- package/dist/factor/validate.js +68 -50
- package/dist/fgca/__tests__/resolver.test.js +185 -0
- package/dist/fgca/index.d.ts +2 -0
- package/dist/fgca/index.js +1 -0
- package/dist/fgca/resolver.d.ts +49 -0
- package/dist/fgca/resolver.js +124 -0
- package/dist/goals/index.d.ts +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/process-blueprint/__tests__/layout.test.js +37 -0
- package/dist/process-blueprint/layout.js +5 -1
- package/dist/process-blueprint/types.d.ts +22 -1
- package/dist/stakeholder/__tests__/validate.test.js +49 -47
- package/dist/stakeholder/index.d.ts +1 -0
- package/dist/stakeholder/types.d.ts +3 -4
- package/dist/stakeholder/types.js +2 -3
- package/dist/stakeholder/validate.d.ts +6 -1
- package/dist/stakeholder/validate.js +71 -50
- package/dist/target-state/__tests__/validate.test.js +43 -27
- package/dist/target-state/index.d.ts +2 -0
- package/dist/target-state/index.js +1 -0
- package/dist/target-state/types.d.ts +6 -1
- package/dist/target-state/types.js +8 -7
- package/dist/target-state/validate.d.ts +6 -1
- package/dist/target-state/validate.js +69 -45
- package/dist/theme/themes.js +69 -69
- package/dist/webview/__tests__/entry.test.js +11 -11
- package/dist/webview/render-activities.d.ts +3 -1
- package/dist/webview/render-activities.js +2 -2
- package/dist/webview/render-activity-card.js +104 -77
- package/dist/webview/render-fgca.d.ts +4 -0
- package/dist/webview/render-fgca.js +2 -5
- package/dist/webview/render-goals.d.ts +1 -0
- package/dist/webview/render-goals.js +15 -15
- package/dist/webview/render-process-blueprint.js +10 -10
- package/package.json +40 -40
- package/src/__tests__/edge-path.test.ts +58 -46
- package/src/__tests__/scope.test.ts +27 -27
- package/src/__tests__/yaml-normalize.test.ts +158 -158
- package/src/activities/__tests__/cpm.test.ts +164 -164
- package/src/activities/__tests__/gantt.test.ts +245 -245
- package/src/activities/__tests__/layout.test.ts +112 -112
- package/src/activities/__tests__/validate.test.ts +409 -409
- package/src/activities/cpm.ts +95 -95
- package/src/activities/gantt.ts +276 -276
- package/src/activities/index.ts +28 -28
- package/src/activities/layout.ts +112 -112
- package/src/activities/types.ts +148 -148
- package/src/activities/validate.ts +357 -357
- package/src/activity-card/__tests__/example.test.ts +85 -66
- package/src/activity-card/__tests__/layout.test.ts +169 -63
- package/src/activity-card/__tests__/resolver.test.ts +307 -161
- package/src/activity-card/__tests__/validate.test.ts +131 -131
- package/src/activity-card/index.ts +38 -32
- package/src/activity-card/layout.ts +324 -194
- package/src/activity-card/resolver.ts +415 -337
- package/src/activity-card/types.ts +376 -237
- package/src/activity-card/validate.ts +162 -158
- package/src/actor/__tests__/validate.test.ts +70 -31
- package/src/actor/index.ts +1 -1
- package/src/actor/types.ts +21 -7
- package/src/actor/validate.ts +59 -27
- package/src/applications/__tests__/validate.test.ts +256 -256
- package/src/applications/index.ts +11 -11
- package/src/applications/types.ts +43 -43
- package/src/applications/validate.ts +131 -131
- package/src/assertion/__tests__/example.test.ts +37 -37
- package/src/assertion/__tests__/validate.test.ts +142 -142
- package/src/assertion/index.ts +12 -12
- package/src/assertion/types.ts +60 -58
- package/src/assertion/validate.ts +145 -145
- package/src/blocks/__tests__/layout.test.ts +169 -169
- package/src/blocks/__tests__/validate.test.ts +260 -260
- package/src/blocks/index.ts +17 -17
- package/src/blocks/layout.ts +186 -186
- package/src/blocks/types.ts +62 -62
- package/src/blocks/validate.ts +206 -206
- package/src/capability-map/__tests__/validate.test.ts +238 -238
- package/src/capability-map/index.ts +8 -8
- package/src/capability-map/types.ts +29 -29
- package/src/capability-map/validate.ts +127 -127
- package/src/change/__tests__/validate.test.ts +43 -34
- package/src/change/index.ts +1 -0
- package/src/change/types.ts +5 -3
- package/src/change/validate.ts +75 -43
- package/src/compliance/__tests__/classify.test.ts +64 -27
- package/src/compliance/__tests__/confidence.test.ts +98 -98
- package/src/compliance/__tests__/coverage-metric.test.ts +300 -188
- package/src/compliance/__tests__/demo-scenario.test.ts +273 -273
- package/src/compliance/__tests__/example.test.ts +57 -57
- package/src/compliance/__tests__/gap-report.test.ts +128 -128
- package/src/compliance/__tests__/html.test.ts +141 -141
- package/src/compliance/__tests__/impact.test.ts +840 -481
- package/src/compliance/__tests__/markdown.test.ts +94 -94
- package/src/compliance/__tests__/views.test.ts +64 -64
- package/src/compliance/classify.ts +100 -84
- package/src/compliance/confidence.ts +69 -69
- package/src/compliance/coverage-metric.ts +404 -263
- package/src/compliance/gap-report.ts +94 -94
- package/src/compliance/html.ts +298 -295
- package/src/compliance/impact.ts +861 -677
- package/src/compliance/index.ts +57 -56
- package/src/compliance/markdown.ts +136 -135
- package/src/compliance/reverse-index.ts +33 -33
- package/src/compliance/types.ts +136 -120
- package/src/compliance/views.ts +46 -46
- package/src/compliance-matrix/__tests__/build.test.ts +200 -200
- package/src/compliance-matrix/__tests__/example.test.ts +57 -57
- package/src/compliance-matrix/index.ts +11 -11
- package/src/compliance-matrix/types.ts +102 -102
- package/src/edge-path.ts +50 -44
- package/src/factor/__tests__/validate.test.ts +80 -45
- package/src/factor/index.ts +3 -7
- package/src/factor/types.ts +14 -18
- package/src/factor/validate.ts +77 -51
- package/src/fgca/__tests__/chain-table.test.ts +172 -172
- package/src/fgca/__tests__/layout.test.ts +179 -179
- package/src/fgca/__tests__/parse-canonical.test.ts +248 -248
- package/src/fgca/__tests__/preview-layout.test.ts +124 -124
- package/src/fgca/__tests__/resolver.test.ts +209 -0
- package/src/fgca/__tests__/validate.test.ts +142 -142
- package/src/fgca/chain-table.ts +225 -225
- package/src/fgca/index.ts +47 -45
- package/src/fgca/layout.ts +204 -204
- package/src/fgca/nodeTypes.ts +11 -11
- package/src/fgca/nodes/FGCAActivityNode.tsx +65 -65
- package/src/fgca/nodes/FGCAChangeNode.tsx +64 -64
- package/src/fgca/nodes/FGCAFactorNode.tsx +64 -64
- package/src/fgca/nodes/FGCAGoalNode.tsx +72 -72
- package/src/fgca/parse-canonical.ts +400 -400
- package/src/fgca/preview-layout.ts +217 -217
- package/src/fgca/resolver.ts +157 -0
- package/src/fgca/types.ts +50 -50
- package/src/fgca/validate.ts +126 -126
- package/src/geometry.ts +9 -9
- package/src/goals/__tests__/layout.test.ts +175 -175
- package/src/goals/__tests__/mutations.test.ts +111 -111
- package/src/goals/__tests__/parse-canonical.test.ts +145 -145
- package/src/goals/__tests__/validate.test.ts +202 -202
- package/src/goals/index.ts +20 -21
- package/src/goals/layout.ts +172 -172
- package/src/goals/mutations.ts +123 -123
- package/src/goals/parse-canonical.ts +286 -286
- package/src/goals/types.ts +85 -85
- package/src/goals/validate.ts +136 -136
- package/src/index.ts +28 -23
- package/src/process-blueprint/__tests__/compliance-lane.test.ts +320 -320
- package/src/process-blueprint/__tests__/layout.test.ts +240 -195
- package/src/process-blueprint/__tests__/validate.test.ts +310 -310
- package/src/process-blueprint/index.ts +31 -31
- package/src/process-blueprint/layout.ts +535 -531
- package/src/process-blueprint/types.ts +263 -242
- package/src/process-blueprint/validate.ts +181 -181
- package/src/process-map/__tests__/validate.test.ts +223 -223
- package/src/process-map/index.ts +10 -10
- package/src/process-map/types.ts +36 -36
- package/src/process-map/validate.ts +135 -135
- package/src/products/__tests__/validate.test.ts +239 -239
- package/src/products/index.ts +9 -9
- package/src/products/types.ts +31 -31
- package/src/products/validate.ts +112 -112
- package/src/repo-validate/__tests__/validate-repo.test.ts +149 -149
- package/src/repo-validate/index.ts +2 -2
- package/src/repo-validate/types.ts +50 -50
- package/src/repo-validate/validate-repo.ts +196 -196
- package/src/requirement/__tests__/example.test.ts +24 -24
- package/src/requirement/__tests__/validate.test.ts +96 -96
- package/src/requirement/index.ts +4 -4
- package/src/requirement/types.ts +35 -35
- package/src/requirement/validate.ts +92 -92
- package/src/scenarios/__tests__/validate.test.ts +209 -209
- package/src/scenarios/index.ts +15 -15
- package/src/scenarios/types.ts +37 -37
- package/src/scenarios/validate.ts +122 -122
- package/src/schema-version.ts +16 -16
- package/src/scope.ts +46 -46
- package/src/stakeholder/__tests__/validate.test.ts +51 -47
- package/src/stakeholder/index.ts +1 -0
- package/src/stakeholder/types.ts +7 -7
- package/src/stakeholder/validate.ts +79 -52
- package/src/target-state/__tests__/validate.test.ts +48 -27
- package/src/target-state/index.ts +2 -0
- package/src/target-state/types.ts +11 -7
- package/src/target-state/validate.ts +76 -46
- package/src/theme/index.ts +4 -4
- package/src/theme/themes.ts +237 -237
- package/src/theme/tokens.ts +133 -133
- package/src/typed-id.ts +56 -56
- package/src/validation-types.ts +36 -36
- package/src/webview/__tests__/entry-notations.test.ts +77 -77
- package/src/webview/__tests__/entry.test.ts +64 -64
- package/src/webview/__tests__/render-goals.test.ts +72 -72
- package/src/webview/entry.ts +292 -292
- package/src/webview/render-activities.ts +5 -3
- package/src/webview/render-activity-card.ts +266 -234
- package/src/webview/render-fgca.ts +6 -5
- package/src/webview/render-goals.ts +111 -110
- package/src/webview/render-process-blueprint.ts +207 -207
- package/src/webview/styles.css +322 -323
- package/src/yaml-normalize.ts +56 -56
- package/dist/element-envelope.d.ts +0 -15
- package/dist/element-envelope.js +0 -55
- package/dist/issues/__tests__/layout.test.js +0 -62
- package/dist/issues/__tests__/validate.test.d.ts +0 -1
- package/dist/issues/__tests__/validate.test.js +0 -82
- package/dist/issues/index.d.ts +0 -4
- package/dist/issues/index.js +0 -2
- package/dist/issues/layout.d.ts +0 -10
- package/dist/issues/layout.js +0 -82
- package/dist/issues/types.d.ts +0 -61
- package/dist/issues/types.js +0 -6
- package/dist/issues/validate.d.ts +0 -3
- package/dist/issues/validate.js +0 -143
- package/dist/webview/transitrix-render.css +0 -323
- package/dist/webview/transitrix-render.js +0 -6452
- package/dist/webview/transitrix-render.js.map +0 -7
- package/src/element-envelope.ts +0 -76
- /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
|
+
}
|
package/src/schema-version.ts
CHANGED
|
@@ -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
|
|
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[] =>
|
|
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
|
|
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
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
expect(
|
|
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
|
-
|
|
48
|
-
|
|
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
|
|
52
|
-
expect(codes({ ...valid(), id: '
|
|
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
|
-
|
|
57
|
-
|
|
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
|
|
64
|
-
expect(codes({ ...valid(),
|
|
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('
|
|
68
|
-
|
|
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 —
|
|
79
|
+
describe('validateStakeholder — STAKE-002 (actor binding)', () => {
|
|
75
80
|
it('flags a missing actor', () => {
|
|
76
|
-
const
|
|
77
|
-
delete
|
|
78
|
-
expect(codes(
|
|
81
|
+
const s = valid();
|
|
82
|
+
delete s.actor;
|
|
83
|
+
expect(codes(s)).toContain('STAKE-002');
|
|
79
84
|
});
|
|
80
85
|
|
|
81
|
-
it('flags an actor
|
|
82
|
-
expect(codes({ ...valid(), actor: 'ROLE-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
expect(codes(
|
|
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
|
|
93
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
});
|
package/src/stakeholder/index.ts
CHANGED
package/src/stakeholder/types.ts
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
// STAKEHOLDER — motivation-layer interest primitive.
|
|
2
|
-
// Schema: methodology notations/ELEMENT_PRIMITIVES.md §7.
|
|
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
|
-
/**
|
|
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
|
}
|