@ybotok/loam 0.2.0-alpha.1 → 0.2.0-alpha.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (901) hide show
  1. package/CHANGELOG.md +3448 -0
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +47 -23
  5. package/QUICKSTART.md +189 -12
  6. package/README.md +274 -65
  7. package/ROADMAP.md +572 -31
  8. package/SCHEMA.md +1709 -155
  9. package/WORKFLOW.md +298 -72
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/adopt/render.js +50 -9
  15. package/dist/commands/adopt/render.js.map +1 -1
  16. package/dist/commands/archive/archive.js +16 -19
  17. package/dist/commands/archive/archive.js.map +1 -1
  18. package/dist/commands/archive/closing.js +42 -0
  19. package/dist/commands/archive/closing.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
  21. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  23. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  24. package/dist/commands/archive/plan/contracts/openapi.js +77 -43
  25. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  26. package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
  27. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  28. package/dist/commands/archive/plan/document/head.js +151 -0
  29. package/dist/commands/archive/plan/document/head.js.map +1 -0
  30. package/dist/commands/archive/plan/document/links.js +284 -0
  31. package/dist/commands/archive/plan/document/links.js.map +1 -0
  32. package/dist/commands/archive/plan/document/run.js +136 -0
  33. package/dist/commands/archive/plan/document/run.js.map +1 -0
  34. package/dist/commands/archive/plan/gate.js +21 -19
  35. package/dist/commands/archive/plan/gate.js.map +1 -1
  36. package/dist/commands/archive/plan/landscape.js +167 -30
  37. package/dist/commands/archive/plan/landscape.js.map +1 -1
  38. package/dist/commands/archive/plan/model/extending.js +85 -0
  39. package/dist/commands/archive/plan/model/extending.js.map +1 -0
  40. package/dist/commands/archive/plan/refusal.js +14 -19
  41. package/dist/commands/archive/plan/refusal.js.map +1 -1
  42. package/dist/commands/archive/plan/specs.js +137 -56
  43. package/dist/commands/archive/plan/specs.js.map +1 -1
  44. package/dist/commands/archive/plan/state.js +3 -0
  45. package/dist/commands/archive/plan/state.js.map +1 -1
  46. package/dist/commands/archive/run.js +134 -30
  47. package/dist/commands/archive/run.js.map +1 -1
  48. package/dist/commands/archive/shipped.js +49 -0
  49. package/dist/commands/archive/shipped.js.map +1 -0
  50. package/dist/commands/context/context.js +85 -18
  51. package/dist/commands/context/context.js.map +1 -1
  52. package/dist/commands/context/print.js +29 -7
  53. package/dist/commands/context/print.js.map +1 -1
  54. package/dist/commands/delta/delta.js +102 -14
  55. package/dist/commands/delta/delta.js.map +1 -1
  56. package/dist/commands/delta/print.js +17 -1
  57. package/dist/commands/delta/print.js.map +1 -1
  58. package/dist/commands/dependencies.js +3 -7
  59. package/dist/commands/dependencies.js.map +1 -1
  60. package/dist/commands/diff/diff.js +20 -27
  61. package/dist/commands/diff/diff.js.map +1 -1
  62. package/dist/commands/doctor.js +60 -1
  63. package/dist/commands/doctor.js.map +1 -1
  64. package/dist/commands/explain/explain.js +89 -13
  65. package/dist/commands/explain/explain.js.map +1 -1
  66. package/dist/commands/explore.js +16 -10
  67. package/dist/commands/explore.js.map +1 -1
  68. package/dist/commands/gate/gate.js +45 -8
  69. package/dist/commands/gate/gate.js.map +1 -1
  70. package/dist/commands/gate/print.js +15 -3
  71. package/dist/commands/gate/print.js.map +1 -1
  72. package/dist/commands/gherkin/commit.js +13 -54
  73. package/dist/commands/gherkin/commit.js.map +1 -1
  74. package/dist/commands/gherkin/gherkin.js +36 -20
  75. package/dist/commands/gherkin/gherkin.js.map +1 -1
  76. package/dist/commands/gherkin/reconcile.js +10 -13
  77. package/dist/commands/gherkin/reconcile.js.map +1 -1
  78. package/dist/commands/init/first-hour.js +44 -0
  79. package/dist/commands/init/first-hour.js.map +1 -1
  80. package/dist/commands/init/init.js +18 -22
  81. package/dist/commands/init/init.js.map +1 -1
  82. package/dist/commands/list/campaign/campaign.js +4 -6
  83. package/dist/commands/list/campaign/campaign.js.map +1 -1
  84. package/dist/commands/list/json.js +5 -7
  85. package/dist/commands/list/json.js.map +1 -1
  86. package/dist/commands/list/list.js +44 -29
  87. package/dist/commands/list/list.js.map +1 -1
  88. package/dist/commands/list/print.js +52 -9
  89. package/dist/commands/list/print.js.map +1 -1
  90. package/dist/commands/list/suites/suites.js +140 -0
  91. package/dist/commands/list/suites/suites.js.map +1 -0
  92. package/dist/commands/list/views.js +37 -34
  93. package/dist/commands/list/views.js.map +1 -1
  94. package/dist/commands/mcp/dispatch.js +91 -36
  95. package/dist/commands/mcp/dispatch.js.map +1 -1
  96. package/dist/commands/mcp/mcp.js +56 -15
  97. package/dist/commands/mcp/mcp.js.map +1 -1
  98. package/dist/commands/mcp/serve.js.map +1 -1
  99. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  100. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  101. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  102. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  103. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  104. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  105. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  106. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  107. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  108. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  109. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  110. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  111. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  112. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  113. package/dist/commands/new/capability-template.js +118 -0
  114. package/dist/commands/new/capability-template.js.map +1 -0
  115. package/dist/commands/new/commit.js +8 -22
  116. package/dist/commands/new/commit.js.map +1 -1
  117. package/dist/commands/new/modifies.js +206 -0
  118. package/dist/commands/new/modifies.js.map +1 -0
  119. package/dist/commands/new/new.js +59 -14
  120. package/dist/commands/new/new.js.map +1 -1
  121. package/dist/commands/new/templates.js +48 -124
  122. package/dist/commands/new/templates.js.map +1 -1
  123. package/dist/commands/open.js +3 -7
  124. package/dist/commands/open.js.map +1 -1
  125. package/dist/commands/policy/gate.js +27 -0
  126. package/dist/commands/policy/gate.js.map +1 -1
  127. package/dist/commands/rebase/plan.js +2 -2
  128. package/dist/commands/rebase/plan.js.map +1 -1
  129. package/dist/commands/rebase/rebase.js +60 -35
  130. package/dist/commands/rebase/rebase.js.map +1 -1
  131. package/dist/commands/seed/commit.js +55 -26
  132. package/dist/commands/seed/commit.js.map +1 -1
  133. package/dist/commands/seed/plan.js +158 -13
  134. package/dist/commands/seed/plan.js.map +1 -1
  135. package/dist/commands/seed/seed.js +13 -11
  136. package/dist/commands/seed/seed.js.map +1 -1
  137. package/dist/commands/show/feature.js +9 -5
  138. package/dist/commands/show/feature.js.map +1 -1
  139. package/dist/commands/show/introduced.js +157 -0
  140. package/dist/commands/show/introduced.js.map +1 -0
  141. package/dist/commands/show/marks.js +35 -6
  142. package/dist/commands/show/marks.js.map +1 -1
  143. package/dist/commands/show/service.js +41 -33
  144. package/dist/commands/show/service.js.map +1 -1
  145. package/dist/commands/show/show.js +17 -7
  146. package/dist/commands/show/show.js.map +1 -1
  147. package/dist/commands/status/fleet-next.js +90 -0
  148. package/dist/commands/status/fleet-next.js.map +1 -0
  149. package/dist/commands/status/json.js +2 -0
  150. package/dist/commands/status/json.js.map +1 -1
  151. package/dist/commands/status/print.js +89 -37
  152. package/dist/commands/status/print.js.map +1 -1
  153. package/dist/commands/status/status.js +3 -7
  154. package/dist/commands/status/status.js.map +1 -1
  155. package/dist/commands/steps/steps.js +103 -23
  156. package/dist/commands/steps/steps.js.map +1 -1
  157. package/dist/commands/subsystem/edit.js +7 -7
  158. package/dist/commands/subsystem/edit.js.map +1 -1
  159. package/dist/commands/subsystem/move.js +106 -46
  160. package/dist/commands/subsystem/move.js.map +1 -1
  161. package/dist/commands/subsystem/query.js +2 -4
  162. package/dist/commands/subsystem/query.js.map +1 -1
  163. package/dist/commands/subsystem/subsystem.js +4 -7
  164. package/dist/commands/subsystem/subsystem.js.map +1 -1
  165. package/dist/commands/subsystem/sync.js +289 -74
  166. package/dist/commands/subsystem/sync.js.map +1 -1
  167. package/dist/commands/subsystem/txn/exclude.js +132 -0
  168. package/dist/commands/subsystem/txn/exclude.js.map +1 -0
  169. package/dist/commands/subsystem/txn/links.js +128 -0
  170. package/dist/commands/subsystem/txn/links.js.map +1 -0
  171. package/dist/commands/subsystem/txn/report.js +174 -0
  172. package/dist/commands/subsystem/txn/report.js.map +1 -0
  173. package/dist/commands/subsystem/txn/txn.js +147 -45
  174. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  175. package/dist/commands/subsystem/txn/views.js +200 -21
  176. package/dist/commands/subsystem/txn/views.js.map +1 -1
  177. package/dist/commands/tasks/actions.js +107 -0
  178. package/dist/commands/tasks/actions.js.map +1 -0
  179. package/dist/commands/tasks/render.js +253 -0
  180. package/dist/commands/tasks/render.js.map +1 -0
  181. package/dist/commands/tasks/tasks.js +119 -0
  182. package/dist/commands/tasks/tasks.js.map +1 -0
  183. package/dist/commands/unarchive/manifest.js +42 -26
  184. package/dist/commands/unarchive/manifest.js.map +1 -1
  185. package/dist/commands/unarchive/restore.js +83 -33
  186. package/dist/commands/unarchive/restore.js.map +1 -1
  187. package/dist/commands/unarchive/unarchive.js +22 -30
  188. package/dist/commands/unarchive/unarchive.js.map +1 -1
  189. package/dist/commands/validate/arch-coverage.js +78 -27
  190. package/dist/commands/validate/arch-coverage.js.map +1 -1
  191. package/dist/commands/validate/checks/fleet-shape.js +34 -126
  192. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  193. package/dist/commands/validate/checks/health-deps.js +9 -6
  194. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  195. package/dist/commands/validate/checks/introduced.js +102 -0
  196. package/dist/commands/validate/checks/introduced.js.map +1 -0
  197. package/dist/commands/validate/checks/requirements.js +18 -8
  198. package/dist/commands/validate/checks/requirements.js.map +1 -1
  199. package/dist/commands/validate/checks/sources.js +11 -2
  200. package/dist/commands/validate/checks/sources.js.map +1 -1
  201. package/dist/commands/validate/coverage/covers-after.js +105 -0
  202. package/dist/commands/validate/coverage/covers-after.js.map +1 -0
  203. package/dist/commands/validate/feature.js +26 -20
  204. package/dist/commands/validate/feature.js.map +1 -1
  205. package/dist/commands/validate/fleet/census.js +37 -13
  206. package/dist/commands/validate/fleet/census.js.map +1 -1
  207. package/dist/commands/validate/fleet/landscape.js +144 -126
  208. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  209. package/dist/commands/validate/fleet/load.js +66 -0
  210. package/dist/commands/validate/fleet/load.js.map +1 -1
  211. package/dist/commands/validate/fleet/map/attest.js +98 -0
  212. package/dist/commands/validate/fleet/map/attest.js.map +1 -0
  213. package/dist/commands/validate/fleet/map/bindings.js +107 -0
  214. package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
  215. package/dist/commands/validate/fleet/map/consumers.js +244 -0
  216. package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
  217. package/dist/commands/validate/fleet/map/isolation.js +116 -0
  218. package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
  219. package/dist/commands/validate/fleet/map/usecases.js +54 -0
  220. package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
  221. package/dist/commands/validate/fleet/obligations.js +44 -14
  222. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  223. package/dist/commands/validate/fleet/scorecard/print.js +10 -2
  224. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  225. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  226. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  227. package/dist/commands/validate/fleet/usecases/place.js.map +1 -1
  228. package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
  229. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
  230. package/dist/commands/validate/fleet/views/ids.js +50 -15
  231. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  232. package/dist/commands/validate/fleet/views/projects.js +278 -0
  233. package/dist/commands/validate/fleet/views/projects.js.map +1 -0
  234. package/dist/commands/validate/fleet/views/root-documents.js +83 -0
  235. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
  236. package/dist/commands/validate/fleet/views/stale.js +40 -47
  237. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  238. package/dist/commands/validate/links/corpus.js +1 -4
  239. package/dist/commands/validate/links/corpus.js.map +1 -1
  240. package/dist/commands/validate/links/glossary.js.map +1 -1
  241. package/dist/commands/validate/render/repeats.js +63 -0
  242. package/dist/commands/validate/render/repeats.js.map +1 -0
  243. package/dist/commands/validate/render/rollups.js +115 -0
  244. package/dist/commands/validate/render/rollups.js.map +1 -0
  245. package/dist/commands/validate/render/text.js +199 -0
  246. package/dist/commands/validate/render/text.js.map +1 -0
  247. package/dist/commands/validate/report.js +36 -160
  248. package/dist/commands/validate/report.js.map +1 -1
  249. package/dist/commands/validate/scope.js +113 -0
  250. package/dist/commands/validate/scope.js.map +1 -0
  251. package/dist/commands/validate/service/api.js +21 -107
  252. package/dist/commands/validate/service/api.js.map +1 -1
  253. package/dist/commands/validate/service/contracts/generated.js +134 -0
  254. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  255. package/dist/commands/validate/service/events/events.js +36 -14
  256. package/dist/commands/validate/service/events/events.js.map +1 -1
  257. package/dist/commands/validate/service/events/message-producers.js +1 -1
  258. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  259. package/dist/commands/validate/service/evidence-pins.js +56 -10
  260. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  261. package/dist/commands/validate/service/model/diverged.js +226 -0
  262. package/dist/commands/validate/service/model/diverged.js.map +1 -0
  263. package/dist/commands/validate/service/model/grade.js +163 -0
  264. package/dist/commands/validate/service/model/grade.js.map +1 -0
  265. package/dist/commands/validate/service/service.js +121 -61
  266. package/dist/commands/validate/service/service.js.map +1 -1
  267. package/dist/commands/validate/service/specs.js +88 -28
  268. package/dist/commands/validate/service/specs.js.map +1 -1
  269. package/dist/commands/validate/service/spine.js +38 -3
  270. package/dist/commands/validate/service/spine.js.map +1 -1
  271. package/dist/commands/validate/service/usecases/tags.js +113 -0
  272. package/dist/commands/validate/service/usecases/tags.js.map +1 -0
  273. package/dist/commands/validate/service/usecases/usecases.js +209 -0
  274. package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
  275. package/dist/commands/validate/validate.js +85 -60
  276. package/dist/commands/validate/validate.js.map +1 -1
  277. package/dist/commands/verify/cross/diff.js +2 -6
  278. package/dist/commands/verify/cross/diff.js.map +1 -1
  279. package/dist/commands/verify/evidence/contract.js +1 -1
  280. package/dist/commands/verify/evidence/contract.js.map +1 -1
  281. package/dist/commands/verify/evidence/read.js +12 -3
  282. package/dist/commands/verify/evidence/read.js.map +1 -1
  283. package/dist/commands/verify/frozen.js +45 -23
  284. package/dist/commands/verify/frozen.js.map +1 -1
  285. package/dist/commands/verify/record.js +69 -17
  286. package/dist/commands/verify/record.js.map +1 -1
  287. package/dist/commands/verify/report.js +27 -44
  288. package/dist/commands/verify/report.js.map +1 -1
  289. package/dist/commands/verify/results.js +112 -30
  290. package/dist/commands/verify/results.js.map +1 -1
  291. package/dist/commands/verify/verify.js +55 -52
  292. package/dist/commands/verify/verify.js.map +1 -1
  293. package/dist/commands/vouch/pack/pack.js +2 -2
  294. package/dist/commands/vouch/pack/pack.js.map +1 -1
  295. package/dist/commands/vouch/pack/print.js +1 -6
  296. package/dist/commands/vouch/pack/print.js.map +1 -1
  297. package/dist/commands/vouch/pack/sections.js +1 -2
  298. package/dist/commands/vouch/pack/sections.js.map +1 -1
  299. package/dist/commands/vouch/run.js +26 -81
  300. package/dist/commands/vouch/run.js.map +1 -1
  301. package/dist/commands/vouch/sample/plan.js +20 -34
  302. package/dist/commands/vouch/sample/plan.js.map +1 -1
  303. package/dist/commands/vouch/vet/verify.js +39 -7
  304. package/dist/commands/vouch/vet/verify.js.map +1 -1
  305. package/dist/commands/vouch/vouch.js +10 -17
  306. package/dist/commands/vouch/vouch.js.map +1 -1
  307. package/dist/core/agent/agents-md/command-map.js +81 -106
  308. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  309. package/dist/core/agent/agents-md/map/explain.js +21 -9
  310. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  311. package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
  312. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  313. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  314. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  315. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  316. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  317. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  318. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  319. package/dist/core/agent/agents-md/map/status.js +24 -8
  320. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  321. package/dist/core/agent/agents-md/map/subsystem.js +80 -8
  322. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  323. package/dist/core/agent/agents-md/map/usecases.js +88 -10
  324. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  325. package/dist/core/agent/agents-md/refusals.js +49 -22
  326. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  327. package/dist/core/agent/agents-md.js +39 -6
  328. package/dist/core/agent/agents-md.js.map +1 -1
  329. package/dist/core/agent/agents-stamp.js +10 -0
  330. package/dist/core/agent/agents-stamp.js.map +1 -1
  331. package/dist/core/agent/protocol.js +1 -1
  332. package/dist/core/agent/protocol.js.map +1 -1
  333. package/dist/core/agent/scaffold.js +10 -15
  334. package/dist/core/agent/scaffold.js.map +1 -1
  335. package/dist/core/agent/support/report.js +46 -5
  336. package/dist/core/agent/support/report.js.map +1 -1
  337. package/dist/core/agent/tools/dialects.js +1 -0
  338. package/dist/core/agent/tools/dialects.js.map +1 -1
  339. package/dist/core/agent/tools/registry.js +41 -76
  340. package/dist/core/agent/tools/registry.js.map +1 -1
  341. package/dist/core/agent/workflows/adopt.js +158 -11
  342. package/dist/core/agent/workflows/adopt.js.map +1 -1
  343. package/dist/core/agent/workflows/check.js +78 -39
  344. package/dist/core/agent/workflows/check.js.map +1 -1
  345. package/dist/core/agent/workflows/closing.js +88 -27
  346. package/dist/core/agent/workflows/closing.js.map +1 -1
  347. package/dist/core/agent/workflows/feature.js +128 -13
  348. package/dist/core/agent/workflows/feature.js.map +1 -1
  349. package/dist/core/agent/workflows/implement.js +96 -13
  350. package/dist/core/agent/workflows/implement.js.map +1 -1
  351. package/dist/core/agent/workflows/reference/authoring.js +106 -4
  352. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  353. package/dist/core/agent/workflows/reference/codes.js +8 -6
  354. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  355. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  356. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  357. package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
  358. package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
  359. package/dist/core/agent/workflows/reference/reference.js +6 -3
  360. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  361. package/dist/core/agent/workflows/reference/spine.js +68 -27
  362. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  363. package/dist/core/asyncapi/baseline/plan.js +14 -81
  364. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  365. package/dist/core/asyncapi/digest.js +30 -25
  366. package/dist/core/asyncapi/digest.js.map +1 -1
  367. package/dist/core/asyncapi/merge/components.js +5 -4
  368. package/dist/core/asyncapi/merge/components.js.map +1 -1
  369. package/dist/core/asyncapi/merge/error.js +7 -2
  370. package/dist/core/asyncapi/merge/error.js.map +1 -1
  371. package/dist/core/asyncapi/merge/markers.js +32 -50
  372. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  373. package/dist/core/asyncapi/merge/merge.js +33 -53
  374. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  375. package/dist/core/asyncapi/merge/pin.js +12 -36
  376. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  377. package/dist/core/asyncapi/model.js.map +1 -1
  378. package/dist/core/asyncapi/read.js +36 -12
  379. package/dist/core/asyncapi/read.js.map +1 -1
  380. package/dist/core/brief/brief.js +96 -36
  381. package/dist/core/brief/brief.js.map +1 -1
  382. package/dist/core/brief/checks.js +138 -3
  383. package/dist/core/brief/checks.js.map +1 -1
  384. package/dist/core/brief/landscape.js +209 -130
  385. package/dist/core/brief/landscape.js.map +1 -1
  386. package/dist/core/brief/map/extend.js +84 -0
  387. package/dist/core/brief/map/extend.js.map +1 -0
  388. package/dist/core/brief/map/owed.js +266 -0
  389. package/dist/core/brief/map/owed.js.map +1 -0
  390. package/dist/core/brief/targets.js +109 -31
  391. package/dist/core/brief/targets.js.map +1 -1
  392. package/dist/core/brief/unchecked.js +14 -3
  393. package/dist/core/brief/unchecked.js.map +1 -1
  394. package/dist/core/c4/arch.js +90 -182
  395. package/dist/core/c4/arch.js.map +1 -1
  396. package/dist/core/c4/direction/direction.js +114 -0
  397. package/dist/core/c4/direction/direction.js.map +1 -0
  398. package/dist/core/c4/likec4.js +169 -14
  399. package/dist/core/c4/likec4.js.map +1 -1
  400. package/dist/core/c4/locate/unlocated.js +150 -0
  401. package/dist/core/c4/locate/unlocated.js.map +1 -0
  402. package/dist/core/c4/parsed/deployment.js +160 -0
  403. package/dist/core/c4/parsed/deployment.js.map +1 -0
  404. package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
  405. package/dist/core/c4/parsed/values.js +54 -1
  406. package/dist/core/c4/parsed/values.js.map +1 -1
  407. package/dist/core/c4/parsed/view-ids.js +39 -1
  408. package/dist/core/c4/parsed/view-ids.js.map +1 -1
  409. package/dist/core/c4/prefetch/fleet.js +134 -0
  410. package/dist/core/c4/prefetch/fleet.js.map +1 -0
  411. package/dist/core/c4/project/architecture.js +123 -7
  412. package/dist/core/c4/project/architecture.js.map +1 -1
  413. package/dist/core/c4/project/batch.js +140 -0
  414. package/dist/core/c4/project/batch.js.map +1 -0
  415. package/dist/core/c4/project/documents.js +43 -12
  416. package/dist/core/c4/project/documents.js.map +1 -1
  417. package/dist/core/c4/project/load.js +79 -43
  418. package/dist/core/c4/project/load.js.map +1 -1
  419. package/dist/core/c4/project/staged.js +277 -0
  420. package/dist/core/c4/project/staged.js.map +1 -0
  421. package/dist/core/c4/resolve/attested.js +0 -0
  422. package/dist/core/c4/resolve/attested.js.map +1 -0
  423. package/dist/core/c4/resolve/external.js +51 -0
  424. package/dist/core/c4/resolve/external.js.map +1 -0
  425. package/dist/core/c4/resolve/service.js +78 -17
  426. package/dist/core/c4/resolve/service.js.map +1 -1
  427. package/dist/core/c4/resolve/steps.js +290 -0
  428. package/dist/core/c4/resolve/steps.js.map +1 -0
  429. package/dist/core/c4/resolve/systems.js +154 -0
  430. package/dist/core/c4/resolve/systems.js.map +1 -0
  431. package/dist/core/c4/root-project/exclude.js +352 -0
  432. package/dist/core/c4/root-project/exclude.js.map +1 -0
  433. package/dist/core/c4/seed/template.js +65 -1
  434. package/dist/core/c4/seed/template.js.map +1 -1
  435. package/dist/core/c4/service-model/created/models.js +96 -0
  436. package/dist/core/c4/service-model/created/models.js.map +1 -0
  437. package/dist/core/c4/service-model/documents.js +47 -0
  438. package/dist/core/c4/service-model/documents.js.map +1 -0
  439. package/dist/core/c4/service-model/fleet/extending.js +82 -0
  440. package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
  441. package/dist/core/c4/service-model/load.js +165 -0
  442. package/dist/core/c4/service-model/load.js.map +1 -0
  443. package/dist/core/c4/service-model/renderer.js +128 -0
  444. package/dist/core/c4/service-model/renderer.js.map +1 -0
  445. package/dist/core/c4/service-model/shape.js +106 -0
  446. package/dist/core/c4/service-model/shape.js.map +1 -0
  447. package/dist/core/c4/service-model/slice.js +188 -0
  448. package/dist/core/c4/service-model/slice.js.map +1 -0
  449. package/dist/core/c4/source-mask.js +297 -10
  450. package/dist/core/c4/source-mask.js.map +1 -1
  451. package/dist/core/c4/source-scan.js +222 -141
  452. package/dist/core/c4/source-scan.js.map +1 -1
  453. package/dist/core/c4/splice/authored-source.js +79 -13
  454. package/dist/core/c4/splice/authored-source.js.map +1 -1
  455. package/dist/core/c4/splice/contract.js +20 -0
  456. package/dist/core/c4/splice/contract.js.map +1 -1
  457. package/dist/core/c4/splice/delta-blocks.js +20 -20
  458. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  459. package/dist/core/c4/splice/identity/declared.js +87 -0
  460. package/dist/core/c4/splice/identity/declared.js.map +1 -0
  461. package/dist/core/c4/splice/identity/edges.js +37 -0
  462. package/dist/core/c4/splice/identity/edges.js.map +1 -1
  463. package/dist/core/c4/splice/identity/existing.js +80 -0
  464. package/dist/core/c4/splice/identity/existing.js.map +1 -0
  465. package/dist/core/c4/splice/identity/interior.js +80 -0
  466. package/dist/core/c4/splice/identity/interior.js.map +1 -0
  467. package/dist/core/c4/splice/identity/owner.js +50 -0
  468. package/dist/core/c4/splice/identity/owner.js.map +1 -0
  469. package/dist/core/c4/splice/landscape-merge.js +182 -114
  470. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  471. package/dist/core/c4/splice/model/merge.js +151 -0
  472. package/dist/core/c4/splice/model/merge.js.map +1 -0
  473. package/dist/core/c4/splice/placement.js +160 -56
  474. package/dist/core/c4/splice/placement.js.map +1 -1
  475. package/dist/core/c4/splice/text/spot.js +131 -0
  476. package/dist/core/c4/splice/text/spot.js.map +1 -0
  477. package/dist/core/c4/workspace.js +13 -19
  478. package/dist/core/c4/workspace.js.map +1 -1
  479. package/dist/core/capabilities/capabilities.js +6 -6
  480. package/dist/core/capabilities/capabilities.js.map +1 -1
  481. package/dist/core/capabilities/delta/doc.js +7 -10
  482. package/dist/core/capabilities/delta/doc.js.map +1 -1
  483. package/dist/core/capabilities/delta/uncovered.js +2 -2
  484. package/dist/core/capabilities/delta/uncovered.js.map +1 -1
  485. package/dist/core/capabilities/findings.js +18 -22
  486. package/dist/core/capabilities/findings.js.map +1 -1
  487. package/dist/core/capabilities/realizes/findings.js +12 -19
  488. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  489. package/dist/core/capabilities/realizes/join.js +0 -20
  490. package/dist/core/capabilities/realizes/join.js.map +1 -1
  491. package/dist/core/capabilities/rollup.js +6 -0
  492. package/dist/core/capabilities/rollup.js.map +1 -1
  493. package/dist/core/capabilities/tree.js +6 -28
  494. package/dist/core/capabilities/tree.js.map +1 -1
  495. package/dist/core/capabilities/usecase-join.js +3 -13
  496. package/dist/core/capabilities/usecase-join.js.map +1 -1
  497. package/dist/core/coherence/authoring/scaffold.js +11 -3
  498. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  499. package/dist/core/coherence/authoring/sentinels.js +2 -1
  500. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  501. package/dist/core/coherence/calls/called.js +52 -0
  502. package/dist/core/coherence/calls/called.js.map +1 -0
  503. package/dist/core/coherence/coherence.js +103 -22
  504. package/dist/core/coherence/coherence.js.map +1 -1
  505. package/dist/core/coherence/consumed/operations.js +70 -0
  506. package/dist/core/coherence/consumed/operations.js.map +1 -0
  507. package/dist/core/coherence/consumed/refusal.js +38 -0
  508. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  509. package/dist/core/coherence/covers/health.js +85 -0
  510. package/dist/core/coherence/covers/health.js.map +1 -0
  511. package/dist/core/coherence/declared.js +60 -34
  512. package/dist/core/coherence/declared.js.map +1 -1
  513. package/dist/core/coherence/events/declared.js +44 -59
  514. package/dist/core/coherence/events/declared.js.map +1 -1
  515. package/dist/core/coherence/events/events.js +147 -16
  516. package/dist/core/coherence/events/events.js.map +1 -1
  517. package/dist/core/coherence/events/grades.js +31 -8
  518. package/dist/core/coherence/events/grades.js.map +1 -1
  519. package/dist/core/coherence/events/lookups.js +28 -87
  520. package/dist/core/coherence/events/lookups.js.map +1 -1
  521. package/dist/core/coherence/events/produced.js +100 -0
  522. package/dist/core/coherence/events/produced.js.map +1 -0
  523. package/dist/core/coherence/lookups.js +18 -78
  524. package/dist/core/coherence/lookups.js.map +1 -1
  525. package/dist/core/coherence/retired/archived.js +285 -0
  526. package/dist/core/coherence/retired/archived.js.map +1 -0
  527. package/dist/core/coherence/retired/event-pair.js +62 -0
  528. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  529. package/dist/core/coherence/retired/merged.js +79 -0
  530. package/dist/core/coherence/retired/merged.js.map +1 -0
  531. package/dist/core/coherence/retired/retirement.js +49 -0
  532. package/dist/core/coherence/retired/retirement.js.map +1 -0
  533. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  534. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  535. package/dist/core/coherence/retired/revival/revived.js +297 -0
  536. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  537. package/dist/core/consumers/after.js +113 -0
  538. package/dist/core/consumers/after.js.map +1 -0
  539. package/dist/core/consumers/consumers.js +296 -0
  540. package/dist/core/consumers/consumers.js.map +1 -0
  541. package/dist/core/consumers/current.js +211 -0
  542. package/dist/core/consumers/current.js.map +1 -0
  543. package/dist/core/consumers/scan.js +46 -0
  544. package/dist/core/consumers/scan.js.map +1 -0
  545. package/dist/core/consumers/state.js +27 -0
  546. package/dist/core/consumers/state.js.map +1 -0
  547. package/dist/core/delta/delta.js +51 -6
  548. package/dist/core/delta/delta.js.map +1 -1
  549. package/dist/core/dependencies/fanin.js +2 -2
  550. package/dist/core/dependencies/fanin.js.map +1 -1
  551. package/dist/core/deployment/delta.js +191 -0
  552. package/dist/core/deployment/delta.js.map +1 -0
  553. package/dist/core/deployment/objects.js +145 -0
  554. package/dist/core/deployment/objects.js.map +1 -0
  555. package/dist/core/diff/base-git.js +94 -12
  556. package/dist/core/diff/base-git.js.map +1 -1
  557. package/dist/core/diff/base-state.js +49 -21
  558. package/dist/core/diff/base-state.js.map +1 -1
  559. package/dist/core/diff/joins.js +136 -0
  560. package/dist/core/diff/joins.js.map +1 -0
  561. package/dist/core/diff/scope/changed-paths.js +22 -6
  562. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  563. package/dist/core/diff/scope/edges.js +82 -0
  564. package/dist/core/diff/scope/edges.js.map +1 -0
  565. package/dist/core/diff/semantic.js +100 -31
  566. package/dist/core/diff/semantic.js.map +1 -1
  567. package/dist/core/docs.js +31 -22
  568. package/dist/core/docs.js.map +1 -1
  569. package/dist/core/doctor/agents.js +34 -18
  570. package/dist/core/doctor/agents.js.map +1 -1
  571. package/dist/core/doctor/config.js +7 -3
  572. package/dist/core/doctor/config.js.map +1 -1
  573. package/dist/core/doctor/doctor.js +91 -11
  574. package/dist/core/doctor/doctor.js.map +1 -1
  575. package/dist/core/doctor/report.js.map +1 -1
  576. package/dist/core/doctor/reports/scan.js +242 -0
  577. package/dist/core/doctor/reports/scan.js.map +1 -0
  578. package/dist/core/document/apply.js +158 -11
  579. package/dist/core/document/apply.js.map +1 -1
  580. package/dist/core/document/frontmatter.js +55 -0
  581. package/dist/core/document/frontmatter.js.map +1 -1
  582. package/dist/core/document/parse.js +29 -6
  583. package/dist/core/document/parse.js.map +1 -1
  584. package/dist/core/document/scenarios.js +17 -0
  585. package/dist/core/document/scenarios.js.map +1 -1
  586. package/dist/core/document/spec.js +4 -11
  587. package/dist/core/document/spec.js.map +1 -1
  588. package/dist/core/envelope/config.js +10 -0
  589. package/dist/core/envelope/config.js.map +1 -1
  590. package/dist/core/envelope/json.js +34 -0
  591. package/dist/core/envelope/json.js.map +1 -1
  592. package/dist/core/explain/families.js +26 -21
  593. package/dist/core/explain/families.js.map +1 -1
  594. package/dist/core/explain/lookup.js +14 -1
  595. package/dist/core/explain/lookup.js.map +1 -1
  596. package/dist/core/explain/refusals.js +5 -5
  597. package/dist/core/explain/refusals.js.map +1 -1
  598. package/dist/core/explain/terms/documents.js +227 -0
  599. package/dist/core/explain/terms/documents.js.map +1 -0
  600. package/dist/core/explain/terms/done-check.js +144 -0
  601. package/dist/core/explain/terms/done-check.js.map +1 -0
  602. package/dist/core/explain/terms/entry.js +14 -0
  603. package/dist/core/explain/terms/entry.js.map +1 -0
  604. package/dist/core/explain/terms/stages.js +71 -0
  605. package/dist/core/explain/terms/stages.js.map +1 -0
  606. package/dist/core/explain/terms/tasks.js +97 -0
  607. package/dist/core/explain/terms/tasks.js.map +1 -0
  608. package/dist/core/explain/terms.js +68 -109
  609. package/dist/core/explain/terms.js.map +1 -1
  610. package/dist/core/explore/explore.js +40 -10
  611. package/dist/core/explore/explore.js.map +1 -1
  612. package/dist/core/fleet-context.js +81 -95
  613. package/dist/core/fleet-context.js.map +1 -1
  614. package/dist/core/gate/checks.js +11 -4
  615. package/dist/core/gate/checks.js.map +1 -1
  616. package/dist/core/gate/partners.js +69 -28
  617. package/dist/core/gate/partners.js.map +1 -1
  618. package/dist/core/gate/report.js.map +1 -1
  619. package/dist/core/gate/verification.js +75 -12
  620. package/dist/core/gate/verification.js.map +1 -1
  621. package/dist/core/gherkin/stale.js +13 -37
  622. package/dist/core/gherkin/stale.js.map +1 -1
  623. package/dist/core/glossary/delta.js +10 -7
  624. package/dist/core/glossary/delta.js.map +1 -1
  625. package/dist/core/glossary/tree.js +6 -19
  626. package/dist/core/glossary/tree.js.map +1 -1
  627. package/dist/core/kernel/concurrency.js +19 -0
  628. package/dist/core/kernel/concurrency.js.map +1 -1
  629. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  630. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  631. package/dist/core/kernel/path-safety.js +38 -0
  632. package/dist/core/kernel/path-safety.js.map +1 -1
  633. package/dist/core/kernel/records.js +11 -0
  634. package/dist/core/kernel/records.js.map +1 -1
  635. package/dist/core/links/backlinks.js +1 -3
  636. package/dist/core/links/backlinks.js.map +1 -1
  637. package/dist/core/links/corpus.js +51 -37
  638. package/dist/core/links/corpus.js.map +1 -1
  639. package/dist/core/links/findings.js.map +1 -1
  640. package/dist/core/links/parse.js +38 -9
  641. package/dist/core/links/parse.js.map +1 -1
  642. package/dist/core/mcp/argv.js +101 -0
  643. package/dist/core/mcp/argv.js.map +1 -1
  644. package/dist/core/mcp/author-tools.js +2 -1
  645. package/dist/core/mcp/author-tools.js.map +1 -1
  646. package/dist/core/mcp/protocol.js +2 -2
  647. package/dist/core/mcp/protocol.js.map +1 -1
  648. package/dist/core/mcp/tools.js +43 -33
  649. package/dist/core/mcp/tools.js.map +1 -1
  650. package/dist/core/openapi/baseline/gate.js +53 -12
  651. package/dist/core/openapi/baseline/gate.js.map +1 -1
  652. package/dist/core/openapi/baseline/plan.js +40 -10
  653. package/dist/core/openapi/baseline/plan.js.map +1 -1
  654. package/dist/core/openapi/baseline/record.js +0 -1
  655. package/dist/core/openapi/baseline/record.js.map +1 -1
  656. package/dist/core/openapi/contract-text.js +33 -0
  657. package/dist/core/openapi/contract-text.js.map +1 -0
  658. package/dist/core/openapi/contract-tree.js +301 -0
  659. package/dist/core/openapi/contract-tree.js.map +1 -0
  660. package/dist/core/openapi/depth.js +38 -24
  661. package/dist/core/openapi/depth.js.map +1 -1
  662. package/dist/core/openapi/digest.js +52 -2
  663. package/dist/core/openapi/digest.js.map +1 -1
  664. package/dist/core/openapi/doc.js +12 -4
  665. package/dist/core/openapi/doc.js.map +1 -1
  666. package/dist/core/openapi/merge/components.js +25 -60
  667. package/dist/core/openapi/merge/components.js.map +1 -1
  668. package/dist/core/openapi/merge/error.js +3 -13
  669. package/dist/core/openapi/merge/error.js.map +1 -1
  670. package/dist/core/openapi/merge/markers.js +26 -30
  671. package/dist/core/openapi/merge/markers.js.map +1 -1
  672. package/dist/core/openapi/merge/merge.js +28 -52
  673. package/dist/core/openapi/merge/merge.js.map +1 -1
  674. package/dist/core/openapi/merge/pin.js +17 -35
  675. package/dist/core/openapi/merge/pin.js.map +1 -1
  676. package/dist/core/openspec/decide/verdict.js.map +1 -1
  677. package/dist/core/openspec/inventory.js +4 -5
  678. package/dist/core/openspec/inventory.js.map +1 -1
  679. package/dist/core/openspec/model/mapping.js +30 -28
  680. package/dist/core/openspec/model/mapping.js.map +1 -1
  681. package/dist/core/openspec/model/model.js +12 -7
  682. package/dist/core/openspec/model/model.js.map +1 -1
  683. package/dist/core/openspec/scan/changes.js +10 -4
  684. package/dist/core/openspec/scan/changes.js.map +1 -1
  685. package/dist/core/openspec/scan/config.js +7 -16
  686. package/dist/core/openspec/scan/config.js.map +1 -1
  687. package/dist/core/openspec/scan/shape.js +63 -19
  688. package/dist/core/openspec/scan/shape.js.map +1 -1
  689. package/dist/core/openspec/scan/walk.js +21 -38
  690. package/dist/core/openspec/scan/walk.js.map +1 -1
  691. package/dist/core/pack/living.js +43 -4
  692. package/dist/core/pack/living.js.map +1 -1
  693. package/dist/core/pack/pack.js +50 -6
  694. package/dist/core/pack/pack.js.map +1 -1
  695. package/dist/core/projection/arch-slice.js +2 -2
  696. package/dist/core/projection/arch-slice.js.map +1 -1
  697. package/dist/core/projection/introduced.js +14 -0
  698. package/dist/core/projection/introduced.js.map +1 -0
  699. package/dist/core/provenance/findings.js +48 -12
  700. package/dist/core/provenance/findings.js.map +1 -1
  701. package/dist/core/provenance/git.js +33 -57
  702. package/dist/core/provenance/git.js.map +1 -1
  703. package/dist/core/provenance/gitq/moves.js +71 -114
  704. package/dist/core/provenance/gitq/moves.js.map +1 -1
  705. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  706. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  707. package/dist/core/provenance/sources.js +23 -6
  708. package/dist/core/provenance/sources.js.map +1 -1
  709. package/dist/core/provenance/stamp.js +5 -17
  710. package/dist/core/provenance/stamp.js.map +1 -1
  711. package/dist/core/repo/authored/paths.js +18 -0
  712. package/dist/core/repo/authored/paths.js.map +1 -1
  713. package/dist/core/repo/entries.js.map +1 -1
  714. package/dist/core/repo/paths.js +92 -6
  715. package/dist/core/repo/paths.js.map +1 -1
  716. package/dist/core/repo/repo.js +74 -14
  717. package/dist/core/repo/repo.js.map +1 -1
  718. package/dist/core/repo/tree/find.js +12 -3
  719. package/dist/core/repo/tree/find.js.map +1 -1
  720. package/dist/core/repo/tree/fs.js +27 -1
  721. package/dist/core/repo/tree/fs.js.map +1 -1
  722. package/dist/core/repo/tree/render/body.js +258 -0
  723. package/dist/core/repo/tree/render/body.js.map +1 -0
  724. package/dist/core/repo/tree/render/projects.js +182 -0
  725. package/dist/core/repo/tree/render/projects.js.map +1 -0
  726. package/dist/core/repo/tree/render/service/header.js +125 -0
  727. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  728. package/dist/core/repo/tree/render/service/peers.js +28 -0
  729. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  730. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  731. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  732. package/dist/core/repo/tree/render/service/views.js +78 -0
  733. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  734. package/dist/core/repo/tree/render/stale.js +42 -0
  735. package/dist/core/repo/tree/render/stale.js.map +1 -0
  736. package/dist/core/repo/tree/render/text.js +84 -0
  737. package/dist/core/repo/tree/render/text.js.map +1 -0
  738. package/dist/core/repo/tree/render/views.js +293 -0
  739. package/dist/core/repo/tree/render/views.js.map +1 -0
  740. package/dist/core/repo/tree/walk.js +53 -32
  741. package/dist/core/repo/tree/walk.js.map +1 -1
  742. package/dist/core/results.js +47 -12
  743. package/dist/core/results.js.map +1 -1
  744. package/dist/core/scaffold/landscape.js +121 -13
  745. package/dist/core/scaffold/landscape.js.map +1 -1
  746. package/dist/core/scaffold/legacy.js +271 -0
  747. package/dist/core/scaffold/legacy.js.map +1 -0
  748. package/dist/core/staging/commit.js +26 -15
  749. package/dist/core/staging/commit.js.map +1 -1
  750. package/dist/core/staging/lock.js +74 -12
  751. package/dist/core/staging/lock.js.map +1 -1
  752. package/dist/core/staging/recovery/intent.js +2 -5
  753. package/dist/core/staging/recovery/intent.js.map +1 -1
  754. package/dist/core/staging/recovery/recover.js +84 -6
  755. package/dist/core/staging/recovery/recover.js.map +1 -1
  756. package/dist/core/staging/snapshot/manifest.js +188 -0
  757. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  758. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
  759. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  760. package/dist/core/staging/txn/forward.js +24 -14
  761. package/dist/core/staging/txn/forward.js.map +1 -1
  762. package/dist/core/staging/writes.js.map +1 -1
  763. package/dist/core/status/actions/execution.js +11 -0
  764. package/dist/core/status/actions/execution.js.map +1 -1
  765. package/dist/core/status/contracts.js +39 -15
  766. package/dist/core/status/contracts.js.map +1 -1
  767. package/dist/core/status/feature/artifacts.js +33 -8
  768. package/dist/core/status/feature/artifacts.js.map +1 -1
  769. package/dist/core/status/feature/feature.js +24 -17
  770. package/dist/core/status/feature/feature.js.map +1 -1
  771. package/dist/core/status/feature/next.js +20 -7
  772. package/dist/core/status/feature/next.js.map +1 -1
  773. package/dist/core/status/feature/words.js +96 -0
  774. package/dist/core/status/feature/words.js.map +1 -0
  775. package/dist/core/status/fleet/fleet.js +43 -14
  776. package/dist/core/status/fleet/fleet.js.map +1 -1
  777. package/dist/core/status/fleet/next.js +153 -58
  778. package/dist/core/status/fleet/next.js.map +1 -1
  779. package/dist/core/status/report.js.map +1 -1
  780. package/dist/core/status/scan.js.map +1 -1
  781. package/dist/core/status/verification.js +30 -15
  782. package/dist/core/status/verification.js.map +1 -1
  783. package/dist/core/tasks/packet.js +199 -0
  784. package/dist/core/tasks/packet.js.map +1 -0
  785. package/dist/core/tasks/record.js +43 -0
  786. package/dist/core/tasks/record.js.map +1 -0
  787. package/dist/core/tasks/sources.js +181 -0
  788. package/dist/core/tasks/sources.js.map +1 -0
  789. package/dist/core/tasks/stamps.js +76 -0
  790. package/dist/core/tasks/stamps.js.map +1 -0
  791. package/dist/core/tasks/unit.js +0 -0
  792. package/dist/core/tasks/unit.js.map +1 -0
  793. package/dist/core/usecases/capability.js +11 -14
  794. package/dist/core/usecases/capability.js.map +1 -1
  795. package/dist/core/usecases/delta/claims.js +31 -7
  796. package/dist/core/usecases/delta/claims.js.map +1 -1
  797. package/dist/core/usecases/delta/flows.js +5 -7
  798. package/dist/core/usecases/delta/flows.js.map +1 -1
  799. package/dist/core/usecases/delta/modified.js +315 -0
  800. package/dist/core/usecases/delta/modified.js.map +1 -0
  801. package/dist/core/usecases/delta/overlay.js +32 -191
  802. package/dist/core/usecases/delta/overlay.js.map +1 -1
  803. package/dist/core/usecases/fleet.js +195 -36
  804. package/dist/core/usecases/fleet.js.map +1 -1
  805. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
  806. package/dist/core/usecases/grade/capability-tag.js.map +1 -0
  807. package/dist/core/usecases/grade/feature.js +88 -0
  808. package/dist/core/usecases/grade/feature.js.map +1 -0
  809. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
  810. package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
  811. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
  812. package/dist/core/usecases/grade/steps.js.map +1 -0
  813. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
  814. package/dist/core/usecases/grade/usecases.js.map +1 -0
  815. package/dist/core/usecases/operations.js +26 -14
  816. package/dist/core/usecases/operations.js.map +1 -1
  817. package/dist/core/usecases/place.js.map +1 -1
  818. package/dist/core/usecases/service/exclude.js +119 -0
  819. package/dist/core/usecases/service/exclude.js.map +1 -0
  820. package/dist/core/usecases/service/flows.js +164 -0
  821. package/dist/core/usecases/service/flows.js.map +1 -0
  822. package/dist/core/usecases/suites/participants.js +83 -0
  823. package/dist/core/usecases/suites/participants.js.map +1 -0
  824. package/dist/core/usecases/suites/suites.js +45 -0
  825. package/dist/core/usecases/suites/suites.js.map +1 -0
  826. package/dist/core/verify/answers.js +12 -5
  827. package/dist/core/verify/answers.js.map +1 -1
  828. package/dist/core/verify/build.js +102 -82
  829. package/dist/core/verify/build.js.map +1 -1
  830. package/dist/core/verify/checklist.js +70 -12
  831. package/dist/core/verify/checklist.js.map +1 -1
  832. package/dist/core/verify/location.js +67 -0
  833. package/dist/core/verify/location.js.map +1 -0
  834. package/dist/core/verify/pins/lint.js +3 -2
  835. package/dist/core/verify/pins/lint.js.map +1 -1
  836. package/dist/core/verify/pins/pin.js +1 -1
  837. package/dist/core/verify/pins/pin.js.map +1 -1
  838. package/dist/core/verify/read/file.js +224 -0
  839. package/dist/core/verify/read/file.js.map +1 -0
  840. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  841. package/dist/core/verify/read/shape.js.map +1 -0
  842. package/dist/core/verify/read/union.js +105 -0
  843. package/dist/core/verify/read/union.js.map +1 -0
  844. package/dist/core/verify/record.js +35 -12
  845. package/dist/core/verify/record.js.map +1 -1
  846. package/dist/core/verify/store/commit.js +40 -30
  847. package/dist/core/verify/store/commit.js.map +1 -1
  848. package/dist/core/verify/store/render.js +8 -4
  849. package/dist/core/verify/store/render.js.map +1 -1
  850. package/dist/core/vocabulary/issue.js +4 -0
  851. package/dist/core/vocabulary/issue.js.map +1 -1
  852. package/dist/core/vocabulary/maturity.js +75 -4
  853. package/dist/core/vocabulary/maturity.js.map +1 -1
  854. package/dist/core/vocabulary/report.js +16 -1
  855. package/dist/core/vocabulary/report.js.map +1 -1
  856. package/examples/README.md +54 -11
  857. package/examples/docs/architecture/landscape.likec4 +62 -6
  858. package/examples/docs/architecture/services.likec4 +70 -0
  859. package/examples/docs/architecture/subsystems.likec4 +7 -0
  860. package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
  861. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  862. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  863. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
  864. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  865. package/examples/docs/likec4.config.json +0 -1
  866. package/examples/docs/services/checkout-web/model.likec4 +39 -56
  867. package/examples/docs/services/order-service/arch.spec.md +13 -6
  868. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  869. package/examples/docs/services/order-service/model.likec4 +58 -70
  870. package/examples/docs/services/payment-service/arch.spec.md +7 -4
  871. package/examples/docs/services/payment-service/model.likec4 +38 -37
  872. package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
  873. package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
  874. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  875. package/examples/docs/services/platform/notification-service/health.yaml +8 -3
  876. package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
  877. package/package.json +1 -1
  878. package/dist/commands/validate/checks/vocabulary.js +0 -31
  879. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  880. package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
  881. package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
  882. package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
  883. package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
  884. package/dist/core/agent/agents-md/artifacts.js +0 -220
  885. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  886. package/dist/core/agent/agents-md/cycle.js +0 -165
  887. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  888. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  889. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  890. package/dist/core/agent/agents-md/spine.js +0 -40
  891. package/dist/core/agent/agents-md/spine.js.map +0 -1
  892. package/dist/core/coherence/events/merged.js +0 -40
  893. package/dist/core/coherence/events/merged.js.map +0 -1
  894. package/dist/core/diff/current-state.js +0 -96
  895. package/dist/core/diff/current-state.js.map +0 -1
  896. package/dist/core/diff/victims.js +0 -247
  897. package/dist/core/diff/victims.js.map +0 -1
  898. package/dist/core/repo/tree/views.js +0 -172
  899. package/dist/core/repo/tree/views.js.map +0 -1
  900. package/dist/core/staging/snapshot.js.map +0 -1
  901. package/dist/core/verify/file.js.map +0 -1
