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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1026) hide show
  1. package/CHANGELOG.md +2548 -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 +494 -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 +317 -0
  24. package/dist/commands/archive/plan/gate.js.map +1 -0
  25. package/dist/commands/archive/plan/landscape.js +161 -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 +251 -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 +194 -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 +236 -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 +250 -0
  246. package/dist/commands/validate/service/service.js.map +1 -0
  247. package/dist/commands/validate/service/specs.js +219 -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 +334 -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 +47 -0
  392. package/dist/core/brief/unchecked.js.map +1 -0
  393. package/dist/core/c4/arch.js +282 -0
  394. package/dist/core/c4/arch.js.map +1 -0
  395. package/dist/core/c4/likec4.js +88 -0
  396. package/dist/core/c4/likec4.js.map +1 -0
  397. package/dist/core/c4/parsed/dynamic-views.js +132 -0
  398. package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
  399. package/dist/core/c4/parsed/specification.js +78 -0
  400. package/dist/core/c4/parsed/specification.js.map +1 -0
  401. package/dist/core/c4/parsed/values.js +57 -0
  402. package/dist/core/c4/parsed/values.js.map +1 -0
  403. package/dist/core/c4/parsed/view-ids.js +75 -0
  404. package/dist/core/c4/parsed/view-ids.js.map +1 -0
  405. package/dist/core/c4/project/architecture.js +45 -0
  406. package/dist/core/c4/project/architecture.js.map +1 -0
  407. package/dist/core/c4/project/documents.js +58 -0
  408. package/dist/core/c4/project/documents.js.map +1 -0
  409. package/dist/core/c4/project/load.js +183 -0
  410. package/dist/core/c4/project/load.js.map +1 -0
  411. package/dist/core/c4/resolve/service.js +113 -0
  412. package/dist/core/c4/resolve/service.js.map +1 -0
  413. package/dist/core/c4/seed/fleet-file.js +201 -0
  414. package/dist/core/c4/seed/fleet-file.js.map +1 -0
  415. package/dist/core/c4/seed/items.js +91 -0
  416. package/dist/core/c4/seed/items.js.map +1 -0
  417. package/dist/core/c4/seed/stamp.js +48 -0
  418. package/dist/core/c4/seed/stamp.js.map +1 -0
  419. package/dist/core/c4/seed/template.js +109 -0
  420. package/dist/core/c4/seed/template.js.map +1 -0
  421. package/dist/core/c4/source-mask.js +82 -0
  422. package/dist/core/c4/source-mask.js.map +1 -0
  423. package/dist/core/c4/source-scan.js +227 -0
  424. package/dist/core/c4/source-scan.js.map +1 -0
  425. package/dist/core/c4/splice/authored-source.js +101 -0
  426. package/dist/core/c4/splice/authored-source.js.map +1 -0
  427. package/dist/core/c4/splice/contract.js +10 -0
  428. package/dist/core/c4/splice/contract.js.map +1 -0
  429. package/dist/core/c4/splice/delta-blocks.js +126 -0
  430. package/dist/core/c4/splice/delta-blocks.js.map +1 -0
  431. package/dist/core/c4/splice/identity/edges.js +36 -0
  432. package/dist/core/c4/splice/identity/edges.js.map +1 -0
  433. package/dist/core/c4/splice/landscape-merge.js +269 -0
  434. package/dist/core/c4/splice/landscape-merge.js.map +1 -0
  435. package/dist/core/c4/splice/placement.js +194 -0
  436. package/dist/core/c4/splice/placement.js.map +1 -0
  437. package/dist/core/c4/workspace.js +172 -0
  438. package/dist/core/c4/workspace.js.map +1 -0
  439. package/dist/core/capabilities/authoring/notes.js +141 -0
  440. package/dist/core/capabilities/authoring/notes.js.map +1 -0
  441. package/dist/core/capabilities/capabilities.js +124 -0
  442. package/dist/core/capabilities/capabilities.js.map +1 -0
  443. package/dist/core/capabilities/delta/doc.js +50 -0
  444. package/dist/core/capabilities/delta/doc.js.map +1 -0
  445. package/dist/core/capabilities/delta/overlay.js +129 -0
  446. package/dist/core/capabilities/delta/overlay.js.map +1 -0
  447. package/dist/core/capabilities/delta/summary.js +57 -0
  448. package/dist/core/capabilities/delta/summary.js.map +1 -0
  449. package/dist/core/capabilities/delta/tree.js +43 -0
  450. package/dist/core/capabilities/delta/tree.js.map +1 -0
  451. package/dist/core/capabilities/delta/uncovered.js +222 -0
  452. package/dist/core/capabilities/delta/uncovered.js.map +1 -0
  453. package/dist/core/capabilities/findings.js +342 -0
  454. package/dist/core/capabilities/findings.js.map +1 -0
  455. package/dist/core/capabilities/realizes/findings.js +181 -0
  456. package/dist/core/capabilities/realizes/findings.js.map +1 -0
  457. package/dist/core/capabilities/realizes/join.js +143 -0
  458. package/dist/core/capabilities/realizes/join.js.map +1 -0
  459. package/dist/core/capabilities/rollup.js +199 -0
  460. package/dist/core/capabilities/rollup.js.map +1 -0
  461. package/dist/core/capabilities/tree.js +126 -0
  462. package/dist/core/capabilities/tree.js.map +1 -0
  463. package/dist/core/capabilities/usecase-join.js +277 -0
  464. package/dist/core/capabilities/usecase-join.js.map +1 -0
  465. package/dist/core/coherence/authoring/scaffold.js +177 -0
  466. package/dist/core/coherence/authoring/scaffold.js.map +1 -0
  467. package/dist/core/coherence/authoring/sentinels.js +74 -0
  468. package/dist/core/coherence/authoring/sentinels.js.map +1 -0
  469. package/dist/core/coherence/coherence.js +251 -0
  470. package/dist/core/coherence/coherence.js.map +1 -0
  471. package/dist/core/coherence/declared.js +269 -0
  472. package/dist/core/coherence/declared.js.map +1 -0
  473. package/dist/core/coherence/events/declared.js +206 -0
  474. package/dist/core/coherence/events/declared.js.map +1 -0
  475. package/dist/core/coherence/events/events.js +204 -0
  476. package/dist/core/coherence/events/events.js.map +1 -0
  477. package/dist/core/coherence/events/grades.js +166 -0
  478. package/dist/core/coherence/events/grades.js.map +1 -0
  479. package/dist/core/coherence/events/lookups.js +122 -0
  480. package/dist/core/coherence/events/lookups.js.map +1 -0
  481. package/dist/core/coherence/events/merged.js +40 -0
  482. package/dist/core/coherence/events/merged.js.map +1 -0
  483. package/dist/core/coherence/living.js +198 -0
  484. package/dist/core/coherence/living.js.map +1 -0
  485. package/dist/core/coherence/lookups.js +155 -0
  486. package/dist/core/coherence/lookups.js.map +1 -0
  487. package/dist/core/coherence/pending.js +39 -0
  488. package/dist/core/coherence/pending.js.map +1 -0
  489. package/dist/core/conflict-markers.js +60 -0
  490. package/dist/core/conflict-markers.js.map +1 -0
  491. package/dist/core/delta/claims.js +73 -0
  492. package/dist/core/delta/claims.js.map +1 -0
  493. package/dist/core/delta/delta.js +296 -0
  494. package/dist/core/delta/delta.js.map +1 -0
  495. package/dist/core/delta/document.js +177 -0
  496. package/dist/core/delta/document.js.map +1 -0
  497. package/dist/core/delta/scope.js +48 -0
  498. package/dist/core/delta/scope.js.map +1 -0
  499. package/dist/core/delta/select.js +237 -0
  500. package/dist/core/delta/select.js.map +1 -0
  501. package/dist/core/dependencies/dependencies.js +141 -0
  502. package/dist/core/dependencies/dependencies.js.map +1 -0
  503. package/dist/core/dependencies/facts.js +165 -0
  504. package/dist/core/dependencies/facts.js.map +1 -0
  505. package/dist/core/dependencies/fanin.js +88 -0
  506. package/dist/core/dependencies/fanin.js.map +1 -0
  507. package/dist/core/dependencies/graph.js +106 -0
  508. package/dist/core/dependencies/graph.js.map +1 -0
  509. package/dist/core/diff/base-git.js +148 -0
  510. package/dist/core/diff/base-git.js.map +1 -0
  511. package/dist/core/diff/base-state.js +209 -0
  512. package/dist/core/diff/base-state.js.map +1 -0
  513. package/dist/core/diff/current-state.js +96 -0
  514. package/dist/core/diff/current-state.js.map +1 -0
  515. package/dist/core/diff/scope/changed-paths.js +188 -0
  516. package/dist/core/diff/scope/changed-paths.js.map +1 -0
  517. package/dist/core/diff/semantic.js +243 -0
  518. package/dist/core/diff/semantic.js.map +1 -0
  519. package/dist/core/diff/victims.js +247 -0
  520. package/dist/core/diff/victims.js.map +1 -0
  521. package/dist/core/docs.js +7 -64
  522. package/dist/core/docs.js.map +1 -1
  523. package/dist/core/doctor/agents.js +222 -0
  524. package/dist/core/doctor/agents.js.map +1 -0
  525. package/dist/core/doctor/config.js +135 -0
  526. package/dist/core/doctor/config.js.map +1 -0
  527. package/dist/core/doctor/doctor.js +275 -0
  528. package/dist/core/doctor/doctor.js.map +1 -0
  529. package/dist/core/doctor/report.js +13 -0
  530. package/dist/core/doctor/report.js.map +1 -0
  531. package/dist/core/doctor/residue.js +110 -0
  532. package/dist/core/doctor/residue.js.map +1 -0
  533. package/dist/core/document/apply.js +77 -0
  534. package/dist/core/document/apply.js.map +1 -0
  535. package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
  536. package/dist/core/document/frontmatter.js.map +1 -0
  537. package/dist/core/document/grammar/directives.js +143 -0
  538. package/dist/core/document/grammar/directives.js.map +1 -0
  539. package/dist/core/document/parse.js +263 -0
  540. package/dist/core/document/parse.js.map +1 -0
  541. package/dist/core/document/scenarios.js +221 -0
  542. package/dist/core/document/scenarios.js.map +1 -0
  543. package/dist/core/document/spec.js +241 -0
  544. package/dist/core/document/spec.js.map +1 -0
  545. package/dist/core/{config.js → envelope/config.js} +79 -18
  546. package/dist/core/envelope/config.js.map +1 -0
  547. package/dist/core/envelope/json.js +175 -0
  548. package/dist/core/envelope/json.js.map +1 -0
  549. package/dist/core/{version.js → envelope/version.js} +3 -3
  550. package/dist/core/envelope/version.js.map +1 -0
  551. package/dist/core/explain/families.js +175 -0
  552. package/dist/core/explain/families.js.map +1 -0
  553. package/dist/core/explain/fix-tables.js +178 -0
  554. package/dist/core/explain/fix-tables.js.map +1 -0
  555. package/dist/core/explain/lookup.js +181 -0
  556. package/dist/core/explain/lookup.js.map +1 -0
  557. package/dist/core/explain/refusals.js +49 -0
  558. package/dist/core/explain/refusals.js.map +1 -0
  559. package/dist/core/explain/terms.js +144 -0
  560. package/dist/core/explain/terms.js.map +1 -0
  561. package/dist/core/explore/describe.js +105 -0
  562. package/dist/core/explore/describe.js.map +1 -0
  563. package/dist/core/{explore.js → explore/explore.js} +50 -101
  564. package/dist/core/explore/explore.js.map +1 -0
  565. package/dist/core/fleet-context.js +120 -78
  566. package/dist/core/fleet-context.js.map +1 -1
  567. package/dist/core/gate/checks.js +201 -0
  568. package/dist/core/gate/checks.js.map +1 -0
  569. package/dist/core/gate/partners.js +205 -0
  570. package/dist/core/gate/partners.js.map +1 -0
  571. package/dist/core/gate/report.js +47 -0
  572. package/dist/core/gate/report.js.map +1 -0
  573. package/dist/core/gate/verification.js +113 -0
  574. package/dist/core/gate/verification.js.map +1 -0
  575. package/dist/core/gherkin/digest.js +60 -0
  576. package/dist/core/gherkin/digest.js.map +1 -0
  577. package/dist/core/gherkin/emit.js +177 -0
  578. package/dist/core/gherkin/emit.js.map +1 -0
  579. package/dist/core/gherkin/read.js +76 -0
  580. package/dist/core/gherkin/read.js.map +1 -0
  581. package/dist/core/gherkin/stale.js +234 -0
  582. package/dist/core/gherkin/stale.js.map +1 -0
  583. package/dist/core/gherkin/stamp.js +77 -0
  584. package/dist/core/gherkin/stamp.js.map +1 -0
  585. package/dist/core/gherkin/steps/catalogue.js +154 -0
  586. package/dist/core/gherkin/steps/catalogue.js.map +1 -0
  587. package/dist/core/gherkin/steps/inventory.js +98 -0
  588. package/dist/core/gherkin/steps/inventory.js.map +1 -0
  589. package/dist/core/gherkin/steps/phrase.js +72 -0
  590. package/dist/core/gherkin/steps/phrase.js.map +1 -0
  591. package/dist/core/glossary/delta.js +106 -0
  592. package/dist/core/glossary/delta.js.map +1 -0
  593. package/dist/core/glossary/tree.js +86 -0
  594. package/dist/core/glossary/tree.js.map +1 -0
  595. package/dist/core/kernel/concurrency.js.map +1 -0
  596. package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
  597. package/dist/core/kernel/document-bytes.js.map +1 -0
  598. package/dist/core/kernel/fences.js +42 -0
  599. package/dist/core/kernel/fences.js.map +1 -0
  600. package/dist/core/kernel/ids/capability.js +72 -0
  601. package/dist/core/kernel/ids/capability.js.map +1 -0
  602. package/dist/core/kernel/ids/dirs.js +33 -0
  603. package/dist/core/kernel/ids/dirs.js.map +1 -0
  604. package/dist/core/kernel/ids/feature.js +75 -0
  605. package/dist/core/kernel/ids/feature.js.map +1 -0
  606. package/dist/core/kernel/ids/service.js +156 -0
  607. package/dist/core/kernel/ids/service.js.map +1 -0
  608. package/dist/core/kernel/ids/subsystem.js +63 -0
  609. package/dist/core/kernel/ids/subsystem.js.map +1 -0
  610. package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
  611. package/dist/core/kernel/path-safety.js.map +1 -0
  612. package/dist/core/kernel/records.js.map +1 -0
  613. package/dist/core/links/backlinks.js +68 -0
  614. package/dist/core/links/backlinks.js.map +1 -0
  615. package/dist/core/links/case.js +74 -0
  616. package/dist/core/links/case.js.map +1 -0
  617. package/dist/core/links/corpus.js +104 -0
  618. package/dist/core/links/corpus.js.map +1 -0
  619. package/dist/core/links/findings.js +110 -0
  620. package/dist/core/links/findings.js.map +1 -0
  621. package/dist/core/links/parse.js +236 -0
  622. package/dist/core/links/parse.js.map +1 -0
  623. package/dist/core/mcp/argv.js +111 -0
  624. package/dist/core/mcp/argv.js.map +1 -0
  625. package/dist/core/mcp/author-tools.js +72 -0
  626. package/dist/core/mcp/author-tools.js.map +1 -0
  627. package/dist/core/mcp/framing.js +46 -0
  628. package/dist/core/mcp/framing.js.map +1 -0
  629. package/dist/core/mcp/protocol.js +280 -0
  630. package/dist/core/mcp/protocol.js.map +1 -0
  631. package/dist/core/mcp/tools.js +314 -0
  632. package/dist/core/mcp/tools.js.map +1 -0
  633. package/dist/core/obligations/obligations.js +106 -0
  634. package/dist/core/obligations/obligations.js.map +1 -0
  635. package/dist/core/openapi/baseline/gate.js +166 -0
  636. package/dist/core/openapi/baseline/gate.js.map +1 -0
  637. package/dist/core/openapi/baseline/plan.js +112 -0
  638. package/dist/core/openapi/baseline/plan.js.map +1 -0
  639. package/dist/core/openapi/baseline/record.js +175 -0
  640. package/dist/core/openapi/baseline/record.js.map +1 -0
  641. package/dist/core/openapi/depth.js +122 -0
  642. package/dist/core/openapi/depth.js.map +1 -0
  643. package/dist/core/openapi/digest.js +189 -0
  644. package/dist/core/openapi/digest.js.map +1 -0
  645. package/dist/core/{openapi.js → openapi/doc.js} +44 -106
  646. package/dist/core/openapi/doc.js.map +1 -0
  647. package/dist/core/openapi/merge/components.js +234 -0
  648. package/dist/core/openapi/merge/components.js.map +1 -0
  649. package/dist/core/openapi/merge/error.js +26 -0
  650. package/dist/core/openapi/merge/error.js.map +1 -0
  651. package/dist/core/openapi/merge/markers.js +208 -0
  652. package/dist/core/openapi/merge/markers.js.map +1 -0
  653. package/dist/core/openapi/merge/merge.js +256 -0
  654. package/dist/core/openapi/merge/merge.js.map +1 -0
  655. package/dist/core/openapi/merge/pin.js +128 -0
  656. package/dist/core/openapi/merge/pin.js.map +1 -0
  657. package/dist/core/openspec/decide/capabilities.js +244 -0
  658. package/dist/core/openspec/decide/capabilities.js.map +1 -0
  659. package/dist/core/openspec/decide/dispositions.js +63 -0
  660. package/dist/core/openspec/decide/dispositions.js.map +1 -0
  661. package/dist/core/openspec/decide/renames.js +179 -0
  662. package/dist/core/openspec/decide/renames.js.map +1 -0
  663. package/dist/core/openspec/decide/verdict.js +84 -0
  664. package/dist/core/openspec/decide/verdict.js.map +1 -0
  665. package/dist/core/openspec/inventory.js +221 -0
  666. package/dist/core/openspec/inventory.js.map +1 -0
  667. package/dist/core/openspec/model/mapping.js +76 -0
  668. package/dist/core/openspec/model/mapping.js.map +1 -0
  669. package/dist/core/openspec/model/model.js +65 -0
  670. package/dist/core/openspec/model/model.js.map +1 -0
  671. package/dist/core/openspec/scan/changes.js +235 -0
  672. package/dist/core/openspec/scan/changes.js.map +1 -0
  673. package/dist/core/openspec/scan/config.js +200 -0
  674. package/dist/core/openspec/scan/config.js.map +1 -0
  675. package/dist/core/openspec/scan/shape.js +260 -0
  676. package/dist/core/openspec/scan/shape.js.map +1 -0
  677. package/dist/core/openspec/scan/walk.js +133 -0
  678. package/dist/core/openspec/scan/walk.js.map +1 -0
  679. package/dist/core/openspec/scan/workspace.js +71 -0
  680. package/dist/core/openspec/scan/workspace.js.map +1 -0
  681. package/dist/core/owners/codeowners.js +141 -0
  682. package/dist/core/owners/codeowners.js.map +1 -0
  683. package/dist/core/pack/features.js +120 -0
  684. package/dist/core/pack/features.js.map +1 -0
  685. package/dist/core/pack/joins.js +112 -0
  686. package/dist/core/pack/joins.js.map +1 -0
  687. package/dist/core/pack/living.js +118 -0
  688. package/dist/core/pack/living.js.map +1 -0
  689. package/dist/core/pack/pack.js +76 -0
  690. package/dist/core/pack/pack.js.map +1 -0
  691. package/dist/core/permissions/permissions.js +87 -0
  692. package/dist/core/permissions/permissions.js.map +1 -0
  693. package/dist/core/projection/api.js +84 -0
  694. package/dist/core/projection/api.js.map +1 -0
  695. package/dist/core/projection/arch-slice.js +63 -0
  696. package/dist/core/projection/arch-slice.js.map +1 -0
  697. package/dist/core/projection/events.js +34 -0
  698. package/dist/core/projection/events.js.map +1 -0
  699. package/dist/core/provenance/findings.js +208 -0
  700. package/dist/core/provenance/findings.js.map +1 -0
  701. package/dist/core/provenance/git.js +225 -0
  702. package/dist/core/provenance/git.js.map +1 -0
  703. package/dist/core/provenance/gitq/moves.js +182 -0
  704. package/dist/core/provenance/gitq/moves.js.map +1 -0
  705. package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
  706. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
  707. package/dist/core/provenance/sample/scope.js +238 -0
  708. package/dist/core/provenance/sample/scope.js.map +1 -0
  709. package/dist/core/provenance/sample/sections.js +61 -0
  710. package/dist/core/provenance/sample/sections.js.map +1 -0
  711. package/dist/core/provenance/sources.js +263 -0
  712. package/dist/core/provenance/sources.js.map +1 -0
  713. package/dist/core/provenance/stamp.js +232 -0
  714. package/dist/core/provenance/stamp.js.map +1 -0
  715. package/dist/core/provenance/walk.js +157 -0
  716. package/dist/core/provenance/walk.js.map +1 -0
  717. package/dist/core/repo/authored/paths.js +192 -0
  718. package/dist/core/repo/authored/paths.js.map +1 -0
  719. package/dist/core/repo/entries.js +126 -0
  720. package/dist/core/repo/entries.js.map +1 -0
  721. package/dist/core/repo/paths.js +236 -0
  722. package/dist/core/repo/paths.js.map +1 -0
  723. package/dist/core/repo/repo.js +248 -0
  724. package/dist/core/repo/repo.js.map +1 -0
  725. package/dist/core/repo/service-target.js +153 -0
  726. package/dist/core/repo/service-target.js.map +1 -0
  727. package/dist/core/repo/state.js +58 -0
  728. package/dist/core/repo/state.js.map +1 -0
  729. package/dist/core/repo/tree/find.js +52 -0
  730. package/dist/core/repo/tree/find.js.map +1 -0
  731. package/dist/core/repo/tree/fs.js +64 -0
  732. package/dist/core/repo/tree/fs.js.map +1 -0
  733. package/dist/core/repo/tree/marker.js +61 -0
  734. package/dist/core/repo/tree/marker.js.map +1 -0
  735. package/dist/core/repo/tree/views.js +172 -0
  736. package/dist/core/repo/tree/views.js.map +1 -0
  737. package/dist/core/repo/tree/walk.js +234 -0
  738. package/dist/core/repo/tree/walk.js.map +1 -0
  739. package/dist/core/results.js +99 -2
  740. package/dist/core/results.js.map +1 -1
  741. package/dist/core/scaffold/landscape.js +131 -0
  742. package/dist/core/scaffold/landscape.js.map +1 -0
  743. package/dist/core/scaffold/readme.js +84 -0
  744. package/dist/core/scaffold/readme.js.map +1 -0
  745. package/dist/core/staging/commit.js +241 -0
  746. package/dist/core/staging/commit.js.map +1 -0
  747. package/dist/core/staging/interrupted.js +96 -0
  748. package/dist/core/staging/interrupted.js.map +1 -0
  749. package/dist/core/staging/lock.js +206 -0
  750. package/dist/core/staging/lock.js.map +1 -0
  751. package/dist/core/staging/recovery/finding.js +35 -0
  752. package/dist/core/staging/recovery/finding.js.map +1 -0
  753. package/dist/core/staging/recovery/intent.js +98 -0
  754. package/dist/core/staging/recovery/intent.js.map +1 -0
  755. package/dist/core/staging/recovery/recover.js +126 -0
  756. package/dist/core/staging/recovery/recover.js.map +1 -0
  757. package/dist/core/staging/recovery/residue.js +62 -0
  758. package/dist/core/staging/recovery/residue.js.map +1 -0
  759. package/dist/core/staging/snapshot.js +202 -0
  760. package/dist/core/staging/snapshot.js.map +1 -0
  761. package/dist/core/staging/txn/forward.js +171 -0
  762. package/dist/core/staging/txn/forward.js.map +1 -0
  763. package/dist/core/staging/txn/journal.js +139 -0
  764. package/dist/core/staging/txn/journal.js.map +1 -0
  765. package/dist/core/staging/txn/transaction.js +75 -0
  766. package/dist/core/staging/txn/transaction.js.map +1 -0
  767. package/dist/core/staging/writes.js +113 -0
  768. package/dist/core/staging/writes.js.map +1 -0
  769. package/dist/core/status/actions/execution.js +49 -0
  770. package/dist/core/status/actions/execution.js.map +1 -0
  771. package/dist/core/status/contracts.js +102 -0
  772. package/dist/core/status/contracts.js.map +1 -0
  773. package/dist/core/status/feature/artifacts.js +171 -0
  774. package/dist/core/status/feature/artifacts.js.map +1 -0
  775. package/dist/core/status/feature/feature.js +207 -0
  776. package/dist/core/status/feature/feature.js.map +1 -0
  777. package/dist/core/status/feature/next.js +150 -0
  778. package/dist/core/status/feature/next.js.map +1 -0
  779. package/dist/core/status/feature/state.js +21 -0
  780. package/dist/core/status/feature/state.js.map +1 -0
  781. package/dist/core/status/fleet/fleet.js +187 -0
  782. package/dist/core/status/fleet/fleet.js.map +1 -0
  783. package/dist/core/status/fleet/next.js +207 -0
  784. package/dist/core/status/fleet/next.js.map +1 -0
  785. package/dist/core/status/interrupted.js +97 -0
  786. package/dist/core/status/interrupted.js.map +1 -0
  787. package/dist/core/status/report.js +31 -0
  788. package/dist/core/status/report.js.map +1 -0
  789. package/dist/core/status/scan.js +41 -0
  790. package/dist/core/status/scan.js.map +1 -0
  791. package/dist/core/status/verification.js +201 -0
  792. package/dist/core/status/verification.js.map +1 -0
  793. package/dist/core/usecases/capability.js +169 -0
  794. package/dist/core/usecases/capability.js.map +1 -0
  795. package/dist/core/usecases/delta/claims.js +116 -0
  796. package/dist/core/usecases/delta/claims.js.map +1 -0
  797. package/dist/core/usecases/delta/flows.js +103 -0
  798. package/dist/core/usecases/delta/flows.js.map +1 -0
  799. package/dist/core/usecases/delta/overlay.js +235 -0
  800. package/dist/core/usecases/delta/overlay.js.map +1 -0
  801. package/dist/core/usecases/fleet.js +137 -0
  802. package/dist/core/usecases/fleet.js.map +1 -0
  803. package/dist/core/usecases/operations.js +111 -0
  804. package/dist/core/usecases/operations.js.map +1 -0
  805. package/dist/core/usecases/place.js +44 -0
  806. package/dist/core/usecases/place.js.map +1 -0
  807. package/dist/core/usecases/touch.js +101 -0
  808. package/dist/core/usecases/touch.js.map +1 -0
  809. package/dist/core/verify/answers.js +173 -0
  810. package/dist/core/verify/answers.js.map +1 -0
  811. package/dist/core/verify/build.js +150 -0
  812. package/dist/core/verify/build.js.map +1 -0
  813. package/dist/core/verify/checklist.js +201 -0
  814. package/dist/core/verify/checklist.js.map +1 -0
  815. package/dist/core/verify/claims/identity.js +61 -0
  816. package/dist/core/verify/claims/identity.js.map +1 -0
  817. package/dist/core/verify/cross/diff.js +97 -0
  818. package/dist/core/verify/cross/diff.js.map +1 -0
  819. package/dist/core/verify/evidence/contract.js +169 -0
  820. package/dist/core/verify/evidence/contract.js.map +1 -0
  821. package/dist/core/verify/file.js +257 -0
  822. package/dist/core/verify/file.js.map +1 -0
  823. package/dist/core/verify/pins/lint.js +157 -0
  824. package/dist/core/verify/pins/lint.js.map +1 -0
  825. package/dist/core/verify/pins/pin.js +95 -0
  826. package/dist/core/verify/pins/pin.js.map +1 -0
  827. package/dist/core/verify/record.js +142 -0
  828. package/dist/core/verify/record.js.map +1 -0
  829. package/dist/core/verify/store/commit.js +89 -0
  830. package/dist/core/verify/store/commit.js.map +1 -0
  831. package/dist/core/verify/store/render.js +57 -0
  832. package/dist/core/verify/store/render.js.map +1 -0
  833. package/dist/core/vocabulary/health.js +91 -0
  834. package/dist/core/vocabulary/health.js.map +1 -0
  835. package/dist/core/vocabulary/issue.js +55 -0
  836. package/dist/core/vocabulary/issue.js.map +1 -0
  837. package/dist/core/vocabulary/maturity.js +145 -0
  838. package/dist/core/vocabulary/maturity.js.map +1 -0
  839. package/dist/core/{report.js → vocabulary/report.js} +30 -2
  840. package/dist/core/vocabulary/report.js.map +1 -0
  841. package/dist/core/vocabulary/steps.js +266 -0
  842. package/dist/core/vocabulary/steps.js.map +1 -0
  843. package/dist/core/workspace/discover.js +239 -0
  844. package/dist/core/workspace/discover.js.map +1 -0
  845. package/dist/core/workspace/render.js +52 -0
  846. package/dist/core/workspace/render.js.map +1 -0
  847. package/examples/README.md +189 -0
  848. package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
  849. package/examples/docs/architecture/capabilities.yaml +31 -0
  850. package/examples/docs/architecture/landscape.likec4 +280 -0
  851. package/examples/docs/architecture/obligations.yaml +23 -0
  852. package/examples/docs/architecture/permissions.yaml +50 -0
  853. package/examples/docs/architecture/subsystems.likec4 +11 -0
  854. package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
  855. package/examples/docs/capabilities/checkout/spec.md +55 -0
  856. package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
  857. package/examples/docs/capabilities/order-notifications/spec.md +43 -0
  858. package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
  859. package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
  860. package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
  861. package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
  862. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
  863. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
  864. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
  865. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
  866. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
  867. package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
  868. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
  869. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
  870. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
  871. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
  872. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
  873. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
  874. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
  875. package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
  876. package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
  877. package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
  878. package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
  879. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
  880. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
  881. package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
  882. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
  883. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
  884. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
  885. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
  886. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
  887. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
  888. package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
  889. package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
  890. package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
  891. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
  892. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
  893. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
  894. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
  895. package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
  896. package/examples/docs/glossary/order.md +15 -0
  897. package/examples/docs/glossary/payments/authorization.md +14 -0
  898. package/examples/docs/likec4.config.json +9 -0
  899. package/examples/docs/loam.json +3 -0
  900. package/examples/docs/services/checkout-web/model.likec4 +109 -0
  901. package/examples/docs/services/checkout-web/spec.md +91 -0
  902. package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
  903. package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
  904. package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
  905. package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
  906. package/examples/docs/services/order-service/arch.spec.md +127 -0
  907. package/examples/docs/services/order-service/asyncapi.yaml +108 -0
  908. package/examples/docs/services/order-service/health.yaml +57 -0
  909. package/examples/docs/services/order-service/model.likec4 +138 -0
  910. package/examples/docs/services/order-service/openapi.yaml +257 -0
  911. package/examples/docs/services/order-service/runbook.md +62 -0
  912. package/examples/docs/services/order-service/spec.md +134 -0
  913. package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
  914. package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
  915. package/examples/docs/services/payment-service/arch.spec.md +73 -0
  916. package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
  917. package/examples/docs/services/payment-service/health.yaml +39 -0
  918. package/examples/docs/services/payment-service/model.likec4 +67 -0
  919. package/examples/docs/services/payment-service/openapi.yaml +151 -0
  920. package/examples/docs/services/payment-service/runbook.md +37 -0
  921. package/examples/docs/services/payment-service/spec.md +88 -0
  922. package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
  923. package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
  924. package/examples/docs/services/platform/identity-service/health.yaml +70 -0
  925. package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
  926. package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
  927. package/examples/docs/services/platform/identity-service/runbook.md +95 -0
  928. package/examples/docs/services/platform/identity-service/spec.md +119 -0
  929. package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
  930. package/examples/docs/services/platform/notification-service/health.yaml +35 -0
  931. package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
  932. package/examples/docs/services/platform/notification-service/runbook.md +48 -0
  933. package/examples/docs/services/platform/notification-service/spec.md +112 -0
  934. package/examples/docs/services/platform/subsystem.yaml +2 -0
  935. package/examples/fleet.yaml +57 -0
  936. package/package.json +19 -6
  937. package/dist/commands/adopt.js +0 -240
  938. package/dist/commands/adopt.js.map +0 -1
  939. package/dist/commands/archive.js +0 -1313
  940. package/dist/commands/archive.js.map +0 -1
  941. package/dist/commands/delta.js +0 -388
  942. package/dist/commands/delta.js.map +0 -1
  943. package/dist/commands/docs-repo-gate.js.map +0 -1
  944. package/dist/commands/format.js +0 -17
  945. package/dist/commands/format.js.map +0 -1
  946. package/dist/commands/gherkin.js +0 -309
  947. package/dist/commands/gherkin.js.map +0 -1
  948. package/dist/commands/init.js.map +0 -1
  949. package/dist/commands/list.js +0 -295
  950. package/dist/commands/list.js.map +0 -1
  951. package/dist/commands/migrate-openspec.js +0 -950
  952. package/dist/commands/migrate-openspec.js.map +0 -1
  953. package/dist/commands/new.js +0 -413
  954. package/dist/commands/new.js.map +0 -1
  955. package/dist/commands/rebase.js +0 -356
  956. package/dist/commands/rebase.js.map +0 -1
  957. package/dist/commands/show.js.map +0 -1
  958. package/dist/commands/status.js +0 -262
  959. package/dist/commands/status.js.map +0 -1
  960. package/dist/commands/unarchive.js.map +0 -1
  961. package/dist/commands/validate.js +0 -1773
  962. package/dist/commands/validate.js.map +0 -1
  963. package/dist/commands/verify.js +0 -775
  964. package/dist/commands/verify.js.map +0 -1
  965. package/dist/commands/vouch.js +0 -421
  966. package/dist/commands/vouch.js.map +0 -1
  967. package/dist/core/agent.js +0 -2173
  968. package/dist/core/agent.js.map +0 -1
  969. package/dist/core/agents-stamp.js.map +0 -1
  970. package/dist/core/arch.js +0 -130
  971. package/dist/core/arch.js.map +0 -1
  972. package/dist/core/asyncapi.js.map +0 -1
  973. package/dist/core/brief.js +0 -755
  974. package/dist/core/brief.js.map +0 -1
  975. package/dist/core/coherence.js +0 -577
  976. package/dist/core/coherence.js.map +0 -1
  977. package/dist/core/concurrency.js.map +0 -1
  978. package/dist/core/config.js.map +0 -1
  979. package/dist/core/delta.js +0 -476
  980. package/dist/core/delta.js.map +0 -1
  981. package/dist/core/dependencies.js +0 -356
  982. package/dist/core/dependencies.js.map +0 -1
  983. package/dist/core/doctor.js +0 -596
  984. package/dist/core/doctor.js.map +0 -1
  985. package/dist/core/document-bytes.js.map +0 -1
  986. package/dist/core/explore.js.map +0 -1
  987. package/dist/core/frontmatter.js.map +0 -1
  988. package/dist/core/gherkin.js +0 -463
  989. package/dist/core/gherkin.js.map +0 -1
  990. package/dist/core/health.js +0 -67
  991. package/dist/core/health.js.map +0 -1
  992. package/dist/core/ids.js +0 -122
  993. package/dist/core/ids.js.map +0 -1
  994. package/dist/core/issue.js +0 -9
  995. package/dist/core/issue.js.map +0 -1
  996. package/dist/core/json.js +0 -74
  997. package/dist/core/json.js.map +0 -1
  998. package/dist/core/likec4.js +0 -489
  999. package/dist/core/likec4.js.map +0 -1
  1000. package/dist/core/maturity.js +0 -66
  1001. package/dist/core/maturity.js.map +0 -1
  1002. package/dist/core/openapi-merge.js +0 -552
  1003. package/dist/core/openapi-merge.js.map +0 -1
  1004. package/dist/core/openapi.js.map +0 -1
  1005. package/dist/core/openspec-inventory.js +0 -1440
  1006. package/dist/core/openspec-inventory.js.map +0 -1
  1007. package/dist/core/path-safety.js.map +0 -1
  1008. package/dist/core/provenance.js +0 -787
  1009. package/dist/core/provenance.js.map +0 -1
  1010. package/dist/core/records.js.map +0 -1
  1011. package/dist/core/repo.js +0 -438
  1012. package/dist/core/repo.js.map +0 -1
  1013. package/dist/core/report.js.map +0 -1
  1014. package/dist/core/spec.js +0 -505
  1015. package/dist/core/spec.js.map +0 -1
  1016. package/dist/core/staging.js +0 -890
  1017. package/dist/core/staging.js.map +0 -1
  1018. package/dist/core/status.js +0 -1132
  1019. package/dist/core/status.js.map +0 -1
  1020. package/dist/core/steps.js +0 -72
  1021. package/dist/core/steps.js.map +0 -1
  1022. package/dist/core/verify.js +0 -807
  1023. package/dist/core/verify.js.map +0 -1
  1024. package/dist/core/version.js.map +0 -1
  1025. /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
  1026. /package/dist/core/{records.js → kernel/records.js} +0 -0
