@ybotok/loam 0.1.0-beta.3 → 0.2.0-alpha.2

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 (1036) hide show
  1. package/CHANGELOG.md +2635 -86
  2. package/COMPARISON.md +80 -63
  3. package/CONTRIBUTING.md +84 -0
  4. package/MIGRATING-from-OpenSpec.md +213 -54
  5. package/QUICKSTART.md +125 -0
  6. package/README.md +450 -150
  7. package/ROADMAP.md +599 -0
  8. package/SCHEMA.md +2355 -256
  9. package/SECURITY.md +21 -3
  10. package/WORKFLOW.md +701 -0
  11. package/dist/cli.js +113 -19
  12. package/dist/cli.js.map +1 -1
  13. package/dist/commands/adopt/adopt.js +209 -0
  14. package/dist/commands/adopt/adopt.js.map +1 -0
  15. package/dist/commands/adopt/render.js +268 -0
  16. package/dist/commands/adopt/render.js.map +1 -0
  17. package/dist/commands/archive/archive.js +77 -0
  18. package/dist/commands/archive/archive.js.map +1 -0
  19. package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
  20. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
  21. package/dist/commands/archive/plan/contracts/openapi.js +159 -0
  22. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
  23. package/dist/commands/archive/plan/gate.js +323 -0
  24. package/dist/commands/archive/plan/gate.js.map +1 -0
  25. package/dist/commands/archive/plan/landscape.js +180 -0
  26. package/dist/commands/archive/plan/landscape.js.map +1 -0
  27. package/dist/commands/archive/plan/refusal.js +111 -0
  28. package/dist/commands/archive/plan/refusal.js.map +1 -0
  29. package/dist/commands/archive/plan/specs.js +237 -0
  30. package/dist/commands/archive/plan/specs.js.map +1 -0
  31. package/dist/commands/archive/plan/state.js +27 -0
  32. package/dist/commands/archive/plan/state.js.map +1 -0
  33. package/dist/commands/archive/run.js +252 -0
  34. package/dist/commands/archive/run.js.map +1 -0
  35. package/dist/commands/context/context.js +99 -0
  36. package/dist/commands/context/context.js.map +1 -0
  37. package/dist/commands/context/print.js +283 -0
  38. package/dist/commands/context/print.js.map +1 -0
  39. package/dist/commands/delta/delta.js +279 -0
  40. package/dist/commands/delta/delta.js.map +1 -0
  41. package/dist/commands/delta/print.js +205 -0
  42. package/dist/commands/delta/print.js.map +1 -0
  43. package/dist/commands/dependencies.js +9 -7
  44. package/dist/commands/dependencies.js.map +1 -1
  45. package/dist/commands/diff/diff.js +93 -0
  46. package/dist/commands/diff/diff.js.map +1 -0
  47. package/dist/commands/diff/print.js +38 -0
  48. package/dist/commands/diff/print.js.map +1 -0
  49. package/dist/commands/doctor.js +5 -4
  50. package/dist/commands/doctor.js.map +1 -1
  51. package/dist/commands/explain/explain.js +151 -0
  52. package/dist/commands/explain/explain.js.map +1 -0
  53. package/dist/commands/explore.js +29 -24
  54. package/dist/commands/explore.js.map +1 -1
  55. package/dist/commands/gate/gate.js +115 -0
  56. package/dist/commands/gate/gate.js.map +1 -0
  57. package/dist/commands/gate/print.js +54 -0
  58. package/dist/commands/gate/print.js.map +1 -0
  59. package/dist/commands/gherkin/commit.js +146 -0
  60. package/dist/commands/gherkin/commit.js.map +1 -0
  61. package/dist/commands/gherkin/gherkin.js +246 -0
  62. package/dist/commands/gherkin/gherkin.js.map +1 -0
  63. package/dist/commands/gherkin/reconcile.js +149 -0
  64. package/dist/commands/gherkin/reconcile.js.map +1 -0
  65. package/dist/commands/gherkin/render.js +77 -0
  66. package/dist/commands/gherkin/render.js.map +1 -0
  67. package/dist/commands/init/example.js +204 -0
  68. package/dist/commands/init/example.js.map +1 -0
  69. package/dist/commands/init/first-hour.js +28 -0
  70. package/dist/commands/init/first-hour.js.map +1 -0
  71. package/dist/commands/{init.js → init/init.js} +146 -71
  72. package/dist/commands/init/init.js.map +1 -0
  73. package/dist/commands/init/mcp.js +131 -0
  74. package/dist/commands/init/mcp.js.map +1 -0
  75. package/dist/commands/init/options.js +78 -0
  76. package/dist/commands/init/options.js.map +1 -0
  77. package/dist/commands/instructions.js +72 -14
  78. package/dist/commands/instructions.js.map +1 -1
  79. package/dist/commands/list/campaign/campaign.js +65 -0
  80. package/dist/commands/list/campaign/campaign.js.map +1 -0
  81. package/dist/commands/list/campaign/owners.js +65 -0
  82. package/dist/commands/list/campaign/owners.js.map +1 -0
  83. package/dist/commands/list/json.js +157 -0
  84. package/dist/commands/list/json.js.map +1 -0
  85. package/dist/commands/list/list.js +343 -0
  86. package/dist/commands/list/list.js.map +1 -0
  87. package/dist/commands/list/print.js +301 -0
  88. package/dist/commands/list/print.js.map +1 -0
  89. package/dist/commands/list/review.js +120 -0
  90. package/dist/commands/list/review.js.map +1 -0
  91. package/dist/commands/list/views.js +105 -0
  92. package/dist/commands/list/views.js.map +1 -0
  93. package/dist/commands/mcp/dispatch.js +183 -0
  94. package/dist/commands/mcp/dispatch.js.map +1 -0
  95. package/dist/commands/mcp/mcp.js +34 -0
  96. package/dist/commands/mcp/mcp.js.map +1 -0
  97. package/dist/commands/mcp/serve.js +55 -0
  98. package/dist/commands/mcp/serve.js.map +1 -0
  99. package/dist/commands/migrate-openspec/materialize/active.js +170 -0
  100. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
  101. package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
  102. package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
  103. package/dist/commands/migrate-openspec/materialize/living.js +62 -0
  104. package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
  105. package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
  106. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
  107. package/dist/commands/migrate-openspec/materialize/target.js +219 -0
  108. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
  109. package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
  110. package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
  111. package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
  112. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
  113. package/dist/commands/migrate-openspec/openspec/error.js +19 -0
  114. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
  115. package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
  116. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
  117. package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
  118. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
  119. package/dist/commands/migrate-openspec/openspec/read.js +49 -0
  120. package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
  121. package/dist/commands/migrate-openspec/print.js +89 -0
  122. package/dist/commands/migrate-openspec/print.js.map +1 -0
  123. package/dist/commands/new/commit.js +76 -0
  124. package/dist/commands/new/commit.js.map +1 -0
  125. package/dist/commands/new/new.js +283 -0
  126. package/dist/commands/new/new.js.map +1 -0
  127. package/dist/commands/new/templates.js +355 -0
  128. package/dist/commands/new/templates.js.map +1 -0
  129. package/dist/commands/open.js +203 -0
  130. package/dist/commands/open.js.map +1 -0
  131. package/dist/commands/policy/format.js +152 -0
  132. package/dist/commands/policy/format.js.map +1 -0
  133. package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
  134. package/dist/commands/policy/gate.js.map +1 -0
  135. package/dist/commands/rebase/edit.js +106 -0
  136. package/dist/commands/rebase/edit.js.map +1 -0
  137. package/dist/commands/rebase/living.js +185 -0
  138. package/dist/commands/rebase/living.js.map +1 -0
  139. package/dist/commands/rebase/plan.js +245 -0
  140. package/dist/commands/rebase/plan.js.map +1 -0
  141. package/dist/commands/rebase/rebase.js +275 -0
  142. package/dist/commands/rebase/rebase.js.map +1 -0
  143. package/dist/commands/seed/commit.js +192 -0
  144. package/dist/commands/seed/commit.js.map +1 -0
  145. package/dist/commands/seed/plan.js +133 -0
  146. package/dist/commands/seed/plan.js.map +1 -0
  147. package/dist/commands/seed/seed.js +172 -0
  148. package/dist/commands/seed/seed.js.map +1 -0
  149. package/dist/commands/show/feature.js +178 -0
  150. package/dist/commands/show/feature.js.map +1 -0
  151. package/dist/commands/show/marks.js +24 -0
  152. package/dist/commands/show/marks.js.map +1 -0
  153. package/dist/commands/{show.js → show/service.js} +63 -152
  154. package/dist/commands/show/service.js.map +1 -0
  155. package/dist/commands/show/show.js +77 -0
  156. package/dist/commands/show/show.js.map +1 -0
  157. package/dist/commands/status/json.js +45 -0
  158. package/dist/commands/status/json.js.map +1 -0
  159. package/dist/commands/status/print.js +157 -0
  160. package/dist/commands/status/print.js.map +1 -0
  161. package/dist/commands/status/status.js +119 -0
  162. package/dist/commands/status/status.js.map +1 -0
  163. package/dist/commands/steps/steps.js +189 -0
  164. package/dist/commands/steps/steps.js.map +1 -0
  165. package/dist/commands/subsystem/edit.js +192 -0
  166. package/dist/commands/subsystem/edit.js.map +1 -0
  167. package/dist/commands/subsystem/move.js +265 -0
  168. package/dist/commands/subsystem/move.js.map +1 -0
  169. package/dist/commands/subsystem/query.js +115 -0
  170. package/dist/commands/subsystem/query.js.map +1 -0
  171. package/dist/commands/subsystem/subsystem.js +101 -0
  172. package/dist/commands/subsystem/subsystem.js.map +1 -0
  173. package/dist/commands/subsystem/sync.js +103 -0
  174. package/dist/commands/subsystem/sync.js.map +1 -0
  175. package/dist/commands/subsystem/txn/txn.js +94 -0
  176. package/dist/commands/subsystem/txn/txn.js.map +1 -0
  177. package/dist/commands/subsystem/txn/views.js +70 -0
  178. package/dist/commands/subsystem/txn/views.js.map +1 -0
  179. package/dist/commands/unarchive/manifest.js +160 -0
  180. package/dist/commands/unarchive/manifest.js.map +1 -0
  181. package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
  182. package/dist/commands/unarchive/restore.js.map +1 -0
  183. package/dist/commands/unarchive/unarchive.js +67 -0
  184. package/dist/commands/unarchive/unarchive.js.map +1 -0
  185. package/dist/commands/validate/arch-coverage.js +204 -0
  186. package/dist/commands/validate/arch-coverage.js.map +1 -0
  187. package/dist/commands/validate/checks/fleet-shape.js +296 -0
  188. package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
  189. package/dist/commands/validate/checks/health-deps.js +55 -0
  190. package/dist/commands/validate/checks/health-deps.js.map +1 -0
  191. package/dist/commands/validate/checks/requirements.js +254 -0
  192. package/dist/commands/validate/checks/requirements.js.map +1 -0
  193. package/dist/commands/validate/checks/sources.js +76 -0
  194. package/dist/commands/validate/checks/sources.js.map +1 -0
  195. package/dist/commands/validate/checks/vocabulary.js +31 -0
  196. package/dist/commands/validate/checks/vocabulary.js.map +1 -0
  197. package/dist/commands/validate/feature.js +216 -0
  198. package/dist/commands/validate/feature.js.map +1 -0
  199. package/dist/commands/validate/fleet/census.js +69 -0
  200. package/dist/commands/validate/fleet/census.js.map +1 -0
  201. package/dist/commands/validate/fleet/kind-tags.js +39 -0
  202. package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
  203. package/dist/commands/validate/fleet/landscape.js +353 -0
  204. package/dist/commands/validate/fleet/landscape.js.map +1 -0
  205. package/dist/commands/validate/fleet/load.js +52 -0
  206. package/dist/commands/validate/fleet/load.js.map +1 -0
  207. package/dist/commands/validate/fleet/obligations.js +270 -0
  208. package/dist/commands/validate/fleet/obligations.js.map +1 -0
  209. package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
  210. package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
  211. package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
  212. package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
  213. package/dist/commands/validate/fleet/scorecard/print.js +63 -0
  214. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
  215. package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
  216. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
  217. package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
  218. package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
  219. package/dist/commands/validate/fleet/usecases/place.js +13 -0
  220. package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
  221. package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
  222. package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
  223. package/dist/commands/validate/fleet/usecases/steps.js +197 -0
  224. package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
  225. package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
  226. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
  227. package/dist/commands/validate/fleet/views/ids.js +40 -0
  228. package/dist/commands/validate/fleet/views/ids.js.map +1 -0
  229. package/dist/commands/validate/fleet/views/stale.js +59 -0
  230. package/dist/commands/validate/fleet/views/stale.js.map +1 -0
  231. package/dist/commands/validate/links/corpus.js +92 -0
  232. package/dist/commands/validate/links/corpus.js.map +1 -0
  233. package/dist/commands/validate/links/glossary.js +75 -0
  234. package/dist/commands/validate/links/glossary.js.map +1 -0
  235. package/dist/commands/validate/report.js +269 -0
  236. package/dist/commands/validate/report.js.map +1 -0
  237. package/dist/commands/validate/service/api.js +338 -0
  238. package/dist/commands/validate/service/api.js.map +1 -0
  239. package/dist/commands/validate/service/events/events.js +219 -0
  240. package/dist/commands/validate/service/events/events.js.map +1 -0
  241. package/dist/commands/validate/service/events/message-producers.js +77 -0
  242. package/dist/commands/validate/service/events/message-producers.js.map +1 -0
  243. package/dist/commands/validate/service/evidence-pins.js +131 -0
  244. package/dist/commands/validate/service/evidence-pins.js.map +1 -0
  245. package/dist/commands/validate/service/service.js +251 -0
  246. package/dist/commands/validate/service/service.js.map +1 -0
  247. package/dist/commands/validate/service/specs.js +251 -0
  248. package/dist/commands/validate/service/specs.js.map +1 -0
  249. package/dist/commands/validate/service/spine.js +113 -0
  250. package/dist/commands/validate/service/spine.js.map +1 -0
  251. package/dist/commands/validate/validate.js +338 -0
  252. package/dist/commands/validate/validate.js.map +1 -0
  253. package/dist/commands/verify/cross/diff.js +182 -0
  254. package/dist/commands/verify/cross/diff.js.map +1 -0
  255. package/dist/commands/verify/evidence/contract.js +32 -0
  256. package/dist/commands/verify/evidence/contract.js.map +1 -0
  257. package/dist/commands/verify/evidence/pins.js +52 -0
  258. package/dist/commands/verify/evidence/pins.js.map +1 -0
  259. package/dist/commands/verify/evidence/read.js +72 -0
  260. package/dist/commands/verify/evidence/read.js.map +1 -0
  261. package/dist/commands/verify/frozen.js +241 -0
  262. package/dist/commands/verify/frozen.js.map +1 -0
  263. package/dist/commands/verify/record.js +226 -0
  264. package/dist/commands/verify/record.js.map +1 -0
  265. package/dist/commands/verify/report.js +223 -0
  266. package/dist/commands/verify/report.js.map +1 -0
  267. package/dist/commands/verify/results.js +246 -0
  268. package/dist/commands/verify/results.js.map +1 -0
  269. package/dist/commands/verify/verify.js +217 -0
  270. package/dist/commands/verify/verify.js.map +1 -0
  271. package/dist/commands/vouch/contract.js +11 -0
  272. package/dist/commands/vouch/contract.js.map +1 -0
  273. package/dist/commands/vouch/pack/contract.js +2 -0
  274. package/dist/commands/vouch/pack/contract.js.map +1 -0
  275. package/dist/commands/vouch/pack/pack.js +266 -0
  276. package/dist/commands/vouch/pack/pack.js.map +1 -0
  277. package/dist/commands/vouch/pack/print.js +271 -0
  278. package/dist/commands/vouch/pack/print.js.map +1 -0
  279. package/dist/commands/vouch/pack/sections.js +142 -0
  280. package/dist/commands/vouch/pack/sections.js.map +1 -0
  281. package/dist/commands/vouch/prompt/confirm.js +46 -0
  282. package/dist/commands/vouch/prompt/confirm.js.map +1 -0
  283. package/dist/commands/vouch/report.js +101 -0
  284. package/dist/commands/vouch/report.js.map +1 -0
  285. package/dist/commands/vouch/run.js +285 -0
  286. package/dist/commands/vouch/run.js.map +1 -0
  287. package/dist/commands/vouch/sample/plan.js +125 -0
  288. package/dist/commands/vouch/sample/plan.js.map +1 -0
  289. package/dist/commands/vouch/sample/print.js +109 -0
  290. package/dist/commands/vouch/sample/print.js.map +1 -0
  291. package/dist/commands/vouch/vet/verify.js +123 -0
  292. package/dist/commands/vouch/vet/verify.js.map +1 -0
  293. package/dist/commands/vouch/vouch.js +176 -0
  294. package/dist/commands/vouch/vouch.js.map +1 -0
  295. package/dist/core/agent/agents-md/artifacts.js +220 -0
  296. package/dist/core/agent/agents-md/artifacts.js.map +1 -0
  297. package/dist/core/agent/agents-md/command-map.js +324 -0
  298. package/dist/core/agent/agents-md/command-map.js.map +1 -0
  299. package/dist/core/agent/agents-md/cycle.js +165 -0
  300. package/dist/core/agent/agents-md/cycle.js.map +1 -0
  301. package/dist/core/agent/agents-md/map/explain.js +34 -0
  302. package/dist/core/agent/agents-md/map/explain.js.map +1 -0
  303. package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
  304. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
  305. package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
  306. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
  307. package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
  308. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
  309. package/dist/core/agent/agents-md/map/mcp.js +41 -0
  310. package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
  311. package/dist/core/agent/agents-md/map/status.js +62 -0
  312. package/dist/core/agent/agents-md/map/status.js.map +1 -0
  313. package/dist/core/agent/agents-md/map/subsystem.js +32 -0
  314. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
  315. package/dist/core/agent/agents-md/map/usecases.js +60 -0
  316. package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
  317. package/dist/core/agent/agents-md/refusals.js +246 -0
  318. package/dist/core/agent/agents-md/refusals.js.map +1 -0
  319. package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
  320. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
  321. package/dist/core/agent/agents-md/spine.js +40 -0
  322. package/dist/core/agent/agents-md/spine.js.map +1 -0
  323. package/dist/core/agent/agents-md.js +170 -0
  324. package/dist/core/agent/agents-md.js.map +1 -0
  325. package/dist/core/{agents-stamp.js → agent/agents-stamp.js} +50 -0
  326. package/dist/core/agent/agents-stamp.js.map +1 -0
  327. package/dist/core/agent/contract.js +2 -0
  328. package/dist/core/agent/contract.js.map +1 -0
  329. package/dist/core/agent/protocol.js +193 -0
  330. package/dist/core/agent/protocol.js.map +1 -0
  331. package/dist/core/agent/scaffold.js +259 -0
  332. package/dist/core/agent/scaffold.js.map +1 -0
  333. package/dist/core/agent/support/report.js +103 -0
  334. package/dist/core/agent/support/report.js.map +1 -0
  335. package/dist/core/agent/tools/dialects.js +150 -0
  336. package/dist/core/agent/tools/dialects.js.map +1 -0
  337. package/dist/core/agent/tools/registry.js +228 -0
  338. package/dist/core/agent/tools/registry.js.map +1 -0
  339. package/dist/core/agent/workflows/adopt.js +133 -0
  340. package/dist/core/agent/workflows/adopt.js.map +1 -0
  341. package/dist/core/agent/workflows/check.js +337 -0
  342. package/dist/core/agent/workflows/check.js.map +1 -0
  343. package/dist/core/agent/workflows/closing.js +262 -0
  344. package/dist/core/agent/workflows/closing.js.map +1 -0
  345. package/dist/core/agent/workflows/feature.js +105 -0
  346. package/dist/core/agent/workflows/feature.js.map +1 -0
  347. package/dist/core/agent/workflows/implement.js +105 -0
  348. package/dist/core/agent/workflows/implement.js.map +1 -0
  349. package/dist/core/agent/workflows/reference/authoring.js +209 -0
  350. package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
  351. package/dist/core/agent/workflows/reference/codes.js +65 -0
  352. package/dist/core/agent/workflows/reference/codes.js.map +1 -0
  353. package/dist/core/agent/workflows/reference/done-check.js +188 -0
  354. package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
  355. package/dist/core/agent/workflows/reference/reference.js +16 -0
  356. package/dist/core/agent/workflows/reference/reference.js.map +1 -0
  357. package/dist/core/agent/workflows/reference/spine.js +297 -0
  358. package/dist/core/agent/workflows/reference/spine.js.map +1 -0
  359. package/dist/core/asyncapi/baseline/plan.js +110 -0
  360. package/dist/core/asyncapi/baseline/plan.js.map +1 -0
  361. package/dist/core/asyncapi/baseline/surfaces.js +54 -0
  362. package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
  363. package/dist/core/asyncapi/depth.js +164 -0
  364. package/dist/core/asyncapi/depth.js.map +1 -0
  365. package/dist/core/asyncapi/digest.js +141 -0
  366. package/dist/core/asyncapi/digest.js.map +1 -0
  367. package/dist/core/asyncapi/merge/components.js +91 -0
  368. package/dist/core/asyncapi/merge/components.js.map +1 -0
  369. package/dist/core/asyncapi/merge/error.js +33 -0
  370. package/dist/core/asyncapi/merge/error.js.map +1 -0
  371. package/dist/core/asyncapi/merge/markers.js +308 -0
  372. package/dist/core/asyncapi/merge/markers.js.map +1 -0
  373. package/dist/core/asyncapi/merge/merge.js +215 -0
  374. package/dist/core/asyncapi/merge/merge.js.map +1 -0
  375. package/dist/core/asyncapi/merge/pin.js +107 -0
  376. package/dist/core/asyncapi/merge/pin.js.map +1 -0
  377. package/dist/core/asyncapi/model.js +11 -0
  378. package/dist/core/asyncapi/model.js.map +1 -0
  379. package/dist/core/asyncapi/producers.js +50 -0
  380. package/dist/core/asyncapi/producers.js.map +1 -0
  381. package/dist/core/{asyncapi.js → asyncapi/read.js} +95 -109
  382. package/dist/core/asyncapi/read.js.map +1 -0
  383. package/dist/core/brief/brief.js +225 -0
  384. package/dist/core/brief/brief.js.map +1 -0
  385. package/dist/core/brief/checks.js +250 -0
  386. package/dist/core/brief/checks.js.map +1 -0
  387. package/dist/core/brief/landscape.js +194 -0
  388. package/dist/core/brief/landscape.js.map +1 -0
  389. package/dist/core/brief/targets.js +226 -0
  390. package/dist/core/brief/targets.js.map +1 -0
  391. package/dist/core/brief/unchecked.js +48 -0
  392. package/dist/core/brief/unchecked.js.map +1 -0
  393. package/dist/core/c4/arch.js +174 -0
  394. package/dist/core/c4/arch.js.map +1 -0
  395. package/dist/core/c4/likec4.js +90 -0
  396. package/dist/core/c4/likec4.js.map +1 -0
  397. package/dist/core/c4/parsed/deployment.js +158 -0
  398. package/dist/core/c4/parsed/deployment.js.map +1 -0
  399. package/dist/core/c4/parsed/dynamic-views.js +132 -0
  400. package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
  401. package/dist/core/c4/parsed/specification.js +78 -0
  402. package/dist/core/c4/parsed/specification.js.map +1 -0
  403. package/dist/core/c4/parsed/values.js +57 -0
  404. package/dist/core/c4/parsed/values.js.map +1 -0
  405. package/dist/core/c4/parsed/view-ids.js +75 -0
  406. package/dist/core/c4/parsed/view-ids.js.map +1 -0
  407. package/dist/core/c4/project/architecture.js +45 -0
  408. package/dist/core/c4/project/architecture.js.map +1 -0
  409. package/dist/core/c4/project/documents.js +58 -0
  410. package/dist/core/c4/project/documents.js.map +1 -0
  411. package/dist/core/c4/project/load.js +187 -0
  412. package/dist/core/c4/project/load.js.map +1 -0
  413. package/dist/core/c4/project/staged.js +220 -0
  414. package/dist/core/c4/project/staged.js.map +1 -0
  415. package/dist/core/c4/resolve/service.js +159 -0
  416. package/dist/core/c4/resolve/service.js.map +1 -0
  417. package/dist/core/c4/resolve/steps.js +144 -0
  418. package/dist/core/c4/resolve/steps.js.map +1 -0
  419. package/dist/core/c4/seed/fleet-file.js +201 -0
  420. package/dist/core/c4/seed/fleet-file.js.map +1 -0
  421. package/dist/core/c4/seed/items.js +91 -0
  422. package/dist/core/c4/seed/items.js.map +1 -0
  423. package/dist/core/c4/seed/stamp.js +48 -0
  424. package/dist/core/c4/seed/stamp.js.map +1 -0
  425. package/dist/core/c4/seed/template.js +109 -0
  426. package/dist/core/c4/seed/template.js.map +1 -0
  427. package/dist/core/c4/source-mask.js +82 -0
  428. package/dist/core/c4/source-mask.js.map +1 -0
  429. package/dist/core/c4/source-scan.js +227 -0
  430. package/dist/core/c4/source-scan.js.map +1 -0
  431. package/dist/core/c4/splice/authored-source.js +101 -0
  432. package/dist/core/c4/splice/authored-source.js.map +1 -0
  433. package/dist/core/c4/splice/contract.js +10 -0
  434. package/dist/core/c4/splice/contract.js.map +1 -0
  435. package/dist/core/c4/splice/delta-blocks.js +139 -0
  436. package/dist/core/c4/splice/delta-blocks.js.map +1 -0
  437. package/dist/core/c4/splice/identity/edges.js +36 -0
  438. package/dist/core/c4/splice/identity/edges.js.map +1 -0
  439. package/dist/core/c4/splice/landscape-merge.js +269 -0
  440. package/dist/core/c4/splice/landscape-merge.js.map +1 -0
  441. package/dist/core/c4/splice/placement.js +194 -0
  442. package/dist/core/c4/splice/placement.js.map +1 -0
  443. package/dist/core/c4/workspace.js +174 -0
  444. package/dist/core/c4/workspace.js.map +1 -0
  445. package/dist/core/capabilities/authoring/notes.js +141 -0
  446. package/dist/core/capabilities/authoring/notes.js.map +1 -0
  447. package/dist/core/capabilities/capabilities.js +124 -0
  448. package/dist/core/capabilities/capabilities.js.map +1 -0
  449. package/dist/core/capabilities/delta/doc.js +50 -0
  450. package/dist/core/capabilities/delta/doc.js.map +1 -0
  451. package/dist/core/capabilities/delta/overlay.js +129 -0
  452. package/dist/core/capabilities/delta/overlay.js.map +1 -0
  453. package/dist/core/capabilities/delta/summary.js +57 -0
  454. package/dist/core/capabilities/delta/summary.js.map +1 -0
  455. package/dist/core/capabilities/delta/tree.js +43 -0
  456. package/dist/core/capabilities/delta/tree.js.map +1 -0
  457. package/dist/core/capabilities/delta/uncovered.js +222 -0
  458. package/dist/core/capabilities/delta/uncovered.js.map +1 -0
  459. package/dist/core/capabilities/findings.js +342 -0
  460. package/dist/core/capabilities/findings.js.map +1 -0
  461. package/dist/core/capabilities/realizes/findings.js +181 -0
  462. package/dist/core/capabilities/realizes/findings.js.map +1 -0
  463. package/dist/core/capabilities/realizes/join.js +143 -0
  464. package/dist/core/capabilities/realizes/join.js.map +1 -0
  465. package/dist/core/capabilities/rollup.js +199 -0
  466. package/dist/core/capabilities/rollup.js.map +1 -0
  467. package/dist/core/capabilities/tree.js +126 -0
  468. package/dist/core/capabilities/tree.js.map +1 -0
  469. package/dist/core/capabilities/usecase-join.js +277 -0
  470. package/dist/core/capabilities/usecase-join.js.map +1 -0
  471. package/dist/core/coherence/authoring/scaffold.js +177 -0
  472. package/dist/core/coherence/authoring/scaffold.js.map +1 -0
  473. package/dist/core/coherence/authoring/sentinels.js +74 -0
  474. package/dist/core/coherence/authoring/sentinels.js.map +1 -0
  475. package/dist/core/coherence/coherence.js +287 -0
  476. package/dist/core/coherence/coherence.js.map +1 -0
  477. package/dist/core/coherence/declared.js +269 -0
  478. package/dist/core/coherence/declared.js.map +1 -0
  479. package/dist/core/coherence/events/declared.js +206 -0
  480. package/dist/core/coherence/events/declared.js.map +1 -0
  481. package/dist/core/coherence/events/events.js +204 -0
  482. package/dist/core/coherence/events/events.js.map +1 -0
  483. package/dist/core/coherence/events/grades.js +166 -0
  484. package/dist/core/coherence/events/grades.js.map +1 -0
  485. package/dist/core/coherence/events/lookups.js +122 -0
  486. package/dist/core/coherence/events/lookups.js.map +1 -0
  487. package/dist/core/coherence/events/merged.js +40 -0
  488. package/dist/core/coherence/events/merged.js.map +1 -0
  489. package/dist/core/coherence/living.js +198 -0
  490. package/dist/core/coherence/living.js.map +1 -0
  491. package/dist/core/coherence/lookups.js +155 -0
  492. package/dist/core/coherence/lookups.js.map +1 -0
  493. package/dist/core/coherence/pending.js +39 -0
  494. package/dist/core/coherence/pending.js.map +1 -0
  495. package/dist/core/conflict-markers.js +60 -0
  496. package/dist/core/conflict-markers.js.map +1 -0
  497. package/dist/core/delta/claims.js +73 -0
  498. package/dist/core/delta/claims.js.map +1 -0
  499. package/dist/core/delta/delta.js +296 -0
  500. package/dist/core/delta/delta.js.map +1 -0
  501. package/dist/core/delta/document.js +177 -0
  502. package/dist/core/delta/document.js.map +1 -0
  503. package/dist/core/delta/scope.js +48 -0
  504. package/dist/core/delta/scope.js.map +1 -0
  505. package/dist/core/delta/select.js +237 -0
  506. package/dist/core/delta/select.js.map +1 -0
  507. package/dist/core/dependencies/dependencies.js +141 -0
  508. package/dist/core/dependencies/dependencies.js.map +1 -0
  509. package/dist/core/dependencies/facts.js +165 -0
  510. package/dist/core/dependencies/facts.js.map +1 -0
  511. package/dist/core/dependencies/fanin.js +88 -0
  512. package/dist/core/dependencies/fanin.js.map +1 -0
  513. package/dist/core/dependencies/graph.js +106 -0
  514. package/dist/core/dependencies/graph.js.map +1 -0
  515. package/dist/core/deployment/delta.js +188 -0
  516. package/dist/core/deployment/delta.js.map +1 -0
  517. package/dist/core/deployment/objects.js +145 -0
  518. package/dist/core/deployment/objects.js.map +1 -0
  519. package/dist/core/diff/base-git.js +148 -0
  520. package/dist/core/diff/base-git.js.map +1 -0
  521. package/dist/core/diff/base-state.js +209 -0
  522. package/dist/core/diff/base-state.js.map +1 -0
  523. package/dist/core/diff/current-state.js +96 -0
  524. package/dist/core/diff/current-state.js.map +1 -0
  525. package/dist/core/diff/scope/changed-paths.js +188 -0
  526. package/dist/core/diff/scope/changed-paths.js.map +1 -0
  527. package/dist/core/diff/semantic.js +243 -0
  528. package/dist/core/diff/semantic.js.map +1 -0
  529. package/dist/core/diff/victims.js +247 -0
  530. package/dist/core/diff/victims.js.map +1 -0
  531. package/dist/core/docs.js +7 -64
  532. package/dist/core/docs.js.map +1 -1
  533. package/dist/core/doctor/agents.js +222 -0
  534. package/dist/core/doctor/agents.js.map +1 -0
  535. package/dist/core/doctor/config.js +135 -0
  536. package/dist/core/doctor/config.js.map +1 -0
  537. package/dist/core/doctor/doctor.js +275 -0
  538. package/dist/core/doctor/doctor.js.map +1 -0
  539. package/dist/core/doctor/report.js +13 -0
  540. package/dist/core/doctor/report.js.map +1 -0
  541. package/dist/core/doctor/residue.js +110 -0
  542. package/dist/core/doctor/residue.js.map +1 -0
  543. package/dist/core/document/apply.js +77 -0
  544. package/dist/core/document/apply.js.map +1 -0
  545. package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
  546. package/dist/core/document/frontmatter.js.map +1 -0
  547. package/dist/core/document/grammar/directives.js +143 -0
  548. package/dist/core/document/grammar/directives.js.map +1 -0
  549. package/dist/core/document/parse.js +263 -0
  550. package/dist/core/document/parse.js.map +1 -0
  551. package/dist/core/document/scenarios.js +221 -0
  552. package/dist/core/document/scenarios.js.map +1 -0
  553. package/dist/core/document/spec.js +241 -0
  554. package/dist/core/document/spec.js.map +1 -0
  555. package/dist/core/{config.js → envelope/config.js} +79 -18
  556. package/dist/core/envelope/config.js.map +1 -0
  557. package/dist/core/envelope/json.js +175 -0
  558. package/dist/core/envelope/json.js.map +1 -0
  559. package/dist/core/{version.js → envelope/version.js} +3 -3
  560. package/dist/core/envelope/version.js.map +1 -0
  561. package/dist/core/explain/families.js +175 -0
  562. package/dist/core/explain/families.js.map +1 -0
  563. package/dist/core/explain/fix-tables.js +178 -0
  564. package/dist/core/explain/fix-tables.js.map +1 -0
  565. package/dist/core/explain/lookup.js +181 -0
  566. package/dist/core/explain/lookup.js.map +1 -0
  567. package/dist/core/explain/refusals.js +49 -0
  568. package/dist/core/explain/refusals.js.map +1 -0
  569. package/dist/core/explain/terms.js +144 -0
  570. package/dist/core/explain/terms.js.map +1 -0
  571. package/dist/core/explore/describe.js +105 -0
  572. package/dist/core/explore/describe.js.map +1 -0
  573. package/dist/core/{explore.js → explore/explore.js} +50 -101
  574. package/dist/core/explore/explore.js.map +1 -0
  575. package/dist/core/fleet-context.js +120 -78
  576. package/dist/core/fleet-context.js.map +1 -1
  577. package/dist/core/gate/checks.js +201 -0
  578. package/dist/core/gate/checks.js.map +1 -0
  579. package/dist/core/gate/partners.js +205 -0
  580. package/dist/core/gate/partners.js.map +1 -0
  581. package/dist/core/gate/report.js +47 -0
  582. package/dist/core/gate/report.js.map +1 -0
  583. package/dist/core/gate/verification.js +113 -0
  584. package/dist/core/gate/verification.js.map +1 -0
  585. package/dist/core/gherkin/digest.js +60 -0
  586. package/dist/core/gherkin/digest.js.map +1 -0
  587. package/dist/core/gherkin/emit.js +177 -0
  588. package/dist/core/gherkin/emit.js.map +1 -0
  589. package/dist/core/gherkin/read.js +76 -0
  590. package/dist/core/gherkin/read.js.map +1 -0
  591. package/dist/core/gherkin/stale.js +234 -0
  592. package/dist/core/gherkin/stale.js.map +1 -0
  593. package/dist/core/gherkin/stamp.js +77 -0
  594. package/dist/core/gherkin/stamp.js.map +1 -0
  595. package/dist/core/gherkin/steps/catalogue.js +154 -0
  596. package/dist/core/gherkin/steps/catalogue.js.map +1 -0
  597. package/dist/core/gherkin/steps/inventory.js +98 -0
  598. package/dist/core/gherkin/steps/inventory.js.map +1 -0
  599. package/dist/core/gherkin/steps/phrase.js +72 -0
  600. package/dist/core/gherkin/steps/phrase.js.map +1 -0
  601. package/dist/core/glossary/delta.js +106 -0
  602. package/dist/core/glossary/delta.js.map +1 -0
  603. package/dist/core/glossary/tree.js +86 -0
  604. package/dist/core/glossary/tree.js.map +1 -0
  605. package/dist/core/kernel/concurrency.js.map +1 -0
  606. package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
  607. package/dist/core/kernel/document-bytes.js.map +1 -0
  608. package/dist/core/kernel/fences.js +42 -0
  609. package/dist/core/kernel/fences.js.map +1 -0
  610. package/dist/core/kernel/ids/capability.js +72 -0
  611. package/dist/core/kernel/ids/capability.js.map +1 -0
  612. package/dist/core/kernel/ids/dirs.js +33 -0
  613. package/dist/core/kernel/ids/dirs.js.map +1 -0
  614. package/dist/core/kernel/ids/feature.js +75 -0
  615. package/dist/core/kernel/ids/feature.js.map +1 -0
  616. package/dist/core/kernel/ids/service.js +156 -0
  617. package/dist/core/kernel/ids/service.js.map +1 -0
  618. package/dist/core/kernel/ids/subsystem.js +63 -0
  619. package/dist/core/kernel/ids/subsystem.js.map +1 -0
  620. package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
  621. package/dist/core/kernel/path-safety.js.map +1 -0
  622. package/dist/core/kernel/records.js.map +1 -0
  623. package/dist/core/links/backlinks.js +68 -0
  624. package/dist/core/links/backlinks.js.map +1 -0
  625. package/dist/core/links/case.js +74 -0
  626. package/dist/core/links/case.js.map +1 -0
  627. package/dist/core/links/corpus.js +104 -0
  628. package/dist/core/links/corpus.js.map +1 -0
  629. package/dist/core/links/findings.js +110 -0
  630. package/dist/core/links/findings.js.map +1 -0
  631. package/dist/core/links/parse.js +236 -0
  632. package/dist/core/links/parse.js.map +1 -0
  633. package/dist/core/mcp/argv.js +111 -0
  634. package/dist/core/mcp/argv.js.map +1 -0
  635. package/dist/core/mcp/author-tools.js +72 -0
  636. package/dist/core/mcp/author-tools.js.map +1 -0
  637. package/dist/core/mcp/framing.js +46 -0
  638. package/dist/core/mcp/framing.js.map +1 -0
  639. package/dist/core/mcp/protocol.js +280 -0
  640. package/dist/core/mcp/protocol.js.map +1 -0
  641. package/dist/core/mcp/tools.js +314 -0
  642. package/dist/core/mcp/tools.js.map +1 -0
  643. package/dist/core/obligations/obligations.js +106 -0
  644. package/dist/core/obligations/obligations.js.map +1 -0
  645. package/dist/core/openapi/baseline/gate.js +166 -0
  646. package/dist/core/openapi/baseline/gate.js.map +1 -0
  647. package/dist/core/openapi/baseline/plan.js +112 -0
  648. package/dist/core/openapi/baseline/plan.js.map +1 -0
  649. package/dist/core/openapi/baseline/record.js +175 -0
  650. package/dist/core/openapi/baseline/record.js.map +1 -0
  651. package/dist/core/openapi/depth.js +122 -0
  652. package/dist/core/openapi/depth.js.map +1 -0
  653. package/dist/core/openapi/digest.js +189 -0
  654. package/dist/core/openapi/digest.js.map +1 -0
  655. package/dist/core/{openapi.js → openapi/doc.js} +44 -106
  656. package/dist/core/openapi/doc.js.map +1 -0
  657. package/dist/core/openapi/merge/components.js +234 -0
  658. package/dist/core/openapi/merge/components.js.map +1 -0
  659. package/dist/core/openapi/merge/error.js +26 -0
  660. package/dist/core/openapi/merge/error.js.map +1 -0
  661. package/dist/core/openapi/merge/markers.js +208 -0
  662. package/dist/core/openapi/merge/markers.js.map +1 -0
  663. package/dist/core/openapi/merge/merge.js +256 -0
  664. package/dist/core/openapi/merge/merge.js.map +1 -0
  665. package/dist/core/openapi/merge/pin.js +128 -0
  666. package/dist/core/openapi/merge/pin.js.map +1 -0
  667. package/dist/core/openspec/decide/capabilities.js +244 -0
  668. package/dist/core/openspec/decide/capabilities.js.map +1 -0
  669. package/dist/core/openspec/decide/dispositions.js +63 -0
  670. package/dist/core/openspec/decide/dispositions.js.map +1 -0
  671. package/dist/core/openspec/decide/renames.js +179 -0
  672. package/dist/core/openspec/decide/renames.js.map +1 -0
  673. package/dist/core/openspec/decide/verdict.js +84 -0
  674. package/dist/core/openspec/decide/verdict.js.map +1 -0
  675. package/dist/core/openspec/inventory.js +221 -0
  676. package/dist/core/openspec/inventory.js.map +1 -0
  677. package/dist/core/openspec/model/mapping.js +76 -0
  678. package/dist/core/openspec/model/mapping.js.map +1 -0
  679. package/dist/core/openspec/model/model.js +65 -0
  680. package/dist/core/openspec/model/model.js.map +1 -0
  681. package/dist/core/openspec/scan/changes.js +235 -0
  682. package/dist/core/openspec/scan/changes.js.map +1 -0
  683. package/dist/core/openspec/scan/config.js +200 -0
  684. package/dist/core/openspec/scan/config.js.map +1 -0
  685. package/dist/core/openspec/scan/shape.js +260 -0
  686. package/dist/core/openspec/scan/shape.js.map +1 -0
  687. package/dist/core/openspec/scan/walk.js +133 -0
  688. package/dist/core/openspec/scan/walk.js.map +1 -0
  689. package/dist/core/openspec/scan/workspace.js +71 -0
  690. package/dist/core/openspec/scan/workspace.js.map +1 -0
  691. package/dist/core/owners/codeowners.js +141 -0
  692. package/dist/core/owners/codeowners.js.map +1 -0
  693. package/dist/core/pack/features.js +120 -0
  694. package/dist/core/pack/features.js.map +1 -0
  695. package/dist/core/pack/joins.js +112 -0
  696. package/dist/core/pack/joins.js.map +1 -0
  697. package/dist/core/pack/living.js +137 -0
  698. package/dist/core/pack/living.js.map +1 -0
  699. package/dist/core/pack/pack.js +76 -0
  700. package/dist/core/pack/pack.js.map +1 -0
  701. package/dist/core/permissions/permissions.js +87 -0
  702. package/dist/core/permissions/permissions.js.map +1 -0
  703. package/dist/core/projection/api.js +84 -0
  704. package/dist/core/projection/api.js.map +1 -0
  705. package/dist/core/projection/arch-slice.js +63 -0
  706. package/dist/core/projection/arch-slice.js.map +1 -0
  707. package/dist/core/projection/events.js +34 -0
  708. package/dist/core/projection/events.js.map +1 -0
  709. package/dist/core/provenance/findings.js +208 -0
  710. package/dist/core/provenance/findings.js.map +1 -0
  711. package/dist/core/provenance/git.js +225 -0
  712. package/dist/core/provenance/git.js.map +1 -0
  713. package/dist/core/provenance/gitq/moves.js +182 -0
  714. package/dist/core/provenance/gitq/moves.js.map +1 -0
  715. package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
  716. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
  717. package/dist/core/provenance/sample/scope.js +238 -0
  718. package/dist/core/provenance/sample/scope.js.map +1 -0
  719. package/dist/core/provenance/sample/sections.js +61 -0
  720. package/dist/core/provenance/sample/sections.js.map +1 -0
  721. package/dist/core/provenance/sources.js +263 -0
  722. package/dist/core/provenance/sources.js.map +1 -0
  723. package/dist/core/provenance/stamp.js +232 -0
  724. package/dist/core/provenance/stamp.js.map +1 -0
  725. package/dist/core/provenance/walk.js +157 -0
  726. package/dist/core/provenance/walk.js.map +1 -0
  727. package/dist/core/repo/authored/paths.js +210 -0
  728. package/dist/core/repo/authored/paths.js.map +1 -0
  729. package/dist/core/repo/entries.js +126 -0
  730. package/dist/core/repo/entries.js.map +1 -0
  731. package/dist/core/repo/paths.js +277 -0
  732. package/dist/core/repo/paths.js.map +1 -0
  733. package/dist/core/repo/repo.js +248 -0
  734. package/dist/core/repo/repo.js.map +1 -0
  735. package/dist/core/repo/service-target.js +153 -0
  736. package/dist/core/repo/service-target.js.map +1 -0
  737. package/dist/core/repo/state.js +58 -0
  738. package/dist/core/repo/state.js.map +1 -0
  739. package/dist/core/repo/tree/find.js +52 -0
  740. package/dist/core/repo/tree/find.js.map +1 -0
  741. package/dist/core/repo/tree/fs.js +64 -0
  742. package/dist/core/repo/tree/fs.js.map +1 -0
  743. package/dist/core/repo/tree/marker.js +61 -0
  744. package/dist/core/repo/tree/marker.js.map +1 -0
  745. package/dist/core/repo/tree/views.js +172 -0
  746. package/dist/core/repo/tree/views.js.map +1 -0
  747. package/dist/core/repo/tree/walk.js +234 -0
  748. package/dist/core/repo/tree/walk.js.map +1 -0
  749. package/dist/core/results.js +99 -2
  750. package/dist/core/results.js.map +1 -1
  751. package/dist/core/scaffold/landscape.js +131 -0
  752. package/dist/core/scaffold/landscape.js.map +1 -0
  753. package/dist/core/scaffold/readme.js +84 -0
  754. package/dist/core/scaffold/readme.js.map +1 -0
  755. package/dist/core/staging/commit.js +241 -0
  756. package/dist/core/staging/commit.js.map +1 -0
  757. package/dist/core/staging/interrupted.js +96 -0
  758. package/dist/core/staging/interrupted.js.map +1 -0
  759. package/dist/core/staging/lock.js +206 -0
  760. package/dist/core/staging/lock.js.map +1 -0
  761. package/dist/core/staging/recovery/finding.js +35 -0
  762. package/dist/core/staging/recovery/finding.js.map +1 -0
  763. package/dist/core/staging/recovery/intent.js +98 -0
  764. package/dist/core/staging/recovery/intent.js.map +1 -0
  765. package/dist/core/staging/recovery/recover.js +126 -0
  766. package/dist/core/staging/recovery/recover.js.map +1 -0
  767. package/dist/core/staging/recovery/residue.js +62 -0
  768. package/dist/core/staging/recovery/residue.js.map +1 -0
  769. package/dist/core/staging/snapshot.js +202 -0
  770. package/dist/core/staging/snapshot.js.map +1 -0
  771. package/dist/core/staging/txn/forward.js +171 -0
  772. package/dist/core/staging/txn/forward.js.map +1 -0
  773. package/dist/core/staging/txn/journal.js +139 -0
  774. package/dist/core/staging/txn/journal.js.map +1 -0
  775. package/dist/core/staging/txn/transaction.js +75 -0
  776. package/dist/core/staging/txn/transaction.js.map +1 -0
  777. package/dist/core/staging/writes.js +113 -0
  778. package/dist/core/staging/writes.js.map +1 -0
  779. package/dist/core/status/actions/execution.js +49 -0
  780. package/dist/core/status/actions/execution.js.map +1 -0
  781. package/dist/core/status/contracts.js +102 -0
  782. package/dist/core/status/contracts.js.map +1 -0
  783. package/dist/core/status/feature/artifacts.js +196 -0
  784. package/dist/core/status/feature/artifacts.js.map +1 -0
  785. package/dist/core/status/feature/feature.js +207 -0
  786. package/dist/core/status/feature/feature.js.map +1 -0
  787. package/dist/core/status/feature/next.js +150 -0
  788. package/dist/core/status/feature/next.js.map +1 -0
  789. package/dist/core/status/feature/state.js +21 -0
  790. package/dist/core/status/feature/state.js.map +1 -0
  791. package/dist/core/status/fleet/fleet.js +197 -0
  792. package/dist/core/status/fleet/fleet.js.map +1 -0
  793. package/dist/core/status/fleet/next.js +207 -0
  794. package/dist/core/status/fleet/next.js.map +1 -0
  795. package/dist/core/status/interrupted.js +97 -0
  796. package/dist/core/status/interrupted.js.map +1 -0
  797. package/dist/core/status/report.js +31 -0
  798. package/dist/core/status/report.js.map +1 -0
  799. package/dist/core/status/scan.js +41 -0
  800. package/dist/core/status/scan.js.map +1 -0
  801. package/dist/core/status/verification.js +201 -0
  802. package/dist/core/status/verification.js.map +1 -0
  803. package/dist/core/usecases/capability.js +169 -0
  804. package/dist/core/usecases/capability.js.map +1 -0
  805. package/dist/core/usecases/delta/claims.js +116 -0
  806. package/dist/core/usecases/delta/claims.js.map +1 -0
  807. package/dist/core/usecases/delta/flows.js +103 -0
  808. package/dist/core/usecases/delta/flows.js.map +1 -0
  809. package/dist/core/usecases/delta/overlay.js +72 -0
  810. package/dist/core/usecases/delta/overlay.js.map +1 -0
  811. package/dist/core/usecases/fleet.js +137 -0
  812. package/dist/core/usecases/fleet.js.map +1 -0
  813. package/dist/core/usecases/operations.js +111 -0
  814. package/dist/core/usecases/operations.js.map +1 -0
  815. package/dist/core/usecases/place.js +44 -0
  816. package/dist/core/usecases/place.js.map +1 -0
  817. package/dist/core/usecases/touch.js +101 -0
  818. package/dist/core/usecases/touch.js.map +1 -0
  819. package/dist/core/verify/answers.js +173 -0
  820. package/dist/core/verify/answers.js.map +1 -0
  821. package/dist/core/verify/build.js +150 -0
  822. package/dist/core/verify/build.js.map +1 -0
  823. package/dist/core/verify/checklist.js +201 -0
  824. package/dist/core/verify/checklist.js.map +1 -0
  825. package/dist/core/verify/claims/identity.js +61 -0
  826. package/dist/core/verify/claims/identity.js.map +1 -0
  827. package/dist/core/verify/cross/diff.js +97 -0
  828. package/dist/core/verify/cross/diff.js.map +1 -0
  829. package/dist/core/verify/evidence/contract.js +169 -0
  830. package/dist/core/verify/evidence/contract.js.map +1 -0
  831. package/dist/core/verify/file.js +257 -0
  832. package/dist/core/verify/file.js.map +1 -0
  833. package/dist/core/verify/pins/lint.js +157 -0
  834. package/dist/core/verify/pins/lint.js.map +1 -0
  835. package/dist/core/verify/pins/pin.js +95 -0
  836. package/dist/core/verify/pins/pin.js.map +1 -0
  837. package/dist/core/verify/record.js +142 -0
  838. package/dist/core/verify/record.js.map +1 -0
  839. package/dist/core/verify/store/commit.js +89 -0
  840. package/dist/core/verify/store/commit.js.map +1 -0
  841. package/dist/core/verify/store/render.js +57 -0
  842. package/dist/core/verify/store/render.js.map +1 -0
  843. package/dist/core/vocabulary/health.js +91 -0
  844. package/dist/core/vocabulary/health.js.map +1 -0
  845. package/dist/core/vocabulary/issue.js +58 -0
  846. package/dist/core/vocabulary/issue.js.map +1 -0
  847. package/dist/core/vocabulary/maturity.js +145 -0
  848. package/dist/core/vocabulary/maturity.js.map +1 -0
  849. package/dist/core/{report.js → vocabulary/report.js} +30 -2
  850. package/dist/core/vocabulary/report.js.map +1 -0
  851. package/dist/core/vocabulary/steps.js +266 -0
  852. package/dist/core/vocabulary/steps.js.map +1 -0
  853. package/dist/core/workspace/discover.js +239 -0
  854. package/dist/core/workspace/discover.js.map +1 -0
  855. package/dist/core/workspace/render.js +52 -0
  856. package/dist/core/workspace/render.js.map +1 -0
  857. package/examples/README.md +189 -0
  858. package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
  859. package/examples/docs/architecture/capabilities.yaml +31 -0
  860. package/examples/docs/architecture/landscape.likec4 +280 -0
  861. package/examples/docs/architecture/obligations.yaml +23 -0
  862. package/examples/docs/architecture/permissions.yaml +50 -0
  863. package/examples/docs/architecture/subsystems.likec4 +11 -0
  864. package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
  865. package/examples/docs/capabilities/checkout/spec.md +55 -0
  866. package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
  867. package/examples/docs/capabilities/order-notifications/spec.md +43 -0
  868. package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
  869. package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
  870. package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
  871. package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
  872. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
  873. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
  874. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
  875. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
  876. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
  877. package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
  878. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
  879. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
  880. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
  881. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
  882. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
  883. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
  884. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
  885. package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
  886. package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
  887. package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
  888. package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
  889. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
  890. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
  891. package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
  892. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
  893. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
  894. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
  895. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
  896. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
  897. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
  898. package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
  899. package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
  900. package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
  901. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
  902. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
  903. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
  904. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
  905. package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
  906. package/examples/docs/glossary/order.md +15 -0
  907. package/examples/docs/glossary/payments/authorization.md +14 -0
  908. package/examples/docs/likec4.config.json +9 -0
  909. package/examples/docs/loam.json +3 -0
  910. package/examples/docs/services/checkout-web/model.likec4 +109 -0
  911. package/examples/docs/services/checkout-web/spec.md +91 -0
  912. package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
  913. package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
  914. package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
  915. package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
  916. package/examples/docs/services/order-service/arch.spec.md +127 -0
  917. package/examples/docs/services/order-service/asyncapi.yaml +108 -0
  918. package/examples/docs/services/order-service/health.yaml +57 -0
  919. package/examples/docs/services/order-service/model.likec4 +138 -0
  920. package/examples/docs/services/order-service/openapi.yaml +257 -0
  921. package/examples/docs/services/order-service/runbook.md +62 -0
  922. package/examples/docs/services/order-service/spec.md +134 -0
  923. package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
  924. package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
  925. package/examples/docs/services/payment-service/arch.spec.md +73 -0
  926. package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
  927. package/examples/docs/services/payment-service/health.yaml +39 -0
  928. package/examples/docs/services/payment-service/model.likec4 +67 -0
  929. package/examples/docs/services/payment-service/openapi.yaml +151 -0
  930. package/examples/docs/services/payment-service/runbook.md +37 -0
  931. package/examples/docs/services/payment-service/spec.md +88 -0
  932. package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
  933. package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
  934. package/examples/docs/services/platform/identity-service/health.yaml +70 -0
  935. package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
  936. package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
  937. package/examples/docs/services/platform/identity-service/runbook.md +95 -0
  938. package/examples/docs/services/platform/identity-service/spec.md +119 -0
  939. package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
  940. package/examples/docs/services/platform/notification-service/health.yaml +35 -0
  941. package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
  942. package/examples/docs/services/platform/notification-service/runbook.md +48 -0
  943. package/examples/docs/services/platform/notification-service/spec.md +112 -0
  944. package/examples/docs/services/platform/subsystem.yaml +2 -0
  945. package/examples/fleet.yaml +57 -0
  946. package/package.json +19 -6
  947. package/dist/commands/adopt.js +0 -240
  948. package/dist/commands/adopt.js.map +0 -1
  949. package/dist/commands/archive.js +0 -1313
  950. package/dist/commands/archive.js.map +0 -1
  951. package/dist/commands/delta.js +0 -388
  952. package/dist/commands/delta.js.map +0 -1
  953. package/dist/commands/docs-repo-gate.js.map +0 -1
  954. package/dist/commands/format.js +0 -17
  955. package/dist/commands/format.js.map +0 -1
  956. package/dist/commands/gherkin.js +0 -309
  957. package/dist/commands/gherkin.js.map +0 -1
  958. package/dist/commands/init.js.map +0 -1
  959. package/dist/commands/list.js +0 -295
  960. package/dist/commands/list.js.map +0 -1
  961. package/dist/commands/migrate-openspec.js +0 -950
  962. package/dist/commands/migrate-openspec.js.map +0 -1
  963. package/dist/commands/new.js +0 -413
  964. package/dist/commands/new.js.map +0 -1
  965. package/dist/commands/rebase.js +0 -356
  966. package/dist/commands/rebase.js.map +0 -1
  967. package/dist/commands/show.js.map +0 -1
  968. package/dist/commands/status.js +0 -262
  969. package/dist/commands/status.js.map +0 -1
  970. package/dist/commands/unarchive.js.map +0 -1
  971. package/dist/commands/validate.js +0 -1773
  972. package/dist/commands/validate.js.map +0 -1
  973. package/dist/commands/verify.js +0 -775
  974. package/dist/commands/verify.js.map +0 -1
  975. package/dist/commands/vouch.js +0 -421
  976. package/dist/commands/vouch.js.map +0 -1
  977. package/dist/core/agent.js +0 -2173
  978. package/dist/core/agent.js.map +0 -1
  979. package/dist/core/agents-stamp.js.map +0 -1
  980. package/dist/core/arch.js +0 -130
  981. package/dist/core/arch.js.map +0 -1
  982. package/dist/core/asyncapi.js.map +0 -1
  983. package/dist/core/brief.js +0 -755
  984. package/dist/core/brief.js.map +0 -1
  985. package/dist/core/coherence.js +0 -577
  986. package/dist/core/coherence.js.map +0 -1
  987. package/dist/core/concurrency.js.map +0 -1
  988. package/dist/core/config.js.map +0 -1
  989. package/dist/core/delta.js +0 -476
  990. package/dist/core/delta.js.map +0 -1
  991. package/dist/core/dependencies.js +0 -356
  992. package/dist/core/dependencies.js.map +0 -1
  993. package/dist/core/doctor.js +0 -596
  994. package/dist/core/doctor.js.map +0 -1
  995. package/dist/core/document-bytes.js.map +0 -1
  996. package/dist/core/explore.js.map +0 -1
  997. package/dist/core/frontmatter.js.map +0 -1
  998. package/dist/core/gherkin.js +0 -463
  999. package/dist/core/gherkin.js.map +0 -1
  1000. package/dist/core/health.js +0 -67
  1001. package/dist/core/health.js.map +0 -1
  1002. package/dist/core/ids.js +0 -122
  1003. package/dist/core/ids.js.map +0 -1
  1004. package/dist/core/issue.js +0 -9
  1005. package/dist/core/issue.js.map +0 -1
  1006. package/dist/core/json.js +0 -74
  1007. package/dist/core/json.js.map +0 -1
  1008. package/dist/core/likec4.js +0 -489
  1009. package/dist/core/likec4.js.map +0 -1
  1010. package/dist/core/maturity.js +0 -66
  1011. package/dist/core/maturity.js.map +0 -1
  1012. package/dist/core/openapi-merge.js +0 -552
  1013. package/dist/core/openapi-merge.js.map +0 -1
  1014. package/dist/core/openapi.js.map +0 -1
  1015. package/dist/core/openspec-inventory.js +0 -1440
  1016. package/dist/core/openspec-inventory.js.map +0 -1
  1017. package/dist/core/path-safety.js.map +0 -1
  1018. package/dist/core/provenance.js +0 -787
  1019. package/dist/core/provenance.js.map +0 -1
  1020. package/dist/core/records.js.map +0 -1
  1021. package/dist/core/repo.js +0 -438
  1022. package/dist/core/repo.js.map +0 -1
  1023. package/dist/core/report.js.map +0 -1
  1024. package/dist/core/spec.js +0 -505
  1025. package/dist/core/spec.js.map +0 -1
  1026. package/dist/core/staging.js +0 -890
  1027. package/dist/core/staging.js.map +0 -1
  1028. package/dist/core/status.js +0 -1132
  1029. package/dist/core/status.js.map +0 -1
  1030. package/dist/core/steps.js +0 -72
  1031. package/dist/core/steps.js.map +0 -1
  1032. package/dist/core/verify.js +0 -807
  1033. package/dist/core/verify.js.map +0 -1
  1034. package/dist/core/version.js.map +0 -1
  1035. /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
  1036. /package/dist/core/{records.js → kernel/records.js} +0 -0
