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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1256) hide show
  1. package/CHANGELOG.md +5992 -82
  2. package/COMPARISON.md +168 -63
  3. package/CONTRIBUTING.md +121 -0
  4. package/MIGRATING-from-OpenSpec.md +238 -55
  5. package/QUICKSTART.md +302 -0
  6. package/README.md +653 -144
  7. package/ROADMAP.md +1035 -0
  8. package/SCHEMA.md +3916 -263
  9. package/SECURITY.md +21 -3
  10. package/WORKFLOW.md +927 -0
  11. package/dist/cli.js +133 -21
  12. package/dist/cli.js.map +1 -1
  13. package/dist/commands/adopt/adopt.js +204 -0
  14. package/dist/commands/adopt/adopt.js.map +1 -0
  15. package/dist/commands/adopt/render.js +309 -0
  16. package/dist/commands/adopt/render.js.map +1 -0
  17. package/dist/commands/archive/archive.js +74 -0
  18. package/dist/commands/archive/archive.js.map +1 -0
  19. package/dist/commands/archive/closing.js +42 -0
  20. package/dist/commands/archive/closing.js.map +1 -0
  21. package/dist/commands/archive/plan/contracts/asyncapi.js +172 -0
  22. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
  23. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  24. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  25. package/dist/commands/archive/plan/contracts/openapi.js +193 -0
  26. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
  27. package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
  28. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  29. package/dist/commands/archive/plan/document/head.js +151 -0
  30. package/dist/commands/archive/plan/document/head.js.map +1 -0
  31. package/dist/commands/archive/plan/document/links.js +284 -0
  32. package/dist/commands/archive/plan/document/links.js.map +1 -0
  33. package/dist/commands/archive/plan/document/run.js +136 -0
  34. package/dist/commands/archive/plan/document/run.js.map +1 -0
  35. package/dist/commands/archive/plan/gate.js +319 -0
  36. package/dist/commands/archive/plan/gate.js.map +1 -0
  37. package/dist/commands/archive/plan/landscape.js +298 -0
  38. package/dist/commands/archive/plan/landscape.js.map +1 -0
  39. package/dist/commands/archive/plan/model/extending.js +85 -0
  40. package/dist/commands/archive/plan/model/extending.js.map +1 -0
  41. package/dist/commands/archive/plan/refusal.js +106 -0
  42. package/dist/commands/archive/plan/refusal.js.map +1 -0
  43. package/dist/commands/archive/plan/specs.js +318 -0
  44. package/dist/commands/archive/plan/specs.js.map +1 -0
  45. package/dist/commands/archive/plan/state.js +30 -0
  46. package/dist/commands/archive/plan/state.js.map +1 -0
  47. package/dist/commands/archive/run.js +355 -0
  48. package/dist/commands/archive/run.js.map +1 -0
  49. package/dist/commands/archive/shipped.js +49 -0
  50. package/dist/commands/archive/shipped.js.map +1 -0
  51. package/dist/commands/context/context.js +166 -0
  52. package/dist/commands/context/context.js.map +1 -0
  53. package/dist/commands/context/print.js +305 -0
  54. package/dist/commands/context/print.js.map +1 -0
  55. package/dist/commands/delta/delta.js +367 -0
  56. package/dist/commands/delta/delta.js.map +1 -0
  57. package/dist/commands/delta/print.js +221 -0
  58. package/dist/commands/delta/print.js.map +1 -0
  59. package/dist/commands/dependencies.js +6 -8
  60. package/dist/commands/dependencies.js.map +1 -1
  61. package/dist/commands/diff/diff.js +86 -0
  62. package/dist/commands/diff/diff.js.map +1 -0
  63. package/dist/commands/diff/print.js +38 -0
  64. package/dist/commands/diff/print.js.map +1 -0
  65. package/dist/commands/doctor.js +65 -5
  66. package/dist/commands/doctor.js.map +1 -1
  67. package/dist/commands/explain/explain.js +227 -0
  68. package/dist/commands/explain/explain.js.map +1 -0
  69. package/dist/commands/explore.js +39 -28
  70. package/dist/commands/explore.js.map +1 -1
  71. package/dist/commands/gate/gate.js +152 -0
  72. package/dist/commands/gate/gate.js.map +1 -0
  73. package/dist/commands/gate/print.js +66 -0
  74. package/dist/commands/gate/print.js.map +1 -0
  75. package/dist/commands/gherkin/commit.js +105 -0
  76. package/dist/commands/gherkin/commit.js.map +1 -0
  77. package/dist/commands/gherkin/gherkin.js +262 -0
  78. package/dist/commands/gherkin/gherkin.js.map +1 -0
  79. package/dist/commands/gherkin/reconcile.js +146 -0
  80. package/dist/commands/gherkin/reconcile.js.map +1 -0
  81. package/dist/commands/gherkin/render.js +77 -0
  82. package/dist/commands/gherkin/render.js.map +1 -0
  83. package/dist/commands/init/example.js +204 -0
  84. package/dist/commands/init/example.js.map +1 -0
  85. package/dist/commands/init/first-hour.js +72 -0
  86. package/dist/commands/init/first-hour.js.map +1 -0
  87. package/dist/commands/{init.js → init/init.js} +142 -71
  88. package/dist/commands/init/init.js.map +1 -0
  89. package/dist/commands/init/mcp.js +131 -0
  90. package/dist/commands/init/mcp.js.map +1 -0
  91. package/dist/commands/init/options.js +78 -0
  92. package/dist/commands/init/options.js.map +1 -0
  93. package/dist/commands/instructions.js +72 -14
  94. package/dist/commands/instructions.js.map +1 -1
  95. package/dist/commands/list/campaign/campaign.js +63 -0
  96. package/dist/commands/list/campaign/campaign.js.map +1 -0
  97. package/dist/commands/list/campaign/owners.js +65 -0
  98. package/dist/commands/list/campaign/owners.js.map +1 -0
  99. package/dist/commands/list/json.js +155 -0
  100. package/dist/commands/list/json.js.map +1 -0
  101. package/dist/commands/list/list.js +358 -0
  102. package/dist/commands/list/list.js.map +1 -0
  103. package/dist/commands/list/print.js +344 -0
  104. package/dist/commands/list/print.js.map +1 -0
  105. package/dist/commands/list/review.js +120 -0
  106. package/dist/commands/list/review.js.map +1 -0
  107. package/dist/commands/list/suites/suites.js +140 -0
  108. package/dist/commands/list/suites/suites.js.map +1 -0
  109. package/dist/commands/list/views.js +108 -0
  110. package/dist/commands/list/views.js.map +1 -0
  111. package/dist/commands/mcp/dispatch.js +238 -0
  112. package/dist/commands/mcp/dispatch.js.map +1 -0
  113. package/dist/commands/mcp/mcp.js +75 -0
  114. package/dist/commands/mcp/mcp.js.map +1 -0
  115. package/dist/commands/mcp/serve.js +55 -0
  116. package/dist/commands/mcp/serve.js.map +1 -0
  117. package/dist/commands/migrate-openspec/materialize/active.js +175 -0
  118. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
  119. package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
  120. package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
  121. package/dist/commands/migrate-openspec/materialize/living.js +62 -0
  122. package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
  123. package/dist/commands/migrate-openspec/materialize/stage.js +143 -0
  124. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
  125. package/dist/commands/migrate-openspec/materialize/target.js +219 -0
  126. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
  127. package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
  128. package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
  129. package/dist/commands/migrate-openspec/openspec/decisions.js +86 -0
  130. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
  131. package/dist/commands/migrate-openspec/openspec/error.js +18 -0
  132. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
  133. package/dist/commands/migrate-openspec/openspec/mapping.js +162 -0
  134. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
  135. package/dist/commands/migrate-openspec/openspec/paths.js +40 -0
  136. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
  137. package/dist/commands/migrate-openspec/openspec/read.js +49 -0
  138. package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
  139. package/dist/commands/migrate-openspec/print.js +89 -0
  140. package/dist/commands/migrate-openspec/print.js.map +1 -0
  141. package/dist/commands/new/capability-template.js +118 -0
  142. package/dist/commands/new/capability-template.js.map +1 -0
  143. package/dist/commands/new/commit.js +62 -0
  144. package/dist/commands/new/commit.js.map +1 -0
  145. package/dist/commands/new/modifies.js +206 -0
  146. package/dist/commands/new/modifies.js.map +1 -0
  147. package/dist/commands/new/new.js +328 -0
  148. package/dist/commands/new/new.js.map +1 -0
  149. package/dist/commands/new/templates.js +279 -0
  150. package/dist/commands/new/templates.js.map +1 -0
  151. package/dist/commands/open.js +199 -0
  152. package/dist/commands/open.js.map +1 -0
  153. package/dist/commands/policy/format.js +152 -0
  154. package/dist/commands/policy/format.js.map +1 -0
  155. package/dist/commands/{docs-repo-gate.js → policy/gate.js} +38 -11
  156. package/dist/commands/policy/gate.js.map +1 -0
  157. package/dist/commands/rebase/edit.js +106 -0
  158. package/dist/commands/rebase/edit.js.map +1 -0
  159. package/dist/commands/rebase/living.js +185 -0
  160. package/dist/commands/rebase/living.js.map +1 -0
  161. package/dist/commands/rebase/plan.js +245 -0
  162. package/dist/commands/rebase/plan.js.map +1 -0
  163. package/dist/commands/rebase/rebase.js +300 -0
  164. package/dist/commands/rebase/rebase.js.map +1 -0
  165. package/dist/commands/seed/commit.js +221 -0
  166. package/dist/commands/seed/commit.js.map +1 -0
  167. package/dist/commands/seed/plan.js +278 -0
  168. package/dist/commands/seed/plan.js.map +1 -0
  169. package/dist/commands/seed/seed.js +174 -0
  170. package/dist/commands/seed/seed.js.map +1 -0
  171. package/dist/commands/show/feature.js +182 -0
  172. package/dist/commands/show/feature.js.map +1 -0
  173. package/dist/commands/show/introduced.js +157 -0
  174. package/dist/commands/show/introduced.js.map +1 -0
  175. package/dist/commands/show/marks.js +53 -0
  176. package/dist/commands/show/marks.js.map +1 -0
  177. package/dist/commands/show/service.js +239 -0
  178. package/dist/commands/show/service.js.map +1 -0
  179. package/dist/commands/show/show.js +87 -0
  180. package/dist/commands/show/show.js.map +1 -0
  181. package/dist/commands/status/fleet-next.js +90 -0
  182. package/dist/commands/status/fleet-next.js.map +1 -0
  183. package/dist/commands/status/json.js +47 -0
  184. package/dist/commands/status/json.js.map +1 -0
  185. package/dist/commands/status/print.js +209 -0
  186. package/dist/commands/status/print.js.map +1 -0
  187. package/dist/commands/status/status.js +115 -0
  188. package/dist/commands/status/status.js.map +1 -0
  189. package/dist/commands/steps/steps.js +269 -0
  190. package/dist/commands/steps/steps.js.map +1 -0
  191. package/dist/commands/subsystem/edit.js +192 -0
  192. package/dist/commands/subsystem/edit.js.map +1 -0
  193. package/dist/commands/subsystem/move.js +325 -0
  194. package/dist/commands/subsystem/move.js.map +1 -0
  195. package/dist/commands/subsystem/query.js +113 -0
  196. package/dist/commands/subsystem/query.js.map +1 -0
  197. package/dist/commands/subsystem/subsystem.js +98 -0
  198. package/dist/commands/subsystem/subsystem.js.map +1 -0
  199. package/dist/commands/subsystem/sync.js +318 -0
  200. package/dist/commands/subsystem/sync.js.map +1 -0
  201. package/dist/commands/subsystem/txn/exclude.js +132 -0
  202. package/dist/commands/subsystem/txn/exclude.js.map +1 -0
  203. package/dist/commands/subsystem/txn/links.js +128 -0
  204. package/dist/commands/subsystem/txn/links.js.map +1 -0
  205. package/dist/commands/subsystem/txn/report.js +174 -0
  206. package/dist/commands/subsystem/txn/report.js.map +1 -0
  207. package/dist/commands/subsystem/txn/txn.js +196 -0
  208. package/dist/commands/subsystem/txn/txn.js.map +1 -0
  209. package/dist/commands/subsystem/txn/views.js +249 -0
  210. package/dist/commands/subsystem/txn/views.js.map +1 -0
  211. package/dist/commands/tasks/actions.js +107 -0
  212. package/dist/commands/tasks/actions.js.map +1 -0
  213. package/dist/commands/tasks/render.js +253 -0
  214. package/dist/commands/tasks/render.js.map +1 -0
  215. package/dist/commands/tasks/tasks.js +119 -0
  216. package/dist/commands/tasks/tasks.js.map +1 -0
  217. package/dist/commands/unarchive/manifest.js +176 -0
  218. package/dist/commands/unarchive/manifest.js.map +1 -0
  219. package/dist/commands/unarchive/restore.js +254 -0
  220. package/dist/commands/unarchive/restore.js.map +1 -0
  221. package/dist/commands/unarchive/unarchive.js +59 -0
  222. package/dist/commands/unarchive/unarchive.js.map +1 -0
  223. package/dist/commands/validate/arch-coverage.js +245 -0
  224. package/dist/commands/validate/arch-coverage.js.map +1 -0
  225. package/dist/commands/validate/checks/fleet-shape.js +204 -0
  226. package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
  227. package/dist/commands/validate/checks/health-deps.js +58 -0
  228. package/dist/commands/validate/checks/health-deps.js.map +1 -0
  229. package/dist/commands/validate/checks/introduced.js +102 -0
  230. package/dist/commands/validate/checks/introduced.js.map +1 -0
  231. package/dist/commands/validate/checks/requirements.js +264 -0
  232. package/dist/commands/validate/checks/requirements.js.map +1 -0
  233. package/dist/commands/validate/checks/sources.js +85 -0
  234. package/dist/commands/validate/checks/sources.js.map +1 -0
  235. package/dist/commands/validate/coverage/covers-after.js +105 -0
  236. package/dist/commands/validate/coverage/covers-after.js.map +1 -0
  237. package/dist/commands/validate/feature.js +222 -0
  238. package/dist/commands/validate/feature.js.map +1 -0
  239. package/dist/commands/validate/fleet/census.js +93 -0
  240. package/dist/commands/validate/fleet/census.js.map +1 -0
  241. package/dist/commands/validate/fleet/kind-tags.js +39 -0
  242. package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
  243. package/dist/commands/validate/fleet/landscape.js +371 -0
  244. package/dist/commands/validate/fleet/landscape.js.map +1 -0
  245. package/dist/commands/validate/fleet/load.js +118 -0
  246. package/dist/commands/validate/fleet/load.js.map +1 -0
  247. package/dist/commands/validate/fleet/map/attest.js +98 -0
  248. package/dist/commands/validate/fleet/map/attest.js.map +1 -0
  249. package/dist/commands/validate/fleet/map/bindings.js +107 -0
  250. package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
  251. package/dist/commands/validate/fleet/map/consumers.js +244 -0
  252. package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
  253. package/dist/commands/validate/fleet/map/isolation.js +116 -0
  254. package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
  255. package/dist/commands/validate/fleet/map/usecases.js +54 -0
  256. package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
  257. package/dist/commands/validate/fleet/obligations.js +266 -0
  258. package/dist/commands/validate/fleet/obligations.js.map +1 -0
  259. package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
  260. package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
  261. package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
  262. package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
  263. package/dist/commands/validate/fleet/scorecard/print.js +71 -0
  264. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
  265. package/dist/commands/validate/fleet/scorecard/scorecard.js +162 -0
  266. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
  267. package/dist/commands/validate/fleet/usecases/place.js +13 -0
  268. package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
  269. package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
  270. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
  271. package/dist/commands/validate/fleet/views/ids.js +75 -0
  272. package/dist/commands/validate/fleet/views/ids.js.map +1 -0
  273. package/dist/commands/validate/fleet/views/projects.js +278 -0
  274. package/dist/commands/validate/fleet/views/projects.js.map +1 -0
  275. package/dist/commands/validate/fleet/views/root-documents.js +83 -0
  276. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
  277. package/dist/commands/validate/fleet/views/stale.js +52 -0
  278. package/dist/commands/validate/fleet/views/stale.js.map +1 -0
  279. package/dist/commands/validate/links/corpus.js +89 -0
  280. package/dist/commands/validate/links/corpus.js.map +1 -0
  281. package/dist/commands/validate/links/glossary.js +75 -0
  282. package/dist/commands/validate/links/glossary.js.map +1 -0
  283. package/dist/commands/validate/render/repeats.js +63 -0
  284. package/dist/commands/validate/render/repeats.js.map +1 -0
  285. package/dist/commands/validate/render/rollups.js +115 -0
  286. package/dist/commands/validate/render/rollups.js.map +1 -0
  287. package/dist/commands/validate/render/text.js +199 -0
  288. package/dist/commands/validate/render/text.js.map +1 -0
  289. package/dist/commands/validate/report.js +145 -0
  290. package/dist/commands/validate/report.js.map +1 -0
  291. package/dist/commands/validate/scope.js +113 -0
  292. package/dist/commands/validate/scope.js.map +1 -0
  293. package/dist/commands/validate/service/api.js +252 -0
  294. package/dist/commands/validate/service/api.js.map +1 -0
  295. package/dist/commands/validate/service/contracts/generated.js +134 -0
  296. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  297. package/dist/commands/validate/service/events/events.js +241 -0
  298. package/dist/commands/validate/service/events/events.js.map +1 -0
  299. package/dist/commands/validate/service/events/message-producers.js +77 -0
  300. package/dist/commands/validate/service/events/message-producers.js.map +1 -0
  301. package/dist/commands/validate/service/evidence-pins.js +177 -0
  302. package/dist/commands/validate/service/evidence-pins.js.map +1 -0
  303. package/dist/commands/validate/service/model/diverged.js +226 -0
  304. package/dist/commands/validate/service/model/diverged.js.map +1 -0
  305. package/dist/commands/validate/service/model/grade.js +163 -0
  306. package/dist/commands/validate/service/model/grade.js.map +1 -0
  307. package/dist/commands/validate/service/service.js +310 -0
  308. package/dist/commands/validate/service/service.js.map +1 -0
  309. package/dist/commands/validate/service/specs.js +279 -0
  310. package/dist/commands/validate/service/specs.js.map +1 -0
  311. package/dist/commands/validate/service/spine.js +148 -0
  312. package/dist/commands/validate/service/spine.js.map +1 -0
  313. package/dist/commands/validate/service/usecases/tags.js +113 -0
  314. package/dist/commands/validate/service/usecases/tags.js.map +1 -0
  315. package/dist/commands/validate/service/usecases/usecases.js +209 -0
  316. package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
  317. package/dist/commands/validate/validate.js +363 -0
  318. package/dist/commands/validate/validate.js.map +1 -0
  319. package/dist/commands/verify/cross/diff.js +178 -0
  320. package/dist/commands/verify/cross/diff.js.map +1 -0
  321. package/dist/commands/verify/evidence/contract.js +32 -0
  322. package/dist/commands/verify/evidence/contract.js.map +1 -0
  323. package/dist/commands/verify/evidence/pins.js +52 -0
  324. package/dist/commands/verify/evidence/pins.js.map +1 -0
  325. package/dist/commands/verify/evidence/read.js +81 -0
  326. package/dist/commands/verify/evidence/read.js.map +1 -0
  327. package/dist/commands/verify/frozen.js +263 -0
  328. package/dist/commands/verify/frozen.js.map +1 -0
  329. package/dist/commands/verify/record.js +278 -0
  330. package/dist/commands/verify/record.js.map +1 -0
  331. package/dist/commands/verify/report.js +206 -0
  332. package/dist/commands/verify/report.js.map +1 -0
  333. package/dist/commands/verify/results.js +328 -0
  334. package/dist/commands/verify/results.js.map +1 -0
  335. package/dist/commands/verify/verify.js +220 -0
  336. package/dist/commands/verify/verify.js.map +1 -0
  337. package/dist/commands/vouch/contract.js +11 -0
  338. package/dist/commands/vouch/contract.js.map +1 -0
  339. package/dist/commands/vouch/pack/contract.js +2 -0
  340. package/dist/commands/vouch/pack/contract.js.map +1 -0
  341. package/dist/commands/vouch/pack/pack.js +266 -0
  342. package/dist/commands/vouch/pack/pack.js.map +1 -0
  343. package/dist/commands/vouch/pack/print.js +266 -0
  344. package/dist/commands/vouch/pack/print.js.map +1 -0
  345. package/dist/commands/vouch/pack/sections.js +141 -0
  346. package/dist/commands/vouch/pack/sections.js.map +1 -0
  347. package/dist/commands/vouch/prompt/confirm.js +46 -0
  348. package/dist/commands/vouch/prompt/confirm.js.map +1 -0
  349. package/dist/commands/vouch/report.js +101 -0
  350. package/dist/commands/vouch/report.js.map +1 -0
  351. package/dist/commands/vouch/run.js +230 -0
  352. package/dist/commands/vouch/run.js.map +1 -0
  353. package/dist/commands/vouch/sample/plan.js +111 -0
  354. package/dist/commands/vouch/sample/plan.js.map +1 -0
  355. package/dist/commands/vouch/sample/print.js +109 -0
  356. package/dist/commands/vouch/sample/print.js.map +1 -0
  357. package/dist/commands/vouch/vet/verify.js +155 -0
  358. package/dist/commands/vouch/vet/verify.js.map +1 -0
  359. package/dist/commands/vouch/vouch.js +169 -0
  360. package/dist/commands/vouch/vouch.js.map +1 -0
  361. package/dist/core/agent/agents-md/command-map.js +299 -0
  362. package/dist/core/agent/agents-md/command-map.js.map +1 -0
  363. package/dist/core/agent/agents-md/map/explain.js +46 -0
  364. package/dist/core/agent/agents-md/map/explain.js.map +1 -0
  365. package/dist/core/agent/agents-md/map/lenses/context.js +50 -0
  366. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
  367. package/dist/core/agent/agents-md/map/lenses/diff.js +59 -0
  368. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
  369. package/dist/core/agent/agents-md/map/lenses/gate.js +65 -0
  370. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
  371. package/dist/core/agent/agents-md/map/mcp.js +44 -0
  372. package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
  373. package/dist/core/agent/agents-md/map/status.js +78 -0
  374. package/dist/core/agent/agents-md/map/status.js.map +1 -0
  375. package/dist/core/agent/agents-md/map/subsystem.js +104 -0
  376. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
  377. package/dist/core/agent/agents-md/map/usecases.js +138 -0
  378. package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
  379. package/dist/core/agent/agents-md/refusals.js +273 -0
  380. package/dist/core/agent/agents-md/refusals.js.map +1 -0
  381. package/dist/core/agent/agents-md.js +203 -0
  382. package/dist/core/agent/agents-md.js.map +1 -0
  383. package/dist/core/{agents-stamp.js → agent/agents-stamp.js} +60 -0
  384. package/dist/core/agent/agents-stamp.js.map +1 -0
  385. package/dist/core/agent/contract.js +2 -0
  386. package/dist/core/agent/contract.js.map +1 -0
  387. package/dist/core/agent/protocol.js +193 -0
  388. package/dist/core/agent/protocol.js.map +1 -0
  389. package/dist/core/agent/scaffold.js +254 -0
  390. package/dist/core/agent/scaffold.js.map +1 -0
  391. package/dist/core/agent/support/report.js +144 -0
  392. package/dist/core/agent/support/report.js.map +1 -0
  393. package/dist/core/agent/tools/dialects.js +151 -0
  394. package/dist/core/agent/tools/dialects.js.map +1 -0
  395. package/dist/core/agent/tools/registry.js +193 -0
  396. package/dist/core/agent/tools/registry.js.map +1 -0
  397. package/dist/core/agent/workflows/adopt.js +280 -0
  398. package/dist/core/agent/workflows/adopt.js.map +1 -0
  399. package/dist/core/agent/workflows/check.js +373 -0
  400. package/dist/core/agent/workflows/check.js.map +1 -0
  401. package/dist/core/agent/workflows/closing.js +323 -0
  402. package/dist/core/agent/workflows/closing.js.map +1 -0
  403. package/dist/core/agent/workflows/feature.js +220 -0
  404. package/dist/core/agent/workflows/feature.js.map +1 -0
  405. package/dist/core/agent/workflows/implement.js +188 -0
  406. package/dist/core/agent/workflows/implement.js.map +1 -0
  407. package/dist/core/agent/workflows/reference/authoring.js +311 -0
  408. package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
  409. package/dist/core/agent/workflows/reference/codes.js +67 -0
  410. package/dist/core/agent/workflows/reference/codes.js.map +1 -0
  411. package/dist/core/agent/workflows/reference/done-check.js +210 -0
  412. package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
  413. package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
  414. package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
  415. package/dist/core/agent/workflows/reference/reference.js +19 -0
  416. package/dist/core/agent/workflows/reference/reference.js.map +1 -0
  417. package/dist/core/agent/workflows/reference/spine.js +338 -0
  418. package/dist/core/agent/workflows/reference/spine.js.map +1 -0
  419. package/dist/core/asyncapi/baseline/plan.js +43 -0
  420. package/dist/core/asyncapi/baseline/plan.js.map +1 -0
  421. package/dist/core/asyncapi/baseline/surfaces.js +54 -0
  422. package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
  423. package/dist/core/asyncapi/depth.js +164 -0
  424. package/dist/core/asyncapi/depth.js.map +1 -0
  425. package/dist/core/asyncapi/digest.js +146 -0
  426. package/dist/core/asyncapi/digest.js.map +1 -0
  427. package/dist/core/asyncapi/merge/components.js +92 -0
  428. package/dist/core/asyncapi/merge/components.js.map +1 -0
  429. package/dist/core/asyncapi/merge/error.js +38 -0
  430. package/dist/core/asyncapi/merge/error.js.map +1 -0
  431. package/dist/core/asyncapi/merge/markers.js +290 -0
  432. package/dist/core/asyncapi/merge/markers.js.map +1 -0
  433. package/dist/core/asyncapi/merge/merge.js +195 -0
  434. package/dist/core/asyncapi/merge/merge.js.map +1 -0
  435. package/dist/core/asyncapi/merge/pin.js +83 -0
  436. package/dist/core/asyncapi/merge/pin.js.map +1 -0
  437. package/dist/core/asyncapi/model.js +11 -0
  438. package/dist/core/asyncapi/model.js.map +1 -0
  439. package/dist/core/asyncapi/producers.js +50 -0
  440. package/dist/core/asyncapi/producers.js.map +1 -0
  441. package/dist/core/asyncapi/read.js +263 -0
  442. package/dist/core/asyncapi/read.js.map +1 -0
  443. package/dist/core/brief/brief.js +285 -0
  444. package/dist/core/brief/brief.js.map +1 -0
  445. package/dist/core/brief/checks.js +385 -0
  446. package/dist/core/brief/checks.js.map +1 -0
  447. package/dist/core/brief/landscape.js +273 -0
  448. package/dist/core/brief/landscape.js.map +1 -0
  449. package/dist/core/brief/map/extend.js +84 -0
  450. package/dist/core/brief/map/extend.js.map +1 -0
  451. package/dist/core/brief/map/owed.js +266 -0
  452. package/dist/core/brief/map/owed.js.map +1 -0
  453. package/dist/core/brief/targets.js +304 -0
  454. package/dist/core/brief/targets.js.map +1 -0
  455. package/dist/core/brief/unchecked.js +58 -0
  456. package/dist/core/brief/unchecked.js.map +1 -0
  457. package/dist/core/c4/arch.js +190 -0
  458. package/dist/core/c4/arch.js.map +1 -0
  459. package/dist/core/c4/direction/direction.js +114 -0
  460. package/dist/core/c4/direction/direction.js.map +1 -0
  461. package/dist/core/c4/likec4.js +243 -0
  462. package/dist/core/c4/likec4.js.map +1 -0
  463. package/dist/core/c4/locate/unlocated.js +150 -0
  464. package/dist/core/c4/locate/unlocated.js.map +1 -0
  465. package/dist/core/c4/parsed/deployment.js +160 -0
  466. package/dist/core/c4/parsed/deployment.js.map +1 -0
  467. package/dist/core/c4/parsed/dynamic-views.js +132 -0
  468. package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
  469. package/dist/core/c4/parsed/specification.js +78 -0
  470. package/dist/core/c4/parsed/specification.js.map +1 -0
  471. package/dist/core/c4/parsed/values.js +110 -0
  472. package/dist/core/c4/parsed/values.js.map +1 -0
  473. package/dist/core/c4/parsed/view-ids.js +113 -0
  474. package/dist/core/c4/parsed/view-ids.js.map +1 -0
  475. package/dist/core/c4/prefetch/fleet.js +134 -0
  476. package/dist/core/c4/prefetch/fleet.js.map +1 -0
  477. package/dist/core/c4/project/architecture.js +161 -0
  478. package/dist/core/c4/project/architecture.js.map +1 -0
  479. package/dist/core/c4/project/batch.js +140 -0
  480. package/dist/core/c4/project/batch.js.map +1 -0
  481. package/dist/core/c4/project/documents.js +89 -0
  482. package/dist/core/c4/project/documents.js.map +1 -0
  483. package/dist/core/c4/project/load.js +219 -0
  484. package/dist/core/c4/project/load.js.map +1 -0
  485. package/dist/core/c4/project/staged.js +277 -0
  486. package/dist/core/c4/project/staged.js.map +1 -0
  487. package/dist/core/c4/resolve/attested.js +0 -0
  488. package/dist/core/c4/resolve/attested.js.map +1 -0
  489. package/dist/core/c4/resolve/external.js +51 -0
  490. package/dist/core/c4/resolve/external.js.map +1 -0
  491. package/dist/core/c4/resolve/service.js +174 -0
  492. package/dist/core/c4/resolve/service.js.map +1 -0
  493. package/dist/core/c4/resolve/steps.js +290 -0
  494. package/dist/core/c4/resolve/steps.js.map +1 -0
  495. package/dist/core/c4/resolve/systems.js +154 -0
  496. package/dist/core/c4/resolve/systems.js.map +1 -0
  497. package/dist/core/c4/root-project/exclude.js +352 -0
  498. package/dist/core/c4/root-project/exclude.js.map +1 -0
  499. package/dist/core/c4/seed/fleet-file.js +201 -0
  500. package/dist/core/c4/seed/fleet-file.js.map +1 -0
  501. package/dist/core/c4/seed/items.js +91 -0
  502. package/dist/core/c4/seed/items.js.map +1 -0
  503. package/dist/core/c4/seed/stamp.js +48 -0
  504. package/dist/core/c4/seed/stamp.js.map +1 -0
  505. package/dist/core/c4/seed/template.js +173 -0
  506. package/dist/core/c4/seed/template.js.map +1 -0
  507. package/dist/core/c4/service-model/created/models.js +96 -0
  508. package/dist/core/c4/service-model/created/models.js.map +1 -0
  509. package/dist/core/c4/service-model/documents.js +47 -0
  510. package/dist/core/c4/service-model/documents.js.map +1 -0
  511. package/dist/core/c4/service-model/fleet/extending.js +82 -0
  512. package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
  513. package/dist/core/c4/service-model/load.js +165 -0
  514. package/dist/core/c4/service-model/load.js.map +1 -0
  515. package/dist/core/c4/service-model/renderer.js +128 -0
  516. package/dist/core/c4/service-model/renderer.js.map +1 -0
  517. package/dist/core/c4/service-model/shape.js +106 -0
  518. package/dist/core/c4/service-model/shape.js.map +1 -0
  519. package/dist/core/c4/service-model/slice.js +188 -0
  520. package/dist/core/c4/service-model/slice.js.map +1 -0
  521. package/dist/core/c4/source-mask.js +369 -0
  522. package/dist/core/c4/source-mask.js.map +1 -0
  523. package/dist/core/c4/source-scan.js +308 -0
  524. package/dist/core/c4/source-scan.js.map +1 -0
  525. package/dist/core/c4/splice/authored-source.js +167 -0
  526. package/dist/core/c4/splice/authored-source.js.map +1 -0
  527. package/dist/core/c4/splice/contract.js +30 -0
  528. package/dist/core/c4/splice/contract.js.map +1 -0
  529. package/dist/core/c4/splice/delta-blocks.js +126 -0
  530. package/dist/core/c4/splice/delta-blocks.js.map +1 -0
  531. package/dist/core/c4/splice/identity/declared.js +87 -0
  532. package/dist/core/c4/splice/identity/declared.js.map +1 -0
  533. package/dist/core/c4/splice/identity/edges.js +73 -0
  534. package/dist/core/c4/splice/identity/edges.js.map +1 -0
  535. package/dist/core/c4/splice/identity/existing.js +80 -0
  536. package/dist/core/c4/splice/identity/existing.js.map +1 -0
  537. package/dist/core/c4/splice/identity/interior.js +80 -0
  538. package/dist/core/c4/splice/identity/interior.js.map +1 -0
  539. package/dist/core/c4/splice/identity/owner.js +50 -0
  540. package/dist/core/c4/splice/identity/owner.js.map +1 -0
  541. package/dist/core/c4/splice/landscape-merge.js +337 -0
  542. package/dist/core/c4/splice/landscape-merge.js.map +1 -0
  543. package/dist/core/c4/splice/model/merge.js +151 -0
  544. package/dist/core/c4/splice/model/merge.js.map +1 -0
  545. package/dist/core/c4/splice/placement.js +298 -0
  546. package/dist/core/c4/splice/placement.js.map +1 -0
  547. package/dist/core/c4/splice/text/spot.js +131 -0
  548. package/dist/core/c4/splice/text/spot.js.map +1 -0
  549. package/dist/core/c4/workspace.js +166 -0
  550. package/dist/core/c4/workspace.js.map +1 -0
  551. package/dist/core/capabilities/authoring/notes.js +141 -0
  552. package/dist/core/capabilities/authoring/notes.js.map +1 -0
  553. package/dist/core/capabilities/capabilities.js +124 -0
  554. package/dist/core/capabilities/capabilities.js.map +1 -0
  555. package/dist/core/capabilities/delta/doc.js +47 -0
  556. package/dist/core/capabilities/delta/doc.js.map +1 -0
  557. package/dist/core/capabilities/delta/overlay.js +129 -0
  558. package/dist/core/capabilities/delta/overlay.js.map +1 -0
  559. package/dist/core/capabilities/delta/summary.js +57 -0
  560. package/dist/core/capabilities/delta/summary.js.map +1 -0
  561. package/dist/core/capabilities/delta/tree.js +43 -0
  562. package/dist/core/capabilities/delta/tree.js.map +1 -0
  563. package/dist/core/capabilities/delta/uncovered.js +222 -0
  564. package/dist/core/capabilities/delta/uncovered.js.map +1 -0
  565. package/dist/core/capabilities/findings.js +338 -0
  566. package/dist/core/capabilities/findings.js.map +1 -0
  567. package/dist/core/capabilities/realizes/findings.js +174 -0
  568. package/dist/core/capabilities/realizes/findings.js.map +1 -0
  569. package/dist/core/capabilities/realizes/join.js +123 -0
  570. package/dist/core/capabilities/realizes/join.js.map +1 -0
  571. package/dist/core/capabilities/rollup.js +205 -0
  572. package/dist/core/capabilities/rollup.js.map +1 -0
  573. package/dist/core/capabilities/tree.js +104 -0
  574. package/dist/core/capabilities/tree.js.map +1 -0
  575. package/dist/core/capabilities/usecase-join.js +267 -0
  576. package/dist/core/capabilities/usecase-join.js.map +1 -0
  577. package/dist/core/coherence/authoring/scaffold.js +185 -0
  578. package/dist/core/coherence/authoring/scaffold.js.map +1 -0
  579. package/dist/core/coherence/authoring/sentinels.js +75 -0
  580. package/dist/core/coherence/authoring/sentinels.js.map +1 -0
  581. package/dist/core/coherence/calls/called.js +52 -0
  582. package/dist/core/coherence/calls/called.js.map +1 -0
  583. package/dist/core/coherence/coherence.js +332 -0
  584. package/dist/core/coherence/coherence.js.map +1 -0
  585. package/dist/core/coherence/consumed/operations.js +70 -0
  586. package/dist/core/coherence/consumed/operations.js.map +1 -0
  587. package/dist/core/coherence/consumed/refusal.js +38 -0
  588. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  589. package/dist/core/coherence/covers/health.js +85 -0
  590. package/dist/core/coherence/covers/health.js.map +1 -0
  591. package/dist/core/coherence/declared.js +295 -0
  592. package/dist/core/coherence/declared.js.map +1 -0
  593. package/dist/core/coherence/events/declared.js +191 -0
  594. package/dist/core/coherence/events/declared.js.map +1 -0
  595. package/dist/core/coherence/events/events.js +335 -0
  596. package/dist/core/coherence/events/events.js.map +1 -0
  597. package/dist/core/coherence/events/grades.js +189 -0
  598. package/dist/core/coherence/events/grades.js.map +1 -0
  599. package/dist/core/coherence/events/lookups.js +63 -0
  600. package/dist/core/coherence/events/lookups.js.map +1 -0
  601. package/dist/core/coherence/events/produced.js +100 -0
  602. package/dist/core/coherence/events/produced.js.map +1 -0
  603. package/dist/core/coherence/living.js +198 -0
  604. package/dist/core/coherence/living.js.map +1 -0
  605. package/dist/core/coherence/lookups.js +95 -0
  606. package/dist/core/coherence/lookups.js.map +1 -0
  607. package/dist/core/coherence/pending.js +39 -0
  608. package/dist/core/coherence/pending.js.map +1 -0
  609. package/dist/core/coherence/retired/archived.js +285 -0
  610. package/dist/core/coherence/retired/archived.js.map +1 -0
  611. package/dist/core/coherence/retired/event-pair.js +62 -0
  612. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  613. package/dist/core/coherence/retired/merged.js +79 -0
  614. package/dist/core/coherence/retired/merged.js.map +1 -0
  615. package/dist/core/coherence/retired/retirement.js +49 -0
  616. package/dist/core/coherence/retired/retirement.js.map +1 -0
  617. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  618. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  619. package/dist/core/coherence/retired/revival/revived.js +297 -0
  620. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  621. package/dist/core/conflict-markers.js +60 -0
  622. package/dist/core/conflict-markers.js.map +1 -0
  623. package/dist/core/consumers/after.js +113 -0
  624. package/dist/core/consumers/after.js.map +1 -0
  625. package/dist/core/consumers/consumers.js +296 -0
  626. package/dist/core/consumers/consumers.js.map +1 -0
  627. package/dist/core/consumers/current.js +211 -0
  628. package/dist/core/consumers/current.js.map +1 -0
  629. package/dist/core/consumers/scan.js +46 -0
  630. package/dist/core/consumers/scan.js.map +1 -0
  631. package/dist/core/consumers/state.js +27 -0
  632. package/dist/core/consumers/state.js.map +1 -0
  633. package/dist/core/delta/claims.js +73 -0
  634. package/dist/core/delta/claims.js.map +1 -0
  635. package/dist/core/delta/delta.js +341 -0
  636. package/dist/core/delta/delta.js.map +1 -0
  637. package/dist/core/delta/document.js +177 -0
  638. package/dist/core/delta/document.js.map +1 -0
  639. package/dist/core/delta/scope.js +48 -0
  640. package/dist/core/delta/scope.js.map +1 -0
  641. package/dist/core/delta/select.js +237 -0
  642. package/dist/core/delta/select.js.map +1 -0
  643. package/dist/core/dependencies/dependencies.js +141 -0
  644. package/dist/core/dependencies/dependencies.js.map +1 -0
  645. package/dist/core/dependencies/facts.js +165 -0
  646. package/dist/core/dependencies/facts.js.map +1 -0
  647. package/dist/core/dependencies/fanin.js +88 -0
  648. package/dist/core/dependencies/fanin.js.map +1 -0
  649. package/dist/core/dependencies/graph.js +106 -0
  650. package/dist/core/dependencies/graph.js.map +1 -0
  651. package/dist/core/deployment/delta.js +191 -0
  652. package/dist/core/deployment/delta.js.map +1 -0
  653. package/dist/core/deployment/objects.js +145 -0
  654. package/dist/core/deployment/objects.js.map +1 -0
  655. package/dist/core/diff/base-git.js +230 -0
  656. package/dist/core/diff/base-git.js.map +1 -0
  657. package/dist/core/diff/base-state.js +237 -0
  658. package/dist/core/diff/base-state.js.map +1 -0
  659. package/dist/core/diff/joins.js +136 -0
  660. package/dist/core/diff/joins.js.map +1 -0
  661. package/dist/core/diff/scope/changed-paths.js +204 -0
  662. package/dist/core/diff/scope/changed-paths.js.map +1 -0
  663. package/dist/core/diff/scope/edges.js +82 -0
  664. package/dist/core/diff/scope/edges.js.map +1 -0
  665. package/dist/core/diff/semantic.js +312 -0
  666. package/dist/core/diff/semantic.js.map +1 -0
  667. package/dist/core/docs.js +37 -85
  668. package/dist/core/docs.js.map +1 -1
  669. package/dist/core/doctor/agents.js +238 -0
  670. package/dist/core/doctor/agents.js.map +1 -0
  671. package/dist/core/doctor/config.js +139 -0
  672. package/dist/core/doctor/config.js.map +1 -0
  673. package/dist/core/doctor/doctor.js +355 -0
  674. package/dist/core/doctor/doctor.js.map +1 -0
  675. package/dist/core/doctor/report.js +13 -0
  676. package/dist/core/doctor/report.js.map +1 -0
  677. package/dist/core/doctor/reports/scan.js +242 -0
  678. package/dist/core/doctor/reports/scan.js.map +1 -0
  679. package/dist/core/doctor/residue.js +110 -0
  680. package/dist/core/doctor/residue.js.map +1 -0
  681. package/dist/core/document/apply.js +224 -0
  682. package/dist/core/document/apply.js.map +1 -0
  683. package/dist/core/{frontmatter.js → document/frontmatter.js} +103 -2
  684. package/dist/core/document/frontmatter.js.map +1 -0
  685. package/dist/core/document/grammar/directives.js +143 -0
  686. package/dist/core/document/grammar/directives.js.map +1 -0
  687. package/dist/core/document/parse.js +286 -0
  688. package/dist/core/document/parse.js.map +1 -0
  689. package/dist/core/document/scenarios.js +238 -0
  690. package/dist/core/document/scenarios.js.map +1 -0
  691. package/dist/core/document/spec.js +234 -0
  692. package/dist/core/document/spec.js.map +1 -0
  693. package/dist/core/{config.js → envelope/config.js} +89 -18
  694. package/dist/core/envelope/config.js.map +1 -0
  695. package/dist/core/envelope/json.js +209 -0
  696. package/dist/core/envelope/json.js.map +1 -0
  697. package/dist/core/{version.js → envelope/version.js} +3 -3
  698. package/dist/core/envelope/version.js.map +1 -0
  699. package/dist/core/explain/families.js +180 -0
  700. package/dist/core/explain/families.js.map +1 -0
  701. package/dist/core/explain/fix-tables.js +178 -0
  702. package/dist/core/explain/fix-tables.js.map +1 -0
  703. package/dist/core/explain/lookup.js +194 -0
  704. package/dist/core/explain/lookup.js.map +1 -0
  705. package/dist/core/explain/refusals.js +49 -0
  706. package/dist/core/explain/refusals.js.map +1 -0
  707. package/dist/core/explain/terms/documents.js +227 -0
  708. package/dist/core/explain/terms/documents.js.map +1 -0
  709. package/dist/core/explain/terms/done-check.js +144 -0
  710. package/dist/core/explain/terms/done-check.js.map +1 -0
  711. package/dist/core/explain/terms/entry.js +14 -0
  712. package/dist/core/explain/terms/entry.js.map +1 -0
  713. package/dist/core/explain/terms/stages.js +71 -0
  714. package/dist/core/explain/terms/stages.js.map +1 -0
  715. package/dist/core/explain/terms/tasks.js +97 -0
  716. package/dist/core/explain/terms/tasks.js.map +1 -0
  717. package/dist/core/explain/terms.js +103 -0
  718. package/dist/core/explain/terms.js.map +1 -0
  719. package/dist/core/explore/describe.js +105 -0
  720. package/dist/core/explore/describe.js.map +1 -0
  721. package/dist/core/explore/explore.js +204 -0
  722. package/dist/core/explore/explore.js.map +1 -0
  723. package/dist/core/fleet-context.js +174 -146
  724. package/dist/core/fleet-context.js.map +1 -1
  725. package/dist/core/gate/checks.js +208 -0
  726. package/dist/core/gate/checks.js.map +1 -0
  727. package/dist/core/gate/partners.js +246 -0
  728. package/dist/core/gate/partners.js.map +1 -0
  729. package/dist/core/gate/report.js +47 -0
  730. package/dist/core/gate/report.js.map +1 -0
  731. package/dist/core/gate/verification.js +176 -0
  732. package/dist/core/gate/verification.js.map +1 -0
  733. package/dist/core/gherkin/digest.js +60 -0
  734. package/dist/core/gherkin/digest.js.map +1 -0
  735. package/dist/core/gherkin/emit.js +177 -0
  736. package/dist/core/gherkin/emit.js.map +1 -0
  737. package/dist/core/gherkin/read.js +76 -0
  738. package/dist/core/gherkin/read.js.map +1 -0
  739. package/dist/core/gherkin/stale.js +210 -0
  740. package/dist/core/gherkin/stale.js.map +1 -0
  741. package/dist/core/gherkin/stamp.js +77 -0
  742. package/dist/core/gherkin/stamp.js.map +1 -0
  743. package/dist/core/gherkin/steps/catalogue.js +154 -0
  744. package/dist/core/gherkin/steps/catalogue.js.map +1 -0
  745. package/dist/core/gherkin/steps/inventory.js +98 -0
  746. package/dist/core/gherkin/steps/inventory.js.map +1 -0
  747. package/dist/core/gherkin/steps/phrase.js +72 -0
  748. package/dist/core/gherkin/steps/phrase.js.map +1 -0
  749. package/dist/core/glossary/delta.js +109 -0
  750. package/dist/core/glossary/delta.js.map +1 -0
  751. package/dist/core/glossary/tree.js +73 -0
  752. package/dist/core/glossary/tree.js.map +1 -0
  753. package/dist/core/{concurrency.js → kernel/concurrency.js} +19 -0
  754. package/dist/core/kernel/concurrency.js.map +1 -0
  755. package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
  756. package/dist/core/kernel/document-bytes.js.map +1 -0
  757. package/dist/core/kernel/fences.js +42 -0
  758. package/dist/core/kernel/fences.js.map +1 -0
  759. package/dist/core/kernel/ids/capability.js +72 -0
  760. package/dist/core/kernel/ids/capability.js.map +1 -0
  761. package/dist/core/kernel/ids/dirs.js +33 -0
  762. package/dist/core/kernel/ids/dirs.js.map +1 -0
  763. package/dist/core/kernel/ids/feature.js +75 -0
  764. package/dist/core/kernel/ids/feature.js.map +1 -0
  765. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  766. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  767. package/dist/core/kernel/ids/service.js +156 -0
  768. package/dist/core/kernel/ids/service.js.map +1 -0
  769. package/dist/core/kernel/ids/subsystem.js +63 -0
  770. package/dist/core/kernel/ids/subsystem.js.map +1 -0
  771. package/dist/core/{path-safety.js → kernel/path-safety.js} +56 -4
  772. package/dist/core/kernel/path-safety.js.map +1 -0
  773. package/dist/core/{records.js → kernel/records.js} +11 -0
  774. package/dist/core/kernel/records.js.map +1 -0
  775. package/dist/core/links/backlinks.js +66 -0
  776. package/dist/core/links/backlinks.js.map +1 -0
  777. package/dist/core/links/case.js +74 -0
  778. package/dist/core/links/case.js.map +1 -0
  779. package/dist/core/links/corpus.js +118 -0
  780. package/dist/core/links/corpus.js.map +1 -0
  781. package/dist/core/links/findings.js +110 -0
  782. package/dist/core/links/findings.js.map +1 -0
  783. package/dist/core/links/parse.js +265 -0
  784. package/dist/core/links/parse.js.map +1 -0
  785. package/dist/core/mcp/argv.js +212 -0
  786. package/dist/core/mcp/argv.js.map +1 -0
  787. package/dist/core/mcp/author-tools.js +73 -0
  788. package/dist/core/mcp/author-tools.js.map +1 -0
  789. package/dist/core/mcp/framing.js +46 -0
  790. package/dist/core/mcp/framing.js.map +1 -0
  791. package/dist/core/mcp/protocol.js +280 -0
  792. package/dist/core/mcp/protocol.js.map +1 -0
  793. package/dist/core/mcp/tools.js +324 -0
  794. package/dist/core/mcp/tools.js.map +1 -0
  795. package/dist/core/obligations/obligations.js +106 -0
  796. package/dist/core/obligations/obligations.js.map +1 -0
  797. package/dist/core/openapi/baseline/gate.js +207 -0
  798. package/dist/core/openapi/baseline/gate.js.map +1 -0
  799. package/dist/core/openapi/baseline/plan.js +142 -0
  800. package/dist/core/openapi/baseline/plan.js.map +1 -0
  801. package/dist/core/openapi/baseline/record.js +174 -0
  802. package/dist/core/openapi/baseline/record.js.map +1 -0
  803. package/dist/core/openapi/contract-text.js +33 -0
  804. package/dist/core/openapi/contract-text.js.map +1 -0
  805. package/dist/core/openapi/contract-tree.js +301 -0
  806. package/dist/core/openapi/contract-tree.js.map +1 -0
  807. package/dist/core/openapi/depth.js +136 -0
  808. package/dist/core/openapi/depth.js.map +1 -0
  809. package/dist/core/openapi/digest.js +239 -0
  810. package/dist/core/openapi/digest.js.map +1 -0
  811. package/dist/core/{openapi.js → openapi/doc.js} +54 -108
  812. package/dist/core/openapi/doc.js.map +1 -0
  813. package/dist/core/openapi/merge/components.js +199 -0
  814. package/dist/core/openapi/merge/components.js.map +1 -0
  815. package/dist/core/openapi/merge/error.js +16 -0
  816. package/dist/core/openapi/merge/error.js.map +1 -0
  817. package/dist/core/openapi/merge/markers.js +204 -0
  818. package/dist/core/openapi/merge/markers.js.map +1 -0
  819. package/dist/core/openapi/merge/merge.js +232 -0
  820. package/dist/core/openapi/merge/merge.js.map +1 -0
  821. package/dist/core/openapi/merge/pin.js +110 -0
  822. package/dist/core/openapi/merge/pin.js.map +1 -0
  823. package/dist/core/openspec/decide/capabilities.js +244 -0
  824. package/dist/core/openspec/decide/capabilities.js.map +1 -0
  825. package/dist/core/openspec/decide/dispositions.js +63 -0
  826. package/dist/core/openspec/decide/dispositions.js.map +1 -0
  827. package/dist/core/openspec/decide/renames.js +179 -0
  828. package/dist/core/openspec/decide/renames.js.map +1 -0
  829. package/dist/core/openspec/decide/verdict.js +84 -0
  830. package/dist/core/openspec/decide/verdict.js.map +1 -0
  831. package/dist/core/openspec/inventory.js +220 -0
  832. package/dist/core/openspec/inventory.js.map +1 -0
  833. package/dist/core/openspec/model/mapping.js +78 -0
  834. package/dist/core/openspec/model/mapping.js.map +1 -0
  835. package/dist/core/openspec/model/model.js +70 -0
  836. package/dist/core/openspec/model/model.js.map +1 -0
  837. package/dist/core/openspec/scan/changes.js +241 -0
  838. package/dist/core/openspec/scan/changes.js.map +1 -0
  839. package/dist/core/openspec/scan/config.js +191 -0
  840. package/dist/core/openspec/scan/config.js.map +1 -0
  841. package/dist/core/openspec/scan/shape.js +304 -0
  842. package/dist/core/openspec/scan/shape.js.map +1 -0
  843. package/dist/core/openspec/scan/walk.js +116 -0
  844. package/dist/core/openspec/scan/walk.js.map +1 -0
  845. package/dist/core/openspec/scan/workspace.js +71 -0
  846. package/dist/core/openspec/scan/workspace.js.map +1 -0
  847. package/dist/core/owners/codeowners.js +141 -0
  848. package/dist/core/owners/codeowners.js.map +1 -0
  849. package/dist/core/pack/features.js +120 -0
  850. package/dist/core/pack/features.js.map +1 -0
  851. package/dist/core/pack/joins.js +112 -0
  852. package/dist/core/pack/joins.js.map +1 -0
  853. package/dist/core/pack/living.js +157 -0
  854. package/dist/core/pack/living.js.map +1 -0
  855. package/dist/core/pack/pack.js +120 -0
  856. package/dist/core/pack/pack.js.map +1 -0
  857. package/dist/core/permissions/permissions.js +87 -0
  858. package/dist/core/permissions/permissions.js.map +1 -0
  859. package/dist/core/projection/api.js +84 -0
  860. package/dist/core/projection/api.js.map +1 -0
  861. package/dist/core/projection/arch-slice.js +63 -0
  862. package/dist/core/projection/arch-slice.js.map +1 -0
  863. package/dist/core/projection/events.js +34 -0
  864. package/dist/core/projection/events.js.map +1 -0
  865. package/dist/core/projection/introduced.js +14 -0
  866. package/dist/core/projection/introduced.js.map +1 -0
  867. package/dist/core/provenance/findings.js +244 -0
  868. package/dist/core/provenance/findings.js.map +1 -0
  869. package/dist/core/provenance/git.js +201 -0
  870. package/dist/core/provenance/git.js.map +1 -0
  871. package/dist/core/provenance/gitq/moves.js +139 -0
  872. package/dist/core/provenance/gitq/moves.js.map +1 -0
  873. package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
  874. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
  875. package/dist/core/provenance/sample/scope.js +238 -0
  876. package/dist/core/provenance/sample/scope.js.map +1 -0
  877. package/dist/core/provenance/sample/sections.js +61 -0
  878. package/dist/core/provenance/sample/sections.js.map +1 -0
  879. package/dist/core/provenance/sources.js +280 -0
  880. package/dist/core/provenance/sources.js.map +1 -0
  881. package/dist/core/provenance/stamp.js +220 -0
  882. package/dist/core/provenance/stamp.js.map +1 -0
  883. package/dist/core/provenance/walk.js +157 -0
  884. package/dist/core/provenance/walk.js.map +1 -0
  885. package/dist/core/repo/authored/paths.js +210 -0
  886. package/dist/core/repo/authored/paths.js.map +1 -0
  887. package/dist/core/repo/entries.js +126 -0
  888. package/dist/core/repo/entries.js.map +1 -0
  889. package/dist/core/repo/paths.js +322 -0
  890. package/dist/core/repo/paths.js.map +1 -0
  891. package/dist/core/repo/repo.js +308 -0
  892. package/dist/core/repo/repo.js.map +1 -0
  893. package/dist/core/repo/service-target.js +153 -0
  894. package/dist/core/repo/service-target.js.map +1 -0
  895. package/dist/core/repo/state.js +58 -0
  896. package/dist/core/repo/state.js.map +1 -0
  897. package/dist/core/repo/tree/find.js +61 -0
  898. package/dist/core/repo/tree/find.js.map +1 -0
  899. package/dist/core/repo/tree/fs.js +90 -0
  900. package/dist/core/repo/tree/fs.js.map +1 -0
  901. package/dist/core/repo/tree/marker.js +61 -0
  902. package/dist/core/repo/tree/marker.js.map +1 -0
  903. package/dist/core/repo/tree/render/body.js +258 -0
  904. package/dist/core/repo/tree/render/body.js.map +1 -0
  905. package/dist/core/repo/tree/render/projects.js +182 -0
  906. package/dist/core/repo/tree/render/projects.js.map +1 -0
  907. package/dist/core/repo/tree/render/service/header.js +125 -0
  908. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  909. package/dist/core/repo/tree/render/service/peers.js +28 -0
  910. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  911. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  912. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  913. package/dist/core/repo/tree/render/service/views.js +78 -0
  914. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  915. package/dist/core/repo/tree/render/stale.js +42 -0
  916. package/dist/core/repo/tree/render/stale.js.map +1 -0
  917. package/dist/core/repo/tree/render/text.js +84 -0
  918. package/dist/core/repo/tree/render/text.js.map +1 -0
  919. package/dist/core/repo/tree/render/views.js +293 -0
  920. package/dist/core/repo/tree/render/views.js.map +1 -0
  921. package/dist/core/repo/tree/walk.js +255 -0
  922. package/dist/core/repo/tree/walk.js.map +1 -0
  923. package/dist/core/results.js +146 -14
  924. package/dist/core/results.js.map +1 -1
  925. package/dist/core/scaffold/landscape.js +239 -0
  926. package/dist/core/scaffold/landscape.js.map +1 -0
  927. package/dist/core/scaffold/legacy.js +271 -0
  928. package/dist/core/scaffold/legacy.js.map +1 -0
  929. package/dist/core/scaffold/readme.js +84 -0
  930. package/dist/core/scaffold/readme.js.map +1 -0
  931. package/dist/core/staging/commit.js +252 -0
  932. package/dist/core/staging/commit.js.map +1 -0
  933. package/dist/core/staging/interrupted.js +96 -0
  934. package/dist/core/staging/interrupted.js.map +1 -0
  935. package/dist/core/staging/lock.js +268 -0
  936. package/dist/core/staging/lock.js.map +1 -0
  937. package/dist/core/staging/recovery/finding.js +35 -0
  938. package/dist/core/staging/recovery/finding.js.map +1 -0
  939. package/dist/core/staging/recovery/intent.js +95 -0
  940. package/dist/core/staging/recovery/intent.js.map +1 -0
  941. package/dist/core/staging/recovery/recover.js +204 -0
  942. package/dist/core/staging/recovery/recover.js.map +1 -0
  943. package/dist/core/staging/recovery/residue.js +62 -0
  944. package/dist/core/staging/recovery/residue.js.map +1 -0
  945. package/dist/core/staging/snapshot/manifest.js +188 -0
  946. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  947. package/dist/core/staging/snapshot/snapshot.js +196 -0
  948. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  949. package/dist/core/staging/txn/forward.js +181 -0
  950. package/dist/core/staging/txn/forward.js.map +1 -0
  951. package/dist/core/staging/txn/journal.js +139 -0
  952. package/dist/core/staging/txn/journal.js.map +1 -0
  953. package/dist/core/staging/txn/transaction.js +75 -0
  954. package/dist/core/staging/txn/transaction.js.map +1 -0
  955. package/dist/core/staging/writes.js +113 -0
  956. package/dist/core/staging/writes.js.map +1 -0
  957. package/dist/core/status/actions/execution.js +60 -0
  958. package/dist/core/status/actions/execution.js.map +1 -0
  959. package/dist/core/status/contracts.js +126 -0
  960. package/dist/core/status/contracts.js.map +1 -0
  961. package/dist/core/status/feature/artifacts.js +196 -0
  962. package/dist/core/status/feature/artifacts.js.map +1 -0
  963. package/dist/core/status/feature/feature.js +214 -0
  964. package/dist/core/status/feature/feature.js.map +1 -0
  965. package/dist/core/status/feature/next.js +163 -0
  966. package/dist/core/status/feature/next.js.map +1 -0
  967. package/dist/core/status/feature/state.js +21 -0
  968. package/dist/core/status/feature/state.js.map +1 -0
  969. package/dist/core/status/feature/words.js +96 -0
  970. package/dist/core/status/feature/words.js.map +1 -0
  971. package/dist/core/status/fleet/fleet.js +216 -0
  972. package/dist/core/status/fleet/fleet.js.map +1 -0
  973. package/dist/core/status/fleet/next.js +302 -0
  974. package/dist/core/status/fleet/next.js.map +1 -0
  975. package/dist/core/status/interrupted.js +97 -0
  976. package/dist/core/status/interrupted.js.map +1 -0
  977. package/dist/core/status/report.js +31 -0
  978. package/dist/core/status/report.js.map +1 -0
  979. package/dist/core/status/scan.js +41 -0
  980. package/dist/core/status/scan.js.map +1 -0
  981. package/dist/core/status/verification.js +216 -0
  982. package/dist/core/status/verification.js.map +1 -0
  983. package/dist/core/tasks/packet.js +199 -0
  984. package/dist/core/tasks/packet.js.map +1 -0
  985. package/dist/core/tasks/record.js +43 -0
  986. package/dist/core/tasks/record.js.map +1 -0
  987. package/dist/core/tasks/sources.js +181 -0
  988. package/dist/core/tasks/sources.js.map +1 -0
  989. package/dist/core/tasks/stamps.js +76 -0
  990. package/dist/core/tasks/stamps.js.map +1 -0
  991. package/dist/core/tasks/unit.js +0 -0
  992. package/dist/core/tasks/unit.js.map +1 -0
  993. package/dist/core/usecases/capability.js +166 -0
  994. package/dist/core/usecases/capability.js.map +1 -0
  995. package/dist/core/usecases/delta/claims.js +140 -0
  996. package/dist/core/usecases/delta/claims.js.map +1 -0
  997. package/dist/core/usecases/delta/flows.js +101 -0
  998. package/dist/core/usecases/delta/flows.js.map +1 -0
  999. package/dist/core/usecases/delta/modified.js +315 -0
  1000. package/dist/core/usecases/delta/modified.js.map +1 -0
  1001. package/dist/core/usecases/delta/overlay.js +76 -0
  1002. package/dist/core/usecases/delta/overlay.js.map +1 -0
  1003. package/dist/core/usecases/fleet.js +296 -0
  1004. package/dist/core/usecases/fleet.js.map +1 -0
  1005. package/dist/core/usecases/grade/capability-tag.js +93 -0
  1006. package/dist/core/usecases/grade/capability-tag.js.map +1 -0
  1007. package/dist/core/usecases/grade/feature.js +88 -0
  1008. package/dist/core/usecases/grade/feature.js.map +1 -0
  1009. package/dist/core/usecases/grade/requirement-tag.js +94 -0
  1010. package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
  1011. package/dist/core/usecases/grade/steps.js +266 -0
  1012. package/dist/core/usecases/grade/steps.js.map +1 -0
  1013. package/dist/core/usecases/grade/usecases.js +112 -0
  1014. package/dist/core/usecases/grade/usecases.js.map +1 -0
  1015. package/dist/core/usecases/operations.js +123 -0
  1016. package/dist/core/usecases/operations.js.map +1 -0
  1017. package/dist/core/usecases/place.js +44 -0
  1018. package/dist/core/usecases/place.js.map +1 -0
  1019. package/dist/core/usecases/service/exclude.js +119 -0
  1020. package/dist/core/usecases/service/exclude.js.map +1 -0
  1021. package/dist/core/usecases/service/flows.js +164 -0
  1022. package/dist/core/usecases/service/flows.js.map +1 -0
  1023. package/dist/core/usecases/suites/participants.js +83 -0
  1024. package/dist/core/usecases/suites/participants.js.map +1 -0
  1025. package/dist/core/usecases/suites/suites.js +45 -0
  1026. package/dist/core/usecases/suites/suites.js.map +1 -0
  1027. package/dist/core/usecases/touch.js +101 -0
  1028. package/dist/core/usecases/touch.js.map +1 -0
  1029. package/dist/core/verify/answers.js +180 -0
  1030. package/dist/core/verify/answers.js.map +1 -0
  1031. package/dist/core/verify/build.js +170 -0
  1032. package/dist/core/verify/build.js.map +1 -0
  1033. package/dist/core/verify/checklist.js +259 -0
  1034. package/dist/core/verify/checklist.js.map +1 -0
  1035. package/dist/core/verify/claims/identity.js +61 -0
  1036. package/dist/core/verify/claims/identity.js.map +1 -0
  1037. package/dist/core/verify/cross/diff.js +97 -0
  1038. package/dist/core/verify/cross/diff.js.map +1 -0
  1039. package/dist/core/verify/evidence/contract.js +169 -0
  1040. package/dist/core/verify/evidence/contract.js.map +1 -0
  1041. package/dist/core/verify/location.js +67 -0
  1042. package/dist/core/verify/location.js.map +1 -0
  1043. package/dist/core/verify/pins/lint.js +158 -0
  1044. package/dist/core/verify/pins/lint.js.map +1 -0
  1045. package/dist/core/verify/pins/pin.js +95 -0
  1046. package/dist/core/verify/pins/pin.js.map +1 -0
  1047. package/dist/core/verify/read/file.js +224 -0
  1048. package/dist/core/verify/read/file.js.map +1 -0
  1049. package/dist/core/verify/read/shape.js +169 -0
  1050. package/dist/core/verify/read/shape.js.map +1 -0
  1051. package/dist/core/verify/read/union.js +105 -0
  1052. package/dist/core/verify/read/union.js.map +1 -0
  1053. package/dist/core/verify/record.js +165 -0
  1054. package/dist/core/verify/record.js.map +1 -0
  1055. package/dist/core/verify/store/commit.js +99 -0
  1056. package/dist/core/verify/store/commit.js.map +1 -0
  1057. package/dist/core/verify/store/render.js +61 -0
  1058. package/dist/core/verify/store/render.js.map +1 -0
  1059. package/dist/core/vocabulary/health.js +91 -0
  1060. package/dist/core/vocabulary/health.js.map +1 -0
  1061. package/dist/core/vocabulary/issue.js +59 -0
  1062. package/dist/core/vocabulary/issue.js.map +1 -0
  1063. package/dist/core/vocabulary/maturity.js +216 -0
  1064. package/dist/core/vocabulary/maturity.js.map +1 -0
  1065. package/dist/core/vocabulary/report.js +90 -0
  1066. package/dist/core/vocabulary/report.js.map +1 -0
  1067. package/dist/core/vocabulary/steps.js +266 -0
  1068. package/dist/core/vocabulary/steps.js.map +1 -0
  1069. package/dist/core/workspace/discover.js +239 -0
  1070. package/dist/core/workspace/discover.js.map +1 -0
  1071. package/dist/core/workspace/render.js +52 -0
  1072. package/dist/core/workspace/render.js.map +1 -0
  1073. package/examples/README.md +232 -0
  1074. package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
  1075. package/examples/docs/architecture/capabilities.yaml +31 -0
  1076. package/examples/docs/architecture/landscape.likec4 +336 -0
  1077. package/examples/docs/architecture/obligations.yaml +23 -0
  1078. package/examples/docs/architecture/permissions.yaml +50 -0
  1079. package/examples/docs/architecture/services.likec4 +70 -0
  1080. package/examples/docs/architecture/subsystems.likec4 +18 -0
  1081. package/examples/docs/architecture/usecases/order-notification.likec4 +74 -0
  1082. package/examples/docs/capabilities/checkout/spec.md +55 -0
  1083. package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
  1084. package/examples/docs/capabilities/order-notifications/spec.md +43 -0
  1085. package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
  1086. package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
  1087. package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
  1088. package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
  1089. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +132 -0
  1090. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
  1091. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
  1092. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
  1093. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
  1094. package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
  1095. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +31 -0
  1096. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
  1097. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
  1098. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
  1099. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
  1100. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
  1101. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
  1102. package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
  1103. package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
  1104. package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
  1105. package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
  1106. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +73 -0
  1107. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
  1108. package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
  1109. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
  1110. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
  1111. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
  1112. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
  1113. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
  1114. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
  1115. package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
  1116. package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
  1117. package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
  1118. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
  1119. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
  1120. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
  1121. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
  1122. package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
  1123. package/examples/docs/glossary/order.md +15 -0
  1124. package/examples/docs/glossary/payments/authorization.md +14 -0
  1125. package/examples/docs/likec4.config.json +8 -0
  1126. package/examples/docs/loam.json +3 -0
  1127. package/examples/docs/services/checkout-web/model.likec4 +92 -0
  1128. package/examples/docs/services/checkout-web/spec.md +91 -0
  1129. package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
  1130. package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
  1131. package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
  1132. package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
  1133. package/examples/docs/services/order-service/arch.spec.md +134 -0
  1134. package/examples/docs/services/order-service/asyncapi.yaml +109 -0
  1135. package/examples/docs/services/order-service/health.yaml +57 -0
  1136. package/examples/docs/services/order-service/model.likec4 +126 -0
  1137. package/examples/docs/services/order-service/openapi.yaml +257 -0
  1138. package/examples/docs/services/order-service/runbook.md +62 -0
  1139. package/examples/docs/services/order-service/spec.md +134 -0
  1140. package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
  1141. package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
  1142. package/examples/docs/services/payment-service/arch.spec.md +76 -0
  1143. package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
  1144. package/examples/docs/services/payment-service/health.yaml +39 -0
  1145. package/examples/docs/services/payment-service/model.likec4 +68 -0
  1146. package/examples/docs/services/payment-service/openapi.yaml +151 -0
  1147. package/examples/docs/services/payment-service/runbook.md +37 -0
  1148. package/examples/docs/services/payment-service/spec.md +88 -0
  1149. package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
  1150. package/examples/docs/services/platform/identity-service/arch.spec.md +120 -0
  1151. package/examples/docs/services/platform/identity-service/health.yaml +70 -0
  1152. package/examples/docs/services/platform/identity-service/model.likec4 +81 -0
  1153. package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
  1154. package/examples/docs/services/platform/identity-service/runbook.md +95 -0
  1155. package/examples/docs/services/platform/identity-service/spec.md +119 -0
  1156. package/examples/docs/services/platform/notification-service/asyncapi.yaml +200 -0
  1157. package/examples/docs/services/platform/notification-service/health.yaml +40 -0
  1158. package/examples/docs/services/platform/notification-service/model.likec4 +97 -0
  1159. package/examples/docs/services/platform/notification-service/runbook.md +48 -0
  1160. package/examples/docs/services/platform/notification-service/spec.md +112 -0
  1161. package/examples/docs/services/platform/subsystem.yaml +2 -0
  1162. package/examples/fleet.yaml +57 -0
  1163. package/package.json +19 -6
  1164. package/dist/commands/adopt.js +0 -240
  1165. package/dist/commands/adopt.js.map +0 -1
  1166. package/dist/commands/archive.js +0 -1313
  1167. package/dist/commands/archive.js.map +0 -1
  1168. package/dist/commands/delta.js +0 -388
  1169. package/dist/commands/delta.js.map +0 -1
  1170. package/dist/commands/docs-repo-gate.js.map +0 -1
  1171. package/dist/commands/format.js +0 -17
  1172. package/dist/commands/format.js.map +0 -1
  1173. package/dist/commands/gherkin.js +0 -309
  1174. package/dist/commands/gherkin.js.map +0 -1
  1175. package/dist/commands/init.js.map +0 -1
  1176. package/dist/commands/list.js +0 -295
  1177. package/dist/commands/list.js.map +0 -1
  1178. package/dist/commands/migrate-openspec.js +0 -950
  1179. package/dist/commands/migrate-openspec.js.map +0 -1
  1180. package/dist/commands/new.js +0 -413
  1181. package/dist/commands/new.js.map +0 -1
  1182. package/dist/commands/rebase.js +0 -356
  1183. package/dist/commands/rebase.js.map +0 -1
  1184. package/dist/commands/show.js +0 -320
  1185. package/dist/commands/show.js.map +0 -1
  1186. package/dist/commands/status.js +0 -262
  1187. package/dist/commands/status.js.map +0 -1
  1188. package/dist/commands/unarchive.js +0 -317
  1189. package/dist/commands/unarchive.js.map +0 -1
  1190. package/dist/commands/validate.js +0 -1773
  1191. package/dist/commands/validate.js.map +0 -1
  1192. package/dist/commands/verify.js +0 -775
  1193. package/dist/commands/verify.js.map +0 -1
  1194. package/dist/commands/vouch.js +0 -421
  1195. package/dist/commands/vouch.js.map +0 -1
  1196. package/dist/core/agent.js +0 -2173
  1197. package/dist/core/agent.js.map +0 -1
  1198. package/dist/core/agents-stamp.js.map +0 -1
  1199. package/dist/core/arch.js +0 -130
  1200. package/dist/core/arch.js.map +0 -1
  1201. package/dist/core/asyncapi.js +0 -253
  1202. package/dist/core/asyncapi.js.map +0 -1
  1203. package/dist/core/brief.js +0 -755
  1204. package/dist/core/brief.js.map +0 -1
  1205. package/dist/core/coherence.js +0 -577
  1206. package/dist/core/coherence.js.map +0 -1
  1207. package/dist/core/concurrency.js.map +0 -1
  1208. package/dist/core/config.js.map +0 -1
  1209. package/dist/core/delta.js +0 -476
  1210. package/dist/core/delta.js.map +0 -1
  1211. package/dist/core/dependencies.js +0 -356
  1212. package/dist/core/dependencies.js.map +0 -1
  1213. package/dist/core/doctor.js +0 -596
  1214. package/dist/core/doctor.js.map +0 -1
  1215. package/dist/core/document-bytes.js.map +0 -1
  1216. package/dist/core/explore.js +0 -225
  1217. package/dist/core/explore.js.map +0 -1
  1218. package/dist/core/frontmatter.js.map +0 -1
  1219. package/dist/core/gherkin.js +0 -463
  1220. package/dist/core/gherkin.js.map +0 -1
  1221. package/dist/core/health.js +0 -67
  1222. package/dist/core/health.js.map +0 -1
  1223. package/dist/core/ids.js +0 -122
  1224. package/dist/core/ids.js.map +0 -1
  1225. package/dist/core/issue.js +0 -9
  1226. package/dist/core/issue.js.map +0 -1
  1227. package/dist/core/json.js +0 -74
  1228. package/dist/core/json.js.map +0 -1
  1229. package/dist/core/likec4.js +0 -489
  1230. package/dist/core/likec4.js.map +0 -1
  1231. package/dist/core/maturity.js +0 -66
  1232. package/dist/core/maturity.js.map +0 -1
  1233. package/dist/core/openapi-merge.js +0 -552
  1234. package/dist/core/openapi-merge.js.map +0 -1
  1235. package/dist/core/openapi.js.map +0 -1
  1236. package/dist/core/openspec-inventory.js +0 -1440
  1237. package/dist/core/openspec-inventory.js.map +0 -1
  1238. package/dist/core/path-safety.js.map +0 -1
  1239. package/dist/core/provenance.js +0 -787
  1240. package/dist/core/provenance.js.map +0 -1
  1241. package/dist/core/records.js.map +0 -1
  1242. package/dist/core/repo.js +0 -438
  1243. package/dist/core/repo.js.map +0 -1
  1244. package/dist/core/report.js +0 -47
  1245. package/dist/core/report.js.map +0 -1
  1246. package/dist/core/spec.js +0 -505
  1247. package/dist/core/spec.js.map +0 -1
  1248. package/dist/core/staging.js +0 -890
  1249. package/dist/core/staging.js.map +0 -1
  1250. package/dist/core/status.js +0 -1132
  1251. package/dist/core/status.js.map +0 -1
  1252. package/dist/core/steps.js +0 -72
  1253. package/dist/core/steps.js.map +0 -1
  1254. package/dist/core/verify.js +0 -807
  1255. package/dist/core/verify.js.map +0 -1
  1256. package/dist/core/version.js.map +0 -1
