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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (901) hide show
  1. package/CHANGELOG.md +3448 -0
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +47 -23
  5. package/QUICKSTART.md +189 -12
  6. package/README.md +274 -65
  7. package/ROADMAP.md +572 -31
  8. package/SCHEMA.md +1709 -155
  9. package/WORKFLOW.md +298 -72
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/adopt/render.js +50 -9
  15. package/dist/commands/adopt/render.js.map +1 -1
  16. package/dist/commands/archive/archive.js +16 -19
  17. package/dist/commands/archive/archive.js.map +1 -1
  18. package/dist/commands/archive/closing.js +42 -0
  19. package/dist/commands/archive/closing.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
  21. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  23. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  24. package/dist/commands/archive/plan/contracts/openapi.js +77 -43
  25. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  26. package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
  27. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  28. package/dist/commands/archive/plan/document/head.js +151 -0
  29. package/dist/commands/archive/plan/document/head.js.map +1 -0
  30. package/dist/commands/archive/plan/document/links.js +284 -0
  31. package/dist/commands/archive/plan/document/links.js.map +1 -0
  32. package/dist/commands/archive/plan/document/run.js +136 -0
  33. package/dist/commands/archive/plan/document/run.js.map +1 -0
  34. package/dist/commands/archive/plan/gate.js +21 -19
  35. package/dist/commands/archive/plan/gate.js.map +1 -1
  36. package/dist/commands/archive/plan/landscape.js +167 -30
  37. package/dist/commands/archive/plan/landscape.js.map +1 -1
  38. package/dist/commands/archive/plan/model/extending.js +85 -0
  39. package/dist/commands/archive/plan/model/extending.js.map +1 -0
  40. package/dist/commands/archive/plan/refusal.js +14 -19
  41. package/dist/commands/archive/plan/refusal.js.map +1 -1
  42. package/dist/commands/archive/plan/specs.js +137 -56
  43. package/dist/commands/archive/plan/specs.js.map +1 -1
  44. package/dist/commands/archive/plan/state.js +3 -0
  45. package/dist/commands/archive/plan/state.js.map +1 -1
  46. package/dist/commands/archive/run.js +134 -30
  47. package/dist/commands/archive/run.js.map +1 -1
  48. package/dist/commands/archive/shipped.js +49 -0
  49. package/dist/commands/archive/shipped.js.map +1 -0
  50. package/dist/commands/context/context.js +85 -18
  51. package/dist/commands/context/context.js.map +1 -1
  52. package/dist/commands/context/print.js +29 -7
  53. package/dist/commands/context/print.js.map +1 -1
  54. package/dist/commands/delta/delta.js +102 -14
  55. package/dist/commands/delta/delta.js.map +1 -1
  56. package/dist/commands/delta/print.js +17 -1
  57. package/dist/commands/delta/print.js.map +1 -1
  58. package/dist/commands/dependencies.js +3 -7
  59. package/dist/commands/dependencies.js.map +1 -1
  60. package/dist/commands/diff/diff.js +20 -27
  61. package/dist/commands/diff/diff.js.map +1 -1
  62. package/dist/commands/doctor.js +60 -1
  63. package/dist/commands/doctor.js.map +1 -1
  64. package/dist/commands/explain/explain.js +89 -13
  65. package/dist/commands/explain/explain.js.map +1 -1
  66. package/dist/commands/explore.js +16 -10
  67. package/dist/commands/explore.js.map +1 -1
  68. package/dist/commands/gate/gate.js +45 -8
  69. package/dist/commands/gate/gate.js.map +1 -1
  70. package/dist/commands/gate/print.js +15 -3
  71. package/dist/commands/gate/print.js.map +1 -1
  72. package/dist/commands/gherkin/commit.js +13 -54
  73. package/dist/commands/gherkin/commit.js.map +1 -1
  74. package/dist/commands/gherkin/gherkin.js +36 -20
  75. package/dist/commands/gherkin/gherkin.js.map +1 -1
  76. package/dist/commands/gherkin/reconcile.js +10 -13
  77. package/dist/commands/gherkin/reconcile.js.map +1 -1
  78. package/dist/commands/init/first-hour.js +44 -0
  79. package/dist/commands/init/first-hour.js.map +1 -1
  80. package/dist/commands/init/init.js +18 -22
  81. package/dist/commands/init/init.js.map +1 -1
  82. package/dist/commands/list/campaign/campaign.js +4 -6
  83. package/dist/commands/list/campaign/campaign.js.map +1 -1
  84. package/dist/commands/list/json.js +5 -7
  85. package/dist/commands/list/json.js.map +1 -1
  86. package/dist/commands/list/list.js +44 -29
  87. package/dist/commands/list/list.js.map +1 -1
  88. package/dist/commands/list/print.js +52 -9
  89. package/dist/commands/list/print.js.map +1 -1
  90. package/dist/commands/list/suites/suites.js +140 -0
  91. package/dist/commands/list/suites/suites.js.map +1 -0
  92. package/dist/commands/list/views.js +37 -34
  93. package/dist/commands/list/views.js.map +1 -1
  94. package/dist/commands/mcp/dispatch.js +91 -36
  95. package/dist/commands/mcp/dispatch.js.map +1 -1
  96. package/dist/commands/mcp/mcp.js +56 -15
  97. package/dist/commands/mcp/mcp.js.map +1 -1
  98. package/dist/commands/mcp/serve.js.map +1 -1
  99. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  100. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  101. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  102. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  103. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  104. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  105. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  106. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  107. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  108. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  109. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  110. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  111. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  112. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  113. package/dist/commands/new/capability-template.js +118 -0
  114. package/dist/commands/new/capability-template.js.map +1 -0
  115. package/dist/commands/new/commit.js +8 -22
  116. package/dist/commands/new/commit.js.map +1 -1
  117. package/dist/commands/new/modifies.js +206 -0
  118. package/dist/commands/new/modifies.js.map +1 -0
  119. package/dist/commands/new/new.js +59 -14
  120. package/dist/commands/new/new.js.map +1 -1
  121. package/dist/commands/new/templates.js +48 -124
  122. package/dist/commands/new/templates.js.map +1 -1
  123. package/dist/commands/open.js +3 -7
  124. package/dist/commands/open.js.map +1 -1
  125. package/dist/commands/policy/gate.js +27 -0
  126. package/dist/commands/policy/gate.js.map +1 -1
  127. package/dist/commands/rebase/plan.js +2 -2
  128. package/dist/commands/rebase/plan.js.map +1 -1
  129. package/dist/commands/rebase/rebase.js +60 -35
  130. package/dist/commands/rebase/rebase.js.map +1 -1
  131. package/dist/commands/seed/commit.js +55 -26
  132. package/dist/commands/seed/commit.js.map +1 -1
  133. package/dist/commands/seed/plan.js +158 -13
  134. package/dist/commands/seed/plan.js.map +1 -1
  135. package/dist/commands/seed/seed.js +13 -11
  136. package/dist/commands/seed/seed.js.map +1 -1
  137. package/dist/commands/show/feature.js +9 -5
  138. package/dist/commands/show/feature.js.map +1 -1
  139. package/dist/commands/show/introduced.js +157 -0
  140. package/dist/commands/show/introduced.js.map +1 -0
  141. package/dist/commands/show/marks.js +35 -6
  142. package/dist/commands/show/marks.js.map +1 -1
  143. package/dist/commands/show/service.js +41 -33
  144. package/dist/commands/show/service.js.map +1 -1
  145. package/dist/commands/show/show.js +17 -7
  146. package/dist/commands/show/show.js.map +1 -1
  147. package/dist/commands/status/fleet-next.js +90 -0
  148. package/dist/commands/status/fleet-next.js.map +1 -0
  149. package/dist/commands/status/json.js +2 -0
  150. package/dist/commands/status/json.js.map +1 -1
  151. package/dist/commands/status/print.js +89 -37
  152. package/dist/commands/status/print.js.map +1 -1
  153. package/dist/commands/status/status.js +3 -7
  154. package/dist/commands/status/status.js.map +1 -1
  155. package/dist/commands/steps/steps.js +103 -23
  156. package/dist/commands/steps/steps.js.map +1 -1
  157. package/dist/commands/subsystem/edit.js +7 -7
  158. package/dist/commands/subsystem/edit.js.map +1 -1
  159. package/dist/commands/subsystem/move.js +106 -46
  160. package/dist/commands/subsystem/move.js.map +1 -1
  161. package/dist/commands/subsystem/query.js +2 -4
  162. package/dist/commands/subsystem/query.js.map +1 -1
  163. package/dist/commands/subsystem/subsystem.js +4 -7
  164. package/dist/commands/subsystem/subsystem.js.map +1 -1
  165. package/dist/commands/subsystem/sync.js +289 -74
  166. package/dist/commands/subsystem/sync.js.map +1 -1
  167. package/dist/commands/subsystem/txn/exclude.js +132 -0
  168. package/dist/commands/subsystem/txn/exclude.js.map +1 -0
  169. package/dist/commands/subsystem/txn/links.js +128 -0
  170. package/dist/commands/subsystem/txn/links.js.map +1 -0
  171. package/dist/commands/subsystem/txn/report.js +174 -0
  172. package/dist/commands/subsystem/txn/report.js.map +1 -0
  173. package/dist/commands/subsystem/txn/txn.js +147 -45
  174. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  175. package/dist/commands/subsystem/txn/views.js +200 -21
  176. package/dist/commands/subsystem/txn/views.js.map +1 -1
  177. package/dist/commands/tasks/actions.js +107 -0
  178. package/dist/commands/tasks/actions.js.map +1 -0
  179. package/dist/commands/tasks/render.js +253 -0
  180. package/dist/commands/tasks/render.js.map +1 -0
  181. package/dist/commands/tasks/tasks.js +119 -0
  182. package/dist/commands/tasks/tasks.js.map +1 -0
  183. package/dist/commands/unarchive/manifest.js +42 -26
  184. package/dist/commands/unarchive/manifest.js.map +1 -1
  185. package/dist/commands/unarchive/restore.js +83 -33
  186. package/dist/commands/unarchive/restore.js.map +1 -1
  187. package/dist/commands/unarchive/unarchive.js +22 -30
  188. package/dist/commands/unarchive/unarchive.js.map +1 -1
  189. package/dist/commands/validate/arch-coverage.js +78 -27
  190. package/dist/commands/validate/arch-coverage.js.map +1 -1
  191. package/dist/commands/validate/checks/fleet-shape.js +34 -126
  192. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  193. package/dist/commands/validate/checks/health-deps.js +9 -6
  194. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  195. package/dist/commands/validate/checks/introduced.js +102 -0
  196. package/dist/commands/validate/checks/introduced.js.map +1 -0
  197. package/dist/commands/validate/checks/requirements.js +18 -8
  198. package/dist/commands/validate/checks/requirements.js.map +1 -1
  199. package/dist/commands/validate/checks/sources.js +11 -2
  200. package/dist/commands/validate/checks/sources.js.map +1 -1
  201. package/dist/commands/validate/coverage/covers-after.js +105 -0
  202. package/dist/commands/validate/coverage/covers-after.js.map +1 -0
  203. package/dist/commands/validate/feature.js +26 -20
  204. package/dist/commands/validate/feature.js.map +1 -1
  205. package/dist/commands/validate/fleet/census.js +37 -13
  206. package/dist/commands/validate/fleet/census.js.map +1 -1
  207. package/dist/commands/validate/fleet/landscape.js +144 -126
  208. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  209. package/dist/commands/validate/fleet/load.js +66 -0
  210. package/dist/commands/validate/fleet/load.js.map +1 -1
  211. package/dist/commands/validate/fleet/map/attest.js +98 -0
  212. package/dist/commands/validate/fleet/map/attest.js.map +1 -0
  213. package/dist/commands/validate/fleet/map/bindings.js +107 -0
  214. package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
  215. package/dist/commands/validate/fleet/map/consumers.js +244 -0
  216. package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
  217. package/dist/commands/validate/fleet/map/isolation.js +116 -0
  218. package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
  219. package/dist/commands/validate/fleet/map/usecases.js +54 -0
  220. package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
  221. package/dist/commands/validate/fleet/obligations.js +44 -14
  222. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  223. package/dist/commands/validate/fleet/scorecard/print.js +10 -2
  224. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  225. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  226. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  227. package/dist/commands/validate/fleet/usecases/place.js.map +1 -1
  228. package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
  229. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
  230. package/dist/commands/validate/fleet/views/ids.js +50 -15
  231. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  232. package/dist/commands/validate/fleet/views/projects.js +278 -0
  233. package/dist/commands/validate/fleet/views/projects.js.map +1 -0
  234. package/dist/commands/validate/fleet/views/root-documents.js +83 -0
  235. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
  236. package/dist/commands/validate/fleet/views/stale.js +40 -47
  237. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  238. package/dist/commands/validate/links/corpus.js +1 -4
  239. package/dist/commands/validate/links/corpus.js.map +1 -1
  240. package/dist/commands/validate/links/glossary.js.map +1 -1
  241. package/dist/commands/validate/render/repeats.js +63 -0
  242. package/dist/commands/validate/render/repeats.js.map +1 -0
  243. package/dist/commands/validate/render/rollups.js +115 -0
  244. package/dist/commands/validate/render/rollups.js.map +1 -0
  245. package/dist/commands/validate/render/text.js +199 -0
  246. package/dist/commands/validate/render/text.js.map +1 -0
  247. package/dist/commands/validate/report.js +36 -160
  248. package/dist/commands/validate/report.js.map +1 -1
  249. package/dist/commands/validate/scope.js +113 -0
  250. package/dist/commands/validate/scope.js.map +1 -0
  251. package/dist/commands/validate/service/api.js +21 -107
  252. package/dist/commands/validate/service/api.js.map +1 -1
  253. package/dist/commands/validate/service/contracts/generated.js +134 -0
  254. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  255. package/dist/commands/validate/service/events/events.js +36 -14
  256. package/dist/commands/validate/service/events/events.js.map +1 -1
  257. package/dist/commands/validate/service/events/message-producers.js +1 -1
  258. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  259. package/dist/commands/validate/service/evidence-pins.js +56 -10
  260. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  261. package/dist/commands/validate/service/model/diverged.js +226 -0
  262. package/dist/commands/validate/service/model/diverged.js.map +1 -0
  263. package/dist/commands/validate/service/model/grade.js +163 -0
  264. package/dist/commands/validate/service/model/grade.js.map +1 -0
  265. package/dist/commands/validate/service/service.js +121 -61
  266. package/dist/commands/validate/service/service.js.map +1 -1
  267. package/dist/commands/validate/service/specs.js +88 -28
  268. package/dist/commands/validate/service/specs.js.map +1 -1
  269. package/dist/commands/validate/service/spine.js +38 -3
  270. package/dist/commands/validate/service/spine.js.map +1 -1
  271. package/dist/commands/validate/service/usecases/tags.js +113 -0
  272. package/dist/commands/validate/service/usecases/tags.js.map +1 -0
  273. package/dist/commands/validate/service/usecases/usecases.js +209 -0
  274. package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
  275. package/dist/commands/validate/validate.js +85 -60
  276. package/dist/commands/validate/validate.js.map +1 -1
  277. package/dist/commands/verify/cross/diff.js +2 -6
  278. package/dist/commands/verify/cross/diff.js.map +1 -1
  279. package/dist/commands/verify/evidence/contract.js +1 -1
  280. package/dist/commands/verify/evidence/contract.js.map +1 -1
  281. package/dist/commands/verify/evidence/read.js +12 -3
  282. package/dist/commands/verify/evidence/read.js.map +1 -1
  283. package/dist/commands/verify/frozen.js +45 -23
  284. package/dist/commands/verify/frozen.js.map +1 -1
  285. package/dist/commands/verify/record.js +69 -17
  286. package/dist/commands/verify/record.js.map +1 -1
  287. package/dist/commands/verify/report.js +27 -44
  288. package/dist/commands/verify/report.js.map +1 -1
  289. package/dist/commands/verify/results.js +112 -30
  290. package/dist/commands/verify/results.js.map +1 -1
  291. package/dist/commands/verify/verify.js +55 -52
  292. package/dist/commands/verify/verify.js.map +1 -1
  293. package/dist/commands/vouch/pack/pack.js +2 -2
  294. package/dist/commands/vouch/pack/pack.js.map +1 -1
  295. package/dist/commands/vouch/pack/print.js +1 -6
  296. package/dist/commands/vouch/pack/print.js.map +1 -1
  297. package/dist/commands/vouch/pack/sections.js +1 -2
  298. package/dist/commands/vouch/pack/sections.js.map +1 -1
  299. package/dist/commands/vouch/run.js +26 -81
  300. package/dist/commands/vouch/run.js.map +1 -1
  301. package/dist/commands/vouch/sample/plan.js +20 -34
  302. package/dist/commands/vouch/sample/plan.js.map +1 -1
  303. package/dist/commands/vouch/vet/verify.js +39 -7
  304. package/dist/commands/vouch/vet/verify.js.map +1 -1
  305. package/dist/commands/vouch/vouch.js +10 -17
  306. package/dist/commands/vouch/vouch.js.map +1 -1
  307. package/dist/core/agent/agents-md/command-map.js +81 -106
  308. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  309. package/dist/core/agent/agents-md/map/explain.js +21 -9
  310. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  311. package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
  312. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  313. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  314. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  315. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  316. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  317. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  318. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  319. package/dist/core/agent/agents-md/map/status.js +24 -8
  320. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  321. package/dist/core/agent/agents-md/map/subsystem.js +80 -8
  322. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  323. package/dist/core/agent/agents-md/map/usecases.js +88 -10
  324. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  325. package/dist/core/agent/agents-md/refusals.js +49 -22
  326. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  327. package/dist/core/agent/agents-md.js +39 -6
  328. package/dist/core/agent/agents-md.js.map +1 -1
  329. package/dist/core/agent/agents-stamp.js +10 -0
  330. package/dist/core/agent/agents-stamp.js.map +1 -1
  331. package/dist/core/agent/protocol.js +1 -1
  332. package/dist/core/agent/protocol.js.map +1 -1
  333. package/dist/core/agent/scaffold.js +10 -15
  334. package/dist/core/agent/scaffold.js.map +1 -1
  335. package/dist/core/agent/support/report.js +46 -5
  336. package/dist/core/agent/support/report.js.map +1 -1
  337. package/dist/core/agent/tools/dialects.js +1 -0
  338. package/dist/core/agent/tools/dialects.js.map +1 -1
  339. package/dist/core/agent/tools/registry.js +41 -76
  340. package/dist/core/agent/tools/registry.js.map +1 -1
  341. package/dist/core/agent/workflows/adopt.js +158 -11
  342. package/dist/core/agent/workflows/adopt.js.map +1 -1
  343. package/dist/core/agent/workflows/check.js +78 -39
  344. package/dist/core/agent/workflows/check.js.map +1 -1
  345. package/dist/core/agent/workflows/closing.js +88 -27
  346. package/dist/core/agent/workflows/closing.js.map +1 -1
  347. package/dist/core/agent/workflows/feature.js +128 -13
  348. package/dist/core/agent/workflows/feature.js.map +1 -1
  349. package/dist/core/agent/workflows/implement.js +96 -13
  350. package/dist/core/agent/workflows/implement.js.map +1 -1
  351. package/dist/core/agent/workflows/reference/authoring.js +106 -4
  352. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  353. package/dist/core/agent/workflows/reference/codes.js +8 -6
  354. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  355. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  356. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  357. package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
  358. package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
  359. package/dist/core/agent/workflows/reference/reference.js +6 -3
  360. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  361. package/dist/core/agent/workflows/reference/spine.js +68 -27
  362. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  363. package/dist/core/asyncapi/baseline/plan.js +14 -81
  364. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  365. package/dist/core/asyncapi/digest.js +30 -25
  366. package/dist/core/asyncapi/digest.js.map +1 -1
  367. package/dist/core/asyncapi/merge/components.js +5 -4
  368. package/dist/core/asyncapi/merge/components.js.map +1 -1
  369. package/dist/core/asyncapi/merge/error.js +7 -2
  370. package/dist/core/asyncapi/merge/error.js.map +1 -1
  371. package/dist/core/asyncapi/merge/markers.js +32 -50
  372. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  373. package/dist/core/asyncapi/merge/merge.js +33 -53
  374. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  375. package/dist/core/asyncapi/merge/pin.js +12 -36
  376. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  377. package/dist/core/asyncapi/model.js.map +1 -1
  378. package/dist/core/asyncapi/read.js +36 -12
  379. package/dist/core/asyncapi/read.js.map +1 -1
  380. package/dist/core/brief/brief.js +96 -36
  381. package/dist/core/brief/brief.js.map +1 -1
  382. package/dist/core/brief/checks.js +138 -3
  383. package/dist/core/brief/checks.js.map +1 -1
  384. package/dist/core/brief/landscape.js +209 -130
  385. package/dist/core/brief/landscape.js.map +1 -1
  386. package/dist/core/brief/map/extend.js +84 -0
  387. package/dist/core/brief/map/extend.js.map +1 -0
  388. package/dist/core/brief/map/owed.js +266 -0
  389. package/dist/core/brief/map/owed.js.map +1 -0
  390. package/dist/core/brief/targets.js +109 -31
  391. package/dist/core/brief/targets.js.map +1 -1
  392. package/dist/core/brief/unchecked.js +14 -3
  393. package/dist/core/brief/unchecked.js.map +1 -1
  394. package/dist/core/c4/arch.js +90 -182
  395. package/dist/core/c4/arch.js.map +1 -1
  396. package/dist/core/c4/direction/direction.js +114 -0
  397. package/dist/core/c4/direction/direction.js.map +1 -0
  398. package/dist/core/c4/likec4.js +169 -14
  399. package/dist/core/c4/likec4.js.map +1 -1
  400. package/dist/core/c4/locate/unlocated.js +150 -0
  401. package/dist/core/c4/locate/unlocated.js.map +1 -0
  402. package/dist/core/c4/parsed/deployment.js +160 -0
  403. package/dist/core/c4/parsed/deployment.js.map +1 -0
  404. package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
  405. package/dist/core/c4/parsed/values.js +54 -1
  406. package/dist/core/c4/parsed/values.js.map +1 -1
  407. package/dist/core/c4/parsed/view-ids.js +39 -1
  408. package/dist/core/c4/parsed/view-ids.js.map +1 -1
  409. package/dist/core/c4/prefetch/fleet.js +134 -0
  410. package/dist/core/c4/prefetch/fleet.js.map +1 -0
  411. package/dist/core/c4/project/architecture.js +123 -7
  412. package/dist/core/c4/project/architecture.js.map +1 -1
  413. package/dist/core/c4/project/batch.js +140 -0
  414. package/dist/core/c4/project/batch.js.map +1 -0
  415. package/dist/core/c4/project/documents.js +43 -12
  416. package/dist/core/c4/project/documents.js.map +1 -1
  417. package/dist/core/c4/project/load.js +79 -43
  418. package/dist/core/c4/project/load.js.map +1 -1
  419. package/dist/core/c4/project/staged.js +277 -0
  420. package/dist/core/c4/project/staged.js.map +1 -0
  421. package/dist/core/c4/resolve/attested.js +0 -0
  422. package/dist/core/c4/resolve/attested.js.map +1 -0
  423. package/dist/core/c4/resolve/external.js +51 -0
  424. package/dist/core/c4/resolve/external.js.map +1 -0
  425. package/dist/core/c4/resolve/service.js +78 -17
  426. package/dist/core/c4/resolve/service.js.map +1 -1
  427. package/dist/core/c4/resolve/steps.js +290 -0
  428. package/dist/core/c4/resolve/steps.js.map +1 -0
  429. package/dist/core/c4/resolve/systems.js +154 -0
  430. package/dist/core/c4/resolve/systems.js.map +1 -0
  431. package/dist/core/c4/root-project/exclude.js +352 -0
  432. package/dist/core/c4/root-project/exclude.js.map +1 -0
  433. package/dist/core/c4/seed/template.js +65 -1
  434. package/dist/core/c4/seed/template.js.map +1 -1
  435. package/dist/core/c4/service-model/created/models.js +96 -0
  436. package/dist/core/c4/service-model/created/models.js.map +1 -0
  437. package/dist/core/c4/service-model/documents.js +47 -0
  438. package/dist/core/c4/service-model/documents.js.map +1 -0
  439. package/dist/core/c4/service-model/fleet/extending.js +82 -0
  440. package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
  441. package/dist/core/c4/service-model/load.js +165 -0
  442. package/dist/core/c4/service-model/load.js.map +1 -0
  443. package/dist/core/c4/service-model/renderer.js +128 -0
  444. package/dist/core/c4/service-model/renderer.js.map +1 -0
  445. package/dist/core/c4/service-model/shape.js +106 -0
  446. package/dist/core/c4/service-model/shape.js.map +1 -0
  447. package/dist/core/c4/service-model/slice.js +188 -0
  448. package/dist/core/c4/service-model/slice.js.map +1 -0
  449. package/dist/core/c4/source-mask.js +297 -10
  450. package/dist/core/c4/source-mask.js.map +1 -1
  451. package/dist/core/c4/source-scan.js +222 -141
  452. package/dist/core/c4/source-scan.js.map +1 -1
  453. package/dist/core/c4/splice/authored-source.js +79 -13
  454. package/dist/core/c4/splice/authored-source.js.map +1 -1
  455. package/dist/core/c4/splice/contract.js +20 -0
  456. package/dist/core/c4/splice/contract.js.map +1 -1
  457. package/dist/core/c4/splice/delta-blocks.js +20 -20
  458. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  459. package/dist/core/c4/splice/identity/declared.js +87 -0
  460. package/dist/core/c4/splice/identity/declared.js.map +1 -0
  461. package/dist/core/c4/splice/identity/edges.js +37 -0
  462. package/dist/core/c4/splice/identity/edges.js.map +1 -1
  463. package/dist/core/c4/splice/identity/existing.js +80 -0
  464. package/dist/core/c4/splice/identity/existing.js.map +1 -0
  465. package/dist/core/c4/splice/identity/interior.js +80 -0
  466. package/dist/core/c4/splice/identity/interior.js.map +1 -0
  467. package/dist/core/c4/splice/identity/owner.js +50 -0
  468. package/dist/core/c4/splice/identity/owner.js.map +1 -0
  469. package/dist/core/c4/splice/landscape-merge.js +182 -114
  470. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  471. package/dist/core/c4/splice/model/merge.js +151 -0
  472. package/dist/core/c4/splice/model/merge.js.map +1 -0
  473. package/dist/core/c4/splice/placement.js +160 -56
  474. package/dist/core/c4/splice/placement.js.map +1 -1
  475. package/dist/core/c4/splice/text/spot.js +131 -0
  476. package/dist/core/c4/splice/text/spot.js.map +1 -0
  477. package/dist/core/c4/workspace.js +13 -19
  478. package/dist/core/c4/workspace.js.map +1 -1
  479. package/dist/core/capabilities/capabilities.js +6 -6
  480. package/dist/core/capabilities/capabilities.js.map +1 -1
  481. package/dist/core/capabilities/delta/doc.js +7 -10
  482. package/dist/core/capabilities/delta/doc.js.map +1 -1
  483. package/dist/core/capabilities/delta/uncovered.js +2 -2
  484. package/dist/core/capabilities/delta/uncovered.js.map +1 -1
  485. package/dist/core/capabilities/findings.js +18 -22
  486. package/dist/core/capabilities/findings.js.map +1 -1
  487. package/dist/core/capabilities/realizes/findings.js +12 -19
  488. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  489. package/dist/core/capabilities/realizes/join.js +0 -20
  490. package/dist/core/capabilities/realizes/join.js.map +1 -1
  491. package/dist/core/capabilities/rollup.js +6 -0
  492. package/dist/core/capabilities/rollup.js.map +1 -1
  493. package/dist/core/capabilities/tree.js +6 -28
  494. package/dist/core/capabilities/tree.js.map +1 -1
  495. package/dist/core/capabilities/usecase-join.js +3 -13
  496. package/dist/core/capabilities/usecase-join.js.map +1 -1
  497. package/dist/core/coherence/authoring/scaffold.js +11 -3
  498. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  499. package/dist/core/coherence/authoring/sentinels.js +2 -1
  500. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  501. package/dist/core/coherence/calls/called.js +52 -0
  502. package/dist/core/coherence/calls/called.js.map +1 -0
  503. package/dist/core/coherence/coherence.js +103 -22
  504. package/dist/core/coherence/coherence.js.map +1 -1
  505. package/dist/core/coherence/consumed/operations.js +70 -0
  506. package/dist/core/coherence/consumed/operations.js.map +1 -0
  507. package/dist/core/coherence/consumed/refusal.js +38 -0
  508. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  509. package/dist/core/coherence/covers/health.js +85 -0
  510. package/dist/core/coherence/covers/health.js.map +1 -0
  511. package/dist/core/coherence/declared.js +60 -34
  512. package/dist/core/coherence/declared.js.map +1 -1
  513. package/dist/core/coherence/events/declared.js +44 -59
  514. package/dist/core/coherence/events/declared.js.map +1 -1
  515. package/dist/core/coherence/events/events.js +147 -16
  516. package/dist/core/coherence/events/events.js.map +1 -1
  517. package/dist/core/coherence/events/grades.js +31 -8
  518. package/dist/core/coherence/events/grades.js.map +1 -1
  519. package/dist/core/coherence/events/lookups.js +28 -87
  520. package/dist/core/coherence/events/lookups.js.map +1 -1
  521. package/dist/core/coherence/events/produced.js +100 -0
  522. package/dist/core/coherence/events/produced.js.map +1 -0
  523. package/dist/core/coherence/lookups.js +18 -78
  524. package/dist/core/coherence/lookups.js.map +1 -1
  525. package/dist/core/coherence/retired/archived.js +285 -0
  526. package/dist/core/coherence/retired/archived.js.map +1 -0
  527. package/dist/core/coherence/retired/event-pair.js +62 -0
  528. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  529. package/dist/core/coherence/retired/merged.js +79 -0
  530. package/dist/core/coherence/retired/merged.js.map +1 -0
  531. package/dist/core/coherence/retired/retirement.js +49 -0
  532. package/dist/core/coherence/retired/retirement.js.map +1 -0
  533. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  534. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  535. package/dist/core/coherence/retired/revival/revived.js +297 -0
  536. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  537. package/dist/core/consumers/after.js +113 -0
  538. package/dist/core/consumers/after.js.map +1 -0
  539. package/dist/core/consumers/consumers.js +296 -0
  540. package/dist/core/consumers/consumers.js.map +1 -0
  541. package/dist/core/consumers/current.js +211 -0
  542. package/dist/core/consumers/current.js.map +1 -0
  543. package/dist/core/consumers/scan.js +46 -0
  544. package/dist/core/consumers/scan.js.map +1 -0
  545. package/dist/core/consumers/state.js +27 -0
  546. package/dist/core/consumers/state.js.map +1 -0
  547. package/dist/core/delta/delta.js +51 -6
  548. package/dist/core/delta/delta.js.map +1 -1
  549. package/dist/core/dependencies/fanin.js +2 -2
  550. package/dist/core/dependencies/fanin.js.map +1 -1
  551. package/dist/core/deployment/delta.js +191 -0
  552. package/dist/core/deployment/delta.js.map +1 -0
  553. package/dist/core/deployment/objects.js +145 -0
  554. package/dist/core/deployment/objects.js.map +1 -0
  555. package/dist/core/diff/base-git.js +94 -12
  556. package/dist/core/diff/base-git.js.map +1 -1
  557. package/dist/core/diff/base-state.js +49 -21
  558. package/dist/core/diff/base-state.js.map +1 -1
  559. package/dist/core/diff/joins.js +136 -0
  560. package/dist/core/diff/joins.js.map +1 -0
  561. package/dist/core/diff/scope/changed-paths.js +22 -6
  562. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  563. package/dist/core/diff/scope/edges.js +82 -0
  564. package/dist/core/diff/scope/edges.js.map +1 -0
  565. package/dist/core/diff/semantic.js +100 -31
  566. package/dist/core/diff/semantic.js.map +1 -1
  567. package/dist/core/docs.js +31 -22
  568. package/dist/core/docs.js.map +1 -1
  569. package/dist/core/doctor/agents.js +34 -18
  570. package/dist/core/doctor/agents.js.map +1 -1
  571. package/dist/core/doctor/config.js +7 -3
  572. package/dist/core/doctor/config.js.map +1 -1
  573. package/dist/core/doctor/doctor.js +91 -11
  574. package/dist/core/doctor/doctor.js.map +1 -1
  575. package/dist/core/doctor/report.js.map +1 -1
  576. package/dist/core/doctor/reports/scan.js +242 -0
  577. package/dist/core/doctor/reports/scan.js.map +1 -0
  578. package/dist/core/document/apply.js +158 -11
  579. package/dist/core/document/apply.js.map +1 -1
  580. package/dist/core/document/frontmatter.js +55 -0
  581. package/dist/core/document/frontmatter.js.map +1 -1
  582. package/dist/core/document/parse.js +29 -6
  583. package/dist/core/document/parse.js.map +1 -1
  584. package/dist/core/document/scenarios.js +17 -0
  585. package/dist/core/document/scenarios.js.map +1 -1
  586. package/dist/core/document/spec.js +4 -11
  587. package/dist/core/document/spec.js.map +1 -1
  588. package/dist/core/envelope/config.js +10 -0
  589. package/dist/core/envelope/config.js.map +1 -1
  590. package/dist/core/envelope/json.js +34 -0
  591. package/dist/core/envelope/json.js.map +1 -1
  592. package/dist/core/explain/families.js +26 -21
  593. package/dist/core/explain/families.js.map +1 -1
  594. package/dist/core/explain/lookup.js +14 -1
  595. package/dist/core/explain/lookup.js.map +1 -1
  596. package/dist/core/explain/refusals.js +5 -5
  597. package/dist/core/explain/refusals.js.map +1 -1
  598. package/dist/core/explain/terms/documents.js +227 -0
  599. package/dist/core/explain/terms/documents.js.map +1 -0
  600. package/dist/core/explain/terms/done-check.js +144 -0
  601. package/dist/core/explain/terms/done-check.js.map +1 -0
  602. package/dist/core/explain/terms/entry.js +14 -0
  603. package/dist/core/explain/terms/entry.js.map +1 -0
  604. package/dist/core/explain/terms/stages.js +71 -0
  605. package/dist/core/explain/terms/stages.js.map +1 -0
  606. package/dist/core/explain/terms/tasks.js +97 -0
  607. package/dist/core/explain/terms/tasks.js.map +1 -0
  608. package/dist/core/explain/terms.js +68 -109
  609. package/dist/core/explain/terms.js.map +1 -1
  610. package/dist/core/explore/explore.js +40 -10
  611. package/dist/core/explore/explore.js.map +1 -1
  612. package/dist/core/fleet-context.js +81 -95
  613. package/dist/core/fleet-context.js.map +1 -1
  614. package/dist/core/gate/checks.js +11 -4
  615. package/dist/core/gate/checks.js.map +1 -1
  616. package/dist/core/gate/partners.js +69 -28
  617. package/dist/core/gate/partners.js.map +1 -1
  618. package/dist/core/gate/report.js.map +1 -1
  619. package/dist/core/gate/verification.js +75 -12
  620. package/dist/core/gate/verification.js.map +1 -1
  621. package/dist/core/gherkin/stale.js +13 -37
  622. package/dist/core/gherkin/stale.js.map +1 -1
  623. package/dist/core/glossary/delta.js +10 -7
  624. package/dist/core/glossary/delta.js.map +1 -1
  625. package/dist/core/glossary/tree.js +6 -19
  626. package/dist/core/glossary/tree.js.map +1 -1
  627. package/dist/core/kernel/concurrency.js +19 -0
  628. package/dist/core/kernel/concurrency.js.map +1 -1
  629. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  630. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  631. package/dist/core/kernel/path-safety.js +38 -0
  632. package/dist/core/kernel/path-safety.js.map +1 -1
  633. package/dist/core/kernel/records.js +11 -0
  634. package/dist/core/kernel/records.js.map +1 -1
  635. package/dist/core/links/backlinks.js +1 -3
  636. package/dist/core/links/backlinks.js.map +1 -1
  637. package/dist/core/links/corpus.js +51 -37
  638. package/dist/core/links/corpus.js.map +1 -1
  639. package/dist/core/links/findings.js.map +1 -1
  640. package/dist/core/links/parse.js +38 -9
  641. package/dist/core/links/parse.js.map +1 -1
  642. package/dist/core/mcp/argv.js +101 -0
  643. package/dist/core/mcp/argv.js.map +1 -1
  644. package/dist/core/mcp/author-tools.js +2 -1
  645. package/dist/core/mcp/author-tools.js.map +1 -1
  646. package/dist/core/mcp/protocol.js +2 -2
  647. package/dist/core/mcp/protocol.js.map +1 -1
  648. package/dist/core/mcp/tools.js +43 -33
  649. package/dist/core/mcp/tools.js.map +1 -1
  650. package/dist/core/openapi/baseline/gate.js +53 -12
  651. package/dist/core/openapi/baseline/gate.js.map +1 -1
  652. package/dist/core/openapi/baseline/plan.js +40 -10
  653. package/dist/core/openapi/baseline/plan.js.map +1 -1
  654. package/dist/core/openapi/baseline/record.js +0 -1
  655. package/dist/core/openapi/baseline/record.js.map +1 -1
  656. package/dist/core/openapi/contract-text.js +33 -0
  657. package/dist/core/openapi/contract-text.js.map +1 -0
  658. package/dist/core/openapi/contract-tree.js +301 -0
  659. package/dist/core/openapi/contract-tree.js.map +1 -0
  660. package/dist/core/openapi/depth.js +38 -24
  661. package/dist/core/openapi/depth.js.map +1 -1
  662. package/dist/core/openapi/digest.js +52 -2
  663. package/dist/core/openapi/digest.js.map +1 -1
  664. package/dist/core/openapi/doc.js +12 -4
  665. package/dist/core/openapi/doc.js.map +1 -1
  666. package/dist/core/openapi/merge/components.js +25 -60
  667. package/dist/core/openapi/merge/components.js.map +1 -1
  668. package/dist/core/openapi/merge/error.js +3 -13
  669. package/dist/core/openapi/merge/error.js.map +1 -1
  670. package/dist/core/openapi/merge/markers.js +26 -30
  671. package/dist/core/openapi/merge/markers.js.map +1 -1
  672. package/dist/core/openapi/merge/merge.js +28 -52
  673. package/dist/core/openapi/merge/merge.js.map +1 -1
  674. package/dist/core/openapi/merge/pin.js +17 -35
  675. package/dist/core/openapi/merge/pin.js.map +1 -1
  676. package/dist/core/openspec/decide/verdict.js.map +1 -1
  677. package/dist/core/openspec/inventory.js +4 -5
  678. package/dist/core/openspec/inventory.js.map +1 -1
  679. package/dist/core/openspec/model/mapping.js +30 -28
  680. package/dist/core/openspec/model/mapping.js.map +1 -1
  681. package/dist/core/openspec/model/model.js +12 -7
  682. package/dist/core/openspec/model/model.js.map +1 -1
  683. package/dist/core/openspec/scan/changes.js +10 -4
  684. package/dist/core/openspec/scan/changes.js.map +1 -1
  685. package/dist/core/openspec/scan/config.js +7 -16
  686. package/dist/core/openspec/scan/config.js.map +1 -1
  687. package/dist/core/openspec/scan/shape.js +63 -19
  688. package/dist/core/openspec/scan/shape.js.map +1 -1
  689. package/dist/core/openspec/scan/walk.js +21 -38
  690. package/dist/core/openspec/scan/walk.js.map +1 -1
  691. package/dist/core/pack/living.js +43 -4
  692. package/dist/core/pack/living.js.map +1 -1
  693. package/dist/core/pack/pack.js +50 -6
  694. package/dist/core/pack/pack.js.map +1 -1
  695. package/dist/core/projection/arch-slice.js +2 -2
  696. package/dist/core/projection/arch-slice.js.map +1 -1
  697. package/dist/core/projection/introduced.js +14 -0
  698. package/dist/core/projection/introduced.js.map +1 -0
  699. package/dist/core/provenance/findings.js +48 -12
  700. package/dist/core/provenance/findings.js.map +1 -1
  701. package/dist/core/provenance/git.js +33 -57
  702. package/dist/core/provenance/git.js.map +1 -1
  703. package/dist/core/provenance/gitq/moves.js +71 -114
  704. package/dist/core/provenance/gitq/moves.js.map +1 -1
  705. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  706. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  707. package/dist/core/provenance/sources.js +23 -6
  708. package/dist/core/provenance/sources.js.map +1 -1
  709. package/dist/core/provenance/stamp.js +5 -17
  710. package/dist/core/provenance/stamp.js.map +1 -1
  711. package/dist/core/repo/authored/paths.js +18 -0
  712. package/dist/core/repo/authored/paths.js.map +1 -1
  713. package/dist/core/repo/entries.js.map +1 -1
  714. package/dist/core/repo/paths.js +92 -6
  715. package/dist/core/repo/paths.js.map +1 -1
  716. package/dist/core/repo/repo.js +74 -14
  717. package/dist/core/repo/repo.js.map +1 -1
  718. package/dist/core/repo/tree/find.js +12 -3
  719. package/dist/core/repo/tree/find.js.map +1 -1
  720. package/dist/core/repo/tree/fs.js +27 -1
  721. package/dist/core/repo/tree/fs.js.map +1 -1
  722. package/dist/core/repo/tree/render/body.js +258 -0
  723. package/dist/core/repo/tree/render/body.js.map +1 -0
  724. package/dist/core/repo/tree/render/projects.js +182 -0
  725. package/dist/core/repo/tree/render/projects.js.map +1 -0
  726. package/dist/core/repo/tree/render/service/header.js +125 -0
  727. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  728. package/dist/core/repo/tree/render/service/peers.js +28 -0
  729. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  730. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  731. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  732. package/dist/core/repo/tree/render/service/views.js +78 -0
  733. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  734. package/dist/core/repo/tree/render/stale.js +42 -0
  735. package/dist/core/repo/tree/render/stale.js.map +1 -0
  736. package/dist/core/repo/tree/render/text.js +84 -0
  737. package/dist/core/repo/tree/render/text.js.map +1 -0
  738. package/dist/core/repo/tree/render/views.js +293 -0
  739. package/dist/core/repo/tree/render/views.js.map +1 -0
  740. package/dist/core/repo/tree/walk.js +53 -32
  741. package/dist/core/repo/tree/walk.js.map +1 -1
  742. package/dist/core/results.js +47 -12
  743. package/dist/core/results.js.map +1 -1
  744. package/dist/core/scaffold/landscape.js +121 -13
  745. package/dist/core/scaffold/landscape.js.map +1 -1
  746. package/dist/core/scaffold/legacy.js +271 -0
  747. package/dist/core/scaffold/legacy.js.map +1 -0
  748. package/dist/core/staging/commit.js +26 -15
  749. package/dist/core/staging/commit.js.map +1 -1
  750. package/dist/core/staging/lock.js +74 -12
  751. package/dist/core/staging/lock.js.map +1 -1
  752. package/dist/core/staging/recovery/intent.js +2 -5
  753. package/dist/core/staging/recovery/intent.js.map +1 -1
  754. package/dist/core/staging/recovery/recover.js +84 -6
  755. package/dist/core/staging/recovery/recover.js.map +1 -1
  756. package/dist/core/staging/snapshot/manifest.js +188 -0
  757. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  758. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
  759. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  760. package/dist/core/staging/txn/forward.js +24 -14
  761. package/dist/core/staging/txn/forward.js.map +1 -1
  762. package/dist/core/staging/writes.js.map +1 -1
  763. package/dist/core/status/actions/execution.js +11 -0
  764. package/dist/core/status/actions/execution.js.map +1 -1
  765. package/dist/core/status/contracts.js +39 -15
  766. package/dist/core/status/contracts.js.map +1 -1
  767. package/dist/core/status/feature/artifacts.js +33 -8
  768. package/dist/core/status/feature/artifacts.js.map +1 -1
  769. package/dist/core/status/feature/feature.js +24 -17
  770. package/dist/core/status/feature/feature.js.map +1 -1
  771. package/dist/core/status/feature/next.js +20 -7
  772. package/dist/core/status/feature/next.js.map +1 -1
  773. package/dist/core/status/feature/words.js +96 -0
  774. package/dist/core/status/feature/words.js.map +1 -0
  775. package/dist/core/status/fleet/fleet.js +43 -14
  776. package/dist/core/status/fleet/fleet.js.map +1 -1
  777. package/dist/core/status/fleet/next.js +153 -58
  778. package/dist/core/status/fleet/next.js.map +1 -1
  779. package/dist/core/status/report.js.map +1 -1
  780. package/dist/core/status/scan.js.map +1 -1
  781. package/dist/core/status/verification.js +30 -15
  782. package/dist/core/status/verification.js.map +1 -1
  783. package/dist/core/tasks/packet.js +199 -0
  784. package/dist/core/tasks/packet.js.map +1 -0
  785. package/dist/core/tasks/record.js +43 -0
  786. package/dist/core/tasks/record.js.map +1 -0
  787. package/dist/core/tasks/sources.js +181 -0
  788. package/dist/core/tasks/sources.js.map +1 -0
  789. package/dist/core/tasks/stamps.js +76 -0
  790. package/dist/core/tasks/stamps.js.map +1 -0
  791. package/dist/core/tasks/unit.js +0 -0
  792. package/dist/core/tasks/unit.js.map +1 -0
  793. package/dist/core/usecases/capability.js +11 -14
  794. package/dist/core/usecases/capability.js.map +1 -1
  795. package/dist/core/usecases/delta/claims.js +31 -7
  796. package/dist/core/usecases/delta/claims.js.map +1 -1
  797. package/dist/core/usecases/delta/flows.js +5 -7
  798. package/dist/core/usecases/delta/flows.js.map +1 -1
  799. package/dist/core/usecases/delta/modified.js +315 -0
  800. package/dist/core/usecases/delta/modified.js.map +1 -0
  801. package/dist/core/usecases/delta/overlay.js +32 -191
  802. package/dist/core/usecases/delta/overlay.js.map +1 -1
  803. package/dist/core/usecases/fleet.js +195 -36
  804. package/dist/core/usecases/fleet.js.map +1 -1
  805. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
  806. package/dist/core/usecases/grade/capability-tag.js.map +1 -0
  807. package/dist/core/usecases/grade/feature.js +88 -0
  808. package/dist/core/usecases/grade/feature.js.map +1 -0
  809. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
  810. package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
  811. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
  812. package/dist/core/usecases/grade/steps.js.map +1 -0
  813. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
  814. package/dist/core/usecases/grade/usecases.js.map +1 -0
  815. package/dist/core/usecases/operations.js +26 -14
  816. package/dist/core/usecases/operations.js.map +1 -1
  817. package/dist/core/usecases/place.js.map +1 -1
  818. package/dist/core/usecases/service/exclude.js +119 -0
  819. package/dist/core/usecases/service/exclude.js.map +1 -0
  820. package/dist/core/usecases/service/flows.js +164 -0
  821. package/dist/core/usecases/service/flows.js.map +1 -0
  822. package/dist/core/usecases/suites/participants.js +83 -0
  823. package/dist/core/usecases/suites/participants.js.map +1 -0
  824. package/dist/core/usecases/suites/suites.js +45 -0
  825. package/dist/core/usecases/suites/suites.js.map +1 -0
  826. package/dist/core/verify/answers.js +12 -5
  827. package/dist/core/verify/answers.js.map +1 -1
  828. package/dist/core/verify/build.js +102 -82
  829. package/dist/core/verify/build.js.map +1 -1
  830. package/dist/core/verify/checklist.js +70 -12
  831. package/dist/core/verify/checklist.js.map +1 -1
  832. package/dist/core/verify/location.js +67 -0
  833. package/dist/core/verify/location.js.map +1 -0
  834. package/dist/core/verify/pins/lint.js +3 -2
  835. package/dist/core/verify/pins/lint.js.map +1 -1
  836. package/dist/core/verify/pins/pin.js +1 -1
  837. package/dist/core/verify/pins/pin.js.map +1 -1
  838. package/dist/core/verify/read/file.js +224 -0
  839. package/dist/core/verify/read/file.js.map +1 -0
  840. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  841. package/dist/core/verify/read/shape.js.map +1 -0
  842. package/dist/core/verify/read/union.js +105 -0
  843. package/dist/core/verify/read/union.js.map +1 -0
  844. package/dist/core/verify/record.js +35 -12
  845. package/dist/core/verify/record.js.map +1 -1
  846. package/dist/core/verify/store/commit.js +40 -30
  847. package/dist/core/verify/store/commit.js.map +1 -1
  848. package/dist/core/verify/store/render.js +8 -4
  849. package/dist/core/verify/store/render.js.map +1 -1
  850. package/dist/core/vocabulary/issue.js +4 -0
  851. package/dist/core/vocabulary/issue.js.map +1 -1
  852. package/dist/core/vocabulary/maturity.js +75 -4
  853. package/dist/core/vocabulary/maturity.js.map +1 -1
  854. package/dist/core/vocabulary/report.js +16 -1
  855. package/dist/core/vocabulary/report.js.map +1 -1
  856. package/examples/README.md +54 -11
  857. package/examples/docs/architecture/landscape.likec4 +62 -6
  858. package/examples/docs/architecture/services.likec4 +70 -0
  859. package/examples/docs/architecture/subsystems.likec4 +7 -0
  860. package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
  861. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  862. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  863. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
  864. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  865. package/examples/docs/likec4.config.json +0 -1
  866. package/examples/docs/services/checkout-web/model.likec4 +39 -56
  867. package/examples/docs/services/order-service/arch.spec.md +13 -6
  868. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  869. package/examples/docs/services/order-service/model.likec4 +58 -70
  870. package/examples/docs/services/payment-service/arch.spec.md +7 -4
  871. package/examples/docs/services/payment-service/model.likec4 +38 -37
  872. package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
  873. package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
  874. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  875. package/examples/docs/services/platform/notification-service/health.yaml +8 -3
  876. package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
  877. package/package.json +1 -1
  878. package/dist/commands/validate/checks/vocabulary.js +0 -31
  879. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  880. package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
  881. package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
  882. package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
  883. package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
  884. package/dist/core/agent/agents-md/artifacts.js +0 -220
  885. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  886. package/dist/core/agent/agents-md/cycle.js +0 -165
  887. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  888. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  889. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  890. package/dist/core/agent/agents-md/spine.js +0 -40
  891. package/dist/core/agent/agents-md/spine.js.map +0 -1
  892. package/dist/core/coherence/events/merged.js +0 -40
  893. package/dist/core/coherence/events/merged.js.map +0 -1
  894. package/dist/core/diff/current-state.js +0 -96
  895. package/dist/core/diff/current-state.js.map +0 -1
  896. package/dist/core/diff/victims.js +0 -247
  897. package/dist/core/diff/victims.js.map +0 -1
  898. package/dist/core/repo/tree/views.js +0 -172
  899. package/dist/core/repo/tree/views.js.map +0 -1
  900. package/dist/core/staging/snapshot.js.map +0 -1
  901. package/dist/core/verify/file.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"spine.js","sourceRoot":"","sources":["../../../../../src/core/agent/workflows/reference/spine.ts"],"names":[],"mappings":"AAqBA,MAAM,CAAC,MAAM,UAAU,GAAmB;IACxC,IAAI,EAAE,YAAY;IAClB,WAAW,EAAE,mFAAmF;IAChG,0EAA0E;IAC1E,4EAA4E;IAC5E,yEAAyE;IACzE,YAAY,EAAE,EAAE;IAChB,OAAO,EACL,qNAAqN;IACvN,UAAU,EAAE,8BAA8B;IAC1C,YAAY,EAAE,EAAE;IAChB,KAAK,EAAE;QACL,cAAc;QACd,wCAAwC;QACxC,yBAAyB;QACzB,sCAAsC;KACvC;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsRP;CACA,CAAC","sourcesContent":["/**\n * The `loam-spine` reference page: how the artifacts JOIN.\n *\n * Operation and message ids, coverage and permission references, the\n * capability tree and its `Realizes:` join, the requirement baseline pins\n * (`Based-On:` / `x-loam-based-on`), how to draw a shared broker, and how to\n * declare a message produced outside the fleet.\n *\n * This is the grammar a reader consults while writing ONE document, which is\n * why it is a printed page rather than a section of AGENTS.md: it is consulted\n * at a moment you can name, and paying for it on every invocation of every\n * session bought nothing. `## Which element IS which service` deliberately\n * stayed behind in ../../agents-md/spine.ts — an agent cannot read the\n * landscape at all without it, so it is orientation rather than reference.\n *\n * The text is the AGENTS.md section verbatim; nothing was rewritten to move\n * it, so a cross-reference from the remaining file still names the same\n * headings. See ./reference.ts for why these four pages exist.\n */\nimport type { CommandContent } from \"../../contract.js\";\n\nexport const LOAM_SPINE: CommandContent = {\n name: \"loam-spine\",\n description: \"Reference: the ID spine — every join between the artifacts, and the baseline pins\",\n // A reference page takes no arguments: it is one document, printed whole.\n // The empty hint is what `loam instructions` prints beside the name, and an\n // invented one (\"<none>\") would read as an argument somebody could pass.\n argumentHint: \"\",\n purpose:\n \"The exact joins the artifacts are held together by — operationId, message name, `Covers:`, `Requires:`, `Capability:`/`Realizes:` — and the `Based-On:` pins that stop two features in flight reverting each other.\",\n invocation: \"loam instructions loam-spine\",\n placeholders: [],\n spine: [\n \"The ID spine\",\n \"The requirement baseline — `Based-On:`\",\n \"Drawing a shared broker\",\n \"A message produced outside the fleet\",\n ],\n body: `The ID spine: how the artifacts join, and what pins a change to the version it was written against.\n\n## The ID spine\n\nThe spine is a family of exact joins, not one magic id: feature tag; service\nbinding; synchronous operation; asynchronous message; architecture coverage;\nauthorization permission. Three artifacts describe the same synchronous call in\nthree languages. They are joined by the **operationId**, and that join is what\n\\`loam validate\\` checks:\n\n- architecture — a C4 edge names the operation it calls:\n \\`checkoutWeb -> paymentService 'Calls authorizePayment' { metadata { op 'authorizePayment' } }\\`\n- behaviour — a requirement declares the operations it governs:\n \\`Operations: authorizePayment\\`\n- contract — the provider's \\`openapi.yaml\\` defines it:\n \\`operationId: authorizePayment\\`\n\nSpell it identically in all three places. A mismatch is an error, not a warning:\nan edge calling an operation nobody defines is a broken contract between services.\n\nFeatures are joined the same way: a delta's new elements and edges carry the\nfeature id as a LikeC4 tag (\\`#FEAT-101\\`), and that tag is exactly what\n\\`loam archive\\` folds into the living landscape. Untagged elements in a delta are\ncontext for the diagram, not changes.\n\nThe other joins use the same discipline:\n\n- \\`metadata { publishes|consumes 'message' }\\` on an edge ↔\n \\`Publishes:\\`/\\`Consumes:\\` in a requirement ↔ the AsyncAPI 3 message name;\n- \\`Covers:\\` in an architecture requirement ↔ a C4 element/edge or\n \\`alert:<id>\\` / \\`sli:<id>\\` from health.yaml;\n- \\`Requires: <subject>/<permission>\\` in either requirement file ↔ the fleet\n declaration in \\`architecture/permissions.yaml\\`;\n- \\`Capability:\\` in either requirement file ↔ a declared id in\n \\`architecture/capabilities.yaml\\`. A LIST, comma-separated — the relation is\n many-to-many in both directions.\n\nThe permissions file is opt-in: a fleet with no \\`Requires:\\` line owes none.\nOnce a requirement names a permission, an undeclared pair is\n\\`permissions.unknown\\` (error). An unreadable vocabulary is\n\\`permissions.invalid\\`; a declaration nothing requires is\n\\`permissions.unenforced\\` (warn). The subject is what the permission is checked\nON (user, profile, service), not necessarily the caller. \\`owned_by\\` and\n\\`enforced_by\\` are explanatory and are not resolved against services; this is\na checked document join, not proof that the identity provider implements it.\n\nThe capability axis opts in the OTHER way: the FILES are the opt-in, not the\nline. A fleet with neither \\`architecture/capabilities.yaml\\` nor a\n\\`capabilities/\\` directory gets no capability findings at all, however many\n\\`Capability:\\` lines its requirements already carry.\n\nA capability may be declared on either side, and the vocabulary is the UNION.\nA name alone is a line in \\`architecture/capabilities.yaml\\` —\n\\`capabilities: {<id>: {description, owner}}\\`, nested ids such as\n\\`payments/refunds\\` kept as one flat key. A name with prose behind it gets a\ndocument instead: \\`capabilities/<id>/spec.md\\`, the AUTHORED business tree,\none directory per capability with nesting spelled by the tree\n(\\`capabilities/payments/refunds/spec.md\\`). The directory is the list — there\nis no manifest — and its existence is what opts the fleet in, so \\`loam init\\`\ndoes not scaffold it. A directory holding neither the document nor a\ncapability beneath it is \\`capability.doc-missing\\` (warn).\n\nThe document carries narrative and then \\`## Requirements\\`, in the same\ngrammar every spec.md uses, with two rules of its own. Every requirement needs\na \\`Requirement-ID:\\` — these documents outlive the services that realize them,\nso identity is the line and not the heading; without one it is\n\\`capability.requirement-unidentified\\` (error). And a capability requirement\nmust be observable OUTSIDE the fleet: \\`Operations:\\`, \\`Covers:\\`,\n\\`Publishes:\\` and \\`Consumes:\\` all resolve against one service's own contract,\nso carrying any of them is \\`capability.requirement-service-scoped\\` (error) —\nwrite it in that service's spec.md instead. Naming no service is an authoring\nrule that PR review holds, not one loam checks: matching service names in prose\nwould be a heuristic, and loam does not guess. The axis's own two joins are\nrefused here as well (\\`capability.requirement-inert-join\\`, error): they point\nINTO the tree, so nothing reads them written inside it.\n\nA FEATURE changes a capability document with a delta, not by editing it:\n\\`features/<FEAT>/capabilities/<id>/spec.md\\`, the same\n\\`## ADDED|MODIFIED|REMOVED Requirements\\` grammar the service deltas use, the\nsame nesting (\\`features/<FEAT>/capabilities/payments/refunds/\\`), the same\n\\`Based-On:\\` pins, and the same \\`delta.*\\` refusals with the capability id as\nthe subject. \\`scaffold.placeholder\\` reads it too, so an unedited template\nheading refuses the archive here as it does in a spec delta, and\n\\`capability.doc-missing\\` (warn) names a directory in it that holds no\n\\`spec.md\\` — an empty one is not a delta, so the merge would carry nothing.\n\\`loam archive\\` merges it into the living document in the same\ntransaction as the service merge, creating \\`capabilities/<id>/spec.md\\` — and\nso the whole tree — when this is the first feature to mention it; the heading\nis the id and the narrative above \\`## Requirements\\` is yours to write, because\nloam merges requirements and never prose. The document's three rules above are\ngraded on the delta BEFORE the merge, so a capability requirement filed at the\nwrong altitude is refused while you still hold it. Editing the living document\ndirectly is still legal; the delta is what makes two features touching one\npromise collide loudly instead of silently. \\`loam new <FEAT> --capability <id>\\`\nscaffolds that delta, and \\`loam show <FEAT>\\` and \\`loam delta <FEAT>\\` list the\npromises a feature changes — a capability delta names no service, so without\nthose two a business-only feature reads as a feature carrying nothing.\n\nA service requirement says which promise it serves with \\`Realizes:\\`, entries\nspelled \\`<capability-id>#<Requirement-ID>\\`. This is the join the tree exists\nfor, and it is NOT the same claim as \\`Capability:\\` beside it — that names a\ntheme, this names one promise, and a requirement commonly carries both. The\ncapability half of an entry is what makes the target addressable, since a\n\\`Requirement-ID\\` is unique only inside its own document; the separator is the\nLAST \\`#\\`, because the requirement half's grammar excludes one and the\ncapability half's does not. Written by whoever implements the requirement,\nnever by the analyst — a business document must not be edited every time the\nfleet rearranges which service carries which part.\n\nBoth directions are graded. An entry naming no capability requirement is\n\\`capability.realizes-unknown\\` (error; in a feature delta it gates\n\\`loam archive\\`, \\`--approve\\`-overridable), and its message says which of the\nfive failures happened — malformed entry, undeclared capability, declared but\nundocumented, document with no requirements, or an id that document does not\ndeclare. A capability requirement nothing keeps is\n\\`capability.requirement-unrealized\\` (warn, one per requirement): it never\ngates, because writing the business document ahead of the fleet is the intended\nuse. \\`loam list capabilities --json\\` carries the whole join — each capability's\nrequirements, and what realizes each one.\n\n\\`Realizes:\\` is not the only way to keep a promise, and for a criterion that\nCROSSES services it is not enough: \"I enter a login and a password and I am in\"\nbelongs to no single service's spec, because each promises only its own part. A\nUSE CASE can carry it, because it IS the hop sequence. So a \\`dynamic view\\`\nalready tagged \\`#cap-<slug>\\` may carry \\`#req-<slug>\\` as well, naming one of\nthat capability's requirements — the architect's answer to the analyst's\npromise, graded by the same four \\`usecase.*\\` codes down to the operation. The\nsecond tag is SCOPED by the first: a \\`Requirement-ID\\` is unique only inside its\nown document, so a \\`#req-\\` tag on a view that resolves no capability, or two, is\n\\`usecase.requirement-unresolved\\` (error) rather than a guess. Several \\`#req-\\`\ntags on one view are legal — a flow commonly keeps two promises. Both tags spell\ntheir id with every character outside \\`[A-Za-z0-9_-]\\` flattened to \\`-\\`,\nbecause that is all a LikeC4 tag name accepts.\n\nAgainst that vocabulary an undeclared name is \\`capability.unknown\\` (error; in\na feature delta it gates \\`loam archive\\`, \\`--approve\\`-overridable), an\nunreadable \\`capabilities.yaml\\` is \\`capability.invalid\\` exactly once per run\nwith every grade that resolves against the vocabulary suspended behind it, and\na declared capability no living requirement realizes is\n\\`capability.unrealized\\` (warn, one per capability). The fleet total is\nreadable: \\`loam list capabilities\\` reports each capability's realizing\nrequirements, their services and the draft/verified split, and\n\\`loam explore --capability <id>\\` seeds an exploration from the realizing\nservices.\n\n## The requirement baseline — \\`Based-On:\\`\n\nA MODIFIED requirement carries its FULL new text, not a diff, so archive does not\nmerge your wording into the living one — it REPLACES it. When two features rewrite\nthe same requirement, the loser loses everything they wrote, scenarios and all.\n\n\\`loam validate\\` names the other feature while both are in flight\n(\\`delta.modified-conflict\\`), but that warning cannot see the case that actually\nhappens: the first feature archives, stops being an active feature, the second\nrevalidates GREEN, and its archive lands text written against a document that no\nlonger exists — \\`+0 ~1 -0\\`, exit 0, nobody told.\n\nSo a MODIFIED or REMOVED requirement pins the living version it was written\nagainst, directly under its identity:\n\n\\`\\`\\`markdown\n### Requirement: Cancel an order\nRequirement-ID: REQ-042\nBased-On: 3bcc7a40f2b23ff3\n\\`\\`\\`\n\nYou do not compute that value — \\`loam rebase FEAT-101\\` writes it, for every\nMODIFIED/REMOVED requirement in the feature, from the living text as it stands\nright now. Run it when you finish authoring a delta, and again after you resolve\na collision. Archive refuses on a stale pin (\\`delta.baseline-stale\\`) and says\nboth digests.\n\n**Restamping is not resolving.** A pin claims \"I read this version\". If\n\\`loam rebase\\` reports a requirement as moved, re-read the living requirement and\nfold in what you still mean BEFORE you ship — otherwise you have re-pinned to a\ndocument you never read, and archive will merge your text over theirs with\nloam's blessing. There is no automatic three-way merge and there will not be one:\nrequirement prose does not merge, people do.\n\nA delta adopted from OpenSpec has no such line and gets a warning\n(\\`delta.baseline-missing\\`) — one that GATES the archive: unpinned is exactly\nthe shape that reverts someone else's landed change at exit 0. A migrated\ncorpus is not stranded by this: \\`loam rebase <FEAT>\\` pins every delta in one\ncommand, and a human's \\`--approve\\` archives unpinned when the loss is meant.\n\n### The same pin on the contract axis — and why it matters more there\n\nA feature's \\`openapi.yaml\\` is a COMPLETE document, not a patch: you restate the\nliving contract around the slot you are changing, and the merge upserts every\noperation the document spells. So the collision on this axis needs no overlap at\nall. Two features touching one service, editing DIFFERENT operations, destroy\neach other — the second to archive quietly pushes its authoring-time copy of the\noperation it never meant to touch back over whatever landed in between.\n\nThe same marker fixes it, as a vendor extension beside \\`x-loam-remove\\`:\n\n\\`\\`\\`yaml\npaths:\n /orders/{id}/cancel:\n post:\n operationId: cancelOrder\n x-loam-based-on: 86feabe621ed887d\n\\`\\`\\`\n\n\\`loam rebase\\` writes one on EVERY operation in the delta, pinned to the living\nversion. That is what lets the merge tell a QUOTE from an EDIT:\n\n- pin equals the operation's own content → you quoted it → **the merge skips it\n entirely** and the living contract keeps whatever it holds. Archive prints\n \\`· quotes …\\` so the plan never silently writes less than your delta spells.\n- pin equals the living operation → you edited it, nobody else did → merged.\n- pin matches neither → you edited it AND somebody landed a change to it →\n \\`openapi.baseline-stale\\`, refused.\n\nNot covered, deliberately, and worth knowing: path-level keys (\\`parameters\\`,\n\\`servers\\`) and \\`components/schemas\\` are still upserted wholesale, so a restated\nschema can still revert another feature's change to it —\n\\`openapi.path-item-modified\\` and \\`openapi.component-modified\\` are your only\nwarning there. Removal markers carry no pin either; \\`openapi.remove-target-mismatch\\`\nis what guards those.\n\n## Drawing a shared broker\n\nA broker drawn as ONE element is the node every service in the fleet points at,\nand any view over it is a star nobody can read. Nothing in loam catches this —\nloam parses the model and renders no view, so the map can be perfectly valid and\ncompletely illegible. Two habits keep it readable, and both are cheap only\nbefore the fleet is drawn:\n\n**Model the topic, not the broker.** Nest a topic per channel inside the broker's\nelement and point the edges at it — \\`paymentService -> kafka.paymentEvents\\`, not\n\\`paymentService -> kafka\\`. One hub of degree sixty becomes twelve of degree\nfive, and it is the truer model besides: what a producer and a consumer share is\nthe topic, never the broker. Every check joins the same way, because an edge into\na nested element resolves to the element that owns it. Declare the kind once and\ntag it there —\n\n specification {\n element topic {\n #external\n style { shape queue }\n }\n }\n\n— because **LikeC4 does not inherit tags**: a topic nested inside an \\`#external\\`\nbroker is not external itself, and \\`loam validate --all\\` will ask for a\n\\`services/payment.events/\\` nobody owes (\\`landscape.service-undocumented\\`).\n\n**Scope the views.** \\`exclude element.tag = #external\\` keeps the fleet overview to\nthe calls between our own services; a second view over the broker draws the event\nspine on its own. Views are LikeC4's and loam computes none, so this costs loam\nnothing and is worth doing anyway — an \\`include *\\` over the whole fleet map is\nalso the one view whose computation takes minutes rather than milliseconds. The\nscaffolded landscape applies the same pruning to ubiquitous infrastructure with\n\\`#platform\\`: the fleet view excludes it, and the platform view's\n\\`include * -> element.tag = #platform\\` (the relationship form — the obvious\nspelling draws boxes with no edges) keeps \"who depends on it\" answerable.\n\n## A message produced outside the fleet\n\nA platform service that is not ours — a config service, an auth broker —\nproduces messages our services consume, and no \\`services/<id>/\\` will ever hold\nits contract. Declare the production on the landscape, where the element\nalready says \\`#external\\`:\n\n externalConfig = softwareSystem 'external-config' {\n #external\n }\n externalConfig -> kafka.configTopic 'publishes config refreshes' {\n metadata { publishes 'config.ConfigRefreshed' }\n }\n\n\\`spine.message-unproduced\\` then stops firing — the message HAS a producer,\noutside the fleet — and the contract question moves to the one file that can\nanswer it: the consuming service's own asyncapi.yaml. While that file declares\nno shape for the message, \\`spine.message-external\\` (warn) says so; once the\nconsumer declares the payload, the axis is quiet. A truthful map and a green\nbuild no longer exclude each other, which is the exact choice this used to force.\n`,\n};\n"]}
