@primmel/primmel 1.3.2 → 1.4.1

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 (423) hide show
  1. package/dist/index.d.ts +40 -4
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +112 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/src/check-allowlist.d.ts +56 -0
  6. package/dist/src/check-allowlist.d.ts.map +1 -0
  7. package/dist/src/check-allowlist.js +307 -0
  8. package/dist/src/check-allowlist.js.map +1 -0
  9. package/dist/src/check-rules.d.ts +17 -0
  10. package/dist/src/check-rules.d.ts.map +1 -0
  11. package/dist/src/check-rules.js +351 -0
  12. package/dist/src/check-rules.js.map +1 -0
  13. package/dist/src/check.d.ts +115 -0
  14. package/dist/src/check.d.ts.map +1 -0
  15. package/dist/src/check.js +4164 -0
  16. package/dist/src/check.js.map +1 -0
  17. package/dist/src/export/rdf-competency-questions.d.ts +11 -0
  18. package/dist/src/export/rdf-competency-questions.d.ts.map +1 -0
  19. package/dist/src/export/rdf-competency-questions.js +99 -0
  20. package/dist/src/export/rdf-competency-questions.js.map +1 -0
  21. package/dist/src/export/rdf-shapes.d.ts +3 -0
  22. package/dist/src/export/rdf-shapes.d.ts.map +1 -0
  23. package/dist/src/export/rdf-shapes.js +225 -0
  24. package/dist/src/export/rdf-shapes.js.map +1 -0
  25. package/dist/src/export/rdf-vocabulary.d.ts +125 -0
  26. package/dist/src/export/rdf-vocabulary.d.ts.map +1 -0
  27. package/dist/src/export/rdf-vocabulary.js +230 -0
  28. package/dist/src/export/rdf-vocabulary.js.map +1 -0
  29. package/dist/src/export/rdf.d.ts +92 -0
  30. package/dist/src/export/rdf.d.ts.map +1 -0
  31. package/dist/src/export/rdf.js +707 -0
  32. package/dist/src/export/rdf.js.map +1 -0
  33. package/dist/src/export/reqif.d.ts +81 -0
  34. package/dist/src/export/reqif.d.ts.map +1 -0
  35. package/dist/src/export/reqif.js +703 -0
  36. package/dist/src/export/reqif.js.map +1 -0
  37. package/dist/src/instance-resolution.d.ts +68 -0
  38. package/dist/src/instance-resolution.d.ts.map +1 -0
  39. package/dist/src/instance-resolution.js +239 -0
  40. package/dist/src/instance-resolution.js.map +1 -0
  41. package/dist/src/mapping-coverage.d.ts +220 -0
  42. package/dist/src/mapping-coverage.d.ts.map +1 -0
  43. package/dist/src/mapping-coverage.js +528 -0
  44. package/dist/src/mapping-coverage.js.map +1 -0
  45. package/dist/src/model-diff.d.ts +198 -0
  46. package/dist/src/model-diff.d.ts.map +1 -0
  47. package/dist/src/model-diff.js +914 -0
  48. package/dist/src/model-diff.js.map +1 -0
  49. package/dist/src/operational-state.d.ts +112 -0
  50. package/dist/src/operational-state.d.ts.map +1 -0
  51. package/dist/src/operational-state.js +162 -0
  52. package/dist/src/operational-state.js.map +1 -0
  53. package/dist/src/package-diff.d.ts +36 -0
  54. package/dist/src/package-diff.d.ts.map +1 -0
  55. package/dist/src/package-diff.js +104 -0
  56. package/dist/src/package-diff.js.map +1 -0
  57. package/dist/src/ser-des/config/acceptance.d.ts +10 -0
  58. package/dist/src/ser-des/config/acceptance.d.ts.map +1 -0
  59. package/dist/src/ser-des/config/acceptance.js +163 -0
  60. package/dist/src/ser-des/config/acceptance.js.map +1 -0
  61. package/dist/src/ser-des/config/activityArchetype.d.ts +5 -0
  62. package/dist/src/ser-des/config/activityArchetype.d.ts.map +1 -0
  63. package/dist/src/ser-des/config/activityArchetype.js +78 -0
  64. package/dist/src/ser-des/config/activityArchetype.js.map +1 -0
  65. package/dist/src/ser-des/config/artifact.d.ts +16 -0
  66. package/dist/src/ser-des/config/artifact.d.ts.map +1 -0
  67. package/dist/src/ser-des/config/artifact.js +392 -0
  68. package/dist/src/ser-des/config/artifact.js.map +1 -0
  69. package/dist/src/ser-des/config/calculation.d.ts.map +1 -1
  70. package/dist/src/ser-des/config/calculation.js +214 -12
  71. package/dist/src/ser-des/config/calculation.js.map +1 -1
  72. package/dist/src/ser-des/config/comment.d.ts +34 -0
  73. package/dist/src/ser-des/config/comment.d.ts.map +1 -0
  74. package/dist/src/ser-des/config/comment.js +121 -0
  75. package/dist/src/ser-des/config/comment.js.map +1 -0
  76. package/dist/src/ser-des/config/competenceKind.d.ts +8 -0
  77. package/dist/src/ser-des/config/competenceKind.d.ts.map +1 -0
  78. package/dist/src/ser-des/config/competenceKind.js +266 -0
  79. package/dist/src/ser-des/config/competenceKind.js.map +1 -0
  80. package/dist/src/ser-des/config/composition.d.ts +6 -0
  81. package/dist/src/ser-des/config/composition.d.ts.map +1 -0
  82. package/dist/src/ser-des/config/composition.js +165 -0
  83. package/dist/src/ser-des/config/composition.js.map +1 -0
  84. package/dist/src/ser-des/config/conformanceClass.d.ts +9 -0
  85. package/dist/src/ser-des/config/conformanceClass.d.ts.map +1 -0
  86. package/dist/src/ser-des/config/conformanceClass.js +128 -0
  87. package/dist/src/ser-des/config/conformanceClass.js.map +1 -0
  88. package/dist/src/ser-des/config/conformanceTest.d.ts +6 -0
  89. package/dist/src/ser-des/config/conformanceTest.d.ts.map +1 -0
  90. package/dist/src/ser-des/config/conformanceTest.js +929 -0
  91. package/dist/src/ser-des/config/conformanceTest.js.map +1 -0
  92. package/dist/src/ser-des/config/constraint.d.ts +5 -0
  93. package/dist/src/ser-des/config/constraint.d.ts.map +1 -0
  94. package/dist/src/ser-des/config/constraint.js +108 -0
  95. package/dist/src/ser-des/config/constraint.js.map +1 -0
  96. package/dist/src/ser-des/config/data.d.ts.map +1 -1
  97. package/dist/src/ser-des/config/data.js +105 -12
  98. package/dist/src/ser-des/config/data.js.map +1 -1
  99. package/dist/src/ser-des/config/design.d.ts +9 -0
  100. package/dist/src/ser-des/config/design.d.ts.map +1 -0
  101. package/dist/src/ser-des/config/design.js +847 -0
  102. package/dist/src/ser-des/config/design.js.map +1 -0
  103. package/dist/src/ser-des/config/discrepancyRecord.d.ts +5 -0
  104. package/dist/src/ser-des/config/discrepancyRecord.d.ts.map +1 -0
  105. package/dist/src/ser-des/config/discrepancyRecord.js +109 -0
  106. package/dist/src/ser-des/config/discrepancyRecord.js.map +1 -0
  107. package/dist/src/ser-des/config/dual.d.ts +9 -0
  108. package/dist/src/ser-des/config/dual.d.ts.map +1 -0
  109. package/dist/src/ser-des/config/dual.js +84 -0
  110. package/dist/src/ser-des/config/dual.js.map +1 -0
  111. package/dist/src/ser-des/config/field-parser.d.ts +92 -7
  112. package/dist/src/ser-des/config/field-parser.d.ts.map +1 -1
  113. package/dist/src/ser-des/config/field-parser.js +844 -29
  114. package/dist/src/ser-des/config/field-parser.js.map +1 -1
  115. package/dist/src/ser-des/config/form.d.ts.map +1 -1
  116. package/dist/src/ser-des/config/form.js +356 -134
  117. package/dist/src/ser-des/config/form.js.map +1 -1
  118. package/dist/src/ser-des/config/formulasUsed.d.ts +9 -0
  119. package/dist/src/ser-des/config/formulasUsed.d.ts.map +1 -0
  120. package/dist/src/ser-des/config/formulasUsed.js +126 -0
  121. package/dist/src/ser-des/config/formulasUsed.js.map +1 -0
  122. package/dist/src/ser-des/config/index.d.ts.map +1 -1
  123. package/dist/src/ser-des/config/index.js +180 -1
  124. package/dist/src/ser-des/config/index.js.map +1 -1
  125. package/dist/src/ser-des/config/instance.d.ts +22 -0
  126. package/dist/src/ser-des/config/instance.d.ts.map +1 -0
  127. package/dist/src/ser-des/config/instance.js +266 -0
  128. package/dist/src/ser-des/config/instance.js.map +1 -0
  129. package/dist/src/ser-des/config/invariant.d.ts +9 -0
  130. package/dist/src/ser-des/config/invariant.d.ts.map +1 -0
  131. package/dist/src/ser-des/config/invariant.js +143 -0
  132. package/dist/src/ser-des/config/invariant.js.map +1 -0
  133. package/dist/src/ser-des/config/mapProfile.d.ts.map +1 -1
  134. package/dist/src/ser-des/config/mapProfile.js +199 -14
  135. package/dist/src/ser-des/config/mapProfile.js.map +1 -1
  136. package/dist/src/ser-des/config/metadata.js +6 -6
  137. package/dist/src/ser-des/config/metadata.js.map +1 -1
  138. package/dist/src/ser-des/config/monitor.d.ts +9 -0
  139. package/dist/src/ser-des/config/monitor.d.ts.map +1 -0
  140. package/dist/src/ser-des/config/monitor.js +322 -0
  141. package/dist/src/ser-des/config/monitor.js.map +1 -0
  142. package/dist/src/ser-des/config/note.d.ts.map +1 -1
  143. package/dist/src/ser-des/config/note.js +16 -1
  144. package/dist/src/ser-des/config/note.js.map +1 -1
  145. package/dist/src/ser-des/config/packageManifest.d.ts +5 -0
  146. package/dist/src/ser-des/config/packageManifest.d.ts.map +1 -0
  147. package/dist/src/ser-des/config/packageManifest.js +349 -0
  148. package/dist/src/ser-des/config/packageManifest.js.map +1 -0
  149. package/dist/src/ser-des/config/passport.d.ts +9 -0
  150. package/dist/src/ser-des/config/passport.d.ts.map +1 -0
  151. package/dist/src/ser-des/config/passport.js +191 -0
  152. package/dist/src/ser-des/config/passport.js.map +1 -0
  153. package/dist/src/ser-des/config/process.d.ts +5 -3
  154. package/dist/src/ser-des/config/process.d.ts.map +1 -1
  155. package/dist/src/ser-des/config/process.js +915 -8
  156. package/dist/src/ser-des/config/process.js.map +1 -1
  157. package/dist/src/ser-des/config/quantity.d.ts +21 -0
  158. package/dist/src/ser-des/config/quantity.d.ts.map +1 -0
  159. package/dist/src/ser-des/config/quantity.js +114 -0
  160. package/dist/src/ser-des/config/quantity.js.map +1 -0
  161. package/dist/src/ser-des/config/quantityRegister.d.ts +9 -0
  162. package/dist/src/ser-des/config/quantityRegister.d.ts.map +1 -0
  163. package/dist/src/ser-des/config/quantityRegister.js +202 -0
  164. package/dist/src/ser-des/config/quantityRegister.js.map +1 -0
  165. package/dist/src/ser-des/config/reference.d.ts.map +1 -1
  166. package/dist/src/ser-des/config/reference.js +6 -0
  167. package/dist/src/ser-des/config/reference.js.map +1 -1
  168. package/dist/src/ser-des/config/referenceMaterial.d.ts +5 -0
  169. package/dist/src/ser-des/config/referenceMaterial.d.ts.map +1 -0
  170. package/dist/src/ser-des/config/referenceMaterial.js +296 -0
  171. package/dist/src/ser-des/config/referenceMaterial.js.map +1 -0
  172. package/dist/src/ser-des/config/requirement.d.ts +16 -0
  173. package/dist/src/ser-des/config/requirement.d.ts.map +1 -0
  174. package/dist/src/ser-des/config/requirement.js +579 -0
  175. package/dist/src/ser-des/config/requirement.js.map +1 -0
  176. package/dist/src/ser-des/config/role.d.ts.map +1 -1
  177. package/dist/src/ser-des/config/role.js +12 -0
  178. package/dist/src/ser-des/config/role.js.map +1 -1
  179. package/dist/src/ser-des/config/series.d.ts +9 -0
  180. package/dist/src/ser-des/config/series.d.ts.map +1 -0
  181. package/dist/src/ser-des/config/series.js +121 -0
  182. package/dist/src/ser-des/config/series.js.map +1 -0
  183. package/dist/src/ser-des/config/sourceDiscrepancy.d.ts +11 -0
  184. package/dist/src/ser-des/config/sourceDiscrepancy.d.ts.map +1 -0
  185. package/dist/src/ser-des/config/sourceDiscrepancy.js +83 -0
  186. package/dist/src/ser-des/config/sourceDiscrepancy.js.map +1 -0
  187. package/dist/src/ser-des/config/stateMachine.d.ts.map +1 -1
  188. package/dist/src/ser-des/config/stateMachine.js +189 -38
  189. package/dist/src/ser-des/config/stateMachine.js.map +1 -1
  190. package/dist/src/ser-des/config/subform.d.ts.map +1 -1
  191. package/dist/src/ser-des/config/subform.js +25 -33
  192. package/dist/src/ser-des/config/subform.js.map +1 -1
  193. package/dist/src/ser-des/config/subject.d.ts +58 -0
  194. package/dist/src/ser-des/config/subject.d.ts.map +1 -0
  195. package/dist/src/ser-des/config/subject.js +2093 -0
  196. package/dist/src/ser-des/config/subject.js.map +1 -0
  197. package/dist/src/ser-des/config/symbol.d.ts.map +1 -1
  198. package/dist/src/ser-des/config/symbol.js +130 -6
  199. package/dist/src/ser-des/config/symbol.js.map +1 -1
  200. package/dist/src/ser-des/config/table.d.ts.map +1 -1
  201. package/dist/src/ser-des/config/table.js +373 -7
  202. package/dist/src/ser-des/config/table.js.map +1 -1
  203. package/dist/src/ser-des/config/term.d.ts.map +1 -1
  204. package/dist/src/ser-des/config/term.js +107 -0
  205. package/dist/src/ser-des/config/term.js.map +1 -1
  206. package/dist/src/ser-des/config/testPointSet.d.ts +5 -0
  207. package/dist/src/ser-des/config/testPointSet.d.ts.map +1 -0
  208. package/dist/src/ser-des/config/testPointSet.js +199 -0
  209. package/dist/src/ser-des/config/testPointSet.js.map +1 -0
  210. package/dist/src/ser-des/config/testSequence.d.ts +9 -0
  211. package/dist/src/ser-des/config/testSequence.d.ts.map +1 -0
  212. package/dist/src/ser-des/config/testSequence.js +206 -0
  213. package/dist/src/ser-des/config/testSequence.js.map +1 -0
  214. package/dist/src/ser-des/config/text.d.ts +5 -0
  215. package/dist/src/ser-des/config/text.d.ts.map +1 -0
  216. package/dist/src/ser-des/config/text.js +106 -0
  217. package/dist/src/ser-des/config/text.js.map +1 -0
  218. package/dist/src/ser-des/config/twin.d.ts +31 -0
  219. package/dist/src/ser-des/config/twin.d.ts.map +1 -0
  220. package/dist/src/ser-des/config/twin.js +362 -0
  221. package/dist/src/ser-des/config/twin.js.map +1 -0
  222. package/dist/src/ser-des/config/verdict.d.ts +5 -0
  223. package/dist/src/ser-des/config/verdict.d.ts.map +1 -0
  224. package/dist/src/ser-des/config/verdict.js +159 -0
  225. package/dist/src/ser-des/config/verdict.js.map +1 -0
  226. package/dist/src/ser-des/config/viewProfile.d.ts.map +1 -1
  227. package/dist/src/ser-des/config/viewProfile.js +7 -0
  228. package/dist/src/ser-des/config/viewProfile.js.map +1 -1
  229. package/dist/src/ser-des/includes.d.ts.map +1 -1
  230. package/dist/src/ser-des/includes.js +5 -2
  231. package/dist/src/ser-des/includes.js.map +1 -1
  232. package/dist/src/ser-des/index.d.ts +12 -0
  233. package/dist/src/ser-des/index.d.ts.map +1 -1
  234. package/dist/src/ser-des/index.js +42 -0
  235. package/dist/src/ser-des/index.js.map +1 -1
  236. package/dist/src/ser-des/package.d.ts +53 -0
  237. package/dist/src/ser-des/package.d.ts.map +1 -0
  238. package/dist/src/ser-des/package.js +404 -0
  239. package/dist/src/ser-des/package.js.map +1 -0
  240. package/dist/src/ser-des/parse-block.d.ts.map +1 -1
  241. package/dist/src/ser-des/parse-block.js +7 -1
  242. package/dist/src/ser-des/parse-block.js.map +1 -1
  243. package/dist/src/ser-des/parse.d.ts.map +1 -1
  244. package/dist/src/ser-des/parse.js +45 -0
  245. package/dist/src/ser-des/parse.js.map +1 -1
  246. package/dist/src/ser-des/prm.d.ts +53 -0
  247. package/dist/src/ser-des/prm.d.ts.map +1 -0
  248. package/dist/src/ser-des/prm.js +257 -0
  249. package/dist/src/ser-des/prm.js.map +1 -0
  250. package/dist/src/ser-des/resolve.d.ts.map +1 -1
  251. package/dist/src/ser-des/resolve.js +1 -0
  252. package/dist/src/ser-des/resolve.js.map +1 -1
  253. package/dist/src/ser-des/tokenize.d.ts +11 -0
  254. package/dist/src/ser-des/tokenize.d.ts.map +1 -1
  255. package/dist/src/ser-des/tokenize.js +28 -1
  256. package/dist/src/ser-des/tokenize.js.map +1 -1
  257. package/dist/src/ser-des/types.d.ts +56 -1
  258. package/dist/src/ser-des/types.d.ts.map +1 -1
  259. package/dist/src/spelling.d.ts +35 -0
  260. package/dist/src/spelling.d.ts.map +1 -0
  261. package/dist/src/spelling.js +140 -0
  262. package/dist/src/spelling.js.map +1 -0
  263. package/dist/src/text-coverage.d.ts +164 -0
  264. package/dist/src/text-coverage.d.ts.map +1 -0
  265. package/dist/src/text-coverage.js +679 -0
  266. package/dist/src/text-coverage.js.map +1 -0
  267. package/dist/src/time.d.ts +55 -0
  268. package/dist/src/time.d.ts.map +1 -0
  269. package/dist/src/time.js +223 -0
  270. package/dist/src/time.js.map +1 -0
  271. package/dist/src/type-expr.d.ts +31 -0
  272. package/dist/src/type-expr.d.ts.map +1 -0
  273. package/dist/src/type-expr.js +110 -0
  274. package/dist/src/type-expr.js.map +1 -0
  275. package/dist/src/types/Acceptance.d.ts +31 -0
  276. package/dist/src/types/Acceptance.d.ts.map +1 -0
  277. package/dist/src/types/Acceptance.js +12 -0
  278. package/dist/src/types/Acceptance.js.map +1 -0
  279. package/dist/src/types/ActivityArchetype.d.ts +14 -0
  280. package/dist/src/types/ActivityArchetype.d.ts.map +1 -0
  281. package/dist/src/types/ActivityArchetype.js +17 -0
  282. package/dist/src/types/ActivityArchetype.js.map +1 -0
  283. package/dist/src/types/Artifact.d.ts +117 -0
  284. package/dist/src/types/Artifact.d.ts.map +1 -0
  285. package/dist/src/types/Artifact.js +35 -0
  286. package/dist/src/types/Artifact.js.map +1 -0
  287. package/dist/src/types/Calculation.d.ts +49 -0
  288. package/dist/src/types/Calculation.d.ts.map +1 -1
  289. package/dist/src/types/Comment.d.ts +23 -0
  290. package/dist/src/types/Comment.d.ts.map +1 -0
  291. package/dist/src/types/Comment.js +3 -0
  292. package/dist/src/types/Comment.js.map +1 -0
  293. package/dist/src/types/CompetenceKind.d.ts +70 -0
  294. package/dist/src/types/CompetenceKind.d.ts.map +1 -0
  295. package/dist/src/types/CompetenceKind.js +3 -0
  296. package/dist/src/types/CompetenceKind.js.map +1 -0
  297. package/dist/src/types/Composition.d.ts +80 -0
  298. package/dist/src/types/Composition.d.ts.map +1 -0
  299. package/dist/src/types/Composition.js +44 -0
  300. package/dist/src/types/Composition.js.map +1 -0
  301. package/dist/src/types/ConformanceClass.d.ts +22 -0
  302. package/dist/src/types/ConformanceClass.d.ts.map +1 -0
  303. package/dist/src/types/ConformanceClass.js +7 -0
  304. package/dist/src/types/ConformanceClass.js.map +1 -0
  305. package/dist/src/types/ConformanceTest.d.ts +206 -0
  306. package/dist/src/types/ConformanceTest.d.ts.map +1 -0
  307. package/dist/src/types/ConformanceTest.js +3 -0
  308. package/dist/src/types/ConformanceTest.js.map +1 -0
  309. package/dist/src/types/Constraint.d.ts +34 -0
  310. package/dist/src/types/Constraint.d.ts.map +1 -0
  311. package/dist/src/types/Constraint.js +3 -0
  312. package/dist/src/types/Constraint.js.map +1 -0
  313. package/dist/src/types/Design.d.ts +147 -0
  314. package/dist/src/types/Design.d.ts.map +1 -0
  315. package/dist/src/types/Design.js +10 -0
  316. package/dist/src/types/Design.js.map +1 -0
  317. package/dist/src/types/DiscrepancyRecord.d.ts +42 -0
  318. package/dist/src/types/DiscrepancyRecord.d.ts.map +1 -0
  319. package/dist/src/types/DiscrepancyRecord.js +3 -0
  320. package/dist/src/types/DiscrepancyRecord.js.map +1 -0
  321. package/dist/src/types/Form.d.ts +132 -1
  322. package/dist/src/types/Form.d.ts.map +1 -1
  323. package/dist/src/types/FormulasUsed.d.ts +95 -0
  324. package/dist/src/types/FormulasUsed.d.ts.map +1 -0
  325. package/dist/src/types/FormulasUsed.js +64 -0
  326. package/dist/src/types/FormulasUsed.js.map +1 -0
  327. package/dist/src/types/Instance.d.ts +105 -0
  328. package/dist/src/types/Instance.d.ts.map +1 -0
  329. package/dist/src/types/Instance.js +40 -0
  330. package/dist/src/types/Instance.js.map +1 -0
  331. package/dist/src/types/Invariant.d.ts +82 -0
  332. package/dist/src/types/Invariant.d.ts.map +1 -0
  333. package/dist/src/types/Invariant.js +50 -0
  334. package/dist/src/types/Invariant.js.map +1 -0
  335. package/dist/src/types/MapProfile.d.ts +36 -1
  336. package/dist/src/types/MapProfile.d.ts.map +1 -1
  337. package/dist/src/types/Monitor.d.ts +144 -0
  338. package/dist/src/types/Monitor.d.ts.map +1 -0
  339. package/dist/src/types/Monitor.js +74 -0
  340. package/dist/src/types/Monitor.js.map +1 -0
  341. package/dist/src/types/Note.d.ts +4 -1
  342. package/dist/src/types/Note.d.ts.map +1 -1
  343. package/dist/src/types/Package.d.ts +161 -0
  344. package/dist/src/types/Package.d.ts.map +1 -0
  345. package/dist/src/types/Package.js +3 -0
  346. package/dist/src/types/Package.js.map +1 -0
  347. package/dist/src/types/Passport.d.ts +125 -0
  348. package/dist/src/types/Passport.d.ts.map +1 -0
  349. package/dist/src/types/Passport.js +84 -0
  350. package/dist/src/types/Passport.js.map +1 -0
  351. package/dist/src/types/Quantity.d.ts +99 -0
  352. package/dist/src/types/Quantity.d.ts.map +1 -0
  353. package/dist/src/types/Quantity.js +24 -0
  354. package/dist/src/types/Quantity.js.map +1 -0
  355. package/dist/src/types/Reference.d.ts +2 -0
  356. package/dist/src/types/Reference.d.ts.map +1 -1
  357. package/dist/src/types/ReferenceMaterial.d.ts +53 -0
  358. package/dist/src/types/ReferenceMaterial.d.ts.map +1 -0
  359. package/dist/src/types/ReferenceMaterial.js +14 -0
  360. package/dist/src/types/ReferenceMaterial.js.map +1 -0
  361. package/dist/src/types/Requirement.d.ts +111 -0
  362. package/dist/src/types/Requirement.d.ts.map +1 -0
  363. package/dist/src/types/Requirement.js +10 -0
  364. package/dist/src/types/Requirement.js.map +1 -0
  365. package/dist/src/types/Role.d.ts +4 -0
  366. package/dist/src/types/Role.d.ts.map +1 -1
  367. package/dist/src/types/Series.d.ts +31 -0
  368. package/dist/src/types/Series.d.ts.map +1 -0
  369. package/dist/src/types/Series.js +14 -0
  370. package/dist/src/types/Series.js.map +1 -0
  371. package/dist/src/types/SourceDiscrepancy.d.ts +21 -0
  372. package/dist/src/types/SourceDiscrepancy.d.ts.map +1 -0
  373. package/dist/src/types/SourceDiscrepancy.js +13 -0
  374. package/dist/src/types/SourceDiscrepancy.js.map +1 -0
  375. package/dist/src/types/Standard.d.ts +68 -0
  376. package/dist/src/types/Standard.d.ts.map +1 -1
  377. package/dist/src/types/StateMachine.d.ts +43 -0
  378. package/dist/src/types/StateMachine.d.ts.map +1 -1
  379. package/dist/src/types/StateMachine.js +7 -0
  380. package/dist/src/types/StateMachine.js.map +1 -1
  381. package/dist/src/types/Subject.d.ts +432 -0
  382. package/dist/src/types/Subject.d.ts.map +1 -0
  383. package/dist/src/types/Subject.js +13 -0
  384. package/dist/src/types/Subject.js.map +1 -0
  385. package/dist/src/types/Symbol.d.ts +31 -1
  386. package/dist/src/types/Symbol.d.ts.map +1 -1
  387. package/dist/src/types/Table.d.ts +43 -0
  388. package/dist/src/types/Table.d.ts.map +1 -1
  389. package/dist/src/types/Term.d.ts +29 -0
  390. package/dist/src/types/Term.d.ts.map +1 -1
  391. package/dist/src/types/TestPointSet.d.ts +33 -0
  392. package/dist/src/types/TestPointSet.d.ts.map +1 -0
  393. package/dist/src/types/TestPointSet.js +11 -0
  394. package/dist/src/types/TestPointSet.js.map +1 -0
  395. package/dist/src/types/TestSequence.d.ts +105 -0
  396. package/dist/src/types/TestSequence.d.ts.map +1 -0
  397. package/dist/src/types/TestSequence.js +65 -0
  398. package/dist/src/types/TestSequence.js.map +1 -0
  399. package/dist/src/types/Text.d.ts +21 -0
  400. package/dist/src/types/Text.d.ts.map +1 -0
  401. package/dist/src/types/Text.js +38 -0
  402. package/dist/src/types/Text.js.map +1 -0
  403. package/dist/src/types/Time.d.ts +43 -0
  404. package/dist/src/types/Time.d.ts.map +1 -0
  405. package/dist/src/types/Time.js +14 -0
  406. package/dist/src/types/Time.js.map +1 -0
  407. package/dist/src/types/Twin.d.ts +155 -0
  408. package/dist/src/types/Twin.d.ts.map +1 -0
  409. package/dist/src/types/Twin.js +102 -0
  410. package/dist/src/types/Twin.js.map +1 -0
  411. package/dist/src/types/Verdict.d.ts +40 -0
  412. package/dist/src/types/Verdict.d.ts.map +1 -0
  413. package/dist/src/types/Verdict.js +14 -0
  414. package/dist/src/types/Verdict.js.map +1 -0
  415. package/dist/src/types/ViewProfile.d.ts +6 -0
  416. package/dist/src/types/ViewProfile.d.ts.map +1 -1
  417. package/dist/src/types/data.d.ts +27 -0
  418. package/dist/src/types/data.d.ts.map +1 -1
  419. package/dist/src/types/process.d.ts +231 -0
  420. package/dist/src/types/process.d.ts.map +1 -1
  421. package/dist-browser/index.mjs +6699 -661
  422. package/dist-cli/primmel.mjs +21462 -0
  423. package/package.json +61 -5
