@primmel/primmel 1.21.12 → 1.21.13

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.
Files changed (117) hide show
  1. package/dist/src/check-allowlist.d.ts +12 -0
  2. package/dist/src/check-allowlist.d.ts.map +1 -1
  3. package/dist/src/check-allowlist.js +35 -4
  4. package/dist/src/check-allowlist.js.map +1 -1
  5. package/dist/src/check-rules.d.ts.map +1 -1
  6. package/dist/src/check-rules.js +30 -0
  7. package/dist/src/check-rules.js.map +1 -1
  8. package/dist/src/check.d.ts +38 -0
  9. package/dist/src/check.d.ts.map +1 -1
  10. package/dist/src/check.js +1127 -4
  11. package/dist/src/check.js.map +1 -1
  12. package/dist/src/export/retrieval.d.ts +9 -1
  13. package/dist/src/export/retrieval.d.ts.map +1 -1
  14. package/dist/src/export/retrieval.js +29 -0
  15. package/dist/src/export/retrieval.js.map +1 -1
  16. package/dist/src/expressions/index.d.ts +69 -0
  17. package/dist/src/expressions/index.d.ts.map +1 -0
  18. package/dist/src/expressions/index.js +342 -0
  19. package/dist/src/expressions/index.js.map +1 -0
  20. package/dist/src/fmt.d.ts +11 -0
  21. package/dist/src/fmt.d.ts.map +1 -0
  22. package/dist/src/fmt.js +71 -0
  23. package/dist/src/fmt.js.map +1 -0
  24. package/dist/src/ser-des/config/calculation.d.ts.map +1 -1
  25. package/dist/src/ser-des/config/calculation.js +46 -0
  26. package/dist/src/ser-des/config/calculation.js.map +1 -1
  27. package/dist/src/ser-des/config/competenceKind.d.ts.map +1 -1
  28. package/dist/src/ser-des/config/competenceKind.js +9 -20
  29. package/dist/src/ser-des/config/competenceKind.js.map +1 -1
  30. package/dist/src/ser-des/config/conformanceTest.d.ts.map +1 -1
  31. package/dist/src/ser-des/config/conformanceTest.js +33 -6
  32. package/dist/src/ser-des/config/conformanceTest.js.map +1 -1
  33. package/dist/src/ser-des/config/constraint.d.ts.map +1 -1
  34. package/dist/src/ser-des/config/constraint.js +24 -2
  35. package/dist/src/ser-des/config/constraint.js.map +1 -1
  36. package/dist/src/ser-des/config/index.d.ts +7 -0
  37. package/dist/src/ser-des/config/index.d.ts.map +1 -1
  38. package/dist/src/ser-des/config/index.js +50 -0
  39. package/dist/src/ser-des/config/index.js.map +1 -1
  40. package/dist/src/ser-des/config/lmlAligned.d.ts +16 -0
  41. package/dist/src/ser-des/config/lmlAligned.d.ts.map +1 -0
  42. package/dist/src/ser-des/config/lmlAligned.js +337 -0
  43. package/dist/src/ser-des/config/lmlAligned.js.map +1 -0
  44. package/dist/src/ser-des/config/packageManifest.d.ts.map +1 -1
  45. package/dist/src/ser-des/config/packageManifest.js +3 -0
  46. package/dist/src/ser-des/config/packageManifest.js.map +1 -1
  47. package/dist/src/ser-des/config/process.d.ts.map +1 -1
  48. package/dist/src/ser-des/config/process.js +192 -0
  49. package/dist/src/ser-des/config/process.js.map +1 -1
  50. package/dist/src/ser-des/config/requirement.d.ts.map +1 -1
  51. package/dist/src/ser-des/config/requirement.js +123 -7
  52. package/dist/src/ser-des/config/requirement.js.map +1 -1
  53. package/dist/src/ser-des/config/symbol.d.ts.map +1 -1
  54. package/dist/src/ser-des/config/symbol.js +21 -0
  55. package/dist/src/ser-des/config/symbol.js.map +1 -1
  56. package/dist/src/ser-des/config/table.d.ts.map +1 -1
  57. package/dist/src/ser-des/config/table.js +7 -0
  58. package/dist/src/ser-des/config/table.js.map +1 -1
  59. package/dist/src/ser-des/config/uncertaintyBudget.d.ts +5 -0
  60. package/dist/src/ser-des/config/uncertaintyBudget.d.ts.map +1 -0
  61. package/dist/src/ser-des/config/uncertaintyBudget.js +211 -0
  62. package/dist/src/ser-des/config/uncertaintyBudget.js.map +1 -0
  63. package/dist/src/ser-des/config/verdict.d.ts.map +1 -1
  64. package/dist/src/ser-des/config/verdict.js +12 -2
  65. package/dist/src/ser-des/config/verdict.js.map +1 -1
  66. package/dist/src/ser-des/dump.d.ts.map +1 -1
  67. package/dist/src/ser-des/dump.js +46 -0
  68. package/dist/src/ser-des/dump.js.map +1 -1
  69. package/dist/src/ser-des/expression-carrier.d.ts +17 -0
  70. package/dist/src/ser-des/expression-carrier.d.ts.map +1 -0
  71. package/dist/src/ser-des/expression-carrier.js +72 -0
  72. package/dist/src/ser-des/expression-carrier.js.map +1 -0
  73. package/dist/src/ser-des/package.d.ts.map +1 -1
  74. package/dist/src/ser-des/package.js +54 -4
  75. package/dist/src/ser-des/package.js.map +1 -1
  76. package/dist/src/ser-des/parse.d.ts.map +1 -1
  77. package/dist/src/ser-des/parse.js +36 -2
  78. package/dist/src/ser-des/parse.js.map +1 -1
  79. package/dist/src/ser-des/shape-front-end.d.ts.map +1 -1
  80. package/dist/src/ser-des/shape-front-end.js +24 -5
  81. package/dist/src/ser-des/shape-front-end.js.map +1 -1
  82. package/dist/src/ser-des/types.d.ts +10 -0
  83. package/dist/src/ser-des/types.d.ts.map +1 -1
  84. package/dist/src/text-coverage.d.ts.map +1 -1
  85. package/dist/src/text-coverage.js +52 -0
  86. package/dist/src/text-coverage.js.map +1 -1
  87. package/dist/src/types/Calculation.d.ts +14 -0
  88. package/dist/src/types/Calculation.d.ts.map +1 -1
  89. package/dist/src/types/CompetenceKind.d.ts +6 -14
  90. package/dist/src/types/CompetenceKind.d.ts.map +1 -1
  91. package/dist/src/types/ConformanceTest.d.ts +5 -1
  92. package/dist/src/types/ConformanceTest.d.ts.map +1 -1
  93. package/dist/src/types/Constraint.d.ts +10 -1
  94. package/dist/src/types/Constraint.d.ts.map +1 -1
  95. package/dist/src/types/LmlAligned.d.ts +76 -0
  96. package/dist/src/types/LmlAligned.d.ts.map +1 -0
  97. package/dist/src/types/LmlAligned.js +8 -0
  98. package/dist/src/types/LmlAligned.js.map +1 -0
  99. package/dist/src/types/Package.d.ts +7 -0
  100. package/dist/src/types/Package.d.ts.map +1 -1
  101. package/dist/src/types/Requirement.d.ts +33 -0
  102. package/dist/src/types/Requirement.d.ts.map +1 -1
  103. package/dist/src/types/Standard.d.ts +12 -0
  104. package/dist/src/types/Standard.d.ts.map +1 -1
  105. package/dist/src/types/Symbol.d.ts +8 -0
  106. package/dist/src/types/Symbol.d.ts.map +1 -1
  107. package/dist/src/types/UncertaintyBudget.d.ts +68 -0
  108. package/dist/src/types/UncertaintyBudget.d.ts.map +1 -0
  109. package/dist/src/types/UncertaintyBudget.js +3 -0
  110. package/dist/src/types/UncertaintyBudget.js.map +1 -0
  111. package/dist/src/types/Verdict.d.ts +3 -1
  112. package/dist/src/types/Verdict.d.ts.map +1 -1
  113. package/dist/src/types/process.d.ts +52 -1
  114. package/dist/src/types/process.d.ts.map +1 -1
  115. package/dist-browser/index.mjs +2286 -1441
  116. package/dist-cli/primmel.mjs +2883 -76
  117. 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