1
+ {"version":3,"file":"spine.js","sourceRoot":"","sources":["../../../../../src/core/agent/workflows/reference/spine.ts"],"names":[],"mappings":"AAsBA,MAAM,CAAC,MAAM,UAAU,GAAmB;IACxC,IAAI,EAAE,YAAY;IAClB,WAAW,EAAE,mFAAmF;IAChG,0EAA0E;IAC1E,4EAA4E;IAC5E,yEAAyE;IACzE,YAAY,EAAE,EAAE;IAChB,OAAO,EACL,qNAAqN;IACvN,UAAU,EAAE,8BAA8B;IAC1C,YAAY,EAAE,EAAE;IAChB,KAAK,EAAE;QACL,cAAc;QACd,wCAAwC;QACxC,yBAAyB;QACzB,sCAAsC;KACvC;IACD,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+TP;CACA,CAAC","sourcesContent":["/**\n * The `loam-spine` reference page: how the artifacts JOIN.\n *\n * Operation and message ids, coverage and permission references, the\n * capability tree and its `Realizes:` join, the requirement baseline pins\n * (`Based-On:` / `x-loam-based-on`), how to draw a shared broker, and how to\n * declare a message produced outside the fleet.\n *\n * This is the grammar a reader consults while writing ONE document, which is\n * why it is a printed page rather than a section of AGENTS.md: it is consulted\n * at a moment you can name, and paying for it on every invocation of every\n * session bought nothing. Which element IS which service deliberately stayed\n * in AGENTS.md — today as ORIENTATION's `## The identity rule` in\n * ../../agents-md.ts — because an agent cannot read the landscape at all\n * without it, so it is orientation rather than reference.\n *\n * The text is the AGENTS.md section verbatim; nothing was rewritten to move\n * it, so a cross-reference from the remaining file still names the same\n * headings. See ./reference.ts for why these four pages exist.\n */\nimport type { CommandContent } from \"../../contract.js\";\n\nexport const LOAM_SPINE: CommandContent = {\n name: \"loam-spine\",\n description: \"Reference: the ID spine — every join between the artifacts, and the baseline pins\",\n // A reference page takes no arguments: it is one document, printed whole.\n // The empty hint is what `loam instructions` prints beside the name, and an\n // invented one (\"<none>\") would read as an argument somebody could pass.\n argumentHint: \"\",\n purpose:\n \"The exact joins the artifacts are held together by — operationId, message name, `Covers:`, `Requires:`, `Capability:`/`Realizes:` — and the `Based-On:` pins that stop two features in flight reverting each other.\",\n invocation: \"loam instructions loam-spine\",\n placeholders: [],\n spine: [\n \"The ID spine\",\n \"The requirement baseline — `Based-On:`\",\n \"Drawing a shared broker\",\n \"A message produced outside the fleet\",\n ],\n body: `The ID spine: how the artifacts join, and what pins a change to the version it was written against.\n\n## The ID spine\n\nThe spine is a family of exact joins, not one magic id: feature tag; service\nbinding; synchronous operation; asynchronous message; architecture coverage;\nauthorization permission. Three artifacts describe the same synchronous call in\nthree languages. They are joined by the **operationId**, and that join is what\n\\`loam validate\\` checks:\n\n- architecture — a C4 edge names the operation it calls:\n \\`checkoutWeb -> paymentService 'Calls authorizePayment' { metadata { op 'authorizePayment' } }\\`\n- behaviour — a requirement declares the operations it governs:\n \\`Operations: authorizePayment\\`\n- contract — the provider's \\`openapi.yaml\\` defines it:\n \\`operationId: authorizePayment\\`\n\nSpell it identically in all three places. A mismatch is an error, not a warning:\nan edge calling an operation nobody defines is a broken contract between services.\n\nFeatures are joined the same way: a delta's new elements and edges carry the\nfeature id as a LikeC4 tag (\\`#FEAT-101\\`), and that tag is exactly what\n\\`loam archive\\` folds into the living landscape. Untagged elements in a delta are\ncontext for the diagram, not changes.\n\nThe other joins use the same discipline:\n\n- \\`metadata { publishes|consumes 'message' }\\` on an edge ↔\n \\`Publishes:\\`/\\`Consumes:\\` in a requirement ↔ the AsyncAPI 3 message name;\n- \\`Covers:\\` in an architecture requirement ↔ a C4 element/edge or\n \\`alert:<id>\\` / \\`sli:<id>\\` from health.yaml;\n- \\`Requires: <subject>/<permission>\\` in either requirement file ↔ the fleet\n declaration in \\`architecture/permissions.yaml\\`;\n- \\`Capability:\\` in either requirement file ↔ a declared id in\n \\`architecture/capabilities.yaml\\`. A LIST, comma-separated — the relation is\n many-to-many in both directions.\n\nThe permissions file is opt-in: a fleet with no \\`Requires:\\` line owes none.\nOnce a requirement names a permission, an undeclared pair is\n\\`permissions.unknown\\` (error). An unreadable vocabulary is\n\\`permissions.invalid\\`; a declaration nothing requires is\n\\`permissions.unenforced\\` (warn). The subject is what the permission is checked\nON (user, profile, service), not necessarily the caller. \\`owned_by\\` and\n\\`enforced_by\\` are explanatory and are not resolved against services; this is\na checked document join, not proof that the identity provider implements it.\n\nThe capability axis opts in the OTHER way: the FILES are the opt-in, not the\nline. A fleet with neither \\`architecture/capabilities.yaml\\` nor a\n\\`capabilities/\\` directory gets no capability findings at all, however many\n\\`Capability:\\` lines its requirements already carry.\n\nA capability may be declared on either side, and the vocabulary is the UNION.\nA name alone is a line in \\`architecture/capabilities.yaml\\` —\n\\`capabilities: {<id>: {description, owner}}\\`, nested ids such as\n\\`payments/refunds\\` kept as one flat key. A name with prose behind it gets a\ndocument instead: \\`capabilities/<id>/spec.md\\`, the AUTHORED business tree,\none directory per capability with nesting spelled by the tree\n(\\`capabilities/payments/refunds/spec.md\\`). The directory is the list — there\nis no manifest — and its existence is what opts the fleet in, so \\`loam init\\`\ndoes not scaffold it. A directory holding neither the document nor a\ncapability beneath it is \\`capability.doc-missing\\` (warn).\n\nThe document carries narrative and then \\`## Requirements\\`, in the same\ngrammar every spec.md uses, with two rules of its own. Every requirement needs\na \\`Requirement-ID:\\` — these documents outlive the services that realize them,\nso identity is the line and not the heading; without one it is\n\\`capability.requirement-unidentified\\` (error). And a capability requirement\nmust be observable OUTSIDE the fleet: \\`Operations:\\`, \\`Covers:\\`,\n\\`Publishes:\\` and \\`Consumes:\\` all resolve against one service's own contract,\nso carrying any of them is \\`capability.requirement-service-scoped\\` (error) —\nwrite it in that service's spec.md instead. Naming no service is an authoring\nrule that PR review holds, not one loam checks: matching service names in prose\nwould be a heuristic, and loam does not guess. The axis's own two joins are\nrefused here as well (\\`capability.requirement-inert-join\\`, error): they point\nINTO the tree, so nothing reads them written inside it.\n\nA FEATURE changes a capability document with a delta, not by editing it:\n\\`features/<FEAT>/capabilities/<id>/spec.md\\`, the same\n\\`## ADDED|MODIFIED|REMOVED Requirements\\` grammar the service deltas use, the\nsame nesting (\\`features/<FEAT>/capabilities/payments/refunds/\\`), the same\n\\`Based-On:\\` pins, and the same \\`delta.*\\` refusals with the capability id as\nthe subject. \\`scaffold.placeholder\\` reads it too, so an unedited template\nheading refuses the archive here as it does in a spec delta, and\n\\`capability.doc-missing\\` (warn) names a directory in it that holds no\n\\`spec.md\\` — an empty one is not a delta, so the merge would carry nothing.\n\\`loam archive\\` merges it into the living document in the same\ntransaction as the service merge, creating \\`capabilities/<id>/spec.md\\` — and\nso the whole tree — when this is the first feature to mention it. Only what\nthe delta marks as the document's own comes across: its H1 as the title\n(unless it names the feature or calls itself a delta — then the title is the\nid), its \\`## Purpose\\` section as the narrative, and a line linking back to the\narchived delta for everything else, because loam merges requirements and never\nprose; after that the narrative is yours to edit in place. The document's three rules above are\ngraded on the delta BEFORE the merge, so a capability requirement filed at the\nwrong altitude is refused while you still hold it. Editing the living document\ndirectly is still legal; the delta is what makes two features touching one\npromise collide loudly instead of silently. \\`loam new <FEAT> --capability <id>\\`\nscaffolds that delta, and \\`loam show <FEAT>\\` and \\`loam delta <FEAT>\\` list the\npromises a feature changes — a capability delta names no service, so without\nthose two a business-only feature reads as a feature carrying nothing.\n\nA service requirement says which promise it serves with \\`Realizes:\\`, entries\nspelled \\`<capability-id>#<Requirement-ID>\\`. This is the join the tree exists\nfor, and it is NOT the same claim as \\`Capability:\\` beside it — that names a\ntheme, this names one promise, and a requirement commonly carries both. The\ncapability half of an entry is what makes the target addressable, since a\n\\`Requirement-ID\\` is unique only inside its own document; the separator is the\nLAST \\`#\\`, because the requirement half's grammar excludes one and the\ncapability half's does not. Written by whoever implements the requirement,\nnever by the analyst — a business document must not be edited every time the\nfleet rearranges which service carries which part.\n\nBoth directions are graded. An entry naming no capability requirement is\n\\`capability.realizes-unknown\\` (error; in a feature delta it gates\n\\`loam archive\\`, \\`--approve\\`-overridable), and its message says which of the\nfive failures happened — malformed entry, undeclared capability, declared but\nundocumented, document with no requirements, or an id that document does not\ndeclare. A capability requirement nothing keeps is\n\\`capability.requirement-unrealized\\` (warn, one per requirement): it never\ngates, because writing the business document ahead of the fleet is the intended\nuse. \\`loam list capabilities --json\\` carries the whole join — each capability's\nrequirements, and what realizes each one.\n\n\\`Realizes:\\` is not the only way to keep a promise, and for a criterion that\nCROSSES services it is not enough: \"I enter a login and a password and I am in\"\nbelongs to no single service's spec, because each promises only its own part. A\nUSE CASE can carry it, because it IS the hop sequence. So a \\`dynamic view\\`\nalready tagged \\`#cap-<slug>\\` may carry \\`#req-<slug>\\` as well, naming one of\nthat capability's requirements — the architect's answer to the analyst's\npromise, graded by the same four \\`usecase.*\\` codes down to the operation. The\nsecond tag is SCOPED by the first: a \\`Requirement-ID\\` is unique only inside its\nown document, so a \\`#req-\\` tag on a view that resolves no capability, or two, is\n\\`usecase.requirement-unresolved\\` (error) rather than a guess. Several \\`#req-\\`\ntags on one view are legal — a flow commonly keeps two promises. Both tags spell\ntheir id with every character outside \\`[A-Za-z0-9_-]\\` flattened to \\`-\\`,\nbecause that is all a LikeC4 tag name accepts.\n\nAgainst that vocabulary an undeclared name is \\`capability.unknown\\` (error; in\na feature delta it gates \\`loam archive\\`, \\`--approve\\`-overridable), an\nunreadable \\`capabilities.yaml\\` is \\`capability.invalid\\` exactly once per run\nwith every grade that resolves against the vocabulary suspended behind it, and\na declared capability no living requirement realizes is\n\\`capability.unrealized\\` (warn, one per capability). The fleet total is\nreadable: \\`loam list capabilities\\` reports each capability's realizing\nrequirements, their services and the draft/verified split, and\n\\`loam explore --capability <id>\\` seeds an exploration from the realizing\nservices.\n\n## The requirement baseline — \\`Based-On:\\`\n\nA MODIFIED requirement carries its FULL new text, not a diff, so archive does not\nmerge your wording into the living one — it REPLACES it. When two features rewrite\nthe same requirement, the loser loses everything they wrote, scenarios and all.\n\n\\`loam validate\\` names the other feature while both are in flight\n(\\`delta.modified-conflict\\`), but that warning cannot see the case that actually\nhappens: the first feature archives, stops being an active feature, the second\nrevalidates GREEN, and its archive lands text written against a document that no\nlonger exists — \\`+0 ~1 -0\\`, exit 0, nobody told.\n\nSo a MODIFIED or REMOVED requirement pins the living version it was written\nagainst, directly under its identity:\n\n\\`\\`\\`markdown\n### Requirement: Cancel an order\nRequirement-ID: REQ-042\nBased-On: 3bcc7a40f2b23ff3\n\\`\\`\\`\n\nYou do not compute that value — \\`loam rebase FEAT-101\\` writes it, for every\nMODIFIED/REMOVED requirement in the feature, from the living text as it stands\nright now. Run it when you finish authoring a delta, and again after you resolve\na collision. Archive refuses on a stale pin (\\`delta.baseline-stale\\`) and says\nboth digests.\n\n**Restamping is not resolving.** A pin claims \"I read this version\". If\n\\`loam rebase\\` reports a requirement as moved, re-read the living requirement and\nfold in what you still mean BEFORE you ship — otherwise you have re-pinned to a\ndocument you never read, and archive will merge your text over theirs with\nloam's blessing. There is no automatic three-way merge and there will not be one:\nrequirement prose does not merge, people do.\n\nA delta adopted from OpenSpec has no such line and gets a warning\n(\\`delta.baseline-missing\\`) — one that GATES the archive: unpinned is exactly\nthe shape that reverts someone else's landed change at exit 0. A migrated\ncorpus is not stranded by this: \\`loam rebase <FEAT>\\` pins every delta in one\ncommand, and a human's \\`--approve\\` archives unpinned when the loss is meant.\n\n### The same pin on the contract axis\n\nA feature's \\`openapi.yaml\\` is a self-contained document holding ONLY what the\nfeature adds, changes or removes — not a copy of the living contract. The merge\nwalks only the paths and methods the delta spells: an operation you leave out\nstays exactly as the living contract has it, a local \\`$ref\\`\n(\\`#/components/schemas/Order\\`) resolves into the living document, and a new\noperation has no living version to pin — an add-only delta needs no rebase.\n\nWhat the merge does upsert is every operation the document DOES spell. So an\noperation you quote without changing it — copied for context, or a whole\ncontract pasted in — is a collision waiting for someone else's edit: two\nfeatures over one service, each quoting the operation the other edits, destroy\neach other, and the second to archive pushes its authoring-time copy back over\nwhatever landed in between. Quote nothing you do not change. Each quote costs a\npin, and a parallel feature that REMOVES the quoted operation makes it worse\neither way: pinned, the quote is \\`openapi.baseline-invalid\\`, an error that\nblocks your archive, and \\`openapi.op-revived\\` beside it; unpinned, it is\n\\`openapi.op-revived\\` alone — a warning that gates, because the merge would\nread the dead operation as a new one and put it back (\\`asyncapi.slot-revived\\`\nis the same finding on a channel, operation or message slot). A parallel\nfeature that MOVES the operation to another address does the same to a quote\nof the old one, which archived would answer the operationId at both. The fix\nfor both is to delete the quote. Keep it only when the feature brings a\nretired operation back ON PURPOSE: then drop any pin, and a human archives\nwith \\`--approve\\`. \\`loam rebase\\` never pins a retired operation as new —\nnothing lives at its slot, so there is no version to pin — and prints the same\nfinding (\\`revived[]\\` in \\`--json\\`), so the decision stays with you. It judges\nthe delta as it will write it, pins included: a quote of an address another\noperation lives at now — one a move vacated, since re-occupied — IS repinned,\nover the operation living there, which makes it an edit the merge would write;\nthe revival that edit makes is reported by that rebase (or its \\`--dry-run\\`),\nnot first by the next validate. The check reads the removal\nmarkers of the features under \\`features/archive/\\` — a removal made by\nhand-editing a living contract is invisible to it.\n\nThe guard against that collision, for every living operation the delta does\nspell, is a pin — a vendor extension beside \\`x-loam-remove\\`:\n\n\\`\\`\\`yaml\npaths:\n /orders/{id}/cancel:\n post:\n operationId: cancelOrder\n x-loam-based-on: 86feabe621ed887d\n\\`\\`\\`\n\n\\`loam rebase\\` writes one on every operation in the delta the living contract\nalready holds, pinned to the living version; what the feature adds gets none.\nThat is what lets the merge tell a QUOTE from an EDIT:\n\n- pin equals the operation's own content → you quoted it → **the merge skips it\n entirely** and the living contract keeps whatever it holds. Archive prints\n \\`· quotes …\\` so the plan never silently writes less than your delta spells.\n- pin equals the living operation → you edited it, nobody else did → merged.\n- pin matches neither → you edited it AND somebody landed a change to it →\n \\`openapi.baseline-stale\\`, refused.\n\nThe two surfaces an in-operation key cannot reach — path-level keys\n(\\`parameters\\`, \\`servers\\`, \\`summary\\`, \\`x-*\\`) and every\n\\`components/<kind>/<name>\\` — are pinned the same way through one root record,\n\\`x-loam-baselines\\`, which \\`loam rebase\\` also writes and the merge strips: a\nquoted key or component is skipped, an edited one is written and warned as\n\\`openapi.path-item-modified\\` / \\`openapi.component-modified\\`. A new component\nis copied in when something the merge writes references it; a living one you\nonly \\`$ref\\` needs no copy in the delta at all. Removal markers carry no pin;\n\\`openapi.remove-target-mismatch\\` is what guards those.\n\n## Drawing a shared broker\n\nA broker drawn as ONE element is the node every service in the fleet points at,\nand any view over it is a star nobody can read. Nothing in loam catches this —\nloam parses the model and renders no view, so the map can be perfectly valid and\ncompletely illegible. Two habits keep it readable, and both are cheap only\nbefore the fleet is drawn:\n\n**Model the topic, not the broker.** Nest a topic per channel inside the broker's\nelement and point the edges at it — \\`paymentService -> kafka.paymentEvents\\`, not\n\\`paymentService -> kafka\\`. One hub of degree sixty becomes twelve of degree\nfive, and it is the truer model besides: what a producer and a consumer share is\nthe topic, never the broker. Every check joins the same way, because an edge into\na nested element resolves to the element that owns it. Declare the kind once —\n\n specification {\n element topic {\n style { shape queue }\n }\n }\n\nThe KIND is what does the work: since 2026-09-07 loam reads \\`database\\`, \\`queue\\`,\n\\`topic\\`, \\`cache\\` and \\`bucket\\` as infrastructure rather than as candidate\nservices, so a topic drawn this way is out of the service census and nobody is\nasked for a \\`services/payment.events/\\` (measured: the same element spelled\n\\`container\\` earns \\`landscape.service-undocumented\\`, spelled \\`topic\\` it earns\nnothing). This used to need \\`#external\\` ON THE KIND as a workaround, because\n**LikeC4 does not inherit tags** and a topic nested inside an \\`#external\\` broker\nis not external itself. That workaround is no longer owed. Tag the kind\n\\`#external\\` if you want the topics out of the fleet VIEW as well — that is what\nthe next paragraph's \\`exclude\\` reads, and it is a rendering choice now rather\nthan a way to silence a check.\n\n**Scope the views.** \\`exclude element.tag = #external\\` keeps the fleet overview to\nthe calls between our own services; a second view over the broker draws the event\nspine on its own. Views are LikeC4's and loam computes none, so this costs loam\nnothing and is worth doing anyway — an \\`include *\\` over the whole fleet map is\nalso the one view whose computation takes minutes rather than milliseconds. The\nscaffolded landscape applies the same pruning to ubiquitous infrastructure with\n\\`#platform\\`: the fleet view excludes it, and the platform view's\n\\`include * -> element.tag = #platform\\` (the relationship form — the obvious\nspelling draws boxes with no edges) keeps \"who depends on it\" answerable.\n\n## A message produced outside the fleet\n\nA platform service that is not ours — a config service, an auth broker —\nproduces messages our services consume, and no \\`services/<id>/\\` will ever hold\nits contract. Declare the production on the landscape, where the element\nalready says \\`#external\\`:\n\n externalConfig = softwareSystem 'external-config' {\n #external\n }\n externalConfig -> kafka.configTopic 'publishes config refreshes' {\n metadata { publishes 'config.ConfigRefreshed' }\n }\n\n\\`spine.message-unproduced\\` then stops firing — the message HAS a producer,\noutside the fleet — and the contract question moves to the one file that can\nanswer it: the consuming service's own asyncapi.yaml. While that file declares\nno shape for the message, \\`spine.message-external\\` (warn) says so; once the\nconsumer declares the payload, the axis is quiet. A truthful map and a green\nbuild no longer exclude each other, which is the exact choice this used to force.\n`,\n};\n"]}
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * What `loam rebase` writes on the event axis's SURFACE half: the
3
3
  * `x-loam-baselines` record, rebuilt wholesale from what is on disk right now
