@ybotok/loam 0.2.0-alpha.6 → 0.2.0-alpha.9

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 (691) hide show
  1. package/CHANGELOG.md +1477 -1
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +40 -22
  5. package/QUICKSTART.md +181 -9
  6. package/README.md +256 -57
  7. package/ROADMAP.md +108 -11
  8. package/SCHEMA.md +721 -130
  9. package/WORKFLOW.md +272 -62
  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/archive/archive.js +15 -18
  15. package/dist/commands/archive/archive.js.map +1 -1
  16. package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
  17. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  18. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  19. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/openapi.js +25 -42
  21. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
  23. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  24. package/dist/commands/archive/plan/gate.js +3 -7
  25. package/dist/commands/archive/plan/gate.js.map +1 -1
  26. package/dist/commands/archive/plan/landscape.js +4 -4
  27. package/dist/commands/archive/plan/landscape.js.map +1 -1
  28. package/dist/commands/archive/plan/refusal.js +8 -18
  29. package/dist/commands/archive/plan/refusal.js.map +1 -1
  30. package/dist/commands/archive/plan/specs.js +134 -25
  31. package/dist/commands/archive/plan/specs.js.map +1 -1
  32. package/dist/commands/archive/plan/state.js +1 -0
  33. package/dist/commands/archive/plan/state.js.map +1 -1
  34. package/dist/commands/archive/run.js +82 -33
  35. package/dist/commands/archive/run.js.map +1 -1
  36. package/dist/commands/archive/shipped.js +49 -0
  37. package/dist/commands/archive/shipped.js.map +1 -0
  38. package/dist/commands/context/context.js +4 -7
  39. package/dist/commands/context/context.js.map +1 -1
  40. package/dist/commands/context/print.js +2 -2
  41. package/dist/commands/context/print.js.map +1 -1
  42. package/dist/commands/delta/delta.js +44 -12
  43. package/dist/commands/delta/delta.js.map +1 -1
  44. package/dist/commands/delta/print.js +1 -1
  45. package/dist/commands/delta/print.js.map +1 -1
  46. package/dist/commands/dependencies.js +3 -7
  47. package/dist/commands/dependencies.js.map +1 -1
  48. package/dist/commands/diff/diff.js +20 -27
  49. package/dist/commands/diff/diff.js.map +1 -1
  50. package/dist/commands/explore.js +4 -8
  51. package/dist/commands/explore.js.map +1 -1
  52. package/dist/commands/gate/gate.js +45 -8
  53. package/dist/commands/gate/gate.js.map +1 -1
  54. package/dist/commands/gate/print.js +15 -3
  55. package/dist/commands/gate/print.js.map +1 -1
  56. package/dist/commands/gherkin/commit.js +13 -54
  57. package/dist/commands/gherkin/commit.js.map +1 -1
  58. package/dist/commands/gherkin/gherkin.js +36 -20
  59. package/dist/commands/gherkin/gherkin.js.map +1 -1
  60. package/dist/commands/gherkin/reconcile.js +10 -13
  61. package/dist/commands/gherkin/reconcile.js.map +1 -1
  62. package/dist/commands/init/first-hour.js +44 -0
  63. package/dist/commands/init/first-hour.js.map +1 -1
  64. package/dist/commands/init/init.js +18 -22
  65. package/dist/commands/init/init.js.map +1 -1
  66. package/dist/commands/list/campaign/campaign.js +4 -6
  67. package/dist/commands/list/campaign/campaign.js.map +1 -1
  68. package/dist/commands/list/json.js +0 -4
  69. package/dist/commands/list/json.js.map +1 -1
  70. package/dist/commands/list/list.js +38 -23
  71. package/dist/commands/list/list.js.map +1 -1
  72. package/dist/commands/list/print.js +38 -6
  73. package/dist/commands/list/print.js.map +1 -1
  74. package/dist/commands/list/suites/suites.js +140 -0
  75. package/dist/commands/list/suites/suites.js.map +1 -0
  76. package/dist/commands/list/views.js +28 -33
  77. package/dist/commands/list/views.js.map +1 -1
  78. package/dist/commands/mcp/dispatch.js +91 -36
  79. package/dist/commands/mcp/dispatch.js.map +1 -1
  80. package/dist/commands/mcp/mcp.js +56 -15
  81. package/dist/commands/mcp/mcp.js.map +1 -1
  82. package/dist/commands/mcp/serve.js.map +1 -1
  83. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  84. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  85. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  86. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  87. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  88. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  89. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  90. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  91. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  92. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  93. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  94. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  95. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  96. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  97. package/dist/commands/new/capability-template.js +118 -0
  98. package/dist/commands/new/capability-template.js.map +1 -0
  99. package/dist/commands/new/commit.js +8 -22
  100. package/dist/commands/new/commit.js.map +1 -1
  101. package/dist/commands/new/modifies.js +206 -0
  102. package/dist/commands/new/modifies.js.map +1 -0
  103. package/dist/commands/new/new.js +59 -14
  104. package/dist/commands/new/new.js.map +1 -1
  105. package/dist/commands/new/templates.js +48 -124
  106. package/dist/commands/new/templates.js.map +1 -1
  107. package/dist/commands/open.js +3 -7
  108. package/dist/commands/open.js.map +1 -1
  109. package/dist/commands/policy/gate.js +27 -0
  110. package/dist/commands/policy/gate.js.map +1 -1
  111. package/dist/commands/rebase/plan.js +2 -2
  112. package/dist/commands/rebase/plan.js.map +1 -1
  113. package/dist/commands/rebase/rebase.js +60 -35
  114. package/dist/commands/rebase/rebase.js.map +1 -1
  115. package/dist/commands/seed/commit.js +55 -26
  116. package/dist/commands/seed/commit.js.map +1 -1
  117. package/dist/commands/seed/plan.js +64 -13
  118. package/dist/commands/seed/plan.js.map +1 -1
  119. package/dist/commands/seed/seed.js +6 -9
  120. package/dist/commands/seed/seed.js.map +1 -1
  121. package/dist/commands/show/show.js +3 -7
  122. package/dist/commands/show/show.js.map +1 -1
  123. package/dist/commands/status/print.js +13 -4
  124. package/dist/commands/status/print.js.map +1 -1
  125. package/dist/commands/status/status.js +3 -7
  126. package/dist/commands/status/status.js.map +1 -1
  127. package/dist/commands/steps/steps.js +3 -7
  128. package/dist/commands/steps/steps.js.map +1 -1
  129. package/dist/commands/subsystem/edit.js +7 -7
  130. package/dist/commands/subsystem/edit.js.map +1 -1
  131. package/dist/commands/subsystem/move.js +60 -25
  132. package/dist/commands/subsystem/move.js.map +1 -1
  133. package/dist/commands/subsystem/query.js +2 -4
  134. package/dist/commands/subsystem/query.js.map +1 -1
  135. package/dist/commands/subsystem/subsystem.js +4 -7
  136. package/dist/commands/subsystem/subsystem.js.map +1 -1
  137. package/dist/commands/subsystem/sync.js +141 -71
  138. package/dist/commands/subsystem/sync.js.map +1 -1
  139. package/dist/commands/subsystem/txn/report.js +55 -5
  140. package/dist/commands/subsystem/txn/report.js.map +1 -1
  141. package/dist/commands/subsystem/txn/txn.js +121 -45
  142. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  143. package/dist/commands/subsystem/txn/views.js +82 -12
  144. package/dist/commands/subsystem/txn/views.js.map +1 -1
  145. package/dist/commands/tasks/render.js +116 -0
  146. package/dist/commands/tasks/render.js.map +1 -0
  147. package/dist/commands/tasks/tasks.js +113 -0
  148. package/dist/commands/tasks/tasks.js.map +1 -0
  149. package/dist/commands/unarchive/manifest.js +6 -9
  150. package/dist/commands/unarchive/manifest.js.map +1 -1
  151. package/dist/commands/unarchive/restore.js +17 -28
  152. package/dist/commands/unarchive/restore.js.map +1 -1
  153. package/dist/commands/unarchive/unarchive.js +22 -30
  154. package/dist/commands/unarchive/unarchive.js.map +1 -1
  155. package/dist/commands/validate/arch-coverage.js +6 -15
  156. package/dist/commands/validate/arch-coverage.js.map +1 -1
  157. package/dist/commands/validate/checks/fleet-shape.js +4 -49
  158. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  159. package/dist/commands/validate/checks/health-deps.js +9 -6
  160. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  161. package/dist/commands/validate/checks/requirements.js +18 -8
  162. package/dist/commands/validate/checks/requirements.js.map +1 -1
  163. package/dist/commands/validate/checks/sources.js +6 -2
  164. package/dist/commands/validate/checks/sources.js.map +1 -1
  165. package/dist/commands/validate/feature.js +23 -19
  166. package/dist/commands/validate/feature.js.map +1 -1
  167. package/dist/commands/validate/fleet/census.js +26 -3
  168. package/dist/commands/validate/fleet/census.js.map +1 -1
  169. package/dist/commands/validate/fleet/landscape.js +35 -32
  170. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  171. package/dist/commands/validate/fleet/map/attest.js +7 -12
  172. package/dist/commands/validate/fleet/map/attest.js.map +1 -1
  173. package/dist/commands/validate/fleet/map/bindings.js +16 -5
  174. package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
  175. package/dist/commands/validate/fleet/map/consumers.js +43 -122
  176. package/dist/commands/validate/fleet/map/consumers.js.map +1 -1
  177. package/dist/commands/validate/fleet/map/usecases.js +5 -11
  178. package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
  179. package/dist/commands/validate/fleet/obligations.js +1 -5
  180. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  181. package/dist/commands/validate/fleet/scorecard/print.js +5 -1
  182. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  183. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  184. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  185. package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
  186. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
  187. package/dist/commands/validate/fleet/views/fleet-project.js +1 -1
  188. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
  189. package/dist/commands/validate/fleet/views/ids.js +33 -7
  190. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  191. package/dist/commands/validate/fleet/views/root-documents.js +17 -35
  192. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
  193. package/dist/commands/validate/fleet/views/stale.js +31 -14
  194. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  195. package/dist/commands/validate/links/corpus.js +1 -4
  196. package/dist/commands/validate/links/corpus.js.map +1 -1
  197. package/dist/commands/validate/links/glossary.js.map +1 -1
  198. package/dist/commands/validate/report.js +5 -4
  199. package/dist/commands/validate/report.js.map +1 -1
  200. package/dist/commands/validate/scope.js +113 -0
  201. package/dist/commands/validate/scope.js.map +1 -0
  202. package/dist/commands/validate/service/api.js +16 -104
  203. package/dist/commands/validate/service/api.js.map +1 -1
  204. package/dist/commands/validate/service/contracts/generated.js +134 -0
  205. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  206. package/dist/commands/validate/service/events/events.js +1 -1
  207. package/dist/commands/validate/service/events/events.js.map +1 -1
  208. package/dist/commands/validate/service/events/message-producers.js +1 -1
  209. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  210. package/dist/commands/validate/service/evidence-pins.js +56 -10
  211. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  212. package/dist/commands/validate/service/service.js +16 -26
  213. package/dist/commands/validate/service/service.js.map +1 -1
  214. package/dist/commands/validate/service/specs.js +31 -38
  215. package/dist/commands/validate/service/specs.js.map +1 -1
  216. package/dist/commands/validate/service/spine.js +8 -11
  217. package/dist/commands/validate/service/spine.js.map +1 -1
  218. package/dist/commands/validate/service/usecases/usecases.js +3 -3
  219. package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
  220. package/dist/commands/validate/validate.js +44 -26
  221. package/dist/commands/validate/validate.js.map +1 -1
  222. package/dist/commands/verify/cross/diff.js +2 -6
  223. package/dist/commands/verify/cross/diff.js.map +1 -1
  224. package/dist/commands/verify/evidence/contract.js +1 -1
  225. package/dist/commands/verify/evidence/contract.js.map +1 -1
  226. package/dist/commands/verify/evidence/read.js +12 -3
  227. package/dist/commands/verify/evidence/read.js.map +1 -1
  228. package/dist/commands/verify/frozen.js +45 -23
  229. package/dist/commands/verify/frozen.js.map +1 -1
  230. package/dist/commands/verify/record.js +69 -17
  231. package/dist/commands/verify/record.js.map +1 -1
  232. package/dist/commands/verify/report.js +27 -44
  233. package/dist/commands/verify/report.js.map +1 -1
  234. package/dist/commands/verify/results.js +112 -30
  235. package/dist/commands/verify/results.js.map +1 -1
  236. package/dist/commands/verify/verify.js +54 -51
  237. package/dist/commands/verify/verify.js.map +1 -1
  238. package/dist/commands/vouch/pack/pack.js +2 -2
  239. package/dist/commands/vouch/pack/pack.js.map +1 -1
  240. package/dist/commands/vouch/pack/print.js +1 -6
  241. package/dist/commands/vouch/pack/print.js.map +1 -1
  242. package/dist/commands/vouch/pack/sections.js +1 -2
  243. package/dist/commands/vouch/pack/sections.js.map +1 -1
  244. package/dist/commands/vouch/run.js +26 -81
  245. package/dist/commands/vouch/run.js.map +1 -1
  246. package/dist/commands/vouch/sample/plan.js +20 -34
  247. package/dist/commands/vouch/sample/plan.js.map +1 -1
  248. package/dist/commands/vouch/vet/verify.js +39 -7
  249. package/dist/commands/vouch/vet/verify.js.map +1 -1
  250. package/dist/commands/vouch/vouch.js +10 -17
  251. package/dist/commands/vouch/vouch.js.map +1 -1
  252. package/dist/core/agent/agents-md/command-map.js +57 -102
  253. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  254. package/dist/core/agent/agents-md/map/explain.js +4 -3
  255. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  256. package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
  257. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  258. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  259. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  260. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  261. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  262. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  263. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  264. package/dist/core/agent/agents-md/map/status.js +9 -3
  265. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  266. package/dist/core/agent/agents-md/map/subsystem.js +31 -17
  267. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  268. package/dist/core/agent/agents-md/map/usecases.js +24 -6
  269. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  270. package/dist/core/agent/agents-md/refusals.js +37 -21
  271. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  272. package/dist/core/agent/agents-md.js +25 -10
  273. package/dist/core/agent/agents-md.js.map +1 -1
  274. package/dist/core/agent/agents-stamp.js +10 -0
  275. package/dist/core/agent/agents-stamp.js.map +1 -1
  276. package/dist/core/agent/scaffold.js +10 -15
  277. package/dist/core/agent/scaffold.js.map +1 -1
  278. package/dist/core/agent/tools/dialects.js +1 -0
  279. package/dist/core/agent/tools/dialects.js.map +1 -1
  280. package/dist/core/agent/tools/registry.js +41 -76
  281. package/dist/core/agent/tools/registry.js.map +1 -1
  282. package/dist/core/agent/workflows/adopt.js +5 -3
  283. package/dist/core/agent/workflows/adopt.js.map +1 -1
  284. package/dist/core/agent/workflows/check.js +25 -22
  285. package/dist/core/agent/workflows/check.js.map +1 -1
  286. package/dist/core/agent/workflows/closing.js +54 -21
  287. package/dist/core/agent/workflows/closing.js.map +1 -1
  288. package/dist/core/agent/workflows/feature.js +79 -11
  289. package/dist/core/agent/workflows/feature.js.map +1 -1
  290. package/dist/core/agent/workflows/implement.js +91 -13
  291. package/dist/core/agent/workflows/implement.js.map +1 -1
  292. package/dist/core/agent/workflows/reference/authoring.js +3 -1
  293. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  294. package/dist/core/agent/workflows/reference/codes.js +8 -6
  295. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  296. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  297. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  298. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  299. package/dist/core/agent/workflows/reference/spine.js +62 -24
  300. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  301. package/dist/core/asyncapi/baseline/plan.js +14 -81
  302. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  303. package/dist/core/asyncapi/digest.js +30 -25
  304. package/dist/core/asyncapi/digest.js.map +1 -1
  305. package/dist/core/asyncapi/merge/components.js +5 -4
  306. package/dist/core/asyncapi/merge/components.js.map +1 -1
  307. package/dist/core/asyncapi/merge/error.js +7 -2
  308. package/dist/core/asyncapi/merge/error.js.map +1 -1
  309. package/dist/core/asyncapi/merge/markers.js +30 -50
  310. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  311. package/dist/core/asyncapi/merge/merge.js +31 -53
  312. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  313. package/dist/core/asyncapi/merge/pin.js +10 -36
  314. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  315. package/dist/core/asyncapi/model.js.map +1 -1
  316. package/dist/core/asyncapi/read.js +36 -12
  317. package/dist/core/asyncapi/read.js.map +1 -1
  318. package/dist/core/brief/brief.js +30 -25
  319. package/dist/core/brief/brief.js.map +1 -1
  320. package/dist/core/brief/landscape.js +8 -12
  321. package/dist/core/brief/landscape.js.map +1 -1
  322. package/dist/core/brief/map/extend.js +8 -5
  323. package/dist/core/brief/map/extend.js.map +1 -1
  324. package/dist/core/brief/map/owed.js +8 -26
  325. package/dist/core/brief/map/owed.js.map +1 -1
  326. package/dist/core/brief/targets.js +79 -5
  327. package/dist/core/brief/targets.js.map +1 -1
  328. package/dist/core/brief/unchecked.js +1 -1
  329. package/dist/core/brief/unchecked.js.map +1 -1
  330. package/dist/core/c4/likec4.js +147 -16
  331. package/dist/core/c4/likec4.js.map +1 -1
  332. package/dist/core/c4/locate/unlocated.js +150 -0
  333. package/dist/core/c4/locate/unlocated.js.map +1 -0
  334. package/dist/core/c4/parsed/values.js +54 -1
  335. package/dist/core/c4/parsed/values.js.map +1 -1
  336. package/dist/core/c4/prefetch/fleet.js +1 -9
  337. package/dist/core/c4/prefetch/fleet.js.map +1 -1
  338. package/dist/core/c4/project/architecture.js +31 -9
  339. package/dist/core/c4/project/architecture.js.map +1 -1
  340. package/dist/core/c4/project/documents.js +36 -10
  341. package/dist/core/c4/project/documents.js.map +1 -1
  342. package/dist/core/c4/project/load.js +14 -21
  343. package/dist/core/c4/project/load.js.map +1 -1
  344. package/dist/core/c4/project/staged.js +7 -3
  345. package/dist/core/c4/project/staged.js.map +1 -1
  346. package/dist/core/c4/resolve/external.js +51 -0
  347. package/dist/core/c4/resolve/external.js.map +1 -0
  348. package/dist/core/c4/resolve/service.js +25 -1
  349. package/dist/core/c4/resolve/service.js.map +1 -1
  350. package/dist/core/c4/seed/template.js +15 -1
  351. package/dist/core/c4/seed/template.js.map +1 -1
  352. package/dist/core/c4/service-model/shape.js +2 -22
  353. package/dist/core/c4/service-model/shape.js.map +1 -1
  354. package/dist/core/c4/source-mask.js +288 -4
  355. package/dist/core/c4/source-mask.js.map +1 -1
  356. package/dist/core/c4/source-scan.js +203 -147
  357. package/dist/core/c4/source-scan.js.map +1 -1
  358. package/dist/core/c4/splice/authored-source.js +19 -0
  359. package/dist/core/c4/splice/authored-source.js.map +1 -1
  360. package/dist/core/c4/splice/delta-blocks.js +5 -18
  361. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  362. package/dist/core/c4/splice/landscape-merge.js +13 -8
  363. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  364. package/dist/core/c4/splice/model/merge.js +10 -6
  365. package/dist/core/c4/splice/model/merge.js.map +1 -1
  366. package/dist/core/c4/splice/placement.js +108 -18
  367. package/dist/core/c4/splice/placement.js.map +1 -1
  368. package/dist/core/c4/workspace.js +13 -22
  369. package/dist/core/c4/workspace.js.map +1 -1
  370. package/dist/core/capabilities/capabilities.js +6 -6
  371. package/dist/core/capabilities/capabilities.js.map +1 -1
  372. package/dist/core/capabilities/delta/doc.js +7 -10
  373. package/dist/core/capabilities/delta/doc.js.map +1 -1
  374. package/dist/core/capabilities/findings.js +15 -20
  375. package/dist/core/capabilities/findings.js.map +1 -1
  376. package/dist/core/capabilities/realizes/findings.js +12 -19
  377. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  378. package/dist/core/capabilities/realizes/join.js +0 -20
  379. package/dist/core/capabilities/realizes/join.js.map +1 -1
  380. package/dist/core/capabilities/rollup.js +6 -0
  381. package/dist/core/capabilities/rollup.js.map +1 -1
  382. package/dist/core/capabilities/tree.js +6 -28
  383. package/dist/core/capabilities/tree.js.map +1 -1
  384. package/dist/core/capabilities/usecase-join.js +3 -13
  385. package/dist/core/capabilities/usecase-join.js.map +1 -1
  386. package/dist/core/coherence/authoring/scaffold.js +11 -3
  387. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  388. package/dist/core/coherence/authoring/sentinels.js +2 -1
  389. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  390. package/dist/core/coherence/coherence.js +30 -16
  391. package/dist/core/coherence/coherence.js.map +1 -1
  392. package/dist/core/coherence/consumed/operations.js +70 -0
  393. package/dist/core/coherence/consumed/operations.js.map +1 -0
  394. package/dist/core/coherence/consumed/refusal.js +38 -0
  395. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  396. package/dist/core/coherence/declared.js +60 -34
  397. package/dist/core/coherence/declared.js.map +1 -1
  398. package/dist/core/coherence/events/declared.js +44 -59
  399. package/dist/core/coherence/events/declared.js.map +1 -1
  400. package/dist/core/coherence/events/events.js +119 -8
  401. package/dist/core/coherence/events/events.js.map +1 -1
  402. package/dist/core/coherence/events/grades.js +31 -8
  403. package/dist/core/coherence/events/grades.js.map +1 -1
  404. package/dist/core/coherence/events/lookups.js +14 -82
  405. package/dist/core/coherence/events/lookups.js.map +1 -1
  406. package/dist/core/coherence/lookups.js +18 -78
  407. package/dist/core/coherence/lookups.js.map +1 -1
  408. package/dist/core/coherence/retired/archived.js +285 -0
  409. package/dist/core/coherence/retired/archived.js.map +1 -0
  410. package/dist/core/coherence/retired/event-pair.js +62 -0
  411. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  412. package/dist/core/coherence/retired/merged.js +79 -0
  413. package/dist/core/coherence/retired/merged.js.map +1 -0
  414. package/dist/core/coherence/retired/retirement.js +49 -0
  415. package/dist/core/coherence/retired/retirement.js.map +1 -0
  416. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  417. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  418. package/dist/core/coherence/retired/revival/revived.js +297 -0
  419. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  420. package/dist/core/consumers/after.js +113 -0
  421. package/dist/core/consumers/after.js.map +1 -0
  422. package/dist/core/consumers/consumers.js +296 -0
  423. package/dist/core/consumers/consumers.js.map +1 -0
  424. package/dist/core/consumers/current.js +211 -0
  425. package/dist/core/consumers/current.js.map +1 -0
  426. package/dist/core/consumers/scan.js +46 -0
  427. package/dist/core/consumers/scan.js.map +1 -0
  428. package/dist/core/consumers/state.js +27 -0
  429. package/dist/core/consumers/state.js.map +1 -0
  430. package/dist/core/delta/delta.js +1 -1
  431. package/dist/core/delta/delta.js.map +1 -1
  432. package/dist/core/deployment/delta.js +15 -16
  433. package/dist/core/deployment/delta.js.map +1 -1
  434. package/dist/core/diff/base-git.js +94 -12
  435. package/dist/core/diff/base-git.js.map +1 -1
  436. package/dist/core/diff/base-state.js +28 -16
  437. package/dist/core/diff/base-state.js.map +1 -1
  438. package/dist/core/diff/joins.js +136 -0
  439. package/dist/core/diff/joins.js.map +1 -0
  440. package/dist/core/diff/scope/changed-paths.js +22 -6
  441. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  442. package/dist/core/diff/scope/edges.js +82 -0
  443. package/dist/core/diff/scope/edges.js.map +1 -0
  444. package/dist/core/diff/semantic.js +100 -31
  445. package/dist/core/diff/semantic.js.map +1 -1
  446. package/dist/core/doctor/agents.js +34 -18
  447. package/dist/core/doctor/agents.js.map +1 -1
  448. package/dist/core/doctor/report.js.map +1 -1
  449. package/dist/core/document/apply.js +10 -2
  450. package/dist/core/document/apply.js.map +1 -1
  451. package/dist/core/document/frontmatter.js +55 -0
  452. package/dist/core/document/frontmatter.js.map +1 -1
  453. package/dist/core/document/parse.js +29 -6
  454. package/dist/core/document/parse.js.map +1 -1
  455. package/dist/core/document/scenarios.js +17 -0
  456. package/dist/core/document/scenarios.js.map +1 -1
  457. package/dist/core/document/spec.js +4 -11
  458. package/dist/core/document/spec.js.map +1 -1
  459. package/dist/core/envelope/config.js +10 -0
  460. package/dist/core/envelope/config.js.map +1 -1
  461. package/dist/core/envelope/json.js +34 -0
  462. package/dist/core/envelope/json.js.map +1 -1
  463. package/dist/core/explain/families.js +17 -12
  464. package/dist/core/explain/families.js.map +1 -1
  465. package/dist/core/explain/refusals.js +3 -3
  466. package/dist/core/explain/refusals.js.map +1 -1
  467. package/dist/core/explain/terms.js +18 -9
  468. package/dist/core/explain/terms.js.map +1 -1
  469. package/dist/core/explore/explore.js +2 -7
  470. package/dist/core/explore/explore.js.map +1 -1
  471. package/dist/core/fleet-context.js +34 -95
  472. package/dist/core/fleet-context.js.map +1 -1
  473. package/dist/core/gate/checks.js +10 -3
  474. package/dist/core/gate/checks.js.map +1 -1
  475. package/dist/core/gate/partners.js +64 -19
  476. package/dist/core/gate/partners.js.map +1 -1
  477. package/dist/core/gate/report.js.map +1 -1
  478. package/dist/core/gate/verification.js +75 -12
  479. package/dist/core/gate/verification.js.map +1 -1
  480. package/dist/core/gherkin/stale.js +13 -37
  481. package/dist/core/gherkin/stale.js.map +1 -1
  482. package/dist/core/glossary/tree.js +6 -19
  483. package/dist/core/glossary/tree.js.map +1 -1
  484. package/dist/core/kernel/concurrency.js +19 -0
  485. package/dist/core/kernel/concurrency.js.map +1 -1
  486. package/dist/core/kernel/records.js +11 -0
  487. package/dist/core/kernel/records.js.map +1 -1
  488. package/dist/core/links/backlinks.js +1 -3
  489. package/dist/core/links/backlinks.js.map +1 -1
  490. package/dist/core/links/corpus.js +5 -36
  491. package/dist/core/links/corpus.js.map +1 -1
  492. package/dist/core/mcp/argv.js +101 -0
  493. package/dist/core/mcp/argv.js.map +1 -1
  494. package/dist/core/mcp/author-tools.js +2 -1
  495. package/dist/core/mcp/author-tools.js.map +1 -1
  496. package/dist/core/mcp/protocol.js +2 -2
  497. package/dist/core/mcp/protocol.js.map +1 -1
  498. package/dist/core/mcp/tools.js +43 -33
  499. package/dist/core/mcp/tools.js.map +1 -1
  500. package/dist/core/openapi/baseline/gate.js +53 -12
  501. package/dist/core/openapi/baseline/gate.js.map +1 -1
  502. package/dist/core/openapi/baseline/plan.js +22 -8
  503. package/dist/core/openapi/baseline/plan.js.map +1 -1
  504. package/dist/core/openapi/baseline/record.js +0 -1
  505. package/dist/core/openapi/baseline/record.js.map +1 -1
  506. package/dist/core/openapi/contract-text.js +33 -0
  507. package/dist/core/openapi/contract-text.js.map +1 -0
  508. package/dist/core/openapi/contract-tree.js +56 -0
  509. package/dist/core/openapi/contract-tree.js.map +1 -0
  510. package/dist/core/openapi/depth.js +38 -24
  511. package/dist/core/openapi/depth.js.map +1 -1
  512. package/dist/core/openapi/digest.js +52 -2
  513. package/dist/core/openapi/digest.js.map +1 -1
  514. package/dist/core/openapi/doc.js +12 -4
  515. package/dist/core/openapi/doc.js.map +1 -1
  516. package/dist/core/openapi/merge/components.js +25 -60
  517. package/dist/core/openapi/merge/components.js.map +1 -1
  518. package/dist/core/openapi/merge/error.js +3 -13
  519. package/dist/core/openapi/merge/error.js.map +1 -1
  520. package/dist/core/openapi/merge/markers.js +26 -30
  521. package/dist/core/openapi/merge/markers.js.map +1 -1
  522. package/dist/core/openapi/merge/merge.js +26 -52
  523. package/dist/core/openapi/merge/merge.js.map +1 -1
  524. package/dist/core/openapi/merge/pin.js +15 -35
  525. package/dist/core/openapi/merge/pin.js.map +1 -1
  526. package/dist/core/openspec/decide/verdict.js.map +1 -1
  527. package/dist/core/openspec/inventory.js +4 -5
  528. package/dist/core/openspec/inventory.js.map +1 -1
  529. package/dist/core/openspec/model/mapping.js +30 -28
  530. package/dist/core/openspec/model/mapping.js.map +1 -1
  531. package/dist/core/openspec/model/model.js +12 -7
  532. package/dist/core/openspec/model/model.js.map +1 -1
  533. package/dist/core/openspec/scan/changes.js +10 -4
  534. package/dist/core/openspec/scan/changes.js.map +1 -1
  535. package/dist/core/openspec/scan/config.js +7 -16
  536. package/dist/core/openspec/scan/config.js.map +1 -1
  537. package/dist/core/openspec/scan/shape.js +63 -19
  538. package/dist/core/openspec/scan/shape.js.map +1 -1
  539. package/dist/core/openspec/scan/walk.js +21 -38
  540. package/dist/core/openspec/scan/walk.js.map +1 -1
  541. package/dist/core/pack/living.js +2 -16
  542. package/dist/core/pack/living.js.map +1 -1
  543. package/dist/core/provenance/findings.js +48 -12
  544. package/dist/core/provenance/findings.js.map +1 -1
  545. package/dist/core/provenance/git.js +33 -57
  546. package/dist/core/provenance/git.js.map +1 -1
  547. package/dist/core/provenance/gitq/moves.js +71 -114
  548. package/dist/core/provenance/gitq/moves.js.map +1 -1
  549. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  550. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  551. package/dist/core/provenance/sources.js +23 -6
  552. package/dist/core/provenance/sources.js.map +1 -1
  553. package/dist/core/provenance/stamp.js +5 -17
  554. package/dist/core/provenance/stamp.js.map +1 -1
  555. package/dist/core/repo/entries.js.map +1 -1
  556. package/dist/core/repo/paths.js +34 -29
  557. package/dist/core/repo/paths.js.map +1 -1
  558. package/dist/core/repo/repo.js +24 -14
  559. package/dist/core/repo/repo.js.map +1 -1
  560. package/dist/core/repo/tree/find.js +12 -3
  561. package/dist/core/repo/tree/find.js.map +1 -1
  562. package/dist/core/repo/tree/fs.js +27 -1
  563. package/dist/core/repo/tree/fs.js.map +1 -1
  564. package/dist/core/repo/tree/render/body.js +45 -6
  565. package/dist/core/repo/tree/render/body.js.map +1 -1
  566. package/dist/core/repo/tree/render/service/header.js +125 -0
  567. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  568. package/dist/core/repo/tree/render/service/peers.js +28 -0
  569. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  570. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  571. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  572. package/dist/core/repo/tree/render/service/views.js +78 -0
  573. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  574. package/dist/core/repo/tree/render/stale.js +19 -2
  575. package/dist/core/repo/tree/render/stale.js.map +1 -1
  576. package/dist/core/repo/tree/render/views.js +102 -33
  577. package/dist/core/repo/tree/render/views.js.map +1 -1
  578. package/dist/core/repo/tree/walk.js +8 -30
  579. package/dist/core/repo/tree/walk.js.map +1 -1
  580. package/dist/core/results.js +47 -12
  581. package/dist/core/results.js.map +1 -1
  582. package/dist/core/staging/commit.js +10 -12
  583. package/dist/core/staging/commit.js.map +1 -1
  584. package/dist/core/staging/lock.js +74 -12
  585. package/dist/core/staging/lock.js.map +1 -1
  586. package/dist/core/staging/recovery/intent.js +2 -5
  587. package/dist/core/staging/recovery/intent.js.map +1 -1
  588. package/dist/core/staging/recovery/recover.js +60 -1
  589. package/dist/core/staging/recovery/recover.js.map +1 -1
  590. package/dist/core/staging/snapshot/manifest.js +152 -0
  591. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  592. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
  593. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  594. package/dist/core/status/actions/execution.js +11 -0
  595. package/dist/core/status/actions/execution.js.map +1 -1
  596. package/dist/core/status/feature/artifacts.js +2 -2
  597. package/dist/core/status/feature/artifacts.js.map +1 -1
  598. package/dist/core/status/feature/feature.js +5 -11
  599. package/dist/core/status/feature/feature.js.map +1 -1
  600. package/dist/core/status/fleet/fleet.js +6 -2
  601. package/dist/core/status/fleet/fleet.js.map +1 -1
  602. package/dist/core/status/fleet/next.js +26 -3
  603. package/dist/core/status/fleet/next.js.map +1 -1
  604. package/dist/core/status/report.js.map +1 -1
  605. package/dist/core/status/verification.js +30 -15
  606. package/dist/core/status/verification.js.map +1 -1
  607. package/dist/core/tasks/packet.js +174 -0
  608. package/dist/core/tasks/packet.js.map +1 -0
  609. package/dist/core/tasks/record.js +43 -0
  610. package/dist/core/tasks/record.js.map +1 -0
  611. package/dist/core/tasks/sources.js +181 -0
  612. package/dist/core/tasks/sources.js.map +1 -0
  613. package/dist/core/tasks/stamps.js +76 -0
  614. package/dist/core/tasks/stamps.js.map +1 -0
  615. package/dist/core/tasks/unit.js +0 -0
  616. package/dist/core/tasks/unit.js.map +1 -0
  617. package/dist/core/usecases/capability.js +11 -14
  618. package/dist/core/usecases/capability.js.map +1 -1
  619. package/dist/core/usecases/delta/flows.js +5 -7
  620. package/dist/core/usecases/delta/flows.js.map +1 -1
  621. package/dist/core/usecases/fleet.js +178 -31
  622. package/dist/core/usecases/fleet.js.map +1 -1
  623. package/dist/core/usecases/operations.js +5 -4
  624. package/dist/core/usecases/operations.js.map +1 -1
  625. package/dist/core/usecases/service/flows.js +9 -6
  626. package/dist/core/usecases/service/flows.js.map +1 -1
  627. package/dist/core/usecases/suites/participants.js +83 -0
  628. package/dist/core/usecases/suites/participants.js.map +1 -0
  629. package/dist/core/usecases/suites/suites.js +45 -0
  630. package/dist/core/usecases/suites/suites.js.map +1 -0
  631. package/dist/core/verify/answers.js +12 -5
  632. package/dist/core/verify/answers.js.map +1 -1
  633. package/dist/core/verify/build.js +102 -82
  634. package/dist/core/verify/build.js.map +1 -1
  635. package/dist/core/verify/checklist.js +45 -10
  636. package/dist/core/verify/checklist.js.map +1 -1
  637. package/dist/core/verify/location.js +67 -0
  638. package/dist/core/verify/location.js.map +1 -0
  639. package/dist/core/verify/pins/lint.js +3 -2
  640. package/dist/core/verify/pins/lint.js.map +1 -1
  641. package/dist/core/verify/pins/pin.js +1 -1
  642. package/dist/core/verify/pins/pin.js.map +1 -1
  643. package/dist/core/verify/read/file.js +224 -0
  644. package/dist/core/verify/read/file.js.map +1 -0
  645. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  646. package/dist/core/verify/read/shape.js.map +1 -0
  647. package/dist/core/verify/read/union.js +105 -0
  648. package/dist/core/verify/read/union.js.map +1 -0
  649. package/dist/core/verify/record.js +35 -12
  650. package/dist/core/verify/record.js.map +1 -1
  651. package/dist/core/verify/store/commit.js +40 -30
  652. package/dist/core/verify/store/commit.js.map +1 -1
  653. package/dist/core/verify/store/render.js +8 -4
  654. package/dist/core/verify/store/render.js.map +1 -1
  655. package/dist/core/vocabulary/issue.js +1 -0
  656. package/dist/core/vocabulary/issue.js.map +1 -1
  657. package/dist/core/vocabulary/maturity.js +53 -3
  658. package/dist/core/vocabulary/maturity.js.map +1 -1
  659. package/examples/README.md +28 -9
  660. package/examples/docs/architecture/landscape.likec4 +21 -7
  661. package/examples/docs/architecture/services.likec4 +70 -0
  662. package/examples/docs/architecture/subsystems.likec4 +3 -0
  663. package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
  664. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  665. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  666. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  667. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  668. package/examples/docs/services/order-service/model.likec4 +10 -8
  669. package/examples/docs/services/payment-service/model.likec4 +6 -4
  670. package/examples/docs/services/platform/identity-service/model.likec4 +9 -7
  671. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  672. package/examples/docs/services/platform/notification-service/model.likec4 +4 -5
  673. package/package.json +1 -1
  674. package/dist/commands/validate/checks/vocabulary.js +0 -16
  675. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  676. package/dist/core/agent/agents-md/artifacts.js +0 -221
  677. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  678. package/dist/core/agent/agents-md/cycle.js +0 -180
  679. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  680. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  681. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  682. package/dist/core/agent/agents-md/spine.js +0 -40
  683. package/dist/core/agent/agents-md/spine.js.map +0 -1
  684. package/dist/core/coherence/events/merged.js +0 -40
  685. package/dist/core/coherence/events/merged.js.map +0 -1
  686. package/dist/core/diff/current-state.js +0 -96
  687. package/dist/core/diff/current-state.js.map +0 -1
  688. package/dist/core/diff/victims.js +0 -256
  689. package/dist/core/diff/victims.js.map +0 -1
  690. package/dist/core/staging/snapshot.js.map +0 -1
  691. package/dist/core/verify/file.js.map +0 -1