@@ -0,0 +1,338 @@
1
+ /**
2
+ * The HTTP contract axis: openapi.yaml graded against the living spec.
3
+ *
4
+ * What it decides for everything downstream is whether there is a contract to
5
+ * read at all. A missing or unparseable openapi.yaml is ONE finding about ONE
6
+ * file, and that fact has to travel: grading the fleet map's inbound edges
7
+ * against the empty operation set it leaves behind turns one broken YAML into
8
+ * one `spine.op-undefined` per consumer, each of them accusing the landscape of
9
+ * a fault that is not there. `ServiceContract` is how it travels; `./spine.js`
10
+ * is what reads it.
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 { closeIds } from "../../../core/c4/arch.js";
19
+ import {} from "../../../core/c4/likec4.js";
20
+ import {} from "../../../core/kernel/ids/service.js";
21
+ import {} from "../../../core/repo/paths.js";
22
+ import {} from "../../../core/vocabulary/report.js";
23
+ import { responseGovernanceFindings } from "../checks/requirements.js";
24
+ import {} from "../../../core/document/spec.js";
25
+ import { readOpenapi } from "../../../core/openapi/doc.js";
26
+ import { FleetContext } from "../../../core/fleet-context.js";
27
+ export async function apiAxisFindings(axis) {
28
+ const { service, me, paths, reqs, livingReqs, land, landSvcOf, fleet } = axis;
29
+ const findings = [];
30
+ // API coverage: every operation in openapi.yaml is governed by a requirement.
31
+ const api = await readOpenapi(paths.openapi, fleet);
32
+ const removeMarkers = api.ops.filter((o) => o.remove);
33
+ 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));
36
+ // Inbound calls the landscape can PROVE: op-linked edges whose target
37
+ // resolves to this service. Null when the landscape proves nothing at all
38
+ // (absent, or it did not parse) — which is not the same fact as "nobody
39
+ // calls me", and the two must not be graded alike.
40
+ const inboundOps = land === null || land.errors.length > 0
41
+ ? null
42
+ : land.relationships.filter((r) => r.op !== undefined && landSvcOf(r.target) === me);
43
+ // The other half of the evidence a contract is owed: the living spec's own
44
+ // `Operations:` lines. A requirement on its way out governs nothing.
45
+ const governedOps = livingReqs.flatMap((r) => r.operations);
46
+ /** No contract to read: deleted, renamed, or never written. */
47
+ const contractMissing = !existsSync(paths.openapi);
48
+ if (contractMissing) {
49
+ // A contract that is gone is not a reason to stop grading the API axis —
50
+ // it IS the axis's answer, and every check below used to live inside the
51
+ // file-exists branch, so deleting or misspelling the file turned the whole
52
+ // axis (including the documented `spec-api.op-undefined`) green.
53
+ //
54
+ // What decides the severity is whether anything already written down is
55
+ // holding a join into it: a living `Operations:` line, or an op-linked
56
+ // landscape edge. With one of those, the absence breaks a link somebody
57
+ // authored — an error. With neither, this is a service that legitimately
58
+ // has no HTTP surface (a worker nobody calls), and a fleet mid-rollout
59
+ // must not go red for it — a warning, or, when the landscape PROVES nobody
60
+ // calls it, the documented silence.
61
+ //
62
+ // One finding, not one per dangling link: the fix is a single file, and
63
+ // the links it strands ride along as details.
64
+ const dangling = [...new Set([...governedOps, ...(inboundOps ?? []).map((r) => r.op)])].sort();
65
+ if (dangling.length > 0) {
66
+ findings.push({
67
+ severity: "error",
68
+ code: "service.no-openapi",
69
+ subject: service,
70
+ message: `No OpenAPI contract at ${paths.openapi}, and ${dangling.length} operation link(s) already point into it — ` +
71
+ `every requirement and landscape edge naming one of them resolves to nothing until the file is back`,
72
+ details: dangling,
73
+ });
74
+ }
75
+ else if (inboundOps === null) {
76
+ findings.push({
77
+ severity: "warn",
78
+ code: "service.no-openapi",
79
+ message: `No OpenAPI contract at ${paths.openapi} — API coverage and the landscape spine are unchecked`,
80
+ });
81
+ }
82
+ }
83
+ else if (api.unreadable) {
84
+ // A contract that EXISTS but does not read is a broken source of truth, not
85
+ // an empty one: swallowing it into zero operations used to grade every
86
+ // inbound landscape edge `spine.op-undefined` — a false diagnosis pointing
87
+ // at the landscape when the truth was this file. So the file is the error,
88
+ // and every check that reads the contract (api.*, the spine's op
89
+ // resolution) is suspended below, the landscape.invalid discipline.
90
+ findings.push({
91
+ severity: "error",
92
+ code: "openapi.invalid",
93
+ message: `${service}: openapi.yaml does not parse — API coverage and the landscape spine are unchecked`,
94
+ ...(api.error === undefined ? {} : { details: [api.error] }),
95
+ });
96
+ }
97
+ else {
98
+ if (removeMarkers.length > 0) {
99
+ findings.push({
100
+ severity: "error",
101
+ code: "openapi.remove-marker-living",
102
+ 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`,
103
+ });
104
+ }
105
+ // The same marker written at PATH level, beside the methods. `readOpenapi`
106
+ // is keyed by (path, method), which is precisely why the check above cannot
107
+ // see this one and precisely how one reached a living contract in the first
108
+ // place: it addresses no operation, so it retired nothing, and it stayed
109
+ // invisible afterwards. Error, like its method-level sibling — a
110
+ // feature-only key published to every consumer of the fleet, and one that
111
+ // keeps the empty-path cleanup from ever firing. Same code as the archive
112
+ // plan's: one breach, one name, wherever it is found.
113
+ for (const removed of api.pathLevelRemovals) {
114
+ findings.push({
115
+ severity: "error",
116
+ code: "openapi.remove-marker-path-level",
117
+ subject: service,
118
+ message: `${service}: living openapi.yaml carries x-loam-remove at PATH level on '${removed}' — ` +
119
+ `a removal marker names ONE operation, so beside the methods it retires nothing and is not a contract key either, ` +
120
+ `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.`,
121
+ });
122
+ }
123
+ // Two slots claiming one operationId in a LIVING contract. `readOpenapi`
124
+ // has computed this since the merge needed it, and only the feature path
125
+ // (coherence) ever read it — so the ambiguity was reachable only when some
126
+ // unrelated feature happened to carry a delta for this service, and the
127
+ // fleet gate that actually runs in CI was blind to it. Same code and same
128
+ // sentence as the feature-scope check: one breach, one name.
129
+ for (const id of api.duplicateIds) {
130
+ const slots = api.ops.filter((op) => op.id === id).map((op) => `${op.method} ${op.path}`);
131
+ findings.push({
132
+ severity: "warn",
133
+ code: "openapi.duplicate-operationid",
134
+ subject: service,
135
+ 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`,
136
+ });
137
+ }
138
+ // The contract-depth probes. Form validated and depth did not: an
139
+ // operation whose one response is `description: OK`, and a `$ref` to a
140
+ // schema nobody wrote, both read as cleanly as a complete contract while
141
+ // a reader still cannot rebuild its payloads. Presence
142
+ // probes over the document's own keys; nothing reads what a schema says.
143
+ const undescribed = liveOps.filter((op) => op.undescribed === true).map((op) => op.id);
144
+ if (undescribed.length > 0) {
145
+ findings.push({
146
+ severity: "warn",
147
+ code: "openapi.response-undescribed",
148
+ subject: service,
149
+ message: `${service}: ${undescribed.length} operation(s) declare no response schema (${undescribed.join(", ")}) — ` +
150
+ `the contract names the operation and says nothing about what it returns, so a reader still ` +
151
+ `needs the code; declare content with a schema on at least one response (204/304 excepted)`,
152
+ });
153
+ }
154
+ if (api.danglingRefs.length > 0) {
155
+ findings.push({
156
+ severity: "warn",
157
+ code: "openapi.ref-unresolved",
158
+ subject: service,
159
+ message: `${service}: openapi.yaml contains ${api.danglingRefs.length} internal $ref(s) that resolve to ` +
160
+ `nothing in the document — the operation validates while its schema is silently absent ` +
161
+ `(the archive plan refuses the same breach in a feature delta, as an error)`,
162
+ details: api.danglingRefs,
163
+ });
164
+ }
165
+ const defined = new Set(ops);
166
+ // `Operations:` on a LIVING requirement, resolved against this service's own
167
+ // contract. Nothing did this before: the same spine is checked inside a
168
+ // feature delta (coherence's spec-api.op-undefined) and then never again, so
169
+ // a typo that shipped, or an operation later renamed out of openapi.yaml,
170
+ // left a living requirement governing an operation that does not exist —
171
+ // green forever, and every downstream join through that id silently empty.
172
+ // Same code and severity as the feature-scope check on purpose: one breach,
173
+ // one name, wherever it is found.
174
+ for (const r of livingReqs) {
175
+ for (const op of r.operations) {
176
+ if (defined.has(op))
177
+ continue;
178
+ const close = closeIds(op, ops);
179
+ findings.push({
180
+ severity: "error",
181
+ code: "spec-api.op-undefined",
182
+ subject: service,
183
+ message: `${service}: requirement '${r.name}' governs '${op}', not defined in ${service}'s OpenAPI` +
184
+ (close.length > 0 ? `. Did you mean: ${close.join(", ")}?` : ""),
185
+ });
186
+ }
187
+ }
188
+ if (ops.length > 0) {
189
+ const governed = new Set(livingReqs.flatMap((r) => r.operations));
190
+ const orphans = ops.filter((op) => !governed.has(op));
191
+ if (orphans.length === 0) {
192
+ findings.push({
193
+ severity: "ok",
194
+ code: "api.covered",
195
+ // Both counts on purpose: the ratio is the one signal that lets a
196
+ // reviewer smell a thin baseline from the summary — forty behaviours
197
+ // filed under one requirement passes every check, and "covered" alone
198
+ // read as done.
199
+ message: `${service}: API covered (${ops.length} operation(s) governed by requirements · ${livingReqs.length} living requirement(s))`,
200
+ });
201
+ }
202
+ else {
203
+ findings.push({
204
+ severity: "warn",
205
+ code: "api.ungoverned",
206
+ message: `${service}: ${orphans.length} operation(s) not governed by any requirement — ${orphans.join(", ")}`,
207
+ });
208
+ }
209
+ findings.push(...responseGovernanceFindings(service, liveOps, livingReqs));
210
+ // The migration-debt case: requirements exist, the API exists, and no
211
+ // `Operations:` line ties them — every cross-axis check above and in
212
+ // feature mode is vacuously green. Once per service, not per operation;
213
+ // with zero requirements the spec (or its absence) is the finding instead.
214
+ if (reqs.length > 0 && reqs.every((r) => r.operations.length === 0)) {
215
+ findings.push({
216
+ severity: "warn",
217
+ code: "api.ops-unlinked",
218
+ message: `${service}: openapi.yaml defines ${ops.length} operation(s) but no requirement links any — the API axis is unchecked for this service`,
219
+ });
220
+ }
221
+ // Lifecycle: a requirement whose `Operations:` list resolves ONLY to
222
+ // deprecated operations governs behaviour the contract is retiring.
223
+ // Deprecation is the documented first step of removing an op; the explicit
224
+ // feature marker is the final step. Until that delta archives, the op stays
225
+ // live, so the fix is migration or a coordinated retirement. Ops the contract does not define at all
226
+ // prove nothing here and are left to spec-api.op-undefined above.
227
+ for (const r of reqs) {
228
+ const resolved = r.operations.filter((op) => defined.has(op));
229
+ if (resolved.length === 0 || !resolved.every((op) => deprecatedOps.has(op)))
230
+ continue;
231
+ findings.push({
232
+ severity: "warn",
233
+ code: "api.requirement-deprecated",
234
+ 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`,
235
+ });
236
+ }
237
+ }
238
+ }
239
+ return {
240
+ findings,
241
+ contract: { ops, deprecated: deprecatedOps, unreadable: api.unreadable || contractMissing },
242
+ };
243
+ }
244
+ /**
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.
271
+ */
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
+ }];
337
+ }
338
+ //# sourceMappingURL=api.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,219 @@
1
+ /**
2
+ * The async contract axis — AsyncAPI messages on the landscape spine.
3
+ *
4
+ * The HTTP spine (`./spine.js`) and this one are mirror images, and the mirror
5
+ * is the whole reason this is a second axis rather than a second `metadata`
6
+ * key. On the HTTP axis the PROVIDER owns the contract: an edge names an
7
+ * operation and the target's openapi.yaml settles it, locally, in one file.
8
+ * Here the PRODUCER owns the message and the consumer joins to it from another
9
+ * repository — so "is this message declared" is local, while "does anybody
10
+ * publish it" is a fleet question with no local answer at all. That second
11
+ * question is the only check on either spine that reads other services'
12
+ * contracts; it lives in `./message-producers.js`, and this axis only decides
13
+ * when it is worth paying for — exactly when something actually consumes.
14
+ */
15
+ import { existsSync } from "node:fs";
16
+ import {} from "../../../../core/kernel/ids/service.js";
17
+ import {} from "../../../../core/c4/likec4.js";
18
+ import {} from "../../../../core/repo/paths.js";
19
+ import {} from "../../../../core/vocabulary/report.js";
20
+ import {} from "../../../../core/document/spec.js";
21
+ import { readAsyncapi, slotsOf } from "../../../../core/asyncapi/read.js";
22
+ import { FleetContext } from "../../../../core/fleet-context.js";
23
+ import { producerFindings } from "./message-producers.js";
24
+ export async function eventAxisFindings(axis) {
25
+ const { docsDir, service, me, paths, reqs, livingReqs, archReqs, land, landSvcOf, known, fleet } = axis;
26
+ const findings = [];
27
+ // Where this axis started, so the closing ok-finding can be "nothing here
28
+ // broke" rather than a second, independently-computed claim about the same
29
+ // facts. A positive finding derived from the same walk cannot disagree with it.
30
+ const eventFindingsAt = findings.length;
31
+ const events = await readAsyncapi(paths.asyncapi, fleet);
32
+ const sentHere = new Set(events.sent);
33
+ const receivedHere = new Set(events.received);
34
+ // Every edge that binds THIS service to a message: `publishes` binds the
35
+ // 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
39
+ ? []
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
+ }
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
+ });
50
+ // Both requirement namespaces, and a REMOVED requirement links nothing —
51
+ // the `livingReqs` rule the API axis already follows, applied to the pair.
52
+ const eventLinks = [...livingReqs, ...archReqs.filter((r) => r.kind !== "REMOVED")].flatMap((r) => [
53
+ ...r.publishes.map((m) => ({ direction: "publishes", message: m, requirement: r.name })),
54
+ ...r.consumes.map((m) => ({ direction: "consumes", message: m, requirement: r.name })),
55
+ ]);
56
+ if (!existsSync(paths.asyncapi)) {
57
+ // Deliberately TWO grades, not the three `service.no-openapi` uses. Its
58
+ // middle grade — warn when the landscape cannot prove nobody calls this
59
+ // service — rests on HTTP being the default: most services expose one, so
60
+ // "I could not look" is worth saying. An event contract is genuinely
61
+ // optional; most services in a legacy fleet touch no topic at all. Warning
62
+ // every one of them the moment a landscape fails to parse would put a
63
+ // finding on the whole fleet that names a file nobody owes.
64
+ //
65
+ // So: something already joins into the absent file, or silence.
66
+ const dangling = [
67
+ ...new Set([...eventEdges.map((e) => e.message), ...eventLinks.map((e) => e.message)]),
68
+ ].sort();
69
+ if (dangling.length > 0) {
70
+ findings.push({
71
+ severity: "error",
72
+ code: "service.no-asyncapi",
73
+ subject: service,
74
+ message: `No async contract at ${paths.asyncapi}, and ${dangling.length} message link(s) already point into it — ` +
75
+ `every landscape edge and requirement naming one of them resolves to nothing until the file is back`,
76
+ details: dangling,
77
+ });
78
+ }
79
+ }
80
+ else if (events.unreadable) {
81
+ // The openapi.invalid discipline: a contract that EXISTS but does not read
82
+ // is a broken source of truth, not an empty one. Grading the links against
83
+ // an empty message set would turn one file into one `spine.message-undefined`
84
+ // per edge, every one of them pointing at the landscape.
85
+ findings.push({
86
+ severity: "error",
87
+ code: "asyncapi.invalid",
88
+ subject: service,
89
+ message: `${service}: asyncapi.yaml does not parse — the event spine is unchecked for this service`,
90
+ ...(events.error === undefined ? {} : { details: [events.error] }),
91
+ });
92
+ }
93
+ else {
94
+ // Feature-only bookkeeping published to the fleet — the openapi axis's
95
+ // `openapi.remove-marker-living` discipline, one code for every depth the
96
+ // reader collects markers at (slots and inline channel messages alike).
97
+ if (events.markers.length > 0) {
98
+ findings.push({
99
+ severity: "error",
100
+ code: "asyncapi.remove-marker-living",
101
+ subject: service,
102
+ message: `${service}: living asyncapi.yaml carries x-loam-remove: true at ${events.markers.join(", ")} — removal markers are valid only in feature deltas; retire a slot through a feature's asyncapi.yaml instead`,
103
+ });
104
+ }
105
+ for (const name of events.duplicateNames) {
106
+ findings.push({
107
+ severity: "warn",
108
+ code: "asyncapi.duplicate-message",
109
+ subject: service,
110
+ message: `${service}: asyncapi.yaml declares message '${name}' in ${slotsOf(events, name).join(" and ")} — every join on the name (an edge's metadata { publishes }, a requirement's Publishes: line) picks one of those slots arbitrarily`,
111
+ });
112
+ }
113
+ // The contract-depth probes. Form validated and depth did not: a payload
114
+ // of bare `type: object` and a `$ref` to nothing both read as cleanly as
115
+ // a full schema, while a reader still cannot rebuild the payload from the
116
+ // green contract. Presence probes only — a non-JSON schemaFormat
117
+ // is skipped, so Avro stays a document change.
118
+ const empties = events.messages.filter((m) => m.payloadEmpty === true).map((m) => m.name);
119
+ if (empties.length > 0) {
120
+ findings.push({
121
+ severity: "warn",
122
+ code: "asyncapi.payload-undescribed",
123
+ subject: service,
124
+ message: `${service}: ${empties.length} message(s) declare no payload shape (${empties.join(", ")}) — ` +
125
+ `the name joins the spine, but nothing defines what a consumer would read; ` +
126
+ `declare properties, or a schemaFormat for a non-JSON schema`,
127
+ });
128
+ }
129
+ if (events.danglingRefs.length > 0) {
130
+ findings.push({
131
+ severity: "warn",
132
+ code: "asyncapi.ref-unresolved",
133
+ subject: service,
134
+ message: `${service}: asyncapi.yaml contains ${events.danglingRefs.length} internal $ref(s) that resolve ` +
135
+ `to nothing in the document — whatever they were meant to carry is silently absent from the ` +
136
+ `spine's view of this contract`,
137
+ details: events.danglingRefs,
138
+ });
139
+ }
140
+ // Local resolution, both directions and both sources of a claim. An edge and
141
+ // a requirement making the same broken claim get the same sentence under
142
+ // different codes, matching how `spine.op-undefined` and
143
+ // `spec-api.op-undefined` split the HTTP axis: one is the fleet map's
144
+ // mistake, the other is this document's, and the fix is in a different file.
145
+ const declares = (direction, message) => direction === "publishes" ? sentHere.has(message) : receivedHere.has(message);
146
+ const action = (direction) => direction === "publishes" ? "send" : "receive";
147
+ for (const e of eventEdges) {
148
+ if (declares(e.direction, e.message))
149
+ continue;
150
+ findings.push({
151
+ severity: "error",
152
+ code: "spine.message-undefined",
153
+ subject: service,
154
+ message: `${service}: landscape edge ${e.direction === "publishes" ? `${service} → ${e.other}` : `${e.other} → ${service}`} ` +
155
+ `${e.direction} '${e.message}', but ${service}'s asyncapi.yaml declares no operation with action: ${action(e.direction)} for it`,
156
+ });
157
+ }
158
+ for (const e of eventLinks) {
159
+ if (declares(e.direction, e.message))
160
+ continue;
161
+ findings.push({
162
+ severity: "error",
163
+ code: "spec-event.message-undefined",
164
+ subject: service,
165
+ message: `${service}: requirement '${e.requirement}' ${e.direction} '${e.message}', but ${service}'s asyncapi.yaml declares no operation with action: ${action(e.direction)} for it`,
166
+ });
167
+ }
168
+ // The migration-debt case, `api.ops-unlinked`'s twin: a contract and
169
+ // requirements that never name each other leave this axis vacuously green.
170
+ if (events.messages.length > 0 && reqs.length + archReqs.length > 0 && eventLinks.length === 0) {
171
+ findings.push({
172
+ severity: "warn",
173
+ code: "event.messages-unlinked",
174
+ subject: service,
175
+ message: `${service}: asyncapi.yaml declares ${events.messages.length} message(s) but no requirement links any — the event axis is unchecked for this service`,
176
+ });
177
+ }
178
+ // `api.ungoverned`'s event twin, and the per-message half the check above
179
+ // cannot see: `event.messages-unlinked` fires only when ZERO links exist,
180
+ // so a contract with five declared messages and three governed ones
181
+ // reported nothing about the other two. The join is against requirement
182
+ // lines ONLY, deliberately ignoring landscape edges — an edge is a claim
183
+ // about traffic, a requirement is governance — exactly as the API axis
184
+ // ignores inbound edges when grading coverage.
185
+ const governed = new Set(eventLinks.map((e) => e.message));
186
+ const orphans = [...new Set(events.messages.map((m) => m.name))].filter((name) => !governed.has(name));
187
+ if (orphans.length > 0) {
188
+ findings.push({
189
+ severity: "warn",
190
+ code: "event.ungoverned",
191
+ subject: service,
192
+ message: `${service}: ${orphans.length} message(s) not governed by any requirement's Publishes:/Consumes: line — ${orphans.join(", ")}`,
193
+ });
194
+ }
195
+ }
196
+ // The consumed-message list is the handover to the fleet question in
197
+ // ./message-producers.js — a fleet where nothing consumes walks nothing.
198
+ const consumed = [
199
+ ...new Set([
200
+ ...eventEdges.filter((e) => e.direction === "consumes").map((e) => e.message),
201
+ ...eventLinks.filter((e) => e.direction === "consumes").map((e) => e.message),
202
+ ]),
203
+ ].sort();
204
+ findings.push(...(await producerFindings({ docsDir, service, land, landSvcOf, known, events, consumed, fleet })));
205
+ // Positive confirmation, on the same rule `spine.resolved` follows: claimed
206
+ // only where the axis actually checked something. A service that touches no
207
+ // topic says nothing here — an "event axis clean" on every worker in the fleet
208
+ // would be a green tick for work nobody did.
209
+ const eventChecks = eventEdges.length + eventLinks.length;
210
+ if (eventChecks > 0 && findings.length === eventFindingsAt) {
211
+ findings.push({
212
+ severity: "ok",
213
+ code: "event.covered",
214
+ message: `${service}: event spine (${eventChecks} message link(s) resolve to asyncapi.yaml)`,
215
+ });
216
+ }
217
+ return findings;
218
+ }
219
+ //# sourceMappingURL=events.js.map