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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1026) hide show
  1. package/CHANGELOG.md +2548 -86
  2. package/COMPARISON.md +80 -63
  3. package/CONTRIBUTING.md +84 -0
  4. package/MIGRATING-from-OpenSpec.md +213 -54
  5. package/QUICKSTART.md +125 -0
  6. package/README.md +450 -150
  7. package/ROADMAP.md +494 -0
  8. package/SCHEMA.md +2355 -256
  9. package/SECURITY.md +21 -3
  10. package/WORKFLOW.md +701 -0
  11. package/dist/cli.js +113 -19
  12. package/dist/cli.js.map +1 -1
  13. package/dist/commands/adopt/adopt.js +209 -0
  14. package/dist/commands/adopt/adopt.js.map +1 -0
  15. package/dist/commands/adopt/render.js +268 -0
  16. package/dist/commands/adopt/render.js.map +1 -0
  17. package/dist/commands/archive/archive.js +77 -0
  18. package/dist/commands/archive/archive.js.map +1 -0
  19. package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
  20. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
  21. package/dist/commands/archive/plan/contracts/openapi.js +159 -0
  22. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
  23. package/dist/commands/archive/plan/gate.js +317 -0
  24. package/dist/commands/archive/plan/gate.js.map +1 -0
  25. package/dist/commands/archive/plan/landscape.js +161 -0
  26. package/dist/commands/archive/plan/landscape.js.map +1 -0
  27. package/dist/commands/archive/plan/refusal.js +111 -0
  28. package/dist/commands/archive/plan/refusal.js.map +1 -0
  29. package/dist/commands/archive/plan/specs.js +237 -0
  30. package/dist/commands/archive/plan/specs.js.map +1 -0
  31. package/dist/commands/archive/plan/state.js +27 -0
  32. package/dist/commands/archive/plan/state.js.map +1 -0
  33. package/dist/commands/archive/run.js +251 -0
  34. package/dist/commands/archive/run.js.map +1 -0
  35. package/dist/commands/context/context.js +99 -0
  36. package/dist/commands/context/context.js.map +1 -0
  37. package/dist/commands/context/print.js +283 -0
  38. package/dist/commands/context/print.js.map +1 -0
  39. package/dist/commands/delta/delta.js +279 -0
  40. package/dist/commands/delta/delta.js.map +1 -0
  41. package/dist/commands/delta/print.js +205 -0
  42. package/dist/commands/delta/print.js.map +1 -0
  43. package/dist/commands/dependencies.js +9 -7
  44. package/dist/commands/dependencies.js.map +1 -1
  45. package/dist/commands/diff/diff.js +93 -0
  46. package/dist/commands/diff/diff.js.map +1 -0
  47. package/dist/commands/diff/print.js +38 -0
  48. package/dist/commands/diff/print.js.map +1 -0
  49. package/dist/commands/doctor.js +5 -4
  50. package/dist/commands/doctor.js.map +1 -1
  51. package/dist/commands/explain/explain.js +151 -0
  52. package/dist/commands/explain/explain.js.map +1 -0
  53. package/dist/commands/explore.js +29 -24
  54. package/dist/commands/explore.js.map +1 -1
  55. package/dist/commands/gate/gate.js +115 -0
  56. package/dist/commands/gate/gate.js.map +1 -0
  57. package/dist/commands/gate/print.js +54 -0
  58. package/dist/commands/gate/print.js.map +1 -0
  59. package/dist/commands/gherkin/commit.js +146 -0
  60. package/dist/commands/gherkin/commit.js.map +1 -0
  61. package/dist/commands/gherkin/gherkin.js +246 -0
  62. package/dist/commands/gherkin/gherkin.js.map +1 -0
  63. package/dist/commands/gherkin/reconcile.js +149 -0
  64. package/dist/commands/gherkin/reconcile.js.map +1 -0
  65. package/dist/commands/gherkin/render.js +77 -0
  66. package/dist/commands/gherkin/render.js.map +1 -0
  67. package/dist/commands/init/example.js +204 -0
  68. package/dist/commands/init/example.js.map +1 -0
  69. package/dist/commands/init/first-hour.js +28 -0
  70. package/dist/commands/init/first-hour.js.map +1 -0
  71. package/dist/commands/{init.js → init/init.js} +146 -71
  72. package/dist/commands/init/init.js.map +1 -0
  73. package/dist/commands/init/mcp.js +131 -0
  74. package/dist/commands/init/mcp.js.map +1 -0
  75. package/dist/commands/init/options.js +78 -0
  76. package/dist/commands/init/options.js.map +1 -0
  77. package/dist/commands/instructions.js +72 -14
  78. package/dist/commands/instructions.js.map +1 -1
  79. package/dist/commands/list/campaign/campaign.js +65 -0
  80. package/dist/commands/list/campaign/campaign.js.map +1 -0
  81. package/dist/commands/list/campaign/owners.js +65 -0
  82. package/dist/commands/list/campaign/owners.js.map +1 -0
  83. package/dist/commands/list/json.js +157 -0
  84. package/dist/commands/list/json.js.map +1 -0
  85. package/dist/commands/list/list.js +343 -0
  86. package/dist/commands/list/list.js.map +1 -0
  87. package/dist/commands/list/print.js +301 -0
  88. package/dist/commands/list/print.js.map +1 -0
  89. package/dist/commands/list/review.js +120 -0
  90. package/dist/commands/list/review.js.map +1 -0
  91. package/dist/commands/list/views.js +105 -0
  92. package/dist/commands/list/views.js.map +1 -0
  93. package/dist/commands/mcp/dispatch.js +183 -0
  94. package/dist/commands/mcp/dispatch.js.map +1 -0
  95. package/dist/commands/mcp/mcp.js +34 -0
  96. package/dist/commands/mcp/mcp.js.map +1 -0
  97. package/dist/commands/mcp/serve.js +55 -0
  98. package/dist/commands/mcp/serve.js.map +1 -0
  99. package/dist/commands/migrate-openspec/materialize/active.js +170 -0
  100. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
  101. package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
  102. package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
  103. package/dist/commands/migrate-openspec/materialize/living.js +62 -0
  104. package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
  105. package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
  106. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
  107. package/dist/commands/migrate-openspec/materialize/target.js +219 -0
  108. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
  109. package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
  110. package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
  111. package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
  112. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
  113. package/dist/commands/migrate-openspec/openspec/error.js +19 -0
  114. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
  115. package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
  116. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
  117. package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
  118. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
  119. package/dist/commands/migrate-openspec/openspec/read.js +49 -0
  120. package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
  121. package/dist/commands/migrate-openspec/print.js +89 -0
  122. package/dist/commands/migrate-openspec/print.js.map +1 -0
  123. package/dist/commands/new/commit.js +76 -0
  124. package/dist/commands/new/commit.js.map +1 -0
  125. package/dist/commands/new/new.js +283 -0
  126. package/dist/commands/new/new.js.map +1 -0
  127. package/dist/commands/new/templates.js +355 -0
  128. package/dist/commands/new/templates.js.map +1 -0
  129. package/dist/commands/open.js +203 -0
  130. package/dist/commands/open.js.map +1 -0
  131. package/dist/commands/policy/format.js +152 -0
  132. package/dist/commands/policy/format.js.map +1 -0
  133. package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
  134. package/dist/commands/policy/gate.js.map +1 -0
  135. package/dist/commands/rebase/edit.js +106 -0
  136. package/dist/commands/rebase/edit.js.map +1 -0
  137. package/dist/commands/rebase/living.js +185 -0
  138. package/dist/commands/rebase/living.js.map +1 -0
  139. package/dist/commands/rebase/plan.js +245 -0
  140. package/dist/commands/rebase/plan.js.map +1 -0
  141. package/dist/commands/rebase/rebase.js +275 -0
  142. package/dist/commands/rebase/rebase.js.map +1 -0
  143. package/dist/commands/seed/commit.js +192 -0
  144. package/dist/commands/seed/commit.js.map +1 -0
  145. package/dist/commands/seed/plan.js +133 -0
  146. package/dist/commands/seed/plan.js.map +1 -0
  147. package/dist/commands/seed/seed.js +172 -0
  148. package/dist/commands/seed/seed.js.map +1 -0
  149. package/dist/commands/show/feature.js +178 -0
  150. package/dist/commands/show/feature.js.map +1 -0
  151. package/dist/commands/show/marks.js +24 -0
  152. package/dist/commands/show/marks.js.map +1 -0
  153. package/dist/commands/{show.js → show/service.js} +63 -152
  154. package/dist/commands/show/service.js.map +1 -0
  155. package/dist/commands/show/show.js +77 -0
  156. package/dist/commands/show/show.js.map +1 -0
  157. package/dist/commands/status/json.js +45 -0
  158. package/dist/commands/status/json.js.map +1 -0
  159. package/dist/commands/status/print.js +157 -0
  160. package/dist/commands/status/print.js.map +1 -0
  161. package/dist/commands/status/status.js +119 -0
  162. package/dist/commands/status/status.js.map +1 -0
  163. package/dist/commands/steps/steps.js +189 -0
  164. package/dist/commands/steps/steps.js.map +1 -0
  165. package/dist/commands/subsystem/edit.js +192 -0
  166. package/dist/commands/subsystem/edit.js.map +1 -0
  167. package/dist/commands/subsystem/move.js +265 -0
  168. package/dist/commands/subsystem/move.js.map +1 -0
  169. package/dist/commands/subsystem/query.js +115 -0
  170. package/dist/commands/subsystem/query.js.map +1 -0
  171. package/dist/commands/subsystem/subsystem.js +101 -0
  172. package/dist/commands/subsystem/subsystem.js.map +1 -0
  173. package/dist/commands/subsystem/sync.js +103 -0
  174. package/dist/commands/subsystem/sync.js.map +1 -0
  175. package/dist/commands/subsystem/txn/txn.js +94 -0
  176. package/dist/commands/subsystem/txn/txn.js.map +1 -0
  177. package/dist/commands/subsystem/txn/views.js +70 -0
  178. package/dist/commands/subsystem/txn/views.js.map +1 -0
  179. package/dist/commands/unarchive/manifest.js +160 -0
  180. package/dist/commands/unarchive/manifest.js.map +1 -0
  181. package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
  182. package/dist/commands/unarchive/restore.js.map +1 -0
  183. package/dist/commands/unarchive/unarchive.js +67 -0
  184. package/dist/commands/unarchive/unarchive.js.map +1 -0
  185. package/dist/commands/validate/arch-coverage.js +194 -0
  186. package/dist/commands/validate/arch-coverage.js.map +1 -0
  187. package/dist/commands/validate/checks/fleet-shape.js +296 -0
  188. package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
  189. package/dist/commands/validate/checks/health-deps.js +55 -0
  190. package/dist/commands/validate/checks/health-deps.js.map +1 -0
  191. package/dist/commands/validate/checks/requirements.js +254 -0
  192. package/dist/commands/validate/checks/requirements.js.map +1 -0
  193. package/dist/commands/validate/checks/sources.js +76 -0
  194. package/dist/commands/validate/checks/sources.js.map +1 -0
  195. package/dist/commands/validate/checks/vocabulary.js +31 -0
  196. package/dist/commands/validate/checks/vocabulary.js.map +1 -0
  197. package/dist/commands/validate/feature.js +216 -0
  198. package/dist/commands/validate/feature.js.map +1 -0
  199. package/dist/commands/validate/fleet/census.js +69 -0
  200. package/dist/commands/validate/fleet/census.js.map +1 -0
  201. package/dist/commands/validate/fleet/kind-tags.js +39 -0
  202. package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
  203. package/dist/commands/validate/fleet/landscape.js +353 -0
  204. package/dist/commands/validate/fleet/landscape.js.map +1 -0
  205. package/dist/commands/validate/fleet/load.js +52 -0
  206. package/dist/commands/validate/fleet/load.js.map +1 -0
  207. package/dist/commands/validate/fleet/obligations.js +236 -0
  208. package/dist/commands/validate/fleet/obligations.js.map +1 -0
  209. package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
  210. package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
  211. package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
  212. package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
  213. package/dist/commands/validate/fleet/scorecard/print.js +63 -0
  214. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
  215. package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
  216. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
  217. package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
  218. package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
  219. package/dist/commands/validate/fleet/usecases/place.js +13 -0
  220. package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
  221. package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
  222. package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
  223. package/dist/commands/validate/fleet/usecases/steps.js +197 -0
  224. package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
  225. package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
  226. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
  227. package/dist/commands/validate/fleet/views/ids.js +40 -0
  228. package/dist/commands/validate/fleet/views/ids.js.map +1 -0
  229. package/dist/commands/validate/fleet/views/stale.js +59 -0
  230. package/dist/commands/validate/fleet/views/stale.js.map +1 -0
  231. package/dist/commands/validate/links/corpus.js +92 -0
  232. package/dist/commands/validate/links/corpus.js.map +1 -0
  233. package/dist/commands/validate/links/glossary.js +75 -0
  234. package/dist/commands/validate/links/glossary.js.map +1 -0
  235. package/dist/commands/validate/report.js +269 -0
  236. package/dist/commands/validate/report.js.map +1 -0
  237. package/dist/commands/validate/service/api.js +338 -0
  238. package/dist/commands/validate/service/api.js.map +1 -0
  239. package/dist/commands/validate/service/events/events.js +219 -0
  240. package/dist/commands/validate/service/events/events.js.map +1 -0
  241. package/dist/commands/validate/service/events/message-producers.js +77 -0
  242. package/dist/commands/validate/service/events/message-producers.js.map +1 -0
  243. package/dist/commands/validate/service/evidence-pins.js +131 -0
  244. package/dist/commands/validate/service/evidence-pins.js.map +1 -0
  245. package/dist/commands/validate/service/service.js +250 -0
  246. package/dist/commands/validate/service/service.js.map +1 -0
  247. package/dist/commands/validate/service/specs.js +219 -0
  248. package/dist/commands/validate/service/specs.js.map +1 -0
  249. package/dist/commands/validate/service/spine.js +113 -0
  250. package/dist/commands/validate/service/spine.js.map +1 -0
  251. package/dist/commands/validate/validate.js +338 -0
  252. package/dist/commands/validate/validate.js.map +1 -0
  253. package/dist/commands/verify/cross/diff.js +182 -0
  254. package/dist/commands/verify/cross/diff.js.map +1 -0
  255. package/dist/commands/verify/evidence/contract.js +32 -0
  256. package/dist/commands/verify/evidence/contract.js.map +1 -0
  257. package/dist/commands/verify/evidence/pins.js +52 -0
  258. package/dist/commands/verify/evidence/pins.js.map +1 -0
  259. package/dist/commands/verify/evidence/read.js +72 -0
  260. package/dist/commands/verify/evidence/read.js.map +1 -0
  261. package/dist/commands/verify/frozen.js +241 -0
  262. package/dist/commands/verify/frozen.js.map +1 -0
  263. package/dist/commands/verify/record.js +226 -0
  264. package/dist/commands/verify/record.js.map +1 -0
  265. package/dist/commands/verify/report.js +223 -0
  266. package/dist/commands/verify/report.js.map +1 -0
  267. package/dist/commands/verify/results.js +246 -0
  268. package/dist/commands/verify/results.js.map +1 -0
  269. package/dist/commands/verify/verify.js +217 -0
  270. package/dist/commands/verify/verify.js.map +1 -0
  271. package/dist/commands/vouch/contract.js +11 -0
  272. package/dist/commands/vouch/contract.js.map +1 -0
  273. package/dist/commands/vouch/pack/contract.js +2 -0
  274. package/dist/commands/vouch/pack/contract.js.map +1 -0
  275. package/dist/commands/vouch/pack/pack.js +266 -0
  276. package/dist/commands/vouch/pack/pack.js.map +1 -0
  277. package/dist/commands/vouch/pack/print.js +271 -0
  278. package/dist/commands/vouch/pack/print.js.map +1 -0
  279. package/dist/commands/vouch/pack/sections.js +142 -0
  280. package/dist/commands/vouch/pack/sections.js.map +1 -0
  281. package/dist/commands/vouch/prompt/confirm.js +46 -0
  282. package/dist/commands/vouch/prompt/confirm.js.map +1 -0
  283. package/dist/commands/vouch/report.js +101 -0
  284. package/dist/commands/vouch/report.js.map +1 -0
  285. package/dist/commands/vouch/run.js +285 -0
  286. package/dist/commands/vouch/run.js.map +1 -0
  287. package/dist/commands/vouch/sample/plan.js +125 -0
  288. package/dist/commands/vouch/sample/plan.js.map +1 -0
  289. package/dist/commands/vouch/sample/print.js +109 -0
  290. package/dist/commands/vouch/sample/print.js.map +1 -0
  291. package/dist/commands/vouch/vet/verify.js +123 -0
  292. package/dist/commands/vouch/vet/verify.js.map +1 -0
  293. package/dist/commands/vouch/vouch.js +176 -0
  294. package/dist/commands/vouch/vouch.js.map +1 -0
  295. package/dist/core/agent/agents-md/artifacts.js +220 -0
  296. package/dist/core/agent/agents-md/artifacts.js.map +1 -0
  297. package/dist/core/agent/agents-md/command-map.js +324 -0
  298. package/dist/core/agent/agents-md/command-map.js.map +1 -0
  299. package/dist/core/agent/agents-md/cycle.js +165 -0
  300. package/dist/core/agent/agents-md/cycle.js.map +1 -0
  301. package/dist/core/agent/agents-md/map/explain.js +34 -0
  302. package/dist/core/agent/agents-md/map/explain.js.map +1 -0
  303. package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
  304. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
  305. package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
  306. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
  307. package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
  308. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
  309. package/dist/core/agent/agents-md/map/mcp.js +41 -0
  310. package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
  311. package/dist/core/agent/agents-md/map/status.js +62 -0
  312. package/dist/core/agent/agents-md/map/status.js.map +1 -0
  313. package/dist/core/agent/agents-md/map/subsystem.js +32 -0
  314. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
  315. package/dist/core/agent/agents-md/map/usecases.js +60 -0
  316. package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
  317. package/dist/core/agent/agents-md/refusals.js +246 -0
  318. package/dist/core/agent/agents-md/refusals.js.map +1 -0
  319. package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
  320. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
  321. package/dist/core/agent/agents-md/spine.js +40 -0
  322. package/dist/core/agent/agents-md/spine.js.map +1 -0
  323. package/dist/core/agent/agents-md.js +170 -0
  324. package/dist/core/agent/agents-md.js.map +1 -0
  325. package/dist/core/{agents-stamp.js → agent/agents-stamp.js} +50 -0
  326. package/dist/core/agent/agents-stamp.js.map +1 -0
  327. package/dist/core/agent/contract.js +2 -0
  328. package/dist/core/agent/contract.js.map +1 -0
  329. package/dist/core/agent/protocol.js +193 -0
  330. package/dist/core/agent/protocol.js.map +1 -0
  331. package/dist/core/agent/scaffold.js +259 -0
  332. package/dist/core/agent/scaffold.js.map +1 -0
  333. package/dist/core/agent/support/report.js +103 -0
  334. package/dist/core/agent/support/report.js.map +1 -0
  335. package/dist/core/agent/tools/dialects.js +150 -0
  336. package/dist/core/agent/tools/dialects.js.map +1 -0
  337. package/dist/core/agent/tools/registry.js +228 -0
  338. package/dist/core/agent/tools/registry.js.map +1 -0
  339. package/dist/core/agent/workflows/adopt.js +133 -0
  340. package/dist/core/agent/workflows/adopt.js.map +1 -0
  341. package/dist/core/agent/workflows/check.js +334 -0
  342. package/dist/core/agent/workflows/check.js.map +1 -0
  343. package/dist/core/agent/workflows/closing.js +262 -0
  344. package/dist/core/agent/workflows/closing.js.map +1 -0
  345. package/dist/core/agent/workflows/feature.js +105 -0
  346. package/dist/core/agent/workflows/feature.js.map +1 -0
  347. package/dist/core/agent/workflows/implement.js +105 -0
  348. package/dist/core/agent/workflows/implement.js.map +1 -0
  349. package/dist/core/agent/workflows/reference/authoring.js +209 -0
  350. package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
  351. package/dist/core/agent/workflows/reference/codes.js +65 -0
  352. package/dist/core/agent/workflows/reference/codes.js.map +1 -0
  353. package/dist/core/agent/workflows/reference/done-check.js +188 -0
  354. package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
  355. package/dist/core/agent/workflows/reference/reference.js +16 -0
  356. package/dist/core/agent/workflows/reference/reference.js.map +1 -0
  357. package/dist/core/agent/workflows/reference/spine.js +297 -0
  358. package/dist/core/agent/workflows/reference/spine.js.map +1 -0
  359. package/dist/core/asyncapi/baseline/plan.js +110 -0
  360. package/dist/core/asyncapi/baseline/plan.js.map +1 -0
  361. package/dist/core/asyncapi/baseline/surfaces.js +54 -0
  362. package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
  363. package/dist/core/asyncapi/depth.js +164 -0
  364. package/dist/core/asyncapi/depth.js.map +1 -0
  365. package/dist/core/asyncapi/digest.js +141 -0
  366. package/dist/core/asyncapi/digest.js.map +1 -0
  367. package/dist/core/asyncapi/merge/components.js +91 -0
  368. package/dist/core/asyncapi/merge/components.js.map +1 -0
  369. package/dist/core/asyncapi/merge/error.js +33 -0
  370. package/dist/core/asyncapi/merge/error.js.map +1 -0
  371. package/dist/core/asyncapi/merge/markers.js +308 -0
  372. package/dist/core/asyncapi/merge/markers.js.map +1 -0
  373. package/dist/core/asyncapi/merge/merge.js +215 -0
  374. package/dist/core/asyncapi/merge/merge.js.map +1 -0
  375. package/dist/core/asyncapi/merge/pin.js +107 -0
  376. package/dist/core/asyncapi/merge/pin.js.map +1 -0
  377. package/dist/core/asyncapi/model.js +11 -0
  378. package/dist/core/asyncapi/model.js.map +1 -0
  379. package/dist/core/asyncapi/producers.js +50 -0
  380. package/dist/core/asyncapi/producers.js.map +1 -0
  381. package/dist/core/{asyncapi.js → asyncapi/read.js} +95 -109
  382. package/dist/core/asyncapi/read.js.map +1 -0
  383. package/dist/core/brief/brief.js +225 -0
  384. package/dist/core/brief/brief.js.map +1 -0
  385. package/dist/core/brief/checks.js +250 -0
  386. package/dist/core/brief/checks.js.map +1 -0
  387. package/dist/core/brief/landscape.js +194 -0
  388. package/dist/core/brief/landscape.js.map +1 -0
  389. package/dist/core/brief/targets.js +226 -0
  390. package/dist/core/brief/targets.js.map +1 -0
  391. package/dist/core/brief/unchecked.js +47 -0
  392. package/dist/core/brief/unchecked.js.map +1 -0
  393. package/dist/core/c4/arch.js +282 -0
  394. package/dist/core/c4/arch.js.map +1 -0
  395. package/dist/core/c4/likec4.js +88 -0
  396. package/dist/core/c4/likec4.js.map +1 -0
  397. package/dist/core/c4/parsed/dynamic-views.js +132 -0
  398. package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
  399. package/dist/core/c4/parsed/specification.js +78 -0
  400. package/dist/core/c4/parsed/specification.js.map +1 -0
  401. package/dist/core/c4/parsed/values.js +57 -0
  402. package/dist/core/c4/parsed/values.js.map +1 -0
  403. package/dist/core/c4/parsed/view-ids.js +75 -0
  404. package/dist/core/c4/parsed/view-ids.js.map +1 -0
  405. package/dist/core/c4/project/architecture.js +45 -0
  406. package/dist/core/c4/project/architecture.js.map +1 -0
  407. package/dist/core/c4/project/documents.js +58 -0
  408. package/dist/core/c4/project/documents.js.map +1 -0
  409. package/dist/core/c4/project/load.js +183 -0
  410. package/dist/core/c4/project/load.js.map +1 -0
  411. package/dist/core/c4/resolve/service.js +113 -0
  412. package/dist/core/c4/resolve/service.js.map +1 -0
  413. package/dist/core/c4/seed/fleet-file.js +201 -0
  414. package/dist/core/c4/seed/fleet-file.js.map +1 -0
  415. package/dist/core/c4/seed/items.js +91 -0
  416. package/dist/core/c4/seed/items.js.map +1 -0
  417. package/dist/core/c4/seed/stamp.js +48 -0
  418. package/dist/core/c4/seed/stamp.js.map +1 -0
  419. package/dist/core/c4/seed/template.js +109 -0
  420. package/dist/core/c4/seed/template.js.map +1 -0
  421. package/dist/core/c4/source-mask.js +82 -0
  422. package/dist/core/c4/source-mask.js.map +1 -0
  423. package/dist/core/c4/source-scan.js +227 -0
  424. package/dist/core/c4/source-scan.js.map +1 -0
  425. package/dist/core/c4/splice/authored-source.js +101 -0
  426. package/dist/core/c4/splice/authored-source.js.map +1 -0
  427. package/dist/core/c4/splice/contract.js +10 -0
  428. package/dist/core/c4/splice/contract.js.map +1 -0
  429. package/dist/core/c4/splice/delta-blocks.js +126 -0
  430. package/dist/core/c4/splice/delta-blocks.js.map +1 -0
  431. package/dist/core/c4/splice/identity/edges.js +36 -0
  432. package/dist/core/c4/splice/identity/edges.js.map +1 -0
  433. package/dist/core/c4/splice/landscape-merge.js +269 -0
  434. package/dist/core/c4/splice/landscape-merge.js.map +1 -0
  435. package/dist/core/c4/splice/placement.js +194 -0
  436. package/dist/core/c4/splice/placement.js.map +1 -0
  437. package/dist/core/c4/workspace.js +172 -0
  438. package/dist/core/c4/workspace.js.map +1 -0
  439. package/dist/core/capabilities/authoring/notes.js +141 -0
  440. package/dist/core/capabilities/authoring/notes.js.map +1 -0
  441. package/dist/core/capabilities/capabilities.js +124 -0
  442. package/dist/core/capabilities/capabilities.js.map +1 -0
  443. package/dist/core/capabilities/delta/doc.js +50 -0
  444. package/dist/core/capabilities/delta/doc.js.map +1 -0
  445. package/dist/core/capabilities/delta/overlay.js +129 -0
  446. package/dist/core/capabilities/delta/overlay.js.map +1 -0
  447. package/dist/core/capabilities/delta/summary.js +57 -0
  448. package/dist/core/capabilities/delta/summary.js.map +1 -0
  449. package/dist/core/capabilities/delta/tree.js +43 -0
  450. package/dist/core/capabilities/delta/tree.js.map +1 -0
  451. package/dist/core/capabilities/delta/uncovered.js +222 -0
  452. package/dist/core/capabilities/delta/uncovered.js.map +1 -0
  453. package/dist/core/capabilities/findings.js +342 -0
  454. package/dist/core/capabilities/findings.js.map +1 -0
  455. package/dist/core/capabilities/realizes/findings.js +181 -0
  456. package/dist/core/capabilities/realizes/findings.js.map +1 -0
  457. package/dist/core/capabilities/realizes/join.js +143 -0
  458. package/dist/core/capabilities/realizes/join.js.map +1 -0
  459. package/dist/core/capabilities/rollup.js +199 -0
  460. package/dist/core/capabilities/rollup.js.map +1 -0
  461. package/dist/core/capabilities/tree.js +126 -0
  462. package/dist/core/capabilities/tree.js.map +1 -0
  463. package/dist/core/capabilities/usecase-join.js +277 -0
  464. package/dist/core/capabilities/usecase-join.js.map +1 -0
  465. package/dist/core/coherence/authoring/scaffold.js +177 -0
  466. package/dist/core/coherence/authoring/scaffold.js.map +1 -0
  467. package/dist/core/coherence/authoring/sentinels.js +74 -0
  468. package/dist/core/coherence/authoring/sentinels.js.map +1 -0
  469. package/dist/core/coherence/coherence.js +251 -0
  470. package/dist/core/coherence/coherence.js.map +1 -0
  471. package/dist/core/coherence/declared.js +269 -0
  472. package/dist/core/coherence/declared.js.map +1 -0
  473. package/dist/core/coherence/events/declared.js +206 -0
  474. package/dist/core/coherence/events/declared.js.map +1 -0
  475. package/dist/core/coherence/events/events.js +204 -0
  476. package/dist/core/coherence/events/events.js.map +1 -0
  477. package/dist/core/coherence/events/grades.js +166 -0
  478. package/dist/core/coherence/events/grades.js.map +1 -0
  479. package/dist/core/coherence/events/lookups.js +122 -0
  480. package/dist/core/coherence/events/lookups.js.map +1 -0
  481. package/dist/core/coherence/events/merged.js +40 -0
  482. package/dist/core/coherence/events/merged.js.map +1 -0
  483. package/dist/core/coherence/living.js +198 -0
  484. package/dist/core/coherence/living.js.map +1 -0
  485. package/dist/core/coherence/lookups.js +155 -0
  486. package/dist/core/coherence/lookups.js.map +1 -0
  487. package/dist/core/coherence/pending.js +39 -0
  488. package/dist/core/coherence/pending.js.map +1 -0
  489. package/dist/core/conflict-markers.js +60 -0
  490. package/dist/core/conflict-markers.js.map +1 -0
  491. package/dist/core/delta/claims.js +73 -0
  492. package/dist/core/delta/claims.js.map +1 -0
  493. package/dist/core/delta/delta.js +296 -0
  494. package/dist/core/delta/delta.js.map +1 -0
  495. package/dist/core/delta/document.js +177 -0
  496. package/dist/core/delta/document.js.map +1 -0
  497. package/dist/core/delta/scope.js +48 -0
  498. package/dist/core/delta/scope.js.map +1 -0
  499. package/dist/core/delta/select.js +237 -0
  500. package/dist/core/delta/select.js.map +1 -0
  501. package/dist/core/dependencies/dependencies.js +141 -0
  502. package/dist/core/dependencies/dependencies.js.map +1 -0
  503. package/dist/core/dependencies/facts.js +165 -0
  504. package/dist/core/dependencies/facts.js.map +1 -0
  505. package/dist/core/dependencies/fanin.js +88 -0
  506. package/dist/core/dependencies/fanin.js.map +1 -0
  507. package/dist/core/dependencies/graph.js +106 -0
  508. package/dist/core/dependencies/graph.js.map +1 -0
  509. package/dist/core/diff/base-git.js +148 -0
  510. package/dist/core/diff/base-git.js.map +1 -0
  511. package/dist/core/diff/base-state.js +209 -0
  512. package/dist/core/diff/base-state.js.map +1 -0
  513. package/dist/core/diff/current-state.js +96 -0
  514. package/dist/core/diff/current-state.js.map +1 -0
  515. package/dist/core/diff/scope/changed-paths.js +188 -0
  516. package/dist/core/diff/scope/changed-paths.js.map +1 -0
  517. package/dist/core/diff/semantic.js +243 -0
  518. package/dist/core/diff/semantic.js.map +1 -0
  519. package/dist/core/diff/victims.js +247 -0
  520. package/dist/core/diff/victims.js.map +1 -0
  521. package/dist/core/docs.js +7 -64
  522. package/dist/core/docs.js.map +1 -1
  523. package/dist/core/doctor/agents.js +222 -0
  524. package/dist/core/doctor/agents.js.map +1 -0
  525. package/dist/core/doctor/config.js +135 -0
  526. package/dist/core/doctor/config.js.map +1 -0
  527. package/dist/core/doctor/doctor.js +275 -0
  528. package/dist/core/doctor/doctor.js.map +1 -0
  529. package/dist/core/doctor/report.js +13 -0
  530. package/dist/core/doctor/report.js.map +1 -0
  531. package/dist/core/doctor/residue.js +110 -0
  532. package/dist/core/doctor/residue.js.map +1 -0
  533. package/dist/core/document/apply.js +77 -0
  534. package/dist/core/document/apply.js.map +1 -0
  535. package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
  536. package/dist/core/document/frontmatter.js.map +1 -0
  537. package/dist/core/document/grammar/directives.js +143 -0
  538. package/dist/core/document/grammar/directives.js.map +1 -0
  539. package/dist/core/document/parse.js +263 -0
  540. package/dist/core/document/parse.js.map +1 -0
  541. package/dist/core/document/scenarios.js +221 -0
  542. package/dist/core/document/scenarios.js.map +1 -0
  543. package/dist/core/document/spec.js +241 -0
  544. package/dist/core/document/spec.js.map +1 -0
  545. package/dist/core/{config.js → envelope/config.js} +79 -18
  546. package/dist/core/envelope/config.js.map +1 -0
  547. package/dist/core/envelope/json.js +175 -0
  548. package/dist/core/envelope/json.js.map +1 -0
  549. package/dist/core/{version.js → envelope/version.js} +3 -3
  550. package/dist/core/envelope/version.js.map +1 -0
  551. package/dist/core/explain/families.js +175 -0
  552. package/dist/core/explain/families.js.map +1 -0
  553. package/dist/core/explain/fix-tables.js +178 -0
  554. package/dist/core/explain/fix-tables.js.map +1 -0
  555. package/dist/core/explain/lookup.js +181 -0
  556. package/dist/core/explain/lookup.js.map +1 -0
  557. package/dist/core/explain/refusals.js +49 -0
  558. package/dist/core/explain/refusals.js.map +1 -0
  559. package/dist/core/explain/terms.js +144 -0
  560. package/dist/core/explain/terms.js.map +1 -0
  561. package/dist/core/explore/describe.js +105 -0
  562. package/dist/core/explore/describe.js.map +1 -0
  563. package/dist/core/{explore.js → explore/explore.js} +50 -101
  564. package/dist/core/explore/explore.js.map +1 -0
  565. package/dist/core/fleet-context.js +120 -78
  566. package/dist/core/fleet-context.js.map +1 -1
  567. package/dist/core/gate/checks.js +201 -0
  568. package/dist/core/gate/checks.js.map +1 -0
  569. package/dist/core/gate/partners.js +205 -0
  570. package/dist/core/gate/partners.js.map +1 -0
  571. package/dist/core/gate/report.js +47 -0
  572. package/dist/core/gate/report.js.map +1 -0
  573. package/dist/core/gate/verification.js +113 -0
  574. package/dist/core/gate/verification.js.map +1 -0
  575. package/dist/core/gherkin/digest.js +60 -0
  576. package/dist/core/gherkin/digest.js.map +1 -0
  577. package/dist/core/gherkin/emit.js +177 -0
  578. package/dist/core/gherkin/emit.js.map +1 -0
  579. package/dist/core/gherkin/read.js +76 -0
  580. package/dist/core/gherkin/read.js.map +1 -0
  581. package/dist/core/gherkin/stale.js +234 -0
  582. package/dist/core/gherkin/stale.js.map +1 -0
  583. package/dist/core/gherkin/stamp.js +77 -0
  584. package/dist/core/gherkin/stamp.js.map +1 -0
  585. package/dist/core/gherkin/steps/catalogue.js +154 -0
  586. package/dist/core/gherkin/steps/catalogue.js.map +1 -0
  587. package/dist/core/gherkin/steps/inventory.js +98 -0
  588. package/dist/core/gherkin/steps/inventory.js.map +1 -0
  589. package/dist/core/gherkin/steps/phrase.js +72 -0
  590. package/dist/core/gherkin/steps/phrase.js.map +1 -0
  591. package/dist/core/glossary/delta.js +106 -0
  592. package/dist/core/glossary/delta.js.map +1 -0
  593. package/dist/core/glossary/tree.js +86 -0
  594. package/dist/core/glossary/tree.js.map +1 -0
  595. package/dist/core/kernel/concurrency.js.map +1 -0
  596. package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
  597. package/dist/core/kernel/document-bytes.js.map +1 -0
  598. package/dist/core/kernel/fences.js +42 -0
  599. package/dist/core/kernel/fences.js.map +1 -0
  600. package/dist/core/kernel/ids/capability.js +72 -0
  601. package/dist/core/kernel/ids/capability.js.map +1 -0
  602. package/dist/core/kernel/ids/dirs.js +33 -0
  603. package/dist/core/kernel/ids/dirs.js.map +1 -0
  604. package/dist/core/kernel/ids/feature.js +75 -0
  605. package/dist/core/kernel/ids/feature.js.map +1 -0
  606. package/dist/core/kernel/ids/service.js +156 -0
  607. package/dist/core/kernel/ids/service.js.map +1 -0
  608. package/dist/core/kernel/ids/subsystem.js +63 -0
  609. package/dist/core/kernel/ids/subsystem.js.map +1 -0
  610. package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
  611. package/dist/core/kernel/path-safety.js.map +1 -0
  612. package/dist/core/kernel/records.js.map +1 -0
  613. package/dist/core/links/backlinks.js +68 -0
  614. package/dist/core/links/backlinks.js.map +1 -0
  615. package/dist/core/links/case.js +74 -0
  616. package/dist/core/links/case.js.map +1 -0
  617. package/dist/core/links/corpus.js +104 -0
  618. package/dist/core/links/corpus.js.map +1 -0
  619. package/dist/core/links/findings.js +110 -0
  620. package/dist/core/links/findings.js.map +1 -0
  621. package/dist/core/links/parse.js +236 -0
  622. package/dist/core/links/parse.js.map +1 -0
  623. package/dist/core/mcp/argv.js +111 -0
  624. package/dist/core/mcp/argv.js.map +1 -0
  625. package/dist/core/mcp/author-tools.js +72 -0
  626. package/dist/core/mcp/author-tools.js.map +1 -0
  627. package/dist/core/mcp/framing.js +46 -0
  628. package/dist/core/mcp/framing.js.map +1 -0
  629. package/dist/core/mcp/protocol.js +280 -0
  630. package/dist/core/mcp/protocol.js.map +1 -0
  631. package/dist/core/mcp/tools.js +314 -0
  632. package/dist/core/mcp/tools.js.map +1 -0
  633. package/dist/core/obligations/obligations.js +106 -0
  634. package/dist/core/obligations/obligations.js.map +1 -0
  635. package/dist/core/openapi/baseline/gate.js +166 -0
  636. package/dist/core/openapi/baseline/gate.js.map +1 -0
  637. package/dist/core/openapi/baseline/plan.js +112 -0
  638. package/dist/core/openapi/baseline/plan.js.map +1 -0
  639. package/dist/core/openapi/baseline/record.js +175 -0
  640. package/dist/core/openapi/baseline/record.js.map +1 -0
  641. package/dist/core/openapi/depth.js +122 -0
  642. package/dist/core/openapi/depth.js.map +1 -0
  643. package/dist/core/openapi/digest.js +189 -0
  644. package/dist/core/openapi/digest.js.map +1 -0
  645. package/dist/core/{openapi.js → openapi/doc.js} +44 -106
  646. package/dist/core/openapi/doc.js.map +1 -0
  647. package/dist/core/openapi/merge/components.js +234 -0
  648. package/dist/core/openapi/merge/components.js.map +1 -0
  649. package/dist/core/openapi/merge/error.js +26 -0
  650. package/dist/core/openapi/merge/error.js.map +1 -0
  651. package/dist/core/openapi/merge/markers.js +208 -0
  652. package/dist/core/openapi/merge/markers.js.map +1 -0
  653. package/dist/core/openapi/merge/merge.js +256 -0
  654. package/dist/core/openapi/merge/merge.js.map +1 -0
  655. package/dist/core/openapi/merge/pin.js +128 -0
  656. package/dist/core/openapi/merge/pin.js.map +1 -0
  657. package/dist/core/openspec/decide/capabilities.js +244 -0
  658. package/dist/core/openspec/decide/capabilities.js.map +1 -0
  659. package/dist/core/openspec/decide/dispositions.js +63 -0
  660. package/dist/core/openspec/decide/dispositions.js.map +1 -0
  661. package/dist/core/openspec/decide/renames.js +179 -0
  662. package/dist/core/openspec/decide/renames.js.map +1 -0
  663. package/dist/core/openspec/decide/verdict.js +84 -0
  664. package/dist/core/openspec/decide/verdict.js.map +1 -0
  665. package/dist/core/openspec/inventory.js +221 -0
  666. package/dist/core/openspec/inventory.js.map +1 -0
  667. package/dist/core/openspec/model/mapping.js +76 -0
  668. package/dist/core/openspec/model/mapping.js.map +1 -0
  669. package/dist/core/openspec/model/model.js +65 -0
  670. package/dist/core/openspec/model/model.js.map +1 -0
  671. package/dist/core/openspec/scan/changes.js +235 -0
  672. package/dist/core/openspec/scan/changes.js.map +1 -0
  673. package/dist/core/openspec/scan/config.js +200 -0
  674. package/dist/core/openspec/scan/config.js.map +1 -0
  675. package/dist/core/openspec/scan/shape.js +260 -0
  676. package/dist/core/openspec/scan/shape.js.map +1 -0
  677. package/dist/core/openspec/scan/walk.js +133 -0
  678. package/dist/core/openspec/scan/walk.js.map +1 -0
  679. package/dist/core/openspec/scan/workspace.js +71 -0
  680. package/dist/core/openspec/scan/workspace.js.map +1 -0
  681. package/dist/core/owners/codeowners.js +141 -0
  682. package/dist/core/owners/codeowners.js.map +1 -0
  683. package/dist/core/pack/features.js +120 -0
  684. package/dist/core/pack/features.js.map +1 -0
  685. package/dist/core/pack/joins.js +112 -0
  686. package/dist/core/pack/joins.js.map +1 -0
  687. package/dist/core/pack/living.js +118 -0
  688. package/dist/core/pack/living.js.map +1 -0
  689. package/dist/core/pack/pack.js +76 -0
  690. package/dist/core/pack/pack.js.map +1 -0
  691. package/dist/core/permissions/permissions.js +87 -0
  692. package/dist/core/permissions/permissions.js.map +1 -0
  693. package/dist/core/projection/api.js +84 -0
  694. package/dist/core/projection/api.js.map +1 -0
  695. package/dist/core/projection/arch-slice.js +63 -0
  696. package/dist/core/projection/arch-slice.js.map +1 -0
  697. package/dist/core/projection/events.js +34 -0
  698. package/dist/core/projection/events.js.map +1 -0
  699. package/dist/core/provenance/findings.js +208 -0
  700. package/dist/core/provenance/findings.js.map +1 -0
  701. package/dist/core/provenance/git.js +225 -0
  702. package/dist/core/provenance/git.js.map +1 -0
  703. package/dist/core/provenance/gitq/moves.js +182 -0
  704. package/dist/core/provenance/gitq/moves.js.map +1 -0
  705. package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
  706. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
  707. package/dist/core/provenance/sample/scope.js +238 -0
  708. package/dist/core/provenance/sample/scope.js.map +1 -0
  709. package/dist/core/provenance/sample/sections.js +61 -0
  710. package/dist/core/provenance/sample/sections.js.map +1 -0
  711. package/dist/core/provenance/sources.js +263 -0
  712. package/dist/core/provenance/sources.js.map +1 -0
  713. package/dist/core/provenance/stamp.js +232 -0
  714. package/dist/core/provenance/stamp.js.map +1 -0
  715. package/dist/core/provenance/walk.js +157 -0
  716. package/dist/core/provenance/walk.js.map +1 -0
  717. package/dist/core/repo/authored/paths.js +192 -0
  718. package/dist/core/repo/authored/paths.js.map +1 -0
  719. package/dist/core/repo/entries.js +126 -0
  720. package/dist/core/repo/entries.js.map +1 -0
  721. package/dist/core/repo/paths.js +236 -0
  722. package/dist/core/repo/paths.js.map +1 -0
  723. package/dist/core/repo/repo.js +248 -0
  724. package/dist/core/repo/repo.js.map +1 -0
  725. package/dist/core/repo/service-target.js +153 -0
  726. package/dist/core/repo/service-target.js.map +1 -0
  727. package/dist/core/repo/state.js +58 -0
  728. package/dist/core/repo/state.js.map +1 -0
  729. package/dist/core/repo/tree/find.js +52 -0
  730. package/dist/core/repo/tree/find.js.map +1 -0
  731. package/dist/core/repo/tree/fs.js +64 -0
  732. package/dist/core/repo/tree/fs.js.map +1 -0
  733. package/dist/core/repo/tree/marker.js +61 -0
  734. package/dist/core/repo/tree/marker.js.map +1 -0
  735. package/dist/core/repo/tree/views.js +172 -0
  736. package/dist/core/repo/tree/views.js.map +1 -0
  737. package/dist/core/repo/tree/walk.js +234 -0
  738. package/dist/core/repo/tree/walk.js.map +1 -0
  739. package/dist/core/results.js +99 -2
  740. package/dist/core/results.js.map +1 -1
  741. package/dist/core/scaffold/landscape.js +131 -0
  742. package/dist/core/scaffold/landscape.js.map +1 -0
  743. package/dist/core/scaffold/readme.js +84 -0
  744. package/dist/core/scaffold/readme.js.map +1 -0
  745. package/dist/core/staging/commit.js +241 -0
  746. package/dist/core/staging/commit.js.map +1 -0
  747. package/dist/core/staging/interrupted.js +96 -0
  748. package/dist/core/staging/interrupted.js.map +1 -0
  749. package/dist/core/staging/lock.js +206 -0
  750. package/dist/core/staging/lock.js.map +1 -0
  751. package/dist/core/staging/recovery/finding.js +35 -0
  752. package/dist/core/staging/recovery/finding.js.map +1 -0
  753. package/dist/core/staging/recovery/intent.js +98 -0
  754. package/dist/core/staging/recovery/intent.js.map +1 -0
  755. package/dist/core/staging/recovery/recover.js +126 -0
  756. package/dist/core/staging/recovery/recover.js.map +1 -0
  757. package/dist/core/staging/recovery/residue.js +62 -0
  758. package/dist/core/staging/recovery/residue.js.map +1 -0
  759. package/dist/core/staging/snapshot.js +202 -0
  760. package/dist/core/staging/snapshot.js.map +1 -0
  761. package/dist/core/staging/txn/forward.js +171 -0
  762. package/dist/core/staging/txn/forward.js.map +1 -0
  763. package/dist/core/staging/txn/journal.js +139 -0
  764. package/dist/core/staging/txn/journal.js.map +1 -0
  765. package/dist/core/staging/txn/transaction.js +75 -0
  766. package/dist/core/staging/txn/transaction.js.map +1 -0
  767. package/dist/core/staging/writes.js +113 -0
  768. package/dist/core/staging/writes.js.map +1 -0
  769. package/dist/core/status/actions/execution.js +49 -0
  770. package/dist/core/status/actions/execution.js.map +1 -0
  771. package/dist/core/status/contracts.js +102 -0
  772. package/dist/core/status/contracts.js.map +1 -0
  773. package/dist/core/status/feature/artifacts.js +171 -0
  774. package/dist/core/status/feature/artifacts.js.map +1 -0
  775. package/dist/core/status/feature/feature.js +207 -0
  776. package/dist/core/status/feature/feature.js.map +1 -0
  777. package/dist/core/status/feature/next.js +150 -0
  778. package/dist/core/status/feature/next.js.map +1 -0
  779. package/dist/core/status/feature/state.js +21 -0
  780. package/dist/core/status/feature/state.js.map +1 -0
  781. package/dist/core/status/fleet/fleet.js +187 -0
  782. package/dist/core/status/fleet/fleet.js.map +1 -0
  783. package/dist/core/status/fleet/next.js +207 -0
  784. package/dist/core/status/fleet/next.js.map +1 -0
  785. package/dist/core/status/interrupted.js +97 -0
  786. package/dist/core/status/interrupted.js.map +1 -0
  787. package/dist/core/status/report.js +31 -0
  788. package/dist/core/status/report.js.map +1 -0
  789. package/dist/core/status/scan.js +41 -0
  790. package/dist/core/status/scan.js.map +1 -0
  791. package/dist/core/status/verification.js +201 -0
  792. package/dist/core/status/verification.js.map +1 -0
  793. package/dist/core/usecases/capability.js +169 -0
  794. package/dist/core/usecases/capability.js.map +1 -0
  795. package/dist/core/usecases/delta/claims.js +116 -0
  796. package/dist/core/usecases/delta/claims.js.map +1 -0
  797. package/dist/core/usecases/delta/flows.js +103 -0
  798. package/dist/core/usecases/delta/flows.js.map +1 -0
  799. package/dist/core/usecases/delta/overlay.js +235 -0
  800. package/dist/core/usecases/delta/overlay.js.map +1 -0
  801. package/dist/core/usecases/fleet.js +137 -0
  802. package/dist/core/usecases/fleet.js.map +1 -0
  803. package/dist/core/usecases/operations.js +111 -0
  804. package/dist/core/usecases/operations.js.map +1 -0
  805. package/dist/core/usecases/place.js +44 -0
  806. package/dist/core/usecases/place.js.map +1 -0
  807. package/dist/core/usecases/touch.js +101 -0
  808. package/dist/core/usecases/touch.js.map +1 -0
  809. package/dist/core/verify/answers.js +173 -0
  810. package/dist/core/verify/answers.js.map +1 -0
  811. package/dist/core/verify/build.js +150 -0
  812. package/dist/core/verify/build.js.map +1 -0
  813. package/dist/core/verify/checklist.js +201 -0
  814. package/dist/core/verify/checklist.js.map +1 -0
  815. package/dist/core/verify/claims/identity.js +61 -0
  816. package/dist/core/verify/claims/identity.js.map +1 -0
  817. package/dist/core/verify/cross/diff.js +97 -0
  818. package/dist/core/verify/cross/diff.js.map +1 -0
  819. package/dist/core/verify/evidence/contract.js +169 -0
  820. package/dist/core/verify/evidence/contract.js.map +1 -0
  821. package/dist/core/verify/file.js +257 -0
  822. package/dist/core/verify/file.js.map +1 -0
  823. package/dist/core/verify/pins/lint.js +157 -0
  824. package/dist/core/verify/pins/lint.js.map +1 -0
  825. package/dist/core/verify/pins/pin.js +95 -0
  826. package/dist/core/verify/pins/pin.js.map +1 -0
  827. package/dist/core/verify/record.js +142 -0
  828. package/dist/core/verify/record.js.map +1 -0
  829. package/dist/core/verify/store/commit.js +89 -0
  830. package/dist/core/verify/store/commit.js.map +1 -0
  831. package/dist/core/verify/store/render.js +57 -0
  832. package/dist/core/verify/store/render.js.map +1 -0
  833. package/dist/core/vocabulary/health.js +91 -0
  834. package/dist/core/vocabulary/health.js.map +1 -0
  835. package/dist/core/vocabulary/issue.js +55 -0
  836. package/dist/core/vocabulary/issue.js.map +1 -0
  837. package/dist/core/vocabulary/maturity.js +145 -0
  838. package/dist/core/vocabulary/maturity.js.map +1 -0
  839. package/dist/core/{report.js → vocabulary/report.js} +30 -2
  840. package/dist/core/vocabulary/report.js.map +1 -0
  841. package/dist/core/vocabulary/steps.js +266 -0
  842. package/dist/core/vocabulary/steps.js.map +1 -0
  843. package/dist/core/workspace/discover.js +239 -0
  844. package/dist/core/workspace/discover.js.map +1 -0
  845. package/dist/core/workspace/render.js +52 -0
  846. package/dist/core/workspace/render.js.map +1 -0
  847. package/examples/README.md +189 -0
  848. package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
  849. package/examples/docs/architecture/capabilities.yaml +31 -0
  850. package/examples/docs/architecture/landscape.likec4 +280 -0
  851. package/examples/docs/architecture/obligations.yaml +23 -0
  852. package/examples/docs/architecture/permissions.yaml +50 -0
  853. package/examples/docs/architecture/subsystems.likec4 +11 -0
  854. package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
  855. package/examples/docs/capabilities/checkout/spec.md +55 -0
  856. package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
  857. package/examples/docs/capabilities/order-notifications/spec.md +43 -0
  858. package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
  859. package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
  860. package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
  861. package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
  862. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
  863. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
  864. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
  865. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
  866. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
  867. package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
  868. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
  869. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
  870. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
  871. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
  872. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
  873. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
  874. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
  875. package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
  876. package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
  877. package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
  878. package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
  879. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
  880. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
  881. package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
  882. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
  883. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
  884. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
  885. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
  886. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
  887. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
  888. package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
  889. package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
  890. package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
  891. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
  892. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
  893. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
  894. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
  895. package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
  896. package/examples/docs/glossary/order.md +15 -0
  897. package/examples/docs/glossary/payments/authorization.md +14 -0
  898. package/examples/docs/likec4.config.json +9 -0
  899. package/examples/docs/loam.json +3 -0
  900. package/examples/docs/services/checkout-web/model.likec4 +109 -0
  901. package/examples/docs/services/checkout-web/spec.md +91 -0
  902. package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
  903. package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
  904. package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
  905. package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
  906. package/examples/docs/services/order-service/arch.spec.md +127 -0
  907. package/examples/docs/services/order-service/asyncapi.yaml +108 -0
  908. package/examples/docs/services/order-service/health.yaml +57 -0
  909. package/examples/docs/services/order-service/model.likec4 +138 -0
  910. package/examples/docs/services/order-service/openapi.yaml +257 -0
  911. package/examples/docs/services/order-service/runbook.md +62 -0
  912. package/examples/docs/services/order-service/spec.md +134 -0
  913. package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
  914. package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
  915. package/examples/docs/services/payment-service/arch.spec.md +73 -0
  916. package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
  917. package/examples/docs/services/payment-service/health.yaml +39 -0
  918. package/examples/docs/services/payment-service/model.likec4 +67 -0
  919. package/examples/docs/services/payment-service/openapi.yaml +151 -0
  920. package/examples/docs/services/payment-service/runbook.md +37 -0
  921. package/examples/docs/services/payment-service/spec.md +88 -0
  922. package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
  923. package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
  924. package/examples/docs/services/platform/identity-service/health.yaml +70 -0
  925. package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
  926. package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
  927. package/examples/docs/services/platform/identity-service/runbook.md +95 -0
  928. package/examples/docs/services/platform/identity-service/spec.md +119 -0
  929. package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
  930. package/examples/docs/services/platform/notification-service/health.yaml +35 -0
  931. package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
  932. package/examples/docs/services/platform/notification-service/runbook.md +48 -0
  933. package/examples/docs/services/platform/notification-service/spec.md +112 -0
  934. package/examples/docs/services/platform/subsystem.yaml +2 -0
  935. package/examples/fleet.yaml +57 -0
  936. package/package.json +19 -6
  937. package/dist/commands/adopt.js +0 -240
  938. package/dist/commands/adopt.js.map +0 -1
  939. package/dist/commands/archive.js +0 -1313
  940. package/dist/commands/archive.js.map +0 -1
  941. package/dist/commands/delta.js +0 -388
  942. package/dist/commands/delta.js.map +0 -1
  943. package/dist/commands/docs-repo-gate.js.map +0 -1
  944. package/dist/commands/format.js +0 -17
  945. package/dist/commands/format.js.map +0 -1
  946. package/dist/commands/gherkin.js +0 -309
  947. package/dist/commands/gherkin.js.map +0 -1
  948. package/dist/commands/init.js.map +0 -1
  949. package/dist/commands/list.js +0 -295
  950. package/dist/commands/list.js.map +0 -1
  951. package/dist/commands/migrate-openspec.js +0 -950
  952. package/dist/commands/migrate-openspec.js.map +0 -1
  953. package/dist/commands/new.js +0 -413
  954. package/dist/commands/new.js.map +0 -1
  955. package/dist/commands/rebase.js +0 -356
  956. package/dist/commands/rebase.js.map +0 -1
  957. package/dist/commands/show.js.map +0 -1
  958. package/dist/commands/status.js +0 -262
  959. package/dist/commands/status.js.map +0 -1
  960. package/dist/commands/unarchive.js.map +0 -1
  961. package/dist/commands/validate.js +0 -1773
  962. package/dist/commands/validate.js.map +0 -1
  963. package/dist/commands/verify.js +0 -775
  964. package/dist/commands/verify.js.map +0 -1
  965. package/dist/commands/vouch.js +0 -421
  966. package/dist/commands/vouch.js.map +0 -1
  967. package/dist/core/agent.js +0 -2173
  968. package/dist/core/agent.js.map +0 -1
  969. package/dist/core/agents-stamp.js.map +0 -1
  970. package/dist/core/arch.js +0 -130
  971. package/dist/core/arch.js.map +0 -1
  972. package/dist/core/asyncapi.js.map +0 -1
  973. package/dist/core/brief.js +0 -755
  974. package/dist/core/brief.js.map +0 -1
  975. package/dist/core/coherence.js +0 -577
  976. package/dist/core/coherence.js.map +0 -1
  977. package/dist/core/concurrency.js.map +0 -1
  978. package/dist/core/config.js.map +0 -1
  979. package/dist/core/delta.js +0 -476
  980. package/dist/core/delta.js.map +0 -1
  981. package/dist/core/dependencies.js +0 -356
  982. package/dist/core/dependencies.js.map +0 -1
  983. package/dist/core/doctor.js +0 -596
  984. package/dist/core/doctor.js.map +0 -1
  985. package/dist/core/document-bytes.js.map +0 -1
  986. package/dist/core/explore.js.map +0 -1
  987. package/dist/core/frontmatter.js.map +0 -1
  988. package/dist/core/gherkin.js +0 -463
  989. package/dist/core/gherkin.js.map +0 -1
  990. package/dist/core/health.js +0 -67
  991. package/dist/core/health.js.map +0 -1
  992. package/dist/core/ids.js +0 -122
  993. package/dist/core/ids.js.map +0 -1
  994. package/dist/core/issue.js +0 -9
  995. package/dist/core/issue.js.map +0 -1
  996. package/dist/core/json.js +0 -74
  997. package/dist/core/json.js.map +0 -1
  998. package/dist/core/likec4.js +0 -489
  999. package/dist/core/likec4.js.map +0 -1
  1000. package/dist/core/maturity.js +0 -66
  1001. package/dist/core/maturity.js.map +0 -1
  1002. package/dist/core/openapi-merge.js +0 -552
  1003. package/dist/core/openapi-merge.js.map +0 -1
  1004. package/dist/core/openapi.js.map +0 -1
  1005. package/dist/core/openspec-inventory.js +0 -1440
  1006. package/dist/core/openspec-inventory.js.map +0 -1
  1007. package/dist/core/path-safety.js.map +0 -1
  1008. package/dist/core/provenance.js +0 -787
  1009. package/dist/core/provenance.js.map +0 -1
  1010. package/dist/core/records.js.map +0 -1
  1011. package/dist/core/repo.js +0 -438
  1012. package/dist/core/repo.js.map +0 -1
  1013. package/dist/core/report.js.map +0 -1
  1014. package/dist/core/spec.js +0 -505
  1015. package/dist/core/spec.js.map +0 -1
  1016. package/dist/core/staging.js +0 -890
  1017. package/dist/core/staging.js.map +0 -1
  1018. package/dist/core/status.js +0 -1132
  1019. package/dist/core/status.js.map +0 -1
  1020. package/dist/core/steps.js +0 -72
  1021. package/dist/core/steps.js.map +0 -1
  1022. package/dist/core/verify.js +0 -807
  1023. package/dist/core/verify.js.map +0 -1
  1024. package/dist/core/version.js.map +0 -1
  1025. /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
  1026. /package/dist/core/{records.js → kernel/records.js} +0 -0
