@primmel/primmel 1.21.12 → 1.21.14
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-allowlist.d.ts +12 -0
- package/dist/src/check-allowlist.d.ts.map +1 -1
- package/dist/src/check-allowlist.js +35 -4
- package/dist/src/check-allowlist.js.map +1 -1
- package/dist/src/check-rules.d.ts.map +1 -1
- package/dist/src/check-rules.js +30 -0
- package/dist/src/check-rules.js.map +1 -1
- package/dist/src/check.d.ts +38 -0
- package/dist/src/check.d.ts.map +1 -1
- package/dist/src/check.js +1155 -4
- package/dist/src/check.js.map +1 -1
- package/dist/src/export/retrieval.d.ts +9 -1
- package/dist/src/export/retrieval.d.ts.map +1 -1
- package/dist/src/export/retrieval.js +29 -0
- package/dist/src/export/retrieval.js.map +1 -1
- package/dist/src/expressions/index.d.ts +83 -0
- package/dist/src/expressions/index.d.ts.map +1 -0
- package/dist/src/expressions/index.js +382 -0
- package/dist/src/expressions/index.js.map +1 -0
- package/dist/src/fmt.d.ts +11 -0
- package/dist/src/fmt.d.ts.map +1 -0
- package/dist/src/fmt.js +71 -0
- package/dist/src/fmt.js.map +1 -0
- package/dist/src/ser-des/config/calculation.d.ts.map +1 -1
- package/dist/src/ser-des/config/calculation.js +46 -0
- package/dist/src/ser-des/config/calculation.js.map +1 -1
- package/dist/src/ser-des/config/competenceKind.d.ts.map +1 -1
- package/dist/src/ser-des/config/competenceKind.js +9 -20
- package/dist/src/ser-des/config/competenceKind.js.map +1 -1
- package/dist/src/ser-des/config/conformanceTest.d.ts.map +1 -1
- package/dist/src/ser-des/config/conformanceTest.js +33 -6
- package/dist/src/ser-des/config/conformanceTest.js.map +1 -1
- package/dist/src/ser-des/config/constraint.d.ts.map +1 -1
- package/dist/src/ser-des/config/constraint.js +24 -2
- package/dist/src/ser-des/config/constraint.js.map +1 -1
- package/dist/src/ser-des/config/index.d.ts +7 -0
- package/dist/src/ser-des/config/index.d.ts.map +1 -1
- package/dist/src/ser-des/config/index.js +50 -0
- package/dist/src/ser-des/config/index.js.map +1 -1
- package/dist/src/ser-des/config/lmlAligned.d.ts +16 -0
- package/dist/src/ser-des/config/lmlAligned.d.ts.map +1 -0
- package/dist/src/ser-des/config/lmlAligned.js +337 -0
- package/dist/src/ser-des/config/lmlAligned.js.map +1 -0
- package/dist/src/ser-des/config/packageManifest.d.ts.map +1 -1
- package/dist/src/ser-des/config/packageManifest.js +3 -0
- package/dist/src/ser-des/config/packageManifest.js.map +1 -1
- package/dist/src/ser-des/config/process.d.ts.map +1 -1
- package/dist/src/ser-des/config/process.js +192 -0
- package/dist/src/ser-des/config/process.js.map +1 -1
- package/dist/src/ser-des/config/requirement.d.ts.map +1 -1
- package/dist/src/ser-des/config/requirement.js +123 -7
- package/dist/src/ser-des/config/requirement.js.map +1 -1
- package/dist/src/ser-des/config/symbol.d.ts.map +1 -1
- package/dist/src/ser-des/config/symbol.js +21 -0
- package/dist/src/ser-des/config/symbol.js.map +1 -1
- package/dist/src/ser-des/config/table.d.ts.map +1 -1
- package/dist/src/ser-des/config/table.js +7 -0
- package/dist/src/ser-des/config/table.js.map +1 -1
- package/dist/src/ser-des/config/uncertaintyBudget.d.ts +5 -0
- package/dist/src/ser-des/config/uncertaintyBudget.d.ts.map +1 -0
- package/dist/src/ser-des/config/uncertaintyBudget.js +211 -0
- package/dist/src/ser-des/config/uncertaintyBudget.js.map +1 -0
- package/dist/src/ser-des/config/verdict.d.ts.map +1 -1
- package/dist/src/ser-des/config/verdict.js +12 -2
- package/dist/src/ser-des/config/verdict.js.map +1 -1
- package/dist/src/ser-des/dump.d.ts.map +1 -1
- package/dist/src/ser-des/dump.js +46 -0
- package/dist/src/ser-des/dump.js.map +1 -1
- package/dist/src/ser-des/expression-carrier.d.ts +17 -0
- package/dist/src/ser-des/expression-carrier.d.ts.map +1 -0
- package/dist/src/ser-des/expression-carrier.js +72 -0
- package/dist/src/ser-des/expression-carrier.js.map +1 -0
- package/dist/src/ser-des/package.d.ts.map +1 -1
- package/dist/src/ser-des/package.js +54 -4
- 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 +36 -2
- package/dist/src/ser-des/parse.js.map +1 -1
- package/dist/src/ser-des/shape-front-end.d.ts.map +1 -1
- package/dist/src/ser-des/shape-front-end.js +24 -5
- package/dist/src/ser-des/shape-front-end.js.map +1 -1
- package/dist/src/ser-des/types.d.ts +10 -0
- package/dist/src/ser-des/types.d.ts.map +1 -1
- package/dist/src/text-coverage.d.ts.map +1 -1
- package/dist/src/text-coverage.js +52 -0
- package/dist/src/text-coverage.js.map +1 -1
- package/dist/src/types/Calculation.d.ts +14 -0
- package/dist/src/types/Calculation.d.ts.map +1 -1
- package/dist/src/types/CompetenceKind.d.ts +6 -14
- package/dist/src/types/CompetenceKind.d.ts.map +1 -1
- package/dist/src/types/ConformanceTest.d.ts +5 -1
- package/dist/src/types/ConformanceTest.d.ts.map +1 -1
- package/dist/src/types/Constraint.d.ts +10 -1
- package/dist/src/types/Constraint.d.ts.map +1 -1
- package/dist/src/types/LmlAligned.d.ts +76 -0
- package/dist/src/types/LmlAligned.d.ts.map +1 -0
- package/dist/src/types/LmlAligned.js +8 -0
- package/dist/src/types/LmlAligned.js.map +1 -0
- package/dist/src/types/Package.d.ts +7 -0
- package/dist/src/types/Package.d.ts.map +1 -1
- package/dist/src/types/Requirement.d.ts +33 -0
- package/dist/src/types/Requirement.d.ts.map +1 -1
- package/dist/src/types/Standard.d.ts +12 -0
- package/dist/src/types/Standard.d.ts.map +1 -1
- package/dist/src/types/Symbol.d.ts +8 -0
- package/dist/src/types/Symbol.d.ts.map +1 -1
- package/dist/src/types/UncertaintyBudget.d.ts +68 -0
- package/dist/src/types/UncertaintyBudget.d.ts.map +1 -0
- package/dist/src/types/UncertaintyBudget.js +3 -0
- package/dist/src/types/UncertaintyBudget.js.map +1 -0
- package/dist/src/types/Verdict.d.ts +3 -1
- package/dist/src/types/Verdict.d.ts.map +1 -1
- package/dist/src/types/process.d.ts +52 -1
- package/dist/src/types/process.d.ts.map +1 -1
- package/dist-browser/index.mjs +2791 -1925
- package/dist-cli/primmel.mjs +2931 -76
- package/package.json +1 -1
package/dist/src/check.js
CHANGED
|
@@ -396,12 +396,14 @@ exports.checkTermAliases = checkTermAliases;
|
|
|
396
396
|
exports.checkDimensions = checkDimensions;
|
|
397
397
|
exports.checkReferences = checkReferences;
|
|
398
398
|
exports.checkVerdictChains = checkVerdictChains;
|
|
399
|
+
exports.checkLmlAligned = checkLmlAligned;
|
|
399
400
|
exports.checkEditionLifecycle = checkEditionLifecycle;
|
|
400
401
|
const fs_1 = require("fs");
|
|
401
402
|
const path_1 = require("path");
|
|
402
403
|
const package_1 = require("./ser-des/package");
|
|
403
404
|
const prm_1 = require("./ser-des/prm");
|
|
404
405
|
const mapping_coverage_1 = require("./mapping-coverage");
|
|
406
|
+
const expressions_1 = require("./expressions");
|
|
405
407
|
const time_1 = require("./time");
|
|
406
408
|
const type_expr_1 = require("./type-expr");
|
|
407
409
|
const model_diff_1 = require("./model-diff");
|
|
@@ -412,6 +414,7 @@ const Passport_1 = require("./types/Passport");
|
|
|
412
414
|
const TestSequence_1 = require("./types/TestSequence");
|
|
413
415
|
const FormulasUsed_1 = require("./types/FormulasUsed");
|
|
414
416
|
const tokenize_1 = require("./ser-des/tokenize");
|
|
417
|
+
const lmlAligned_1 = require("./ser-des/config/lmlAligned");
|
|
415
418
|
const operational_state_1 = require("./operational-state");
|
|
416
419
|
const check_rules_1 = require("./check-rules");
|
|
417
420
|
const instance_resolution_1 = require("./instance-resolution");
|
|
@@ -541,6 +544,9 @@ function checkPackage(dir, options = {}) {
|
|
|
541
544
|
issues.push(...checkReferences(standard));
|
|
542
545
|
// ── C116/C117: the verdict chain (MN 114 v3.2, clause 11.3) ────────
|
|
543
546
|
issues.push(...checkVerdictChains(standard));
|
|
547
|
+
// ── C158–C162 + C164 + C167: the LML-aligned substrate (the spec's
|
|
548
|
+
// validation contract, language-v3 §16) ─────────────────────────────
|
|
549
|
+
issues.push(...checkLmlAligned(standard));
|
|
544
550
|
// ── C149: the closed-derivation rule (the typed kernel, R3) ────────
|
|
545
551
|
issues.push(...checkClosedDerivations(standard));
|
|
546
552
|
// ── C150/C151: the attestation's references and claim lineage ──────
|
|
@@ -636,10 +642,12 @@ function checkPackage(dir, options = {}) {
|
|
|
636
642
|
};
|
|
637
643
|
(form.fields ?? []).forEach(checkField);
|
|
638
644
|
}
|
|
639
|
-
// C2 — reference targets resolve
|
|
645
|
+
// C2 — reference targets resolve (a requirement class is a legitimate
|
|
646
|
+
// verification target: a test may verify a whole regime)
|
|
647
|
+
const classIds = new Set((standard.requirementClasses ?? []).map(rc => rc.id));
|
|
640
648
|
for (const t of standard.conformanceTests ?? []) {
|
|
641
649
|
for (const target of t.targets ?? []) {
|
|
642
|
-
if (!reqIds.has(target)) {
|
|
650
|
+
if (!reqIds.has(target) && !classIds.has(target)) {
|
|
643
651
|
err('C2', `conformance test ${t.id}: target "${target}" is not a declared requirement`);
|
|
644
652
|
}
|
|
645
653
|
}
|
|
@@ -796,9 +804,19 @@ function checkPackage(dir, options = {}) {
|
|
|
796
804
|
// targeting test AND no recorded non-test verification warns. The
|
|
797
805
|
// reverse leg is unchanged: a test targeting nothing warns.
|
|
798
806
|
const covered = new Set();
|
|
807
|
+
const classMembers = new Map();
|
|
808
|
+
for (const rc of standard.requirementClasses ?? []) {
|
|
809
|
+
classMembers.set(rc.id, rc.members ?? []);
|
|
810
|
+
}
|
|
811
|
+
const coverWithMembers = (id) => {
|
|
812
|
+
covered.add(id);
|
|
813
|
+
for (const m of classMembers.get(id) ?? []) {
|
|
814
|
+
covered.add(m);
|
|
815
|
+
}
|
|
816
|
+
};
|
|
799
817
|
for (const t of standard.conformanceTests ?? []) {
|
|
800
818
|
for (const target of t.targets ?? []) {
|
|
801
|
-
|
|
819
|
+
coverWithMembers(target);
|
|
802
820
|
}
|
|
803
821
|
}
|
|
804
822
|
const provisioned = new Set();
|
|
@@ -834,6 +852,1021 @@ function checkPackage(dir, options = {}) {
|
|
|
834
852
|
warn('C5', `conformance test ${t.id}: targets no requirement`);
|
|
835
853
|
}
|
|
836
854
|
}
|
|
855
|
+
// ── C171 expression-identifiers-resolve ─────────────────────────────
|
|
856
|
+
// The machine-executable expressions are the executable core: every
|
|
857
|
+
// identifier inside a limit expression, a derivation, or a derive
|
|
858
|
+
// must resolve to a declared name in scope — the expression's own
|
|
859
|
+
// uses list, the test's declared variables, or the package's
|
|
860
|
+
// symbols/datums/verdicts. An identifier naming nothing is air inside
|
|
861
|
+
// the formula. Dotted paths (the subject-chain binds) and the OCL
|
|
862
|
+
// keywords are not identifiers for this purpose.
|
|
863
|
+
const OCL_WORDS = new Set([
|
|
864
|
+
'and',
|
|
865
|
+
'or',
|
|
866
|
+
'not',
|
|
867
|
+
'xor',
|
|
868
|
+
'implies',
|
|
869
|
+
'if',
|
|
870
|
+
'then',
|
|
871
|
+
'else',
|
|
872
|
+
'endif',
|
|
873
|
+
'true',
|
|
874
|
+
'false',
|
|
875
|
+
'null',
|
|
876
|
+
'invalid',
|
|
877
|
+
'abs',
|
|
878
|
+
'max',
|
|
879
|
+
'min',
|
|
880
|
+
'floor',
|
|
881
|
+
'ceil',
|
|
882
|
+
'mod',
|
|
883
|
+
'div',
|
|
884
|
+
'round',
|
|
885
|
+
'sqrt',
|
|
886
|
+
'exp',
|
|
887
|
+
'ln',
|
|
888
|
+
'log',
|
|
889
|
+
'sum',
|
|
890
|
+
'count',
|
|
891
|
+
'exists',
|
|
892
|
+
'forall',
|
|
893
|
+
'oclIsUndefined',
|
|
894
|
+
'oclIsKindOf',
|
|
895
|
+
'oclIsTypeOf',
|
|
896
|
+
'String',
|
|
897
|
+
'Integer',
|
|
898
|
+
'Real',
|
|
899
|
+
'Boolean',
|
|
900
|
+
'Set',
|
|
901
|
+
'Bag',
|
|
902
|
+
'Sequence',
|
|
903
|
+
'Collection',
|
|
904
|
+
// the subject-chain roots: every expression reads the dispatch
|
|
905
|
+
// context through them — context, never air
|
|
906
|
+
'sample',
|
|
907
|
+
'family',
|
|
908
|
+
'group',
|
|
909
|
+
'model',
|
|
910
|
+
'instrument',
|
|
911
|
+
'specimen',
|
|
912
|
+
'max_of',
|
|
913
|
+
'min_of',
|
|
914
|
+
'mean_of',
|
|
915
|
+
'stddev_of',
|
|
916
|
+
'count_of',
|
|
917
|
+
]);
|
|
918
|
+
const globalExprNames = new Set([
|
|
919
|
+
...(standard.symbols ?? []).map(s => s.id),
|
|
920
|
+
...(standard.symbols ?? []).map(s => s.id.split('/').pop() ?? ''),
|
|
921
|
+
...(standard.datums ?? []).map(d => d.id),
|
|
922
|
+
...(standard.verdicts ?? []).map(v => v.id),
|
|
923
|
+
// the lookup tables: a dotted helper call's OBJECT is declared here —
|
|
924
|
+
// the method name rides the declared object
|
|
925
|
+
...(standard.tables ?? []).map(v => v.id),
|
|
926
|
+
// the subject anatomy: a declared attribute is a declaration
|
|
927
|
+
...(standard.attributeDefinitions ?? []).map(a => a.id),
|
|
928
|
+
...(standard.attributeDefinitions ?? []).map(a => a.id.split('/').pop() ?? ''),
|
|
929
|
+
// the calculations: a derivation may call a declared calculation
|
|
930
|
+
// (twin-cert's lookupMPE) — the callable registry
|
|
931
|
+
...(standard.calculations ?? []).map(c => c.id),
|
|
932
|
+
...(standard.calculations ?? []).map(c => c.id.split('/').pop() ?? ''),
|
|
933
|
+
].filter(x => x !== ''));
|
|
934
|
+
// The declared quantity kinds (C177 kind-coherence): only the
|
|
935
|
+
// declarations that STATE a kind participate; everything else is
|
|
936
|
+
// unknown and never conflicts.
|
|
937
|
+
const symbolKinds = new Map();
|
|
938
|
+
for (const s of standard.symbols ?? []) {
|
|
939
|
+
if (s.quantityKind) {
|
|
940
|
+
symbolKinds.set(s.id, s.quantityKind);
|
|
941
|
+
symbolKinds.set(s.id.split('/').pop() ?? '', s.quantityKind);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
const attributeKinds = new Map();
|
|
945
|
+
for (const a of standard.attributeDefinitions ?? []) {
|
|
946
|
+
if (a.quantityKind) {
|
|
947
|
+
attributeKinds.set(a.id, a.quantityKind);
|
|
948
|
+
attributeKinds.set(a.id.split('/').pop() ?? '', a.quantityKind);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
const verdictKinds = new Map();
|
|
952
|
+
for (const v of standard.verdicts ?? []) {
|
|
953
|
+
if (v.quantityKind) {
|
|
954
|
+
verdictKinds.set(v.id, v.quantityKind);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
const tableColumnTypes = new Map();
|
|
958
|
+
for (const t of standard.tables ?? []) {
|
|
959
|
+
const cols = new Map();
|
|
960
|
+
for (const c of t.columnDefs ?? []) {
|
|
961
|
+
cols.set(c.name, (c.type || '').toLowerCase());
|
|
962
|
+
}
|
|
963
|
+
if (cols.size > 0) {
|
|
964
|
+
tableColumnTypes.set(t.id, cols);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
const bare = (q) => q.split(':').pop() ?? q;
|
|
968
|
+
const usesCovers = (uses, id) => uses.some(u => {
|
|
969
|
+
const tail = bare(u.replace(/\./g, ':'));
|
|
970
|
+
return u === id || tail === id || u.endsWith('.' + id);
|
|
971
|
+
});
|
|
972
|
+
const exprIds = (expr) => {
|
|
973
|
+
// dotted paths are WHOLE references: the interior segments are the
|
|
974
|
+
// chain's structure — only the final segment names the datum, and
|
|
975
|
+
// usesCovers judges the path against the uses entries.
|
|
976
|
+
const paths = expr.match(/[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*/g) ?? [];
|
|
977
|
+
const out = [];
|
|
978
|
+
const seen = new Set();
|
|
979
|
+
for (const p of paths) {
|
|
980
|
+
if (p.includes('.')) {
|
|
981
|
+
const segs = p.split('.');
|
|
982
|
+
// a method call on a declared object (table.permits(...)) rides
|
|
983
|
+
// the declared object — the final segment is its method
|
|
984
|
+
if (globalExprNames.has(segs[0])) {
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
const finalSeg = segs[segs.length - 1];
|
|
988
|
+
if (!seen.has(p)) {
|
|
989
|
+
seen.add(p);
|
|
990
|
+
if (!OCL_WORDS.has(finalSeg)) {
|
|
991
|
+
out.push(finalSeg);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
if (!OCL_WORDS.has(p) && !seen.has(p)) {
|
|
997
|
+
seen.add(p);
|
|
998
|
+
out.push(p);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
return out;
|
|
1002
|
+
};
|
|
1003
|
+
const unresolvedIn = (expr, scope, uses) => {
|
|
1004
|
+
const scopeSet = new Set([...scope, ...globalExprNames]);
|
|
1005
|
+
const seen = new Set();
|
|
1006
|
+
const out = [];
|
|
1007
|
+
for (const id of exprIds(expr)) {
|
|
1008
|
+
if (seen.has(id)) {
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
seen.add(id);
|
|
1012
|
+
if (OCL_WORDS.has(id) || scopeSet.has(id) || usesCovers(uses, id)) {
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
1015
|
+
out.push(id);
|
|
1016
|
+
}
|
|
1017
|
+
return out;
|
|
1018
|
+
};
|
|
1019
|
+
// The mapped computations (register: the mapped-callee plane): a
|
|
1020
|
+
// certification program maps to its Recommendations (`maps_to`), and
|
|
1021
|
+
// its derivations invoke the mapped Recommendation's declared
|
|
1022
|
+
// computations — by reference, never restated. The ids join the
|
|
1023
|
+
// callable registry, locator-gated like C168's scoped leg.
|
|
1024
|
+
const mappedCalcIds = new Set();
|
|
1025
|
+
{
|
|
1026
|
+
const mapsTo = standard.packageManifest?.mapsTo ?? [];
|
|
1027
|
+
for (const target of mapsTo) {
|
|
1028
|
+
if (!options.resolvePackage) {
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
try {
|
|
1032
|
+
const depDir = options.resolvePackage(target);
|
|
1033
|
+
if (!depDir) {
|
|
1034
|
+
continue;
|
|
1035
|
+
}
|
|
1036
|
+
const dep = (0, package_1.loadPackageWithIssues)(depDir);
|
|
1037
|
+
for (const c of dep.standard?.calculations ?? []) {
|
|
1038
|
+
mappedCalcIds.add(c.id);
|
|
1039
|
+
mappedCalcIds.add(c.id.split('/').pop() ?? '');
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
catch {
|
|
1043
|
+
// Unreadable mapped package — C27 reports the resolution
|
|
1044
|
+
// failure; this registry stays silent rather than
|
|
1045
|
+
// double-reporting.
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
for (const x of mappedCalcIds) {
|
|
1049
|
+
if (x !== '') {
|
|
1050
|
+
globalExprNames.add(x);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
// The v3.3 inline limit: the AST walked against the args scope. The
|
|
1055
|
+
// body's identifiers are the args and nothing else (plus `self`-rooted
|
|
1056
|
+
// paths — the dispatch context — and the closed function vocabulary);
|
|
1057
|
+
// `memberScope` (a class law) adds the containment to the scope.
|
|
1058
|
+
const INLINE_FUNCTION_WORDS = new Set([
|
|
1059
|
+
'abs',
|
|
1060
|
+
'max',
|
|
1061
|
+
'min',
|
|
1062
|
+
'floor',
|
|
1063
|
+
'ceil',
|
|
1064
|
+
'round',
|
|
1065
|
+
'sqrt',
|
|
1066
|
+
'exp',
|
|
1067
|
+
'ln',
|
|
1068
|
+
'log',
|
|
1069
|
+
'mod',
|
|
1070
|
+
'sum',
|
|
1071
|
+
'count',
|
|
1072
|
+
'exists',
|
|
1073
|
+
'forall',
|
|
1074
|
+
// the series aggregations (register W4): the GUM type A vocabulary —
|
|
1075
|
+
// the statistics of a captured series; the evaluation is plurimath's
|
|
1076
|
+
'max_of',
|
|
1077
|
+
'min_of',
|
|
1078
|
+
'mean_of',
|
|
1079
|
+
'stddev_of',
|
|
1080
|
+
'count_of',
|
|
1081
|
+
]);
|
|
1082
|
+
const DECLARATION_KINDS = new Set([
|
|
1083
|
+
'symbol',
|
|
1084
|
+
'table',
|
|
1085
|
+
'datum',
|
|
1086
|
+
'verdict',
|
|
1087
|
+
'attribute',
|
|
1088
|
+
'process',
|
|
1089
|
+
]);
|
|
1090
|
+
// the dispatch-context roots: the chain the expression reads the
|
|
1091
|
+
// subject through — allowed as PATH roots, never as bare scalars
|
|
1092
|
+
const CONTEXT_ROOTS = new Set([
|
|
1093
|
+
'sample',
|
|
1094
|
+
'family',
|
|
1095
|
+
'group',
|
|
1096
|
+
'model',
|
|
1097
|
+
'instrument',
|
|
1098
|
+
'specimen',
|
|
1099
|
+
]);
|
|
1100
|
+
const checkInlineLimit = (ownerKind, ownerId, limit, memberScope) => {
|
|
1101
|
+
let ast;
|
|
1102
|
+
try {
|
|
1103
|
+
ast = (0, expressions_1.parseExpression)(limit.expression);
|
|
1104
|
+
}
|
|
1105
|
+
catch (e) {
|
|
1106
|
+
err('C171', `${ownerKind} ${ownerId}: the inline limit expression does not parse — ${String(e.message ?? e)} (expression-identifiers-resolve)`);
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
const ids = (0, expressions_1.freeIdentifiers)(ast);
|
|
1110
|
+
const args = limit.args ?? [];
|
|
1111
|
+
const argNames = new Set(args.map(a => a.name));
|
|
1112
|
+
if (memberScope === null &&
|
|
1113
|
+
args.length === 0 &&
|
|
1114
|
+
ids.names.some(n => !n.includes('.'))) {
|
|
1115
|
+
// a body over pure dispatch-context paths (`sample.test_context.*`)
|
|
1116
|
+
// binds nothing and is legal; any bare identifier is ambient air
|
|
1117
|
+
err('C171', `${ownerKind} ${ownerId}: the inline limit declares no arguments — every identifier must bind explicitly, args { name <- <declaration> } (expression-identifiers-resolve)`);
|
|
1118
|
+
}
|
|
1119
|
+
for (const call of new Set(ids.calls)) {
|
|
1120
|
+
if (INLINE_FUNCTION_WORDS.has(call)) {
|
|
1121
|
+
continue;
|
|
1122
|
+
}
|
|
1123
|
+
if (globalExprNames.has(call)) {
|
|
1124
|
+
// a declared callable — the calculation registry (twin-cert's
|
|
1125
|
+
// mpe(…), R 49's relative_error(…))
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
if (call.includes('.')) {
|
|
1129
|
+
const root = call.split('.')[0];
|
|
1130
|
+
if (argNames.has(root) ||
|
|
1131
|
+
globalExprNames.has(root) ||
|
|
1132
|
+
root === 'self' ||
|
|
1133
|
+
CONTEXT_ROOTS.has(root)) {
|
|
1134
|
+
// the method rides its declared object — the root is the check
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
if (argNames.has(call)) {
|
|
1139
|
+
// an arg-bound callable — the table lookup the args declare
|
|
1140
|
+
continue;
|
|
1141
|
+
}
|
|
1142
|
+
err('C171', `${ownerKind} ${ownerId}: the limit calls "${call}(…)", which no argument binds and which is not in the expression function vocabulary (abs max min floor ceil round sqrt exp ln log mod sum count exists forall) — air inside the formula (expression-identifiers-resolve)`);
|
|
1143
|
+
}
|
|
1144
|
+
for (const id of new Set(ids.names)) {
|
|
1145
|
+
const root = id.split('.')[0];
|
|
1146
|
+
if (id.includes('.') &&
|
|
1147
|
+
(root === 'self' || argNames.has(root) || CONTEXT_ROOTS.has(root))) {
|
|
1148
|
+
continue;
|
|
1149
|
+
}
|
|
1150
|
+
if (argNames.has(id)) {
|
|
1151
|
+
continue;
|
|
1152
|
+
}
|
|
1153
|
+
if (memberScope !== null && memberScope.has(id)) {
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
const lawScoped = memberScope !== null;
|
|
1157
|
+
const fix = lawScoped
|
|
1158
|
+
? 'the law composes the containment only — its identifiers are the members'
|
|
1159
|
+
: 'add args { ' + id + ' <- <declaration> } or remove it';
|
|
1160
|
+
err(lawScoped ? 'C172' : 'C171', `${ownerKind} ${ownerId}: the limit expression names "${id}", which no argument binds — ${fix} (${lawScoped ? 'requirement-algebra-resolves' : 'expression-identifiers-resolve'})`);
|
|
1161
|
+
}
|
|
1162
|
+
for (const a of args) {
|
|
1163
|
+
if (a.source.includes(':')) {
|
|
1164
|
+
const segs = a.source.split(':');
|
|
1165
|
+
if (segs.some(s => s.length === 0)) {
|
|
1166
|
+
err('C171', `${ownerKind} ${ownerId}: argument "${a.name}" binds to "${a.source}" — a binding is <kind>:<id> in this package or <package>:<kind>:<id> across a uses edge (expression-identifiers-resolve)`);
|
|
1167
|
+
continue;
|
|
1168
|
+
}
|
|
1169
|
+
if (segs.length === 2) {
|
|
1170
|
+
const [kind, id] = segs;
|
|
1171
|
+
if (kind === 'process') {
|
|
1172
|
+
// register 19: `process:<p>[.<step>[.<io>]]` — the dot
|
|
1173
|
+
// descends the declared structure, the leaf names a
|
|
1174
|
+
// declared io
|
|
1175
|
+
const segs2 = id.split('.');
|
|
1176
|
+
const proc = (standard.processes ?? []).find(pp => pp.id === segs2[0]);
|
|
1177
|
+
if (!proc) {
|
|
1178
|
+
err('C171', `${ownerKind} ${ownerId}: argument "${a.name}" binds to "${a.source}", which no declared process names — air inside the formula (expression-identifiers-resolve)`);
|
|
1179
|
+
}
|
|
1180
|
+
else if (segs2.length >= 2) {
|
|
1181
|
+
const stp = (proc.steps ?? []).find(ss => ss.id === segs2[1]);
|
|
1182
|
+
if (!stp) {
|
|
1183
|
+
err('C171', `${ownerKind} ${ownerId}: argument "${a.name}" binds to "${a.source}" — the process declares no step "${segs2[1]}" (expression-identifiers-resolve)`);
|
|
1184
|
+
}
|
|
1185
|
+
else if (segs2.length >= 3) {
|
|
1186
|
+
const io = segs2.slice(2).join('.');
|
|
1187
|
+
if (!(stp.sigInputs ?? []).includes(io) &&
|
|
1188
|
+
!(stp.sigOutputs ?? []).includes(io)) {
|
|
1189
|
+
err('C171', `${ownerKind} ${ownerId}: argument "${a.name}" binds to "${a.source}" — the step declares no io "${io}" (expression-identifiers-resolve)`);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
continue;
|
|
1194
|
+
}
|
|
1195
|
+
if (kind !== 'observable') {
|
|
1196
|
+
if (!DECLARATION_KINDS.has(kind)) {
|
|
1197
|
+
err('C171', `${ownerKind} ${ownerId}: argument "${a.name}" binds to "${a.source}" — "${kind}" is not a declaration kind (symbol table datum verdict attribute observable) (expression-identifiers-resolve)`);
|
|
1198
|
+
continue;
|
|
1199
|
+
}
|
|
1200
|
+
const idRoot = id.split('.')[0];
|
|
1201
|
+
const idTail = id.split('.').pop() ?? id;
|
|
1202
|
+
if (!globalExprNames.has(id) &&
|
|
1203
|
+
!globalExprNames.has(idRoot) &&
|
|
1204
|
+
!globalExprNames.has(idTail)) {
|
|
1205
|
+
err('C171', `${ownerKind} ${ownerId}: argument "${a.name}" binds to "${a.source}", which no declared ${kind} names — air inside the formula (expression-identifiers-resolve)`);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
continue;
|
|
1209
|
+
}
|
|
1210
|
+
// <package>:<kind>:<id> across a uses edge — the composed gate
|
|
1211
|
+
// resolves it against the closure.
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
if (a.source === 'self' || a.source.startsWith('self.')) {
|
|
1215
|
+
continue;
|
|
1216
|
+
}
|
|
1217
|
+
const bare = (q) => q.split(':').pop() ?? q;
|
|
1218
|
+
const sourceBare = bare(a.source.replace(/\./g, ':'));
|
|
1219
|
+
if (memberScope !== null) {
|
|
1220
|
+
if (!memberScope.has(a.source) && !memberScope.has(sourceBare)) {
|
|
1221
|
+
err('C172', `requirement class ${ownerId}: the law binds "${a.name}" to "${a.source}", which is not a contained member (requirement-algebra-resolves)`);
|
|
1222
|
+
}
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1225
|
+
if (!globalExprNames.has(a.source) &&
|
|
1226
|
+
!globalExprNames.has(sourceBare) &&
|
|
1227
|
+
!usesCovers(limit.uses, sourceBare)) {
|
|
1228
|
+
err('C171', `${ownerKind} ${ownerId}: argument "${a.name}" binds to "${a.source}", which no symbol, datum, verdict, attribute, or uses entry declares — air inside the formula (expression-identifiers-resolve)`);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
// ── C177 kind-coherence ──────────────────────────────────────────
|
|
1232
|
+
// The comparisons compare operands of the same declared quantity
|
|
1233
|
+
// kind. The kinds come from the args' declared sources; an operand
|
|
1234
|
+
// of unknown kind or a bare number never conflicts — the check is
|
|
1235
|
+
// exact where the declarations are, silent everywhere else.
|
|
1236
|
+
const kindLabel = (k) => k === undefined
|
|
1237
|
+
? 'an operand of unknown kind'
|
|
1238
|
+
: k === 'TEXT'
|
|
1239
|
+
? 'text'
|
|
1240
|
+
: k === null
|
|
1241
|
+
? 'a bare number'
|
|
1242
|
+
: `the "${k}" quantity`;
|
|
1243
|
+
const argKindOf = (source) => {
|
|
1244
|
+
if (source.includes(':')) {
|
|
1245
|
+
const segs = source.split(':');
|
|
1246
|
+
if (segs.length !== 2) {
|
|
1247
|
+
return undefined;
|
|
1248
|
+
}
|
|
1249
|
+
const [kind, id] = segs;
|
|
1250
|
+
if (kind === 'symbol') {
|
|
1251
|
+
return symbolKinds.get(id.split('.').pop() ?? id);
|
|
1252
|
+
}
|
|
1253
|
+
if (kind === 'attribute') {
|
|
1254
|
+
return attributeKinds.get(id.split('.').pop() ?? id);
|
|
1255
|
+
}
|
|
1256
|
+
if (kind === 'verdict') {
|
|
1257
|
+
return verdictKinds.get(id.split('.')[0]);
|
|
1258
|
+
}
|
|
1259
|
+
if (kind === 'table') {
|
|
1260
|
+
const segs2 = id.split('.');
|
|
1261
|
+
const colType = segs2.length === 2
|
|
1262
|
+
? tableColumnTypes.get(segs2[0])?.get(segs2[1])
|
|
1263
|
+
: undefined;
|
|
1264
|
+
return colType === 'string' ? 'TEXT' : undefined;
|
|
1265
|
+
}
|
|
1266
|
+
return undefined;
|
|
1267
|
+
}
|
|
1268
|
+
if (source === 'self' || source.startsWith('self.')) {
|
|
1269
|
+
return undefined;
|
|
1270
|
+
}
|
|
1271
|
+
return symbolKinds.get(source) ?? attributeKinds.get(source) ?? undefined;
|
|
1272
|
+
};
|
|
1273
|
+
const kindConflicts = new Set();
|
|
1274
|
+
const kindMixErr = (node, l, r) => {
|
|
1275
|
+
const key = (0, expressions_1.renderExpression)(node);
|
|
1276
|
+
if (kindConflicts.has(key)) {
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
kindConflicts.add(key);
|
|
1280
|
+
err('C177', `${ownerKind} ${ownerId}: ${kindLabel(l)} is set against ${kindLabel(r)} in "${(0, expressions_1.renderExpression)(node)}" — the operands must share the quantity kind (kind-coherence)`);
|
|
1281
|
+
};
|
|
1282
|
+
const kindOf = (node) => {
|
|
1283
|
+
switch (node.type) {
|
|
1284
|
+
case 'num':
|
|
1285
|
+
return null;
|
|
1286
|
+
case 'str':
|
|
1287
|
+
return null;
|
|
1288
|
+
case 'bool':
|
|
1289
|
+
return null;
|
|
1290
|
+
case 'ifelse': {
|
|
1291
|
+
kindOf(node.cond);
|
|
1292
|
+
const l = kindOf(node.then);
|
|
1293
|
+
const r = kindOf(node.else);
|
|
1294
|
+
if (l !== undefined &&
|
|
1295
|
+
r !== undefined &&
|
|
1296
|
+
l !== null &&
|
|
1297
|
+
r !== null &&
|
|
1298
|
+
l !== r) {
|
|
1299
|
+
kindMixErr(node, l, r);
|
|
1300
|
+
}
|
|
1301
|
+
return l !== undefined && l !== null ? l : r;
|
|
1302
|
+
}
|
|
1303
|
+
case 'name': {
|
|
1304
|
+
const root = node.path[0];
|
|
1305
|
+
if (node.path.length > 1 &&
|
|
1306
|
+
(root === 'self' || CONTEXT_ROOTS.has(root))) {
|
|
1307
|
+
return undefined;
|
|
1308
|
+
}
|
|
1309
|
+
const arg = args.find(a => a.name === root);
|
|
1310
|
+
return arg ? argKindOf(arg.source) : undefined;
|
|
1311
|
+
}
|
|
1312
|
+
case 'call': {
|
|
1313
|
+
if (node.callee.includes('.')) {
|
|
1314
|
+
return undefined;
|
|
1315
|
+
}
|
|
1316
|
+
const kinded = [
|
|
1317
|
+
'abs',
|
|
1318
|
+
'max',
|
|
1319
|
+
'min',
|
|
1320
|
+
'floor',
|
|
1321
|
+
'ceil',
|
|
1322
|
+
'round',
|
|
1323
|
+
'sqrt',
|
|
1324
|
+
'exp',
|
|
1325
|
+
'ln',
|
|
1326
|
+
'log',
|
|
1327
|
+
'mod',
|
|
1328
|
+
];
|
|
1329
|
+
if (!kinded.includes(node.callee)) {
|
|
1330
|
+
return null;
|
|
1331
|
+
}
|
|
1332
|
+
let acc = undefined;
|
|
1333
|
+
for (const a of node.args) {
|
|
1334
|
+
const k = kindOf(a);
|
|
1335
|
+
if (k === undefined) {
|
|
1336
|
+
continue;
|
|
1337
|
+
}
|
|
1338
|
+
if (acc === undefined || acc === null) {
|
|
1339
|
+
acc = k;
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
1342
|
+
if (k !== undefined && k !== null && k !== acc) {
|
|
1343
|
+
kindMixErr(node, acc, k);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
return acc;
|
|
1347
|
+
}
|
|
1348
|
+
case 'unop':
|
|
1349
|
+
return node.op === 'not' ? null : kindOf(node.operand);
|
|
1350
|
+
case 'nav':
|
|
1351
|
+
// the iteration predicate's truth over the collection: the
|
|
1352
|
+
// boolean iterators; select keeps the source's kind — its
|
|
1353
|
+
// element kind is the collection's, unknown here; collect
|
|
1354
|
+
// projects to a collection of the body's element kind
|
|
1355
|
+
return node.op === 'select'
|
|
1356
|
+
? undefined
|
|
1357
|
+
: node.op === 'collect'
|
|
1358
|
+
? kindOf(node.body)
|
|
1359
|
+
: null;
|
|
1360
|
+
case 'binop': {
|
|
1361
|
+
if (['<=', '>=', '<', '>', '=', '!=', '≠'].includes(node.op)) {
|
|
1362
|
+
const l = kindOf(node.left);
|
|
1363
|
+
const r = kindOf(node.right);
|
|
1364
|
+
if (l !== undefined &&
|
|
1365
|
+
r !== undefined &&
|
|
1366
|
+
l !== null &&
|
|
1367
|
+
r !== null &&
|
|
1368
|
+
l !== r) {
|
|
1369
|
+
kindMixErr(node, l, r);
|
|
1370
|
+
}
|
|
1371
|
+
return null;
|
|
1372
|
+
}
|
|
1373
|
+
if (['and', 'or', 'xor', 'implies'].includes(node.op)) {
|
|
1374
|
+
kindOf(node.left);
|
|
1375
|
+
kindOf(node.right);
|
|
1376
|
+
return null;
|
|
1377
|
+
}
|
|
1378
|
+
const l = kindOf(node.left);
|
|
1379
|
+
const r = kindOf(node.right);
|
|
1380
|
+
if (l !== undefined &&
|
|
1381
|
+
r !== undefined &&
|
|
1382
|
+
l !== null &&
|
|
1383
|
+
r !== null &&
|
|
1384
|
+
l !== r) {
|
|
1385
|
+
kindMixErr(node, l, r);
|
|
1386
|
+
}
|
|
1387
|
+
return l !== undefined && l !== null ? l : r;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1391
|
+
kindOf(ast);
|
|
1392
|
+
};
|
|
1393
|
+
for (const r of standard.requirements ?? []) {
|
|
1394
|
+
if (r.limit?.argsBound) {
|
|
1395
|
+
checkInlineLimit('requirement', r.id, r.limit, null);
|
|
1396
|
+
continue;
|
|
1397
|
+
}
|
|
1398
|
+
const expr = r.limit?.expression;
|
|
1399
|
+
if (!expr) {
|
|
1400
|
+
continue;
|
|
1401
|
+
}
|
|
1402
|
+
const ocl = expr.replace(/^ocl\{/, '').replace(/\}$/, '');
|
|
1403
|
+
const uses = r.limit?.uses ?? [];
|
|
1404
|
+
for (const id of unresolvedIn(ocl, [], uses)) {
|
|
1405
|
+
err('C171', `requirement ${r.id}: the limit expression names "${id}", which no uses entry, symbol, datum, or verdict declares — air inside the formula (expression-identifiers-resolve)`);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
// The inline carriers (derive / derivation / check blocks): the
|
|
1409
|
+
// AST-exact resolution — the free names against the carrier's declared
|
|
1410
|
+
// scope plus the package's declared registries; the calls against the
|
|
1411
|
+
// function vocabulary and the declared callables.
|
|
1412
|
+
const resolveInlineCarrier = (ownerKind, ownerId, what, src, scope) => {
|
|
1413
|
+
let ast;
|
|
1414
|
+
try {
|
|
1415
|
+
ast = (0, expressions_1.parseExpression)(src);
|
|
1416
|
+
}
|
|
1417
|
+
catch (e) {
|
|
1418
|
+
err('C171', `${ownerKind} ${ownerId}: the ${what} does not parse — ${String(e.message ?? e)} (expression-identifiers-resolve)`);
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
const ids = (0, expressions_1.freeIdentifiers)(ast);
|
|
1422
|
+
for (const call of new Set(ids.calls)) {
|
|
1423
|
+
if (INLINE_FUNCTION_WORDS.has(call)) {
|
|
1424
|
+
continue;
|
|
1425
|
+
}
|
|
1426
|
+
if (call.includes('.')) {
|
|
1427
|
+
const root = call.split('.')[0];
|
|
1428
|
+
if (scope.has(root) ||
|
|
1429
|
+
globalExprNames.has(root) ||
|
|
1430
|
+
root === 'self' ||
|
|
1431
|
+
CONTEXT_ROOTS.has(root)) {
|
|
1432
|
+
continue;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
if (scope.has(call) || globalExprNames.has(call)) {
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1438
|
+
err('C171', `${ownerKind} ${ownerId}: the ${what} calls "${call}(…)", which nothing declares and which is not in the expression function vocabulary — air inside the formula (expression-identifiers-resolve)`);
|
|
1439
|
+
}
|
|
1440
|
+
for (const id of new Set(ids.names)) {
|
|
1441
|
+
const root = id.split('.')[0];
|
|
1442
|
+
if (id.includes('.') &&
|
|
1443
|
+
(scope.has(root) || root === 'self' || CONTEXT_ROOTS.has(root))) {
|
|
1444
|
+
continue;
|
|
1445
|
+
}
|
|
1446
|
+
if (scope.has(id)) {
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
if (globalExprNames.has(id) ||
|
|
1450
|
+
globalExprNames.has(id.split('.').pop() ?? id)) {
|
|
1451
|
+
continue;
|
|
1452
|
+
}
|
|
1453
|
+
err('C171', `${ownerKind} ${ownerId}: the ${what} names "${id}", which nothing in scope declares — air inside the formula (expression-identifiers-resolve)`);
|
|
1454
|
+
}
|
|
1455
|
+
};
|
|
1456
|
+
for (const v of standard.verdicts ?? []) {
|
|
1457
|
+
const expr = v.derive;
|
|
1458
|
+
if (!expr) {
|
|
1459
|
+
continue;
|
|
1460
|
+
}
|
|
1461
|
+
const inputs = v.inputs ?? [];
|
|
1462
|
+
if (v.deriveBound) {
|
|
1463
|
+
resolveInlineCarrier('verdict', v.id, 'derive', expr, new Set(inputs));
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
// the legacy leg: the ocl{…} wrapper strips before the regex walk
|
|
1467
|
+
const ocl = expr.replace(/^ocl\{/, '').replace(/\}$/, '');
|
|
1468
|
+
for (const id of unresolvedIn(ocl, inputs, [])) {
|
|
1469
|
+
err('C171', `verdict ${v.id}: the derive expression names "${id}", which no input declares — air inside the formula (expression-identifiers-resolve)`);
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
for (const t of standard.conformanceTests ?? []) {
|
|
1473
|
+
const scope = (t.variables ?? []).map(v => v.name);
|
|
1474
|
+
for (const v of t.variables ?? []) {
|
|
1475
|
+
if (!v.derivation) {
|
|
1476
|
+
continue;
|
|
1477
|
+
}
|
|
1478
|
+
if (v.derivationBound) {
|
|
1479
|
+
resolveInlineCarrier('conformance test', t.id, `variable ${v.name}'s derivation`, v.derivation, new Set(scope));
|
|
1480
|
+
continue;
|
|
1481
|
+
}
|
|
1482
|
+
const ocl = v.derivation.replace(/^ocl\{/, '').replace(/\}$/, '');
|
|
1483
|
+
for (const id of unresolvedIn(ocl, scope, [])) {
|
|
1484
|
+
err('C171', `conformance test ${t.id}: variable ${v.name}'s derivation names "${id}", which no sibling variable, symbol, datum, or verdict declares — air inside the formula (expression-identifiers-resolve)`);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
for (const c of standard.constraints ?? []) {
|
|
1489
|
+
if (!c.checkBound || !c.check) {
|
|
1490
|
+
continue;
|
|
1491
|
+
}
|
|
1492
|
+
const cArgs = c.args ?? [];
|
|
1493
|
+
const cScope = new Set(cArgs.map(a => a.name));
|
|
1494
|
+
for (const a of cArgs) {
|
|
1495
|
+
if (a.source.includes(':')) {
|
|
1496
|
+
const segs = a.source.split(':');
|
|
1497
|
+
if (segs.length === 2 && segs[0] !== 'observable') {
|
|
1498
|
+
const [kind, id] = segs;
|
|
1499
|
+
if (!DECLARATION_KINDS.has(kind)) {
|
|
1500
|
+
err('C171', `constraint ${c.id}: argument "${a.name}" binds to "${a.source}" — "${kind}" is not a declaration kind (expression-identifiers-resolve)`);
|
|
1501
|
+
}
|
|
1502
|
+
else {
|
|
1503
|
+
const idRoot = id.split('.')[0];
|
|
1504
|
+
const idTail = id.split('.').pop() ?? id;
|
|
1505
|
+
if (!globalExprNames.has(id) &&
|
|
1506
|
+
!globalExprNames.has(idRoot) &&
|
|
1507
|
+
!globalExprNames.has(idTail)) {
|
|
1508
|
+
err('C171', `constraint ${c.id}: argument "${a.name}" binds to "${a.source}", which no declared ${kind} names — air inside the formula (expression-identifiers-resolve)`);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
continue;
|
|
1513
|
+
}
|
|
1514
|
+
if (a.source === 'self' || a.source.startsWith('self.')) {
|
|
1515
|
+
continue;
|
|
1516
|
+
}
|
|
1517
|
+
const bare = a.source.split(':').pop() ?? a.source;
|
|
1518
|
+
if (!globalExprNames.has(a.source) && !globalExprNames.has(bare)) {
|
|
1519
|
+
err('C171', `constraint ${c.id}: argument "${a.name}" binds to "${a.source}", which nothing declares — air inside the formula (expression-identifiers-resolve)`);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
resolveInlineCarrier('constraint', c.id, 'check', c.check, cScope);
|
|
1523
|
+
// C84's inline restatement: the check is one BOOLEAN expression
|
|
1524
|
+
try {
|
|
1525
|
+
const ast = (0, expressions_1.parseExpression)(c.check);
|
|
1526
|
+
const booleanTop = (ast.type === 'binop' &&
|
|
1527
|
+
[
|
|
1528
|
+
'<=',
|
|
1529
|
+
'>=',
|
|
1530
|
+
'<',
|
|
1531
|
+
'>',
|
|
1532
|
+
'=',
|
|
1533
|
+
'!=',
|
|
1534
|
+
'≠',
|
|
1535
|
+
'and',
|
|
1536
|
+
'or',
|
|
1537
|
+
'xor',
|
|
1538
|
+
'implies',
|
|
1539
|
+
].includes(ast.op)) ||
|
|
1540
|
+
(ast.type === 'nav' && ['forAll', 'exists'].includes(ast.op));
|
|
1541
|
+
if (!booleanTop) {
|
|
1542
|
+
err('C84', `constraint ${c.id}: the check does not evaluate to a boolean — an invariant is one comparison or logical composition (constraint-shape)`);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
catch {
|
|
1546
|
+
// already reported by resolveInlineCarrier
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
// ── C172 requirement-algebra-resolves ───────────────────────────────
|
|
1550
|
+
// Containment is conjunction: a requirement class's members are its
|
|
1551
|
+
// body, and the class's limit expression (the LAW) names its members —
|
|
1552
|
+
// and/or/xor/implies, the OCL booleans. The law's identifiers resolve
|
|
1553
|
+
// to members only; every member appears in the law or the class
|
|
1554
|
+
// carries no law (pure conjunction); a child's modality is never
|
|
1555
|
+
// weaker than the class's.
|
|
1556
|
+
const memberSet = new Map();
|
|
1557
|
+
for (const rc of standard.requirementClasses ?? []) {
|
|
1558
|
+
memberSet.set(rc.id, new Set((rc.members ?? []).map(String)));
|
|
1559
|
+
}
|
|
1560
|
+
for (const rc of standard.requirementClasses ?? []) {
|
|
1561
|
+
const law = rc.limit?.expression;
|
|
1562
|
+
const members = memberSet.get(rc.id) ?? new Set();
|
|
1563
|
+
if (!law) {
|
|
1564
|
+
continue;
|
|
1565
|
+
}
|
|
1566
|
+
if (rc.limit?.argsBound) {
|
|
1567
|
+
checkInlineLimit('requirement class', rc.id, rc.limit, members);
|
|
1568
|
+
const covered = new Set();
|
|
1569
|
+
try {
|
|
1570
|
+
for (const n of (0, expressions_1.freeIdentifiers)((0, expressions_1.parseExpression)(law)).names) {
|
|
1571
|
+
covered.add(n);
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
catch {
|
|
1575
|
+
// already reported inside checkInlineLimit
|
|
1576
|
+
}
|
|
1577
|
+
for (const a of rc.limit.args ?? []) {
|
|
1578
|
+
covered.add(a.name);
|
|
1579
|
+
covered.add(a.source);
|
|
1580
|
+
}
|
|
1581
|
+
for (const m of members) {
|
|
1582
|
+
if (!covered.has(m)) {
|
|
1583
|
+
warn('C172', `requirement class ${rc.id}: member ${m} does not appear in the law — either it is always-required (add it with \`and\`) or it is orphaned (requirement-algebra-resolves)`);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
continue;
|
|
1587
|
+
}
|
|
1588
|
+
const ocl = law.replace(/^ocl\{/, '').replace(/\}$/, '');
|
|
1589
|
+
const ids = (ocl.match(/[A-Za-z_][A-Za-z0-9_]*/g) ?? []).filter(x => !OCL_WORDS.has(x));
|
|
1590
|
+
const lawIds = new Set();
|
|
1591
|
+
for (const id of ids) {
|
|
1592
|
+
lawIds.add(id);
|
|
1593
|
+
if (!members.has(id)) {
|
|
1594
|
+
err('C172', `requirement class ${rc.id}: the law names "${id}", which is not a contained member — the algebra composes the containment only (requirement-algebra-resolves)`);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
for (const m of members) {
|
|
1598
|
+
if (!lawIds.has(m)) {
|
|
1599
|
+
warn('C172', `requirement class ${rc.id}: member ${m} does not appear in the law — either it is always-required (add it with \`and\`) or it is orphaned (requirement-algebra-resolves)`);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
// ── C176 language-declared-wellformed ───────────────────────────────
|
|
1604
|
+
// A declared language generation must be well-formed (v<digit>[.digit])
|
|
1605
|
+
// — the strict-parse contract's key. The unknown-keyword errors come
|
|
1606
|
+
// from the parse itself (the strict mode); this rule only checks the
|
|
1607
|
+
// declaration's shape.
|
|
1608
|
+
// ── C178 legacy-carrier-deprecated ──────────────────────────────────
|
|
1609
|
+
// The deprecation mechanism (register W6): the legacy quoted
|
|
1610
|
+
// expression carriers (`expression "ocl{…}"`, the quoted derive /
|
|
1611
|
+
// derivation / check) warn once a package declares the generation
|
|
1612
|
+
// that deprecates them. The generation constant is INERT until the
|
|
1613
|
+
// owner's release bump — no package declares it today, so nothing
|
|
1614
|
+
// warns; flipping the constant (or packages declaring the newer
|
|
1615
|
+
// generation) activates the ratchet. The inline blocks are the only
|
|
1616
|
+
// forward form.
|
|
1617
|
+
const DEPRECATION_GENERATION = 'v3.4';
|
|
1618
|
+
const generationRank = (g) => {
|
|
1619
|
+
const m = g.match(/^v(\d+)(?:\.(\d+))?$/);
|
|
1620
|
+
if (!m) {
|
|
1621
|
+
return -1;
|
|
1622
|
+
}
|
|
1623
|
+
return Number(m[1]) * 1000 + Number(m[2] ?? '0');
|
|
1624
|
+
};
|
|
1625
|
+
try {
|
|
1626
|
+
const mf = (0, package_1.readPackageManifest)(dir);
|
|
1627
|
+
if (mf.language !== undefined) {
|
|
1628
|
+
if (!/^v\d+(\.\d+)?$/.test(mf.language)) {
|
|
1629
|
+
err('C176', `the declared language "${mf.language}" is not a generation (v<major>[.<minor>]) (language-declared-wellformed)`);
|
|
1630
|
+
}
|
|
1631
|
+
else if (generationRank(mf.language) >= generationRank(DEPRECATION_GENERATION)) {
|
|
1632
|
+
let legacy = 0;
|
|
1633
|
+
for (const r of standard.requirements ?? []) {
|
|
1634
|
+
if (r.limit && !r.limit.argsBound && r.limit.expression) {
|
|
1635
|
+
legacy += 1;
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
for (const v of standard.verdicts ?? []) {
|
|
1639
|
+
if (v.derive &&
|
|
1640
|
+
!v.deriveBound) {
|
|
1641
|
+
legacy += 1;
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
for (const t of standard.conformanceTests ?? []) {
|
|
1645
|
+
for (const v of t.variables ?? []) {
|
|
1646
|
+
if (v.derivation && !v.derivationBound) {
|
|
1647
|
+
legacy += 1;
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
for (const p of t.preconditions ?? []) {
|
|
1651
|
+
if (p.check && !p.checkBound) {
|
|
1652
|
+
legacy += 1;
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
for (const c of standard.constraints ?? []) {
|
|
1657
|
+
if (c.check && !c.checkBound) {
|
|
1658
|
+
legacy += 1;
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
if (legacy > 0) {
|
|
1662
|
+
warn('C178', `${legacy} quoted expression carrier${legacy === 1 ? '' : 's'} — the ocl{…} string form is deprecated as of ${DEPRECATION_GENERATION}; migrate to the inline blocks (expression { … }, derive { … }, derivation { … }, check { … }) (legacy-carrier-deprecated)`);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
catch {
|
|
1668
|
+
// the manifest unreadable — the load errors own that
|
|
1669
|
+
}
|
|
1670
|
+
// ── C179 uncertainty-budget-resolves ────────────────────────────────
|
|
1671
|
+
// The budget is a declaration, never arithmetic: its target is a
|
|
1672
|
+
// declared verdict (the measured quantity it backs), a Type A
|
|
1673
|
+
// component names a declared test variable that holds a captured
|
|
1674
|
+
// series, a Type B component states its distribution and half-width,
|
|
1675
|
+
// and the coverage factor is a positive number. The propagation law
|
|
1676
|
+
// is the GUM's — the evaluation plane's, never restated here.
|
|
1677
|
+
{
|
|
1678
|
+
const seriesVariables = new Map();
|
|
1679
|
+
for (const t of standard.conformanceTests ?? []) {
|
|
1680
|
+
for (const v of t.variables ?? []) {
|
|
1681
|
+
seriesVariables.set(v.name, v.series !== null);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
for (const b of standard.uncertaintyBudgets ?? []) {
|
|
1685
|
+
if (!b.target) {
|
|
1686
|
+
err('C179', `uncertainty_budget ${b.id}: declares no target — the budget backs a declared verdict's measured quantity (uncertainty-budget-resolves)`);
|
|
1687
|
+
}
|
|
1688
|
+
else if (!(standard.verdicts ?? []).some(v => v.id === b.target)) {
|
|
1689
|
+
err('C179', `uncertainty_budget ${b.id}: target "${b.target}" is not a declared verdict — the budget backs a measured quantity the verdicts own (uncertainty-budget-resolves)`);
|
|
1690
|
+
}
|
|
1691
|
+
if (b.components.length === 0) {
|
|
1692
|
+
warn('C179', `uncertainty_budget ${b.id}: declares no components — an empty budget states no uncertainty (uncertainty-budget-resolves)`);
|
|
1693
|
+
}
|
|
1694
|
+
if (b.coverageK && !(Number(b.coverageK) > 0)) {
|
|
1695
|
+
err('C179', `uncertainty_budget ${b.id}: coverage k "${b.coverageK}" is not a positive number (uncertainty-budget-resolves)`);
|
|
1696
|
+
}
|
|
1697
|
+
for (const c of b.components) {
|
|
1698
|
+
if (c.type === 'A') {
|
|
1699
|
+
if (!c.series) {
|
|
1700
|
+
err('C179', `uncertainty_budget ${b.id}: component ${c.id} is Type A but names no series — the statistics compute from a declared captured series (uncertainty-budget-resolves)`);
|
|
1701
|
+
}
|
|
1702
|
+
else if (!seriesVariables.has(c.series)) {
|
|
1703
|
+
err('C179', `uncertainty_budget ${b.id}: component ${c.id}'s series "${c.series}" is not a declared test variable (uncertainty-budget-resolves)`);
|
|
1704
|
+
}
|
|
1705
|
+
else if (!seriesVariables.get(c.series)) {
|
|
1706
|
+
err('C179', `uncertainty_budget ${b.id}: component ${c.id}'s series "${c.series}" declares no series shape — a Type A evaluation needs the captured series' axes and cell (uncertainty-budget-resolves)`);
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
else {
|
|
1710
|
+
if (!c.distribution) {
|
|
1711
|
+
err('C179', `uncertainty_budget ${b.id}: component ${c.id} is Type B but states no distribution (uncertainty-budget-resolves)`);
|
|
1712
|
+
}
|
|
1713
|
+
if (!c.halfWidth) {
|
|
1714
|
+
err('C179', `uncertainty_budget ${b.id}: component ${c.id} is Type B but states no half_width — the distribution spans a half-width (uncertainty-budget-resolves)`);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
// ── C173 step-plane-refs-resolve (register 19) ──────────────────────
|
|
1721
|
+
// A signature step's completion is one expression over the step's own
|
|
1722
|
+
// declared ios: the identifiers resolve to `inputs`/`outputs`, the
|
|
1723
|
+
// calls stay in the closed vocabulary, and nothing in the completion
|
|
1724
|
+
// is air. The calls bindings ride C76's machinery (the typed pass-1
|
|
1725
|
+
// fills the same fields the flow dialect uses).
|
|
1726
|
+
for (const p of standard.processes ?? []) {
|
|
1727
|
+
for (const st of p.steps ?? []) {
|
|
1728
|
+
if (!st.completion) {
|
|
1729
|
+
continue;
|
|
1730
|
+
}
|
|
1731
|
+
const ios = new Set([...st.sigInputs, ...st.sigOutputs]);
|
|
1732
|
+
let ids;
|
|
1733
|
+
try {
|
|
1734
|
+
ids = (0, expressions_1.freeIdentifiers)((0, expressions_1.parseExpression)(st.completion.replace(/^ocl\{/, '').replace(/\}$/, '')));
|
|
1735
|
+
}
|
|
1736
|
+
catch (e) {
|
|
1737
|
+
err('C173', `process ${p.id}: step "${st.id}" completion does not parse — ${String(e.message ?? e)} — the completion is one expression over the step's ios (step-plane-refs-resolve)`);
|
|
1738
|
+
continue;
|
|
1739
|
+
}
|
|
1740
|
+
for (const call of new Set(ids.calls)) {
|
|
1741
|
+
if (!INLINE_FUNCTION_WORDS.has(call)) {
|
|
1742
|
+
err('C173', `process ${p.id}: step "${st.id}" completion calls "${call}(…)", which is not in the expression function vocabulary (step-plane-refs-resolve)`);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
for (const id of new Set(ids.names)) {
|
|
1746
|
+
const root = id.split('.')[0];
|
|
1747
|
+
if (id.includes('.') && (root === 'self' || CONTEXT_ROOTS.has(root))) {
|
|
1748
|
+
continue;
|
|
1749
|
+
}
|
|
1750
|
+
if (ios.has(id) || ios.has(root)) {
|
|
1751
|
+
continue;
|
|
1752
|
+
}
|
|
1753
|
+
err('C173', `process ${p.id}: step "${st.id}" completion names "${id}", which is not a declared io of the step — the completion reads the step's own inputs and outputs (step-plane-refs-resolve)`);
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
// ── C170 edition-lineage-declared ───────────────────────────────────
|
|
1758
|
+
// A package declaring more than one edition carries a declarative
|
|
1759
|
+
// edition mapping (edition-mapping.json, primmel-edition-mapping/1)
|
|
1760
|
+
// naming both edition states — the cross-edition linkage is an
|
|
1761
|
+
// artifact, never implicit. Born-at-edition packages map the prior
|
|
1762
|
+
// edition with model_state: null and the reason.
|
|
1763
|
+
try {
|
|
1764
|
+
const mf = (0, package_1.readPackageManifest)(dir);
|
|
1765
|
+
const eds = (mf.editions ?? []).map(String);
|
|
1766
|
+
if (eds.length > 1) {
|
|
1767
|
+
const map = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(dir, 'edition-mapping.json'), 'utf8'));
|
|
1768
|
+
if (map.kind !== 'primmel-edition-mapping/1') {
|
|
1769
|
+
err('C170', `edition-mapping.json: kind "${String(map.kind)}" is not primmel-edition-mapping/1 (edition-lineage-declared)`);
|
|
1770
|
+
}
|
|
1771
|
+
else {
|
|
1772
|
+
const fromEd = map.from?.edition === undefined
|
|
1773
|
+
? undefined
|
|
1774
|
+
: String(map.from.edition);
|
|
1775
|
+
const toEd = map.to?.edition === undefined ? undefined : String(map.to.edition);
|
|
1776
|
+
for (const [side, ed] of [
|
|
1777
|
+
['from', fromEd],
|
|
1778
|
+
['to', toEd],
|
|
1779
|
+
]) {
|
|
1780
|
+
if (!ed || !eds.includes(ed)) {
|
|
1781
|
+
err('C170', `edition-mapping.json: the ${side} edition ${String(ed)} is not among the manifest's declared editions [${eds.join(', ')}] (edition-lineage-declared)`);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
catch (e) {
|
|
1788
|
+
const code = e.code;
|
|
1789
|
+
if (code === 'ENOENT') {
|
|
1790
|
+
const eds = (() => {
|
|
1791
|
+
try {
|
|
1792
|
+
return ((0, package_1.readPackageManifest)(dir).editions ?? []).map(String);
|
|
1793
|
+
}
|
|
1794
|
+
catch {
|
|
1795
|
+
return [];
|
|
1796
|
+
}
|
|
1797
|
+
})();
|
|
1798
|
+
if (eds.length > 1) {
|
|
1799
|
+
err('C170', `the package declares ${eds.length} editions [${eds.join(', ')}] but ships no edition-mapping.json — the cross-edition linkage is an artifact, never implicit (edition-lineage-declared)`);
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
else if (e instanceof SyntaxError) {
|
|
1803
|
+
err('C170', `edition-mapping.json is not valid JSON: ${e.message} (edition-lineage-declared)`);
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
// ── C168 method-ref-resolves ────────────────────────────────────────
|
|
1807
|
+
// A conformance test's method_ref names THE PROCESS the test realizes:
|
|
1808
|
+
// a bare id resolved in this package, or a scoped <package>:<id>
|
|
1809
|
+
// reference resolved read-only through the locator (the target's own
|
|
1810
|
+
// content validates the id; nothing merges — the C31 doctrine). A
|
|
1811
|
+
// bibliographic standard id is not a resolution target: the method is
|
|
1812
|
+
// a process to be realized, not a document to cite. Locator-gated
|
|
1813
|
+
// like C27-C31 — without a locator the scoped leg stays silent.
|
|
1814
|
+
const methodTargetIds = (s) => new Set([
|
|
1815
|
+
...(s.processes ?? []).map(p => p.id),
|
|
1816
|
+
...(s.conformanceTests ?? []).map(x => x.id),
|
|
1817
|
+
]);
|
|
1818
|
+
const localMethodIds = methodTargetIds(standard);
|
|
1819
|
+
const scopedMethodIds = new Map();
|
|
1820
|
+
const resolveCitation = (elemId, ref) => {
|
|
1821
|
+
const colon = ref.indexOf(':');
|
|
1822
|
+
if (colon <= 0) {
|
|
1823
|
+
if (!localMethodIds.has(ref)) {
|
|
1824
|
+
err('C168', `${elemId}: the reference "${ref}" names no process or conformance test declared in this package (method-ref-resolves)`);
|
|
1825
|
+
}
|
|
1826
|
+
return;
|
|
1827
|
+
}
|
|
1828
|
+
if (!options.resolvePackage) {
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1831
|
+
const pkgId = ref.slice(0, colon);
|
|
1832
|
+
const localId = ref.slice(colon + 1);
|
|
1833
|
+
let ids = scopedMethodIds.get(pkgId);
|
|
1834
|
+
if (!ids) {
|
|
1835
|
+
ids = new Set();
|
|
1836
|
+
try {
|
|
1837
|
+
const depDir = options.resolvePackage(pkgId);
|
|
1838
|
+
if (depDir) {
|
|
1839
|
+
const dep = (0, package_1.loadPackageWithIssues)(depDir);
|
|
1840
|
+
ids = methodTargetIds(dep.standard);
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
catch {
|
|
1844
|
+
// Unreadable dependency — C27 reports the resolution failure;
|
|
1845
|
+
// this rule stays silent rather than double-reporting.
|
|
1846
|
+
}
|
|
1847
|
+
scopedMethodIds.set(pkgId, ids);
|
|
1848
|
+
}
|
|
1849
|
+
if (!ids.has(localId)) {
|
|
1850
|
+
err('C168', `${elemId}: the reference "${ref}" names no process or conformance test declared in package "${pkgId}" (method-ref-resolves)`);
|
|
1851
|
+
}
|
|
1852
|
+
};
|
|
1853
|
+
for (const t of standard.conformanceTests ?? []) {
|
|
1854
|
+
const ref = t.methodRef;
|
|
1855
|
+
if (ref) {
|
|
1856
|
+
resolveCitation(t.id, ref);
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
// The process bodies' call edges ride the same resolution: a step's
|
|
1860
|
+
// `calls <p>` (typed or raw-plane) names a process to be realized.
|
|
1861
|
+
for (const pr of standard.processes ?? []) {
|
|
1862
|
+
const raw = pr;
|
|
1863
|
+
for (const st of raw.steps ?? []) {
|
|
1864
|
+
const call = st.calls || st.signatureBlock?.match(/\bcalls +([\w:/.-]+)/)?.[1];
|
|
1865
|
+
if (call) {
|
|
1866
|
+
resolveCitation(`${pr.id}/step/${st.id ?? ''}`, call);
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
837
1870
|
// C6 — subject anatomy: every aspect declared under exactly one family
|
|
838
1871
|
// (anatomy-family). The parser records wrong-family and undeclared
|
|
839
1872
|
// aspect keys on Subject.misplacedAspects; value-level markers catch
|
|
@@ -1724,7 +2757,7 @@ function checkPackage(dir, options = {}) {
|
|
|
1724
2757
|
if (c.stereotype !== 'inv') {
|
|
1725
2758
|
err('C84', `constraint ${c.id}: stereotype "${c.stereotype || '(none)'}" — a constraint is always «inv», the subject's declaration-level invariant (constraint-shape)`);
|
|
1726
2759
|
}
|
|
1727
|
-
if (!OCL_CHECK.test(c.check)) {
|
|
2760
|
+
if (!c.checkBound && !OCL_CHECK.test(c.check)) {
|
|
1728
2761
|
err('C84', `constraint ${c.id}: check is not one OCL boolean expression ocl{…} over the subject's declared anatomy (constraint-shape)`);
|
|
1729
2762
|
}
|
|
1730
2763
|
if (!c.violationMeaning || c.violationMeaning.trim() === '') {
|
|
@@ -6571,6 +7604,124 @@ function checkVerdictChains(standard) {
|
|
|
6571
7604
|
}
|
|
6572
7605
|
return issues;
|
|
6573
7606
|
}
|
|
7607
|
+
/**
|
|
7608
|
+
* C158–C162 + C164 + C167 — the LML-aligned substrate (TODO.lang-gaps;
|
|
7609
|
+
* the spec's validation contract, language-v3 §16). The five constructs
|
|
7610
|
+
* (wireMappings / stringFormats / namespaces / datums / checks) carry
|
|
7611
|
+
* their declared data; the checker does the discipline:
|
|
7612
|
+
*
|
|
7613
|
+
* C158 prose-quarantine: free text stays in the description block — a
|
|
7614
|
+
* clause citation inside a datum's description is a reference in
|
|
7615
|
+
* prose, and the reference belongs in a urn (the deletion test's
|
|
7616
|
+
* kernel-side proxy: the citation patterns are exactly the prose a
|
|
7617
|
+
* re-locator would trip over). An unfinished-prose TODO.roadmap
|
|
7618
|
+
* marker warns.
|
|
7619
|
+
* C159 urn-only-refs: a wire mapping's namespace names a declared
|
|
7620
|
+
* namespace block (the serializer emits the prefix/uri pair).
|
|
7621
|
+
* C160 signature-closure: every name a check consumes (its inputs) is
|
|
7622
|
+
* a declared datum or symbol — a check over an undeclared input is
|
|
7623
|
+
* uncomputable.
|
|
7624
|
+
* C161 check-surface: a datum's render hints name their notation, and
|
|
7625
|
+
* the notation vocabulary is closed. (The parser silently drops an
|
|
7626
|
+
* unknown notation token, so parsed models never carry one — this
|
|
7627
|
+
* leg guards programmatically built Standards and pins the closed
|
|
7628
|
+
* vocabulary against renderer growth.)
|
|
7629
|
+
* C162 namespace-coherence: the metadata namespace string names a
|
|
7630
|
+
* declared namespace block when one is declared at all.
|
|
7631
|
+
* C164 wire-mapping-coverage: a mapping's format is in the wire
|
|
7632
|
+
* vocabulary and its class names a declared dataclass when
|
|
7633
|
+
* dataclasses are declared.
|
|
7634
|
+
* C167 qualified-unit-reference: a datum's unit and quantity kind use
|
|
7635
|
+
* the closed qualified `<namespace>:<id>` form (si:…, unitsdb:…) —
|
|
7636
|
+
* a bare unit token is exactly the ambiguity the registries exist
|
|
7637
|
+
* to close.
|
|
7638
|
+
*
|
|
7639
|
+
* C163 change-levels runs over `primmel diff --exit-code` (the existing
|
|
7640
|
+
* diff classifier — model-diff.ts); C165/C166 await the calculation
|
|
7641
|
+
* math plane. All three are catalog-only entries (check-rules.ts) with
|
|
7642
|
+
* no kernel leg here — deliberately, not by omission.
|
|
7643
|
+
*/
|
|
7644
|
+
function checkLmlAligned(standard) {
|
|
7645
|
+
const issues = [];
|
|
7646
|
+
const err = (check, message) => issues.push({ check, severity: 'error', message });
|
|
7647
|
+
const warn = (check, message) => issues.push({ check, severity: 'warning', message });
|
|
7648
|
+
const namespaceIds = new Set((standard.namespaces ?? []).map(n => n.id));
|
|
7649
|
+
// The mapping targets the LML class name as authored (`mapping xml
|
|
7650
|
+
// Ceramic`); a registerable class declares `Ceramic#data`, so the
|
|
7651
|
+
// target matches the id or the id with its `#data` register suffix
|
|
7652
|
+
// stripped (spec §Serialization's authored shape).
|
|
7653
|
+
const dataclassIds = new Set();
|
|
7654
|
+
for (const c of standard.dataclasses ?? []) {
|
|
7655
|
+
dataclassIds.add(c.id);
|
|
7656
|
+
dataclassIds.add(c.id.replace(/#data$/, ''));
|
|
7657
|
+
}
|
|
7658
|
+
const symbolIds = new Set((standard.symbols ?? []).map(s => s.id));
|
|
7659
|
+
const datumIds = new Set((standard.datums ?? []).map(d => d.id));
|
|
7660
|
+
// ── C158 prose-quarantine ──────────────────────────────────────────
|
|
7661
|
+
// A datum with a label and an empty description is the FINE form; the
|
|
7662
|
+
// quarantine breaks only when the description smuggles a citation.
|
|
7663
|
+
const CLAUSE_CITATION = /clause-\d|§\d/i;
|
|
7664
|
+
for (const d of standard.datums ?? []) {
|
|
7665
|
+
if (!d.description) {
|
|
7666
|
+
continue;
|
|
7667
|
+
}
|
|
7668
|
+
if (CLAUSE_CITATION.test(d.description)) {
|
|
7669
|
+
err('C158', `datum ${d.id}: the description cites a clause — a reference in prose belongs in a urn reference, not the quarantined free text (prose-quarantine)`);
|
|
7670
|
+
}
|
|
7671
|
+
else if (d.description.includes('TODO.roadmap')) {
|
|
7672
|
+
warn('C158', `datum ${d.id}: the description carries the unfinished-prose marker TODO.roadmap (prose-quarantine)`);
|
|
7673
|
+
}
|
|
7674
|
+
}
|
|
7675
|
+
// ── C159 urn-only-refs ─────────────────────────────────────────────
|
|
7676
|
+
for (const w of standard.wireMappings ?? []) {
|
|
7677
|
+
if (w.namespaceRef && !namespaceIds.has(w.namespaceRef)) {
|
|
7678
|
+
err('C159', `mapping ${w.id}: namespace "${w.namespaceRef}" names no declared namespace block (urn-only-refs)`);
|
|
7679
|
+
}
|
|
7680
|
+
}
|
|
7681
|
+
// ── C160 signature-closure ─────────────────────────────────────────
|
|
7682
|
+
for (const c of standard.checks ?? []) {
|
|
7683
|
+
for (const inp of c.inputs ?? []) {
|
|
7684
|
+
if (!(datumIds.has(inp) || symbolIds.has(inp))) {
|
|
7685
|
+
err('C160', `check ${c.id}: input "${inp}" resolves to no declared datum or symbol — the check consumes an undeclared name (signature-closure)`);
|
|
7686
|
+
}
|
|
7687
|
+
}
|
|
7688
|
+
}
|
|
7689
|
+
// ── C161 check-surface ─────────────────────────────────────────────
|
|
7690
|
+
for (const d of standard.datums ?? []) {
|
|
7691
|
+
for (const r of d.renders ?? []) {
|
|
7692
|
+
if (!lmlAligned_1.MATH_NOTATIONS.includes(r.notation)) {
|
|
7693
|
+
err('C161', `datum ${d.id}: render notation "${r.notation}" is outside the closed vocabulary (${lmlAligned_1.MATH_NOTATIONS.join('|')}) (check-surface)`);
|
|
7694
|
+
}
|
|
7695
|
+
}
|
|
7696
|
+
}
|
|
7697
|
+
// ── C162 namespace-coherence ───────────────────────────────────────
|
|
7698
|
+
const metaNamespace = standard.meta?.namespace ?? '';
|
|
7699
|
+
if (metaNamespace &&
|
|
7700
|
+
namespaceIds.size > 0 &&
|
|
7701
|
+
!namespaceIds.has(metaNamespace)) {
|
|
7702
|
+
err('C162', `metadata namespace "${metaNamespace}" names no declared namespace block (namespace-coherence)`);
|
|
7703
|
+
}
|
|
7704
|
+
// ── C164 wire-mapping-coverage ─────────────────────────────────────
|
|
7705
|
+
for (const w of standard.wireMappings ?? []) {
|
|
7706
|
+
if (!lmlAligned_1.MAPPING_FORMATS.includes(w.format)) {
|
|
7707
|
+
err('C164', `mapping ${w.id}: format "${w.format}" is outside the wire vocabulary (${lmlAligned_1.MAPPING_FORMATS.join('|')}) (wire-mapping-coverage)`);
|
|
7708
|
+
}
|
|
7709
|
+
if (dataclassIds.size > 0 && !dataclassIds.has(w.target)) {
|
|
7710
|
+
err('C164', `mapping ${w.id}: the class "${w.target}" is no declared dataclass — a class serialized to a format carries the declared mapping (wire-mapping-coverage)`);
|
|
7711
|
+
}
|
|
7712
|
+
}
|
|
7713
|
+
// ── C167 qualified-unit-reference ──────────────────────────────────
|
|
7714
|
+
const QUALIFIED_REF = /^[a-z]+:[A-Za-z0-9_-]+$/;
|
|
7715
|
+
for (const d of standard.datums ?? []) {
|
|
7716
|
+
if (d.unit && !QUALIFIED_REF.test(d.unit)) {
|
|
7717
|
+
err('C167', `datum ${d.id}: unit "${d.unit}" is bare — a unit reference uses the qualified <namespace>:<id> form (si:…, unitsdb:…) (qualified-unit-reference)`);
|
|
7718
|
+
}
|
|
7719
|
+
if (d.quantityKind && !QUALIFIED_REF.test(d.quantityKind)) {
|
|
7720
|
+
err('C167', `datum ${d.id}: quantity_kind "${d.quantityKind}" is bare — a quantity-kind reference uses the qualified <namespace>:<id> form (si:…, unitsdb:…) (qualified-unit-reference)`);
|
|
7721
|
+
}
|
|
7722
|
+
}
|
|
7723
|
+
return issues;
|
|
7724
|
+
}
|
|
6574
7725
|
/**
|
|
6575
7726
|
* C77–C80 + C85 + C113 — edition lifecycle (TODO.roadmap/28; doctrine ch. 13
|
|
6576
7727
|
* §13.4/§13.7) and the manifest base URN (TODO.roadmap/27). Editions are
|