@primmel/primmel 1.11.1 → 1.13.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 (104) hide show
  1. package/dist/src/check-rules.d.ts.map +1 -1
  2. package/dist/src/check-rules.js +71 -0
  3. package/dist/src/check-rules.js.map +1 -1
  4. package/dist/src/check.d.ts.map +1 -1
  5. package/dist/src/check.js +457 -3
  6. package/dist/src/check.js.map +1 -1
  7. package/dist/src/model-diff.d.ts.map +1 -1
  8. package/dist/src/model-diff.js +22 -0
  9. package/dist/src/model-diff.js.map +1 -1
  10. package/dist/src/ser-des/config/calculation.d.ts.map +1 -1
  11. package/dist/src/ser-des/config/calculation.js +134 -18
  12. package/dist/src/ser-des/config/calculation.js.map +1 -1
  13. package/dist/src/ser-des/config/commonTestCondition.d.ts +5 -0
  14. package/dist/src/ser-des/config/commonTestCondition.d.ts.map +1 -0
  15. package/dist/src/ser-des/config/commonTestCondition.js +75 -0
  16. package/dist/src/ser-des/config/commonTestCondition.js.map +1 -0
  17. package/dist/src/ser-des/config/documentModule.d.ts +5 -0
  18. package/dist/src/ser-des/config/documentModule.d.ts.map +1 -0
  19. package/dist/src/ser-des/config/documentModule.js +109 -0
  20. package/dist/src/ser-des/config/documentModule.js.map +1 -0
  21. package/dist/src/ser-des/config/formulaNote.d.ts +5 -0
  22. package/dist/src/ser-des/config/formulaNote.d.ts.map +1 -0
  23. package/dist/src/ser-des/config/formulaNote.js +66 -0
  24. package/dist/src/ser-des/config/formulaNote.js.map +1 -0
  25. package/dist/src/ser-des/config/index.d.ts.map +1 -1
  26. package/dist/src/ser-des/config/index.js +98 -0
  27. package/dist/src/ser-des/config/index.js.map +1 -1
  28. package/dist/src/ser-des/config/informativeAnnex.d.ts +5 -0
  29. package/dist/src/ser-des/config/informativeAnnex.d.ts.map +1 -0
  30. package/dist/src/ser-des/config/informativeAnnex.js +185 -0
  31. package/dist/src/ser-des/config/informativeAnnex.js.map +1 -0
  32. package/dist/src/ser-des/config/partAnnex.d.ts +5 -0
  33. package/dist/src/ser-des/config/partAnnex.d.ts.map +1 -0
  34. package/dist/src/ser-des/config/partAnnex.js +91 -0
  35. package/dist/src/ser-des/config/partAnnex.js.map +1 -0
  36. package/dist/src/ser-des/config/process.d.ts.map +1 -1
  37. package/dist/src/ser-des/config/process.js +369 -3
  38. package/dist/src/ser-des/config/process.js.map +1 -1
  39. package/dist/src/ser-des/config/processModel.d.ts +13 -0
  40. package/dist/src/ser-des/config/processModel.d.ts.map +1 -0
  41. package/dist/src/ser-des/config/processModel.js +163 -0
  42. package/dist/src/ser-des/config/processModel.js.map +1 -0
  43. package/dist/src/ser-des/config/schemeType.d.ts +7 -0
  44. package/dist/src/ser-des/config/schemeType.d.ts.map +1 -0
  45. package/dist/src/ser-des/config/schemeType.js +232 -0
  46. package/dist/src/ser-des/config/schemeType.js.map +1 -0
  47. package/dist/src/ser-des/config/storyline.d.ts +7 -0
  48. package/dist/src/ser-des/config/storyline.d.ts.map +1 -0
  49. package/dist/src/ser-des/config/storyline.js +264 -0
  50. package/dist/src/ser-des/config/storyline.js.map +1 -0
  51. package/dist/src/ser-des/config/subject.d.ts.map +1 -1
  52. package/dist/src/ser-des/config/subject.js +93 -0
  53. package/dist/src/ser-des/config/subject.js.map +1 -1
  54. package/dist/src/ser-des/package.d.ts.map +1 -1
  55. package/dist/src/ser-des/package.js +36 -8
  56. package/dist/src/ser-des/package.js.map +1 -1
  57. package/dist/src/ser-des/parse.d.ts.map +1 -1
  58. package/dist/src/ser-des/parse.js +13 -0
  59. package/dist/src/ser-des/parse.js.map +1 -1
  60. package/dist/src/ser-des/types.d.ts +22 -0
  61. package/dist/src/ser-des/types.d.ts.map +1 -1
  62. package/dist/src/types/Calculation.d.ts +35 -0
  63. package/dist/src/types/Calculation.d.ts.map +1 -1
  64. package/dist/src/types/CommonTestCondition.d.ts +15 -0
  65. package/dist/src/types/CommonTestCondition.d.ts.map +1 -0
  66. package/dist/src/types/CommonTestCondition.js +22 -0
  67. package/dist/src/types/CommonTestCondition.js.map +1 -0
  68. package/dist/src/types/DocumentModule.d.ts +27 -0
  69. package/dist/src/types/DocumentModule.d.ts.map +1 -0
  70. package/dist/src/types/DocumentModule.js +38 -0
  71. package/dist/src/types/DocumentModule.js.map +1 -0
  72. package/dist/src/types/InformativeAnnex.d.ts +44 -0
  73. package/dist/src/types/InformativeAnnex.d.ts.map +1 -0
  74. package/dist/src/types/InformativeAnnex.js +35 -0
  75. package/dist/src/types/InformativeAnnex.js.map +1 -0
  76. package/dist/src/types/Package.d.ts +7 -3
  77. package/dist/src/types/Package.d.ts.map +1 -1
  78. package/dist/src/types/PartAnnex.d.ts +18 -0
  79. package/dist/src/types/PartAnnex.d.ts.map +1 -0
  80. package/dist/src/types/PartAnnex.js +29 -0
  81. package/dist/src/types/PartAnnex.js.map +1 -0
  82. package/dist/src/types/ProcessModel.d.ts +27 -0
  83. package/dist/src/types/ProcessModel.d.ts.map +1 -0
  84. package/dist/src/types/ProcessModel.js +31 -0
  85. package/dist/src/types/ProcessModel.js.map +1 -0
  86. package/dist/src/types/SchemeType.d.ts +51 -0
  87. package/dist/src/types/SchemeType.d.ts.map +1 -0
  88. package/dist/src/types/SchemeType.js +41 -0
  89. package/dist/src/types/SchemeType.js.map +1 -0
  90. package/dist/src/types/Standard.d.ts +36 -0
  91. package/dist/src/types/Standard.d.ts.map +1 -1
  92. package/dist/src/types/Storyline.d.ts +62 -0
  93. package/dist/src/types/Storyline.d.ts.map +1 -0
  94. package/dist/src/types/Storyline.js +51 -0
  95. package/dist/src/types/Storyline.js.map +1 -0
  96. package/dist/src/types/Subject.d.ts +33 -0
  97. package/dist/src/types/Subject.d.ts.map +1 -1
  98. package/dist/src/types/Symbol.d.ts +17 -0
  99. package/dist/src/types/Symbol.d.ts.map +1 -1
  100. package/dist/src/types/process.d.ts +99 -1
  101. package/dist/src/types/process.d.ts.map +1 -1
  102. package/dist-browser/index.mjs +1791 -988
  103. package/dist-cli/primmel.mjs +2131 -56
  104. package/package.json +1 -1