@@ -13,7 +13,8 @@
13
13
  * `./record.ts`, so a value edge back into `merge/` would be a package cycle.
14
14
  * That is also why an unreadable document is answered with "nothing to pin"
15
15
  * instead of `OpenapiMergeError`: every caller chains this AFTER
16
- * `pinOpenapiOperations`, which has already thrown that error for any document
16
+ * `pinOpenapiOperations` (on the event axis, after `pinAsyncapiSlots` and its
17
+ * `AsyncapiMergeError`), which has already thrown that error for any document
17
18
  * either side cannot parse, so the silent answer here is unreachable in
18
19
  * practice and merely keeps the failure ownership where it already lives.
19
20
  */
@@ -27,11 +28,22 @@ import { buildRecord, entryFor, readBaselineRecord, restatedSurfaces, surfaceIn
27
28
  * the living one. The entry is always `valueDigest` of the LIVING value, never
28
29
  * of the delta's own — the one rule that yields both merge verdicts: a surface
29
30
  * the author only QUOTED equals its own entry, one they EDITED differs from it.
31
+ *
30
32
  * A surface with no living counterpart is `unresolved` and deliberately absent
31
- * from the record: there is no baseline to claim, and inventing one would make
32
- * `classifyBaselineDigests` call a genuinely new value `unfounded`.
33
+ * from the record. Stamping a pin against nothing is not a harmless extra key:
34
+ * `classifyBaselineDigests` calls a pin with no living side `unfounded`, and
35
+ * the gate then refuses the delta as `openapi.baseline-invalid` (or
36
+ * `asyncapi.baseline-invalid`) — rebase would be writing the file the gate
37
+ * sends its author back to rebase.
38
+ *
39
+ * `surfacesOf` is the one thing the two contract axes do differently: the
40
+ * OpenAPI default walks `paths` and `components`, and the event axis passes
41
+ * `asyncapiSurfaces` (core/asyncapi/baseline/surfaces.ts says why it is a
42
+ * separate enumeration). Everything after the walk — the record, its sorted
43
+ * writer, the idempotence check — is axis-neutral, which is why the event
44
+ * planner is this function rather than a copy of it.
33
45
  */
34
- export function planOpenapiBaselines(featureText, livingText, _service) {
46
+ export function planOpenapiBaselines(featureText, livingText, surfacesOf = restatedSurfaces) {
35
47
  const doc = parseDocument(featureText);
36
48
  if (doc.errors.length > 0)
37
49
  return { text: null, pins: [] };
@@ -51,11 +63,12 @@ export function planOpenapiBaselines(featureText, livingText, _service) {
51
63
  const pathItems = new Map();
52
64
  const components = new Map();
53
65
  const pins = [];
54
- for (const surface of restatedSurfaces(featPlain)) {
66
+ for (const surface of surfacesOf(featPlain)) {
55
67
  // A malformed current entry reads back as absent (readBaselineRecord drops
56
68
  // it into `problems`), so this run reports `pinned` and overwrites it —
57
- // rebase is the command the gate's `openapi.baseline-invalid` sends people
58
- // to, and it must repair what it names.
69
+ // rebase is the command the gate's `openapi.baseline-invalid` (and
70
+ // `asyncapi.baseline-invalid`) sends people to, and it must repair what it
71
+ // names.
59
72
  const from = entryFor(current, surface);
60
73
  const base = {
61
74
  kind: surface.kind,
@@ -85,7 +98,8 @@ export function planOpenapiBaselines(featureText, livingText, _service) {
85
98
  }
86
99
  // The written form drops empty sections, and the whole key when both are —
87
100
  // a bare `x-loam-baselines: {}` would be bookkeeping about nothing, in every
88
- // delta whose surfaces are all new.
101
+ // delta whose surfaces are all new. An AsyncAPI document has no path items,
102
+ // so on that axis `pathItems` is always the empty section dropped here.
89
103
  const rebuilt = buildRecord({ pathItems, components });
90
104
  const written = {};
91
105
  if (Object.keys(rebuilt.pathItems).length > 0)
@@ -1 +1 @@
1
- {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../../src/core/openapi/baseline/plan.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What `loam rebase` writes on the surface axis: the `x-loam-baselines` record,\n * rebuilt wholesale from what is on disk right now.\n *\n * The record is rebuilt rather than patched for the same reason the operation\n * pin is always the LIVING digest: a pin is a claim about what its author read,\n * and an entry left behind for a surface the delta no longer restates — or one\n * whose living counterpart is gone — is a claim nobody is making any more.\n * Wholesale rebuild prunes both without a second code path, and the sorted\n * serialization (`buildRecord`) is what makes a second run byte-identical.\n *\n * This module must not import from `../merge/` — the merge reads\n * `./record.ts`, so a value edge back into `merge/` would be a package cycle.\n * That is also why an unreadable document is answered with \"nothing to pin\"\n * instead of `OpenapiMergeError`: every caller chains this AFTER\n * `pinOpenapiOperations`, which has already thrown that error for any document\n * either side cannot parse, so the silent answer here is unreachable in\n * practice and merely keeps the failure ownership where it already lives.\n */\nimport { isDeepStrictEqual } from \"node:util\";\nimport { parseDocument } from \"yaml\";\nimport { isRecord } from \"../../kernel/records.js\";\nimport { OPENAPI_BASELINES_KEY, valueDigest } from \"../digest.js\";\nimport { buildRecord, entryFor, readBaselineRecord, restatedSurfaces, surfaceIn } from \"./record.js\";\n\n/** What happened to one restated surface's baseline entry. */\nexport interface SurfacePin {\n /** Which surface family the entry pins. */\n kind: \"path-item\" | \"component\";\n /** `/orders '<key>'` for a path-item key; `<kind>/<name>` for a component. */\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 OpenapiBaselinePlan {\n /** The rewritten contract, or null when the record already says what this run would write. */\n text: string | null;\n pins: SurfacePin[];\n}\n\n/**\n * Pin every restated path-item key and component of a feature contract against\n * the living one. The entry is always `valueDigest` of the LIVING value, never\n * of the delta's own — the one rule that yields both merge verdicts: a surface\n * the author only QUOTED equals its own entry, one they EDITED differs from it.\n * A surface with no living counterpart is `unresolved` and deliberately absent\n * from the record: there is no baseline to claim, and inventing one would make\n * `classifyBaselineDigests` call a genuinely new value `unfounded`.\n */\nexport function planOpenapiBaselines(\n featureText: string,\n livingText: string,\n _service: string,\n): OpenapiBaselinePlan {\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 pathItems = new Map<string, Map<string, string>>();\n const components = new Map<string, string>();\n const pins: SurfacePin[] = [];\n for (const surface of restatedSurfaces(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 the gate's `openapi.baseline-invalid` sends people\n // to, and it must repair what it names.\n const from = entryFor(current, surface);\n const base = {\n kind: surface.kind,\n target: surface.kind === \"path-item\" ? `${surface.path} '${surface.key}'` : surface.id,\n } as const;\n const livingValue = surfaceIn(livingPlain, surface);\n if (!livingValue.found) {\n pins.push({ ...base, status: \"unresolved\", from: from ?? null, to: null });\n continue;\n }\n const digest = valueDigest(livingValue.value);\n if (surface.kind === \"path-item\") {\n let keys = pathItems.get(surface.path);\n if (keys === undefined) pathItems.set(surface.path, (keys = new Map()));\n keys.set(surface.key, digest);\n } else {\n components.set(surface.id, digest);\n }\n pins.push({\n ...base,\n status: from === digest ? \"unchanged\" : from === undefined ? \"pinned\" : \"repinned\",\n from: from ?? null,\n to: digest,\n });\n }\n\n // The written form drops empty sections, and the whole key when both are —\n // a bare `x-loam-baselines: {}` would be bookkeeping about nothing, in every\n // delta whose surfaces are all new.\n const rebuilt = buildRecord({ pathItems, components });\n const written: Record<string, unknown> = {};\n if (Object.keys(rebuilt.pathItems).length > 0) written[\"pathItems\"] = rebuilt.pathItems;\n if (Object.keys(rebuilt.components).length > 0) written[\"components\"] = rebuilt.components;\n const raw = isRecord(featPlain) ? featPlain[OPENAPI_BASELINES_KEY] : undefined;\n if (Object.keys(written).length === 0) {\n if (raw === undefined) return { text: null, pins };\n doc.deleteIn([OPENAPI_BASELINES_KEY]);\n return { text: doc.toString(), pins };\n }\n // Deep-equal, not byte-equal: the comparison must ignore key order exactly as\n // the digests do, so a record an author reordered by hand is left in their\n // order — and a second run over this function's own sorted output finds the\n // parse equal and writes nothing, which is the byte-for-byte idempotence\n // `loam rebase` promises.\n if (isDeepStrictEqual(raw, written)) return { text: null, pins };\n doc.setIn([OPENAPI_BASELINES_KEY], written);\n return { text: doc.toString(), pins };\n}\n"]}
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../../src/core/openapi/baseline/plan.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What `loam rebase` writes on the surface axis: the `x-loam-baselines` record,\n * rebuilt wholesale from what is on disk right now.\n *\n * The record is rebuilt rather than patched for the same reason the operation\n * pin is always the LIVING digest: a pin is a claim about what its author read,\n * and an entry left behind for a surface the delta no longer restates — or one\n * whose living counterpart is gone — is a claim nobody is making any more.\n * Wholesale rebuild prunes both without a second code path, and the sorted\n * serialization (`buildRecord`) is what makes a second run byte-identical.\n *\n * This module must not import from `../merge/` — the merge reads\n * `./record.ts`, so a value edge back into `merge/` would be a package cycle.\n * That is also why an unreadable document is answered with \"nothing to pin\"\n * instead of `OpenapiMergeError`: every caller chains this AFTER\n * `pinOpenapiOperations` (on the event axis, after `pinAsyncapiSlots` and its\n * `AsyncapiMergeError`), which has already thrown that error for any document\n * either side cannot parse, so the silent answer here is unreachable in\n * practice and merely keeps the failure ownership where it already lives.\n */\nimport { isDeepStrictEqual } from \"node:util\";\nimport { parseDocument } from \"yaml\";\nimport { isRecord } from \"../../kernel/records.js\";\nimport { OPENAPI_BASELINES_KEY, valueDigest } from \"../digest.js\";\nimport { buildRecord, entryFor, readBaselineRecord, restatedSurfaces, surfaceIn, type RestatedSurface } from \"./record.js\";\n\n/** What happened to one restated surface's baseline entry. */\nexport interface SurfacePin {\n /** Which surface family the entry pins. */\n kind: \"path-item\" | \"component\";\n /** `/orders '<key>'` for a path-item key; `<kind>/<name>` for a component. */\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 OpenapiBaselinePlan {\n /** The rewritten contract, or null when the record already says what this run would write. */\n text: string | null;\n pins: SurfacePin[];\n}\n\n/**\n * Pin every restated path-item key and component of a feature contract against\n * the living one. The entry is always `valueDigest` of the LIVING value, never\n * of the delta's own — the one rule that yields both merge verdicts: a surface\n * the 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 `openapi.baseline-invalid` (or\n * `asyncapi.baseline-invalid`) — rebase would be writing the file the gate\n * sends its author back to rebase.\n *\n * `surfacesOf` is the one thing the two contract axes do differently: the\n * OpenAPI default walks `paths` and `components`, and the event axis passes\n * `asyncapiSurfaces` (core/asyncapi/baseline/surfaces.ts says why it is a\n * separate enumeration). Everything after the walk — the record, its sorted\n * writer, the idempotence check — is axis-neutral, which is why the event\n * planner is this function rather than a copy of it.\n */\nexport function planOpenapiBaselines(\n featureText: string,\n livingText: string,\n surfacesOf: (plain: unknown) => readonly RestatedSurface[] = restatedSurfaces,\n): OpenapiBaselinePlan {\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 pathItems = new Map<string, Map<string, string>>();\n const components = new Map<string, string>();\n const pins: SurfacePin[] = [];\n for (const surface of surfacesOf(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 the gate's `openapi.baseline-invalid` (and\n // `asyncapi.baseline-invalid`) sends people to, and it must repair what it\n // names.\n const from = entryFor(current, surface);\n const base = {\n kind: surface.kind,\n target: surface.kind === \"path-item\" ? `${surface.path} '${surface.key}'` : surface.id,\n } as const;\n const livingValue = surfaceIn(livingPlain, surface);\n if (!livingValue.found) {\n pins.push({ ...base, status: \"unresolved\", from: from ?? null, to: null });\n continue;\n }\n const digest = valueDigest(livingValue.value);\n if (surface.kind === \"path-item\") {\n let keys = pathItems.get(surface.path);\n if (keys === undefined) pathItems.set(surface.path, (keys = new Map()));\n keys.set(surface.key, digest);\n } else {\n components.set(surface.id, digest);\n }\n pins.push({\n ...base,\n status: from === digest ? \"unchanged\" : from === undefined ? \"pinned\" : \"repinned\",\n from: from ?? null,\n to: digest,\n });\n }\n\n // The written form drops empty sections, and the whole key when both are —\n // a bare `x-loam-baselines: {}` would be bookkeeping about nothing, in every\n // delta whose surfaces are all new. An AsyncAPI document has no path items,\n // so on that axis `pathItems` is always the empty section dropped here.\n const rebuilt = buildRecord({ pathItems, components });\n const written: Record<string, unknown> = {};\n if (Object.keys(rebuilt.pathItems).length > 0) written[\"pathItems\"] = rebuilt.pathItems;\n if (Object.keys(rebuilt.components).length > 0) written[\"components\"] = rebuilt.components;\n const raw = isRecord(featPlain) ? featPlain[OPENAPI_BASELINES_KEY] : undefined;\n if (Object.keys(written).length === 0) {\n if (raw === undefined) return { text: null, pins };\n doc.deleteIn([OPENAPI_BASELINES_KEY]);\n return { text: doc.toString(), pins };\n }\n // Deep-equal, not byte-equal: the comparison must ignore key order exactly as\n // the digests do, so a record an author reordered by hand is left in their\n // order — and a second run over this function's own sorted output finds the\n // parse equal and writes nothing, which is the byte-for-byte idempotence\n // `loam rebase` promises.\n if (isDeepStrictEqual(raw, written)) return { text: null, pins };\n doc.setIn([OPENAPI_BASELINES_KEY], written);\n return { text: doc.toString(), pins };\n}\n"]}
@@ -12,7 +12,6 @@
12
12
  import { isRecord } from "../../kernel/records.js";
13
13
  import { HTTP_METHODS } from "../doc.js";
14
14
  import { FEATURE_ONLY_KEYS, OPENAPI_BASELINES_KEY, OPERATION_DIGEST_RE } from "../digest.js";
15
- export const EMPTY_RECORD = { pathItems: {}, components: {} };
16
15
  /** The record a plain-parsed feature document carries, plus everything wrong with it. */
17
16
  export function readBaselineRecord(plain) {
18
17
  const problems = [];
@@ -1 +1 @@
1
- {"version":3,"file":"record.js","sourceRoot":"","sources":["../../../../src/core/openapi/baseline/record.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The `x-loam-baselines` record: what it holds, how it reads back, and which\n * of a feature document's surfaces it may speak for.\n *\n * One enumeration (`restatedSurfaces`) shared by the rebase plan, the gate and\n * the merge, so the three cannot drift into different ideas of what a feature\n * \"restates\" — the exact drift the operation axis's shared classifier exists\n * to prevent. Reading is deliberately permissive-with-problems: a malformed\n * record is DESCRIBED (`problems`), never diagnosed here — the gate refuses\n * it, parsers never print, which is doc.ts's own doctrine.\n */\nimport { isRecord } from \"../../kernel/records.js\";\nimport { HTTP_METHODS } from \"../doc.js\";\nimport { FEATURE_ONLY_KEYS, OPENAPI_BASELINES_KEY, OPERATION_DIGEST_RE } from \"../digest.js\";\n\n/** The record as read: per-(path, key) digests and per-(kind/name) digests. */\nexport interface BaselineRecord {\n pathItems: Record<string, Record<string, string>>;\n components: Record<string, string>;\n}\n\nexport const EMPTY_RECORD: BaselineRecord = { pathItems: {}, components: {} };\n\n/** One surface a feature restates: a path-item non-method key, or a component. */\nexport type RestatedSurface =\n | { kind: \"path-item\"; path: string; key: string; value: unknown }\n | { kind: \"component\"; id: string; value: unknown };\n\n/** The record a plain-parsed feature document carries, plus everything wrong with it. */\nexport function readBaselineRecord(plain: unknown): { record: BaselineRecord; problems: string[] } {\n const problems: string[] = [];\n const record: BaselineRecord = { pathItems: {}, components: {} };\n if (!isRecord(plain)) return { record, problems };\n const raw = plain[OPENAPI_BASELINES_KEY];\n if (raw === undefined) return { record, problems };\n if (!isRecord(raw)) {\n problems.push(`${OPENAPI_BASELINES_KEY} is not a mapping`);\n return { record, problems };\n }\n const items = raw[\"pathItems\"];\n if (items !== undefined) {\n if (!isRecord(items)) problems.push(`${OPENAPI_BASELINES_KEY}.pathItems is not a mapping`);\n else {\n for (const [path, keys] of Object.entries(items)) {\n if (!isRecord(keys)) {\n problems.push(`${OPENAPI_BASELINES_KEY}.pathItems['${path}'] is not a mapping`);\n continue;\n }\n for (const [key, digest] of Object.entries(keys)) {\n if (typeof digest !== \"string\" || !OPERATION_DIGEST_RE.test(digest)) {\n problems.push(`${OPENAPI_BASELINES_KEY}.pathItems['${path}']['${key}'] is not a 16-hex digest`);\n continue;\n }\n (record.pathItems[path] ??= {})[key] = digest;\n }\n }\n }\n }\n const comps = raw[\"components\"];\n if (comps !== undefined) {\n if (!isRecord(comps)) problems.push(`${OPENAPI_BASELINES_KEY}.components is not a mapping`);\n else {\n for (const [id, digest] of Object.entries(comps)) {\n if (typeof digest !== \"string\" || !OPERATION_DIGEST_RE.test(digest)) {\n problems.push(`${OPENAPI_BASELINES_KEY}.components['${id}'] is not a 16-hex digest`);\n continue;\n }\n record.components[id] = digest;\n }\n }\n }\n for (const key of Object.keys(raw)) {\n if (key !== \"pathItems\" && key !== \"components\") problems.push(`${OPENAPI_BASELINES_KEY}.${key} is not a recognised section`);\n }\n return { record, problems };\n}\n\n/**\n * Every surface this feature document RESTATES — the enumeration the plan\n * pins, the gate grades and the merge classifies. Path-item surfaces are the\n * non-HTTP-method keys (the feature-only markers are bookkeeping, not\n * surfaces; a METHOD whose value is a removal marker is the operation pin's\n * territory — but a non-method key is a surface whatever its value looks\n * like, because skipping the removal SHAPE here once let it through unpinned\n * and the merge wrote `{x-loam-remove: true}` over a living shared\n * `parameters` at exit 0). Component surfaces are every\n * `components/<kind>/<name>` the document declares.\n */\nexport function restatedSurfaces(plain: unknown): RestatedSurface[] {\n const out: RestatedSurface[] = [];\n if (!isRecord(plain)) return out;\n const paths = plain[\"paths\"];\n if (isRecord(paths)) {\n for (const [path, item] of Object.entries(paths)) {\n if (!isRecord(item)) continue;\n for (const [key, value] of Object.entries(item)) {\n // Methods are the operation pin's territory and the feature-only\n // markers are bookkeeping; everything else is a surface — INCLUDING a\n // non-method key whose value happens to be removal-shaped. Skipping\n // that shape here once let it through unpinned and ungraded, and the\n // merge then wrote `{x-loam-remove: true}` over a living shared\n // `parameters` array at exit 0. The merge's own publishable filter\n // (markers.ts withoutFeatureMarkers) must keep agreeing with this\n // pair of exclusions — the surfaces test pins the agreement.\n if (HTTP_METHODS.has(key) || FEATURE_ONLY_KEYS.has(key)) continue;\n out.push({ kind: \"path-item\", path, key, value });\n }\n }\n }\n const comps = plain[\"components\"];\n if (isRecord(comps)) {\n for (const [kind, names] of Object.entries(comps)) {\n if (!isRecord(names)) continue;\n for (const [name, value] of Object.entries(names)) {\n out.push({ kind: \"component\", id: `${kind}/${name}`, value });\n }\n }\n }\n return out;\n}\n\n/**\n * The value one plain-parsed document holds for one surface, `found`\n * distinguishing \"absent\" from a legal null. ONE spelling for the plan and the\n * gate, because \"does the living contract have this surface\" is half of every\n * verdict — two lookups could disagree about absence, and the disagreement\n * would surface as a pin graded against the wrong baseline.\n */\n/**\n * The record's entry for one surface, or undefined — the mirror of\n * `surfaceIn`, and one spelling for the same reason: four open-coded lookups\n * could disagree about absence, and a disagreement surfaces as a pin graded\n * against the wrong baseline.\n */\nexport function entryFor(record: BaselineRecord, surface: RestatedSurface): string | undefined {\n return surface.kind === \"path-item\" ? record.pathItems[surface.path]?.[surface.key] : record.components[surface.id];\n}\n\nexport function surfaceIn(plain: unknown, surface: RestatedSurface): { found: boolean; value: unknown } {\n const absent = { found: false, value: undefined };\n if (!isRecord(plain)) return absent;\n if (surface.kind === \"path-item\") {\n const paths = plain[\"paths\"];\n if (!isRecord(paths)) return absent;\n const item = paths[surface.path];\n if (!isRecord(item) || !(surface.key in item)) return absent;\n return { found: true, value: item[surface.key] };\n }\n // The first `/` is the kind boundary, exactly as the merge's `#/components/`\n // regex reads it — OpenAPI component names cannot legally contain one.\n const cut = surface.id.indexOf(\"/\");\n const comps = plain[\"components\"];\n if (!isRecord(comps)) return absent;\n const names = comps[surface.id.slice(0, cut)];\n const name = surface.id.slice(cut + 1);\n if (!isRecord(names) || !(name in names)) return absent;\n return { found: true, value: names[name] };\n}\n\n/**\n * The record as rebase writes it: lexicographically sorted at every level, so\n * a second run is byte-identical and diffs stay readable.\n */\nexport function buildRecord(pins: { pathItems: Map<string, Map<string, string>>; components: Map<string, string> }): BaselineRecord {\n const sorted = (keys: Iterable<string>): string[] => [...keys].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));\n const record: BaselineRecord = { pathItems: {}, components: {} };\n for (const path of sorted(pins.pathItems.keys())) {\n const keys = pins.pathItems.get(path)!;\n const entry: Record<string, string> = {};\n for (const key of sorted(keys.keys())) entry[key] = keys.get(key)!;\n record.pathItems[path] = entry;\n }\n for (const id of sorted(pins.components.keys())) record.components[id] = pins.components.get(id)!;\n return record;\n}\n"]}
1
+ {"version":3,"file":"record.js","sourceRoot":"","sources":["../../../../src/core/openapi/baseline/record.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The `x-loam-baselines` record: what it holds, how it reads back, and which\n * of a feature document's surfaces it may speak for.\n *\n * One enumeration (`restatedSurfaces`) shared by the rebase plan, the gate and\n * the merge, so the three cannot drift into different ideas of what a feature\n * \"restates\" — the exact drift the operation axis's shared classifier exists\n * to prevent. Reading is deliberately permissive-with-problems: a malformed\n * record is DESCRIBED (`problems`), never diagnosed here — the gate refuses\n * it, parsers never print, which is doc.ts's own doctrine.\n */\nimport { isRecord } from \"../../kernel/records.js\";\nimport { HTTP_METHODS } from \"../doc.js\";\nimport { FEATURE_ONLY_KEYS, OPENAPI_BASELINES_KEY, OPERATION_DIGEST_RE } from \"../digest.js\";\n\n/** The record as read: per-(path, key) digests and per-(kind/name) digests. */\nexport interface BaselineRecord {\n pathItems: Record<string, Record<string, string>>;\n components: Record<string, string>;\n}\n\n/** One surface a feature restates: a path-item non-method key, or a component. */\nexport type RestatedSurface =\n | { kind: \"path-item\"; path: string; key: string; value: unknown }\n | { kind: \"component\"; id: string; value: unknown };\n\n/** The record a plain-parsed feature document carries, plus everything wrong with it. */\nexport function readBaselineRecord(plain: unknown): { record: BaselineRecord; problems: string[] } {\n const problems: string[] = [];\n const record: BaselineRecord = { pathItems: {}, components: {} };\n if (!isRecord(plain)) return { record, problems };\n const raw = plain[OPENAPI_BASELINES_KEY];\n if (raw === undefined) return { record, problems };\n if (!isRecord(raw)) {\n problems.push(`${OPENAPI_BASELINES_KEY} is not a mapping`);\n return { record, problems };\n }\n const items = raw[\"pathItems\"];\n if (items !== undefined) {\n if (!isRecord(items)) problems.push(`${OPENAPI_BASELINES_KEY}.pathItems is not a mapping`);\n else {\n for (const [path, keys] of Object.entries(items)) {\n if (!isRecord(keys)) {\n problems.push(`${OPENAPI_BASELINES_KEY}.pathItems['${path}'] is not a mapping`);\n continue;\n }\n for (const [key, digest] of Object.entries(keys)) {\n if (typeof digest !== \"string\" || !OPERATION_DIGEST_RE.test(digest)) {\n problems.push(`${OPENAPI_BASELINES_KEY}.pathItems['${path}']['${key}'] is not a 16-hex digest`);\n continue;\n }\n (record.pathItems[path] ??= {})[key] = digest;\n }\n }\n }\n }\n const comps = raw[\"components\"];\n if (comps !== undefined) {\n if (!isRecord(comps)) problems.push(`${OPENAPI_BASELINES_KEY}.components is not a mapping`);\n else {\n for (const [id, digest] of Object.entries(comps)) {\n if (typeof digest !== \"string\" || !OPERATION_DIGEST_RE.test(digest)) {\n problems.push(`${OPENAPI_BASELINES_KEY}.components['${id}'] is not a 16-hex digest`);\n continue;\n }\n record.components[id] = digest;\n }\n }\n }\n for (const key of Object.keys(raw)) {\n if (key !== \"pathItems\" && key !== \"components\") problems.push(`${OPENAPI_BASELINES_KEY}.${key} is not a recognised section`);\n }\n return { record, problems };\n}\n\n/**\n * Every surface this feature document RESTATES — the enumeration the plan\n * pins, the gate grades and the merge classifies. Path-item surfaces are the\n * non-HTTP-method keys (the feature-only markers are bookkeeping, not\n * surfaces; a METHOD whose value is a removal marker is the operation pin's\n * territory — but a non-method key is a surface whatever its value looks\n * like, because skipping the removal SHAPE here once let it through unpinned\n * and the merge wrote `{x-loam-remove: true}` over a living shared\n * `parameters` at exit 0). Component surfaces are every\n * `components/<kind>/<name>` the document declares.\n */\nexport function restatedSurfaces(plain: unknown): RestatedSurface[] {\n const out: RestatedSurface[] = [];\n if (!isRecord(plain)) return out;\n const paths = plain[\"paths\"];\n if (isRecord(paths)) {\n for (const [path, item] of Object.entries(paths)) {\n if (!isRecord(item)) continue;\n for (const [key, value] of Object.entries(item)) {\n // Methods are the operation pin's territory and the feature-only\n // markers are bookkeeping; everything else is a surface — INCLUDING a\n // non-method key whose value happens to be removal-shaped. Skipping\n // that shape here once let it through unpinned and ungraded, and the\n // merge then wrote `{x-loam-remove: true}` over a living shared\n // `parameters` array at exit 0. The merge's own publishable filter\n // (markers.ts withoutFeatureMarkers) must keep agreeing with this\n // pair of exclusions — the surfaces test pins the agreement.\n if (HTTP_METHODS.has(key) || FEATURE_ONLY_KEYS.has(key)) continue;\n out.push({ kind: \"path-item\", path, key, value });\n }\n }\n }\n const comps = plain[\"components\"];\n if (isRecord(comps)) {\n for (const [kind, names] of Object.entries(comps)) {\n if (!isRecord(names)) continue;\n for (const [name, value] of Object.entries(names)) {\n out.push({ kind: \"component\", id: `${kind}/${name}`, value });\n }\n }\n }\n return out;\n}\n\n/**\n * The value one plain-parsed document holds for one surface, `found`\n * distinguishing \"absent\" from a legal null. ONE spelling for the plan and the\n * gate, because \"does the living contract have this surface\" is half of every\n * verdict — two lookups could disagree about absence, and the disagreement\n * would surface as a pin graded against the wrong baseline.\n */\n/**\n * The record's entry for one surface, or undefined — the mirror of\n * `surfaceIn`, and one spelling for the same reason: four open-coded lookups\n * could disagree about absence, and a disagreement surfaces as a pin graded\n * against the wrong baseline.\n */\nexport function entryFor(record: BaselineRecord, surface: RestatedSurface): string | undefined {\n return surface.kind === \"path-item\" ? record.pathItems[surface.path]?.[surface.key] : record.components[surface.id];\n}\n\nexport function surfaceIn(plain: unknown, surface: RestatedSurface): { found: boolean; value: unknown } {\n const absent = { found: false, value: undefined };\n if (!isRecord(plain)) return absent;\n if (surface.kind === \"path-item\") {\n const paths = plain[\"paths\"];\n if (!isRecord(paths)) return absent;\n const item = paths[surface.path];\n if (!isRecord(item) || !(surface.key in item)) return absent;\n return { found: true, value: item[surface.key] };\n }\n // The first `/` is the kind boundary, exactly as the merge's `#/components/`\n // regex reads it — OpenAPI component names cannot legally contain one.\n const cut = surface.id.indexOf(\"/\");\n const comps = plain[\"components\"];\n if (!isRecord(comps)) return absent;\n const names = comps[surface.id.slice(0, cut)];\n const name = surface.id.slice(cut + 1);\n if (!isRecord(names) || !(name in names)) return absent;\n return { found: true, value: names[name] };\n}\n\n/**\n * The record as rebase writes it: lexicographically sorted at every level, so\n * a second run is byte-identical and diffs stay readable.\n */\nexport function buildRecord(pins: { pathItems: Map<string, Map<string, string>>; components: Map<string, string> }): BaselineRecord {\n const sorted = (keys: Iterable<string>): string[] => [...keys].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));\n const record: BaselineRecord = { pathItems: {}, components: {} };\n for (const path of sorted(pins.pathItems.keys())) {\n const keys = pins.pathItems.get(path)!;\n const entry: Record<string, string> = {};\n for (const key of sorted(keys.keys())) entry[key] = keys.get(key)!;\n record.pathItems[path] = entry;\n }\n for (const id of sorted(pins.components.keys())) record.components[id] = pins.components.get(id)!;\n return record;\n}\n"]}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The decode-tolerant read of a contract's text, shared by both contract axes'
3
+ * feature gates.
4
+ *
5
+ * A leaf of its own, named for what it does, at the lowest package both gates
6
+ * already import: kept in the event axis's walk, the OpenAPI gate reached a
7
+ * general-purpose read through a module about AsyncAPI deltas.
8
+ */
9
+ import { readFile } from "node:fs/promises";
10
+ import { NotUtf8DocumentError } from "../kernel/document-bytes.js";
11
+ /**
12
+ * Read a document's text, answering undefined for bytes that do not decode.
13
+ *
14
+ * `context.readText` DECODES and THROWS on non-UTF-8 bytes, where the
15
+ * no-context `readFile` substitutes U+FFFD and lets the contract reader flag
16
+ * the document unreadable. Uncaught, one service's UTF-16 living contract took
17
+ * down the whole feature target under validate's context while archive's
18
+ * no-context path sailed past. Either text failing to decode gets the answer
19
+ * the baseline gates give an unreadable parse: that side's checks are skipped.
20
+ * Both contract axes' feature gates read this way: ../coherence/declared.ts for
21
+ * OpenAPI and ../coherence/events/declared.ts for AsyncAPI.
22
+ */
23
+ export async function readOr(p, context) {
24
+ try {
25
+ return context === undefined ? await readFile(p, "utf8") : await context.readText(p);
26
+ }
27
+ catch (err) {
28
+ if (err instanceof NotUtf8DocumentError)
29
+ return undefined;
30
+ throw err;
31
+ }
32
+ }
33
+ //# sourceMappingURL=contract-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-text.js","sourceRoot":"","sources":["../../../src/core/openapi/contract-text.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAGnE;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,CAAS,EAAE,OAAsB;IAC5D,IAAI,CAAC;QACH,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IACvF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,oBAAoB;YAAE,OAAO,SAAS,CAAC;QAC1D,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC","sourcesContent":["/**\n * The decode-tolerant read of a contract's text, shared by both contract axes'\n * feature gates.\n *\n * A leaf of its own, named for what it does, at the lowest package both gates\n * already import: kept in the event axis's walk, the OpenAPI gate reached a\n * general-purpose read through a module about AsyncAPI deltas.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { NotUtf8DocumentError } from \"../kernel/document-bytes.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\n\n/**\n * Read a document's text, answering undefined for bytes that do not decode.\n *\n * `context.readText` DECODES and THROWS on non-UTF-8 bytes, where the\n * no-context `readFile` substitutes U+FFFD and lets the contract reader flag\n * the document unreadable. Uncaught, one service's UTF-16 living contract took\n * down the whole feature target under validate's context while archive's\n * no-context path sailed past. Either text failing to decode gets the answer\n * the baseline gates give an unreadable parse: that side's checks are skipped.\n * Both contract axes' feature gates read this way: ../coherence/declared.ts for\n * OpenAPI and ../coherence/events/declared.ts for AsyncAPI.\n */\nexport async function readOr(p: string, context?: FleetContext): Promise<string | undefined> {\n try {\n return context === undefined ? await readFile(p, \"utf8\") : await context.readText(p);\n } catch (err) {\n if (err instanceof NotUtf8DocumentError) return undefined;\n throw err;\n }\n}\n"]}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The one way both contract axes read and write a contract document: parse it,
3
+ * refuse its first parse error, resolve it, and hand it back as text.
4
+ *
5
+ * The helpers are axis-neutral and take the refusal to raise as a `fail`
6
+ * callback, because the event axis's six entry points spelled the same parse,
7
+ * first-error, resolve and serialize sequence the OpenAPI axis's did, against
8
+ * its own error class (../asyncapi/merge/error.ts, beside ./merge/error.ts). A
9
+ * leaf of its own, named for what it holds, at the lowest package both merge
10
+ * packages already import — not inside either axis's refusal module, where the
11
+ * event axis would read its contracts through a file named for an error.
12
+ * Import direction is asyncapi → openapi, as everywhere else on the contract
13
+ * axes.
14
+ */
15
+ import { parseDocument } from "yaml";
16
+ function errorMessage(error) {
17
+ return error instanceof Error ? error.message : String(error);
18
+ }
19
+ /**
20
+ * Parse a contract, refuse its first parse error, and resolve it. `check` runs
21
+ * between the two, on the AST: the event axis refuses an ambiguous slot key
22
+ * there, BEFORE resolution, so a document that is both ambiguous and carries
23
+ * an unresolvable alias is refused for the ambiguity, exactly as it was when
24
+ * each entry point spelled the sequence out.
25
+ */
26
+ export function readTree(text, fail, check) {
27
+ const doc = parseDocument(text);
28
+ if (doc.errors.length > 0)
29
+ throw fail(doc.errors[0].message);
30
+ check?.(doc);
31
+ return { doc, plain: resolveTree(doc, fail) };
32
+ }
33
+ /**
34
+ * Resolve aliases once with the document's own anchor context. Calling an
35
+ * individual AST node's toJSON() loses that context and can silently turn an
36
+ * aliased operation, slot or component into the wrong value. An alias that
37
+ * cannot be resolved at all is a document loam cannot read, and says so.
38
+ */
39
+ export function resolveTree(doc, fail) {
40
+ try {
41
+ return doc.toJS() ?? {};
42
+ }
43
+ catch (error) {
44
+ throw fail(errorMessage(error));
45
+ }
46
+ }
47
+ /** One spelling of "hand the document back", so every writer fails the same way. */
48
+ export function writeTree(doc, fail) {
49
+ try {
50
+ return doc.toString();
51
+ }
52
+ catch (error) {
53
+ throw fail(errorMessage(error));
54
+ }
55
+ }
56
+ //# sourceMappingURL=contract-tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-tree.js","sourceRoot":"","sources":["../../../src/core/openapi/contract-tree.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,aAAa,EAAiB,MAAM,MAAM,CAAC;AAEpD,SAAS,YAAY,CAAC,KAAc;IAClC,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC;AAQD;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,IAA+B,EAAE,KAA+B;IACrG,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,OAAO,CAAC,CAAC;IAC9D,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;IACb,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC;AAChD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,GAAa,EAAE,IAA+B;IACxE,IAAI,CAAC;QACH,OAAO,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;IAC1B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;IAClC,CAAC;AACH,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,SAAS,CAAC,GAAa,EAAE,IAA+B;IACtE,IAAI,CAAC;QACH,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;IACxB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;IAClC,CAAC;AACH,CAAC","sourcesContent":["/**\n * The one way both contract axes read and write a contract document: parse it,\n * refuse its first parse error, resolve it, and hand it back as text.\n *\n * The helpers are axis-neutral and take the refusal to raise as a `fail`\n * callback, because the event axis's six entry points spelled the same parse,\n * first-error, resolve and serialize sequence the OpenAPI axis's did, against\n * its own error class (../asyncapi/merge/error.ts, beside ./merge/error.ts). A\n * leaf of its own, named for what it holds, at the lowest package both merge\n * packages already import — not inside either axis's refusal module, where the\n * event axis would read its contracts through a file named for an error.\n * Import direction is asyncapi → openapi, as everywhere else on the contract\n * axes.\n */\nimport { parseDocument, type Document } from \"yaml\";\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** A contract's AST, for the writes, and its resolved plain tree, for every shape question. */\nexport interface ContractTree {\n doc: Document.Parsed;\n plain: unknown;\n}\n\n/**\n * Parse a contract, refuse its first parse error, and resolve it. `check` runs\n * between the two, on the AST: the event axis refuses an ambiguous slot key\n * there, BEFORE resolution, so a document that is both ambiguous and carries\n * an unresolvable alias is refused for the ambiguity, exactly as it was when\n * each entry point spelled the sequence out.\n */\nexport function readTree(text: string, fail: (detail: string) => Error, check?: (doc: Document) => void): ContractTree {\n const doc = parseDocument(text);\n if (doc.errors.length > 0) throw fail(doc.errors[0]!.message);\n check?.(doc);\n return { doc, plain: resolveTree(doc, fail) };\n}\n\n/**\n * Resolve aliases once with the document's own anchor context. Calling an\n * individual AST node's toJSON() loses that context and can silently turn an\n * aliased operation, slot or component into the wrong value. An alias that\n * cannot be resolved at all is a document loam cannot read, and says so.\n */\nexport function resolveTree(doc: Document, fail: (detail: string) => Error): unknown {\n try {\n return doc.toJS() ?? {};\n } catch (error) {\n throw fail(errorMessage(error));\n }\n}\n\n/** One spelling of \"hand the document back\", so every writer fails the same way. */\nexport function writeTree(doc: Document, fail: (detail: string) => Error): string {\n try {\n return doc.toString();\n } catch (error) {\n throw fail(errorMessage(error));\n }\n}\n"]}
@@ -10,10 +10,14 @@
10
10
  * says, only whether one was declared at all, so the reader's structure-only
11
11
  * stance holds.
12
12
  *
13
- * A local pointer walk rather than an import from `merge/components.ts`: the merge
14
- * package already imports this package (`HTTP_METHODS` from doc.ts), so the
15
- * reverse import would be a package cycle — the same reason read-side
16
- * `resolvePointer` copies exist on the asyncapi axis.
13
+ * This module used to keep a private pointer walk rather than import the
14
+ * merge's: the merge package already imports this package (`HTTP_METHODS` from
15
+ * doc.ts), so the reverse import would have been a package cycle — the same
16
+ * reason read-side `resolvePointer` copies exist on the asyncapi axis. The two
17
+ * plain `$ref` questions now live HERE, at the axis root (`collectRefs` and
18
+ * `resolvePointer`, at the bottom), so the private copy went: the dangling-ref
19
+ * probe below, the merge's component closure and the reader's closure digest
20
+ * (`closureDigester`, ./digest.ts) all ask the same two questions.
17
21
  */
18
22
  /** Status codes whose response legitimately carries no body. */
19
23
  const NO_CONTENT = new Set(["204", "304"]);
@@ -78,45 +82,55 @@ export function failureCodesOf(op) {
78
82
  * scope and never graded, matching the merge's documented stance.
79
83
  */
80
84
  export function danglingRefs(root) {
81
- const out = new Set();
82
- const walk = (node) => {
83
- if (Array.isArray(node)) {
84
- for (const value of node)
85
+ // A Set keeps first-insertion order, so dedup and document order both hold.
86
+ return [...new Set(collectRefs(root).filter((ref) => ref.startsWith("#/") && !resolvePointer(root, ref).found))];
87
+ }
88
+ /*
89
+ * `$ref` as a pair of plain questions — which ones does this tree contain, and
90
+ * what does one point at. Both lived in `./merge/components.ts`, beside the
91
+ * component closure that was their only caller, until the reader needed the same
92
+ * walk; the header says why the root and not there.
93
+ */
94
+ /** Every `$ref` string in a tree, in document order — duplicates kept, external refs included. */
95
+ export function collectRefs(node) {
96
+ const out = [];
97
+ const walk = (current) => {
98
+ if (Array.isArray(current)) {
99
+ for (const value of current)
85
100
  walk(value);
86
101
  return;
87
102
  }
88
- if (node === null || typeof node !== "object")
89
- return;
90
- for (const [key, value] of Object.entries(node)) {
91
- if (key === "$ref" && typeof value === "string") {
92
- if (value.startsWith("#/") && !resolves(root, value))
93
- out.add(value);
94
- continue;
103
+ if (current !== null && typeof current === "object") {
104
+ for (const [key, value] of Object.entries(current)) {
105
+ if (key === "$ref" && typeof value === "string")
106
+ out.push(value);
107
+ else
108
+ walk(value);
95
109
  }
96
- walk(value);
97
110
  }
98
111
  };
99
- walk(root);
100
- return [...out];
112
+ walk(node);
113
+ return out;
101
114
  }
102
- /** Does a local JSON pointer (`#/a/b~1c`) reach a node in this tree? */
103
- function resolves(root, ref) {
115
+ /** Resolve a local JSON pointer (`#/a/b~1c`) against a plain JS tree. */
116
+ export function resolvePointer(root, ref) {
104
117
  let current = root;
105
118
  for (const raw of ref.slice(2).split("/")) {
106
119
  const segment = raw.replace(/~1/g, "/").replace(/~0/g, "~");
107
120
  if (Array.isArray(current)) {
108
121
  const index = Number(segment);
109
- if (!Number.isInteger(index) || index < 0 || index >= current.length)
110
- return false;
122
+ if (!Number.isInteger(index) || index < 0 || index >= current.length) {
123
+ return { found: false, value: undefined };
124
+ }
111
125
  current = current[index];
112
126
  }
113
127
  else if (current !== null && typeof current === "object" && segment in current) {
114
128
  current = current[segment];
115
129
  }
116
130
  else {
117
- return false;
131
+ return { found: false, value: undefined };
118
132
  }
119
133
  }
120
- return true;
134
+ return { found: true, value: current };
121
135
  }
122
136
  //# sourceMappingURL=depth.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"depth.js","sourceRoot":"","sources":["../../../src/core/openapi/depth.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The contract-depth probes: does an operation say anything about what it\n * returns, and does every internal `$ref` point at something.\n *\n * Form validated and depth did not, and that was invisible from the output:\n * an operation whose one response is `description: OK`, and a `$ref` to a\n * schema nobody ever wrote, both validate exactly as cleanly as a complete\n * contract even though a reader still needs the code. Everything here is a\n * PRESENCE probe over the document's own keys: nothing reads what a schema\n * says, only whether one was declared at all, so the reader's structure-only\n * stance holds.\n *\n * A local pointer walk rather than an import from `merge/components.ts`: the merge\n * package already imports this package (`HTTP_METHODS` from doc.ts), so the\n * reverse import would be a package cycle — the same reason read-side\n * `resolvePointer` copies exist on the asyncapi axis.\n */\n\n/** Status codes whose response legitimately carries no body. */\nconst NO_CONTENT = new Set([\"204\", \"304\"]);\n\n/**\n * True when no response declares content with a schema while at least one\n * response should carry a body — an operation with no `responses` at all\n * counts, one whose only responses are 204/304 does not. A response that is\n * itself a `$ref` counts as described: somebody wrote the shape down, and the\n * dangling case is the ref probe's finding, not this one's.\n */\nexport function responseUndescribed(op: unknown): boolean {\n if (op === null || typeof op !== \"object\") return true;\n const responses = (op as Record<string, unknown>)[\"responses\"];\n if (responses === null || responses === undefined || typeof responses !== \"object\" || Array.isArray(responses)) {\n return true;\n }\n const entries = Object.entries(responses as Record<string, unknown>);\n if (entries.length === 0) return true;\n let owesBody = false;\n for (const [code, resp] of entries) {\n if (NO_CONTENT.has(code)) continue;\n owesBody = true;\n if (resp === null || typeof resp !== \"object\" || Array.isArray(resp)) continue;\n const rec = resp as Record<string, unknown>;\n if (\"$ref\" in rec) return false;\n const content = rec[\"content\"];\n if (content === null || typeof content !== \"object\" || Array.isArray(content)) continue;\n for (const media of Object.values(content as Record<string, unknown>)) {\n if (media !== null && typeof media === \"object\" && \"schema\" in (media as Record<string, unknown>)) {\n return false;\n }\n }\n }\n return owesBody;\n}\n\n/**\n * The non-2xx response codes an operation declares, in document order.\n *\n * Exact three-digit codes only. `default` names no case — it is whatever else\n * happens — and the `4XX`/`5XX` wildcards name a class rather than a refusal,\n * so no scenario could be written for either and demanding one would teach an\n * author to write a scenario about nothing. 1xx and 3xx are excluded with 2xx:\n * a redirect or a continue is not the service saying no.\n */\nexport function failureCodesOf(op: unknown): string[] {\n if (op === null || typeof op !== \"object\") return [];\n const responses = (op as Record<string, unknown>)[\"responses\"];\n if (responses === null || typeof responses !== \"object\" || Array.isArray(responses)) return [];\n return Object.keys(responses as Record<string, unknown>).filter((code) => /^[45]\\d\\d$/.test(code));\n}\n\n/**\n * Every internal `#/` reference in the document that resolves to nothing —\n * deduped, document order. External references (URLs, file paths) are out of\n * scope and never graded, matching the merge's documented stance.\n */\nexport function danglingRefs(root: unknown): string[] {\n const out = new Set<string>();\n const walk = (node: unknown): void => {\n if (Array.isArray(node)) {\n for (const value of node) walk(value);\n return;\n }\n if (node === null || typeof node !== \"object\") return;\n for (const [key, value] of Object.entries(node as Record<string, unknown>)) {\n if (key === \"$ref\" && typeof value === \"string\") {\n if (value.startsWith(\"#/\") && !resolves(root, value)) out.add(value);\n continue;\n }\n walk(value);\n }\n };\n walk(root);\n return [...out];\n}\n\n/** Does a local JSON pointer (`#/a/b~1c`) reach a node in this tree? */\nfunction resolves(root: unknown, ref: string): boolean {\n let current: unknown = root;\n for (const raw of ref.slice(2).split(\"/\")) {\n const segment = raw.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n if (Array.isArray(current)) {\n const index = Number(segment);\n if (!Number.isInteger(index) || index < 0 || index >= current.length) return false;\n current = current[index];\n } else if (current !== null && typeof current === \"object\" && segment in (current as Record<string, unknown>)) {\n current = (current as Record<string, unknown>)[segment];\n } else {\n return false;\n }\n }\n return true;\n}\n"]}
1
+ {"version":3,"file":"depth.js","sourceRoot":"","sources":["../../../src/core/openapi/depth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,gEAAgE;AAChE,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AAE3C;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAAW;IAC7C,IAAI,EAAE,KAAK,IAAI,IAAI,OAAO,EAAE,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACvD,MAAM,SAAS,GAAI,EAA8B,CAAC,WAAW,CAAC,CAAC;IAC/D,IAAI,SAAS,KAAK,IAAI,IAAI,SAAS,KAAK,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QAC/G,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,SAAoC,CAAC,CAAC;IACrE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,OAAO,EAAE,CAAC;QACnC,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QACnC,QAAQ,GAAG,IAAI,CAAC;QAChB,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,SAAS;QAC/E,MAAM,GAAG,GAAG,IAA+B,CAAC;QAC5C,IAAI,MAAM,IAAI,GAAG;YAAE,OAAO,KAAK,CAAC;QAChC,MAAM,OAAO,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC;QAC/B,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;YAAE,SAAS;QACxF,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,OAAkC,CAAC,EAAE,CAAC;YACtE,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,QAAQ,IAAK,KAAiC,EAAE,CAAC;gBAClG,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,EAAW;IACxC,IAAI,EAAE,KAAK,IAAI,IAAI,OAAO,EAAE,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IACrD,MAAM,SAAS,GAAI,EAA8B,CAAC,WAAW,CAAC,CAAC;IAC/D,IAAI,SAAS,KAAK,IAAI,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,CAAC;IAC/F,OAAO,MAAM,CAAC,IAAI,CAAC,SAAoC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AACrG,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,IAAa;IACxC,4EAA4E;IAC5E,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnH,CAAC;AAED;;;;;GAKG;AAEH,kGAAkG;AAClG,MAAM,UAAU,WAAW,CAAC,IAAa;IACvC,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,MAAM,IAAI,GAAG,CAAC,OAAgB,EAAQ,EAAE;QACtC,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3B,KAAK,MAAM,KAAK,IAAI,OAAO;gBAAE,IAAI,CAAC,KAAK,CAAC,CAAC;YACzC,OAAO;QACT,CAAC;QACD,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YACpD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;gBACnD,IAAI,GAAG,KAAK,MAAM,IAAI,OAAO,KAAK,KAAK,QAAQ;oBAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;;oBAC5D,IAAI,CAAC,KAAK,CAAC,CAAC;YACnB,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IACF,IAAI,CAAC,IAAI,CAAC,CAAC;IACX,OAAO,GAAG,CAAC;AACb,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,cAAc,CAAC,IAAa,EAAE,GAAW;IACvD,IAAI,OAAO,GAAY,IAAI,CAAC;IAC5B,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1C,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAC5D,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3B,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;YAC9B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;gBACrE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;YAC5C,CAAC;YACD,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;aAAM,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,IAAK,OAAmC,EAAE,CAAC;YAC9G,OAAO,GAAI,OAAmC,CAAC,OAAO,CAAC,CAAC;QAC1D,CAAC;aAAM,CAAC;YACN,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;QAC5C,CAAC;IACH,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AACzC,CAAC","sourcesContent":["/**\n * The contract-depth probes: does an operation say anything about what it\n * returns, and does every internal `$ref` point at something.\n *\n * Form validated and depth did not, and that was invisible from the output:\n * an operation whose one response is `description: OK`, and a `$ref` to a\n * schema nobody ever wrote, both validate exactly as cleanly as a complete\n * contract even though a reader still needs the code. Everything here is a\n * PRESENCE probe over the document's own keys: nothing reads what a schema\n * says, only whether one was declared at all, so the reader's structure-only\n * stance holds.\n *\n * This module used to keep a private pointer walk rather than import the\n * merge's: the merge package already imports this package (`HTTP_METHODS` from\n * doc.ts), so the reverse import would have been a package cycle — the same\n * reason read-side `resolvePointer` copies exist on the asyncapi axis. The two\n * plain `$ref` questions now live HERE, at the axis root (`collectRefs` and\n * `resolvePointer`, at the bottom), so the private copy went: the dangling-ref\n * probe below, the merge's component closure and the reader's closure digest\n * (`closureDigester`, ./digest.ts) all ask the same two questions.\n */\n\n/** Status codes whose response legitimately carries no body. */\nconst NO_CONTENT = new Set([\"204\", \"304\"]);\n\n/**\n * True when no response declares content with a schema while at least one\n * response should carry a body — an operation with no `responses` at all\n * counts, one whose only responses are 204/304 does not. A response that is\n * itself a `$ref` counts as described: somebody wrote the shape down, and the\n * dangling case is the ref probe's finding, not this one's.\n */\nexport function responseUndescribed(op: unknown): boolean {\n if (op === null || typeof op !== \"object\") return true;\n const responses = (op as Record<string, unknown>)[\"responses\"];\n if (responses === null || responses === undefined || typeof responses !== \"object\" || Array.isArray(responses)) {\n return true;\n }\n const entries = Object.entries(responses as Record<string, unknown>);\n if (entries.length === 0) return true;\n let owesBody = false;\n for (const [code, resp] of entries) {\n if (NO_CONTENT.has(code)) continue;\n owesBody = true;\n if (resp === null || typeof resp !== \"object\" || Array.isArray(resp)) continue;\n const rec = resp as Record<string, unknown>;\n if (\"$ref\" in rec) return false;\n const content = rec[\"content\"];\n if (content === null || typeof content !== \"object\" || Array.isArray(content)) continue;\n for (const media of Object.values(content as Record<string, unknown>)) {\n if (media !== null && typeof media === \"object\" && \"schema\" in (media as Record<string, unknown>)) {\n return false;\n }\n }\n }\n return owesBody;\n}\n\n/**\n * The non-2xx response codes an operation declares, in document order.\n *\n * Exact three-digit codes only. `default` names no case — it is whatever else\n * happens — and the `4XX`/`5XX` wildcards name a class rather than a refusal,\n * so no scenario could be written for either and demanding one would teach an\n * author to write a scenario about nothing. 1xx and 3xx are excluded with 2xx:\n * a redirect or a continue is not the service saying no.\n */\nexport function failureCodesOf(op: unknown): string[] {\n if (op === null || typeof op !== \"object\") return [];\n const responses = (op as Record<string, unknown>)[\"responses\"];\n if (responses === null || typeof responses !== \"object\" || Array.isArray(responses)) return [];\n return Object.keys(responses as Record<string, unknown>).filter((code) => /^[45]\\d\\d$/.test(code));\n}\n\n/**\n * Every internal `#/` reference in the document that resolves to nothing —\n * deduped, document order. External references (URLs, file paths) are out of\n * scope and never graded, matching the merge's documented stance.\n */\nexport function danglingRefs(root: unknown): string[] {\n // A Set keeps first-insertion order, so dedup and document order both hold.\n return [...new Set(collectRefs(root).filter((ref) => ref.startsWith(\"#/\") && !resolvePointer(root, ref).found))];\n}\n\n/*\n * `$ref` as a pair of plain questions — which ones does this tree contain, and\n * what does one point at. Both lived in `./merge/components.ts`, beside the\n * component closure that was their only caller, until the reader needed the same\n * walk; the header says why the root and not there.\n */\n\n/** Every `$ref` string in a tree, in document order — duplicates kept, external refs included. */\nexport function collectRefs(node: unknown): string[] {\n const out: string[] = [];\n const walk = (current: unknown): void => {\n if (Array.isArray(current)) {\n for (const value of current) walk(value);\n return;\n }\n if (current !== null && typeof current === \"object\") {\n for (const [key, value] of Object.entries(current)) {\n if (key === \"$ref\" && typeof value === \"string\") out.push(value);\n else walk(value);\n }\n }\n };\n walk(node);\n return out;\n}\n\n/** Resolve a local JSON pointer (`#/a/b~1c`) against a plain JS tree. */\nexport function resolvePointer(root: unknown, ref: string): { found: boolean; value: unknown } {\n let current: unknown = root;\n for (const raw of ref.slice(2).split(\"/\")) {\n const segment = raw.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n if (Array.isArray(current)) {\n const index = Number(segment);\n if (!Number.isInteger(index) || index < 0 || index >= current.length) {\n return { found: false, value: undefined };\n }\n current = current[index];\n } else if (current !== null && typeof current === \"object\" && segment in (current as Record<string, unknown>)) {\n current = (current as Record<string, unknown>)[segment];\n } else {\n return { found: false, value: undefined };\n }\n }\n return { found: true, value: current };\n}\n"]}
@@ -10,14 +10,17 @@
10
10
  * is that it fires only for real ones.
11
11
  */
12
12
  import { createHash } from "node:crypto";
13
+ import { collectRefs, resolvePointer } from "./depth.js";
13
14
  /**
14
15
  * The baseline marker: which living version of this operation a FEATURE delta
15
16
  * was written against. Same idea as a requirement's `Based-On:` (core/document/spec.ts),
16
17
  * in the vendor-extension shape this axis already uses for `x-loam-remove`.
17
18
  *
18
19
  * This axis needs it MORE than the requirement axis does, because a feature's
19
- * openapi.yaml is a COMPLETE document rather than a patch: authors restate the
20
- * living contract around the slot they are changing, and the merge upserts every
20
+ * openapi.yaml may spell operations it does not change: it needs only what it
21
+ * adds, changes or removes (the merge leaves everything else to the living
22
+ * contract), but restating more is legal — for context, or a contract pasted
23
+ * whole, which is what loam's own docs once taught — and the merge upserts every
21
24
  * operation the document spells. So a feature that never meant to touch
22
25
  * `refundOrder` — it merely quoted it — pushes its authoring-time copy back over
23
26
  * whatever landed in between, reverting another team's shipped change without
@@ -154,6 +157,53 @@ export function valueDigest(node) {
154
157
  export function operationDigest(node) {
155
158
  return valueDigest(withoutOperationBaseline(node));
156
159
  }
160
+ /**
161
+ * The identity of what a node PROMISES rather than of its own bytes: the
162
+ * `operationDigest` input plus every internal `$ref` it reaches, transitively,
163
+ * each paired with the digest of what it resolves to in `root`. One digester per
164
+ * document; call it once per operation or message.
165
+ *
166
+ * `operationDigest` stops at the node on purpose — a pin says which version of
167
+ * THIS slot a delta was written against, and components carry their own pins.
168
+ * But a reviewer asking "did this operation change?" means the contract a
169
+ * consumer codes against, and in a factored document that is mostly somewhere
170
+ * else: renaming a required field of `#/components/schemas/Payment` leaves every
171
+ * operation returning it byte-identical, and `loam diff` reported exactly that
172
+ * PR as "no fleet-meaningful changes". This digest is what diff compares.
173
+ *
174
+ * The memo is per document and per ref, so a schema twenty operations reference
175
+ * is resolved, walked and hashed once. A ref that resolves to nothing still
176
+ * contributes — as a marker no 16-hex digest can equal — so a dangling ref that
177
+ * starts resolving reads as a change. External refs (anything not `#/`) are not
178
+ * followed, matching every other reader here; the ref string itself is part of
179
+ * the node's own bytes, so re-pointing one still moves the digest.
180
+ */
181
+ export function closureDigester(root) {
182
+ const targets = new Map();
183
+ const target = (ref) => {
184
+ let hit = targets.get(ref);
185
+ if (hit === undefined) {
186
+ const resolved = resolvePointer(root, ref);
187
+ hit = resolved.found ? { digest: valueDigest(resolved.value), refs: collectRefs(resolved.value) } : { digest: "unresolved", refs: [] };
188
+ targets.set(ref, hit);
189
+ }
190
+ return hit;
191
+ };
192
+ return (node) => {
193
+ const reached = new Map();
194
+ const queue = collectRefs(node);
195
+ while (queue.length > 0) {
196
+ const ref = queue.pop();
197
+ if (!ref.startsWith("#/") || reached.has(ref))
198
+ continue;
199
+ const hit = target(ref);
200
+ reached.set(ref, hit.digest);
201
+ queue.push(...hit.refs);
202
+ }
203
+ // canonicalJson sorts keys, so the walk order above never reaches the digest.
204
+ return valueDigest({ node: withoutOperationBaseline(node), refs: Object.fromEntries(reached) });
205
+ };
206
+ }
157
207
  /**
158
208
  * What a pin says, from digests alone — the one definition every decider
159
209
  * shares: the gate (./baseline/gate.ts) and the merge
@@ -1 +1 @@
1
- {"version":3,"file":"digest.js","sourceRoot":"","sources":["../../../src/core/openapi/digest.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What operation content HASHES to, and what a recorded hash means when it is\n * read back.\n *\n * A module of its own because the digest and the comparison it must agree with\n * are a matched pair: key order must not matter here, since the merge's own\n * idea of \"this operation differs\" is `isDeepStrictEqual`, which ignores it. A\n * second spelling of either would go stale over a reordered `summary` pair the\n * merge calls identical — a false collision on the one check whose whole worth\n * is that it fires only for real ones.\n */\nimport { createHash } from \"node:crypto\";\n\n/**\n * The baseline marker: which living version of this operation a FEATURE delta\n * was written against. Same idea as a requirement's `Based-On:` (core/document/spec.ts),\n * in the vendor-extension shape this axis already uses for `x-loam-remove`.\n *\n * This axis needs it MORE than the requirement axis does, because a feature's\n * openapi.yaml is a COMPLETE document rather than a patch: authors restate the\n * living contract around the slot they are changing, and the merge upserts every\n * operation the document spells. So a feature that never meant to touch\n * `refundOrder` — it merely quoted it — pushes its authoring-time copy back over\n * whatever landed in between, reverting another team's shipped change without\n * either feature overlapping the other at all. The pin is what lets the merge\n * tell a QUOTE from an EDIT: a quote equals its own baseline, and is skipped.\n */\nexport const OPENAPI_BASELINE_KEY = \"x-loam-based-on\";\n\n/**\n * The feature-only ROOT record that pins the surfaces the operation pin cannot\n * reach: path-item non-method keys and components. `{ pathItems: {<path>:\n * {<key>: <16-hex>}}, components: {\"<kind>/<name>\": <16-hex>} }` — written by\n * `loam rebase`, graded by validate/archive, consumed by the merge, and\n * stripped from every living document exactly like the pin. A single root\n * record rather than in-value extensions because a component's value can be a\n * bare boolean (JSON Schema allows `true`), which no in-value key survives.\n */\nexport const OPENAPI_BASELINES_KEY = \"x-loam-baselines\";\n\n/** The feature-only explicit removal marker's key. */\nexport const OPENAPI_REMOVE_KEY = \"x-loam-remove\";\n\n/** Is this operation node a feature-only explicit removal marker? */\nexport function isRemoval(node: unknown): boolean {\n return node !== null &&\n typeof node === \"object\" &&\n (node as Record<string, unknown>)[OPENAPI_REMOVE_KEY] === true;\n}\n\n/**\n * Every key that is an instruction to loam rather than part of the contract.\n * At PATH level none has any meaning — `x-loam-remove` addresses one\n * operation and `x-loam-based-on` pins one — so a path item carrying either\n * is an authoring mistake, and one that must not be published whatever else\n * is done about it. Lives HERE, in the openapi root beside the keys it names,\n * because both `merge/` and `baseline/` consume it and a value edge between\n * those two packages in either direction would be a package cycle.\n */\nexport const FEATURE_ONLY_KEYS: ReadonlySet<string> = new Set([OPENAPI_REMOVE_KEY, OPENAPI_BASELINE_KEY]);\n\n/**\n * `value` with every feature-only key removed, at ANY depth — or the value\n * unchanged (same reference) when it carries none. The component closure\n * writes values VERBATIM into the living contract, and a component holding an\n * operation shape (a `pathItems` component, a callback) can nest the markers\n * where the path-level strip never walks: `--approve` once published\n * `x-loam-remove: true` inside a living component, invisible even to\n * `validate`, whose marker sweep reads `paths` alone.\n */\nexport function withoutFeatureKeysDeep(value: unknown): unknown {\n if (value === null || typeof value !== \"object\") return value;\n if (Array.isArray(value)) {\n let changed = false;\n const out = value.map((v) => {\n const stripped = withoutFeatureKeysDeep(v);\n if (stripped !== v) changed = true;\n return stripped;\n });\n return changed ? out : value;\n }\n const record = value as Record<string, unknown>;\n let changed = false;\n const out: Record<string, unknown> = {};\n for (const [key, v] of Object.entries(record)) {\n if (FEATURE_ONLY_KEYS.has(key)) {\n changed = true;\n continue;\n }\n const stripped = withoutFeatureKeysDeep(v);\n if (stripped !== v) changed = true;\n out[key] = stripped;\n }\n return changed ? out : value;\n}\n\n/** How much of the sha256 an `x-loam-based-on` carries — the length every loam digest uses. */\nexport const OPERATION_DIGEST_LENGTH = 16;\n\n/** The value shape of an `x-loam-based-on`, exactly as `operationDigest` writes it. */\nexport const OPERATION_DIGEST_RE = new RegExp(`^[0-9a-f]{${OPERATION_DIGEST_LENGTH}}$`);\n\n/**\n * Canonical JSON for a resolved YAML value: object keys sorted, arrays left in\n * order, everything else as JSON writes it.\n *\n * Key ORDER must not matter, because the merge's own idea of \"this operation\n * differs\" is `isDeepStrictEqual` (./merge/merge.ts), which ignores it. A\n * digest that disagreed with that comparison would go stale over a reordered\n * `summary`/`operationId` pair the merge itself calls identical — a false\n * collision on the one check whose worth is that it fires only for real ones.\n *\n * Exported for the ASYNCAPI axis (core/asyncapi/digest.ts), whose slot\n * digests are the same rule over different sections: one spelling of the\n * canonical form for both axes, because two spellings would eventually\n * disagree — the exact drift this module's own header names as the failure\n * mode. The import direction is asyncapi → openapi only; nothing under\n * core/openapi/ may import from core/asyncapi/, or the package graph cycles.\n */\nexport function canonicalJson(node: unknown): string {\n if (node === null || typeof node !== \"object\") return JSON.stringify(node) ?? \"null\";\n if (Array.isArray(node)) return `[${node.map(canonicalJson).join(\",\")}]`;\n const entries = Object.entries(node as Record<string, unknown>)\n .filter(([, value]) => value !== undefined)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n return `{${entries.map(([key, value]) => `${JSON.stringify(key)}:${canonicalJson(value)}`).join(\",\")}}`;\n}\n\n/**\n * The operation without its own baseline marker — what a digest is taken over,\n * and what the merge writes.\n *\n * A pin is a statement ABOUT a delta, never part of the operation it describes.\n * Inside the digest input, an operation's identity would depend on the pin\n * pointing at it and no baseline could ever be self-consistent; inside the\n * merged contract, a living operation would carry a pin to a version of itself\n * and the next feature's baseline would hash the previous feature's bookkeeping.\n */\nexport function withoutOperationBaseline(node: unknown): unknown {\n if (node === null || typeof node !== \"object\" || Array.isArray(node)) return node;\n const record = node as Record<string, unknown>;\n if (!(OPENAPI_BASELINE_KEY in record)) return node;\n const rest = { ...record };\n delete rest[OPENAPI_BASELINE_KEY];\n return rest;\n}\n\n/**\n * The identity of ANY resolved value: sha256 of its canonical form. The one\n * digest the path-item and component baselines carry — order-of-keys blind,\n * array-order sensitive, legal over scalars and booleans, exactly as the\n * operation digest always was.\n */\nexport function valueDigest(node: unknown): string {\n return createHash(\"sha256\").update(canonicalJson(node), \"utf8\").digest(\"hex\").slice(0, OPERATION_DIGEST_LENGTH);\n}\n\n/** The identity of an operation's CONTENT: sha256 of its canonical form, pin excluded. */\nexport function operationDigest(node: unknown): string {\n return valueDigest(withoutOperationBaseline(node));\n}\n\n/**\n * What a pin says, from digests alone — the one definition every decider\n * shares: the gate (./baseline/gate.ts) and the merge\n * (./merge/{merge,components,pin}.ts) alike. Two spellings of\n * this rule would eventually disagree about which operations a merge writes,\n * and the disagreement would be invisible until a contract came back wrong.\n *\n * Order matters: `quote` is settled against the delta's OWN content before the\n * living contract is consulted. An operation equal to its own baseline was not\n * edited, so what living holds now is nobody's business but the feature that\n * put it there.\n */\nexport function classifyBaselineDigests(\n pin: string | undefined,\n ownDigest: string,\n livingDigest: string | undefined,\n): \"unpinned\" | \"quote\" | \"edit\" | \"stale\" | \"unfounded\" {\n if (pin === undefined) return \"unpinned\";\n if (livingDigest === undefined) return \"unfounded\";\n if (pin === ownDigest) return \"quote\";\n if (pin === livingDigest) return \"edit\";\n return \"stale\";\n}\n\n/** The `x-loam-based-on` a node declares, or undefined — non-strings included, so validate can refuse them. */\nexport function operationBaselineOf(node: unknown): string | undefined {\n if (node === null || typeof node !== \"object\" || Array.isArray(node)) return undefined;\n const value = (node as Record<string, unknown>)[OPENAPI_BASELINE_KEY];\n if (value === undefined) return undefined;\n return typeof value === \"string\" ? value.trim() : String(value);\n}\n\n"]}
1
+ {"version":3,"file":"digest.js","sourceRoot":"","sources":["../../../src/core/openapi/digest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEzD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,iBAAiB,CAAC;AAEtD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,kBAAkB,CAAC;AAExD,sDAAsD;AACtD,MAAM,CAAC,MAAM,kBAAkB,GAAG,eAAe,CAAC;AAElD,qEAAqE;AACrE,MAAM,UAAU,SAAS,CAAC,IAAa;IACrC,OAAO,IAAI,KAAK,IAAI;QAClB,OAAO,IAAI,KAAK,QAAQ;QACvB,IAAgC,CAAC,kBAAkB,CAAC,KAAK,IAAI,CAAC;AACnE,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAwB,IAAI,GAAG,CAAC,CAAC,kBAAkB,EAAE,oBAAoB,CAAC,CAAC,CAAC;AAE1G;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAc;IACnD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC9D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YAC1B,MAAM,QAAQ,GAAG,sBAAsB,CAAC,CAAC,CAAC,CAAC;YAC3C,IAAI,QAAQ,KAAK,CAAC;gBAAE,OAAO,GAAG,IAAI,CAAC;YACnC,OAAO,QAAQ,CAAC;QAClB,CAAC,CAAC,CAAC;QACH,OAAO,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;IAC/B,CAAC;IACD,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9C,IAAI,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/B,OAAO,GAAG,IAAI,CAAC;YACf,SAAS;QACX,CAAC;QACD,MAAM,QAAQ,GAAG,sBAAsB,CAAC,CAAC,CAAC,CAAC;QAC3C,IAAI,QAAQ,KAAK,CAAC;YAAE,OAAO,GAAG,IAAI,CAAC;QACnC,GAAG,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC;IACtB,CAAC;IACD,OAAO,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;AAC/B,CAAC;AAED,+FAA+F;AAC/F,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,CAAC;AAE1C,uFAAuF;AACvF,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,MAAM,CAAC,aAAa,uBAAuB,IAAI,CAAC,CAAC;AAExF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,aAAa,CAAC,IAAa;IACzC,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC;IACrF,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IACzE,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,IAA+B,CAAC;SAC5D,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC;SAC1C,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AAC1G,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAAa;IACpD,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAClF,MAAM,MAAM,GAAG,IAA+B,CAAC;IAC/C,IAAI,CAAC,CAAC,oBAAoB,IAAI,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IACnD,MAAM,IAAI,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC;IAC3B,OAAO,IAAI,CAAC,oBAAoB,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,IAAa;IACvC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,uBAAuB,CAAC,CAAC;AAClH,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,eAAe,CAAC,IAAa;IAC3C,OAAO,WAAW,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAC,CAAC;AACrD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,eAAe,CAAC,IAAa;IAC3C,MAAM,OAAO,GAAG,IAAI,GAAG,EAA8C,CAAC;IACtE,MAAM,MAAM,GAAG,CAAC,GAAW,EAAsC,EAAE;QACjE,IAAI,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC3B,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAC3C,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;YACvI,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACxB,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;IACF,OAAO,CAAC,IAAI,EAAE,EAAE;QACd,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;QAC1C,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QAChC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,EAAG,CAAC;YACzB,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,SAAS;YACxD,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;YACxB,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;YAC7B,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC;QACD,8EAA8E;QAC9E,OAAO,WAAW,CAAC,EAAE,IAAI,EAAE,wBAAwB,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAClG,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,uBAAuB,CACrC,GAAuB,EACvB,SAAiB,EACjB,YAAgC;IAEhC,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,UAAU,CAAC;IACzC,IAAI,YAAY,KAAK,SAAS;QAAE,OAAO,WAAW,CAAC;IACnD,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC;IACtC,IAAI,GAAG,KAAK,YAAY;QAAE,OAAO,MAAM,CAAC;IACxC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,+GAA+G;AAC/G,MAAM,UAAU,mBAAmB,CAAC,IAAa;IAC/C,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACvF,MAAM,KAAK,GAAI,IAAgC,CAAC,oBAAoB,CAAC,CAAC;IACtE,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAClE,CAAC","sourcesContent":["/**\n * What operation content HASHES to, and what a recorded hash means when it is\n * read back.\n *\n * A module of its own because the digest and the comparison it must agree with\n * are a matched pair: key order must not matter here, since the merge's own\n * idea of \"this operation differs\" is `isDeepStrictEqual`, which ignores it. A\n * second spelling of either would go stale over a reordered `summary` pair the\n * merge calls identical — a false collision on the one check whose whole worth\n * is that it fires only for real ones.\n */\nimport { createHash } from \"node:crypto\";\nimport { collectRefs, resolvePointer } from \"./depth.js\";\n\n/**\n * The baseline marker: which living version of this operation a FEATURE delta\n * was written against. Same idea as a requirement's `Based-On:` (core/document/spec.ts),\n * in the vendor-extension shape this axis already uses for `x-loam-remove`.\n *\n * This axis needs it MORE than the requirement axis does, because a feature's\n * openapi.yaml may spell operations it does not change: it needs only what it\n * adds, changes or removes (the merge leaves everything else to the living\n * contract), but restating more is legal — for context, or a contract pasted\n * whole, which is what loam's own docs once taught — and the merge upserts every\n * operation the document spells. So a feature that never meant to touch\n * `refundOrder` — it merely quoted it — pushes its authoring-time copy back over\n * whatever landed in between, reverting another team's shipped change without\n * either feature overlapping the other at all. The pin is what lets the merge\n * tell a QUOTE from an EDIT: a quote equals its own baseline, and is skipped.\n */\nexport const OPENAPI_BASELINE_KEY = \"x-loam-based-on\";\n\n/**\n * The feature-only ROOT record that pins the surfaces the operation pin cannot\n * reach: path-item non-method keys and components. `{ pathItems: {<path>:\n * {<key>: <16-hex>}}, components: {\"<kind>/<name>\": <16-hex>} }` — written by\n * `loam rebase`, graded by validate/archive, consumed by the merge, and\n * stripped from every living document exactly like the pin. A single root\n * record rather than in-value extensions because a component's value can be a\n * bare boolean (JSON Schema allows `true`), which no in-value key survives.\n */\nexport const OPENAPI_BASELINES_KEY = \"x-loam-baselines\";\n\n/** The feature-only explicit removal marker's key. */\nexport const OPENAPI_REMOVE_KEY = \"x-loam-remove\";\n\n/** Is this operation node a feature-only explicit removal marker? */\nexport function isRemoval(node: unknown): boolean {\n return node !== null &&\n typeof node === \"object\" &&\n (node as Record<string, unknown>)[OPENAPI_REMOVE_KEY] === true;\n}\n\n/**\n * Every key that is an instruction to loam rather than part of the contract.\n * At PATH level none has any meaning — `x-loam-remove` addresses one\n * operation and `x-loam-based-on` pins one — so a path item carrying either\n * is an authoring mistake, and one that must not be published whatever else\n * is done about it. Lives HERE, in the openapi root beside the keys it names,\n * because both `merge/` and `baseline/` consume it and a value edge between\n * those two packages in either direction would be a package cycle.\n */\nexport const FEATURE_ONLY_KEYS: ReadonlySet<string> = new Set([OPENAPI_REMOVE_KEY, OPENAPI_BASELINE_KEY]);\n\n/**\n * `value` with every feature-only key removed, at ANY depth — or the value\n * unchanged (same reference) when it carries none. The component closure\n * writes values VERBATIM into the living contract, and a component holding an\n * operation shape (a `pathItems` component, a callback) can nest the markers\n * where the path-level strip never walks: `--approve` once published\n * `x-loam-remove: true` inside a living component, invisible even to\n * `validate`, whose marker sweep reads `paths` alone.\n */\nexport function withoutFeatureKeysDeep(value: unknown): unknown {\n if (value === null || typeof value !== \"object\") return value;\n if (Array.isArray(value)) {\n let changed = false;\n const out = value.map((v) => {\n const stripped = withoutFeatureKeysDeep(v);\n if (stripped !== v) changed = true;\n return stripped;\n });\n return changed ? out : value;\n }\n const record = value as Record<string, unknown>;\n let changed = false;\n const out: Record<string, unknown> = {};\n for (const [key, v] of Object.entries(record)) {\n if (FEATURE_ONLY_KEYS.has(key)) {\n changed = true;\n continue;\n }\n const stripped = withoutFeatureKeysDeep(v);\n if (stripped !== v) changed = true;\n out[key] = stripped;\n }\n return changed ? out : value;\n}\n\n/** How much of the sha256 an `x-loam-based-on` carries — the length every loam digest uses. */\nexport const OPERATION_DIGEST_LENGTH = 16;\n\n/** The value shape of an `x-loam-based-on`, exactly as `operationDigest` writes it. */\nexport const OPERATION_DIGEST_RE = new RegExp(`^[0-9a-f]{${OPERATION_DIGEST_LENGTH}}$`);\n\n/**\n * Canonical JSON for a resolved YAML value: object keys sorted, arrays left in\n * order, everything else as JSON writes it.\n *\n * Key ORDER must not matter, because the merge's own idea of \"this operation\n * differs\" is `isDeepStrictEqual` (./merge/merge.ts), which ignores it. A\n * digest that disagreed with that comparison would go stale over a reordered\n * `summary`/`operationId` pair the merge itself calls identical — a false\n * collision on the one check whose worth is that it fires only for real ones.\n *\n * Exported for the ASYNCAPI axis (core/asyncapi/digest.ts), whose slot\n * digests are the same rule over different sections: one spelling of the\n * canonical form for both axes, because two spellings would eventually\n * disagree — the exact drift this module's own header names as the failure\n * mode. The import direction is asyncapi → openapi only; nothing under\n * core/openapi/ may import from core/asyncapi/, or the package graph cycles.\n */\nexport function canonicalJson(node: unknown): string {\n if (node === null || typeof node !== \"object\") return JSON.stringify(node) ?? \"null\";\n if (Array.isArray(node)) return `[${node.map(canonicalJson).join(\",\")}]`;\n const entries = Object.entries(node as Record<string, unknown>)\n .filter(([, value]) => value !== undefined)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n return `{${entries.map(([key, value]) => `${JSON.stringify(key)}:${canonicalJson(value)}`).join(\",\")}}`;\n}\n\n/**\n * The operation without its own baseline marker — what a digest is taken over,\n * and what the merge writes.\n *\n * A pin is a statement ABOUT a delta, never part of the operation it describes.\n * Inside the digest input, an operation's identity would depend on the pin\n * pointing at it and no baseline could ever be self-consistent; inside the\n * merged contract, a living operation would carry a pin to a version of itself\n * and the next feature's baseline would hash the previous feature's bookkeeping.\n */\nexport function withoutOperationBaseline(node: unknown): unknown {\n if (node === null || typeof node !== \"object\" || Array.isArray(node)) return node;\n const record = node as Record<string, unknown>;\n if (!(OPENAPI_BASELINE_KEY in record)) return node;\n const rest = { ...record };\n delete rest[OPENAPI_BASELINE_KEY];\n return rest;\n}\n\n/**\n * The identity of ANY resolved value: sha256 of its canonical form. The one\n * digest the path-item and component baselines carry — order-of-keys blind,\n * array-order sensitive, legal over scalars and booleans, exactly as the\n * operation digest always was.\n */\nexport function valueDigest(node: unknown): string {\n return createHash(\"sha256\").update(canonicalJson(node), \"utf8\").digest(\"hex\").slice(0, OPERATION_DIGEST_LENGTH);\n}\n\n/** The identity of an operation's CONTENT: sha256 of its canonical form, pin excluded. */\nexport function operationDigest(node: unknown): string {\n return valueDigest(withoutOperationBaseline(node));\n}\n\n/**\n * The identity of what a node PROMISES rather than of its own bytes: the\n * `operationDigest` input plus every internal `$ref` it reaches, transitively,\n * each paired with the digest of what it resolves to in `root`. One digester per\n * document; call it once per operation or message.\n *\n * `operationDigest` stops at the node on purpose — a pin says which version of\n * THIS slot a delta was written against, and components carry their own pins.\n * But a reviewer asking \"did this operation change?\" means the contract a\n * consumer codes against, and in a factored document that is mostly somewhere\n * else: renaming a required field of `#/components/schemas/Payment` leaves every\n * operation returning it byte-identical, and `loam diff` reported exactly that\n * PR as \"no fleet-meaningful changes\". This digest is what diff compares.\n *\n * The memo is per document and per ref, so a schema twenty operations reference\n * is resolved, walked and hashed once. A ref that resolves to nothing still\n * contributes — as a marker no 16-hex digest can equal — so a dangling ref that\n * starts resolving reads as a change. External refs (anything not `#/`) are not\n * followed, matching every other reader here; the ref string itself is part of\n * the node's own bytes, so re-pointing one still moves the digest.\n */\nexport function closureDigester(root: unknown): (node: unknown) => string {\n const targets = new Map<string, { digest: string; refs: string[] }>();\n const target = (ref: string): { digest: string; refs: string[] } => {\n let hit = targets.get(ref);\n if (hit === undefined) {\n const resolved = resolvePointer(root, ref);\n hit = resolved.found ? { digest: valueDigest(resolved.value), refs: collectRefs(resolved.value) } : { digest: \"unresolved\", refs: [] };\n targets.set(ref, hit);\n }\n return hit;\n };\n return (node) => {\n const reached = new Map<string, string>();\n const queue = collectRefs(node);\n while (queue.length > 0) {\n const ref = queue.pop()!;\n if (!ref.startsWith(\"#/\") || reached.has(ref)) continue;\n const hit = target(ref);\n reached.set(ref, hit.digest);\n queue.push(...hit.refs);\n }\n // canonicalJson sorts keys, so the walk order above never reaches the digest.\n return valueDigest({ node: withoutOperationBaseline(node), refs: Object.fromEntries(reached) });\n };\n}\n\n/**\n * What a pin says, from digests alone — the one definition every decider\n * shares: the gate (./baseline/gate.ts) and the merge\n * (./merge/{merge,components,pin}.ts) alike. Two spellings of\n * this rule would eventually disagree about which operations a merge writes,\n * and the disagreement would be invisible until a contract came back wrong.\n *\n * Order matters: `quote` is settled against the delta's OWN content before the\n * living contract is consulted. An operation equal to its own baseline was not\n * edited, so what living holds now is nobody's business but the feature that\n * put it there.\n */\nexport function classifyBaselineDigests(\n pin: string | undefined,\n ownDigest: string,\n livingDigest: string | undefined,\n): \"unpinned\" | \"quote\" | \"edit\" | \"stale\" | \"unfounded\" {\n if (pin === undefined) return \"unpinned\";\n if (livingDigest === undefined) return \"unfounded\";\n if (pin === ownDigest) return \"quote\";\n if (pin === livingDigest) return \"edit\";\n return \"stale\";\n}\n\n/** The `x-loam-based-on` a node declares, or undefined — non-strings included, so validate can refuse them. */\nexport function operationBaselineOf(node: unknown): string | undefined {\n if (node === null || typeof node !== \"object\" || Array.isArray(node)) return undefined;\n const value = (node as Record<string, unknown>)[OPENAPI_BASELINE_KEY];\n if (value === undefined) return undefined;\n return typeof value === \"string\" ? value.trim() : String(value);\n}\n\n"]}