@@ -0,0 +1,243 @@
1
+ /**
2
+ * The diff computation: join the base fleet (`./base-state.ts`) against the
3
+ * working tree (`./current-state.ts`) and say, per service, what changed in
4
+ * FLEET-MEANINGFUL terms — requirements by identity and digest, operations
5
+ * and messages by their join tokens, cross-service joins by tuple — with the
6
+ * current consumers of every removal named (`./victims.ts`).
7
+ *
8
+ * Two disciplines rule every branch here. Removal is graded against who still
9
+ * depends on it: a removal nobody names is a warning, a removal the fleet
10
+ * still joins to is an error and sets `breaking`. And "nobody could look" is
11
+ * never "nothing changed": an unreadable artifact on EITHER side suspends
12
+ * that subject's axis — recorded in `unreadable[]`, counted in the summary,
13
+ * carried into the exit code by the command — instead of letting an
14
+ * unreadable base read as everything-added or an unreadable working file
15
+ * read as everything-removed.
16
+ */
17
+ import { requirementDigest } from "../document/spec.js";
18
+ import { consumerJoinChanges } from "./victims.js";
19
+ /** Severity is a fact about the code, not a call-site choice — one table, no drift. */
20
+ const DIFF_SEVERITY = {
21
+ "diff.service-added": "ok",
22
+ "diff.service-removed": "warn",
23
+ "diff.requirement-added": "ok",
24
+ "diff.requirement-removed": "warn",
25
+ "diff.requirement-modified": "ok",
26
+ "diff.op-added": "ok",
27
+ "diff.op-removed": "warn",
28
+ "diff.op-removed-consumed": "error",
29
+ "diff.op-deprecated": "warn",
30
+ "diff.message-added": "ok",
31
+ "diff.message-removed": "warn",
32
+ "diff.message-removed-consumed": "error",
33
+ "diff.consumer-added": "ok",
34
+ "diff.consumer-removed": "ok",
35
+ };
36
+ function found(code, message, details) {
37
+ const carry = details !== undefined && details.length > 0;
38
+ return { severity: DIFF_SEVERITY[code], code, message, ...(carry ? { details } : {}) };
39
+ }
40
+ /** A side the service does not exist on: absence everywhere, honestly. */
41
+ const ABSENT = { kind: "absent" };
42
+ const emptyState = (id) => ({ id, dir: "", spec: ABSENT, archSpec: ABSENT, openapi: ABSENT, asyncapi: ABSENT });
43
+ /**
44
+ * Guard one axis pair: record unreadable sides on the sink and say whether
45
+ * the axis may be compared at all. Reading one good side against a suspended
46
+ * one would grade absence it never proved.
47
+ */
48
+ function comparable(sink, axis, pair) {
49
+ for (const side of ["base", "current"]) {
50
+ const state = pair[side];
51
+ if (state.kind === "unreadable")
52
+ sink.unreadable.push({ side, axis, path: state.path, error: state.error });
53
+ }
54
+ return pair.base.kind !== "unreadable" && pair.current.kind !== "unreadable";
55
+ }
56
+ const valueOr = (axis) => (axis.kind === "read" ? axis.value : []);
57
+ /** Identity: `Requirement-ID:` when authored, the heading otherwise — the same join `loam rebase` pins against. */
58
+ const reqKey = (r) => (r.id !== undefined ? `id:${r.id}` : `name:${r.name}`);
59
+ const reqLabel = (r) => (r.id !== undefined ? `${r.name} [${r.id}]` : r.name);
60
+ /** First declaration wins a duplicated key — the same arbitrary-but-stated pick every join on these axes makes. */
61
+ function firstWins(items, key) {
62
+ const map = new Map();
63
+ for (const item of items)
64
+ if (!map.has(key(item)))
65
+ map.set(key(item), item);
66
+ return map;
67
+ }
68
+ /** Entry comparator by map key — explicit, because the default sort stringifies whole entries. */
69
+ const byKey = (a, b) => (a[0] < b[0] ? -1 : 1);
70
+ function requirementsDiff(sink, pair) {
71
+ // v1 diffs spec.md's requirements only; arch.spec.md is read for the
72
+ // Consumes: join but its requirement inventory is deferred (the two axes
73
+ // are separate namespaces, and diffing both doubles the surface). Both
74
+ // pairs are guarded BEFORE the early return, so a service whose two spec
75
+ // files are both unreadable reports both suspensions, not just the first.
76
+ const specComparable = comparable(sink, "spec", { base: pair.base.spec, current: pair.current.spec });
77
+ comparable(sink, "archSpec", { base: pair.base.archSpec, current: pair.current.archSpec });
78
+ if (!specComparable)
79
+ return;
80
+ const base = firstWins(valueOr(pair.base.spec), reqKey);
81
+ const current = firstWins(valueOr(pair.current.spec), reqKey);
82
+ for (const [key, r] of [...current].sort(byKey)) {
83
+ const before = base.get(key);
84
+ if (before === undefined)
85
+ sink.findings.push(found("diff.requirement-added", `living requirement added since base: '${reqLabel(r)}'`));
86
+ else if (requirementDigest(before) !== requirementDigest(r)) {
87
+ sink.findings.push(found("diff.requirement-modified", `living requirement modified since base: '${reqLabel(r)}'`));
88
+ }
89
+ }
90
+ for (const [key, r] of [...base].sort(byKey)) {
91
+ if (!current.has(key))
92
+ sink.findings.push(found("diff.requirement-removed", `living requirement removed since base: '${reqLabel(r)}'`));
93
+ }
94
+ }
95
+ async function opsDiff(sink, pair, victims) {
96
+ if (!comparable(sink, "openapi", { base: pair.base.openapi, current: pair.current.openapi }))
97
+ return;
98
+ const base = firstWins(valueOr(pair.base.openapi), (op) => op.id);
99
+ const current = firstWins(valueOr(pair.current.openapi), (op) => op.id);
100
+ for (const [id, op] of [...current].sort(byKey)) {
101
+ const before = base.get(id);
102
+ if (before === undefined) {
103
+ sink.findings.push(found("diff.op-added", `operation '${id}' added since base (${op.method.toUpperCase()} ${op.path})`));
104
+ }
105
+ else if (!before.deprecated && op.deprecated) {
106
+ const scan = await victims.opConsumers(sink.id, id);
107
+ // Three honest phrasings — never a confident negative over a scan that skipped an unreadable consumer document.
108
+ const stance = scan.victims.length > 0 ? `${scan.victims.length} current consumer(s) still name it`
109
+ : scan.suspended.length > 0 ? "whether current consumers name it could not be fully answered"
110
+ : "no current consumer names it";
111
+ sink.findings.push(found("diff.op-deprecated", `operation '${id}' is deprecated since base — ${stance}`, [...scan.victims, ...scan.suspended]));
112
+ }
113
+ }
114
+ for (const [id] of [...base].sort(byKey)) {
115
+ if (current.has(id))
116
+ continue;
117
+ const scan = await victims.opConsumers(sink.id, id);
118
+ if (scan.victims.length > 0) {
119
+ sink.findings.push(found("diff.op-removed-consumed", `operation '${id}' was removed since base and the current fleet still names it — these joins break`, [...scan.victims, ...scan.suspended]));
120
+ }
121
+ else if (scan.suspended.length > 0) {
122
+ // "Nobody could be scanned" is not "nobody names it" — the consumer's own
123
+ // unreadable[] entry already carries the run to exit 1; this message must
124
+ // not assert a negative the scan never proved.
125
+ sink.findings.push(found("diff.op-removed", `operation '${id}' was removed since base; whether the current fleet still names it could NOT be fully answered — ${scan.suspended.length} consumer document(s) were not scannable`, scan.suspended));
126
+ }
127
+ else {
128
+ sink.findings.push(found("diff.op-removed", `operation '${id}' was removed since base; no current landscape edge or living requirement names it`));
129
+ }
130
+ }
131
+ }
132
+ async function messagesDiff(sink, pair, victims) {
133
+ if (!comparable(sink, "asyncapi", { base: pair.base.asyncapi, current: pair.current.asyncapi }))
134
+ return;
135
+ const base = firstWins(valueOr(pair.base.asyncapi), (m) => m.name);
136
+ const current = firstWins(valueOr(pair.current.asyncapi), (m) => m.name);
137
+ for (const [name] of [...current].sort(byKey)) {
138
+ if (!base.has(name))
139
+ sink.findings.push(found("diff.message-added", `message '${name}' added since base`));
140
+ }
141
+ for (const [name] of [...base].sort(byKey)) {
142
+ if (current.has(name))
143
+ continue;
144
+ // Fleet-global join: a name another current service still declares is not
145
+ // orphaned (positive evidence only — an unreadable contract never vouches).
146
+ const elsewhere = victims.declarers(name).filter((d) => d !== sink.id);
147
+ if (elsewhere.length > 0) {
148
+ sink.findings.push(found("diff.message-removed", `message '${name}' was removed since base; still declared by ${elsewhere.join(", ")}`));
149
+ continue;
150
+ }
151
+ const scan = await victims.messageConsumers(sink.id, name);
152
+ if (scan.victims.length > 0) {
153
+ sink.findings.push(found("diff.message-removed-consumed", `message '${name}' was removed since base and the current fleet still consumes it — these joins break`, [...scan.victims, ...scan.suspended]));
154
+ }
155
+ else if (scan.suspended.length > 0) {
156
+ sink.findings.push(found("diff.message-removed", `message '${name}' was removed since base; whether anything still consumes it could NOT be fully answered — ${scan.suspended.length} consumer document(s) were not scannable`, scan.suspended));
157
+ }
158
+ else {
159
+ sink.findings.push(found("diff.message-removed", `message '${name}' was removed since base; nothing currently consumes it`));
160
+ }
161
+ }
162
+ }
163
+ /** The graded join changes (`victims.ts`) as findings on each PROVIDER — the service whose surface the join lands on. */
164
+ function consumerFindings(input) {
165
+ const changes = consumerJoinChanges({
166
+ base: input.base.services,
167
+ baseAmbiguous: new Set(input.base.ambiguous.keys()),
168
+ current: input.current.services,
169
+ currentAmbiguous: new Set(input.current.ambiguous.keys()),
170
+ });
171
+ const byProvider = new Map();
172
+ for (const { t, change } of changes) {
173
+ const what = t.kind === "op" ? `operation '${t.token}'` : `message '${t.token}'`;
174
+ const message = change === "added"
175
+ ? `${t.consumer}'s living requirements now name this service's ${what} — a cross-service join appeared since base`
176
+ : `${t.consumer}'s living requirements no longer name this service's ${what} — a cross-service join went away`;
177
+ // The call sits directly in the push — assigned to a `const x = found(…)`
178
+ // it reads as an arrow-function DECLARATION to the stable-code collector,
179
+ // and both consumer codes silently leave the guarded set.
180
+ byProvider.set(t.provider, [
181
+ ...(byProvider.get(t.provider) ?? []),
182
+ found(change === "added" ? "diff.consumer-added" : "diff.consumer-removed", message),
183
+ ]);
184
+ }
185
+ return byProvider;
186
+ }
187
+ async function diffService(unit) {
188
+ const sink = { id: unit.id, change: "unchanged", findings: [], unreadable: [] };
189
+ if (unit.base === undefined)
190
+ sink.findings.push(found("diff.service-added", `service '${unit.id}' added since base`));
191
+ if (unit.current === undefined && unit.base !== undefined) {
192
+ sink.findings.push(found("diff.service-removed", `service '${unit.id}' (${unit.base.dir}) is gone from the working tree`));
193
+ }
194
+ const pair = { base: unit.base ?? emptyState(unit.id), current: unit.current ?? emptyState(unit.id) };
195
+ requirementsDiff(sink, pair);
196
+ await opsDiff(sink, pair, unit.victims);
197
+ await messagesDiff(sink, pair, unit.victims);
198
+ return sink;
199
+ }
200
+ export async function computeDiff(input) {
201
+ const sides = [input.base.services, input.base.ambiguous, input.current.services, input.current.ambiguous];
202
+ const ids = [...new Set(sides.flatMap((side) => [...side.keys()]))].sort();
203
+ const joins = consumerFindings({ base: input.base, current: input.current });
204
+ const services = [];
205
+ // Sequential on purpose: the report's order IS the contract (identical
206
+ // states must emit identical bytes), the work per service is in-memory
207
+ // joins, and the one expensive read — the victim index's landscape — is
208
+ // memoised behind its first ask, so a fan-out would buy nothing.
209
+ for (const id of ids) {
210
+ const claimants = [...(input.base.ambiguous.get(id) ?? []), ...(input.current.ambiguous.get(id) ?? [])];
211
+ if (claimants.length > 0) {
212
+ // An id two directories claim — either side — has no single state to
213
+ // diff; the whole subject suspends (containment), and the live walk's
214
+ // `subsystem.name-collision` names the same defect for validate.
215
+ services.push({ id, change: "changed", findings: [], unreadable: [], ambiguous: claimants });
216
+ continue;
217
+ }
218
+ const sink = await diffService({ id, base: input.base.services.get(id), current: input.current.services.get(id), victims: input.victims });
219
+ sink.findings.push(...(joins.get(id) ?? []));
220
+ if (input.base.services.has(id) && !input.current.services.has(id))
221
+ sink.change = "removed";
222
+ else if (!input.base.services.has(id))
223
+ sink.change = "added";
224
+ else
225
+ sink.change = sink.findings.length > 0 || sink.unreadable.length > 0 ? "changed" : "unchanged";
226
+ services.push(sink);
227
+ }
228
+ const all = services.flatMap((s) => s.findings);
229
+ const count = (codes) => all.filter(codes).length;
230
+ return {
231
+ services,
232
+ breaking: all.some((f) => f.severity === "error"),
233
+ summary: {
234
+ added: count((f) => f.code.endsWith("-added")),
235
+ removed: count((f) => f.code.endsWith("-removed") || f.code.endsWith("-removed-consumed")),
236
+ modified: count((f) => f.code === "diff.requirement-modified"),
237
+ deprecated: count((f) => f.code === "diff.op-deprecated"),
238
+ unreadable: services.reduce((n, s) => n + s.unreadable.length, 0) +
239
+ services.filter((s) => s.ambiguous !== undefined).length,
240
+ },
241
+ };
242
+ }
243
+ //# sourceMappingURL=semantic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semantic.js","sourceRoot":"","sources":["../../../src/core/diff/semantic.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The diff computation: join the base fleet (`./base-state.ts`) against the\n * working tree (`./current-state.ts`) and say, per service, what changed in\n * FLEET-MEANINGFUL terms — requirements by identity and digest, operations\n * and messages by their join tokens, cross-service joins by tuple — with the\n * current consumers of every removal named (`./victims.ts`).\n *\n * Two disciplines rule every branch here. Removal is graded against who still\n * depends on it: a removal nobody names is a warning, a removal the fleet\n * still joins to is an error and sets `breaking`. And \"nobody could look\" is\n * never \"nothing changed\": an unreadable artifact on EITHER side suspends\n * that subject's axis — recorded in `unreadable[]`, counted in the summary,\n * carried into the exit code by the command — instead of letting an\n * unreadable base read as everything-added or an unreadable working file\n * read as everything-removed.\n */\nimport { requirementDigest, type Requirement } from \"../document/spec.js\";\nimport type { Severity, Finding } from \"../vocabulary/report.js\";\nimport type { AxisState, BaseFleet, ServiceState } from \"./base-state.js\";\nimport type { CurrentFleet } from \"./current-state.js\";\nimport { consumerJoinChanges, type VictimIndex } from \"./victims.js\";\n\n/**\n * The stable finding codes `loam diff` emits. Codes are the machine contract;\n * prose may be reworded, these may not. Documented (backticked) in the\n * generated AGENTS.md's diff section — codes-drift enforces that.\n */\nexport type DiffCode =\n /** A `services/<id>/` directory exists now that did not at the base ref. */\n | \"diff.service-added\"\n /** A service present at the base ref is gone from the working tree. */\n | \"diff.service-removed\"\n /** A living requirement (by `Requirement-ID:`, else heading) not present at base. */\n | \"diff.requirement-added\"\n /** A base requirement no longer in the living spec. */\n | \"diff.requirement-removed\"\n /** Same requirement identity on both sides, `requirementDigest` moved — rebase pins never move it. */\n | \"diff.requirement-modified\"\n /** An operationId in the living contract absent at base. */\n | \"diff.op-added\"\n /** A base operationId gone from the living contract, and no current consumer names it. */\n | \"diff.op-removed\"\n /** Removed since base AND the current fleet still names it — landscape edge or foreign living requirement; details list the victims. Sets `breaking`. */\n | \"diff.op-removed-consumed\"\n /** `deprecated: true` introduced since base; details name the current consumers. */\n | \"diff.op-deprecated\"\n /** An AsyncAPI message name absent at base. */\n | \"diff.message-added\"\n /** A base message gone, nothing currently consumes it (or another service still declares it — the details say). */\n | \"diff.message-removed\"\n /** Removed since base AND still consumed — a `Consumes:` line or a consumes-edge; details list the victims. Sets `breaking`. */\n | \"diff.message-removed-consumed\"\n /** A cross-service join appeared since base: another service's living requirement now names this service's operation or message. */\n | \"diff.consumer-added\"\n /** A cross-service join present at base is gone. */\n | \"diff.consumer-removed\";\n\n/** Severity is a fact about the code, not a call-site choice — one table, no drift. */\nconst DIFF_SEVERITY: Record<DiffCode, Severity> = {\n \"diff.service-added\": \"ok\",\n \"diff.service-removed\": \"warn\",\n \"diff.requirement-added\": \"ok\",\n \"diff.requirement-removed\": \"warn\",\n \"diff.requirement-modified\": \"ok\",\n \"diff.op-added\": \"ok\",\n \"diff.op-removed\": \"warn\",\n \"diff.op-removed-consumed\": \"error\",\n \"diff.op-deprecated\": \"warn\",\n \"diff.message-added\": \"ok\",\n \"diff.message-removed\": \"warn\",\n \"diff.message-removed-consumed\": \"error\",\n \"diff.consumer-added\": \"ok\",\n \"diff.consumer-removed\": \"ok\",\n};\n\nfunction found(code: DiffCode, message: string, details?: string[]): Finding {\n const carry = details !== undefined && details.length > 0;\n return { severity: DIFF_SEVERITY[code], code, message, ...(carry ? { details } : {}) };\n}\n\n/** One suspended axis: which side could not be read, and why. */\nexport interface UnreadableEntry {\n side: \"base\" | \"current\";\n axis: \"spec\" | \"archSpec\" | \"openapi\" | \"asyncapi\";\n path: string;\n error: string;\n}\n\nexport interface ServiceDiff {\n id: string;\n change: \"added\" | \"removed\" | \"changed\" | \"unchanged\";\n findings: Finding[];\n unreadable: UnreadableEntry[];\n /** Directories claiming this id when more than one does, on either side — every finding suspended. */\n ambiguous?: string[];\n}\n\nexport interface FleetDiff {\n services: ServiceDiff[];\n /** True iff any error-severity finding — a removal the fleet still consumes. */\n breaking: boolean;\n /** `unreadable` counts suspended axes plus ambiguous base identities: places nobody could look. */\n summary: { added: number; removed: number; modified: number; deprecated: number; unreadable: number };\n}\n\n/** A side the service does not exist on: absence everywhere, honestly. */\nconst ABSENT = { kind: \"absent\" } as const;\nconst emptyState = (id: string): ServiceState => ({ id, dir: \"\", spec: ABSENT, archSpec: ABSENT, openapi: ABSENT, asyncapi: ABSENT });\n\n/**\n * Guard one axis pair: record unreadable sides on the sink and say whether\n * the axis may be compared at all. Reading one good side against a suspended\n * one would grade absence it never proved.\n */\nfunction comparable(sink: ServiceDiff, axis: UnreadableEntry[\"axis\"], pair: { base: AxisState<unknown>; current: AxisState<unknown> }): boolean {\n for (const side of [\"base\", \"current\"] as const) {\n const state = pair[side];\n if (state.kind === \"unreadable\") sink.unreadable.push({ side, axis, path: state.path, error: state.error });\n }\n return pair.base.kind !== \"unreadable\" && pair.current.kind !== \"unreadable\";\n}\n\nconst valueOr = <T>(axis: AxisState<T[]>): T[] => (axis.kind === \"read\" ? axis.value : []);\n\n/** Identity: `Requirement-ID:` when authored, the heading otherwise — the same join `loam rebase` pins against. */\nconst reqKey = (r: Requirement): string => (r.id !== undefined ? `id:${r.id}` : `name:${r.name}`);\nconst reqLabel = (r: Requirement): string => (r.id !== undefined ? `${r.name} [${r.id}]` : r.name);\n\n/** First declaration wins a duplicated key — the same arbitrary-but-stated pick every join on these axes makes. */\nfunction firstWins<T>(items: T[], key: (item: T) => string): Map<string, T> {\n const map = new Map<string, T>();\n for (const item of items) if (!map.has(key(item))) map.set(key(item), item);\n return map;\n}\n\n/** Entry comparator by map key — explicit, because the default sort stringifies whole entries. */\nconst byKey = <T>(a: [string, T], b: [string, T]): number => (a[0] < b[0] ? -1 : 1);\n\nfunction requirementsDiff(sink: ServiceDiff, pair: { base: ServiceState; current: ServiceState }): void {\n // v1 diffs spec.md's requirements only; arch.spec.md is read for the\n // Consumes: join but its requirement inventory is deferred (the two axes\n // are separate namespaces, and diffing both doubles the surface). Both\n // pairs are guarded BEFORE the early return, so a service whose two spec\n // files are both unreadable reports both suspensions, not just the first.\n const specComparable = comparable(sink, \"spec\", { base: pair.base.spec, current: pair.current.spec });\n comparable(sink, \"archSpec\", { base: pair.base.archSpec, current: pair.current.archSpec });\n if (!specComparable) return;\n const base = firstWins(valueOr(pair.base.spec), reqKey);\n const current = firstWins(valueOr(pair.current.spec), reqKey);\n for (const [key, r] of [...current].sort(byKey)) {\n const before = base.get(key);\n if (before === undefined) sink.findings.push(found(\"diff.requirement-added\", `living requirement added since base: '${reqLabel(r)}'`));\n else if (requirementDigest(before) !== requirementDigest(r)) {\n sink.findings.push(found(\"diff.requirement-modified\", `living requirement modified since base: '${reqLabel(r)}'`));\n }\n }\n for (const [key, r] of [...base].sort(byKey)) {\n if (!current.has(key)) sink.findings.push(found(\"diff.requirement-removed\", `living requirement removed since base: '${reqLabel(r)}'`));\n }\n}\n\nasync function opsDiff(sink: ServiceDiff, pair: { base: ServiceState; current: ServiceState }, victims: VictimIndex): Promise<void> {\n if (!comparable(sink, \"openapi\", { base: pair.base.openapi, current: pair.current.openapi })) return;\n const base = firstWins(valueOr(pair.base.openapi), (op) => op.id);\n const current = firstWins(valueOr(pair.current.openapi), (op) => op.id);\n for (const [id, op] of [...current].sort(byKey)) {\n const before = base.get(id);\n if (before === undefined) {\n sink.findings.push(found(\"diff.op-added\", `operation '${id}' added since base (${op.method.toUpperCase()} ${op.path})`));\n } else if (!before.deprecated && op.deprecated) {\n const scan = await victims.opConsumers(sink.id, id);\n // Three honest phrasings — never a confident negative over a scan that skipped an unreadable consumer document.\n const stance = scan.victims.length > 0 ? `${scan.victims.length} current consumer(s) still name it`\n : scan.suspended.length > 0 ? \"whether current consumers name it could not be fully answered\"\n : \"no current consumer names it\";\n sink.findings.push(found(\"diff.op-deprecated\", `operation '${id}' is deprecated since base — ${stance}`, [...scan.victims, ...scan.suspended]));\n }\n }\n for (const [id] of [...base].sort(byKey)) {\n if (current.has(id)) continue;\n const scan = await victims.opConsumers(sink.id, id);\n if (scan.victims.length > 0) {\n sink.findings.push(found(\"diff.op-removed-consumed\", `operation '${id}' was removed since base and the current fleet still names it — these joins break`, [...scan.victims, ...scan.suspended]));\n } else if (scan.suspended.length > 0) {\n // \"Nobody could be scanned\" is not \"nobody names it\" — the consumer's own\n // unreadable[] entry already carries the run to exit 1; this message must\n // not assert a negative the scan never proved.\n sink.findings.push(found(\"diff.op-removed\", `operation '${id}' was removed since base; whether the current fleet still names it could NOT be fully answered — ${scan.suspended.length} consumer document(s) were not scannable`, scan.suspended));\n } else {\n sink.findings.push(found(\"diff.op-removed\", `operation '${id}' was removed since base; no current landscape edge or living requirement names it`));\n }\n }\n}\n\nasync function messagesDiff(sink: ServiceDiff, pair: { base: ServiceState; current: ServiceState }, victims: VictimIndex): Promise<void> {\n if (!comparable(sink, \"asyncapi\", { base: pair.base.asyncapi, current: pair.current.asyncapi })) return;\n const base = firstWins(valueOr(pair.base.asyncapi), (m) => m.name);\n const current = firstWins(valueOr(pair.current.asyncapi), (m) => m.name);\n for (const [name] of [...current].sort(byKey)) {\n if (!base.has(name)) sink.findings.push(found(\"diff.message-added\", `message '${name}' added since base`));\n }\n for (const [name] of [...base].sort(byKey)) {\n if (current.has(name)) continue;\n // Fleet-global join: a name another current service still declares is not\n // orphaned (positive evidence only — an unreadable contract never vouches).\n const elsewhere = victims.declarers(name).filter((d) => d !== sink.id);\n if (elsewhere.length > 0) {\n sink.findings.push(found(\"diff.message-removed\", `message '${name}' was removed since base; still declared by ${elsewhere.join(\", \")}`));\n continue;\n }\n const scan = await victims.messageConsumers(sink.id, name);\n if (scan.victims.length > 0) {\n sink.findings.push(found(\"diff.message-removed-consumed\", `message '${name}' was removed since base and the current fleet still consumes it — these joins break`, [...scan.victims, ...scan.suspended]));\n } else if (scan.suspended.length > 0) {\n sink.findings.push(found(\"diff.message-removed\", `message '${name}' was removed since base; whether anything still consumes it could NOT be fully answered — ${scan.suspended.length} consumer document(s) were not scannable`, scan.suspended));\n } else {\n sink.findings.push(found(\"diff.message-removed\", `message '${name}' was removed since base; nothing currently consumes it`));\n }\n }\n}\n\n/** The graded join changes (`victims.ts`) as findings on each PROVIDER — the service whose surface the join lands on. */\nfunction consumerFindings(input: { base: BaseFleet; current: CurrentFleet }): Map<string, Finding[]> {\n const changes = consumerJoinChanges({\n base: input.base.services,\n baseAmbiguous: new Set(input.base.ambiguous.keys()),\n current: input.current.services,\n currentAmbiguous: new Set(input.current.ambiguous.keys()),\n });\n const byProvider = new Map<string, Finding[]>();\n for (const { t, change } of changes) {\n const what = t.kind === \"op\" ? `operation '${t.token}'` : `message '${t.token}'`;\n const message = change === \"added\"\n ? `${t.consumer}'s living requirements now name this service's ${what} — a cross-service join appeared since base`\n : `${t.consumer}'s living requirements no longer name this service's ${what} — a cross-service join went away`;\n // The call sits directly in the push — assigned to a `const x = found(…)`\n // it reads as an arrow-function DECLARATION to the stable-code collector,\n // and both consumer codes silently leave the guarded set.\n byProvider.set(t.provider, [\n ...(byProvider.get(t.provider) ?? []),\n found(change === \"added\" ? \"diff.consumer-added\" : \"diff.consumer-removed\", message),\n ]);\n }\n return byProvider;\n}\n\nasync function diffService(unit: { id: string; base?: ServiceState; current?: ServiceState; victims: VictimIndex }): Promise<ServiceDiff> {\n const sink: ServiceDiff = { id: unit.id, change: \"unchanged\", findings: [], unreadable: [] };\n if (unit.base === undefined) sink.findings.push(found(\"diff.service-added\", `service '${unit.id}' added since base`));\n if (unit.current === undefined && unit.base !== undefined) {\n sink.findings.push(found(\"diff.service-removed\", `service '${unit.id}' (${unit.base.dir}) is gone from the working tree`));\n }\n const pair = { base: unit.base ?? emptyState(unit.id), current: unit.current ?? emptyState(unit.id) };\n requirementsDiff(sink, pair);\n await opsDiff(sink, pair, unit.victims);\n await messagesDiff(sink, pair, unit.victims);\n return sink;\n}\n\nexport async function computeDiff(input: { base: BaseFleet; current: CurrentFleet; victims: VictimIndex }): Promise<FleetDiff> {\n const sides = [input.base.services, input.base.ambiguous, input.current.services, input.current.ambiguous];\n const ids = [...new Set(sides.flatMap((side) => [...side.keys()]))].sort();\n const joins = consumerFindings({ base: input.base, current: input.current });\n const services: ServiceDiff[] = [];\n // Sequential on purpose: the report's order IS the contract (identical\n // states must emit identical bytes), the work per service is in-memory\n // joins, and the one expensive read — the victim index's landscape — is\n // memoised behind its first ask, so a fan-out would buy nothing.\n for (const id of ids) {\n const claimants = [...(input.base.ambiguous.get(id) ?? []), ...(input.current.ambiguous.get(id) ?? [])];\n if (claimants.length > 0) {\n // An id two directories claim — either side — has no single state to\n // diff; the whole subject suspends (containment), and the live walk's\n // `subsystem.name-collision` names the same defect for validate.\n services.push({ id, change: \"changed\", findings: [], unreadable: [], ambiguous: claimants });\n continue;\n }\n const sink = await diffService({ id, base: input.base.services.get(id), current: input.current.services.get(id), victims: input.victims });\n sink.findings.push(...(joins.get(id) ?? []));\n if (input.base.services.has(id) && !input.current.services.has(id)) sink.change = \"removed\";\n else if (!input.base.services.has(id)) sink.change = \"added\";\n else sink.change = sink.findings.length > 0 || sink.unreadable.length > 0 ? \"changed\" : \"unchanged\";\n services.push(sink);\n }\n const all = services.flatMap((s) => s.findings);\n const count = (codes: (f: Finding) => boolean): number => all.filter(codes).length;\n return {\n services,\n breaking: all.some((f) => f.severity === \"error\"),\n summary: {\n added: count((f) => f.code.endsWith(\"-added\")),\n removed: count((f) => f.code.endsWith(\"-removed\") || f.code.endsWith(\"-removed-consumed\")),\n modified: count((f) => f.code === \"diff.requirement-modified\"),\n deprecated: count((f) => f.code === \"diff.op-deprecated\"),\n unreadable:\n services.reduce((n, s) => n + s.unreadable.length, 0) +\n services.filter((s) => s.ambiguous !== undefined).length,\n },\n };\n}\n"]}
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Who, in the CURRENT fleet, still depends on a thing the diff says was
3
+ * removed or deprecated — the joins that turn "an operation left the
4
+ * contract" into "and these named consumers break".
5
+ *
6
+ * THREE SOURCES answer that, and only the first two are copies of anything.
7
+ *
8
+ * The first two are deliberately a SECOND copy of the consumer join the
9
+ * coherence gate asks, built on the same shared primitives, and both copies
10
+ * say so:
11
+ *
12
+ * - the operation half mirrors `core/coherence/lookups.ts`'s
13
+ * `edgeConsumers`/`requirementConsumers` (landscape edges with
14
+ * `metadata { op }` resolved through `serviceResolver` WITH the enumerated
15
+ * fleet — the container lesson recorded there — plus other services'
16
+ * living `Operations:` lines, spec.md only, exactly as that module scans);
17
+ * - the message half mirrors `core/coherence/events/lookups.ts`'s
18
+ * `messageConsumers` (every `consumes` edge naming the message except one
19
+ * INTO the provider, plus other services' `Consumes:` lines over BOTH spec
20
+ * axes — the outbox requirement lives in arch.spec.md as often as in
21
+ * spec.md, that module's lesson).
22
+ *
23
+ * The copies cannot be one module today: coherence's lookups are closures
24
+ * over a feature's `DeltaScope` and its lazy caches; this one is an index
25
+ * over an already-read fleet. Rule 13 (docs/DESIGN.md) governs — a THIRD
26
+ * copy of this join forces the shared extraction, and whoever changes the
27
+ * victim strings or the scan rules in either module owes the other the same
28
+ * change. The victim string spellings are kept byte-compatible with the
29
+ * coherence findings on purpose, so a reader meets one vocabulary.
30
+ *
31
+ * The THIRD source is not a copy and does not fall under that rule: the fleet's
32
+ * USE CASES, joined in `core/usecases/operations.ts`. A hop of a
33
+ * capability-tagged `dynamic view` that the model attributes to the operation
34
+ * being removed is a business flow that stops working, and saying so — "step 4
35
+ * of Checkout breaks" — is the only one of the three that names a consequence a
36
+ * human weighs rather than a document they then have to open. It has its own
37
+ * module because it asks a different question of a different reader (an
38
+ * attribution over the whole `architecture/` PROJECT, not a scan of one
39
+ * landscape file), and because the attributed/contested distinction it turns on
40
+ * belongs beside `attributeStep`, not beside a requirement scan. Coherence has
41
+ * no equivalent to keep in step with, so nothing here owes it a mirror.
42
+ */
43
+ import { existsSync } from "node:fs";
44
+ import {} from "../c4/likec4.js";
45
+ import { serviceResolver } from "../c4/resolve/service.js";
46
+ import { landscapePath } from "../repo/paths.js";
47
+ import { readUseCases } from "../usecases/fleet.js";
48
+ import { hopsConsuming, hopsExercising } from "../usecases/operations.js";
49
+ /** The requirements one current service contributes to a scan, per axis rule. */
50
+ function reqsOf(state, axes) {
51
+ return axes.flatMap((axis) => {
52
+ const a = state[axis];
53
+ return a.kind === "read" ? a.value : [];
54
+ });
55
+ }
56
+ const EDGE = (resolve, r) => `edge ${resolve(r.source)} → ${resolve(r.target)}${r.title === undefined ? "" : ` ("${r.title}")`}`;
57
+ /** Entry comparator by map key — deterministic suspension order. */
58
+ const byFirst = (a, b) => (a[0] < b[0] ? -1 : 1);
59
+ export function victimIndex(snap) {
60
+ // Lazy on purpose, the coherence lookups' own discipline: the landscape is
61
+ // a full LikeC4 workspace spin-up, and a diff that removes nothing must not
62
+ // pay for it. An absent landscape proves nothing; an unreadable one proves
63
+ // nothing either way — `landscape.invalid` is validate's finding to make,
64
+ // and inventing victims out of a parse error would point the reader at the
65
+ // wrong file (core/coherence/lookups.ts's doctrine, verbatim).
66
+ let drawn;
67
+ const landscape = () => (drawn ??= (async () => {
68
+ const path = landscapePath(snap.docsDir);
69
+ const doc = existsSync(path) ? await snap.context.loadLikeC4(path) : null;
70
+ const usable = doc !== null && doc.errors.length === 0 ? doc : null;
71
+ // The enumerated fleet rides into the resolver so a landscape that
72
+ // models CONTAINERS stays visible: without `known`, an edge into
73
+ // `payment.api` resolves to a service called "api" that has never
74
+ // existed and the join finds nobody — the exact defect DESIGN's
75
+ // serviceResolver-known row records.
76
+ return {
77
+ usable,
78
+ resolve: usable === null ? null : serviceResolver(usable.elements, new Set(snap.current.keys())),
79
+ };
80
+ })());
81
+ // Sorted, so victim order never depends on the enumeration's walk order.
82
+ const ids = [...snap.current.keys()].sort();
83
+ /**
84
+ * The use-case half, lazy for the same reason and one step further.
85
+ *
86
+ * `readUseCases` carries its own cheap gate — a byte scan for the reserved
87
+ * tag prefix — so a fleet that declares no use case never starts LikeC4 here
88
+ * at all, and a fleet that does pays for the `architecture/` project exactly
89
+ * once per `loam diff`, at the first removal that asks. The fleet set is the
90
+ * same one the landscape resolver above is built from, so the two halves
91
+ * cannot disagree about which service an element stands for.
92
+ */
93
+ let flows;
94
+ const useCases = () => (flows ??= readUseCases({ docsDir: snap.docsDir, known: new Set(snap.current.keys()) }));
95
+ /**
96
+ * The requirement half of one scan, `tupleVisible`'s own discipline applied
97
+ * to victims: an unreadable consumer axis contributes a SUSPENSION, never a
98
+ * silent nothing — "consuming nothing" and "loam refused to read the file"
99
+ * are opposite facts, and collapsing them is how a breaking removal used to
100
+ * grade down to a confident warning.
101
+ */
102
+ const scanRequirements = (unit) => {
103
+ const out = { victims: [], suspended: [] };
104
+ for (const other of ids.filter((id) => id !== unit.provider)) {
105
+ const state = snap.current.get(other);
106
+ for (const axis of unit.axes) {
107
+ const a = state[axis];
108
+ if (a.kind === "unreadable") {
109
+ out.suspended.push(`${other} — ${a.path} could not be read, so its requirements could not be scanned`);
110
+ continue;
111
+ }
112
+ if (a.kind !== "read")
113
+ continue;
114
+ for (const r of a.value)
115
+ if (unit.hit(r))
116
+ out.victims.push(`${other}'s living requirement '${r.name}'`);
117
+ }
118
+ }
119
+ for (const [id, dirs] of [...snap.ambiguous].sort(byFirst)) {
120
+ if (id !== unit.provider)
121
+ out.suspended.push(`${id} — claimed by ${dirs.join(" and ")}, so its documents could not be scanned`);
122
+ }
123
+ return out;
124
+ };
125
+ const opConsumers = async (provider, op) => {
126
+ const { usable, resolve } = await landscape();
127
+ const scan = scanRequirements({ provider, axes: ["spec"], hit: (r) => r.operations.includes(op) });
128
+ const edges = [];
129
+ if (usable !== null && resolve !== null) {
130
+ for (const r of usable.relationships) {
131
+ if (r.op === op && resolve(r.target) === provider)
132
+ edges.push(EDGE(resolve, r));
133
+ }
134
+ }
135
+ // Flows last in each list, deliberately: the edge and the requirement say
136
+ // WHICH document to open, and the flow says what the reader is about to
137
+ // break — so a `details[]` scanned top-down ends on the consequence.
138
+ const hops = hopsExercising(await useCases(), { provider, op });
139
+ return {
140
+ victims: [...edges, ...scan.victims, ...hops.breaks],
141
+ suspended: [...scan.suspended, ...hops.unsure],
142
+ };
143
+ };
144
+ const messageConsumers = async (provider, name) => {
145
+ const { usable, resolve } = await landscape();
146
+ const scan = scanRequirements({ provider, axes: ["spec", "archSpec"], hit: (r) => r.consumes.includes(name) });
147
+ const edges = [];
148
+ if (usable !== null && resolve !== null) {
149
+ for (const r of usable.relationships) {
150
+ // The message join is fleet-global, so every edge naming it counts —
151
+ // except one into the provider itself, whose consumption leaves with
152
+ // the same change (events/lookups.ts's exclusion, verbatim).
153
+ if (r.consumes === name && resolve(r.target) !== provider)
154
+ edges.push(EDGE(resolve, r));
155
+ }
156
+ }
157
+ const hops = hopsConsuming(await useCases(), { provider, name });
158
+ return { victims: [...edges, ...scan.victims, ...hops], suspended: scan.suspended };
159
+ };
160
+ const declarers = (name) => ids.filter((id) => {
161
+ const axis = snap.current.get(id).asyncapi;
162
+ return axis.kind === "read" && axis.value.some((m) => m.name === name);
163
+ });
164
+ return { opConsumers, messageConsumers, declarers };
165
+ }
166
+ /** The map key a tuple is joined on between the two states. */
167
+ function tupleKey(t) {
168
+ return `${t.kind}\0${t.consumer}\0${t.provider}\0${t.token}`;
169
+ }
170
+ /**
171
+ * Every cross-service join one state holds, keyed for the base/current join.
172
+ * Axis rules mirror the two consumer scans above: `Operations:` from spec.md
173
+ * only, `Consumes:` from both spec axes. Unreadable axes simply contribute
174
+ * nothing here — `semantic.ts` guards the GRADING so a join is never called
175
+ * added or removed off a side nobody could read.
176
+ */
177
+ export function consumerTuples(states) {
178
+ const providersOf = (pick) => {
179
+ const map = new Map();
180
+ for (const id of [...states.keys()].sort()) {
181
+ for (const token of pick(states.get(id)))
182
+ map.set(token, [...(map.get(token) ?? []), id]);
183
+ }
184
+ return map;
185
+ };
186
+ const opProviders = providersOf((s) => (s.openapi.kind === "read" ? s.openapi.value.map((op) => op.id) : []));
187
+ const msgProviders = providersOf((s) => (s.asyncapi.kind === "read" ? s.asyncapi.value.map((m) => m.name) : []));
188
+ const out = new Map();
189
+ const add = (t) => {
190
+ out.set(tupleKey(t), t);
191
+ };
192
+ for (const consumer of [...states.keys()].sort()) {
193
+ const state = states.get(consumer);
194
+ for (const r of reqsOf(state, ["spec"])) {
195
+ for (const op of r.operations) {
196
+ for (const p of opProviders.get(op) ?? [])
197
+ if (p !== consumer)
198
+ add({ kind: "op", consumer, provider: p, token: op });
199
+ }
200
+ }
201
+ for (const r of reqsOf(state, ["spec", "archSpec"])) {
202
+ for (const name of r.consumes) {
203
+ for (const p of msgProviders.get(name) ?? [])
204
+ if (p !== consumer)
205
+ add({ kind: "message", consumer, provider: p, token: name });
206
+ }
207
+ }
208
+ }
209
+ return out;
210
+ }
211
+ /** May a tuple be graded off this side? Absence answers; unreadable does not; an ambiguous base identity answers nothing. */
212
+ function tupleVisible(states, ambiguous, t) {
213
+ if (ambiguous.has(t.consumer) || ambiguous.has(t.provider))
214
+ return false;
215
+ const readable = (id, axes) => {
216
+ const state = states.get(id);
217
+ return state === undefined || axes.every((axis) => state[axis].kind !== "unreadable");
218
+ };
219
+ return (readable(t.consumer, t.kind === "op" ? ["spec"] : ["spec", "archSpec"]) &&
220
+ readable(t.provider, [t.kind === "op" ? "openapi" : "asyncapi"]));
221
+ }
222
+ /**
223
+ * The joins that appeared or went away between base and current, sorted for
224
+ * deterministic output (provider, kind, token, consumer). The visibility
225
+ * guard is the containment rule: a join is never called "added" off a base
226
+ * side nobody could read, nor "removed" off an unreadable working file — the
227
+ * suspended axis already carries that fact.
228
+ */
229
+ export function consumerJoinChanges(input) {
230
+ const baseTuples = consumerTuples(input.base);
231
+ const currentTuples = consumerTuples(input.current);
232
+ const graded = [];
233
+ for (const [key, t] of currentTuples) {
234
+ if (!baseTuples.has(key) && tupleVisible(input.base, input.baseAmbiguous, t))
235
+ graded.push({ t, change: "added" });
236
+ }
237
+ for (const [key, t] of baseTuples) {
238
+ if (!currentTuples.has(key) && tupleVisible(input.current, input.currentAmbiguous, t))
239
+ graded.push({ t, change: "removed" });
240
+ }
241
+ return graded.sort((a, b) => {
242
+ const ka = `${a.t.provider}\0${a.t.kind}\0${a.t.token}\0${a.t.consumer}\0${a.change}`;
243
+ const kb = `${b.t.provider}\0${b.t.kind}\0${b.t.token}\0${b.t.consumer}\0${b.change}`;
244
+ return ka < kb ? -1 : 1;
245
+ });
246
+ }
247
+ //# sourceMappingURL=victims.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"victims.js","sourceRoot":"","sources":["../../../src/core/diff/victims.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Who, in the CURRENT fleet, still depends on a thing the diff says was\n * removed or deprecated — the joins that turn \"an operation left the\n * contract\" into \"and these named consumers break\".\n *\n * THREE SOURCES answer that, and only the first two are copies of anything.\n *\n * The first two are deliberately a SECOND copy of the consumer join the\n * coherence gate asks, built on the same shared primitives, and both copies\n * say so:\n *\n * - the operation half mirrors `core/coherence/lookups.ts`'s\n * `edgeConsumers`/`requirementConsumers` (landscape edges with\n * `metadata { op }` resolved through `serviceResolver` WITH the enumerated\n * fleet — the container lesson recorded there — plus other services'\n * living `Operations:` lines, spec.md only, exactly as that module scans);\n * - the message half mirrors `core/coherence/events/lookups.ts`'s\n * `messageConsumers` (every `consumes` edge naming the message except one\n * INTO the provider, plus other services' `Consumes:` lines over BOTH spec\n * axes — the outbox requirement lives in arch.spec.md as often as in\n * spec.md, that module's lesson).\n *\n * The copies cannot be one module today: coherence's lookups are closures\n * over a feature's `DeltaScope` and its lazy caches; this one is an index\n * over an already-read fleet. Rule 13 (docs/DESIGN.md) governs — a THIRD\n * copy of this join forces the shared extraction, and whoever changes the\n * victim strings or the scan rules in either module owes the other the same\n * change. The victim string spellings are kept byte-compatible with the\n * coherence findings on purpose, so a reader meets one vocabulary.\n *\n * The THIRD source is not a copy and does not fall under that rule: the fleet's\n * USE CASES, joined in `core/usecases/operations.ts`. A hop of a\n * capability-tagged `dynamic view` that the model attributes to the operation\n * being removed is a business flow that stops working, and saying so — \"step 4\n * of Checkout breaks\" — is the only one of the three that names a consequence a\n * human weighs rather than a document they then have to open. It has its own\n * module because it asks a different question of a different reader (an\n * attribution over the whole `architecture/` PROJECT, not a scan of one\n * landscape file), and because the attributed/contested distinction it turns on\n * belongs beside `attributeStep`, not beside a requirement scan. Coherence has\n * no equivalent to keep in step with, so nothing here owes it a mirror.\n */\nimport { existsSync } from \"node:fs\";\nimport { type LoadedDoc, type Rel } from \"../c4/likec4.js\";\nimport { serviceResolver } from \"../c4/resolve/service.js\";\nimport { landscapePath } from \"../repo/paths.js\";\nimport { readUseCases, type UseCaseScan } from \"../usecases/fleet.js\";\nimport { hopsConsuming, hopsExercising } from \"../usecases/operations.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport type { Requirement } from \"../document/spec.js\";\nimport type { ServiceState } from \"./base-state.js\";\n\n/**\n * One consumer scan's answer, honest about its own coverage. `victims` is\n * positive evidence; `suspended` names the consumers whose relevant document\n * could not be read (or whose identity is ambiguous), because a scan that\n * skipped a file loam REFUSED to read must never let the caller assert\n * \"nobody names it\" — the fail-open shape this type exists to forbid.\n */\nexport interface ConsumerScan {\n victims: string[];\n suspended: string[];\n}\n\n/** The current-state consumer questions `semantic.ts` asks per removal. */\nexport interface VictimIndex {\n /** Current consumers of `provider`'s operation `op`: landscape edges into the provider, then other services' living requirements, then the use-case hops the model attributes to it (contested hops ride as suspensions). */\n opConsumers(provider: string, op: string): Promise<ConsumerScan>;\n /** Current consumers of message `name` a `provider` is dropping: consumes-edges (not into the provider), then other services' `Consumes:` lines over both spec axes, then the use-case hops backed by such an edge. */\n messageConsumers(provider: string, name: string): Promise<ConsumerScan>;\n /** Current services whose asyncapi declares `name` — positive evidence only; a message still declared elsewhere is not orphaned by one producer dropping it. */\n declarers(name: string): string[];\n}\n\n/** The requirements one current service contributes to a scan, per axis rule. */\nfunction reqsOf(state: ServiceState, axes: readonly (\"spec\" | \"archSpec\")[]): Requirement[] {\n return axes.flatMap((axis) => {\n const a = state[axis];\n return a.kind === \"read\" ? a.value : [];\n });\n}\n\nconst EDGE = (resolve: (id: string) => string, r: Rel): string =>\n `edge ${resolve(r.source)} → ${resolve(r.target)}${r.title === undefined ? \"\" : ` (\"${r.title}\")`}`;\n\n/** Entry comparator by map key — deterministic suspension order. */\nconst byFirst = <T>(a: [string, T], b: [string, T]): number => (a[0] < b[0] ? -1 : 1);\n\n/** The landscape half of the index, loaded at most once and only when a removal actually asks. */\ninterface Drawn {\n usable: LoadedDoc | null;\n resolve: ((id: string) => string) | null;\n}\n\nexport function victimIndex(snap: {\n docsDir: DocsDir;\n context: FleetContext;\n current: ReadonlyMap<string, ServiceState>;\n /** Ids the working tree claims twice (current-state.ts) — their documents cannot be scanned, so they ride as suspended consumers. */\n ambiguous: ReadonlyMap<string, string[]>;\n}): VictimIndex {\n // Lazy on purpose, the coherence lookups' own discipline: the landscape is\n // a full LikeC4 workspace spin-up, and a diff that removes nothing must not\n // pay for it. An absent landscape proves nothing; an unreadable one proves\n // nothing either way — `landscape.invalid` is validate's finding to make,\n // and inventing victims out of a parse error would point the reader at the\n // wrong file (core/coherence/lookups.ts's doctrine, verbatim).\n let drawn: Promise<Drawn> | undefined;\n const landscape = (): Promise<Drawn> =>\n (drawn ??= (async (): Promise<Drawn> => {\n const path = landscapePath(snap.docsDir);\n const doc: LoadedDoc | null = existsSync(path) ? await snap.context.loadLikeC4(path) : null;\n const usable = doc !== null && doc.errors.length === 0 ? doc : null;\n // The enumerated fleet rides into the resolver so a landscape that\n // models CONTAINERS stays visible: without `known`, an edge into\n // `payment.api` resolves to a service called \"api\" that has never\n // existed and the join finds nobody — the exact defect DESIGN's\n // serviceResolver-known row records.\n return {\n usable,\n resolve: usable === null ? null : serviceResolver(usable.elements, new Set(snap.current.keys())),\n };\n })());\n\n // Sorted, so victim order never depends on the enumeration's walk order.\n const ids = [...snap.current.keys()].sort();\n\n /**\n * The use-case half, lazy for the same reason and one step further.\n *\n * `readUseCases` carries its own cheap gate — a byte scan for the reserved\n * tag prefix — so a fleet that declares no use case never starts LikeC4 here\n * at all, and a fleet that does pays for the `architecture/` project exactly\n * once per `loam diff`, at the first removal that asks. The fleet set is the\n * same one the landscape resolver above is built from, so the two halves\n * cannot disagree about which service an element stands for.\n */\n let flows: Promise<UseCaseScan> | undefined;\n const useCases = (): Promise<UseCaseScan> =>\n (flows ??= readUseCases({ docsDir: snap.docsDir, known: new Set(snap.current.keys()) }));\n\n /**\n * The requirement half of one scan, `tupleVisible`'s own discipline applied\n * to victims: an unreadable consumer axis contributes a SUSPENSION, never a\n * silent nothing — \"consuming nothing\" and \"loam refused to read the file\"\n * are opposite facts, and collapsing them is how a breaking removal used to\n * grade down to a confident warning.\n */\n const scanRequirements = (unit: { provider: string; axes: readonly (\"spec\" | \"archSpec\")[]; hit: (r: Requirement) => boolean }): ConsumerScan => {\n const out: ConsumerScan = { victims: [], suspended: [] };\n for (const other of ids.filter((id) => id !== unit.provider)) {\n const state = snap.current.get(other)!;\n for (const axis of unit.axes) {\n const a = state[axis];\n if (a.kind === \"unreadable\") {\n out.suspended.push(`${other} — ${a.path} could not be read, so its requirements could not be scanned`);\n continue;\n }\n if (a.kind !== \"read\") continue;\n for (const r of a.value) if (unit.hit(r)) out.victims.push(`${other}'s living requirement '${r.name}'`);\n }\n }\n for (const [id, dirs] of [...snap.ambiguous].sort(byFirst)) {\n if (id !== unit.provider) out.suspended.push(`${id} — claimed by ${dirs.join(\" and \")}, so its documents could not be scanned`);\n }\n return out;\n };\n\n const opConsumers = async (provider: string, op: string): Promise<ConsumerScan> => {\n const { usable, resolve } = await landscape();\n const scan = scanRequirements({ provider, axes: [\"spec\"], hit: (r) => r.operations.includes(op) });\n const edges: string[] = [];\n if (usable !== null && resolve !== null) {\n for (const r of usable.relationships) {\n if (r.op === op && resolve(r.target) === provider) edges.push(EDGE(resolve, r));\n }\n }\n // Flows last in each list, deliberately: the edge and the requirement say\n // WHICH document to open, and the flow says what the reader is about to\n // break — so a `details[]` scanned top-down ends on the consequence.\n const hops = hopsExercising(await useCases(), { provider, op });\n return {\n victims: [...edges, ...scan.victims, ...hops.breaks],\n suspended: [...scan.suspended, ...hops.unsure],\n };\n };\n\n const messageConsumers = async (provider: string, name: string): Promise<ConsumerScan> => {\n const { usable, resolve } = await landscape();\n const scan = scanRequirements({ provider, axes: [\"spec\", \"archSpec\"], hit: (r) => r.consumes.includes(name) });\n const edges: string[] = [];\n if (usable !== null && resolve !== null) {\n for (const r of usable.relationships) {\n // The message join is fleet-global, so every edge naming it counts —\n // except one into the provider itself, whose consumption leaves with\n // the same change (events/lookups.ts's exclusion, verbatim).\n if (r.consumes === name && resolve(r.target) !== provider) edges.push(EDGE(resolve, r));\n }\n }\n const hops = hopsConsuming(await useCases(), { provider, name });\n return { victims: [...edges, ...scan.victims, ...hops], suspended: scan.suspended };\n };\n\n const declarers = (name: string): string[] =>\n ids.filter((id) => {\n const axis = snap.current.get(id)!.asyncapi;\n return axis.kind === \"read\" && axis.value.some((m) => m.name === name);\n });\n\n return { opConsumers, messageConsumers, declarers };\n}\n\n/**\n * One cross-service join: `consumer`'s living requirements name a `token` that\n * `provider`'s contract defines. The same \"who depends on what\" question the\n * index above answers per removal, asked here of a WHOLE state — base or\n * current — so `semantic.ts` can report joins that appeared or went away.\n */\nexport interface ConsumerTuple {\n kind: \"op\" | \"message\";\n consumer: string;\n provider: string;\n token: string;\n}\n\n/** The map key a tuple is joined on between the two states. */\nfunction tupleKey(t: ConsumerTuple): string {\n return `${t.kind}\\0${t.consumer}\\0${t.provider}\\0${t.token}`;\n}\n\n/**\n * Every cross-service join one state holds, keyed for the base/current join.\n * Axis rules mirror the two consumer scans above: `Operations:` from spec.md\n * only, `Consumes:` from both spec axes. Unreadable axes simply contribute\n * nothing here — `semantic.ts` guards the GRADING so a join is never called\n * added or removed off a side nobody could read.\n */\nexport function consumerTuples(states: ReadonlyMap<string, ServiceState>): Map<string, ConsumerTuple> {\n const providersOf = (pick: (s: ServiceState) => string[]): Map<string, string[]> => {\n const map = new Map<string, string[]>();\n for (const id of [...states.keys()].sort()) {\n for (const token of pick(states.get(id)!)) map.set(token, [...(map.get(token) ?? []), id]);\n }\n return map;\n };\n const opProviders = providersOf((s) => (s.openapi.kind === \"read\" ? s.openapi.value.map((op) => op.id) : []));\n const msgProviders = providersOf((s) => (s.asyncapi.kind === \"read\" ? s.asyncapi.value.map((m) => m.name) : []));\n const out = new Map<string, ConsumerTuple>();\n const add = (t: ConsumerTuple): void => {\n out.set(tupleKey(t), t);\n };\n for (const consumer of [...states.keys()].sort()) {\n const state = states.get(consumer)!;\n for (const r of reqsOf(state, [\"spec\"])) {\n for (const op of r.operations) {\n for (const p of opProviders.get(op) ?? []) if (p !== consumer) add({ kind: \"op\", consumer, provider: p, token: op });\n }\n }\n for (const r of reqsOf(state, [\"spec\", \"archSpec\"])) {\n for (const name of r.consumes) {\n for (const p of msgProviders.get(name) ?? []) if (p !== consumer) add({ kind: \"message\", consumer, provider: p, token: name });\n }\n }\n }\n return out;\n}\n\n/** May a tuple be graded off this side? Absence answers; unreadable does not; an ambiguous base identity answers nothing. */\nfunction tupleVisible(states: ReadonlyMap<string, ServiceState>, ambiguous: ReadonlySet<string>, t: ConsumerTuple): boolean {\n if (ambiguous.has(t.consumer) || ambiguous.has(t.provider)) return false;\n const readable = (id: string, axes: (\"spec\" | \"archSpec\" | \"openapi\" | \"asyncapi\")[]): boolean => {\n const state = states.get(id);\n return state === undefined || axes.every((axis) => state[axis].kind !== \"unreadable\");\n };\n return (\n readable(t.consumer, t.kind === \"op\" ? [\"spec\"] : [\"spec\", \"archSpec\"]) &&\n readable(t.provider, [t.kind === \"op\" ? \"openapi\" : \"asyncapi\"])\n );\n}\n\n/** One graded cross-service join change between the two states. */\nexport interface ConsumerChange {\n t: ConsumerTuple;\n change: \"added\" | \"removed\";\n}\n\n/**\n * The joins that appeared or went away between base and current, sorted for\n * deterministic output (provider, kind, token, consumer). The visibility\n * guard is the containment rule: a join is never called \"added\" off a base\n * side nobody could read, nor \"removed\" off an unreadable working file — the\n * suspended axis already carries that fact.\n */\nexport function consumerJoinChanges(input: {\n base: ReadonlyMap<string, ServiceState>;\n baseAmbiguous: ReadonlySet<string>;\n current: ReadonlyMap<string, ServiceState>;\n currentAmbiguous: ReadonlySet<string>;\n}): ConsumerChange[] {\n const baseTuples = consumerTuples(input.base);\n const currentTuples = consumerTuples(input.current);\n const graded: ConsumerChange[] = [];\n for (const [key, t] of currentTuples) {\n if (!baseTuples.has(key) && tupleVisible(input.base, input.baseAmbiguous, t)) graded.push({ t, change: \"added\" });\n }\n for (const [key, t] of baseTuples) {\n if (!currentTuples.has(key) && tupleVisible(input.current, input.currentAmbiguous, t)) graded.push({ t, change: \"removed\" });\n }\n return graded.sort((a, b) => {\n const ka = `${a.t.provider}\\0${a.t.kind}\\0${a.t.token}\\0${a.t.consumer}\\0${a.change}`;\n const kb = `${b.t.provider}\\0${b.t.kind}\\0${b.t.token}\\0${b.t.consumer}\\0${b.change}`;\n return ka < kb ? -1 : 1;\n });\n}\n"]}