4
- * — `core/openapi/baseline/plan.ts`'s mirror, over component surfaces instead
5
- * of path items and components.
4
+ * — `core/openapi/baseline/plan.ts`'s planner itself, over component surfaces
5
+ * instead of path items and components.
6
6
  *
7
7
  * A ROOT record rather than an in-value pin, and that is not symmetry for its
8
8
  * own sake: a slot value is always a mapping, so `x-loam-based-on` has
@@ -17,94 +17,27 @@
17
17
  * a claim nobody is making any more. The sorted serialization (`buildRecord`)
18
18
  * is what makes a second run byte-identical.
19
19
  *
20
+ * The planner is SHARED, not mirrored: it used to be a line-for-line copy of
21
+ * the OpenAPI one minus the path-item arm, and the only real difference was
22
+ * which walk enumerates the surfaces. `asyncapiSurfaces` is that walk, so this
23
+ * module passes it and owns nothing else.
24
+ *
20
25
  * This module must not import from `../merge/`: the merge reads this package,
21
26
  * so a value edge back would be a package cycle. That is also why an
22
27
  * unreadable document is answered with "nothing to pin" rather than an
23
28
  * `AsyncapiMergeError` — every caller chains this AFTER `pinAsyncapiSlots`,
24
29
  * which has already thrown that error for a document either side cannot parse.