- covered.add(target);
819
+ coverWithMembers(target);
802
820
  }
803
821
  }
804
822
  const provisioned = new Set();
@@ -834,6 +852,993 @@ 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 (call.includes('.')) {
1124
+ const root = call.split('.')[0];
1125
+ if (argNames.has(root) || root === 'self' || CONTEXT_ROOTS.has(root)) {
1126
+ // the method rides its declared object — the root is the check
1127
+ continue;
1128
+ }
1129
+ }
1130
+ if (argNames.has(call)) {
1131
+ // an arg-bound callable — the table lookup the args declare
1132
+ continue;
1133
+ }
1134
+ 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)`);
1135
+ }
1136
+ for (const id of new Set(ids.names)) {
1137
+ const root = id.split('.')[0];
1138
+ if (id.includes('.') &&
1139
+ (root === 'self' || argNames.has(root) || CONTEXT_ROOTS.has(root))) {
1140
+ continue;
1141
+ }
1142
+ if (argNames.has(id)) {
1143
+ continue;
1144
+ }
1145
+ if (memberScope !== null && memberScope.has(id)) {
1146
+ continue;
1147
+ }
1148
+ const lawScoped = memberScope !== null;
1149
+ const fix = lawScoped
1150
+ ? 'the law composes the containment only — its identifiers are the members'
1151
+ : 'add args { ' + id + ' <- <declaration> } or remove it';
1152
+ err(lawScoped ? 'C172' : 'C171', `${ownerKind} ${ownerId}: the limit expression names "${id}", which no argument binds — ${fix} (${lawScoped ? 'requirement-algebra-resolves' : 'expression-identifiers-resolve'})`);
1153
+ }
1154
+ for (const a of args) {
1155
+ if (a.source.includes(':')) {
1156
+ const segs = a.source.split(':');
1157
+ if (segs.some(s => s.length === 0)) {
1158
+ 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)`);
1159
+ continue;
1160
+ }
1161
+ if (segs.length === 2) {
1162
+ const [kind, id] = segs;
1163
+ if (kind === 'process') {
1164
+ // register 19: `process:<p>[.<step>[.<io>]]` — the dot
1165
+ // descends the declared structure, the leaf names a
1166
+ // declared io
1167
+ const segs2 = id.split('.');
1168
+ const proc = (standard.processes ?? []).find(pp => pp.id === segs2[0]);
1169
+ if (!proc) {
1170
+ err('C171', `${ownerKind} ${ownerId}: argument "${a.name}" binds to "${a.source}", which no declared process names — air inside the formula (expression-identifiers-resolve)`);
1171
+ }
1172
+ else if (segs2.length >= 2) {
1173
+ const stp = (proc.steps ?? []).find(ss => ss.id === segs2[1]);
1174
+ if (!stp) {
1175
+ err('C171', `${ownerKind} ${ownerId}: argument "${a.name}" binds to "${a.source}" — the process declares no step "${segs2[1]}" (expression-identifiers-resolve)`);
1176
+ }
1177
+ else if (segs2.length >= 3) {
1178
+ const io = segs2.slice(2).join('.');
1179
+ if (!(stp.sigInputs ?? []).includes(io) &&
1180
+ !(stp.sigOutputs ?? []).includes(io)) {
1181
+ err('C171', `${ownerKind} ${ownerId}: argument "${a.name}" binds to "${a.source}" — the step declares no io "${io}" (expression-identifiers-resolve)`);
1182
+ }
1183
+ }
1184
+ }
1185
+ continue;
1186
+ }
1187
+ if (kind !== 'observable') {
1188
+ if (!DECLARATION_KINDS.has(kind)) {
1189
+ 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)`);
1190
+ continue;
1191
+ }
1192
+ const idRoot = id.split('.')[0];
1193
+ const idTail = id.split('.').pop() ?? id;
1194
+ if (!globalExprNames.has(id) &&
1195
+ !globalExprNames.has(idRoot) &&
1196
+ !globalExprNames.has(idTail)) {
1197
+ err('C171', `${ownerKind} ${ownerId}: argument "${a.name}" binds to "${a.source}", which no declared ${kind} names — air inside the formula (expression-identifiers-resolve)`);
1198
+ }
1199
+ }
1200
+ continue;
1201
+ }
1202
+ // <package>:<kind>:<id> across a uses edge — the composed gate
1203
+ // resolves it against the closure.
1204
+ continue;
1205
+ }
1206
+ if (a.source === 'self' || a.source.startsWith('self.')) {
1207
+ continue;
1208
+ }
1209
+ const bare = (q) => q.split(':').pop() ?? q;
1210
+ const sourceBare = bare(a.source.replace(/\./g, ':'));
1211
+ if (memberScope !== null) {
1212
+ if (!memberScope.has(a.source) && !memberScope.has(sourceBare)) {
1213
+ err('C172', `requirement class ${ownerId}: the law binds "${a.name}" to "${a.source}", which is not a contained member (requirement-algebra-resolves)`);
1214
+ }
1215
+ continue;
1216
+ }
1217
+ if (!globalExprNames.has(a.source) &&
1218
+ !globalExprNames.has(sourceBare) &&
1219
+ !usesCovers(limit.uses, sourceBare)) {
1220
+ 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)`);
1221
+ }
1222
+ }
1223
+ // ── C177 kind-coherence ──────────────────────────────────────────
1224
+ // The comparisons compare operands of the same declared quantity
1225
+ // kind. The kinds come from the args' declared sources; an operand
1226
+ // of unknown kind or a bare number never conflicts — the check is
1227
+ // exact where the declarations are, silent everywhere else.
1228
+ const kindLabel = (k) => k === undefined
1229
+ ? 'an operand of unknown kind'
1230
+ : k === 'TEXT'
1231
+ ? 'text'
1232
+ : k === null
1233
+ ? 'a bare number'
1234
+ : `the "${k}" quantity`;
1235
+ const argKindOf = (source) => {
1236
+ if (source.includes(':')) {
1237
+ const segs = source.split(':');
1238
+ if (segs.length !== 2) {
1239
+ return undefined;
1240
+ }
1241
+ const [kind, id] = segs;
1242
+ if (kind === 'symbol') {
1243
+ return symbolKinds.get(id.split('.').pop() ?? id);
1244
+ }
1245
+ if (kind === 'attribute') {
1246
+ return attributeKinds.get(id.split('.').pop() ?? id);
1247
+ }
1248
+ if (kind === 'verdict') {
1249
+ return verdictKinds.get(id.split('.')[0]);
1250
+ }
1251
+ if (kind === 'table') {
1252
+ const segs2 = id.split('.');
1253
+ const colType = segs2.length === 2
1254
+ ? tableColumnTypes.get(segs2[0])?.get(segs2[1])
1255
+ : undefined;
1256
+ return colType === 'string' ? 'TEXT' : undefined;
1257
+ }
1258
+ return undefined;
1259
+ }
1260
+ if (source === 'self' || source.startsWith('self.')) {
1261
+ return undefined;
1262
+ }
1263
+ return symbolKinds.get(source) ?? attributeKinds.get(source) ?? undefined;
1264
+ };
1265
+ const kindConflicts = new Set();
1266
+ const kindMixErr = (node, l, r) => {
1267
+ const key = (0, expressions_1.renderExpression)(node);
1268
+ if (kindConflicts.has(key)) {
1269
+ return;
1270
+ }
1271
+ kindConflicts.add(key);
1272
+ 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)`);
1273
+ };
1274
+ const kindOf = (node) => {
1275
+ switch (node.type) {
1276
+ case 'num':
1277
+ return null;
1278
+ case 'str':
1279
+ return null;
1280
+ case 'name': {
1281
+ const root = node.path[0];
1282
+ if (node.path.length > 1 &&
1283
+ (root === 'self' || CONTEXT_ROOTS.has(root))) {
1284
+ return undefined;
1285
+ }
1286
+ const arg = args.find(a => a.name === root);
1287
+ return arg ? argKindOf(arg.source) : undefined;
1288
+ }
1289
+ case 'call': {
1290
+ if (node.callee.includes('.')) {
1291
+ return undefined;
1292
+ }
1293
+ const kinded = [
1294
+ 'abs',
1295
+ 'max',
1296
+ 'min',
1297
+ 'floor',
1298
+ 'ceil',
1299
+ 'round',
1300
+ 'sqrt',
1301
+ 'exp',
1302
+ 'ln',
1303
+ 'log',
1304
+ 'mod',
1305
+ ];
1306
+ if (!kinded.includes(node.callee)) {
1307
+ return null;
1308
+ }
1309
+ let acc = undefined;
1310
+ for (const a of node.args) {
1311
+ const k = kindOf(a);
1312
+ if (k === undefined) {
1313
+ continue;
1314
+ }
1315
+ if (acc === undefined || acc === null) {
1316
+ acc = k;
1317
+ continue;
1318
+ }
1319
+ if (k !== undefined && k !== null && k !== acc) {
1320
+ kindMixErr(node, acc, k);
1321
+ }
1322
+ }
1323
+ return acc;
1324
+ }
1325
+ case 'unop':
1326
+ return node.op === 'not' ? null : kindOf(node.operand);
1327
+ case 'nav':
1328
+ // the iteration predicate's truth over the collection: the
1329
+ // boolean iterators; select keeps the source's kind — its
1330
+ // element kind is the collection's, unknown here
1331
+ return node.op === 'select' ? undefined : null;
1332
+ case 'binop': {
1333
+ if (['<=', '>=', '<', '>', '=', '!=', '≠'].includes(node.op)) {
1334
+ const l = kindOf(node.left);
1335
+ const r = kindOf(node.right);
1336
+ if (l !== undefined &&
1337
+ r !== undefined &&
1338
+ l !== null &&
1339
+ r !== null &&
1340
+ l !== r) {
1341
+ kindMixErr(node, l, r);
1342
+ }
1343
+ return null;
1344
+ }
1345
+ if (['and', 'or', 'xor', 'implies'].includes(node.op)) {
1346
+ kindOf(node.left);
1347
+ kindOf(node.right);
1348
+ return null;
1349
+ }
1350
+ const l = kindOf(node.left);
1351
+ const r = kindOf(node.right);
1352
+ if (l !== undefined &&
1353
+ r !== undefined &&
1354
+ l !== null &&
1355
+ r !== null &&
1356
+ l !== r) {
1357
+ kindMixErr(node, l, r);
1358
+ }
1359
+ return l !== undefined && l !== null ? l : r;
1360
+ }
1361
+ }
1362
+ };
1363
+ kindOf(ast);
1364
+ };
1365
+ for (const r of standard.requirements ?? []) {
1366
+ if (r.limit?.argsBound) {
1367
+ checkInlineLimit('requirement', r.id, r.limit, null);
1368
+ continue;
1369
+ }
1370
+ const expr = r.limit?.expression;
1371
+ if (!expr) {
1372
+ continue;
1373
+ }
1374
+ const ocl = expr.replace(/^ocl\{/, '').replace(/\}$/, '');
1375
+ const uses = r.limit?.uses ?? [];
1376
+ for (const id of unresolvedIn(ocl, [], uses)) {
1377
+ 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)`);
1378
+ }
1379
+ }
1380
+ // The inline carriers (derive / derivation / check blocks): the
1381
+ // AST-exact resolution — the free names against the carrier's declared
1382
+ // scope plus the package's declared registries; the calls against the
1383
+ // function vocabulary and the declared callables.
1384
+ const resolveInlineCarrier = (ownerKind, ownerId, what, src, scope) => {
1385
+ let ast;
1386
+ try {
1387
+ ast = (0, expressions_1.parseExpression)(src);
1388
+ }
1389
+ catch (e) {
1390
+ err('C171', `${ownerKind} ${ownerId}: the ${what} does not parse — ${String(e.message ?? e)} (expression-identifiers-resolve)`);
1391
+ return;
1392
+ }
1393
+ const ids = (0, expressions_1.freeIdentifiers)(ast);
1394
+ for (const call of new Set(ids.calls)) {
1395
+ if (INLINE_FUNCTION_WORDS.has(call)) {
1396
+ continue;
1397
+ }
1398
+ if (call.includes('.')) {
1399
+ const root = call.split('.')[0];
1400
+ if (scope.has(root) ||
1401
+ globalExprNames.has(root) ||
1402
+ root === 'self' ||
1403
+ CONTEXT_ROOTS.has(root)) {
1404
+ continue;
1405
+ }
1406
+ }
1407
+ if (scope.has(call) || globalExprNames.has(call)) {
1408
+ continue;
1409
+ }
1410
+ 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)`);
1411
+ }
1412
+ for (const id of new Set(ids.names)) {
1413
+ const root = id.split('.')[0];
1414
+ if (id.includes('.') &&
1415
+ (scope.has(root) || root === 'self' || CONTEXT_ROOTS.has(root))) {
1416
+ continue;
1417
+ }
1418
+ if (scope.has(id)) {
1419
+ continue;
1420
+ }
1421
+ if (globalExprNames.has(id) ||
1422
+ globalExprNames.has(id.split('.').pop() ?? id)) {
1423
+ continue;
1424
+ }
1425
+ err('C171', `${ownerKind} ${ownerId}: the ${what} names "${id}", which nothing in scope declares — air inside the formula (expression-identifiers-resolve)`);
1426
+ }
1427
+ };
1428
+ for (const v of standard.verdicts ?? []) {
1429
+ const expr = v.derive;
1430
+ if (!expr) {
1431
+ continue;
1432
+ }
1433
+ const inputs = v.inputs ?? [];
1434
+ if (v.deriveBound) {
1435
+ resolveInlineCarrier('verdict', v.id, 'derive', expr, new Set(inputs));
1436
+ continue;
1437
+ }
1438
+ // the legacy leg: the ocl{…} wrapper strips before the regex walk
1439
+ const ocl = expr.replace(/^ocl\{/, '').replace(/\}$/, '');
1440
+ for (const id of unresolvedIn(ocl, inputs, [])) {
1441
+ err('C171', `verdict ${v.id}: the derive expression names "${id}", which no input declares — air inside the formula (expression-identifiers-resolve)`);
1442
+ }
1443
+ }
1444
+ for (const t of standard.conformanceTests ?? []) {
1445
+ const scope = (t.variables ?? []).map(v => v.name);
1446
+ for (const v of t.variables ?? []) {
1447
+ if (!v.derivation) {
1448
+ continue;
1449
+ }
1450
+ if (v.derivationBound) {
1451
+ resolveInlineCarrier('conformance test', t.id, `variable ${v.name}'s derivation`, v.derivation, new Set(scope));
1452
+ continue;
1453
+ }
1454
+ const ocl = v.derivation.replace(/^ocl\{/, '').replace(/\}$/, '');
1455
+ for (const id of unresolvedIn(ocl, scope, [])) {
1456
+ 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)`);
1457
+ }
1458
+ }
1459
+ }
1460
+ for (const c of standard.constraints ?? []) {
1461
+ if (!c.checkBound || !c.check) {
1462
+ continue;
1463
+ }
1464
+ const cArgs = c.args ?? [];
1465
+ const cScope = new Set(cArgs.map(a => a.name));
1466
+ for (const a of cArgs) {
1467
+ if (a.source.includes(':')) {
1468
+ const segs = a.source.split(':');
1469
+ if (segs.length === 2 && segs[0] !== 'observable') {
1470
+ const [kind, id] = segs;
1471
+ if (!DECLARATION_KINDS.has(kind)) {
1472
+ err('C171', `constraint ${c.id}: argument "${a.name}" binds to "${a.source}" — "${kind}" is not a declaration kind (expression-identifiers-resolve)`);
1473
+ }
1474
+ else {
1475
+ const idRoot = id.split('.')[0];
1476
+ const idTail = id.split('.').pop() ?? id;
1477
+ if (!globalExprNames.has(id) &&
1478
+ !globalExprNames.has(idRoot) &&
1479
+ !globalExprNames.has(idTail)) {
1480
+ err('C171', `constraint ${c.id}: argument "${a.name}" binds to "${a.source}", which no declared ${kind} names — air inside the formula (expression-identifiers-resolve)`);
1481
+ }
1482
+ }
1483
+ }
1484
+ continue;
1485
+ }
1486
+ if (a.source === 'self' || a.source.startsWith('self.')) {
1487
+ continue;
1488
+ }
1489
+ const bare = a.source.split(':').pop() ?? a.source;
1490
+ if (!globalExprNames.has(a.source) && !globalExprNames.has(bare)) {
1491
+ err('C171', `constraint ${c.id}: argument "${a.name}" binds to "${a.source}", which nothing declares — air inside the formula (expression-identifiers-resolve)`);
1492
+ }
1493
+ }
1494
+ resolveInlineCarrier('constraint', c.id, 'check', c.check, cScope);
1495
+ // C84's inline restatement: the check is one BOOLEAN expression
1496
+ try {
1497
+ const ast = (0, expressions_1.parseExpression)(c.check);
1498
+ const booleanTop = (ast.type === 'binop' &&
1499
+ [
1500
+ '<=',
1501
+ '>=',
1502
+ '<',
1503
+ '>',
1504
+ '=',
1505
+ '!=',
1506
+ '≠',
1507
+ 'and',
1508
+ 'or',
1509
+ 'xor',
1510
+ 'implies',
1511
+ ].includes(ast.op)) ||
1512
+ (ast.type === 'nav' && ['forAll', 'exists'].includes(ast.op));
1513
+ if (!booleanTop) {
1514
+ err('C84', `constraint ${c.id}: the check does not evaluate to a boolean — an invariant is one comparison or logical composition (constraint-shape)`);
1515
+ }
1516
+ }
1517
+ catch {
1518
+ // already reported by resolveInlineCarrier
1519
+ }
1520
+ }
1521
+ // ── C172 requirement-algebra-resolves ───────────────────────────────
1522
+ // Containment is conjunction: a requirement class's members are its
1523
+ // body, and the class's limit expression (the LAW) names its members —
1524
+ // and/or/xor/implies, the OCL booleans. The law's identifiers resolve
1525
+ // to members only; every member appears in the law or the class
1526
+ // carries no law (pure conjunction); a child's modality is never
1527
+ // weaker than the class's.
1528
+ const memberSet = new Map();
1529
+ for (const rc of standard.requirementClasses ?? []) {
1530
+ memberSet.set(rc.id, new Set((rc.members ?? []).map(String)));
1531
+ }
1532
+ for (const rc of standard.requirementClasses ?? []) {
1533
+ const law = rc.limit?.expression;
1534
+ const members = memberSet.get(rc.id) ?? new Set();
1535
+ if (!law) {
1536
+ continue;
1537
+ }
1538
+ if (rc.limit?.argsBound) {
1539
+ checkInlineLimit('requirement class', rc.id, rc.limit, members);
1540
+ const covered = new Set();
1541
+ try {
1542
+ for (const n of (0, expressions_1.freeIdentifiers)((0, expressions_1.parseExpression)(law)).names) {
1543
+ covered.add(n);
1544
+ }
1545
+ }
1546
+ catch {
1547
+ // already reported inside checkInlineLimit
1548
+ }
1549
+ for (const a of rc.limit.args ?? []) {
1550
+ covered.add(a.name);
1551
+ covered.add(a.source);
1552
+ }
1553
+ for (const m of members) {
1554
+ if (!covered.has(m)) {
1555
+ 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)`);
1556
+ }
1557
+ }
1558
+ continue;
1559
+ }
1560
+ const ocl = law.replace(/^ocl\{/, '').replace(/\}$/, '');
1561
+ const ids = (ocl.match(/[A-Za-z_][A-Za-z0-9_]*/g) ?? []).filter(x => !OCL_WORDS.has(x));
1562
+ const lawIds = new Set();
1563
+ for (const id of ids) {
1564
+ lawIds.add(id);
1565
+ if (!members.has(id)) {
1566
+ 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)`);
1567
+ }
1568
+ }
1569
+ for (const m of members) {
1570
+ if (!lawIds.has(m)) {
1571
+ 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)`);
1572
+ }
1573
+ }
1574
+ }
1575
+ // ── C176 language-declared-wellformed ───────────────────────────────
1576
+ // A declared language generation must be well-formed (v<digit>[.digit])
1577
+ // — the strict-parse contract's key. The unknown-keyword errors come
1578
+ // from the parse itself (the strict mode); this rule only checks the
1579
+ // declaration's shape.
1580
+ // ── C178 legacy-carrier-deprecated ──────────────────────────────────
1581
+ // The deprecation mechanism (register W6): the legacy quoted
1582
+ // expression carriers (`expression "ocl{…}"`, the quoted derive /
1583
+ // derivation / check) warn once a package declares the generation
1584
+ // that deprecates them. The generation constant is INERT until the
1585
+ // owner's release bump — no package declares it today, so nothing
1586
+ // warns; flipping the constant (or packages declaring the newer
1587
+ // generation) activates the ratchet. The inline blocks are the only
1588
+ // forward form.
1589
+ const DEPRECATION_GENERATION = 'v3.4';
1590
+ const generationRank = (g) => {
1591
+ const m = g.match(/^v(\d+)(?:\.(\d+))?$/);
1592
+ if (!m) {
1593
+ return -1;
1594
+ }
1595
+ return Number(m[1]) * 1000 + Number(m[2] ?? '0');
1596
+ };
1597
+ try {
1598
+ const mf = (0, package_1.readPackageManifest)(dir);
1599
+ if (mf.language !== undefined) {
1600
+ if (!/^v\d+(\.\d+)?$/.test(mf.language)) {
1601
+ err('C176', `the declared language "${mf.language}" is not a generation (v<major>[.<minor>]) (language-declared-wellformed)`);
1602
+ }
1603
+ else if (generationRank(mf.language) >= generationRank(DEPRECATION_GENERATION)) {
1604
+ let legacy = 0;
1605
+ for (const r of standard.requirements ?? []) {
1606
+ if (r.limit && !r.limit.argsBound && r.limit.expression) {
1607
+ legacy += 1;
1608
+ }
1609
+ }
1610
+ for (const v of standard.verdicts ?? []) {
1611
+ if (v.derive &&
1612
+ !v.deriveBound) {
1613
+ legacy += 1;
1614
+ }
1615
+ }
1616
+ for (const t of standard.conformanceTests ?? []) {
1617
+ for (const v of t.variables ?? []) {
1618
+ if (v.derivation && !v.derivationBound) {
1619
+ legacy += 1;
1620
+ }
1621
+ }
1622
+ for (const p of t.preconditions ?? []) {
1623
+ if (p.check && !p.checkBound) {
1624
+ legacy += 1;
1625
+ }
1626
+ }
1627
+ }
1628
+ for (const c of standard.constraints ?? []) {
1629
+ if (c.check && !c.checkBound) {
1630
+ legacy += 1;
1631
+ }
1632
+ }
1633
+ if (legacy > 0) {
1634
+ 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)`);
1635
+ }
1636
+ }
1637
+ }
1638
+ }
1639
+ catch {
1640
+ // the manifest unreadable — the load errors own that
1641
+ }
1642
+ // ── C179 uncertainty-budget-resolves ────────────────────────────────
1643
+ // The budget is a declaration, never arithmetic: its target is a
1644
+ // declared verdict (the measured quantity it backs), a Type A
1645
+ // component names a declared test variable that holds a captured
1646
+ // series, a Type B component states its distribution and half-width,
1647
+ // and the coverage factor is a positive number. The propagation law
1648
+ // is the GUM's — the evaluation plane's, never restated here.
1649
+ {
1650
+ const seriesVariables = new Map();
1651
+ for (const t of standard.conformanceTests ?? []) {
1652
+ for (const v of t.variables ?? []) {
1653
+ seriesVariables.set(v.name, v.series !== null);
1654
+ }
1655
+ }
1656
+ for (const b of standard.uncertaintyBudgets ?? []) {
1657
+ if (!b.target) {
1658
+ err('C179', `uncertainty_budget ${b.id}: declares no target — the budget backs a declared verdict's measured quantity (uncertainty-budget-resolves)`);
1659
+ }
1660
+ else if (!(standard.verdicts ?? []).some(v => v.id === b.target)) {
1661
+ 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)`);
1662
+ }
1663
+ if (b.components.length === 0) {
1664
+ warn('C179', `uncertainty_budget ${b.id}: declares no components — an empty budget states no uncertainty (uncertainty-budget-resolves)`);
1665
+ }
1666
+ if (b.coverageK && !(Number(b.coverageK) > 0)) {
1667
+ err('C179', `uncertainty_budget ${b.id}: coverage k "${b.coverageK}" is not a positive number (uncertainty-budget-resolves)`);
1668
+ }
1669
+ for (const c of b.components) {
1670
+ if (c.type === 'A') {
1671
+ if (!c.series) {
1672
+ 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)`);
1673
+ }
1674
+ else if (!seriesVariables.has(c.series)) {
1675
+ err('C179', `uncertainty_budget ${b.id}: component ${c.id}'s series "${c.series}" is not a declared test variable (uncertainty-budget-resolves)`);
1676
+ }
1677
+ else if (!seriesVariables.get(c.series)) {
1678
+ 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)`);
1679
+ }
1680
+ }
1681
+ else {
1682
+ if (!c.distribution) {
1683
+ err('C179', `uncertainty_budget ${b.id}: component ${c.id} is Type B but states no distribution (uncertainty-budget-resolves)`);
1684
+ }
1685
+ if (!c.halfWidth) {
1686
+ 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)`);
1687
+ }
1688
+ }
1689
+ }
1690
+ }
1691
+ }
1692
+ // ── C173 step-plane-refs-resolve (register 19) ──────────────────────
1693
+ // A signature step's completion is one expression over the step's own
1694
+ // declared ios: the identifiers resolve to `inputs`/`outputs`, the
1695
+ // calls stay in the closed vocabulary, and nothing in the completion
1696
+ // is air. The calls bindings ride C76's machinery (the typed pass-1
1697
+ // fills the same fields the flow dialect uses).
1698
+ for (const p of standard.processes ?? []) {
1699
+ for (const st of p.steps ?? []) {
1700
+ if (!st.completion) {
1701
+ continue;
1702
+ }
1703
+ const ios = new Set([...st.sigInputs, ...st.sigOutputs]);
1704
+ let ids;
1705
+ try {
1706
+ ids = (0, expressions_1.freeIdentifiers)((0, expressions_1.parseExpression)(st.completion.replace(/^ocl\{/, '').replace(/\}$/, '')));
1707
+ }
1708
+ catch (e) {
1709
+ 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)`);
1710
+ continue;
1711
+ }
1712
+ for (const call of new Set(ids.calls)) {
1713
+ if (!INLINE_FUNCTION_WORDS.has(call)) {
1714
+ err('C173', `process ${p.id}: step "${st.id}" completion calls "${call}(…)", which is not in the expression function vocabulary (step-plane-refs-resolve)`);
1715
+ }
1716
+ }
1717
+ for (const id of new Set(ids.names)) {
1718
+ const root = id.split('.')[0];
1719
+ if (id.includes('.') && (root === 'self' || CONTEXT_ROOTS.has(root))) {
1720
+ continue;
1721
+ }
1722
+ if (ios.has(id) || ios.has(root)) {
1723
+ continue;
1724
+ }
1725
+ 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)`);
1726
+ }
1727
+ }
1728
+ }
1729
+ // ── C170 edition-lineage-declared ───────────────────────────────────
1730
+ // A package declaring more than one edition carries a declarative
1731
+ // edition mapping (edition-mapping.json, primmel-edition-mapping/1)
1732
+ // naming both edition states — the cross-edition linkage is an
1733
+ // artifact, never implicit. Born-at-edition packages map the prior
1734
+ // edition with model_state: null and the reason.
1735
+ try {
1736
+ const mf = (0, package_1.readPackageManifest)(dir);
1737
+ const eds = (mf.editions ?? []).map(String);
1738
+ if (eds.length > 1) {
1739
+ const map = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(dir, 'edition-mapping.json'), 'utf8'));
1740
+ if (map.kind !== 'primmel-edition-mapping/1') {
1741
+ err('C170', `edition-mapping.json: kind "${String(map.kind)}" is not primmel-edition-mapping/1 (edition-lineage-declared)`);
1742
+ }
1743
+ else {
1744
+ const fromEd = map.from?.edition === undefined
1745
+ ? undefined
1746
+ : String(map.from.edition);
1747
+ const toEd = map.to?.edition === undefined ? undefined : String(map.to.edition);
1748
+ for (const [side, ed] of [
1749
+ ['from', fromEd],
1750
+ ['to', toEd],
1751
+ ]) {
1752
+ if (!ed || !eds.includes(ed)) {
1753
+ err('C170', `edition-mapping.json: the ${side} edition ${String(ed)} is not among the manifest's declared editions [${eds.join(', ')}] (edition-lineage-declared)`);
1754
+ }
1755
+ }
1756
+ }
1757
+ }
1758
+ }
1759
+ catch (e) {
1760
+ const code = e.code;
1761
+ if (code === 'ENOENT') {
1762
+ const eds = (() => {
1763
+ try {
1764
+ return ((0, package_1.readPackageManifest)(dir).editions ?? []).map(String);
1765
+ }
1766
+ catch {
1767
+ return [];
1768
+ }
1769
+ })();
1770
+ if (eds.length > 1) {
1771
+ 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)`);
1772
+ }
1773
+ }
1774
+ else if (e instanceof SyntaxError) {
1775
+ err('C170', `edition-mapping.json is not valid JSON: ${e.message} (edition-lineage-declared)`);
1776
+ }
1777
+ }
1778
+ // ── C168 method-ref-resolves ────────────────────────────────────────
1779
+ // A conformance test's method_ref names THE PROCESS the test realizes:
1780
+ // a bare id resolved in this package, or a scoped <package>:<id>
1781
+ // reference resolved read-only through the locator (the target's own
1782
+ // content validates the id; nothing merges — the C31 doctrine). A
1783
+ // bibliographic standard id is not a resolution target: the method is
1784
+ // a process to be realized, not a document to cite. Locator-gated
1785
+ // like C27-C31 — without a locator the scoped leg stays silent.
1786
+ const methodTargetIds = (s) => new Set([
1787
+ ...(s.processes ?? []).map(p => p.id),
1788
+ ...(s.conformanceTests ?? []).map(x => x.id),
1789
+ ]);
1790
+ const localMethodIds = methodTargetIds(standard);
1791
+ const scopedMethodIds = new Map();
1792
+ const resolveCitation = (elemId, ref) => {
1793
+ const colon = ref.indexOf(':');
1794
+ if (colon <= 0) {
1795
+ if (!localMethodIds.has(ref)) {
1796
+ err('C168', `${elemId}: the reference "${ref}" names no process or conformance test declared in this package (method-ref-resolves)`);
1797
+ }
1798
+ return;
1799
+ }
1800
+ if (!options.resolvePackage) {
1801
+ return;
1802
+ }
1803
+ const pkgId = ref.slice(0, colon);
1804
+ const localId = ref.slice(colon + 1);
1805
+ let ids = scopedMethodIds.get(pkgId);
1806
+ if (!ids) {
1807
+ ids = new Set();
1808
+ try {
1809
+ const depDir = options.resolvePackage(pkgId);
1810
+ if (depDir) {
1811
+ const dep = (0, package_1.loadPackageWithIssues)(depDir);
1812
+ ids = methodTargetIds(dep.standard);
1813
+ }
1814
+ }
1815
+ catch {
1816
+ // Unreadable dependency — C27 reports the resolution failure;
1817
+ // this rule stays silent rather than double-reporting.
1818
+ }
1819
+ scopedMethodIds.set(pkgId, ids);
1820
+ }
1821
+ if (!ids.has(localId)) {
1822
+ err('C168', `${elemId}: the reference "${ref}" names no process or conformance test declared in package "${pkgId}" (method-ref-resolves)`);
1823
+ }
1824
+ };
1825
+ for (const t of standard.conformanceTests ?? []) {
1826
+ const ref = t.methodRef;
1827
+ if (ref) {
1828
+ resolveCitation(t.id, ref);
1829
+ }
1830
+ }
1831
+ // The process bodies' call edges ride the same resolution: a step's
1832
+ // `calls <p>` (typed or raw-plane) names a process to be realized.
1833
+ for (const pr of standard.processes ?? []) {
1834
+ const raw = pr;
1835
+ for (const st of raw.steps ?? []) {
1836
+ const call = st.calls || st.signatureBlock?.match(/\bcalls +([\w:/.-]+)/)?.[1];
1837
+ if (call) {
1838
+ resolveCitation(`${pr.id}/step/${st.id ?? ''}`, call);
1839
+ }
1840
+ }
1841
+ }
837
1842
  // C6 — subject anatomy: every aspect declared under exactly one family
838
1843
  // (anatomy-family). The parser records wrong-family and undeclared
839
1844
  // aspect keys on Subject.misplacedAspects; value-level markers catch
@@ -1724,7 +2729,7 @@ function checkPackage(dir, options = {}) {
1724
2729
  if (c.stereotype !== 'inv') {
1725
2730
  err('C84', `constraint ${c.id}: stereotype "${c.stereotype || '(none)'}" — a constraint is always «inv», the subject's declaration-level invariant (constraint-shape)`);
1726
2731
  }
1727
- if (!OCL_CHECK.test(c.check)) {
2732
+ if (!c.checkBound && !OCL_CHECK.test(c.check)) {
1728
2733
  err('C84', `constraint ${c.id}: check is not one OCL boolean expression ocl{…} over the subject's declared anatomy (constraint-shape)`);
1729
2734
  }
1730
2735
  if (!c.violationMeaning || c.violationMeaning.trim() === '') {
@@ -6571,6 +7576,124 @@ function checkVerdictChains(standard) {
6571
7576
  }
6572
7577
  return issues;
6573
7578
  }
7579
+ /**
7580
+ * C158–C162 + C164 + C167 — the LML-aligned substrate (TODO.lang-gaps;
7581
+ * the spec's validation contract, language-v3 §16). The five constructs
7582
+ * (wireMappings / stringFormats / namespaces / datums / checks) carry
7583
+ * their declared data; the checker does the discipline:
7584
+ *
7585
+ * C158 prose-quarantine: free text stays in the description block — a
7586
+ * clause citation inside a datum's description is a reference in
7587
+ * prose, and the reference belongs in a urn (the deletion test's
7588
+ * kernel-side proxy: the citation patterns are exactly the prose a
7589
+ * re-locator would trip over). An unfinished-prose TODO.roadmap
7590
+ * marker warns.
7591
+ * C159 urn-only-refs: a wire mapping's namespace names a declared
7592
+ * namespace block (the serializer emits the prefix/uri pair).
7593
+ * C160 signature-closure: every name a check consumes (its inputs) is
7594
+ * a declared datum or symbol — a check over an undeclared input is
7595
+ * uncomputable.
7596
+ * C161 check-surface: a datum's render hints name their notation, and
7597
+ * the notation vocabulary is closed. (The parser silently drops an
7598
+ * unknown notation token, so parsed models never carry one — this
7599
+ * leg guards programmatically built Standards and pins the closed
7600
+ * vocabulary against renderer growth.)
7601
+ * C162 namespace-coherence: the metadata namespace string names a
7602
+ * declared namespace block when one is declared at all.
7603
+ * C164 wire-mapping-coverage: a mapping's format is in the wire
7604
+ * vocabulary and its class names a declared dataclass when
7605
+ * dataclasses are declared.
7606
+ * C167 qualified-unit-reference: a datum's unit and quantity kind use
7607
+ * the closed qualified `<namespace>:<id>` form (si:…, unitsdb:…) —
7608
+ * a bare unit token is exactly the ambiguity the registries exist
7609
+ * to close.
7610
+ *
7611
+ * C163 change-levels runs over `primmel diff --exit-code` (the existing
7612
+ * diff classifier — model-diff.ts); C165/C166 await the calculation
7613
+ * math plane. All three are catalog-only entries (check-rules.ts) with
7614
+ * no kernel leg here — deliberately, not by omission.
7615
+ */
7616
+ function checkLmlAligned(standard) {
7617
+ const issues = [];
7618
+ const err = (check, message) => issues.push({ check, severity: 'error', message });
7619
+ const warn = (check, message) => issues.push({ check, severity: 'warning', message });
7620
+ const namespaceIds = new Set((standard.namespaces ?? []).map(n => n.id));
7621
+ // The mapping targets the LML class name as authored (`mapping xml
7622
+ // Ceramic`); a registerable class declares `Ceramic#data`, so the
7623
+ // target matches the id or the id with its `#data` register suffix
7624
+ // stripped (spec §Serialization's authored shape).
7625
+ const dataclassIds = new Set();
7626
+ for (const c of standard.dataclasses ?? []) {
7627
+ dataclassIds.add(c.id);
7628
+ dataclassIds.add(c.id.replace(/#data$/, ''));
7629
+ }
7630
+ const symbolIds = new Set((standard.symbols ?? []).map(s => s.id));
7631
+ const datumIds = new Set((standard.datums ?? []).map(d => d.id));
7632
+ // ── C158 prose-quarantine ──────────────────────────────────────────
7633
+ // A datum with a label and an empty description is the FINE form; the
7634
+ // quarantine breaks only when the description smuggles a citation.
7635
+ const CLAUSE_CITATION = /clause-\d|§\d/i;
7636
+ for (const d of standard.datums ?? []) {
7637
+ if (!d.description) {
7638
+ continue;
7639
+ }
7640
+ if (CLAUSE_CITATION.test(d.description)) {
7641
+ 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)`);
7642
+ }
7643
+ else if (d.description.includes('TODO.roadmap')) {
7644
+ warn('C158', `datum ${d.id}: the description carries the unfinished-prose marker TODO.roadmap (prose-quarantine)`);
7645
+ }
7646
+ }
7647
+ // ── C159 urn-only-refs ─────────────────────────────────────────────
7648
+ for (const w of standard.wireMappings ?? []) {
7649
+ if (w.namespaceRef && !namespaceIds.has(w.namespaceRef)) {
7650
+ err('C159', `mapping ${w.id}: namespace "${w.namespaceRef}" names no declared namespace block (urn-only-refs)`);
7651
+ }
7652
+ }
7653
+ // ── C160 signature-closure ─────────────────────────────────────────
7654
+ for (const c of standard.checks ?? []) {
7655
+ for (const inp of c.inputs ?? []) {
7656
+ if (!(datumIds.has(inp) || symbolIds.has(inp))) {
7657
+ err('C160', `check ${c.id}: input "${inp}" resolves to no declared datum or symbol — the check consumes an undeclared name (signature-closure)`);
7658
+ }
7659
+ }
7660
+ }
7661
+ // ── C161 check-surface ─────────────────────────────────────────────
7662
+ for (const d of standard.datums ?? []) {
7663
+ for (const r of d.renders ?? []) {
7664
+ if (!lmlAligned_1.MATH_NOTATIONS.includes(r.notation)) {
7665
+ err('C161', `datum ${d.id}: render notation "${r.notation}" is outside the closed vocabulary (${lmlAligned_1.MATH_NOTATIONS.join('|')}) (check-surface)`);
7666
+ }
7667
+ }
7668
+ }
7669
+ // ── C162 namespace-coherence ───────────────────────────────────────
7670
+ const metaNamespace = standard.meta?.namespace ?? '';
7671
+ if (metaNamespace &&
7672
+ namespaceIds.size > 0 &&
7673
+ !namespaceIds.has(metaNamespace)) {
7674
+ err('C162', `metadata namespace "${metaNamespace}" names no declared namespace block (namespace-coherence)`);
7675
+ }
7676
+ // ── C164 wire-mapping-coverage ─────────────────────────────────────
7677
+ for (const w of standard.wireMappings ?? []) {
7678
+ if (!lmlAligned_1.MAPPING_FORMATS.includes(w.format)) {
7679
+ err('C164', `mapping ${w.id}: format "${w.format}" is outside the wire vocabulary (${lmlAligned_1.MAPPING_FORMATS.join('|')}) (wire-mapping-coverage)`);
7680
+ }
7681
+ if (dataclassIds.size > 0 && !dataclassIds.has(w.target)) {
7682
+ 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)`);
7683
+ }
7684
+ }
7685
+ // ── C167 qualified-unit-reference ──────────────────────────────────
7686
+ const QUALIFIED_REF = /^[a-z]+:[A-Za-z0-9_-]+$/;
7687
+ for (const d of standard.datums ?? []) {
7688
+ if (d.unit && !QUALIFIED_REF.test(d.unit)) {
7689
+ err('C167', `datum ${d.id}: unit "${d.unit}" is bare — a unit reference uses the qualified <namespace>:<id> form (si:…, unitsdb:…) (qualified-unit-reference)`);
7690
+ }
7691
+ if (d.quantityKind && !QUALIFIED_REF.test(d.quantityKind)) {
7692
+ 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)`);
7693
+ }
7694
+ }
7695
+ return issues;
7696
+ }
6574
7697
  /**
6575
7698
  * C77–C80 + C85 + C113 — edition lifecycle (TODO.roadmap/28; doctrine ch. 13
6576
7699
  * §13.4/§13.7) and the manifest base URN (TODO.roadmap/27). Editions are