@primmel/primmel 1.19.0 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +7 -3
  4. package/dist/index.js.map +1 -1
  5. package/dist/src/check-rules.d.ts +1 -1
  6. package/dist/src/check-rules.d.ts.map +1 -1
  7. package/dist/src/check-rules.js +53 -15
  8. package/dist/src/check-rules.js.map +1 -1
  9. package/dist/src/check.d.ts +4 -0
  10. package/dist/src/check.d.ts.map +1 -1
  11. package/dist/src/check.js +519 -132
  12. package/dist/src/check.js.map +1 -1
  13. package/dist/src/export/cddal.d.ts +25 -0
  14. package/dist/src/export/cddal.d.ts.map +1 -0
  15. package/dist/src/export/cddal.js +213 -0
  16. package/dist/src/export/cddal.js.map +1 -0
  17. package/dist/src/export/retrieval.d.ts +147 -6
  18. package/dist/src/export/retrieval.d.ts.map +1 -1
  19. package/dist/src/export/retrieval.js +254 -25
  20. package/dist/src/export/retrieval.js.map +1 -1
  21. package/dist/src/export/unitsdb.d.ts +19 -0
  22. package/dist/src/export/unitsdb.d.ts.map +1 -0
  23. package/dist/src/export/unitsdb.js +82 -0
  24. package/dist/src/export/unitsdb.js.map +1 -0
  25. package/dist/src/export/yaml-lite.d.ts +13 -0
  26. package/dist/src/export/yaml-lite.d.ts.map +1 -0
  27. package/dist/src/export/yaml-lite.js +219 -0
  28. package/dist/src/export/yaml-lite.js.map +1 -0
  29. package/dist/src/instance-resolution.d.ts +19 -0
  30. package/dist/src/instance-resolution.d.ts.map +1 -1
  31. package/dist/src/instance-resolution.js +87 -1
  32. package/dist/src/instance-resolution.js.map +1 -1
  33. package/dist/src/model-diff.d.ts.map +1 -1
  34. package/dist/src/model-diff.js +0 -5
  35. package/dist/src/model-diff.js.map +1 -1
  36. package/dist/src/runtime.d.ts +143 -0
  37. package/dist/src/runtime.d.ts.map +1 -0
  38. package/dist/src/runtime.js +648 -0
  39. package/dist/src/runtime.js.map +1 -0
  40. package/dist/src/ser-des/config/artifact.d.ts.map +1 -1
  41. package/dist/src/ser-des/config/artifact.js +6 -0
  42. package/dist/src/ser-des/config/artifact.js.map +1 -1
  43. package/dist/src/ser-des/config/attestation.d.ts +5 -0
  44. package/dist/src/ser-des/config/attestation.d.ts.map +1 -0
  45. package/dist/src/ser-des/config/attestation.js +143 -0
  46. package/dist/src/ser-des/config/attestation.js.map +1 -0
  47. package/dist/src/ser-des/config/conformanceTest.d.ts.map +1 -1
  48. package/dist/src/ser-des/config/conformanceTest.js +310 -0
  49. package/dist/src/ser-des/config/conformanceTest.js.map +1 -1
  50. package/dist/src/ser-des/config/data.d.ts.map +1 -1
  51. package/dist/src/ser-des/config/data.js +98 -0
  52. package/dist/src/ser-des/config/data.js.map +1 -1
  53. package/dist/src/ser-des/config/documentModule.js +5 -5
  54. package/dist/src/ser-des/config/documentModule.js.map +1 -1
  55. package/dist/src/ser-des/config/index.d.ts +8 -0
  56. package/dist/src/ser-des/config/index.d.ts.map +1 -1
  57. package/dist/src/ser-des/config/index.js +22 -49
  58. package/dist/src/ser-des/config/index.js.map +1 -1
  59. package/dist/src/ser-des/config/instance.d.ts.map +1 -1
  60. package/dist/src/ser-des/config/instance.js +24 -0
  61. package/dist/src/ser-des/config/instance.js.map +1 -1
  62. package/dist/src/ser-des/config/process.d.ts.map +1 -1
  63. package/dist/src/ser-des/config/process.js +8 -0
  64. package/dist/src/ser-des/config/process.js.map +1 -1
  65. package/dist/src/ser-des/config/processSequence.d.ts +19 -0
  66. package/dist/src/ser-des/config/processSequence.d.ts.map +1 -0
  67. package/dist/src/ser-des/config/processSequence.js +77 -0
  68. package/dist/src/ser-des/config/processSequence.js.map +1 -0
  69. package/dist/src/ser-des/config/quantity.d.ts +4 -1
  70. package/dist/src/ser-des/config/quantity.d.ts.map +1 -1
  71. package/dist/src/ser-des/config/quantity.js +63 -1
  72. package/dist/src/ser-des/config/quantity.js.map +1 -1
  73. package/dist/src/ser-des/config/subject.d.ts.map +1 -1
  74. package/dist/src/ser-des/config/subject.js +14 -0
  75. package/dist/src/ser-des/config/subject.js.map +1 -1
  76. package/dist/src/ser-des/index.d.ts +4 -1
  77. package/dist/src/ser-des/index.d.ts.map +1 -1
  78. package/dist/src/ser-des/index.js +3 -1
  79. package/dist/src/ser-des/index.js.map +1 -1
  80. package/dist/src/ser-des/package.d.ts.map +1 -1
  81. package/dist/src/ser-des/package.js +9 -7
  82. package/dist/src/ser-des/package.js.map +1 -1
  83. package/dist/src/ser-des/parse.d.ts +24 -0
  84. package/dist/src/ser-des/parse.d.ts.map +1 -1
  85. package/dist/src/ser-des/parse.js +69 -33
  86. package/dist/src/ser-des/parse.js.map +1 -1
  87. package/dist/src/ser-des/shape-front-end.d.ts +8 -0
  88. package/dist/src/ser-des/shape-front-end.d.ts.map +1 -0
  89. package/dist/src/ser-des/shape-front-end.js +215 -0
  90. package/dist/src/ser-des/shape-front-end.js.map +1 -0
  91. package/dist/src/ser-des/types.d.ts +4 -10
  92. package/dist/src/ser-des/types.d.ts.map +1 -1
  93. package/dist/src/types/Artifact.d.ts +7 -0
  94. package/dist/src/types/Artifact.d.ts.map +1 -1
  95. package/dist/src/types/Attestation.d.ts +60 -0
  96. package/dist/src/types/Attestation.d.ts.map +1 -0
  97. package/dist/src/types/Attestation.js +20 -0
  98. package/dist/src/types/Attestation.js.map +1 -0
  99. package/dist/src/types/ConformanceTest.d.ts +86 -0
  100. package/dist/src/types/ConformanceTest.d.ts.map +1 -1
  101. package/dist/src/types/DocumentModule.d.ts +1 -1
  102. package/dist/src/types/DocumentModule.d.ts.map +1 -1
  103. package/dist/src/types/Instance.d.ts +6 -2
  104. package/dist/src/types/Instance.d.ts.map +1 -1
  105. package/dist/src/types/ProcessModel.d.ts +0 -11
  106. package/dist/src/types/ProcessModel.d.ts.map +1 -1
  107. package/dist/src/types/ProcessModel.js +2 -28
  108. package/dist/src/types/ProcessModel.js.map +1 -1
  109. package/dist/src/types/Quantity.d.ts +22 -0
  110. package/dist/src/types/Quantity.d.ts.map +1 -1
  111. package/dist/src/types/Standard.d.ts +2 -13
  112. package/dist/src/types/Standard.d.ts.map +1 -1
  113. package/dist/src/types/Subject.d.ts +14 -0
  114. package/dist/src/types/Subject.d.ts.map +1 -1
  115. package/dist/src/types/data.d.ts +13 -0
  116. package/dist/src/types/data.d.ts.map +1 -1
  117. package/dist/src/types/process.d.ts +8 -0
  118. package/dist/src/types/process.d.ts.map +1 -1
  119. package/dist-browser/index.mjs +1659 -1629
  120. package/dist-cli/primmel.mjs +2015 -875
  121. package/package.json +15 -3
  122. package/dist/src/ser-des/config/processModel.d.ts +0 -13
  123. package/dist/src/ser-des/config/processModel.d.ts.map +0 -1
  124. package/dist/src/ser-des/config/processModel.js +0 -163
  125. package/dist/src/ser-des/config/processModel.js.map +0 -1
  126. package/dist/src/ser-des/config/storyline.d.ts +0 -7
  127. package/dist/src/ser-des/config/storyline.d.ts.map +0 -1
  128. package/dist/src/ser-des/config/storyline.js +0 -264
  129. package/dist/src/ser-des/config/storyline.js.map +0 -1
  130. package/dist/src/ser-des/config/workflowConfig.d.ts +0 -5
  131. package/dist/src/ser-des/config/workflowConfig.d.ts.map +0 -1
  132. package/dist/src/ser-des/config/workflowConfig.js +0 -129
  133. package/dist/src/ser-des/config/workflowConfig.js.map +0 -1
  134. package/dist/src/ser-des/config/workflowStage.d.ts +0 -5
  135. package/dist/src/ser-des/config/workflowStage.d.ts.map +0 -1
  136. package/dist/src/ser-des/config/workflowStage.js +0 -96
  137. package/dist/src/ser-des/config/workflowStage.js.map +0 -1
  138. package/dist/src/types/Storyline.d.ts +0 -62
  139. package/dist/src/types/Storyline.d.ts.map +0 -1
  140. package/dist/src/types/Storyline.js +0 -51
  141. package/dist/src/types/Storyline.js.map +0 -1