25
30
  */
26
- import { isDeepStrictEqual } from "node:util";
27
- import { parseDocument } from "yaml";
28
- import { isRecord } from "../../kernel/records.js";
29
- import { buildRecord, entryFor, readBaselineRecord, surfaceIn } from "../../openapi/baseline/record.js";
30
- import { valueDigest } from "../../openapi/digest.js";
31
- import { ASYNCAPI_BASELINES_KEY } from "../digest.js";
31
+ import { planOpenapiBaselines } from "../../openapi/baseline/plan.js";
32
32
  import { asyncapiSurfaces } from "./surfaces.js";
33
33
  /**
34
34
  * Pin every component surface of a feature event contract against the living
35
- * one. The entry is always `valueDigest` of the LIVING value, never of the
36
- * delta's own — the one rule that yields both merge verdicts: a surface the
37
- * author only QUOTED equals its own entry, one they EDITED differs from it.
38
- *
39
- * A surface with no living counterpart is `unresolved` and deliberately absent
40
- * from the record. Stamping a pin against nothing is not a harmless extra key:
41
- * `classifyBaselineDigests` calls a pin with no living side `unfounded`, and
42
- * the gate then refuses the delta as `asyncapi.baseline-invalid` — rebase
43
- * would be writing the file the gate sends its author back to rebase.
35
+ * one — every `kind` in the answer is `"component"`, since an event contract
36
+ * has no path items. The rules (the LIVING digest, `unresolved` for a surface
37
+ * the living contract lacks, the idempotent rewrite) are the shared planner's,
38
+ * stated there once.
44
39
  */
