@ybotok/loam 0.2.0-alpha.7 → 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 (648) hide show
  1. package/CHANGELOG.md +1315 -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 +83 -7
  8. package/SCHEMA.md +569 -98
  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 +33 -11
  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 +21 -25
  116. package/dist/commands/seed/commit.js.map +1 -1
  117. package/dist/commands/seed/seed.js +6 -9
  118. package/dist/commands/seed/seed.js.map +1 -1
  119. package/dist/commands/show/show.js +3 -7
  120. package/dist/commands/show/show.js.map +1 -1
  121. package/dist/commands/status/print.js +13 -4
  122. package/dist/commands/status/print.js.map +1 -1
  123. package/dist/commands/status/status.js +3 -7
  124. package/dist/commands/status/status.js.map +1 -1
  125. package/dist/commands/steps/steps.js +3 -7
  126. package/dist/commands/steps/steps.js.map +1 -1
  127. package/dist/commands/subsystem/edit.js +3 -5
  128. package/dist/commands/subsystem/edit.js.map +1 -1
  129. package/dist/commands/subsystem/move.js +5 -8
  130. package/dist/commands/subsystem/move.js.map +1 -1
  131. package/dist/commands/subsystem/query.js +2 -4
  132. package/dist/commands/subsystem/query.js.map +1 -1
  133. package/dist/commands/subsystem/subsystem.js +4 -7
  134. package/dist/commands/subsystem/subsystem.js.map +1 -1
  135. package/dist/commands/subsystem/sync.js +14 -41
  136. package/dist/commands/subsystem/sync.js.map +1 -1
  137. package/dist/commands/subsystem/txn/txn.js +17 -38
  138. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  139. package/dist/commands/subsystem/txn/views.js +32 -5
  140. package/dist/commands/subsystem/txn/views.js.map +1 -1
  141. package/dist/commands/tasks/render.js +116 -0
  142. package/dist/commands/tasks/render.js.map +1 -0
  143. package/dist/commands/tasks/tasks.js +113 -0
  144. package/dist/commands/tasks/tasks.js.map +1 -0
  145. package/dist/commands/unarchive/manifest.js +6 -9
  146. package/dist/commands/unarchive/manifest.js.map +1 -1
  147. package/dist/commands/unarchive/restore.js +17 -28
  148. package/dist/commands/unarchive/restore.js.map +1 -1
  149. package/dist/commands/unarchive/unarchive.js +22 -30
  150. package/dist/commands/unarchive/unarchive.js.map +1 -1
  151. package/dist/commands/validate/arch-coverage.js +6 -15
  152. package/dist/commands/validate/arch-coverage.js.map +1 -1
  153. package/dist/commands/validate/checks/fleet-shape.js +4 -49
  154. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  155. package/dist/commands/validate/checks/requirements.js +18 -8
  156. package/dist/commands/validate/checks/requirements.js.map +1 -1
  157. package/dist/commands/validate/checks/sources.js +6 -2
  158. package/dist/commands/validate/checks/sources.js.map +1 -1
  159. package/dist/commands/validate/feature.js +23 -19
  160. package/dist/commands/validate/feature.js.map +1 -1
  161. package/dist/commands/validate/fleet/landscape.js +23 -23
  162. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  163. package/dist/commands/validate/fleet/map/attest.js +6 -10
  164. package/dist/commands/validate/fleet/map/attest.js.map +1 -1
  165. package/dist/commands/validate/fleet/map/bindings.js +1 -3
  166. package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
  167. package/dist/commands/validate/fleet/map/usecases.js +5 -11
  168. package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
  169. package/dist/commands/validate/fleet/obligations.js +1 -5
  170. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  171. package/dist/commands/validate/fleet/scorecard/print.js +5 -1
  172. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  173. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  174. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  175. package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
  176. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
  177. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
  178. package/dist/commands/validate/fleet/views/root-documents.js +8 -28
  179. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
  180. package/dist/commands/validate/links/corpus.js +1 -4
  181. package/dist/commands/validate/links/corpus.js.map +1 -1
  182. package/dist/commands/validate/links/glossary.js.map +1 -1
  183. package/dist/commands/validate/report.js +5 -4
  184. package/dist/commands/validate/report.js.map +1 -1
  185. package/dist/commands/validate/scope.js +113 -0
  186. package/dist/commands/validate/scope.js.map +1 -0
  187. package/dist/commands/validate/service/api.js +16 -104
  188. package/dist/commands/validate/service/api.js.map +1 -1
  189. package/dist/commands/validate/service/contracts/generated.js +134 -0
  190. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  191. package/dist/commands/validate/service/events/events.js +1 -1
  192. package/dist/commands/validate/service/events/events.js.map +1 -1
  193. package/dist/commands/validate/service/events/message-producers.js +1 -1
  194. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  195. package/dist/commands/validate/service/evidence-pins.js +56 -10
  196. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  197. package/dist/commands/validate/service/service.js +16 -26
  198. package/dist/commands/validate/service/service.js.map +1 -1
  199. package/dist/commands/validate/service/specs.js +31 -38
  200. package/dist/commands/validate/service/specs.js.map +1 -1
  201. package/dist/commands/validate/service/spine.js +8 -11
  202. package/dist/commands/validate/service/spine.js.map +1 -1
  203. package/dist/commands/validate/service/usecases/usecases.js +3 -3
  204. package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
  205. package/dist/commands/validate/validate.js +44 -26
  206. package/dist/commands/validate/validate.js.map +1 -1
  207. package/dist/commands/verify/cross/diff.js +2 -6
  208. package/dist/commands/verify/cross/diff.js.map +1 -1
  209. package/dist/commands/verify/evidence/contract.js +1 -1
  210. package/dist/commands/verify/evidence/contract.js.map +1 -1
  211. package/dist/commands/verify/evidence/read.js +12 -3
  212. package/dist/commands/verify/evidence/read.js.map +1 -1
  213. package/dist/commands/verify/frozen.js +45 -23
  214. package/dist/commands/verify/frozen.js.map +1 -1
  215. package/dist/commands/verify/record.js +69 -17
  216. package/dist/commands/verify/record.js.map +1 -1
  217. package/dist/commands/verify/report.js +27 -44
  218. package/dist/commands/verify/report.js.map +1 -1
  219. package/dist/commands/verify/results.js +112 -30
  220. package/dist/commands/verify/results.js.map +1 -1
  221. package/dist/commands/verify/verify.js +54 -51
  222. package/dist/commands/verify/verify.js.map +1 -1
  223. package/dist/commands/vouch/pack/pack.js +2 -2
  224. package/dist/commands/vouch/pack/pack.js.map +1 -1
  225. package/dist/commands/vouch/pack/print.js +1 -6
  226. package/dist/commands/vouch/pack/print.js.map +1 -1
  227. package/dist/commands/vouch/pack/sections.js +1 -2
  228. package/dist/commands/vouch/pack/sections.js.map +1 -1
  229. package/dist/commands/vouch/run.js +26 -81
  230. package/dist/commands/vouch/run.js.map +1 -1
  231. package/dist/commands/vouch/sample/plan.js +20 -34
  232. package/dist/commands/vouch/sample/plan.js.map +1 -1
  233. package/dist/commands/vouch/vet/verify.js +39 -7
  234. package/dist/commands/vouch/vet/verify.js.map +1 -1
  235. package/dist/commands/vouch/vouch.js +10 -17
  236. package/dist/commands/vouch/vouch.js.map +1 -1
  237. package/dist/core/agent/agents-md/command-map.js +56 -101
  238. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  239. package/dist/core/agent/agents-md/map/explain.js +4 -3
  240. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  241. package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
  242. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  243. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  244. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  245. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  246. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  247. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  248. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  249. package/dist/core/agent/agents-md/map/status.js +9 -3
  250. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  251. package/dist/core/agent/agents-md/map/subsystem.js +4 -3
  252. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  253. package/dist/core/agent/agents-md/map/usecases.js +24 -6
  254. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  255. package/dist/core/agent/agents-md/refusals.js +32 -18
  256. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  257. package/dist/core/agent/agents-md.js +9 -3
  258. package/dist/core/agent/agents-md.js.map +1 -1
  259. package/dist/core/agent/agents-stamp.js +10 -0
  260. package/dist/core/agent/agents-stamp.js.map +1 -1
  261. package/dist/core/agent/scaffold.js +10 -15
  262. package/dist/core/agent/scaffold.js.map +1 -1
  263. package/dist/core/agent/tools/dialects.js +1 -0
  264. package/dist/core/agent/tools/dialects.js.map +1 -1
  265. package/dist/core/agent/tools/registry.js +41 -76
  266. package/dist/core/agent/tools/registry.js.map +1 -1
  267. package/dist/core/agent/workflows/adopt.js +5 -3
  268. package/dist/core/agent/workflows/adopt.js.map +1 -1
  269. package/dist/core/agent/workflows/check.js +20 -16
  270. package/dist/core/agent/workflows/check.js.map +1 -1
  271. package/dist/core/agent/workflows/closing.js +54 -21
  272. package/dist/core/agent/workflows/closing.js.map +1 -1
  273. package/dist/core/agent/workflows/feature.js +79 -11
  274. package/dist/core/agent/workflows/feature.js.map +1 -1
  275. package/dist/core/agent/workflows/implement.js +91 -13
  276. package/dist/core/agent/workflows/implement.js.map +1 -1
  277. package/dist/core/agent/workflows/reference/authoring.js +3 -1
  278. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  279. package/dist/core/agent/workflows/reference/codes.js +8 -6
  280. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  281. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  282. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  283. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  284. package/dist/core/agent/workflows/reference/spine.js +50 -18
  285. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  286. package/dist/core/asyncapi/baseline/plan.js +14 -81
  287. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  288. package/dist/core/asyncapi/digest.js +30 -25
  289. package/dist/core/asyncapi/digest.js.map +1 -1
  290. package/dist/core/asyncapi/merge/components.js +5 -4
  291. package/dist/core/asyncapi/merge/components.js.map +1 -1
  292. package/dist/core/asyncapi/merge/error.js +7 -2
  293. package/dist/core/asyncapi/merge/error.js.map +1 -1
  294. package/dist/core/asyncapi/merge/markers.js +30 -50
  295. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  296. package/dist/core/asyncapi/merge/merge.js +31 -53
  297. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  298. package/dist/core/asyncapi/merge/pin.js +10 -36
  299. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  300. package/dist/core/asyncapi/model.js.map +1 -1
  301. package/dist/core/asyncapi/read.js +36 -12
  302. package/dist/core/asyncapi/read.js.map +1 -1
  303. package/dist/core/brief/brief.js +6 -18
  304. package/dist/core/brief/brief.js.map +1 -1
  305. package/dist/core/brief/landscape.js +8 -12
  306. package/dist/core/brief/landscape.js.map +1 -1
  307. package/dist/core/brief/map/extend.js +8 -5
  308. package/dist/core/brief/map/extend.js.map +1 -1
  309. package/dist/core/brief/map/owed.js +8 -26
  310. package/dist/core/brief/map/owed.js.map +1 -1
  311. package/dist/core/brief/targets.js +15 -5
  312. package/dist/core/brief/targets.js.map +1 -1
  313. package/dist/core/c4/likec4.js +147 -16
  314. package/dist/core/c4/likec4.js.map +1 -1
  315. package/dist/core/c4/locate/unlocated.js +150 -0
  316. package/dist/core/c4/locate/unlocated.js.map +1 -0
  317. package/dist/core/c4/parsed/values.js +54 -1
  318. package/dist/core/c4/parsed/values.js.map +1 -1
  319. package/dist/core/c4/prefetch/fleet.js +1 -9
  320. package/dist/core/c4/prefetch/fleet.js.map +1 -1
  321. package/dist/core/c4/project/documents.js +31 -6
  322. package/dist/core/c4/project/documents.js.map +1 -1
  323. package/dist/core/c4/project/load.js +6 -17
  324. package/dist/core/c4/project/load.js.map +1 -1
  325. package/dist/core/c4/resolve/external.js +51 -0
  326. package/dist/core/c4/resolve/external.js.map +1 -0
  327. package/dist/core/c4/resolve/service.js +25 -1
  328. package/dist/core/c4/resolve/service.js.map +1 -1
  329. package/dist/core/c4/seed/template.js +15 -1
  330. package/dist/core/c4/seed/template.js.map +1 -1
  331. package/dist/core/c4/service-model/shape.js +2 -22
  332. package/dist/core/c4/service-model/shape.js.map +1 -1
  333. package/dist/core/c4/source-mask.js +288 -4
  334. package/dist/core/c4/source-mask.js.map +1 -1
  335. package/dist/core/c4/source-scan.js +203 -147
  336. package/dist/core/c4/source-scan.js.map +1 -1
  337. package/dist/core/c4/splice/authored-source.js +19 -0
  338. package/dist/core/c4/splice/authored-source.js.map +1 -1
  339. package/dist/core/c4/splice/delta-blocks.js +5 -18
  340. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  341. package/dist/core/c4/splice/landscape-merge.js +13 -8
  342. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  343. package/dist/core/c4/splice/model/merge.js +10 -6
  344. package/dist/core/c4/splice/model/merge.js.map +1 -1
  345. package/dist/core/c4/splice/placement.js +108 -18
  346. package/dist/core/c4/splice/placement.js.map +1 -1
  347. package/dist/core/c4/workspace.js +13 -22
  348. package/dist/core/c4/workspace.js.map +1 -1
  349. package/dist/core/capabilities/capabilities.js +6 -6
  350. package/dist/core/capabilities/capabilities.js.map +1 -1
  351. package/dist/core/capabilities/delta/doc.js +7 -10
  352. package/dist/core/capabilities/delta/doc.js.map +1 -1
  353. package/dist/core/capabilities/findings.js +15 -20
  354. package/dist/core/capabilities/findings.js.map +1 -1
  355. package/dist/core/capabilities/realizes/findings.js +12 -19
  356. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  357. package/dist/core/capabilities/realizes/join.js +0 -20
  358. package/dist/core/capabilities/realizes/join.js.map +1 -1
  359. package/dist/core/capabilities/rollup.js +6 -0
  360. package/dist/core/capabilities/rollup.js.map +1 -1
  361. package/dist/core/capabilities/tree.js +6 -28
  362. package/dist/core/capabilities/tree.js.map +1 -1
  363. package/dist/core/capabilities/usecase-join.js +3 -13
  364. package/dist/core/capabilities/usecase-join.js.map +1 -1
  365. package/dist/core/coherence/authoring/scaffold.js +11 -3
  366. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  367. package/dist/core/coherence/authoring/sentinels.js +2 -1
  368. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  369. package/dist/core/coherence/coherence.js +30 -16
  370. package/dist/core/coherence/coherence.js.map +1 -1
  371. package/dist/core/coherence/consumed/operations.js +70 -0
  372. package/dist/core/coherence/consumed/operations.js.map +1 -0
  373. package/dist/core/coherence/consumed/refusal.js +38 -0
  374. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  375. package/dist/core/coherence/declared.js +60 -34
  376. package/dist/core/coherence/declared.js.map +1 -1
  377. package/dist/core/coherence/events/declared.js +44 -59
  378. package/dist/core/coherence/events/declared.js.map +1 -1
  379. package/dist/core/coherence/events/events.js +119 -8
  380. package/dist/core/coherence/events/events.js.map +1 -1
  381. package/dist/core/coherence/events/grades.js +31 -8
  382. package/dist/core/coherence/events/grades.js.map +1 -1
  383. package/dist/core/coherence/events/lookups.js +14 -82
  384. package/dist/core/coherence/events/lookups.js.map +1 -1
  385. package/dist/core/coherence/lookups.js +18 -78
  386. package/dist/core/coherence/lookups.js.map +1 -1
  387. package/dist/core/coherence/retired/archived.js +285 -0
  388. package/dist/core/coherence/retired/archived.js.map +1 -0
  389. package/dist/core/coherence/retired/event-pair.js +62 -0
  390. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  391. package/dist/core/coherence/retired/merged.js +79 -0
  392. package/dist/core/coherence/retired/merged.js.map +1 -0
  393. package/dist/core/coherence/retired/retirement.js +49 -0
  394. package/dist/core/coherence/retired/retirement.js.map +1 -0
  395. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  396. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  397. package/dist/core/coherence/retired/revival/revived.js +297 -0
  398. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  399. package/dist/core/consumers/after.js +113 -0
  400. package/dist/core/consumers/after.js.map +1 -0
  401. package/dist/core/consumers/consumers.js +296 -0
  402. package/dist/core/consumers/consumers.js.map +1 -0
  403. package/dist/core/consumers/current.js +211 -0
  404. package/dist/core/consumers/current.js.map +1 -0
  405. package/dist/core/consumers/scan.js +46 -0
  406. package/dist/core/consumers/scan.js.map +1 -0
  407. package/dist/core/consumers/state.js +27 -0
  408. package/dist/core/consumers/state.js.map +1 -0
  409. package/dist/core/delta/delta.js +1 -1
  410. package/dist/core/delta/delta.js.map +1 -1
  411. package/dist/core/deployment/delta.js +3 -6
  412. package/dist/core/deployment/delta.js.map +1 -1
  413. package/dist/core/diff/base-git.js +94 -12
  414. package/dist/core/diff/base-git.js.map +1 -1
  415. package/dist/core/diff/base-state.js +28 -16
  416. package/dist/core/diff/base-state.js.map +1 -1
  417. package/dist/core/diff/joins.js +136 -0
  418. package/dist/core/diff/joins.js.map +1 -0
  419. package/dist/core/diff/scope/changed-paths.js +22 -6
  420. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  421. package/dist/core/diff/scope/edges.js +82 -0
  422. package/dist/core/diff/scope/edges.js.map +1 -0
  423. package/dist/core/diff/semantic.js +100 -31
  424. package/dist/core/diff/semantic.js.map +1 -1
  425. package/dist/core/doctor/agents.js +34 -18
  426. package/dist/core/doctor/agents.js.map +1 -1
  427. package/dist/core/doctor/report.js.map +1 -1
  428. package/dist/core/document/apply.js +10 -2
  429. package/dist/core/document/apply.js.map +1 -1
  430. package/dist/core/document/frontmatter.js +55 -0
  431. package/dist/core/document/frontmatter.js.map +1 -1
  432. package/dist/core/document/parse.js +29 -6
  433. package/dist/core/document/parse.js.map +1 -1
  434. package/dist/core/document/scenarios.js +17 -0
  435. package/dist/core/document/scenarios.js.map +1 -1
  436. package/dist/core/document/spec.js +4 -11
  437. package/dist/core/document/spec.js.map +1 -1
  438. package/dist/core/envelope/config.js +10 -0
  439. package/dist/core/envelope/config.js.map +1 -1
  440. package/dist/core/envelope/json.js +34 -0
  441. package/dist/core/envelope/json.js.map +1 -1
  442. package/dist/core/explain/families.js +17 -12
  443. package/dist/core/explain/families.js.map +1 -1
  444. package/dist/core/explain/refusals.js +3 -3
  445. package/dist/core/explain/refusals.js.map +1 -1
  446. package/dist/core/explain/terms.js +18 -9
  447. package/dist/core/explain/terms.js.map +1 -1
  448. package/dist/core/explore/explore.js +2 -7
  449. package/dist/core/explore/explore.js.map +1 -1
  450. package/dist/core/fleet-context.js +34 -95
  451. package/dist/core/fleet-context.js.map +1 -1
  452. package/dist/core/gate/checks.js +5 -2
  453. package/dist/core/gate/checks.js.map +1 -1
  454. package/dist/core/gate/partners.js +4 -2
  455. package/dist/core/gate/partners.js.map +1 -1
  456. package/dist/core/gate/report.js.map +1 -1
  457. package/dist/core/gate/verification.js +75 -12
  458. package/dist/core/gate/verification.js.map +1 -1
  459. package/dist/core/gherkin/stale.js +13 -37
  460. package/dist/core/gherkin/stale.js.map +1 -1
  461. package/dist/core/glossary/tree.js +6 -19
  462. package/dist/core/glossary/tree.js.map +1 -1
  463. package/dist/core/kernel/concurrency.js +19 -0
  464. package/dist/core/kernel/concurrency.js.map +1 -1
  465. package/dist/core/kernel/records.js +11 -0
  466. package/dist/core/kernel/records.js.map +1 -1
  467. package/dist/core/links/backlinks.js +1 -3
  468. package/dist/core/links/backlinks.js.map +1 -1
  469. package/dist/core/links/corpus.js +5 -36
  470. package/dist/core/links/corpus.js.map +1 -1
  471. package/dist/core/mcp/argv.js +101 -0
  472. package/dist/core/mcp/argv.js.map +1 -1
  473. package/dist/core/mcp/author-tools.js +2 -1
  474. package/dist/core/mcp/author-tools.js.map +1 -1
  475. package/dist/core/mcp/protocol.js +2 -2
  476. package/dist/core/mcp/protocol.js.map +1 -1
  477. package/dist/core/mcp/tools.js +43 -33
  478. package/dist/core/mcp/tools.js.map +1 -1
  479. package/dist/core/openapi/baseline/gate.js +53 -12
  480. package/dist/core/openapi/baseline/gate.js.map +1 -1
  481. package/dist/core/openapi/baseline/plan.js +22 -8
  482. package/dist/core/openapi/baseline/plan.js.map +1 -1
  483. package/dist/core/openapi/baseline/record.js +0 -1
  484. package/dist/core/openapi/baseline/record.js.map +1 -1
  485. package/dist/core/openapi/contract-text.js +33 -0
  486. package/dist/core/openapi/contract-text.js.map +1 -0
  487. package/dist/core/openapi/contract-tree.js +56 -0
  488. package/dist/core/openapi/contract-tree.js.map +1 -0
  489. package/dist/core/openapi/depth.js +38 -24
  490. package/dist/core/openapi/depth.js.map +1 -1
  491. package/dist/core/openapi/digest.js +52 -2
  492. package/dist/core/openapi/digest.js.map +1 -1
  493. package/dist/core/openapi/doc.js +12 -4
  494. package/dist/core/openapi/doc.js.map +1 -1
  495. package/dist/core/openapi/merge/components.js +25 -60
  496. package/dist/core/openapi/merge/components.js.map +1 -1
  497. package/dist/core/openapi/merge/error.js +3 -13
  498. package/dist/core/openapi/merge/error.js.map +1 -1
  499. package/dist/core/openapi/merge/markers.js +26 -30
  500. package/dist/core/openapi/merge/markers.js.map +1 -1
  501. package/dist/core/openapi/merge/merge.js +26 -52
  502. package/dist/core/openapi/merge/merge.js.map +1 -1
  503. package/dist/core/openapi/merge/pin.js +15 -35
  504. package/dist/core/openapi/merge/pin.js.map +1 -1
  505. package/dist/core/openspec/decide/verdict.js.map +1 -1
  506. package/dist/core/openspec/inventory.js +4 -5
  507. package/dist/core/openspec/inventory.js.map +1 -1
  508. package/dist/core/openspec/model/mapping.js +30 -28
  509. package/dist/core/openspec/model/mapping.js.map +1 -1
  510. package/dist/core/openspec/model/model.js +12 -7
  511. package/dist/core/openspec/model/model.js.map +1 -1
  512. package/dist/core/openspec/scan/changes.js +10 -4
  513. package/dist/core/openspec/scan/changes.js.map +1 -1
  514. package/dist/core/openspec/scan/config.js +7 -16
  515. package/dist/core/openspec/scan/config.js.map +1 -1
  516. package/dist/core/openspec/scan/shape.js +63 -19
  517. package/dist/core/openspec/scan/shape.js.map +1 -1
  518. package/dist/core/openspec/scan/walk.js +21 -38
  519. package/dist/core/openspec/scan/walk.js.map +1 -1
  520. package/dist/core/pack/living.js +2 -16
  521. package/dist/core/pack/living.js.map +1 -1
  522. package/dist/core/provenance/findings.js +48 -12
  523. package/dist/core/provenance/findings.js.map +1 -1
  524. package/dist/core/provenance/git.js +33 -57
  525. package/dist/core/provenance/git.js.map +1 -1
  526. package/dist/core/provenance/gitq/moves.js +71 -114
  527. package/dist/core/provenance/gitq/moves.js.map +1 -1
  528. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  529. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  530. package/dist/core/provenance/sources.js +23 -6
  531. package/dist/core/provenance/sources.js.map +1 -1
  532. package/dist/core/provenance/stamp.js +5 -17
  533. package/dist/core/provenance/stamp.js.map +1 -1
  534. package/dist/core/repo/entries.js.map +1 -1
  535. package/dist/core/repo/repo.js +24 -14
  536. package/dist/core/repo/repo.js.map +1 -1
  537. package/dist/core/repo/tree/find.js +12 -3
  538. package/dist/core/repo/tree/find.js.map +1 -1
  539. package/dist/core/repo/tree/fs.js +27 -1
  540. package/dist/core/repo/tree/fs.js.map +1 -1
  541. package/dist/core/repo/tree/walk.js +8 -30
  542. package/dist/core/repo/tree/walk.js.map +1 -1
  543. package/dist/core/results.js +47 -12
  544. package/dist/core/results.js.map +1 -1
  545. package/dist/core/staging/commit.js +10 -12
  546. package/dist/core/staging/commit.js.map +1 -1
  547. package/dist/core/staging/lock.js +74 -12
  548. package/dist/core/staging/lock.js.map +1 -1
  549. package/dist/core/staging/recovery/intent.js +2 -5
  550. package/dist/core/staging/recovery/intent.js.map +1 -1
  551. package/dist/core/staging/recovery/recover.js +60 -1
  552. package/dist/core/staging/recovery/recover.js.map +1 -1
  553. package/dist/core/staging/snapshot/manifest.js +152 -0
  554. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  555. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
  556. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  557. package/dist/core/status/actions/execution.js +11 -0
  558. package/dist/core/status/actions/execution.js.map +1 -1
  559. package/dist/core/status/feature/artifacts.js +2 -2
  560. package/dist/core/status/feature/artifacts.js.map +1 -1
  561. package/dist/core/status/feature/feature.js +5 -11
  562. package/dist/core/status/feature/feature.js.map +1 -1
  563. package/dist/core/status/fleet/fleet.js +6 -2
  564. package/dist/core/status/fleet/fleet.js.map +1 -1
  565. package/dist/core/status/fleet/next.js +26 -3
  566. package/dist/core/status/fleet/next.js.map +1 -1
  567. package/dist/core/status/report.js.map +1 -1
  568. package/dist/core/status/verification.js +30 -15
  569. package/dist/core/status/verification.js.map +1 -1
  570. package/dist/core/tasks/packet.js +174 -0
  571. package/dist/core/tasks/packet.js.map +1 -0
  572. package/dist/core/tasks/record.js +43 -0
  573. package/dist/core/tasks/record.js.map +1 -0
  574. package/dist/core/tasks/sources.js +181 -0
  575. package/dist/core/tasks/sources.js.map +1 -0
  576. package/dist/core/tasks/stamps.js +76 -0
  577. package/dist/core/tasks/stamps.js.map +1 -0
  578. package/dist/core/tasks/unit.js +0 -0
  579. package/dist/core/tasks/unit.js.map +1 -0
  580. package/dist/core/usecases/capability.js +11 -14
  581. package/dist/core/usecases/capability.js.map +1 -1
  582. package/dist/core/usecases/delta/flows.js +5 -7
  583. package/dist/core/usecases/delta/flows.js.map +1 -1
  584. package/dist/core/usecases/fleet.js +178 -31
  585. package/dist/core/usecases/fleet.js.map +1 -1
  586. package/dist/core/usecases/operations.js +5 -4
  587. package/dist/core/usecases/operations.js.map +1 -1
  588. package/dist/core/usecases/service/flows.js +9 -6
  589. package/dist/core/usecases/service/flows.js.map +1 -1
  590. package/dist/core/usecases/suites/participants.js +83 -0
  591. package/dist/core/usecases/suites/participants.js.map +1 -0
  592. package/dist/core/usecases/suites/suites.js +45 -0
  593. package/dist/core/usecases/suites/suites.js.map +1 -0
  594. package/dist/core/verify/answers.js +12 -5
  595. package/dist/core/verify/answers.js.map +1 -1
  596. package/dist/core/verify/build.js +102 -82
  597. package/dist/core/verify/build.js.map +1 -1
  598. package/dist/core/verify/checklist.js +3 -0
  599. package/dist/core/verify/checklist.js.map +1 -1
  600. package/dist/core/verify/location.js +67 -0
  601. package/dist/core/verify/location.js.map +1 -0
  602. package/dist/core/verify/pins/lint.js +3 -2
  603. package/dist/core/verify/pins/lint.js.map +1 -1
  604. package/dist/core/verify/pins/pin.js +1 -1
  605. package/dist/core/verify/pins/pin.js.map +1 -1
  606. package/dist/core/verify/read/file.js +224 -0
  607. package/dist/core/verify/read/file.js.map +1 -0
  608. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  609. package/dist/core/verify/read/shape.js.map +1 -0
  610. package/dist/core/verify/read/union.js +105 -0
  611. package/dist/core/verify/read/union.js.map +1 -0
  612. package/dist/core/verify/record.js +35 -12
  613. package/dist/core/verify/record.js.map +1 -1
  614. package/dist/core/verify/store/commit.js +40 -30
  615. package/dist/core/verify/store/commit.js.map +1 -1
  616. package/dist/core/verify/store/render.js +8 -4
  617. package/dist/core/verify/store/render.js.map +1 -1
  618. package/dist/core/vocabulary/issue.js +1 -0
  619. package/dist/core/vocabulary/issue.js.map +1 -1
  620. package/dist/core/vocabulary/maturity.js +12 -2
  621. package/dist/core/vocabulary/maturity.js.map +1 -1
  622. package/examples/README.md +21 -6
  623. package/examples/docs/architecture/landscape.likec4 +8 -0
  624. package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
  625. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  626. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  627. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  628. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  629. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  630. package/package.json +1 -1
  631. package/dist/commands/validate/checks/vocabulary.js +0 -16
  632. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  633. package/dist/core/agent/agents-md/artifacts.js +0 -221
  634. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  635. package/dist/core/agent/agents-md/cycle.js +0 -180
  636. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  637. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  638. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  639. package/dist/core/agent/agents-md/spine.js +0 -40
  640. package/dist/core/agent/agents-md/spine.js.map +0 -1
  641. package/dist/core/coherence/events/merged.js +0 -40
  642. package/dist/core/coherence/events/merged.js.map +0 -1
  643. package/dist/core/diff/current-state.js +0 -96
  644. package/dist/core/diff/current-state.js.map +0 -1
  645. package/dist/core/diff/victims.js +0 -256
  646. package/dist/core/diff/victims.js.map +0 -1
  647. package/dist/core/staging/snapshot.js.map +0 -1
  648. package/dist/core/verify/file.js.map +0 -1