@@ -0,0 +1,122 @@
1
+ /**
2
+ * The contract-depth probes: does an operation say anything about what it
3
+ * returns, and does every internal `$ref` point at something.
4
+ *
5
+ * Form validated and depth did not, and that was invisible from the output:
6
+ * an operation whose one response is `description: OK`, and a `$ref` to a
7
+ * schema nobody ever wrote, both validate exactly as cleanly as a complete
8
+ * contract even though a reader still needs the code. Everything here is a
9
+ * PRESENCE probe over the document's own keys: nothing reads what a schema
10
+ * says, only whether one was declared at all, so the reader's structure-only
11
+ * stance holds.
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.
17
+ */
18
+ /** Status codes whose response legitimately carries no body. */
19
+ const NO_CONTENT = new Set(["204", "304"]);
20
+ /**
21
+ * True when no response declares content with a schema while at least one
22
+ * response should carry a body — an operation with no `responses` at all
23
+ * counts, one whose only responses are 204/304 does not. A response that is
24
+ * itself a `$ref` counts as described: somebody wrote the shape down, and the
25
+ * dangling case is the ref probe's finding, not this one's.
26
+ */
27
+ export function responseUndescribed(op) {
28
+ if (op === null || typeof op !== "object")
29
+ return true;
30
+ const responses = op["responses"];
31
+ if (responses === null || responses === undefined || typeof responses !== "object" || Array.isArray(responses)) {
32
+ return true;
33
+ }
34
+ const entries = Object.entries(responses);
35
+ if (entries.length === 0)
36
+ return true;
37
+ let owesBody = false;
38
+ for (const [code, resp] of entries) {
39
+ if (NO_CONTENT.has(code))
40
+ continue;
41
+ owesBody = true;
42
+ if (resp === null || typeof resp !== "object" || Array.isArray(resp))
43
+ continue;
44
+ const rec = resp;
45
+ if ("$ref" in rec)
46
+ return false;
47
+ const content = rec["content"];
48
+ if (content === null || typeof content !== "object" || Array.isArray(content))
49
+ continue;
50
+ for (const media of Object.values(content)) {
51
+ if (media !== null && typeof media === "object" && "schema" in media) {
52
+ return false;
53
+ }
54
+ }
55
+ }
56
+ return owesBody;
57
+ }
58
+ /**
59
+ * The non-2xx response codes an operation declares, in document order.
60
+ *
61
+ * Exact three-digit codes only. `default` names no case — it is whatever else
62
+ * happens — and the `4XX`/`5XX` wildcards name a class rather than a refusal,
63
+ * so no scenario could be written for either and demanding one would teach an
64
+ * author to write a scenario about nothing. 1xx and 3xx are excluded with 2xx:
65
+ * a redirect or a continue is not the service saying no.
66
+ */
67
+ export function failureCodesOf(op) {
68
+ if (op === null || typeof op !== "object")
69
+ return [];
70
+ const responses = op["responses"];
71
+ if (responses === null || typeof responses !== "object" || Array.isArray(responses))
72
+ return [];
73
+ return Object.keys(responses).filter((code) => /^[45]\d\d$/.test(code));
74
+ }
75
+ /**
76
+ * Every internal `#/` reference in the document that resolves to nothing —
77
+ * deduped, document order. External references (URLs, file paths) are out of
78
+ * scope and never graded, matching the merge's documented stance.
79
+ */
80
+ 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
+ walk(value);
86
+ return;
87
+ }
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;
95
+ }
96
+ walk(value);
97
+ }
98
+ };
99
+ walk(root);
100
+ return [...out];
101
+ }
102
+ /** Does a local JSON pointer (`#/a/b~1c`) reach a node in this tree? */
103
+ function resolves(root, ref) {
104
+ let current = root;
105
+ for (const raw of ref.slice(2).split("/")) {
106
+ const segment = raw.replace(/~1/g, "/").replace(/~0/g, "~");
107
+ if (Array.isArray(current)) {
108
+ const index = Number(segment);
109
+ if (!Number.isInteger(index) || index < 0 || index >= current.length)
110
+ return false;
111
+ current = current[index];
112
+ }
113
+ else if (current !== null && typeof current === "object" && segment in current) {
114
+ current = current[segment];
115
+ }
116
+ else {
117
+ return false;
118
+ }
119
+ }
120
+ return true;
121
+ }
122
+ //# sourceMappingURL=depth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"depth.js","sourceRoot":"","sources":["../../../src/core/openapi/depth.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The contract-depth probes: does an operation say anything about what it\n * returns, and does every internal `$ref` point at something.\n *\n * Form validated and depth did not, and that was invisible from the output:\n * an operation whose one response is `description: OK`, and a `$ref` to a\n * schema nobody ever wrote, both validate exactly as cleanly as a complete\n * contract even though a reader still needs the code. Everything here is a\n * PRESENCE probe over the document's own keys: nothing reads what a schema\n * says, only whether one was declared at all, so the reader's structure-only\n * stance holds.\n *\n * A local pointer walk rather than an import from `merge/components.ts`: the merge\n * package already imports this package (`HTTP_METHODS` from doc.ts), so the\n * reverse import would be a package cycle — the same reason read-side\n * `resolvePointer` copies exist on the asyncapi axis.\n */\n\n/** Status codes whose response legitimately carries no body. */\nconst NO_CONTENT = new Set([\"204\", \"304\"]);\n\n/**\n * True when no response declares content with a schema while at least one\n * response should carry a body — an operation with no `responses` at all\n * counts, one whose only responses are 204/304 does not. A response that is\n * itself a `$ref` counts as described: somebody wrote the shape down, and the\n * dangling case is the ref probe's finding, not this one's.\n */\nexport function responseUndescribed(op: unknown): boolean {\n if (op === null || typeof op !== \"object\") return true;\n const responses = (op as Record<string, unknown>)[\"responses\"];\n if (responses === null || responses === undefined || typeof responses !== \"object\" || Array.isArray(responses)) {\n return true;\n }\n const entries = Object.entries(responses as Record<string, unknown>);\n if (entries.length === 0) return true;\n let owesBody = false;\n for (const [code, resp] of entries) {\n if (NO_CONTENT.has(code)) continue;\n owesBody = true;\n if (resp === null || typeof resp !== \"object\" || Array.isArray(resp)) continue;\n const rec = resp as Record<string, unknown>;\n if (\"$ref\" in rec) return false;\n const content = rec[\"content\"];\n if (content === null || typeof content !== \"object\" || Array.isArray(content)) continue;\n for (const media of Object.values(content as Record<string, unknown>)) {\n if (media !== null && typeof media === \"object\" && \"schema\" in (media as Record<string, unknown>)) {\n return false;\n }\n }\n }\n return owesBody;\n}\n\n/**\n * The non-2xx response codes an operation declares, in document order.\n *\n * Exact three-digit codes only. `default` names no case — it is whatever else\n * happens — and the `4XX`/`5XX` wildcards name a class rather than a refusal,\n * so no scenario could be written for either and demanding one would teach an\n * author to write a scenario about nothing. 1xx and 3xx are excluded with 2xx:\n * a redirect or a continue is not the service saying no.\n */\nexport function failureCodesOf(op: unknown): string[] {\n if (op === null || typeof op !== \"object\") return [];\n const responses = (op as Record<string, unknown>)[\"responses\"];\n if (responses === null || typeof responses !== \"object\" || Array.isArray(responses)) return [];\n return Object.keys(responses as Record<string, unknown>).filter((code) => /^[45]\\d\\d$/.test(code));\n}\n\n/**\n * Every internal `#/` reference in the document that resolves to nothing —\n * deduped, document order. External references (URLs, file paths) are out of\n * scope and never graded, matching the merge's documented stance.\n */\nexport function danglingRefs(root: unknown): string[] {\n const out = new Set<string>();\n const walk = (node: unknown): void => {\n if (Array.isArray(node)) {\n for (const value of node) walk(value);\n return;\n }\n if (node === null || typeof node !== \"object\") return;\n for (const [key, value] of Object.entries(node as Record<string, unknown>)) {\n if (key === \"$ref\" && typeof value === \"string\") {\n if (value.startsWith(\"#/\") && !resolves(root, value)) out.add(value);\n continue;\n }\n walk(value);\n }\n };\n walk(root);\n return [...out];\n}\n\n/** Does a local JSON pointer (`#/a/b~1c`) reach a node in this tree? */\nfunction resolves(root: unknown, ref: string): boolean {\n let current: unknown = root;\n for (const raw of ref.slice(2).split(\"/\")) {\n const segment = raw.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n if (Array.isArray(current)) {\n const index = Number(segment);\n if (!Number.isInteger(index) || index < 0 || index >= current.length) return false;\n current = current[index];\n } else if (current !== null && typeof current === \"object\" && segment in (current as Record<string, unknown>)) {\n current = (current as Record<string, unknown>)[segment];\n } else {\n return false;\n }\n }\n return true;\n}\n"]}
@@ -0,0 +1,189 @@
1
+ /**
2
+ * What operation content HASHES to, and what a recorded hash means when it is
3
+ * read back.
4
+ *
5
+ * A module of its own because the digest and the comparison it must agree with
6
+ * are a matched pair: key order must not matter here, since the merge's own
7
+ * idea of "this operation differs" is `isDeepStrictEqual`, which ignores it. A
8
+ * second spelling of either would go stale over a reordered `summary` pair the
9
+ * merge calls identical — a false collision on the one check whose whole worth
10
+ * is that it fires only for real ones.
11
+ */
12
+ import { createHash } from "node:crypto";
13
+ /**
14
+ * The baseline marker: which living version of this operation a FEATURE delta
15
+ * was written against. Same idea as a requirement's `Based-On:` (core/document/spec.ts),
16
+ * in the vendor-extension shape this axis already uses for `x-loam-remove`.
17
+ *
18
+ * 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
21
+ * operation the document spells. So a feature that never meant to touch
22
+ * `refundOrder` — it merely quoted it — pushes its authoring-time copy back over
23
+ * whatever landed in between, reverting another team's shipped change without
24
+ * either feature overlapping the other at all. The pin is what lets the merge
25
+ * tell a QUOTE from an EDIT: a quote equals its own baseline, and is skipped.
26
+ */
27
+ export const OPENAPI_BASELINE_KEY = "x-loam-based-on";
28
+ /**
29
+ * The feature-only ROOT record that pins the surfaces the operation pin cannot
30
+ * reach: path-item non-method keys and components. `{ pathItems: {<path>:
31
+ * {<key>: <16-hex>}}, components: {"<kind>/<name>": <16-hex>} }` — written by
32
+ * `loam rebase`, graded by validate/archive, consumed by the merge, and
33
+ * stripped from every living document exactly like the pin. A single root
34
+ * record rather than in-value extensions because a component's value can be a
35
+ * bare boolean (JSON Schema allows `true`), which no in-value key survives.
36
+ */
37
+ export const OPENAPI_BASELINES_KEY = "x-loam-baselines";
38
+ /** The feature-only explicit removal marker's key. */
39
+ export const OPENAPI_REMOVE_KEY = "x-loam-remove";
40
+ /** Is this operation node a feature-only explicit removal marker? */
41
+ export function isRemoval(node) {
42
+ return node !== null &&
43
+ typeof node === "object" &&
44
+ node[OPENAPI_REMOVE_KEY] === true;
45
+ }
46
+ /**
47
+ * Every key that is an instruction to loam rather than part of the contract.
48
+ * At PATH level none has any meaning — `x-loam-remove` addresses one
49
+ * operation and `x-loam-based-on` pins one — so a path item carrying either
50
+ * is an authoring mistake, and one that must not be published whatever else
51
+ * is done about it. Lives HERE, in the openapi root beside the keys it names,
52
+ * because both `merge/` and `baseline/` consume it and a value edge between
53
+ * those two packages in either direction would be a package cycle.
54
+ */
55
+ export const FEATURE_ONLY_KEYS = new Set([OPENAPI_REMOVE_KEY, OPENAPI_BASELINE_KEY]);
56
+ /**
57
+ * `value` with every feature-only key removed, at ANY depth — or the value
58
+ * unchanged (same reference) when it carries none. The component closure
59
+ * writes values VERBATIM into the living contract, and a component holding an
60
+ * operation shape (a `pathItems` component, a callback) can nest the markers
61
+ * where the path-level strip never walks: `--approve` once published
62
+ * `x-loam-remove: true` inside a living component, invisible even to
63
+ * `validate`, whose marker sweep reads `paths` alone.
64
+ */
65
+ export function withoutFeatureKeysDeep(value) {
66
+ if (value === null || typeof value !== "object")
67
+ return value;
68
+ if (Array.isArray(value)) {
69
+ let changed = false;
70
+ const out = value.map((v) => {
71
+ const stripped = withoutFeatureKeysDeep(v);
72
+ if (stripped !== v)
73
+ changed = true;
74
+ return stripped;
75
+ });
76
+ return changed ? out : value;
77
+ }
78
+ const record = value;
79
+ let changed = false;
80
+ const out = {};
81
+ for (const [key, v] of Object.entries(record)) {
82
+ if (FEATURE_ONLY_KEYS.has(key)) {
83
+ changed = true;
84
+ continue;
85
+ }
86
+ const stripped = withoutFeatureKeysDeep(v);
87
+ if (stripped !== v)
88
+ changed = true;
89
+ out[key] = stripped;
90
+ }
91
+ return changed ? out : value;
92
+ }
93
+ /** How much of the sha256 an `x-loam-based-on` carries — the length every loam digest uses. */
94
+ export const OPERATION_DIGEST_LENGTH = 16;
95
+ /** The value shape of an `x-loam-based-on`, exactly as `operationDigest` writes it. */
96
+ export const OPERATION_DIGEST_RE = new RegExp(`^[0-9a-f]{${OPERATION_DIGEST_LENGTH}}$`);
97
+ /**
98
+ * Canonical JSON for a resolved YAML value: object keys sorted, arrays left in
99
+ * order, everything else as JSON writes it.
100
+ *
101
+ * Key ORDER must not matter, because the merge's own idea of "this operation
102
+ * differs" is `isDeepStrictEqual` (./merge/merge.ts), which ignores it. A
103
+ * digest that disagreed with that comparison would go stale over a reordered
104
+ * `summary`/`operationId` pair the merge itself calls identical — a false
105
+ * collision on the one check whose worth is that it fires only for real ones.
106
+ *
107
+ * Exported for the ASYNCAPI axis (core/asyncapi/digest.ts), whose slot
108
+ * digests are the same rule over different sections: one spelling of the
109
+ * canonical form for both axes, because two spellings would eventually
110
+ * disagree — the exact drift this module's own header names as the failure
111
+ * mode. The import direction is asyncapi → openapi only; nothing under
112
+ * core/openapi/ may import from core/asyncapi/, or the package graph cycles.
113
+ */
114
+ export function canonicalJson(node) {
115
+ if (node === null || typeof node !== "object")
116
+ return JSON.stringify(node) ?? "null";
117
+ if (Array.isArray(node))
118
+ return `[${node.map(canonicalJson).join(",")}]`;
119
+ const entries = Object.entries(node)
120
+ .filter(([, value]) => value !== undefined)
121
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
122
+ return `{${entries.map(([key, value]) => `${JSON.stringify(key)}:${canonicalJson(value)}`).join(",")}}`;
123
+ }
124
+ /**
125
+ * The operation without its own baseline marker — what a digest is taken over,
126
+ * and what the merge writes.
127
+ *
128
+ * A pin is a statement ABOUT a delta, never part of the operation it describes.
129
+ * Inside the digest input, an operation's identity would depend on the pin
130
+ * pointing at it and no baseline could ever be self-consistent; inside the
131
+ * merged contract, a living operation would carry a pin to a version of itself
132
+ * and the next feature's baseline would hash the previous feature's bookkeeping.
133
+ */
134
+ export function withoutOperationBaseline(node) {
135
+ if (node === null || typeof node !== "object" || Array.isArray(node))
136
+ return node;
137
+ const record = node;
138
+ if (!(OPENAPI_BASELINE_KEY in record))
139
+ return node;
140
+ const rest = { ...record };
141
+ delete rest[OPENAPI_BASELINE_KEY];
142
+ return rest;
143
+ }
144
+ /**
145
+ * The identity of ANY resolved value: sha256 of its canonical form. The one
146
+ * digest the path-item and component baselines carry — order-of-keys blind,
147
+ * array-order sensitive, legal over scalars and booleans, exactly as the
148
+ * operation digest always was.
149
+ */
150
+ export function valueDigest(node) {
151
+ return createHash("sha256").update(canonicalJson(node), "utf8").digest("hex").slice(0, OPERATION_DIGEST_LENGTH);
152
+ }
153
+ /** The identity of an operation's CONTENT: sha256 of its canonical form, pin excluded. */
154
+ export function operationDigest(node) {
155
+ return valueDigest(withoutOperationBaseline(node));
156
+ }
157
+ /**
158
+ * What a pin says, from digests alone — the one definition every decider
159
+ * shares: the gate (./baseline/gate.ts) and the merge
160
+ * (./merge/{merge,components,pin}.ts) alike. Two spellings of
161
+ * this rule would eventually disagree about which operations a merge writes,
162
+ * and the disagreement would be invisible until a contract came back wrong.
163
+ *
164
+ * Order matters: `quote` is settled against the delta's OWN content before the
165
+ * living contract is consulted. An operation equal to its own baseline was not
166
+ * edited, so what living holds now is nobody's business but the feature that
167
+ * put it there.
168
+ */
169
+ export function classifyBaselineDigests(pin, ownDigest, livingDigest) {
170
+ if (pin === undefined)
171
+ return "unpinned";
172
+ if (livingDigest === undefined)
173
+ return "unfounded";
174
+ if (pin === ownDigest)
175
+ return "quote";
176
+ if (pin === livingDigest)
177
+ return "edit";
178
+ return "stale";
179
+ }
180
+ /** The `x-loam-based-on` a node declares, or undefined — non-strings included, so validate can refuse them. */
181
+ export function operationBaselineOf(node) {
182
+ if (node === null || typeof node !== "object" || Array.isArray(node))
183
+ return undefined;
184
+ const value = node[OPENAPI_BASELINE_KEY];
185
+ if (value === undefined)
186
+ return undefined;
187
+ return typeof value === "string" ? value.trim() : String(value);
188
+ }
189
+ //# sourceMappingURL=digest.js.map
@@ -0,0 +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,9 +1,22 @@
1
+ /**
2
+ * A service's OpenAPI contract as loam reads it: which keys hold operations,
3
+ * what an operation is once loam's own markers are accounted for, and what the
4
+ * fleet will provide after a feature is applied.
5
+ *
6
+ * Split from `./digest.ts` because the two answer different questions about the
7
+ * same file. This one is about STRUCTURE — a path item's operation-bearing
8
+ * keys, an `operationId`, a removal marker — and every reader of a contract
9
+ * comes through here. The digest is about IDENTITY, and only the pin and the
10
+ * merge need it.
11
+ */
1
12
  import { isUtf8 } from "node:buffer";