45
- export function planAsyncapiBaselines(featureText, livingText, _service) {
46
- const doc = parseDocument(featureText);
47
- if (doc.errors.length > 0)
48
- return { text: null, pins: [] };
49
- let featPlain;
50
- let livingPlain;
51
- try {
52
- featPlain = doc.toJS() ?? {};
53
- const living = parseDocument(livingText);
54
- if (living.errors.length > 0)
55
- return { text: null, pins: [] };
56
- livingPlain = living.toJS() ?? {};
57
- }
58
- catch {
59
- return { text: null, pins: [] };
60
- }
61
- const current = readBaselineRecord(featPlain).record;
62
- const components = new Map();
63
- const pins = [];
64
- for (const surface of asyncapiSurfaces(featPlain)) {
65
- // A malformed current entry reads back as absent (readBaselineRecord drops
66
- // it into `problems`), so this run reports `pinned` and overwrites it —
67
- // rebase is the command `asyncapi.baseline-invalid` sends people to, and
68
- // it must repair what it names.
69
- const from = entryFor(current, surface);
70
- const livingValue = surfaceIn(livingPlain, surface);
71
- if (!livingValue.found) {
72
- pins.push({ target: surface.id, status: "unresolved", from: from ?? null, to: null });
73
- continue;
74
- }
75
- const digest = valueDigest(livingValue.value);
76
- components.set(surface.id, digest);
77
- pins.push({
78
- target: surface.id,
79
- status: from === digest ? "unchanged" : from === undefined ? "pinned" : "repinned",
80
- from: from ?? null,
81
- to: digest,
82
- });
83
- }
84
- // The whole key goes when no section survives — a bare `x-loam-baselines:
85
- // {}` would be bookkeeping about nothing, in every delta whose surfaces are
86
- // all new. `buildRecord` is the OpenAPI axis's sorted writer, given an empty
87
- // `pathItems`: an AsyncAPI document has no path items, and the section it
88
- // would write is dropped below with every other empty one.
89
- const rebuilt = buildRecord({ pathItems: new Map(), components });
90
- const written = {};
91
- if (Object.keys(rebuilt.components).length > 0)
92
- written["components"] = rebuilt.components;
93
- const raw = isRecord(featPlain) ? featPlain[ASYNCAPI_BASELINES_KEY] : undefined;
94
- if (Object.keys(written).length === 0) {
95
- if (raw === undefined)
96
- return { text: null, pins };
97
- doc.deleteIn([ASYNCAPI_BASELINES_KEY]);
98
- return { text: doc.toString(), pins };
99
- }
100
- // Deep-equal, not byte-equal: the comparison must ignore key order exactly
101
- // as the digests do, so a record an author reordered by hand is left in
102
- // their order — and a second run over this function's own sorted output
103
- // finds the parse equal and writes nothing, which is the byte-for-byte
104
- // idempotence `loam rebase` promises.
105
- if (isDeepStrictEqual(raw, written))
106
- return { text: null, pins };
107
- doc.setIn([ASYNCAPI_BASELINES_KEY], written);
108
- return { text: doc.toString(), pins };
40
+ export function planAsyncapiBaselines(featureText, livingText) {
41
+ return planOpenapiBaselines(featureText, livingText, asyncapiSurfaces);
109
42
  }
110
43
  //# sourceMappingURL=plan.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../../src/core/asyncapi/baseline/plan.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What `loam rebase` writes on the event axis's SURFACE half: the\n * `x-loam-baselines` record, rebuilt wholesale from what is on disk right now\n * — `core/openapi/baseline/plan.ts`'s mirror, over component surfaces instead\n * of path items and components.\n *\n * A ROOT record rather than an in-value pin, and that is not symmetry for its\n * own sake: a slot value is always a mapping, so `x-loam-based-on` has\n * somewhere to live inside it, but a `components/schemas/<name>` value is a\n * JSON Schema — where an in-value loam key would be a schema keyword, and\n * where JSON Schema's own `true` is a legal component that no in-value key\n * survives at all.\n *\n * Rebuilt rather than patched for the reason the OpenAPI plan states: a pin is\n * a claim about what its author read, and an entry left behind for a surface\n * the delta no longer declares — or one whose living counterpart is gone — is\n * a claim nobody is making any more. The sorted serialization (`buildRecord`)\n * is what makes a second run byte-identical.\n *\n * This module must not import from `../merge/`: the merge reads this package,\n * so a value edge back would be a package cycle. That is also why an\n * unreadable document is answered with \"nothing to pin\" rather than an\n * `AsyncapiMergeError` — every caller chains this AFTER `pinAsyncapiSlots`,\n * which has already thrown that error for a document either side cannot parse.\n */\nimport { isDeepStrictEqual } from \"node:util\";\nimport { parseDocument } from \"yaml\";\nimport { isRecord } from \"../../kernel/records.js\";\nimport { buildRecord, entryFor, readBaselineRecord, surfaceIn } from \"../../openapi/baseline/record.js\";\nimport { valueDigest } from \"../../openapi/digest.js\";\nimport { ASYNCAPI_BASELINES_KEY } from \"../digest.js\";\nimport { asyncapiSurfaces } from \"./surfaces.js\";\n\n/** What happened to one declared surface's baseline entry. */\nexport interface AsyncapiSurfacePin {\n /** `<kind>/<name>` — the component surface the entry pins. */\n target: string;\n status:\n /** It had no entry and now has one. */\n | \"pinned\"\n /** It named an older living version; it now names the current one. */\n | \"repinned\"\n /** It already named the current living version — no write. */\n | \"unchanged\"\n /** The living contract has no such surface: this feature is adding it, and it gets NO entry. */\n | \"unresolved\";\n from: string | null;\n to: string | null;\n}\n\nexport interface AsyncapiBaselinePlan {\n /** The rewritten contract, or null when the record already says what this run would write. */\n text: string | null;\n pins: AsyncapiSurfacePin[];\n}\n\n/**\n * Pin every component surface of a feature event contract against the living\n * one. The entry is always `valueDigest` of the LIVING value, never of the\n * delta's own — the one rule that yields both merge verdicts: a surface the\n * author only QUOTED equals its own entry, one they EDITED differs from it.\n *\n * A surface with no living counterpart is `unresolved` and deliberately absent\n * from the record. Stamping a pin against nothing is not a harmless extra key:\n * `classifyBaselineDigests` calls a pin with no living side `unfounded`, and\n * the gate then refuses the delta as `asyncapi.baseline-invalid` — rebase\n * would be writing the file the gate sends its author back to rebase.\n */\nexport function planAsyncapiBaselines(\n featureText: string,\n livingText: string,\n _service: string,\n): AsyncapiBaselinePlan {\n const doc = parseDocument(featureText);\n if (doc.errors.length > 0) return { text: null, pins: [] };\n let featPlain: unknown;\n let livingPlain: unknown;\n try {\n featPlain = doc.toJS() ?? {};\n const living = parseDocument(livingText);\n if (living.errors.length > 0) return { text: null, pins: [] };\n livingPlain = living.toJS() ?? {};\n } catch {\n return { text: null, pins: [] };\n }\n\n const current = readBaselineRecord(featPlain).record;\n const components = new Map<string, string>();\n const pins: AsyncapiSurfacePin[] = [];\n for (const surface of asyncapiSurfaces(featPlain)) {\n // A malformed current entry reads back as absent (readBaselineRecord drops\n // it into `problems`), so this run reports `pinned` and overwrites it —\n // rebase is the command `asyncapi.baseline-invalid` sends people to, and\n // it must repair what it names.\n const from = entryFor(current, surface);\n const livingValue = surfaceIn(livingPlain, surface);\n if (!livingValue.found) {\n pins.push({ target: surface.id, status: \"unresolved\", from: from ?? null, to: null });\n continue;\n }\n const digest = valueDigest(livingValue.value);\n components.set(surface.id, digest);\n pins.push({\n target: surface.id,\n status: from === digest ? \"unchanged\" : from === undefined ? \"pinned\" : \"repinned\",\n from: from ?? null,\n to: digest,\n });\n }\n\n // The whole key goes when no section survives — a bare `x-loam-baselines:\n // {}` would be bookkeeping about nothing, in every delta whose surfaces are\n // all new. `buildRecord` is the OpenAPI axis's sorted writer, given an empty\n // `pathItems`: an AsyncAPI document has no path items, and the section it\n // would write is dropped below with every other empty one.\n const rebuilt = buildRecord({ pathItems: new Map(), components });\n const written: Record<string, unknown> = {};\n if (Object.keys(rebuilt.components).length > 0) written[\"components\"] = rebuilt.components;\n const raw = isRecord(featPlain) ? featPlain[ASYNCAPI_BASELINES_KEY] : undefined;\n if (Object.keys(written).length === 0) {\n if (raw === undefined) return { text: null, pins };\n doc.deleteIn([ASYNCAPI_BASELINES_KEY]);\n return { text: doc.toString(), pins };\n }\n // Deep-equal, not byte-equal: the comparison must ignore key order exactly\n // as the digests do, so a record an author reordered by hand is left in\n // their order — and a second run over this function's own sorted output\n // finds the parse equal and writes nothing, which is the byte-for-byte\n // idempotence `loam rebase` promises.\n if (isDeepStrictEqual(raw, written)) return { text: null, pins };\n doc.setIn([ASYNCAPI_BASELINES_KEY], written);\n return { text: doc.toString(), pins };\n}\n"]}
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../../src/core/asyncapi/baseline/plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,EAAE,oBAAoB,EAA4B,MAAM,gCAAgC,CAAC;AAChG,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,WAAmB,EAAE,UAAkB;IAC3E,OAAO,oBAAoB,CAAC,WAAW,EAAE,UAAU,EAAE,gBAAgB,CAAC,CAAC;AACzE,CAAC","sourcesContent":["/**\n * What `loam rebase` writes on the event axis's SURFACE half: the\n * `x-loam-baselines` record, rebuilt wholesale from what is on disk right now\n * — `core/openapi/baseline/plan.ts`'s planner itself, over component surfaces\n * instead of path items and components.\n *\n * A ROOT record rather than an in-value pin, and that is not symmetry for its\n * own sake: a slot value is always a mapping, so `x-loam-based-on` has\n * somewhere to live inside it, but a `components/schemas/<name>` value is a\n * JSON Schema — where an in-value loam key would be a schema keyword, and\n * where JSON Schema's own `true` is a legal component that no in-value key\n * survives at all.\n *\n * Rebuilt rather than patched for the reason the OpenAPI plan states: a pin is\n * a claim about what its author read, and an entry left behind for a surface\n * the delta no longer declares — or one whose living counterpart is gone — is\n * a claim nobody is making any more. The sorted serialization (`buildRecord`)\n * is what makes a second run byte-identical.\n *\n * The planner is SHARED, not mirrored: it used to be a line-for-line copy of\n * the OpenAPI one minus the path-item arm, and the only real difference was\n * which walk enumerates the surfaces. `asyncapiSurfaces` is that walk, so this\n * module passes it and owns nothing else.\n *\n * This module must not import from `../merge/`: the merge reads this package,\n * so a value edge back would be a package cycle. That is also why an\n * unreadable document is answered with \"nothing to pin\" rather than an\n * `AsyncapiMergeError` — every caller chains this AFTER `pinAsyncapiSlots`,\n * which has already thrown that error for a document either side cannot parse.\n */\nimport { planOpenapiBaselines, type OpenapiBaselinePlan } from \"../../openapi/baseline/plan.js\";\nimport { asyncapiSurfaces } from \"./surfaces.js\";\n\n/**\n * Pin every component surface of a feature event contract against the living\n * one — every `kind` in the answer is `\"component\"`, since an event contract\n * has no path items. The rules (the LIVING digest, `unresolved` for a surface\n * the living contract lacks, the idempotent rewrite) are the shared planner's,\n * stated there once.\n */\nexport function planAsyncapiBaselines(featureText: string, livingText: string): OpenapiBaselinePlan {\n return planOpenapiBaselines(featureText, livingText, asyncapiSurfaces);\n}\n"]}
@@ -28,14 +28,14 @@
28
28
  * is content identity (a changed payload moves it), never a join on payload