@@ -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":"AAAA;;;;;;;;;;;;;;;;GAgBG;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,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,MAAM,IAAI,GAAG,CAAC,IAAa,EAAQ,EAAE;QACnC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACxB,KAAK,MAAM,KAAK,IAAI,IAAI;gBAAE,IAAI,CAAC,KAAK,CAAC,CAAC;YACtC,OAAO;QACT,CAAC;QACD,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO;QACtD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAA+B,CAAC,EAAE,CAAC;YAC3E,IAAI,GAAG,KAAK,MAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAChD,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;oBAAE,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBACrE,SAAS;YACX,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC;IACF,IAAI,CAAC,IAAI,CAAC,CAAC;IACX,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC;AAClB,CAAC;AAED,wEAAwE;AACxE,SAAS,QAAQ,CAAC,IAAa,EAAE,GAAW;IAC1C,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;gBAAE,OAAO,KAAK,CAAC;YACnF,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,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,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 * 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":"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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 * 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":"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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\";\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"]}
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import { isUtf8 } from "node:buffer";
13
13
  import { danglingRefs, failureCodesOf, responseUndescribed } from "./depth.js";
14
- import { operationBaselineOf, operationDigest } from "./digest.js";
14
+ import { closureDigester, operationBaselineOf, operationDigest } from "./digest.js";
15
15
  import { existsSync } from "node:fs";
