@transitrix/diagrams 1.8.20 → 1.9.18
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/activity-card/types.js +1 -1
- package/dist/agreement.d.ts +15 -0
- package/dist/agreement.js +61 -0
- package/dist/applications/index.d.ts +2 -0
- package/dist/applications/index.js +1 -0
- package/dist/applications/resolve-maturity.d.ts +16 -0
- package/dist/applications/resolve-maturity.js +57 -0
- package/dist/applications/validate.js +20 -0
- package/dist/capability-map/CapabilityCardNode.d.ts +1 -1
- package/dist/capability-map/CapabilityRootNode.d.ts +1 -1
- package/dist/capability-map/index.d.ts +2 -0
- package/dist/capability-map/index.js +1 -0
- package/dist/capability-map/resolve-maturity.d.ts +20 -0
- package/dist/capability-map/resolve-maturity.js +72 -0
- package/dist/capability-map/validate.js +1 -2
- package/dist/compliance/classify.d.ts +10 -1
- package/dist/compliance/classify.js +44 -2
- package/dist/compliance/confidence.js +1 -1
- package/dist/compliance/gap-report.d.ts +31 -1
- package/dist/compliance/gap-report.js +55 -3
- package/dist/compliance/html.d.ts +1 -1
- package/dist/compliance/html.js +15 -7
- package/dist/compliance/impact.js +1 -1
- package/dist/compliance/index.d.ts +3 -1
- package/dist/compliance/index.js +2 -0
- package/dist/compliance/markdown.js +26 -4
- package/dist/compliance/reverse-index.d.ts +6 -1
- package/dist/compliance/reverse-index.js +27 -3
- package/dist/compliance/trace.js +5 -1
- package/dist/compliance/types.d.ts +70 -0
- package/dist/compliance/types.js +1 -2
- package/dist/compliance/views.js +1 -1
- package/dist/compliance-matrix/types.d.ts +1 -1
- package/dist/compliance-matrix/types.js +1 -1
- package/dist/confidence/score.js +1 -1
- package/dist/constraint/validate.js +3 -0
- package/dist/edge-path.js +1 -1
- package/dist/factor/validate.js +2 -2
- package/dist/fgca/chain-table.js +1 -1
- package/dist/fgca/nodeTypes.d.ts +4 -4
- package/dist/fgca/nodes/FGCAActivityNode.d.ts +1 -1
- package/dist/fgca/nodes/FGCAChangeNode.d.ts +1 -1
- package/dist/fgca/nodes/FGCAFactorNode.d.ts +1 -1
- package/dist/fgca/nodes/FGCAGoalNode.d.ts +1 -1
- package/dist/fgca/parse-canonical.d.ts +2 -2
- package/dist/fgca/parse-canonical.js +2 -2
- package/dist/fgca/preview-layout.d.ts +1 -1
- package/dist/goals/GoalNode.d.ts +1 -1
- package/dist/goals/layout.d.ts +1 -1
- package/dist/goals/layout.js +1 -1
- package/dist/goals/parse-canonical.d.ts +1 -1
- package/dist/goals/parse-canonical.js +1 -1
- package/dist/goals/types.d.ts +1 -1
- package/dist/goals/validate.js +2 -2
- package/dist/link-suspicion.d.ts +43 -0
- package/dist/link-suspicion.js +156 -0
- package/dist/metric/index.d.ts +4 -0
- package/dist/metric/index.js +2 -0
- package/dist/metric/types.d.ts +24 -0
- package/dist/metric/types.js +7 -0
- package/dist/metric/validate.d.ts +7 -0
- package/dist/metric/validate.js +90 -0
- package/dist/need/index.d.ts +3 -0
- package/dist/need/index.js +1 -0
- package/dist/need/types.d.ts +21 -0
- package/dist/need/types.js +3 -0
- package/dist/need/validate.d.ts +7 -0
- package/dist/need/validate.js +60 -0
- package/dist/repo-validate/check-versioned-attributes.d.ts +4 -0
- package/dist/repo-validate/check-versioned-attributes.js +105 -0
- package/dist/repo-validate/validate-repo.js +3 -0
- package/dist/requirement/index.d.ts +2 -2
- package/dist/requirement/index.js +1 -1
- package/dist/requirement/types.d.ts +18 -0
- package/dist/requirement/types.js +2 -0
- package/dist/requirement/validate.js +22 -1
- package/dist/risk/index.d.ts +4 -0
- package/dist/risk/index.js +2 -0
- package/dist/risk/types.d.ts +25 -0
- package/dist/risk/types.js +3 -0
- package/dist/risk/validate.d.ts +7 -0
- package/dist/risk/validate.js +108 -0
- package/dist/schema-version.d.ts +9 -6
- package/dist/schema-version.js +9 -6
- package/dist/theme/themes.js +3 -2
- package/dist/validation/index.d.ts +4 -0
- package/dist/validation/index.js +2 -0
- package/dist/validation/types.d.ts +31 -0
- package/dist/validation/types.js +21 -0
- package/dist/validation/validate.d.ts +7 -0
- package/dist/validation/validate.js +106 -0
- package/dist/verification/index.d.ts +4 -0
- package/dist/verification/index.js +2 -0
- package/dist/verification/types.d.ts +31 -0
- package/dist/verification/types.js +12 -0
- package/dist/verification/validate.d.ts +7 -0
- package/dist/verification/validate.js +104 -0
- package/dist/versioned-attribute/index.d.ts +4 -0
- package/dist/versioned-attribute/index.js +2 -0
- package/dist/versioned-attribute/resolve.d.ts +15 -0
- package/dist/versioned-attribute/resolve.js +37 -0
- package/dist/versioned-attribute/types.d.ts +12 -0
- package/dist/versioned-attribute/types.js +6 -0
- package/dist/versioned-attribute/validate.d.ts +17 -0
- package/dist/versioned-attribute/validate.js +87 -0
- package/dist/webview/render-applications.d.ts +15 -1
- package/dist/webview/render-applications.js +7 -2
- package/dist/webview/render-blocks.js +6 -4
- package/dist/webview/render-capability-map.d.ts +16 -1
- package/dist/webview/render-capability-map.js +4 -1
- package/dist/webview/render-process.d.ts +1 -1
- package/dist/webview/render-process.js +5 -5
- package/package.json +9 -6
- package/src/activity-card/types.ts +1 -1
- package/src/agreement.ts +71 -0
- package/src/applications/index.ts +3 -0
- package/src/applications/resolve-maturity.ts +66 -0
- package/src/applications/validate.ts +23 -0
- package/src/capability-map/index.ts +7 -0
- package/src/capability-map/resolve-maturity.ts +84 -0
- package/src/capability-map/validate.ts +1 -2
- package/src/compliance/classify.ts +54 -3
- package/src/compliance/confidence.ts +1 -1
- package/src/compliance/gap-report.ts +91 -4
- package/src/compliance/html.ts +19 -7
- package/src/compliance/impact.ts +1 -1
- package/src/compliance/index.ts +6 -0
- package/src/compliance/markdown.ts +18 -4
- package/src/compliance/reverse-index.ts +35 -4
- package/src/compliance/trace.ts +7 -1
- package/src/compliance/types.ts +75 -2
- package/src/compliance/views.ts +1 -1
- package/src/compliance-matrix/types.ts +2 -2
- package/src/confidence/score.ts +1 -1
- package/src/constraint/validate.ts +4 -0
- package/src/edge-path.ts +1 -1
- package/src/factor/validate.ts +2 -2
- package/src/fgca/chain-table.ts +1 -1
- package/src/fgca/parse-canonical.ts +2 -2
- package/src/fgca/preview-layout.ts +1 -1
- package/src/goals/layout.ts +1 -1
- package/src/goals/parse-canonical.ts +1 -1
- package/src/goals/types.ts +1 -1
- package/src/goals/validate.ts +2 -2
- package/src/link-suspicion.ts +181 -0
- package/src/metric/index.ts +4 -0
- package/src/metric/types.ts +38 -0
- package/src/metric/validate.ts +102 -0
- package/src/need/index.ts +3 -0
- package/src/need/types.ts +31 -0
- package/src/need/validate.ts +71 -0
- package/src/repo-validate/check-versioned-attributes.ts +112 -0
- package/src/repo-validate/validate-repo.ts +3 -0
- package/src/requirement/index.ts +2 -2
- package/src/requirement/types.ts +25 -0
- package/src/requirement/validate.ts +25 -1
- package/src/risk/index.ts +4 -0
- package/src/risk/types.ts +35 -0
- package/src/risk/validate.ts +121 -0
- package/src/schema-version.ts +9 -6
- package/src/theme/themes.ts +3 -2
- package/src/validation/index.ts +4 -0
- package/src/validation/types.ts +57 -0
- package/src/validation/validate.ts +113 -0
- package/src/verification/index.ts +4 -0
- package/src/verification/types.ts +48 -0
- package/src/verification/validate.ts +111 -0
- package/src/versioned-attribute/index.ts +4 -0
- package/src/versioned-attribute/resolve.ts +45 -0
- package/src/versioned-attribute/types.ts +19 -0
- package/src/versioned-attribute/validate.ts +109 -0
- package/src/webview/render-applications.ts +25 -2
- package/src/webview/render-blocks.ts +8 -4
- package/src/webview/render-capability-map.ts +23 -1
- package/src/webview/render-process.ts +5 -5
- package/src/webview/styles.css +6 -0
package/src/requirement/types.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// REQUIREMENT — motivation-layer positive obligation.
|
|
2
2
|
// Schema: methodology notations/elements/15-requirement.md §2.
|
|
3
3
|
|
|
4
|
+
import type { AgreementValue } from '../agreement.js';
|
|
5
|
+
|
|
4
6
|
export type RequirementSeverity = 'high' | 'medium' | 'low';
|
|
5
7
|
|
|
6
8
|
/** Standard canon admission gate checks (CONTRACT.md §6). */
|
|
@@ -13,6 +15,16 @@ export interface GateChecks {
|
|
|
13
15
|
/** TYPEs a requirement may be derived from (15-requirement.md §2, REQ-003). */
|
|
14
16
|
export const REQUIREMENT_DERIVED_FROM_TYPES = ['LAW', 'REGULATION', 'POLICY', 'INTERNAL_STANDARD'] as const;
|
|
15
17
|
|
|
18
|
+
/** ISO/IEC/IEEE 29148 specification-tier ladder (15-requirement.md §2.5, REQ-005). */
|
|
19
|
+
export type RequirementLevel = 'stakeholder' | 'system' | 'software';
|
|
20
|
+
|
|
21
|
+
export const REQUIREMENT_LEVELS: readonly RequirementLevel[] = ['stakeholder', 'system', 'software'];
|
|
22
|
+
|
|
23
|
+
/** Functional vs quality classification (15-requirement.md §2.6, REQ-006). */
|
|
24
|
+
export type RequirementKind = 'functional' | 'quality';
|
|
25
|
+
|
|
26
|
+
export const REQUIREMENT_KINDS: readonly RequirementKind[] = ['functional', 'quality'];
|
|
27
|
+
|
|
16
28
|
export interface Requirement {
|
|
17
29
|
notation: 'requirement';
|
|
18
30
|
id: string;
|
|
@@ -20,8 +32,21 @@ export interface Requirement {
|
|
|
20
32
|
description: string;
|
|
21
33
|
/** Organisation-defined priority. */
|
|
22
34
|
severity?: RequirementSeverity;
|
|
35
|
+
/** ISO/IEC/IEEE 29148 specification tier (§2.5, REQ-005). */
|
|
36
|
+
level?: RequirementLevel;
|
|
37
|
+
/** Functional vs quality classification (§2.6, REQ-006). */
|
|
38
|
+
kind?: RequirementKind;
|
|
23
39
|
/** Typed IDs of the codex source documents this requirement is drawn from. */
|
|
24
40
|
derived_from?: string[];
|
|
41
|
+
/** Typed ID of the upstream NEED this requirement traces to (§2.7, REQ-SERVES-001). Optional, singular. */
|
|
42
|
+
serves?: string;
|
|
43
|
+
|
|
44
|
+
// Agreement axis (CONTRACT.md §6.3, when present) — independent of admission.
|
|
45
|
+
/** `draft` \| `agreed` \| `disputed`. Absent ⇒ `agreed` (back-compat). */
|
|
46
|
+
agreement?: AgreementValue;
|
|
47
|
+
/** Required whenever `agreement` is present (AGREE-003). Written only by a human when `agreement: agreed` (AGREE-002). */
|
|
48
|
+
agreed_by?: string;
|
|
49
|
+
agreed_at?: string;
|
|
25
50
|
|
|
26
51
|
// Admission record (CONTRACT.md §6).
|
|
27
52
|
zone: 'canon';
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
|
|
12
12
|
import type { ValidationError, ValidationWarning, ValidationResult } from '../validation-types.js';
|
|
13
13
|
import { typeOfId, isCanonicalIdOfType, type CanonCatalog } from '../typed-id.js';
|
|
14
|
-
import {
|
|
14
|
+
import { checkAgreement } from '../agreement.js';
|
|
15
|
+
import { REQUIREMENT_DERIVED_FROM_TYPES, REQUIREMENT_LEVELS, REQUIREMENT_KINDS } from './types.js';
|
|
15
16
|
|
|
16
17
|
export interface RequirementValidateOptions {
|
|
17
18
|
/** When provided, REQ-002 enforces that each `derived_from` id is admitted. */
|
|
@@ -88,5 +89,28 @@ export function validateRequirement(input: unknown, options: RequirementValidate
|
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
91
|
|
|
92
|
+
// REQ-005 — level closed vocabulary (optional; only checked when present).
|
|
93
|
+
if (r.level !== undefined && !(REQUIREMENT_LEVELS as readonly string[]).includes(r.level as string)) {
|
|
94
|
+
errors.push({ code: 'REQ-005', message: `level "${String(r.level)}" must be one of ${REQUIREMENT_LEVELS.join(', ')}.`, path: 'level' });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// REQ-006 — kind closed vocabulary (optional; only checked when present).
|
|
98
|
+
if (r.kind !== undefined && !(REQUIREMENT_KINDS as readonly string[]).includes(r.kind as string)) {
|
|
99
|
+
errors.push({ code: 'REQ-006', message: `kind "${String(r.kind)}" must be one of ${REQUIREMENT_KINDS.join(', ')}.`, path: 'kind' });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// REQ-SERVES-001 — serves resolves to an admitted NEED (optional, singular).
|
|
103
|
+
if (r.serves !== undefined) {
|
|
104
|
+
const serves = r.serves;
|
|
105
|
+
if (typeof serves !== 'string' || serves.trim() === '' || typeOfId(serves) !== 'NEED') {
|
|
106
|
+
errors.push({ code: 'REQ-SERVES-001', message: `serves "${String(serves)}" must be a typed NEED id.`, path: 'serves' });
|
|
107
|
+
} else if (options.catalog && options.catalog.typeOf(serves) === undefined) {
|
|
108
|
+
errors.push({ code: 'REQ-SERVES-001', message: `serves "${serves}" does not resolve to an admitted NEED.`, path: 'serves' });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// AGREE-001..003 — agreement axis (CONTRACT.md §6.3.1), optional; absent ⇒ agreed.
|
|
113
|
+
errors.push(...checkAgreement(r));
|
|
114
|
+
|
|
91
115
|
return { valid: errors.length === 0, errors, warnings };
|
|
92
116
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// RISK — motivation-layer projected event (no ArchiMate counterpart).
|
|
2
|
+
// Schema: methodology notations/ELEMENT_PRIMITIVES.md §7.26.
|
|
3
|
+
|
|
4
|
+
import type { GateChecks } from '../requirement/types.js';
|
|
5
|
+
|
|
6
|
+
/** Likelihood / impact / residual severity vocabulary (§7.26). */
|
|
7
|
+
export type RiskLevel = 'low' | 'medium' | 'high';
|
|
8
|
+
|
|
9
|
+
export const RISK_LEVELS: readonly RiskLevel[] = ['low', 'medium', 'high'];
|
|
10
|
+
|
|
11
|
+
export interface Risk {
|
|
12
|
+
notation: 'risk';
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
likelihood: RiskLevel;
|
|
16
|
+
impact: RiskLevel;
|
|
17
|
+
residual: RiskLevel;
|
|
18
|
+
/** ROLE-… accountable for tracking and treating this risk. */
|
|
19
|
+
owner_role: string;
|
|
20
|
+
/** Typed IDs of the elements this risk threatens. Non-empty; any TYPE. */
|
|
21
|
+
threatens: string[];
|
|
22
|
+
/** Typed IDs (REQUIREMENT-… / CONSTRAINT-…) of the treatment obligations. */
|
|
23
|
+
treated_by?: string[];
|
|
24
|
+
description?: string;
|
|
25
|
+
|
|
26
|
+
// Admission record (CONTRACT.md §6).
|
|
27
|
+
zone: 'canon';
|
|
28
|
+
admitted_at: string;
|
|
29
|
+
admitted_by: string;
|
|
30
|
+
gate_checks: GateChecks;
|
|
31
|
+
|
|
32
|
+
// Primitive lifecycle (CONTRACT.md §7).
|
|
33
|
+
valid_from: string;
|
|
34
|
+
valid_to: string | null;
|
|
35
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// RISK validator — methodology ELEMENT_PRIMITIVES.md §7.26 / §9.
|
|
2
|
+
//
|
|
3
|
+
// Codes:
|
|
4
|
+
// RISK-001 — shape / id grammar / required envelope + per-type fields
|
|
5
|
+
// (likelihood, impact, residual, owner_role, threatens).
|
|
6
|
+
// RISK-002 — likelihood / impact / residual outside {low, medium, high}.
|
|
7
|
+
// RISK-003 — threatens is empty, or an entry does not resolve to an
|
|
8
|
+
// admitted element in canon (no TYPE restriction — any core
|
|
9
|
+
// element may be threatened).
|
|
10
|
+
// RISK-004 — a treated_by entry does not resolve, or resolves to a TYPE
|
|
11
|
+
// other than REQUIREMENT / CONSTRAINT.
|
|
12
|
+
// RISK-COVERAGE-001 (warning) — treated_by is empty/absent (an untreated
|
|
13
|
+
// risk). Unlike the cross-cutting *-COVERAGE-001 rules this
|
|
14
|
+
// reads only the RISK element's own field — no catalogue scan
|
|
15
|
+
// required — so it is implemented inline here rather than via
|
|
16
|
+
// the compliance reverse-index.
|
|
17
|
+
//
|
|
18
|
+
// Resolution rules (RISK-003/004) that need artefact *existence* run only
|
|
19
|
+
// when a `CanonCatalog` is supplied; the TYPE check RISK-004 also carries is
|
|
20
|
+
// derivable from the id prefix and runs either way.
|
|
21
|
+
|
|
22
|
+
import type { ValidationError, ValidationWarning, ValidationResult } from '../validation-types.js';
|
|
23
|
+
import { typeOfId, isCanonicalIdOfType, type CanonCatalog } from '../typed-id.js';
|
|
24
|
+
import { RISK_LEVELS } from './types.js';
|
|
25
|
+
|
|
26
|
+
export interface RiskValidateOptions {
|
|
27
|
+
/** When provided, RISK-003/004 enforce artefact existence for cross-refs. */
|
|
28
|
+
catalog?: CanonCatalog;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const TREATED_BY_TYPES = ['REQUIREMENT', 'CONSTRAINT'] as const;
|
|
32
|
+
|
|
33
|
+
export function validateRisk(input: unknown, options: RiskValidateOptions = {}): ValidationResult {
|
|
34
|
+
const errors: ValidationError[] = [];
|
|
35
|
+
const warnings: ValidationWarning[] = [];
|
|
36
|
+
|
|
37
|
+
if (input === null || typeof input !== 'object' || Array.isArray(input)) {
|
|
38
|
+
errors.push({ code: 'RISK-001', message: 'Risk must be a YAML mapping.' });
|
|
39
|
+
return { valid: false, errors, warnings };
|
|
40
|
+
}
|
|
41
|
+
const r = input as Record<string, unknown>;
|
|
42
|
+
|
|
43
|
+
// RISK-001 — id grammar + notation tag + envelope + plain per-type fields.
|
|
44
|
+
if (!isCanonicalIdOfType(r.id, 'RISK')) {
|
|
45
|
+
errors.push({ code: 'RISK-001', message: `id "${String(r.id)}" must match RISK-[<middle>-]<INTEGER>.`, path: 'id' });
|
|
46
|
+
}
|
|
47
|
+
if (r.notation !== 'risk') {
|
|
48
|
+
errors.push({ code: 'RISK-001', message: 'notation must be the fixed value "risk".', path: 'notation' });
|
|
49
|
+
}
|
|
50
|
+
if (r.zone !== 'canon') {
|
|
51
|
+
errors.push({ code: 'RISK-001', message: 'zone is required and must be "canon".', path: 'zone' });
|
|
52
|
+
}
|
|
53
|
+
for (const f of ['name', 'admitted_at', 'admitted_by', 'valid_from', 'owner_role'] as const) {
|
|
54
|
+
if (typeof r[f] !== 'string' || (r[f] as string).trim() === '') {
|
|
55
|
+
errors.push({ code: 'RISK-001', message: `${f} is required.`, path: f });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (r.gate_checks === null || typeof r.gate_checks !== 'object') {
|
|
59
|
+
errors.push({ code: 'RISK-001', message: 'gate_checks is required and must be a mapping.', path: 'gate_checks' });
|
|
60
|
+
}
|
|
61
|
+
if (!('valid_to' in r) || !(typeof r.valid_to === 'string' || r.valid_to === null)) {
|
|
62
|
+
errors.push({ code: 'RISK-001', message: 'valid_to is required (an ISO date string or null).', path: 'valid_to' });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// likelihood / impact / residual — required (RISK-001) + enum (RISK-002).
|
|
66
|
+
for (const f of ['likelihood', 'impact', 'residual'] as const) {
|
|
67
|
+
const v = r[f];
|
|
68
|
+
if (v === undefined || v === null || v === '') {
|
|
69
|
+
errors.push({ code: 'RISK-001', message: `${f} is required.`, path: f });
|
|
70
|
+
} else if (!(RISK_LEVELS as readonly string[]).includes(v as string)) {
|
|
71
|
+
errors.push({ code: 'RISK-002', message: `${f} "${String(v)}" must be one of ${RISK_LEVELS.join(', ')}.`, path: f });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// threatens — required non-empty list (RISK-001), each entry resolves (RISK-003).
|
|
76
|
+
const threatens = r.threatens;
|
|
77
|
+
if (!Array.isArray(threatens) || threatens.length === 0) {
|
|
78
|
+
errors.push({ code: 'RISK-001', message: 'threatens is required and must be a non-empty list of typed IDs.', path: 'threatens' });
|
|
79
|
+
} else {
|
|
80
|
+
threatens.forEach((ref, i) => {
|
|
81
|
+
const type = typeOfId(ref);
|
|
82
|
+
if (!type) {
|
|
83
|
+
errors.push({ code: 'RISK-003', message: `threatens[${i}] "${String(ref)}" is not a resolvable typed ID.`, path: `threatens[${i}]` });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (options.catalog && options.catalog.typeOf(ref as string) === undefined) {
|
|
87
|
+
errors.push({ code: 'RISK-003', message: `threatens[${i}] "${String(ref)}" does not resolve to an admitted element.`, path: `threatens[${i}]` });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// treated_by — optional list; each entry must resolve to REQUIREMENT/CONSTRAINT (RISK-004).
|
|
93
|
+
const treatedBy = r.treated_by;
|
|
94
|
+
if (treatedBy !== undefined) {
|
|
95
|
+
if (!Array.isArray(treatedBy)) {
|
|
96
|
+
errors.push({ code: 'RISK-001', message: 'treated_by must be a list of typed IDs.', path: 'treated_by' });
|
|
97
|
+
} else {
|
|
98
|
+
treatedBy.forEach((ref, i) => {
|
|
99
|
+
const type = typeOfId(ref);
|
|
100
|
+
if (!type || !(TREATED_BY_TYPES as readonly string[]).includes(type as typeof TREATED_BY_TYPES[number])) {
|
|
101
|
+
errors.push({ code: 'RISK-004', message: `treated_by[${i}] "${String(ref)}" must resolve to a REQUIREMENT or CONSTRAINT.`, path: `treated_by[${i}]` });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (options.catalog && options.catalog.typeOf(ref as string) === undefined) {
|
|
105
|
+
errors.push({ code: 'RISK-004', message: `treated_by[${i}] "${String(ref)}" does not resolve to an admitted artefact.`, path: `treated_by[${i}]` });
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// RISK-COVERAGE-001 (warning) — untreated risk; single-file check, no catalogue needed.
|
|
112
|
+
if (!Array.isArray(treatedBy) || treatedBy.length === 0) {
|
|
113
|
+
warnings.push({
|
|
114
|
+
code: 'RISK-COVERAGE-001',
|
|
115
|
+
message: 'treated_by is empty or absent — this risk has no recorded treatment obligation.',
|
|
116
|
+
path: 'treated_by',
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
121
|
+
}
|
package/src/schema-version.ts
CHANGED
|
@@ -4,13 +4,16 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Source of truth: the methodology `methodology_version`, declared in an adopter
|
|
6
6
|
* repository's `transitrix.yaml` per the methodology `notations/MANIFEST.md`
|
|
7
|
-
* (single source of truth for a repo's conformance).
|
|
8
|
-
*
|
|
7
|
+
* (single source of truth for a repo's conformance).
|
|
8
|
+
*
|
|
9
|
+
* This is the release this build *targets*, not the newest release that exists —
|
|
10
|
+
* the methodology may be ahead. Raise it only together with the vocabulary and
|
|
11
|
+
* rule coverage that release requires, so the two never disagree. Individual
|
|
12
|
+
* hardcoded vocabulary literals may still lag the declared release; the
|
|
13
|
+
* per-module drift check is what makes such a gap visible, not this constant.
|
|
9
14
|
*
|
|
10
15
|
* This constant is kept in lockstep with the project manifest's
|
|
11
16
|
* `transitrix.methodologyVersion` (`package.json`); the
|
|
12
|
-
* `tests/schema-version.test.ts` unit test asserts the two are equal.
|
|
13
|
-
* that additionally asserts both equal the methodology's published SoT lands
|
|
14
|
-
* separately (SV-1 PR2) — do not bundle it here.
|
|
17
|
+
* `tests/schema-version.test.ts` unit test asserts the two are equal.
|
|
15
18
|
*/
|
|
16
|
-
export const SCHEMA_VERSION = '
|
|
19
|
+
export const SCHEMA_VERSION = '3.1.0';
|
package/src/theme/themes.ts
CHANGED
|
@@ -118,7 +118,7 @@ function shellCss(): string {
|
|
|
118
118
|
const cv = CSS_VAR;
|
|
119
119
|
const t = TYPOGRAPHY;
|
|
120
120
|
// Full-height flex chain lifted from blocks-preview / activities-preview
|
|
121
|
-
// into the shared shell
|
|
121
|
+
// into the shared shell. Body is a flex column
|
|
122
122
|
// sized to the iframe; #canvas takes the remaining height with its own
|
|
123
123
|
// scroll region. Without this, #canvas was content-height and any
|
|
124
124
|
// overflow-x scrollbar landed mid-panel with empty space below it — the
|
|
@@ -184,7 +184,8 @@ ${levelRules}
|
|
|
184
184
|
.tree-maturity-5{fill:var(${cv.treeMaturity5});}
|
|
185
185
|
.tree-collapse-btn{cursor:pointer;fill:white;stroke:var(${cv.nodeStroke});stroke-width:1.5;}
|
|
186
186
|
.tree-collapse-btn:hover{fill:var(${cv.bgElevated});}
|
|
187
|
-
.tree-collapse-lbl{fill:var(${cv.textSecondary});font-family:${t.fontFamily};font-size:12px;font-weight:700;dominant-baseline:central;pointer-events:none;}
|
|
187
|
+
.tree-collapse-lbl{fill:var(${cv.textSecondary});font-family:${t.fontFamily};font-size:12px;font-weight:700;dominant-baseline:central;pointer-events:none;}
|
|
188
|
+
.blocks-grid-border{fill:none;stroke:var(${cv.border});}`;
|
|
188
189
|
}
|
|
189
190
|
|
|
190
191
|
/**
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { Validation, ValidationMethod, ValidationOutcome } from './types.js';
|
|
2
|
+
export { VALIDATION_METHODS, VALIDATION_OUTCOMES, CLOSED_VALIDATION_OUTCOMES } from './types.js';
|
|
3
|
+
export { validateValidation } from './validate.js';
|
|
4
|
+
export type { ValidationValidateOptions } from './validate.js';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// VALIDATION — validation-domain claim against a NEED.
|
|
2
|
+
// Schema: methodology notations/elements/28-validation.md §2.
|
|
3
|
+
//
|
|
4
|
+
// VALIDATION is the validation-domain counterpart to VERIFICATION (same claim
|
|
5
|
+
// shape, anchored one layer further upstream on NEED instead of REQUIREMENT).
|
|
6
|
+
|
|
7
|
+
import type { GateChecks } from '../requirement/types.js';
|
|
8
|
+
import type { Evidence } from '../assertion/types.js';
|
|
9
|
+
|
|
10
|
+
/** Validation method vocabulary (28-validation.md §3). */
|
|
11
|
+
export type ValidationMethod = 'user_acceptance' | 'field_trial' | 'stakeholder_review' | 'usability_study';
|
|
12
|
+
|
|
13
|
+
export const VALIDATION_METHODS: readonly ValidationMethod[] = [
|
|
14
|
+
'user_acceptance',
|
|
15
|
+
'field_trial',
|
|
16
|
+
'stakeholder_review',
|
|
17
|
+
'usability_study',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
/** Pass/fail judgement vocabulary (28-validation.md §3). */
|
|
21
|
+
export type ValidationOutcome = 'pass' | 'fail' | 'inconclusive' | 'not_yet_run';
|
|
22
|
+
|
|
23
|
+
export const VALIDATION_OUTCOMES: readonly ValidationOutcome[] = [
|
|
24
|
+
'pass',
|
|
25
|
+
'fail',
|
|
26
|
+
'inconclusive',
|
|
27
|
+
'not_yet_run',
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
/** Outcomes that count as "closed" — the trace link has resolved one way or
|
|
31
|
+
* the other, distinct from still-open (`not_yet_run` / `inconclusive`)
|
|
32
|
+
* (28-validation.md §5, NEED-VALIDATION-COVERAGE-002). */
|
|
33
|
+
export const CLOSED_VALIDATION_OUTCOMES: readonly ValidationOutcome[] = ['pass', 'fail'];
|
|
34
|
+
|
|
35
|
+
export interface Validation {
|
|
36
|
+
notation: 'validation';
|
|
37
|
+
id: string;
|
|
38
|
+
/** Typed ID of the NEED this validation targets. */
|
|
39
|
+
validates: string;
|
|
40
|
+
method: ValidationMethod;
|
|
41
|
+
protocol: string;
|
|
42
|
+
result?: string;
|
|
43
|
+
outcome: ValidationOutcome;
|
|
44
|
+
evidence?: Evidence[];
|
|
45
|
+
performed_at?: string;
|
|
46
|
+
performed_by?: string;
|
|
47
|
+
|
|
48
|
+
// Admission record (CONTRACT.md §6).
|
|
49
|
+
zone: 'canon';
|
|
50
|
+
admitted_at: string;
|
|
51
|
+
admitted_by: string;
|
|
52
|
+
gate_checks: GateChecks;
|
|
53
|
+
|
|
54
|
+
// Primitive lifecycle (CONTRACT.md §7).
|
|
55
|
+
valid_from: string;
|
|
56
|
+
valid_to: string | null;
|
|
57
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// VALIDATION validator — methodology notations/elements/28-validation.md §5.
|
|
2
|
+
//
|
|
3
|
+
// Implements VALID-001..006. The shared HDR-/LIFECYCLE- rules and the
|
|
4
|
+
// cross-cutting NEED-VALIDATION-COVERAGE-001/002 reverse-trace rules are
|
|
5
|
+
// owned elsewhere (CONTRACT.md §8) and are out of scope for this
|
|
6
|
+
// single-artefact validator — same posture as `validateVerification`
|
|
7
|
+
// (../verification/validate.ts), which VALIDATION mirrors one layer upstream
|
|
8
|
+
// (anchored on NEED instead of REQUIREMENT).
|
|
9
|
+
//
|
|
10
|
+
// Resolution rules (VALID-002/005) that need artefact *existence* run only
|
|
11
|
+
// when a `CanonCatalog` is supplied; the TYPE check VALID-002 also carries is
|
|
12
|
+
// derivable from the id prefix and runs either way.
|
|
13
|
+
|
|
14
|
+
import type { ValidationError, ValidationWarning, ValidationResult } from '../validation-types.js';
|
|
15
|
+
import { typeOfId, isCanonicalIdOfType, type CanonCatalog } from '../typed-id.js';
|
|
16
|
+
import { VALIDATION_METHODS, VALIDATION_OUTCOMES } from './types.js';
|
|
17
|
+
|
|
18
|
+
export interface ValidationValidateOptions {
|
|
19
|
+
/** When provided, the resolution rules enforce artefact existence. */
|
|
20
|
+
catalog?: CanonCatalog;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function validateValidation(input: unknown, options: ValidationValidateOptions = {}): ValidationResult {
|
|
24
|
+
const errors: ValidationError[] = [];
|
|
25
|
+
const warnings: ValidationWarning[] = [];
|
|
26
|
+
const { catalog } = options;
|
|
27
|
+
|
|
28
|
+
if (input === null || typeof input !== 'object' || Array.isArray(input)) {
|
|
29
|
+
errors.push({ code: 'VALID-001', message: 'Validation must be a YAML mapping.' });
|
|
30
|
+
return { valid: false, errors, warnings };
|
|
31
|
+
}
|
|
32
|
+
const v = input as Record<string, unknown>;
|
|
33
|
+
|
|
34
|
+
// ── VALID-001 — id grammar + plain required fields ───────────────────────
|
|
35
|
+
if (!isCanonicalIdOfType(v.id, 'VALIDATION')) {
|
|
36
|
+
errors.push({ code: 'VALID-001', message: `id "${String(v.id)}" must match VALIDATION-[<middle>-]<INTEGER>.`, path: 'id' });
|
|
37
|
+
}
|
|
38
|
+
if (v.notation !== 'validation') {
|
|
39
|
+
errors.push({ code: 'VALID-001', message: 'notation must be the fixed value "validation".', path: 'notation' });
|
|
40
|
+
}
|
|
41
|
+
if (v.zone !== 'canon') {
|
|
42
|
+
errors.push({ code: 'VALID-001', message: 'zone is required and must be "canon".', path: 'zone' });
|
|
43
|
+
}
|
|
44
|
+
for (const f of ['admitted_at', 'admitted_by', 'valid_from', 'protocol'] as const) {
|
|
45
|
+
if (typeof v[f] !== 'string' || (v[f] as string).trim() === '') {
|
|
46
|
+
errors.push({ code: 'VALID-001', message: `${f} is required.`, path: f });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (v.gate_checks === null || typeof v.gate_checks !== 'object') {
|
|
50
|
+
errors.push({ code: 'VALID-001', message: 'gate_checks is required and must be a mapping.', path: 'gate_checks' });
|
|
51
|
+
}
|
|
52
|
+
if (!('valid_to' in v) || !(typeof v.valid_to === 'string' || v.valid_to === null)) {
|
|
53
|
+
errors.push({ code: 'VALID-001', message: 'valid_to is required (an ISO date string or null).', path: 'valid_to' });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── VALID-002 — validates → NEED ─────────────────────────────────────────
|
|
57
|
+
const validates = v.validates;
|
|
58
|
+
if (typeof validates !== 'string' || validates.trim() === '') {
|
|
59
|
+
errors.push({ code: 'VALID-002', message: 'validates is required and must be a typed NEED id.', path: 'validates' });
|
|
60
|
+
} else if (catalog) {
|
|
61
|
+
const t = catalog.typeOf(validates);
|
|
62
|
+
if (t === undefined) {
|
|
63
|
+
errors.push({ code: 'VALID-002', message: `validates "${validates}" does not resolve to an admitted artefact.`, path: 'validates' });
|
|
64
|
+
} else if (t !== 'NEED') {
|
|
65
|
+
errors.push({ code: 'VALID-002', message: `validates "${validates}" resolves to a ${t}, not a NEED.`, path: 'validates' });
|
|
66
|
+
}
|
|
67
|
+
} else if (typeOfId(validates) !== 'NEED') {
|
|
68
|
+
errors.push({ code: 'VALID-002', message: `validates "${validates}" must be a NEED typed id.`, path: 'validates' });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── VALID-003 — method enum ───────────────────────────────────────────────
|
|
72
|
+
const method = v.method;
|
|
73
|
+
if (method === undefined || method === null) {
|
|
74
|
+
errors.push({ code: 'VALID-001', message: 'method is required.', path: 'method' });
|
|
75
|
+
} else if (!VALIDATION_METHODS.includes(method as never)) {
|
|
76
|
+
errors.push({ code: 'VALID-003', message: `method "${String(method)}" must be one of ${VALIDATION_METHODS.join(', ')}.`, path: 'method' });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── VALID-004 — outcome enum ──────────────────────────────────────────────
|
|
80
|
+
const outcome = v.outcome;
|
|
81
|
+
if (outcome === undefined || outcome === null) {
|
|
82
|
+
errors.push({ code: 'VALID-001', message: 'outcome is required.', path: 'outcome' });
|
|
83
|
+
} else if (!VALIDATION_OUTCOMES.includes(outcome as never)) {
|
|
84
|
+
errors.push({ code: 'VALID-004', message: `outcome "${String(outcome)}" must be one of ${VALIDATION_OUTCOMES.join(', ')}.`, path: 'outcome' });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── VALID-005 — canonical_ref evidence resolves ──────────────────────────
|
|
88
|
+
if (v.evidence !== undefined) {
|
|
89
|
+
if (!Array.isArray(v.evidence)) {
|
|
90
|
+
errors.push({ code: 'VALID-001', message: 'evidence must be a list.', path: 'evidence' });
|
|
91
|
+
} else {
|
|
92
|
+
v.evidence.forEach((e, i) => {
|
|
93
|
+
if (e === null || typeof e !== 'object') return;
|
|
94
|
+
const entry = e as Record<string, unknown>;
|
|
95
|
+
if (entry.kind !== 'canonical_ref') return;
|
|
96
|
+
const ref = entry.ref;
|
|
97
|
+
if (!typeOfId(ref)) {
|
|
98
|
+
errors.push({ code: 'VALID-005', message: `evidence[${i}] canonical_ref "${String(ref)}" is not a resolvable typed ID.`, path: `evidence[${i}].ref` });
|
|
99
|
+
} else if (catalog && catalog.typeOf(ref as string) === undefined) {
|
|
100
|
+
errors.push({ code: 'VALID-005', message: `evidence[${i}] canonical_ref "${String(ref)}" does not resolve.`, path: `evidence[${i}].ref` });
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── VALID-006 (warning) — pass outcome with no evidence ──────────────────
|
|
107
|
+
const evidenceEmpty = !Array.isArray(v.evidence) || v.evidence.length === 0;
|
|
108
|
+
if (evidenceEmpty && outcome === 'pass') {
|
|
109
|
+
warnings.push({ code: 'VALID-006', message: 'outcome "pass" has no evidence — an undefended positive claim.', path: 'evidence' });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
113
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { Verification, VerificationMethod, VerificationOutcome } from './types.js';
|
|
2
|
+
export { VERIFICATION_METHODS, VERIFICATION_OUTCOMES, CLOSED_VERIFICATION_OUTCOMES } from './types.js';
|
|
3
|
+
export { validateVerification } from './validate.js';
|
|
4
|
+
export type { VerificationValidateOptions } from './validate.js';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// VERIFICATION — engineering V&V claim against a REQUIREMENT.
|
|
2
|
+
// Schema: methodology notations/elements/27-verification.md §2.
|
|
3
|
+
|
|
4
|
+
import type { GateChecks } from '../requirement/types.js';
|
|
5
|
+
import type { Evidence } from '../assertion/types.js';
|
|
6
|
+
|
|
7
|
+
/** V&V method vocabulary (27-verification.md §3). */
|
|
8
|
+
export type VerificationMethod = 'test' | 'analysis' | 'inspection' | 'demonstration';
|
|
9
|
+
|
|
10
|
+
export const VERIFICATION_METHODS: readonly VerificationMethod[] = [
|
|
11
|
+
'test', 'analysis', 'inspection', 'demonstration',
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
/** Pass/fail judgement vocabulary (27-verification.md §3). */
|
|
15
|
+
export type VerificationOutcome = 'pass' | 'fail' | 'inconclusive' | 'not_yet_run';
|
|
16
|
+
|
|
17
|
+
export const VERIFICATION_OUTCOMES: readonly VerificationOutcome[] = [
|
|
18
|
+
'pass', 'fail', 'inconclusive', 'not_yet_run',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
/** Outcomes that count as "closed" — the trace link has resolved one way or
|
|
22
|
+
* the other, distinct from still-open (`not_yet_run` / `inconclusive`)
|
|
23
|
+
* (27-verification.md §5, REQ-VERIF-COVERAGE-002). */
|
|
24
|
+
export const CLOSED_VERIFICATION_OUTCOMES: readonly VerificationOutcome[] = ['pass', 'fail'];
|
|
25
|
+
|
|
26
|
+
export interface Verification {
|
|
27
|
+
notation: 'verification';
|
|
28
|
+
id: string;
|
|
29
|
+
/** Typed ID of the REQUIREMENT this verification targets. */
|
|
30
|
+
verifies: string;
|
|
31
|
+
method: VerificationMethod;
|
|
32
|
+
protocol: string;
|
|
33
|
+
result?: string;
|
|
34
|
+
outcome: VerificationOutcome;
|
|
35
|
+
evidence?: Evidence[];
|
|
36
|
+
performed_at?: string;
|
|
37
|
+
performed_by?: string;
|
|
38
|
+
|
|
39
|
+
// Admission record (CONTRACT.md §6).
|
|
40
|
+
zone: 'canon';
|
|
41
|
+
admitted_at: string;
|
|
42
|
+
admitted_by: string;
|
|
43
|
+
gate_checks: GateChecks;
|
|
44
|
+
|
|
45
|
+
// Primitive lifecycle (CONTRACT.md §7).
|
|
46
|
+
valid_from: string;
|
|
47
|
+
valid_to: string | null;
|
|
48
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// VERIFICATION validator — methodology notations/elements/27-verification.md §5.
|
|
2
|
+
//
|
|
3
|
+
// Implements VERIF-001..006. The shared HDR-/LIFECYCLE- rules and the
|
|
4
|
+
// cross-cutting REQ-VERIF-COVERAGE-001/002 reverse-trace rules are owned
|
|
5
|
+
// elsewhere (CONTRACT.md §8) and are out of scope for this single-artefact
|
|
6
|
+
// validator — same posture as `validateAssertion` (../assertion/validate.ts).
|
|
7
|
+
//
|
|
8
|
+
// Resolution rules (VERIF-002/005) that need artefact *existence* run only
|
|
9
|
+
// when a `CanonCatalog` is supplied; the TYPE check VERIF-002 also carries is
|
|
10
|
+
// derivable from the id prefix and runs either way.
|
|
11
|
+
|
|
12
|
+
import type { ValidationError, ValidationWarning, ValidationResult } from '../validation-types.js';
|
|
13
|
+
import { typeOfId, isCanonicalIdOfType, type CanonCatalog } from '../typed-id.js';
|
|
14
|
+
import { VERIFICATION_METHODS, VERIFICATION_OUTCOMES } from './types.js';
|
|
15
|
+
|
|
16
|
+
export interface VerificationValidateOptions {
|
|
17
|
+
/** When provided, the resolution rules enforce artefact existence. */
|
|
18
|
+
catalog?: CanonCatalog;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function validateVerification(input: unknown, options: VerificationValidateOptions = {}): ValidationResult {
|
|
22
|
+
const errors: ValidationError[] = [];
|
|
23
|
+
const warnings: ValidationWarning[] = [];
|
|
24
|
+
const { catalog } = options;
|
|
25
|
+
|
|
26
|
+
if (input === null || typeof input !== 'object' || Array.isArray(input)) {
|
|
27
|
+
errors.push({ code: 'VERIF-001', message: 'Verification must be a YAML mapping.' });
|
|
28
|
+
return { valid: false, errors, warnings };
|
|
29
|
+
}
|
|
30
|
+
const v = input as Record<string, unknown>;
|
|
31
|
+
|
|
32
|
+
// ── VERIF-001 — id grammar + plain required fields ───────────────────────
|
|
33
|
+
if (!isCanonicalIdOfType(v.id, 'VERIFICATION')) {
|
|
34
|
+
errors.push({ code: 'VERIF-001', message: `id "${String(v.id)}" must match VERIFICATION-[<middle>-]<INTEGER>.`, path: 'id' });
|
|
35
|
+
}
|
|
36
|
+
if (v.notation !== 'verification') {
|
|
37
|
+
errors.push({ code: 'VERIF-001', message: 'notation must be the fixed value "verification".', path: 'notation' });
|
|
38
|
+
}
|
|
39
|
+
if (v.zone !== 'canon') {
|
|
40
|
+
errors.push({ code: 'VERIF-001', message: 'zone is required and must be "canon".', path: 'zone' });
|
|
41
|
+
}
|
|
42
|
+
for (const f of ['admitted_at', 'admitted_by', 'valid_from', 'protocol'] as const) {
|
|
43
|
+
if (typeof v[f] !== 'string' || (v[f] as string).trim() === '') {
|
|
44
|
+
errors.push({ code: 'VERIF-001', message: `${f} is required.`, path: f });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (v.gate_checks === null || typeof v.gate_checks !== 'object') {
|
|
48
|
+
errors.push({ code: 'VERIF-001', message: 'gate_checks is required and must be a mapping.', path: 'gate_checks' });
|
|
49
|
+
}
|
|
50
|
+
if (!('valid_to' in v) || !(typeof v.valid_to === 'string' || v.valid_to === null)) {
|
|
51
|
+
errors.push({ code: 'VERIF-001', message: 'valid_to is required (an ISO date string or null).', path: 'valid_to' });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── VERIF-002 — verifies → REQUIREMENT ───────────────────────────────────
|
|
55
|
+
const verifies = v.verifies;
|
|
56
|
+
if (typeof verifies !== 'string' || verifies.trim() === '') {
|
|
57
|
+
errors.push({ code: 'VERIF-002', message: 'verifies is required and must be a typed REQUIREMENT id.', path: 'verifies' });
|
|
58
|
+
} else if (catalog) {
|
|
59
|
+
const t = catalog.typeOf(verifies);
|
|
60
|
+
if (t === undefined) {
|
|
61
|
+
errors.push({ code: 'VERIF-002', message: `verifies "${verifies}" does not resolve to an admitted artefact.`, path: 'verifies' });
|
|
62
|
+
} else if (t !== 'REQUIREMENT') {
|
|
63
|
+
errors.push({ code: 'VERIF-002', message: `verifies "${verifies}" resolves to a ${t}, not a REQUIREMENT.`, path: 'verifies' });
|
|
64
|
+
}
|
|
65
|
+
} else if (typeOfId(verifies) !== 'REQUIREMENT') {
|
|
66
|
+
errors.push({ code: 'VERIF-002', message: `verifies "${verifies}" must be a REQUIREMENT typed id.`, path: 'verifies' });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── VERIF-003 — method enum ───────────────────────────────────────────────
|
|
70
|
+
const method = v.method;
|
|
71
|
+
if (method === undefined || method === null) {
|
|
72
|
+
errors.push({ code: 'VERIF-001', message: 'method is required.', path: 'method' });
|
|
73
|
+
} else if (!VERIFICATION_METHODS.includes(method as never)) {
|
|
74
|
+
errors.push({ code: 'VERIF-003', message: `method "${String(method)}" must be one of ${VERIFICATION_METHODS.join(', ')}.`, path: 'method' });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── VERIF-004 — outcome enum ──────────────────────────────────────────────
|
|
78
|
+
const outcome = v.outcome;
|
|
79
|
+
if (outcome === undefined || outcome === null) {
|
|
80
|
+
errors.push({ code: 'VERIF-001', message: 'outcome is required.', path: 'outcome' });
|
|
81
|
+
} else if (!VERIFICATION_OUTCOMES.includes(outcome as never)) {
|
|
82
|
+
errors.push({ code: 'VERIF-004', message: `outcome "${String(outcome)}" must be one of ${VERIFICATION_OUTCOMES.join(', ')}.`, path: 'outcome' });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── VERIF-005 — canonical_ref evidence resolves ──────────────────────────
|
|
86
|
+
if (v.evidence !== undefined) {
|
|
87
|
+
if (!Array.isArray(v.evidence)) {
|
|
88
|
+
errors.push({ code: 'VERIF-001', message: 'evidence must be a list.', path: 'evidence' });
|
|
89
|
+
} else {
|
|
90
|
+
v.evidence.forEach((e, i) => {
|
|
91
|
+
if (e === null || typeof e !== 'object') return;
|
|
92
|
+
const entry = e as Record<string, unknown>;
|
|
93
|
+
if (entry.kind !== 'canonical_ref') return;
|
|
94
|
+
const ref = entry.ref;
|
|
95
|
+
if (!typeOfId(ref)) {
|
|
96
|
+
errors.push({ code: 'VERIF-005', message: `evidence[${i}] canonical_ref "${String(ref)}" is not a resolvable typed ID.`, path: `evidence[${i}].ref` });
|
|
97
|
+
} else if (catalog && catalog.typeOf(ref as string) === undefined) {
|
|
98
|
+
errors.push({ code: 'VERIF-005', message: `evidence[${i}] canonical_ref "${String(ref)}" does not resolve.`, path: `evidence[${i}].ref` });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── VERIF-006 (warning) — pass outcome with no evidence ──────────────────
|
|
105
|
+
const evidenceEmpty = !Array.isArray(v.evidence) || v.evidence.length === 0;
|
|
106
|
+
if (evidenceEmpty && outcome === 'pass') {
|
|
107
|
+
warnings.push({ code: 'VERIF-006', message: 'outcome "pass" has no evidence — an undefended positive claim.', path: 'evidence' });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
111
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { AttributeVersionEntry, VersionedAttributeSidecar } from './types.js';
|
|
2
|
+
export { resolveAttributeValue, resolveAttributes, todayIso } from './resolve.js';
|
|
3
|
+
export type { AttributeArrayFinding } from './validate.js';
|
|
4
|
+
export { parseSidecar, validateAttributeArray, validateSidecar } from './validate.js';
|