@@ -0,0 +1,196 @@
1
+ /**
2
+ * The undo snapshot: the bytes `archive` is about to overwrite, parked inside
3
+ * the feature directory so they travel with it into `features/archive/`.
4
+ *
5
+ * Separate from the commit because the merge is not invertible — a MODIFIED
6
+ * requirement's previous text appears nowhere in the delta, and a landscape
7
+ * rewritten by hand since cannot be un-rewritten by re-reading one. `unarchive`
8
+ * puts bytes back; it does not recompute them. Nothing on the commit path reads
9
+ * this module: only `archive` writes a snapshot and only `unarchive` honours one.
10
+ */
11
+ import { existsSync } from "node:fs";
12
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
13
+ import { dirname, join } from "node:path";
14
+ import { repoPath } from "../../envelope/json.js";
15
+ import { acrossRename, resolvePortableFileInsideLexically, UnsafePathError } from "../../kernel/path-safety.js";
16
+ // Every `isRecord` below guards the manifest loam itself wrote, read back inside
17
+ // a REFUSAL path — `../lock.ts` records why that is where a thrown `TypeError`
18
+ // costs the most.
19
+ import { isRecord } from "../../kernel/records.js";
20
+ import { isDigest, sha256 } from "../writes.js";
21
+ import { message, quietRm } from "../commit.js";
22
+ import { isReadableSnapshotVersion, preImageRel, SNAPSHOT_DIR, SNAPSHOT_MANIFEST, SNAPSHOT_VERSION, snapshotDir, } from "./manifest.js";
23
+ /** A snapshot directory that must not be replaced, because it is the only copy of the true pre-images. */
24
+ export class SnapshotClobberError extends Error {
25
+ dir;
26
+ name = "SnapshotClobberError";
27
+ constructor(dir, detail) {
28
+ super(`${dir} already holds an undo snapshot that does NOT match the living docs (${detail}) — a previous archive of ` +
29
+ `this feature wrote to the living docs and never took them back, and these are the only copies of what they ` +
30
+ `said before. Overwriting them would make that merge permanent and unreportable. Run \`loam archive\` again ` +
31
+ `after restoring the living docs from version control, or delete ${SNAPSHOT_DIR}/ if you know the merge never landed.`);
32
+ this.dir = dir;
33
+ }
34
+ }
35
+ export async function writeSnapshot(request) {
36
+ const { featureDir, docsDir, staged } = request;
37
+ const { featureId, dirName } = request.feature;
38
+ const dir = snapshotDir(featureDir);
39
+ await assertSnapshotReplaceable(request);
40
+ // A leftover from a rolled-back run would describe a merge that never happened.
41
+ await quietRm(dir);
42
+ const files = [];
43
+ for (const s of staged) {
44
+ // The feature's own documents by where the move leaves them; everything
45
+ // else by where it is written (`SnapshotRequest.movesTo` says why).
46
+ const rel = repoPath(docsDir, acrossRename(s.write.path, featureDir, request.movesTo));
47
+ // services/ entries are re-keyed by (service, artifact) so they survive the
48
+ // service directory moving between archive and unarchive; the literal path
49
+ // is still recorded — it keys the pre-image below, and it is the fallback.
50
+ const key = request.serviceKeyOf(rel);
51
+ files.push({
52
+ path: rel,
53
+ ...key,
54
+ existed: s.before !== null,
55
+ after: sha256(s.content ?? Buffer.alloc(0)),
56
+ before: s.before === null ? null : sha256(s.before),
57
+ });
58
+ if (s.before !== null) {
59
+ // Under the suffixed name (`./manifest.ts`, version 4): a pre-image kept
60
+ // under its living name is a second living document to every tool that
61
+ // reads by extension.
62
+ const dest = join(dir, "files", preImageRel(SNAPSHOT_VERSION, rel));
63
+ await mkdir(dirname(dest), { recursive: true });
64
+ await writeFile(dest, s.before);
65
+ }
66
+ }
67
+ const manifest = {
68
+ version: SNAPSHOT_VERSION,
69
+ feature: featureId,
70
+ dirName,
71
+ archivedAt: new Date().toISOString(),
72
+ ...(request.checklist === undefined ? {} : { checklist: request.checklist }),
73
+ files,
74
+ };
75
+ // Manifest last: its presence is what says the pre-images beside it are complete.
76
+ await mkdir(dir, { recursive: true });
77
+ await writeFile(join(dir, SNAPSHOT_MANIFEST), JSON.stringify(manifest, null, 2) + "\n", "utf8");
78
+ }
79
+ /**
80
+ * Refuse to replace a snapshot whose pre-images are still the only record of
81
+ * what the living docs said.
82
+ *
83
+ * A snapshot in an ACTIVE feature directory means a previous archive of this
84
+ * same feature wrote it — and since a clean rollback deletes it, finding one
85
+ * means that run either died mid-commit or reported `rollback-incomplete`. If
86
+ * its pre-images still match the living docs, nothing of that run landed and
87
+ * the snapshot is stale bookkeeping. If they do not, the living docs carry
88
+ * that run's writes, these files are the only way back, and the unconditional
89
+ * clear turned a repairable half-merge into a permanent one — after which
90
+ * `unarchive` restored the half-merged text and said the docs were back.
91
+ *
92
+ * Version 2 and 3 manifests are graded by the same staleness question — version
93
+ * 2 over its literal paths — because refusing them as "a different loam" would
94
+ * turn every leftover snapshot an older loam wrote into a permanent archive
95
+ * refusal on upgrade. The question reads only the manifest and the living docs,
96
+ * never a pre-image, so where each layout stores its pre-images does not enter
97
+ * into it.
98
+ */
99
+ async function assertSnapshotReplaceable(request) {
100
+ const { docsDir, serviceDirOf } = request;
101
+ const dir = snapshotDir(request.featureDir);
102
+ const manifestPath = join(dir, SNAPSHOT_MANIFEST);
103
+ // The manifest is written LAST, so a `files/` tree without one is a snapshot
104
+ // whose run died before it could swap anything: it describes no merge.
105
+ if (!existsSync(manifestPath))
106
+ return;
107
+ let manifest;
108
+ try {
109
+ manifest = JSON.parse(await readFile(manifestPath, "utf8"));
110
+ }
111
+ catch {
112
+ throw new SnapshotClobberError(dir, `${SNAPSHOT_MANIFEST} cannot be read, so nothing can say whether it is stale`);
113
+ }
114
+ // The cast that used to stand here asserted the shape rather than checking
115
+ // it: well-formed JSON that is not an object answered `undefined` to every
116
+ // field below and slipped through as "a different loam", and a bare `null`
117
+ // threw a TypeError out of the refusal itself. Both left this directory —
118
+ // which may hold the only copies of what the living docs said — one step from
119
+ // being replaced, without the staleness question ever having been asked.
120
+ if (!isRecord(manifest)) {
121
+ throw new SnapshotClobberError(dir, `${SNAPSHOT_MANIFEST} cannot be read, so nothing can say whether it is stale`);
122
+ }
123
+ if (!isReadableSnapshotVersion(manifest.version) || !Array.isArray(manifest.files)) {
124
+ throw new SnapshotClobberError(dir, `it was written by a different loam and cannot be checked against the living docs`);
125
+ }
126
+ // `Array.isArray` narrows to `any[]`; naming the element type here is what
127
+ // keeps that `any` from spreading past the one row being read.
128
+ const rows = manifest.files;
129
+ const landed = [];
130
+ for (const row of rows) {
131
+ const entry = readSnapshotClaim(row);
132
+ if (entry === null) {
133
+ throw new SnapshotClobberError(dir, `${SNAPSHOT_MANIFEST} is malformed, so nothing can say whether it is stale`);
134
+ }
135
+ let target;
136
+ try {
137
+ // These paths are data, and they are the ones this function goes and
138
+ // reads. They name living documents, so containment is lexical: a docs
139
+ // repo may legitimately mount a service directory through a symlink, and
140
+ // `unarchive` resolves the same field the same way before it writes back.
141
+ target = resolvePortableFileInsideLexically(docsDir, entry.path, `snapshot path '${entry.path}'`);
142
+ // A re-keyed entry's CURRENT location is wherever the enumeration says
143
+ // its service lives today — grading staleness at the old address would
144
+ // call a merely-moved file a landed merge. The literal path was still
145
+ // resolved above on purpose: its containment is part of what "loam wrote
146
+ // this manifest" means, and it stays the pre-image's key. An id the
147
+ // enumeration no longer answers falls back to that literal path.
148
+ if (entry.service !== undefined && entry.artifact !== undefined) {
149
+ const current = serviceDirOf(entry.service);
150
+ if (current !== null) {
151
+ target = resolvePortableFileInsideLexically(docsDir, `${current}/${entry.artifact}`, `snapshot path '${entry.path}'`);
152
+ }
153
+ }
154
+ }
155
+ catch (err) {
156
+ if (!(err instanceof UnsafePathError))
157
+ throw err;
158
+ throw new SnapshotClobberError(dir, `${message(err)}, so nothing can say whether it is stale`);
159
+ }
160
+ // A row of the feature's own documents names where the move WOULD have
161
+ // left it, and a snapshot found here is one whose feature never moved: the
162
+ // bytes to grade are still under this feature directory. Graded at the
163
+ // literal path instead, every such row read as absent — a landed merge —
164
+ // and an archive that a crash or a rollback interrupted could never be
165
+ // retried over its own leftover. Named where it was graded, too: the
166
+ // refusal sends a person to restore the file from version control, and
167
+ // the archived path names a directory that does not exist yet.
168
+ const graded = acrossRename(target, request.movesTo, request.featureDir);
169
+ const now = existsSync(graded) ? sha256(await readFile(graded)) : null;
170
+ // `existed: false` means archive created the file, so "nothing landed"
171
+ // means it is still absent.
172
+ const untouched = entry.existed ? now === entry.before : now === null;
173
+ if (!untouched)
174
+ landed.push(graded === target ? entry.path : repoPath(docsDir, graded));
175
+ }
176
+ if (landed.length > 0)
177
+ throw new SnapshotClobberError(dir, `${landed.length} file(s) already carry that merge: ${landed.join(", ")}`);
178
+ }
179
+ function readSnapshotClaim(value) {
180
+ if (!isRecord(value))
181
+ return null;
182
+ const { path, existed, before, service, artifact } = value;
183
+ if (typeof path !== "string" || typeof existed !== "boolean")
184
+ return null;
185
+ let key;
186
+ if (service === undefined && artifact === undefined)
187
+ key = {};
188
+ else if (typeof service === "string" && typeof artifact === "string")
189
+ key = { service, artifact };
190
+ else
191
+ return null;
192
+ if (existed)
193
+ return isDigest(before) ? { path, existed, before, ...key } : null;
194
+ return before === null ? { path, existed, before, ...key } : null;
195
+ }
196
+ //# sourceMappingURL=snapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../../../src/core/staging/snapshot/snapshot.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The undo snapshot: the bytes `archive` is about to overwrite, parked inside\n * the feature directory so they travel with it into `features/archive/`.\n *\n * Separate from the commit because the merge is not invertible — a MODIFIED\n * requirement's previous text appears nowhere in the delta, and a landscape\n * rewritten by hand since cannot be un-rewritten by re-reading one. `unarchive`\n * puts bytes back; it does not recompute them. Nothing on the commit path reads\n * this module: only `archive` writes a snapshot and only `unarchive` honours one.\n */\nimport { existsSync } from \"node:fs\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { repoPath } from \"../../envelope/json.js\";\nimport { acrossRename, resolvePortableFileInsideLexically, UnsafePathError } from \"../../kernel/path-safety.js\";\n// Every `isRecord` below guards the manifest loam itself wrote, read back inside\n// a REFUSAL path — `../lock.ts` records why that is where a thrown `TypeError`\n// costs the most.\nimport { isRecord } from \"../../kernel/records.js\";\nimport { isDigest, sha256, type StagedWrite } from \"../writes.js\";\nimport { message, quietRm } from \"../commit.js\";\nimport {\n isReadableSnapshotVersion,\n preImageRel,\n SNAPSHOT_DIR,\n SNAPSHOT_MANIFEST,\n SNAPSHOT_VERSION,\n snapshotDir,\n type SnapshotEntry,\n type SnapshotManifest,\n} from \"./manifest.js\";\n\n/** A snapshot directory that must not be replaced, because it is the only copy of the true pre-images. */\nexport class SnapshotClobberError extends Error {\n override readonly name = \"SnapshotClobberError\";\n\n constructor(readonly dir: string, detail: string) {\n super(\n `${dir} already holds an undo snapshot that does NOT match the living docs (${detail}) — a previous archive of ` +\n `this feature wrote to the living docs and never took them back, and these are the only copies of what they ` +\n `said before. Overwriting them would make that merge permanent and unreportable. Run \\`loam archive\\` again ` +\n `after restoring the living docs from version control, or delete ${SNAPSHOT_DIR}/ if you know the merge never landed.`,\n );\n }\n}\n\n/** The (service, artifact) key a `services/` path decomposes into. */\nexport interface ServiceKey {\n service: string;\n artifact: string;\n}\n\n/**\n * Everything `writeSnapshot` needs, as one object. The resolvers are INJECTED\n * by the caller — `archive` builds them from its view of the tree — because\n * how a path decomposes into a service identity is repo-layout knowledge, and\n * this module's subject is parking and checking bytes.\n */\nexport interface SnapshotRequest {\n featureDir: string;\n /**\n * Where the archive moves `featureDir` — the directory this snapshot is\n * about to travel into. A staged write INSIDE `featureDir` is one of the\n * feature's own documents (the archive re-expresses their links for the\n * move), and it is recorded at its image under here: that is where its bytes\n * sit whenever an undo can be asked for, so `unarchive` restores it by its\n * literal path before moving the directory back, exactly as it restores\n * every other row. The other reader, the clobber guard below, meets a\n * snapshot BEFORE its feature moved and grades such a row at its image back\n * under `featureDir`.\n */\n movesTo: string;\n docsDir: string;\n feature: { featureId: string; dirName: string };\n staged: StagedWrite[];\n /** Docs-relative path → the service it belongs to; null for landscape/features paths. */\n serviceKeyOf: (rel: string) => ServiceKey | null;\n /** Service id → its CURRENT docs-relative directory; null when not enumerated. */\n serviceDirOf: (service: string) => string | null;\n /**\n * Recorded verbatim as the manifest's `checklist` when given. Opaque here —\n * this module parks bytes and never learns what a checklist is; `archive`\n * derives it and `gate --since` reads it back (`./manifest.ts`).\n */\n checklist?: string;\n}\n\nexport async function writeSnapshot(request: SnapshotRequest): Promise<void> {\n const { featureDir, docsDir, staged } = request;\n const { featureId, dirName } = request.feature;\n const dir = snapshotDir(featureDir);\n await assertSnapshotReplaceable(request);\n // A leftover from a rolled-back run would describe a merge that never happened.\n await quietRm(dir);\n\n const files: SnapshotEntry[] = [];\n for (const s of staged) {\n // The feature's own documents by where the move leaves them; everything\n // else by where it is written (`SnapshotRequest.movesTo` says why).\n const rel = repoPath(docsDir, acrossRename(s.write.path, featureDir, request.movesTo));\n // services/ entries are re-keyed by (service, artifact) so they survive the\n // service directory moving between archive and unarchive; the literal path\n // is still recorded — it keys the pre-image below, and it is the fallback.\n const key = request.serviceKeyOf(rel);\n files.push({\n path: rel,\n ...key,\n existed: s.before !== null,\n after: sha256(s.content ?? Buffer.alloc(0)),\n before: s.before === null ? null : sha256(s.before),\n });\n if (s.before !== null) {\n // Under the suffixed name (`./manifest.ts`, version 4): a pre-image kept\n // under its living name is a second living document to every tool that\n // reads by extension.\n const dest = join(dir, \"files\", preImageRel(SNAPSHOT_VERSION, rel));\n await mkdir(dirname(dest), { recursive: true });\n await writeFile(dest, s.before);\n }\n }\n\n const manifest: SnapshotManifest = {\n version: SNAPSHOT_VERSION,\n feature: featureId,\n dirName,\n archivedAt: new Date().toISOString(),\n ...(request.checklist === undefined ? {} : { checklist: request.checklist }),\n files,\n };\n // Manifest last: its presence is what says the pre-images beside it are complete.\n await mkdir(dir, { recursive: true });\n await writeFile(join(dir, SNAPSHOT_MANIFEST), JSON.stringify(manifest, null, 2) + \"\\n\", \"utf8\");\n}\n\n/**\n * Refuse to replace a snapshot whose pre-images are still the only record of\n * what the living docs said.\n *\n * A snapshot in an ACTIVE feature directory means a previous archive of this\n * same feature wrote it — and since a clean rollback deletes it, finding one\n * means that run either died mid-commit or reported `rollback-incomplete`. If\n * its pre-images still match the living docs, nothing of that run landed and\n * the snapshot is stale bookkeeping. If they do not, the living docs carry\n * that run's writes, these files are the only way back, and the unconditional\n * clear turned a repairable half-merge into a permanent one — after which\n * `unarchive` restored the half-merged text and said the docs were back.\n *\n * Version 2 and 3 manifests are graded by the same staleness question — version\n * 2 over its literal paths — because refusing them as \"a different loam\" would\n * turn every leftover snapshot an older loam wrote into a permanent archive\n * refusal on upgrade. The question reads only the manifest and the living docs,\n * never a pre-image, so where each layout stores its pre-images does not enter\n * into it.\n */\nasync function assertSnapshotReplaceable(request: SnapshotRequest): Promise<void> {\n const { docsDir, serviceDirOf } = request;\n const dir = snapshotDir(request.featureDir);\n const manifestPath = join(dir, SNAPSHOT_MANIFEST);\n // The manifest is written LAST, so a `files/` tree without one is a snapshot\n // whose run died before it could swap anything: it describes no merge.\n if (!existsSync(manifestPath)) return;\n let manifest: unknown;\n try {\n manifest = JSON.parse(await readFile(manifestPath, \"utf8\"));\n } catch {\n throw new SnapshotClobberError(dir, `${SNAPSHOT_MANIFEST} cannot be read, so nothing can say whether it is stale`);\n }\n // The cast that used to stand here asserted the shape rather than checking\n // it: well-formed JSON that is not an object answered `undefined` to every\n // field below and slipped through as \"a different loam\", and a bare `null`\n // threw a TypeError out of the refusal itself. Both left this directory —\n // which may hold the only copies of what the living docs said — one step from\n // being replaced, without the staleness question ever having been asked.\n if (!isRecord(manifest)) {\n throw new SnapshotClobberError(dir, `${SNAPSHOT_MANIFEST} cannot be read, so nothing can say whether it is stale`);\n }\n if (!isReadableSnapshotVersion(manifest.version) || !Array.isArray(manifest.files)) {\n throw new SnapshotClobberError(dir, `it was written by a different loam and cannot be checked against the living docs`);\n }\n // `Array.isArray` narrows to `any[]`; naming the element type here is what\n // keeps that `any` from spreading past the one row being read.\n const rows: unknown[] = manifest.files;\n const landed: string[] = [];\n for (const row of rows) {\n const entry = readSnapshotClaim(row);\n if (entry === null) {\n throw new SnapshotClobberError(dir, `${SNAPSHOT_MANIFEST} is malformed, so nothing can say whether it is stale`);\n }\n let target: string;\n try {\n // These paths are data, and they are the ones this function goes and\n // reads. They name living documents, so containment is lexical: a docs\n // repo may legitimately mount a service directory through a symlink, and\n // `unarchive` resolves the same field the same way before it writes back.\n target = resolvePortableFileInsideLexically(docsDir, entry.path, `snapshot path '${entry.path}'`);\n // A re-keyed entry's CURRENT location is wherever the enumeration says\n // its service lives today — grading staleness at the old address would\n // call a merely-moved file a landed merge. The literal path was still\n // resolved above on purpose: its containment is part of what \"loam wrote\n // this manifest\" means, and it stays the pre-image's key. An id the\n // enumeration no longer answers falls back to that literal path.\n if (entry.service !== undefined && entry.artifact !== undefined) {\n const current = serviceDirOf(entry.service);\n if (current !== null) {\n target = resolvePortableFileInsideLexically(\n docsDir,\n `${current}/${entry.artifact}`,\n `snapshot path '${entry.path}'`,\n );\n }\n }\n } catch (err) {\n if (!(err instanceof UnsafePathError)) throw err;\n throw new SnapshotClobberError(dir, `${message(err)}, so nothing can say whether it is stale`);\n }\n // A row of the feature's own documents names where the move WOULD have\n // left it, and a snapshot found here is one whose feature never moved: the\n // bytes to grade are still under this feature directory. Graded at the\n // literal path instead, every such row read as absent — a landed merge —\n // and an archive that a crash or a rollback interrupted could never be\n // retried over its own leftover. Named where it was graded, too: the\n // refusal sends a person to restore the file from version control, and\n // the archived path names a directory that does not exist yet.\n const graded = acrossRename(target, request.movesTo, request.featureDir);\n const now = existsSync(graded) ? sha256(await readFile(graded)) : null;\n // `existed: false` means archive created the file, so \"nothing landed\"\n // means it is still absent.\n const untouched = entry.existed ? now === entry.before : now === null;\n if (!untouched) landed.push(graded === target ? entry.path : repoPath(docsDir, graded));\n }\n if (landed.length > 0) throw new SnapshotClobberError(dir, `${landed.length} file(s) already carry that merge: ${landed.join(\", \")}`);\n}\n\n/**\n * One manifest row read back as the record `writeSnapshot` wrote, or null when\n * the file does not actually say.\n *\n * Every field the staleness question consumes is checked, because an unchecked\n * field does not read as missing here — it reads as an ANSWER. An `existed`\n * that is not a boolean is falsy, and a `false` claim is graded by asking\n * whether the file is ABSENT: a manifest recording a pre-image for a deleted\n * file therefore graded as stale bookkeeping and was cleared, taking the only\n * copy of that pre-image with it. A `before` of the wrong type equals no digest\n * at all, so the opposite row accused an untouched file of carrying a merge.\n *\n * `unarchive` asks a stricter version of the same question of the same file\n * (`after`, `archivedAt`, the feature name, no duplicates) because it is about\n * to write THROUGH it; that check stays where it is, with its own refusals.\n *\n * `service`/`artifact` are read when present (a keyed `services/` row) and\n * must arrive as a pair: half a key is a manifest that does not say where the\n * file lives, and grading through the literal path anyway would be a guess.\n */\ninterface SnapshotClaim {\n path: string;\n existed: boolean;\n before: string | null;\n service?: string;\n artifact?: string;\n}\n\nfunction readSnapshotClaim(value: unknown): SnapshotClaim | null {\n if (!isRecord(value)) return null;\n const { path, existed, before, service, artifact } = value;\n if (typeof path !== \"string\" || typeof existed !== \"boolean\") return null;\n let key: { service: string; artifact: string } | Record<string, never>;\n if (service === undefined && artifact === undefined) key = {};\n else if (typeof service === \"string\" && typeof artifact === \"string\") key = { service, artifact };\n else return null;\n if (existed) return isDigest(before) ? { path, existed, before, ...key } : null;\n return before === null ? { path, existed, before, ...key } : null;\n}\n\n"]}
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Roll a version-2 or version-3 journal FORWARD: finish the renames a killed
3
+ * commit did not reach, from the staged temps that are already durable on
4
+ * disk (and, for a version-3 record, the directory renames after them).
5
+ *
6
+ * Forward and not undo, because that is what is actually durable: the
7
+ * after-bytes were written and (per stageWrites) fully on disk before the
8
+ * journal existed, while the OVERWRITTEN files' pre-images are durable
9
+ * nowhere — the rename that replaced them destroyed the only copy. The plan
10
+ * was validated completely before the first rename, so the post-state is
11
+ * exactly what a successful run would have produced; unarchive's
12
+ * finish-never-undo recovery is the precedent.
13
+ *
14
+ * The refuse-to-choose doctrine is rollbackStaged's, verbatim: a file whose
15
+ * bytes are neither the before- nor the after-digest was edited by somebody
16
+ * since the crash, and completing over it would destroy one of two truths.
17
+ */
18
+ import { existsSync, readFileSync } from "node:fs";
19
+ import { readFile, rename, rm } from "node:fs/promises";
20
+ import { dirname, join } from "node:path";
21
+ import { quietRmdir } from "../commit.js";
22
+ import { sha256 } from "../writes.js";
23
+ import { COMMIT_INTENT, InterruptedCommitError, sweepPidTemps } from "../interrupted.js";
24
+ import { clearTxnIntent } from "./journal.js";
25
+ export async function recoverForward(root, intent) {
26
+ const abs = (rel) => join(root, ...rel.split("/"));
27
+ // A directory rename is classified by pure existence — a rename moves the
28
+ // inode, so "which end exists" is exactly as discriminating as a digest is
29
+ // for a file. Both ends existing (or neither) is a state no step of the
30
+ // commit produces: somebody made a directory since the crash, and choosing
31
+ // which tree to keep is the refuse-to-choose case below.
32
+ const movesDone = [];
33
+ const movesPending = [];
34
+ const unknownMoves = [];
35
+ for (const m of intent.moves) {
36
+ const fromExists = existsSync(abs(m.from));
37
+ const toExists = existsSync(abs(m.to));
38
+ if (toExists && !fromExists)
39
+ movesDone.push(m);
40
+ else if (fromExists && !toExists)
41
+ movesPending.push(m);
42
+ else
43
+ unknownMoves.push(`${m.from} -> ${m.to}`);
44
+ }
45
+ // Where a recorded file IS now. The journal records a file at the path its
46
+ // swap wrote — and a `subsystem move` swaps the documents whose links it
47
+ // rewrites INSIDE a directory it then renames, so once that rename has
48
+ // landed the file is under the moved directory, not at the recorded path.
49
+ // Looked for there, it would read as absent: "edited since", a refusal over
50
+ // a commit that only needed its journal cleared. Classified moves first for
51
+ // exactly this; a file under a move still pending is where it was recorded.
52
+ const target = (f) => {
53
+ const landed = movesDone.find((m) => f.path.startsWith(`${m.from}/`));
54
+ return landed === undefined ? abs(f.path) : abs(`${landed.to}${f.path.slice(landed.from.length)}`);
55
+ };
56
+ const done = [];
57
+ const pending = [];
58
+ const unknown = [];
59
+ for (const f of intent.files) {
60
+ const file = target(f);
61
+ const now = existsSync(file) ? sha256(await readFile(file)) : null;
62
+ if (now === f.after)
63
+ done.push(f);
64
+ else if (now === f.before)
65
+ pending.push(f);
66
+ else
67
+ unknown.push(f.path);
68
+ }
69
+ if (unknown.length > 0 || unknownMoves.length > 0) {
70
+ // Two vocabularies on purpose: a FILE in neither recorded state holds
71
+ // edited bytes, while a DIRECTORY rename in neither state means both ends
72
+ // exist (or neither does) — nothing was "edited", somebody made or
73
+ // restored a directory since the crash. One sentence covering both told
74
+ // the operator to hunt for edited bytes in a directory pair. And the
75
+ // interrupted work is named by what it WAS (`command` + `target`), not by
76
+ // `intent.rerun` — that string is the REPAIR spelling (`loam subsystem
77
+ // sync` repairs all four subsystem verbs), so "a `loam subsystem sync`
78
+ // was interrupted" named a command nobody ran.
79
+ const parts = [
80
+ ...(unknown.length > 0
81
+ ? [
82
+ `${unknown.length} of the file(s) it was writing now hold bytes that are neither what it found ` +
83
+ `nor what it wrote — somebody edited them since: ${unknown.join(", ")}`,
84
+ ]
85
+ : []),
86
+ ...(unknownMoves.length > 0
87
+ ? [
88
+ `the directory rename(s) ${unknownMoves.join(", ")} are in neither recorded state — both ends ` +
89
+ `exist, or neither does, so loam cannot tell which tree to keep`,
90
+ ]
91
+ : []),
92
+ ];
93
+ throw new InterruptedCommitError(`a \`${intent.command}\` commit for '${intent.target}' was interrupted mid-commit ` +
94
+ `(pid ${intent.pid} on ${intent.host}, ${intent.at}) and ${parts.join("; ")}. ` +
95
+ `loam will not choose between those two truths. ` +
96
+ `Reconcile them against version control, delete ${COMMIT_INTENT}, then re-run \`${intent.rerun}\`.`);
97
+ }
98
+ // Whether forward is still POSSIBLE is decided before a single byte moves:
99
+ // completing some files and then refusing on a missing temp would leave the
100
+ // repo in a state neither the old nor the new tree ever had.
101
+ const missing = pending.filter((f) => f.after !== null && !usableTemp(root, f));
102
+ if (missing.length > 0) {
103
+ // A file whose before and after digests are EQUAL — an unchanged file
104
+ // carried through a commit — reads as "done" in whichever state it is in,
105
+ // so it is not evidence that any rename landed. Only a file that could
106
+ // tell the two states apart counts as one — and a landed directory MOVE
107
+ // always can, so it counts too.
108
+ const evidence = done.filter((f) => f.before !== f.after);
109
+ if (evidence.length === 0 && movesDone.length === 0) {
110
+ // Nothing ever swapped and the temps are gone — this is the tree a
111
+ // completed ROLLBACK leaves when the crash landed between the rollback
112
+ // and the journal's removal. The complete pre-state is on disk, which
113
+ // is one of the two states recovery promises; declaring it beats
114
+ // refusing over bytes that are already consistent.
115
+ await finish(root, intent);
116
+ return { command: intent.command, feature: intent.target, outcome: "consistent", repaired: [] };
117
+ }
118
+ throw new InterruptedCommitError(`a \`${intent.command}\` commit for '${intent.target}' was interrupted and ${missing.map((f) => f.path).join(", ")} ` +
119
+ `${missing.length === 1 ? "is" : "are"} still in the pre-commit state, but the staged bytes that would ` +
120
+ `finish ${missing.length === 1 ? "it" : "them"} are gone or no longer match the digest recorded before the ` +
121
+ `crash. Restore the tree from version control, delete ${COMMIT_INTENT}, then re-run \`${intent.rerun}\`.`);
122
+ }
123
+ const repaired = [];
124
+ for (const f of pending) {
125
+ const file = target(f);
126
+ if (f.after === null) {
127
+ await rm(file, { force: true });
128
+ await quietRmdir(dirname(file));
129
+ }
130
+ else {
131
+ await rename(join(root, ...f.tmp.split("/")), file);
132
+ }
133
+ repaired.push(f.path);
134
+ }
135
+ // Renames after the file swaps, in recorded order — the order the commit
136
+ // itself performs them. One that cannot land now (the destination's parent
137
+ // deleted since the crash, a Windows EPERM on an open handle) is a refusal
138
+ // naming the pair, not a half-finished loop: nothing before it is undone,
139
+ // and the journal stays for the next attempt.
140
+ for (const m of movesPending) {
141
+ try {
142
+ await rename(abs(m.from), abs(m.to));
143
+ }
144
+ catch (err) {
145
+ throw new InterruptedCommitError(`a \`${intent.command}\` commit for '${intent.target}' was interrupted and the rename ${m.from} -> ${m.to} ` +
146
+ `that would finish it cannot be performed now (${err instanceof Error ? err.message : String(err)}). ` +
147
+ `Complete or undo it by hand against version control, delete ${COMMIT_INTENT}, then re-run \`${intent.rerun}\`.`);
148
+ }
149
+ repaired.push(m.to);
150
+ }
151
+ await finish(root, intent);
152
+ return {
153
+ command: intent.command,
154
+ feature: intent.target,
155
+ outcome: repaired.length > 0 ? "repaired" : "completed",
156
+ repaired,
157
+ };
158
+ }
159
+ /** Is the recorded temp still there with exactly the bytes the journal promised? */
160
+ function usableTemp(root, f) {
161
+ if (f.tmp === null)
162
+ return false;
163
+ const path = join(root, ...f.tmp.split("/"));
164
+ try {
165
+ return sha256(readFileSync(path)) === f.after;
166
+ }
167
+ catch {
168
+ return false;
169
+ }
170
+ }
171
+ async function finish(root, intent) {
172
+ const dirs = intent.files.flatMap((f) => {
173
+ const out = [dirname(join(root, ...f.path.split("/")))];
174
+ if (f.tmp !== null)
175
+ out.push(dirname(join(root, ...f.tmp.split("/"))));
176
+ return out;
177
+ });
178
+ await sweepPidTemps(dirs, intent.pid);
179
+ await clearTxnIntent(root);
180
+ }
181
+ //# sourceMappingURL=forward.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forward.js","sourceRoot":"","sources":["../../../../src/core/staging/txn/forward.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Roll a version-2 or version-3 journal FORWARD: finish the renames a killed\n * commit did not reach, from the staged temps that are already durable on\n * disk (and, for a version-3 record, the directory renames after them).\n *\n * Forward and not undo, because that is what is actually durable: the\n * after-bytes were written and (per stageWrites) fully on disk before the\n * journal existed, while the OVERWRITTEN files' pre-images are durable\n * nowhere — the rename that replaced them destroyed the only copy. The plan\n * was validated completely before the first rename, so the post-state is\n * exactly what a successful run would have produced; unarchive's\n * finish-never-undo recovery is the precedent.\n *\n * The refuse-to-choose doctrine is rollbackStaged's, verbatim: a file whose\n * bytes are neither the before- nor the after-digest was edited by somebody\n * since the crash, and completing over it would destroy one of two truths.\n */\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { readFile, rename, rm } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { quietRmdir } from \"../commit.js\";\nimport { sha256 } from \"../writes.js\";\nimport { COMMIT_INTENT, type CommitRecovery, InterruptedCommitError, sweepPidTemps } from \"../interrupted.js\";\nimport { clearTxnIntent, type TxnIntent, type TxnIntentFile, type TxnIntentMove } from \"./journal.js\";\n\nexport async function recoverForward(root: string, intent: TxnIntent): Promise<CommitRecovery> {\n const abs = (rel: string): string => join(root, ...rel.split(\"/\"));\n // A directory rename is classified by pure existence — a rename moves the\n // inode, so \"which end exists\" is exactly as discriminating as a digest is\n // for a file. Both ends existing (or neither) is a state no step of the\n // commit produces: somebody made a directory since the crash, and choosing\n // which tree to keep is the refuse-to-choose case below.\n const movesDone: TxnIntentMove[] = [];\n const movesPending: TxnIntentMove[] = [];\n const unknownMoves: string[] = [];\n for (const m of intent.moves) {\n const fromExists = existsSync(abs(m.from));\n const toExists = existsSync(abs(m.to));\n if (toExists && !fromExists) movesDone.push(m);\n else if (fromExists && !toExists) movesPending.push(m);\n else unknownMoves.push(`${m.from} -> ${m.to}`);\n }\n // Where a recorded file IS now. The journal records a file at the path its\n // swap wrote — and a `subsystem move` swaps the documents whose links it\n // rewrites INSIDE a directory it then renames, so once that rename has\n // landed the file is under the moved directory, not at the recorded path.\n // Looked for there, it would read as absent: \"edited since\", a refusal over\n // a commit that only needed its journal cleared. Classified moves first for\n // exactly this; a file under a move still pending is where it was recorded.\n const target = (f: TxnIntentFile): string => {\n const landed = movesDone.find((m) => f.path.startsWith(`${m.from}/`));\n return landed === undefined ? abs(f.path) : abs(`${landed.to}${f.path.slice(landed.from.length)}`);\n };\n const done: TxnIntentFile[] = [];\n const pending: TxnIntentFile[] = [];\n const unknown: string[] = [];\n for (const f of intent.files) {\n const file = target(f);\n const now = existsSync(file) ? sha256(await readFile(file)) : null;\n if (now === f.after) done.push(f);\n else if (now === f.before) pending.push(f);\n else unknown.push(f.path);\n }\n if (unknown.length > 0 || unknownMoves.length > 0) {\n // Two vocabularies on purpose: a FILE in neither recorded state holds\n // edited bytes, while a DIRECTORY rename in neither state means both ends\n // exist (or neither does) — nothing was \"edited\", somebody made or\n // restored a directory since the crash. One sentence covering both told\n // the operator to hunt for edited bytes in a directory pair. And the\n // interrupted work is named by what it WAS (`command` + `target`), not by\n // `intent.rerun` — that string is the REPAIR spelling (`loam subsystem\n // sync` repairs all four subsystem verbs), so \"a `loam subsystem sync`\n // was interrupted\" named a command nobody ran.\n const parts = [\n ...(unknown.length > 0\n ? [\n `${unknown.length} of the file(s) it was writing now hold bytes that are neither what it found ` +\n `nor what it wrote — somebody edited them since: ${unknown.join(\", \")}`,\n ]\n : []),\n ...(unknownMoves.length > 0\n ? [\n `the directory rename(s) ${unknownMoves.join(\", \")} are in neither recorded state — both ends ` +\n `exist, or neither does, so loam cannot tell which tree to keep`,\n ]\n : []),\n ];\n throw new InterruptedCommitError(\n `a \\`${intent.command}\\` commit for '${intent.target}' was interrupted mid-commit ` +\n `(pid ${intent.pid} on ${intent.host}, ${intent.at}) and ${parts.join(\"; \")}. ` +\n `loam will not choose between those two truths. ` +\n `Reconcile them against version control, delete ${COMMIT_INTENT}, then re-run \\`${intent.rerun}\\`.`,\n );\n }\n\n // Whether forward is still POSSIBLE is decided before a single byte moves:\n // completing some files and then refusing on a missing temp would leave the\n // repo in a state neither the old nor the new tree ever had.\n const missing = pending.filter((f) => f.after !== null && !usableTemp(root, f));\n if (missing.length > 0) {\n // A file whose before and after digests are EQUAL — an unchanged file\n // carried through a commit — reads as \"done\" in whichever state it is in,\n // so it is not evidence that any rename landed. Only a file that could\n // tell the two states apart counts as one — and a landed directory MOVE\n // always can, so it counts too.\n const evidence = done.filter((f) => f.before !== f.after);\n if (evidence.length === 0 && movesDone.length === 0) {\n // Nothing ever swapped and the temps are gone — this is the tree a\n // completed ROLLBACK leaves when the crash landed between the rollback\n // and the journal's removal. The complete pre-state is on disk, which\n // is one of the two states recovery promises; declaring it beats\n // refusing over bytes that are already consistent.\n await finish(root, intent);\n return { command: intent.command, feature: intent.target, outcome: \"consistent\", repaired: [] };\n }\n throw new InterruptedCommitError(\n `a \\`${intent.command}\\` commit for '${intent.target}' was interrupted and ${missing.map((f) => f.path).join(\", \")} ` +\n `${missing.length === 1 ? \"is\" : \"are\"} still in the pre-commit state, but the staged bytes that would ` +\n `finish ${missing.length === 1 ? \"it\" : \"them\"} are gone or no longer match the digest recorded before the ` +\n `crash. Restore the tree from version control, delete ${COMMIT_INTENT}, then re-run \\`${intent.rerun}\\`.`,\n );\n }\n\n const repaired: string[] = [];\n for (const f of pending) {\n const file = target(f);\n if (f.after === null) {\n await rm(file, { force: true });\n await quietRmdir(dirname(file));\n } else {\n await rename(join(root, ...f.tmp!.split(\"/\")), file);\n }\n repaired.push(f.path);\n }\n // Renames after the file swaps, in recorded order — the order the commit\n // itself performs them. One that cannot land now (the destination's parent\n // deleted since the crash, a Windows EPERM on an open handle) is a refusal\n // naming the pair, not a half-finished loop: nothing before it is undone,\n // and the journal stays for the next attempt.\n for (const m of movesPending) {\n try {\n await rename(abs(m.from), abs(m.to));\n } catch (err) {\n throw new InterruptedCommitError(\n `a \\`${intent.command}\\` commit for '${intent.target}' was interrupted and the rename ${m.from} -> ${m.to} ` +\n `that would finish it cannot be performed now (${err instanceof Error ? err.message : String(err)}). ` +\n `Complete or undo it by hand against version control, delete ${COMMIT_INTENT}, then re-run \\`${intent.rerun}\\`.`,\n );\n }\n repaired.push(m.to);\n }\n await finish(root, intent);\n return {\n command: intent.command,\n feature: intent.target,\n outcome: repaired.length > 0 ? \"repaired\" : \"completed\",\n repaired,\n };\n}\n\n/** Is the recorded temp still there with exactly the bytes the journal promised? */\nfunction usableTemp(root: string, f: TxnIntentFile): boolean {\n if (f.tmp === null) return false;\n const path = join(root, ...f.tmp.split(\"/\"));\n try {\n return sha256(readFileSync(path)) === f.after;\n } catch {\n return false;\n }\n}\n\nasync function finish(root: string, intent: TxnIntent): Promise<void> {\n const dirs = intent.files.flatMap((f) => {\n const out = [dirname(join(root, ...f.path.split(\"/\")))];\n if (f.tmp !== null) out.push(dirname(join(root, ...f.tmp.split(\"/\"))));\n return out;\n });\n await sweepPidTemps(dirs, intent.pid);\n await clearTxnIntent(root);\n}\n\n"]}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * The version-2 `.loam-commit` journal: the record a SMALL journaled
3
+ * transaction writes before its first rename, for the writers whose commits
4
+ * have no snapshot and no final directory move — rebase, vouch, new, gherkin.
5
+ *
6
+ * Version 1 (archive/unarchive) restores from a snapshot that travels in the
7
+ * feature directory; none of the smaller writers has one, which is exactly
8
+ * why the roadmap says extract a smaller transaction rather than reuse — this
9
+ * journal therefore records, per file, the digest of both states AND the
10
+ * root-relative staged temp still holding the after-bytes, so recovery can
11
+ * roll FORWARD from what is already durable instead of pretending pre-images
12
+ * exist somewhere. `rerun` is the exact `loam …` line doctor prints as the
13
+ * fix: recovery runs at the start of the next invocation of the same command.
14
+ *
15
+ * A txn/ sub-package importing ONLY the staging root, never `recovery/`:
16
+ * recovery/ already imports the root and will import txn/ for dispatch, so an
17
+ * edge back from here would be the repo's first package cycle.
18
+ */
19
+ import { existsSync } from "node:fs";
20
+ import { readFile } from "node:fs/promises";
21
+ import { hostname } from "node:os";
22
+ import { join, relative } from "node:path";
23
+ import { isPathInside } from "../../kernel/path-safety.js";
24
+ import { quietRm } from "../commit.js";
25
+ import { COMMIT_INTENT, writeDurable } from "../interrupted.js";
26
+ import { isDigest, sha256 } from "../writes.js";
27
+ const TXN_VERSION = 2;
28
+ /**
29
+ * The version whose records CARRY DIRECTORY MOVES — `subsystem move`'s
30
+ * journal, the first consumer whose commit renames directories beside its one
31
+ * staged file. A version of its own, and version 2 is still what every
32
+ * move-less writer stamps, deliberately: an old binary must keep recovering
33
+ * the version-2 records it always could, and it must REFUSE a record with
34
+ * moves rather than roll the file half forward and leave the renames behind —
35
+ * an unknown version already reads as "a commit this loam cannot read", which
36
+ * is `doctor`'s honest blocker, not a silent half-repair.
37
+ */
38
+ const TXN_MOVES_VERSION = 3;
39
+ /** Root-relative with forward slashes — the journal must survive being read on another machine. */
40
+ function rel(root, abs) {
41
+ return relative(root, abs).split(/[\\/]/).join("/");
42
+ }
43
+ export async function writeTxnIntent(spec, staged) {
44
+ const moves = (spec.moves ?? []).map((m) => ({ from: rel(spec.root, m.from), to: rel(spec.root, m.to) }));
45
+ const intent = {
46
+ version: moves.length > 0 ? TXN_MOVES_VERSION : TXN_VERSION,
47
+ command: spec.command,
48
+ rerun: spec.rerun,
49
+ target: spec.target,
50
+ pid: process.pid,
51
+ host: hostname(),
52
+ at: new Date().toISOString(),
53
+ files: staged.map((s) => ({
54
+ path: rel(spec.root, s.write.path),
55
+ before: s.before === null ? null : sha256(s.before),
56
+ after: s.content === null ? null : sha256(s.content),
57
+ tmp: s.tmp === null ? null : rel(spec.root, s.tmp),
58
+ })),
59
+ moves,
60
+ };
61
+ await writeDurable(join(spec.root, COMMIT_INTENT), JSON.stringify(intent, null, 2) + "\n");
62
+ }
63
+ export async function clearTxnIntent(root) {
64
+ await quietRm(join(root, COMMIT_INTENT));
65
+ }
66
+ /**
67
+ * The journal as written, or null when absent, unreadable, or not version 2 —
68
+ * the same strict grading version 1 gets: a record that cannot be trusted
69
+ * whole is not a record, and `doctor` describes the difference.
70
+ */
71
+ export async function readTxnIntent(root) {
72
+ const path = join(root, COMMIT_INTENT);
73
+ if (!existsSync(path))
74
+ return null;
75
+ let parsed;
76
+ try {
77
+ parsed = JSON.parse(await readFile(path, "utf8"));
78
+ }
79
+ catch {
80
+ return null;
81
+ }
82
+ const i = parsed;
83
+ if (i === null || typeof i !== "object")
84
+ return null;
85
+ if (i.version !== TXN_VERSION && i.version !== TXN_MOVES_VERSION)
86
+ return null;
87
+ if (typeof i.command !== "string" || typeof i.rerun !== "string" || typeof i.target !== "string")
88
+ return null;
89
+ if (typeof i.pid !== "number" || typeof i.host !== "string" || typeof i.at !== "string")
90
+ return null;
91
+ if (!Array.isArray(i.files))
92
+ return null;
93
+ // A version-2 record predates the field; normalise so recovery reads one
94
+ // shape. A version-3 record's moves are validated exactly as its files are:
95
+ // recovery RENAMES what they name, so an entry escaping the root fails the
96
+ // whole record closed.
97
+ if (i.moves === undefined)
98
+ i.moves = [];
99
+ if (!Array.isArray(i.moves))
100
+ return null;
101
+ if (i.version === TXN_VERSION && i.moves.length > 0)
102
+ return null;
103
+ for (const m of i.moves) {
104
+ if (typeof m?.from !== "string" || typeof m?.to !== "string")
105
+ return null;
106
+ if (!insideRoot(root, m.from) || !insideRoot(root, m.to))
107
+ return null;
108
+ }
109
+ for (const f of i.files) {
110
+ if (typeof f?.path !== "string")
111
+ return null;
112
+ if (!digestOrNull(f.before) || !digestOrNull(f.after))
113
+ return null;
114
+ if (f.tmp !== null && typeof f.tmp !== "string")
115
+ return null;
116
+ // A write (after non-null) with no temp recorded could never be rolled
117
+ // forward; refusing the whole record here keeps that impossibility a
118
+ // `doctor` sentence instead of a mid-recovery surprise.
119
+ if (f.after !== null && f.tmp === null)
120
+ return null;
121
+ // The journal is untrusted input — a plain file that travels in a git
122
+ // checkout — and recovery RENAMES OVER and DELETES what these entries
123
+ // name. An entry escaping the root (`../outside`, an absolute path) would
124
+ // turn roll-forward into an arbitrary write anywhere on the machine, so
125
+ // it fails the whole record closed: `doctor` grades it exactly as any
126
+ // other unreadable journal, a blocker naming the file for a human.
127
+ if (!insideRoot(root, f.path) || (f.tmp !== null && !insideRoot(root, f.tmp)))
128
+ return null;
129
+ }
130
+ return i;
131
+ }
132
+ function digestOrNull(value) {
133
+ return value === null || isDigest(value);
134
+ }
135
+ /** Lexical containment of a recorded root-relative path — recovery acts on the join. */
136
+ function insideRoot(root, rel) {
137
+ return isPathInside(join(root), join(root, ...rel.split("/")));
138
+ }
139
+ //# sourceMappingURL=journal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"journal.js","sourceRoot":"","sources":["../../../../src/core/staging/txn/journal.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The version-2 `.loam-commit` journal: the record a SMALL journaled\n * transaction writes before its first rename, for the writers whose commits\n * have no snapshot and no final directory move — rebase, vouch, new, gherkin.\n *\n * Version 1 (archive/unarchive) restores from a snapshot that travels in the\n * feature directory; none of the smaller writers has one, which is exactly\n * why the roadmap says extract a smaller transaction rather than reuse — this\n * journal therefore records, per file, the digest of both states AND the\n * root-relative staged temp still holding the after-bytes, so recovery can\n * roll FORWARD from what is already durable instead of pretending pre-images\n * exist somewhere. `rerun` is the exact `loam …` line doctor prints as the\n * fix: recovery runs at the start of the next invocation of the same command.\n *\n * A txn/ sub-package importing ONLY the staging root, never `recovery/`:\n * recovery/ already imports the root and will import txn/ for dispatch, so an\n * edge back from here would be the repo's first package cycle.\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { hostname } from \"node:os\";\nimport { join, relative } from \"node:path\";\nimport { isPathInside } from \"../../kernel/path-safety.js\";\nimport { quietRm } from \"../commit.js\";\nimport { COMMIT_INTENT, writeDurable } from \"../interrupted.js\";\nimport { isDigest, sha256, type StagedWrite } from \"../writes.js\";\n\nconst TXN_VERSION = 2;\n/**\n * The version whose records CARRY DIRECTORY MOVES — `subsystem move`'s\n * journal, the first consumer whose commit renames directories beside its one\n * staged file. A version of its own, and version 2 is still what every\n * move-less writer stamps, deliberately: an old binary must keep recovering\n * the version-2 records it always could, and it must REFUSE a record with\n * moves rather than roll the file half forward and leave the renames behind —\n * an unknown version already reads as \"a commit this loam cannot read\", which\n * is `doctor`'s honest blocker, not a silent half-repair.\n */\nconst TXN_MOVES_VERSION = 3;\n\n/** One directory rename inside a transaction: root-relative, forward slashes. */\nexport interface TxnIntentMove {\n from: string;\n to: string;\n}\n\nexport interface TxnIntentFile {\n /** Root-relative path, forward slashes. */\n path: string;\n /** sha256 of the bytes before the swap; null when the swap creates the file. */\n before: string | null;\n /** sha256 of the bytes the swap writes; null when the swap deletes the file. */\n after: string | null;\n /** Root-relative staged temp holding the after-bytes; null for a deletion. */\n tmp: string | null;\n}\n\nexport interface TxnIntent {\n version: number;\n /** Which command was committing — a sentence a human reads, not a branch. */\n command: string;\n /** The exact `loam …` invocation whose next run recovers this journal. */\n rerun: string;\n /** What the command was committing for: a feature id, or a service. */\n target: string;\n pid: number;\n host: string;\n at: string;\n files: TxnIntentFile[];\n /**\n * Directory renames the commit performs AFTER its file swaps, in this\n * order. `[]` on every version-2 record (the reader normalises absence):\n * only `subsystem move`/`rename` write any, and their presence is what\n * bumps the record to version 3.\n */\n moves: TxnIntentMove[];\n}\n\n/** What a transaction tells the journal about itself; the caller's one spelling of its identity. */\nexport interface TxnSpec {\n /** The repo root the journal lives in — the docs repo, or gherkin's owned root. */\n root: string;\n command: string;\n rerun: string;\n target: string;\n /** Directory renames to perform after the swaps — ABSOLUTE paths; the journal stores them root-relative. */\n moves?: { from: string; to: string }[];\n}\n\n/** Root-relative with forward slashes — the journal must survive being read on another machine. */\nfunction rel(root: string, abs: string): string {\n return relative(root, abs).split(/[\\\\/]/).join(\"/\");\n}\n\nexport async function writeTxnIntent(spec: TxnSpec, staged: StagedWrite[]): Promise<void> {\n const moves = (spec.moves ?? []).map((m) => ({ from: rel(spec.root, m.from), to: rel(spec.root, m.to) }));\n const intent: TxnIntent = {\n version: moves.length > 0 ? TXN_MOVES_VERSION : TXN_VERSION,\n command: spec.command,\n rerun: spec.rerun,\n target: spec.target,\n pid: process.pid,\n host: hostname(),\n at: new Date().toISOString(),\n files: staged.map((s) => ({\n path: rel(spec.root, s.write.path),\n before: s.before === null ? null : sha256(s.before),\n after: s.content === null ? null : sha256(s.content),\n tmp: s.tmp === null ? null : rel(spec.root, s.tmp),\n })),\n moves,\n };\n await writeDurable(join(spec.root, COMMIT_INTENT), JSON.stringify(intent, null, 2) + \"\\n\");\n}\n\nexport async function clearTxnIntent(root: string): Promise<void> {\n await quietRm(join(root, COMMIT_INTENT));\n}\n\n/**\n * The journal as written, or null when absent, unreadable, or not version 2 —\n * the same strict grading version 1 gets: a record that cannot be trusted\n * whole is not a record, and `doctor` describes the difference.\n */\nexport async function readTxnIntent(root: string): Promise<TxnIntent | null> {\n const path = join(root, COMMIT_INTENT);\n if (!existsSync(path)) return null;\n let parsed: unknown;\n try {\n parsed = JSON.parse(await readFile(path, \"utf8\"));\n } catch {\n return null;\n }\n const i = parsed as Partial<TxnIntent>;\n if (i === null || typeof i !== \"object\") return null;\n if (i.version !== TXN_VERSION && i.version !== TXN_MOVES_VERSION) return null;\n if (typeof i.command !== \"string\" || typeof i.rerun !== \"string\" || typeof i.target !== \"string\") return null;\n if (typeof i.pid !== \"number\" || typeof i.host !== \"string\" || typeof i.at !== \"string\") return null;\n if (!Array.isArray(i.files)) return null;\n // A version-2 record predates the field; normalise so recovery reads one\n // shape. A version-3 record's moves are validated exactly as its files are:\n // recovery RENAMES what they name, so an entry escaping the root fails the\n // whole record closed.\n if (i.moves === undefined) i.moves = [];\n if (!Array.isArray(i.moves)) return null;\n if (i.version === TXN_VERSION && i.moves.length > 0) return null;\n for (const m of i.moves) {\n if (typeof m?.from !== \"string\" || typeof m?.to !== \"string\") return null;\n if (!insideRoot(root, m.from) || !insideRoot(root, m.to)) return null;\n }\n for (const f of i.files) {\n if (typeof f?.path !== \"string\") return null;\n if (!digestOrNull(f.before) || !digestOrNull(f.after)) return null;\n if (f.tmp !== null && typeof f.tmp !== \"string\") return null;\n // A write (after non-null) with no temp recorded could never be rolled\n // forward; refusing the whole record here keeps that impossibility a\n // `doctor` sentence instead of a mid-recovery surprise.\n if (f.after !== null && f.tmp === null) return null;\n // The journal is untrusted input — a plain file that travels in a git\n // checkout — and recovery RENAMES OVER and DELETES what these entries\n // name. An entry escaping the root (`../outside`, an absolute path) would\n // turn roll-forward into an arbitrary write anywhere on the machine, so\n // it fails the whole record closed: `doctor` grades it exactly as any\n // other unreadable journal, a blocker naming the file for a human.\n if (!insideRoot(root, f.path) || (f.tmp !== null && !insideRoot(root, f.tmp))) return null;\n }\n return i as TxnIntent;\n}\n\nfunction digestOrNull(value: unknown): boolean {\n return value === null || isDigest(value);\n}\n\n/** Lexical containment of a recorded root-relative path — recovery acts on the join. */\nfunction insideRoot(root: string, rel: string): boolean {\n return isPathInside(join(root), join(root, ...rel.split(\"/\")));\n}\n"]}