@transitrix/diagrams 1.8.21 → 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.js +14 -6
- 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 +18 -6
- 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
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { GateChecks } from '../requirement/types.js';
|
|
2
|
+
/** Likelihood / impact / residual severity vocabulary (§7.26). */
|
|
3
|
+
export type RiskLevel = 'low' | 'medium' | 'high';
|
|
4
|
+
export declare const RISK_LEVELS: readonly RiskLevel[];
|
|
5
|
+
export interface Risk {
|
|
6
|
+
notation: 'risk';
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
likelihood: RiskLevel;
|
|
10
|
+
impact: RiskLevel;
|
|
11
|
+
residual: RiskLevel;
|
|
12
|
+
/** ROLE-… accountable for tracking and treating this risk. */
|
|
13
|
+
owner_role: string;
|
|
14
|
+
/** Typed IDs of the elements this risk threatens. Non-empty; any TYPE. */
|
|
15
|
+
threatens: string[];
|
|
16
|
+
/** Typed IDs (REQUIREMENT-… / CONSTRAINT-…) of the treatment obligations. */
|
|
17
|
+
treated_by?: string[];
|
|
18
|
+
description?: string;
|
|
19
|
+
zone: 'canon';
|
|
20
|
+
admitted_at: string;
|
|
21
|
+
admitted_by: string;
|
|
22
|
+
gate_checks: GateChecks;
|
|
23
|
+
valid_from: string;
|
|
24
|
+
valid_to: string | null;
|
|
25
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ValidationResult } from '../validation-types.js';
|
|
2
|
+
import { type CanonCatalog } from '../typed-id.js';
|
|
3
|
+
export interface RiskValidateOptions {
|
|
4
|
+
/** When provided, RISK-003/004 enforce artefact existence for cross-refs. */
|
|
5
|
+
catalog?: CanonCatalog;
|
|
6
|
+
}
|
|
7
|
+
export declare function validateRisk(input: unknown, options?: RiskValidateOptions): ValidationResult;
|
|
@@ -0,0 +1,108 @@
|
|
|
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
|
+
import { typeOfId, isCanonicalIdOfType } from '../typed-id.js';
|
|
22
|
+
import { RISK_LEVELS } from './types.js';
|
|
23
|
+
const TREATED_BY_TYPES = ['REQUIREMENT', 'CONSTRAINT'];
|
|
24
|
+
export function validateRisk(input, options = {}) {
|
|
25
|
+
const errors = [];
|
|
26
|
+
const warnings = [];
|
|
27
|
+
if (input === null || typeof input !== 'object' || Array.isArray(input)) {
|
|
28
|
+
errors.push({ code: 'RISK-001', message: 'Risk must be a YAML mapping.' });
|
|
29
|
+
return { valid: false, errors, warnings };
|
|
30
|
+
}
|
|
31
|
+
const r = input;
|
|
32
|
+
// RISK-001 — id grammar + notation tag + envelope + plain per-type fields.
|
|
33
|
+
if (!isCanonicalIdOfType(r.id, 'RISK')) {
|
|
34
|
+
errors.push({ code: 'RISK-001', message: `id "${String(r.id)}" must match RISK-[<middle>-]<INTEGER>.`, path: 'id' });
|
|
35
|
+
}
|
|
36
|
+
if (r.notation !== 'risk') {
|
|
37
|
+
errors.push({ code: 'RISK-001', message: 'notation must be the fixed value "risk".', path: 'notation' });
|
|
38
|
+
}
|
|
39
|
+
if (r.zone !== 'canon') {
|
|
40
|
+
errors.push({ code: 'RISK-001', message: 'zone is required and must be "canon".', path: 'zone' });
|
|
41
|
+
}
|
|
42
|
+
for (const f of ['name', 'admitted_at', 'admitted_by', 'valid_from', 'owner_role']) {
|
|
43
|
+
if (typeof r[f] !== 'string' || r[f].trim() === '') {
|
|
44
|
+
errors.push({ code: 'RISK-001', message: `${f} is required.`, path: f });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (r.gate_checks === null || typeof r.gate_checks !== 'object') {
|
|
48
|
+
errors.push({ code: 'RISK-001', message: 'gate_checks is required and must be a mapping.', path: 'gate_checks' });
|
|
49
|
+
}
|
|
50
|
+
if (!('valid_to' in r) || !(typeof r.valid_to === 'string' || r.valid_to === null)) {
|
|
51
|
+
errors.push({ code: 'RISK-001', message: 'valid_to is required (an ISO date string or null).', path: 'valid_to' });
|
|
52
|
+
}
|
|
53
|
+
// likelihood / impact / residual — required (RISK-001) + enum (RISK-002).
|
|
54
|
+
for (const f of ['likelihood', 'impact', 'residual']) {
|
|
55
|
+
const v = r[f];
|
|
56
|
+
if (v === undefined || v === null || v === '') {
|
|
57
|
+
errors.push({ code: 'RISK-001', message: `${f} is required.`, path: f });
|
|
58
|
+
}
|
|
59
|
+
else if (!RISK_LEVELS.includes(v)) {
|
|
60
|
+
errors.push({ code: 'RISK-002', message: `${f} "${String(v)}" must be one of ${RISK_LEVELS.join(', ')}.`, path: f });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// threatens — required non-empty list (RISK-001), each entry resolves (RISK-003).
|
|
64
|
+
const threatens = r.threatens;
|
|
65
|
+
if (!Array.isArray(threatens) || threatens.length === 0) {
|
|
66
|
+
errors.push({ code: 'RISK-001', message: 'threatens is required and must be a non-empty list of typed IDs.', path: 'threatens' });
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
threatens.forEach((ref, i) => {
|
|
70
|
+
const type = typeOfId(ref);
|
|
71
|
+
if (!type) {
|
|
72
|
+
errors.push({ code: 'RISK-003', message: `threatens[${i}] "${String(ref)}" is not a resolvable typed ID.`, path: `threatens[${i}]` });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (options.catalog && options.catalog.typeOf(ref) === undefined) {
|
|
76
|
+
errors.push({ code: 'RISK-003', message: `threatens[${i}] "${String(ref)}" does not resolve to an admitted element.`, path: `threatens[${i}]` });
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
// treated_by — optional list; each entry must resolve to REQUIREMENT/CONSTRAINT (RISK-004).
|
|
81
|
+
const treatedBy = r.treated_by;
|
|
82
|
+
if (treatedBy !== undefined) {
|
|
83
|
+
if (!Array.isArray(treatedBy)) {
|
|
84
|
+
errors.push({ code: 'RISK-001', message: 'treated_by must be a list of typed IDs.', path: 'treated_by' });
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
treatedBy.forEach((ref, i) => {
|
|
88
|
+
const type = typeOfId(ref);
|
|
89
|
+
if (!type || !TREATED_BY_TYPES.includes(type)) {
|
|
90
|
+
errors.push({ code: 'RISK-004', message: `treated_by[${i}] "${String(ref)}" must resolve to a REQUIREMENT or CONSTRAINT.`, path: `treated_by[${i}]` });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (options.catalog && options.catalog.typeOf(ref) === undefined) {
|
|
94
|
+
errors.push({ code: 'RISK-004', message: `treated_by[${i}] "${String(ref)}" does not resolve to an admitted artefact.`, path: `treated_by[${i}]` });
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// RISK-COVERAGE-001 (warning) — untreated risk; single-file check, no catalogue needed.
|
|
100
|
+
if (!Array.isArray(treatedBy) || treatedBy.length === 0) {
|
|
101
|
+
warnings.push({
|
|
102
|
+
code: 'RISK-COVERAGE-001',
|
|
103
|
+
message: 'treated_by is empty or absent — this risk has no recorded treatment obligation.',
|
|
104
|
+
path: 'treated_by',
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
108
|
+
}
|
package/dist/schema-version.d.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 declare const SCHEMA_VERSION = "
|
|
19
|
+
export declare const SCHEMA_VERSION = "3.1.0";
|
package/dist/schema-version.js
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/dist/theme/themes.js
CHANGED
|
@@ -99,7 +99,7 @@ function shellCss() {
|
|
|
99
99
|
const cv = CSS_VAR;
|
|
100
100
|
const t = TYPOGRAPHY;
|
|
101
101
|
// Full-height flex chain lifted from blocks-preview / activities-preview
|
|
102
|
-
// into the shared shell
|
|
102
|
+
// into the shared shell. Body is a flex column
|
|
103
103
|
// sized to the iframe; #canvas takes the remaining height with its own
|
|
104
104
|
// scroll region. Without this, #canvas was content-height and any
|
|
105
105
|
// overflow-x scrollbar landed mid-panel with empty space below it — the
|
|
@@ -162,7 +162,8 @@ ${levelRules}
|
|
|
162
162
|
.tree-maturity-5{fill:var(${cv.treeMaturity5});}
|
|
163
163
|
.tree-collapse-btn{cursor:pointer;fill:white;stroke:var(${cv.nodeStroke});stroke-width:1.5;}
|
|
164
164
|
.tree-collapse-btn:hover{fill:var(${cv.bgElevated});}
|
|
165
|
-
.tree-collapse-lbl{fill:var(${cv.textSecondary});font-family:${t.fontFamily};font-size:12px;font-weight:700;dominant-baseline:central;pointer-events:none;}
|
|
165
|
+
.tree-collapse-lbl{fill:var(${cv.textSecondary});font-family:${t.fontFamily};font-size:12px;font-weight:700;dominant-baseline:central;pointer-events:none;}
|
|
166
|
+
.blocks-grid-border{fill:none;stroke:var(${cv.border});}`;
|
|
166
167
|
}
|
|
167
168
|
/**
|
|
168
169
|
* Generates a self-contained CSS block for embedding inside a standalone SVG file.
|
|
@@ -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,31 @@
|
|
|
1
|
+
import type { GateChecks } from '../requirement/types.js';
|
|
2
|
+
import type { Evidence } from '../assertion/types.js';
|
|
3
|
+
/** Validation method vocabulary (28-validation.md §3). */
|
|
4
|
+
export type ValidationMethod = 'user_acceptance' | 'field_trial' | 'stakeholder_review' | 'usability_study';
|
|
5
|
+
export declare const VALIDATION_METHODS: readonly ValidationMethod[];
|
|
6
|
+
/** Pass/fail judgement vocabulary (28-validation.md §3). */
|
|
7
|
+
export type ValidationOutcome = 'pass' | 'fail' | 'inconclusive' | 'not_yet_run';
|
|
8
|
+
export declare const VALIDATION_OUTCOMES: readonly ValidationOutcome[];
|
|
9
|
+
/** Outcomes that count as "closed" — the trace link has resolved one way or
|
|
10
|
+
* the other, distinct from still-open (`not_yet_run` / `inconclusive`)
|
|
11
|
+
* (28-validation.md §5, NEED-VALIDATION-COVERAGE-002). */
|
|
12
|
+
export declare const CLOSED_VALIDATION_OUTCOMES: readonly ValidationOutcome[];
|
|
13
|
+
export interface Validation {
|
|
14
|
+
notation: 'validation';
|
|
15
|
+
id: string;
|
|
16
|
+
/** Typed ID of the NEED this validation targets. */
|
|
17
|
+
validates: string;
|
|
18
|
+
method: ValidationMethod;
|
|
19
|
+
protocol: string;
|
|
20
|
+
result?: string;
|
|
21
|
+
outcome: ValidationOutcome;
|
|
22
|
+
evidence?: Evidence[];
|
|
23
|
+
performed_at?: string;
|
|
24
|
+
performed_by?: string;
|
|
25
|
+
zone: 'canon';
|
|
26
|
+
admitted_at: string;
|
|
27
|
+
admitted_by: string;
|
|
28
|
+
gate_checks: GateChecks;
|
|
29
|
+
valid_from: string;
|
|
30
|
+
valid_to: string | null;
|
|
31
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
export const VALIDATION_METHODS = [
|
|
7
|
+
'user_acceptance',
|
|
8
|
+
'field_trial',
|
|
9
|
+
'stakeholder_review',
|
|
10
|
+
'usability_study',
|
|
11
|
+
];
|
|
12
|
+
export const VALIDATION_OUTCOMES = [
|
|
13
|
+
'pass',
|
|
14
|
+
'fail',
|
|
15
|
+
'inconclusive',
|
|
16
|
+
'not_yet_run',
|
|
17
|
+
];
|
|
18
|
+
/** Outcomes that count as "closed" — the trace link has resolved one way or
|
|
19
|
+
* the other, distinct from still-open (`not_yet_run` / `inconclusive`)
|
|
20
|
+
* (28-validation.md §5, NEED-VALIDATION-COVERAGE-002). */
|
|
21
|
+
export const CLOSED_VALIDATION_OUTCOMES = ['pass', 'fail'];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ValidationResult } from '../validation-types.js';
|
|
2
|
+
import { type CanonCatalog } from '../typed-id.js';
|
|
3
|
+
export interface ValidationValidateOptions {
|
|
4
|
+
/** When provided, the resolution rules enforce artefact existence. */
|
|
5
|
+
catalog?: CanonCatalog;
|
|
6
|
+
}
|
|
7
|
+
export declare function validateValidation(input: unknown, options?: ValidationValidateOptions): ValidationResult;
|
|
@@ -0,0 +1,106 @@
|
|
|
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
|
+
import { typeOfId, isCanonicalIdOfType } from '../typed-id.js';
|
|
14
|
+
import { VALIDATION_METHODS, VALIDATION_OUTCOMES } from './types.js';
|
|
15
|
+
export function validateValidation(input, options = {}) {
|
|
16
|
+
const errors = [];
|
|
17
|
+
const warnings = [];
|
|
18
|
+
const { catalog } = options;
|
|
19
|
+
if (input === null || typeof input !== 'object' || Array.isArray(input)) {
|
|
20
|
+
errors.push({ code: 'VALID-001', message: 'Validation must be a YAML mapping.' });
|
|
21
|
+
return { valid: false, errors, warnings };
|
|
22
|
+
}
|
|
23
|
+
const v = input;
|
|
24
|
+
// ── VALID-001 — id grammar + plain required fields ───────────────────────
|
|
25
|
+
if (!isCanonicalIdOfType(v.id, 'VALIDATION')) {
|
|
26
|
+
errors.push({ code: 'VALID-001', message: `id "${String(v.id)}" must match VALIDATION-[<middle>-]<INTEGER>.`, path: 'id' });
|
|
27
|
+
}
|
|
28
|
+
if (v.notation !== 'validation') {
|
|
29
|
+
errors.push({ code: 'VALID-001', message: 'notation must be the fixed value "validation".', path: 'notation' });
|
|
30
|
+
}
|
|
31
|
+
if (v.zone !== 'canon') {
|
|
32
|
+
errors.push({ code: 'VALID-001', message: 'zone is required and must be "canon".', path: 'zone' });
|
|
33
|
+
}
|
|
34
|
+
for (const f of ['admitted_at', 'admitted_by', 'valid_from', 'protocol']) {
|
|
35
|
+
if (typeof v[f] !== 'string' || v[f].trim() === '') {
|
|
36
|
+
errors.push({ code: 'VALID-001', message: `${f} is required.`, path: f });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (v.gate_checks === null || typeof v.gate_checks !== 'object') {
|
|
40
|
+
errors.push({ code: 'VALID-001', message: 'gate_checks is required and must be a mapping.', path: 'gate_checks' });
|
|
41
|
+
}
|
|
42
|
+
if (!('valid_to' in v) || !(typeof v.valid_to === 'string' || v.valid_to === null)) {
|
|
43
|
+
errors.push({ code: 'VALID-001', message: 'valid_to is required (an ISO date string or null).', path: 'valid_to' });
|
|
44
|
+
}
|
|
45
|
+
// ── VALID-002 — validates → NEED ─────────────────────────────────────────
|
|
46
|
+
const validates = v.validates;
|
|
47
|
+
if (typeof validates !== 'string' || validates.trim() === '') {
|
|
48
|
+
errors.push({ code: 'VALID-002', message: 'validates is required and must be a typed NEED id.', path: 'validates' });
|
|
49
|
+
}
|
|
50
|
+
else if (catalog) {
|
|
51
|
+
const t = catalog.typeOf(validates);
|
|
52
|
+
if (t === undefined) {
|
|
53
|
+
errors.push({ code: 'VALID-002', message: `validates "${validates}" does not resolve to an admitted artefact.`, path: 'validates' });
|
|
54
|
+
}
|
|
55
|
+
else if (t !== 'NEED') {
|
|
56
|
+
errors.push({ code: 'VALID-002', message: `validates "${validates}" resolves to a ${t}, not a NEED.`, path: 'validates' });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else if (typeOfId(validates) !== 'NEED') {
|
|
60
|
+
errors.push({ code: 'VALID-002', message: `validates "${validates}" must be a NEED typed id.`, path: 'validates' });
|
|
61
|
+
}
|
|
62
|
+
// ── VALID-003 — method enum ───────────────────────────────────────────────
|
|
63
|
+
const method = v.method;
|
|
64
|
+
if (method === undefined || method === null) {
|
|
65
|
+
errors.push({ code: 'VALID-001', message: 'method is required.', path: 'method' });
|
|
66
|
+
}
|
|
67
|
+
else if (!VALIDATION_METHODS.includes(method)) {
|
|
68
|
+
errors.push({ code: 'VALID-003', message: `method "${String(method)}" must be one of ${VALIDATION_METHODS.join(', ')}.`, path: 'method' });
|
|
69
|
+
}
|
|
70
|
+
// ── VALID-004 — outcome enum ──────────────────────────────────────────────
|
|
71
|
+
const outcome = v.outcome;
|
|
72
|
+
if (outcome === undefined || outcome === null) {
|
|
73
|
+
errors.push({ code: 'VALID-001', message: 'outcome is required.', path: 'outcome' });
|
|
74
|
+
}
|
|
75
|
+
else if (!VALIDATION_OUTCOMES.includes(outcome)) {
|
|
76
|
+
errors.push({ code: 'VALID-004', message: `outcome "${String(outcome)}" must be one of ${VALIDATION_OUTCOMES.join(', ')}.`, path: 'outcome' });
|
|
77
|
+
}
|
|
78
|
+
// ── VALID-005 — canonical_ref evidence resolves ──────────────────────────
|
|
79
|
+
if (v.evidence !== undefined) {
|
|
80
|
+
if (!Array.isArray(v.evidence)) {
|
|
81
|
+
errors.push({ code: 'VALID-001', message: 'evidence must be a list.', path: 'evidence' });
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
v.evidence.forEach((e, i) => {
|
|
85
|
+
if (e === null || typeof e !== 'object')
|
|
86
|
+
return;
|
|
87
|
+
const entry = e;
|
|
88
|
+
if (entry.kind !== 'canonical_ref')
|
|
89
|
+
return;
|
|
90
|
+
const ref = entry.ref;
|
|
91
|
+
if (!typeOfId(ref)) {
|
|
92
|
+
errors.push({ code: 'VALID-005', message: `evidence[${i}] canonical_ref "${String(ref)}" is not a resolvable typed ID.`, path: `evidence[${i}].ref` });
|
|
93
|
+
}
|
|
94
|
+
else if (catalog && catalog.typeOf(ref) === undefined) {
|
|
95
|
+
errors.push({ code: 'VALID-005', message: `evidence[${i}] canonical_ref "${String(ref)}" does not resolve.`, path: `evidence[${i}].ref` });
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// ── VALID-006 (warning) — pass outcome with no evidence ──────────────────
|
|
101
|
+
const evidenceEmpty = !Array.isArray(v.evidence) || v.evidence.length === 0;
|
|
102
|
+
if (evidenceEmpty && outcome === 'pass') {
|
|
103
|
+
warnings.push({ code: 'VALID-006', message: 'outcome "pass" has no evidence — an undefended positive claim.', path: 'evidence' });
|
|
104
|
+
}
|
|
105
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
106
|
+
}
|
|
@@ -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,31 @@
|
|
|
1
|
+
import type { GateChecks } from '../requirement/types.js';
|
|
2
|
+
import type { Evidence } from '../assertion/types.js';
|
|
3
|
+
/** V&V method vocabulary (27-verification.md §3). */
|
|
4
|
+
export type VerificationMethod = 'test' | 'analysis' | 'inspection' | 'demonstration';
|
|
5
|
+
export declare const VERIFICATION_METHODS: readonly VerificationMethod[];
|
|
6
|
+
/** Pass/fail judgement vocabulary (27-verification.md §3). */
|
|
7
|
+
export type VerificationOutcome = 'pass' | 'fail' | 'inconclusive' | 'not_yet_run';
|
|
8
|
+
export declare const VERIFICATION_OUTCOMES: readonly VerificationOutcome[];
|
|
9
|
+
/** Outcomes that count as "closed" — the trace link has resolved one way or
|
|
10
|
+
* the other, distinct from still-open (`not_yet_run` / `inconclusive`)
|
|
11
|
+
* (27-verification.md §5, REQ-VERIF-COVERAGE-002). */
|
|
12
|
+
export declare const CLOSED_VERIFICATION_OUTCOMES: readonly VerificationOutcome[];
|
|
13
|
+
export interface Verification {
|
|
14
|
+
notation: 'verification';
|
|
15
|
+
id: string;
|
|
16
|
+
/** Typed ID of the REQUIREMENT this verification targets. */
|
|
17
|
+
verifies: string;
|
|
18
|
+
method: VerificationMethod;
|
|
19
|
+
protocol: string;
|
|
20
|
+
result?: string;
|
|
21
|
+
outcome: VerificationOutcome;
|
|
22
|
+
evidence?: Evidence[];
|
|
23
|
+
performed_at?: string;
|
|
24
|
+
performed_by?: string;
|
|
25
|
+
zone: 'canon';
|
|
26
|
+
admitted_at: string;
|
|
27
|
+
admitted_by: string;
|
|
28
|
+
gate_checks: GateChecks;
|
|
29
|
+
valid_from: string;
|
|
30
|
+
valid_to: string | null;
|
|
31
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// VERIFICATION — engineering V&V claim against a REQUIREMENT.
|
|
2
|
+
// Schema: methodology notations/elements/27-verification.md §2.
|
|
3
|
+
export const VERIFICATION_METHODS = [
|
|
4
|
+
'test', 'analysis', 'inspection', 'demonstration',
|
|
5
|
+
];
|
|
6
|
+
export const VERIFICATION_OUTCOMES = [
|
|
7
|
+
'pass', 'fail', 'inconclusive', 'not_yet_run',
|
|
8
|
+
];
|
|
9
|
+
/** Outcomes that count as "closed" — the trace link has resolved one way or
|
|
10
|
+
* the other, distinct from still-open (`not_yet_run` / `inconclusive`)
|
|
11
|
+
* (27-verification.md §5, REQ-VERIF-COVERAGE-002). */
|
|
12
|
+
export const CLOSED_VERIFICATION_OUTCOMES = ['pass', 'fail'];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ValidationResult } from '../validation-types.js';
|
|
2
|
+
import { type CanonCatalog } from '../typed-id.js';
|
|
3
|
+
export interface VerificationValidateOptions {
|
|
4
|
+
/** When provided, the resolution rules enforce artefact existence. */
|
|
5
|
+
catalog?: CanonCatalog;
|
|
6
|
+
}
|
|
7
|
+
export declare function validateVerification(input: unknown, options?: VerificationValidateOptions): ValidationResult;
|
|
@@ -0,0 +1,104 @@
|
|
|
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
|
+
import { typeOfId, isCanonicalIdOfType } from '../typed-id.js';
|
|
12
|
+
import { VERIFICATION_METHODS, VERIFICATION_OUTCOMES } from './types.js';
|
|
13
|
+
export function validateVerification(input, options = {}) {
|
|
14
|
+
const errors = [];
|
|
15
|
+
const warnings = [];
|
|
16
|
+
const { catalog } = options;
|
|
17
|
+
if (input === null || typeof input !== 'object' || Array.isArray(input)) {
|
|
18
|
+
errors.push({ code: 'VERIF-001', message: 'Verification must be a YAML mapping.' });
|
|
19
|
+
return { valid: false, errors, warnings };
|
|
20
|
+
}
|
|
21
|
+
const v = input;
|
|
22
|
+
// ── VERIF-001 — id grammar + plain required fields ───────────────────────
|
|
23
|
+
if (!isCanonicalIdOfType(v.id, 'VERIFICATION')) {
|
|
24
|
+
errors.push({ code: 'VERIF-001', message: `id "${String(v.id)}" must match VERIFICATION-[<middle>-]<INTEGER>.`, path: 'id' });
|
|
25
|
+
}
|
|
26
|
+
if (v.notation !== 'verification') {
|
|
27
|
+
errors.push({ code: 'VERIF-001', message: 'notation must be the fixed value "verification".', path: 'notation' });
|
|
28
|
+
}
|
|
29
|
+
if (v.zone !== 'canon') {
|
|
30
|
+
errors.push({ code: 'VERIF-001', message: 'zone is required and must be "canon".', path: 'zone' });
|
|
31
|
+
}
|
|
32
|
+
for (const f of ['admitted_at', 'admitted_by', 'valid_from', 'protocol']) {
|
|
33
|
+
if (typeof v[f] !== 'string' || v[f].trim() === '') {
|
|
34
|
+
errors.push({ code: 'VERIF-001', message: `${f} is required.`, path: f });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (v.gate_checks === null || typeof v.gate_checks !== 'object') {
|
|
38
|
+
errors.push({ code: 'VERIF-001', message: 'gate_checks is required and must be a mapping.', path: 'gate_checks' });
|
|
39
|
+
}
|
|
40
|
+
if (!('valid_to' in v) || !(typeof v.valid_to === 'string' || v.valid_to === null)) {
|
|
41
|
+
errors.push({ code: 'VERIF-001', message: 'valid_to is required (an ISO date string or null).', path: 'valid_to' });
|
|
42
|
+
}
|
|
43
|
+
// ── VERIF-002 — verifies → REQUIREMENT ───────────────────────────────────
|
|
44
|
+
const verifies = v.verifies;
|
|
45
|
+
if (typeof verifies !== 'string' || verifies.trim() === '') {
|
|
46
|
+
errors.push({ code: 'VERIF-002', message: 'verifies is required and must be a typed REQUIREMENT id.', path: 'verifies' });
|
|
47
|
+
}
|
|
48
|
+
else if (catalog) {
|
|
49
|
+
const t = catalog.typeOf(verifies);
|
|
50
|
+
if (t === undefined) {
|
|
51
|
+
errors.push({ code: 'VERIF-002', message: `verifies "${verifies}" does not resolve to an admitted artefact.`, path: 'verifies' });
|
|
52
|
+
}
|
|
53
|
+
else if (t !== 'REQUIREMENT') {
|
|
54
|
+
errors.push({ code: 'VERIF-002', message: `verifies "${verifies}" resolves to a ${t}, not a REQUIREMENT.`, path: 'verifies' });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else if (typeOfId(verifies) !== 'REQUIREMENT') {
|
|
58
|
+
errors.push({ code: 'VERIF-002', message: `verifies "${verifies}" must be a REQUIREMENT typed id.`, path: 'verifies' });
|
|
59
|
+
}
|
|
60
|
+
// ── VERIF-003 — method enum ───────────────────────────────────────────────
|
|
61
|
+
const method = v.method;
|
|
62
|
+
if (method === undefined || method === null) {
|
|
63
|
+
errors.push({ code: 'VERIF-001', message: 'method is required.', path: 'method' });
|
|
64
|
+
}
|
|
65
|
+
else if (!VERIFICATION_METHODS.includes(method)) {
|
|
66
|
+
errors.push({ code: 'VERIF-003', message: `method "${String(method)}" must be one of ${VERIFICATION_METHODS.join(', ')}.`, path: 'method' });
|
|
67
|
+
}
|
|
68
|
+
// ── VERIF-004 — outcome enum ──────────────────────────────────────────────
|
|
69
|
+
const outcome = v.outcome;
|
|
70
|
+
if (outcome === undefined || outcome === null) {
|
|
71
|
+
errors.push({ code: 'VERIF-001', message: 'outcome is required.', path: 'outcome' });
|
|
72
|
+
}
|
|
73
|
+
else if (!VERIFICATION_OUTCOMES.includes(outcome)) {
|
|
74
|
+
errors.push({ code: 'VERIF-004', message: `outcome "${String(outcome)}" must be one of ${VERIFICATION_OUTCOMES.join(', ')}.`, path: 'outcome' });
|
|
75
|
+
}
|
|
76
|
+
// ── VERIF-005 — canonical_ref evidence resolves ──────────────────────────
|
|
77
|
+
if (v.evidence !== undefined) {
|
|
78
|
+
if (!Array.isArray(v.evidence)) {
|
|
79
|
+
errors.push({ code: 'VERIF-001', message: 'evidence must be a list.', path: 'evidence' });
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
v.evidence.forEach((e, i) => {
|
|
83
|
+
if (e === null || typeof e !== 'object')
|
|
84
|
+
return;
|
|
85
|
+
const entry = e;
|
|
86
|
+
if (entry.kind !== 'canonical_ref')
|
|
87
|
+
return;
|
|
88
|
+
const ref = entry.ref;
|
|
89
|
+
if (!typeOfId(ref)) {
|
|
90
|
+
errors.push({ code: 'VERIF-005', message: `evidence[${i}] canonical_ref "${String(ref)}" is not a resolvable typed ID.`, path: `evidence[${i}].ref` });
|
|
91
|
+
}
|
|
92
|
+
else if (catalog && catalog.typeOf(ref) === undefined) {
|
|
93
|
+
errors.push({ code: 'VERIF-005', message: `evidence[${i}] canonical_ref "${String(ref)}" does not resolve.`, path: `evidence[${i}].ref` });
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// ── VERIF-006 (warning) — pass outcome with no evidence ──────────────────
|
|
99
|
+
const evidenceEmpty = !Array.isArray(v.evidence) || v.evidence.length === 0;
|
|
100
|
+
if (evidenceEmpty && outcome === 'pass') {
|
|
101
|
+
warnings.push({ code: 'VERIF-006', message: 'outcome "pass" has no evidence — an undefended positive claim.', path: 'evidence' });
|
|
102
|
+
}
|
|
103
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
104
|
+
}
|
|
@@ -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';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AttributeVersionEntry, VersionedAttributeSidecar } from './types.js';
|
|
2
|
+
/** Pick the entry with the largest `valid_from <= atDate`.
|
|
3
|
+
* Returns `undefined` when no entry has taken effect yet as of `atDate`
|
|
4
|
+
* (the attribute has not yet taken its first value — CONTRACT.md §9.2 rule
|
|
5
|
+
* 4). Returns `null` when the resolved entry is itself a gap marker
|
|
6
|
+
* (`value: null` — attribute currently unset). */
|
|
7
|
+
export declare function resolveAttributeValue(entries: AttributeVersionEntry[] | undefined, atDate: string): unknown;
|
|
8
|
+
/** Resolve every attribute in a sidecar at `atDate`. An attribute with no
|
|
9
|
+
* entry yet in effect is omitted from the result (equivalent to
|
|
10
|
+
* `resolveAttributeValue` returning `undefined`) rather than reported as
|
|
11
|
+
* `null`, which is reserved for an explicit gap marker. */
|
|
12
|
+
export declare function resolveAttributes(sidecar: VersionedAttributeSidecar | null | undefined, atDate: string): Record<string, unknown>;
|
|
13
|
+
/** Today, as a quoted-ISO-8601 date string — the default projection date
|
|
14
|
+
* every resolver/renderer falls back to when the caller supplies none. */
|
|
15
|
+
export declare function todayIso(): string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Current-value resolution for a versioned attribute — CONTRACT.md §9.2.
|
|
2
|
+
/** Pick the entry with the largest `valid_from <= atDate`.
|
|
3
|
+
* Returns `undefined` when no entry has taken effect yet as of `atDate`
|
|
4
|
+
* (the attribute has not yet taken its first value — CONTRACT.md §9.2 rule
|
|
5
|
+
* 4). Returns `null` when the resolved entry is itself a gap marker
|
|
6
|
+
* (`value: null` — attribute currently unset). */
|
|
7
|
+
export function resolveAttributeValue(entries, atDate) {
|
|
8
|
+
if (!entries || entries.length === 0)
|
|
9
|
+
return undefined;
|
|
10
|
+
let best;
|
|
11
|
+
for (const entry of entries) {
|
|
12
|
+
if (entry.valid_from <= atDate && (!best || entry.valid_from > best.valid_from)) {
|
|
13
|
+
best = entry;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return best ? best.value : undefined;
|
|
17
|
+
}
|
|
18
|
+
/** Resolve every attribute in a sidecar at `atDate`. An attribute with no
|
|
19
|
+
* entry yet in effect is omitted from the result (equivalent to
|
|
20
|
+
* `resolveAttributeValue` returning `undefined`) rather than reported as
|
|
21
|
+
* `null`, which is reserved for an explicit gap marker. */
|
|
22
|
+
export function resolveAttributes(sidecar, atDate) {
|
|
23
|
+
const out = {};
|
|
24
|
+
if (!sidecar)
|
|
25
|
+
return out;
|
|
26
|
+
for (const name of Object.keys(sidecar.attribute_versions)) {
|
|
27
|
+
const value = resolveAttributeValue(sidecar.attribute_versions[name], atDate);
|
|
28
|
+
if (value !== undefined)
|
|
29
|
+
out[name] = value;
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
/** Today, as a quoted-ISO-8601 date string — the default projection date
|
|
34
|
+
* every resolver/renderer falls back to when the caller supplies none. */
|
|
35
|
+
export function todayIso() {
|
|
36
|
+
return new Date().toISOString().slice(0, 10);
|
|
37
|
+
}
|