2
- import { createHash } from "node:crypto";
13
+ import { danglingRefs, failureCodesOf, responseUndescribed } from "./depth.js";
14
+ import { operationBaselineOf, operationDigest } from "./digest.js";
3
15
  import { existsSync } from "node:fs";
4
16
  import { readFile } from "node:fs/promises";
5
17
  import { parse } from "yaml";
6
- import { featureSpecPaths, servicePaths } from "./repo.js";
18
+ import { featureSpecPaths } from "../repo/paths.js";
19
+ import { locateServicePaths } from "../repo/service-target.js";
7
20
  /**
8
21
  * The path-item keys that hold operations. A path item also carries `summary`,
9
22
  * `parameters`, `servers` and vendor `x-*` extensions — an object-valued one with
@@ -13,104 +26,6 @@ import { featureSpecPaths, servicePaths } from "./repo.js";
13
26
  * of the same keys.
14
27
  */
15
28
  export const HTTP_METHODS = new Set(["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
16
- /**
17
- * The baseline marker: which living version of this operation a FEATURE delta
18
- * was written against. Same idea as a requirement's `Based-On:` (core/spec.ts),
19
- * in the vendor-extension shape this axis already uses for `x-loam-remove`.
20
- *
21
- * This axis needs it MORE than the requirement axis does, because a feature's
22
- * openapi.yaml is a COMPLETE document rather than a patch: authors restate the
23
- * living contract around the slot they are changing, and the merge upserts every
24
- * operation the document spells. So a feature that never meant to touch
25
- * `refundOrder` — it merely quoted it — pushes its authoring-time copy back over
26
- * whatever landed in between, reverting another team's shipped change without
27
- * either feature overlapping the other at all. The pin is what lets the merge
28
- * tell a QUOTE from an EDIT: a quote equals its own baseline, and is skipped.
29
- */
30
- export const OPENAPI_BASELINE_KEY = "x-loam-based-on";
31
- /** How much of the sha256 an `x-loam-based-on` carries — the length every loam digest uses. */
32
- export const OPERATION_DIGEST_LENGTH = 16;
33
- /** The value shape of an `x-loam-based-on`, exactly as `operationDigest` writes it. */
34
- export const OPERATION_DIGEST_RE = new RegExp(`^[0-9a-f]{${OPERATION_DIGEST_LENGTH}}$`);
35
- /**
36
- * Canonical JSON for a resolved YAML value: object keys sorted, arrays left in
37
- * order, everything else as JSON writes it.
38
- *
39
- * Key ORDER must not matter, because the merge's own idea of "this operation
40
- * differs" is `isDeepStrictEqual` (core/openapi-merge.ts), which ignores it. A
41
- * digest that disagreed with that comparison would go stale over a reordered
42
- * `summary`/`operationId` pair the merge itself calls identical — a false
43
- * collision on the one check whose worth is that it fires only for real ones.
44
- */
45
- function canonicalJson(node) {
46
- if (node === null || typeof node !== "object")
47
- return JSON.stringify(node) ?? "null";
48
- if (Array.isArray(node))
49
- return `[${node.map(canonicalJson).join(",")}]`;
50
- const entries = Object.entries(node)
51
- .filter(([, value]) => value !== undefined)
52
- .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
53
- return `{${entries.map(([key, value]) => `${JSON.stringify(key)}:${canonicalJson(value)}`).join(",")}}`;
54
- }
55
- /**
56
- * The operation without its own baseline marker — what a digest is taken over,
57
- * and what the merge writes.
58
- *
59
- * A pin is a statement ABOUT a delta, never part of the operation it describes.
60
- * Inside the digest input, an operation's identity would depend on the pin
61
- * pointing at it and no baseline could ever be self-consistent; inside the
62
- * merged contract, a living operation would carry a pin to a version of itself
63
- * and the next feature's baseline would hash the previous feature's bookkeeping.
64
- */
65
- export function withoutOperationBaseline(node) {
66
- if (node === null || typeof node !== "object" || Array.isArray(node))
67
- return node;
68
- const record = node;
69
- if (!(OPENAPI_BASELINE_KEY in record))
70
- return node;
71
- const rest = { ...record };
72
- delete rest[OPENAPI_BASELINE_KEY];
73
- return rest;
74
- }
75
- /** The identity of an operation's CONTENT: sha256 of its canonical form, pin excluded. */
76
- export function operationDigest(node) {
77
- return createHash("sha256")
78
- .update(canonicalJson(withoutOperationBaseline(node)), "utf8")
79
- .digest("hex")
80
- .slice(0, OPERATION_DIGEST_LENGTH);
81
- }
82
- /**
83
- * What a pin says, from digests alone — the one definition both the gate
84
- * (core/coherence.ts, which has parsed Operations) and the merge
85
- * (core/openapi-merge.ts, which has raw YAML trees) decide by. Two spellings of
86
- * this rule would eventually disagree about which operations a merge writes,
87
- * and the disagreement would be invisible until a contract came back wrong.
88
- *
89
- * Order matters: `quote` is settled against the delta's OWN content before the
90
- * living contract is consulted. An operation equal to its own baseline was not
91
- * edited, so what living holds now is nobody's business but the feature that
92
- * put it there.
93
- */
94
- export function classifyBaselineDigests(pin, ownDigest, livingDigest) {
95
- if (pin === undefined)
96
- return "unpinned";
97
- if (livingDigest === undefined)
98
- return "unfounded";
99
- if (pin === ownDigest)
100
- return "quote";
101
- if (pin === livingDigest)
102
- return "edit";
103
- return "stale";
104
- }
105
- /** The `x-loam-based-on` a node declares, or undefined — non-strings included, so validate can refuse them. */
106
- export function operationBaselineOf(node) {
107
- if (node === null || typeof node !== "object" || Array.isArray(node))
108
- return undefined;
109
- const value = node[OPENAPI_BASELINE_KEY];
110
- if (value === undefined)
111
- return undefined;
112
- return typeof value === "string" ? value.trim() : String(value);
113
- }
114
29
  /**
115
30
  * Extract operations from an OpenAPI document by walking the parsed YAML
116
31
  * structure (paths.<path>.<method>, method filtered to the HTTP set).
@@ -149,9 +64,20 @@ export async function readOpenapi(openapiPath, context) {
149
64
  // reads the contract stays suspended.
150
65
  if (!isUtf8(bytes))
151
66
  return { ...empty(), unreadable: true, error: "file is not valid UTF-8" };
67
+ return openapiFromText(bytes.toString("utf8"));
68
+ }
69
+ /**
70
+ * The same read over text already in hand — the parse half of `readOpenapi`,
71
+ * split out so `loam diff` can read a contract fetched from a base git ref
72
+ * (core/diff/base-state.ts) without writing it to disk. One spelling of the
73
+ * walk for both entries, so a base-ref contract can never parse differently
74
+ * from the living file the same bytes would produce; the byte concerns
75
+ * (existence, UTF-8) stay with the reader that holds the bytes.
76
+ */
77
+ export function openapiFromText(text) {
152
78
  let doc;
153
79
  try {
154
- doc = parse(bytes.toString("utf8"));
80
+ doc = parse(text);
155
81
  }
156
82
  catch (e) {
157
83
  return { ...empty(), unreadable: true, error: e instanceof Error ? e.message : String(e) };
@@ -162,9 +88,12 @@ export async function readOpenapi(openapiPath, context) {
162
88
  if (doc !== null && (typeof doc !== "object" || Array.isArray(doc))) {
163
89
  return { ...empty(), unreadable: true, error: "document is not a YAML mapping" };
164
90
  }
91
+ // Refs are probed over the WHOLE document: a schema in `components` that
92
+ // `$ref`s a neighbour nobody wrote is exactly as broken as one under a path.
93
+ const refs = danglingRefs(doc);
165
94
  const paths = doc?.paths;
166
95
  if (!paths || typeof paths !== "object")
167
- return empty();
96
+ return { ...empty(), danglingRefs: refs };
168
97
  const ops = [];
169
98
  const anonymousRemovals = [];
170
99
  const pathLevelRemovals = [];
@@ -196,14 +125,23 @@ export async function readOpenapi(openapiPath, context) {
196
125
  method,
197
126
  digest: operationDigest(op),
198
127
  ...(basedOn === undefined ? {} : { basedOn }),
128
+ ...(responseUndescribed(op) ? { undescribed: true } : {}),
129
+ failureCodes: failureCodesOf(op),
199
130
  });
200
131
  }
201
132
  }
202
133
  const duplicateIds = [...seen].filter(([, n]) => n > 1).map(([id]) => id);
203
- return { ops, duplicateIds, anonymousRemovals, pathLevelRemovals, unreadable: false };
134
+ return { ops, duplicateIds, anonymousRemovals, pathLevelRemovals, unreadable: false, danglingRefs: refs };
204
135
  }
205
136
  function empty() {
206
- return { ops: [], duplicateIds: [], anonymousRemovals: [], pathLevelRemovals: [], unreadable: false };
137
+ return {
138
+ ops: [],
139
+ duplicateIds: [],
140
+ anonymousRemovals: [],
141
+ pathLevelRemovals: [],
142
+ unreadable: false,
143
+ danglingRefs: [],
144
+ };
207
145
  }
208
146
  /** The operations alone — for every caller whose own finding already covers the unreadable case. */
209
147
  export async function operations(openapiPath, context) {
@@ -236,7 +174,7 @@ export async function operationIds(openapiPath, context) {
236
174
  * it.
237
175
  */
238
176
  export async function serviceOperationIds(docsDir, service, featureDir, context) {
239
- const ids = new Set((await operations(servicePaths(docsDir, service).openapi, context)).filter((op) => !op.remove).map((op) => op.id));
177
+ const ids = new Set((await operations((await locateServicePaths(docsDir, service, context)).openapi, context)).filter((op) => !op.remove).map((op) => op.id));
240
178
  // Absence is `undefined`, the shape the optional parameter declares. The
241
179
  // truthiness test also read "" as "no feature", which is a caller mistake
242
180
  // worth surfacing rather than answering as though the feature carried no
@@ -252,4 +190,4 @@ export async function serviceOperationIds(docsDir, service, featureDir, context)
252
190
  }
253
191
  return [...ids];
254
192
  }
255
- //# sourceMappingURL=openapi.js.map
193
+ //# sourceMappingURL=doc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doc.js","sourceRoot":"","sources":["../../../src/core/openapi/doc.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A service's OpenAPI contract as loam reads it: which keys hold operations,\n * what an operation is once loam's own markers are accounted for, and what the\n * fleet will provide after a feature is applied.\n *\n * Split from `./digest.ts` because the two answer different questions about the\n * same file. This one is about STRUCTURE — a path item's operation-bearing\n * keys, an `operationId`, a removal marker — and every reader of a contract\n * comes through here. The digest is about IDENTITY, and only the pin and the\n * merge need it.\n */\nimport { isUtf8 } from \"node:buffer\";\nimport { danglingRefs, failureCodesOf, responseUndescribed } from \"./depth.js\";\nimport { operationBaselineOf, operationDigest } from \"./digest.js\";\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { parse } from \"yaml\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport type { PathableService } from \"../kernel/ids/service.js\";\nimport { featureSpecPaths } from \"../repo/paths.js\";\nimport { locateServicePaths } from \"../repo/service-target.js\";\nimport type { DocsDir, FeatureDir } from \"../kernel/ids/dirs.js\";\n\n/**\n * The path-item keys that hold operations. A path item also carries `summary`,\n * `parameters`, `servers` and vendor `x-*` extensions — an object-valued one with\n * an `operationId` inside (x-legacy and friends) is not an operation, and a\n * phantom id from it would make a broken contract look \"available\" to the\n * op-exists checks. Shared with archive's merge, which asks the same question\n * of the same keys.\n */\nexport const HTTP_METHODS = new Set([\"get\", \"put\", \"post\", \"delete\", \"options\", \"head\", \"patch\", \"trace\"]);\n\n/** One operation as the reader sees it, including loam's feature-only removal marker. */\nexport interface Operation {\n id: string;\n /**\n * The standard per-operation OpenAPI `deprecated` flag — lifecycle\n * visibility, the documented first step of retiring an op.\n */\n deprecated: boolean;\n /**\n * `x-loam-remove: true` asks a FEATURE delta to remove this exact operation\n * on archive. The marker is never a callable operation and is illegal in a\n * living contract.\n */\n remove: boolean;\n /** Exact slot the marker/upsert addresses; removal is deliberately path+method-safe. */\n path: string;\n method: string;\n /**\n * `operationDigest` of this operation as it stands in ITS OWN document —\n * computed here so every caller compares content the one way the merge does.\n */\n digest: string;\n /**\n * The living version this operation was written against, from\n * `x-loam-based-on`. Feature deltas only: a living contract never carries one\n * (the merge strips it), and an adopted contract never had one.\n */\n basedOn?: string;\n /**\n * Present when no response declares content with a schema while at least one\n * should carry a body (`depth.ts`). Set only when true, so the many callers\n * that compare operations structurally never see the field on a described\n * contract.\n */\n undescribed?: true;\n /**\n * Declared non-2xx response codes, in document order — the refusals this\n * operation promises. `default` and any `4XX`/`5XX` wildcard are excluded:\n * neither names a case a scenario could be written for.\n *\n * The contract is the only place in the corpus that enumerates what an\n * operation REFUSES, and until this was read nothing joined those refusals to\n * anything. `api.ungoverned` grades whole operations against requirements,\n * so an operation documented by its happy path alone — one requirement, one\n * scenario, twelve declared failure codes — passed every check loam had.\n */\n failureCodes: string[];\n}\n\n/** A `x-loam-remove: true` marker with no usable operationId — a slot named but no operation named. */\nexport interface AnonymousRemoval {\n path: string;\n method: string;\n}\n\n/** The parse of one OpenAPI document: its operations, and whether the file could be read at all. */\nexport interface OpenapiDoc {\n ops: Operation[];\n /**\n * operationIds this document defines in more than one (path, method) slot.\n * A LIVING contract with two slots claiming one id is ambiguous to every\n * check that joins on the id — a requirement's `Operations:` line, an edge's\n * `metadata { op }`, a removal marker — and the join silently picks one.\n */\n duplicateIds: string[];\n /**\n * Removal markers the operation reader cannot name. They are invisible to\n * every id-keyed check, so a delta carrying one used to archive cleanly and\n * write `x-loam-remove: true` straight into a living contract.\n */\n anonymousRemovals: AnonymousRemoval[];\n /**\n * Paths carrying `x-loam-remove: true` at PATH level, beside the methods\n * rather than inside one.\n *\n * Not an operation, so nothing keyed by (path, method) could see it — and this\n * reader is method-keyed like the merge was. A marker written there addressed\n * no operation, so archive copied it into the living contract verbatim, where\n * it was invisible to `openapi.remove-marker-living` for the same reason and\n * kept the empty-path cleanup from ever firing. Reported as its own list\n * because the answer differs by document: in a FEATURE delta it is an\n * authoring mistake, in a LIVING contract it is a marker that must never have\n * been published.\n */\n pathLevelRemovals: string[];\n /**\n * True when the file EXISTS but cannot be read as an OpenAPI document —\n * broken YAML, or a document that is not a mapping. A missing file is not\n * unreadable (absence is `service.no-openapi`'s question), and an empty one\n * parses to null and honestly defines nothing.\n */\n unreadable: boolean;\n /** The parser's own message, when there is one to quote back. */\n error?: string;\n /** Internal `#/` refs that resolve to nothing in this document (`depth.ts`). */\n danglingRefs: string[];\n}\n\n/**\n * Extract operations from an OpenAPI document by walking the parsed YAML\n * structure (paths.<path>.<method>, method filtered to the HTTP set).\n * Structure-aware on purpose: a regex scan both drops legal ids (kebab-case,\n * dotted) and picks up phantom ids from description text. Each operation\n * carries its id and its `deprecated: true` flag (exactly `true` counts —\n * strings and truthy noise do not). `ops` is the defined set, keyed by the\n * (path, method) SLOT — not by operationId.\n *\n * Keying by id used to drop the second slot claiming a name, which made the\n * single most common contract edit invisible: relocating an endpoint is a\n * removal marker at the old slot plus an upsert at the new one, the SAME\n * operationId twice. Whichever came second in document order vanished, so\n * either the marker addressed a slot loam could not see (`remove-target-missing`\n * on a perfectly good delta) or the new definition did. Two slots claiming one\n * id in a LIVING contract is a real ambiguity rather than a merge failure, so\n * it rides out as `duplicateIds` for the caller to grade.\n *\n * An unreadable document yields [] ops PLUS the `unreadable` flag. Parsers\n * never diagnose — the policy of every reader here — so the failure travels as\n * a flag for validate to grade (`openapi.invalid`). Swallowing it into a bare\n * [] made a broken contract indistinguishable from an EMPTY one, and the spine\n * check then reported every inbound edge broken (`spine.op-undefined`) — a\n * false diagnosis pointing at the landscape when the truth was this file.\n */\nexport async function readOpenapi(openapiPath: string, context?: FleetContext): Promise<OpenapiDoc> {\n if (context !== undefined) return context.readOpenapi(openapiPath);\n if (!existsSync(openapiPath)) return empty();\n const bytes = await readFile(openapiPath);\n // A contract whose bytes are not UTF-8 is as unreadable as broken YAML, and\n // for a worse reason: `toString(\"utf8\")` would substitute U+FFFD and hand the\n // parser a document nobody wrote. It travels as `unreadable` so validate\n // grades it `openapi.invalid` — the file is the error, and every check that\n // reads the contract stays suspended.\n if (!isUtf8(bytes)) return { ...empty(), unreadable: true, error: \"file is not valid UTF-8\" };\n return openapiFromText(bytes.toString(\"utf8\"));\n}\n\n/**\n * The same read over text already in hand — the parse half of `readOpenapi`,\n * split out so `loam diff` can read a contract fetched from a base git ref\n * (core/diff/base-state.ts) without writing it to disk. One spelling of the\n * walk for both entries, so a base-ref contract can never parse differently\n * from the living file the same bytes would produce; the byte concerns\n * (existence, UTF-8) stay with the reader that holds the bytes.\n */\nexport function openapiFromText(text: string): OpenapiDoc {\n let doc: unknown;\n try {\n doc = parse(text);\n } catch (e) {\n return { ...empty(), unreadable: true, error: e instanceof Error ? e.message : String(e) };\n }\n // A scalar or sequence document is as unreadable as broken YAML: there is no\n // mapping to look `paths` up in, so nothing can be concluded from it. null\n // (an empty file) stays readable — it defines nothing, and says so honestly.\n if (doc !== null && (typeof doc !== \"object\" || Array.isArray(doc))) {\n return { ...empty(), unreadable: true, error: \"document is not a YAML mapping\" };\n }\n // Refs are probed over the WHOLE document: a schema in `components` that\n // `$ref`s a neighbour nobody wrote is exactly as broken as one under a path.\n const refs = danglingRefs(doc);\n const paths = (doc as { paths?: unknown } | null)?.paths;\n if (!paths || typeof paths !== \"object\") return { ...empty(), danglingRefs: refs };\n const ops: Operation[] = [];\n const anonymousRemovals: AnonymousRemoval[] = [];\n const pathLevelRemovals: string[] = [];\n const seen = new Map<string, number>();\n for (const [path, item] of Object.entries(paths as Record<string, unknown>)) {\n if (!item || typeof item !== \"object\") continue;\n if ((item as Record<string, unknown>)[\"x-loam-remove\"] === true) pathLevelRemovals.push(path);\n for (const [method, op] of Object.entries(item as Record<string, unknown>)) {\n if (!HTTP_METHODS.has(method)) continue;\n if (!op || typeof op !== \"object\") continue;\n const id = (op as Record<string, unknown>)[\"operationId\"];\n const remove = (op as Record<string, unknown>)[\"x-loam-remove\"] === true;\n if (typeof id !== \"string\" || id.length === 0) {\n if (remove) anonymousRemovals.push({ path, method });\n continue;\n }\n seen.set(id, (seen.get(id) ?? 0) + 1);\n const basedOn = operationBaselineOf(op);\n ops.push({\n id,\n deprecated: (op as Record<string, unknown>)[\"deprecated\"] === true,\n remove,\n path,\n method,\n digest: operationDigest(op),\n ...(basedOn === undefined ? {} : { basedOn }),\n ...(responseUndescribed(op) ? { undescribed: true as const } : {}),\n failureCodes: failureCodesOf(op),\n });\n }\n }\n const duplicateIds = [...seen].filter(([, n]) => n > 1).map(([id]) => id);\n return { ops, duplicateIds, anonymousRemovals, pathLevelRemovals, unreadable: false, danglingRefs: refs };\n}\n\nfunction empty(): OpenapiDoc {\n return {\n ops: [],\n duplicateIds: [],\n anonymousRemovals: [],\n pathLevelRemovals: [],\n unreadable: false,\n danglingRefs: [],\n };\n}\n\n/** The operations alone — for every caller whose own finding already covers the unreadable case. */\nexport async function operations(openapiPath: string, context?: FleetContext): Promise<Operation[]> {\n return (await readOpenapi(openapiPath, context)).ops;\n}\n\n/** The operationIds alone — `operations` for every caller that asks only \"does it exist\". */\nexport async function operationIds(openapiPath: string, context?: FleetContext): Promise<string[]> {\n return (await operations(openapiPath, context)).map((o) => o.id);\n}\n\n/**\n * The operationIds a service will provide after a feature is applied: living\n * operations, plus feature upserts, minus explicit feature removals.\n *\n * Removals are applied BEFORE upserts, which is what makes the answer\n * independent of the order the feature's document happens to spell them in.\n * Interleaving them one operation at a time meant a relocation — the same id\n * removed from `/old` and defined on `/new` — answered \"defined\" or \"gone\"\n * depending on which path sorted first in the YAML, and the requirement\n * governing it was reported undefined (`spec-api.op-undefined`) on exactly one\n * of the two spellings of the same change.\n *\n * A context is threaded into the READS and never asked for the answer. It used\n * to own a second copy of this function, and that copy interleaved removals\n * with upserts — the exact bug the paragraph above describes as fixed. So the\n * commands that pass a context (`validate`, `status`) and the one that does not\n * (`archive`) disagreed about whether a relocated operation still existed, and\n * `spec-api.op-undefined` — an error that gates archive — fired or not\n * depending on which path key the author happened to type first. Memoising is\n * the context's job; the ordering rule is this function's, and there is one of\n * it.\n */\nexport async function serviceOperationIds(\n docsDir: DocsDir,\n service: PathableService,\n featureDir?: FeatureDir,\n context?: FleetContext,\n): Promise<string[]> {\n const ids = new Set(\n (await operations((await locateServicePaths(docsDir, service, context)).openapi, context)).filter((op) => !op.remove).map((op) => op.id),\n );\n // Absence is `undefined`, the shape the optional parameter declares. The\n // truthiness test also read \"\" as \"no feature\", which is a caller mistake\n // worth surfacing rather than answering as though the feature carried no\n // contract delta.\n if (featureDir !== undefined) {\n const featOps = await operations(featureSpecPaths(featureDir, service).openapi, context);\n for (const op of featOps) if (op.remove) ids.delete(op.id);\n for (const op of featOps) if (!op.remove) ids.add(op.id);\n }\n return [...ids];\n}\n\n"]}