@@ -10,11 +10,6 @@
10
10
  * is what reads it.
11
11
  */
12
12
  import { existsSync } from "node:fs";
13
- import { readFile } from "node:fs/promises";
14
- import { createHash } from "node:crypto";
15
- import { parse as parseYaml } from "yaml";
16
- import { resolveInside } from "../../../core/kernel/path-safety.js";
17
- import { canonicalJson } from "../../../core/openapi/digest.js";
18
13
  import { closeIds } from "../../../core/c4/arch.js";
19
14
  import {} from "../../../core/c4/likec4.js";
20
15
  import {} from "../../../core/kernel/ids/service.js";
@@ -24,15 +19,19 @@ import { responseGovernanceFindings } from "../checks/requirements.js";
24
19
  import {} from "../../../core/document/spec.js";
25
20
  import { readOpenapi } from "../../../core/openapi/doc.js";
26
21
  import { FleetContext } from "../../../core/fleet-context.js";
22
+ import { repoPath } from "../../../core/envelope/json.js";
23
+ import {} from "../../../core/kernel/ids/dirs.js";
27
24
  export async function apiAxisFindings(axis) {
28
- const { service, me, paths, reqs, livingReqs, land, landSvcOf, fleet } = axis;
25
+ const { docsDir, service, me, paths, reqs, livingReqs, land, landSvcOf, fleet } = axis;
29
26
  const findings = [];
30
27
  // API coverage: every operation in openapi.yaml is governed by a requirement.
31
28
  const api = await readOpenapi(paths.openapi, fleet);
32
29
  const removeMarkers = api.ops.filter((o) => o.remove);
33
30
  const liveOps = api.ops.filter((o) => !o.remove);
34
- const ops = liveOps.map((o) => o.id);
35
- const deprecatedOps = new Set(liveOps.filter((o) => o.deprecated).map((o) => o.id));
31
+ /** No contract to read: deleted, renamed, or never written. */
32
+ const contractMissing = !existsSync(paths.openapi);
33
+ const contract = contractOf(api, contractMissing);
34
+ const { ops, deprecated: deprecatedOps } = contract;
36
35
  // Inbound calls the landscape can PROVE: op-linked edges whose target
37
36
  // resolves to this service. Null when the landscape proves nothing at all
38
37
  // (absent, or it did not parse) — which is not the same fact as "nobody
@@ -43,8 +42,6 @@ export async function apiAxisFindings(axis) {
43
42
  // The other half of the evidence a contract is owed: the living spec's own
44
43
  // `Operations:` lines. A requirement on its way out governs nothing.
45
44
  const governedOps = livingReqs.flatMap((r) => r.operations);
46
- /** No contract to read: deleted, renamed, or never written. */
47
- const contractMissing = !existsSync(paths.openapi);
48
45
  if (contractMissing) {
49
46
  // A contract that is gone is not a reason to stop grading the API axis —
50
47
  // it IS the axis's answer, and every check below used to live inside the
@@ -67,7 +64,7 @@ export async function apiAxisFindings(axis) {
67
64
  severity: "error",
68
65
  code: "service.no-openapi",
69
66
  subject: service,
70
- message: `No OpenAPI contract at ${paths.openapi}, and ${dangling.length} operation link(s) already point into it — ` +
67
+ message: `No OpenAPI contract at ${repoPath(docsDir, paths.openapi)}, and ${dangling.length} operation link(s) already point into it — ` +
71
68
  `every requirement and landscape edge naming one of them resolves to nothing until the file is back`,
72
69
  details: dangling,
73
70
  });
@@ -76,7 +73,7 @@ export async function apiAxisFindings(axis) {
76
73
  findings.push({
77
74
  severity: "warn",
78
75
  code: "service.no-openapi",
79
- message: `No OpenAPI contract at ${paths.openapi} — API coverage and the landscape spine are unchecked`,
76
+ message: `No OpenAPI contract at ${repoPath(docsDir, paths.openapi)} — API coverage and the landscape spine are unchecked`,
80
77
  });
81
78
  }
82
79
  }
@@ -236,103 +233,20 @@ export async function apiAxisFindings(axis) {
236
233
  }
237
234
  }
