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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (691) hide show
  1. package/CHANGELOG.md +1477 -1
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +40 -22
  5. package/QUICKSTART.md +181 -9
  6. package/README.md +256 -57
  7. package/ROADMAP.md +108 -11
  8. package/SCHEMA.md +721 -130
  9. package/WORKFLOW.md +272 -62
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/archive/archive.js +15 -18
  15. package/dist/commands/archive/archive.js.map +1 -1
  16. package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
  17. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  18. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  19. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/openapi.js +25 -42
  21. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
  23. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  24. package/dist/commands/archive/plan/gate.js +3 -7
  25. package/dist/commands/archive/plan/gate.js.map +1 -1
  26. package/dist/commands/archive/plan/landscape.js +4 -4
  27. package/dist/commands/archive/plan/landscape.js.map +1 -1
  28. package/dist/commands/archive/plan/refusal.js +8 -18
  29. package/dist/commands/archive/plan/refusal.js.map +1 -1
  30. package/dist/commands/archive/plan/specs.js +134 -25
  31. package/dist/commands/archive/plan/specs.js.map +1 -1
  32. package/dist/commands/archive/plan/state.js +1 -0
  33. package/dist/commands/archive/plan/state.js.map +1 -1
  34. package/dist/commands/archive/run.js +82 -33
  35. package/dist/commands/archive/run.js.map +1 -1
  36. package/dist/commands/archive/shipped.js +49 -0
  37. package/dist/commands/archive/shipped.js.map +1 -0
  38. package/dist/commands/context/context.js +4 -7
  39. package/dist/commands/context/context.js.map +1 -1
  40. package/dist/commands/context/print.js +2 -2
  41. package/dist/commands/context/print.js.map +1 -1
  42. package/dist/commands/delta/delta.js +44 -12
  43. package/dist/commands/delta/delta.js.map +1 -1
  44. package/dist/commands/delta/print.js +1 -1
  45. package/dist/commands/delta/print.js.map +1 -1
  46. package/dist/commands/dependencies.js +3 -7
  47. package/dist/commands/dependencies.js.map +1 -1
  48. package/dist/commands/diff/diff.js +20 -27
  49. package/dist/commands/diff/diff.js.map +1 -1
  50. package/dist/commands/explore.js +4 -8
  51. package/dist/commands/explore.js.map +1 -1
  52. package/dist/commands/gate/gate.js +45 -8
  53. package/dist/commands/gate/gate.js.map +1 -1
  54. package/dist/commands/gate/print.js +15 -3
  55. package/dist/commands/gate/print.js.map +1 -1
  56. package/dist/commands/gherkin/commit.js +13 -54
  57. package/dist/commands/gherkin/commit.js.map +1 -1
  58. package/dist/commands/gherkin/gherkin.js +36 -20
  59. package/dist/commands/gherkin/gherkin.js.map +1 -1
  60. package/dist/commands/gherkin/reconcile.js +10 -13
  61. package/dist/commands/gherkin/reconcile.js.map +1 -1
  62. package/dist/commands/init/first-hour.js +44 -0
  63. package/dist/commands/init/first-hour.js.map +1 -1
  64. package/dist/commands/init/init.js +18 -22
  65. package/dist/commands/init/init.js.map +1 -1
  66. package/dist/commands/list/campaign/campaign.js +4 -6
  67. package/dist/commands/list/campaign/campaign.js.map +1 -1
  68. package/dist/commands/list/json.js +0 -4
  69. package/dist/commands/list/json.js.map +1 -1
  70. package/dist/commands/list/list.js +38 -23
  71. package/dist/commands/list/list.js.map +1 -1
  72. package/dist/commands/list/print.js +38 -6
  73. package/dist/commands/list/print.js.map +1 -1
  74. package/dist/commands/list/suites/suites.js +140 -0
  75. package/dist/commands/list/suites/suites.js.map +1 -0
  76. package/dist/commands/list/views.js +28 -33
  77. package/dist/commands/list/views.js.map +1 -1
  78. package/dist/commands/mcp/dispatch.js +91 -36
  79. package/dist/commands/mcp/dispatch.js.map +1 -1
  80. package/dist/commands/mcp/mcp.js +56 -15
  81. package/dist/commands/mcp/mcp.js.map +1 -1
  82. package/dist/commands/mcp/serve.js.map +1 -1
  83. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  84. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  85. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  86. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  87. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  88. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  89. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  90. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  91. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  92. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  93. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  94. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  95. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  96. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  97. package/dist/commands/new/capability-template.js +118 -0
  98. package/dist/commands/new/capability-template.js.map +1 -0
  99. package/dist/commands/new/commit.js +8 -22
  100. package/dist/commands/new/commit.js.map +1 -1
  101. package/dist/commands/new/modifies.js +206 -0
  102. package/dist/commands/new/modifies.js.map +1 -0
  103. package/dist/commands/new/new.js +59 -14
  104. package/dist/commands/new/new.js.map +1 -1
  105. package/dist/commands/new/templates.js +48 -124
  106. package/dist/commands/new/templates.js.map +1 -1
  107. package/dist/commands/open.js +3 -7
  108. package/dist/commands/open.js.map +1 -1
  109. package/dist/commands/policy/gate.js +27 -0
  110. package/dist/commands/policy/gate.js.map +1 -1
  111. package/dist/commands/rebase/plan.js +2 -2
  112. package/dist/commands/rebase/plan.js.map +1 -1
  113. package/dist/commands/rebase/rebase.js +60 -35
  114. package/dist/commands/rebase/rebase.js.map +1 -1
  115. package/dist/commands/seed/commit.js +55 -26
  116. package/dist/commands/seed/commit.js.map +1 -1
  117. package/dist/commands/seed/plan.js +64 -13
  118. package/dist/commands/seed/plan.js.map +1 -1
  119. package/dist/commands/seed/seed.js +6 -9
  120. package/dist/commands/seed/seed.js.map +1 -1
  121. package/dist/commands/show/show.js +3 -7
  122. package/dist/commands/show/show.js.map +1 -1
  123. package/dist/commands/status/print.js +13 -4
  124. package/dist/commands/status/print.js.map +1 -1
  125. package/dist/commands/status/status.js +3 -7
  126. package/dist/commands/status/status.js.map +1 -1
  127. package/dist/commands/steps/steps.js +3 -7
  128. package/dist/commands/steps/steps.js.map +1 -1
  129. package/dist/commands/subsystem/edit.js +7 -7
  130. package/dist/commands/subsystem/edit.js.map +1 -1
  131. package/dist/commands/subsystem/move.js +60 -25
  132. package/dist/commands/subsystem/move.js.map +1 -1
  133. package/dist/commands/subsystem/query.js +2 -4
  134. package/dist/commands/subsystem/query.js.map +1 -1
  135. package/dist/commands/subsystem/subsystem.js +4 -7
  136. package/dist/commands/subsystem/subsystem.js.map +1 -1
  137. package/dist/commands/subsystem/sync.js +141 -71
  138. package/dist/commands/subsystem/sync.js.map +1 -1
  139. package/dist/commands/subsystem/txn/report.js +55 -5
  140. package/dist/commands/subsystem/txn/report.js.map +1 -1
  141. package/dist/commands/subsystem/txn/txn.js +121 -45
  142. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  143. package/dist/commands/subsystem/txn/views.js +82 -12
  144. package/dist/commands/subsystem/txn/views.js.map +1 -1
  145. package/dist/commands/tasks/render.js +116 -0
  146. package/dist/commands/tasks/render.js.map +1 -0
  147. package/dist/commands/tasks/tasks.js +113 -0
  148. package/dist/commands/tasks/tasks.js.map +1 -0
  149. package/dist/commands/unarchive/manifest.js +6 -9
  150. package/dist/commands/unarchive/manifest.js.map +1 -1
  151. package/dist/commands/unarchive/restore.js +17 -28
  152. package/dist/commands/unarchive/restore.js.map +1 -1
  153. package/dist/commands/unarchive/unarchive.js +22 -30
  154. package/dist/commands/unarchive/unarchive.js.map +1 -1
  155. package/dist/commands/validate/arch-coverage.js +6 -15
  156. package/dist/commands/validate/arch-coverage.js.map +1 -1
  157. package/dist/commands/validate/checks/fleet-shape.js +4 -49
  158. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  159. package/dist/commands/validate/checks/health-deps.js +9 -6
  160. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  161. package/dist/commands/validate/checks/requirements.js +18 -8
  162. package/dist/commands/validate/checks/requirements.js.map +1 -1
  163. package/dist/commands/validate/checks/sources.js +6 -2
  164. package/dist/commands/validate/checks/sources.js.map +1 -1
  165. package/dist/commands/validate/feature.js +23 -19
  166. package/dist/commands/validate/feature.js.map +1 -1
  167. package/dist/commands/validate/fleet/census.js +26 -3
  168. package/dist/commands/validate/fleet/census.js.map +1 -1
  169. package/dist/commands/validate/fleet/landscape.js +35 -32
  170. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  171. package/dist/commands/validate/fleet/map/attest.js +7 -12
  172. package/dist/commands/validate/fleet/map/attest.js.map +1 -1
  173. package/dist/commands/validate/fleet/map/bindings.js +16 -5
  174. package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
  175. package/dist/commands/validate/fleet/map/consumers.js +43 -122
  176. package/dist/commands/validate/fleet/map/consumers.js.map +1 -1
  177. package/dist/commands/validate/fleet/map/usecases.js +5 -11
  178. package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
  179. package/dist/commands/validate/fleet/obligations.js +1 -5
  180. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  181. package/dist/commands/validate/fleet/scorecard/print.js +5 -1
  182. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  183. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  184. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  185. package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
  186. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
  187. package/dist/commands/validate/fleet/views/fleet-project.js +1 -1
  188. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
  189. package/dist/commands/validate/fleet/views/ids.js +33 -7
  190. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  191. package/dist/commands/validate/fleet/views/root-documents.js +17 -35
  192. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
  193. package/dist/commands/validate/fleet/views/stale.js +31 -14
  194. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  195. package/dist/commands/validate/links/corpus.js +1 -4
  196. package/dist/commands/validate/links/corpus.js.map +1 -1
  197. package/dist/commands/validate/links/glossary.js.map +1 -1
  198. package/dist/commands/validate/report.js +5 -4
  199. package/dist/commands/validate/report.js.map +1 -1
  200. package/dist/commands/validate/scope.js +113 -0
  201. package/dist/commands/validate/scope.js.map +1 -0
  202. package/dist/commands/validate/service/api.js +16 -104
  203. package/dist/commands/validate/service/api.js.map +1 -1
  204. package/dist/commands/validate/service/contracts/generated.js +134 -0
  205. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  206. package/dist/commands/validate/service/events/events.js +1 -1
  207. package/dist/commands/validate/service/events/events.js.map +1 -1
  208. package/dist/commands/validate/service/events/message-producers.js +1 -1
  209. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  210. package/dist/commands/validate/service/evidence-pins.js +56 -10
  211. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  212. package/dist/commands/validate/service/service.js +16 -26
  213. package/dist/commands/validate/service/service.js.map +1 -1
  214. package/dist/commands/validate/service/specs.js +31 -38
  215. package/dist/commands/validate/service/specs.js.map +1 -1
  216. package/dist/commands/validate/service/spine.js +8 -11
  217. package/dist/commands/validate/service/spine.js.map +1 -1
  218. package/dist/commands/validate/service/usecases/usecases.js +3 -3
  219. package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
  220. package/dist/commands/validate/validate.js +44 -26
  221. package/dist/commands/validate/validate.js.map +1 -1
  222. package/dist/commands/verify/cross/diff.js +2 -6
  223. package/dist/commands/verify/cross/diff.js.map +1 -1
  224. package/dist/commands/verify/evidence/contract.js +1 -1
  225. package/dist/commands/verify/evidence/contract.js.map +1 -1
  226. package/dist/commands/verify/evidence/read.js +12 -3
  227. package/dist/commands/verify/evidence/read.js.map +1 -1
  228. package/dist/commands/verify/frozen.js +45 -23
  229. package/dist/commands/verify/frozen.js.map +1 -1
  230. package/dist/commands/verify/record.js +69 -17
  231. package/dist/commands/verify/record.js.map +1 -1
  232. package/dist/commands/verify/report.js +27 -44
  233. package/dist/commands/verify/report.js.map +1 -1
  234. package/dist/commands/verify/results.js +112 -30
  235. package/dist/commands/verify/results.js.map +1 -1
  236. package/dist/commands/verify/verify.js +54 -51
  237. package/dist/commands/verify/verify.js.map +1 -1
  238. package/dist/commands/vouch/pack/pack.js +2 -2
  239. package/dist/commands/vouch/pack/pack.js.map +1 -1
  240. package/dist/commands/vouch/pack/print.js +1 -6
  241. package/dist/commands/vouch/pack/print.js.map +1 -1
  242. package/dist/commands/vouch/pack/sections.js +1 -2
  243. package/dist/commands/vouch/pack/sections.js.map +1 -1
  244. package/dist/commands/vouch/run.js +26 -81
  245. package/dist/commands/vouch/run.js.map +1 -1
  246. package/dist/commands/vouch/sample/plan.js +20 -34
  247. package/dist/commands/vouch/sample/plan.js.map +1 -1
  248. package/dist/commands/vouch/vet/verify.js +39 -7
  249. package/dist/commands/vouch/vet/verify.js.map +1 -1
  250. package/dist/commands/vouch/vouch.js +10 -17
  251. package/dist/commands/vouch/vouch.js.map +1 -1
  252. package/dist/core/agent/agents-md/command-map.js +57 -102
  253. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  254. package/dist/core/agent/agents-md/map/explain.js +4 -3
  255. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  256. package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
  257. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  258. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  259. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  260. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  261. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  262. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  263. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  264. package/dist/core/agent/agents-md/map/status.js +9 -3
  265. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  266. package/dist/core/agent/agents-md/map/subsystem.js +31 -17
  267. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  268. package/dist/core/agent/agents-md/map/usecases.js +24 -6
  269. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  270. package/dist/core/agent/agents-md/refusals.js +37 -21
  271. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  272. package/dist/core/agent/agents-md.js +25 -10
  273. package/dist/core/agent/agents-md.js.map +1 -1
  274. package/dist/core/agent/agents-stamp.js +10 -0
  275. package/dist/core/agent/agents-stamp.js.map +1 -1
  276. package/dist/core/agent/scaffold.js +10 -15
  277. package/dist/core/agent/scaffold.js.map +1 -1
  278. package/dist/core/agent/tools/dialects.js +1 -0
  279. package/dist/core/agent/tools/dialects.js.map +1 -1
  280. package/dist/core/agent/tools/registry.js +41 -76
  281. package/dist/core/agent/tools/registry.js.map +1 -1
  282. package/dist/core/agent/workflows/adopt.js +5 -3
  283. package/dist/core/agent/workflows/adopt.js.map +1 -1
  284. package/dist/core/agent/workflows/check.js +25 -22
  285. package/dist/core/agent/workflows/check.js.map +1 -1
  286. package/dist/core/agent/workflows/closing.js +54 -21
  287. package/dist/core/agent/workflows/closing.js.map +1 -1
  288. package/dist/core/agent/workflows/feature.js +79 -11
  289. package/dist/core/agent/workflows/feature.js.map +1 -1
  290. package/dist/core/agent/workflows/implement.js +91 -13
  291. package/dist/core/agent/workflows/implement.js.map +1 -1
  292. package/dist/core/agent/workflows/reference/authoring.js +3 -1
  293. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  294. package/dist/core/agent/workflows/reference/codes.js +8 -6
  295. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  296. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  297. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  298. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  299. package/dist/core/agent/workflows/reference/spine.js +62 -24
  300. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  301. package/dist/core/asyncapi/baseline/plan.js +14 -81
  302. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  303. package/dist/core/asyncapi/digest.js +30 -25
  304. package/dist/core/asyncapi/digest.js.map +1 -1
  305. package/dist/core/asyncapi/merge/components.js +5 -4
  306. package/dist/core/asyncapi/merge/components.js.map +1 -1
  307. package/dist/core/asyncapi/merge/error.js +7 -2
  308. package/dist/core/asyncapi/merge/error.js.map +1 -1
  309. package/dist/core/asyncapi/merge/markers.js +30 -50
  310. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  311. package/dist/core/asyncapi/merge/merge.js +31 -53
  312. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  313. package/dist/core/asyncapi/merge/pin.js +10 -36
  314. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  315. package/dist/core/asyncapi/model.js.map +1 -1
  316. package/dist/core/asyncapi/read.js +36 -12
  317. package/dist/core/asyncapi/read.js.map +1 -1
  318. package/dist/core/brief/brief.js +30 -25
  319. package/dist/core/brief/brief.js.map +1 -1
  320. package/dist/core/brief/landscape.js +8 -12
  321. package/dist/core/brief/landscape.js.map +1 -1
  322. package/dist/core/brief/map/extend.js +8 -5
  323. package/dist/core/brief/map/extend.js.map +1 -1
  324. package/dist/core/brief/map/owed.js +8 -26
  325. package/dist/core/brief/map/owed.js.map +1 -1
  326. package/dist/core/brief/targets.js +79 -5
  327. package/dist/core/brief/targets.js.map +1 -1
  328. package/dist/core/brief/unchecked.js +1 -1
  329. package/dist/core/brief/unchecked.js.map +1 -1
  330. package/dist/core/c4/likec4.js +147 -16
  331. package/dist/core/c4/likec4.js.map +1 -1
  332. package/dist/core/c4/locate/unlocated.js +150 -0
  333. package/dist/core/c4/locate/unlocated.js.map +1 -0
  334. package/dist/core/c4/parsed/values.js +54 -1
  335. package/dist/core/c4/parsed/values.js.map +1 -1
  336. package/dist/core/c4/prefetch/fleet.js +1 -9
  337. package/dist/core/c4/prefetch/fleet.js.map +1 -1
  338. package/dist/core/c4/project/architecture.js +31 -9
  339. package/dist/core/c4/project/architecture.js.map +1 -1
  340. package/dist/core/c4/project/documents.js +36 -10
  341. package/dist/core/c4/project/documents.js.map +1 -1
  342. package/dist/core/c4/project/load.js +14 -21
  343. package/dist/core/c4/project/load.js.map +1 -1
  344. package/dist/core/c4/project/staged.js +7 -3
  345. package/dist/core/c4/project/staged.js.map +1 -1
  346. package/dist/core/c4/resolve/external.js +51 -0
  347. package/dist/core/c4/resolve/external.js.map +1 -0
  348. package/dist/core/c4/resolve/service.js +25 -1
  349. package/dist/core/c4/resolve/service.js.map +1 -1
  350. package/dist/core/c4/seed/template.js +15 -1
  351. package/dist/core/c4/seed/template.js.map +1 -1
  352. package/dist/core/c4/service-model/shape.js +2 -22
  353. package/dist/core/c4/service-model/shape.js.map +1 -1
  354. package/dist/core/c4/source-mask.js +288 -4
  355. package/dist/core/c4/source-mask.js.map +1 -1
  356. package/dist/core/c4/source-scan.js +203 -147
  357. package/dist/core/c4/source-scan.js.map +1 -1
  358. package/dist/core/c4/splice/authored-source.js +19 -0
  359. package/dist/core/c4/splice/authored-source.js.map +1 -1
  360. package/dist/core/c4/splice/delta-blocks.js +5 -18
  361. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  362. package/dist/core/c4/splice/landscape-merge.js +13 -8
  363. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  364. package/dist/core/c4/splice/model/merge.js +10 -6
  365. package/dist/core/c4/splice/model/merge.js.map +1 -1
  366. package/dist/core/c4/splice/placement.js +108 -18
  367. package/dist/core/c4/splice/placement.js.map +1 -1
  368. package/dist/core/c4/workspace.js +13 -22
  369. package/dist/core/c4/workspace.js.map +1 -1
  370. package/dist/core/capabilities/capabilities.js +6 -6
  371. package/dist/core/capabilities/capabilities.js.map +1 -1
  372. package/dist/core/capabilities/delta/doc.js +7 -10
  373. package/dist/core/capabilities/delta/doc.js.map +1 -1
  374. package/dist/core/capabilities/findings.js +15 -20
  375. package/dist/core/capabilities/findings.js.map +1 -1
  376. package/dist/core/capabilities/realizes/findings.js +12 -19
  377. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  378. package/dist/core/capabilities/realizes/join.js +0 -20
  379. package/dist/core/capabilities/realizes/join.js.map +1 -1
  380. package/dist/core/capabilities/rollup.js +6 -0
  381. package/dist/core/capabilities/rollup.js.map +1 -1
  382. package/dist/core/capabilities/tree.js +6 -28
  383. package/dist/core/capabilities/tree.js.map +1 -1
  384. package/dist/core/capabilities/usecase-join.js +3 -13
  385. package/dist/core/capabilities/usecase-join.js.map +1 -1
  386. package/dist/core/coherence/authoring/scaffold.js +11 -3
  387. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  388. package/dist/core/coherence/authoring/sentinels.js +2 -1
  389. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  390. package/dist/core/coherence/coherence.js +30 -16
  391. package/dist/core/coherence/coherence.js.map +1 -1
  392. package/dist/core/coherence/consumed/operations.js +70 -0
  393. package/dist/core/coherence/consumed/operations.js.map +1 -0
  394. package/dist/core/coherence/consumed/refusal.js +38 -0
  395. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  396. package/dist/core/coherence/declared.js +60 -34
  397. package/dist/core/coherence/declared.js.map +1 -1
  398. package/dist/core/coherence/events/declared.js +44 -59
  399. package/dist/core/coherence/events/declared.js.map +1 -1
  400. package/dist/core/coherence/events/events.js +119 -8
  401. package/dist/core/coherence/events/events.js.map +1 -1
  402. package/dist/core/coherence/events/grades.js +31 -8
  403. package/dist/core/coherence/events/grades.js.map +1 -1
  404. package/dist/core/coherence/events/lookups.js +14 -82
  405. package/dist/core/coherence/events/lookups.js.map +1 -1
  406. package/dist/core/coherence/lookups.js +18 -78
  407. package/dist/core/coherence/lookups.js.map +1 -1
  408. package/dist/core/coherence/retired/archived.js +285 -0
  409. package/dist/core/coherence/retired/archived.js.map +1 -0
  410. package/dist/core/coherence/retired/event-pair.js +62 -0
  411. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  412. package/dist/core/coherence/retired/merged.js +79 -0
  413. package/dist/core/coherence/retired/merged.js.map +1 -0
  414. package/dist/core/coherence/retired/retirement.js +49 -0
  415. package/dist/core/coherence/retired/retirement.js.map +1 -0
  416. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  417. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  418. package/dist/core/coherence/retired/revival/revived.js +297 -0
  419. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  420. package/dist/core/consumers/after.js +113 -0
  421. package/dist/core/consumers/after.js.map +1 -0
  422. package/dist/core/consumers/consumers.js +296 -0
  423. package/dist/core/consumers/consumers.js.map +1 -0
  424. package/dist/core/consumers/current.js +211 -0
  425. package/dist/core/consumers/current.js.map +1 -0
  426. package/dist/core/consumers/scan.js +46 -0
  427. package/dist/core/consumers/scan.js.map +1 -0
  428. package/dist/core/consumers/state.js +27 -0
  429. package/dist/core/consumers/state.js.map +1 -0
  430. package/dist/core/delta/delta.js +1 -1
  431. package/dist/core/delta/delta.js.map +1 -1
  432. package/dist/core/deployment/delta.js +15 -16
  433. package/dist/core/deployment/delta.js.map +1 -1
  434. package/dist/core/diff/base-git.js +94 -12
  435. package/dist/core/diff/base-git.js.map +1 -1
  436. package/dist/core/diff/base-state.js +28 -16
  437. package/dist/core/diff/base-state.js.map +1 -1
  438. package/dist/core/diff/joins.js +136 -0
  439. package/dist/core/diff/joins.js.map +1 -0
  440. package/dist/core/diff/scope/changed-paths.js +22 -6
  441. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  442. package/dist/core/diff/scope/edges.js +82 -0
  443. package/dist/core/diff/scope/edges.js.map +1 -0
  444. package/dist/core/diff/semantic.js +100 -31
  445. package/dist/core/diff/semantic.js.map +1 -1
  446. package/dist/core/doctor/agents.js +34 -18
  447. package/dist/core/doctor/agents.js.map +1 -1
  448. package/dist/core/doctor/report.js.map +1 -1
  449. package/dist/core/document/apply.js +10 -2
  450. package/dist/core/document/apply.js.map +1 -1
  451. package/dist/core/document/frontmatter.js +55 -0
  452. package/dist/core/document/frontmatter.js.map +1 -1
  453. package/dist/core/document/parse.js +29 -6
  454. package/dist/core/document/parse.js.map +1 -1
  455. package/dist/core/document/scenarios.js +17 -0
  456. package/dist/core/document/scenarios.js.map +1 -1
  457. package/dist/core/document/spec.js +4 -11
  458. package/dist/core/document/spec.js.map +1 -1
  459. package/dist/core/envelope/config.js +10 -0
  460. package/dist/core/envelope/config.js.map +1 -1
  461. package/dist/core/envelope/json.js +34 -0
  462. package/dist/core/envelope/json.js.map +1 -1
  463. package/dist/core/explain/families.js +17 -12
  464. package/dist/core/explain/families.js.map +1 -1
  465. package/dist/core/explain/refusals.js +3 -3
  466. package/dist/core/explain/refusals.js.map +1 -1
  467. package/dist/core/explain/terms.js +18 -9
  468. package/dist/core/explain/terms.js.map +1 -1
  469. package/dist/core/explore/explore.js +2 -7
  470. package/dist/core/explore/explore.js.map +1 -1
  471. package/dist/core/fleet-context.js +34 -95
  472. package/dist/core/fleet-context.js.map +1 -1
  473. package/dist/core/gate/checks.js +10 -3
  474. package/dist/core/gate/checks.js.map +1 -1
  475. package/dist/core/gate/partners.js +64 -19
  476. package/dist/core/gate/partners.js.map +1 -1
  477. package/dist/core/gate/report.js.map +1 -1
  478. package/dist/core/gate/verification.js +75 -12
  479. package/dist/core/gate/verification.js.map +1 -1
  480. package/dist/core/gherkin/stale.js +13 -37
  481. package/dist/core/gherkin/stale.js.map +1 -1
  482. package/dist/core/glossary/tree.js +6 -19
  483. package/dist/core/glossary/tree.js.map +1 -1
  484. package/dist/core/kernel/concurrency.js +19 -0
  485. package/dist/core/kernel/concurrency.js.map +1 -1
  486. package/dist/core/kernel/records.js +11 -0
  487. package/dist/core/kernel/records.js.map +1 -1
  488. package/dist/core/links/backlinks.js +1 -3
  489. package/dist/core/links/backlinks.js.map +1 -1
  490. package/dist/core/links/corpus.js +5 -36
  491. package/dist/core/links/corpus.js.map +1 -1
  492. package/dist/core/mcp/argv.js +101 -0
  493. package/dist/core/mcp/argv.js.map +1 -1
  494. package/dist/core/mcp/author-tools.js +2 -1
  495. package/dist/core/mcp/author-tools.js.map +1 -1
  496. package/dist/core/mcp/protocol.js +2 -2
  497. package/dist/core/mcp/protocol.js.map +1 -1
  498. package/dist/core/mcp/tools.js +43 -33
  499. package/dist/core/mcp/tools.js.map +1 -1
  500. package/dist/core/openapi/baseline/gate.js +53 -12
  501. package/dist/core/openapi/baseline/gate.js.map +1 -1
  502. package/dist/core/openapi/baseline/plan.js +22 -8
  503. package/dist/core/openapi/baseline/plan.js.map +1 -1
  504. package/dist/core/openapi/baseline/record.js +0 -1
  505. package/dist/core/openapi/baseline/record.js.map +1 -1
  506. package/dist/core/openapi/contract-text.js +33 -0
  507. package/dist/core/openapi/contract-text.js.map +1 -0
  508. package/dist/core/openapi/contract-tree.js +56 -0
  509. package/dist/core/openapi/contract-tree.js.map +1 -0
  510. package/dist/core/openapi/depth.js +38 -24
  511. package/dist/core/openapi/depth.js.map +1 -1
  512. package/dist/core/openapi/digest.js +52 -2
  513. package/dist/core/openapi/digest.js.map +1 -1
  514. package/dist/core/openapi/doc.js +12 -4
  515. package/dist/core/openapi/doc.js.map +1 -1
  516. package/dist/core/openapi/merge/components.js +25 -60
  517. package/dist/core/openapi/merge/components.js.map +1 -1
  518. package/dist/core/openapi/merge/error.js +3 -13
  519. package/dist/core/openapi/merge/error.js.map +1 -1
  520. package/dist/core/openapi/merge/markers.js +26 -30
  521. package/dist/core/openapi/merge/markers.js.map +1 -1
  522. package/dist/core/openapi/merge/merge.js +26 -52
  523. package/dist/core/openapi/merge/merge.js.map +1 -1
  524. package/dist/core/openapi/merge/pin.js +15 -35
  525. package/dist/core/openapi/merge/pin.js.map +1 -1
  526. package/dist/core/openspec/decide/verdict.js.map +1 -1
  527. package/dist/core/openspec/inventory.js +4 -5
  528. package/dist/core/openspec/inventory.js.map +1 -1
  529. package/dist/core/openspec/model/mapping.js +30 -28
  530. package/dist/core/openspec/model/mapping.js.map +1 -1
  531. package/dist/core/openspec/model/model.js +12 -7
  532. package/dist/core/openspec/model/model.js.map +1 -1
  533. package/dist/core/openspec/scan/changes.js +10 -4
  534. package/dist/core/openspec/scan/changes.js.map +1 -1
  535. package/dist/core/openspec/scan/config.js +7 -16
  536. package/dist/core/openspec/scan/config.js.map +1 -1
  537. package/dist/core/openspec/scan/shape.js +63 -19
  538. package/dist/core/openspec/scan/shape.js.map +1 -1
  539. package/dist/core/openspec/scan/walk.js +21 -38
  540. package/dist/core/openspec/scan/walk.js.map +1 -1
  541. package/dist/core/pack/living.js +2 -16
  542. package/dist/core/pack/living.js.map +1 -1
  543. package/dist/core/provenance/findings.js +48 -12
  544. package/dist/core/provenance/findings.js.map +1 -1
  545. package/dist/core/provenance/git.js +33 -57
  546. package/dist/core/provenance/git.js.map +1 -1
  547. package/dist/core/provenance/gitq/moves.js +71 -114
  548. package/dist/core/provenance/gitq/moves.js.map +1 -1
  549. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  550. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  551. package/dist/core/provenance/sources.js +23 -6
  552. package/dist/core/provenance/sources.js.map +1 -1
  553. package/dist/core/provenance/stamp.js +5 -17
  554. package/dist/core/provenance/stamp.js.map +1 -1
  555. package/dist/core/repo/entries.js.map +1 -1
  556. package/dist/core/repo/paths.js +34 -29
  557. package/dist/core/repo/paths.js.map +1 -1
  558. package/dist/core/repo/repo.js +24 -14
  559. package/dist/core/repo/repo.js.map +1 -1
  560. package/dist/core/repo/tree/find.js +12 -3
  561. package/dist/core/repo/tree/find.js.map +1 -1
  562. package/dist/core/repo/tree/fs.js +27 -1
  563. package/dist/core/repo/tree/fs.js.map +1 -1
  564. package/dist/core/repo/tree/render/body.js +45 -6
  565. package/dist/core/repo/tree/render/body.js.map +1 -1
  566. package/dist/core/repo/tree/render/service/header.js +125 -0
  567. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  568. package/dist/core/repo/tree/render/service/peers.js +28 -0
  569. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  570. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  571. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  572. package/dist/core/repo/tree/render/service/views.js +78 -0
  573. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  574. package/dist/core/repo/tree/render/stale.js +19 -2
  575. package/dist/core/repo/tree/render/stale.js.map +1 -1
  576. package/dist/core/repo/tree/render/views.js +102 -33
  577. package/dist/core/repo/tree/render/views.js.map +1 -1
  578. package/dist/core/repo/tree/walk.js +8 -30
  579. package/dist/core/repo/tree/walk.js.map +1 -1
  580. package/dist/core/results.js +47 -12
  581. package/dist/core/results.js.map +1 -1
  582. package/dist/core/staging/commit.js +10 -12
  583. package/dist/core/staging/commit.js.map +1 -1
  584. package/dist/core/staging/lock.js +74 -12
  585. package/dist/core/staging/lock.js.map +1 -1
  586. package/dist/core/staging/recovery/intent.js +2 -5
  587. package/dist/core/staging/recovery/intent.js.map +1 -1
  588. package/dist/core/staging/recovery/recover.js +60 -1
  589. package/dist/core/staging/recovery/recover.js.map +1 -1
  590. package/dist/core/staging/snapshot/manifest.js +152 -0
  591. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  592. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
  593. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  594. package/dist/core/status/actions/execution.js +11 -0
  595. package/dist/core/status/actions/execution.js.map +1 -1
  596. package/dist/core/status/feature/artifacts.js +2 -2
  597. package/dist/core/status/feature/artifacts.js.map +1 -1
  598. package/dist/core/status/feature/feature.js +5 -11
  599. package/dist/core/status/feature/feature.js.map +1 -1
  600. package/dist/core/status/fleet/fleet.js +6 -2
  601. package/dist/core/status/fleet/fleet.js.map +1 -1
  602. package/dist/core/status/fleet/next.js +26 -3
  603. package/dist/core/status/fleet/next.js.map +1 -1
  604. package/dist/core/status/report.js.map +1 -1
  605. package/dist/core/status/verification.js +30 -15
  606. package/dist/core/status/verification.js.map +1 -1
  607. package/dist/core/tasks/packet.js +174 -0
  608. package/dist/core/tasks/packet.js.map +1 -0
  609. package/dist/core/tasks/record.js +43 -0
  610. package/dist/core/tasks/record.js.map +1 -0
  611. package/dist/core/tasks/sources.js +181 -0
  612. package/dist/core/tasks/sources.js.map +1 -0
  613. package/dist/core/tasks/stamps.js +76 -0
  614. package/dist/core/tasks/stamps.js.map +1 -0
  615. package/dist/core/tasks/unit.js +0 -0
  616. package/dist/core/tasks/unit.js.map +1 -0
  617. package/dist/core/usecases/capability.js +11 -14
  618. package/dist/core/usecases/capability.js.map +1 -1
  619. package/dist/core/usecases/delta/flows.js +5 -7
  620. package/dist/core/usecases/delta/flows.js.map +1 -1
  621. package/dist/core/usecases/fleet.js +178 -31
  622. package/dist/core/usecases/fleet.js.map +1 -1
  623. package/dist/core/usecases/operations.js +5 -4
  624. package/dist/core/usecases/operations.js.map +1 -1
  625. package/dist/core/usecases/service/flows.js +9 -6
  626. package/dist/core/usecases/service/flows.js.map +1 -1
  627. package/dist/core/usecases/suites/participants.js +83 -0
  628. package/dist/core/usecases/suites/participants.js.map +1 -0
  629. package/dist/core/usecases/suites/suites.js +45 -0
  630. package/dist/core/usecases/suites/suites.js.map +1 -0
  631. package/dist/core/verify/answers.js +12 -5
  632. package/dist/core/verify/answers.js.map +1 -1
  633. package/dist/core/verify/build.js +102 -82
  634. package/dist/core/verify/build.js.map +1 -1
  635. package/dist/core/verify/checklist.js +45 -10
  636. package/dist/core/verify/checklist.js.map +1 -1
  637. package/dist/core/verify/location.js +67 -0
  638. package/dist/core/verify/location.js.map +1 -0
  639. package/dist/core/verify/pins/lint.js +3 -2
  640. package/dist/core/verify/pins/lint.js.map +1 -1
  641. package/dist/core/verify/pins/pin.js +1 -1
  642. package/dist/core/verify/pins/pin.js.map +1 -1
  643. package/dist/core/verify/read/file.js +224 -0
  644. package/dist/core/verify/read/file.js.map +1 -0
  645. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  646. package/dist/core/verify/read/shape.js.map +1 -0
  647. package/dist/core/verify/read/union.js +105 -0
  648. package/dist/core/verify/read/union.js.map +1 -0
  649. package/dist/core/verify/record.js +35 -12
  650. package/dist/core/verify/record.js.map +1 -1
  651. package/dist/core/verify/store/commit.js +40 -30
  652. package/dist/core/verify/store/commit.js.map +1 -1
  653. package/dist/core/verify/store/render.js +8 -4
  654. package/dist/core/verify/store/render.js.map +1 -1
  655. package/dist/core/vocabulary/issue.js +1 -0
  656. package/dist/core/vocabulary/issue.js.map +1 -1
  657. package/dist/core/vocabulary/maturity.js +53 -3
  658. package/dist/core/vocabulary/maturity.js.map +1 -1
  659. package/examples/README.md +28 -9
  660. package/examples/docs/architecture/landscape.likec4 +21 -7
  661. package/examples/docs/architecture/services.likec4 +70 -0
  662. package/examples/docs/architecture/subsystems.likec4 +3 -0
  663. package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
  664. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  665. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  666. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  667. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  668. package/examples/docs/services/order-service/model.likec4 +10 -8
  669. package/examples/docs/services/payment-service/model.likec4 +6 -4
  670. package/examples/docs/services/platform/identity-service/model.likec4 +9 -7
  671. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  672. package/examples/docs/services/platform/notification-service/model.likec4 +4 -5
  673. package/package.json +1 -1
  674. package/dist/commands/validate/checks/vocabulary.js +0 -16
  675. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  676. package/dist/core/agent/agents-md/artifacts.js +0 -221
  677. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  678. package/dist/core/agent/agents-md/cycle.js +0 -180
  679. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  680. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  681. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  682. package/dist/core/agent/agents-md/spine.js +0 -40
  683. package/dist/core/agent/agents-md/spine.js.map +0 -1
  684. package/dist/core/coherence/events/merged.js +0 -40
  685. package/dist/core/coherence/events/merged.js.map +0 -1
  686. package/dist/core/diff/current-state.js +0 -96
  687. package/dist/core/diff/current-state.js.map +0 -1
  688. package/dist/core/diff/victims.js +0 -256
  689. package/dist/core/diff/victims.js.map +0 -1
  690. package/dist/core/staging/snapshot.js.map +0 -1
  691. package/dist/core/verify/file.js.map +0 -1