29
29
  * fields — read.ts's header carries the full doctrine.
30
30
  */
31
- import { isRemoval, OPENAPI_BASELINE_KEY, OPENAPI_BASELINES_KEY, OPENAPI_REMOVE_KEY, operationBaselineOf, operationDigest, withoutOperationBaseline, } from "../openapi/digest.js";
31
+ import { isRemoval, OPENAPI_BASELINE_KEY, OPENAPI_BASELINES_KEY, operationBaselineOf, operationDigest, } from "../openapi/digest.js";
32
32
  /**
33
33
  * The baseline marker: which living version of a slot a FEATURE delta was
34
34
  * written against. The OpenAPI axis's key, not a restatement of its
35
- * spelling, because a feature's asyncapi.yaml is a complete document for
36
- * the same reason its openapi.yaml is: authors restate the living contract
37
- * around the slot they change, and the pin is what lets the merge tell a
38
- * QUOTE from an EDIT.
35
+ * spelling, because a feature's asyncapi.yaml may restate living slots it
36
+ * does not change for the same reason its openapi.yaml may — it needs only
37
+ * the slots it adds, changes or removes, but quoting more is legal — and the
38
+ * pin is what lets the merge tell a QUOTE from an EDIT.
39
39
  */
40
40
  export const ASYNCAPI_BASELINE_KEY = OPENAPI_BASELINE_KEY;
41
41
  /**
@@ -52,39 +52,44 @@ export const ASYNCAPI_BASELINE_KEY = OPENAPI_BASELINE_KEY;
52
52
  * own `true` is a legal component that no in-value key survives at all.
53
53
  */
54
54
  export const ASYNCAPI_BASELINES_KEY = OPENAPI_BASELINES_KEY;
55
- /** The feature-only explicit removal marker's key — the OpenAPI axis's, aliased. */
56
- export const ASYNCAPI_REMOVE_KEY = OPENAPI_REMOVE_KEY;
55
+ /**
56
+ * A slot's identity as one map key — the (section, key) pair, NUL-joined: no
57
+ * section name or YAML key a slot walk returns can contain it. Spelled once,
58
+ * because the gate's walk, the rebase revival read, the marker and baseline
59
+ * graders, the pin planner and the merge all build or look up the same map,
60
+ * and a lookup spelled differently from its build finds nothing without
61
+ * failing.
62
+ */
63
+ export function slotKey(slot) {
64
+ return `${slot.section}\0${slot.key}`;
65
+ }
66
+ /** Slots by `slotKey` — the living side of every slot judgement. */
67
+ export function slotsByKey(slots) {
68
+ return new Map(slots.map((slot) => [slotKey(slot), slot]));
69
+ }
57
70
  /** Is this slot value a feature-only explicit removal marker? One test for both axes. */
58
71
  export const isSlotRemoval = isRemoval;
59
72
  /** The `x-loam-based-on` a slot declares, or undefined — `operationBaselineOf`, aliased. */
60
73
  export const slotBaselineOf = operationBaselineOf;
61
- /**
62
- * The slot without its own baseline marker — what a digest is taken over. A
63
- * pin is a statement ABOUT a delta, never part of the slot it describes:
64
- * inside the digest input, no baseline could ever be self-consistent. The
65
- * OpenAPI rule, aliased — the key is the same key.
66
- */
67
- export const withoutSlotBaseline = withoutOperationBaseline;
68
74
  /**
69
75
  * The identity of a slot's CONTENT: sha256 of its canonical form, pin
70
76
  * excluded. `operationDigest` under this axis's name — the same canonical
71
- * JSON, the same cut, so a pin either axis writes means the same thing.
77
+ * JSON, the same cut, so a pin either axis writes means the same thing. The
78
+ * pin is cut because it is a statement ABOUT a delta, never part of the slot
79
+ * it describes: inside the digest input, no baseline could ever be
80
+ * self-consistent (`withoutOperationBaseline` in ../openapi/digest.ts has the
81
+ * whole argument).
72
82
  */