16
16
  import { readFile } from "node:fs/promises";
17
17
  import { parse } from "yaml";
@@ -33,7 +33,9 @@ export const HTTP_METHODS = new Set(["get", "put", "post", "delete", "options",
33
33
  * dotted) and picks up phantom ids from description text. Each operation
34
34
  * carries its id and its `deprecated: true` flag (exactly `true` counts —
35
35
  * strings and truthy noise do not). `ops` is the defined set, keyed by the
36
- * (path, method) SLOT — not by operationId.
36
+ * (path, method) SLOT — not by operationId. An operation written with no
37
+ * operationId is not in it — nothing that joins on the id could name it —
38
+ * but it is in `anonymousOps`, by its slot, for the questions asked BY slot.
37
39
  *
38
40
  * Keying by id used to drop the second slot claiming a name, which made the
39
41
  * single most common contract edit invisible: relocating an endpoint is a
@@ -96,8 +98,10 @@ export function openapiFromText(text) {
96
98
  return { ...empty(), danglingRefs: refs };
97
99
  const ops = [];
98
100
  const anonymousRemovals = [];
101
+ const anonymousOps = [];
99
102
  const pathLevelRemovals = [];
100
103
  const seen = new Map();
104
+ const closureDigest = closureDigester(doc);
101
105
  for (const [path, item] of Object.entries(paths)) {
102
106
  if (!item || typeof item !== "object")
103
107
  continue;
@@ -110,13 +114,15 @@ export function openapiFromText(text) {
110
114
  continue;
111
115
  const id = op["operationId"];
112
116
  const remove = op["x-loam-remove"] === true;
117
+ const basedOn = operationBaselineOf(op);
113
118
  if (typeof id !== "string" || id.length === 0) {
114
119
  if (remove)
115
120
  anonymousRemovals.push({ path, method });
121
+ else
122
+ anonymousOps.push({ path, method, digest: operationDigest(op), ...(basedOn === undefined ? {} : { basedOn }) });
116
123
  continue;
117
124
  }
118
125
  seen.set(id, (seen.get(id) ?? 0) + 1);
119
- const basedOn = operationBaselineOf(op);
120
126
  ops.push({
121
127
  id,
122
128
  deprecated: op["deprecated"] === true,
@@ -124,6 +130,7 @@ export function openapiFromText(text) {
124
130
  path,
125
131
  method,
126
132
  digest: operationDigest(op),
133
+ closureDigest: closureDigest(op),
127
134
  ...(basedOn === undefined ? {} : { basedOn }),
128
135
  ...(responseUndescribed(op) ? { undescribed: true } : {}),
129
136
  failureCodes: failureCodesOf(op),
@@ -131,13 +138,14 @@ export function openapiFromText(text) {
131
138
  }
132
139
  }
133
140
  const duplicateIds = [...seen].filter(([, n]) => n > 1).map(([id]) => id);
134
- return { ops, duplicateIds, anonymousRemovals, pathLevelRemovals, unreadable: false, danglingRefs: refs };
141
+ return { ops, duplicateIds, anonymousRemovals, anonymousOps, pathLevelRemovals, unreadable: false, danglingRefs: refs };
135
142
  }
136
143
  function empty() {
137
144
  return {
138
145
  ops: [],
139
146
  duplicateIds: [],
140
147
  anonymousRemovals: [],
148
+ anonymousOps: [],
141
149
  pathLevelRemovals: [],
142
150
  unreadable: false,
143
151
  danglingRefs: [],