238
235
  }
239
- return {
240
- findings,
241
- contract: { ops, deprecated: deprecatedOps, unreadable: api.unreadable || contractMissing },
242
- };
236
+ return { findings, contract };
243
237
  }
244
238
  /**
245
- * The build's own contract document, compared with the copy the docs repo
246
- * committed — `openapi.generated-stale`, `contracts.source-missing`,
247
- * `contracts.source-invalid`.
248
- *
249
- * WHY THIS IS NOT AN EXTRACTOR. It reads no line of service code and derives no
250
- * meaning: it parses a standard document the team's own build emitted, at a
251
- * path a human wrote into `loam.json`, and compares a digest. That is the same
252
- * category as `verify --results` ingesting a cucumber report, and it is the
253
- * only thing in the product that can notice the assumption SCHEMA quietly makes
254
- * — that the committed `services/<id>/openapi.yaml` is the contract the service
255
- * actually serves. Most fleets generate that document; a fleet that copies it
256
- * by hand in month one is a fleet whose spine checks grade last quarter's
257
- * endpoints by month six, and until now nothing could say so.
258
- *
259
- * IT NEVER WRITES. The copy stays a human `cp`, reviewed in a pull request,
260
- * because that is what makes the docs-repo document a contract somebody agreed
261
- * to rather than a cache of whatever the build last produced. loam reports the
262
- * divergence and stops.
263
- *
264
- * THE DIGEST IS OVER CANONICAL JSON, never raw bytes. Two generator versions
265
- * that order keys differently, or a YAML dumper that re-wraps a description,
266
- * produce identical documents and must not produce a permanent warning nobody
267
- * can clear — the lesson `requirementDigest` already records one package over.
268
- *
269
- * Runs only in the SERVICE repo. From the docs repo there is no build output to
270
- * read and the whole family stays silent, exactly as `sources` provenance does.
239
+ * What the spine may conclude from one contract read — spelled once, because a
240
+ * `--base` run now grades a changed edge against a provider it does not grade
241
+ * as a target (`../scope.ts`), and two spellings of "which operations are
242
+ * live" would be two chances for a removal marker to count as defined.
271
243
  */