package/dist/src/check.js CHANGED
@@ -360,17 +360,6 @@
360
360
  // part_annex register's declaration shape — letters unique per
361
361
  // package, the source provenance required; the obligation
362
362
  // vocabulary is parse-enforced upstream
363
- // C129 storyline-shape (smart TODO.roadmap/40 batch 4): the demo-seed
364
- // shapes — the id_prefix pattern (register-free), party →
365
- // demo_world participant seeds (gated), record stores → entity-
366
- // class stores (gated), in-construct record cross-references
367
- // (ungated); field-level discipline stays app-side
368
- // C144–C146 the license facets (smart TODO.external-refs/04; the
369
- // entitlement facet + catalog): the catalog-key shape
370
- // (^std:[a-z0-9-]+$), at most one license_key per package (the
371
- // parser's earlier declarations surface here), and the
372
- // license_holder required with the key — a package without a key
373
- // is public content and every leg keys on the key's presence
374
363
  //
375
364
  // Levels (TODO.roadmap/17): the DEFAULT level runs the normal-level
376
365
  // rules at their catalog severities. --audit additionally runs the
@@ -397,6 +386,10 @@
397
386
  Object.defineProperty(exports, "__esModule", { value: true });
398
387
  exports.checkPackage = checkPackage;
399
388
  exports.checkManifestResolution = checkManifestResolution;
389
+ exports.checkArtifactContracts = checkArtifactContracts;
390
+ exports.checkTypedPayloads = checkTypedPayloads;
391
+ exports.checkAttestations = checkAttestations;
392
+ exports.checkClosedDerivations = checkClosedDerivations;
400
393
  exports.checkSpellingCodes = checkSpellingCodes;
401
394
  exports.checkDeclaredPredicates = checkDeclaredPredicates;
402
395
  exports.checkTermAliases = checkTermAliases;
@@ -421,6 +414,7 @@ const FormulasUsed_1 = require("./types/FormulasUsed");
421
414
  const tokenize_1 = require("./ser-des/tokenize");
422
415
  const operational_state_1 = require("./operational-state");
423
416
  const check_rules_1 = require("./check-rules");
417
+ const instance_resolution_1 = require("./instance-resolution");
424
418
  const check_allowlist_1 = require("./check-allowlist");
425
419
  const text_coverage_1 = require("./text-coverage");
426
420
  const spelling_1 = require("./spelling");
@@ -485,7 +479,12 @@ function checkPackage(dir, options = {}) {
485
479
  });
486
480
  standard = loaded.standard;