package/dist/src/check.js CHANGED
@@ -152,7 +152,10 @@
152
152
  // (TODO.roadmap/39b — ISO/IEC 17065 role segregation) are
153
153
  // well-formed: pair members resolve to declared processes (or the
154
154
  // reserved case_personnel token), are distinct, and include the
155
- // owning process
155
+ // owning process; the kind-conditional XOR (smart TODO.roadmap/40
156
+ // batch 2): a disjoint constraint is ONLY a pair, a consultancy
157
+ // bar carries barred relations (required) + an optional period and
158
+ // never a pair
156
159
  // C74 process-io-type-coherence (TODO.roadmap/38): typed transition
157
160
  // boundaries — one name (signature in/out, registers) carries ONE
158
161
  // type; two declarations of the same name whose quantity
@@ -311,6 +314,55 @@
311
314
  // document approving organs, decision_rule edges. Per-register
312
315
  // gating (the C58 doctrine): an edge is checked only when its
313
316
  // target register is in composition scope
317
+ // C121 abstract-process-references-resolve (smart TODO.roadmap/40
318
+ // batch 2): the abstract-process model's framework bindings resolve
319
+ // — roles/organs/participant_kinds lists, decision.rule, the
320
+ // declaration kind + sign|update action vocabulary,
321
+ // discharges_gate, realized_by → process, approved_by → approval,
322
+ // the calendar windows' shape (kind vocabulary; exactly one of
323
+ // years/months), and the process_model's sequence + register
324
+ // maintainer edges. Per-register gating (the C58 doctrine)
325
+ // C122 scheme-type-resolves (smart TODO.roadmap/40 batch 2): the
326
+ // ISO/IEC 17067 register is composable — a manifest's scheme_type
327
+ // token resolves against the composed scheme_type register when one
328
+ // is in scope; a scheme_type's determination / attestation /
329
+ // surveillance.activities entries resolve against the composed
330
+ // scheme_activity_kind menus. C98's no-surveillance set defers to
331
+ // the register's surveillance.required (register-free fallback
332
+ // stays)
333
+ // C123 document-module-references-resolve (smart TODO.roadmap/40 batch
334
+ // 2): the document module's edges resolve — sequence → process,
335
+ // register maintainer → governance_organ (per-register gated), the
336
+ // declared namespace is an absolute requirement-namespace path (the
337
+ // pin C119 prefers over the requirement_class-id derivation), and
338
+ // the informative annex's applies_to names a known package
339
+ // (locator-gated)
340
+ // C124 pair-list-shape (smart TODO.roadmap/40 batch 4): the
341
+ // attribute_definition pair_list block's declaration shape — key +
342
+ // value slots required, component ids unique, key_dimension →
343
+ // applicability dimension (per-register gated); the closed-registry-
344
+ // over-values leg stays app-side
345
+ // C125 formula-variant-shape (smart TODO.roadmap/40 batch 4): the
346
+ // calculation variant block's declaration shape — variant ids
347
+ // unique, the type-conditional facets present (expression /
348
+ // table_lookup / profile_lookup); the params are engine call-site
349
+ // names and carry no resolution leg
350
+ // C126 formula-note-targets-resolve (smart TODO.roadmap/40 batch 4):
351
+ // the formula_note register's reverse applicability resolves —
352
+ // applies_to → symbol (per-register gated)
353
+ // C127 common-test-condition-shape (smart TODO.roadmap/40 batch 4):
354
+ // the common_test_condition register's declaration shape — the
355
+ // description is required; the title stays optional (the r91 map
356
+ // entries carry none) and the reference a free citation string
357
+ // C128 part-annex-shape (smart TODO.roadmap/40 batch 4): the
358
+ // part_annex register's declaration shape — letters unique per
359
+ // package, the source provenance required; the obligation
360
+ // vocabulary is parse-enforced upstream
361
+ // C129 storyline-shape (smart TODO.roadmap/40 batch 4): the demo-seed
362
+ // shapes — the id_prefix pattern (register-free), party →
363
+ // demo_world participant seeds (gated), record stores → entity-
364
+ // class stores (gated), in-construct record cross-references
365
+ // (ungated); field-level discipline stays app-side
314
366
  //