73
83
  export const slotDigest = operationDigest;
74
- /** Entries of a mapping node, or nothing — the walker's one structural read. */
75
- function entriesOf(node) {
84
+ /**
85
+ * Entries of a mapping node, or nothing — the walker's one structural read,
86
+ * and every structural read the reader (./read.ts) makes goes through it too.
87
+ */
88
+ export function entriesOf(node) {
76
89
  if (node === null || typeof node !== "object" || Array.isArray(node))
77
90
  return [];
78
91
  return Object.entries(node);
79
92
  }
80
- /**
81
- * The document path of one slot — where the pin and the merge write. Spelled
82
- * here so `rebase`'s stamp and the merge's upsert cannot disagree about which
83
- * node a (section, key) pair addresses.
84
- */
85
- export function slotPath(section, key) {
86
- return section === "components.messages" ? ["components", "messages", key] : [section, key];
87
- }
88
93
  /**
89
94
  * Every path whose node carries `x-loam-remove: true`, at ANY depth — the
90
95
  * three slot depths the format spec gives the key meaning at, inline channel
@@ -1 +1 @@
1
- {"version":3,"file":"digest.js","sourceRoot":"","sources":["../../../src/core/asyncapi/digest.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Slot identity for the event axis: what a feature-local asyncapi.yaml's\n * slots hash to, and the markers `loam rebase` and the merge read from them.\n *\n * The digest RULE is imported from the OpenAPI axis (../openapi/digest.js)\n * rather than respelled: the marker keys, the removal test, the baseline\n * accessors and the digest itself are ALIASES of that module's — one\n * definition each, renamed to this axis's vocabulary. Two spellings of the\n * one rule is the exact drift that module's own header names as the failure\n * mode, and it is not hypothetical here: the deep strip filters on the\n * OpenAPI key constants, so a second spelling of either key would be two\n * definitions of \"which keys are feature-only\" — the marker leak the strip\n * exists to close. The import direction is asyncapi → openapi only,\n * verified cycle-free — no openapi module imports asyncapi\n * (openapi/depth.ts names it in a comment alone).\n *\n * Identity is the SLOT: the pair (section, key) over the three named\n * sections `channels.<key>`, `operations.<key>` and\n * `components.messages.<key>` — SCHEMA.md's format spec for this axis. An\n * inline channel message (`channels.<ck>.messages.<mk>`) is deliberately\n * NOT a slot: its identity is interior content of its channel's slot, so\n * the walker below never visits it, it cannot carry its own pin, and a\n * removal marker nested on it grades as part of the channel's content —\n * a feature declares or retires an INDIVIDUAL message under\n * `components.messages`, with the channel aliasing it by `$ref`.\n *\n * Nothing here reads inside `payload` as anything but bytes: a slot digest\n * is content identity (a changed payload moves it), never a join on payload\n * fields — read.ts's header carries the full doctrine.\n */\nimport {\n isRemoval,\n OPENAPI_BASELINE_KEY,\n OPENAPI_BASELINES_KEY,\n OPENAPI_REMOVE_KEY,\n operationBaselineOf,\n operationDigest,\n withoutOperationBaseline,\n} from \"../openapi/digest.js\";\n\n/**\n * The baseline marker: which living version of a slot a FEATURE delta was\n * written against. The OpenAPI axis's key, not a restatement of its\n * spelling, because a feature's asyncapi.yaml is a complete document for\n * the same reason its openapi.yaml is: authors restate the living contract\n * around the slot they change, and the pin is what lets the merge tell a\n * QUOTE from an EDIT.\n */\nexport const ASYNCAPI_BASELINE_KEY = OPENAPI_BASELINE_KEY;\n\n/**\n * The feature-only ROOT record that pins what the SLOT pin cannot reach: every\n * `components/<kind>/<name>` outside `messages`. The OpenAPI axis's key and\n * the OpenAPI axis's reader (core/openapi/baseline/record.ts) — one record\n * shape for both axes, because a second spelling of \"which living version was\n * this written against\" is the drift this module's header names.\n *\n * A root record rather than an in-value pin here for the reason it is one\n * there: a slot value is always a mapping, so `x-loam-based-on` has somewhere\n * to live inside it, but a `components/schemas/<name>` value is a JSON Schema\n * — an in-value loam key there would be a schema keyword, and JSON Schema's\n * own `true` is a legal component that no in-value key survives at all.\n */\nexport const ASYNCAPI_BASELINES_KEY = OPENAPI_BASELINES_KEY;\n\n/** The feature-only explicit removal marker's key — the OpenAPI axis's, aliased. */\nexport const ASYNCAPI_REMOVE_KEY = OPENAPI_REMOVE_KEY;\n\n/** The three sections whose entries are slots. */\nexport type AsyncapiSection = \"channels\" | \"operations\" | \"components.messages\";\n\n/** One slot of an AsyncAPI document, as the identity layer sees it. */\nexport interface AsyncapiSlot {\n section: AsyncapiSection;\n /** The entry's key under its section — the slot half of the identity pair. */\n key: string;\n /** The slot's resolved value, pin and marker still in place. */\n node: unknown;\n /** True when the value carries `x-loam-remove: true` — a feature retiring the slot. */\n remove: boolean;\n /** The `x-loam-based-on` the value declares, or undefined — non-strings stringified, so the gate can refuse them. */\n basedOn?: string;\n /** `slotDigest` of the value as it stands in ITS OWN document. */\n digest: string;\n}\n\n/** Is this slot value a feature-only explicit removal marker? One test for both axes. */\nexport const isSlotRemoval = isRemoval;\n\n/** The `x-loam-based-on` a slot declares, or undefined — `operationBaselineOf`, aliased. */\nexport const slotBaselineOf = operationBaselineOf;\n\n/**\n * The slot without its own baseline marker — what a digest is taken over. A\n * pin is a statement ABOUT a delta, never part of the slot it describes:\n * inside the digest input, no baseline could ever be self-consistent. The\n * OpenAPI rule, aliased — the key is the same key.\n */\nexport const withoutSlotBaseline = withoutOperationBaseline;\n\n/**\n * The identity of a slot's CONTENT: sha256 of its canonical form, pin\n * excluded. `operationDigest` under this axis's name — the same canonical\n * JSON, the same cut, so a pin either axis writes means the same thing.\n */\nexport const slotDigest = operationDigest;\n\n/** Entries of a mapping node, or nothing — the walker's one structural read. */\nfunction entriesOf(node: unknown): [string, unknown][] {\n if (node === null || typeof node !== \"object\" || Array.isArray(node)) return [];\n return Object.entries(node as Record<string, unknown>);\n}\n\n/**\n * The document path of one slot — where the pin and the merge write. Spelled\n * here so `rebase`'s stamp and the merge's upsert cannot disagree about which\n * node a (section, key) pair addresses.\n */\nexport function slotPath(section: AsyncapiSection, key: string): string[] {\n return section === \"components.messages\" ? [\"components\", \"messages\", key] : [section, key];\n}\n\n/**\n * Every path whose node carries `x-loam-remove: true`, at ANY depth — the\n * three slot depths the format spec gives the key meaning at, inline channel\n * messages, and every place the key means nothing at all: the document root,\n * `info`, a `components` sibling, the inside of a payload. The reader's\n * `markers` field is built from this walk so `asyncapi.remove-marker-living`\n * can NAME a marker wherever one leaked: the strip removes the loam keys at\n * document depth, and a sweep narrower than the strip is how the OpenAPI\n * axis once published a marker invisible even to validate.\n */\nexport function removalMarkerPaths(node: unknown, at: string): string[] {\n if (node === null || typeof node !== \"object\") return [];\n const out: string[] = [];\n if (isSlotRemoval(node)) out.push(at === \"\" ? \"(document root)\" : at);\n const children = Array.isArray(node)\n ? node.map((value, i) => [String(i), value] as const)\n : Object.entries(node as Record<string, unknown>);\n for (const [key, value] of children) {\n out.push(...removalMarkerPaths(value, at === \"\" ? key : `${at}.${key}`));\n }\n return out;\n}\n\n/**\n * Every slot a parsed document declares, in document order across the three\n * sections. Inline channel messages are NOT visited: they are interior\n * content of their channel's slot (the header carries the decision), so a\n * pin or marker nested on one is part of the `channels.<ck>` value this\n * walker already returns — never a slot of its own.\n */\nexport function asyncapiSlots(root: unknown): AsyncapiSlot[] {\n if (root === null || typeof root !== \"object\" || Array.isArray(root)) return [];\n const doc = root as Record<string, unknown>;\n const slots: AsyncapiSlot[] = [];\n const walk = (section: AsyncapiSection, mapping: unknown): void => {\n for (const [key, node] of entriesOf(mapping)) {\n slots.push({\n section,\n key,\n node,\n remove: isSlotRemoval(node),\n digest: slotDigest(node),\n ...(slotBaselineOf(node) === undefined ? {} : { basedOn: slotBaselineOf(node) }),\n });\n }\n };\n walk(\"channels\", doc[\"channels\"]);\n walk(\"operations\", doc[\"operations\"]);\n walk(\"components.messages\", (doc[\"components\"] as Record<string, unknown> | undefined)?.[\"messages\"]);\n return slots;\n}\n"]}
1
+ {"version":3,"file":"digest.js","sourceRoot":"","sources":["../../../src/core/asyncapi/digest.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Slot identity for the event axis: what a feature-local asyncapi.yaml's\n * slots hash to, and the markers `loam rebase` and the merge read from them.\n *\n * The digest RULE is imported from the OpenAPI axis (../openapi/digest.js)\n * rather than respelled: the marker keys, the removal test, the baseline\n * accessors and the digest itself are ALIASES of that module's — one\n * definition each, renamed to this axis's vocabulary. Two spellings of the\n * one rule is the exact drift that module's own header names as the failure\n * mode, and it is not hypothetical here: the deep strip filters on the\n * OpenAPI key constants, so a second spelling of either key would be two\n * definitions of \"which keys are feature-only\" — the marker leak the strip\n * exists to close. The import direction is asyncapi → openapi only,\n * verified cycle-free — no openapi module imports asyncapi\n * (openapi/depth.ts names it in a comment alone).\n *\n * Identity is the SLOT: the pair (section, key) over the three named\n * sections `channels.<key>`, `operations.<key>` and\n * `components.messages.<key>` — SCHEMA.md's format spec for this axis. An\n * inline channel message (`channels.<ck>.messages.<mk>`) is deliberately\n * NOT a slot: its identity is interior content of its channel's slot, so\n * the walker below never visits it, it cannot carry its own pin, and a\n * removal marker nested on it grades as part of the channel's content —\n * a feature declares or retires an INDIVIDUAL message under\n * `components.messages`, with the channel aliasing it by `$ref`.\n *\n * Nothing here reads inside `payload` as anything but bytes: a slot digest\n * is content identity (a changed payload moves it), never a join on payload\n * fields — read.ts's header carries the full doctrine.\n */\nimport {\n isRemoval,\n OPENAPI_BASELINE_KEY,\n OPENAPI_BASELINES_KEY,\n operationBaselineOf,\n operationDigest,\n} from \"../openapi/digest.js\";\n\n/**\n * The baseline marker: which living version of a slot a FEATURE delta was\n * written against. The OpenAPI axis's key, not a restatement of its\n * spelling, because a feature's asyncapi.yaml may restate living slots it\n * does not change for the same reason its openapi.yaml may — it needs only\n * the slots it adds, changes or removes, but quoting more is legal — and the\n * pin is what lets the merge tell a QUOTE from an EDIT.\n */\nexport const ASYNCAPI_BASELINE_KEY = OPENAPI_BASELINE_KEY;\n\n/**\n * The feature-only ROOT record that pins what the SLOT pin cannot reach: every\n * `components/<kind>/<name>` outside `messages`. The OpenAPI axis's key and\n * the OpenAPI axis's reader (core/openapi/baseline/record.ts) — one record\n * shape for both axes, because a second spelling of \"which living version was\n * this written against\" is the drift this module's header names.\n *\n * A root record rather than an in-value pin here for the reason it is one\n * there: a slot value is always a mapping, so `x-loam-based-on` has somewhere\n * to live inside it, but a `components/schemas/<name>` value is a JSON Schema\n * — an in-value loam key there would be a schema keyword, and JSON Schema's\n * own `true` is a legal component that no in-value key survives at all.\n */\nexport const ASYNCAPI_BASELINES_KEY = OPENAPI_BASELINES_KEY;\n\n/** The three sections whose entries are slots. */\nexport type AsyncapiSection = \"channels\" | \"operations\" | \"components.messages\";\n\n/** One slot of an AsyncAPI document, as the identity layer sees it. */\nexport interface AsyncapiSlot {\n section: AsyncapiSection;\n /** The entry's key under its section — the slot half of the identity pair. */\n key: string;\n /** The slot's resolved value, pin and marker still in place. */\n node: unknown;\n /** True when the value carries `x-loam-remove: true` — a feature retiring the slot. */\n remove: boolean;\n /** The `x-loam-based-on` the value declares, or undefined — non-strings stringified, so the gate can refuse them. */\n basedOn?: string;\n /** `slotDigest` of the value as it stands in ITS OWN document. */\n digest: string;\n}\n\n/**\n * A slot's identity as one map key — the (section, key) pair, NUL-joined: no\n * section name or YAML key a slot walk returns can contain it. Spelled once,\n * because the gate's walk, the rebase revival read, the marker and baseline\n * graders, the pin planner and the merge all build or look up the same map,\n * and a lookup spelled differently from its build finds nothing without\n * failing.\n */\nexport function slotKey(slot: { section: AsyncapiSection; key: string }): string {\n return `${slot.section}\\0${slot.key}`;\n}\n\n/** Slots by `slotKey` — the living side of every slot judgement. */\nexport function slotsByKey(slots: readonly AsyncapiSlot[]): Map<string, AsyncapiSlot> {\n return new Map(slots.map((slot) => [slotKey(slot), slot]));\n}\n\n/** Is this slot value a feature-only explicit removal marker? One test for both axes. */\nexport const isSlotRemoval = isRemoval;\n\n/** The `x-loam-based-on` a slot declares, or undefined — `operationBaselineOf`, aliased. */\nexport const slotBaselineOf = operationBaselineOf;\n\n/**\n * The identity of a slot's CONTENT: sha256 of its canonical form, pin\n * excluded. `operationDigest` under this axis's name — the same canonical\n * JSON, the same cut, so a pin either axis writes means the same thing. The\n * pin is cut because it is a statement ABOUT a delta, never part of the slot\n * it describes: inside the digest input, no baseline could ever be\n * self-consistent (`withoutOperationBaseline` in ../openapi/digest.ts has the\n * whole argument).\n */\nexport const slotDigest = operationDigest;\n\n/**\n * Entries of a mapping node, or nothing — the walker's one structural read,\n * and every structural read the reader (./read.ts) makes goes through it too.\n */\nexport function entriesOf(node: unknown): [string, unknown][] {\n if (node === null || typeof node !== \"object\" || Array.isArray(node)) return [];\n return Object.entries(node as Record<string, unknown>);\n}\n\n/**\n * Every path whose node carries `x-loam-remove: true`, at ANY depth — the\n * three slot depths the format spec gives the key meaning at, inline channel\n * messages, and every place the key means nothing at all: the document root,\n * `info`, a `components` sibling, the inside of a payload. The reader's\n * `markers` field is built from this walk so `asyncapi.remove-marker-living`\n * can NAME a marker wherever one leaked: the strip removes the loam keys at\n * document depth, and a sweep narrower than the strip is how the OpenAPI\n * axis once published a marker invisible even to validate.\n */\nexport function removalMarkerPaths(node: unknown, at: string): string[] {\n if (node === null || typeof node !== \"object\") return [];\n const out: string[] = [];\n if (isSlotRemoval(node)) out.push(at === \"\" ? \"(document root)\" : at);\n const children = Array.isArray(node)\n ? node.map((value, i) => [String(i), value] as const)\n : Object.entries(node as Record<string, unknown>);\n for (const [key, value] of children) {\n out.push(...removalMarkerPaths(value, at === \"\" ? key : `${at}.${key}`));\n }\n return out;\n}\n\n/**\n * Every slot a parsed document declares, in document order across the three\n * sections. Inline channel messages are NOT visited: they are interior\n * content of their channel's slot (the header carries the decision), so a\n * pin or marker nested on one is part of the `channels.<ck>` value this\n * walker already returns — never a slot of its own.\n */\nexport function asyncapiSlots(root: unknown): AsyncapiSlot[] {\n if (root === null || typeof root !== \"object\" || Array.isArray(root)) return [];\n const doc = root as Record<string, unknown>;\n const slots: AsyncapiSlot[] = [];\n const walk = (section: AsyncapiSection, mapping: unknown): void => {\n for (const [key, node] of entriesOf(mapping)) {\n slots.push({\n section,\n key,\n node,\n remove: isSlotRemoval(node),\n digest: slotDigest(node),\n ...(slotBaselineOf(node) === undefined ? {} : { basedOn: slotBaselineOf(node) }),\n });\n }\n };\n walk(\"channels\", doc[\"channels\"]);\n walk(\"operations\", doc[\"operations\"]);\n walk(\"components.messages\", (doc[\"components\"] as Record<string, unknown> | undefined)?.[\"messages\"]);\n return slots;\n}\n"]}
@@ -19,11 +19,12 @@
19
19
  import { isDeepStrictEqual } from "node:util";
20
20
  import { entryFor, surfaceIn } from "../../openapi/baseline/record.js";
21
21
  import { classifyBaselineDigests, valueDigest, withoutFeatureKeysDeep } from "../../openapi/digest.js";
22
- // The OpenAPI axis's component writer, imported rather than respelled: it is
22
+ // The OpenAPI axis's keyed writer, imported rather than respelled: it is
23
23
  // path-agnostic and string-key-matched, so a numeric component name (`404:` is
24
24
  // the YAML number) is REPLACED rather than appended a second time — the same