487
481
  for (const i of loaded.issues) {
488
- if (i.code === 'provides-unconsumed') {
482
+ // The deprecation report (phase 2): the parse-time issue maps to
483
+ // its rule.
484
+ if (i.code === 'construct-deprecated') {
485
+ warn('C154', i.message);
486
+ }
487
+ else if (i.code === 'provides-unconsumed') {
489
488
  warn('C30', i.message);
490
489
  }
491
490
  else if (i.code === 'extends-deprecated') {
@@ -540,11 +539,20 @@ function checkPackage(dir, options = {}) {
540
539
  issues.push(...checkReferences(standard));
541
540
  // ── C116/C117: the verdict chain (MN 114 v3.2, clause 11.3) ────────
542
541
  issues.push(...checkVerdictChains(standard));
542
+ // ── C149: the closed-derivation rule (the typed kernel, R3) ────────
543
+ issues.push(...checkClosedDerivations(standard));
544
+ // ── C150/C151: the attestation's references and claim lineage ──────
545
+ issues.push(...checkAttestations(standard));
546
+ // ── C152: the typed payload rule (the typed kernel, R2) ────────────
547
+ issues.push(...checkTypedPayloads(standard));
548
+ // ── C153: the contract-derivation facet (the typed kernel, clause 10)
549
+ issues.push(...checkArtifactContracts(standard));
543
550
  const reqIds = new Set((standard.requirements ?? []).map((r) => r.id));
544
551
  const testIds = new Set((standard.conformanceTests ?? []).map((t) => t.id));
545
552
  const attrIds = new Set((standard.attributeDefinitions ?? []).map((a) => a.id));
553
+ const roleIds = new Set((standard.roles ?? []).map(r => r.id));
546
554
  const behaviorIds = new Set((standard.behaviors ?? []).map((b) => b.id));
547
- const subjectIds = new Set((standard.subjects ?? []).map((s) => s.id));
555
+ const subjectIds = new Set((standard.subjects ?? []).map(s => s.id));
548
556
  // Observables live in the symbols registry, not the attribute layer —
549
557
  // binds_to / limit.uses may reference them (e.g. sample.test_context
550
558
  // quantities that are measured test outputs).
@@ -1157,7 +1165,9 @@ function checkPackage(dir, options = {}) {
1157
1165
  // C45 — artifact-def-contract: the content contract is well-formed.
1158
1166
  for (const d of standard.artifactDefinitions ?? []) {
1159
1167
  const c = d.contentContract;
1160
- if (c.fields.length === 0) {
1168
+ // A derived contract (contract_from, C153) is the referenced
1169
+ // promise set's projection — its fields are the set's.
1170
+ if (!d.contractFrom && c.fields.length === 0) {
1161
1171
  err('C45', `artifact definition ${d.id}: content contract declares no fields — a definition must contract SOME content (artifact-def-contract)`);
1162
1172
  }
1163
1173
  const seenFields = new Set();
@@ -1692,15 +1702,6 @@ function checkPackage(dir, options = {}) {
1692
1702
  }
1693
1703
  }
1694
1704
  }
1695
- for (const m of standard.processModels ?? []) {
1696
- const where = `process_model ${m.id}`;
1697
- for (const s of m.sequence ?? []) {
1698
- resolveProcessRef(where, 'sequence', s);
1699
- }
1700
- for (const r of m.registers ?? []) {
1701
- resolveOrganAp(`${where} register ${r.id}`, 'maintainer', r.maintainer);
1702
- }
1703
- }
1704
1705
  }
1705
1706
  // ── C84: constraint-shape (TODO.roadmap/51 — BUG.R60-SSOT gap 7) ─────
1706
1707
  // The subject-intrinsic constraint's DECLARATION shape — the kernel
@@ -2034,7 +2035,6 @@ function checkPackage(dir, options = {}) {
2034
2035
  // escalation ACTIONS are runtime — the smart app's monitor service owns
2035
2036
  // them; these rules guarantee the declarations the runtime needs.
2036
2037
  {
2037
- const roleIds = new Set((standard.roles ?? []).map(r => r.id));
2038
2038
  const KNOWN_OUTCOMES = new Set(Monitor_1.MONITOR_OUTCOMES);
2039
2039
  const KNOWN_ACTIONS = new Set(Monitor_1.MONITOR_ESCALATION_ACTIONS);
2040
2040
  const KNOWN_STREAMS = new Set(Monitor_1.MONITOR_STREAMS);
@@ -2338,6 +2338,7 @@ function checkPackage(dir, options = {}) {
2338
2338
  };
2339
2339
  const instrumentIds = new Set((standard.instruments ?? []).map(i => i.id));
2340
2340
  const instanceIds = new Set((standard.instances ?? []).map(i => i.id));
2341
+ const dataclassIds = new Set((standard.dataclasses ?? []).map(c => c.id));
2341
2342
  const attrScopes = new Map((standard.attributeDefinitions ?? []).map(a => [a.id, a.scope]));
2342
2343
  // Classification-dimension scopes, by every spelling a classification
2343
2344
  // map may use: the instrument dimension id, the is_dimension attribute
@@ -2431,9 +2432,53 @@ function checkPackage(dir, options = {}) {
2431
2432
  }
2432
2433
  }
2433
2434
  // C20 — instance-of-resolves: the `of` reference names a declared
2434
- // subject (v3) or instrument (v2) definition.
2435
- if (!subjectIds.has(inst.of) && !instrumentIds.has(inst.of)) {
2436
- err('C20', `instance ${inst.id}: of "${inst.of}" is not a declared subject or instrument (instance-of-resolves)`);
2435
+ // subject (v3), instrument (v2), or ANOTHER INSTANCE (the power-type
2436
+ // discipline — every instance can serve as the definition of further
2437
+ // instantiation).
2438
+ if (!subjectIds.has(inst.of) &&
2439
+ !instrumentIds.has(inst.of) &&
2440
+ !instanceIds.has(inst.of) &&
2441
+ !dataclassIds.has(inst.of)) {
2442
+ err('C20', `instance ${inst.id}: of "${inst.of}" is not a declared subject, instrument, class, or instance (instance-of-resolves)`);
2443
+ }
2444
+ // C155 — power-type-chain (coherence leg): an instance whose `of`
2445
+ // names another instance carries NO upward subject-chain link — the
2446
+ // two are one pattern's two spellings, and mixing them makes the
2447
+ // delegation walk ambiguous.
2448
+ if (instanceIds.has(inst.of)) {
2449
+ const links = ['model', 'group', 'family'].filter(k => inst[k]);
2450
+ if (links.length > 0) {
2451
+ err('C155', `instance ${inst.id}: of "${inst.of}" names an instance and the instance carries an upward ${links.join('/')} link — the power-type chain and the subject-chain profile do not mix (power-type-chain)`);
2452
+ }
2453
+ }
2454
+ }
2455
+ // C155 — power-type-chain (acyclicity leg): the MIXED delegation
2456
+ // graph — of-chains to instances AND the prototype hop (an `of` to a
2457
+ // class whose extends names an instance) — is acyclic: a cycle over
2458
+ // these edges would make the delegation walk non-terminating, and a
2459
+ // class instantiating its own prototype (`instance x of C` while
2460
+ // `C extends { x }`) is the shapes' own mutual recursion.
2461
+ const delegation = (0, instance_resolution_1.delegationSuccessors)(standard);
2462
+ const ofAdj = new Map();
2463
+ for (const [from, to] of delegation) {
2464
+ if (instanceIds.has(to)) {
2465
+ ofAdj.set(from, [to]);
2466
+ }
2467
+ }
2468
+ const ofCycle = findCycle(ofAdj);
2469
+ if (ofCycle) {
2470
+ err('C155', `the power-type of-chain is cyclic: ${ofCycle.join(' → ')} (power-type-chain)`);
2471
+ }
2472
+ // C156 — class-extends-resolves: a class's extends names a declared
2473
+ // class or a declared INSTANCE (the definition side of the power-type
2474
+ // discipline — a class extending an instance specializes that
2475
+ // individual). Successfully merged links clear at resolve time, so a
2476
+ // surviving link is exactly an unresolved one.
2477
+ for (const c of standard.dataclasses ?? []) {
2478
+ if (c.extends &&
2479
+ !dataclassIds.has(c.extends) &&
2480
+ !instanceIds.has(c.extends)) {
2481
+ warn('C156', `class ${c.id}: extends "${c.extends}" is not a declared class or instance (class-extends-resolves)`);
2437
2482
  }
2438
2483
  }
2439
2484
  // C19 — chain-acyclic (integrity part 2): the resolved link graph is
@@ -3171,6 +3216,97 @@ function checkPackage(dir, options = {}) {
3171
3216
  }
3172
3217
  }
3173
3218
  }
3219
+ // ── C147–C148: the executable test programs (smart TODO.twin-demo/03 —
3220
+ // the model-drive bindings) ──
3221
+ // The conformance test's `preparation { … }` / `stimulus { … }` facets
3222
+ // are the machine-executable half of the test declaration: the
3223
+ // preparation program (warm-up, preload discipline, zero check) and the
3224
+ // ordered measurement program (drive, argument expressions over the
3225
+ // subject's declared parameters, hold, the read's freshness bound, the
3226
+ // per-point acceptance reference). C147 test-program-shape: every
3227
+ // program entry's order is a positive integer unique in the program; a
3228
+ // preparation step carries a prose action AND/OR a drive reference; a
3229
+ // step's verify block carries read AND tolerance; a stimulus point
3230
+ // carries a drive and an acceptance reference. C148
3231
+ // test-program-duration: a hold / fresh_within value parses as a
3232
+ // duration window (the serve contract's vocabulary — shorthand 30min or
3233
+ // ISO 8601 fixed-length PT30M), and a stimulus point declares
3234
+ // fresh_within (§14.12's no-stale-semantics doctrine: a measurement
3235
+ // point's read carries its freshness bound, like C63's live bindings).
3236
+ // Drive/acceptance/tolerance REFERENCE RESOLUTION (the world-vocabulary
3237
+ // operation names, the requirement nodes) is the consumer-side
3238
+ // conformance leg's job — the kernel checks syntax/shape only (the
3239
+ // C92/C93 vs R39 split). Packages without test programs are untouched —
3240
+ // the loops are empty.
3241
+ {
3242
+ for (const ct of standard.conformanceTests ?? []) {
3243
+ const prep = ct.preparation;
3244
+ if (prep) {
3245
+ const orders = new Set();
3246
+ for (const step of prep.entries) {
3247
+ const at = step.order !== null
3248
+ ? `preparation step ${step.order}`
3249
+ : 'a preparation step';
3250
+ if (step.order === null ||
3251
+ !Number.isInteger(step.order) ||
3252
+ step.order < 1) {
3253
+ err('C147', `conformance_test ${ct.id}: ${at} declares no positive-integer order (got ${step.order === null ? 'none' : step.order}) — the step order is a positive integer (test-program-shape)`);
3254
+ }
3255
+ else if (orders.has(step.order)) {
3256
+ err('C147', `conformance_test ${ct.id}: preparation step order ${step.order} is declared twice — the order is unique within the program (test-program-shape)`);
3257
+ }
3258
+ else {
3259
+ orders.add(step.order);
3260
+ }
3261
+ if (step.action === '' && step.drive === '') {
3262
+ err('C147', `conformance_test ${ct.id}: ${at} declares neither action nor drive — a preparation step carries a prose action AND/OR a drive reference (test-program-shape)`);
3263
+ }
3264
+ if (step.verify &&
3265
+ (step.verify.read === '' || step.verify.tolerance === '')) {
3266
+ err('C147', `conformance_test ${ct.id}: ${at} declares a verify block without ${step.verify.read === '' ? 'read' : 'tolerance'} — the verification names what to read AND the tolerance reference (test-program-shape)`);
3267
+ }
3268
+ if (step.hold !== '' && (0, time_1.parseFreshnessWindow)(step.hold) === null) {
3269
+ err('C148', `conformance_test ${ct.id}: ${at} hold "${step.hold}" is not a parseable duration (shorthand 500ms/5s/1min/1h/1d or ISO 8601 with fixed-length components, e.g. PT30M) (test-program-duration)`);
3270
+ }
3271
+ }
3272
+ }
3273
+ const stim = ct.stimulus;
3274
+ if (stim) {
3275
+ const orders = new Set();
3276
+ for (const point of stim.entries) {
3277
+ const at = point.order !== null
3278
+ ? `stimulus point ${point.order}`
3279
+ : 'a stimulus point';
3280
+ if (point.order === null ||
3281
+ !Number.isInteger(point.order) ||
3282
+ point.order < 1) {
3283
+ err('C147', `conformance_test ${ct.id}: ${at} declares no positive-integer order (got ${point.order === null ? 'none' : point.order}) — the point order is a positive integer (test-program-shape)`);
3284
+ }
3285
+ else if (orders.has(point.order)) {
3286
+ err('C147', `conformance_test ${ct.id}: stimulus point order ${point.order} is declared twice — the order is unique within the program (test-program-shape)`);
3287
+ }
3288
+ else {
3289
+ orders.add(point.order);
3290
+ }
3291
+ if (point.drive === '') {
3292
+ err('C147', `conformance_test ${ct.id}: ${at} declares no drive — a stimulus point names the drive operation (test-program-shape)`);
3293
+ }
3294
+ if (point.acceptance === '') {
3295
+ err('C147', `conformance_test ${ct.id}: ${at} declares no acceptance — a stimulus point names the MPE/requirement node it is judged against (test-program-shape)`);
3296
+ }
3297
+ if (point.hold !== '' && (0, time_1.parseFreshnessWindow)(point.hold) === null) {
3298
+ err('C148', `conformance_test ${ct.id}: ${at} hold "${point.hold}" is not a parseable duration (shorthand 500ms/5s/1min/1h/1d or ISO 8601 with fixed-length components, e.g. PT30M) (test-program-duration)`);
3299
+ }
3300
+ if (point.freshWithin === '') {
3301
+ err('C148', `conformance_test ${ct.id}: ${at} declares no fresh_within — a measurement point's read carries its freshness bound (§14.12: no stale semantics) (test-program-duration)`);
3302
+ }
3303
+ else if ((0, time_1.parseFreshnessWindow)(point.freshWithin) === null) {
3304
+ err('C148', `conformance_test ${ct.id}: ${at} fresh_within "${point.freshWithin}" is not a parseable freshness window (shorthand 500ms/5s/1min/1h/1d or ISO 8601 with fixed-length components, e.g. PT5S) (test-program-duration)`);
3305
+ }
3306
+ }
3307
+ }
3308
+ }
3309
+ }
3174
3310
  // ── C94: the per-test evaluation-formula traces (smart gap-close E11,
3175
3311
  // analysis/architecture-gaps-2026-07.md; the smart contract
3176
3312
  // data/schemas/formulas-used.yaml) ──
@@ -3598,68 +3734,6 @@ function checkPackage(dir, options = {}) {
3598
3734
  }
3599
3735
  }
3600
3736
  }
3601
- // ── C129: storyline-shape (smart TODO.roadmap/40 batch 4; the ──────
3602
- // packages-as-SSOT epic) ────────────────────────────────────────────
3603
- // The demo-seed shapes: the id_prefix pattern (the register-free shape
3604
- // leg); the party laboratory/authority resolve against the package's
3605
- // participant seeds WHEN a demo_world with participants is in scope
3606
- // (per-register gating, the C58 doctrine); the record stores resolve
3607
- // against the declared entity-class stores WHEN that register is in
3608
- // scope; and the in-construct cross-references resolve — a record
3609
- // field keyed by a sibling record's store names a sibling record of
3610
- // that store (in-construct, ungated). The field-level discipline (the
3611
- // schema's per-slot required fields) stays app-side.
3612
- {
3613
- const participantSeeds = new Set();
3614
- for (const w of standard.demoWorlds ?? []) {
3615
- for (const section of w.participants ?? []) {
3616
- for (const e of section.entries ?? []) {
3617
- participantSeeds.add(e.id);
3618
- }
3619
- }
3620
- }
3621
- const entityStores = new Set((standard.dataclasses ?? [])
3622
- .map(c => c.store)
3623
- .filter((s) => !!s));
3624
- for (const s of standard.storylines ?? []) {
3625
- const where = `storyline ${s.id}`;
3626
- if (s.idPrefix && !/^[a-z0-9-]+$/.test(s.idPrefix)) {
3627
- err('C129', `${where}: id_prefix "${s.idPrefix}" does not match ^[a-z0-9-]+$ — the id-minting prefix is a lowercase slug (storyline-shape)`);
3628
- }
3629
- if (participantSeeds.size > 0) {
3630
- for (const [slot, ref] of [
3631
- ['laboratory', s.party.laboratory],
3632
- ['authority', s.party.authority],
3633
- ]) {
3634
- if (ref && !participantSeeds.has(ref)) {
3635
- err('C129', `${where}: party ${slot} "${ref}" is not a declared participant seed (storyline-shape)`);
3636
- }
3637
- }
3638
- }
3639
- const siblingStores = new Map();
3640
- for (const r of s.records ?? []) {
3641
- if (entityStores.size > 0 && !entityStores.has(r.store)) {
3642
- err('C129', `${where}: record store "${r.store}" is not a declared entity-class store (storyline-shape)`);
3643
- }
3644
- let ids = siblingStores.get(r.store);
3645
- if (!ids) {
3646
- ids = new Set();
3647
- siblingStores.set(r.store, ids);
3648
- }
3649
- ids.add(r.id);
3650
- }
3651
- for (const r of s.records ?? []) {
3652
- for (const [key, value] of Object.entries(r.fields)) {
3653
- const targets = siblingStores.get(key);
3654
- if (targets !== undefined &&
3655
- !Array.isArray(value) &&
3656
- !targets.has(String(value))) {
3657
- err('C129', `${where}: record ${r.store} ${r.id}: ${key} "${String(value)}" is not a sibling ${key} record of this storyline (storyline-shape)`);
3658
- }
3659
- }
3660
- }
3661
- }
3662
- }
3663
3737
  // ── C130: identity-and-aspect-references (smart TODO.roadmap/40 ─────
3664
3738
  // batch 3; the packages-as-SSOT epic) ────────────────────────────────
3665
3739
  // The documentary identity slots + the qualitative aspect register
@@ -4030,7 +4104,6 @@ function checkPackage(dir, options = {}) {
4030
4104
  // (the R26 field-resolution precedent). The phase vocabulary is
4031
4105
  // parse-enforced upstream (no check leg).
4032
4106
  {
4033
- const roleIds = new Set((standard.roles ?? []).map(r => r.id));
4034
4107
  // Class ids may carry a namespace suffix (`Application#data`) while
4035
4108
  // the workflow's inputs/outputs spell the bare entity name — index
4036
4109
  // both spellings (the semantic-status leg's precedent).
@@ -4041,28 +4114,6 @@ function checkPackage(dir, options = {}) {
4041
4114
  dataclassIds.add(c.id.slice(0, c.id.indexOf('#')));
4042
4115
  }
4043
4116
  }
4044
- const CLEAN_TOKEN = /^[A-Za-z_][A-Za-z0-9_]*$/;
4045
- for (const w of standard.workflowConfigs ?? []) {
4046
- for (const s of w.steps ?? []) {
4047
- const where = `workflow_config ${w.id}: step ${s.id}`;
4048
- if (s.actor && roleIds.size > 0 && !roleIds.has(s.actor)) {
4049
- err('C137', `${where}: actor "${s.actor}" is not a declared role (workflow-config-references)`);
4050
- }
4051
- for (const [facet, list] of [
4052
- ['inputs', s.inputs],
4053
- ['outputs', s.outputs],
4054
- ]) {
4055
- for (const entry of list) {
4056
- if (!CLEAN_TOKEN.test(entry)) {
4057
- continue; // a composite documentary string — never resolved
4058
- }
4059
- if (dataclassIds.size > 0 && !dataclassIds.has(entry)) {
4060
- err('C137', `${where}: ${facet} entry "${entry}" is not a declared data class (workflow-config-references)`);
4061
- }
4062
- }
4063
- }
4064
- }
4065
- }
4066
4117
  }
4067
4118
  // ── C138: verification-pathway-references (smart TODO.roadmap/40 ───
4068
4119
  // batch 3; the packages-as-SSOT epic) ────────────────────────────────
@@ -4309,26 +4360,6 @@ function checkPackage(dir, options = {}) {
4309
4360
  }
4310
4361
  // The workflow_stage members-resolve legs (step 5d) — the events
4311
4362
  // stay documentary, never resolved.
4312
- const stageApprovalIds = new Set((standard.approvals ?? []).map(a => a.id));
4313
- const stageGatewayIds = new Set((standard.gateways ?? []).map(g => g.id));
4314
- for (const s of standard.workflowStages ?? []) {
4315
- const where = `workflow_stage ${s.id}`;
4316
- for (const el of s.elements ?? []) {
4317
- if (gwProcessIds.size > 0 && !gwProcessIds.has(el)) {
4318
- err('C142', `${where}: element "${el}" is not a declared process (gateway-edges-resolve)`);
4319
- }
4320
- }
4321
- for (const a of s.approvals ?? []) {
4322
- if (stageApprovalIds.size > 0 && !stageApprovalIds.has(a)) {
4323
- err('C142', `${where}: approval "${a}" is not a declared approval (gateway-edges-resolve)`);
4324
- }
4325
- }
4326
- for (const g of s.gateways ?? []) {
4327
- if (stageGatewayIds.size > 0 && !stageGatewayIds.has(g)) {
4328
- err('C142', `${where}: gateway "${g}" is not a declared gateway (gateway-edges-resolve)`);
4329
- }
4330
- }
4331
- }
4332
4363
  }