315
367
  // Levels (TODO.roadmap/17): the DEFAULT level runs the normal-level
316
368
  // rules at their catalog severities. --audit additionally runs the
@@ -1365,6 +1417,11 @@ function checkPackage(dir, options = {}) {
1365
1417
  // never roles — a scheme may bind one role to evaluation, review AND
1366
1418
  // decision, so the norms quantify over process involvement. Per-
1367
1419
  // assignment runtime enforcement is the platform's business (task 44).
1420
+ // The kind-conditional shape (smart TODO.roadmap/40 batch 2 — the YAML
1421
+ // schema's allOf discipline, data/schemas/abstract-processes.yaml): a
1422
+ // case_personnel_disjoint constraint is ONLY a pair (no period, no
1423
+ // barred relations); a consultancy_bar is ONLY a barred-relations list
1424
+ // with an optional period (never a pair).
1368
1425
  // Mirror of the OIML SMART linker's R24 abstract-process-segregation.
1369
1426
  {
1370
1427
  const SEGREGATION_CASE_PERSONNEL = 'case_personnel';
@@ -1381,6 +1438,9 @@ function checkPackage(dir, options = {}) {
1381
1438
  err('C59', `process ${p.id}: segregation constraint "${s.id}" is case_personnel_disjoint but declares ${s.pair.length} pair members — exactly two are required (segregation-members-resolve)`);
1382
1439
  continue;
1383
1440
  }
1441
+ if (s.period !== '' || s.barred.length > 0) {
1442
+ err('C59', `process ${p.id}: segregation constraint "${s.id}" is case_personnel_disjoint but declares ${s.period !== '' ? `period "${s.period}"` : 'barred relations'} — a disjoint constraint is ONLY a pair; the period/barred facets belong to consultancy_bar (segregation-members-resolve)`);
1443
+ }
1384
1444
  const [a, b] = s.pair;
1385
1445
  if (a === b) {
1386
1446
  err('C59', `process ${p.id}: segregation constraint "${s.id}" declares "${a}" disjoint from itself — the two personnel sets must be distinct (segregation-members-resolve)`);
@@ -1395,6 +1455,134 @@ function checkPackage(dir, options = {}) {
1395
1455
  err('C59', `process ${p.id}: segregation constraint "${s.id}" is declared on process "${p.id}" but neither pair member is "${p.id}" — the constrained process owns its segregation constraints (segregation-members-resolve)`);
1396
1456
  }
1397
1457
  }
1458
+ else {
1459
+ // consultancy_bar — the barred client relations are REQUIRED
1460
+ // (a bar that bars nothing is no bar); the pair is the disjoint
1461
+ // kind's shape and never appears here; the period is optional
1462
+ // (4.2.10 body-specified vs 7.13.6 fixed P2Y).
1463
+ if (s.barred.length === 0) {
1464
+ err('C59', `process ${p.id}: segregation constraint "${s.id}" is consultancy_bar but bars no client relation — the barred list is required (segregation-members-resolve)`);
1465
+ }
1466
+ if (s.pair.length > 0) {
1467
+ err('C59', `process ${p.id}: segregation constraint "${s.id}" is consultancy_bar but declares a pair — the pair is the case_personnel_disjoint shape; a bar carries barred relations and an optional period only (segregation-members-resolve)`);
1468
+ }
1469
+ }
1470
+ }
1471
+ }
1472
+ }
1473
+ // ── C121: abstract-process-references-resolve (smart TODO.roadmap/40 ──
1474
+ // batch 2; the packages-as-SSOT epic) ───────────────────────────────
1475
+ // The abstract-process model's framework bindings resolve against the
1476
+ // composed registers: the role binding list, the organ and participant
1477
+ // kind facets, the decision rule, the declaration kind (plus the sign |
1478
+ // update action vocabulary), the discharged declaration gate, the
1479
+ // realizing processes and the authorizing approvals; the calendar
1480
+ // windows' shape legs (the max_elapsed | min_elapsed kind vocabulary;
1481
+ // exactly one of years/months — the scheme_lifecycle duration
1482
+ // sub-grammar) are register-free. The process_model's sequence members
1483
+ // (→ process) and register maintainers (→ governance_organ) ride the
1484
+ // same rule. Per-register gating (the C58 doctrine): an edge is checked
1485
+ // only when its target register is in composition scope. The windows'
1486
+ // anchor/applies_to FIELD resolution stays consumer-side (the R26
1487
+ // precedent). Mirror of the OIML SMART linker's abstract-process
1488
+ // reference legs.
1489
+ {
1490
+ const apRoleIds = new Set((standard.roles ?? []).map(r => r.id));
1491
+ const apGateIds = new Set((standard.declarationGates ?? []).map(g => g.id));
1492
+ const apRuleIds = new Set((standard.decisionRules ?? []).map(r => r.id));
1493
+ const apApprovalIds = new Set((standard.approvals ?? []).map(a => a.id));
1494
+ // The C120 organ/kind/declaration resolvers report under C120 — the
1495
+ // abstract-process edges are C121's, so the rule carries its own
1496
+ // per-register-gated resolvers over the same composed registers.
1497
+ const resolveOrganAp = (where, facet, id) => {
1498
+ if (organIds.size > 0 && id !== '' && !organIds.has(id)) {
1499
+ err('C121', `${where}: ${facet} "${id}" is not a declared governance organ (abstract-process-references-resolve)`);
1500
+ }
1501
+ };
1502
+ const resolveKindAp = (where, facet, id) => {
1503
+ if (kindIds.size > 0 && id !== '' && !kindIds.has(id)) {
1504
+ err('C121', `${where}: ${facet} "${id}" is not a declared participant kind (abstract-process-references-resolve)`);
1505
+ }
1506
+ };
1507
+ const resolveDeclarationAp = (where, facet, id) => {
1508
+ if (declIds.size > 0 && id !== '' && !declIds.has(id)) {
1509
+ err('C121', `${where}: ${facet} "${id}" is not a declared declaration kind (abstract-process-references-resolve)`);
1510
+ }
1511
+ };
1512
+ const resolveProcessRef = (where, facet, id) => {
1513
+ if (frameworkProcessIds.size > 0 && !frameworkProcessIds.has(id)) {
1514
+ err('C121', `${where}: ${facet} "${id}" is not a declared process (abstract-process-references-resolve)`);
1515
+ }
1516
+ };
1517
+ for (const p of standard.processes ?? []) {
1518
+ const where = `process ${p.id}`;
1519
+ if (apRoleIds.size > 0) {
1520
+ for (const r of p.roles ?? []) {
1521
+ if (!apRoleIds.has(r)) {
1522
+ err('C121', `${where}: roles "${r}" is not a declared role (abstract-process-references-resolve)`);
1523
+ }
1524
+ }
1525
+ }
1526
+ for (const o of p.organs ?? []) {
1527
+ resolveOrganAp(where, 'organs', o);
1528
+ }
1529
+ for (const k of p.participantKinds ?? []) {
1530
+ resolveKindAp(where, 'participant_kinds', k);
1531
+ }
1532
+ if (p.decision) {
1533
+ if (p.decision.rule === '') {
1534
+ err('C121', `${where}: the decision facet declares no rule — a decision names the decision_rule it applies (abstract-process-references-resolve)`);
1535
+ }
1536
+ else if (apRuleIds.size > 0 && !apRuleIds.has(p.decision.rule)) {
1537
+ err('C121', `${where}: decision.rule "${p.decision.rule}" is not a declared decision rule (abstract-process-references-resolve)`);
1538
+ }
1539
+ }
1540
+ if (p.declaration) {
1541
+ if (p.declaration.kind === '') {
1542
+ err('C121', `${where}: the declaration facet declares no kind — a declaration names the declaration_kind it signs or updates (abstract-process-references-resolve)`);
1543
+ }
1544
+ else {
1545
+ resolveDeclarationAp(where, 'declaration.kind', p.declaration.kind);
1546
+ }
1547
+ if (p.declaration.action !== 'sign' &&
1548
+ p.declaration.action !== 'update') {
1549
+ err('C121', `${where}: declaration action "${p.declaration.action || '(none)'}" — a declaration is signed or updated (valid: sign, update) (abstract-process-references-resolve)`);
1550
+ }
1551
+ }
1552
+ if (p.dischargesGate !== '' &&
1553
+ apGateIds.size > 0 &&
1554
+ !apGateIds.has(p.dischargesGate)) {
1555
+ err('C121', `${where}: discharges_gate "${p.dischargesGate}" is not a declared declaration gate (abstract-process-references-resolve)`);
1556
+ }
1557
+ for (const r of p.realizedBy ?? []) {
1558
+ resolveProcessRef(where, 'realized_by', r);
1559
+ }
1560
+ if (apApprovalIds.size > 0) {
1561
+ for (const a of p.approvedBy ?? []) {
1562
+ if (!apApprovalIds.has(a)) {
1563
+ err('C121', `${where}: approved_by "${a}" is not a declared approval (abstract-process-references-resolve)`);
1564
+ }
1565
+ }
1566
+ }
1567
+ for (const w of p.windows ?? []) {
1568
+ const wwhere = `${where} window ${w.id}`;
1569
+ if (w.kind !== 'max_elapsed' && w.kind !== 'min_elapsed') {
1570
+ err('C121', `${wwhere}: kind "${w.kind || '(none)'}" — a calendar window is a deadline or a cooling-off (valid: max_elapsed, min_elapsed) (abstract-process-references-resolve)`);
1571
+ }
1572
+ if (w.windowYears > 0 === w.windowMonths > 0) {
1573
+ err('C121', `${wwhere}: the window duration declares ${w.windowYears > 0
1574
+ ? 'both years and months'
1575
+ : 'neither years nor months'} — exactly one is required (the scheme_lifecycle window sub-grammar) (abstract-process-references-resolve)`);
1576
+ }
1577
+ }
1578
+ }
1579
+ for (const m of standard.processModels ?? []) {
1580
+ const where = `process_model ${m.id}`;
1581
+ for (const s of m.sequence ?? []) {
1582
+ resolveProcessRef(where, 'sequence', s);
1583
+ }
1584
+ for (const r of m.registers ?? []) {
1585
+ resolveOrganAp(`${where} register ${r.id}`, 'maintainer', r.maintainer);
1398
1586
  }
1399
1587
  }
1400
1588
  }