25
- // hazard every slot writer on this axis goes through `slotPair` for.
26
- import { setComponentValue } from "../../openapi/merge/components.js";
25
+ // hazard every slot write on this axis goes through it for, and every slot
26
+ // read through `slotPair`.
27
+ import { setKeyed } from "../../openapi/merge/components.js";
27
28
  import { asyncapiSurfaces } from "../baseline/surfaces.js";
28
29
  import { writableSection } from "./markers.js";
29
30
  /**
@@ -84,7 +85,7 @@ export function mergeAsyncapiComponents(input) {
84
85
  // every use of the anchor, so writing a surface through it would rewrite
85
86
  // every other node that aliases it.
86
87
  writableSection(living, livingPlain, ["components", kind], service);
87
- setComponentValue(living, kind, name, value);
88
+ setKeyed(living, ["components", kind], name, value);
88
89
  }
89
90
  return { componentsModified, componentsQuoted, componentsStale, copied: copies.length > 0 };
90
91
  }
@@ -1 +1 @@
1
- {"version":3,"file":"components.js","sourceRoot":"","sources":["../../../../src/core/asyncapi/merge/components.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The SURFACE half of the event merge: every `components/<kind>/<name>` the\n * feature declares outside `messages`, folded into the living contract by the\n * same verdict the slot loop takes.\n *\n * Verdict-driven and nothing else. A surface is classified against its\n * `x-loam-baselines` entry exactly as a slot is classified against its\n * in-value pin, so a QUOTE is skipped whatever landed on the living value\n * since, and an EDIT is written. There is deliberately no reachability\n * machinery here — the OpenAPI closure parks a genuinely new component until\n * something written reaches it, because on that axis a quoted OPERATION can\n * drag its authoring-time component copy along. This axis has no such carrier:\n * a surface is only ever copied because the delta declares it, and the ref\n * discipline is the post-hoc `danglingRefs` diff the merge runs over the whole\n * merged tree (../depth.ts) rather than the closure's queued `{ref, from}`.\n * Borrowing the parked set would mean a schema the author wrote on purpose\n * silently not merging, which is the defect this module closes.\n */\nimport { isDeepStrictEqual } from \"node:util\";\nimport type { Document } from \"yaml\";\nimport { entryFor, surfaceIn, type BaselineRecord } from \"../../openapi/baseline/record.js\";\nimport { classifyBaselineDigests, valueDigest, withoutFeatureKeysDeep } from \"../../openapi/digest.js\";\n// The OpenAPI axis's component writer, imported rather than respelled: it is\n// path-agnostic and string-key-matched, so a numeric component name (`404:` is\n// the YAML number) is REPLACED rather than appended a second time — the same\n// hazard every slot writer on this axis goes through `slotPair` for.\nimport { setComponentValue } from \"../../openapi/merge/components.js\";\nimport { asyncapiSurfaces } from \"../baseline/surfaces.js\";\nimport { writableSection } from \"./markers.js\";\n\n/** Everything the surface merge needs; one object because the merge is its only caller. */\nexport interface AsyncapiComponentsInput {\n /** The living document the copies are written into. */\n living: Document;\n featPlain: unknown;\n livingPlain: unknown;\n /** The feature's `x-loam-baselines` record, read once by the merge. */\n record: BaselineRecord;\n service: string;\n}\n\n/** What the surface merge computed. */\nexport interface AsyncapiComponentsOutcome {\n /** `<kind>/<name>` of living surfaces overwritten with different content. */\n componentsModified: string[];\n /** Surfaces the delta QUOTED — not copied, living's copy kept. */\n componentsQuoted: string[];\n /** Surfaces written on a stale record entry — under `--approve`, like the slots. */\n componentsStale: string[];\n /**\n * Whether anything was actually written. The merge turns this into `edited`:\n * without it the surface verdicts would be computed, reported, and then\n * dropped on the floor by the `!edited` early return that keeps an all-quote\n * delta byte-identical — the defect surviving in a new shape, with the plan\n * naming components it never wrote.\n */\n copied: boolean;\n}\n\n/**\n * Merge the feature's component surfaces into the living document by verdict:\n *\n * - quote → skipped; the living copy stays, whatever landed on it since.\n * - edit, or stale (under `--approve`) → copied, and reported.\n * - unpinned and living-equal → skipped: there is nothing to write, and a\n * never-rebased delta that merely restates the contract must not churn its\n * bytes (the slot loop's rule, at surface depth).\n * - unpinned and differing → copied, the old upsert kept for never-rebased\n * deltas, and reported as a modification like any other overwrite.\n * - unfounded (pinned, living gone — the gate refused; `--approve`) and NEW\n * (unpinned, living-absent) → copied. Both write a surface the living\n * contract does not have, which is the ordinary shape of a delta that adds\n * a schema.\n *\n * Every write goes through the deep strip first: a component's value can nest\n * loam's keys anywhere (a schema `properties` entry, a `$ref` sibling), and\n * publishing one into a living contract is the failure `--approve` already\n * caused once on the OpenAPI axis.\n */\nexport function mergeAsyncapiComponents(input: AsyncapiComponentsInput): AsyncapiComponentsOutcome {\n const { living, featPlain, livingPlain, record, service } = input;\n const componentsModified: string[] = [];\n const componentsQuoted: string[] = [];\n const componentsStale: string[] = [];\n const copies: Array<{ kind: string; name: string; value: unknown }> = [];\n\n // ONE enumeration (../baseline/surfaces.ts) with the rebase plan and the\n // gate, and ONE living lookup (surfaceIn), so \"which surfaces does this\n // delta declare, and does living have this one\" cannot have two answers.\n for (const surface of asyncapiSurfaces(featPlain)) {\n // The first `/` is the kind boundary, exactly as `surfaceIn` reads it back\n // — a component name cannot legally contain one.\n const cut = surface.id.indexOf(\"/\");\n const inLiving = surfaceIn(livingPlain, surface);\n const verdict = classifyBaselineDigests(\n entryFor(record, surface),\n valueDigest(surface.value),\n inLiving.found ? valueDigest(inLiving.value) : undefined,\n );\n // A QUOTE is not a merge input, on this axis for the slot loop's reason:\n // the author wrote the surface down because an AsyncAPI delta is a\n // COMPLETE document, not because they changed it. Mechanical, not a\n // judgement — `--approve` does not turn it back on.\n if (verdict === \"quote\") {\n componentsQuoted.push(surface.id);\n continue;\n }\n if (verdict === \"stale\") componentsStale.push(surface.id);\n const publish = withoutFeatureKeysDeep(surface.value);\n if (inLiving.found && isDeepStrictEqual(inLiving.value, publish)) continue;\n if (inLiving.found) componentsModified.push(surface.id);\n copies.push({ kind: surface.id.slice(0, cut), name: surface.id.slice(cut + 1), value: publish });\n }\n\n for (const { kind, name, value } of copies) {\n // Guarded at the write, like every slot write: a living `components:\n // *alias` (or an aliased `components.schemas`) is one shared value behind\n // every use of the anchor, so writing a surface through it would rewrite\n // every other node that aliases it.\n writableSection(living, livingPlain, [\"components\", kind], service);\n setComponentValue(living, kind, name, value);\n }\n\n return { componentsModified, componentsQuoted, componentsStale, copied: copies.length > 0 };\n}\n"]}
1
+ {"version":3,"file":"components.js","sourceRoot":"","sources":["../../../../src/core/asyncapi/merge/components.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The SURFACE half of the event merge: every `components/<kind>/<name>` the\n * feature declares outside `messages`, folded into the living contract by the\n * same verdict the slot loop takes.\n *\n * Verdict-driven and nothing else. A surface is classified against its\n * `x-loam-baselines` entry exactly as a slot is classified against its\n * in-value pin, so a QUOTE is skipped whatever landed on the living value\n * since, and an EDIT is written. There is deliberately no reachability\n * machinery here — the OpenAPI closure parks a genuinely new component until\n * something written reaches it, because on that axis a quoted OPERATION can\n * drag its authoring-time component copy along. This axis has no such carrier:\n * a surface is only ever copied because the delta declares it, and the ref\n * discipline is the post-hoc `danglingRefs` diff the merge runs over the whole\n * merged tree (../depth.ts) rather than the closure's queued `{ref, from}`.\n * Borrowing the parked set would mean a schema the author wrote on purpose\n * silently not merging, which is the defect this module closes.\n */\nimport { isDeepStrictEqual } from \"node:util\";\nimport type { Document } from \"yaml\";\nimport { entryFor, surfaceIn, type BaselineRecord } from \"../../openapi/baseline/record.js\";\nimport { classifyBaselineDigests, valueDigest, withoutFeatureKeysDeep } from \"../../openapi/digest.js\";\n// The OpenAPI axis's keyed writer, imported rather than respelled: it is\n// path-agnostic and string-key-matched, so a numeric component name (`404:` is\n// the YAML number) is REPLACED rather than appended a second time — the same\n// hazard every slot write on this axis goes through it for, and every slot\n// read through `slotPair`.\nimport { setKeyed } from \"../../openapi/merge/components.js\";\nimport { asyncapiSurfaces } from \"../baseline/surfaces.js\";\nimport { writableSection } from \"./markers.js\";\n\n/** Everything the surface merge needs; one object because the merge is its only caller. */\nexport interface AsyncapiComponentsInput {\n /** The living document the copies are written into. */\n living: Document;\n featPlain: unknown;\n livingPlain: unknown;\n /** The feature's `x-loam-baselines` record, read once by the merge. */\n record: BaselineRecord;\n service: string;\n}\n\n/** What the surface merge computed. */\nexport interface AsyncapiComponentsOutcome {\n /** `<kind>/<name>` of living surfaces overwritten with different content. */\n componentsModified: string[];\n /** Surfaces the delta QUOTED — not copied, living's copy kept. */\n componentsQuoted: string[];\n /** Surfaces written on a stale record entry — under `--approve`, like the slots. */\n componentsStale: string[];\n /**\n * Whether anything was actually written. The merge turns this into `edited`:\n * without it the surface verdicts would be computed, reported, and then\n * dropped on the floor by the `!edited` early return that keeps an all-quote\n * delta byte-identical — the defect surviving in a new shape, with the plan\n * naming components it never wrote.\n */\n copied: boolean;\n}\n\n/**\n * Merge the feature's component surfaces into the living document by verdict:\n *\n * - quote → skipped; the living copy stays, whatever landed on it since.\n * - edit, or stale (under `--approve`) → copied, and reported.\n * - unpinned and living-equal → skipped: there is nothing to write, and a\n * never-rebased delta that merely restates the contract must not churn its\n * bytes (the slot loop's rule, at surface depth).\n * - unpinned and differing → copied, the old upsert kept for never-rebased\n * deltas, and reported as a modification like any other overwrite.\n * - unfounded (pinned, living gone — the gate refused; `--approve`) and NEW\n * (unpinned, living-absent) → copied. Both write a surface the living\n * contract does not have, which is the ordinary shape of a delta that adds\n * a schema.\n *\n * Every write goes through the deep strip first: a component's value can nest\n * loam's keys anywhere (a schema `properties` entry, a `$ref` sibling), and\n * publishing one into a living contract is the failure `--approve` already\n * caused once on the OpenAPI axis.\n */\nexport function mergeAsyncapiComponents(input: AsyncapiComponentsInput): AsyncapiComponentsOutcome {\n const { living, featPlain, livingPlain, record, service } = input;\n const componentsModified: string[] = [];\n const componentsQuoted: string[] = [];\n const componentsStale: string[] = [];\n const copies: Array<{ kind: string; name: string; value: unknown }> = [];\n\n // ONE enumeration (../baseline/surfaces.ts) with the rebase plan and the\n // gate, and ONE living lookup (surfaceIn), so \"which surfaces does this\n // delta declare, and does living have this one\" cannot have two answers.\n for (const surface of asyncapiSurfaces(featPlain)) {\n // The first `/` is the kind boundary, exactly as `surfaceIn` reads it back\n // — a component name cannot legally contain one.\n const cut = surface.id.indexOf(\"/\");\n const inLiving = surfaceIn(livingPlain, surface);\n const verdict = classifyBaselineDigests(\n entryFor(record, surface),\n valueDigest(surface.value),\n inLiving.found ? valueDigest(inLiving.value) : undefined,\n );\n // A QUOTE is not a merge input, on this axis for the slot loop's reason:\n // the author wrote the surface down because an AsyncAPI delta is a\n // COMPLETE document, not because they changed it. Mechanical, not a\n // judgement — `--approve` does not turn it back on.\n if (verdict === \"quote\") {\n componentsQuoted.push(surface.id);\n continue;\n }\n if (verdict === \"stale\") componentsStale.push(surface.id);\n const publish = withoutFeatureKeysDeep(surface.value);\n if (inLiving.found && isDeepStrictEqual(inLiving.value, publish)) continue;\n if (inLiving.found) componentsModified.push(surface.id);\n copies.push({ kind: surface.id.slice(0, cut), name: surface.id.slice(cut + 1), value: publish });\n }\n\n for (const { kind, name, value } of copies) {\n // Guarded at the write, like every slot write: a living `components:\n // *alias` (or an aliased `components.schemas`) is one shared value behind\n // every use of the anchor, so writing a surface through it would rewrite\n // every other node that aliases it.\n writableSection(living, livingPlain, [\"components\", kind], service);\n setKeyed(living, [\"components\", kind], name, value);\n }\n\n return { componentsModified, componentsQuoted, componentsStale, copied: copies.length > 0 };\n}\n"]}
@@ -27,7 +27,12 @@ export class AsyncapiMergeError extends Error {
27
27
  this.service = service;
28
28
  }
29
29
  }
30
- export function errorMessage(error) {
31
- return error instanceof Error ? error.message : String(error);
30
+ /**
31
+ * The `fail` the shared tree helpers take (../../openapi/contract-tree.ts), for
32
+ * one side of this axis — always the default "is not valid YAML" problem,
33
+ * because every failure they report is a parse or a serialization.
34
+ */
35
+ export function asyncapiRefusal(source, service) {
36
+ return (detail) => new AsyncapiMergeError(source, service, detail);
32
37
  }
33
38
  //# sourceMappingURL=error.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"error.js","sourceRoot":"","sources":["../../../../src/core/asyncapi/merge/error.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH;;;;;;;;GAQG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAIhC,MAAM;IACN,OAAO;IAJA,IAAI,GAAG,oBAAoB,CAAC;IAE9C,YACW,MAA2B,EAC3B,OAAe,EACxB,MAAc,EACd,OAAO,GAAW,mBAAmB;QAErC,KAAK,CAAC,GAAG,MAAM,iBAAiB,OAAO,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC,CAAC;sBALxD,MAAM;uBACN,OAAO;IAKlB,CAAC;CACF;AAED,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC","sourcesContent":["/**\n * The one refusal every stage of the event-contract delta path raises: a\n * document the planner cannot read, named by which side it came from.\n *\n * The mirror of ../../openapi/merge/error.ts, and the package's leaf for the\n * same reason: whichever sibling fails — the pin, the markers or the merge —\n * the command above has to map one domain error onto its own envelope, and a\n * module that owned the error would be imported by its siblings for that\n * reason alone.\n */\n\nexport type AsyncapiMergeSource = \"feature\" | \"living\";\n\n/**\n * A document the merge machinery cannot use. Commands map this domain error\n * to their own envelope. `problem` is the verb phrase between the document's\n * name and the detail — \"is not valid YAML\" for the parse failures that are\n * the common case, overridden where the document parses fine and the refusal\n * is about what it says (a section spelling one slot key twice is legal YAML,\n * and calling it invalid would send the author to a linter that agrees with\n * the file).\n */\nexport class AsyncapiMergeError extends Error {\n override readonly name = \"AsyncapiMergeError\";\n\n constructor(\n readonly source: AsyncapiMergeSource,\n readonly service: string,\n detail: string,\n problem: string = \"is not valid YAML\",\n ) {\n super(`${source} asyncapi for ${service} ${problem}: ${detail}`);\n }\n}\n\nexport function errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"]}
1
+ {"version":3,"file":"error.js","sourceRoot":"","sources":["../../../../src/core/asyncapi/merge/error.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH;;;;;;;;GAQG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAIhC,MAAM;IACN,OAAO;IAJA,IAAI,GAAG,oBAAoB,CAAC;IAE9C,YACW,MAA2B,EAC3B,OAAe,EACxB,MAAc,EACd,OAAO,GAAW,mBAAmB;QAErC,KAAK,CAAC,GAAG,MAAM,iBAAiB,OAAO,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC,CAAC;sBALxD,MAAM;uBACN,OAAO;IAKlB,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,MAA2B,EAAE,OAAe;IAC1E,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,kBAAkB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;AACrE,CAAC","sourcesContent":["/**\n * The one refusal every stage of the event-contract delta path raises: a\n * document the planner cannot read, named by which side it came from.\n *\n * The mirror of ../../openapi/merge/error.ts, and the package's leaf for the\n * same reason: whichever sibling fails — the pin, the markers or the merge —\n * the command above has to map one domain error onto its own envelope, and a\n * module that owned the error would be imported by its siblings for that\n * reason alone.\n */\n\nexport type AsyncapiMergeSource = \"feature\" | \"living\";\n\n/**\n * A document the merge machinery cannot use. Commands map this domain error\n * to their own envelope. `problem` is the verb phrase between the document's\n * name and the detail — \"is not valid YAML\" for the parse failures that are\n * the common case, overridden where the document parses fine and the refusal\n * is about what it says (a section spelling one slot key twice is legal YAML,\n * and calling it invalid would send the author to a linter that agrees with\n * the file).\n */\nexport class AsyncapiMergeError extends Error {\n override readonly name = \"AsyncapiMergeError\";\n\n constructor(\n readonly source: AsyncapiMergeSource,\n readonly service: string,\n detail: string,\n problem: string = \"is not valid YAML\",\n ) {\n super(`${source} asyncapi for ${service} ${problem}: ${detail}`);\n }\n}\n\n/**\n * The `fail` the shared tree helpers take (../../openapi/contract-tree.ts), for\n * one side of this axis — always the default \"is not valid YAML\" problem,\n * because every failure they report is a parse or a serialization.\n */\nexport function asyncapiRefusal(source: AsyncapiMergeSource, service: string): (detail: string) => AsyncapiMergeError {\n return (detail) => new AsyncapiMergeError(source, service, detail);\n}\n"]}