272
- export async function generatedContractFindings(service, repoDir, spelled, committedPath) {
273
- if (spelled === undefined)
274
- return [];
275
- let generatedPath;
276
- try {
277
- // Containment is checked HERE rather than at config load, with the service
278
- // in hand so the refusal can name it — and a path that escapes the repo is
279
- // a finding, never a thrown config error that would take down every other
280
- // command in the repo.
281
- generatedPath = resolveInside(repoDir, spelled, `"contracts.openapi"`);
282
- }
283
- catch (err) {
284
- return [{
285
- severity: "error",
286
- code: "contracts.source-invalid",
287
- subject: service,
288
- message: `${service}: loam.json names contracts.openapi '${spelled}', which does not resolve inside this repository — ` +
289
- `${err instanceof Error ? err.message : String(err)}`,
290
- }];
291
- }
292
- if (!existsSync(generatedPath)) {
293
- return [{
294
- severity: "error",
295
- code: "contracts.source-missing",
296
- subject: service,
297
- message: `${service}: loam.json names contracts.openapi '${spelled}', and nothing is there. ` +
298
- "Either the build has not run in this checkout, or the path is wrong — CI that validates before it builds " +
299
- "sees exactly this. Run the build first, or correct the path",
300
- }];
301
- }
302
- const digests = [];
303
- for (const [label, path] of [["generated", generatedPath], ["committed", committedPath]]) {
304
- if (label === "committed" && !existsSync(path))
305
- return [];
306
- let parsed;
307
- try {
308
- parsed = parseYaml(await readFile(path, "utf8"));
309
- }
310
- catch (err) {
311
- // An unreadable GENERATED document is this check's business; an
312
- // unreadable committed one is already `openapi.invalid` and must not be
313
- // reported twice under two codes.
314
- if (label === "committed")
315
- return [];
316
- return [{
317
- severity: "error",
318
- code: "contracts.source-invalid",
319
- subject: service,
320
- message: `${service}: contracts.openapi '${spelled}' does not parse as YAML or JSON — ` +
321
- `${err instanceof Error ? err.message : String(err)}`,
322
- }];
323
- }
324
- digests.push(createHash("sha256").update(canonicalJson(parsed), "utf8").digest("hex"));
325
- }
326
- if (digests[0] === digests[1])
327
- return [];
328
- return [{
329
- severity: "warn",
330
- code: "openapi.generated-stale",
331
- subject: service,
332
- message: `${service}: the committed openapi.yaml differs from what this repository's build produces (${spelled}). ` +
333
- "The docs-repo copy is what every operation join is graded against, so the fleet is being checked against a " +
334
- "contract this service no longer serves. Review the difference and copy the generated document across — " +
335
- "loam never does that for you, because the committed contract is one somebody agreed to, not a cache of the build",
336
- }];
244
+ export function contractOf(api, missing) {
245
+ const live = api.ops.filter((o) => !o.remove);
246
+ return {
247
+ ops: live.map((o) => o.id),
248
+ deprecated: new Set(live.filter((o) => o.deprecated).map((o) => o.id)),
249
+ unreadable: api.unreadable || missing,
250
+ };
337
251
  }
338
252
  //# sourceMappingURL=api.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"api.js","sourceRoot":"","sources":["../../../../src/commands/validate/service/api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAkB,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAA8C,MAAM,qCAAqC,CAAC;AACjG,OAAO,EAAqB,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAgB,MAAM,oCAAoC,CAAC;AAClE,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAoB,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAkC9D,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,IAAa;IAEb,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;IAC9E,MAAM,QAAQ,GAAc,EAAE,CAAC;IAE/B,8EAA8E;IAC9E,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACpD,MAAM,aAAa,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACtD,MAAM,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACjD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACrC,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACpF,sEAAsE;IACtE,0EAA0E;IAC1E,wEAAwE;IACxE,mDAAmD;IACnD,MAAM,UAAU,GACd,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;QACrC,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,SAAS,IAAI,SAAU,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;IAC1F,2EAA2E;IAC3E,qEAAqE;IACrE,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IAC5D,+DAA+D;IAC/D,MAAM,eAAe,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACnD,IAAI,eAAe,EAAE,CAAC;QACpB,yEAAyE;QACzE,yEAAyE;QACzE,2EAA2E;QAC3E,iEAAiE;QACjE,EAAE;QACF,wEAAwE;QACxE,uEAAuE;QACvE,wEAAwE;QACxE,yEAAyE;QACzE,uEAAuE;QACvE,2EAA2E;QAC3E,oCAAoC;QACpC,EAAE;QACF,wEAAwE;QACxE,8CAA8C;QAC9C,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,WAAW,EAAE,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAChG,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,oBAAoB;gBAC1B,OAAO,EAAE,OAAO;gBAChB,OAAO,EACL,0BAA0B,KAAK,CAAC,OAAO,SAAS,QAAQ,CAAC,MAAM,6CAA6C;oBAC5G,oGAAoG;gBACtG,OAAO,EAAE,QAAQ;aAClB,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;YAC/B,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,MAAM;gBAChB,IAAI,EAAE,oBAAoB;gBAC1B,OAAO,EAAE,0BAA0B,KAAK,CAAC,OAAO,uDAAuD;aACxG,CAAC,CAAC;QACL,CAAC;IACH,CAAC;SAAM,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;QAC1B,4EAA4E;QAC5E,uEAAuE;QACvE,2EAA2E;QAC3E,2EAA2E;QAC3E,iEAAiE;QACjE,oEAAoE;QACpE,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,OAAO;YACjB,IAAI,EAAE,iBAAiB;YACvB,OAAO,EAAE,GAAG,OAAO,oFAAoF;YACvG,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;SAC7D,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,8BAA8B;gBACpC,OAAO,EAAE,GAAG,OAAO,kCAAkC,aAAa,CAAC,MAAM,6BAA6B,aAAa,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,sDAAsD;aACxM,CAAC,CAAC;QACL,CAAC;QACD,2EAA2E;QAC3E,4EAA4E;QAC5E,4EAA4E;QAC5E,yEAAyE;QACzE,iEAAiE;QACjE,0EAA0E;QAC1E,0EAA0E;QAC1E,sDAAsD;QACtD,KAAK,MAAM,OAAO,IAAI,GAAG,CAAC,iBAAiB,EAAE,CAAC;YAC5C,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,kCAAkC;gBACxC,OAAO,EAAE,OAAO;gBAChB,OAAO,EACL,GAAG,OAAO,iEAAiE,OAAO,MAAM;oBACxF,mHAAmH;oBACnH,2JAA2J;aAC9J,CAAC,CAAC;QACL,CAAC;QACD,yEAAyE;QACzE,yEAAyE;QACzE,2EAA2E;QAC3E,wEAAwE;QACxE,0EAA0E;QAC1E,6DAA6D;QAC7D,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,YAAY,EAAE,CAAC;YAClC,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,MAAM,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;YAC1F,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,MAAM;gBAChB,IAAI,EAAE,+BAA+B;gBACrC,OAAO,EAAE,OAAO;gBAChB,OAAO,EAAE,GAAG,OAAO,6CAA6C,EAAE,QAAQ,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,8IAA8I;aAC5O,CAAC,CAAC;QACL,CAAC;QACD,kEAAkE;QAClE,uEAAuE;QACvE,yEAAyE;QACzE,uDAAuD;QACvD,yEAAyE;QACzE,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,WAAW,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACvF,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3B,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,MAAM;gBAChB,IAAI,EAAE,8BAA8B;gBACpC,OAAO,EAAE,OAAO;gBAChB,OAAO,EACL,GAAG,OAAO,KAAK,WAAW,CAAC,MAAM,6CAA6C,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM;oBAC1G,6FAA6F;oBAC7F,2FAA2F;aAC9F,CAAC,CAAC;QACL,CAAC;QACD,IAAI,GAAG,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,MAAM;gBAChB,IAAI,EAAE,wBAAwB;gBAC9B,OAAO,EAAE,OAAO;gBAChB,OAAO,EACL,GAAG,OAAO,2BAA2B,GAAG,CAAC,YAAY,CAAC,MAAM,oCAAoC;oBAChG,wFAAwF;oBACxF,4EAA4E;gBAC9E,OAAO,EAAE,GAAG,CAAC,YAAY;aAC1B,CAAC,CAAC;QACL,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC7B,6EAA6E;QAC7E,wEAAwE;QACxE,6EAA6E;QAC7E,0EAA0E;QAC1E,yEAAyE;QACzE,2EAA2E;QAC3E,4EAA4E;QAC5E,kCAAkC;QAClC,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;YAC3B,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC;gBAC9B,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;oBAAE,SAAS;gBAC9B,MAAM,KAAK,GAAG,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;gBAChC,QAAQ,CAAC,IAAI,CAAC;oBACZ,QAAQ,EAAE,OAAO;oBACjB,IAAI,EAAE,uBAAuB;oBAC7B,OAAO,EAAE,OAAO;oBAChB,OAAO,EACL,GAAG,OAAO,kBAAkB,CAAC,CAAC,IAAI,cAAc,EAAE,qBAAqB,OAAO,YAAY;wBAC1F,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,mBAAmB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;iBACnE,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;YAClE,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;YACtD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACzB,QAAQ,CAAC,IAAI,CAAC;oBACZ,QAAQ,EAAE,IAAI;oBACd,IAAI,EAAE,aAAa;oBACnB,kEAAkE;oBAClE,qEAAqE;oBACrE,sEAAsE;oBACtE,gBAAgB;oBAChB,OAAO,EAAE,GAAG,OAAO,kBAAkB,GAAG,CAAC,MAAM,4CAA4C,UAAU,CAAC,MAAM,yBAAyB;iBACtI,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,QAAQ,CAAC,IAAI,CAAC;oBACZ,QAAQ,EAAE,MAAM;oBAChB,IAAI,EAAE,gBAAgB;oBACtB,OAAO,EAAE,GAAG,OAAO,KAAK,OAAO,CAAC,MAAM,mDAAmD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;iBAC9G,CAAC,CAAC;YACL,CAAC;YACD,QAAQ,CAAC,IAAI,CAAC,GAAG,0BAA0B,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;YAC3E,sEAAsE;YACtE,qEAAqE;YACrE,wEAAwE;YACxE,2EAA2E;YAC3E,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;gBACpE,QAAQ,CAAC,IAAI,CAAC;oBACZ,QAAQ,EAAE,MAAM;oBAChB,IAAI,EAAE,kBAAkB;oBACxB,OAAO,EAAE,GAAG,OAAO,0BAA0B,GAAG,CAAC,MAAM,yFAAyF;iBACjJ,CAAC,CAAC;YACL,CAAC;YACD,qEAAqE;YACrE,oEAAoE;YACpE,2EAA2E;YAC3E,4EAA4E;YAC5E,qGAAqG;YACrG,kEAAkE;YAClE,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;gBACrB,MAAM,QAAQ,GAAG,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC9D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;oBAAE,SAAS;gBACtF,QAAQ,CAAC,IAAI,CAAC;oBACZ,QAAQ,EAAE,MAAM;oBAChB,IAAI,EAAE,4BAA4B;oBAClC,OAAO,EAAE,GAAG,OAAO,kBAAkB,CAAC,CAAC,IAAI,2CAA2C,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,yGAAyG;iBACnN,CAAC,CAAC;YACL,CAAC;QACD,CAAC;IACH,CAAC;IAED,OAAO;QACL,QAAQ;QACR,QAAQ,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,IAAI,eAAe,EAAE;KAC5F,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,OAAe,EACf,OAAe,EACf,OAA2B,EAC3B,aAAqB;IAErB,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACrC,IAAI,aAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,2EAA2E;QAC3E,2EAA2E;QAC3E,0EAA0E;QAC1E,uBAAuB;QACvB,aAAa,GAAG,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,qBAAqB,CAAC,CAAC;IACzE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC;gBACN,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,0BAA0B;gBAChC,OAAO,EAAE,OAAO;gBAChB,OAAO,EACL,GAAG,OAAO,wCAAwC,OAAO,qDAAqD;oBAC9G,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;aACxD,CAAC,CAAC;IACL,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC;gBACN,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,0BAA0B;gBAChC,OAAO,EAAE,OAAO;gBAChB,OAAO,EACL,GAAG,OAAO,wCAAwC,OAAO,2BAA2B;oBACpF,2GAA2G;oBAC3G,6DAA6D;aAChE,CAAC,CAAC;IACL,CAAC;IACD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,aAAa,CAAC,EAAE,CAAC,WAAW,EAAE,aAAa,CAAC,CAAU,EAAE,CAAC;QAClG,IAAI,KAAK,KAAK,WAAW,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QAC1D,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,SAAS,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QACnD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,gEAAgE;YAChE,wEAAwE;YACxE,kCAAkC;YAClC,IAAI,KAAK,KAAK,WAAW;gBAAE,OAAO,EAAE,CAAC;YACrC,OAAO,CAAC;oBACN,QAAQ,EAAE,OAAO;oBACjB,IAAI,EAAE,0BAA0B;oBAChC,OAAO,EAAE,OAAO;oBAChB,OAAO,EACL,GAAG,OAAO,wBAAwB,OAAO,qCAAqC;wBAC9E,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;iBACxD,CAAC,CAAC;QACL,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACzF,CAAC;IACD,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IACzC,OAAO,CAAC;YACN,QAAQ,EAAE,MAAM;YAChB,IAAI,EAAE,yBAAyB;YAC/B,OAAO,EAAE,OAAO;YAChB,OAAO,EACL,GAAG,OAAO,oFAAoF,OAAO,KAAK;gBAC1G,6GAA6G;gBAC7G,yGAAyG;gBACzG,kHAAkH;SACrH,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * The HTTP contract axis: openapi.yaml graded against the living spec.\n *\n * What it decides for everything downstream is whether there is a contract to\n * read at all. A missing or unparseable openapi.yaml is ONE finding about ONE\n * file, and that fact has to travel: grading the fleet map's inbound edges\n * against the empty operation set it leaves behind turns one broken YAML into\n * one `spine.op-undefined` per consumer, each of them accusing the landscape of\n * a fault that is not there. `ServiceContract` is how it travels; `./spine.js`\n * is what reads it.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { createHash } from \"node:crypto\";\nimport { parse as parseYaml } from \"yaml\";\nimport { resolveInside } from \"../../../core/kernel/path-safety.js\";\nimport { canonicalJson } from \"../../../core/openapi/digest.js\";\nimport { closeIds } from \"../../../core/c4/arch.js\";\nimport { type LoadedDoc } from \"../../../core/c4/likec4.js\";\nimport { type DeclaredService, type PathableService } from \"../../../core/kernel/ids/service.js\";\nimport { type ServicePaths } from \"../../../core/repo/paths.js\";\nimport { type Finding } from \"../../../core/vocabulary/report.js\";\nimport { responseGovernanceFindings } from \"../checks/requirements.js\";\nimport { type Requirement } from \"../../../core/document/spec.js\";\nimport { readOpenapi } from \"../../../core/openapi/doc.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\n\n/** What the service target hands the HTTP axis. */\nexport interface ApiAxis {\n service: PathableService;\n /** `service` widened for `===` against the resolver's DOCUMENT text — see service.ts. */\n me: string;\n paths: ServicePaths;\n /** Every requirement the living spec declares, REMOVED ones included. */\n reqs: Requirement[];\n /** The ones that still govern something. */\n livingReqs: Requirement[];\n /** The living landscape, or null when the repo has none. */\n land: LoadedDoc | null;\n /** Its element→service resolver, built once per run; null exactly when `land` is. */\n landSvcOf: ((id: string) => DeclaredService) | null;\n fleet?: FleetContext;\n}\n\n/**\n * What the spine may conclude from this service's contract.\n *\n * `unreadable` covers both shapes of \"there is nothing to read\" — absent and\n * unparseable — because they have the same consequence for an inbound edge:\n * the contract proves neither that the operation exists nor that it does not.\n */\nexport interface ServiceContract {\n /** operationIds the living contract defines, empty when there is nothing to read. */\n ops: string[];\n /** Those of them the contract marks `deprecated: true`. */\n deprecated: ReadonlySet<string>;\n unreadable: boolean;\n}\n\nexport async function apiAxisFindings(\n axis: ApiAxis,\n): Promise<{ findings: Finding[]; contract: ServiceContract }> {\n const { service, me, paths, reqs, livingReqs, land, landSvcOf, fleet } = axis;\n const findings: Finding[] = [];\n\n // API coverage: every operation in openapi.yaml is governed by a requirement.\n const api = await readOpenapi(paths.openapi, fleet);\n const removeMarkers = api.ops.filter((o) => o.remove);\n const liveOps = api.ops.filter((o) => !o.remove);\n const ops = liveOps.map((o) => o.id);\n const deprecatedOps = new Set(liveOps.filter((o) => o.deprecated).map((o) => o.id));\n // Inbound calls the landscape can PROVE: op-linked edges whose target\n // resolves to this service. Null when the landscape proves nothing at all\n // (absent, or it did not parse) — which is not the same fact as \"nobody\n // calls me\", and the two must not be graded alike.\n const inboundOps =\n land === null || land.errors.length > 0\n ? null\n : land.relationships.filter((r) => r.op !== undefined && landSvcOf!(r.target) === me);\n // The other half of the evidence a contract is owed: the living spec's own\n // `Operations:` lines. A requirement on its way out governs nothing.\n const governedOps = livingReqs.flatMap((r) => r.operations);\n /** No contract to read: deleted, renamed, or never written. */\n const contractMissing = !existsSync(paths.openapi);\n if (contractMissing) {\n // A contract that is gone is not a reason to stop grading the API axis —\n // it IS the axis's answer, and every check below used to live inside the\n // file-exists branch, so deleting or misspelling the file turned the whole\n // axis (including the documented `spec-api.op-undefined`) green.\n //\n // What decides the severity is whether anything already written down is\n // holding a join into it: a living `Operations:` line, or an op-linked\n // landscape edge. With one of those, the absence breaks a link somebody\n // authored — an error. With neither, this is a service that legitimately\n // has no HTTP surface (a worker nobody calls), and a fleet mid-rollout\n // must not go red for it — a warning, or, when the landscape PROVES nobody\n // calls it, the documented silence.\n //\n // One finding, not one per dangling link: the fix is a single file, and\n // the links it strands ride along as details.\n const dangling = [...new Set([...governedOps, ...(inboundOps ?? []).map((r) => r.op!)])].sort();\n if (dangling.length > 0) {\n findings.push({\n severity: \"error\",\n code: \"service.no-openapi\",\n subject: service,\n message:\n `No OpenAPI contract at ${paths.openapi}, and ${dangling.length} operation link(s) already point into it — ` +\n `every requirement and landscape edge naming one of them resolves to nothing until the file is back`,\n details: dangling,\n });\n } else if (inboundOps === null) {\n findings.push({\n severity: \"warn\",\n code: \"service.no-openapi\",\n message: `No OpenAPI contract at ${paths.openapi} — API coverage and the landscape spine are unchecked`,\n });\n }\n } else if (api.unreadable) {\n // A contract that EXISTS but does not read is a broken source of truth, not\n // an empty one: swallowing it into zero operations used to grade every\n // inbound landscape edge `spine.op-undefined` — a false diagnosis pointing\n // at the landscape when the truth was this file. So the file is the error,\n // and every check that reads the contract (api.*, the spine's op\n // resolution) is suspended below, the landscape.invalid discipline.\n findings.push({\n severity: \"error\",\n code: \"openapi.invalid\",\n message: `${service}: openapi.yaml does not parse — API coverage and the landscape spine are unchecked`,\n ...(api.error === undefined ? {} : { details: [api.error] }),\n });\n } else {\n if (removeMarkers.length > 0) {\n findings.push({\n severity: \"error\",\n code: \"openapi.remove-marker-living\",\n message: `${service}: living openapi.yaml contains ${removeMarkers.length} x-loam-remove marker(s) (${removeMarkers.map((op) => op.id).join(\", \")}) — removal markers are valid only in feature deltas`,\n });\n }\n // The same marker written at PATH level, beside the methods. `readOpenapi`\n // is keyed by (path, method), which is precisely why the check above cannot\n // see this one and precisely how one reached a living contract in the first\n // place: it addresses no operation, so it retired nothing, and it stayed\n // invisible afterwards. Error, like its method-level sibling — a\n // feature-only key published to every consumer of the fleet, and one that\n // keeps the empty-path cleanup from ever firing. Same code as the archive\n // plan's: one breach, one name, wherever it is found.\n for (const removed of api.pathLevelRemovals) {\n findings.push({\n severity: \"error\",\n code: \"openapi.remove-marker-path-level\",\n subject: service,\n message:\n `${service}: living openapi.yaml carries x-loam-remove at PATH level on '${removed}' — ` +\n `a removal marker names ONE operation, so beside the methods it retires nothing and is not a contract key either, ` +\n `and no id-keyed check can see it. Delete it from the living contract; retire an operation through a feature delta whose marker sits inside the operation.`,\n });\n }\n // Two slots claiming one operationId in a LIVING contract. `readOpenapi`\n // has computed this since the merge needed it, and only the feature path\n // (coherence) ever read it — so the ambiguity was reachable only when some\n // unrelated feature happened to carry a delta for this service, and the\n // fleet gate that actually runs in CI was blind to it. Same code and same\n // sentence as the feature-scope check: one breach, one name.\n for (const id of api.duplicateIds) {\n const slots = api.ops.filter((op) => op.id === id).map((op) => `${op.method} ${op.path}`);\n findings.push({\n severity: \"warn\",\n code: \"openapi.duplicate-operationid\",\n subject: service,\n message: `${service}: the living OpenAPI defines operationId '${id}' at ${slots.join(\" and \")} — every join on the id (a requirement's Operations: line, an edge's metadata { op }, a removal marker) picks one of those slots arbitrarily`,\n });\n }\n // The contract-depth probes. Form validated and depth did not: an\n // operation whose one response is `description: OK`, and a `$ref` to a\n // schema nobody wrote, both read as cleanly as a complete contract while\n // a reader still cannot rebuild its payloads. Presence\n // probes over the document's own keys; nothing reads what a schema says.\n const undescribed = liveOps.filter((op) => op.undescribed === true).map((op) => op.id);\n if (undescribed.length > 0) {\n findings.push({\n severity: \"warn\",\n code: \"openapi.response-undescribed\",\n subject: service,\n message:\n `${service}: ${undescribed.length} operation(s) declare no response schema (${undescribed.join(\", \")}) — ` +\n `the contract names the operation and says nothing about what it returns, so a reader still ` +\n `needs the code; declare content with a schema on at least one response (204/304 excepted)`,\n });\n }\n if (api.danglingRefs.length > 0) {\n findings.push({\n severity: \"warn\",\n code: \"openapi.ref-unresolved\",\n subject: service,\n message:\n `${service}: openapi.yaml contains ${api.danglingRefs.length} internal $ref(s) that resolve to ` +\n `nothing in the document — the operation validates while its schema is silently absent ` +\n `(the archive plan refuses the same breach in a feature delta, as an error)`,\n details: api.danglingRefs,\n });\n }\n const defined = new Set(ops);\n // `Operations:` on a LIVING requirement, resolved against this service's own\n // contract. Nothing did this before: the same spine is checked inside a\n // feature delta (coherence's spec-api.op-undefined) and then never again, so\n // a typo that shipped, or an operation later renamed out of openapi.yaml,\n // left a living requirement governing an operation that does not exist —\n // green forever, and every downstream join through that id silently empty.\n // Same code and severity as the feature-scope check on purpose: one breach,\n // one name, wherever it is found.\n for (const r of livingReqs) {\n for (const op of r.operations) {\n if (defined.has(op)) continue;\n const close = closeIds(op, ops);\n findings.push({\n severity: \"error\",\n code: \"spec-api.op-undefined\",\n subject: service,\n message:\n `${service}: requirement '${r.name}' governs '${op}', not defined in ${service}'s OpenAPI` +\n (close.length > 0 ? `. Did you mean: ${close.join(\", \")}?` : \"\"),\n });\n }\n }\n if (ops.length > 0) {\n const governed = new Set(livingReqs.flatMap((r) => r.operations));\n const orphans = ops.filter((op) => !governed.has(op));\n if (orphans.length === 0) {\n findings.push({\n severity: \"ok\",\n code: \"api.covered\",\n // Both counts on purpose: the ratio is the one signal that lets a\n // reviewer smell a thin baseline from the summary — forty behaviours\n // filed under one requirement passes every check, and \"covered\" alone\n // read as done.\n message: `${service}: API covered (${ops.length} operation(s) governed by requirements · ${livingReqs.length} living requirement(s))`,\n });\n } else {\n findings.push({\n severity: \"warn\",\n code: \"api.ungoverned\",\n message: `${service}: ${orphans.length} operation(s) not governed by any requirement — ${orphans.join(\", \")}`,\n });\n }\n findings.push(...responseGovernanceFindings(service, liveOps, livingReqs));\n // The migration-debt case: requirements exist, the API exists, and no\n // `Operations:` line ties them — every cross-axis check above and in\n // feature mode is vacuously green. Once per service, not per operation;\n // with zero requirements the spec (or its absence) is the finding instead.\n if (reqs.length > 0 && reqs.every((r) => r.operations.length === 0)) {\n findings.push({\n severity: \"warn\",\n code: \"api.ops-unlinked\",\n message: `${service}: openapi.yaml defines ${ops.length} operation(s) but no requirement links any — the API axis is unchecked for this service`,\n });\n }\n // Lifecycle: a requirement whose `Operations:` list resolves ONLY to\n // deprecated operations governs behaviour the contract is retiring.\n // Deprecation is the documented first step of removing an op; the explicit\n // feature marker is the final step. Until that delta archives, the op stays\n // live, so the fix is migration or a coordinated retirement. Ops the contract does not define at all\n // prove nothing here and are left to spec-api.op-undefined above.\n for (const r of reqs) {\n const resolved = r.operations.filter((op) => defined.has(op));\n if (resolved.length === 0 || !resolved.every((op) => deprecatedOps.has(op))) continue;\n findings.push({\n severity: \"warn\",\n code: \"api.requirement-deprecated\",\n message: `${service}: requirement '${r.name}' governs only deprecated operation(s) (${resolved.join(\", \")}) — the behaviour it describes is on its way out; migrate it to the replacement operation, or retire it`,\n });\n }\n }\n }\n\n return {\n findings,\n contract: { ops, deprecated: deprecatedOps, unreadable: api.unreadable || contractMissing },\n };\n}\n\n/**\n * The build's own contract document, compared with the copy the docs repo\n * committed — `openapi.generated-stale`, `contracts.source-missing`,\n * `contracts.source-invalid`.\n *\n * WHY THIS IS NOT AN EXTRACTOR. It reads no line of service code and derives no\n * meaning: it parses a standard document the team's own build emitted, at a\n * path a human wrote into `loam.json`, and compares a digest. That is the same\n * category as `verify --results` ingesting a cucumber report, and it is the\n * only thing in the product that can notice the assumption SCHEMA quietly makes\n * — that the committed `services/<id>/openapi.yaml` is the contract the service\n * actually serves. Most fleets generate that document; a fleet that copies it\n * by hand in month one is a fleet whose spine checks grade last quarter's\n * endpoints by month six, and until now nothing could say so.\n *\n * IT NEVER WRITES. The copy stays a human `cp`, reviewed in a pull request,\n * because that is what makes the docs-repo document a contract somebody agreed\n * to rather than a cache of whatever the build last produced. loam reports the\n * divergence and stops.\n *\n * THE DIGEST IS OVER CANONICAL JSON, never raw bytes. Two generator versions\n * that order keys differently, or a YAML dumper that re-wraps a description,\n * produce identical documents and must not produce a permanent warning nobody\n * can clear — the lesson `requirementDigest` already records one package over.\n *\n * Runs only in the SERVICE repo. From the docs repo there is no build output to\n * read and the whole family stays silent, exactly as `sources` provenance does.\n */\nexport async function generatedContractFindings(\n service: string,\n repoDir: string,\n spelled: string | undefined,\n committedPath: string,\n): Promise<Finding[]> {\n if (spelled === undefined) return [];\n let generatedPath: string;\n try {\n // Containment is checked HERE rather than at config load, with the service\n // in hand so the refusal can name it — and a path that escapes the repo is\n // a finding, never a thrown config error that would take down every other\n // command in the repo.\n generatedPath = resolveInside(repoDir, spelled, `\"contracts.openapi\"`);\n } catch (err) {\n return [{\n severity: \"error\",\n code: \"contracts.source-invalid\",\n subject: service,\n message:\n `${service}: loam.json names contracts.openapi '${spelled}', which does not resolve inside this repository — ` +\n `${err instanceof Error ? err.message : String(err)}`,\n }];\n }\n if (!existsSync(generatedPath)) {\n return [{\n severity: \"error\",\n code: \"contracts.source-missing\",\n subject: service,\n message:\n `${service}: loam.json names contracts.openapi '${spelled}', and nothing is there. ` +\n \"Either the build has not run in this checkout, or the path is wrong — CI that validates before it builds \" +\n \"sees exactly this. Run the build first, or correct the path\",\n }];\n }\n const digests: string[] = [];\n for (const [label, path] of [[\"generated\", generatedPath], [\"committed\", committedPath]] as const) {\n if (label === \"committed\" && !existsSync(path)) return [];\n let parsed: unknown;\n try {\n parsed = parseYaml(await readFile(path, \"utf8\"));\n } catch (err) {\n // An unreadable GENERATED document is this check's business; an\n // unreadable committed one is already `openapi.invalid` and must not be\n // reported twice under two codes.\n if (label === \"committed\") return [];\n return [{\n severity: \"error\",\n code: \"contracts.source-invalid\",\n subject: service,\n message:\n `${service}: contracts.openapi '${spelled}' does not parse as YAML or JSON — ` +\n `${err instanceof Error ? err.message : String(err)}`,\n }];\n }\n digests.push(createHash(\"sha256\").update(canonicalJson(parsed), \"utf8\").digest(\"hex\"));\n }\n if (digests[0] === digests[1]) return [];\n return [{\n severity: \"warn\",\n code: \"openapi.generated-stale\",\n subject: service,\n message:\n `${service}: the committed openapi.yaml differs from what this repository's build produces (${spelled}). ` +\n \"The docs-repo copy is what every operation join is graded against, so the fleet is being checked against a \" +\n \"contract this service no longer serves. Review the difference and copy the generated document across — \" +\n \"loam never does that for you, because the committed contract is one somebody agreed to, not a cache of the build\",\n }];\n}\n"]}
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../../../src/commands/validate/service/api.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The HTTP contract axis: openapi.yaml graded against the living spec.\n *\n * What it decides for everything downstream is whether there is a contract to\n * read at all. A missing or unparseable openapi.yaml is ONE finding about ONE\n * file, and that fact has to travel: grading the fleet map's inbound edges\n * against the empty operation set it leaves behind turns one broken YAML into\n * one `spine.op-undefined` per consumer, each of them accusing the landscape of\n * a fault that is not there. `ServiceContract` is how it travels; `./spine.js`\n * is what reads it.\n */\nimport { existsSync } from \"node:fs\";\nimport { closeIds } from \"../../../core/c4/arch.js\";\nimport { type LoadedDoc } from \"../../../core/c4/likec4.js\";\nimport { type DeclaredService, type PathableService } from \"../../../core/kernel/ids/service.js\";\nimport { type ServicePaths } from \"../../../core/repo/paths.js\";\nimport { type Finding } from \"../../../core/vocabulary/report.js\";\nimport { responseGovernanceFindings } from \"../checks/requirements.js\";\nimport { type Requirement } from \"../../../core/document/spec.js\";\nimport { readOpenapi, type OpenapiDoc } from \"../../../core/openapi/doc.js\";\nimport { FleetContext } from \"../../../core/fleet-context.js\";\nimport { repoPath } from \"../../../core/envelope/json.js\";\nimport { type DocsDir } from \"../../../core/kernel/ids/dirs.js\";\n\n/** What the service target hands the HTTP axis. */\nexport interface ApiAxis {\n /** The docs root, so a message names the contract by its repo-relative path. */\n docsDir: DocsDir;\n service: PathableService;\n /** `service` widened for `===` against the resolver's DOCUMENT text — see service.ts. */\n me: string;\n paths: ServicePaths;\n /** Every requirement the living spec declares, REMOVED ones included. */\n reqs: Requirement[];\n /** The ones that still govern something. */\n livingReqs: Requirement[];\n /** The living landscape, or null when the repo has none. */\n land: LoadedDoc | null;\n /** Its element→service resolver, built once per run; null exactly when `land` is. */\n landSvcOf: ((id: string) => DeclaredService) | null;\n fleet: FleetContext;\n}\n\n/**\n * What the spine may conclude from this service's contract.\n *\n * `unreadable` covers both shapes of \"there is nothing to read\" — absent and\n * unparseable — because they have the same consequence for an inbound edge:\n * the contract proves neither that the operation exists nor that it does not.\n */\nexport interface ServiceContract {\n /** operationIds the living contract defines, empty when there is nothing to read. */\n ops: string[];\n /** Those of them the contract marks `deprecated: true`. */\n deprecated: ReadonlySet<string>;\n unreadable: boolean;\n}\n\nexport async function apiAxisFindings(\n axis: ApiAxis,\n): Promise<{ findings: Finding[]; contract: ServiceContract }> {\n const { docsDir, service, me, paths, reqs, livingReqs, land, landSvcOf, fleet } = axis;\n const findings: Finding[] = [];\n\n // API coverage: every operation in openapi.yaml is governed by a requirement.\n const api = await readOpenapi(paths.openapi, fleet);\n const removeMarkers = api.ops.filter((o) => o.remove);\n const liveOps = api.ops.filter((o) => !o.remove);\n /** No contract to read: deleted, renamed, or never written. */\n const contractMissing = !existsSync(paths.openapi);\n const contract = contractOf(api, contractMissing);\n const { ops, deprecated: deprecatedOps } = contract;\n // Inbound calls the landscape can PROVE: op-linked edges whose target\n // resolves to this service. Null when the landscape proves nothing at all\n // (absent, or it did not parse) — which is not the same fact as \"nobody\n // calls me\", and the two must not be graded alike.\n const inboundOps =\n land === null || land.errors.length > 0\n ? null\n : land.relationships.filter((r) => r.op !== undefined && landSvcOf!(r.target) === me);\n // The other half of the evidence a contract is owed: the living spec's own\n // `Operations:` lines. A requirement on its way out governs nothing.\n const governedOps = livingReqs.flatMap((r) => r.operations);\n if (contractMissing) {\n // A contract that is gone is not a reason to stop grading the API axis —\n // it IS the axis's answer, and every check below used to live inside the\n // file-exists branch, so deleting or misspelling the file turned the whole\n // axis (including the documented `spec-api.op-undefined`) green.\n //\n // What decides the severity is whether anything already written down is\n // holding a join into it: a living `Operations:` line, or an op-linked\n // landscape edge. With one of those, the absence breaks a link somebody\n // authored — an error. With neither, this is a service that legitimately\n // has no HTTP surface (a worker nobody calls), and a fleet mid-rollout\n // must not go red for it — a warning, or, when the landscape PROVES nobody\n // calls it, the documented silence.\n //\n // One finding, not one per dangling link: the fix is a single file, and\n // the links it strands ride along as details.\n const dangling = [...new Set([...governedOps, ...(inboundOps ?? []).map((r) => r.op!)])].sort();\n if (dangling.length > 0) {\n findings.push({\n severity: \"error\",\n code: \"service.no-openapi\",\n subject: service,\n message:\n `No OpenAPI contract at ${repoPath(docsDir, paths.openapi)}, and ${dangling.length} operation link(s) already point into it — ` +\n `every requirement and landscape edge naming one of them resolves to nothing until the file is back`,\n details: dangling,\n });\n } else if (inboundOps === null) {\n findings.push({\n severity: \"warn\",\n code: \"service.no-openapi\",\n message: `No OpenAPI contract at ${repoPath(docsDir, paths.openapi)} — API coverage and the landscape spine are unchecked`,\n });\n }\n } else if (api.unreadable) {\n // A contract that EXISTS but does not read is a broken source of truth, not\n // an empty one: swallowing it into zero operations used to grade every\n // inbound landscape edge `spine.op-undefined` — a false diagnosis pointing\n // at the landscape when the truth was this file. So the file is the error,\n // and every check that reads the contract (api.*, the spine's op\n // resolution) is suspended below, the landscape.invalid discipline.\n findings.push({\n severity: \"error\",\n code: \"openapi.invalid\",\n message: `${service}: openapi.yaml does not parse — API coverage and the landscape spine are unchecked`,\n ...(api.error === undefined ? {} : { details: [api.error] }),\n });\n } else {\n if (removeMarkers.length > 0) {\n findings.push({\n severity: \"error\",\n code: \"openapi.remove-marker-living\",\n message: `${service}: living openapi.yaml contains ${removeMarkers.length} x-loam-remove marker(s) (${removeMarkers.map((op) => op.id).join(\", \")}) — removal markers are valid only in feature deltas`,\n });\n }\n // The same marker written at PATH level, beside the methods. `readOpenapi`\n // is keyed by (path, method), which is precisely why the check above cannot\n // see this one and precisely how one reached a living contract in the first\n // place: it addresses no operation, so it retired nothing, and it stayed\n // invisible afterwards. Error, like its method-level sibling — a\n // feature-only key published to every consumer of the fleet, and one that\n // keeps the empty-path cleanup from ever firing. Same code as the archive\n // plan's: one breach, one name, wherever it is found.\n for (const removed of api.pathLevelRemovals) {\n findings.push({\n severity: \"error\",\n code: \"openapi.remove-marker-path-level\",\n subject: service,\n message:\n `${service}: living openapi.yaml carries x-loam-remove at PATH level on '${removed}' — ` +\n `a removal marker names ONE operation, so beside the methods it retires nothing and is not a contract key either, ` +\n `and no id-keyed check can see it. Delete it from the living contract; retire an operation through a feature delta whose marker sits inside the operation.`,\n });\n }\n // Two slots claiming one operationId in a LIVING contract. `readOpenapi`\n // has computed this since the merge needed it, and only the feature path\n // (coherence) ever read it — so the ambiguity was reachable only when some\n // unrelated feature happened to carry a delta for this service, and the\n // fleet gate that actually runs in CI was blind to it. Same code and same\n // sentence as the feature-scope check: one breach, one name.\n for (const id of api.duplicateIds) {\n const slots = api.ops.filter((op) => op.id === id).map((op) => `${op.method} ${op.path}`);\n findings.push({\n severity: \"warn\",\n code: \"openapi.duplicate-operationid\",\n subject: service,\n message: `${service}: the living OpenAPI defines operationId '${id}' at ${slots.join(\" and \")} — every join on the id (a requirement's Operations: line, an edge's metadata { op }, a removal marker) picks one of those slots arbitrarily`,\n });\n }\n // The contract-depth probes. Form validated and depth did not: an\n // operation whose one response is `description: OK`, and a `$ref` to a\n // schema nobody wrote, both read as cleanly as a complete contract while\n // a reader still cannot rebuild its payloads. Presence\n // probes over the document's own keys; nothing reads what a schema says.\n const undescribed = liveOps.filter((op) => op.undescribed === true).map((op) => op.id);\n if (undescribed.length > 0) {\n findings.push({\n severity: \"warn\",\n code: \"openapi.response-undescribed\",\n subject: service,\n message:\n `${service}: ${undescribed.length} operation(s) declare no response schema (${undescribed.join(\", \")}) — ` +\n `the contract names the operation and says nothing about what it returns, so a reader still ` +\n `needs the code; declare content with a schema on at least one response (204/304 excepted)`,\n });\n }\n if (api.danglingRefs.length > 0) {\n findings.push({\n severity: \"warn\",\n code: \"openapi.ref-unresolved\",\n subject: service,\n message:\n `${service}: openapi.yaml contains ${api.danglingRefs.length} internal $ref(s) that resolve to ` +\n `nothing in the document — the operation validates while its schema is silently absent ` +\n `(the archive plan refuses the same breach in a feature delta, as an error)`,\n details: api.danglingRefs,\n });\n }\n const defined = new Set(ops);\n // `Operations:` on a LIVING requirement, resolved against this service's own\n // contract. Nothing did this before: the same spine is checked inside a\n // feature delta (coherence's spec-api.op-undefined) and then never again, so\n // a typo that shipped, or an operation later renamed out of openapi.yaml,\n // left a living requirement governing an operation that does not exist —\n // green forever, and every downstream join through that id silently empty.\n // Same code and severity as the feature-scope check on purpose: one breach,\n // one name, wherever it is found.\n for (const r of livingReqs) {\n for (const op of r.operations) {\n if (defined.has(op)) continue;\n const close = closeIds(op, ops);\n findings.push({\n severity: \"error\",\n code: \"spec-api.op-undefined\",\n subject: service,\n message:\n `${service}: requirement '${r.name}' governs '${op}', not defined in ${service}'s OpenAPI` +\n (close.length > 0 ? `. Did you mean: ${close.join(\", \")}?` : \"\"),\n });\n }\n }\n if (ops.length > 0) {\n const governed = new Set(livingReqs.flatMap((r) => r.operations));\n const orphans = ops.filter((op) => !governed.has(op));\n if (orphans.length === 0) {\n findings.push({\n severity: \"ok\",\n code: \"api.covered\",\n // Both counts on purpose: the ratio is the one signal that lets a\n // reviewer smell a thin baseline from the summary — forty behaviours\n // filed under one requirement passes every check, and \"covered\" alone\n // read as done.\n message: `${service}: API covered (${ops.length} operation(s) governed by requirements · ${livingReqs.length} living requirement(s))`,\n });\n } else {\n findings.push({\n severity: \"warn\",\n code: \"api.ungoverned\",\n message: `${service}: ${orphans.length} operation(s) not governed by any requirement — ${orphans.join(\", \")}`,\n });\n }\n findings.push(...responseGovernanceFindings(service, liveOps, livingReqs));\n // The migration-debt case: requirements exist, the API exists, and no\n // `Operations:` line ties them — every cross-axis check above and in\n // feature mode is vacuously green. Once per service, not per operation;\n // with zero requirements the spec (or its absence) is the finding instead.\n if (reqs.length > 0 && reqs.every((r) => r.operations.length === 0)) {\n findings.push({\n severity: \"warn\",\n code: \"api.ops-unlinked\",\n message: `${service}: openapi.yaml defines ${ops.length} operation(s) but no requirement links any — the API axis is unchecked for this service`,\n });\n }\n // Lifecycle: a requirement whose `Operations:` list resolves ONLY to\n // deprecated operations governs behaviour the contract is retiring.\n // Deprecation is the documented first step of removing an op; the explicit\n // feature marker is the final step. Until that delta archives, the op stays\n // live, so the fix is migration or a coordinated retirement. Ops the contract does not define at all\n // prove nothing here and are left to spec-api.op-undefined above.\n for (const r of reqs) {\n const resolved = r.operations.filter((op) => defined.has(op));\n if (resolved.length === 0 || !resolved.every((op) => deprecatedOps.has(op))) continue;\n findings.push({\n severity: \"warn\",\n code: \"api.requirement-deprecated\",\n message: `${service}: requirement '${r.name}' governs only deprecated operation(s) (${resolved.join(\", \")}) — the behaviour it describes is on its way out; migrate it to the replacement operation, or retire it`,\n });\n }\n }\n }\n\n return { findings, contract };\n}\n\n/**\n * What the spine may conclude from one contract read — spelled once, because a\n * `--base` run now grades a changed edge against a provider it does not grade\n * as a target (`../scope.ts`), and two spellings of \"which operations are\n * live\" would be two chances for a removal marker to count as defined.\n */\nexport function contractOf(api: OpenapiDoc, missing: boolean): ServiceContract {\n const live = api.ops.filter((o) => !o.remove);\n return {\n ops: live.map((o) => o.id),\n deprecated: new Set(live.filter((o) => o.deprecated).map((o) => o.id)),\n unreadable: api.unreadable || missing,\n };\n}\n"]}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * The generated-contract check: the build's own OpenAPI document against the
3
+ * copy the docs repo committed.
4
+ *
5
+ * It sits beside the HTTP axis (`../api.ts`) rather than inside it because it
6
+ * reads a different document for a different question. The axis grades the
7
+ * committed contract against the living spec; this asks whether that committed
8
+ * contract is still the one the service serves, and it runs only in a service
9
+ * repo. They used to share one file, and naming the operationIds that moved
10
+ * pushed it over the line limit.
11
+ */
12
+ import { existsSync } from "node:fs";
13
+ import { readFile } from "node:fs/promises";
14
+ import { createHash } from "node:crypto";
15
+ import { parse as parseYaml } from "yaml";
16
+ import { resolveInside } from "../../../../core/kernel/path-safety.js";
17
+ import { canonicalJson } from "../../../../core/openapi/digest.js";
18
+ import { openapiFromText } from "../../../../core/openapi/doc.js";
19
+ import {} from "../../../../core/vocabulary/report.js";
20
+ /**
21
+ * The build's own contract document, compared with the copy the docs repo
22
+ * committed — `openapi.generated-stale`, `contracts.source-missing`,
23
+ * `contracts.source-invalid`.
24
+ *
25
+ * WHY THIS IS NOT AN EXTRACTOR. It reads no line of service code and derives no
26
+ * meaning: it parses a standard document the team's own build emitted, at a
27
+ * path a human wrote into `loam.json`, and compares a digest. That is the same
28
+ * category as `verify --results` ingesting a cucumber report, and it is the
29
+ * only thing in the product that can notice the assumption SCHEMA quietly makes
30
+ * — that the committed `services/<id>/openapi.yaml` is the contract the service
31
+ * actually serves. Most fleets generate that document; a fleet that copies it
32
+ * by hand in month one is a fleet whose spine checks grade last quarter's
33
+ * endpoints by month six, and until now nothing could say so.
34
+ *
35
+ * IT NEVER WRITES. The copy stays a human `cp`, reviewed in a pull request,
36
+ * because that is what makes the docs-repo document a contract somebody agreed
37
+ * to rather than a cache of whatever the build last produced. loam reports the
38
+ * divergence and stops.
39
+ *
40
+ * THE DIGEST IS OVER CANONICAL JSON, never raw bytes. Two generator versions
41
+ * that order keys differently, or a YAML dumper that re-wraps a description,
42
+ * produce identical documents and must not produce a permanent warning nobody
43
+ * can clear — the lesson `requirementDigest` (core/document/spec.ts) already records.
44
+ *
45
+ * THE DIGEST DECIDES, THE operationId SETS EXPLAIN. A reworded description and
46
+ * 29 vanished endpoints were one identical warning, each costing a manual diff
47
+ * to tell apart, so a fired warning names the set difference both ways (or says
48
+ * the sets agree). The sets never fire it on their own: the digest still does.
49
+ *
50
+ * Runs only in the SERVICE repo. From the docs repo there is no build output to
51
+ * read and the whole family stays silent, exactly as `sources` provenance does.
52
+ */
53
+ export async function generatedContractFindings(service, repoDir, spelled, committedPath) {
54
+ if (spelled === undefined)
55
+ return [];
56
+ let generatedPath;
57
+ try {
58
+ // Containment is checked HERE rather than at config load, with the service
59
+ // in hand so the refusal can name it — and a path that escapes the repo is
60
+ // a finding, never a thrown config error that would take down every other
61
+ // command in the repo.
62
+ generatedPath = resolveInside(repoDir, spelled, `"contracts.openapi"`);
63
+ }
64
+ catch (err) {
65
+ return [{
66
+ severity: "error",
67
+ code: "contracts.source-invalid",
68
+ subject: service,
69
+ message: `${service}: loam.json names contracts.openapi '${spelled}', which does not resolve inside this repository — ` +
70
+ `${err instanceof Error ? err.message : String(err)}`,
71
+ }];
72
+ }
73
+ if (!existsSync(generatedPath)) {
74
+ return [{
75
+ severity: "error",
76
+ code: "contracts.source-missing",
77
+ subject: service,
78
+ message: `${service}: loam.json names contracts.openapi '${spelled}', and nothing is there. ` +
79
+ "Either the build has not run in this checkout, or the path is wrong — CI that validates before it builds " +
80
+ "sees exactly this. Run the build first, or correct the path",
81
+ }];
82
+ }
83
+ const digests = [];
84
+ const idSets = [];
85
+ for (const [label, path] of [["generated", generatedPath], ["committed", committedPath]]) {
86
+ if (label === "committed" && !existsSync(path))
87
+ return [];
88
+ let parsed, text;
89
+ try {
90
+ text = await readFile(path, "utf8");
91
+ parsed = parseYaml(text);
92
+ }
93
+ catch (err) {
94
+ // An unreadable GENERATED document is this check's business; an
95
+ // unreadable committed one is already `openapi.invalid` and must not be
96
+ // reported twice under two codes.
97
+ if (label === "committed")
98
+ return [];
99
+ return [{
100
+ severity: "error",
101
+ code: "contracts.source-invalid",
102
+ subject: service,
103
+ message: `${service}: contracts.openapi '${spelled}' does not parse as YAML or JSON — ` +
104
+ `${err instanceof Error ? err.message : String(err)}`,
105
+ }];
106
+ }
107
+ digests.push(createHash("sha256").update(canonicalJson(parsed), "utf8").digest("hex"));
108
+ // The walk every operation join uses: "served" means what a spine check sees.
109
+ idSets.push(new Set(openapiFromText(text).ops.map((op) => op.id)));
110
+ }
111
+ if (digests[0] === digests[1])
112
+ return [];
113
+ const [served, committed] = idSets;
114
+ const added = [...served].filter((id) => !committed.has(id)).sort();
115
+ const removed = [...committed].filter((id) => !served.has(id)).sort();
116
+ const details = [
117
+ ...added.map((id) => `served, not in the committed copy: ${id}`),
118
+ ...removed.map((id) => `committed, no longer served: ${id}`),
119
+ ];
120
+ const moved = details.length === 0
121
+ ? "Every operationId matches, so what moved is inside an operation or outside `paths`. "
122
+ : `${added.length} operation(s) served but not committed, ${removed.length} committed but no longer served (details). `;
123
+ return [{
124
+ severity: "warn",
125
+ code: "openapi.generated-stale",
126
+ subject: service,
127
+ message: `${service}: the committed openapi.yaml differs from what this repository's build produces (${spelled}). ${moved}` +
128
+ "The docs-repo copy is what every operation join is graded against, so the fleet is being checked against a " +
129
+ "contract this service no longer serves. Review the difference and copy the generated document across — " +
130
+ "loam never does that for you, because the committed contract is one somebody agreed to, not a cache of the build",
131
+ ...(details.length === 0 ? {} : { details }),
132
+ }];
133
+ }
134
+ //# sourceMappingURL=generated.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generated.js","sourceRoot":"","sources":["../../../../../src/commands/validate/service/contracts/generated.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAAgB,MAAM,uCAAuC,CAAC;AAErE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,OAAe,EACf,OAAe,EACf,OAA2B,EAC3B,aAAqB;IAErB,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACrC,IAAI,aAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,2EAA2E;QAC3E,2EAA2E;QAC3E,0EAA0E;QAC1E,uBAAuB;QACvB,aAAa,GAAG,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,qBAAqB,CAAC,CAAC;IACzE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC;gBACN,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,0BAA0B;gBAChC,OAAO,EAAE,OAAO;gBAChB,OAAO,EACL,GAAG,OAAO,wCAAwC,OAAO,qDAAqD;oBAC9G,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;aACxD,CAAC,CAAC;IACL,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC;gBACN,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,0BAA0B;gBAChC,OAAO,EAAE,OAAO;gBAChB,OAAO,EACL,GAAG,OAAO,wCAAwC,OAAO,2BAA2B;oBACpF,2GAA2G;oBAC3G,6DAA6D;aAChE,CAAC,CAAC;IACL,CAAC;IACD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAkB,EAAE,CAAC;IACjC,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,aAAa,CAAC,EAAE,CAAC,WAAW,EAAE,aAAa,CAAC,CAAU,EAAE,CAAC;QAClG,IAAI,KAAK,KAAK,WAAW,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QAC1D,IAAI,MAAe,EAAE,IAAY,CAAC;QAClC,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YACpC,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,gEAAgE;YAChE,wEAAwE;YACxE,kCAAkC;YAClC,IAAI,KAAK,KAAK,WAAW;gBAAE,OAAO,EAAE,CAAC;YACrC,OAAO,CAAC;oBACN,QAAQ,EAAE,OAAO;oBACjB,IAAI,EAAE,0BAA0B;oBAChC,OAAO,EAAE,OAAO;oBAChB,OAAO,EACL,GAAG,OAAO,wBAAwB,OAAO,qCAAqC;wBAC9E,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;iBACxD,CAAC,CAAC;QACL,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACvF,8EAA8E;QAC9E,MAAM,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACrE,CAAC;IACD,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IACzC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,MAAoC,CAAC;IACjE,MAAM,KAAK,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACpE,MAAM,OAAO,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACtE,MAAM,OAAO,GAAG;QACd,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,sCAAsC,EAAE,EAAE,CAAC;QAChE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,gCAAgC,EAAE,EAAE,CAAC;KAC7D,CAAC;IACF,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC;QAChC,CAAC,CAAC,sFAAsF;QACxF,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,2CAA2C,OAAO,CAAC,MAAM,6CAA6C,CAAC;IAC1H,OAAO,CAAC;YACN,QAAQ,EAAE,MAAM;YAChB,IAAI,EAAE,yBAAyB;YAC/B,OAAO,EAAE,OAAO;YAChB,OAAO,EACL,GAAG,OAAO,oFAAoF,OAAO,MAAM,KAAK,EAAE;gBAClH,6GAA6G;gBAC7G,yGAAyG;gBACzG,kHAAkH;YACpH,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;SAC7C,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * The generated-contract check: the build's own OpenAPI document against the\n * copy the docs repo committed.\n *\n * It sits beside the HTTP axis (`../api.ts`) rather than inside it because it\n * reads a different document for a different question. The axis grades the\n * committed contract against the living spec; this asks whether that committed\n * contract is still the one the service serves, and it runs only in a service\n * repo. They used to share one file, and naming the operationIds that moved\n * pushed it over the line limit.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { createHash } from \"node:crypto\";\nimport { parse as parseYaml } from \"yaml\";\nimport { resolveInside } from \"../../../../core/kernel/path-safety.js\";\nimport { canonicalJson } from \"../../../../core/openapi/digest.js\";\nimport { openapiFromText } from \"../../../../core/openapi/doc.js\";\nimport { type Finding } from \"../../../../core/vocabulary/report.js\";\n\n/**\n * The build's own contract document, compared with the copy the docs repo\n * committed — `openapi.generated-stale`, `contracts.source-missing`,\n * `contracts.source-invalid`.\n *\n * WHY THIS IS NOT AN EXTRACTOR. It reads no line of service code and derives no\n * meaning: it parses a standard document the team's own build emitted, at a\n * path a human wrote into `loam.json`, and compares a digest. That is the same\n * category as `verify --results` ingesting a cucumber report, and it is the\n * only thing in the product that can notice the assumption SCHEMA quietly makes\n * — that the committed `services/<id>/openapi.yaml` is the contract the service\n * actually serves. Most fleets generate that document; a fleet that copies it\n * by hand in month one is a fleet whose spine checks grade last quarter's\n * endpoints by month six, and until now nothing could say so.\n *\n * IT NEVER WRITES. The copy stays a human `cp`, reviewed in a pull request,\n * because that is what makes the docs-repo document a contract somebody agreed\n * to rather than a cache of whatever the build last produced. loam reports the\n * divergence and stops.\n *\n * THE DIGEST IS OVER CANONICAL JSON, never raw bytes. Two generator versions\n * that order keys differently, or a YAML dumper that re-wraps a description,\n * produce identical documents and must not produce a permanent warning nobody\n * can clear — the lesson `requirementDigest` (core/document/spec.ts) already records.\n *\n * THE DIGEST DECIDES, THE operationId SETS EXPLAIN. A reworded description and\n * 29 vanished endpoints were one identical warning, each costing a manual diff\n * to tell apart, so a fired warning names the set difference both ways (or says\n * the sets agree). The sets never fire it on their own: the digest still does.\n *\n * Runs only in the SERVICE repo. From the docs repo there is no build output to\n * read and the whole family stays silent, exactly as `sources` provenance does.\n */\nexport async function generatedContractFindings(\n service: string,\n repoDir: string,\n spelled: string | undefined,\n committedPath: string,\n): Promise<Finding[]> {\n if (spelled === undefined) return [];\n let generatedPath: string;\n try {\n // Containment is checked HERE rather than at config load, with the service\n // in hand so the refusal can name it — and a path that escapes the repo is\n // a finding, never a thrown config error that would take down every other\n // command in the repo.\n generatedPath = resolveInside(repoDir, spelled, `\"contracts.openapi\"`);\n } catch (err) {\n return [{\n severity: \"error\",\n code: \"contracts.source-invalid\",\n subject: service,\n message:\n `${service}: loam.json names contracts.openapi '${spelled}', which does not resolve inside this repository — ` +\n `${err instanceof Error ? err.message : String(err)}`,\n }];\n }\n if (!existsSync(generatedPath)) {\n return [{\n severity: \"error\",\n code: \"contracts.source-missing\",\n subject: service,\n message:\n `${service}: loam.json names contracts.openapi '${spelled}', and nothing is there. ` +\n \"Either the build has not run in this checkout, or the path is wrong — CI that validates before it builds \" +\n \"sees exactly this. Run the build first, or correct the path\",\n }];\n }\n const digests: string[] = [];\n const idSets: Set<string>[] = [];\n for (const [label, path] of [[\"generated\", generatedPath], [\"committed\", committedPath]] as const) {\n if (label === \"committed\" && !existsSync(path)) return [];\n let parsed: unknown, text: string;\n try {\n text = await readFile(path, \"utf8\");\n parsed = parseYaml(text);\n } catch (err) {\n // An unreadable GENERATED document is this check's business; an\n // unreadable committed one is already `openapi.invalid` and must not be\n // reported twice under two codes.\n if (label === \"committed\") return [];\n return [{\n severity: \"error\",\n code: \"contracts.source-invalid\",\n subject: service,\n message:\n `${service}: contracts.openapi '${spelled}' does not parse as YAML or JSON — ` +\n `${err instanceof Error ? err.message : String(err)}`,\n }];\n }\n digests.push(createHash(\"sha256\").update(canonicalJson(parsed), \"utf8\").digest(\"hex\"));\n // The walk every operation join uses: \"served\" means what a spine check sees.\n idSets.push(new Set(openapiFromText(text).ops.map((op) => op.id)));\n }\n if (digests[0] === digests[1]) return [];\n const [served, committed] = idSets as [Set<string>, Set<string>];\n const added = [...served].filter((id) => !committed.has(id)).sort();\n const removed = [...committed].filter((id) => !served.has(id)).sort();\n const details = [\n ...added.map((id) => `served, not in the committed copy: ${id}`),\n ...removed.map((id) => `committed, no longer served: ${id}`),\n ];\n const moved = details.length === 0\n ? \"Every operationId matches, so what moved is inside an operation or outside `paths`. \"\n : `${added.length} operation(s) served but not committed, ${removed.length} committed but no longer served (details). `;\n return [{\n severity: \"warn\",\n code: \"openapi.generated-stale\",\n subject: service,\n message:\n `${service}: the committed openapi.yaml differs from what this repository's build produces (${spelled}). ${moved}` +\n \"The docs-repo copy is what every operation join is graded against, so the fleet is being checked against a \" +\n \"contract this service no longer serves. Review the difference and copy the generated document across — \" +\n \"loam never does that for you, because the committed contract is one somebody agreed to, not a cache of the build\",\n ...(details.length === 0 ? {} : { details }),\n }];\n}\n"]}
@@ -15,11 +15,13 @@
15
15
  import { existsSync } from "node:fs";
16
16
  import {} from "../../../../core/kernel/ids/service.js";
17
17
  import {} from "../../../../core/c4/likec4.js";
18
+ import { eventKeys, infrastructureKind, unboundSentence } from "../../../../core/c4/direction/direction.js";
18
19
  import {} from "../../../../core/repo/paths.js";
19
20
  import {} from "../../../../core/vocabulary/report.js";
20
21
  import {} from "../../../../core/document/spec.js";
21
22
  import { readAsyncapi, slotsOf } from "../../../../core/asyncapi/read.js";
22
23
  import { FleetContext } from "../../../../core/fleet-context.js";
24
+ import { repoPath } from "../../../../core/envelope/json.js";
23
25
  import { producerFindings } from "./message-producers.js";
24
26
  export async function eventAxisFindings(axis) {
25
27
  const { docsDir, service, me, paths, reqs, livingReqs, archReqs, land, landSvcOf, known, fleet } = axis;
@@ -33,20 +35,40 @@ export async function eventAxisFindings(axis) {
33
35
  const receivedHere = new Set(events.received);
34
36
  // Every edge that binds THIS service to a message: `publishes` binds the
35
37
  // edge's source (a service produces what it declares an `action: send` for),
36
- // `consumes` binds its target. One edge may legitimately carry both — a relay
37
- // that reads one topic and writes another is a single arrow in most fleet maps.
38
- const eventEdges = land === null || land.errors.length > 0
38
+ // `consumes` binds its target — `eventKeys` is where that rule is written,
39
+ // once, for this scope and the feature gate alike. One edge may legitimately
40
+ // carry both: a producer drawn straight to its consumer is a single arrow.
41
+ const readable = land !== null && land.errors.length === 0 && landSvcOf !== null;
42
+ const eventEdges = !readable
39
43
  ? []
40
- : land.relationships.flatMap((r) => {
41
- const bound = [];
42
- if (r.publishes !== undefined && landSvcOf(r.source) === me) {
43
- bound.push({ direction: "publishes", message: r.publishes, other: landSvcOf(r.target) });
44
+ : land.relationships.flatMap((r) => eventKeys(r)
45
+ .filter((k) => landSvcOf(k.bound) === me)
46
+ .map((k) => ({ direction: k.direction, message: k.message, other: landSvcOf(k.other) })));
47
+ // The edges the filter above drops that THIS service drew the wrong way
48
+ // round: a key bound to infrastructure (a topic, which neither publishes nor
49
+ // consumes) while this service sits at the other end. It used to fall out of
50
+ // `eventEdges` without a word, so a consume edge drawn from the consumer to
51
+ // the topic graded nothing and said nothing. A key bound to an `#external`
52
+ // system is NOT this case — an outside producer or consumer, which the
53
+ // drawing states correctly (core/c4/direction/direction.ts says why). Raised on
54
+ // the service at the unbound end, because it is the one with a drawing to fix.
55
+ const knownIds = known;
56
+ if (readable) {
57
+ const infraKindOf = infrastructureKind(land.elements);
58
+ for (const r of land.relationships) {
59
+ for (const key of eventKeys(r)) {
60
+ const kind = infraKindOf(key.bound);
61
+ if (kind === undefined || landSvcOf(key.other) !== me || knownIds.has(landSvcOf(key.bound)))
62
+ continue;
63
+ findings.push({
64
+ severity: "warn",
65
+ code: "spine.message-unbound",
66
+ subject: service,
67
+ message: `${service}: landscape ${unboundSentence(r, key, kind)}`,
68
+ });
44
69
  }
45
- if (r.consumes !== undefined && landSvcOf(r.target) === me) {
46
- bound.push({ direction: "consumes", message: r.consumes, other: landSvcOf(r.source) });
47
- }
48
- return bound;
49
- });
70
+ }
71
+ }
50
72
  // Both requirement namespaces, and a REMOVED requirement links nothing —
51
73
  // the `livingReqs` rule the API axis already follows, applied to the pair.
52
74
  const eventLinks = [...livingReqs, ...archReqs.filter((r) => r.kind !== "REMOVED")].flatMap((r) => [
@@ -71,7 +93,7 @@ export async function eventAxisFindings(axis) {
71
93
  severity: "error",
72
94
  code: "service.no-asyncapi",
73
95
  subject: service,
74
- message: `No async contract at ${paths.asyncapi}, and ${dangling.length} message link(s) already point into it — ` +
96
+ message: `No async contract at ${repoPath(docsDir, paths.asyncapi)}, and ${dangling.length} message link(s) already point into it — ` +
75
97
  `every landscape edge and requirement naming one of them resolves to nothing until the file is back`,
76
98
  details: dangling,
77
99
  });
@@ -86,7 +108,7 @@ export async function eventAxisFindings(axis) {
86
108
  severity: "error",
87
109
  code: "asyncapi.invalid",
88
110
  subject: service,
89
- message: `${service}: asyncapi.yaml does not parse — the event spine is unchecked for this service`,
111
+ message: `${service}: asyncapi.yaml does not read as AsyncAPI 3.0 (it does not parse, or it declares an older version) — the event spine is unchecked for this service`,
90
112
  ...(events.error === undefined ? {} : { details: [events.error] }),
91
113
  });
92
114
  }