@@ -2427,6 +2615,88 @@ function checkPackage(dir, options = {}) {
2427
2615
  }
2428
2616
  }
2429
2617
  }
2618
+ // ── C122: scheme-type-resolves (smart TODO.roadmap/40 batch 2; the ──
2619
+ // packages-as-SSOT epic) ────────────────────────────────────────────
2620
+ // The ISO/IEC 17067 register is composable: a manifest's `scheme_type`
2621
+ // token resolves against the composed scheme_type register WHEN one is
2622
+ // in scope (per-register gating, the C58 doctrine — a register-less
2623
+ // package keeps the register-free fallback, the C89 spelling
2624
+ // precedent); and a scheme_type's determination / attestation /
2625
+ // surveillance.activities entries resolve against the composed
2626
+ // scheme_activity_kind menus when THAT register is in scope. C98's
2627
+ // hard-coded no-surveillance set defers to the register's
2628
+ // surveillance.required (below, in the C97–C98 block).
2629
+ {
2630
+ const schemeTypeIds = new Set((standard.schemeTypes ?? []).map(t => t.id));
2631
+ const menuIds = new Set((standard.schemeActivityKinds ?? []).map(k => k.id));
2632
+ const manifestSchemeType = standard.packageManifest?.schemeType;
2633
+ if (schemeTypeIds.size > 0 &&
2634
+ manifestSchemeType &&
2635
+ !schemeTypeIds.has(manifestSchemeType)) {
2636
+ err('C122', `package "${standard.packageManifest.id}": scheme_type "${manifestSchemeType}" is not a declared scheme type of the in-scope ISO/IEC 17067 register (scheme-type-resolves)`);
2637
+ }
2638
+ if (menuIds.size > 0) {
2639
+ const resolveMenuEntry = (where, facet, id) => {
2640
+ if (!menuIds.has(id)) {
2641
+ err('C122', `${where}: ${facet} "${id}" is not a declared scheme activity kind (scheme-type-resolves)`);
2642
+ }
2643
+ };
2644
+ for (const t of standard.schemeTypes ?? []) {
2645
+ const where = `scheme_type ${t.id}`;
2646
+ for (const d of t.determination) {
2647
+ resolveMenuEntry(where, 'determination', d);
2648
+ }
2649
+ for (const a of t.attestation) {
2650
+ resolveMenuEntry(where, 'attestation', a);
2651
+ }
2652
+ for (const s of t.surveillance?.activities ?? []) {
2653
+ resolveMenuEntry(where, 'surveillance.activities', s);
2654
+ }
2655
+ }
2656
+ }
2657
+ }
2658
+ // ── C123: document-module-references-resolve (smart TODO.roadmap/40 ──
2659
+ // batch 2; the packages-as-SSOT epic) ───────────────────────────────
2660
+ // The document module's edges resolve: the sequence members against
2661
+ // the composed processes, the register maintainers against the
2662
+ // governance_organ register (per-register gating, the C58 doctrine),
2663
+ // and the declared namespace is an absolute requirement-namespace path
2664
+ // (the register-free shape leg). The informative annex's applies_to
2665
+ // names a known package — checked through the dependency-manifest
2666
+ // locator, so the leg is gated on a locator being supplied (the
2667
+ // C97-class resolution discipline; without a locator the sibling scan
2668
+ // cannot adjudicate non-sibling ids and the leg stays silent).
2669
+ {
2670
+ const dmOrganIds = new Set((standard.governanceOrgans ?? []).map(o => o.id));
2671
+ const dmProcessIds = new Set((standard.processes ?? []).map(p => p.id));
2672
+ for (const m of standard.documentModules ?? []) {
2673
+ const where = `document_module ${m.id}`;
2674
+ if (m.namespace !== '' && !m.namespace.startsWith('/')) {
2675
+ err('C123', `${where}: namespace "${m.namespace}" is not an absolute requirement-namespace path (e.g. /req/cs/${m.id}) — the declared pin C119 guards must be a path (document-module-references-resolve)`);
2676
+ }
2677
+ if (dmProcessIds.size > 0) {
2678
+ for (const s of m.sequence ?? []) {
2679
+ if (!dmProcessIds.has(s)) {
2680
+ err('C123', `${where}: sequence "${s}" is not a declared process (document-module-references-resolve)`);
2681
+ }
2682
+ }
2683
+ }
2684
+ if (dmOrganIds.size > 0) {
2685
+ for (const r of m.registers ?? []) {
2686
+ if (r.maintainer !== '' && !dmOrganIds.has(r.maintainer)) {
2687
+ err('C123', `${where} register ${r.id}: maintainer "${r.maintainer}" is not a declared governance organ (document-module-references-resolve)`);
2688
+ }
2689
+ }
2690
+ }
2691
+ }
2692
+ if (options.resolvePackage) {
2693
+ for (const a of standard.informativeAnnexes ?? []) {
2694
+ if (a.appliesTo !== '' && !resolveDepManifest(a.appliesTo)) {
2695
+ err('C123', `informative_annex ${a.id}: applies_to "${a.appliesTo}" does not resolve to a known package — the CASCO layer the annex applies to must resolve (document-module-references-resolve)`);
2696
+ }
2697
+ }
2698
+ }
2699
+ }
2430
2700
  // ── C97–C98: the certification program (TODO.v2/01;