@@ -1,40 +0,0 @@
1
- /**
2
- * Which element in the C4 model IS which service — the binding rule, and the
3
- * two-way completeness check `loam validate --all` runs over `services/`.
4
- *
5
- * This module used to carry the whole ID spine: every join between the
6
- * artifacts, the `Based-On:` baseline pins, how to draw a shared broker, and
7
- * how to declare a message produced outside the fleet. Those are now the
8
- * `loam-spine` reference page (../workflows/reference/spine.ts), printed by
9
- * `loam instructions loam-spine` — grammars a reader consults while writing one
10
- * document, in a file that was being truncated by two hosts before it reached
11
- * them (./command-map.ts's header has the measurement).
12
- *
13
- * THIS section stayed, and the line between it and the page it lost is worth
14
- * saying: an agent cannot read the landscape at all without knowing that a box's
15
- * title is its service id unless `metadata { service }` says otherwise. It is
16
- * orientation, not reference — it is needed to form the question, not to answer
17
- * it.
18
- *
19
- * One section of the AGENTS.md template. ../agents-md.ts assembles the
20
- * document by PLAIN CONCATENATION — no join separator — so every section
21
- * starts at the first character of its opening line and ends with the newline
22
- * that closes its last one. Keep that shape when editing, or two sections glue
23
- * onto one line in every docs repo loam scaffolds from now on.
24
- */
25
- export const SPINE = `## Which element IS which service
26
-
27
- An element says which service it is with \`metadata { service 'payment-service' }\`.
28
- Without one, its **title** is used instead — which is what most of this repo relies
29
- on, and also the trap: rename a box in a diagram and every check joining it to
30
- \`services/<svc>/\` silently stops matching. Bind an element whenever its title is
31
- not exactly the directory name, and prefer binding over renaming a directory.
32
-
33
- \`services/\` is the list of services — there is no manifest, and none should be
34
- added. \`loam validate --all\` compares that list to the landscape both ways: a
35
- directory nothing draws is an error, and an element with no directory is a warning.
36
- Systems that are not ours — kafka, a payment gateway — carry \`#external\`, which
37
- says so once and stops the warning for good.
38
-
39
- `;
40
- //# sourceMappingURL=spine.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"spine.js","sourceRoot":"","sources":["../../../../src/core/agent/agents-md/spine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG;;;;;;;;;;;;;;CAcpB,CAAC","sourcesContent":["/**\n * Which element in the C4 model IS which service — the binding rule, and the\n * two-way completeness check `loam validate --all` runs over `services/`.\n *\n * This module used to carry the whole ID spine: every join between the\n * artifacts, the `Based-On:` baseline pins, how to draw a shared broker, and\n * how to declare a message produced outside the fleet. Those are now the\n * `loam-spine` reference page (../workflows/reference/spine.ts), printed by\n * `loam instructions loam-spine` — grammars a reader consults while writing one\n * document, in a file that was being truncated by two hosts before it reached\n * them (./command-map.ts's header has the measurement).\n *\n * THIS section stayed, and the line between it and the page it lost is worth\n * saying: an agent cannot read the landscape at all without knowing that a box's\n * title is its service id unless `metadata { service }` says otherwise. It is\n * orientation, not reference — it is needed to form the question, not to answer\n * it.\n *\n * One section of the AGENTS.md template. ../agents-md.ts assembles the\n * document by PLAIN CONCATENATION — no join separator — so every section\n * starts at the first character of its opening line and ends with the newline\n * that closes its last one. Keep that shape when editing, or two sections glue\n * onto one line in every docs repo loam scaffolds from now on.\n */\nexport const SPINE = `## Which element IS which service\n\nAn element says which service it is with \\`metadata { service 'payment-service' }\\`.\nWithout one, its **title** is used instead — which is what most of this repo relies\non, and also the trap: rename a box in a diagram and every check joining it to\n\\`services/<svc>/\\` silently stops matching. Bind an element whenever its title is\nnot exactly the directory name, and prefer binding over renaming a directory.\n\n\\`services/\\` is the list of services — there is no manifest, and none should be\nadded. \\`loam validate --all\\` compares that list to the landscape both ways: a\ndirectory nothing draws is an error, and an element with no directory is a warning.\nSystems that are not ours — kafka, a payment gateway — carry \\`#external\\`, which\nsays so once and stops the warning for good.\n\n`;\n"]}
@@ -1,40 +0,0 @@
1
- /**
2
- * The contract as the feature would leave it: the REAL merge
3
- * (core/asyncapi/merge/merge.ts) run over the two texts and read back,
4
- * nothing written. One simulation feeds two gate questions — the
5
- * `declares()` join in ./events.ts (a retired or edited-away send must stop
6
- * answering "the service declares this" BEFORE archive, or a leftover
7
- * `Publishes:` line passes the gate and breaks the post-archive fleet) and
8
- * the consumer gate's wire half (a channel or operation removal that stops
9
- * the service SENDING a message the fleet still consumes).
10
- *
11
- * Calling the merge rather than re-deriving its verdicts is the point: a
12
- * second spelling of quote/edit/removal here would eventually disagree with
13
- * the merge about what archive actually writes, and the gate would be
14
- * promising a contract the archive does not produce.
15
- */
16
- import { parse } from "yaml";
17
- import { asyncapiDocOf } from "../../asyncapi/read.js";
18
- import { AsyncapiMergeError } from "../../asyncapi/merge/error.js";
19
- import { mergeAsyncapiSlots } from "../../asyncapi/merge/merge.js";
20
- /**
21
- * Simulate the archive-time merge, or stand down. Both documents are known
22
- * readable here (the walk in ./declared.ts checked), so the only refusals
23
- * left are the merge's own judgement calls — an aliased section, a section
24
- * spelling one slot key twice. `undefined` on those rather than a throw: the
25
- * caller falls back to the feature ∪ living union view, which can only be
26
- * too lenient, and the archive's own plan will surface the refusal by name.
27
- */
28
- export function mergedAsyncapiDoc(input) {
29
- const { livingText, livingDoc, featureText, service } = input;
30
- try {
31
- const out = mergeAsyncapiSlots(livingText, featureText, service);
32
- return out.text === null ? livingDoc : asyncapiDocOf(parse(out.text));
33
- }
34
- catch (error) {
35
- if (error instanceof AsyncapiMergeError)
36
- return undefined;
37
- throw error;
38
- }
39
- }
40
- //# sourceMappingURL=merged.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"merged.js","sourceRoot":"","sources":["../../../../src/core/coherence/events/merged.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAWnE;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAA2B;IAC3D,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;IAC9D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,kBAAkB,CAAC,UAAU,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;QACjE,OAAO,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IACxE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,kBAAkB;YAAE,OAAO,SAAS,CAAC;QAC1D,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["/**\n * The contract as the feature would leave it: the REAL merge\n * (core/asyncapi/merge/merge.ts) run over the two texts and read back,\n * nothing written. One simulation feeds two gate questions — the\n * `declares()` join in ./events.ts (a retired or edited-away send must stop\n * answering \"the service declares this\" BEFORE archive, or a leftover\n * `Publishes:` line passes the gate and breaks the post-archive fleet) and\n * the consumer gate's wire half (a channel or operation removal that stops\n * the service SENDING a message the fleet still consumes).\n *\n * Calling the merge rather than re-deriving its verdicts is the point: a\n * second spelling of quote/edit/removal here would eventually disagree with\n * the merge about what archive actually writes, and the gate would be\n * promising a contract the archive does not produce.\n */\nimport { parse } from \"yaml\";\nimport { asyncapiDocOf } from \"../../asyncapi/read.js\";\nimport type { AsyncapiDoc } from \"../../asyncapi/model.js\";\nimport { AsyncapiMergeError } from \"../../asyncapi/merge/error.js\";\nimport { mergeAsyncapiSlots } from \"../../asyncapi/merge/merge.js\";\n\n/** The two texts the simulation merges, plus the living side's existing parse. */\nexport interface MergeSimulationInput {\n livingText: string;\n /** `readAsyncapi` of livingText — reused verbatim when the merge writes nothing. */\n livingDoc: AsyncapiDoc;\n featureText: string;\n service: string;\n}\n\n/**\n * Simulate the archive-time merge, or stand down. Both documents are known\n * readable here (the walk in ./declared.ts checked), so the only refusals\n * left are the merge's own judgement calls — an aliased section, a section\n * spelling one slot key twice. `undefined` on those rather than a throw: the\n * caller falls back to the feature ∪ living union view, which can only be\n * too lenient, and the archive's own plan will surface the refusal by name.\n */\nexport function mergedAsyncapiDoc(input: MergeSimulationInput): AsyncapiDoc | undefined {\n const { livingText, livingDoc, featureText, service } = input;\n try {\n const out = mergeAsyncapiSlots(livingText, featureText, service);\n return out.text === null ? livingDoc : asyncapiDocOf(parse(out.text));\n } catch (error) {\n if (error instanceof AsyncapiMergeError) return undefined;\n throw error;\n }\n}\n"]}
@@ -1,96 +0,0 @@
1
- /**
2
- * The working-tree side of `loam diff`: the fleet as it stands on disk, read
3
- * through the invocation's `FleetContext` into the SAME `ServiceState` shape
4
- * `./base-state.ts` builds from git — one shape, so `./semantic.ts` joins the
5
- * two sides without caring which one came from the object store.
6
- *
7
- * Containment discipline, per subject and per axis: an artifact that EXISTS
8
- * but cannot be read (non-UTF-8 spec bytes, a directory sitting where a file
9
- * belongs, broken contract YAML) degrades that one axis to `unreadable` with
10
- * its path — never a thrown enumeration, never an empty list a diff would
11
- * read as "everything was removed". Absence stays absence: a file deleted
12
- * between the existence probe and the read is classified by its errno
13
- * (ENOENT), the same TOCTOU discipline `loadConfig` documents.
14
- */
15
- import { existsSync } from "node:fs";
16
- import { repoPath } from "../envelope/json.js";
17
- import { servicePathsAt } from "../repo/paths.js";
18
- import { inOrder } from "../kernel/concurrency.js";
19
- function readFailure(e, rel) {
20
- const errno = e;
21
- if (errno.code === "ENOENT")
22
- return { kind: "absent" };
23
- return { kind: "unreadable", path: rel, error: e instanceof Error ? e.message : String(e) };
24
- }
25
- /** One spec-axis file (`spec.md` / `arch.spec.md`) as parsed requirements. */
26
- async function requirementsAxis(context, abs, rel) {
27
- if (!existsSync(abs))
28
- return { kind: "absent" };
29
- try {
30
- return { kind: "read", value: await context.readRequirements(abs) };
31
- }
32
- catch (e) {
33
- // decodeDocument's non-UTF-8 refusal lands here too: bytes that would
34
- // parse as zero requirements must suspend the axis, not empty it.
35
- return readFailure(e, rel);
36
- }
37
- }
38
- async function openapiAxis(context, abs, rel) {
39
- if (!existsSync(abs))
40
- return { kind: "absent" };
41
- try {
42
- const doc = await context.readOpenapi(abs);
43
- return doc.unreadable
44
- ? { kind: "unreadable", path: rel, error: doc.error ?? "not a readable OpenAPI document" }
45
- : { kind: "read", value: doc.ops.filter((op) => !op.remove) };
46
- }
47
- catch (e) {
48
- return readFailure(e, rel);
49
- }
50
- }
51
- async function asyncapiAxis(context, abs, rel) {
52
- if (!existsSync(abs))
53
- return { kind: "absent" };
54
- try {
55
- const doc = await context.readAsyncapi(abs);
56
- return doc.unreadable
57
- ? { kind: "unreadable", path: rel, error: doc.error ?? "not a readable AsyncAPI document" }
58
- : { kind: "read", value: doc.messages.filter((m) => m.remove !== true) };
59
- }
60
- catch (e) {
61
- return readFailure(e, rel);
62
- }
63
- }
64
- /**
65
- * The current fleet, one `ServiceState` per enumerated service. The
66
- * enumeration itself (the tree walk) may throw `DocsRepoUnavailableError` —
67
- * that is the whole-repo refusal `docsRepoReady` fronts, not a per-subject
68
- * degradation, and it stays a throw for the command layer to map.
69
- */
70
- export async function readCurrentFleet(docsDir, context) {
71
- const entries = await context.listServices(docsDir);
72
- const states = await inOrder(entries, async (entry) => {
73
- const paths = servicePathsAt(entry.dir);
74
- return {
75
- id: entry.id,
76
- dir: repoPath(docsDir, entry.dir),
77
- spec: await requirementsAxis(context, paths.spec, repoPath(docsDir, paths.spec)),
78
- archSpec: await requirementsAxis(context, paths.archSpec, repoPath(docsDir, paths.archSpec)),
79
- openapi: await openapiAxis(context, paths.openapi, repoPath(docsDir, paths.openapi)),
80
- asyncapi: await asyncapiAxis(context, paths.asyncapi, repoPath(docsDir, paths.asyncapi)),
81
- };
82
- });
83
- const ambiguous = currentCollisions(states);
84
- return {
85
- services: new Map(states.filter((s) => !ambiguous.has(s.id)).map((s) => [s.id, s])),
86
- ambiguous,
87
- };
88
- }
89
- /** Ids claimed by more than one enumerated directory, with every claimant named. */
90
- function currentCollisions(states) {
91
- const byId = new Map();
92
- for (const s of states)
93
- byId.set(s.id, [...(byId.get(s.id) ?? []), s.dir]);
94
- return new Map([...byId].filter(([, dirs]) => dirs.length > 1));
95
- }
96
- //# sourceMappingURL=current-state.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"current-state.js","sourceRoot":"","sources":["../../../src/core/diff/current-state.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The working-tree side of `loam diff`: the fleet as it stands on disk, read\n * through the invocation's `FleetContext` into the SAME `ServiceState` shape\n * `./base-state.ts` builds from git — one shape, so `./semantic.ts` joins the\n * two sides without caring which one came from the object store.\n *\n * Containment discipline, per subject and per axis: an artifact that EXISTS\n * but cannot be read (non-UTF-8 spec bytes, a directory sitting where a file\n * belongs, broken contract YAML) degrades that one axis to `unreadable` with\n * its path — never a thrown enumeration, never an empty list a diff would\n * read as \"everything was removed\". Absence stays absence: a file deleted\n * between the existence probe and the read is classified by its errno\n * (ENOENT), the same TOCTOU discipline `loadConfig` documents.\n */\nimport { existsSync } from \"node:fs\";\nimport type { Requirement } from \"../document/spec.js\";\nimport type { Operation } from \"../openapi/doc.js\";\nimport type { EventMessage } from \"../asyncapi/model.js\";\nimport { repoPath } from \"../envelope/json.js\";\nimport { servicePathsAt } from \"../repo/paths.js\";\nimport { inOrder } from \"../kernel/concurrency.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport type { AxisState, ServiceState } from \"./base-state.js\";\n\nexport interface CurrentFleet {\n /** By service id, in the enumeration's (sorted-walk) order. Ids the `ambiguous` map claims are excluded. */\n services: Map<string, ServiceState>;\n /**\n * Ids more than one working-tree directory claims — `subsystem.name-collision`'s\n * live shape, which validate diagnoses. Diffing either claimant would pick a\n * winner nobody chose (a `new Map` collapse here used to keep the LAST one and\n * fabricate a whole change story about a service that never moved), so the\n * subject is suspended exactly as the base side's `ambiguous` map suspends it.\n */\n ambiguous: Map<string, string[]>;\n}\n\nfunction readFailure(e: unknown, rel: string): { kind: \"absent\" } | { kind: \"unreadable\"; path: string; error: string } {\n const errno = e as NodeJS.ErrnoException;\n if (errno.code === \"ENOENT\") return { kind: \"absent\" };\n return { kind: \"unreadable\", path: rel, error: e instanceof Error ? e.message : String(e) };\n}\n\n/** One spec-axis file (`spec.md` / `arch.spec.md`) as parsed requirements. */\nasync function requirementsAxis(context: FleetContext, abs: string, rel: string): Promise<AxisState<Requirement[]>> {\n if (!existsSync(abs)) return { kind: \"absent\" };\n try {\n return { kind: \"read\", value: await context.readRequirements(abs) };\n } catch (e) {\n // decodeDocument's non-UTF-8 refusal lands here too: bytes that would\n // parse as zero requirements must suspend the axis, not empty it.\n return readFailure(e, rel);\n }\n}\n\nasync function openapiAxis(context: FleetContext, abs: string, rel: string): Promise<AxisState<Operation[]>> {\n if (!existsSync(abs)) return { kind: \"absent\" };\n try {\n const doc = await context.readOpenapi(abs);\n return doc.unreadable\n ? { kind: \"unreadable\", path: rel, error: doc.error ?? \"not a readable OpenAPI document\" }\n : { kind: \"read\", value: doc.ops.filter((op) => !op.remove) };\n } catch (e) {\n return readFailure(e, rel);\n }\n}\n\nasync function asyncapiAxis(context: FleetContext, abs: string, rel: string): Promise<AxisState<EventMessage[]>> {\n if (!existsSync(abs)) return { kind: \"absent\" };\n try {\n const doc = await context.readAsyncapi(abs);\n return doc.unreadable\n ? { kind: \"unreadable\", path: rel, error: doc.error ?? \"not a readable AsyncAPI document\" }\n : { kind: \"read\", value: doc.messages.filter((m) => m.remove !== true) };\n } catch (e) {\n return readFailure(e, rel);\n }\n}\n\n/**\n * The current fleet, one `ServiceState` per enumerated service. The\n * enumeration itself (the tree walk) may throw `DocsRepoUnavailableError` —\n * that is the whole-repo refusal `docsRepoReady` fronts, not a per-subject\n * degradation, and it stays a throw for the command layer to map.\n */\nexport async function readCurrentFleet(docsDir: DocsDir, context: FleetContext): Promise<CurrentFleet> {\n const entries = await context.listServices(docsDir);\n const states = await inOrder(entries, async (entry): Promise<ServiceState> => {\n const paths = servicePathsAt(entry.dir);\n return {\n id: entry.id,\n dir: repoPath(docsDir, entry.dir),\n spec: await requirementsAxis(context, paths.spec, repoPath(docsDir, paths.spec)),\n archSpec: await requirementsAxis(context, paths.archSpec, repoPath(docsDir, paths.archSpec)),\n openapi: await openapiAxis(context, paths.openapi, repoPath(docsDir, paths.openapi)),\n asyncapi: await asyncapiAxis(context, paths.asyncapi, repoPath(docsDir, paths.asyncapi)),\n };\n });\n const ambiguous = currentCollisions(states);\n return {\n services: new Map(states.filter((s) => !ambiguous.has(s.id)).map((s) => [s.id, s])),\n ambiguous,\n };\n}\n\n/** Ids claimed by more than one enumerated directory, with every claimant named. */\nfunction currentCollisions(states: readonly ServiceState[]): Map<string, string[]> {\n const byId = new Map<string, string[]>();\n for (const s of states) byId.set(s.id, [...(byId.get(s.id) ?? []), s.dir]);\n return new Map([...byId].filter(([, dirs]) => dirs.length > 1));\n}\n"]}
@@ -1,256 +0,0 @@
1
- /**
2
- * Who, in the CURRENT fleet, still depends on a thing the diff says was
3
- * removed or deprecated — the joins that turn "an operation left the
4
- * contract" into "and these named consumers break".
5
- *
6
- * THREE SOURCES answer that, and only the first two are copies of anything.
7
- *
8
- * The first two are deliberately a SECOND copy of the consumer join the
9
- * coherence gate asks, built on the same shared primitives, and both copies
10
- * say so:
11
- *
12
- * - the operation half mirrors `core/coherence/lookups.ts`'s
13
- * `edgeConsumers`/`requirementConsumers` (landscape edges with
14
- * `metadata { op }` resolved through `serviceResolver` WITH the enumerated
15
- * fleet — the container lesson recorded there — plus other services'
16
- * living `Operations:` lines, spec.md only, exactly as that module scans);
17
- * - the message half mirrors `core/coherence/events/lookups.ts`'s
18
- * `messageConsumers` (every `consumes` edge naming the message except one
19
- * INTO the provider, plus other services' `Consumes:` lines over BOTH spec
20
- * axes — the outbox requirement lives in arch.spec.md as often as in
21
- * spec.md, that module's lesson).
22
- *
23
- * The copies cannot be one module today: coherence's lookups are closures
24
- * over a feature's `DeltaScope` and its lazy caches; this one is an index
25
- * over an already-read fleet. Rule 13 (docs/DESIGN.md) governs — a THIRD
26
- * copy of this join forces the shared extraction, and whoever changes the
27
- * victim strings or the scan rules in either module owes the other the same
28
- * change. The victim string spellings are kept byte-compatible with the
29
- * coherence findings on purpose, so a reader meets one vocabulary.
30
- *
31
- * The THIRD source is not a copy and does not fall under that rule: the fleet's
32
- * USE CASES, joined in `core/usecases/operations.ts`. A hop of a
33
- * capability-tagged `dynamic view` that the model attributes to the operation
34
- * being removed is a business flow that stops working, and saying so — "step 4
35
- * of Checkout breaks" — is the only one of the three that names a consequence a
36
- * human weighs rather than a document they then have to open. It has its own
37
- * module because it asks a different question of a different reader (an
38
- * attribution over the whole `architecture/` PROJECT, not a scan of one
39
- * landscape file), and because the attributed/contested distinction it turns on
40
- * belongs beside `attributeStep`, not beside a requirement scan. Coherence has
41
- * no equivalent to keep in step with, so nothing here owes it a mirror.
42
- */
43
- import { existsSync } from "node:fs";
44
- import {} from "../c4/likec4.js";
45
- import { serviceResolver } from "../c4/resolve/service.js";
46
- import { landscapePath } from "../repo/paths.js";
47
- import { readUseCases } from "../usecases/fleet.js";
48
- import { hopsConsuming, hopsExercising } from "../usecases/operations.js";
49
- /** The requirements one current service contributes to a scan, per axis rule. */
50
- function reqsOf(state, axes) {
51
- return axes.flatMap((axis) => {
52
- const a = state[axis];
53
- return a.kind === "read" ? a.value : [];
54
- });
55
- }
56
- const EDGE = (resolve, r) => `edge ${resolve(r.source)} → ${resolve(r.target)}${r.title === undefined ? "" : ` ("${r.title}")`}`;
57
- /** Entry comparator by map key — deterministic suspension order. */
58
- const byFirst = (a, b) => (a[0] < b[0] ? -1 : 1);
59
- export function victimIndex(snap) {
60
- // Lazy on purpose, the coherence lookups' own discipline: the landscape is
61
- // a full LikeC4 workspace spin-up, and a diff that removes nothing must not
62
- // pay for it. An absent landscape proves nothing; an unreadable one proves
63
- // nothing either way — `landscape.invalid` is validate's finding to make,
64
- // and inventing victims out of a parse error would point the reader at the
65
- // wrong file (core/coherence/lookups.ts's doctrine, verbatim).
66
- let drawn;
67
- const landscape = () => (drawn ??= (async () => {
68
- const path = landscapePath(snap.docsDir);
69
- const doc = existsSync(path) ? await snap.context.loadLikeC4(path) : null;
70
- const usable = doc !== null && doc.errors.length === 0 ? doc : null;
71
- // The enumerated fleet rides into the resolver so a landscape that
72
- // models CONTAINERS stays visible: without `known`, an edge into
73
- // `payment.api` resolves to a service called "api" that has never
74
- // existed and the join finds nobody — the exact defect DESIGN's
75
- // serviceResolver-known row records.
76
- return {
77
- usable,
78
- resolve: usable === null ? null : serviceResolver(usable.elements, new Set(snap.current.keys())),
79
- };
80
- })());
81
- // Sorted, so victim order never depends on the enumeration's walk order.
82
- const ids = [...snap.current.keys()].sort();
83
- /**
84
- * The use-case half, lazy for the same reason and one step further.
85
- *
86
- * `readUseCases` carries its own cheap gate — a byte scan for the reserved
87
- * tag prefix — so a fleet that declares no use case never starts LikeC4 here
88
- * at all, and a fleet that does pays for the `architecture/` project exactly
89
- * once per `loam diff`, at the first removal that asks. The fleet set is the
90
- * same one the landscape resolver above is built from, so the two halves
91
- * cannot disagree about which service an element stands for.
92
- */
93
- let flows;
94
- const useCases = () => (flows ??= readUseCases({ docsDir: snap.docsDir, known: new Set(snap.current.keys()) }));
95
- /**
96
- * The requirement half of one scan, `tupleVisible`'s own discipline applied
97
- * to victims: an unreadable consumer axis contributes a SUSPENSION, never a
98
- * silent nothing — "consuming nothing" and "loam refused to read the file"
99
- * are opposite facts, and collapsing them is how a breaking removal used to
100
- * grade down to a confident warning.
101
- */
102
- const scanRequirements = (unit) => {
103
- const out = { victims: [], suspended: [] };
104
- for (const other of ids.filter((id) => id !== unit.provider)) {
105
- const state = snap.current.get(other);
106
- for (const axis of unit.axes) {
107
- const a = state[axis];
108
- if (a.kind === "unreadable") {
109
- out.suspended.push(`${other} — ${a.path} could not be read, so its requirements could not be scanned`);
110
- continue;
111
- }
112
- if (a.kind !== "read")
113
- continue;
114
- for (const r of a.value)
115
- if (unit.hit(r))
116
- out.victims.push(`${other}'s living requirement '${r.name}'`);
117
- }
118
- }
119
- for (const [id, dirs] of [...snap.ambiguous].sort(byFirst)) {
120
- if (id !== unit.provider)
121
- out.suspended.push(`${id} — claimed by ${dirs.join(" and ")}, so its documents could not be scanned`);
122
- }
123
- return out;
124
- };
125
- const opConsumers = async (provider, op) => {
126
- const { usable, resolve } = await landscape();
127
- const scan = scanRequirements({ provider, axes: ["spec"], hit: (r) => r.operations.includes(op) });
128
- const edges = [];
129
- if (usable !== null && resolve !== null) {
130
- for (const r of usable.relationships) {
131
- // An edge whose SOURCE resolves to the provider as well is the
132
- // provider's own internal call — the rule `usecase.step-unlinked`
133
- // already states, that a service owes no operationId to itself — so it
134
- // is not a consumer of the operation it is losing. Without this the
135
- // downgrade the message half at :198 has always made was unreachable
136
- // for any service that draws its own containers: one internal edge
137
- // carrying the op kept every removal `diff.op-removed-consumed`
138
- // (verification 2026-09-04, R3).
139
- if (r.op === op && resolve(r.target) === provider && resolve(r.source) !== provider) {
140
- edges.push(EDGE(resolve, r));
141
- }
142
- }
143
- }
144
- // Flows last in each list, deliberately: the edge and the requirement say
145
- // WHICH document to open, and the flow says what the reader is about to
146
- // break — so a `details[]` scanned top-down ends on the consequence.
147
- const hops = hopsExercising(await useCases(), { provider, op });
148
- return {
149
- victims: [...edges, ...scan.victims, ...hops.breaks],
150
- suspended: [...scan.suspended, ...hops.unsure],
151
- };
152
- };
153
- const messageConsumers = async (provider, name) => {
154
- const { usable, resolve } = await landscape();
155
- const scan = scanRequirements({ provider, axes: ["spec", "archSpec"], hit: (r) => r.consumes.includes(name) });
156
- const edges = [];
157
- if (usable !== null && resolve !== null) {
158
- for (const r of usable.relationships) {
159
- // The message join is fleet-global, so every edge naming it counts —
160
- // except one into the provider itself, whose consumption leaves with
161
- // the same change (events/lookups.ts's exclusion, verbatim).
162
- if (r.consumes === name && resolve(r.target) !== provider)
163
- edges.push(EDGE(resolve, r));
164
- }
165
- }
166
- const hops = hopsConsuming(await useCases(), { provider, name });
167
- return { victims: [...edges, ...scan.victims, ...hops], suspended: scan.suspended };
168
- };
169
- const declarers = (name) => ids.filter((id) => {
170
- const axis = snap.current.get(id).asyncapi;
171
- return axis.kind === "read" && axis.value.some((m) => m.name === name);
172
- });
173
- return { opConsumers, messageConsumers, declarers };
174
- }
175
- /** The map key a tuple is joined on between the two states. */
176
- function tupleKey(t) {
177
- return `${t.kind}\0${t.consumer}\0${t.provider}\0${t.token}`;
178
- }
179
- /**
180
- * Every cross-service join one state holds, keyed for the base/current join.
181
- * Axis rules mirror the two consumer scans above: `Operations:` from spec.md
182
- * only, `Consumes:` from both spec axes. Unreadable axes simply contribute
183
- * nothing here — `semantic.ts` guards the GRADING so a join is never called
184
- * added or removed off a side nobody could read.
185
- */
186
- export function consumerTuples(states) {
187
- const providersOf = (pick) => {
188
- const map = new Map();
189
- for (const id of [...states.keys()].sort()) {
190
- for (const token of pick(states.get(id)))
191
- map.set(token, [...(map.get(token) ?? []), id]);
192
- }
193
- return map;
194
- };
195
- const opProviders = providersOf((s) => (s.openapi.kind === "read" ? s.openapi.value.map((op) => op.id) : []));
196
- const msgProviders = providersOf((s) => (s.asyncapi.kind === "read" ? s.asyncapi.value.map((m) => m.name) : []));
197
- const out = new Map();
198
- const add = (t) => {
199
- out.set(tupleKey(t), t);
200
- };
201
- for (const consumer of [...states.keys()].sort()) {
202
- const state = states.get(consumer);
203
- for (const r of reqsOf(state, ["spec"])) {
204
- for (const op of r.operations) {
205
- for (const p of opProviders.get(op) ?? [])
206
- if (p !== consumer)
207
- add({ kind: "op", consumer, provider: p, token: op });
208
- }
209
- }
210
- for (const r of reqsOf(state, ["spec", "archSpec"])) {
211
- for (const name of r.consumes) {
212
- for (const p of msgProviders.get(name) ?? [])
213
- if (p !== consumer)
214
- add({ kind: "message", consumer, provider: p, token: name });
215
- }
216
- }
217
- }
218
- return out;
219
- }
220
- /** May a tuple be graded off this side? Absence answers; unreadable does not; an ambiguous base identity answers nothing. */
221
- function tupleVisible(states, ambiguous, t) {
222
- if (ambiguous.has(t.consumer) || ambiguous.has(t.provider))
223
- return false;
224
- const readable = (id, axes) => {
225
- const state = states.get(id);
226
- return state === undefined || axes.every((axis) => state[axis].kind !== "unreadable");
227
- };
228
- return (readable(t.consumer, t.kind === "op" ? ["spec"] : ["spec", "archSpec"]) &&
229
- readable(t.provider, [t.kind === "op" ? "openapi" : "asyncapi"]));
230
- }
231
- /**
232
- * The joins that appeared or went away between base and current, sorted for
233
- * deterministic output (provider, kind, token, consumer). The visibility
234
- * guard is the containment rule: a join is never called "added" off a base
235
- * side nobody could read, nor "removed" off an unreadable working file — the
236
- * suspended axis already carries that fact.
237
- */
238
- export function consumerJoinChanges(input) {
239
- const baseTuples = consumerTuples(input.base);
240
- const currentTuples = consumerTuples(input.current);
241
- const graded = [];
242
- for (const [key, t] of currentTuples) {
243
- if (!baseTuples.has(key) && tupleVisible(input.base, input.baseAmbiguous, t))
244
- graded.push({ t, change: "added" });
245
- }
246
- for (const [key, t] of baseTuples) {
247
- if (!currentTuples.has(key) && tupleVisible(input.current, input.currentAmbiguous, t))
248
- graded.push({ t, change: "removed" });
249
- }
250
- return graded.sort((a, b) => {
251
- const ka = `${a.t.provider}\0${a.t.kind}\0${a.t.token}\0${a.t.consumer}\0${a.change}`;
252
- const kb = `${b.t.provider}\0${b.t.kind}\0${b.t.token}\0${b.t.consumer}\0${b.change}`;
253
- return ka < kb ? -1 : 1;
254
- });
255
- }
256
- //# sourceMappingURL=victims.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"victims.js","sourceRoot":"","sources":["../../../src/core/diff/victims.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Who, in the CURRENT fleet, still depends on a thing the diff says was\n * removed or deprecated — the joins that turn \"an operation left the\n * contract\" into \"and these named consumers break\".\n *\n * THREE SOURCES answer that, and only the first two are copies of anything.\n *\n * The first two are deliberately a SECOND copy of the consumer join the\n * coherence gate asks, built on the same shared primitives, and both copies\n * say so:\n *\n * - the operation half mirrors `core/coherence/lookups.ts`'s\n * `edgeConsumers`/`requirementConsumers` (landscape edges with\n * `metadata { op }` resolved through `serviceResolver` WITH the enumerated\n * fleet — the container lesson recorded there — plus other services'\n * living `Operations:` lines, spec.md only, exactly as that module scans);\n * - the message half mirrors `core/coherence/events/lookups.ts`'s\n * `messageConsumers` (every `consumes` edge naming the message except one\n * INTO the provider, plus other services' `Consumes:` lines over BOTH spec\n * axes — the outbox requirement lives in arch.spec.md as often as in\n * spec.md, that module's lesson).\n *\n * The copies cannot be one module today: coherence's lookups are closures\n * over a feature's `DeltaScope` and its lazy caches; this one is an index\n * over an already-read fleet. Rule 13 (docs/DESIGN.md) governs — a THIRD\n * copy of this join forces the shared extraction, and whoever changes the\n * victim strings or the scan rules in either module owes the other the same\n * change. The victim string spellings are kept byte-compatible with the\n * coherence findings on purpose, so a reader meets one vocabulary.\n *\n * The THIRD source is not a copy and does not fall under that rule: the fleet's\n * USE CASES, joined in `core/usecases/operations.ts`. A hop of a\n * capability-tagged `dynamic view` that the model attributes to the operation\n * being removed is a business flow that stops working, and saying so — \"step 4\n * of Checkout breaks\" — is the only one of the three that names a consequence a\n * human weighs rather than a document they then have to open. It has its own\n * module because it asks a different question of a different reader (an\n * attribution over the whole `architecture/` PROJECT, not a scan of one\n * landscape file), and because the attributed/contested distinction it turns on\n * belongs beside `attributeStep`, not beside a requirement scan. Coherence has\n * no equivalent to keep in step with, so nothing here owes it a mirror.\n */\nimport { existsSync } from \"node:fs\";\nimport { type LoadedDoc, type Rel } from \"../c4/likec4.js\";\nimport { serviceResolver } from \"../c4/resolve/service.js\";\nimport { landscapePath } from \"../repo/paths.js\";\nimport { readUseCases, type UseCaseScan } from \"../usecases/fleet.js\";\nimport { hopsConsuming, hopsExercising } from \"../usecases/operations.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\nimport type { FleetContext } from \"../fleet-context.js\";\nimport type { Requirement } from \"../document/spec.js\";\nimport type { ServiceState } from \"./base-state.js\";\n\n/**\n * One consumer scan's answer, honest about its own coverage. `victims` is\n * positive evidence; `suspended` names the consumers whose relevant document\n * could not be read (or whose identity is ambiguous), because a scan that\n * skipped a file loam REFUSED to read must never let the caller assert\n * \"nobody names it\" — the fail-open shape this type exists to forbid.\n */\nexport interface ConsumerScan {\n victims: string[];\n suspended: string[];\n}\n\n/** The current-state consumer questions `semantic.ts` asks per removal. */\nexport interface VictimIndex {\n /** Current consumers of `provider`'s operation `op`: landscape edges into the provider, then other services' living requirements, then the use-case hops the model attributes to it (contested hops ride as suspensions). */\n opConsumers(provider: string, op: string): Promise<ConsumerScan>;\n /** Current consumers of message `name` a `provider` is dropping: consumes-edges (not into the provider), then other services' `Consumes:` lines over both spec axes, then the use-case hops backed by such an edge. */\n messageConsumers(provider: string, name: string): Promise<ConsumerScan>;\n /** Current services whose asyncapi declares `name` — positive evidence only; a message still declared elsewhere is not orphaned by one producer dropping it. */\n declarers(name: string): string[];\n}\n\n/** The requirements one current service contributes to a scan, per axis rule. */\nfunction reqsOf(state: ServiceState, axes: readonly (\"spec\" | \"archSpec\")[]): Requirement[] {\n return axes.flatMap((axis) => {\n const a = state[axis];\n return a.kind === \"read\" ? a.value : [];\n });\n}\n\nconst EDGE = (resolve: (id: string) => string, r: Rel): string =>\n `edge ${resolve(r.source)} → ${resolve(r.target)}${r.title === undefined ? \"\" : ` (\"${r.title}\")`}`;\n\n/** Entry comparator by map key — deterministic suspension order. */\nconst byFirst = <T>(a: [string, T], b: [string, T]): number => (a[0] < b[0] ? -1 : 1);\n\n/** The landscape half of the index, loaded at most once and only when a removal actually asks. */\ninterface Drawn {\n usable: LoadedDoc | null;\n resolve: ((id: string) => string) | null;\n}\n\nexport function victimIndex(snap: {\n docsDir: DocsDir;\n context: FleetContext;\n current: ReadonlyMap<string, ServiceState>;\n /** Ids the working tree claims twice (current-state.ts) — their documents cannot be scanned, so they ride as suspended consumers. */\n ambiguous: ReadonlyMap<string, string[]>;\n}): VictimIndex {\n // Lazy on purpose, the coherence lookups' own discipline: the landscape is\n // a full LikeC4 workspace spin-up, and a diff that removes nothing must not\n // pay for it. An absent landscape proves nothing; an unreadable one proves\n // nothing either way — `landscape.invalid` is validate's finding to make,\n // and inventing victims out of a parse error would point the reader at the\n // wrong file (core/coherence/lookups.ts's doctrine, verbatim).\n let drawn: Promise<Drawn> | undefined;\n const landscape = (): Promise<Drawn> =>\n (drawn ??= (async (): Promise<Drawn> => {\n const path = landscapePath(snap.docsDir);\n const doc: LoadedDoc | null = existsSync(path) ? await snap.context.loadLikeC4(path) : null;\n const usable = doc !== null && doc.errors.length === 0 ? doc : null;\n // The enumerated fleet rides into the resolver so a landscape that\n // models CONTAINERS stays visible: without `known`, an edge into\n // `payment.api` resolves to a service called \"api\" that has never\n // existed and the join finds nobody — the exact defect DESIGN's\n // serviceResolver-known row records.\n return {\n usable,\n resolve: usable === null ? null : serviceResolver(usable.elements, new Set(snap.current.keys())),\n };\n })());\n\n // Sorted, so victim order never depends on the enumeration's walk order.\n const ids = [...snap.current.keys()].sort();\n\n /**\n * The use-case half, lazy for the same reason and one step further.\n *\n * `readUseCases` carries its own cheap gate — a byte scan for the reserved\n * tag prefix — so a fleet that declares no use case never starts LikeC4 here\n * at all, and a fleet that does pays for the `architecture/` project exactly\n * once per `loam diff`, at the first removal that asks. The fleet set is the\n * same one the landscape resolver above is built from, so the two halves\n * cannot disagree about which service an element stands for.\n */\n let flows: Promise<UseCaseScan> | undefined;\n const useCases = (): Promise<UseCaseScan> =>\n (flows ??= readUseCases({ docsDir: snap.docsDir, known: new Set(snap.current.keys()) }));\n\n /**\n * The requirement half of one scan, `tupleVisible`'s own discipline applied\n * to victims: an unreadable consumer axis contributes a SUSPENSION, never a\n * silent nothing — \"consuming nothing\" and \"loam refused to read the file\"\n * are opposite facts, and collapsing them is how a breaking removal used to\n * grade down to a confident warning.\n */\n const scanRequirements = (unit: { provider: string; axes: readonly (\"spec\" | \"archSpec\")[]; hit: (r: Requirement) => boolean }): ConsumerScan => {\n const out: ConsumerScan = { victims: [], suspended: [] };\n for (const other of ids.filter((id) => id !== unit.provider)) {\n const state = snap.current.get(other)!;\n for (const axis of unit.axes) {\n const a = state[axis];\n if (a.kind === \"unreadable\") {\n out.suspended.push(`${other} — ${a.path} could not be read, so its requirements could not be scanned`);\n continue;\n }\n if (a.kind !== \"read\") continue;\n for (const r of a.value) if (unit.hit(r)) out.victims.push(`${other}'s living requirement '${r.name}'`);\n }\n }\n for (const [id, dirs] of [...snap.ambiguous].sort(byFirst)) {\n if (id !== unit.provider) out.suspended.push(`${id} — claimed by ${dirs.join(\" and \")}, so its documents could not be scanned`);\n }\n return out;\n };\n\n const opConsumers = async (provider: string, op: string): Promise<ConsumerScan> => {\n const { usable, resolve } = await landscape();\n const scan = scanRequirements({ provider, axes: [\"spec\"], hit: (r) => r.operations.includes(op) });\n const edges: string[] = [];\n if (usable !== null && resolve !== null) {\n for (const r of usable.relationships) {\n // An edge whose SOURCE resolves to the provider as well is the\n // provider's own internal call — the rule `usecase.step-unlinked`\n // already states, that a service owes no operationId to itself — so it\n // is not a consumer of the operation it is losing. Without this the\n // downgrade the message half at :198 has always made was unreachable\n // for any service that draws its own containers: one internal edge\n // carrying the op kept every removal `diff.op-removed-consumed`\n // (verification 2026-09-04, R3).\n if (r.op === op && resolve(r.target) === provider && resolve(r.source) !== provider) {\n edges.push(EDGE(resolve, r));\n }\n }\n }\n // Flows last in each list, deliberately: the edge and the requirement say\n // WHICH document to open, and the flow says what the reader is about to\n // break — so a `details[]` scanned top-down ends on the consequence.\n const hops = hopsExercising(await useCases(), { provider, op });\n return {\n victims: [...edges, ...scan.victims, ...hops.breaks],\n suspended: [...scan.suspended, ...hops.unsure],\n };\n };\n\n const messageConsumers = async (provider: string, name: string): Promise<ConsumerScan> => {\n const { usable, resolve } = await landscape();\n const scan = scanRequirements({ provider, axes: [\"spec\", \"archSpec\"], hit: (r) => r.consumes.includes(name) });\n const edges: string[] = [];\n if (usable !== null && resolve !== null) {\n for (const r of usable.relationships) {\n // The message join is fleet-global, so every edge naming it counts —\n // except one into the provider itself, whose consumption leaves with\n // the same change (events/lookups.ts's exclusion, verbatim).\n if (r.consumes === name && resolve(r.target) !== provider) edges.push(EDGE(resolve, r));\n }\n }\n const hops = hopsConsuming(await useCases(), { provider, name });\n return { victims: [...edges, ...scan.victims, ...hops], suspended: scan.suspended };\n };\n\n const declarers = (name: string): string[] =>\n ids.filter((id) => {\n const axis = snap.current.get(id)!.asyncapi;\n return axis.kind === \"read\" && axis.value.some((m) => m.name === name);\n });\n\n return { opConsumers, messageConsumers, declarers };\n}\n\n/**\n * One cross-service join: `consumer`'s living requirements name a `token` that\n * `provider`'s contract defines. The same \"who depends on what\" question the\n * index above answers per removal, asked here of a WHOLE state — base or\n * current — so `semantic.ts` can report joins that appeared or went away.\n */\nexport interface ConsumerTuple {\n kind: \"op\" | \"message\";\n consumer: string;\n provider: string;\n token: string;\n}\n\n/** The map key a tuple is joined on between the two states. */\nfunction tupleKey(t: ConsumerTuple): string {\n return `${t.kind}\\0${t.consumer}\\0${t.provider}\\0${t.token}`;\n}\n\n/**\n * Every cross-service join one state holds, keyed for the base/current join.\n * Axis rules mirror the two consumer scans above: `Operations:` from spec.md\n * only, `Consumes:` from both spec axes. Unreadable axes simply contribute\n * nothing here — `semantic.ts` guards the GRADING so a join is never called\n * added or removed off a side nobody could read.\n */\nexport function consumerTuples(states: ReadonlyMap<string, ServiceState>): Map<string, ConsumerTuple> {\n const providersOf = (pick: (s: ServiceState) => string[]): Map<string, string[]> => {\n const map = new Map<string, string[]>();\n for (const id of [...states.keys()].sort()) {\n for (const token of pick(states.get(id)!)) map.set(token, [...(map.get(token) ?? []), id]);\n }\n return map;\n };\n const opProviders = providersOf((s) => (s.openapi.kind === \"read\" ? s.openapi.value.map((op) => op.id) : []));\n const msgProviders = providersOf((s) => (s.asyncapi.kind === \"read\" ? s.asyncapi.value.map((m) => m.name) : []));\n const out = new Map<string, ConsumerTuple>();\n const add = (t: ConsumerTuple): void => {\n out.set(tupleKey(t), t);\n };\n for (const consumer of [...states.keys()].sort()) {\n const state = states.get(consumer)!;\n for (const r of reqsOf(state, [\"spec\"])) {\n for (const op of r.operations) {\n for (const p of opProviders.get(op) ?? []) if (p !== consumer) add({ kind: \"op\", consumer, provider: p, token: op });\n }\n }\n for (const r of reqsOf(state, [\"spec\", \"archSpec\"])) {\n for (const name of r.consumes) {\n for (const p of msgProviders.get(name) ?? []) if (p !== consumer) add({ kind: \"message\", consumer, provider: p, token: name });\n }\n }\n }\n return out;\n}\n\n/** May a tuple be graded off this side? Absence answers; unreadable does not; an ambiguous base identity answers nothing. */\nfunction tupleVisible(states: ReadonlyMap<string, ServiceState>, ambiguous: ReadonlySet<string>, t: ConsumerTuple): boolean {\n if (ambiguous.has(t.consumer) || ambiguous.has(t.provider)) return false;\n const readable = (id: string, axes: (\"spec\" | \"archSpec\" | \"openapi\" | \"asyncapi\")[]): boolean => {\n const state = states.get(id);\n return state === undefined || axes.every((axis) => state[axis].kind !== \"unreadable\");\n };\n return (\n readable(t.consumer, t.kind === \"op\" ? [\"spec\"] : [\"spec\", \"archSpec\"]) &&\n readable(t.provider, [t.kind === \"op\" ? \"openapi\" : \"asyncapi\"])\n );\n}\n\n/** One graded cross-service join change between the two states. */\nexport interface ConsumerChange {\n t: ConsumerTuple;\n change: \"added\" | \"removed\";\n}\n\n/**\n * The joins that appeared or went away between base and current, sorted for\n * deterministic output (provider, kind, token, consumer). The visibility\n * guard is the containment rule: a join is never called \"added\" off a base\n * side nobody could read, nor \"removed\" off an unreadable working file — the\n * suspended axis already carries that fact.\n */\nexport function consumerJoinChanges(input: {\n base: ReadonlyMap<string, ServiceState>;\n baseAmbiguous: ReadonlySet<string>;\n current: ReadonlyMap<string, ServiceState>;\n currentAmbiguous: ReadonlySet<string>;\n}): ConsumerChange[] {\n const baseTuples = consumerTuples(input.base);\n const currentTuples = consumerTuples(input.current);\n const graded: ConsumerChange[] = [];\n for (const [key, t] of currentTuples) {\n if (!baseTuples.has(key) && tupleVisible(input.base, input.baseAmbiguous, t)) graded.push({ t, change: \"added\" });\n }\n for (const [key, t] of baseTuples) {\n if (!currentTuples.has(key) && tupleVisible(input.current, input.currentAmbiguous, t)) graded.push({ t, change: \"removed\" });\n }\n return graded.sort((a, b) => {\n const ka = `${a.t.provider}\\0${a.t.kind}\\0${a.t.token}\\0${a.t.consumer}\\0${a.change}`;\n const kb = `${b.t.provider}\\0${b.t.kind}\\0${b.t.token}\\0${b.t.consumer}\\0${b.change}`;\n return ka < kb ? -1 : 1;\n });\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../../src/core/staging/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 { resolvePortableFileInsideLexically, UnsafePathError } from \"../kernel/path-safety.js\";\n// Every `isRecord` below guards a record loam itself wrote and is reading back\n// — the lock, the snapshot manifest — whose fields are asked for on the very\n// next line, inside a REFUSAL path. `JSON.parse` answers with any JSON value,\n// not with an object, and that is where a thrown `TypeError` costs the most: it\n// does not make the command fail safely, it replaces a designed refusal with\n// `internal`.\nimport { isRecord } from \"../kernel/records.js\";\nimport { isDigest, sha256, type StagedWrite } from \"./writes.js\";\nimport { message, quietRm } from \"./commit.js\";\n\n/** Where archive parks the pre-images, inside the feature directory so they travel with it — the module doc above says why. */\nexport const SNAPSHOT_DIR = \".loam-before\";\nexport const SNAPSHOT_MANIFEST = \"manifest.json\";\n/**\n * Bumped only when the layout changes in a way `unarchive` must refuse to guess\n * at. 2 added `before`: a version-1 snapshot says nothing about the bytes it\n * would RESTORE, so an edited pre-image read back as an intact one. 3 re-keys\n * `services/` entries by `(service, artifact)`: a literal path pins the service\n * directory where it stood on the day of the archive, and once the subsystem\n * tree lets it move — re-cutting is expected to be routine — an undo addressed\n * by path restores into a location that is gone. The service id never moves.\n */\nexport const SNAPSHOT_VERSION = 3;\n/**\n * Version 2 is still READ, forever: version-2 snapshots already sit inside\n * archived features, which carry them across loam upgrades, and each must keep\n * restoring — and keep being graded for staleness by a retried archive — under\n * its own literal-path rules. Only version 1 is refused (no restore digests).\n */\nexport const SNAPSHOT_COMPAT_VERSION = 2;\n\nexport interface SnapshotEntry {\n /**\n * Docs-repo-relative path, forward slashes, AS ARCHIVED. Load-bearing even\n * beside the key below: it names the pre-image under `.loam-before/files/`,\n * and it is the literal fallback when the id stops being enumerated.\n */\n path: string;\n /**\n * For a `services/` entry: the service id (the leaf directory name — the one\n * thing a re-cut never changes) and the artifact's path inside its directory,\n * resolved through the CURRENT enumeration at restore time. Absent on\n * landscape and `features/` entries, which do not move.\n */\n service?: string;\n artifact?: string;\n /** False when archive CREATED the file: restoring it means deleting it again. */\n existed: boolean;\n /** sha256 of what archive wrote, so unarchive can tell its own merge from later edits. */\n after: string;\n /**\n * sha256 of the pre-image beside this manifest — the bytes `unarchive` will\n * RESTORE. Null when archive created the file (there is no pre-image).\n * `after` only describes what archive wrote; while nothing described the\n * restore source, a pre-image edited in the archived feature directory — a\n * bad merge, a rebase, a stray editor — was written back verbatim and every\n * loam surface certified the result.\n */\n before: string | null;\n}\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\nexport interface SnapshotManifest {\n version: number;\n feature: string;\n dirName: string;\n archivedAt: string;\n files: SnapshotEntry[];\n}\n\nexport function snapshotDir(featureDir: string): string {\n return join(featureDir, SNAPSHOT_DIR);\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 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\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(dir, docsDir, request.serviceDirOf);\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 const rel = repoPath(docsDir, s.write.path);\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 const dest = join(dir, \"files\", 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 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 manifests are graded by the same staleness question over their\n * literal paths — refusing them as \"a different loam\" would turn every leftover\n * version-2 snapshot into a permanent archive refusal on upgrade.\n */\nasync function assertSnapshotReplaceable(\n dir: string,\n docsDir: string,\n serviceDirOf: (service: string) => string | null,\n): Promise<void> {\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 (\n (manifest.version !== SNAPSHOT_VERSION && manifest.version !== SNAPSHOT_COMPAT_VERSION) ||\n !Array.isArray(manifest.files)\n ) {\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 const now = existsSync(target) ? sha256(await readFile(target)) : 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(entry.path);\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 version-3 `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"]}