@@ -0,0 +1,4164 @@
1
+ "use strict";
2
+ // ─────────────────────────────────────────────────────────────────────
3
+ // primmel check — cross-layer linter (Primmel v2 plan, W8).
4
+ //
5
+ // First-class package checks beyond parse-time validation:
6
+ // C1 attribute scope vs bind path scope (forms)
7
+ // C2 reference targets resolve (req targets, test targets, form
8
+ // conformance ids, capability req/test refs, behavior verified_by,
9
+ // requirement binds_to)
10
+ // C3 classification dimension enums (dimension ids + values referenced
11
+ // in applicability blocks and test subjects exist; match all|exact on
12
+ // a single-cardinality dimension warns — it reduces to match any)
13
+ // C4 store names unique (storable classes)
14
+ // C5 every requirement is verified — targeted by ≥1 test, provision'd
15
+ // by a process, or declaring a non-test verification method
16
+ // (refined, TODO.roadmap/17); every test targets ≥1 req
17
+ // C6 subject anatomy: aspect declared under the wrong family (or
18
+ // undeclared aspect kind) is an error (anatomy-family)
19
+ // C7 every has.characteristics entry names its derivation
20
+ // (anatomy-characteristic-derivation)
21
+ // C8 every does.behavior ref resolves to a declared behavior
22
+ // (anatomy-behavior-resolves)
23
+ // C9 a subject's extends target is a declared subject
24
+ // (subject-extends-resolves)
25
+ // C10 executable process: exactly one start event (process-one-start)
26
+ // C11 end events on every terminal path — mandatory on empty gateway
27
+ // branches; flow edges reference declared steps (process-terminal-end)
28
+ // C12 gateway edge conditions + step I/O name declared registers
29
+ // (process-flow-names-resolve)
30
+ // C13 executable steps realize the signature: OUT written (error),
31
+ // IN read (warning) (process-signature-realized)
32
+ // C14 a recurrence (flow cycle) passes through a timer event WITH a
33
+ // declared period — no unguarded infinite self-loops
34
+ // (process-timer-recurrence)
35
+ // C15 a timer_event declares its recurrence period — a period-less
36
+ // timer is not a loop guard (process-timer-period)
37
+ // C16 step ids are unique within one does body
38
+ // (process-step-ids-unique)
39
+ // C17 instance values respect the attribute scope discipline: stated at
40
+ // the declared scope or lower; sample-scope only in test_context of a
41
+ // sample-level instance; test_context/classification level checks
42
+ // (instance-scope)
43
+ // C18 every instance carries definition_versions pins (INV-8)
44
+ // (instance-version-pin)
45
+ // C19 instance chain links (model/group/family) resolve and are acyclic
46
+ // (chain-acyclic)
47
+ // C20 every instance's `of` resolves to a declared subject or instrument
48
+ // (instance-of-resolves)
49
+ // C21 mapping-resolves: both ends of every mapping resolve — the source
50
+ // is a declared component; the target's namespace matches the profile
51
+ // namespace and a declared `Namespace#ElementID` alias element exists
52
+ // C22 mapping-direction: mappings run implementation → reference only
53
+ // (no namespaced source; no mapping into the model's own namespace)
54
+ // C23 mapping-calculus-consistency: an authored coverage assertion that
55
+ // disagrees with the computed calculus is an error
56
+ // C24 import-not-mapping: an import (uses/extends) may not be expressed
57
+ // as a mapping, nor a mapping as an import
58
+ // C25 mapping-description: a mapping without description is a warning
59
+ // at audit strictness
60
+ // C26 view-read-only: a view names declared elements and reads against
61
+ // a declared map namespace — it never invents or edits mappings
62
+ // C27 uses-resolves: every uses/extends entry resolves to a package
63
+ // (needs the resolvePackage locator); extends → uses deprecation
64
+ // is a warning under the same rule
65
+ // C28 uses-no-redefine: a downstream package may reference upstream ids
66
+ // but never redeclare them
67
+ // C29 uses-cycle: the uses graph is acyclic
68
+ // C30 provides-consumed-or-waived: every provides entry is required by a
69
+ // downstream package or explicitly waived (warning)
70
+ // C31 requires-satisfied: every requires entry names a composed package
71
+ // id or one of its provides entries
72
+ // C32 inv1-no-bare-quantity: a value stated for a declared physical
73
+ // quantity (attribute unit, quantity_kind, or QuantityValue
74
+ // value_type) carries a unit — an empty-string unit token counts as
75
+ // bare; numeric condition-set entries carry a unit too (free-text
76
+ // values stay legal) — INV-1, no bare numbers (TODO.roadmap/06)
77
+ // C33 quantity-coherence: register integrity (unit kinds resolve, no
78
+ // cross-register redefinition) + comparison coherence on quantity
79
+ // KINDS (instance values vs attribute definitions, condition-set
80
+ // entries, symbols, verdicts) — mass vs time fails; unmapped units
81
+ // are warnings
82
+ // C34 duality-coherence: a dual carries ≥1 role; each stated role of a
83
+ // dual bound to a declared physical quantity carries a unit (or an
84
+ // explicit kind) — INV-1; both roles stated ⇒ same quantity kind
85
+ // (compatible unit); tolerance belongs to designed, uncertainty to
86
+ // exhibited (warnings)
87
+ // C35 time-format: timer-event recurrence periods are ISO 8601
88
+ // durations (closes task 02's deferred format check); instance
89
+ // values for time-typed attributes (date/datetime/duration/period)
90
+ // match their ISO 8601 shape
91
+ // C36 map-type: a `map<K, V>` field/value type is well-formed
92
+ // (K = string or enum id, V a valid type expression)
93
+ // C37 state-fires-resolve: every step `fires` names a declared
94
+ // transition action of the process's bound state machine
95
+ // (TODO.roadmap/07)
96
+ // C38 state-family-separation: no cross-references between the
97
+ // operational and lifecycle machine families — no lifecycle cascade
98
+ // into an operational machine, no operational cascade into a
99
+ // lifecycle machine, no subject has.state bound to a lifecycle
100
+ // machine
101
+ // C39 state-machine-states-referenced: every `#state` literal in a
102
+ // `self.state` gate resolves against the bound machine's states
103
+ // C40 anatomy-state-resolves: a subject's has.state names a declared
104
+ // state machine (family checked by C38)
105
+ // C41 precondition-on-violation-known: a precondition's on_violation
106
+ // parses as a free string; the only known outcome is "invalid"
107
+ // (others warn) — a state gate declared on_violation fail ALWAYS
108
+ // warns, naming the doctrine: a violated run-validity precondition
109
+ // voids the run, it never fails the instrument
110
+ // C42 promise-target-resolves: a promise's target is a declared
111
+ // characteristic (of the owning subject) or behavior
112
+ // C43 promise-verifiable: no verified_by declared AND no verifying
113
+ // requirement/test derivable (requirements/tests binding the same
114
+ // target) — a warning at authoring (TODO.roadmap/08)
115
+ // C44 promise-not-bare-value: a promise that merely RESTATES a declared
116
+ // attribute value (attribute target + bare quantity level, no
117
+ // conditions, no verification linkage) is an error — bare parameter
118
+ // values stay `origin: declared` attributes. Targeting an
119
+ // attribute/dimension with a genuine claim ABOUT it (conditioned,
120
+ // envelope/range, symbolic level, or verified) is legal.
121
+ // C45 artifact-def-contract: an artifact definition's content contract
122
+ // is well-formed — ≥1 named field, every field typed, no duplicate
123
+ // fields, media entries refine declared fields, produced_when is
124
+ // per_measurement | per_interval <ISO-8601 duration> | on_event
125
+ // <event> (TODO.roadmap/09)
126
+ // C46 artifact-instance-resolves: subject is.artifacts / has.
127
+ // artifact_instances slots resolve; a conformance test's
128
+ // produces_artifacts resolve; an instance's `of` resolves to a
129
+ // declared artifact definition; content fields ⊆ the contract;
130
+ // required contract fields populated; the producer (by) named;
131
+ // per_measurement instances link a producing run/report
132
+ // C47 artifact-evidence-separation: the MECE firewall — an artifact is
133
+ // an output OF the instrument, an EvidenceRecord is a record OF the
134
+ // test; no artifact contract field name doubles as a conformance-test
135
+ // variable/observable or form field (and vice versa)
136
+ // C48 characteristic-one-home: with subject characteristics declared,
137
+ // a `verdict` construct carrying its own `derive` defines a
138
+ // verdict-quantity derivation outside the primary model — an error
139
+ // (TODO.roadmap/10; the OIML SMART verdict-no-shadow /
140
+ // verdict-restatement spirit, re-pointed at the new home)
141
+ // C49 characteristic-behavior-link: a characteristic's behavior ref
142
+ // resolves to a declared behavior
143
+ // C50 characteristic-derivation-inputs: an ocl{…} derivation's reads
144
+ // resolve — self.<p> against the subject's parameters, bare names
145
+ // against the behavior-I/O vocabulary (symbols, attributes,
146
+ // subject parameters)
147
+ // C58 activity-kind-resolves: a process's activity_kind ids resolve
148
+ // against a declared activity_archetype register when one is in
149
+ // scope; the register's own parent references resolve within the
150
+ // register (TODO.roadmap/39; silent in a register-less package)
151
+ // C59 segregation-members-resolve: a process's segregation constraints
152
+ // (TODO.roadmap/39b — ISO/IEC 17065 role segregation) are
153
+ // well-formed: pair members resolve to declared processes (or the
154
+ // reserved case_personnel token), are distinct, and include the
155
+ // owning process
156
+ // C74 process-io-type-coherence (TODO.roadmap/38): typed transition
157
+ // boundaries — one name (signature in/out, registers) carries ONE
158
+ // type; two declarations of the same name whose quantity
159
+ // kinds/units disagree (mass vs time) make every boundary that
160
+ // hands the name off unsound (∘: t₁: A→B, t₂: B→C ⊢ t₂∘t₁: A→C)
161
+ // C75 process-flow-io-cover (TODO.roadmap/38): the step-chain dataflow
162
+ // covers every read — a step read (or edge-condition read) of a
163
+ // declared name that is neither provided (IN parameter, instance
164
+ // value, state) nor written on every incoming flow path is an
165
+ // error; a write no step reads, no condition references, and no
166
+ // OUT parameter names is a dead-output warning (a capture-step
167
+ // write lands in evidence, so it is never dead)
168
+ // C76 subprocess-signature-bound (TODO.roadmap/38): a `calls` step
169
+ // names a declared process (resolved on the composed package — a
170
+ // call across a `uses` boundary is checked post-merge); when the
171
+ // callee declares a signature, the with { in / out } block binds
172
+ // every IN from a caller register and maps every OUT back to one,
173
+ // kind-compatible on both legs
174
+ // C60 serve-targets-resolve (TODO.roadmap/32, doctrine §14.12): every
175
+ // serve binding names a declared aspect of the owning subject
176
+ // ([level.]{parameters|classification|test_context}.<key>, a bare
177
+ // attribute/characteristic/dimension name, state,
178
+ // environmental_context) and a declared, unambiguous endpoint
179
+ // operation of value-channel kind (query | subscribe); the served
180
+ // aspect and the operation payload are unit/quantity-kind coherent.
181
+ // The endpoint legs ride the same rule: an operation's serves names
182
+ // resolve as subject aspects, its does names as declared behaviors
183
+ // C61 payload-schema-quantity: every endpoint operation declares a
184
+ // known kind (query | subscribe | invoke) and a payload schema that
185
+ // is a QuantityValue per INV-1 — quantity kind, unit (the register's
186
+ // dimensionless id when non-quantity), and timestamp true (a value
187
+ // without a time is not evidence, §14.3)
188
+ // C62 access-scope-covers-serves: every endpoint operation is covered
189
+ // by exactly one access scope (public | registered | authority);
190
+ // access entries name declared operations of the endpoint (§14.12:
191
+ // every endpoint operation has an access scope)
192
+ // C63 freshness-required-on-live-bindings: a serve binding without
193
+ // fresh_within is an error (§14.12 — no stale semantics, no live
194
+ // binding); the window must parse (shorthand 5s/1min/1h or ISO 8601
195
+ // with fixed-length components)
196
+ // C64 endpoint-profile-resolves: an endpoint's profile names a declared
197
+ // connector_profile or a built-in (rest_json, mqtt, opc_ua,
198
+ // file_drop) — the model is protocol-neutral; profiles bind
199
+ // protocols (§14.4)
200
+ // C65 monitor-subject-resolves (TODO.roadmap/34, doctrine §14.5/§14.12):
201
+ // a monitor's over set is non-empty and every ref names a declared
202
+ // subject (a monitor watching nothing judges nothing)
203
+ // C66 monitor-trigger-wellformed: at least one trigger — without
204
+ // triggers "continuous" has no clock (§14.5 step 1); a timer's every
205
+ // window parses (the freshness-window syntax); a signal names its
206
+ // signal; a change names an aspect resolving against a monitored
207
+ // subject (the serve aspect vocabulary — ONE resolver)
208
+ // C67 monitor-evaluate-resolves: evaluate refs resolve to
209
+ // requirements/promises applicable to the monitored subjects
210
+ // (§14.12) — all / applicable_to(…) expand per twin at runtime;
211
+ // explicit refs resolve (requirement ids against the package's
212
+ // requirements, promise ids against the monitored subjects'
213
+ // promises)
214
+ // C68 monitor-fail-escalation: a monitor without an escalation path
215
+ // for `fail` is a warning (§14.12, verbatim) — pass accrues history;
216
+ // fail/invalid must act
217
+ // C69 monitor-escalation-resolves: escalation outcomes are verdict
218
+ // outcomes (pass | fail | indeterminate | invalid); actions are
219
+ // notify | flag_certificate | open_service_case; a notify names a
220
+ // declared role
221
+ // C70 monitor-emit-sinks: evidence and verdicts streams are each
222
+ // emitted exactly once with a named sink (§14.5 step 6: appended to
223
+ // the workspace — facts only; permanent)
224
+ //
225
+ // ── Coverage audits (TODO.roadmap/17, concept doc §11.5) ──
226
+ // The aspect↔requirement↔test↔form↔verdict closure. The requirement→test
227
+ // link is C5 (base, refined): a requirement is covered when a
228
+ // conformance test targets it, a process validate_provision's it, or it
229
+ // declares a non-test verification method (definitional / examination /
230
+ // inspection / documentation / computational / deferred — deliberate
231
+ // exclusions are recorded, not gaps); an undeclared gap is always a
232
+ // finding. The remaining links are the coverage family:
233
+ // C51 coverage-test-evidence: a conformance test leaving no evidence —
234
+ // no form declares it (conformance_process), no result_forms, no
235
+ // report_rows, and no form covers a requirement it targets —
236
+ // following inherits_from chains (AUDIT level, warning)
237
+ // C52 coverage-form-judgment: a test-evidence form (binding a
238
+ // conformance process) with no pass_fail, no field
239
+ // evaluation/verdict, and no requirements binding — evidence
240
+ // gathered and never weighed (AUDIT level, warning)
241
+ // C53 coverage-uses-bound: every requirement limit.uses input is
242
+ // bound — unprefixed ids resolve against attributes / symbols /
243
+ // requirements / dimensions / behaviors ('load' is the declared
244
+ // per-load-point free variable, deep-audit-r60 F6); observable: and
245
+ // formula: prefixes resolve against symbols and calculations
246
+ // (NORMAL level, error)
247
+ // C54 coverage-lookup-table-exists: table:/profile: uses prefixes and
248
+ // calculation lookup profiles resolve to declared tables/profiles
249
+ // (lookupMPE/lookupProfile targets) (NORMAL level, error)
250
+ // C55 coverage-budget: the package's declared coverage_budget caps its
251
+ // C51/C52 warning count — exceeded is an error, slack warns (the
252
+ // allowlist only shrinks)
253
+ // C56 allowlist-malformed: every allowlist entry names a real rule id,
254
+ // a non-empty match glob, a reason, and an audit_ref (§11.9)
255
+ // C57 allowlist-stale: an allowlist entry whose rule is active at the
256
+ // current level but matches no issue is an error — the data was
257
+ // fixed, the entry must die (§11.3)
258
+ //
259
+ // ── Edition lifecycle (TODO.roadmap/28, doctrine ch. 13 §13.4/§13.7) ──
260
+ // Versioning relations live on the package manifest, never in subject
261
+ // models; editions are packagings orthogonal to the core.
262
+ // C77 edition-status: a current/preview edition packages the edition
263
+ // register's newest entry (the status enum itself is a parser error,
264
+ // like `kind`)
265
+ // C78 edition-validity-window: the manifest validity window is
266
+ // well-formed ISO 8601; `to` not before `from`
267
+ // C79 edition-supersedes-resolves: supersedes/replaces targets are
268
+ // well-formed URNs, never the package itself, resolve against the
269
+ // edition register (same-document targets), and the supersedes graph
270
+ // over sibling manifests is acyclic
271
+ // C80 edition-pin-resolves (INV-8): every instance's
272
+ // definition_versions pin resolves against the package's edition
273
+ // register — an unresolvable pin breaks re-execution (§13.5)
274
+ // C81 product-maps-resolves: a product reference package declares its
275
+ // manufacturer, product designation, and the standards-reference
276
+ // packages it maps to (maps_to) — the register resolves and agrees
277
+ // with the model's map profiles (TODO.roadmap/36, doctrine ch. 15)
278
+ // C82 product-unmapped-promises: an IS promise of a product reference
279
+ // package that no mapping sources is a brochure claim — a warning
280
+ // at authoring (doctrine ch. 15 §15.2)
281
+ // C83 abstract-import-pinned: a uses edge to a product reference
282
+ // package carries a version pin (uses { <id>@<edition> }) that
283
+ // resolves against the product's edition register — no unpinned
284
+ // reference consumption (doctrine ch. 15 §15.3)
285
+ //
286
+ // Levels (TODO.roadmap/17): the DEFAULT level runs the normal-level
287
+ // rules at their catalog severities. --audit additionally runs the
288
+ // audit-level rules (C25, C51, C52) and enforces the coverage budget.
289
+ // --strict promotes every warning to an error — EXCEPT KNOWN
290
+ // (allowlisted) issues and budget-covered C51/C52 warnings (the budget
291
+ // is their allowance). The package allowlist lives in
292
+ // <dir>/.primmel-allowlist.prl (see check-allowlist.ts); the rule
293
+ // catalog lives in check-rules.ts (`primmel check --rules`).
294
+ //
295
+ // Manifest-only stopgap (TODO.roadmap/05, until task 13 retires the
296
+ // textual-include transport): when checkPackage runs WITHOUT a
297
+ // resolvePackage locator, full composition cannot run — but the shipped
298
+ // manifests still declare uses/requires/provides, so a dangling uses id
299
+ // would go completely unreported. checkManifestResolution() closes that
300
+ // hole: it parses the package's manifest plus the manifests of its
301
+ // SIBLING package directories (id match) and verifies, WITHOUT composing
302
+ // content, that every uses/extends entry resolves, the graph is acyclic,
303
+ // and every requires entry names a closure package id or one of its
304
+ // provides entries. Issues report under the same C27/C29/C31 codes with
305
+ // a "(manifest-only …)" marker.
306
+ // Each check returns issues: { check, severity, message }.
307
+ // ─────────────────────────────────────────────────────────────────────
308
+ Object.defineProperty(exports, "__esModule", { value: true });
309
+ exports.checkPackage = checkPackage;
310
+ exports.checkManifestResolution = checkManifestResolution;
311
+ exports.checkSpellingCodes = checkSpellingCodes;
312
+ exports.checkEditionLifecycle = checkEditionLifecycle;
313
+ const fs_1 = require("fs");
314
+ const path_1 = require("path");
315
+ const package_1 = require("./ser-des/package");
316
+ const prm_1 = require("./ser-des/prm");
317
+ const mapping_coverage_1 = require("./mapping-coverage");
318
+ const time_1 = require("./time");
319
+ const type_expr_1 = require("./type-expr");
320
+ const model_diff_1 = require("./model-diff");
321
+ const Twin_1 = require("./types/Twin");
322
+ const Composition_1 = require("./types/Composition");
323
+ const Monitor_1 = require("./types/Monitor");
324
+ const Passport_1 = require("./types/Passport");
325
+ const TestSequence_1 = require("./types/TestSequence");
326
+ const FormulasUsed_1 = require("./types/FormulasUsed");
327
+ const tokenize_1 = require("./ser-des/tokenize");
328
+ const operational_state_1 = require("./operational-state");
329
+ const check_rules_1 = require("./check-rules");
330
+ const check_allowlist_1 = require("./check-allowlist");
331
+ const text_coverage_1 = require("./text-coverage");
332
+ const spelling_1 = require("./spelling");
333
+ const BIND_SCOPES = {
334
+ 'family.parameters': 'family',
335
+ 'group.parameters': 'group',
336
+ 'model.parameters': 'model',
337
+ 'sample.test_context': 'sample',
338
+ };
339
+ /** Identity paths (not AttributeDefinitions — fields on the entity classes). */
340
+ const IDENTITY_PREFIXES = [
341
+ 'model.identity.',
342
+ 'model.model_designation',
343
+ 'model.hardware_revision',
344
+ 'family.family_designation',
345
+ 'manufacturer.',
346
+ 'sample.serial_number',
347
+ 'sample.sample_number',
348
+ 'sample.status',
349
+ 'sample.condition',
350
+ 'application.',
351
+ 'test_report.',
352
+ 'test_request.',
353
+ ];
354
+ /** Enum-name aliases: a classification path may use the enum name while the
355
+ * attribute/dimension id differs (app resolves both spellings). */
356
+ const DIM_ALIASES = {
357
+ humidity_class: 'humidity_symbol',
358
+ };
359
+ /** A bare numeric literal (same shape as ser-des/config/quantity.ts NUMERIC). */
360
+ const NUMERIC_TOKEN = /^-?\d+(\.\d+)?([eE][+-]?\d+)?$/;
361
+ function isIdentityPath(path) {
362
+ return IDENTITY_PREFIXES.some(p => path.startsWith(p));
363
+ }
364
+ function attrId(standard, id) {
365
+ return (standard.attributeDefinitions ?? []).find((a) => a.id === id);
366
+ }
367
+ function checkPackage(dir, options = {}) {
368
+ const issues = [];
369
+ const err = (check, message) => issues.push({ check, severity: 'error', message });
370
+ const warn = (check, message) => issues.push({ check, severity: 'warning', message });
371
+ // ── C27–C31: `uses` composition (TODO.roadmap/05) ──────────────────
372
+ // Composition errors are HARD load errors (the merge cannot proceed),
373
+ // so the loader throws CompositionError; the linter reports them as
374
+ // rule-identified issues instead. Composition WARNINGS (unconsumed
375
+ // provides, extends deprecation) come back in the load's issue list.
376
+ // The parse-time duplicate-id issues ride the same list — surfaced
377
+ // under C96 (the E11 review finding: every id-keyed collection's
378
+ // uniqueness delegation — C84 constraints, C94 formulas-used — is
379
+ // invisible if the linter drops them here).
380
+ const COMPOSITION_RULE_CHECKS = {
381
+ 'uses-resolves': 'C27',
382
+ 'uses-no-redefine': 'C28',
383
+ 'uses-cycle': 'C29',
384
+ 'requires-satisfied': 'C31',
385
+ };
386
+ let standard;
387
+ try {
388
+ const loaded = (0, package_1.loadPackageWithIssues)(dir, {
389
+ resolvePackage: options.resolvePackage,
390
+ });
391
+ standard = loaded.standard;
392
+ for (const i of loaded.issues) {
393
+ if (i.code === 'provides-unconsumed') {
394
+ warn('C30', i.message);
395
+ }
396
+ else if (i.code === 'extends-deprecated') {
397
+ warn('C27', i.message);
398
+ }
399
+ else if (i.code === 'duplicate-id') {
400
+ err('C96', `${i.message} (duplicate-id)`);
401
+ }
402
+ }
403
+ }
404
+ catch (e) {
405
+ if (e instanceof package_1.CompositionError) {
406
+ err(COMPOSITION_RULE_CHECKS[e.rule] ?? 'C27', e.message);
407
+ return issues;
408
+ }
409
+ throw e;
410
+ }
411
+ // Manifest-only stopgap: without a locator the composition rules cannot
412
+ // run, but the declared imports can still be verified against the
413
+ // manifests of the SIBLING package directories — a dangling uses id or
414
+ // an unsatisfiable requires token is caught in CI without composing
415
+ // content (TODO.roadmap/05, until task 13 retires textual includes).
416
+ if (!options.resolvePackage) {
417
+ const m = standard.packageManifest;
418
+ if (m && ((0, package_1.effectiveUses)(m).length > 0 || (m.requires ?? []).length > 0)) {
419
+ issues.push(...checkManifestResolution(dir));
420
+ }
421
+ }
422
+ // ── C77–C80: edition lifecycle (TODO.roadmap/28, doctrine §13.4/§13.7) ──
423
+ // Versioning relations live on the package manifest, never in subject
424
+ // models: status/register coherence (C77), validity windows (C78),
425
+ // supersedes/replaces resolution + acyclicity across sibling manifests
426
+ // (C79), and the INV-8 pin — every instance's definition_versions
427
+ // resolves against the package's edition register (C80).
428
+ issues.push(...checkEditionLifecycle(dir, standard));
429
+ // ── C89: ISO 24229 spelling codes (TODO.roadmap/25, doctrine ch. 10) ──
430
+ issues.push(...checkSpellingCodes(standard));
431
+ const reqIds = new Set((standard.requirements ?? []).map((r) => r.id));
432
+ const testIds = new Set((standard.conformanceTests ?? []).map((t) => t.id));
433
+ const attrIds = new Set((standard.attributeDefinitions ?? []).map((a) => a.id));
434
+ const behaviorIds = new Set((standard.behaviors ?? []).map((b) => b.id));
435
+ const subjectIds = new Set((standard.subjects ?? []).map((s) => s.id));
436
+ // Observables live in the symbols registry, not the attribute layer —
437
+ // binds_to / limit.uses may reference them (e.g. sample.test_context
438
+ // quantities that are measured test outputs).
439
+ const symbolIds = new Set((standard.symbols ?? []).map((s) => s.id));
440
+ const dimIds = new Map();
441
+ const dimCardinality = new Map();
442
+ for (const inst of standard.instruments ?? []) {
443
+ for (const d of inst.dimensions ?? []) {
444
+ dimIds.set(d.id, new Set(d.values.map(v => v.id)));
445
+ if (d.cardinality) {
446
+ dimCardinality.set(d.id, d.cardinality);
447
+ }
448
+ }
449
+ }
450
+ // C1 — bind path scope vs attribute scope
451
+ for (const form of standard.forms ?? []) {
452
+ const checkField = (f) => {
453
+ if (f.bind) {
454
+ const parts = String(f.bind).split('.');
455
+ const prefix = parts.slice(0, 2).join('.');
456
+ const id = parts[2];
457
+ const scope = BIND_SCOPES[prefix];
458
+ if (isIdentityPath(String(f.bind))) {
459
+ // identity binds are always valid
460
+ }
461
+ else if (scope) {
462
+ if (!attrIds.has(id)) {
463
+ err('C1', `form ${form.id}: bind "${f.bind}" — attribute "${id}" not defined`);
464
+ }
465
+ else {
466
+ const a = attrId(standard, id);
467
+ if (a && a.scope && a.scope !== scope) {
468
+ err('C1', `form ${form.id}: bind "${f.bind}" — attribute scope "${a.scope}" ≠ path scope "${scope}"`);
469
+ }
470
+ }
471
+ }
472
+ }
473
+ (f.fields ?? []).forEach(checkField);
474
+ };
475
+ (form.fields ?? []).forEach(checkField);
476
+ }
477
+ // C2 — reference targets resolve
478
+ for (const t of standard.conformanceTests ?? []) {
479
+ for (const target of t.targets ?? []) {
480
+ if (!reqIds.has(target)) {
481
+ err('C2', `conformance test ${t.id}: target "${target}" is not a declared requirement`);
482
+ }
483
+ }
484
+ if (t.inheritsFrom && !testIds.has(t.inheritsFrom)) {
485
+ err('C2', `conformance test ${t.id}: inherits_from "${t.inheritsFrom}" not found`);
486
+ }
487
+ }
488
+ for (const form of standard.forms ?? []) {
489
+ for (const pid of form.conformanceProcessIds ??
490
+ (form.conformanceProcessId ? [form.conformanceProcessId] : [])) {
491
+ if (!testIds.has(pid)) {
492
+ err('C2', `form ${form.id}: conformance_process "${pid}" not found`);
493
+ }
494
+ }
495
+ }
496
+ for (const c of standard.capabilities ?? []) {
497
+ for (const r of c.satisfiesRequirements ?? []) {
498
+ if (!reqIds.has(r)) {
499
+ err('C2', `capability ${c.id}: satisfies_requirements "${r}" not found`);
500
+ }
501
+ }
502
+ for (const t of c.verifiedByTests ?? []) {
503
+ if (!testIds.has(t)) {
504
+ err('C2', `capability ${c.id}: verified_by_tests "${t}" not found`);
505
+ }
506
+ }
507
+ }
508
+ for (const b of standard.behaviors ?? []) {
509
+ for (const t of b.verifiedBy ?? []) {
510
+ if (!testIds.has(t)) {
511
+ err('C2', `behavior ${b.id}: verified_by "${t}" not found`);
512
+ }
513
+ }
514
+ }
515
+ // A verdict's behavior link (TODO.roadmap/10) resolves to a declared
516
+ // behavior — the transport form of C49 characteristic-behavior-link.
517
+ for (const v of standard.verdicts ?? []) {
518
+ if (v.behavior && !behaviorIds.has(v.behavior)) {
519
+ err('C2', `verdict ${v.id}: behavior "${v.behavior}" is not a declared behavior`);
520
+ }
521
+ }
522
+ for (const r of standard.requirements ?? []) {
523
+ for (const p of r.bindsTo ?? []) {
524
+ const parts = String(p).split('.');
525
+ const id = parts[2];
526
+ if (!id) {
527
+ continue;
528
+ }
529
+ if (parts[1] === 'classification') {
530
+ // classification paths reference DIMENSION ids (with enum-name aliases)
531
+ const dim = DIM_ALIASES[id] ?? id;
532
+ if (!dimIds.has(dim) && !attrIds.has(id) && !attrIds.has(dim)) {
533
+ err('C2', `requirement ${r.id}: binds_to "${p}" — dimension "${id}" not declared`);
534
+ }
535
+ continue;
536
+ }
537
+ if (parts[1] === 'behaviors') {
538
+ // behavior paths reference the behaviors registry (documentary binds)
539
+ if (!behaviorIds.has(id)) {
540
+ err('C2', `requirement ${r.id}: binds_to "${p}" — behavior "${id}" not declared`);
541
+ }
542
+ continue;
543
+ }
544
+ if (isIdentityPath(String(p))) {
545
+ continue;
546
+ }
547
+ if (!attrIds.has(id) && !symbolIds.has(id)) {
548
+ err('C2', `requirement ${r.id}: binds_to "${p}" — attribute "${id}" not defined`);
549
+ }
550
+ }
551
+ // NOTE: limit.uses resolution is the coverage family's anchoring leg
552
+ // — C53 (coverage-uses-bound), below. C2 keeps the binds_to paths.
553
+ }
554
+ // C3 — dimension ids + values exist
555
+ // test_subject reserves two non-dimension annotation keys (cc.yaml keeps
556
+ // the block open via additionalProperties): `component` names the
557
+ // instrument component under test (e.g. R 91's ego speed meter),
558
+ // `description` annotates the subject block. Neither is a dimension.
559
+ const TEST_SUBJECT_RESERVED = new Set(['component', 'description']);
560
+ for (const t of standard.conformanceTests ?? []) {
561
+ for (const [dim, value] of Object.entries(t.testSubject ?? {})) {
562
+ if (TEST_SUBJECT_RESERVED.has(dim)) {
563
+ continue;
564
+ }
565
+ if (!dimIds.has(dim)) {
566
+ err('C3', `conformance test ${t.id}: test_subject dimension "${dim}" not declared`);
567
+ }
568
+ else if (!dimIds.get(dim).has(String(value))) {
569
+ err('C3', `conformance test ${t.id}: test_subject ${dim}="${value}" not in the dimension's values`);
570
+ }
571
+ }
572
+ }
573
+ for (const rc of standard.requirementClasses ?? []) {
574
+ void rc;
575
+ }
576
+ for (const r of standard.requirements ?? []) {
577
+ for (const a of r.applicability ?? []) {
578
+ if (!dimIds.has(a.dimension)) {
579
+ err('C3', `requirement ${r.id}: applicability dimension "${a.dimension}" not declared`);
580
+ }
581
+ else {
582
+ for (const v of a.values ?? []) {
583
+ if (!dimIds.get(a.dimension).has(v)) {
584
+ err('C3', `requirement ${r.id}: applicability ${a.dimension}="${v}" not in the dimension's values`);
585
+ }
586
+ }
587
+ // Universal/exact matching is the set-dimension semantics — on a
588
+ // single-cardinality dimension the selection holds at most one
589
+ // value, so `match all` and `match exact` both reduce to plain
590
+ // membership (equivalent to the default any). Flag the redundant
591
+ // mode so the data author either sets the dimension's cardinality
592
+ // or drops the mode.
593
+ if ((a.match === 'all' || a.match === 'exact') &&
594
+ dimCardinality.get(a.dimension) !== 'set') {
595
+ warn('C3', `requirement ${r.id}: applicability ${a.dimension} match ${a.match} on a single-cardinality dimension — only meaningful on set dimensions ('all' reduces to 'any'; a multi-value 'exact' is unsatisfiable on one value)`);
596
+ }
597
+ }
598
+ }
599
+ }
600
+ // C4 — store names unique
601
+ const stores = new Map();
602
+ for (const c of standard.dataclasses ?? []) {
603
+ if (c.store) {
604
+ if (stores.has(c.store)) {
605
+ err('C4', `store "${c.store}" declared by both ${stores.get(c.store)} and ${c.id}`);
606
+ }
607
+ stores.set(c.store, c.id);
608
+ }
609
+ }
610
+ // C5 — coverage: req ⇄ test linkage (refined, TODO.roadmap/17). A
611
+ // requirement is COVERED — no finding — when any of:
612
+ // (a) a conformance test targets it (the base law);
613
+ // (b) a process validate_provision's it (the OIML-CS `/req/cs/*`
614
+ // provisions are verified BY THE PROCESS, never by a conformance
615
+ // test — the abstract-process doctrine of the smart linker's
616
+ // R19);
617
+ // (c) it declares a non-test verification method (definitional /
618
+ // examination / inspection / documentation / computational /
619
+ // deferred) — a deliberate exclusion is a legitimate answer,
620
+ // recorded, not a gap (concept doc §11.5).
621
+ // An undeclared gap is always a finding (§11.9): a requirement with no
622
+ // targeting test AND no recorded non-test verification warns. The
623
+ // reverse leg is unchanged: a test targeting nothing warns.
624
+ const covered = new Set();
625
+ for (const t of standard.conformanceTests ?? []) {
626
+ for (const target of t.targets ?? []) {
627
+ covered.add(target);
628
+ }
629
+ }
630
+ const provisioned = new Set();
631
+ for (const p of standard.processes ?? []) {
632
+ for (const id of p.provisionRefs ?? []) {
633
+ provisioned.add(id);
634
+ }
635
+ }
636
+ const NON_TEST_VERIFICATION = new Set([
637
+ 'definitional',
638
+ 'examination',
639
+ 'inspection',
640
+ 'documentation',
641
+ 'computational',
642
+ 'deferred',
643
+ // The smart schema's verification-method enum (data/schemas/rc.yaml)
644
+ // additionally allows 'implicit' — reconcile the two vocabularies so
645
+ // an implicit-verified requirement is a recorded exclusion, never a
646
+ // false-positive C5 (unused today).
647
+ 'implicit',
648
+ ]);
649
+ for (const r of standard.requirements ?? []) {
650
+ if (covered.has(r.id) || provisioned.has(r.id)) {
651
+ continue;
652
+ }
653
+ if (NON_TEST_VERIFICATION.has(r.verificationMethod)) {
654
+ continue; // deliberate exclusion — recorded, not a gap
655
+ }
656
+ warn('C5', `requirement ${r.id}: no conformance test targets it and it declares no non-test verification — an undeclared coverage gap is always a finding (req-test-coverage)`);
657
+ }
658
+ for (const t of standard.conformanceTests ?? []) {
659
+ if ((t.targets ?? []).length === 0) {
660
+ warn('C5', `conformance test ${t.id}: targets no requirement`);
661
+ }
662
+ }
663
+ // C6 — subject anatomy: every aspect declared under exactly one family
664
+ // (anatomy-family). The parser records wrong-family and undeclared
665
+ // aspect keys on Subject.misplacedAspects; value-level markers catch
666
+ // the two classic content confusions (a test-dependent value among
667
+ // design parameters; a condition tier among attributes). NOTE: those
668
+ // two value-level checks (the test-dependent marker in
669
+ // design_parameters; condition-tier keys in has.attributes) are
670
+ // HEURISTICS beyond the spec's literal wording — the spec mandates
671
+ // aspect→family placement only.
672
+ const ASPECT_FAMILY = {
673
+ metadata: 'is',
674
+ provenance: 'is',
675
+ structure: 'is',
676
+ design_parameters: 'is',
677
+ designed_conditions: 'is',
678
+ promises: 'is',
679
+ artifacts: 'is',
680
+ endpoint: 'is',
681
+ attributes: 'has',
682
+ dimensions: 'has',
683
+ state: 'has',
684
+ characteristics: 'has',
685
+ environmental_context: 'has',
686
+ artifact_instances: 'has',
687
+ serve: 'has',
688
+ behavior: 'does',
689
+ };
690
+ const CONDITION_TIERS = new Set(['reference', 'rated', 'limiting']);
691
+ for (const s of standard.subjects ?? []) {
692
+ for (const m of s.misplacedAspects ?? []) {
693
+ const home = ASPECT_FAMILY[m.aspect];
694
+ if (home && home !== m.family) {
695
+ err('C6', `subject ${s.id}: aspect "${m.aspect}" declared under family "${m.family}" — it belongs to "${home}" (anatomy-family)`);
696
+ }
697
+ else if (!home) {
698
+ err('C6', `subject ${s.id}: undeclared aspect "${m.aspect}" in family "${m.family}" (anatomy-family)`);
699
+ }
700
+ }
701
+ for (const [k, v] of Object.entries(s.is.designParameters ?? {})) {
702
+ if (/test[-_]dependent/.test(v)) {
703
+ err('C6', `subject ${s.id}: design parameter "${k}" is test-dependent — exhibited values belong to has.attributes (anatomy-family)`);
704
+ }
705
+ }
706
+ for (const k of Object.keys(s.has.attributes ?? {})) {
707
+ if (CONDITION_TIERS.has(k)) {
708
+ err('C6', `subject ${s.id}: attribute "${k}" is a condition tier — designed tiers belong to is.designed_conditions (anatomy-family)`);
709
+ }
710
+ }
711
+ // C7 — every characteristic names its derivation from behavior I/O
712
+ // (anatomy-characteristic-derivation).
713
+ for (const [k, c] of Object.entries(s.has.characteristics ?? {})) {
714
+ if (!c.derivation) {
715
+ err('C7', `subject ${s.id}: characteristic "${k}" names no derivation from behavior I/O (anatomy-characteristic-derivation)`);
716
+ }
717
+ }
718
+ // C8 — every does behavior resolves to a declared behavior
719
+ // (anatomy-behavior-resolves).
720
+ for (const b of s.does.behaviors ?? []) {
721
+ if (!behaviorIds.has(b)) {
722
+ err('C8', `subject ${s.id}: behavior "${b}" is not a declared behavior (anatomy-behavior-resolves)`);
723
+ }
724
+ }
725
+ // C9 — a subject's extends target resolves to a declared subject id
726
+ // (subject-extends-resolves). Successfully merged chains clear
727
+ // `extends` at resolve time, so a surviving link is exactly an
728
+ // unresolved one (missing parent, or a broken cycle).
729
+ if (s.extends && !subjectIds.has(s.extends)) {
730
+ warn('C9', `subject ${s.id}: extends "${s.extends}" is not a declared subject (subject-extends-resolves)`);
731
+ }
732
+ }
733
+ // ── C42–C44: promises (TODO.roadmap/08) ──
734
+ // A promise is a manufacturer claim on a characteristic or a behavior —
735
+ // possibly envelope-shaped, possibly conditional — that evaluation
736
+ // verifies and the certificate prints as promises-as-verified (doctrine
737
+ // ch. 02 §2.3, ch. 15 §15.2). It is NOT a declared attribute value: a
738
+ // claim stated as one bare parameter value stays an `origin: declared`
739
+ // attribute (C44 enforces the distinctness).
740
+ //
741
+ // Verification linkage (C43): a promise may declare `verified_by`
742
+ // requirement/test ids (their resolution is C2, above). When it does
743
+ // not, the linter DERIVES candidates — requirements/tests binding the
744
+ // same target: a requirement binds a characteristic target when its
745
+ // limit.uses or a binds_to leaf names the characteristic or its symbol,
746
+ // and binds a behavior target via a `…behaviors.<id>` binds_to path; a
747
+ // test binds a target when it targets a binding requirement, names the
748
+ // target in an acceptance criterion/variable/observable, or (behaviors)
749
+ // is listed in the behavior's own verified_by. No verification declared
750
+ // or derived is a warning at authoring.
751
+ const requirementBindsPromiseTarget = (r, names) => {
752
+ for (const u of r.limit?.uses ?? []) {
753
+ const leaf = u.split('.').pop() ?? u;
754
+ if (names.has(u) || names.has(leaf)) {
755
+ return true;
756
+ }
757
+ }
758
+ for (const p of r.bindsTo ?? []) {
759
+ const leaf = String(p).split('.').pop() ?? '';
760
+ if (names.has(leaf)) {
761
+ return true;
762
+ }
763
+ }
764
+ return false;
765
+ };
766
+ const testBindsPromiseTarget = (t, names, boundReqs) => {
767
+ for (const target of t.targets ?? []) {
768
+ if (boundReqs.has(target)) {
769
+ return true;
770
+ }
771
+ }
772
+ for (const ac of t.acceptanceCriteria ?? []) {
773
+ if (names.has(ac.item)) {
774
+ return true;
775
+ }
776
+ }
777
+ for (const v of t.variables ?? []) {
778
+ if (names.has(v.name)) {
779
+ return true;
780
+ }
781
+ }
782
+ for (const o of t.observables ?? []) {
783
+ if (names.has(o.name)) {
784
+ return true;
785
+ }
786
+ }
787
+ return false;
788
+ };
789
+ for (const s of standard.subjects ?? []) {
790
+ // Characteristics of THIS subject (extends chains already merged):
791
+ // name → symbol, both spellings legal in promise targets/derivations.
792
+ const characteristicSymbols = new Map();
793
+ for (const [k, c] of Object.entries(s.has.characteristics ?? {})) {
794
+ characteristicSymbols.set(k, c.symbol ?? '');
795
+ }
796
+ // Bare-value targets (C44): declared attribute values — attribute
797
+ // definitions, the subject's design parameters, its has.attributes.
798
+ const bareValueTargets = new Set([
799
+ ...attrIds,
800
+ ...Object.keys(s.is.designParameters ?? {}),
801
+ ...Object.keys(s.has.attributes ?? {}),
802
+ ]);
803
+ for (const p of s.is.promises ?? []) {
804
+ const label = p.id ||
805
+ (p.statement.length > 40
806
+ ? p.statement.slice(0, 37) + '…'
807
+ : p.statement);
808
+ // C2 — declared verified_by ids must resolve to a requirement or a
809
+ // conformance test (same discipline as behavior verified_by).
810
+ for (const v of p.verifiedBy ?? []) {
811
+ if (!reqIds.has(v) && !testIds.has(v)) {
812
+ err('C2', `subject ${s.id}: promise "${label}" verified_by "${v}" is not a declared requirement or conformance test`);
813
+ }
814
+ }
815
+ if (!p.target) {
816
+ // Statement-only shorthand: no target to derive verification
817
+ // against — unless verified_by is declared, the promise is
818
+ // unverifiable (C43, warning at authoring).
819
+ if ((p.verifiedBy ?? []).length === 0) {
820
+ warn('C43', `subject ${s.id}: promise "${label}" has no target and no verified_by — it is unverifiable; give it a characteristic/behavior target or declare the verifying requirement/test (promise-verifiable)`);
821
+ }
822
+ continue;
823
+ }
824
+ const isCharacteristic = characteristicSymbols.has(p.target);
825
+ const isBehavior = behaviorIds.has(p.target);
826
+ if (!isCharacteristic && !isBehavior) {
827
+ if (bareValueTargets.has(p.target)) {
828
+ // C44 (narrowed) — the crime is RESTATING a bare value as a
829
+ // claim, not TARGETING an attribute: fire only when the promise
830
+ // merely restates the declared attribute value — a bare
831
+ // quantity level with no conditions and no verification
832
+ // linkage. A genuine claim ABOUT an attribute (conditioned,
833
+ // envelope/range, symbolic level, or verified) is legal; a
834
+ // characteristic/behavior name wins the collision (above).
835
+ const restatesBareValue = p.level?.kind === 'quantity' &&
836
+ p.conditions === '' &&
837
+ (p.verifiedBy ?? []).length === 0;
838
+ if (restatesBareValue) {
839
+ err('C44', `subject ${s.id}: promise "${label}" only restates the declared attribute value "${p.target}" — a promise claims a characteristic or behavior (optionally conditioned); bare values stay origin: declared attributes (promise-not-bare-value)`);
840
+ }
841
+ }
842
+ else {
843
+ // C42 — the target resolves to nothing the subject can claim.
844
+ err('C42', `subject ${s.id}: promise "${label}" target "${p.target}" is not a declared characteristic or behavior (promise-target-resolves)`);
845
+ }
846
+ continue;
847
+ }
848
+ if ((p.verifiedBy ?? []).length > 0) {
849
+ continue; // verification declared — C43 silent
850
+ }
851
+ // C43 — derive the verifying requirements/tests from the target.
852
+ const names = new Set([p.target]);
853
+ if (isCharacteristic) {
854
+ const sym = characteristicSymbols.get(p.target);
855
+ if (sym) {
856
+ names.add(sym);
857
+ }
858
+ }
859
+ const boundReqs = new Set();
860
+ for (const r of standard.requirements ?? []) {
861
+ if (requirementBindsPromiseTarget(r, names)) {
862
+ boundReqs.add(r.id);
863
+ }
864
+ }
865
+ const boundTests = new Set();
866
+ for (const t of standard.conformanceTests ?? []) {
867
+ if (testBindsPromiseTarget(t, names, boundReqs)) {
868
+ boundTests.add(t.id);
869
+ }
870
+ }
871
+ if (isBehavior) {
872
+ const b = (standard.behaviors ?? []).find(x => x.id === p.target);
873
+ for (const tid of b?.verifiedBy ?? []) {
874
+ boundTests.add(tid);
875
+ }
876
+ }
877
+ if (boundReqs.size === 0 && boundTests.size === 0) {
878
+ warn('C43', `subject ${s.id}: promise "${label}" declares no verified_by and no requirement/test binds its target "${p.target}" — declare the verifying requirement or test (promise-verifiable)`);
879
+ }
880
+ }
881
+ }
882
+ // ── C45–C47: artifacts (TODO.roadmap/09 — gap audit G2) ──
883
+ // An artifact is a required OUTPUT OF THE SUBJECT (the instrument) — not
884
+ // a record of the test. artifact_definition (IS: content contract +
885
+ // produced-when) is referenced from the subject's is.artifacts slot;
886
+ // artifact_instance (HAS/evidence: one produced output checked against
887
+ // the contract) from has.artifact_instances.
888
+ const artifactDefs = new Map((standard.artifactDefinitions ?? []).map(d => [d.id, d]));
889
+ const artifactInstIds = new Set((standard.artifactInstances ?? []).map(a => a.id));
890
+ // C45 — artifact-def-contract: the content contract is well-formed.
891
+ for (const d of standard.artifactDefinitions ?? []) {
892
+ const c = d.contentContract;
893
+ if (c.fields.length === 0) {
894
+ err('C45', `artifact definition ${d.id}: content contract declares no fields — a definition must contract SOME content (artifact-def-contract)`);
895
+ }
896
+ const seenFields = new Set();
897
+ for (const f of c.fields) {
898
+ if (seenFields.has(f.name)) {
899
+ err('C45', `artifact definition ${d.id}: duplicate contract field "${f.name}" (artifact-def-contract)`);
900
+ }
901
+ seenFields.add(f.name);
902
+ if (!f.type) {
903
+ err('C45', `artifact definition ${d.id}: contract field "${f.name}" has no type — fields are named AND typed (artifact-def-contract)`);
904
+ }
905
+ }
906
+ for (const m of c.media) {
907
+ if (!seenFields.has(m.field)) {
908
+ err('C45', `artifact definition ${d.id}: media entry refines "${m.field}", which is not a declared contract field (artifact-def-contract)`);
909
+ }
910
+ }
911
+ const pw = d.producedWhen;
912
+ const KNOWN_PRODUCED_WHEN = new Set([
913
+ 'per_measurement',
914
+ 'per_interval',
915
+ 'on_event',
916
+ ]);
917
+ if (!KNOWN_PRODUCED_WHEN.has(pw.kind)) {
918
+ err('C45', `artifact definition ${d.id}: produced_when "${pw.kind}" is not one of per_measurement | per_interval | on_event (artifact-def-contract)`);
919
+ }
920
+ else if (pw.kind === 'per_interval' && !(0, time_1.isDuration)(pw.interval ?? '')) {
921
+ err('C45', `artifact definition ${d.id}: produced_when per_interval "${pw.interval ?? ''}" is not an ISO-8601 duration (artifact-def-contract)`);
922
+ }
923
+ else if (pw.kind === 'on_event' && !pw.event) {
924
+ err('C45', `artifact definition ${d.id}: produced_when on_event names no event (artifact-def-contract)`);
925
+ }
926
+ }
927
+ // C46 — artifact-instance-resolves: subject slots resolve; an instance's
928
+ // `of` resolves; its content satisfies the contract; produced-when is
929
+ // satisfiable by the linking run/report.
930
+ for (const t of standard.conformanceTests ?? []) {
931
+ for (const a of t.producesArtifacts ?? []) {
932
+ if (!artifactDefs.has(a)) {
933
+ err('C46', `conformance test ${t.id}: produces_artifacts "${a}" is not a declared artifact_definition (artifact-instance-resolves)`);
934
+ }
935
+ }
936
+ }
937
+ for (const s of standard.subjects ?? []) {
938
+ for (const a of s.is.artifacts ?? []) {
939
+ if (!artifactDefs.has(a)) {
940
+ err('C46', `subject ${s.id}: is.artifacts entry "${a}" is not a declared artifact_definition (artifact-instance-resolves)`);
941
+ }
942
+ }
943
+ for (const a of s.has.artifactInstances ?? []) {
944
+ if (!artifactInstIds.has(a)) {
945
+ err('C46', `subject ${s.id}: has.artifact_instances entry "${a}" is not a declared artifact_instance (artifact-instance-resolves)`);
946
+ }
947
+ }
948
+ }
949
+ for (const a of standard.artifactInstances ?? []) {
950
+ const def = artifactDefs.get(a.of);
951
+ if (!def) {
952
+ err('C46', `artifact instance ${a.id}: of "${a.of}" is not a declared artifact_definition (artifact-instance-resolves)`);
953
+ continue; // no contract to check the content against
954
+ }
955
+ if (!a.by) {
956
+ err('C46', `artifact instance ${a.id}: no producer (by) — an artifact instance is unattributable without its producing subject (artifact-instance-resolves)`);
957
+ }
958
+ const contractFields = new Map(def.contentContract.fields.map(f => [f.name, f]));
959
+ for (const key of Object.keys(a.content)) {
960
+ if (!contractFields.has(key)) {
961
+ err('C46', `artifact instance ${a.id}: content field "${key}" is not in the contract of ${def.id} (artifact-instance-resolves)`);
962
+ }
963
+ }
964
+ for (const [name, f] of contractFields) {
965
+ if (!f.optional && !(name in a.content)) {
966
+ err('C46', `artifact instance ${a.id}: required contract field "${name}" of ${def.id} is not populated (artifact-instance-resolves)`);
967
+ }
968
+ }
969
+ // produced-when leg: per_measurement means the instance exists BY a
970
+ // measurement — an instance linking no run/report cannot demonstrate
971
+ // the produced-when rule is satisfied.
972
+ if (def.producedWhen.kind === 'per_measurement' && a.links.length === 0) {
973
+ err('C46', `artifact instance ${a.id}: ${def.id} is produced per_measurement but the instance links no run/report — link the producing measurement run or report (artifact-instance-resolves)`);
974
+ }
975
+ }
976
+ // C47 — artifact-evidence-separation (the MECE firewall): an artifact is
977
+ // an output OF the instrument; an EvidenceRecord is a record OF the test.
978
+ // No artifact contract field name may double as a test-run record slot
979
+ // (conformance-test variable/observable, form field) — a collision means
980
+ // the instrument's output is being duplicated into an evidence-record
981
+ // shape (or test-run evidence is being passed off as the artifact).
982
+ const evidenceSlots = new Map(); // name → where declared
983
+ for (const t of standard.conformanceTests ?? []) {
984
+ for (const v of t.variables ?? []) {
985
+ if (v.name && !evidenceSlots.has(v.name)) {
986
+ evidenceSlots.set(v.name, `a variable of conformance test ${t.id}`);
987
+ }
988
+ }
989
+ for (const o of t.observables ?? []) {
990
+ if (o.name && !evidenceSlots.has(o.name)) {
991
+ evidenceSlots.set(o.name, `an observable of conformance test ${t.id}`);
992
+ }
993
+ }
994
+ }
995
+ const collectFormFieldNames = (fields, formId) => {
996
+ for (const f of fields) {
997
+ if (f.name && !evidenceSlots.has(f.name)) {
998
+ evidenceSlots.set(f.name, `a field of form ${formId}`);
999
+ }
1000
+ collectFormFieldNames(f.fields ?? [], formId);
1001
+ }
1002
+ };
1003
+ for (const form of standard.forms ?? []) {
1004
+ collectFormFieldNames(form.fields ?? [], form.id);
1005
+ }
1006
+ for (const d of standard.artifactDefinitions ?? []) {
1007
+ for (const f of d.contentContract.fields) {
1008
+ const where = evidenceSlots.get(f.name);
1009
+ if (where) {
1010
+ err('C47', `artifact definition ${d.id}: contract field "${f.name}" is also ${where} — an artifact is an output OF the instrument, not a record OF the test; keep artifact content out of evidence-record shapes (and vice versa) (artifact-evidence-separation)`);
1011
+ }
1012
+ }
1013
+ }
1014
+ // ── C48–C50: characteristics (TODO.roadmap/10) ──────────────────────
1015
+ // Characteristics are the quantitative interface of the subject
1016
+ // (doctrine ch. 02 §2.7): DEFINED in the primary model (the subject's
1017
+ // has.characteristics), referenced everywhere else — promises claim
1018
+ // them, requirements constrain them, tests compute them, verdicts
1019
+ // judge them.
1020
+ // C48 — characteristic-one-home: once a standard declares subject
1021
+ // characteristics (the primary-model register), a `verdict` construct
1022
+ // carrying its own `derive` defines a verdict-quantity derivation
1023
+ // OUTSIDE the primary model — the one-home violation the OIML SMART
1024
+ // linker's verdict-no-shadow / verdict-restatement rules guard,
1025
+ // re-pointed at the new home. Packages without subject characteristics
1026
+ // (the v2 instrument trees, whose verdict registry is the
1027
+ // specification-side transport of model/characteristics.yaml) stay
1028
+ // silent: the rule fires only where a primary-model characteristic
1029
+ // register exists.
1030
+ const anySubjectCharacteristics = (standard.subjects ?? []).some(s => Object.keys(s.has.characteristics ?? {}).length > 0);
1031
+ if (anySubjectCharacteristics) {
1032
+ for (const v of standard.verdicts ?? []) {
1033
+ if (v.derive) {
1034
+ err('C48', `verdict ${v.id}: carries a derivation outside the primary model — a verdict quantity derives ONCE in the subject's has.characteristics and is referenced everywhere else (characteristic-one-home)`);
1035
+ }
1036
+ }
1037
+ }
1038
+ // C49 — characteristic-behavior-link: a characteristic's behavior ref
1039
+ // resolves to a declared behavior (closing the
1040
+ // behavior→I/O→characteristic chain).
1041
+ for (const s of standard.subjects ?? []) {
1042
+ for (const [k, c] of Object.entries(s.has.characteristics ?? {})) {
1043
+ if (c.behavior && !behaviorIds.has(c.behavior)) {
1044
+ err('C49', `subject ${s.id}: characteristic "${k}" behavior "${c.behavior}" is not a declared behavior (characteristic-behavior-link)`);
1045
+ }
1046
+ }
1047
+ }
1048
+ // C50 — characteristic-derivation-inputs: the identifiers an ocl{…}
1049
+ // derivation reads resolve to the subject's quantitative vocabulary —
1050
+ // self.<p> reads against the subject's parameters (design parameters,
1051
+ // has.attributes, attribute definitions); bare names against the
1052
+ // behavior-I/O vocabulary (the standard's symbols — the observables
1053
+ // measured of the behavior — plus attribute definitions and the
1054
+ // subject's parameters). Prose derivations are not machine-checkable
1055
+ // and are skipped.
1056
+ const OCL_KEYWORDS = new Set([
1057
+ 'self',
1058
+ 'and',
1059
+ 'or',
1060
+ 'xor',
1061
+ 'not',
1062
+ 'implies',
1063
+ 'true',
1064
+ 'false',
1065
+ 'null',
1066
+ 'if',
1067
+ 'then',
1068
+ 'else',
1069
+ 'endif',
1070
+ 'let',
1071
+ 'in',
1072
+ 'invalid',
1073
+ ]);
1074
+ const derivationReads = (derivation) => {
1075
+ const body = derivation
1076
+ .trim()
1077
+ .replace(/^ocl\{/, '')
1078
+ .replace(/\}$/, '');
1079
+ const selfReads = [];
1080
+ const bareReads = [];
1081
+ for (const m of body.matchAll(/[A-Za-z_][A-Za-z0-9_]*/g)) {
1082
+ const id = m[0];
1083
+ if (OCL_KEYWORDS.has(id)) {
1084
+ continue;
1085
+ }
1086
+ const idx = m.index ?? 0;
1087
+ const rest = body.slice(idx + id.length);
1088
+ if (rest.trimStart().startsWith('(')) {
1089
+ continue;
1090
+ } // function call
1091
+ const before = body.slice(0, idx);
1092
+ if (before.endsWith('self.')) {
1093
+ selfReads.push(id);
1094
+ }
1095
+ else if (before.endsWith('.')) {
1096
+ continue; // navigation segment into a resolved root
1097
+ }
1098
+ else {
1099
+ bareReads.push(id);
1100
+ }
1101
+ }
1102
+ return { selfReads, bareReads };
1103
+ };
1104
+ for (const s of standard.subjects ?? []) {
1105
+ const subjectParams = new Set([
1106
+ ...Object.keys(s.is.designParameters ?? {}),
1107
+ ...Object.keys(s.has.attributes ?? {}),
1108
+ ...attrIds,
1109
+ ]);
1110
+ const ioVocabulary = new Set([
1111
+ ...symbolIds,
1112
+ ...attrIds,
1113
+ ...Object.keys(s.is.designParameters ?? {}),
1114
+ ...Object.keys(s.has.attributes ?? {}),
1115
+ ]);
1116
+ for (const [k, c] of Object.entries(s.has.characteristics ?? {})) {
1117
+ if (!/^ocl\{[\s\S]*\}$/.test(c.derivation.trim())) {
1118
+ continue;
1119
+ }
1120
+ const { selfReads, bareReads } = derivationReads(c.derivation);
1121
+ for (const id of selfReads) {
1122
+ if (!subjectParams.has(id)) {
1123
+ err('C50', `subject ${s.id}: characteristic "${k}" derivation reads self.${id}, which is not a declared subject parameter (design parameter, has.attribute, or attribute definition) — in \`${c.derivation}\` (characteristic-derivation-inputs)`);
1124
+ }
1125
+ }
1126
+ for (const id of bareReads) {
1127
+ if (!ioVocabulary.has(id)) {
1128
+ err('C50', `subject ${s.id}: characteristic "${k}" derivation reads "${id}", which is not a declared symbol, attribute, or subject parameter (behavior I/O) — in \`${c.derivation}\` (characteristic-derivation-inputs)`);
1129
+ }
1130
+ }
1131
+ }
1132
+ }
1133
+ // ── C58: activity-kind-resolves (TODO.roadmap/39) ─────────────────
1134
+ // A process's `activity_kind { … }` classifies it against the ISO/IEC
1135
+ // 17000 functional-approach activity taxonomy (selection; determination
1136
+ // — testing/inspection/audit/validation/verification/peer assessment;
1137
+ // review; decision; attestation — declaration/certification/
1138
+ // accreditation; surveillance …). Classification, not inheritance —
1139
+ // multi-kind is deliberate (ISO/IEC 17065 §7.4 "evaluation" = selection
1140
+ // + determination). Every tagged kind must resolve against a declared
1141
+ // activity_archetype register WHEN ONE IS IN SCOPE (composed via uses);
1142
+ // the rule stays silent in a register-less package — classification is
1143
+ // opt-in and a register-less tree cannot adjudicate. The register's own
1144
+ // `parent` references must likewise resolve to declared archetypes.
1145
+ // Mirror of the OIML SMART linker's R23 activity-kind-resolves.
1146
+ const activityArchetypeIds = new Set((standard.activityArchetypes ?? []).map(a => a.id));
1147
+ if (activityArchetypeIds.size > 0) {
1148
+ // The register's own parent references resolve within the register.
1149
+ for (const a of standard.activityArchetypes ?? []) {
1150
+ if (a.parent !== '' && !activityArchetypeIds.has(a.parent)) {
1151
+ err('C58', `activity_archetype ${a.id}: parent "${a.parent}" is not a declared activity archetype (activity-kind-resolves)`);
1152
+ }
1153
+ }
1154
+ for (const p of standard.processes ?? []) {
1155
+ for (const kind of p.activityKinds ?? []) {
1156
+ if (!activityArchetypeIds.has(kind)) {
1157
+ err('C58', `process ${p.id}: activity_kind "${kind}" is not a declared activity archetype (activity-kind-resolves)`);
1158
+ }
1159
+ }
1160
+ }
1161
+ }
1162
+ // ── C59: segregation-members-resolve (TODO.roadmap/39b) ────────────
1163
+ // A process's `segregation { constraint … }` declares the ISO/IEC 17065
1164
+ // role-segregation constraints as first-class structure (7.5.1 reviewer
1165
+ // ∉ evaluation, 7.6.2 decider ∉ evaluation, 7.13.5 complaint resolution
1166
+ // ∉ case activities, 7.13.6/4.2.10 consultancy bars). Well-formedness:
1167
+ // (a) every pair member resolves to a declared process id or the
1168
+ // reserved `case_personnel` token (the personnel involved in the
1169
+ // certification activities of the case a complaint relates to); (b) the
1170
+ // two members are distinct; (c) the owning process is a member of its
1171
+ // own pair; (d) the kind is declared. Pair members are process ids,
1172
+ // never roles — a scheme may bind one role to evaluation, review AND
1173
+ // decision, so the norms quantify over process involvement. Per-
1174
+ // assignment runtime enforcement is the platform's business (task 44).
1175
+ // Mirror of the OIML SMART linker's R24 abstract-process-segregation.
1176
+ {
1177
+ const SEGREGATION_CASE_PERSONNEL = 'case_personnel';
1178
+ const segregationProcessIds = new Set((standard.processes ?? []).map(p => p.id));
1179
+ for (const p of standard.processes ?? []) {
1180
+ for (const s of p.segregation ?? []) {
1181
+ if (s.kind !== 'case_personnel_disjoint' &&
1182
+ s.kind !== 'consultancy_bar') {
1183
+ err('C59', `process ${p.id}: segregation constraint "${s.id}" declares kind "${s.kind || '(none)'}" — valid kinds: case_personnel_disjoint, consultancy_bar (segregation-members-resolve)`);
1184
+ continue;
1185
+ }
1186
+ if (s.kind === 'case_personnel_disjoint') {
1187
+ if (s.pair.length !== 2) {
1188
+ 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)`);
1189
+ continue;
1190
+ }
1191
+ const [a, b] = s.pair;
1192
+ if (a === b) {
1193
+ err('C59', `process ${p.id}: segregation constraint "${s.id}" declares "${a}" disjoint from itself — the two personnel sets must be distinct (segregation-members-resolve)`);
1194
+ }
1195
+ for (const member of [a, b]) {
1196
+ if (member !== SEGREGATION_CASE_PERSONNEL &&
1197
+ !segregationProcessIds.has(member)) {
1198
+ err('C59', `process ${p.id}: segregation constraint "${s.id}" names pair member "${member}", which is not a declared process (nor the reserved "${SEGREGATION_CASE_PERSONNEL}" token) (segregation-members-resolve)`);
1199
+ }
1200
+ }
1201
+ if (a !== p.id && b !== p.id) {
1202
+ 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)`);
1203
+ }
1204
+ }
1205
+ }
1206
+ }
1207
+ }
1208
+ // ── C84: constraint-shape (TODO.roadmap/51 — BUG.R60-SSOT gap 7) ─────
1209
+ // The subject-intrinsic constraint's DECLARATION shape — the kernel
1210
+ // mirror of the OIML SMART constraints.yaml schema
1211
+ // (data/schemas/constraints.yaml; the resolution legs stay smart-side
1212
+ // as linker rule R32 constraint-references): the stereotype is always
1213
+ // «inv» (the subject's declaration-level invariant; requirements are
1214
+ // the «req» counterpart), the check is ONE OCL boolean expression, the
1215
+ // violation_meaning is REQUIRED (the judgment records what a violation
1216
+ // means, never a bare id), and on_violation is invalid (void
1217
+ // measurement) or indeterminate (the declaration cannot be judged) —
1218
+ // never a fail. A declared source names both doc and clause (clause-URN
1219
+ // provenance). Duplicate constraint ids are the parse-time duplicate-id
1220
+ // rule (surfaced as C96), not a C84 leg.
1221
+ {
1222
+ const OCL_CHECK = /^ocl\{[\s\S]*\}$/;
1223
+ for (const c of standard.constraints ?? []) {
1224
+ if (c.stereotype !== 'inv') {
1225
+ err('C84', `constraint ${c.id}: stereotype "${c.stereotype || '(none)'}" — a constraint is always «inv», the subject's declaration-level invariant (constraint-shape)`);
1226
+ }
1227
+ if (!OCL_CHECK.test(c.check)) {
1228
+ err('C84', `constraint ${c.id}: check is not one OCL boolean expression ocl{…} over the subject's declared anatomy (constraint-shape)`);
1229
+ }
1230
+ if (!c.violationMeaning || c.violationMeaning.trim() === '') {
1231
+ err('C84', `constraint ${c.id}: violation_meaning is required — the invalidated judgment records what the violation means, never a bare id (constraint-shape)`);
1232
+ }
1233
+ if (c.onViolation !== 'invalid' && c.onViolation !== 'indeterminate') {
1234
+ err('C84', `constraint ${c.id}: on_violation "${c.onViolation || '(none)'}" — valid: invalid (void measurement), indeterminate (the declaration cannot be judged); never a fail (constraint-shape)`);
1235
+ }
1236
+ if (c.source && (!c.source.doc || !c.source.clause)) {
1237
+ err('C84', `constraint ${c.id}: source names ${!c.source.doc ? 'no doc' : 'no clause'} — clause-URN provenance carries both doc and clause (constraint-shape)`);
1238
+ }
1239
+ }
1240
+ }
1241
+ // ── The serve aspect vocabulary (TODO.roadmap/32/34) — shared by the
1242
+ // twin checks (C60) and the monitor checks (C66): ONE resolver, derived
1243
+ // from the subject's declared aspects.
1244
+ const TWIN_CHAIN_LEVELS = new Set(['family', 'group', 'model', 'sample']);
1245
+ // The `state` aspect resolves through the subject's bound machine
1246
+ // (has.state) OR through the package's operational machines: a
1247
+ // twin-interface subject block (the smart side's model/twin.yaml
1248
+ // emission, TODO.refactor/16) is PARTIAL anatomy — it carries the
1249
+ // endpoint/serve declarations without re-stating the state binding,
1250
+ // and the state channel binds at runtime (task 33).
1251
+ const packageHasOperationalMachine = (standard.stateMachines ?? []).some(sm => sm.kind === 'operational');
1252
+ /**
1253
+ * Resolve a serve aspect path against the owning subject — the task-03
1254
+ * scope vocabulary: an optional chain-level prefix (family | group |
1255
+ * model | sample), then parameters.<attr> | test_context.<attr> |
1256
+ * classification.<dim> | characteristics.<name>, or a bare
1257
+ * attribute/characteristic/dimension name; the reserved HAS aspects
1258
+ * `state` (the subject's declared state machine) and
1259
+ * `environmental_context` (the logged actual conditions — always
1260
+ * servable, §14.3) close the vocabulary.
1261
+ */
1262
+ const resolveServeAspect = (path, s) => {
1263
+ let rest = path;
1264
+ const parts = path.split('.');
1265
+ if (parts.length > 1 && TWIN_CHAIN_LEVELS.has(parts[0])) {
1266
+ rest = parts.slice(1).join('.');
1267
+ }
1268
+ if (rest === 'state') {
1269
+ return s.has.state || packageHasOperationalMachine
1270
+ ? { kind: 'state' }
1271
+ : null;
1272
+ }
1273
+ if (rest === 'environmental_context') {
1274
+ return { kind: 'environmental_context' };
1275
+ }
1276
+ const segs = rest.split('.');
1277
+ if (segs.length === 2) {
1278
+ const [area, key] = segs;
1279
+ if (area === 'parameters' || area === 'test_context') {
1280
+ const a = attrId(standard, key);
1281
+ return a
1282
+ ? { kind: 'attribute', unit: a.unit, quantityKind: a.quantityKind }
1283
+ : null;
1284
+ }
1285
+ if (area === 'classification') {
1286
+ return dimIds.has(key) || key in (s.has.dimensions ?? {})
1287
+ ? { kind: 'dimension' }
1288
+ : null;
1289
+ }
1290
+ if (area === 'characteristics') {
1291
+ const c = (s.has.characteristics ?? {})[key];
1292
+ return c
1293
+ ? {
1294
+ kind: 'characteristic',
1295
+ unit: c.unit,
1296
+ quantityKind: c.quantityKind,
1297
+ }
1298
+ : null;
1299
+ }
1300
+ return null;
1301
+ }
1302
+ if (segs.length === 1 && rest) {
1303
+ const a = attrId(standard, rest);
1304
+ if (a) {
1305
+ return {
1306
+ kind: 'attribute',
1307
+ unit: a.unit,
1308
+ quantityKind: a.quantityKind,
1309
+ };
1310
+ }
1311
+ const c = (s.has.characteristics ?? {})[rest];
1312
+ if (c) {
1313
+ return {
1314
+ kind: 'characteristic',
1315
+ unit: c.unit,
1316
+ quantityKind: c.quantityKind,
1317
+ };
1318
+ }
1319
+ if (dimIds.has(rest) || rest in (s.has.dimensions ?? {})) {
1320
+ return { kind: 'dimension' };
1321
+ }
1322
+ }
1323
+ return null;
1324
+ };
1325
+ // ── C60–C64: the twin interface (TODO.roadmap/32 — doctrine ch. 14 §14.4) ──
1326
+ // The live twin's integration language: endpoint declarations (IS) and
1327
+ // serve bindings (HAS) on the subject anatomy, plus the connector-profile
1328
+ // registry. The doctrine's validation rules (§14.12, first/second/fifth
1329
+ // bullets): every serve names a declared aspect and a declared operation
1330
+ // with unit coherence; every endpoint operation has an access scope and a
1331
+ // payload schema (QuantityValue with timestamp); a live binding without
1332
+ // fresh_within is an error. Freshness SEMANTICS (stale ⇒ indeterminate)
1333
+ // are runtime — the smart app's verdict service owns them; these rules
1334
+ // guarantee the declarations the runtime needs.
1335
+ {
1336
+ const KNOWN_KINDS = new Set(Twin_1.ENDPOINT_OPERATION_KINDS);
1337
+ const KNOWN_SCOPES = new Set(Twin_1.ENDPOINT_ACCESS_SCOPES);
1338
+ const declaredProfiles = new Set([
1339
+ ...Object.keys(Twin_1.BUILTIN_CONNECTOR_PROFILES),
1340
+ ...(standard.connectorProfiles ?? []).map(p => p.id),
1341
+ ]);
1342
+ for (const s of standard.subjects ?? []) {
1343
+ const endpoints = s.is.endpoints ?? [];
1344
+ // Operation index across the subject's endpoints (name → owners):
1345
+ // serve bindings resolve against the subject's whole API surface.
1346
+ const operations = new Map();
1347
+ for (const e of endpoints) {
1348
+ for (const op of e.operations ?? []) {
1349
+ const list = operations.get(op.name) ?? [];
1350
+ list.push({
1351
+ endpoint: e.id,
1352
+ kind: op.kind,
1353
+ ...(op.payload?.unit ? { payloadUnit: op.payload.unit } : {}),
1354
+ ...(op.payload?.quantityKind
1355
+ ? { payloadKind: op.payload.quantityKind }
1356
+ : {}),
1357
+ });
1358
+ operations.set(op.name, list);
1359
+ }
1360
+ // C64 — endpoint-profile-resolves: the model is protocol-neutral;
1361
+ // profiles bind protocols (§14.4). An endpoint with no profile, or
1362
+ // one outside the registry (declared ∪ built-in), binds nothing.
1363
+ if (!e.profile) {
1364
+ err('C64', `subject ${s.id}: endpoint ${e.id} declares no connector profile — the model is protocol-neutral; profiles bind protocols (endpoint-profile-resolves)`);
1365
+ }
1366
+ else if (!declaredProfiles.has(e.profile)) {
1367
+ err('C64', `subject ${s.id}: endpoint ${e.id} profile "${e.profile}" is not a declared connector_profile nor a built-in (${Object.keys(Twin_1.BUILTIN_CONNECTOR_PROFILES).join(', ')}) (endpoint-profile-resolves)`);
1368
+ }
1369
+ for (const op of e.operations ?? []) {
1370
+ // C61 — payload-schema-quantity: the operation's declared schema
1371
+ // is well-formed (§14.4/§14.12): a known kind, and a payload that
1372
+ // is a QuantityValue per INV-1 — quantity kind, unit (`1` when
1373
+ // dimensionless), timestamp (a value without a time is not
1374
+ // evidence).
1375
+ if (!KNOWN_KINDS.has(op.kind)) {
1376
+ err('C61', `subject ${s.id}: endpoint ${e.id} operation ${op.name} kind "${op.kind || '(none)'}" is not one of query | subscribe | invoke (payload-schema-quantity)`);
1377
+ }
1378
+ if (!op.payload) {
1379
+ err('C61', `subject ${s.id}: endpoint ${e.id} operation ${op.name} declares no payload schema — every endpoint operation has a payload schema (QuantityValue with timestamp, §14.12) (payload-schema-quantity)`);
1380
+ }
1381
+ else {
1382
+ if (!op.payload.quantityKind) {
1383
+ err('C61', `subject ${s.id}: endpoint ${e.id} operation ${op.name} payload names no quantity_kind — INV-1: no bare numbers (payload-schema-quantity)`);
1384
+ }
1385
+ if (!op.payload.unit) {
1386
+ err('C61', `subject ${s.id}: endpoint ${e.id} operation ${op.name} payload names no unit — a QuantityValue always carries a unit (the register's dimensionless id when non-quantity) (payload-schema-quantity)`);
1387
+ }
1388
+ if (!op.payload.timestamp) {
1389
+ err('C61', `subject ${s.id}: endpoint ${e.id} operation ${op.name} payload does not declare timestamp true — a value without a time is not evidence (§14.3) (payload-schema-quantity)`);
1390
+ }
1391
+ }
1392
+ // C60 (endpoint legs) — the operation's own targets resolve:
1393
+ // serves names against the subject's aspects, does names against
1394
+ // declared behaviors (an invoke with no resolvable process
1395
+ // triggers nothing).
1396
+ for (const name of op.serves ?? []) {
1397
+ if (!resolveServeAspect(name, s)) {
1398
+ err('C60', `subject ${s.id}: endpoint ${e.id} operation ${op.name} serves "${name}", which is not a declared aspect of the subject (serve-targets-resolve)`);
1399
+ }
1400
+ }
1401
+ for (const name of op.does ?? []) {
1402
+ if (!behaviorIds.has(name)) {
1403
+ err('C60', `subject ${s.id}: endpoint ${e.id} operation ${op.name} does "${name}", which is not a declared behavior (serve-targets-resolve)`);
1404
+ }
1405
+ }
1406
+ }
1407
+ // C62 — access-scope-covers-serves: every operation is covered by
1408
+ // exactly one access scope (§14.12: every endpoint operation has an
1409
+ // access scope); access entries name declared operations of the
1410
+ // endpoint; scopes are public | registered | authority.
1411
+ const coverage = new Map();
1412
+ for (const [scope, ops] of Object.entries(e.access ?? {})) {
1413
+ if (!KNOWN_SCOPES.has(scope)) {
1414
+ err('C62', `subject ${s.id}: endpoint ${e.id} access scope "${scope}" is not one of public | registered | authority (access-scope-covers-serves)`);
1415
+ }
1416
+ for (const name of ops ?? []) {
1417
+ if (!e.operations.some(op => op.name === name)) {
1418
+ err('C62', `subject ${s.id}: endpoint ${e.id} access ${scope} names "${name}", which is not a declared operation of the endpoint (access-scope-covers-serves)`);
1419
+ }
1420
+ coverage.set(name, (coverage.get(name) ?? 0) + 1);
1421
+ }
1422
+ }
1423
+ for (const op of e.operations ?? []) {
1424
+ const n = coverage.get(op.name) ?? 0;
1425
+ if (n === 0) {
1426
+ err('C62', `subject ${s.id}: endpoint ${e.id} operation ${op.name} has no access scope — every endpoint operation has an access scope (§14.12) (access-scope-covers-serves)`);
1427
+ }
1428
+ else if (n > 1) {
1429
+ err('C62', `subject ${s.id}: endpoint ${e.id} operation ${op.name} is covered by ${n} access scopes — exactly one scope per operation (access-scope-covers-serves)`);
1430
+ }
1431
+ }
1432
+ }
1433
+ for (const b of s.has.serves ?? []) {
1434
+ // C60 — serve-targets-resolve: the binding names a declared aspect
1435
+ // and a declared operation (§14.12); the serving operation is a
1436
+ // value channel (query | subscribe — an invoke triggers a process,
1437
+ // it serves no value); the served aspect and the operation's
1438
+ // payload are unit-coherent.
1439
+ const aspect = resolveServeAspect(b.aspect, s);
1440
+ if (!aspect) {
1441
+ err('C60', `subject ${s.id}: serve "${b.aspect}" does not resolve to a declared aspect of the subject ([level.]{parameters|classification|test_context}.<key>, a bare attribute/characteristic/dimension name, state, environmental_context) (serve-targets-resolve)`);
1442
+ }
1443
+ const owners = operations.get(b.via) ?? [];
1444
+ if (owners.length === 0) {
1445
+ err('C60', `subject ${s.id}: serve "${b.aspect}" via "${b.via}" names no declared operation of the subject's endpoints (serve-targets-resolve)`);
1446
+ }
1447
+ else if (owners.length > 1) {
1448
+ err('C60', `subject ${s.id}: serve "${b.aspect}" via "${b.via}" is ambiguous — the operation is declared on ${owners.length} endpoints (${owners.map(o => o.endpoint).join(', ')}) (serve-targets-resolve)`);
1449
+ }
1450
+ else {
1451
+ const op = owners[0];
1452
+ if (op.kind === 'invoke') {
1453
+ err('C60', `subject ${s.id}: serve "${b.aspect}" via "${b.via}" targets an invoke operation — invoke triggers a process; a serve binding binds a value channel (query | subscribe) (serve-targets-resolve)`);
1454
+ }
1455
+ if (aspect &&
1456
+ op.payloadUnit &&
1457
+ aspect.unit &&
1458
+ op.payloadUnit !== aspect.unit) {
1459
+ err('C60', `subject ${s.id}: serve "${b.aspect}" unit "${aspect.unit}" ≠ operation ${b.via} payload unit "${op.payloadUnit}" — unit coherence between aspect and payload is required (§14.12) (serve-targets-resolve)`);
1460
+ }
1461
+ if (aspect &&
1462
+ op.payloadKind &&
1463
+ aspect.quantityKind &&
1464
+ op.payloadKind !== aspect.quantityKind) {
1465
+ err('C60', `subject ${s.id}: serve "${b.aspect}" quantity kind "${aspect.quantityKind}" ≠ operation ${b.via} payload quantity kind "${op.payloadKind}" (serve-targets-resolve)`);
1466
+ }
1467
+ }
1468
+ // C63 — freshness-required-on-live-bindings: a live binding without
1469
+ // fresh_within is an error (§14.12 — no stale semantics, no live
1470
+ // binding); the window must parse (shorthand 5s/1min/1h or ISO
1471
+ // 8601 with fixed-length components).
1472
+ if (!b.freshWithin) {
1473
+ err('C63', `subject ${s.id}: serve "${b.aspect}" via "${b.via}" declares no fresh_within — a live binding without a freshness window is an error (§14.12: no stale semantics, no live binding) (freshness-required-on-live-bindings)`);
1474
+ }
1475
+ else if ((0, time_1.parseFreshnessWindow)(b.freshWithin) === null) {
1476
+ err('C63', `subject ${s.id}: serve "${b.aspect}" fresh_within "${b.freshWithin}" is not a parseable freshness window (shorthand 500ms/5s/1min/1h/1d or ISO 8601 with fixed-length components, e.g. PT5S) (freshness-required-on-live-bindings)`);
1477
+ }
1478
+ }
1479
+ // C100–C102 — the composition facet (TODO.integration/14): a
1480
+ // composite subject's composed_of checks on all three legs.
1481
+ const composed = s.is.composedOf;
1482
+ if (composed) {
1483
+ const componentIds = new Set(composed.components.map(c => c.id));
1484
+ for (const c of composed.components) {
1485
+ // C100 — composition-components-resolve: an inline
1486
+ // `pkg/subject` product reference names a subject of THIS
1487
+ // package (a bare package id resolves cross-package at the
1488
+ // supply-chain gate — the C81 discipline, registered here).
1489
+ if (c.product.includes('/')) {
1490
+ const sub = c.product
1491
+ .slice(c.product.indexOf('/') + 1)
1492
+ .split('@')[0];
1493
+ // The reference is kebab-form, the subject id PascalCase —
1494
+ // resolve alphanumerically (sample-line ≡ SampleLine); the
1495
+ // @edition pin is the C83 discipline, stripped here.
1496
+ const norm = (x) => x.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
1497
+ if (!(standard.subjects ?? []).some(x => norm(x.id) === norm(sub))) {
1498
+ err('C100', `subject ${s.id}: composed_of component ${c.id} product "${c.product}" names no subject of this package (composition-components-resolve)`);
1499
+ }
1500
+ }
1501
+ if (!c.endpoint) {
1502
+ err('C100', `subject ${s.id}: composed_of component ${c.id} declares no endpoint — the component's projection must serve on a named endpoint (composition-components-resolve)`);
1503
+ }
1504
+ }
1505
+ // C101 — composition-decomposition-covers: every serve of the
1506
+ // composite subject is covered by the decomposition exactly once.
1507
+ const covered = new Map();
1508
+ for (const d of composed.decomposition) {
1509
+ covered.set(d.register, (covered.get(d.register) ?? 0) + 1);
1510
+ if (d.component !== 'composite' && !componentIds.has(d.component)) {
1511
+ err('C100', `subject ${s.id}: decomposition ${d.register} names undeclared component "${d.component}" (composition-components-resolve)`);
1512
+ }
1513
+ // C102 — composition-state-rule-closed: the vocabulary is
1514
+ // closed (any_fault_else_analyzer first — a new rule is a
1515
+ // grammar extension, never a free string).
1516
+ if (d.component === 'composite' &&
1517
+ !Composition_1.COMPOSITION_STATE_RULES.includes(d.rule ?? '')) {
1518
+ err('C102', `subject ${s.id}: decomposition ${d.register} declares unknown composite state rule "${d.rule ?? '(none)'}" — the vocabulary is closed (${Composition_1.COMPOSITION_STATE_RULES.join(', ')}) (composition-state-rule-closed)`);
1519
+ }
1520
+ }
1521
+ for (const serve of s.has.serves ?? []) {
1522
+ if ((covered.get(serve.aspect) ?? 0) !== 1) {
1523
+ err('C101', `subject ${s.id}: serve "${serve.aspect}" is covered ${covered.get(serve.aspect) ?? 0} times by the decomposition — every served register decomposes exactly once (composition-decomposition-covers)`);
1524
+ }
1525
+ }
1526
+ }
1527
+ }
1528
+ }
1529
+ // ── C65–C70: the monitors (TODO.roadmap/34 — doctrine ch. 14 §14.5/§14.12) ──
1530
+ // Continuous compliance: a monitor runs the standard next to the live
1531
+ // twins — triggers (the clock), evaluation refs (applicability-expanded
1532
+ // per twin at runtime), evidence sinks, escalation. The doctrine's
1533
+ // validation rules (§14.12, third/fourth bullets): every monitor's
1534
+ // evaluate refs resolve to requirements/promises applicable to the
1535
+ // monitored subjects; a monitor without an escalation path for `fail`
1536
+ // is a warning. Freshness semantics, the verdict stream, and the
1537
+ // escalation ACTIONS are runtime — the smart app's monitor service owns
1538
+ // them; these rules guarantee the declarations the runtime needs.
1539
+ {
1540
+ const roleIds = new Set((standard.roles ?? []).map(r => r.id));
1541
+ const KNOWN_OUTCOMES = new Set(Monitor_1.MONITOR_OUTCOMES);
1542
+ const KNOWN_ACTIONS = new Set(Monitor_1.MONITOR_ESCALATION_ACTIONS);
1543
+ const KNOWN_STREAMS = new Set(Monitor_1.MONITOR_STREAMS);
1544
+ const KNOWN_REFSET_KINDS = new Set(Monitor_1.MONITOR_REFSET_KINDS);
1545
+ for (const m of standard.monitors ?? []) {
1546
+ const subjects = (standard.subjects ?? []).filter(s => m.over.includes(s.id));
1547
+ // C65 — monitor-subject-resolves: the watched set is non-empty and
1548
+ // every ref names a declared subject (a monitor watching nothing
1549
+ // judges nothing).
1550
+ if (m.over.length === 0) {
1551
+ err('C65', `monitor ${m.id}: declares no subject set — a monitor watches a subject set (over { … }) (monitor-subject-resolves)`);
1552
+ }
1553
+ for (const ref of m.over) {
1554
+ if (!subjectIds.has(ref)) {
1555
+ err('C65', `monitor ${m.id}: over "${ref}" is not a declared subject (monitor-subject-resolves)`);
1556
+ }
1557
+ }
1558
+ // C66 — monitor-trigger-wellformed: without triggers "continuous"
1559
+ // has no clock (§14.5 step 1) — at least one trigger; a timer's
1560
+ // every window parses (the freshness-window syntax); a signal names
1561
+ // its signal; a change names an aspect that resolves against a
1562
+ // monitored subject (the serve aspect vocabulary — ONE resolver).
1563
+ if (m.triggers.length === 0) {
1564
+ err('C66', `monitor ${m.id}: declares no triggers — without triggers, "continuous" has no clock (§14.5 step 1) (monitor-trigger-wellformed)`);
1565
+ }
1566
+ for (const trigger of m.triggers) {
1567
+ if (trigger.kind === 'timer') {
1568
+ if (!trigger.every || (0, time_1.parseFreshnessWindow)(trigger.every) === null) {
1569
+ err('C66', `monitor ${m.id}: timer trigger every "${trigger.every || '(none)'}" is not a parseable window (shorthand 500ms/5s/1min/1h/1d or ISO 8601 with fixed-length components, e.g. PT1H) (monitor-trigger-wellformed)`);
1570
+ }
1571
+ }
1572
+ else if (trigger.kind === 'signal') {
1573
+ if (!trigger.signal) {
1574
+ err('C66', `monitor ${m.id}: signal trigger names no signal (on signal <name>) (monitor-trigger-wellformed)`);
1575
+ }
1576
+ }
1577
+ else if (trigger.kind === 'change') {
1578
+ if (!trigger.aspect) {
1579
+ err('C66', `monitor ${m.id}: change trigger names no aspect (on change <aspect>) (monitor-trigger-wellformed)`);
1580
+ }
1581
+ else if (subjects.length > 0 &&
1582
+ !subjects.some(s => resolveServeAspect(trigger.aspect, s))) {
1583
+ err('C66', `monitor ${m.id}: change trigger aspect "${trigger.aspect}" does not resolve against any monitored subject (the serve aspect vocabulary: [level.]{parameters|classification|test_context}.<key>, a bare attribute/characteristic/dimension name, state, environmental_context) (monitor-trigger-wellformed)`);
1584
+ }
1585
+ }
1586
+ else {
1587
+ err('C66', `monitor ${m.id}: trigger kind "${trigger.kind || '(none)'}" is not one of timer (every) | signal (on signal) | change (on change) (monitor-trigger-wellformed)`);
1588
+ }
1589
+ }
1590
+ // C67 — monitor-evaluate-resolves (§14.12): the evaluate refs
1591
+ // resolve to requirements/promises applicable to the monitored
1592
+ // subjects. `all` and `applicable_to(…)` expand per twin at runtime
1593
+ // (applicability-expanded — always admissible); explicit refs must
1594
+ // resolve: requirement ids against the package's requirements,
1595
+ // promise ids against the monitored subjects' promises.
1596
+ const checkRefSet = (label, set, resolveRef, refKind) => {
1597
+ if (!set.kind) {
1598
+ err('C67', `monitor ${m.id}: evaluate names no ${label} selector (all | applicable_to(…) | { refs… }) — a monitor judges something (monitor-evaluate-resolves)`);
1599
+ return;
1600
+ }
1601
+ if (!KNOWN_REFSET_KINDS.has(set.kind)) {
1602
+ err('C67', `monitor ${m.id}: ${label} selector "${set.kind}" is not one of all | applicable_to(…) | { refs… } (monitor-evaluate-resolves)`);
1603
+ return;
1604
+ }
1605
+ if (set.kind === 'applicable_to' && !set.expression) {
1606
+ err('C67', `monitor ${m.id}: ${label} applicable_to() carries no applicability expression (monitor-evaluate-resolves)`);
1607
+ }
1608
+ if (set.kind === 'refs') {
1609
+ if (set.refs.length === 0) {
1610
+ err('C67', `monitor ${m.id}: ${label} selector { } names no refs (monitor-evaluate-resolves)`);
1611
+ }
1612
+ for (const ref of set.refs) {
1613
+ if (!resolveRef(ref)) {
1614
+ err('C67', `monitor ${m.id}: evaluate ${label} ref "${ref}" is not a declared ${refKind} — evaluate refs resolve to requirements/promises applicable to the monitored subjects (§14.12) (monitor-evaluate-resolves)`);
1615
+ }
1616
+ }
1617
+ }
1618
+ };
1619
+ checkRefSet('requirements', m.evaluate.requirements, r => reqIds.has(r), 'requirement');
1620
+ const promiseIds = new Set(subjects
1621
+ .flatMap(s => (s.is.promises ?? []).map(p => p.id))
1622
+ .filter(id => id !== ''));
1623
+ checkRefSet('promises', m.evaluate.promises, p => promiseIds.has(p), 'promise of the monitored subjects');
1624
+ // C68 — monitor-fail-escalation (§14.12, verbatim): a monitor
1625
+ // without an escalation path for `fail` is a WARNING. Pass accrues
1626
+ // history; fail/invalid must act (notify, flag the certificate,
1627
+ // open a case).
1628
+ if (!m.escalate.some(r => r.outcome === 'fail')) {
1629
+ warn('C68', `monitor ${m.id}: declares no escalation path for fail — §14.12: a monitor without an escalation path for fail is a warning (monitor-fail-escalation)`);
1630
+ }
1631
+ // C69 — monitor-escalation-resolves: escalation outcomes are
1632
+ // verdict outcomes; actions are notify | flag_certificate |
1633
+ // open_service_case; a notify names a declared role.
1634
+ for (const rule of m.escalate) {
1635
+ if (!KNOWN_OUTCOMES.has(rule.outcome)) {
1636
+ err('C69', `monitor ${m.id}: escalate on "${rule.outcome || '(none)'}" — the outcome is not one of ${Monitor_1.MONITOR_OUTCOMES.join(' | ')} (monitor-escalation-resolves)`);
1637
+ }
1638
+ if (rule.actions.length === 0) {
1639
+ err('C69', `monitor ${m.id}: escalate on ${rule.outcome || '(none)'} names no actions — an escalation path acts (notify / flag_certificate / open_service_case) (monitor-escalation-resolves)`);
1640
+ }
1641
+ for (const a of rule.actions) {
1642
+ if (!KNOWN_ACTIONS.has(a.action)) {
1643
+ err('C69', `monitor ${m.id}: escalate on ${rule.outcome} action "${a.action}" is not one of ${Monitor_1.MONITOR_ESCALATION_ACTIONS.join(' | ')} (monitor-escalation-resolves)`);
1644
+ }
1645
+ else if (a.action === 'notify' && !a.role) {
1646
+ err('C69', `monitor ${m.id}: escalate on ${rule.outcome} notify names no role (notify <role>) (monitor-escalation-resolves)`);
1647
+ }
1648
+ else if (a.action === 'notify' &&
1649
+ roleIds.size > 0 &&
1650
+ !roleIds.has(a.role)) {
1651
+ err('C69', `monitor ${m.id}: escalate on ${rule.outcome} notify "${a.role}" is not a declared role (monitor-escalation-resolves)`);
1652
+ }
1653
+ else if (a.action !== 'notify' && a.role) {
1654
+ err('C69', `monitor ${m.id}: escalate on ${rule.outcome} ${a.action} carries a role ("${a.role}") — only notify names a role (monitor-escalation-resolves)`);
1655
+ }
1656
+ }
1657
+ }
1658
+ // C70 — monitor-emit-sinks (§14.5 step 6): evidence is appended to
1659
+ // the workspace and the verdict log — both streams emitted, each
1660
+ // exactly once, with a named sink. An unknown stream kind binds
1661
+ // nothing.
1662
+ const emitted = new Map();
1663
+ for (const sink of m.emit) {
1664
+ if (!KNOWN_STREAMS.has(sink.stream)) {
1665
+ err('C70', `monitor ${m.id}: emit stream "${sink.stream}" is not one of ${Monitor_1.MONITOR_STREAMS.join(' | ')} (monitor-emit-sinks)`);
1666
+ }
1667
+ if (!sink.target) {
1668
+ err('C70', `monitor ${m.id}: emit ${sink.stream} names no sink (${sink.stream} -> <sink>) (monitor-emit-sinks)`);
1669
+ }
1670
+ emitted.set(sink.stream, (emitted.get(sink.stream) ?? 0) + 1);
1671
+ }
1672
+ for (const stream of Monitor_1.MONITOR_STREAMS) {
1673
+ const n = emitted.get(stream) ?? 0;
1674
+ if (n === 0) {
1675
+ err('C70', `monitor ${m.id}: emit declares no ${stream} sink — §14.5 step 6: values seen, rule results, verdicts, timestamps are appended to the workspace (monitor-emit-sinks)`);
1676
+ }
1677
+ else if (n > 1) {
1678
+ err('C70', `monitor ${m.id}: emit declares the ${stream} stream ${n} times — one sink per stream (monitor-emit-sinks)`);
1679
+ }
1680
+ }
1681
+ }
1682
+ }
1683
+ // ── C10–C16: executable process bodies (TODO.roadmap/02) ──
1684
+ // These rules apply only to processes with a `does` body (the
1685
+ // executable form). Abstract processes (signature/invariants, no
1686
+ // `does`) are always valid; v2 canvas processes are untouched.
1687
+ for (const p of standard.processes ?? []) {
1688
+ const flow = p.does;
1689
+ if (!flow) {
1690
+ continue;
1691
+ }
1692
+ const steps = flow.steps ?? [];
1693
+ const edges = flow.edges ?? [];
1694
+ // C16 — step ids are unique within one does body
1695
+ // (process-step-ids-unique). Declared BEFORE the stepById map below
1696
+ // silently keeps the last duplicate.
1697
+ const seenStepIds = new Set();
1698
+ for (const s of steps) {
1699
+ if (seenStepIds.has(s.id)) {
1700
+ err('C16', `process ${p.id}: duplicate step id "${s.id}" in the does body (process-step-ids-unique)`);
1701
+ }
1702
+ seenStepIds.add(s.id);
1703
+ }
1704
+ const stepById = new Map(steps.map(s => [s.id, s]));
1705
+ // The declared name vocabulary for step I/O and edge conditions:
1706
+ // registers + signature IN/OUT parameters + instance-parameter keys +
1707
+ // the process's own `state` (a declared HAS).
1708
+ const declared = new Set(['state']);
1709
+ for (const r of p.registers ?? []) {
1710
+ declared.add(r.name);
1711
+ }
1712
+ for (const prm of p.signature?.inputs ?? []) {
1713
+ declared.add(prm.name);
1714
+ }
1715
+ for (const prm of p.signature?.outputs ?? []) {
1716
+ declared.add(prm.name);
1717
+ }
1718
+ for (const v of Object.values(p.instances?.values ?? {})) {
1719
+ for (const k of Object.keys(v)) {
1720
+ declared.add(k);
1721
+ }
1722
+ }
1723
+ // C10 — exactly one start event per executable process.
1724
+ const starts = steps.filter(s => s.kind === 'start_event');
1725
+ if (starts.length !== 1) {
1726
+ err('C10', `process ${p.id}: executable process has ${starts.length} start events — exactly one required (process-one-start)`);
1727
+ }
1728
+ // C11 — end events on every terminal path (mandatory on empty
1729
+ // gateway branches); flow edges reference declared steps.
1730
+ const outgoing = new Map();
1731
+ for (const e of edges) {
1732
+ if (!stepById.has(e.from)) {
1733
+ err('C11', `process ${p.id}: flow edge starts at undeclared step "${e.from}" (process-terminal-end)`);
1734
+ }
1735
+ if (!stepById.has(e.to)) {
1736
+ err('C11', `process ${p.id}: flow edge ends at undeclared step "${e.to}" (process-terminal-end)`);
1737
+ }
1738
+ outgoing.set(e.from, [...(outgoing.get(e.from) ?? []), e]);
1739
+ }
1740
+ for (const s of steps) {
1741
+ if ((outgoing.get(s.id) ?? []).length === 0 && s.kind !== 'end_event') {
1742
+ err('C11', `process ${p.id}: step "${s.id}" (${s.kind}) is a terminal path with no end event — every terminal path must close at an end_event, mandatory on empty gateway branches (process-terminal-end)`);
1743
+ }
1744
+ }
1745
+ // C12 — gateway edge conditions and step I/O name declared registers.
1746
+ // The caller-side names of a call's `with` bindings are step I/O too
1747
+ // (TODO.roadmap/38): an in-binding READS the caller name, an
1748
+ // out-mapping WRITES it. The callee-side parameter names are checked
1749
+ // against the callee's signature by C76, not here.
1750
+ for (const s of steps) {
1751
+ const ioNames = [...s.reads, ...s.writes];
1752
+ if (s.wait) {
1753
+ ioNames.push(s.wait);
1754
+ }
1755
+ for (const b of s.callIn) {
1756
+ ioNames.push(b.bind);
1757
+ }
1758
+ for (const b of s.callOut) {
1759
+ ioNames.push(b.bind);
1760
+ }
1761
+ for (const n of ioNames) {
1762
+ if (!declared.has(n)) {
1763
+ err('C12', `process ${p.id}: step "${s.id}" names undeclared register "${n}" (process-flow-names-resolve)`);
1764
+ }
1765
+ }
1766
+ if (s.kind === 'gateway') {
1767
+ for (const e of outgoing.get(s.id) ?? []) {
1768
+ const re = /\bself\.([A-Za-z_][A-Za-z0-9_]*)/g;
1769
+ let m;
1770
+ while ((m = re.exec(e.condition)) !== null) {
1771
+ if (!declared.has(m[1])) {
1772
+ err('C12', `process ${p.id}: gateway "${s.id}" edge condition references undeclared register "${m[1]}" (process-flow-names-resolve)`);
1773
+ }
1774
+ }
1775
+ }
1776
+ }
1777
+ }
1778
+ // C13 — the executable steps realize the signature: every OUT
1779
+ // parameter is written (error), every IN parameter is read (warning).
1780
+ // Call bindings count: an in-binding reads the caller name, an
1781
+ // out-mapping writes it (TODO.roadmap/38).
1782
+ if (p.signature) {
1783
+ const written = new Set();
1784
+ const read = new Set();
1785
+ for (const s of steps) {
1786
+ s.writes.forEach(n => written.add(n));
1787
+ s.reads.forEach(n => read.add(n));
1788
+ if (s.wait) {
1789
+ read.add(s.wait);
1790
+ }
1791
+ s.callOut.forEach(b => written.add(b.bind));
1792
+ s.callIn.forEach(b => read.add(b.bind));
1793
+ }
1794
+ for (const o of p.signature.outputs) {
1795
+ if (!written.has(o.name)) {
1796
+ err('C13', `process ${p.id}: OUT parameter "${o.name}" is never written by any step (process-signature-realized)`);
1797
+ }
1798
+ }
1799
+ for (const inp of p.signature.inputs) {
1800
+ if (!read.has(inp.name)) {
1801
+ warn('C13', `process ${p.id}: IN parameter "${inp.name}" is never read by any step (process-signature-realized)`);
1802
+ }
1803
+ }
1804
+ }
1805
+ // C14 — a recurrence (a cycle in the flow graph) must pass through a
1806
+ // timer event WITH a declared period: remove guarded timer nodes and
1807
+ // require the rest acyclic. A period-less timer_event is NOT a guard
1808
+ // (C15) and does not break the cycle here.
1809
+ const timerIds = new Set(steps.filter(s => s.kind === 'timer_event' && s.period).map(s => s.id));
1810
+ const adj = new Map();
1811
+ for (const e of edges) {
1812
+ if (timerIds.has(e.from) || timerIds.has(e.to)) {
1813
+ continue;
1814
+ }
1815
+ adj.set(e.from, [...(adj.get(e.from) ?? []), e.to]);
1816
+ }
1817
+ const cycle = findCycle(adj);
1818
+ if (cycle) {
1819
+ err('C14', `process ${p.id}: recurrence ${cycle.join(' → ')} contains no timer event — an unguarded infinite self-loop (process-timer-recurrence)`);
1820
+ }
1821
+ // C15 — a timer_event must declare its recurrence `period`: without
1822
+ // one it cannot guard a loop (C14) and is almost certainly a
1823
+ // modelling mistake (process-timer-period). The period FORMAT is
1824
+ // validated by C35 (time-format, TODO.roadmap/06); this rule checks
1825
+ // presence only.
1826
+ for (const s of steps) {
1827
+ if (s.kind === 'timer_event' && !s.period) {
1828
+ err('C15', `process ${p.id}: timer_event "${s.id}" declares no period — a timer event without a recurrence period cannot guard a loop (process-timer-period)`);
1829
+ }
1830
+ }
1831
+ }
1832
+ // ── C17–C20: instantiation and delegation (TODO.roadmap/03) ──
1833
+ // The instance plane: scope discipline on stated values (C17), INV-8
1834
+ // version pins (C18), subject-chain integrity (C19), and `of` reference
1835
+ // resolution (C20).
1836
+ const CHAIN_LEVEL_ORDER = {
1837
+ family: 0,
1838
+ group: 1,
1839
+ model: 2,
1840
+ sample: 3,
1841
+ };
1842
+ const instrumentIds = new Set((standard.instruments ?? []).map(i => i.id));
1843
+ const instanceIds = new Set((standard.instances ?? []).map(i => i.id));
1844
+ const attrScopes = new Map((standard.attributeDefinitions ?? []).map(a => [a.id, a.scope]));
1845
+ // Classification-dimension scopes, by every spelling a classification
1846
+ // map may use: the instrument dimension id, the is_dimension attribute
1847
+ // mirror id, and the enum-name aliases (DIM_ALIASES).
1848
+ const dimScopes = new Map();
1849
+ for (const inst of standard.instruments ?? []) {
1850
+ for (const d of inst.dimensions ?? []) {
1851
+ if (d.scope) {
1852
+ dimScopes.set(d.id, d.scope);
1853
+ }
1854
+ }
1855
+ }
1856
+ for (const a of standard.attributeDefinitions ?? []) {
1857
+ if (a.isDimension && a.scope && !dimScopes.has(a.id)) {
1858
+ dimScopes.set(a.id, a.scope);
1859
+ }
1860
+ }
1861
+ for (const [alias, canonical] of Object.entries(DIM_ALIASES)) {
1862
+ const s = dimScopes.get(canonical) ?? attrScopes.get(canonical);
1863
+ if (s && !dimScopes.has(alias)) {
1864
+ dimScopes.set(alias, s);
1865
+ }
1866
+ }
1867
+ for (const inst of standard.instances ?? []) {
1868
+ const levelIdx = CHAIN_LEVEL_ORDER[inst.level];
1869
+ // C17 — instance-scope: every stated value at its declared scope or
1870
+ // lower; sample-scope values only in a sample's test_context.
1871
+ // NOTE (sample-level override gap): the language PERMITS a
1872
+ // non-sample-scope attribute in a sample's has.attributes (the
1873
+ // lower-override law — a sample may restate a wider-scope value), and
1874
+ // C17 accepts it. But the current app plane has no sample
1875
+ // `parameters` field (only `test_context`), so such a value has no
1876
+ // app-side home: recs SHOULD NOT author sample-level attribute
1877
+ // overrides until the app plane grows one (tracked by TODO.roadmap
1878
+ // task 29). A future strict mode may make this an error.
1879
+ if (levelIdx === undefined) {
1880
+ err('C17', `instance ${inst.id}: level "${inst.level}" is not a chain level (family | group | model | sample) (instance-scope)`);
1881
+ }
1882
+ for (const key of Object.keys(inst.has?.attributes ?? {})) {
1883
+ const scope = attrScopes.get(key);
1884
+ if (scope === undefined) {
1885
+ err('C17', `instance ${inst.id}: attribute "${key}" is not a declared attribute_definition — scope cannot be checked (instance-scope)`);
1886
+ }
1887
+ else if (scope === 'sample') {
1888
+ err('C17', `instance ${inst.id}: sample-scope attribute "${key}" stated in has.attributes — sample-scope values live in has.test_context and are never inherited (instance-scope)`);
1889
+ }
1890
+ else if (scope &&
1891
+ levelIdx !== undefined &&
1892
+ CHAIN_LEVEL_ORDER[scope] !== undefined &&
1893
+ levelIdx < CHAIN_LEVEL_ORDER[scope]) {
1894
+ err('C17', `instance ${inst.id}: attribute "${key}" (scope ${scope}) stated at ${inst.level} level — values are stated at their declared scope or lower (instance-scope)`);
1895
+ }
1896
+ }
1897
+ for (const key of Object.keys(inst.has?.testContext ?? {})) {
1898
+ if (inst.level !== 'sample') {
1899
+ err('C17', `instance ${inst.id}: test_context value "${key}" on a ${inst.level}-level instance — test_context exists only at sample level (instance-scope)`);
1900
+ }
1901
+ const scope = attrScopes.get(key);
1902
+ if (scope === undefined) {
1903
+ err('C17', `instance ${inst.id}: attribute "${key}" is not a declared attribute_definition — scope cannot be checked (instance-scope)`);
1904
+ }
1905
+ else if (scope !== 'sample') {
1906
+ err('C17', `instance ${inst.id}: attribute "${key}" (scope ${scope}) stated in test_context — test_context holds sample-scope attributes only (instance-scope)`);
1907
+ }
1908
+ }
1909
+ for (const key of Object.keys(inst.has?.dimensions ?? {})) {
1910
+ if (inst.level === 'sample') {
1911
+ err('C17', `instance ${inst.id}: classification "${key}" on a sample-level instance — samples carry no classification (instance-scope)`);
1912
+ }
1913
+ const scope = dimScopes.get(key);
1914
+ if (scope === undefined) {
1915
+ err('C17', `instance ${inst.id}: classification dimension "${key}" is not declared (instance-scope)`);
1916
+ }
1917
+ else if (levelIdx !== undefined &&
1918
+ CHAIN_LEVEL_ORDER[scope] !== undefined &&
1919
+ levelIdx < CHAIN_LEVEL_ORDER[scope]) {
1920
+ err('C17', `instance ${inst.id}: classification dimension "${key}" (scope ${scope}) stated at ${inst.level} level — values are stated at their declared scope or lower (instance-scope)`);
1921
+ }
1922
+ }
1923
+ // C18 — instance-version-pin: every instance is version-pinned to its
1924
+ // definitions (INV-8); re-execution requires the pin.
1925
+ if (Object.keys(inst.definitionVersions ?? {}).length === 0) {
1926
+ err('C18', `instance ${inst.id}: no definition_versions — every instance is version-pinned to its definitions (INV-8) (instance-version-pin)`);
1927
+ }
1928
+ // C19 — chain-acyclic (integrity part 1): upward chain links resolve
1929
+ // to declared instances.
1930
+ for (const kind of ['model', 'group', 'family']) {
1931
+ const target = inst[kind];
1932
+ if (target && !instanceIds.has(target)) {
1933
+ err('C19', `instance ${inst.id}: chain link ${kind} "${target}" is not a declared instance (chain-acyclic)`);
1934
+ }
1935
+ }
1936
+ // C20 — instance-of-resolves: the `of` reference names a declared
1937
+ // subject (v3) or instrument (v2) definition.
1938
+ if (!subjectIds.has(inst.of) && !instrumentIds.has(inst.of)) {
1939
+ err('C20', `instance ${inst.id}: of "${inst.of}" is not a declared subject or instrument (instance-of-resolves)`);
1940
+ }
1941
+ }
1942
+ // C19 — chain-acyclic (integrity part 2): the resolved link graph is
1943
+ // acyclic (a cycle would make delegation resolution non-terminating).
1944
+ const instAdj = new Map();
1945
+ for (const inst of standard.instances ?? []) {
1946
+ const targets = [inst.model, inst.group, inst.family].filter(t => t && instanceIds.has(t));
1947
+ if (targets.length > 0) {
1948
+ instAdj.set(inst.id, targets);
1949
+ }
1950
+ }
1951
+ const instCycle = findCycle(instAdj);
1952
+ if (instCycle) {
1953
+ err('C19', `instance subject chain cycle: ${instCycle.join(' → ')} (chain-acyclic)`);
1954
+ }
1955
+ // ── C21–C26: the mapping primitive + coverage calculus ──
1956
+ // (TODO.roadmap/04; concept doc §5.2, §5.6, §5.8). Mappings are the ONLY
1957
+ // compliance relation between an implementation model and a reference
1958
+ // model: both ends resolve (C21), the direction is fixed (C22), coverage
1959
+ // is computed not authored (C23), import ≠ mapping (C24), descriptions
1960
+ // are demanded at audit strictness (C25), and views are read-only (C26).
1961
+ const strictness = options.strictness ?? 'normal';
1962
+ const localNs = standard.packageManifest?.id ?? '';
1963
+ const compIds = (0, mapping_coverage_1.componentIds)(standard);
1964
+ // In-model map profiles + standalone .prm files in the package root —
1965
+ // both serializations of the same primitive, linted uniformly.
1966
+ const allProfiles = [...(standard.mapProfiles ?? [])];
1967
+ if ((0, fs_1.existsSync)(dir)) {
1968
+ for (const entry of (0, fs_1.readdirSync)(dir).sort()) {
1969
+ if (!entry.endsWith('.prm')) {
1970
+ continue;
1971
+ }
1972
+ const path = (0, path_1.join)(dir, entry);
1973
+ try {
1974
+ allProfiles.push(...(0, prm_1.prmToMapProfiles)((0, prm_1.loadPrm)((0, fs_1.readFileSync)(path, 'utf8'))));
1975
+ }
1976
+ catch (e) {
1977
+ err('C21', `${entry}: ${e.message} (mapping-resolves)`);
1978
+ }
1979
+ }
1980
+ }
1981
+ // Dependency-manifest resolution for the supply-chain rules (C24's
1982
+ // product_reference exemption, C81, C83): the composition locator when
1983
+ // given, else the SIBLING-package scan (the checkManifestResolution
1984
+ // stopgap pattern) — manifest-only linting (no --with) must still see
1985
+ // package kinds and edition registers. Memoized per package id.
1986
+ const resolveDepManifest = (() => {
1987
+ const cache = new Map();
1988
+ const siblings = new Map();
1989
+ try {
1990
+ const parent = (0, path_1.dirname)((0, path_1.resolve)(dir));
1991
+ for (const entry of (0, fs_1.readdirSync)(parent).sort()) {
1992
+ const full = (0, path_1.join)(parent, entry);
1993
+ try {
1994
+ if (!(0, fs_1.statSync)(full).isDirectory()) {
1995
+ continue;
1996
+ }
1997
+ const m = (0, package_1.readPackageManifest)(full);
1998
+ siblings.set(m.id, m);
1999
+ }
2000
+ catch {
2001
+ // Not a package dir (no/invalid manifest) — not a candidate.
2002
+ }
2003
+ }
2004
+ }
2005
+ catch {
2006
+ // No readable parent — no sibling fallback.
2007
+ }
2008
+ return (id) => {
2009
+ const cached = cache.get(id);
2010
+ if (cached !== undefined) {
2011
+ return cached;
2012
+ }
2013
+ let m = null;
2014
+ const located = options.resolvePackage?.(id);
2015
+ if (located) {
2016
+ try {
2017
+ m = (0, package_1.readPackageManifest)(located);
2018
+ }
2019
+ catch {
2020
+ m = null;
2021
+ }
2022
+ }
2023
+ m ??= siblings.get(id) ?? null;
2024
+ cache.set(id, m);
2025
+ return m;
2026
+ };
2027
+ })();
2028
+ for (const mp of allProfiles) {
2029
+ for (const [source, pairs] of Object.entries(mp.mappings)) {
2030
+ // C22 — direction: the source is a LOCAL component. A namespaced
2031
+ // source (NS#id) maps a reference element — the wrong way round.
2032
+ if (source.includes('#')) {
2033
+ err('C22', `map_profile ${mp.namespace}: mapping source "${source}" is a namespaced reference element — the mapping direction is implementation → reference (mapping-direction)`);
2034
+ }
2035
+ else if (!compIds.has(source)) {
2036
+ err('C21', `map_profile ${mp.namespace}: mapping source "${source}" is not a declared component (mapping-resolves)`);
2037
+ }
2038
+ for (const pair of pairs) {
2039
+ const ref = (0, mapping_coverage_1.parseTargetRef)(pair.target, mp.namespace);
2040
+ if (ref.namespace !== mp.namespace) {
2041
+ err('C21', `map_profile ${mp.namespace}: target "${pair.target}" carries namespace "${ref.namespace}" ≠ the profile namespace "${mp.namespace}" (Namespace#ElementID aliasing) (mapping-resolves)`);
2042
+ }
2043
+ if (localNs && ref.namespace === localNs) {
2044
+ err('C22', `map_profile ${mp.namespace}: target "${pair.target}" is in the model's own namespace — mapping to one's own components inverts the direction (implementation → reference) (mapping-direction)`);
2045
+ }
2046
+ if (!compIds.has(ref.qualified)) {
2047
+ err('C21', `map_profile ${mp.namespace}: target "${ref.qualified}" has no declared alias element — the implementation model declares local copies of the reference elements it maps to (mapping-resolves)`);
2048
+ }
2049
+ // C25 — a mapping without description is a warning at audit
2050
+ // strictness (silent in normal mode).
2051
+ if (strictness === 'audit' && !pair.description) {
2052
+ warn('C25', `map_profile ${mp.namespace}: mapping ${source} -> ${ref.qualified} has no description — how the fulfilment works is demanded at audit strictness (mapping-description)`);
2053
+ }
2054
+ }
2055
+ }
2056
+ }
2057
+ // C23 — mapping-calculus-consistency: coverage is COMPUTED, not
2058
+ // authored. An authored assertion that disagrees with the calculus is
2059
+ // an error — both the per-pair `coverage` on a mapping (its target is
2060
+ // directly mapped, hence fully covered) and the profile-level
2061
+ // `coverage { … }` tripwires over arbitrary reference components. The
2062
+ // reference tree comes from options.references when supplied, else
2063
+ // from the package's own Namespace#… alias forest.
2064
+ //
2065
+ // Usability notes when options.references is ABSENT: the alias forest
2066
+ // is built from the package's own `Namespace#ElementID` process
2067
+ // declarations, which are FRESH declarations — an alias of a gateway
2068
+ // parent must itself re-declare `child_composition gateway`, or the
2069
+ // alias aggregates with the default `all` and assertions compute
2070
+ // against the wrong composition. And closure proposals fire only at
2071
+ // FULL aggregation (every child full) — a gateway parent aggregated to
2072
+ // minimal/partial proposes nothing, so absence of a proposal is not
2073
+ // absence of cover.
2074
+ for (const mp of allProfiles) {
2075
+ const pairAssertions = Object.entries(mp.mappings).flatMap(([source, pairs]) => pairs
2076
+ .filter(p => p.coverage)
2077
+ .map(pair => ({
2078
+ source,
2079
+ pair,
2080
+ ref: (0, mapping_coverage_1.parseTargetRef)(pair.target, mp.namespace),
2081
+ })));
2082
+ const profileAssertions = Object.entries(mp.coverage ?? {}).map(([ref, level]) => ({ ref: (0, mapping_coverage_1.parseTargetRef)(ref, mp.namespace), level }));
2083
+ if (pairAssertions.length === 0 && profileAssertions.length === 0) {
2084
+ continue;
2085
+ }
2086
+ const reference = options.references?.[mp.namespace];
2087
+ const forest = reference
2088
+ ? (0, mapping_coverage_1.buildProcessTree)(reference)
2089
+ : (0, mapping_coverage_1.buildProcessTree)(standard, { idPrefix: mp.namespace + '#' });
2090
+ const report = (0, mapping_coverage_1.computeCoverage)(standard, forest, (0, mapping_coverage_1.mappingsFromProfile)(mp, localNs), mp.namespace, { implementationId: localNs, referenceId: mp.namespace });
2091
+ const computed = new Map(report.components.map(c => [c.id, c.coverage]));
2092
+ for (const a of pairAssertions) {
2093
+ const level = computed.get(a.ref.id);
2094
+ if (level === undefined) {
2095
+ continue; // C21 already flags the unresolvable target
2096
+ }
2097
+ if (level !== a.pair.coverage) {
2098
+ err('C23', `map_profile ${mp.namespace}: ${a.source} -> ${a.ref.qualified} asserts coverage "${a.pair.coverage}" but the calculus computes "${level}" — coverage is computed, not authored (mapping-calculus-consistency)`);
2099
+ }
2100
+ }
2101
+ for (const a of profileAssertions) {
2102
+ const level = computed.get(a.ref.id);
2103
+ if (level === undefined) {
2104
+ err('C23', `map_profile ${mp.namespace}: coverage assertion for "${a.ref.qualified}" names a component outside the reference tree (mapping-calculus-consistency)`);
2105
+ continue;
2106
+ }
2107
+ if (level !== a.level) {
2108
+ err('C23', `map_profile ${mp.namespace}: coverage assertion "${a.ref.qualified}: ${a.level}" disagrees with the computed "${level}" — coverage is computed, not authored (mapping-calculus-consistency)`);
2109
+ }
2110
+ }
2111
+ }
2112
+ // C24 — import-not-mapping: an import (uses/extends) is structural
2113
+ // inclusion; a mapping is a fulfilment claim. Neither may be expressed
2114
+ // as the other (concept doc §5.6 b) — one rule, both directions: the
2115
+ // imported set and the mapped set must be disjoint.
2116
+ //
2117
+ // Supply-chain exemption (TODO.roadmap/36, doctrine ch. 15 §15.3): an
2118
+ // ABSTRACT IMPORT of a product reference package is reference content
2119
+ // cited at a pinned edition, never structural inclusion — the composer
2120
+ // does not merge it (ser-des/package.ts). Mode-1 consumption IS a
2121
+ // mapping ("the user's model maps its usage to the product's promised
2122
+ // aspects"), so a map_profile whose namespace is an imported
2123
+ // product_reference package is the intended pattern, not a violation.
2124
+ const imported = new Set([
2125
+ standard.packageManifest?.extends,
2126
+ ...(standard.packageManifest?.uses ?? []),
2127
+ ].filter((x) => !!x));
2128
+ for (const mp of allProfiles) {
2129
+ if (imported.has(mp.namespace)) {
2130
+ if (resolveDepManifest(mp.namespace)?.kind === 'product_reference') {
2131
+ continue;
2132
+ }
2133
+ err('C24', `map_profile ${mp.namespace}: namespace "${mp.namespace}" is imported (uses/extends) — an import may not be expressed as a mapping, nor a mapping as an import; inclusion ≠ fulfilment (import-not-mapping)`);
2134
+ }
2135
+ }
2136
+ // C26 — view-read-only: a view is a read-only lens. It may only name
2137
+ // declared elements, and its `against` reference must be a namespace
2138
+ // the model actually maps to — a view never adds, removes, or edits
2139
+ // the mappings of the model it reads (the grammar gives view_profile
2140
+ // no mapping slots, and applyView returns a frozen projection).
2141
+ const mappedNamespaces = new Set(allProfiles.map(p => p.namespace));
2142
+ for (const vp of standard.viewProfiles ?? []) {
2143
+ for (const el of vp.visibleElements ?? []) {
2144
+ if (!compIds.has(el)) {
2145
+ err('C26', `view_profile ${vp.id}: visible element "${el}" is not a declared component — a view reads the model, it does not invent it (view-read-only)`);
2146
+ }
2147
+ }
2148
+ if (vp.against && !mappedNamespaces.has(vp.against)) {
2149
+ err('C26', `view_profile ${vp.id}: against "${vp.against}" names no declared map_profile/mapSet namespace — a view reads coverage, it does not create it (view-read-only)`);
2150
+ }
2151
+ }
2152
+ // ── C81–C83: the model supply chain (TODO.roadmap/36; doctrine ch. 15
2153
+ // §15.9) ──
2154
+ // Three publishers — the standard (reference), the manufacturer
2155
+ // (product reference), the user (implementation) — and mapping is the
2156
+ // only relation between them. C81: a product reference package's
2157
+ // declaration resolves (manufacturer, product designation, and the
2158
+ // standards-reference packages it maps to — its map profiles and its
2159
+ // maps_to manifest register must agree, and every maps_to target must
2160
+ // resolve). C82: an unmapped IS promise is a brochure claim — flagged
2161
+ // at authoring (a warning; the mapping is the conformance claim made
2162
+ // computable). C83: abstract imports pin a version — no unpinned
2163
+ // reference consumption, and the pin resolves against the product
2164
+ // package's edition register.
2165
+ const productManifest = standard.packageManifest;
2166
+ if (productManifest?.kind === 'product_reference') {
2167
+ if (!productManifest.manufacturer) {
2168
+ err('C81', `package "${productManifest.id}": a product reference package declares its manufacturer — the model speaks for them (product-maps-resolves)`);
2169
+ }
2170
+ if (!productManifest.product) {
2171
+ err('C81', `package "${productManifest.id}": a product reference package declares its product designation (product-maps-resolves)`);
2172
+ }
2173
+ const mapsTo = productManifest.mapsTo ?? [];
2174
+ if (mapsTo.length === 0) {
2175
+ err('C81', `package "${productManifest.id}": a product reference package declares the standards-reference packages it maps to (maps_to) — an unmapped product model is a brochure (product-maps-resolves)`);
2176
+ }
2177
+ for (const target of mapsTo) {
2178
+ const dm = resolveDepManifest(target);
2179
+ if (!dm) {
2180
+ err('C81', `package "${productManifest.id}": maps_to "${target}" does not resolve to a known package — the standards reference a product maps to must resolve (product-maps-resolves)`);
2181
+ }
2182
+ else if (dm.kind === 'product_reference') {
2183
+ err('C81', `package "${productManifest.id}": maps_to "${target}" is itself a product reference package — a product maps to the standard, never to another product (product-maps-resolves)`);
2184
+ }
2185
+ if (!mappedNamespaces.has(target)) {
2186
+ err('C81', `package "${productManifest.id}": maps_to "${target}" names a standards reference the model never maps to — declare the map_profile/.prm mapSet or drop the entry (product-maps-resolves)`);
2187
+ }
2188
+ }
2189
+ for (const ns of mappedNamespaces) {
2190
+ if (!mapsTo.includes(ns)) {
2191
+ err('C81', `map_profile ${ns}: the model maps to a standards reference it does not declare in its manifest maps_to register (product-maps-resolves)`);
2192
+ }
2193
+ }
2194
+ // C82 — every block-form IS promise is a mapping SOURCE (statement-
2195
+ // only shorthand entries are prose, not mappable claims).
2196
+ const mappedSources = new Set();
2197
+ for (const mp of allProfiles) {
2198
+ for (const source of Object.keys(mp.mappings)) {
2199
+ mappedSources.add(source);
2200
+ }
2201
+ }
2202
+ for (const s of standard.subjects ?? []) {
2203
+ for (const p of s.is?.promises ?? []) {
2204
+ if (!p.id) {
2205
+ continue;
2206
+ }
2207
+ if (!mappedSources.has(p.id)) {
2208
+ warn('C82', `subject ${s.id}: promise "${p.id}" is not mapped to any standard — an unmapped promise is a brochure claim; the mapping is the conformance claim made computable (product-unmapped-promises)`);
2209
+ }
2210
+ }
2211
+ }
2212
+ }
2213
+ // C83 — abstract-import-pinned: a uses/extends edge to a product
2214
+ // reference package carries a version pin (uses { <id>@<edition> }),
2215
+ // and the pin resolves against the product package's edition register
2216
+ // (the C80 register discipline applied to the import edge).
2217
+ if (productManifest) {
2218
+ for (const dep of (0, package_1.effectiveUses)(productManifest)) {
2219
+ const dm = resolveDepManifest(dep);
2220
+ if (dm?.kind !== 'product_reference') {
2221
+ continue;
2222
+ }
2223
+ const pin = productManifest.usePins?.[dep];
2224
+ if (!pin) {
2225
+ err('C83', `package "${productManifest.id}" imports product reference package "${dep}" without a version pin — abstract imports pin a version (uses { ${dep}@<edition> }); no unpinned reference consumption (abstract-import-pinned)`);
2226
+ continue;
2227
+ }
2228
+ const register = new Set([
2229
+ ...(dm.editions ?? []),
2230
+ ...(dm.version ? [dm.version] : []),
2231
+ ]);
2232
+ if (register.size > 0 && !register.has(pin)) {
2233
+ err('C83', `package "${productManifest.id}" pins "${dep}" at "${pin}", which does not resolve against the product package's edition register { ${[...register].join(' ')} } — the pin names a declared edition (abstract-import-pinned)`);
2234
+ }
2235
+ }
2236
+ }
2237
+ // ── C97–C98: the certification program (TODO.v2/01;
2238
+ // analysis/twin-certification-design.md Q4) ──
2239
+ // A fourth publisher with the product_reference shape: the scheme
2240
+ // operator's program is related to recs and product packages by
2241
+ // MAPPING and pinned abstract imports only — composed into nothing.
2242
+ // C97: the program's maps_to register resolves (the C81-class
2243
+ // resolution discipline — the same resolveDepManifest locator/sibling
2244
+ // scan): the program maps to the rec requirement vocabulary it
2245
+ // certifies against, never to another program, and product twin
2246
+ // declarations enter as pinned abstract imports (uses @edition, C83),
2247
+ // never as maps_to entries. C98: a program declaring a no-surveillance
2248
+ // ISO/IEC 17067 scheme shape (type_1a/1b — sample-only; Table 1) while
2249
+ // declaring surveillance machinery (a monitor — the continuous-
2250
+ // evaluation primitive — or a surveillance-classified process) is a
2251
+ // WARNING: the type_1a/1b shape structurally can't say "continuously".
2252
+ if (productManifest?.kind === 'certification_program') {
2253
+ const mapsTo = productManifest.mapsTo ?? [];
2254
+ if (mapsTo.length === 0) {
2255
+ err('C97', `package "${productManifest.id}": a certification program declares the Recommendation vocabulary it certifies against (maps_to) — a program that maps to nothing certifies nothing (program-maps-resolves)`);
2256
+ }
2257
+ for (const target of mapsTo) {
2258
+ const dm = resolveDepManifest(target);
2259
+ if (!dm) {
2260
+ err('C97', `package "${productManifest.id}": maps_to "${target}" does not resolve to a known package — the requirement vocabulary a program certifies against must resolve (program-maps-resolves)`);
2261
+ }
2262
+ else if (dm.kind === 'certification_program') {
2263
+ err('C97', `package "${productManifest.id}": maps_to "${target}" is itself a certification program — a program maps to the standards vocabulary, never to another program (program-maps-resolves)`);
2264
+ }
2265
+ else if (dm.kind === 'product_reference') {
2266
+ err('C97', `package "${productManifest.id}": maps_to "${target}" is a product reference package — the product twin declarations a program certifies are pinned abstract imports (uses { ${target}@<edition> }, C83), never maps_to entries (program-maps-resolves)`);
2267
+ }
2268
+ }
2269
+ // C98 — the no-surveillance scheme shapes (ISO/IEC 17067 Table 1:
2270
+ // type 1a/1b carry no surveillance) versus the program's declared
2271
+ // surveillance machinery. A monitor IS a continuous claim ("without
2272
+ // triggers, 'continuous' has no clock" — types/Monitor.ts); an
2273
+ // activity_kind { surveillance } process is a surveillance claim.
2274
+ const SCHEME_TYPES_WITHOUT_SURVEILLANCE = new Set(['type_1a', 'type_1b']);
2275
+ if (productManifest.schemeType &&
2276
+ SCHEME_TYPES_WITHOUT_SURVEILLANCE.has(productManifest.schemeType)) {
2277
+ const claims = [];
2278
+ if ((standard.monitors ?? []).length > 0) {
2279
+ claims.push(`${standard.monitors.length} monitor(s): ${standard.monitors.map(m => m.id).join(', ')}`);
2280
+ }
2281
+ const surveillanceProcesses = (standard.processes ?? []).filter(p => (p.activityKinds ?? []).includes('surveillance'));
2282
+ if (surveillanceProcesses.length > 0) {
2283
+ claims.push(`surveillance-classified process(es): ${surveillanceProcesses.map(p => p.id).join(', ')}`);
2284
+ }
2285
+ if (claims.length > 0) {
2286
+ warn('C98', `package "${productManifest.id}": scheme_type "${productManifest.schemeType}" certifies a type sample with no surveillance, but the program declares ${claims.join(' and ')} — the ${productManifest.schemeType} shape structurally can't say "continuously"; a surveillance-constitutive program self-classifies type_5 (ISO/IEC 17067 Table 1) (program-surveillance-required)`);
2287
+ }
2288
+ }
2289
+ }
2290
+ // ── C99: the probe-channel provenance facet (smart TODO.v2/01 TCD-2;
2291
+ // analysis/twin-certification-design.md Q2) ──
2292
+ // A measured reference variable declares the physical-side channel its
2293
+ // reading arrives by: reference_instrument (a traceable reference, cited
2294
+ // by equipment-register id) | observer_attestation (a verification
2295
+ // officer reads the physical display into the evidence form — admitted
2296
+ // with the DECLARED traceability limitation, controller decision 3:
2297
+ // "twin ≡ display, not twin ≡ mass" is data, never a comment) |
2298
+ // sim_ground_truth (the acceptance environment only). The kernel checks
2299
+ // shape and vocabulary, register-free; the ref's RESOLUTION against the
2300
+ // equipment/personnel/sim registers is the smart-side linker's R45.
2301
+ {
2302
+ const PROBE_CHANNELS = new Set([
2303
+ 'reference_instrument',
2304
+ 'observer_attestation',
2305
+ 'sim_ground_truth',
2306
+ ]);
2307
+ for (const t of standard.conformanceTests ?? []) {
2308
+ const variableNames = new Set((t.variables ?? []).map(v => v.name));
2309
+ for (const v of t.variables ?? []) {
2310
+ const p = v.provenance;
2311
+ if (!p) {
2312
+ continue;
2313
+ }
2314
+ if (!PROBE_CHANNELS.has(p.channel)) {
2315
+ err('C99', `conformance test ${t.id}: variable "${v.name}" declares provenance channel "${p.channel}" — the probe channel is one of reference_instrument | observer_attestation | sim_ground_truth (variable-provenance-channel)`);
2316
+ }
2317
+ if (p.ref === '') {
2318
+ err('C99', `conformance test ${t.id}: variable "${v.name}" declares channel "${p.channel}" but cites no ref — the channel's register entry (equipment-register id, personnel/participant id, or sim deployment id) is the provenance's substance (variable-provenance-channel)`);
2319
+ }
2320
+ if (p.observedAt === '') {
2321
+ err('C99', `conformance test ${t.id}: variable "${v.name}" declares no observed_at binding — a channel reading without its observation timestamp cannot be paired (variable-provenance-channel)`);
2322
+ }
2323
+ else if (!variableNames.has(p.observedAt)) {
2324
+ err('C99', `conformance test ${t.id}: variable "${v.name}" binds observed_at to "${p.observedAt}", which names no declared variable of the test — the observation timestamp lands in a declared variable (variable-provenance-channel)`);
2325
+ }
2326
+ if (p.channel === 'observer_attestation' && p.limitation === '') {
2327
+ err('C99', `conformance test ${t.id}: variable "${v.name}" declares the observer_attestation channel without its traceability limitation — attestation-only evidence proves "twin ≡ display", not "twin ≡ mass"; the limitation is DECLARED data, never a comment (variable-provenance-channel)`);
2328
+ }
2329
+ if (p.channel !== 'observer_attestation' && p.limitation !== '') {
2330
+ warn('C99', `conformance test ${t.id}: variable "${v.name}" carries a traceability limitation on the ${p.channel} channel — the limitation declaration rides the observer_attestation channel (variable-provenance-channel)`);
2331
+ }
2332
+ if (v.source !== '' && v.source !== 'measured') {
2333
+ warn('C99', `conformance test ${t.id}: variable "${v.name}" is source ${v.source} but declares a physical probe channel — the channel provenance belongs to a measured reference reading (variable-provenance-channel)`);
2334
+ }
2335
+ }
2336
+ }
2337
+ }
2338
+ // ── C86–C88: the model-native DPP (TODO.roadmap/35; doctrine ch. 14
2339
+ // §14.6, ch. 15 §15.6/§15.9) ──
2340
+ // The passport is the product model's named, access-classed PROJECTION —
2341
+ // "it cannot drift from the model because it *is* the model" (§14.6).
2342
+ // §15.9's passport rule: "the passport projection contains only aspects
2343
+ // that resolve — public classes contain nothing marked restricted".
2344
+ // C86: every content entry's class is one of the six declared classes,
2345
+ // and a qualified `<class>.<ref>` resolves against the package's
2346
+ // declared aspects/promises (sustainability refs pass unjudged — the
2347
+ // ESPR delegated-act content models do not exist yet, TODO.roadmap/35
2348
+ // "Out"). C87: an entry marked restricted/authority the public class
2349
+ // reaches (the same entry, or a covering bare class) is a leak. C88:
2350
+ // the UPI scheme declares its pattern and its ESPR level
2351
+ // (model | batch | item). Packages without a passport are untouched —
2352
+ // the loop is empty.
2353
+ {
2354
+ const KNOWN_CONTENT = new Set(Passport_1.PASSPORT_CONTENT_CLASSES);
2355
+ const KNOWN_LEVELS = new Set(Passport_1.PASSPORT_UPI_LEVELS);
2356
+ const entryToken = (e) => e.ref === '' ? e.contentClass : `${e.contentClass}.${e.ref}`;
2357
+ // The per-class resolution domains for qualified entries (C86): the
2358
+ // package's declared aspects/promises, one vocabulary per class.
2359
+ const passportPromiseIds = new Set((standard.subjects ?? []).flatMap(s => (s.is.promises ?? []).map(pr => pr.id).filter(id => id !== '')));
2360
+ const compositionIds = new Set((standard.subjects ?? []).flatMap(s => [
2361
+ ...(s.is.structure ?? []),
2362
+ ...Object.keys(s.is.designParameters ?? {}),
2363
+ ...Object.keys(s.has.characteristics ?? {}),
2364
+ ...(s.has.serves ?? []).map(sv => sv.aspect),
2365
+ ]));
2366
+ const passportArtifactIds = new Set([
2367
+ ...(standard.artifactDefinitions ?? []).map(a => a.id),
2368
+ ...(standard.subjects ?? []).flatMap(s => s.is.artifacts ?? []),
2369
+ ]);
2370
+ const passportMonitorIds = new Set((standard.monitors ?? []).map(m => m.id));
2371
+ // null = the class has no kernel resolution domain (sustainability —
2372
+ // the forward class; its refs pass unjudged).
2373
+ const CONTENT_DOMAINS = {
2374
+ identity: subjectIds,
2375
+ composition: compositionIds,
2376
+ promises_as_verified: passportPromiseIds,
2377
+ live_compliance_status: new Set([
2378
+ ...passportMonitorIds,
2379
+ ...passportPromiseIds,
2380
+ ]),
2381
+ artifacts: passportArtifactIds,
2382
+ sustainability: null,
2383
+ };
2384
+ for (const p of standard.passports ?? []) {
2385
+ // C86 — passport-content-resolves (§15.9).
2386
+ for (const e of p.entries) {
2387
+ if (!KNOWN_CONTENT.has(e.contentClass)) {
2388
+ err('C86', `passport ${p.id}: content entry "${entryToken(e)}" names an unknown content class — one of ${Passport_1.PASSPORT_CONTENT_CLASSES.join(' | ')} (passport-content-resolves)`);
2389
+ continue;
2390
+ }
2391
+ if (e.ref !== '') {
2392
+ const domain = CONTENT_DOMAINS[e.contentClass];
2393
+ if (domain !== null && !domain.has(e.ref)) {
2394
+ err('C86', `passport ${p.id}: content entry "${entryToken(e)}" — "${e.ref}" does not resolve against the package's declared aspects/promises (the passport projection contains only aspects that resolve, §15.9) (passport-content-resolves)`);
2395
+ }
2396
+ }
2397
+ }
2398
+ // C87 — passport-access-leak (§15.9): an entry marked restricted/
2399
+ // authority is reachable from the public class when the public
2400
+ // block carries the same entry or a covering bare class. (The
2401
+ // reverse narrowing — a bare restricted class with one ref made
2402
+ // public — is an authored exception, not a leak.)
2403
+ const publicEntries = p.entries.filter(e => e.access === 'public');
2404
+ const publicTokens = new Set(publicEntries.map(entryToken));
2405
+ const publicBareClasses = new Set(publicEntries.filter(e => e.ref === '').map(e => e.contentClass));
2406
+ for (const e of p.entries) {
2407
+ if (e.access !== 'restricted' && e.access !== 'authority') {
2408
+ continue;
2409
+ }
2410
+ if (publicTokens.has(entryToken(e)) ||
2411
+ publicBareClasses.has(e.contentClass)) {
2412
+ err('C87', `passport ${p.id}: entry "${entryToken(e)}" is marked ${e.access} but reachable from the public class — public classes contain nothing marked restricted (§15.9) (passport-access-leak)`);
2413
+ }
2414
+ }
2415
+ // C88 — passport-upi-scheme (§14.6; ESPR): the unique product
2416
+ // identifier declares its pattern and its level.
2417
+ if (p.upi.pattern === '') {
2418
+ err('C88', `passport ${p.id}: declares no UPI pattern — a passport is reached through its unique product identifier (upi { pattern … level … }) (passport-upi-scheme)`);
2419
+ }
2420
+ if (p.upi.level === '') {
2421
+ err('C88', `passport ${p.id}: the UPI pattern declares no level — the ESPR unique product identifier is model | batch | item level (passport-upi-scheme)`);
2422
+ }
2423
+ else if (!KNOWN_LEVELS.has(p.upi.level)) {
2424
+ err('C88', `passport ${p.id}: UPI level "${p.upi.level}" is not one of ${Passport_1.PASSPORT_UPI_LEVELS.join(' | ')} (passport-upi-scheme)`);
2425
+ }
2426
+ }
2427
+ }
2428
+ // ── C90–C91: the architecture invariants (smart gap-close E9,
2429
+ // analysis/architecture-gaps-2026-07.md; smart
2430
+ // docs/oiml-core/09-invariants.md) ──
2431
+ // The `invariant` construct is the first-class replacement for the
2432
+ // note-family encoding. C90 invariant-shape: every invariant carries
2433
+ // name, statement, and severity (severity is PRESENCE-judged only —
2434
+ // the smart side owns the severity vocabulary), and enforcement is a
2435
+ // non-empty claim list XOR the literal `aspirational` marker — never
2436
+ // empty, never both. C91 invariant-enforcement-grammar: every claim
2437
+ // matches kernel:C<n> | linker:<kebab-name> | gate:<kebab-name>, and
2438
+ // the aspirational marker never appears inside a claims list. Claim
2439
+ // TARGET resolution (does the linker rule exist, does the gate have
2440
+ // evidence) is the smart-side linker rule R38's crosswalk — the
2441
+ // kernel checks syntax/shape only. Packages without invariants are
2442
+ // untouched — the loop is empty.
2443
+ {
2444
+ const CLAIM_GRAMMAR = [
2445
+ /^kernel:C\d+$/,
2446
+ /^linker:[a-z0-9-]+$/,
2447
+ /^gate:[a-z0-9-]+$/,
2448
+ ];
2449
+ for (const inv of standard.invariants ?? []) {
2450
+ // C90 — invariant-shape.
2451
+ if (inv.name === '') {
2452
+ err('C90', `invariant ${inv.id}: declares no name — an invariant carries a short rule name (invariant-shape)`);
2453
+ }
2454
+ if (inv.statement === '') {
2455
+ err('C90', `invariant ${inv.id}: declares no statement — an invariant carries the rule's prose (the default spelling inline; alternates ride text ${inv.id}.statement blocks) (invariant-shape)`);
2456
+ }
2457
+ if (inv.severity === '') {
2458
+ err('C90', `invariant ${inv.id}: declares no severity (invariant-shape)`);
2459
+ }
2460
+ const e = inv.enforcement;
2461
+ if (!e.aspirational && e.claims.length === 0) {
2462
+ err('C90', `invariant ${inv.id}: declares no enforcement — a non-empty claim list (enforcement { kernel:C<n> … }) or the literal marker (enforcement aspirational) (invariant-shape)`);
2463
+ }
2464
+ if (e.aspirational && e.claims.length > 0) {
2465
+ err('C90', `invariant ${inv.id}: enforcement mixes the aspirational marker with ${e.claims.length} claim(s) — one or the other, never both (invariant-shape)`);
2466
+ }
2467
+ // C91 — invariant-enforcement-grammar (syntax only; the smart
2468
+ // side's R38 owns target resolution).
2469
+ for (const claim of e.claims) {
2470
+ if (claim === 'aspirational') {
2471
+ err('C91', `invariant ${inv.id}: the aspirational marker never appears inside a claims list — spell it bare (enforcement aspirational) (invariant-enforcement-grammar)`);
2472
+ }
2473
+ else if (!CLAIM_GRAMMAR.some(re => re.test(claim))) {
2474
+ err('C91', `invariant ${inv.id}: enforcement claim "${claim}" does not match kernel:C<n> | linker:<kebab-name> | gate:<kebab-name> (invariant-enforcement-grammar)`);
2475
+ }
2476
+ }
2477
+ }
2478
+ }
2479
+ // ── C92–C93: the required test orderings (smart gap-close E10,
2480
+ // analysis/architecture-gaps-2026-07.md; the smart contract
2481
+ // data/schemas/test-sequences.yaml) ──
2482
+ // The `test_sequence` construct is the first-class replacement for the
2483
+ // hand-authored supplemental test-sequences.yaml. C92
2484
+ // test-sequence-shape: every sequence carries name, description, and a
2485
+ // non-empty steps list; every step's order is a positive integer
2486
+ // unique in the sequence; every step carries test XOR phase; role
2487
+ // appears only on test steps and only in the baseline | follow_up
2488
+ // vocabulary; depends_on is an integer. C93 test-sequence-integrity:
2489
+ // every depends_on names the order of an EARLIER step of the same
2490
+ // sequence — never itself (self-reference), never a later step
2491
+ // (forward reference), never an order no step declares. With
2492
+ // single-parent earlier-order edges a cycle is impossible by
2493
+ // construction (a cycle needs at least one non-descending edge, which
2494
+ // the per-edge checks flag), so the three explicit per-edge checks
2495
+ // subsume cycle detection. Test-ref RESOLUTION (does /conf/… name a
2496
+ // declared conformance test of the standard) is the smart-side linker
2497
+ // rule R39's job — the kernel checks syntax/shape only, exactly like
2498
+ // E9's C90/C91 vs R38 split. Packages without test sequences are
2499
+ // untouched — the loop is empty.
2500
+ {
2501
+ for (const seq of standard.testSequences ?? []) {
2502
+ // C92 — test-sequence-shape.
2503
+ if (seq.name === '') {
2504
+ err('C92', `test_sequence ${seq.id}: declares no name — a test sequence carries a short name (test-sequence-shape)`);
2505
+ }
2506
+ if (seq.description === '') {
2507
+ err('C92', `test_sequence ${seq.id}: declares no description — a test sequence carries the ordering's prose (the default spelling inline; alternates ride text ${seq.id}.description blocks) (test-sequence-shape)`);
2508
+ }
2509
+ if (seq.steps.length === 0) {
2510
+ err('C92', `test_sequence ${seq.id}: declares no steps — a test sequence is a non-empty ordered step list (test-sequence-shape)`);
2511
+ }
2512
+ const orders = new Set();
2513
+ for (const step of seq.steps) {
2514
+ const at = step.order !== null ? `step ${step.order}` : 'a step';
2515
+ if (step.order === null ||
2516
+ !Number.isInteger(step.order) ||
2517
+ step.order < 1) {
2518
+ err('C92', `test_sequence ${seq.id}: ${at} declares no positive-integer order (got ${step.order === null ? 'none' : step.order}) — the step order is a positive integer (test-sequence-shape)`);
2519
+ }
2520
+ else if (orders.has(step.order)) {
2521
+ err('C92', `test_sequence ${seq.id}: step order ${step.order} is declared twice — the order is unique within the sequence (test-sequence-shape)`);
2522
+ }
2523
+ else {
2524
+ orders.add(step.order);
2525
+ }
2526
+ if ((step.test === '') === (step.phase === '')) {
2527
+ err('C92', `test_sequence ${seq.id}: ${at} carries ${step.test === ''
2528
+ ? 'neither test nor phase'
2529
+ : 'both test and phase'} — exactly one per step (a conformance-test reference XOR an environment-program phase) (test-sequence-shape)`);
2530
+ }
2531
+ if (step.role !== '') {
2532
+ if (step.test === '') {
2533
+ err('C92', `test_sequence ${seq.id}: ${at} declares role "${step.role}" on ${step.phase === ''
2534
+ ? 'a step carrying neither test nor phase'
2535
+ : 'a phase step'} — the role is meaningful on test steps only (test-sequence-shape)`);
2536
+ }
2537
+ else if (!TestSequence_1.TEST_SEQUENCE_STEP_ROLES.includes(step.role)) {
2538
+ err('C92', `test_sequence ${seq.id}: ${at} declares role "${step.role}" — the vocabulary is ${TestSequence_1.TEST_SEQUENCE_STEP_ROLES.join(' | ')} (test-sequence-shape)`);
2539
+ }
2540
+ }
2541
+ if (step.dependsOn !== null && !Number.isInteger(step.dependsOn)) {
2542
+ err('C92', `test_sequence ${seq.id}: ${at} declares depends_on ${step.dependsOn} — the dependency is the integer order of an earlier step (test-sequence-shape)`);
2543
+ }
2544
+ }
2545
+ // C93 — test-sequence-integrity (per-edge, explicit: no
2546
+ // self-reference, no forward reference, no dangling order).
2547
+ for (const step of seq.steps) {
2548
+ if (step.dependsOn === null || !Number.isInteger(step.dependsOn)) {
2549
+ continue; // absent, or malformed — C92 owns the shape
2550
+ }
2551
+ const at = step.order !== null ? `step ${step.order}` : 'a step';
2552
+ if (step.order !== null && step.dependsOn === step.order) {
2553
+ err('C93', `test_sequence ${seq.id}: ${at} depends on itself — depends_on names an EARLIER step (test-sequence-integrity)`);
2554
+ }
2555
+ else if (!orders.has(step.dependsOn)) {
2556
+ err('C93', `test_sequence ${seq.id}: ${at} depends_on ${step.dependsOn} names no step of the sequence (test-sequence-integrity)`);
2557
+ }
2558
+ else if (step.order !== null && step.dependsOn > step.order) {
2559
+ err('C93', `test_sequence ${seq.id}: ${at} depends_on ${step.dependsOn} names a LATER step — a forward reference; depends_on names an EARLIER step (test-sequence-integrity)`);
2560
+ }
2561
+ }
2562
+ }
2563
+ }
2564
+ // ── C94: the per-test evaluation-formula traces (smart gap-close E11,
2565
+ // analysis/architecture-gaps-2026-07.md; the smart contract
2566
+ // data/schemas/formulas-used.yaml) ──
2567
+ // The `formulas_used` construct is the first-class replacement for the
2568
+ // hand-authored supplemental formulas-used.yaml. C94
2569
+ // formulas-used-shape: every trace carries a non-empty test reference
2570
+ // (the block symbol — reachable empty only through a quoted-empty id
2571
+ // spelling), name, description, and a non-empty formulas list; every
2572
+ // formula identifier is well-formed — the snake_case shape the
2573
+ // calculations registry uses for output names (FORMULA_ID_SHAPE).
2574
+ // Entry UNIQUENESS per test is the parse-time duplicate-id rule's
2575
+ // (surfaced as C96) — the collection key IS the test reference, so a
2576
+ // second trace for the same test is the same-id declaration every
2577
+ // id-keyed collection judges. Formula-id RESOLUTION (does e_l exist in
2578
+ // the calculations ∪ formulas registries) is the smart-side linker rule
2579
+ // R41's crosswalk
2580
+ // — the kernel checks syntax/shape only, exactly like E9's C90/C91 vs
2581
+ // R38 and E10's C92/C93 vs R39 split. Packages without formulas-used
2582
+ // traces are untouched — the loop is empty.
2583
+ {
2584
+ for (const fu of standard.formulasUsed ?? []) {
2585
+ if ((0, tokenize_1.stripWrapping)(fu.id) === '') {
2586
+ err('C94', `formulas_used ${fu.id}: declares no test reference — the block symbol is the conformance-test reference the trace is keyed by (formulas-used-shape)`);
2587
+ }
2588
+ if (fu.name === '') {
2589
+ err('C94', `formulas_used ${fu.id}: declares no name — a formulas-used trace carries a short name (formulas-used-shape)`);
2590
+ }
2591
+ if (fu.description === '') {
2592
+ err('C94', `formulas_used ${fu.id}: declares no description — a formulas-used trace carries what the listed formulas compute, with the clause evidence (the default spelling inline; alternates ride text ${fu.id}.description blocks) (formulas-used-shape)`);
2593
+ }
2594
+ if (fu.formulas.length === 0) {
2595
+ err('C94', `formulas_used ${fu.id}: declares no formulas — a formulas-used trace is a non-empty registry-formula id list (formulas-used-shape)`);
2596
+ }
2597
+ for (const f of fu.formulas) {
2598
+ if (!FormulasUsed_1.FORMULA_ID_SHAPE.test(f)) {
2599
+ err('C94', `formulas_used ${fu.id}: formula id "${f}" is not a well-formed registry identifier — the snake_case shape the calculations registry uses (^[a-z][a-z0-9_]*$; resolution is the smart-side linker R41's, never the kernel's) (formulas-used-shape)`);
2600
+ }
2601
+ }
2602
+ }
2603
+ }
2604
+ // ── C32–C36: quantities, time, and the IS↔HAS duality ──
2605
+ // (TODO.roadmap/06; doctrine ch. 6). Coherence is judged on quantity
2606
+ // KINDS — resolved through the package's quantity_registers — never on
2607
+ // unit strings.
2608
+ const kindDecl = new Map(); // kind id → register id
2609
+ for (const reg of standard.quantityRegisters ?? []) {
2610
+ for (const k of reg.kinds) {
2611
+ if (!kindDecl.has(k.id)) {
2612
+ kindDecl.set(k.id, reg.id);
2613
+ }
2614
+ }
2615
+ }
2616
+ const unitToKind = new Map(); // unit id|symbol → kind id
2617
+ const unitHome = new Map(); // unit id|symbol → register id
2618
+ const registersExist = (standard.quantityRegisters ?? []).length > 0;
2619
+ for (const reg of standard.quantityRegisters ?? []) {
2620
+ for (const u of reg.units) {
2621
+ // C33 — the unit's kind must resolve across the package's registers.
2622
+ if (!kindDecl.has(u.kind)) {
2623
+ err('C33', `quantity_register ${reg.id}: unit "${u.id}" declares kind "${u.kind}", which no quantity_register declares (quantity-coherence)`);
2624
+ }
2625
+ // C33 — no cross-register redefinition: a rec EXTENDS the register
2626
+ // with domain units; it never redefines an existing entry.
2627
+ for (const key of new Set([u.id, u.symbol].filter(s => s))) {
2628
+ const prior = unitHome.get(key);
2629
+ if (prior !== undefined) {
2630
+ err('C33', `quantity_register ${reg.id}: unit "${u.id}" ("${key}") redefines a unit already declared by register "${prior}" — a package extends the register, it never redefines entries (quantity-coherence)`);
2631
+ }
2632
+ else {
2633
+ unitHome.set(key, reg.id);
2634
+ unitToKind.set(key, u.kind);
2635
+ }
2636
+ }
2637
+ }
2638
+ }
2639
+ const kindOfUnit = (unit) => unit === undefined ? undefined : unitToKind.get(unit.trim());
2640
+ // C32 — INV-1: no bare numbers. A value stated for a declared physical
2641
+ // quantity (the attribute definition carries a unit, declares a
2642
+ // quantity_kind, or is typed QuantityValue) must itself carry a unit.
2643
+ // An empty-string unit token counts as bare, not as unit'd.
2644
+ const attrDefs = standard.attributeDefinitions ?? [];
2645
+ const isPhysicalAttr = (def) => def.unit !== '' ||
2646
+ def.quantityKind !== '' ||
2647
+ def.valueType === 'QuantityValue';
2648
+ const physicalFacet = (def) => def.unit !== ''
2649
+ ? `unit "${def.unit}"`
2650
+ : def.valueType === 'QuantityValue'
2651
+ ? 'value_type QuantityValue'
2652
+ : `quantity_kind ${def.quantityKind}`;
2653
+ for (const inst of standard.instances ?? []) {
2654
+ const planes = [
2655
+ 'attributes',
2656
+ 'testContext',
2657
+ ];
2658
+ for (const plane of planes) {
2659
+ for (const [key, v] of Object.entries(inst.has?.[plane] ?? {})) {
2660
+ const def = attrDefs.find(a => a.id === key);
2661
+ if (!def) {
2662
+ continue; // C17 reports the undeclared attribute
2663
+ }
2664
+ if (isPhysicalAttr(def) && (v.unit ?? '') === '') {
2665
+ err('C32', `instance ${inst.id}: attribute "${key}" is a declared physical quantity (` +
2666
+ physicalFacet(def) +
2667
+ `) but the stated value is a bare number — INV-1: no bare numbers (inv1-no-bare-quantity)`);
2668
+ }
2669
+ // C33 (warning leg) — a stated unit no register declares is
2670
+ // unmapped (doctrine §6.8: unmapped units are warnings). An empty
2671
+ // unit token is bare (C32 above), not unmapped.
2672
+ if (v.unit !== undefined &&
2673
+ v.unit !== '' &&
2674
+ registersExist &&
2675
+ kindOfUnit(v.unit) === undefined) {
2676
+ warn('C33', `instance ${inst.id}: attribute "${key}" carries unit "${v.unit}", which no quantity_register declares (quantity-coherence)`);
2677
+ }
2678
+ // C33 — stated kind vs the definition's declared kind: an explicit
2679
+ // `kind` on the value, or the stated unit's kind, must agree with
2680
+ // the attribute definition's quantity_kind / declared unit's kind.
2681
+ const statedKind = v.quantityKind ?? kindOfUnit(v.unit);
2682
+ if (def.quantityKind && statedKind && statedKind !== def.quantityKind) {
2683
+ err('C33', `instance ${inst.id}: attribute "${key}" (kind ${def.quantityKind}) stated with ` +
2684
+ (v.quantityKind
2685
+ ? `kind "${v.quantityKind}"`
2686
+ : `unit "${v.unit}" (kind ${statedKind})`) +
2687
+ ` — quantity kinds differ (quantity-coherence)`);
2688
+ }
2689
+ else if (!def.quantityKind && def.unit && v.unit) {
2690
+ const defKind = kindOfUnit(def.unit);
2691
+ const vKind = kindOfUnit(v.unit);
2692
+ if (defKind && vKind && defKind !== vKind) {
2693
+ err('C33', `instance ${inst.id}: attribute "${key}" declared with unit "${def.unit}" (kind ${defKind}) stated with unit "${v.unit}" (kind ${vKind}) — quantity kinds differ (quantity-coherence)`);
2694
+ }
2695
+ }
2696
+ // C35 — time-typed attributes carry ISO 8601 time values.
2697
+ if (['date', 'datetime', 'duration', 'period'].includes(def.valueType) &&
2698
+ !(0, time_1.isValidTimeValue)(def.valueType, String(v.value))) {
2699
+ err('C35', `instance ${inst.id}: attribute "${key}" (value_type ${def.valueType}) value "${String(v.value)}" is not a valid ISO 8601 ${def.valueType} (time-format)`);
2700
+ }
2701
+ }
2702
+ }
2703
+ }
2704
+ // C32/C33 — condition-set entries. C32: a NUMERIC entry value (with or
2705
+ // without tolerance) stated without a unit is a bare physical quantity —
2706
+ // INV-1 (free-text values like "local ambient" stay legal). C33: the
2707
+ // entry's unit must measure the entry's declared quantity kind.
2708
+ for (const cs of standard.conditionSets ?? []) {
2709
+ for (const e of cs.entries ?? []) {
2710
+ if (!e.unit) {
2711
+ if (NUMERIC_TOKEN.test(e.value)) {
2712
+ err('C32', `condition_set ${cs.id}: entry "${e.quantityKind}" is a bare physical value — a numeric condition value carries a unit — INV-1: no bare numbers (inv1-no-bare-quantity)`);
2713
+ }
2714
+ continue;
2715
+ }
2716
+ const eKind = kindOfUnit(e.unit);
2717
+ if (eKind === undefined) {
2718
+ if (registersExist) {
2719
+ warn('C33', `condition_set ${cs.id}: entry "${e.quantityKind}" carries unit "${e.unit}", which no quantity_register declares (quantity-coherence)`);
2720
+ }
2721
+ }
2722
+ else if (kindDecl.has(e.quantityKind) && eKind !== e.quantityKind) {
2723
+ err('C33', `condition_set ${cs.id}: entry "${e.quantityKind}" carries unit "${e.unit}" (kind ${eKind}) — the unit does not measure the entry's quantity kind (quantity-coherence)`);
2724
+ }
2725
+ }
2726
+ }
2727
+ // C33 — symbols and verdict quantities: a declared quantity_kind must
2728
+ // agree with the declared unit's kind.
2729
+ const unitables = [
2730
+ ...(standard.symbols ?? []).map(s => ({
2731
+ kind: 'symbol',
2732
+ id: s.id,
2733
+ unit: s.unit,
2734
+ qk: s.quantityKind,
2735
+ })),
2736
+ ...(standard.verdicts ?? []).map(v => ({
2737
+ kind: 'verdict',
2738
+ id: v.id,
2739
+ unit: v.unit,
2740
+ qk: v.quantityKind,
2741
+ })),
2742
+ ];
2743
+ for (const u of unitables) {
2744
+ if (!u.unit || !u.qk) {
2745
+ continue;
2746
+ }
2747
+ const uKind = kindOfUnit(u.unit);
2748
+ if (uKind && kindDecl.has(u.qk) && uKind !== u.qk) {
2749
+ err('C33', `${u.kind} ${u.id}: declared quantity_kind "${u.qk}" but unit "${u.unit}" measures kind ${uKind} — quantity kinds differ (quantity-coherence)`);
2750
+ }
2751
+ }
2752
+ // C34 — duality-coherence: one value structure, two roles.
2753
+ for (const d of standard.duals ?? []) {
2754
+ if (d.attribute && !attrIds.has(d.attribute)) {
2755
+ err('C34', `dual ${d.id}: attribute "${d.attribute}" is not a declared attribute_definition (duality-coherence)`);
2756
+ }
2757
+ if (!d.designed && !d.exhibited) {
2758
+ err('C34', `dual ${d.id}: neither designed nor exhibited stated — a dual pair carries at least one role (duality-coherence)`);
2759
+ continue;
2760
+ }
2761
+ // Tolerance marks the designed side, uncertainty the measured side —
2762
+ // the two never merge (doctrine §6.3).
2763
+ if (d.designed?.uncertainty !== undefined) {
2764
+ warn('C34', `dual ${d.id}: the designed role carries an uncertainty — uncertainty belongs to the measured (exhibited) side (duality-coherence)`);
2765
+ }
2766
+ if (d.exhibited?.tolerance !== undefined) {
2767
+ warn('C34', `dual ${d.id}: the exhibited role carries a tolerance — tolerance belongs to the specified (designed) side (duality-coherence)`);
2768
+ }
2769
+ // INV-1: a role stated for a declared physical quantity carries a unit
2770
+ // (or an explicit quantity kind) — never a bare number. An empty-string
2771
+ // unit token counts as bare, not as unit'd.
2772
+ const dualAttr = d.attribute
2773
+ ? attrDefs.find(a => a.id === d.attribute)
2774
+ : undefined;
2775
+ if (dualAttr && isPhysicalAttr(dualAttr)) {
2776
+ for (const [role, v] of [
2777
+ ['designed', d.designed],
2778
+ ['exhibited', d.exhibited],
2779
+ ]) {
2780
+ if (v && (v.unit ?? '') === '' && (v.quantityKind ?? '') === '') {
2781
+ err('C34', `dual ${d.id}: role "${role}" of attribute "${d.attribute}" is a bare physical value — INV-1 (duality-coherence)`);
2782
+ }
2783
+ }
2784
+ }
2785
+ if (d.designed && d.exhibited) {
2786
+ const roleKind = (v) => v.quantityKind ?? kindOfUnit(v.unit);
2787
+ const dk = roleKind(d.designed);
2788
+ const ek = roleKind(d.exhibited);
2789
+ for (const [role, v] of [
2790
+ ['designed', d.designed],
2791
+ ['exhibited', d.exhibited],
2792
+ ]) {
2793
+ if (v.unit !== undefined &&
2794
+ v.unit !== '' &&
2795
+ registersExist &&
2796
+ kindOfUnit(v.unit) === undefined) {
2797
+ warn('C34', `dual ${d.id}: the ${role} role carries unit "${v.unit}", which no quantity_register declares (duality-coherence)`);
2798
+ }
2799
+ }
2800
+ if (dk && ek && dk !== ek) {
2801
+ err('C34', `dual ${d.id}: designed (kind ${dk}) vs exhibited (kind ${ek}) — the two roles of one quantity must share a quantity kind (duality-coherence)`);
2802
+ }
2803
+ }
2804
+ }
2805
+ // C35 — timer-event recurrence periods are ISO 8601 durations (closes
2806
+ // task 02's deferred format check; C15 checks presence only).
2807
+ for (const p of standard.processes ?? []) {
2808
+ for (const s of p.does?.steps ?? []) {
2809
+ if (s.kind === 'timer_event' && s.period && !(0, time_1.isDuration)(s.period)) {
2810
+ err('C35', `process ${p.id}: timer_event "${s.id}" period "${s.period}" is not an ISO 8601 duration (time-format)`);
2811
+ }
2812
+ }
2813
+ }
2814
+ // C36 — map<K, V> field/value types are well-formed (K = string or an
2815
+ // enum id; V a valid type expression).
2816
+ for (const c of standard.dataclasses ?? []) {
2817
+ for (const a of c.attributes ?? []) {
2818
+ if (/^map\s*</.test(a.type) && !(0, type_expr_1.isWellFormedMapType)(a.type)) {
2819
+ err('C36', `class ${c.id}: attribute "${a.id}" type "${a.type}" is not a well-formed map<K, V> type — K is string or an enum id, V a type expression (map-type)`);
2820
+ }
2821
+ }
2822
+ }
2823
+ for (const a of standard.attributeDefinitions ?? []) {
2824
+ if (/^map\s*</.test(a.valueType) && !(0, type_expr_1.isWellFormedMapType)(a.valueType)) {
2825
+ err('C36', `attribute_definition ${a.id}: value_type "${a.valueType}" is not a well-formed map<K, V> type — K is string or an enum id, V a type expression (map-type)`);
2826
+ }
2827
+ }
2828
+ const resolveParamType = (type) => {
2829
+ const t = type.trim();
2830
+ if (t === '') {
2831
+ return null;
2832
+ }
2833
+ if (kindDecl.has(t)) {
2834
+ return { kind: t };
2835
+ }
2836
+ const k = kindOfUnit(t);
2837
+ if (k !== undefined) {
2838
+ return { kind: k };
2839
+ }
2840
+ return { token: t };
2841
+ };
2842
+ const typesCompatible = (a, b) => {
2843
+ if (a === null || b === null) {
2844
+ return true;
2845
+ }
2846
+ if ('kind' in a && 'kind' in b) {
2847
+ return a.kind === b.kind;
2848
+ }
2849
+ if ('token' in a && 'token' in b) {
2850
+ return a.token === b.token;
2851
+ }
2852
+ return false;
2853
+ };
2854
+ const processById = new Map((standard.processes ?? []).map(q => [q.id, q]));
2855
+ for (const p of standard.processes ?? []) {
2856
+ // The declared type sites of one name: signature in/out + registers.
2857
+ // A step's read/write names carry no type of their own — the boundary
2858
+ // type of a handoff is whatever the declarations say, so a name with
2859
+ // TWO conflicting declarations is the kind-incompatible boundary.
2860
+ const sites = new Map();
2861
+ const noteSites = (pos) => (prm) => {
2862
+ sites.set(prm.name, [
2863
+ ...(sites.get(prm.name) ?? []),
2864
+ { pos, type: prm.type },
2865
+ ]);
2866
+ };
2867
+ (p.signature?.inputs ?? []).forEach(noteSites('signature in'));
2868
+ (p.signature?.outputs ?? []).forEach(noteSites('signature out'));
2869
+ (p.registers ?? []).forEach(noteSites('registers'));
2870
+ // The first declared site answers the caller-side type of a name
2871
+ // (C74 owns the conflict when sites disagree).
2872
+ const declaredTypeOf = (name) => sites.get(name)?.[0]?.type;
2873
+ // C74 — one name, ONE type (process-io-type-coherence). Runs on the
2874
+ // abstract form too: the signature/registers coherence is a
2875
+ // declaration-level fact, independent of the `does` body.
2876
+ for (const [name, decls] of sites) {
2877
+ let conflict = null;
2878
+ for (let x = 0; x < decls.length && !conflict; x++) {
2879
+ for (let y = x + 1; y < decls.length && !conflict; y++) {
2880
+ if (!typesCompatible(resolveParamType(decls[x].type), resolveParamType(decls[y].type))) {
2881
+ conflict = {
2882
+ a: `${decls[x].pos} "${decls[x].type}"`,
2883
+ b: `${decls[y].pos} "${decls[y].type}"`,
2884
+ };
2885
+ }
2886
+ }
2887
+ }
2888
+ if (conflict) {
2889
+ err('C74', `process ${p.id}: "${name}" declares incompatible types — ${conflict.a} vs ${conflict.b} — one name, one type at a transition boundary (process-io-type-coherence)`);
2890
+ }
2891
+ }
2892
+ const flow = p.does;
2893
+ if (!flow) {
2894
+ continue;
2895
+ }
2896
+ const steps = flow.steps ?? [];
2897
+ const edges = flow.edges ?? [];
2898
+ const stepById = new Map(steps.map(s => [s.id, s]));
2899
+ // Effective step I/O: the declared reads/writes plus the wait target
2900
+ // (a read) and the caller-side names of a call's `with` bindings.
2901
+ const readsOf = (s) => new Set([
2902
+ ...s.reads,
2903
+ ...(s.wait ? [s.wait] : []),
2904
+ ...s.callIn.map(b => b.bind),
2905
+ ]);
2906
+ const writesOf = (s) => new Set([...s.writes, ...s.callOut.map(b => b.bind)]);
2907
+ // The C12 declared-name vocabulary (registers + signature + instance
2908
+ // keys + the process's own state).
2909
+ const declaredNames = new Set(['state', ...sites.keys()]);
2910
+ for (const v of Object.values(p.instances?.values ?? {})) {
2911
+ for (const k of Object.keys(v)) {
2912
+ declaredNames.add(k);
2913
+ }
2914
+ }
2915
+ // Names the process is PROVIDED with at entry: the IN parameters, the
2916
+ // per-classification instance values, and the operational state —
2917
+ // ambient HAS, available at every step without a writer in the flow.
2918
+ const provided = new Set(['state']);
2919
+ for (const prm of p.signature?.inputs ?? []) {
2920
+ provided.add(prm.name);
2921
+ }
2922
+ for (const v of Object.values(p.instances?.values ?? {})) {
2923
+ for (const k of Object.keys(v)) {
2924
+ provided.add(k);
2925
+ }
2926
+ }
2927
+ // Edge conditions read self.<name> AFTER the source step completes.
2928
+ const condReads = new Map();
2929
+ for (const e of edges) {
2930
+ const re = /\bself\.([A-Za-z_][A-Za-z0-9_]*)/g;
2931
+ let m;
2932
+ while ((m = re.exec(e.condition)) !== null) {
2933
+ condReads.set(e.from, (condReads.get(e.from) ?? new Set()).add(m[1]));
2934
+ }
2935
+ }
2936
+ // C75 — the step-chain dataflow covers every read: a must-analysis
2937
+ // (definitely-written on EVERY incoming path) over the flow graph,
2938
+ // computed as a shrink-to-fixpoint from the top (all written names).
2939
+ // Known limitation: the intersection runs over ALL predecessors
2940
+ // uniformly, so a parallel_gateway fork's conjunctive branches are
2941
+ // treated as alternative paths — a value written on one parallel
2942
+ // branch and read after the join (guaranteed bound under the concept
2943
+ // doc §4.2 "unordered conjunction" semantics) is reported uncovered.
2944
+ // The direction is safe (over-reports, never misses a real gap).
2945
+ // Follow-up: distinguish same-fork conjunctive predecessors (union)
2946
+ // from choice predecessors (intersection).
2947
+ const allWritten = new Set();
2948
+ for (const s of steps) {
2949
+ writesOf(s).forEach(n => allWritten.add(n));
2950
+ }
2951
+ const preds = new Map();
2952
+ for (const e of edges) {
2953
+ if (stepById.has(e.from) && stepById.has(e.to)) {
2954
+ preds.set(e.to, [...(preds.get(e.to) ?? []), e.from]);
2955
+ }
2956
+ }
2957
+ const availIn = new Map();
2958
+ for (const s of steps) {
2959
+ availIn.set(s.id, new Set(allWritten));
2960
+ }
2961
+ let changed = true;
2962
+ while (changed) {
2963
+ changed = false;
2964
+ for (const s of steps) {
2965
+ const ps = preds.get(s.id) ?? [];
2966
+ let next = new Set();
2967
+ if (ps.length > 0) {
2968
+ next = new Set(allWritten);
2969
+ for (const pid of ps) {
2970
+ const pOut = new Set([
2971
+ ...(availIn.get(pid) ?? []),
2972
+ ...writesOf(stepById.get(pid)),
2973
+ ]);
2974
+ next = new Set([...next].filter(n => pOut.has(n)));
2975
+ }
2976
+ }
2977
+ const cur = availIn.get(s.id);
2978
+ if (next.size !== cur.size || [...next].some(n => !cur.has(n))) {
2979
+ availIn.set(s.id, next);
2980
+ changed = true;
2981
+ }
2982
+ }
2983
+ }
2984
+ const availOut = (s) => new Set([...(availIn.get(s.id) ?? []), ...writesOf(s)]);
2985
+ // C75 (error leg) — a read with no writer on every incoming path and
2986
+ // no provided home reads a never-bound value. Undeclared names are
2987
+ // C12's finding, not repeated here.
2988
+ for (const s of steps) {
2989
+ for (const n of readsOf(s)) {
2990
+ if (!declaredNames.has(n) || provided.has(n)) {
2991
+ continue;
2992
+ }
2993
+ if (!(availIn.get(s.id) ?? new Set()).has(n)) {
2994
+ err('C75', `process ${p.id}: step "${s.id}" reads "${n}", which is neither an IN parameter/instance value nor written on every incoming flow path (process-flow-io-cover)`);
2995
+ }
2996
+ }
2997
+ for (const n of condReads.get(s.id) ?? new Set()) {
2998
+ if (!declaredNames.has(n) || provided.has(n)) {
2999
+ continue;
3000
+ }
3001
+ if (!availOut(s).has(n)) {
3002
+ err('C75', `process ${p.id}: the edge condition at "${s.id}" reads "${n}", which is neither an IN parameter/instance value nor written on every incoming flow path (process-flow-io-cover)`);
3003
+ }
3004
+ }
3005
+ }
3006
+ // C75 (warning leg) — a write with no reader is a dead output: no
3007
+ // step reads it, no edge condition references it, and no OUT
3008
+ // parameter names it. A capture-step write lands in evidence through
3009
+ // the capture form (the form is its reader), so it is never dead.
3010
+ const allRead = new Set();
3011
+ for (const s of steps) {
3012
+ readsOf(s).forEach(n => allRead.add(n));
3013
+ }
3014
+ for (const names of condReads.values()) {
3015
+ names.forEach(n => allRead.add(n));
3016
+ }
3017
+ const outNames = new Set((p.signature?.outputs ?? []).map(o => o.name));
3018
+ for (const s of steps) {
3019
+ if (s.capture) {
3020
+ continue;
3021
+ }
3022
+ for (const n of writesOf(s)) {
3023
+ if (!declaredNames.has(n)) {
3024
+ continue;
3025
+ }
3026
+ if (!allRead.has(n) && !outNames.has(n)) {
3027
+ warn('C75', `process ${p.id}: step "${s.id}" writes "${n}", which no step reads and no OUT parameter names — a dead output (process-flow-io-cover)`);
3028
+ }
3029
+ }
3030
+ }
3031
+ // C76 — a `calls` step binds the callee's declared signature
3032
+ // completely and kind-compatibly (subprocess-signature-bound). The
3033
+ // callee lookup runs against the COMPOSED process set, so a call
3034
+ // across a `uses` boundary is checked the same way (post-merge).
3035
+ for (const s of steps) {
3036
+ if (!s.calls) {
3037
+ continue;
3038
+ }
3039
+ const callee = processById.get(s.calls);
3040
+ if (!callee) {
3041
+ err('C76', `process ${p.id}: step "${s.id}" calls "${s.calls}", which is not a declared process (subprocess-signature-bound)`);
3042
+ continue;
3043
+ }
3044
+ const sig = callee.signature;
3045
+ if (!sig) {
3046
+ if (s.callIn.length > 0 || s.callOut.length > 0) {
3047
+ err('C76', `process ${p.id}: step "${s.id}" carries a with {…} binding, but "${s.calls}" declares no signature — there is nothing to bind (subprocess-signature-bound)`);
3048
+ }
3049
+ continue;
3050
+ }
3051
+ const checkBindings = (direction, params, bindings) => {
3052
+ for (const prm of params) {
3053
+ const bs = bindings.filter(b => b.param === prm.name);
3054
+ if (bs.length === 0) {
3055
+ err('C76', `process ${p.id}: step "${s.id}" calls "${callee.id}": ${direction} parameter "${prm.name}" is not ${direction === 'IN' ? 'bound from a caller register' : 'mapped back to a caller register'} — the signature binds completely (subprocess-signature-bound)`);
3056
+ }
3057
+ else if (bs.length > 1) {
3058
+ err('C76', `process ${p.id}: step "${s.id}" calls "${callee.id}": ${direction} parameter "${prm.name}" is bound ${bs.length} times — exactly once (subprocess-signature-bound)`);
3059
+ }
3060
+ }
3061
+ for (const b of bindings) {
3062
+ const prm = params.find(q => q.name === b.param);
3063
+ if (!prm) {
3064
+ err('C76', `process ${p.id}: step "${s.id}" calls "${callee.id}": "${b.param}" is not a declared ${direction} parameter of the callee (subprocess-signature-bound)`);
3065
+ continue;
3066
+ }
3067
+ const callerType = declaredTypeOf(b.bind);
3068
+ if (callerType === undefined) {
3069
+ continue; // undeclared caller-side name — C12's finding
3070
+ }
3071
+ // IN: the caller register feeds the callee's input; OUT: the
3072
+ // callee's output lands in the caller register. Kind equality
3073
+ // both ways (no kind hierarchy — see the section header).
3074
+ if (!typesCompatible(resolveParamType(callerType), resolveParamType(prm.type))) {
3075
+ err('C76', `process ${p.id}: step "${s.id}" calls "${callee.id}": caller "${b.bind}" (${callerType || 'untyped'}) and ${direction} parameter "${b.param}" (${prm.type || 'untyped'}) are not kind-compatible (subprocess-signature-bound)`);
3076
+ }
3077
+ }
3078
+ };
3079
+ checkBindings('IN', sig.inputs, s.callIn);
3080
+ checkBindings('OUT', sig.outputs, s.callOut);
3081
+ }
3082
+ }
3083
+ // ── C37–C40: operational state machines (TODO.roadmap/07) ──
3084
+ // The subject's HAS state is a state_machine typed `kind operational`,
3085
+ // bound via has.state (C40), driven by step `fires` (C37), and gated on
3086
+ // by `self.state = #…` preconditions (C39). The operational and
3087
+ // lifecycle families are strictly disjoint (C38).
3088
+ const machineById = new Map((standard.stateMachines ?? []).map(sm => [sm.entityName, sm]));
3089
+ const operationalIds = new Set((standard.stateMachines ?? [])
3090
+ .filter(sm => sm.kind === 'operational')
3091
+ .map(sm => sm.entityName));
3092
+ const lifecycleIds = new Set((standard.stateMachines ?? [])
3093
+ .filter(sm => sm.kind !== 'operational')
3094
+ .map(sm => sm.entityName));
3095
+ // C38 — state-family-separation: the two families never reference each
3096
+ // other. A cascade targets an entity by name; naming the OTHER family's
3097
+ // machine is a cross-family reference, in either direction.
3098
+ for (const sm of standard.stateMachines ?? []) {
3099
+ const operational = sm.kind === 'operational';
3100
+ for (const t of sm.transitions ?? []) {
3101
+ for (const c of t.cascades ?? []) {
3102
+ if (!operational && operationalIds.has(c.targetEntity)) {
3103
+ err('C38', `state_machine ${sm.entityName} (lifecycle): transition ${t.from} -> ${t.to} cascades into operational machine "${c.targetEntity}" — the lifecycle family never targets the operational family (state-family-separation)`);
3104
+ }
3105
+ if (operational && lifecycleIds.has(c.targetEntity)) {
3106
+ err('C38', `state_machine ${sm.entityName} (operational): transition ${t.from} -> ${t.to} cascades into lifecycle machine "${c.targetEntity}" — the operational family never targets a workflow entity's state (state-family-separation)`);
3107
+ }
3108
+ }
3109
+ }
3110
+ }
3111
+ // ── C95: cascade-transition-resolve (smart gap-close E12,
3112
+ // analysis/cascade-machine-routing-design.md §4–§5) ──
3113
+ // A declared cascade that writes `status` on a machinated target must
3114
+ // ROUTE the write through a declared transition of the target's own
3115
+ // machine — the `via <transition-action>` facet — instead of
3116
+ // raw-writing past the machine (the INV-honesty leak E12 closes). The
3117
+ // machines and the entity classes are visible to the kernel in one
3118
+ // composed package (both live in oiml-smart-core — the design's §5
3119
+ // layer argument), so target resolution is intra-package. The eight
3120
+ // legs (§5, verbatim):
3121
+ // 1. via-present: a status-writing step (a mechanical `set`
3122
+ // containing `status`, or a semantic `submit`/`lock`) on a target
3123
+ // entity that has a declared machine declares `via` — self-steps
3124
+ // (target == the owning machine's entity) excepted;
3125
+ // 2. via-resolves: `via` names a transition of the TARGET machine;
3126
+ // 3. via-matches-status: that transition's `to` equals the written
3127
+ // status (`submit` ⇒ SUBMITTED, `lock` ⇒ LOCKED);
3128
+ // 4. via-unguarded: the named transition declares no `guard`;
3129
+ // 5. via-forbidden-elsewhere: no `via` on self / payload-only /
3130
+ // create / notify / record steps or on targets with no declared
3131
+ // machine;
3132
+ // 6. self-consistency: a self-step writing `status` writes the
3133
+ // owning transition's `to`;
3134
+ // 7. status-is-a-state: every written status is a declared state of
3135
+ // the target machine;
3136
+ // 8. fields-resolve: `where` field paths and `set`/`with` field
3137
+ // names resolve against the target entity's declared fields.
3138
+ // ROLLOUT (closed 2026-07-27): leg 1 WARNS → ERRS. During the rollout
3139
+ // window the shipped corpus carried the via-less cross-entity steps
3140
+ // the design enumerated (§3.2), so leg 1 warned (the C33 de-escalation
3141
+ // precedent). The smart declaration leg (gap E12) landed the 13 via
3142
+ // facets + the test_report_recalled edge and deleted the redundant
3143
+ // step, so leg 1 is now the catalogued error and the corpus pin
3144
+ // asserts zero C95 issues of any severity. Legs 2–8 were errors from
3145
+ // day one (leg 8 by the design's explicit decision — every field the
3146
+ // shipped steps write is declared, so a miss is a genuine typo).
3147
+ {
3148
+ // The semantic side-effect actions' documented status meaning (the
3149
+ // walker's vocabulary, state-walk.ts: submit ⇒ SUBMITTED, lock ⇒
3150
+ // LOCKED); notify/record are record-creates, never status writes.
3151
+ const SEMANTIC_STATUS = {
3152
+ submit: 'SUBMITTED',
3153
+ lock: 'LOCKED',
3154
+ };
3155
+ // Entity references (machine entityName, cascade targets) spell the
3156
+ // bare entity name; class ids may carry a namespace suffix
3157
+ // (`TestAssignment#data`). Index both spellings.
3158
+ const dataclassByEntity = new Map();
3159
+ for (const c of standard.dataclasses ?? []) {
3160
+ if (!dataclassByEntity.has(c.id)) {
3161
+ dataclassByEntity.set(c.id, c);
3162
+ }
3163
+ const bare = c.id.includes('#') ? c.id.slice(0, c.id.indexOf('#')) : c.id;
3164
+ if (!dataclassByEntity.has(bare)) {
3165
+ dataclassByEntity.set(bare, c);
3166
+ }
3167
+ }
3168
+ // The left-hand field path of every comparison clause of a where
3169
+ // conjunction (`a = … AND b != …`). Interpolations (${…}) sit on the
3170
+ // right-hand side by construction; a clause this extractor cannot
3171
+ // read stays silent here — the walker's fail-closed clause parsing
3172
+ // owns the runtime judgment.
3173
+ const whereFields = (where) => {
3174
+ const fields = [];
3175
+ for (const clause of where.split(/\s+[Aa][Nn][Dd]\s+/)) {
3176
+ const m = /^([A-Za-z_][A-Za-z0-9_.]*)\s*(?:=|!=|<=|>=|<|>)/.exec(clause.trim());
3177
+ if (m) {
3178
+ fields.push(m[1]);
3179
+ }
3180
+ }
3181
+ return fields;
3182
+ };
3183
+ for (const sm of standard.stateMachines ?? []) {
3184
+ for (const t of sm.transitions ?? []) {
3185
+ const transitionLabel = `${t.from} -> ${t.to}${t.actionName ? ` action ${t.actionName}` : ''}`;
3186
+ t.cascades.forEach((c, k) => {
3187
+ const stepLabel = `state_machine ${sm.entityName}: transition ${transitionLabel}: cascade ${k} (${c.targetEntity})`;
3188
+ const self = c.targetEntity === sm.entityName;
3189
+ const targetMachine = machineById.get(c.targetEntity);
3190
+ const targetClass = dataclassByEntity.get(c.targetEntity);
3191
+ const isCreate = c.create !== null;
3192
+ const semanticStatus = c.action !== null ? SEMANTIC_STATUS[c.action] : undefined;
3193
+ const setStatus = c.set.find(s => s.field === 'status');
3194
+ const writesStatus = !isCreate &&
3195
+ (semanticStatus !== undefined || setStatus !== undefined);
3196
+ const writtenStatus = semanticStatus ?? setStatus?.value ?? '';
3197
+ // Leg 1 — via-present (error severity — the rollout window
3198
+ // closed when the smart declaration leg landed: the corpus is
3199
+ // via-complete).
3200
+ if (writesStatus && targetMachine && !self && c.via === '') {
3201
+ err('C95', `${stepLabel} writes status '${writtenStatus}' on machinated entity '${c.targetEntity}' but declares no via — a status-writing cascade routes through a declared transition of the target's machine (cascade-transition-resolve)`);
3202
+ }
3203
+ // Leg 5 — via-forbidden-elsewhere; legs 2–4 — the routing
3204
+ // contract, judged only where a via is legal (one issue per
3205
+ // defect: a forbidden via reports under leg 5 only).
3206
+ if (c.via !== '') {
3207
+ if (self ||
3208
+ isCreate ||
3209
+ !writesStatus ||
3210
+ !targetMachine ||
3211
+ c.action === 'notify' ||
3212
+ c.action === 'record') {
3213
+ const forbidden = self
3214
+ ? "a self-step (the target is the owning machine's own entity)"
3215
+ : isCreate
3216
+ ? 'a create step (a fresh record has no current-state hop to route)'
3217
+ : c.action === 'notify' || c.action === 'record'
3218
+ ? `an action ${c.action} step (a record-create, never a status hop)`
3219
+ : !writesStatus
3220
+ ? 'a step that writes no status (payload-only)'
3221
+ : `a step on entity '${c.targetEntity}', which declares no state machine`;
3222
+ err('C95', `${stepLabel} declares via '${c.via}' on ${forbidden} — via routes a status write through the target's machine and is forbidden everywhere else (cascade-transition-resolve)`);
3223
+ }
3224
+ else {
3225
+ // Leg 2 — via-resolves. An action name is not unique
3226
+ // across a machine (the design's §4.1 evidence), so the
3227
+ // via names a CANDIDATE SET; legs 3–4 qualify it.
3228
+ const candidates = targetMachine.transitions.filter(tr => tr.actionName === c.via);
3229
+ if (candidates.length === 0) {
3230
+ err('C95', `${stepLabel} via '${c.via}' resolves to no transition of machine '${c.targetEntity}' (cascade-transition-resolve)`);
3231
+ }
3232
+ else {
3233
+ // Leg 3 — via-matches-status: the route must land on
3234
+ // the written status.
3235
+ const landing = candidates.filter(tr => tr.to === writtenStatus);
3236
+ if (landing.length === 0) {
3237
+ err('C95', `${stepLabel} via '${c.via}' names no transition of machine '${c.targetEntity}' whose to is '${writtenStatus}' — the route must land on the written status (cascade-transition-resolve)`);
3238
+ }
3239
+ else if (landing.some(tr => tr.guard !== '')) {
3240
+ // Leg 4 — via-unguarded: a cascade has no caller to
3241
+ // supply computed guard values.
3242
+ err('C95', `${stepLabel} via '${c.via}' names a guarded transition of machine '${c.targetEntity}' — a cascade has no caller to supply guard values; the route must be unguarded (cascade-transition-resolve)`);
3243
+ }
3244
+ }
3245
+ }
3246
+ }
3247
+ // Leg 6 — self-consistency: the self-step's status write
3248
+ // restates the owning transition's `to` (the walker already
3249
+ // wrote it; the set block exists to carry the payload).
3250
+ if (self && writesStatus && writtenStatus !== t.to) {
3251
+ err('C95', `${stepLabel} writes status '${writtenStatus}' on its own entity, but the owning transition's to is '${t.to}' — a self-step's status write restates the owning transition (cascade-transition-resolve)`);
3252
+ }
3253
+ // Leg 7 — status-is-a-state (the typo net: "SUMBITTED" never
3254
+ // reaches runtime).
3255
+ if (writesStatus && targetMachine && writtenStatus !== '') {
3256
+ const stateNames = new Set(targetMachine.states.map(s => s.name));
3257
+ if (!stateNames.has(writtenStatus)) {
3258
+ err('C95', `${stepLabel} writes status '${writtenStatus}', which machine '${c.targetEntity}' does not declare as a state (cascade-transition-resolve)`);
3259
+ }
3260
+ }
3261
+ // Leg 8 — fields-resolve (the design's explicit error leg):
3262
+ // where paths and set/with field names against the target
3263
+ // entity's declared fields. A dotted path resolves its head
3264
+ // (the reference field); the deeper segments ride the
3265
+ // referenced entity and are the runtime's judgment.
3266
+ if (targetClass) {
3267
+ const fieldIds = new Set(targetClass.attributes.map(a => a.id));
3268
+ for (const path of whereFields(c.where)) {
3269
+ const head = path.includes('.')
3270
+ ? path.slice(0, path.indexOf('.'))
3271
+ : path;
3272
+ if (!fieldIds.has(head)) {
3273
+ err('C95', `${stepLabel} where path '${path}' resolves to no declared field of entity '${c.targetEntity}' (cascade-transition-resolve)`);
3274
+ }
3275
+ }
3276
+ for (const s of c.set) {
3277
+ if (!fieldIds.has(s.field)) {
3278
+ err('C95', `${stepLabel} set field '${s.field}' resolves to no declared field of entity '${c.targetEntity}' (cascade-transition-resolve)`);
3279
+ }
3280
+ }
3281
+ for (const key of Object.keys(c.with)) {
3282
+ if (!fieldIds.has(key)) {
3283
+ err('C95', `${stepLabel} with parameter '${key}' resolves to no declared field of entity '${c.targetEntity}' (cascade-transition-resolve)`);
3284
+ }
3285
+ }
3286
+ }
3287
+ });
3288
+ }
3289
+ }
3290
+ }
3291
+ // C37 — state-fires-resolve: a step's `fires` names a transition action
3292
+ // of the process's bound machine (process `state <machineRef>`).
3293
+ // C39 — state-machine-states-referenced: a `self.state = #…` gate in a
3294
+ // precondition names a declared state of the bound machine. Both rules
3295
+ // need the binding, so a missing/dangling binding is reported once per
3296
+ // rule that actually uses it.
3297
+ for (const p of standard.processes ?? []) {
3298
+ const firedSteps = (p.does?.steps ?? []).filter(s => s.fires);
3299
+ const gates = [];
3300
+ for (const pc of p.preconditions ?? []) {
3301
+ for (const g of (0, operational_state_1.extractStateGates)(pc.check)) {
3302
+ gates.push({ preconditionId: pc.id, state: g.state });
3303
+ }
3304
+ }
3305
+ const bound = p.state ? machineById.get(p.state) : undefined;
3306
+ if (firedSteps.length > 0) {
3307
+ if (!p.state) {
3308
+ for (const s of firedSteps) {
3309
+ err('C37', `process ${p.id}: step "${s.id}" fires "${s.fires}" but the process binds no state machine — declare state <machineRef> (state-fires-resolve)`);
3310
+ }
3311
+ }
3312
+ else if (!bound) {
3313
+ err('C37', `process ${p.id}: state machine "${p.state}" is not declared — step fires cannot resolve (state-fires-resolve)`);
3314
+ }
3315
+ else {
3316
+ const actions = new Set(bound.transitions.map(t => t.actionName).filter(a => a));
3317
+ for (const s of firedSteps) {
3318
+ if (!actions.has(s.fires)) {
3319
+ err('C37', `process ${p.id}: step "${s.id}" fires "${s.fires}", which state machine "${p.state}" does not declare as a transition action (state-fires-resolve)`);
3320
+ }
3321
+ }
3322
+ }
3323
+ }
3324
+ if (gates.length > 0) {
3325
+ if (!p.state) {
3326
+ err('C39', `process ${p.id}: precondition "${gates[0].preconditionId}" gates on self.state but the process binds no state machine — declare state <machineRef> (state-machine-states-referenced)`);
3327
+ }
3328
+ else if (!bound) {
3329
+ err('C39', `process ${p.id}: state machine "${p.state}" is not declared — state gates cannot resolve (state-machine-states-referenced)`);
3330
+ }
3331
+ else {
3332
+ const states = new Set(bound.states.map(s => s.name));
3333
+ for (const g of gates) {
3334
+ if (!states.has(g.state)) {
3335
+ err('C39', `process ${p.id}: precondition "${g.preconditionId}" gates on #${g.state}, which is not a declared state of machine "${p.state}" (state-machine-states-referenced)`);
3336
+ }
3337
+ }
3338
+ }
3339
+ }
3340
+ }
3341
+ // C40 — anatomy-state-resolves: a subject's has.state names a declared
3342
+ // state machine. A DECLARED but lifecycle machine is a cross-family
3343
+ // reference and reports under C38 instead (family separation).
3344
+ for (const s of standard.subjects ?? []) {
3345
+ if (!s.has?.state) {
3346
+ continue;
3347
+ }
3348
+ const machine = machineById.get(s.has.state);
3349
+ if (!machine) {
3350
+ err('C40', `subject ${s.id}: has.state "${s.has.state}" is not a declared state machine — a subject's HAS state binds a declared operational machine (anatomy-state-resolves)`);
3351
+ }
3352
+ else if (machine.kind !== 'operational') {
3353
+ err('C38', `subject ${s.id}: has.state "${s.has.state}" is a lifecycle state machine — a subject's HAS state is its OPERATIONAL machine; workflow lifecycle state lives on entities (state-family-separation)`);
3354
+ }
3355
+ }
3356
+ // C41 — precondition-on-violation-known: on_violation parses as a free
3357
+ // string (ser-des/config/process.ts, conformanceTest.ts), and the only
3358
+ // known outcome is `invalid` — a violated run-validity precondition
3359
+ // VOIDS the run; it never fails the instrument (doctrine ch. 04 §4.5).
3360
+ // Any other value warns. A STATE GATE declared `on_violation fail` is
3361
+ // always a warning naming the doctrine: the warm-up pattern voids the
3362
+ // run, and recording `fail` would state a conformity verdict the
3363
+ // instrument never earned.
3364
+ const checkOnViolation = (ownerKind, ownerId, pc) => {
3365
+ if (pc.onViolation === 'invalid') {
3366
+ return;
3367
+ }
3368
+ if (pc.onViolation === 'fail' && (0, operational_state_1.extractStateGates)(pc.check).length > 0) {
3369
+ warn('C41', `${ownerKind} ${ownerId}: precondition "${pc.id}" declares on_violation fail — a violated run-validity precondition VOIDS the run (invalid); it never fails the instrument (doctrine ch. 04 §4.5) (precondition-on-violation-known)`);
3370
+ }
3371
+ else {
3372
+ warn('C41', `${ownerKind} ${ownerId}: precondition "${pc.id}" declares on_violation "${pc.onViolation}" — the only known outcome is "invalid" (precondition-on-violation-known)`);
3373
+ }
3374
+ };
3375
+ for (const p of standard.processes ?? []) {
3376
+ for (const pc of p.preconditions ?? []) {
3377
+ checkOnViolation('process', p.id, pc);
3378
+ }
3379
+ }
3380
+ for (const t of standard.conformanceTests ?? []) {
3381
+ for (const pc of t.preconditions ?? []) {
3382
+ checkOnViolation('conformance_test', t.id, pc);
3383
+ }
3384
+ }
3385
+ // ── C51–C54: the coverage audits (TODO.roadmap/17, concept doc ──
3386
+ // §11.5). The aspect↔requirement↔test↔form↔verdict closure: C5 holds
3387
+ // the requirement→test link; C51/C52 hold the test→form→verdict links
3388
+ // at audit strictness; C53/C54 anchor the requirement's inputs
3389
+ // (uses bound, lookup tables) at the normal level.
3390
+ // C53 — coverage-uses-bound: every requirement limit.uses input is
3391
+ // bound. Unprefixed entries resolve against the package's quantitative
3392
+ // vocabulary (attributes, symbols, requirements, classification
3393
+ // dimensions, behaviors — bare ids or full paths, compared on the
3394
+ // leaf); `observable:`/`formula:` prefixes resolve against the symbol
3395
+ // and calculation registries. `load` is the declared per-load-point
3396
+ // free variable of the MPE lookups (deep-audit-r60 F6 — indeterminate
3397
+ // at verdict time by design). table:/profile: prefixes are C54's.
3398
+ // (Moves C2's former warning leg here and hardens it to an error: a
3399
+ // use input that binds nothing computes nothing.)
3400
+ const calcNames = new Set();
3401
+ for (const c of standard.calculations ?? []) {
3402
+ if (c.id) {
3403
+ calcNames.add(c.id);
3404
+ }
3405
+ if (c.name) {
3406
+ calcNames.add(c.name);
3407
+ }
3408
+ }
3409
+ for (const r of standard.requirements ?? []) {
3410
+ for (const u of r.limit?.uses ?? []) {
3411
+ if (/^(table|profile):/.test(u)) {
3412
+ continue; // C54's lookup-table leg
3413
+ }
3414
+ if (u.startsWith('observable:')) {
3415
+ const id = u.slice('observable:'.length);
3416
+ if (!symbolIds.has(id)) {
3417
+ err('C53', `requirement ${r.id}: limit.uses "${u}" — observable "${id}" is not a declared symbol (coverage-uses-bound)`);
3418
+ }
3419
+ continue;
3420
+ }
3421
+ if (u.startsWith('formula:')) {
3422
+ const id = u.slice('formula:'.length);
3423
+ if (!calcNames.has(id)) {
3424
+ err('C53', `requirement ${r.id}: limit.uses "${u}" — formula "${id}" is not a declared calculation (coverage-uses-bound)`);
3425
+ }
3426
+ continue;
3427
+ }
3428
+ // `uses` may carry bare ids or full paths — compare the last segment.
3429
+ const leaf = u.split('.').pop() ?? u;
3430
+ if (!attrIds.has(leaf) &&
3431
+ !attrIds.has(u) &&
3432
+ !symbolIds.has(leaf) &&
3433
+ !symbolIds.has(u) &&
3434
+ !reqIds.has(u) &&
3435
+ !dimIds.has(leaf) &&
3436
+ !behaviorIds.has(leaf) &&
3437
+ u !== 'load') {
3438
+ err('C53', `requirement ${r.id}: limit.uses "${u}" binds nothing — it is not a declared attribute, symbol, requirement, dimension, or behavior (coverage-uses-bound)`);
3439
+ }
3440
+ }
3441
+ }
3442
+ // C54 — coverage-lookup-table-exists: lookup tables exist. The
3443
+ // table:/profile: uses prefixes and the calculations' lookup profiles
3444
+ // (lookupMPE/lookupProfile targets) resolve to declared tables and
3445
+ // their profile definitions — a dangling lookup breaks evaluation at
3446
+ // runtime. A profile reference resolves as a declared table id, as
3447
+ // `<table>.<name>` with <name> in the table's profileDefs, or as a
3448
+ // bare <name> in the `profiles` table's profileDefs.
3449
+ const tablesById = new Map((standard.tables ?? []).map(t => [t.id, t]));
3450
+ const profileRefResolves = (ref) => {
3451
+ if (tablesById.has(ref)) {
3452
+ return true;
3453
+ }
3454
+ const dot = ref.indexOf('.');
3455
+ if (dot > 0) {
3456
+ const tbl = tablesById.get(ref.slice(0, dot));
3457
+ const name = ref.slice(dot + 1);
3458
+ if (tbl && (tbl.profileDefs ?? []).some(p => p.name === name)) {
3459
+ return true;
3460
+ }
3461
+ }
3462
+ const profiles = tablesById.get('profiles');
3463
+ return (profiles !== undefined &&
3464
+ (profiles.profileDefs ?? []).some(p => p.name === ref));
3465
+ };
3466
+ for (const r of standard.requirements ?? []) {
3467
+ for (const u of r.limit?.uses ?? []) {
3468
+ if (u.startsWith('table:')) {
3469
+ const id = u.slice('table:'.length);
3470
+ if (!tablesById.has(id)) {
3471
+ err('C54', `requirement ${r.id}: limit.uses "${u}" — table "${id}" is not declared (coverage-lookup-table-exists)`);
3472
+ }
3473
+ }
3474
+ else if (u.startsWith('profile:')) {
3475
+ const ref = u.slice('profile:'.length);
3476
+ if (!profileRefResolves(ref)) {
3477
+ err('C54', `requirement ${r.id}: limit.uses "${u}" — profile "${ref}" is not a declared table or profile definition (coverage-lookup-table-exists)`);
3478
+ }
3479
+ }
3480
+ }
3481
+ }
3482
+ for (const c of standard.calculations ?? []) {
3483
+ if (c.profile && !profileRefResolves(c.profile)) {
3484
+ err('C54', `calculation ${c.name || c.id}: lookup profile "${c.profile}" is not a declared table or profile definition (coverage-lookup-table-exists)`);
3485
+ }
3486
+ }
3487
+ // C51 — coverage-test-evidence (AUDIT): a conformance test whose runs
3488
+ // leave no declared evidence — no form declares it via
3489
+ // conformance_process, it names no result_forms / report_rows, and no
3490
+ // form covers a requirement it targets. inherits_from chains inherit
3491
+ // the parent's evidence links (R 60's class-driven instances).
3492
+ if (strictness === 'audit') {
3493
+ const testById = new Map((standard.conformanceTests ?? []).map(t => [t.id, t]));
3494
+ const formDeclaredTests = new Set();
3495
+ for (const f of standard.forms ?? []) {
3496
+ for (const pid of f.conformanceProcessIds ??
3497
+ (f.conformanceProcessId ? [f.conformanceProcessId] : [])) {
3498
+ formDeclaredTests.add(pid);
3499
+ }
3500
+ }
3501
+ const formRequirementSets = (standard.forms ?? []).map(f => new Set(f.requirements ?? []));
3502
+ const hasEvidence = (t, seen) => {
3503
+ if (seen.has(t.id)) {
3504
+ return false;
3505
+ }
3506
+ seen.add(t.id);
3507
+ if (formDeclaredTests.has(t.id)) {
3508
+ return true;
3509
+ }
3510
+ if ((t.resultForms ?? []).length > 0 || (t.reportRows ?? []).length > 0) {
3511
+ return true;
3512
+ }
3513
+ if (formRequirementSets.some(reqs => (t.targets ?? []).some(target => reqs.has(target)))) {
3514
+ return true;
3515
+ }
3516
+ const parent = t.inheritsFrom ? testById.get(t.inheritsFrom) : undefined;
3517
+ return parent !== undefined && hasEvidence(parent, seen);
3518
+ };
3519
+ for (const t of standard.conformanceTests ?? []) {
3520
+ if (!hasEvidence(t, new Set())) {
3521
+ warn('C51', `conformance test ${t.id}: no form records its evidence — the run happens and nothing permanent records it (coverage-test-evidence)`);
3522
+ }
3523
+ }
3524
+ // C52 — coverage-form-judgment (AUDIT): a test-evidence form (one
3525
+ // binding a conformance process) carrying no judgment — no
3526
+ // pass_fail, no field evaluation/verdict, no requirements binding.
3527
+ // Documentary forms (report sections, headers) are not the closure's
3528
+ // "form" slot and stay silent.
3529
+ const formHasJudgment = (f) => {
3530
+ if (f.passFail) {
3531
+ return true;
3532
+ }
3533
+ const walk = (fields) => fields.some(x => x.evaluation || x.verdict || walk(x.fields ?? []));
3534
+ return walk(f.fields ?? []);
3535
+ };
3536
+ for (const f of standard.forms ?? []) {
3537
+ const bindsTest = !!f.conformanceProcessId || (f.conformanceProcessIds ?? []).length > 0;
3538
+ if (!bindsTest) {
3539
+ continue;
3540
+ }
3541
+ if (!formHasJudgment(f) && (f.requirements ?? []).length === 0) {
3542
+ warn('C52', `form ${f.id}: binds a conformance process but computes no judgment — evidence gathered and never weighed (coverage-form-judgment)`);
3543
+ }
3544
+ }
3545
+ }
3546
+ // ── C71–C73: the normative-text coverage metric (TODO.roadmap/26, ──
3547
+ // concept doc §11.6) — layer 5 of the validation stack. The sentence
3548
+ // decomposition ships inside the package (sources-prd/*.sentences.json
3549
+ // + coverage.json); without them the metric is silent. C73 (config
3550
+ // hygiene — malformed payloads, stale allowances/adjudications) runs
3551
+ // at every level; C71 (uncovered normative sentences) is audit-level
3552
+ // and budgeted by the package's text_coverage_budget (C72, applied
3553
+ // with the allowlist below). Duplicate pairs are REPORTED by
3554
+ // `primmel check --coverage`, never auto-failed here.
3555
+ const textCoverageData = (0, text_coverage_1.loadTextCoverageData)(dir);
3556
+ issues.push(...textCoverageData.issues);
3557
+ if (textCoverageData.manifests.length > 0) {
3558
+ const textCoverage = (0, text_coverage_1.computeTextCoverage)(standard, textCoverageData.manifests, textCoverageData.config);
3559
+ issues.push(...textCoverage.configIssues);
3560
+ if (strictness === 'audit') {
3561
+ for (const d of textCoverage.documents) {
3562
+ for (const s of d.uncoveredCounted) {
3563
+ warn('C71', (0, text_coverage_1.uncoveredSentenceMessage)(d.urn, s));
3564
+ }
3565
+ }
3566
+ }
3567
+ }
3568
+ // ── Finalize: the package allowlist + level severities ──
3569
+ // The allowlist (KNOWN/STALE/budget) applies to every level; --strict
3570
+ // then promotes the surviving warnings — except KNOWN issues and
3571
+ // budget-covered C51/C52 warnings (the budget is their allowance).
3572
+ const level = strictness === 'audit' ? 'audit' : 'normal';
3573
+ const { allowlist, issues: allowlistParseIssues } = (0, check_allowlist_1.loadAllowlist)(dir);
3574
+ issues.push(...allowlistParseIssues);
3575
+ const finalized = (0, check_allowlist_1.applyAllowlist)(issues, allowlist, (0, check_rules_1.activeRuleIds)(level)).issues;
3576
+ if (options.strict) {
3577
+ for (const i of finalized) {
3578
+ if (i.severity !== 'warning' || i.known) {
3579
+ continue;
3580
+ }
3581
+ if (check_allowlist_1.BUDGETED_RULES.has(i.check) && allowlist.coverageBudget !== null) {
3582
+ continue; // the budget governs these — C55 reports the excess
3583
+ }
3584
+ if (check_allowlist_1.TEXT_BUDGETED_RULES.has(i.check) &&
3585
+ allowlist.textCoverageBudget !== null) {
3586
+ continue; // the text budget governs these — C72 reports the excess
3587
+ }
3588
+ i.severity = 'error';
3589
+ }
3590
+ }
3591
+ return finalized;
3592
+ }
3593
+ // ─────────────────────────────────────────────────────────────────────
3594
+ // Manifest-only resolution stopgap (TODO.roadmap/05).
3595
+ //
3596
+ // The shipped package.primmel declarations (uses/requires/provides) are
3597
+ // enforced metadata ONLY at this level until task 13 moves content from
3598
+ // textual includes to real composition: composing the real packages
3599
+ // fails by design today (overlays redeclare layer content), so the
3600
+ // composition rules C27–C31 cannot run on them. This lint parses the
3601
+ // manifest of the package at `dir` plus the manifests of its SIBLING
3602
+ // package directories (a package id resolves when a sibling manifest
3603
+ // declares the same id) and verifies — WITHOUT composing content:
3604
+ // - every uses/extends entry resolves (reported under C27);
3605
+ // - the uses graph is acyclic (C29);
3606
+ // - every requires entry names a package in the uses closure or one of
3607
+ // its provides entries (C31).
3608
+ // All messages carry a "(manifest-only …)" marker so CI output cannot be
3609
+ // mistaken for a full composition verdict.
3610
+ // ─────────────────────────────────────────────────────────────────────
3611
+ function checkManifestResolution(dir) {
3612
+ const issues = [];
3613
+ const err = (check, message) => issues.push({ check, severity: 'error', message });
3614
+ const MARKER = '(manifest-only sibling resolution — no content composed)';
3615
+ let root;
3616
+ try {
3617
+ root = (0, package_1.readPackageManifest)(dir);
3618
+ }
3619
+ catch (e) {
3620
+ err('C27', `${e.message} ${MARKER}`);
3621
+ return issues;
3622
+ }
3623
+ // The locator: sibling directories of `dir` carrying a parseable
3624
+ // package.primmel, keyed by their declared id (last wins — the sorted
3625
+ // scan keeps the choice deterministic).
3626
+ const siblings = new Map();
3627
+ const parent = (0, path_1.dirname)((0, path_1.resolve)(dir));
3628
+ for (const entry of (0, fs_1.readdirSync)(parent).sort()) {
3629
+ const full = (0, path_1.join)(parent, entry);
3630
+ try {
3631
+ if (!(0, fs_1.statSync)(full).isDirectory()) {
3632
+ continue;
3633
+ }
3634
+ const m = (0, package_1.readPackageManifest)(full);
3635
+ siblings.set(m.id, m);
3636
+ }
3637
+ catch {
3638
+ // Not a package dir (no/invalid manifest) — not a resolution candidate.
3639
+ }
3640
+ }
3641
+ // Walk the uses closure over manifests only (cycle-safe), collecting
3642
+ // the packages the root transitively imports.
3643
+ const closure = new Map();
3644
+ const state = new Map(); // 1 = in stack, 2 = done
3645
+ const stack = [];
3646
+ const visit = (m) => {
3647
+ state.set(m.id, 1);
3648
+ stack.push(m.id);
3649
+ for (const dep of (0, package_1.effectiveUses)(m)) {
3650
+ const c = state.get(dep) ?? 0;
3651
+ if (c === 1) {
3652
+ err('C29', `uses cycle: ${[...stack.slice(stack.indexOf(dep)), dep].join(' → ')} — package composition must be acyclic (uses-cycle) ${MARKER}`);
3653
+ continue;
3654
+ }
3655
+ if (c === 2) {
3656
+ continue;
3657
+ }
3658
+ const dm = siblings.get(dep);
3659
+ if (!dm) {
3660
+ err('C27', `package "${m.id}" uses "${dep}", which no sibling package directory declares (uses-resolves) ${MARKER}`);
3661
+ continue;
3662
+ }
3663
+ closure.set(dep, dm);
3664
+ visit(dm);
3665
+ }
3666
+ stack.pop();
3667
+ state.set(m.id, 2);
3668
+ };
3669
+ visit(root);
3670
+ // Every requires entry (of the root and of every package in its
3671
+ // closure) names a composed package id or one of its provides entries
3672
+ // — satisfied by another package, never by the requiring one itself.
3673
+ for (const m of [root, ...closure.values()]) {
3674
+ for (const req of m.requires ?? []) {
3675
+ const satisfied = [...closure.values()].some(other => other.id !== m.id &&
3676
+ (other.id === req || (other.provides ?? []).includes(req)));
3677
+ if (!satisfied) {
3678
+ err('C31', `package "${m.id}" requires "${req}", which no used package provides (requires-satisfied) ${MARKER}`);
3679
+ }
3680
+ }
3681
+ }
3682
+ return issues;
3683
+ }
3684
+ /**
3685
+ * A list item's address key in a nested text path (C89): the declared
3686
+ * `name` (a form/subform field, a requirement parameter) or the declared
3687
+ * numeric position (a test-sequence step's `order`, a requirement
3688
+ * subject's `slot`) as its bare token. Keys are declared content, not
3689
+ * positional indexes — a reorder never silently re-points an address; a
3690
+ * rename breaks it loudly, which is the point (the smart-side hygiene
3691
+ * finding that motivated path addressing).
3692
+ */
3693
+ function textAddressKey(item, segment) {
3694
+ if (typeof item !== 'object' || item === null) {
3695
+ return false;
3696
+ }
3697
+ const rec = item;
3698
+ if (rec.name === segment) {
3699
+ return true;
3700
+ }
3701
+ if (typeof rec.order === 'number' && String(rec.order) === segment) {
3702
+ return true;
3703
+ }
3704
+ if (typeof rec.slot === 'number' && String(rec.slot) === segment) {
3705
+ return true;
3706
+ }
3707
+ return false;
3708
+ }
3709
+ /**
3710
+ * C89 — spelling-code-wellformed (TODO.roadmap/25; doctrine ch. 10 §10.7).
3711
+ *
3712
+ * Every human-readable string is spelling-coded per ISO 24229; BCP 47 is
3713
+ * not used. The kernel checks the SYNTAX layer (src/spelling.ts) —
3714
+ * register resolution (does this language/script/country/conversion
3715
+ * system exist) is the consumer's vendored-snapshot discipline:
3716
+ *
3717
+ * - the manifest's default_spelling and declared spellings parse as
3718
+ * spelling codes (language-script[-country][-extension]; the script
3719
+ * is MANDATORY — a bare language code is an error), and the default
3720
+ * belongs to the declared set;
3721
+ * - every `text` block addresses an existing element's prose field.
3722
+ * The address is `<element-id>.<field>` for a top-level field, or
3723
+ * `<element-id>.<path…>.<field>` for prose NESTED inside the element
3724
+ * (smart gap-close E13): the element is the longest dot-boundary
3725
+ * prefix registered in the package (element ids may carry dots), an
3726
+ * intermediate segment names a nested structure, a list item is
3727
+ * keyed by its declared key (name/order/slot — never a positional
3728
+ * index), and the terminal segment is a prose field. A dangling,
3729
+ * scalar, or ambiguous segment is an error. The terminal segment's
3730
+ * snake_case → camelCase fallback is DELIBERATE and terminal-only
3731
+ * (TODO.v2/13 item 3b, pinned — the walk is NOT extended): the
3732
+ * terminal draws from the FIXED prose-field vocabulary authored in
3733
+ * snake_case (the PROSE_FIELDS set — `true_label`, `scope_note`, …)
3734
+ * while the parsed structure holds the property camelCase
3735
+ * (`trueLabel`, …), so the vocabulary lookup translates exactly
3736
+ * there. Intermediate segments are not vocabulary lookups — they
3737
+ * name nested structures by the parsed model's own property names
3738
+ * or key list items by author-declared content (names/orders/slots)
3739
+ * — so a case translation there would bless a second spelling of
3740
+ * machine structure (and an ambiguity where `foo_bar` and `fooBar`
3741
+ * collide); no shipped nested structure name needs it (the corpus
3742
+ * legs);
3743
+ * - every spell entry's code parses; a duplicate code within one
3744
+ * content set is an error (use an extension to distinguish);
3745
+ * - an entry repeating the package's default_spelling is an error —
3746
+ * the default's value belongs inline on the element (`name "…"`);
3747
+ * - every `via` conversion system code parses
3748
+ * (titular:source:target:identifying); a `zz-` user-assigned code
3749
+ * validates with a warning (user-assigned codes are not portable).
3750
+ */
3751
+ function checkSpellingCodes(standard) {
3752
+ const issues = [];
3753
+ const err = (check, message) => issues.push({ check, severity: 'error', message });
3754
+ const warn = (check, message) => issues.push({ check, severity: 'warning', message });
3755
+ const m = standard.packageManifest;
3756
+ if (m?.defaultSpelling) {
3757
+ const parsed = (0, spelling_1.parseSpellingCode)(m.defaultSpelling);
3758
+ if (typeof parsed === 'string') {
3759
+ err('C89', `package "${m.id}": default_spelling "${m.defaultSpelling}" — ${parsed} (spelling-code-wellformed)`);
3760
+ }
3761
+ if ((m.spellings ?? []).length > 0 &&
3762
+ !m.spellings.includes(m.defaultSpelling)) {
3763
+ err('C89', `package "${m.id}": default_spelling "${m.defaultSpelling}" is not in the declared spellings set (spelling-code-wellformed)`);
3764
+ }
3765
+ }
3766
+ for (const s of m?.spellings ?? []) {
3767
+ const parsed = (0, spelling_1.parseSpellingCode)(s);
3768
+ if (typeof parsed === 'string') {
3769
+ err('C89', `package "${m.id}": declared spelling "${s}" — ${parsed} (spelling-code-wellformed)`);
3770
+ }
3771
+ }
3772
+ const texts = standard.texts ?? [];
3773
+ if (texts.length === 0) {
3774
+ return issues;
3775
+ }
3776
+ // The element registry a text block may address — every id-keyed
3777
+ // collection (prose lives on requirements, terms, forms, subjects…).
3778
+ // The walk needs the element OBJECTS, not just the ids: a nested
3779
+ // address (<element-id>.<path…>.<field>) resolves segment by segment
3780
+ // into the addressed element's structure.
3781
+ const elements = new Map();
3782
+ const ID_COLLECTIONS = [
3783
+ 'requirements',
3784
+ 'requirementClasses',
3785
+ 'conformanceTests',
3786
+ 'conformanceClasses',
3787
+ 'terms',
3788
+ 'forms',
3789
+ 'subforms',
3790
+ 'symbols',
3791
+ 'tables',
3792
+ 'calculations',
3793
+ 'notes',
3794
+ 'provisions',
3795
+ 'processes',
3796
+ 'instruments',
3797
+ 'attributeDefinitions',
3798
+ 'capabilities',
3799
+ 'behaviors',
3800
+ 'conditionSets',
3801
+ 'subjects',
3802
+ 'referenceMaterials',
3803
+ 'artifactDefinitions',
3804
+ 'monitors',
3805
+ 'passports',
3806
+ 'invariants',
3807
+ 'testSequences',
3808
+ 'formulasUsed',
3809
+ 'stateMachines',
3810
+ 'figures',
3811
+ 'links',
3812
+ ];
3813
+ for (const field of ID_COLLECTIONS) {
3814
+ for (const item of standard[field] ?? []) {
3815
+ if (item?.id) {
3816
+ elements.set(item.id, item);
3817
+ }
3818
+ }
3819
+ }
3820
+ // The prose-field vocabulary a text block may address (machine content
3821
+ // — identifiers, bind paths, OCL, snake_case keys — is never
3822
+ // spelling-coded, doctrine §10.7).
3823
+ const PROSE_FIELDS = new Set([
3824
+ 'name',
3825
+ 'title',
3826
+ 'label',
3827
+ 'term',
3828
+ 'definition',
3829
+ 'statement',
3830
+ 'description',
3831
+ 'guidance',
3832
+ 'note',
3833
+ 'notes',
3834
+ 'scope_note',
3835
+ 'subject',
3836
+ 'message',
3837
+ 'designation',
3838
+ 'placeholder',
3839
+ 'true_label',
3840
+ 'false_label',
3841
+ 'examples',
3842
+ ]);
3843
+ for (const t of texts) {
3844
+ const dot = t.id.lastIndexOf('.');
3845
+ if (dot <= 0 || dot === t.id.length - 1) {
3846
+ err('C89', `text "${t.id}": the address is <element-id>.<field> (a nested prose field addresses <element-id>.<path…>.<field>) (spelling-code-wellformed)`);
3847
+ }
3848
+ else {
3849
+ const segments = t.id.split('.');
3850
+ if (segments.some(s => s === '')) {
3851
+ err('C89', `text "${t.id}": empty path segment — the address is <element-id>.<path…>.<field> (spelling-code-wellformed)`);
3852
+ }
3853
+ else {
3854
+ // The addressed element is the LONGEST dot-boundary prefix of the
3855
+ // address registered in the package (element ids may themselves
3856
+ // carry dots — r144-3/sec-3.4); the rest is the path into the
3857
+ // element's structure. On a one-segment path this degenerates to
3858
+ // the <element-id>.<field> split.
3859
+ let elementId = '';
3860
+ let path = [];
3861
+ const tried = [];
3862
+ for (let i = segments.length - 1; i >= 1; i--) {
3863
+ const candidate = segments.slice(0, i).join('.');
3864
+ tried.push(candidate);
3865
+ if (elements.has(candidate)) {
3866
+ elementId = candidate;
3867
+ path = segments.slice(i);
3868
+ break;
3869
+ }
3870
+ }
3871
+ if (elementId === '') {
3872
+ // Every dot-boundary prefix was tried (longest first — element
3873
+ // ids may carry dots); the one-level "last-dot split" mental
3874
+ // model would name only the longest, so the message lists the
3875
+ // tried set (TODO.v2/13 item 3a).
3876
+ err('C89', `text "${t.id}": no element "${tried[0]}" in the package — every dot-boundary prefix was tried (${tried.map(c => `"${c}"`).join(', ')}); the addressed element is the LONGEST registered prefix (spelling-code-wellformed)`);
3877
+ }
3878
+ else {
3879
+ // Walk the intermediate segments: an object segment names a
3880
+ // nested structure (an own property whose value is an object or
3881
+ // list), a list segment keys ONE item by its declared key. The
3882
+ // terminal segment must be a prose field the addressed
3883
+ // structure CARRIES (own property — a prose slot the parser
3884
+ // fills only when authored is addressable exactly then; there
3885
+ // is nothing to alternate where no default value lives).
3886
+ const field = path[path.length - 1];
3887
+ let node = elements.get(elementId);
3888
+ let soFar = elementId;
3889
+ let resolved = true;
3890
+ for (let s = 0; s < path.length - 1 && resolved; s++) {
3891
+ const seg = path[s];
3892
+ if (Array.isArray(node)) {
3893
+ const matches = node.filter(item => textAddressKey(item, seg));
3894
+ if (matches.length !== 1) {
3895
+ err('C89', matches.length === 0
3896
+ ? `text "${t.id}": no item keyed "${seg}" under "${soFar}" — a list item addresses by its declared key (a field or parameter name, a step's order, a subject's slot) (spelling-code-wellformed)`
3897
+ : `text "${t.id}": "${seg}" keys ${matches.length} items under "${soFar}" — the address is ambiguous (spelling-code-wellformed)`);
3898
+ resolved = false;
3899
+ }
3900
+ else {
3901
+ node = matches[0];
3902
+ soFar += `.${seg}`;
3903
+ }
3904
+ }
3905
+ else if (!Object.prototype.hasOwnProperty.call(node, seg) ||
3906
+ typeof node[seg] !== 'object' ||
3907
+ node[seg] === null) {
3908
+ err('C89', `text "${t.id}": "${soFar}" has no nested structure "${seg}" (spelling-code-wellformed)`);
3909
+ resolved = false;
3910
+ }
3911
+ else {
3912
+ node = node[seg];
3913
+ soFar += `.${seg}`;
3914
+ }
3915
+ }
3916
+ if (resolved) {
3917
+ if (Array.isArray(node)) {
3918
+ err('C89', `text "${t.id}": "${soFar}" is a list — address one item's prose field by its key ("${soFar}.<key>.${field}") (spelling-code-wellformed)`);
3919
+ }
3920
+ else if (!PROSE_FIELDS.has(field)) {
3921
+ err('C89', `text "${t.id}": "${field}" is not a prose field (spelling-code-wellformed)`);
3922
+ }
3923
+ else if (
3924
+ // The snake_case → camelCase fallback is terminal-only by
3925
+ // design (the docstring's pin): the terminal segment is a
3926
+ // vocabulary lookup (snake_case prose vocabulary → the
3927
+ // parsed camelCase property), an intermediate segment is a
3928
+ // structure/key name taken verbatim.
3929
+ !Object.prototype.hasOwnProperty.call(node, field) &&
3930
+ !Object.prototype.hasOwnProperty.call(node, field.replace(/_([a-z])/g, (_, c) => c.toUpperCase()))) {
3931
+ err('C89', `text "${t.id}": "${soFar}" carries no prose field "${field}" — the default spelling's value belongs inline on the addressed structure (spelling-code-wellformed)`);
3932
+ }
3933
+ }
3934
+ }
3935
+ }
3936
+ }
3937
+ const seen = new Set();
3938
+ for (const e of t.entries) {
3939
+ const parsed = (0, spelling_1.parseSpellingCode)(e.spelling);
3940
+ if (typeof parsed === 'string') {
3941
+ err('C89', `text "${t.id}": spelling code "${e.spelling}" — ${parsed} (spelling-code-wellformed)`);
3942
+ }
3943
+ if (m?.defaultSpelling && e.spelling === m.defaultSpelling) {
3944
+ err('C89', `text "${t.id}": "${e.spelling}" is the package's default spelling — its value belongs inline on the element, not in a text block (spelling-code-wellformed)`);
3945
+ }
3946
+ if (seen.has(e.spelling)) {
3947
+ err('C89', `text "${t.id}": duplicate "${e.spelling}" value — one value per code per content set; use an extension code to distinguish variants (spelling-code-wellformed)`);
3948
+ }
3949
+ seen.add(e.spelling);
3950
+ if (e.via) {
3951
+ const via = (0, spelling_1.parseConversionCode)(e.via);
3952
+ if (typeof via === 'string') {
3953
+ err('C89', `text "${t.id}": via "${e.via}" — ${via} (spelling-code-wellformed)`);
3954
+ }
3955
+ else if (via.titular.toLowerCase().startsWith('zz-')) {
3956
+ warn('C89', `text "${t.id}": via "${e.via}" is a user-assigned (zz-) conversion code — it validates, but user-assigned codes are not portable (spelling-code-wellformed)`);
3957
+ }
3958
+ }
3959
+ }
3960
+ }
3961
+ return issues;
3962
+ }
3963
+ /**
3964
+ * C77–C80 + C85 — edition lifecycle (TODO.roadmap/28; doctrine ch. 13
3965
+ * §13.4/§13.7) and the manifest base URN (TODO.roadmap/27). Editions are
3966
+ * packagings: the relations live on the manifest, and the checks run
3967
+ * WITHOUT composing content (the checkManifestResolution sibling
3968
+ * pattern — supersedes acyclicity is a property of the repo's manifest
3969
+ * set).
3970
+ *
3971
+ * C77 edition-status: a current/preview edition packages the register's
3972
+ * newest entry. (The status ENUM itself is enforced by the manifest
3973
+ * parser, like `kind` — an unknown token is a parse error.)
3974
+ * C78 edition-validity-window: the window is well-formed ISO 8601 and
3975
+ * `to` is not before `from`.
3976
+ * C79 edition-supersedes-resolves: supersedes/replaces targets are
3977
+ * well-formed URNs, never the package itself; a target naming an
3978
+ * earlier edition OF THE SAME document resolves against the edition
3979
+ * register (warning when the register omits it); the supersedes
3980
+ * graph over sibling manifests is acyclic.
3981
+ * C80 edition-pin-resolves (INV-8): every instance's
3982
+ * definition_versions pin resolves against the package's edition
3983
+ * register (editions ∪ {version}) — an unresolvable pin breaks the
3984
+ * re-execution guarantee (§13.5: after an edition change the engine
3985
+ * must know exactly which reports re-judge).
3986
+ * C85 baseurn-wellformed: the base URN is a well-formed IRI — it
3987
+ * grounds every downstream IRI (the RDF projection, provenance
3988
+ * comparisons), and the free-string field was the one manifest
3989
+ * IRI surface nothing validated.
3990
+ */
3991
+ function checkEditionLifecycle(dir, standard) {
3992
+ const issues = [];
3993
+ const err = (check, message) => issues.push({ check, severity: 'error', message });
3994
+ const warn = (check, message) => issues.push({ check, severity: 'warning', message });
3995
+ const m = standard.packageManifest;
3996
+ if (!m) {
3997
+ return issues;
3998
+ }
3999
+ const register = new Set([
4000
+ ...(m.editions ?? []),
4001
+ ...(m.version ? [m.version] : []),
4002
+ ]);
4003
+ // C77 — a current/preview edition is the register's newest entry.
4004
+ if (m.status === 'current' || m.status === 'preview') {
4005
+ const newest = (m.editions ?? [])[0];
4006
+ if (newest !== undefined && m.version !== newest) {
4007
+ err('C77', `package "${m.id}": status ${m.status} but version "${m.version}" is not the edition register's newest entry (${newest}) (edition-status)`);
4008
+ }
4009
+ }
4010
+ // C78 — the validity window is well-formed time (§13.7).
4011
+ if (m.validity) {
4012
+ const { from, to } = m.validity;
4013
+ if (!from || !((0, time_1.isDate)(from) || (0, time_1.isDateTime)(from))) {
4014
+ err('C78', `package "${m.id}": validity window from "${from}" is not an ISO 8601 date/datetime (edition-validity-window)`);
4015
+ }
4016
+ if (to !== undefined) {
4017
+ if (!((0, time_1.isDate)(to) || (0, time_1.isDateTime)(to))) {
4018
+ err('C78', `package "${m.id}": validity window to "${to}" is not an ISO 8601 date/datetime (edition-validity-window)`);
4019
+ }
4020
+ else if (from) {
4021
+ // Compare as INSTANTS, never lexicographically: a mixed
4022
+ // date/datetime pair (from 2021-01-01T00:00:00Z, to 2021-01-01)
4023
+ // is the same moment — string order false-positives on it.
4024
+ const fromMs = (0, time_1.timeInstantMs)(from);
4025
+ const toMs = (0, time_1.timeInstantMs)(to);
4026
+ if (fromMs !== null && toMs !== null && toMs < fromMs) {
4027
+ err('C78', `package "${m.id}": validity window to ${to} is before from ${from} (edition-validity-window)`);
4028
+ }
4029
+ }
4030
+ }
4031
+ }
4032
+ // C85 — the base URN is a well-formed IRI (TODO.roadmap/27, task-27c
4033
+ // review Important 1): a scheme, then no whitespace or IRI delimiters.
4034
+ // baseUrn grounds every downstream IRI (the RDF projection's document
4035
+ // node and instance IRIs, edition-normalized provenance comparisons);
4036
+ // the field is a free string, and a malformed value (`urn:bad urn`)
4037
+ // passed check while exports emitted spec-malformed IRIREFs silently.
4038
+ // The same shape the RDF export guards on (export/rdf.ts IRI_SHAPED) —
4039
+ // duplicated as one line rather than shared across the kernel/export
4040
+ // boundary.
4041
+ if (m.baseUrn &&
4042
+ !/^[A-Za-z][A-Za-z0-9+.-]*:[^\s<>"{}|^`\\]*$/.test(m.baseUrn)) {
4043
+ err('C85', `package "${m.id}": baseUrn "${m.baseUrn}" is not a well-formed IRI (a scheme followed by no whitespace or IRI delimiters) (baseurn-wellformed)`);
4044
+ }
4045
+ // C79 — supersedes/replaces: well-formed URNs, never self, resolving
4046
+ // against the register (same-document targets) and acyclic across the
4047
+ // repo's manifests (§13.7).
4048
+ const relations = [
4049
+ ...(m.supersedes ?? []).map(t => ['supersedes', t]),
4050
+ ...(m.replaces ?? []).map(t => ['replaces', t]),
4051
+ ];
4052
+ const ownBasis = (0, model_diff_1.normalizeSourceRef)(m.baseUrn ?? '', '').basis;
4053
+ for (const [rel, target] of relations) {
4054
+ if (!target.startsWith('urn:')) {
4055
+ err('C79', `package "${m.id}": ${rel} "${target}" is not a URN — versioning relations name published package versions (edition-supersedes-resolves)`);
4056
+ continue;
4057
+ }
4058
+ if (target === m.baseUrn) {
4059
+ err('C79', `package "${m.id}": ${rel} ${target} — a package cannot ${rel} itself (edition-supersedes-resolves)`);
4060
+ continue;
4061
+ }
4062
+ const tNorm = (0, model_diff_1.normalizeSourceRef)(target, '');
4063
+ if (tNorm.basis === ownBasis && tNorm.edition) {
4064
+ if (!register.has(tNorm.edition)) {
4065
+ warn('C79', `package "${m.id}": ${rel} ${target}, but the edition register { ${[...register].join(' ')} } does not list ${tNorm.edition} (edition-supersedes-resolves)`);
4066
+ }
4067
+ }
4068
+ }
4069
+ if (relations.length > 0) {
4070
+ // The supersedes graph over the repo's manifests (sibling dirs keyed
4071
+ // by baseUrn) must be acyclic — no superseding oneself through a
4072
+ // chain. Edges to URNs no sibling declares are external and cannot
4073
+ // close a cycle here.
4074
+ const nodes = new Map(); // baseUrn → relations
4075
+ const parent = (0, path_1.dirname)((0, path_1.resolve)(dir));
4076
+ for (const entry of (0, fs_1.readdirSync)(parent).sort()) {
4077
+ const full = (0, path_1.join)(parent, entry);
4078
+ try {
4079
+ if (!(0, fs_1.statSync)(full).isDirectory()) {
4080
+ continue;
4081
+ }
4082
+ const sib = (0, package_1.readPackageManifest)(full);
4083
+ const key = sib.baseUrn || sib.id;
4084
+ nodes.set(key, [
4085
+ ...(sib.supersedes ?? []).map(t => ['supersedes', t]),
4086
+ ...(sib.replaces ?? []).map(t => ['replaces', t]),
4087
+ ]);
4088
+ }
4089
+ catch {
4090
+ // Not a package dir — not a graph node.
4091
+ }
4092
+ }
4093
+ const adj = new Map();
4094
+ for (const [baseUrn, rels] of nodes) {
4095
+ adj.set(baseUrn, rels
4096
+ .map(([, t]) => t)
4097
+ // Self-edges are reported by the dedicated self-supersession
4098
+ // check above — the cycle detector covers chains of length ≥ 2.
4099
+ .filter(t => nodes.has(t) && t !== baseUrn));
4100
+ }
4101
+ const cycle = findCycle(adj);
4102
+ if (cycle) {
4103
+ err('C79', `supersedes cycle: ${cycle.join(' → ')} — the supersedes graph must be acyclic; a package cannot supersede itself through a chain (edition-supersedes-resolves)`);
4104
+ }
4105
+ }
4106
+ // C80 — INV-8: definition version pins resolve against the edition
4107
+ // register. Silent on a register-less package (nothing to resolve
4108
+ // against); C18 still requires the pins themselves.
4109
+ if (register.size > 0) {
4110
+ for (const inst of standard.instances ?? []) {
4111
+ for (const [definition, version] of Object.entries(inst.definitionVersions ?? {})) {
4112
+ if (!register.has(version)) {
4113
+ err('C80', `instance ${inst.id}: definition_versions pin ${definition} : "${version}" does not resolve against the edition register { ${[...register].join(' ')} } — every executed definition is version-pinned to a declared edition (INV-8) (edition-pin-resolves)`);
4114
+ }
4115
+ }
4116
+ }
4117
+ }
4118
+ return issues;
4119
+ }
4120
+ /**
4121
+ * DFS cycle detection on an adjacency map. Returns one cycle as a node
4122
+ * path (first node repeated at the end), or null when acyclic. Used by
4123
+ * C14 on the flow graph with timer-event nodes removed — any surviving
4124
+ * cycle is an unguarded loop.
4125
+ */
4126
+ function findCycle(adj) {
4127
+ const GRAY = 1;
4128
+ const BLACK = 2;
4129
+ const color = new Map();
4130
+ const stack = [];
4131
+ let cycle = null;
4132
+ const visit = (n) => {
4133
+ if (cycle) {
4134
+ return;
4135
+ }
4136
+ color.set(n, GRAY);
4137
+ stack.push(n);
4138
+ for (const m of adj.get(n) ?? []) {
4139
+ if (cycle) {
4140
+ return;
4141
+ }
4142
+ const c = color.get(m) ?? 0;
4143
+ if (c === GRAY) {
4144
+ cycle = [...stack.slice(stack.indexOf(m)), m];
4145
+ return;
4146
+ }
4147
+ if (c !== BLACK) {
4148
+ visit(m);
4149
+ }
4150
+ }
4151
+ stack.pop();
4152
+ color.set(n, BLACK);
4153
+ };
4154
+ for (const n of adj.keys()) {
4155
+ if (cycle) {
4156
+ break;
4157
+ }
4158
+ if (!color.has(n)) {
4159
+ visit(n);
4160
+ }
4161
+ }
4162
+ return cycle;
4163
+ }
4164
+ //# sourceMappingURL=check.js.map