2431
2701
  // analysis/twin-certification-design.md Q4) ──
2432
2702
  // A fourth publisher with the product_reference shape: the scheme
@@ -2464,9 +2734,16 @@ function checkPackage(dir, options = {}) {
2464
2734
  // surveillance machinery. A monitor IS a continuous claim ("without
2465
2735
  // triggers, 'continuous' has no clock" — types/Monitor.ts); an
2466
2736
  // activity_kind { surveillance } process is a surveillance claim.
2737
+ // When the scheme-type register is in scope (smart TODO.roadmap/40
2738
+ // batch 2 — composed via uses), the register's own
2739
+ // surveillance.required adjudicates; the hard-coded type_1a/1b set
2740
+ // is the register-free fallback (the C89 spelling precedent).
2467
2741
  const SCHEME_TYPES_WITHOUT_SURVEILLANCE = new Set(['type_1a', 'type_1b']);
2468
- if (productManifest.schemeType &&
2469
- SCHEME_TYPES_WITHOUT_SURVEILLANCE.has(productManifest.schemeType)) {
2742
+ const registerEntry = (standard.schemeTypes ?? []).find(t => t.id === productManifest.schemeType);
2743
+ const noSurveillance = registerEntry
2744
+ ? !registerEntry.surveillance?.required
2745
+ : SCHEME_TYPES_WITHOUT_SURVEILLANCE.has(productManifest.schemeType ?? '');
2746
+ if (productManifest.schemeType && noSurveillance) {
2470
2747
  const claims = [];
2471
2748
  if ((standard.monitors ?? []).length > 0) {
2472
2749
  claims.push(`${standard.monitors.length} monitor(s): ${standard.monitors.map(m => m.id).join(', ')}`);
@@ -3066,6 +3343,183 @@ function checkPackage(dir, options = {}) {
3066
3343
  }
3067
3344
  }
3068
3345
  }
3346
+ // ── C124: pair-list-shape (smart TODO.roadmap/40 batch 4; the ──────
3347
+ // packages-as-SSOT epic) ────────────────────────────────────────────
3348
+ // The pair_list block's declaration shape: the key and value slots are
3349
+ // required, the component ids are unique within the block, and the
3350
+ // key_dimension names a declared applicability dimension WHEN the
3351
+ // dimension register is in composition scope (per-register gating, the
3352
+ // C58 doctrine). The closed-registry-over-VALUES leg (pair keys drawn
3353
+ // from the dimension's values ∪ the declared components) quantifies
3354
+ // over app-side records, so it stays app-side — the kernel checks the
3355
+ // declaration shape only.
3356
+ for (const a of standard.attributeDefinitions ?? []) {
3357
+ const pl = a.pairList;
3358
+ if (!pl) {
3359
+ continue;
3360
+ }
3361
+ const where = `attribute_definition ${a.id}: pair_list`;
3362
+ if (!pl.key) {
3363
+ err('C124', `${where}: the key slot is required (pair-list-shape)`);
3364
+ }
3365
+ if (!pl.value) {
3366
+ err('C124', `${where}: the value slot is required (pair-list-shape)`);
3367
+ }
3368
+ const seen = new Set();
3369
+ for (const c of pl.components) {
3370
+ if (seen.has(c.id)) {
3371
+ err('C124', `${where}: component "${c.id}" is declared twice (pair-list-shape)`);
3372
+ }
3373
+ seen.add(c.id);
3374
+ }
3375
+ if (pl.keyDimension && dimIds.size > 0 && !dimIds.has(pl.keyDimension)) {
3376
+ err('C124', `${where}: key_dimension "${pl.keyDimension}" is not a declared applicability dimension (pair-list-shape)`);
3377
+ }
3378
+ }
3379
+ // ── C125: formula-variant-shape (smart TODO.roadmap/40 batch 4; the ─
3380
+ // packages-as-SSOT epic) ────────────────────────────────────────────
3381
+ // The variant block's declaration shape (the top-level calculation
3382
+ // shape discipline mirrored onto the realization): variant ids are
3383
+ // unique within the owning calculation, and the type-conditional
3384
+ // facets are present — an expression variant carries its expression,
3385
+ // a table_lookup variant its lookup block, a profile_lookup variant
3386
+ // its profile path. The variant's params are engine call-site names
3387
+ // and deliberately do NOT resolve against the calculation's inputs —
3388
+ // there is no params-resolve leg (the block stays documentary/
3389
+ // engine-facing).
3390
+ for (const c of standard.calculations ?? []) {
3391
+ const seen = new Set();
3392
+ for (const v of c.variants ?? []) {
3393
+ const where = `calculation ${c.id}: variant ${v.id}`;
3394
+ if (seen.has(v.id)) {
3395
+ err('C125', `${where}: the variant id is declared twice (formula-variant-shape)`);
3396
+ }
3397
+ seen.add(v.id);
3398
+ if (v.type === 'expression' && !v.expression) {
3399
+ err('C125', `${where}: type expression requires the expression facet (formula-variant-shape)`);
3400
+ }
3401
+ if (v.type === 'table_lookup' && !v.lookup) {
3402
+ err('C125', `${where}: type table_lookup requires the lookup block (formula-variant-shape)`);
3403
+ }
3404
+ if (v.type === 'profile_lookup' && !v.profile) {
3405
+ err('C125', `${where}: type profile_lookup requires the profile facet (formula-variant-shape)`);
3406
+ }
3407
+ }
3408
+ }
3409
+ // ── C126: formula-note-targets-resolve (smart TODO.roadmap/40 ──────
3410
+ // batch 4; the packages-as-SSOT epic) ────────────────────────────────
3411
+ // The formula_note register's reverse applicability resolves: every
3412
+ // applies_to entry names a declared symbol WHEN the symbol register
3413
+ // is in composition scope (per-register gating, the C58 doctrine).
3414
+ if (symbolIds.size > 0) {
3415
+ for (const n of standard.formulaNotes ?? []) {
3416
+ for (const s of n.appliesTo ?? []) {
3417
+ if (!symbolIds.has(s)) {
3418
+ err('C126', `formula_note ${n.id}: applies_to "${s}" is not a declared symbol (formula-note-targets-resolve)`);
3419
+ }
3420
+ }
3421
+ }
3422
+ }
3423
+ // ── C127: common-test-condition-shape (smart TODO.roadmap/40 ───────
3424
+ // batch 4; the packages-as-SSOT epic) ────────────────────────────────
3425
+ // The register's declaration shape: the description is the one facet
3426
+ // BOTH YAML shapes carry (the r60/r144/r129 array entries and the r91
3427
+ // keyed-map entries alike) — a condition without its text is a
3428
+ // carrier with no content. The title stays OPTIONAL (the r91 map
3429
+ // entries carry none; requiring it would force content invention at
3430
+ // migration) and the reference stays a free citation string (the
3431
+ // linker owns the citation semantics — no kernel leg).
3432
+ for (const c of standard.commonTestConditions ?? []) {
3433
+ if (!c.description) {
3434
+ err('C127', `common_test_condition ${c.id}: the description is required — a condition entry carries its normative text (common-test-condition-shape)`);
3435
+ }
3436
+ }
3437
+ // ── C128: part-annex-shape (smart TODO.roadmap/40 batch 4; the ─────
3438
+ // packages-as-SSOT epic) ────────────────────────────────────────────
3439
+ // The annex-volume register's declaration shape: letters are unique
3440
+ // per package (the index keys on the printed letter) and every entry
3441
+ // carries its clause-URN provenance. The obligation vocabulary is
3442
+ // parse-enforced upstream (the fail-closed precedent), so it carries
3443
+ // no check leg.
3444
+ {
3445
+ const seenLetters = new Map();
3446
+ for (const a of standard.partAnnexes ?? []) {
3447
+ if (a.letter) {
3448
+ const prior = seenLetters.get(a.letter);
3449
+ if (prior !== undefined) {
3450
+ err('C128', `part_annex ${a.id}: letter "${a.letter}" is already declared by ${prior} — the annex-volume index keys on the printed letter (part-annex-shape)`);
3451
+ }
3452
+ else {
3453
+ seenLetters.set(a.letter, a.id);
3454
+ }
3455
+ }
3456
+ if (!a.source?.doc) {
3457
+ err('C128', `part_annex ${a.id}: the source provenance is required — a documentary register entry cites its part (part-annex-shape)`);
3458
+ }
3459
+ }
3460
+ }
3461
+ // ── C129: storyline-shape (smart TODO.roadmap/40 batch 4; the ──────
3462
+ // packages-as-SSOT epic) ────────────────────────────────────────────
3463
+ // The demo-seed shapes: the id_prefix pattern (the register-free shape
3464
+ // leg); the party laboratory/authority resolve against the package's
3465
+ // participant seeds WHEN a demo_world with participants is in scope
3466
+ // (per-register gating, the C58 doctrine); the record stores resolve
3467
+ // against the declared entity-class stores WHEN that register is in
3468
+ // scope; and the in-construct cross-references resolve — a record
3469
+ // field keyed by a sibling record's store names a sibling record of
3470
+ // that store (in-construct, ungated). The field-level discipline (the
3471
+ // schema's per-slot required fields) stays app-side.
3472
+ {
3473
+ const participantSeeds = new Set();
3474
+ for (const w of standard.demoWorlds ?? []) {
3475
+ for (const section of w.participants ?? []) {
3476
+ for (const e of section.entries ?? []) {
3477
+ participantSeeds.add(e.id);
3478
+ }
3479
+ }
3480
+ }
3481
+ const entityStores = new Set((standard.dataclasses ?? [])
3482
+ .map(c => c.store)
3483
+ .filter((s) => !!s));
3484
+ for (const s of standard.storylines ?? []) {
3485
+ const where = `storyline ${s.id}`;
3486
+ if (s.idPrefix && !/^[a-z0-9-]+$/.test(s.idPrefix)) {
3487
+ err('C129', `${where}: id_prefix "${s.idPrefix}" does not match ^[a-z0-9-]+$ — the id-minting prefix is a lowercase slug (storyline-shape)`);
3488
+ }
3489
+ if (participantSeeds.size > 0) {
3490
+ for (const [slot, ref] of [
3491
+ ['laboratory', s.party.laboratory],
3492
+ ['authority', s.party.authority],
3493
+ ]) {
3494
+ if (ref && !participantSeeds.has(ref)) {
3495
+ err('C129', `${where}: party ${slot} "${ref}" is not a declared participant seed (storyline-shape)`);
3496
+ }
3497
+ }
3498
+ }
3499
+ const siblingStores = new Map();
3500
+ for (const r of s.records ?? []) {
3501
+ if (entityStores.size > 0 && !entityStores.has(r.store)) {
3502
+ err('C129', `${where}: record store "${r.store}" is not a declared entity-class store (storyline-shape)`);
3503
+ }
3504
+ let ids = siblingStores.get(r.store);
3505
+ if (!ids) {
3506
+ ids = new Set();
3507
+ siblingStores.set(r.store, ids);
3508
+ }
3509
+ ids.add(r.id);
3510
+ }
3511
+ for (const r of s.records ?? []) {
3512
+ for (const [key, value] of Object.entries(r.fields)) {
3513
+ const targets = siblingStores.get(key);
3514
+ if (targets !== undefined &&
3515
+ !Array.isArray(value) &&
3516
+ !targets.has(String(value))) {
3517
+ err('C129', `${where}: record ${r.store} ${r.id}: ${key} "${String(value)}" is not a sibling ${key} record of this storyline (storyline-shape)`);
3518
+ }
3519
+ }
3520
+ }
3521
+ }
3522
+ }
3069
3523
  // C34 — duality-coherence: one value structure, two roles.
3070
3524
  for (const d of standard.duals ?? []) {
3071
3525
  if (d.attribute && !attrIds.has(d.attribute)) {