@primmel/primmel 1.13.0 β 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/check-rules.d.ts.map +1 -1
- package/dist/src/check-rules.js +73 -0
- package/dist/src/check-rules.js.map +1 -1
- package/dist/src/check.d.ts.map +1 -1
- package/dist/src/check.js +693 -0
- package/dist/src/check.js.map +1 -1
- package/dist/src/model-diff.d.ts.map +1 -1
- package/dist/src/model-diff.js +35 -0
- package/dist/src/model-diff.js.map +1 -1
- package/dist/src/ser-des/config/applicationDeclaration.d.ts +5 -0
- package/dist/src/ser-des/config/applicationDeclaration.d.ts.map +1 -0
- package/dist/src/ser-des/config/applicationDeclaration.js +102 -0
- package/dist/src/ser-des/config/applicationDeclaration.js.map +1 -0
- package/dist/src/ser-des/config/aspect.d.ts +5 -0
- package/dist/src/ser-des/config/aspect.d.ts.map +1 -0
- package/dist/src/ser-des/config/aspect.js +128 -0
- package/dist/src/ser-des/config/aspect.js.map +1 -0
- package/dist/src/ser-des/config/calculationContext.d.ts +5 -0
- package/dist/src/ser-des/config/calculationContext.d.ts.map +1 -0
- package/dist/src/ser-des/config/calculationContext.js +62 -0
- package/dist/src/ser-des/config/calculationContext.js.map +1 -0
- package/dist/src/ser-des/config/certificateTemplate.d.ts +5 -0
- package/dist/src/ser-des/config/certificateTemplate.d.ts.map +1 -0
- package/dist/src/ser-des/config/certificateTemplate.js +195 -0
- package/dist/src/ser-des/config/certificateTemplate.js.map +1 -0
- package/dist/src/ser-des/config/evaluationDimension.d.ts +5 -0
- package/dist/src/ser-des/config/evaluationDimension.d.ts.map +1 -0
- package/dist/src/ser-des/config/evaluationDimension.js +128 -0
- package/dist/src/ser-des/config/evaluationDimension.js.map +1 -0
- package/dist/src/ser-des/config/evaluationProfile.d.ts +5 -0
- package/dist/src/ser-des/config/evaluationProfile.d.ts.map +1 -0
- package/dist/src/ser-des/config/evaluationProfile.js +61 -0
- package/dist/src/ser-des/config/evaluationProfile.js.map +1 -0
- package/dist/src/ser-des/config/identitySlot.d.ts +5 -0
- package/dist/src/ser-des/config/identitySlot.d.ts.map +1 -0
- package/dist/src/ser-des/config/identitySlot.js +120 -0
- package/dist/src/ser-des/config/identitySlot.js.map +1 -0
- package/dist/src/ser-des/config/index.d.ts.map +1 -1
- package/dist/src/ser-des/config/index.js +148 -0
- package/dist/src/ser-des/config/index.js.map +1 -1
- package/dist/src/ser-des/config/promiseSet.d.ts +5 -0
- package/dist/src/ser-des/config/promiseSet.d.ts.map +1 -0
- package/dist/src/ser-des/config/promiseSet.js +46 -0
- package/dist/src/ser-des/config/promiseSet.js.map +1 -0
- package/dist/src/ser-des/config/selectionRules.d.ts +9 -0
- package/dist/src/ser-des/config/selectionRules.d.ts.map +1 -0
- package/dist/src/ser-des/config/selectionRules.js +372 -0
- package/dist/src/ser-des/config/selectionRules.js.map +1 -0
- package/dist/src/ser-des/config/subject.d.ts +18 -1
- package/dist/src/ser-des/config/subject.d.ts.map +1 -1
- package/dist/src/ser-des/config/subject.js +177 -61
- package/dist/src/ser-des/config/subject.js.map +1 -1
- package/dist/src/ser-des/config/testReport.d.ts +7 -0
- package/dist/src/ser-des/config/testReport.d.ts.map +1 -0
- package/dist/src/ser-des/config/testReport.js +322 -0
- package/dist/src/ser-des/config/testReport.js.map +1 -0
- package/dist/src/ser-des/config/verificationPathway.d.ts +5 -0
- package/dist/src/ser-des/config/verificationPathway.d.ts.map +1 -0
- package/dist/src/ser-des/config/verificationPathway.js +361 -0
- package/dist/src/ser-des/config/verificationPathway.js.map +1 -0
- package/dist/src/ser-des/config/workflowConfig.d.ts +5 -0
- package/dist/src/ser-des/config/workflowConfig.d.ts.map +1 -0
- package/dist/src/ser-des/config/workflowConfig.js +129 -0
- package/dist/src/ser-des/config/workflowConfig.js.map +1 -0
- package/dist/src/ser-des/package.d.ts +10 -0
- package/dist/src/ser-des/package.d.ts.map +1 -1
- package/dist/src/ser-des/package.js +161 -12
- package/dist/src/ser-des/package.js.map +1 -1
- package/dist/src/ser-des/parse.d.ts.map +1 -1
- package/dist/src/ser-des/parse.js +24 -0
- package/dist/src/ser-des/parse.js.map +1 -1
- package/dist/src/ser-des/types.d.ts +27 -0
- package/dist/src/ser-des/types.d.ts.map +1 -1
- package/dist/src/types/ApplicationDeclaration.d.ts +23 -0
- package/dist/src/types/ApplicationDeclaration.d.ts.map +1 -0
- package/dist/src/types/ApplicationDeclaration.js +32 -0
- package/dist/src/types/ApplicationDeclaration.js.map +1 -0
- package/dist/src/types/Aspect.d.ts +30 -0
- package/dist/src/types/Aspect.d.ts.map +1 -0
- package/dist/src/types/Aspect.js +41 -0
- package/dist/src/types/Aspect.js.map +1 -0
- package/dist/src/types/CalculationContext.d.ts +17 -0
- package/dist/src/types/CalculationContext.d.ts.map +1 -0
- package/dist/src/types/CalculationContext.js +34 -0
- package/dist/src/types/CalculationContext.js.map +1 -0
- package/dist/src/types/CertificateTemplate.d.ts +46 -0
- package/dist/src/types/CertificateTemplate.d.ts.map +1 -0
- package/dist/src/types/CertificateTemplate.js +46 -0
- package/dist/src/types/CertificateTemplate.js.map +1 -0
- package/dist/src/types/EvaluationDimension.d.ts +32 -0
- package/dist/src/types/EvaluationDimension.d.ts.map +1 -0
- package/dist/src/types/EvaluationDimension.js +38 -0
- package/dist/src/types/EvaluationDimension.js.map +1 -0
- package/dist/src/types/EvaluationProfile.d.ts +10 -0
- package/dist/src/types/EvaluationProfile.d.ts.map +1 -0
- package/dist/src/types/EvaluationProfile.js +25 -0
- package/dist/src/types/EvaluationProfile.js.map +1 -0
- package/dist/src/types/IdentitySlot.d.ts +24 -0
- package/dist/src/types/IdentitySlot.d.ts.map +1 -0
- package/dist/src/types/IdentitySlot.js +34 -0
- package/dist/src/types/IdentitySlot.js.map +1 -0
- package/dist/src/types/PromiseSet.d.ts +8 -0
- package/dist/src/types/PromiseSet.d.ts.map +1 -0
- package/dist/src/types/PromiseSet.js +35 -0
- package/dist/src/types/PromiseSet.js.map +1 -0
- package/dist/src/types/SelectionRules.d.ts +73 -0
- package/dist/src/types/SelectionRules.d.ts.map +1 -0
- package/dist/src/types/SelectionRules.js +55 -0
- package/dist/src/types/SelectionRules.js.map +1 -0
- package/dist/src/types/Standard.d.ts +53 -0
- package/dist/src/types/Standard.d.ts.map +1 -1
- package/dist/src/types/Subject.d.ts +31 -1
- package/dist/src/types/Subject.d.ts.map +1 -1
- package/dist/src/types/TestReport.d.ts +71 -0
- package/dist/src/types/TestReport.d.ts.map +1 -0
- package/dist/src/types/TestReport.js +73 -0
- package/dist/src/types/TestReport.js.map +1 -0
- package/dist/src/types/VerificationPathway.d.ts +63 -0
- package/dist/src/types/VerificationPathway.d.ts.map +1 -0
- package/dist/src/types/VerificationPathway.js +56 -0
- package/dist/src/types/VerificationPathway.js.map +1 -0
- package/dist/src/types/WorkflowConfig.d.ts +34 -0
- package/dist/src/types/WorkflowConfig.d.ts.map +1 -0
- package/dist/src/types/WorkflowConfig.js +47 -0
- package/dist/src/types/WorkflowConfig.js.map +1 -0
- package/dist-browser/index.mjs +2135 -1017
- package/dist-cli/primmel.mjs +7462 -4450
- package/package.json +1 -1
package/dist/src/check.js
CHANGED
|
@@ -486,6 +486,11 @@ function checkPackage(dir, options = {}) {
|
|
|
486
486
|
else if (i.code === 'duplicate-id') {
|
|
487
487
|
err('C96', `${i.message} (duplicate-id)`);
|
|
488
488
|
}
|
|
489
|
+
else if (i.code === 'orphan-overlay-entry') {
|
|
490
|
+
// The checklist overlay's orphan-entry guard (C141's
|
|
491
|
+
// composition leg β smart TODO.roadmap/40 batch 3).
|
|
492
|
+
err('C141', i.message);
|
|
493
|
+
}
|
|
489
494
|
}
|
|
490
495
|
}
|
|
491
496
|
catch (e) {
|
|
@@ -1030,6 +1035,97 @@ function checkPackage(dir, options = {}) {
|
|
|
1030
1035
|
}
|
|
1031
1036
|
}
|
|
1032
1037
|
}
|
|
1038
|
+
// C42βC44 generalized to the promise_set register (smart
|
|
1039
|
+
// TODO.roadmap/40 batch 3; the packages-as-SSOT epic): the rec promise
|
|
1040
|
+
// registers carry the SAME sub-grammar as is.promises, so the same
|
|
1041
|
+
// discipline fires. The set id binds the owning subject: when that
|
|
1042
|
+
// subject composes, its characteristics resolve exactly as above; when
|
|
1043
|
+
// it does not, the characteristic leg is gated off (per-register
|
|
1044
|
+
// gating, the C58 doctrine) and attribute/behavior resolution stands.
|
|
1045
|
+
for (const ps of standard.promiseSets ?? []) {
|
|
1046
|
+
const owner = (standard.subjects ?? []).find(s => s.id === ps.id);
|
|
1047
|
+
const characteristicSymbols = new Map();
|
|
1048
|
+
for (const [k, c] of Object.entries(owner?.has.characteristics ?? {})) {
|
|
1049
|
+
characteristicSymbols.set(k, c.symbol ?? '');
|
|
1050
|
+
}
|
|
1051
|
+
const bareValueTargets = new Set([
|
|
1052
|
+
...attrIds,
|
|
1053
|
+
...Object.keys(owner?.is.designParameters ?? {}),
|
|
1054
|
+
...Object.keys(owner?.has.attributes ?? {}),
|
|
1055
|
+
]);
|
|
1056
|
+
for (const p of ps.promises ?? []) {
|
|
1057
|
+
const label = p.id ||
|
|
1058
|
+
(p.statement.length > 40
|
|
1059
|
+
? p.statement.slice(0, 37) + 'β¦'
|
|
1060
|
+
: p.statement);
|
|
1061
|
+
const where = `promise_set ${ps.id}: promise "${label}"`;
|
|
1062
|
+
// C2 β declared verified_by ids must resolve (same discipline as
|
|
1063
|
+
// the subject promises).
|
|
1064
|
+
for (const v of p.verifiedBy ?? []) {
|
|
1065
|
+
if (!reqIds.has(v) && !testIds.has(v)) {
|
|
1066
|
+
err('C2', `${where} verified_by "${v}" is not a declared requirement or conformance test`);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
if (!p.target) {
|
|
1070
|
+
if ((p.verifiedBy ?? []).length === 0) {
|
|
1071
|
+
warn('C43', `${where} has no target and no verified_by β it is unverifiable; give it a characteristic/behavior target or declare the verifying requirement/test (promise-verifiable)`);
|
|
1072
|
+
}
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
const isCharacteristic = characteristicSymbols.has(p.target);
|
|
1076
|
+
const isBehavior = behaviorIds.has(p.target);
|
|
1077
|
+
if (!isCharacteristic && !isBehavior) {
|
|
1078
|
+
if (bareValueTargets.has(p.target)) {
|
|
1079
|
+
// C44 (narrowed) β same restatement leg as subject promises.
|
|
1080
|
+
const restatesBareValue = p.level?.kind === 'quantity' &&
|
|
1081
|
+
p.conditions === '' &&
|
|
1082
|
+
(p.verifiedBy ?? []).length === 0;
|
|
1083
|
+
if (restatesBareValue) {
|
|
1084
|
+
err('C44', `${where} only restates the declared attribute value "${p.target}" β a promise claims a characteristic or behavior (optionally conditioned); bare values stay origin: declared attributes (promise-not-bare-value)`);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
else if (owner) {
|
|
1088
|
+
// C42 β gated on the owning subject composing: without it the
|
|
1089
|
+
// characteristic register is out of scope and the target's
|
|
1090
|
+
// characteristic-ness is unknowable.
|
|
1091
|
+
err('C42', `${where} target "${p.target}" is not a declared characteristic or behavior (promise-target-resolves)`);
|
|
1092
|
+
}
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
if ((p.verifiedBy ?? []).length > 0) {
|
|
1096
|
+
continue; // verification declared β C43 silent
|
|
1097
|
+
}
|
|
1098
|
+
// C43 β derive the verifying requirements/tests from the target.
|
|
1099
|
+
const names = new Set([p.target]);
|
|
1100
|
+
if (isCharacteristic) {
|
|
1101
|
+
const sym = characteristicSymbols.get(p.target);
|
|
1102
|
+
if (sym) {
|
|
1103
|
+
names.add(sym);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
const boundReqs = new Set();
|
|
1107
|
+
for (const r of standard.requirements ?? []) {
|
|
1108
|
+
if (requirementBindsPromiseTarget(r, names)) {
|
|
1109
|
+
boundReqs.add(r.id);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
const boundTests = new Set();
|
|
1113
|
+
for (const t of standard.conformanceTests ?? []) {
|
|
1114
|
+
if (testBindsPromiseTarget(t, names, boundReqs)) {
|
|
1115
|
+
boundTests.add(t.id);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
if (isBehavior) {
|
|
1119
|
+
const b = (standard.behaviors ?? []).find(x => x.id === p.target);
|
|
1120
|
+
for (const tid of b?.verifiedBy ?? []) {
|
|
1121
|
+
boundTests.add(tid);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
if (boundReqs.size === 0 && boundTests.size === 0) {
|
|
1125
|
+
warn('C43', `${where} declares no verified_by and no requirement/test binds its target "${p.target}" β declare the verifying requirement or test (promise-verifiable)`);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1033
1129
|
// ββ C45βC47: artifacts (TODO.roadmap/09 β gap audit G2) ββ
|
|
1034
1130
|
// An artifact is a required OUTPUT OF THE SUBJECT (the instrument) β not
|
|
1035
1131
|
// a record of the test. artifact_definition (IS: content contract +
|
|
@@ -3520,6 +3616,603 @@ function checkPackage(dir, options = {}) {
|
|
|
3520
3616
|
}
|
|
3521
3617
|
}
|
|
3522
3618
|
}
|
|
3619
|
+
// ββ C130: identity-and-aspect-references (smart TODO.roadmap/40 βββββ
|
|
3620
|
+
// batch 3; the packages-as-SSOT epic) ββββββββββββββββββββββββββββββββ
|
|
3621
|
+
// The documentary identity slots + the qualitative aspect register
|
|
3622
|
+
// (smart TODO.roadmap/47). Legs: the slot shape leg (at least one
|
|
3623
|
+
// presentation channel β the slot's content is presented somewhere);
|
|
3624
|
+
// the aspect references β term_ref β the term register, component β
|
|
3625
|
+
// an instrument component, attribute β an attribute_definition (a
|
|
3626
|
+
// reference, never a redeclaration β INV-3), and the contains entries
|
|
3627
|
+
// β EITHER a model.identity.<slot> path into the slot register OR a
|
|
3628
|
+
// bare attribute/dimension id (the two namespaces coexist; each leg
|
|
3629
|
+
// checked only when its target register composes β per-register
|
|
3630
|
+
// gating, the C58 doctrine); and the R28 bind-path consumer leg β a
|
|
3631
|
+
// requirement/test binds_to path of the model.identity.<slot> or
|
|
3632
|
+
// model.aspects.<id> form names a declared slot/aspect when the
|
|
3633
|
+
// register is in scope.
|
|
3634
|
+
{
|
|
3635
|
+
const slotIds = new Set((standard.identitySlots ?? []).map(s => s.id));
|
|
3636
|
+
const aspectIds = new Set((standard.aspects ?? []).map(a => a.id));
|
|
3637
|
+
const termIds = new Set((standard.terms ?? []).map(t => t.id));
|
|
3638
|
+
const componentIds = new Set((standard.instruments ?? []).flatMap(i => (i.components ?? []).map(c => c.id)));
|
|
3639
|
+
for (const s of standard.identitySlots ?? []) {
|
|
3640
|
+
if (s.presentation.length === 0) {
|
|
3641
|
+
err('C130', `identity_slot ${s.id}: at least one presentation channel is required β the slot's content is presented somewhere (identity-and-aspect-references)`);
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
// A contains entry resolves through the model.identity path into the
|
|
3645
|
+
// slot register, or as a bare id into the attribute/dimension
|
|
3646
|
+
// registers; null = no target register in scope (the gated skip).
|
|
3647
|
+
const containsResolves = (entry) => {
|
|
3648
|
+
if (entry.startsWith('model.identity.')) {
|
|
3649
|
+
const slot = entry.slice('model.identity.'.length);
|
|
3650
|
+
return slotIds.size > 0 ? slotIds.has(slot) : null;
|
|
3651
|
+
}
|
|
3652
|
+
if (attrIds.size > 0 || dimIds.size > 0) {
|
|
3653
|
+
return attrIds.has(entry) || dimIds.has(entry);
|
|
3654
|
+
}
|
|
3655
|
+
return null;
|
|
3656
|
+
};
|
|
3657
|
+
for (const a of standard.aspects ?? []) {
|
|
3658
|
+
const where = `aspect ${a.id}`;
|
|
3659
|
+
if (a.termRef && termIds.size > 0 && !termIds.has(a.termRef)) {
|
|
3660
|
+
err('C130', `${where}: term_ref "${a.termRef}" is not a declared term (identity-and-aspect-references)`);
|
|
3661
|
+
}
|
|
3662
|
+
if (a.component &&
|
|
3663
|
+
componentIds.size > 0 &&
|
|
3664
|
+
!componentIds.has(a.component)) {
|
|
3665
|
+
err('C130', `${where}: component "${a.component}" is not a declared instrument component (identity-and-aspect-references)`);
|
|
3666
|
+
}
|
|
3667
|
+
if (a.attribute && attrIds.size > 0 && !attrIds.has(a.attribute)) {
|
|
3668
|
+
err('C130', `${where}: attribute "${a.attribute}" is not a declared attribute_definition β an aspect points at a quantity, never redeclares it (identity-and-aspect-references)`);
|
|
3669
|
+
}
|
|
3670
|
+
for (const entry of a.contains ?? []) {
|
|
3671
|
+
if (containsResolves(entry) === false) {
|
|
3672
|
+
err('C130', `${where}: contains entry "${entry}" resolves to neither a model.identity slot nor a declared attribute/dimension (identity-and-aspect-references)`);
|
|
3673
|
+
}
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3676
|
+
// The R28 bind-path consumer leg (requirement + conformance-test
|
|
3677
|
+
// binds_to), per-register gated.
|
|
3678
|
+
const bindOwners = [
|
|
3679
|
+
...(standard.requirements ?? []).map(r => [`requirement ${r.id}`, r.bindsTo ?? []]),
|
|
3680
|
+
...(standard.conformanceTests ?? []).map(t => [`conformance_test ${t.id}`, t.bindsTo ?? []]),
|
|
3681
|
+
];
|
|
3682
|
+
for (const [owner, paths] of bindOwners) {
|
|
3683
|
+
for (const p of paths) {
|
|
3684
|
+
const path = String(p);
|
|
3685
|
+
if (path.startsWith('model.identity.') && slotIds.size > 0) {
|
|
3686
|
+
const slot = path.slice('model.identity.'.length);
|
|
3687
|
+
if (!slotIds.has(slot)) {
|
|
3688
|
+
err('C130', `${owner}: binds_to "${path}" β identity slot "${slot}" is not declared (identity-and-aspect-references)`);
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3691
|
+
else if (path.startsWith('model.aspects.') && aspectIds.size > 0) {
|
|
3692
|
+
const aspect = path.slice('model.aspects.'.length);
|
|
3693
|
+
if (!aspectIds.has(aspect)) {
|
|
3694
|
+
err('C130', `${owner}: binds_to "${path}" β aspect "${aspect}" is not declared (identity-and-aspect-references)`);
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
// ββ C131: promise-certificate-projection (smart TODO.roadmap/40 βββββ
|
|
3701
|
+
// batch 3; the packages-as-SSOT epic) ββββββββββββββββββββββββββββββββ
|
|
3702
|
+
// The certificate print projection's shape, fired on subject promises
|
|
3703
|
+
// AND promise_set entries alike: the content-binding XOR (at most one
|
|
3704
|
+
// of attribute / attributes / dimension; none = a statement row); the
|
|
3705
|
+
// bindings resolve (per-register gated, the C58 doctrine); the type is
|
|
3706
|
+
// the renderer's closed vocabulary (string | integer | number |
|
|
3707
|
+
// quantity | statement) β a check-time ERROR (the owner-settled
|
|
3708
|
+
// renderer contract), kept parse-total against renderer growth; the
|
|
3709
|
+
// label is required (the printed row carries its header). The
|
|
3710
|
+
// obligation vocabulary is parse-enforced upstream (no check leg).
|
|
3711
|
+
{
|
|
3712
|
+
const CERTIFICATE_TYPES = [
|
|
3713
|
+
'string',
|
|
3714
|
+
'integer',
|
|
3715
|
+
'number',
|
|
3716
|
+
'quantity',
|
|
3717
|
+
'statement',
|
|
3718
|
+
];
|
|
3719
|
+
const certs = [];
|
|
3720
|
+
for (const s of standard.subjects ?? []) {
|
|
3721
|
+
for (const p of s.is.promises ?? []) {
|
|
3722
|
+
if (p.certificate) {
|
|
3723
|
+
certs.push([`subject ${s.id}: promise "${p.id}"`, p.certificate]);
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
for (const ps of standard.promiseSets ?? []) {
|
|
3728
|
+
for (const p of ps.promises ?? []) {
|
|
3729
|
+
if (p.certificate) {
|
|
3730
|
+
certs.push([
|
|
3731
|
+
`promise_set ${ps.id}: promise "${p.id}"`,
|
|
3732
|
+
p.certificate,
|
|
3733
|
+
]);
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3737
|
+
for (const [where, cert] of certs) {
|
|
3738
|
+
const bindings = (cert.attribute ? 1 : 0) +
|
|
3739
|
+
(cert.attributes.length > 0 ? 1 : 0) +
|
|
3740
|
+
(cert.dimension ? 1 : 0);
|
|
3741
|
+
if (bindings > 1) {
|
|
3742
|
+
err('C131', `${where}: the certificate content binds by XOR β at most one of attribute / attributes / dimension (promise-certificate-projection)`);
|
|
3743
|
+
}
|
|
3744
|
+
if (cert.attribute && attrIds.size > 0 && !attrIds.has(cert.attribute)) {
|
|
3745
|
+
err('C131', `${where}: certificate attribute "${cert.attribute}" is not a declared attribute_definition (promise-certificate-projection)`);
|
|
3746
|
+
}
|
|
3747
|
+
for (const a of cert.attributes ?? []) {
|
|
3748
|
+
if (attrIds.size > 0 && !attrIds.has(a)) {
|
|
3749
|
+
err('C131', `${where}: certificate attributes entry "${a}" is not a declared attribute_definition (promise-certificate-projection)`);
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
if (cert.dimension && dimIds.size > 0 && !dimIds.has(cert.dimension)) {
|
|
3753
|
+
err('C131', `${where}: certificate dimension "${cert.dimension}" is not a declared classification dimension (promise-certificate-projection)`);
|
|
3754
|
+
}
|
|
3755
|
+
if (!cert.type) {
|
|
3756
|
+
err('C131', `${where}: the certificate type is required β the renderer's row kind (promise-certificate-projection)`);
|
|
3757
|
+
}
|
|
3758
|
+
else if (!CERTIFICATE_TYPES.includes(cert.type)) {
|
|
3759
|
+
err('C131', `${where}: certificate type "${cert.type}" is outside the renderer's closed vocabulary (valid: ${CERTIFICATE_TYPES.join(', ')}) (promise-certificate-projection)`);
|
|
3760
|
+
}
|
|
3761
|
+
if (!cert.label) {
|
|
3762
|
+
err('C131', `${where}: the certificate label is required β the printed row carries its header (promise-certificate-projection)`);
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
// ββ C132: application-declaration-references (smart TODO.roadmap/40 β
|
|
3767
|
+
// batch 3; the packages-as-SSOT epic) ββββββββββββββββββββββββββββββββ
|
|
3768
|
+
// The applicant-facing documentation register: the declaration_form
|
|
3769
|
+
// names a declared form WHEN the form register is in composition scope
|
|
3770
|
+
// (per-register gating, the C58 doctrine), and the document ids are
|
|
3771
|
+
// unique within the declaration. The obligation vocabulary is
|
|
3772
|
+
// parse-enforced upstream (no check leg).
|
|
3773
|
+
{
|
|
3774
|
+
const formIds = new Set((standard.forms ?? []).map(f => f.id));
|
|
3775
|
+
for (const d of standard.applicationDeclarations ?? []) {
|
|
3776
|
+
const where = `application_declaration ${d.id}`;
|
|
3777
|
+
if (d.declarationForm &&
|
|
3778
|
+
formIds.size > 0 &&
|
|
3779
|
+
!formIds.has(d.declarationForm)) {
|
|
3780
|
+
err('C132', `${where}: declaration_form "${d.declarationForm}" is not a declared form (application-declaration-references)`);
|
|
3781
|
+
}
|
|
3782
|
+
const seen = new Set();
|
|
3783
|
+
for (const doc of d.documents ?? []) {
|
|
3784
|
+
if (seen.has(doc.id)) {
|
|
3785
|
+
err('C132', `${where}: document "${doc.id}" is declared twice (application-declaration-references)`);
|
|
3786
|
+
}
|
|
3787
|
+
seen.add(doc.id);
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
}
|
|
3791
|
+
// ββ C133: calculation-context-references (smart TODO.roadmap/40 βββββ
|
|
3792
|
+
// batch 3; the packages-as-SSOT epic) ββββββββββββββββββββββββββββββββ
|
|
3793
|
+
// The evaluation-side wiring: the source is `computed` (expression
|
|
3794
|
+
// required) or a subject-chain source β classification.<dimension>
|
|
3795
|
+
// (resolves, gated) or parameters.<attribute> (resolves, gated). The
|
|
3796
|
+
// legacy schema tokens (dimensions, application, application.specs,
|
|
3797
|
+
// lookup) and anything else ERROR β the modern subject-chain spelling
|
|
3798
|
+
// is forced. The expression's free identifiers name the context's own
|
|
3799
|
+
// fields β a WARNING leg: runtime-bound inputs (r60's `f`, r144's
|
|
3800
|
+
// `measured_value`) are legitimate non-field references (function
|
|
3801
|
+
// calls exempt β an identifier followed by `(` is a callee).
|
|
3802
|
+
{
|
|
3803
|
+
for (const cc of standard.calculationContexts ?? []) {
|
|
3804
|
+
const fieldNames = new Set((cc.fields ?? []).map(f => f.id));
|
|
3805
|
+
for (const f of cc.fields ?? []) {
|
|
3806
|
+
const where = `calculation_context ${cc.id}: field ${f.id}`;
|
|
3807
|
+
if (f.source === 'computed') {
|
|
3808
|
+
if (!f.expression) {
|
|
3809
|
+
err('C133', `${where}: source computed requires the expression facet (calculation-context-references)`);
|
|
3810
|
+
}
|
|
3811
|
+
}
|
|
3812
|
+
else if (f.source.startsWith('classification.') ||
|
|
3813
|
+
f.source.startsWith('parameters.')) {
|
|
3814
|
+
if (f.expression) {
|
|
3815
|
+
err('C133', `${where}: a non-computed source carries no expression (calculation-context-references)`);
|
|
3816
|
+
}
|
|
3817
|
+
if (f.source.startsWith('classification.')) {
|
|
3818
|
+
const dim = f.source.slice('classification.'.length);
|
|
3819
|
+
if (dimIds.size > 0 && !dimIds.has(dim)) {
|
|
3820
|
+
err('C133', `${where}: source "${f.source}" β "${dim}" is not a declared classification dimension (calculation-context-references)`);
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
else {
|
|
3824
|
+
const attr = f.source.slice('parameters.'.length);
|
|
3825
|
+
if (attrIds.size > 0 && !attrIds.has(attr)) {
|
|
3826
|
+
err('C133', `${where}: source "${f.source}" β "${attr}" is not a declared attribute_definition (calculation-context-references)`);
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
else {
|
|
3831
|
+
err('C133', `${where}: source "${f.source}" is not a subject-chain source (classification.<dimension>, parameters.<attribute>, computed) (calculation-context-references)`);
|
|
3832
|
+
}
|
|
3833
|
+
if (f.expression) {
|
|
3834
|
+
const idents = new Set();
|
|
3835
|
+
const re = /[A-Za-z_][A-Za-z0-9_]*/g;
|
|
3836
|
+
let m;
|
|
3837
|
+
while ((m = re.exec(f.expression)) !== null) {
|
|
3838
|
+
if (!/^\s*\(/.test(f.expression.slice(re.lastIndex))) {
|
|
3839
|
+
idents.add(m[0]);
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
for (const ident of idents) {
|
|
3843
|
+
if (!fieldNames.has(ident)) {
|
|
3844
|
+
warn('C133', `${where}: expression identifier "${ident}" is not a field of this context β a runtime-bound input (declare it as a field or bind it at runtime) (calculation-context-references)`);
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
// ββ C134: evaluation-dimension-references (smart TODO.roadmap/40 ββββ
|
|
3852
|
+
// batch 3; the packages-as-SSOT epic) ββββββββββββββββββββββββββββββββ
|
|
3853
|
+
// The form-facing classification field schema: the enum facet resolves
|
|
3854
|
+
// to a declared classification dimension (error, per-register gated),
|
|
3855
|
+
// and the field NAME resolves to an is_dimension attribute_definition
|
|
3856
|
+
// OR a dimension id β the two coexisting conventions (owner decision:
|
|
3857
|
+
// r129's legacy camelCase names carry as-is), a WARNING when neither
|
|
3858
|
+
// (gated on the registers composing). The type/setting vocabularies
|
|
3859
|
+
// are parse-enforced upstream (no check legs).
|
|
3860
|
+
{
|
|
3861
|
+
const dimensionedAttrIds = new Set((standard.attributeDefinitions ?? [])
|
|
3862
|
+
.filter(a => a.isDimension === true)
|
|
3863
|
+
.map(a => a.id));
|
|
3864
|
+
for (const ed of standard.evaluationDimensions ?? []) {
|
|
3865
|
+
for (const f of ed.fields ?? []) {
|
|
3866
|
+
const where = `evaluation_dimensions ${ed.id}: field ${f.id}`;
|
|
3867
|
+
if (f.enumRef && dimIds.size > 0 && !dimIds.has(f.enumRef)) {
|
|
3868
|
+
err('C134', `${where}: enum "${f.enumRef}" is not a declared classification dimension (evaluation-dimension-references)`);
|
|
3869
|
+
}
|
|
3870
|
+
if ((dimensionedAttrIds.size > 0 || dimIds.size > 0) &&
|
|
3871
|
+
!dimensionedAttrIds.has(f.id) &&
|
|
3872
|
+
!dimIds.has(f.id)) {
|
|
3873
|
+
warn('C134', `${where}: the field name "${f.id}" resolves to neither an is_dimension attribute_definition nor a classification dimension β the legacy camelCase convention (evaluation-dimension-references)`);
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
// ββ C135: evaluation-profile-coherence (smart TODO.roadmap/40 βββββββ
|
|
3879
|
+
// batch 3; the packages-as-SSOT epic) ββββββββββββββββββββββββββββββββ
|
|
3880
|
+
// The named dimension-value presets: every dimensions key resolves to
|
|
3881
|
+
// a declared classification dimension and every value to one of that
|
|
3882
|
+
// dimension's declared values (per-register gated, the smart R4/R8
|
|
3883
|
+
// applicability mirror; an OPEN dimension β no declared value set β
|
|
3884
|
+
// accepts any value).
|
|
3885
|
+
{
|
|
3886
|
+
for (const p of standard.evaluationProfiles ?? []) {
|
|
3887
|
+
const where = `evaluation_profile ${p.id}`;
|
|
3888
|
+
for (const [dim, value] of Object.entries(p.dimensions ?? {})) {
|
|
3889
|
+
if (dimIds.size > 0 && !dimIds.has(dim)) {
|
|
3890
|
+
err('C135', `${where}: dimensions key "${dim}" is not a declared classification dimension (evaluation-profile-coherence)`);
|
|
3891
|
+
continue;
|
|
3892
|
+
}
|
|
3893
|
+
const values = dimIds.get(dim);
|
|
3894
|
+
if (values && values.size > 0 && !values.has(value)) {
|
|
3895
|
+
err('C135', `${where}: dimensions entry "${dim}" names value "${value}", which the dimension does not declare (evaluation-profile-coherence)`);
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
}
|
|
3899
|
+
}
|
|
3900
|
+
// ββ C136: certificate-template-references (smart TODO.roadmap/40 ββββ
|
|
3901
|
+
// batch 3; the packages-as-SSOT epic) ββββββββββββββββββββββββββββββββ
|
|
3902
|
+
// The certificate rendering contract: the dimension_labels pattern
|
|
3903
|
+
// placeholders ({dim} / {dim:sep}) resolve to declared classification
|
|
3904
|
+
// dimensions (error, per-register gated); the characteristic bindings
|
|
3905
|
+
// resolve (attribute/attributes β attribute_definitions, dimension β
|
|
3906
|
+
// classification dimensions, per-register gated) with the XOR shape
|
|
3907
|
+
// leg (β€ 1 of attribute | attributes | dimension β none is the
|
|
3908
|
+
// statement row); the characteristic type carries the renderer-driven
|
|
3909
|
+
// closed vocabulary (string | integer | number | quantity |
|
|
3910
|
+
// statement) at ERROR (the owner-settled decision β parse stays
|
|
3911
|
+
// total against renderer growth); the number_format placeholders
|
|
3912
|
+
// check against the known token vocabulary at WARNING (open for
|
|
3913
|
+
// program-specific prefixes like twin-cert's literal TW-1).
|
|
3914
|
+
{
|
|
3915
|
+
const CERTIFICATE_NUMBER_TOKENS = new Set([
|
|
3916
|
+
'shortName',
|
|
3917
|
+
'edition',
|
|
3918
|
+
'scheme',
|
|
3919
|
+
'authority',
|
|
3920
|
+
'year2',
|
|
3921
|
+
'seq',
|
|
3922
|
+
]);
|
|
3923
|
+
const CERTIFICATE_CHARACTERISTIC_TYPES = new Set([
|
|
3924
|
+
'string',
|
|
3925
|
+
'integer',
|
|
3926
|
+
'number',
|
|
3927
|
+
'quantity',
|
|
3928
|
+
'statement',
|
|
3929
|
+
]);
|
|
3930
|
+
const placeholderRe = /\{([^{}:/]+)(?::[^{}]*)?\}/g;
|
|
3931
|
+
for (const t of standard.certificateTemplates ?? []) {
|
|
3932
|
+
const where = `certificate_template ${t.id}`;
|
|
3933
|
+
if (t.numberFormat) {
|
|
3934
|
+
let m;
|
|
3935
|
+
while ((m = placeholderRe.exec(t.numberFormat)) !== null) {
|
|
3936
|
+
if (!CERTIFICATE_NUMBER_TOKENS.has(m[1])) {
|
|
3937
|
+
warn('C136', `${where}: number_format placeholder "{${m[1]}}" is not a known token (${[...CERTIFICATE_NUMBER_TOKENS].join(', ')}) β a program-specific spelling (certificate-template-references)`);
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
if (t.dimensionLabels && dimIds.size > 0) {
|
|
3942
|
+
let m;
|
|
3943
|
+
while ((m = placeholderRe.exec(t.dimensionLabels.pattern)) !== null) {
|
|
3944
|
+
if (!dimIds.has(m[1])) {
|
|
3945
|
+
err('C136', `${where}: dimension_labels placeholder "{${m[1]}}" is not a declared classification dimension (certificate-template-references)`);
|
|
3946
|
+
}
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
for (const c of t.characteristics ?? []) {
|
|
3950
|
+
const cwhere = `${where}: characteristic ${c.id}`;
|
|
3951
|
+
if (c.type && !CERTIFICATE_CHARACTERISTIC_TYPES.has(c.type)) {
|
|
3952
|
+
err('C136', `${cwhere}: type "${c.type}" is outside the renderer vocabulary (string, integer, number, quantity, statement) (certificate-template-references)`);
|
|
3953
|
+
}
|
|
3954
|
+
const bindings = (c.attribute ? 1 : 0) +
|
|
3955
|
+
(c.attributes.length > 0 ? 1 : 0) +
|
|
3956
|
+
(c.dimension ? 1 : 0);
|
|
3957
|
+
if (bindings > 1) {
|
|
3958
|
+
err('C136', `${cwhere}: carries ${bindings} content bindings β the XOR is attribute | attributes | dimension | none (certificate-template-references)`);
|
|
3959
|
+
}
|
|
3960
|
+
if (c.attribute && attrIds.size > 0 && !attrIds.has(c.attribute)) {
|
|
3961
|
+
err('C136', `${cwhere}: attribute "${c.attribute}" is not a declared attribute_definition (certificate-template-references)`);
|
|
3962
|
+
}
|
|
3963
|
+
for (const a of c.attributes) {
|
|
3964
|
+
if (attrIds.size > 0 && !attrIds.has(a)) {
|
|
3965
|
+
err('C136', `${cwhere}: attributes entry "${a}" is not a declared attribute_definition (certificate-template-references)`);
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
if (c.dimension && dimIds.size > 0 && !dimIds.has(c.dimension)) {
|
|
3969
|
+
err('C136', `${cwhere}: dimension "${c.dimension}" is not a declared classification dimension (certificate-template-references)`);
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
// ββ C137: workflow-config-references (smart TODO.roadmap/40 ββββββββ
|
|
3975
|
+
// batch 3; the packages-as-SSOT epic) ββββββββββββββββββββββββββββββββ
|
|
3976
|
+
// The certification workflow step register: the actor resolves against
|
|
3977
|
+
// the composed role register (per-register gated; role ids are
|
|
3978
|
+
// snake_case β the YAML hyphenation is the emitter's); the inputs/
|
|
3979
|
+
// outputs entries resolve against the data-class register ONLY when
|
|
3980
|
+
// the entry is a single clean token (composite strings like
|
|
3981
|
+
// "TestReport containing FormInstance" stay documentary β skipped,
|
|
3982
|
+
// never errored); the gates are prose predicates β never resolved
|
|
3983
|
+
// (the R26 field-resolution precedent). The phase vocabulary is
|
|
3984
|
+
// parse-enforced upstream (no check leg).
|
|
3985
|
+
{
|
|
3986
|
+
const roleIds = new Set((standard.roles ?? []).map(r => r.id));
|
|
3987
|
+
// Class ids may carry a namespace suffix (`Application#data`) while
|
|
3988
|
+
// the workflow's inputs/outputs spell the bare entity name β index
|
|
3989
|
+
// both spellings (the semantic-status leg's precedent).
|
|
3990
|
+
const dataclassIds = new Set();
|
|
3991
|
+
for (const c of standard.dataclasses ?? []) {
|
|
3992
|
+
dataclassIds.add(c.id);
|
|
3993
|
+
if (c.id.includes('#')) {
|
|
3994
|
+
dataclassIds.add(c.id.slice(0, c.id.indexOf('#')));
|
|
3995
|
+
}
|
|
3996
|
+
}
|
|
3997
|
+
const CLEAN_TOKEN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
3998
|
+
for (const w of standard.workflowConfigs ?? []) {
|
|
3999
|
+
for (const s of w.steps ?? []) {
|
|
4000
|
+
const where = `workflow_config ${w.id}: step ${s.id}`;
|
|
4001
|
+
if (s.actor && roleIds.size > 0 && !roleIds.has(s.actor)) {
|
|
4002
|
+
err('C137', `${where}: actor "${s.actor}" is not a declared role (workflow-config-references)`);
|
|
4003
|
+
}
|
|
4004
|
+
for (const [facet, list] of [
|
|
4005
|
+
['inputs', s.inputs],
|
|
4006
|
+
['outputs', s.outputs],
|
|
4007
|
+
]) {
|
|
4008
|
+
for (const entry of list) {
|
|
4009
|
+
if (!CLEAN_TOKEN.test(entry)) {
|
|
4010
|
+
continue; // a composite documentary string β never resolved
|
|
4011
|
+
}
|
|
4012
|
+
if (dataclassIds.size > 0 && !dataclassIds.has(entry)) {
|
|
4013
|
+
err('C137', `${where}: ${facet} entry "${entry}" is not a declared data class (workflow-config-references)`);
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
4016
|
+
}
|
|
4017
|
+
}
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
// ββ C138: verification-pathway-references (smart TODO.roadmap/40 βββ
|
|
4021
|
+
// batch 3; the packages-as-SSOT epic) ββββββββββββββββββββββββββββββββ
|
|
4022
|
+
// The verification pathways beyond type evaluation: the tests resolve
|
|
4023
|
+
// to conformance_tests and the assessment covers to requirements
|
|
4024
|
+
// (per-register gated β the smart R22 mirror); the trigger action
|
|
4025
|
+
// resolves to a transition action of an in-scope LIFECYCLE
|
|
4026
|
+
// state_machine (gated); the trigger event is required iff kind
|
|
4027
|
+
// signal; the validity window carries β₯ 1 of years/months. The kind /
|
|
4028
|
+
// trigger-kind / limits-mode vocabularies are parse-enforced upstream
|
|
4029
|
+
// (no check legs).
|
|
4030
|
+
{
|
|
4031
|
+
const lifecycleActions = new Set();
|
|
4032
|
+
for (const sm of standard.stateMachines ?? []) {
|
|
4033
|
+
if (sm.kind !== 'lifecycle') {
|
|
4034
|
+
continue;
|
|
4035
|
+
}
|
|
4036
|
+
for (const t of sm.transitions ?? []) {
|
|
4037
|
+
if (t.actionName) {
|
|
4038
|
+
lifecycleActions.add(t.actionName);
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
}
|
|
4042
|
+
for (const p of standard.verificationPathways ?? []) {
|
|
4043
|
+
const where = `verification_pathway ${p.id}`;
|
|
4044
|
+
for (const t of p.tests ?? []) {
|
|
4045
|
+
if (testIds.size > 0 && !testIds.has(t)) {
|
|
4046
|
+
err('C138', `${where}: tests entry "${t}" is not a declared conformance_test (verification-pathway-references)`);
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
for (const r of p.assessment?.covers ?? []) {
|
|
4050
|
+
if (reqIds.size > 0 && !reqIds.has(r)) {
|
|
4051
|
+
err('C138', `${where}: assessment covers entry "${r}" is not a declared requirement (verification-pathway-references)`);
|
|
4052
|
+
}
|
|
4053
|
+
}
|
|
4054
|
+
const window = p.validity?.window;
|
|
4055
|
+
if (window && window.years <= 0 && window.months <= 0) {
|
|
4056
|
+
err('C138', `${where}: the validity window carries neither years nor months (verification-pathway-references)`);
|
|
4057
|
+
}
|
|
4058
|
+
for (const t of p.validity?.triggers ?? []) {
|
|
4059
|
+
const twhere = `${where}: trigger ${t.id}`;
|
|
4060
|
+
if (t.kind === 'signal' && !t.event) {
|
|
4061
|
+
err('C138', `${twhere}: kind signal requires the event facet (verification-pathway-references)`);
|
|
4062
|
+
}
|
|
4063
|
+
if (t.kind === 'timer' && t.event) {
|
|
4064
|
+
err('C138', `${twhere}: kind timer carries no event facet β the period is the window's (verification-pathway-references)`);
|
|
4065
|
+
}
|
|
4066
|
+
if (t.action &&
|
|
4067
|
+
lifecycleActions.size > 0 &&
|
|
4068
|
+
!lifecycleActions.has(t.action)) {
|
|
4069
|
+
err('C138', `${twhere}: action "${t.action}" is not a transition action of an in-scope lifecycle state_machine (verification-pathway-references)`);
|
|
4070
|
+
}
|
|
4071
|
+
}
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
// ββ C139: selection-rule-references (smart TODO.roadmap/40 βββββββββ
|
|
4075
|
+
// batch 3; the packages-as-SSOT epic) ββββββββββββββββββββββββββββββββ
|
|
4076
|
+
// The selection rules: the lab criterion's applies_when/match
|
|
4077
|
+
// model_field resolve against the attribute OR dimension registers
|
|
4078
|
+
// (the two coexisting conventions β gated on either register
|
|
4079
|
+
// composing); the match operator's conditional facets hold
|
|
4080
|
+
// (has_capability β required_capability; capability_from_field /
|
|
4081
|
+
// capability_covers_value / capability_prefix_match β
|
|
4082
|
+
// required_capability_prefix + model_field; always_pass β nothing
|
|
4083
|
+
// else); the capability strings are free-form lab-side labels β
|
|
4084
|
+
// never resolved. The sample/governance entries carry shape legs
|
|
4085
|
+
// only (rule / rationale / applicability non-empty). The vocabularies
|
|
4086
|
+
// are parse-enforced upstream (no check legs).
|
|
4087
|
+
{
|
|
4088
|
+
const fieldResolvable = (f) => attrIds.has(f) || dimIds.has(f);
|
|
4089
|
+
const fieldRegistersCompose = attrIds.size > 0 || dimIds.size > 0;
|
|
4090
|
+
for (const c of standard.labSelectionCriteria ?? []) {
|
|
4091
|
+
const where = `lab_selection_criterion ${c.id}`;
|
|
4092
|
+
if (c.appliesWhen?.modelField &&
|
|
4093
|
+
fieldRegistersCompose &&
|
|
4094
|
+
!fieldResolvable(c.appliesWhen.modelField)) {
|
|
4095
|
+
err('C139', `${where}: applies_when model_field "${c.appliesWhen.modelField}" resolves to neither an attribute_definition nor a classification dimension (selection-rule-references)`);
|
|
4096
|
+
}
|
|
4097
|
+
const m = c.match;
|
|
4098
|
+
if (m) {
|
|
4099
|
+
if (m.modelField &&
|
|
4100
|
+
fieldRegistersCompose &&
|
|
4101
|
+
!fieldResolvable(m.modelField)) {
|
|
4102
|
+
err('C139', `${where}: match model_field "${m.modelField}" resolves to neither an attribute_definition nor a classification dimension (selection-rule-references)`);
|
|
4103
|
+
}
|
|
4104
|
+
if (m.operator === 'has_capability' && !m.requiredCapability) {
|
|
4105
|
+
err('C139', `${where}: operator has_capability requires the required_capability facet (selection-rule-references)`);
|
|
4106
|
+
}
|
|
4107
|
+
if ((m.operator === 'capability_from_field' ||
|
|
4108
|
+
m.operator === 'capability_covers_value' ||
|
|
4109
|
+
m.operator === 'capability_prefix_match') &&
|
|
4110
|
+
(!m.requiredCapabilityPrefix || !m.modelField)) {
|
|
4111
|
+
err('C139', `${where}: operator ${m.operator} requires required_capability_prefix and model_field (selection-rule-references)`);
|
|
4112
|
+
}
|
|
4113
|
+
if (m.operator === 'always_pass' &&
|
|
4114
|
+
(m.requiredCapability ||
|
|
4115
|
+
m.requiredCapabilityPrefix ||
|
|
4116
|
+
m.modelField ||
|
|
4117
|
+
m.labCapabilityPrefix)) {
|
|
4118
|
+
err('C139', `${where}: operator always_pass carries no other match facet (selection-rule-references)`);
|
|
4119
|
+
}
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
4122
|
+
for (const r of standard.sampleSelectionRules ?? []) {
|
|
4123
|
+
const where = `sample_selection_rule ${r.id}`;
|
|
4124
|
+
for (const [facet, v] of [
|
|
4125
|
+
['rule', r.rule],
|
|
4126
|
+
['rationale', r.rationale],
|
|
4127
|
+
['applicability', r.applicability],
|
|
4128
|
+
]) {
|
|
4129
|
+
if (!v) {
|
|
4130
|
+
err('C139', `${where}: the ${facet} facet is required (selection-rule-references)`);
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
for (const r of standard.specimenGovernanceRules ?? []) {
|
|
4135
|
+
const where = `specimen_governance_rule ${r.id}`;
|
|
4136
|
+
for (const [facet, v] of [
|
|
4137
|
+
['rule', r.rule],
|
|
4138
|
+
['rationale', r.rationale],
|
|
4139
|
+
['applicability', r.applicability],
|
|
4140
|
+
]) {
|
|
4141
|
+
if (!v) {
|
|
4142
|
+
err('C139', `${where}: the ${facet} facet is required (selection-rule-references)`);
|
|
4143
|
+
}
|
|
4144
|
+
}
|
|
4145
|
+
}
|
|
4146
|
+
}
|
|
4147
|
+
// ββ C140: test-report-skeleton-references (smart TODO.roadmap/40 βββ
|
|
4148
|
+
// batch 3; the packages-as-SSOT epic) ββββββββββββββββββββββββββββββββ
|
|
4149
|
+
// The evaluation-report structure: the form entry id resolves to a
|
|
4150
|
+
// declared form, the conformance_test facet to a conformance_test, the
|
|
4151
|
+
// requirements to requirements (all per-register gated β the smart
|
|
4152
|
+
// R29/R35 mirrors); a conditional form carries its inclusion rule
|
|
4153
|
+
// (applicability) or a documentary note (warning). The required
|
|
4154
|
+
// vocabulary is parse-enforced upstream (no check leg).
|
|
4155
|
+
{
|
|
4156
|
+
const formIds = new Set((standard.forms ?? []).map(f => f.id));
|
|
4157
|
+
const checkFormEntries = (where, entries) => {
|
|
4158
|
+
for (const f of entries) {
|
|
4159
|
+
const fwhere = `${where}: form ${f.id}`;
|
|
4160
|
+
if (formIds.size > 0 && !formIds.has(f.id)) {
|
|
4161
|
+
err('C140', `${fwhere}: the entry id is not a declared form (test-report-skeleton-references)`);
|
|
4162
|
+
}
|
|
4163
|
+
if (f.conformanceTest &&
|
|
4164
|
+
testIds.size > 0 &&
|
|
4165
|
+
!testIds.has(f.conformanceTest)) {
|
|
4166
|
+
err('C140', `${fwhere}: conformance_test "${f.conformanceTest}" is not a declared conformance_test (test-report-skeleton-references)`);
|
|
4167
|
+
}
|
|
4168
|
+
for (const r of f.requirements) {
|
|
4169
|
+
if (reqIds.size > 0 && !reqIds.has(r)) {
|
|
4170
|
+
err('C140', `${fwhere}: requirements entry "${r}" is not a declared requirement (test-report-skeleton-references)`);
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
if (f.required === 'conditional' &&
|
|
4174
|
+
f.applicability.length === 0 &&
|
|
4175
|
+
!f.notes) {
|
|
4176
|
+
warn('C140', `${fwhere}: required conditional carries neither applicability nor notes β the inclusion rule is undocumented (test-report-skeleton-references)`);
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
};
|
|
4180
|
+
for (const s of standard.testReportSkeletons ?? []) {
|
|
4181
|
+
for (const section of s.sections ?? []) {
|
|
4182
|
+
const where = `test_report_skeleton ${s.id}: section ${section.id}`;
|
|
4183
|
+
checkFormEntries(where, section.forms ?? []);
|
|
4184
|
+
for (const sub of section.subsections ?? []) {
|
|
4185
|
+
checkFormEntries(`${where}: subsection "${sub.title}"`, sub.forms ?? []);
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
}
|
|
4189
|
+
}
|
|
4190
|
+
// ββ C141: checklist-entry-shape (smart TODO.roadmap/40 batch 3; ββββ
|
|
4191
|
+
// the packages-as-SSOT epic) βββββββββββββββββββββββββββββββββββββββββ
|
|
4192
|
+
// The OIML-CS test-report checklist (PD-05 Β§4.4.3): the element
|
|
4193
|
+
// letter rides the aβr vocabulary at WARNING (check-time, never
|
|
4194
|
+
// parse-enforced β the element count can grow); the entry ids are
|
|
4195
|
+
// unique per checklist; the overlay orphan-entry leg fires at
|
|
4196
|
+
// COMPOSITION (the package.ts merge loop reports
|
|
4197
|
+
// orphan-overlay-entry, surfaced under this rule above). The
|
|
4198
|
+
// obligation vocabulary is parse-enforced upstream; the
|
|
4199
|
+
// source/validation strings are engine expressions β documentary,
|
|
4200
|
+
// never resolved.
|
|
4201
|
+
{
|
|
4202
|
+
for (const c of standard.testReportChecklists ?? []) {
|
|
4203
|
+
const where = `test_report_checklist ${c.id}`;
|
|
4204
|
+
const seen = new Set();
|
|
4205
|
+
for (const e of c.entries ?? []) {
|
|
4206
|
+
if (seen.has(e.id)) {
|
|
4207
|
+
err('C141', `${where}: entry "${e.id}" is declared twice (checklist-entry-shape)`);
|
|
4208
|
+
}
|
|
4209
|
+
seen.add(e.id);
|
|
4210
|
+
if (e.element !== null && !/^[a-r]$/.test(e.element)) {
|
|
4211
|
+
warn('C141', `${where}: entry "${e.id}" element "${e.element}" is outside the PD-05 Β§4.4.3 vocabulary (aβr) β the element count can grow, confirm the extension (checklist-entry-shape)`);
|
|
4212
|
+
}
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
4215
|
+
}
|
|
3523
4216
|
// C34 β duality-coherence: one value structure, two roles.
|
|
3524
4217
|
for (const d of standard.duals ?? []) {
|
|
3525
4218
|
if (d.attribute && !attrIds.has(d.attribute)) {
|