4333
4364
  // ── C143: approval-references-resolve (smart TODO.roadmap/40 batch ──
4334
4365
  // 5; the packages-as-SSOT epic) ─────────────────────────────────────
@@ -5582,6 +5613,362 @@ function textAddressKey(item, segment) {
5582
5613
  * (titular:source:target:identifying); a `zz-` user-assigned code
5583
5614
  * validates with a warning (user-assigned codes are not portable).
5584
5615
  */
5616
+ // ── C153: artifact-contract-derivation (the typed kernel, clause 10) ─
5617
+ // An artifact definition whose content is the projection of declared
5618
+ // claims declares contract_from; the referenced set resolves, and a
5619
+ // definition that restates inline fields while deriving its contract
5620
+ // is the restatement the rule exists to prevent.
5621
+ function checkArtifactContracts(standard) {
5622
+ const issues = [];
5623
+ const err = (check, message) => issues.push({ check, severity: 'error', message });
5624
+ const setIds = new Set();
5625
+ for (const ps of standard.promiseSets ?? []) {
5626
+ setIds.add(ps.id);
5627
+ setIds.add(`${ps.id}.promise_set`);
5628
+ }
5629
+ for (const s of standard.subjects ?? []) {
5630
+ if ((s.is?.promises ?? []).length > 0) {
5631
+ setIds.add(s.id);
5632
+ setIds.add(`${s.id}.promise_set`);
5633
+ }
5634
+ }
5635
+ for (const d of standard.artifactDefinitions ?? []) {
5636
+ if (!d.contractFrom) {
5637
+ continue;
5638
+ }
5639
+ if (!setIds.has(d.contractFrom)) {
5640
+ err('C153', `artifact_definition ${d.id}: contract_from "${d.contractFrom}" does not resolve to a declared promise set (artifact-contract-derivation)`);
5641
+ }
5642
+ const c = d.contentContract;
5643
+ if (c.fields.length > 0 || c.structure || c.media.length > 0) {
5644
+ err('C153', `artifact_definition ${d.id}: declares both contract_from and inline contract fields — the derived contract is the referenced set's projection, never a restatement (artifact-contract-derivation)`);
5645
+ }
5646
+ }
5647
+ return issues;
5648
+ }
5649
+ // ── C152: payload-typed-by-class (the typed kernel R2, clause 10) ────
5650
+ // A dimension that declares payload_class types every value's
5651
+ // definition: the class resolves, every payload field is a field of
5652
+ // that class, and every value carries its definition — a value IS its
5653
+ // definition, not a symbol pointing at a table. The attribute-side
5654
+ // bridge resolves too: an attribute_definition's class reference names
5655
+ // a declared class.
5656
+ function checkTypedPayloads(standard) {
5657
+ const issues = [];
5658
+ const err = (check, message) => issues.push({ check, severity: 'error', message });
5659
+ const classFields = new Map();
5660
+ for (const c of standard.dataclasses ?? []) {
5661
+ classFields.set(c.id, new Set(c.attributes.map(a => a.id)));
5662
+ }
5663
+ // The power-type discipline reaches the typing bridge: an INSTANCE
5664
+ // is a class, and its shape is the fields of the class its `of`
5665
+ // chain terminates at (the prototype hop's mirror — an individual
5666
+ // typed by its definition's fields).
5667
+ const instanceShape = (id) => {
5668
+ const inst = (standard.instances ?? []).find(i => i.id === id);
5669
+ if (!inst) {
5670
+ return undefined;
5671
+ }
5672
+ let cur = inst;
5673
+ const seen = new Set([cur.id]);
5674
+ while (cur.of) {
5675
+ const next = (standard.instances ?? []).find(i => i.id === cur.of);
5676
+ if (!next || seen.has(next.id)) {
5677
+ break;
5678
+ }
5679
+ seen.add(next.id);
5680
+ cur = next;
5681
+ }
5682
+ return cur.of !== '' ? classFields.get(cur.of) : undefined;
5683
+ };
5684
+ const typedFields = (id) => classFields.get(id) ?? instanceShape(id);
5685
+ const checkDim = (owner, d) => {
5686
+ if (!d.payloadClass) {
5687
+ return;
5688
+ }
5689
+ const fields = typedFields(d.payloadClass);
5690
+ if (!fields) {
5691
+ err('C152', `${owner} dimension ${d.id}: payload_class "${d.payloadClass}" is not a declared class (payload-typed-by-class)`);
5692
+ return;
5693
+ }
5694
+ for (const v of d.values) {
5695
+ const keys = Object.keys(v.payload ?? {});
5696
+ if (keys.length === 0) {
5697
+ err('C152', `${owner} dimension ${d.id}: value ${v.id} carries no payload — a value of a payload_class dimension IS its definition (payload-typed-by-class)`);
5698
+ continue;
5699
+ }
5700
+ for (const key of keys) {
5701
+ if (!fields.has(key)) {
5702
+ err('C152', `${owner} dimension ${d.id}: value ${v.id} payload field "${key}" is not a field of class "${d.payloadClass}" (payload-typed-by-class)`);
5703
+ }
5704
+ }
5705
+ }
5706
+ };
5707
+ for (const inst of standard.instruments ?? []) {
5708
+ for (const d of inst.dimensions ?? []) {
5709
+ checkDim(`instrument ${inst.id}`, d);
5710
+ }
5711
+ }
5712
+ for (const d of standard.dimensions ?? []) {
5713
+ checkDim('top-level', d);
5714
+ }
5715
+ for (const a of standard.attributeDefinitions ?? []) {
5716
+ if (a.classRef && !typedFields(a.classRef)) {
5717
+ err('C152', `attribute_definition ${a.id}: class "${a.classRef}" is not a declared class (payload-typed-by-class)`);
5718
+ }
5719
+ }
5720
+ return issues;
5721
+ }
5722
+ // ── C150/C151: the attestation's references and claim lineage ───────
5723
+ // C150 attestation-references-resolve: the subject is a declared
5724
+ // instance, the promise set resolves in `Subject.promise_set_id` form,
5725
+ // every basis entry carries kind and id, and every claim's declaration
5726
+ // and verdict references are stated. C151
5727
+ // attestation-claim-lineage: every promise the certificate prints as
5728
+ // mandatory must be carried by a claim — a mandatory characteristic is
5729
+ // carried by lineage, never restated (clause 19).
5730
+ function checkAttestations(standard) {
5731
+ const issues = [];
5732
+ const err = (check, message) => issues.push({ check, severity: 'error', message });
5733
+ const roleIds = new Set((standard.roles ?? []).map(r => r.id));
5734
+ const instanceIds = new Set((standard.instances ?? []).map(i => i.id));
5735
+ const verdictIds = new Set((standard.verdicts ?? []).map(v => v.id));
5736
+ const setViews = new Map();
5737
+ for (const ps of standard.promiseSets ?? []) {
5738
+ setViews.set(ps.id, { promises: ps.promises });
5739
+ setViews.set(`${ps.id}.promise_set`, { promises: ps.promises });
5740
+ }
5741
+ for (const s of standard.subjects ?? []) {
5742
+ if ((s.is?.promises ?? []).length > 0) {
5743
+ setViews.set(s.id, { promises: s.is.promises });
5744
+ }
5745
+ }
5746
+ const resolveSet = (ref) => setViews.get(ref) ??
5747
+ setViews.get(ref.split('.').slice(1).join('.')) ??
5748
+ null;
5749
+ const mandatoryOf = (view) => {
5750
+ const out = new Set();
5751
+ for (const p of view.promises) {
5752
+ if (p.certificate?.obligation === 'mandatory') {
5753
+ out.add(p.id);
5754
+ }
5755
+ }
5756
+ return out;
5757
+ };
5758
+ for (const a of standard.attestations ?? []) {
5759
+ if (!a.subject) {
5760
+ err('C150', `attestation ${a.id}: no subject facet (attestation-references-resolve)`);
5761
+ }
5762
+ else if (!instanceIds.has(a.subject)) {
5763
+ err('C150', `attestation ${a.id}: subject "${a.subject}" is not a declared instance (attestation-references-resolve)`);
5764
+ }
5765
+ if (!a.promises) {
5766
+ err('C150', `attestation ${a.id}: no promises facet (attestation-references-resolve)`);
5767
+ }
5768
+ const set = a.promises ? resolveSet(a.promises) : null;
5769
+ if (a.promises && !set) {
5770
+ err('C150', `attestation ${a.id}: promises "${a.promises}" does not resolve to a declared promise set (attestation-references-resolve)`);
5771
+ }
5772
+ for (const b of a.basis) {
5773
+ if (!b.kind || !b.id) {
5774
+ err('C150', `attestation ${a.id}: a basis entry needs an evidence kind and an id (attestation-references-resolve)`);
5775
+ }
5776
+ }
5777
+ if (a.authority.role &&
5778
+ roleIds.size > 0 &&
5779
+ !roleIds.has(a.authority.role)) {
5780
+ err('C150', `attestation ${a.id}: authority role "${a.authority.role}" is not a declared role (attestation-references-resolve)`);
5781
+ }
5782
+ const carried = new Set(a.claims.map(c => c.promise));
5783
+ for (const c of a.claims) {
5784
+ if (set && !set.promises.some(p => p.id === c.promise)) {
5785
+ err('C150', `attestation ${a.id}: claim "${c.promise}" is not a promise of "${a.promises}" (attestation-references-resolve)`);
5786
+ }
5787
+ if (!c.declared) {
5788
+ err('C151', `attestation ${a.id}: claim "${c.promise}" carries no declaration reference (attestation-claim-lineage)`);
5789
+ }
5790
+ if (!c.validatedBy) {
5791
+ err('C151', `attestation ${a.id}: claim "${c.promise}" carries no verdict reference (attestation-claim-lineage)`);
5792
+ }
5793
+ else if (verdictIds.size > 0 &&
5794
+ !c.validatedBy.startsWith('/') &&
5795
+ !verdictIds.has(c.validatedBy)) {
5796
+ // Bare ids resolve inside the package; slash ids are the
5797
+ // requirement-anatomy path form and resolve in the linker.
5798
+ err('C150', `attestation ${a.id}: claim "${c.promise}" names verdict "${c.validatedBy}", which is not declared (attestation-references-resolve)`);
5799
+ }
5800
+ }
5801
+ if (set) {
5802
+ for (const pid of mandatoryOf(set)) {
5803
+ if (!carried.has(pid)) {
5804
+ err('C151', `attestation ${a.id}: the mandatory promise "${pid}" of "${a.promises}" is carried by no claim — a mandatory characteristic is carried by lineage, never restated (attestation-claim-lineage)`);
5805
+ }
5806
+ }
5807
+ }
5808
+ }
5809
+ return issues;
5810
+ }
5811
+ // ── C149: closed-derivation-references-declared ─────────────────────
5812
+ // The typed kernel's rule R3: a derivation expression names only
5813
+ // declared definitions — declared calculations, tables, symbols,
5814
+ // measurements/variables, data classes, enums, and attributes of the
5815
+ // model. The checker rejects an expression that references anything
5816
+ // else and names the missing declaration, so the undefined-reference
5817
+ // defect class cannot be authored.
5818
+ const CLOSED_DERIVATION_STOP = new Set([
5819
+ 'self',
5820
+ 'p',
5821
+ 'ocl',
5822
+ 'true',
5823
+ 'false',
5824
+ 'null',
5825
+ 'undefined',
5826
+ // OCL navigation and collection operators
5827
+ 'and',
5828
+ 'or',
5829
+ 'not',
5830
+ 'implies',
5831
+ 'if',
5832
+ 'then',
5833
+ 'else',
5834
+ 'endif',
5835
+ 'xor',
5836
+ 'let',
5837
+ 'in',
5838
+ 'exists',
5839
+ 'forAll',
5840
+ 'select',
5841
+ 'reject',
5842
+ 'collect',
5843
+ 'isEmpty',
5844
+ 'size',
5845
+ 'sum',
5846
+ 'min',
5847
+ 'max',
5848
+ 'abs',
5849
+ 'round',
5850
+ 'floor',
5851
+ 'sqrt',
5852
+ 'includes',
5853
+ 'excludes',
5854
+ 'includesAll',
5855
+ 'excludesAll',
5856
+ 'count',
5857
+ 'first',
5858
+ 'last',
5859
+ 'asSet',
5860
+ 'asOrderedSet',
5861
+ 'asSequence',
5862
+ 'asBag',
5863
+ 'sortedBy',
5864
+ 'iterate',
5865
+ 'oclIsUndefined',
5866
+ 'oclIsKindOf',
5867
+ 'oclIsTypeOf',
5868
+ 'allInstances',
5869
+ // table-lookup engine vocabulary (the lookup facet names these)
5870
+ 'lookup',
5871
+ 'profile',
5872
+ // OCL collection statistics and arithmetic
5873
+ 'average',
5874
+ 'mean',
5875
+ 'size',
5876
+ 'div',
5877
+ 'mod',
5878
+ 'collect',
5879
+ ]);
5880
+ // Attribute navigation: `head.field…` resolves when the HEAD is a
5881
+ // declared name (an input, a calculation, an attribute, a class-typed
5882
+ // value); the tail is navigation over that head's type, judged by the
5883
+ // class layer (C152), not by the closed-derivation rule.
5884
+ const CLOSED_DERIVATION_PATH_RE = /\b[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)+/g;
5885
+ const CLOSED_DERIVATION_IDENT_RE = /[A-Za-z_][A-Za-z0-9_]*/g;
5886
+ function checkClosedDerivations(standard) {
5887
+ const issues = [];
5888
+ const err = (check, message) => issues.push({ check, severity: 'error', message });
5889
+ // The declared universe every derivation may reference.
5890
+ const declared = new Set();
5891
+ for (const c of standard.calculations ?? []) {
5892
+ declared.add(c.id);
5893
+ declared.add(c.name);
5894
+ }
5895
+ for (const tb of standard.tables ?? []) {
5896
+ declared.add(tb.id);
5897
+ }
5898
+ for (const s of standard.symbols ?? []) {
5899
+ declared.add(s.id);
5900
+ }
5901
+ for (const v of standard.variables ?? []) {
5902
+ declared.add(v.id);
5903
+ }
5904
+ for (const a of standard.attributeDefinitions ?? []) {
5905
+ declared.add(a.id);
5906
+ }
5907
+ for (const e of standard.enums ?? []) {
5908
+ declared.add(e.id);
5909
+ }
5910
+ for (const d of standard.dataclasses ?? []) {
5911
+ declared.add(d.id);
5912
+ }
5913
+ for (const inst of standard.instruments ?? []) {
5914
+ for (const d of inst.dimensions ?? []) {
5915
+ declared.add(d.id);
5916
+ }
5917
+ }
5918
+ for (const d of standard.dimensions ?? []) {
5919
+ declared.add(d.id);
5920
+ }
5921
+ // A calculation's own output name is declared too (requirements bind
5922
+ // it), and so is every input of every calculation.
5923
+ for (const c of standard.calculations ?? []) {
5924
+ if (c.output?.name) {
5925
+ declared.add(c.output.name);
5926
+ }
5927
+ for (const i of c.inputs ?? []) {
5928
+ declared.add(i.name);
5929
+ }
5930
+ }
5931
+ const localsOf = (extra) => new Set([...declared, ...extra]);
5932
+ const checkExpression = (expr, locals, where) => {
5933
+ if (!expr) {
5934
+ return;
5935
+ }
5936
+ // Values and lambdas are not references: single-quoted literals are
5937
+ // values; an identifier before `|` is a lambda parameter.
5938
+ const noLiterals = expr.replace(/'[^']*'/g, ' ');
5939
+ const noLambdas = noLiterals.replace(/\b([A-Za-z_][A-Za-z0-9_]*)\s*\|/g, ' ');
5940
+ const stripped = noLambdas.replace(CLOSED_DERIVATION_PATH_RE, ' ');
5941
+ const seen = new Set();
5942
+ for (const raw of stripped.match(CLOSED_DERIVATION_IDENT_RE) ?? []) {
5943
+ if (CLOSED_DERIVATION_STOP.has(raw) ||
5944
+ locals.has(raw) ||
5945
+ seen.has(raw) ||
5946
+ /^[a-z][0-9]?$/.test(raw)) {
5947
+ continue;
5948
+ }
5949
+ seen.add(raw);
5950
+ err('C149', `${where}: expression references "${raw}", which no declared calculation, table, symbol, measurement, or attribute defines (closed-derivation-references-declared)`);
5951
+ }
5952
+ };
5953
+ for (const a of standard.attributeDefinitions ?? []) {
5954
+ checkExpression(a.derived, localsOf([]), `attribute_definition ${a.id}`);
5955
+ }
5956
+ for (const c of standard.calculations ?? []) {
5957
+ // The normative body is the declared expression; engine variants
5958
+ // are consumer code and outside the language's semantics.
5959
+ const inputs = (c.inputs ?? []).map(i => i.name);
5960
+ checkExpression(c.expression, localsOf(inputs), `calculation ${c.id}`);
5961
+ }
5962
+ for (const s of standard.symbols ?? []) {
5963
+ if (s.formula?.expression) {
5964
+ checkExpression(s.formula.expression, localsOf(s.formula.inputs ?? []), `symbol ${s.id}`);
5965
+ }
5966
+ }
5967
+ for (const v of standard.verdicts ?? []) {
5968
+ checkExpression(v.derive, localsOf(v.inputs ?? []), `verdict ${v.id}`);
5969
+ }
5970
+ return issues;
5971
+ }
5585
5972
  function checkSpellingCodes(standard) {
5586
5973
  const issues = [];
5587
5974
  const err = (check, message) => issues.push({ check, severity: 'error', message });