@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 +1 @@
1
- {"version":3,"file":"delta.js","sourceRoot":"","sources":["../../../src/core/deployment/delta.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A feature's own topology: `features/<FEAT>/deployment/<name>.likec4`.\n *\n * Until this slot existed a change that stood up a standby cluster had to be\n * written in two places at once — the requirement in the feature, the topology\n * edited straight into `architecture/` — because `delta.likec4` refuses a\n * `deployment { }` block and always will: that document re-declares the\n * landscape's own identifiers and carries its own `specification`, so it cannot\n * be staged beside the map in one LikeC4 project. A document that declares\n * NEITHER can, which is exactly why the use-case axis put its flows in files of\n * their own, and why this mirrors it rather than inventing a shape.\n *\n * WHAT MAKES IT WORK, and it was measured before the axis was written: LikeC4\n * `extend` resolves across documents of one project. A feature writes\n *\n * deployment {\n * extend eu {\n * dcC = datacenter 'DC-C' { k8sC = cluster 'cluster-c' { … } }\n * }\n * eu.dcA.k8sA -> eu.dcC.k8sC 'async replication'\n * }\n *\n * and the living region gains a datacenter with the living file untouched. So\n * the merge is a copy, not a splice, and `unarchive` needs nothing new — a\n * create is undone by deleting, which the snapshot manifest already records for\n * every write in the plan.\n *\n * CREATE-ONLY, for `core/usecases/delta/flows.ts`'s reasons rather than a\n * weaker version of them. A requirement delta merges INTO a living document and\n * two features editing one requirement would overwrite each other silently; a\n * topology document has no such algebra — the file is one block, the merge is a\n * whole-file copy, and there is nothing to merge partially. Rewriting a living\n * one through a feature would be a silent whole-file replacement with no pin to\n * collide on, where editing `architecture/<name>.likec4` directly in the same\n * pull request produces an ordinary git conflict a human resolves.\n *\n * ## Graded against the map its own merge would leave behind\n *\n * `ARCH-LOAM-FEATURE-CORPUS`, and this axis needs it more literally than the\n * use-case one does: a feature that stands a service up in a new cluster\n * declares that service in its `delta.likec4` and instances it here, so the\n * element the topology names exists ONLY post-merge. Read against the living\n * landscape it is unresolved, LikeC4 refuses the whole project, and loam would\n * report a hole against a file whose only fault is arriving with the change\n * that makes it true.\n *\n * The staging is `core/c4/project/staged.ts`, shared with the use-case slot\n * rather than copied — the two would have disagreed first about the merge\n * preview, which is the one thing both refusals rest on.\n */\nimport { existsSync } from \"node:fs\";\nimport { basename, join, relative, resolve } from \"node:path\";\nimport { architectureDir } from \"../c4/project/architecture.js\";\nimport { stageMergedProject } from \"../c4/project/staged.js\";\nimport { type LoadedDoc } from \"../c4/likec4.js\";\nimport type { ExtendingModel } from \"../c4/splice/contract.js\";\nimport { architectureDocuments } from \"../c4/project/documents.js\";\nimport { featureDeploymentDir } from \"../repo/authored/paths.js\";\nimport { reservedArchitectureName } from \"../repo/paths.js\";\nimport type { DocsDir, FeatureDir } from \"../kernel/ids/dirs.js\";\nimport { type Issue } from \"../vocabulary/issue.js\";\n\n/** One topology document a feature carries. */\nexport interface FeatureDeployment {\n /**\n * The path INSIDE the feature's `deployment/` directory, `/`-separated — the\n * id this axis has. Nesting is spelled by the tree, as it is for every other\n * authored tree in the repo.\n */\n rel: string;\n /** The absolute path of the authored file. */\n path: string;\n}\n\n/**\n * The topology documents one feature carries, ordered by path.\n *\n * Zero readdirs when the feature has no `deployment/` directory, which is every\n * feature in a fleet that has not adopted the axis: `architectureDocuments`\n * answers an absent directory with an empty list rather than a throw. That\n * directory's existence is the whole per-feature opt-in.\n */\nexport async function featureDeployments(featureDir: FeatureDir): Promise<FeatureDeployment[]> {\n const dir = featureDeploymentDir(featureDir);\n const paths = await architectureDocuments(dir);\n const base = resolve(dir);\n return paths.map((path) => ({ rel: relative(base, path).split(/[\\\\/]/).join(\"/\"), path }));\n}\n\n/**\n * The LIVING path a feature-local topology document addresses.\n *\n * Straight into `architecture/`, with no subdirectory of its own — unlike\n * `architecture/usecases/`, and the difference is the model rather than taste.\n * A `deployment { }` block is part of the fleet's ONE architecture project and\n * is read as such by every loader; filing it under a subdirectory would suggest\n * a second project, which is exactly the reading `subsystems.likec4` and\n * `landscape.likec4` already refuse by sitting at the top level together.\n *\n * `rel` is joined un-split, which `node:path` normalizes. What holds the join is\n * PROVENANCE: every `rel` here came out of an `architectureDocuments` walk, so\n * each component is a directory entry that exists. No caller takes one from argv.\n */\nexport function livingDeploymentPath(docsDir: DocsDir, rel: string): string {\n return join(architectureDir(docsDir), rel);\n}\n\n/**\n * The two things that can go wrong about a topology document's NAME, before\n * anything about its contents is asked.\n *\n * `deployment.doc-reserved` — the name is one loam owns inside `architecture/`.\n * The merge writes straight into that directory, unlike a flow, which lands\n * under `usecases/`, so `<name>` is free to collide with the living map, the\n * generated subsystem views, or the flow tree. `reservedArchitectureName`\n * carries which and why. It is asked FIRST and WITHOUT an existence test,\n * because two of the three fail silently when the target does not exist yet.\n *\n * `deployment.doc-exists` — a topology document this feature introduces that\n * the living `architecture/` already holds.\n *\n * An ERROR and never a warning-that-gates, for `usecase.flow-exists`'s reason:\n * there is no legal reading of it. The merge would replace an authored topology\n * wholesale, and no `--approve` makes that a different act than it is. Two\n * features introducing the same file is the case that makes it load-bearing\n * rather than pedantic — whichever archives first creates the living document,\n * and the second is told before it merges that its own file is the one to\n * delete.\n *\n * The collision is on the FILE, not on what is inside it. Two features may both\n * extend the same living region from two documents of their own and both\n * archive; that is the shape `extend` exists for, and refusing it would make the\n * slot useless for the fleet-wide change it was built for.\n */\nexport async function deploymentDeltaIssues(docsDir: DocsDir, featureDir: FeatureDir): Promise<Issue[]> {\n const docs = await featureDeployments(featureDir);\n const issues: Issue[] = [];\n // The reservation runs FIRST and independently of existence, because two of\n // the three names it refuses need not exist for the merge to be wrong. A\n // topology copied over `subsystems.likec4` is destroyed by the next\n // `loam subsystem sync` and is invisible to every project load until then —\n // the merge would report `created architecture/subsystems.likec4` and nothing\n // downstream would ever say another word about it.\n for (const doc of docs) {\n const reason = reservedArchitectureName(doc.rel);\n if (reason === null) continue;\n issues.push({\n severity: \"error\",\n code: \"deployment.doc-reserved\",\n subject: doc.rel,\n message:\n `features/<FEAT>/deployment/${doc.rel} would merge to architecture/${doc.rel}, and that name is loam's: ` +\n `${reason}. Rename the feature's document — its name is yours to choose, and \\`extend\\` is how it adds to ` +\n \"what a living document already declares.\",\n });\n }\n return issues.concat(\n docs\n .filter((doc) => reservedArchitectureName(doc.rel) === null)\n .filter((doc) => existsSync(livingDeploymentPath(docsDir, doc.rel)))\n .map((doc) => ({\n severity: \"error\" as const,\n code: \"deployment.doc-exists\" as const,\n subject: doc.rel,\n message:\n `architecture/${doc.rel} already exists, so this feature's topology would replace an authored one wholesale — ` +\n \"a feature-local deployment document INTRODUCES topology, it does not rewrite it. \" +\n `Delete features/<FEAT>/deployment/${doc.rel} and edit architecture/${doc.rel} directly in this same change, ` +\n \"where git produces an ordinary conflict if somebody else is editing it too. \" +\n `To ADD to what that file declares, write a document of another name that says \\`extend\\` — ` +\n `\\`${suggest(doc.rel)}\\` — which is what lets two features grow one region without touching each other.`,\n })),\n );\n}\n\n/** What the caller needs to grade this feature's topology against the merge preview. */\nexport interface DeploymentOverlayRequest {\n docsDir: DocsDir;\n featureDir: FeatureDir;\n /** The feature's id — the tag that selects the delta's own additions. */\n featureId: string;\n /**\n * The caller's memoised LikeC4 read, when it has one. A FUNCTION rather than\n * the `FleetContext` itself, for the reason the use-case overlay states: one\n * memoised read does not justify an edge from this package to\n * `core/fleet-context.ts`, and the edge would push it up a DAG level for\n * nothing.\n */\n load?: (path: string) => Promise<LoadedDoc>;\n /**\n * The fleet's extending models, for the merge preview — the SAME list\n * `loam archive` routes with. A preview computed without them holds interior\n * the archive puts in `services/<…>/model.likec4`, so a topology document\n * naming `<service-fqn>.<container>` reads clean here and against nothing\n * once it is living; `deployment.doc-invalid` is the gate that then fails\n * open (verification 2026-09-04, review F8).\n *\n * A THUNK for `load`'s reason and one more: the early return below is the\n * axis's per-feature opt-in, and a feature with no `deployment/` directory\n * must not pay a fleet enumeration to be told so.\n */\n models?: () => Promise<readonly ExtendingModel[]>;\n}\n\n/** Whether this feature's topology could be read at all, against the map its merge would leave. */\nexport type DeploymentOverlay = { kind: \"read\" } | { kind: \"unreadable\"; errors: string[] };\n\n/**\n * Parse this feature's topology documents over the post-merge map.\n *\n * A feature with no `deployment/` directory is `read` at the cost of one walk\n * over a directory that is not there — the axis's per-feature opt-in, and the\n * whole price a fleet that has not adopted it pays. Nothing is staged, nothing\n * is parsed, and no temp tree is made.\n *\n * The verdict is deliberately BINARY. The use-case overlay hands its caller the\n * views it read because the axis has questions about their content; this one\n * has none — a topology document is checked by being parseable against the map\n * the merge will write, and everything else about it is graded by\n * `loam validate --all` once it is living, exactly as the landscape is.\n */\nexport async function readFeatureDeployments(req: DeploymentOverlayRequest): Promise<DeploymentOverlay> {\n const docs = await featureDeployments(req.featureDir);\n if (docs.length === 0) return { kind: \"read\" };\n // AFTER the early return: the fleet read is paid only by a feature that\n // actually brings a topology document.\n const models = req.models === undefined ? undefined : await req.models();\n const staged = await stageMergedProject({\n docsDir: req.docsDir,\n featureDir: req.featureDir,\n featureId: req.featureId,\n documents: docs.map((doc) => ({ rel: doc.rel, path: doc.path })),\n ...(req.load === undefined ? {} : { load: req.load }),\n ...(models === undefined ? {} : { models }),\n });\n return staged.kind === \"unreadable\" ? { kind: \"unreadable\", errors: staged.errors } : { kind: \"read\" };\n}\n\n/** A free name near the colliding one, so the fix is a rename the author can copy. */\nfunction suggest(rel: string): string {\n const name = basename(rel).replace(/\\.likec4$/, \"\");\n const dir = rel.slice(0, rel.length - basename(rel).length);\n return `${dir}${name}-extend.likec4`;\n}\n"]}
1
+ {"version":3,"file":"delta.js","sourceRoot":"","sources":["../../../src/core/deployment/delta.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A feature's own topology: `features/<FEAT>/deployment/<name>.likec4`.\n *\n * Until this slot existed a change that stood up a standby cluster had to be\n * written in two places at once — the requirement in the feature, the topology\n * edited straight into `architecture/` — because `delta.likec4` refuses a\n * `deployment { }` block and always will: that document re-declares the\n * landscape's own identifiers and carries its own `specification`, so it cannot\n * be staged beside the map in one LikeC4 project. A document that declares\n * NEITHER can, which is exactly why the use-case axis put its flows in files of\n * their own, and why this mirrors it rather than inventing a shape.\n *\n * WHAT MAKES IT WORK, and it was measured before the axis was written: LikeC4\n * `extend` resolves across documents of one project. A feature writes\n *\n * deployment {\n * extend eu {\n * dcC = datacenter 'DC-C' { k8sC = cluster 'cluster-c' { … } }\n * }\n * eu.dcA.k8sA -> eu.dcC.k8sC 'async replication'\n * }\n *\n * and the living region gains a datacenter with the living file untouched. So\n * the merge is a copy, not a splice, and `unarchive` needs nothing new — a\n * create is undone by deleting, which the snapshot manifest already records for\n * every write in the plan.\n *\n * CREATE-ONLY, for `core/usecases/delta/flows.ts`'s reasons rather than a\n * weaker version of them. A requirement delta merges INTO a living document and\n * two features editing one requirement would overwrite each other silently; a\n * topology document has no such algebra — the file is one block, the merge is a\n * whole-file copy, and there is nothing to merge partially. Rewriting a living\n * one through a feature would be a silent whole-file replacement with no pin to\n * collide on, where editing `architecture/<name>.likec4` directly in the same\n * pull request produces an ordinary git conflict a human resolves.\n *\n * ## Graded against the map its own merge would leave behind\n *\n * `ARCH-LOAM-FEATURE-CORPUS`, and this axis needs it more literally than the\n * use-case one does: a feature that stands a service up in a new cluster\n * declares that service in its `delta.likec4` and instances it here, so the\n * element the topology names exists ONLY post-merge. Read against the living\n * landscape it is unresolved, LikeC4 refuses the whole project, and loam would\n * report a hole against a file whose only fault is arriving with the change\n * that makes it true.\n *\n * The staging is `core/c4/project/staged.ts`, shared with the use-case slot\n * rather than copied — the two would have disagreed first about the merge\n * preview, which is the one thing both refusals rest on.\n */\nimport { existsSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport { architectureDir } from \"../c4/project/architecture.js\";\nimport { stageMergedProject } from \"../c4/project/staged.js\";\nimport { type LoadedDoc } from \"../c4/likec4.js\";\nimport type { ExtendingModel } from \"../c4/splice/contract.js\";\nimport { relativeDocuments } from \"../c4/project/documents.js\";\nimport { featureDeploymentDir } from \"../repo/authored/paths.js\";\nimport { reservedArchitectureName } from \"../repo/paths.js\";\nimport type { DocsDir, FeatureDir } from \"../kernel/ids/dirs.js\";\nimport { type Issue } from \"../vocabulary/issue.js\";\n\n/** One topology document a feature carries. */\nexport interface FeatureDeployment {\n /**\n * The path INSIDE the feature's `deployment/` directory, `/`-separated — the\n * id this axis has. Nesting is spelled by the tree, as it is for every other\n * authored tree in the repo.\n */\n rel: string;\n /** The absolute path of the authored file. */\n path: string;\n}\n\n/**\n * The topology documents one feature carries, ordered by path.\n *\n * Zero readdirs when the feature has no `deployment/` directory, which is every\n * feature in a fleet that has not adopted the axis: `architectureDocuments`\n * answers an absent directory with an empty list rather than a throw. That\n * directory's existence is the whole per-feature opt-in.\n */\nexport async function featureDeployments(featureDir: FeatureDir): Promise<FeatureDeployment[]> {\n return relativeDocuments(featureDeploymentDir(featureDir));\n}\n\n/**\n * The LIVING path a feature-local topology document addresses.\n *\n * Straight into `architecture/`, with no subdirectory of its own — unlike\n * `architecture/usecases/`, and the difference is the model rather than taste.\n * A `deployment { }` block is part of the fleet's ONE architecture project and\n * is read as such by every loader; filing it under a subdirectory would suggest\n * a second project, which is exactly the reading `subsystems.likec4` and\n * `landscape.likec4` already refuse by sitting at the top level together.\n *\n * `rel` is joined un-split, which `node:path` normalizes. What holds the join is\n * PROVENANCE: every `rel` here came out of an `architectureDocuments` walk, so\n * each component is a directory entry that exists. No caller takes one from argv.\n */\nexport function livingDeploymentPath(docsDir: DocsDir, rel: string): string {\n return join(architectureDir(docsDir), rel);\n}\n\n/**\n * The two things that can go wrong about a topology document's NAME, before\n * anything about its contents is asked.\n *\n * `deployment.doc-reserved` — the name is one loam owns inside `architecture/`.\n * The merge writes straight into that directory, unlike a flow, which lands\n * under `usecases/`, so `<name>` is free to collide with the living map, either\n * of the two GENERATED views files (`subsystems.likec4` and `services.likec4`),\n * or the flow tree. `reservedArchitectureName` carries which and why. It is\n * asked FIRST and WITHOUT an existence test, because three of the four fail\n * silently when the target does not exist yet.\n *\n * `deployment.doc-exists` — a topology document this feature introduces that\n * the living `architecture/` already holds.\n *\n * An ERROR and never a warning-that-gates, for `usecase.flow-exists`'s reason:\n * there is no legal reading of it. The merge would replace an authored topology\n * wholesale, and no `--approve` makes that a different act than it is. Two\n * features introducing the same file is the case that makes it load-bearing\n * rather than pedantic — whichever archives first creates the living document,\n * and the second is told before it merges that its own file is the one to\n * delete.\n *\n * The collision is on the FILE, not on what is inside it. Two features may both\n * extend the same living region from two documents of their own and both\n * archive; that is the shape `extend` exists for, and refusing it would make the\n * slot useless for the fleet-wide change it was built for.\n */\nexport async function deploymentDeltaIssues(docsDir: DocsDir, featureDir: FeatureDir): Promise<Issue[]> {\n const docs = await featureDeployments(featureDir);\n const issues: Issue[] = [];\n // The reservation runs FIRST and independently of existence, because three of\n // the four names it refuses need not exist for the merge to be wrong. A\n // topology copied over either GENERATED file — `subsystems.likec4` or\n // `services.likec4` — is destroyed by the next `loam subsystem sync` and is\n // invisible to every project load until then: the merge would report\n // `created architecture/subsystems.likec4` and nothing downstream would ever\n // say another word about it.\n for (const doc of docs) {\n const reason = reservedArchitectureName(doc.rel);\n if (reason === null) continue;\n issues.push({\n severity: \"error\",\n code: \"deployment.doc-reserved\",\n subject: doc.rel,\n message:\n `features/<FEAT>/deployment/${doc.rel} would merge to architecture/${doc.rel}, and that name is loam's: ` +\n `${reason}. Rename the feature's document — its name is yours to choose, and \\`extend\\` is how it adds to ` +\n \"what a living document already declares.\",\n });\n }\n return issues.concat(\n docs\n .filter((doc) => reservedArchitectureName(doc.rel) === null)\n .filter((doc) => existsSync(livingDeploymentPath(docsDir, doc.rel)))\n .map((doc) => ({\n severity: \"error\" as const,\n code: \"deployment.doc-exists\" as const,\n subject: doc.rel,\n message:\n `architecture/${doc.rel} already exists, so this feature's topology would replace an authored one wholesale — ` +\n \"a feature-local deployment document INTRODUCES topology, it does not rewrite it. \" +\n `Delete features/<FEAT>/deployment/${doc.rel} and edit architecture/${doc.rel} directly in this same change, ` +\n \"where git produces an ordinary conflict if somebody else is editing it too. \" +\n `To ADD to what that file declares, write a document of another name that says \\`extend\\` — ` +\n `\\`${suggest(doc.rel)}\\` — which is what lets two features grow one region without touching each other.`,\n })),\n );\n}\n\n/** What the caller needs to grade this feature's topology against the merge preview. */\nexport interface DeploymentOverlayRequest {\n docsDir: DocsDir;\n featureDir: FeatureDir;\n /** The feature's id — the tag that selects the delta's own additions. */\n featureId: string;\n /**\n * The caller's memoised LikeC4 read, when it has one. A FUNCTION rather than\n * the `FleetContext` itself, for the reason the use-case overlay states: one\n * memoised read does not justify an edge from this package to\n * `core/fleet-context.ts`, and the edge would push it up a DAG level for\n * nothing.\n */\n load?: (path: string) => Promise<LoadedDoc>;\n /**\n * The fleet's extending models, for the merge preview — the SAME list\n * `loam archive` routes with. A preview computed without them holds interior\n * the archive puts in `services/<…>/model.likec4`, so a topology document\n * naming `<service-fqn>.<container>` reads clean here and against nothing\n * once it is living; `deployment.doc-invalid` is the gate that then fails\n * open (verification 2026-09-04, review F8).\n *\n * A THUNK for `load`'s reason and one more: the early return below is the\n * axis's per-feature opt-in, and a feature with no `deployment/` directory\n * must not pay a fleet enumeration to be told so.\n */\n models?: () => Promise<readonly ExtendingModel[]>;\n}\n\n/** Whether this feature's topology could be read at all, against the map its merge would leave. */\nexport type DeploymentOverlay = { kind: \"read\" } | { kind: \"unreadable\"; errors: string[] };\n\n/**\n * Parse this feature's topology documents over the post-merge map.\n *\n * A feature with no `deployment/` directory is `read` at the cost of one walk\n * over a directory that is not there — the axis's per-feature opt-in, and the\n * whole price a fleet that has not adopted it pays. Nothing is staged, nothing\n * is parsed, and no temp tree is made.\n *\n * The verdict is deliberately BINARY. The use-case overlay hands its caller the\n * views it read because the axis has questions about their content; this one\n * has none — a topology document is checked by being parseable against the map\n * the merge will write, and everything else about it is graded by\n * `loam validate --all` once it is living, exactly as the landscape is.\n */\nexport async function readFeatureDeployments(req: DeploymentOverlayRequest): Promise<DeploymentOverlay> {\n const docs = await featureDeployments(req.featureDir);\n if (docs.length === 0) return { kind: \"read\" };\n // AFTER the early return: the fleet read is paid only by a feature that\n // actually brings a topology document.\n const models = req.models === undefined ? undefined : await req.models();\n const staged = await stageMergedProject({\n docsDir: req.docsDir,\n featureDir: req.featureDir,\n featureId: req.featureId,\n documents: docs.map((doc) => ({ rel: doc.rel, path: doc.path })),\n ...(req.load === undefined ? {} : { load: req.load }),\n ...(models === undefined ? {} : { models }),\n });\n return staged.kind === \"unreadable\" ? { kind: \"unreadable\", errors: staged.errors } : { kind: \"read\" };\n}\n\n/** A free name near the colliding one, so the fix is a rename the author can copy. */\nfunction suggest(rel: string): string {\n const name = basename(rel).replace(/\\.likec4$/, \"\");\n const dir = rel.slice(0, rel.length - basename(rel).length);\n return `${dir}${name}-extend.likec4`;\n}\n"]}
@@ -1,11 +1,20 @@
1
1
  /**
2
- * Git-at-a-ref reader for the DOCS repo — the base side of `loam diff`.
2
+ * Git-at-a-ref reader for the DOCS repo — the base side of `loam diff`, and
3
+ * the "what had already shipped" side of `loam gate --since`.
3
4
  *
4
- * Three questions, all read-only: does this docs directory sit in a git
5
+ * Five questions, all read-only: does this docs directory sit in a git
5
6
  * repository and where (`resolveBase`), what did `services/` hold at the base
6
- * commit (`listBaseTree`), and what were one file's bytes there
7
- * (`showBaseFile`). No checkout, no temp writes — the base state is read
8
- * straight out of the object store with `rev-parse`/`ls-tree`/`show`.
7
+ * commit (`listBaseTree`), which subdirectories did one docs directory hold
8
+ * there (`listBaseSubdirs` — gate's `features/archive/` question) and which of
9
+ * them differ in the working tree now (`subdirsChangedSince` — its second),
10
+ * and what were one file's bytes there (`showBaseFile`). No checkout, no temp
11
+ * writes — the base state is read straight out of the object store with
12
+ * `rev-parse`/`ls-tree`/`show`, and the comparison with `diff`/`ls-files`.
13
+ *
14
+ * Gate takes diff's doctrine below along with the mechanics: a `--since` ref
15
+ * git cannot answer for is a refusal, never "nothing was archived since",
16
+ * because that silence would pass a deploy over every feature it could not
17
+ * see — the same fail-open as a silently empty diff base, one command over.
9
18
  *
10
19
  * The doctrine here is deliberately NOT provenance's. `../provenance/git.ts`
11
20
  * answers every git decline with "git will not say" — null, no finding —
@@ -21,6 +30,7 @@
21
30
  * construction.
22
31
  */
23
32
  import { spawn } from "node:child_process";
33
+ import { relative, sep } from "node:path";
24
34
  import { collectStderr, declineDetail, GIT_TIMEOUT_MS, gitSaid, gitText } from "../provenance/git.js";
25
35
  /**
26
36
  * How many bytes one base file may be — the same generosity as
@@ -31,8 +41,8 @@ import { collectStderr, declineDetail, GIT_TIMEOUT_MS, gitSaid, gitText } from "
31
41
  * killed and the file reads as unreadable for its subject, never truncated.
32
42
  *
33
43
  * The question-asking mechanics — `gitText`, `GitAnswer`, `gitSaid`,
34
- * `declineDetail`, `collectStderr` — are the shared set exported beside
35
- * `collectStdout`: `../provenance/gitq/vouched-ancestor.ts` asks with the
44
+ * `declineDetail`, `collectStderr` — are the shared set `../provenance/git.ts`
45
+ * exports: `../provenance/gitq/vouched-ancestor.ts` asks with the
36
46
  * same shapes and the opposite consequence (it degrades to "full read" where
37
47
  * this module's callers REFUSE), and the helpers stay verdict-free so the two
38
48
  * doctrines cannot bleed into each other through a shared line.
@@ -73,15 +83,87 @@ export async function resolveBase(docsDir, ref) {
73
83
  }
74
84
  /**
75
85
  * Every file path under `services/` at the base commit, DOCS-RELATIVE
76
- * (`services/...`, prefix already stripped). `--full-tree` anchors both the
77
- * pathspec and the output at the repository root regardless of cwd, so the
78
- * one prefix from `resolveBase` is the whole story; `-z` because git quotes
79
- * non-ASCII paths in line output. An empty answer is git ANSWERING — the base
86
+ * (`services/...`, prefix already stripped — `lsTree` below holds the
87
+ * mechanics both listings share). An empty answer is git ANSWERING — the base
80
88
  * commit simply has no `services/` — and only a non-zero exit is a failure.
81
89
  */
82
90
  export async function listBaseTree(base) {
91
+ return lsTree(base, "-r", "services");
92
+ }
93
+ /**
94
+ * The NAMES of the immediate subdirectories of one docs directory at the base
95
+ * commit — `loam gate --since`'s question: which `features/archive/<FEAT>/`
96
+ * directories already existed at the ref, so every other archived feature
97
+ * shipped after it. `dir` is the absolute path a `repo/paths.ts` builder
98
+ * spells (`archiveDir`), made docs-relative here, so the layout keeps its one
99
+ * spelling; the separator is forced to `/` because git paths are slash-joined
100
+ * on every host. `-d` lists trees only, and the trailing slash asks for the
101
+ * directory's CHILDREN rather than the directory entry itself. An empty answer
102
+ * is git answering — the base had no such directory — and only a decline is
103
+ * `failed`, which the caller refuses on, as `listBaseTree`'s callers do.
104
+ */
105
+ export async function listBaseSubdirs(base, dir) {
106
+ const rel = `${relative(base.docsDir, dir).split(sep).join("/")}/`;
107
+ const listing = await lsTree(base, "-d", rel);
108
+ if (listing.kind === "failed")
109
+ return listing;
110
+ return {
111
+ kind: "ok",
112
+ names: listing.paths.filter((p) => p.startsWith(rel)).map((p) => p.slice(rel.length)),
113
+ };
114
+ }
115
+ /**
116
+ * The NAMES of the immediate subdirectories of one docs directory whose
117
+ * contents differ between the base commit and the WORKING TREE — committed
118
+ * since or not — gate's second `features/archive/` question. A directory that
119
+ * was already there at the ref is not thereby old: `loam unarchive`, an edit
120
+ * and `loam archive` again leave a feature at the very path it shipped from
121
+ * before, and reading presence alone graded that re-shipped feature as one an
122
+ * earlier deploy had already answered for.
123
+ *
124
+ * Not a comparison of tree ids, though `ls-tree` has the ref's: the working
125
+ * tree has no id until somebody commits it, and minting one means writing
126
+ * objects — this module asks git, it never writes. The same two questions as
127
+ * `validate --base`'s scope (`./scope/changed-paths.ts`) instead, narrowed to
128
+ * the one directory: `git diff` for every tracked path that differs from the
129
+ * ref, `--no-renames` so a moved file names the place it left as well as the
130
+ * one it reached, and `ls-files --others` for what nobody has committed yet —
131
+ * the re-archive a pipeline is about to ship is exactly that. Both pathspecs
132
+ * are relative to `docsDir`, where git runs; both listings answer
133
+ * root-relative (`git diff` always, `ls-files` under `--full-name`), so the
134
+ * one prefix strip serves both. A decline is `failed`, and the caller decides
135
+ * which way that fails.
136
+ */
137
+ export async function subdirsChangedSince(base, dir) {
138
+ const rel = `${relative(base.docsDir, dir).split(sep).join("/")}/`;
139
+ const tracked = await gitText(base.docsDir, ["diff", "--name-only", "--no-renames", "--no-ext-diff", "-z", base.commit, "--", rel]);
140
+ if (tracked.spawnError !== undefined || tracked.code !== 0 || tracked.overflowed) {
141
+ return { kind: "failed", detail: tracked.overflowed ? "the changed-path listing exceeded the output cap." : declineDetail(tracked) };
142
+ }
143
+ const untracked = await gitText(base.docsDir, ["ls-files", "--others", "--exclude-standard", "--full-name", "-z", "--", rel]);
144
+ if (untracked.spawnError !== undefined || untracked.code !== 0 || untracked.overflowed) {
145
+ return { kind: "failed", detail: untracked.overflowed ? "the untracked-path listing exceeded the output cap." : declineDetail(untracked) };
146
+ }
147
+ const under = `${base.prefix}${rel}`;
148
+ const names = new Set();
149
+ for (const path of [...tracked.out.split("\0"), ...untracked.out.split("\0")]) {
150
+ if (!path.startsWith(under))
151
+ continue;
152
+ const name = path.slice(under.length).split("/")[0];
153
+ if (name !== "")
154
+ names.add(name);
155
+ }
156
+ return { kind: "ok", names };
157
+ }
158
+ /**
159
+ * The one `ls-tree` both listings ask, DOCS-RELATIVE (prefix already
160
+ * stripped). `--full-tree` anchors both the pathspec and the output at the
161
+ * repository root regardless of cwd, so the one prefix from `resolveBase` is
162
+ * the whole story; `-z` because git quotes non-ASCII paths in line output.
163
+ */
164
+ async function lsTree(base, mode, root) {
83
165
  const answer = await gitText(base.docsDir, [
84
- "ls-tree", "-r", "-z", "--name-only", "--full-tree", base.commit, "--", `${base.prefix}services`,
166
+ "ls-tree", mode, "-z", "--name-only", "--full-tree", base.commit, "--", `${base.prefix}${root}`,
85
167
  ]);
86
168
  if (answer.spawnError !== undefined || answer.code !== 0 || answer.overflowed) {
87
169
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"base-git.js","sourceRoot":"","sources":["../../../src/core/diff/base-git.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Git-at-a-ref reader for the DOCS repo — the base side of `loam diff`.\n *\n * Three questions, all read-only: does this docs directory sit in a git\n * repository and where (`resolveBase`), what did `services/` hold at the base\n * commit (`listBaseTree`), and what were one file's bytes there\n * (`showBaseFile`). No checkout, no temp writes — the base state is read\n * straight out of the object store with `rev-parse`/`ls-tree`/`show`.\n *\n * The doctrine here is deliberately NOT provenance's. `../provenance/git.ts`\n * answers every git decline with \"git will not say\" — null, no finding —\n * because provenance has no denominator without git and nothing to report.\n * Diff is the opposite case: without the base there is no honest diff at all,\n * and a silently empty base would report the whole fleet as ADDED — the exact\n * \"green over zero services\" failure `docsRepoReady` exists to prevent, one\n * step later. So every decline travels as a typed outcome the command REFUSES\n * on, carrying git's own words (bounded) so the refusal can say why.\n *\n * What is shared with provenance is the mechanics: the one timeout and the\n * capped stdout reader, so rule 20 (every child spawn bounded) holds by\n * construction.\n */\nimport { spawn } from \"node:child_process\";\nimport { collectStderr, declineDetail, GIT_TIMEOUT_MS, gitSaid, gitText } from \"../provenance/git.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\n\n/**\n * How many bytes one base file may be — the same generosity as\n * `../provenance/git.ts`'s stdout cap, restated here because that constant is\n * private to its module and this one collects BYTES, not text (a base\n * contract's UTF-8-ness is a fact to grade, and `setEncoding(\"utf8\")` would\n * substitute U+FFFD before anyone could check). Past the cap the child is\n * killed and the file reads as unreadable for its subject, never truncated.\n *\n * The question-asking mechanics — `gitText`, `GitAnswer`, `gitSaid`,\n * `declineDetail`, `collectStderr` — are the shared set exported beside\n * `collectStdout`: `../provenance/gitq/vouched-ancestor.ts` asks with the\n * same shapes and the opposite consequence (it degrades to \"full read\" where\n * this module's callers REFUSE), and the helpers stay verdict-free so the two\n * doctrines cannot bleed into each other through a shared line.\n */\nconst MAX_BASE_FILE_BYTES = 64 * 1024 * 1024;\n\nexport type BaseResolution =\n | { kind: \"no-git\"; detail: string }\n | { kind: \"no-ref\"; detail: string }\n | { kind: \"ok\"; commit: string; prefix: string };\n\n/**\n * Resolve the base: is `docsDir` inside a git work tree, where does it sit in\n * that repository, and which commit does `ref` name?\n *\n * `--show-prefix` is asked FIRST and is not decoration: a docs repo checked\n * out as a subdirectory of a larger repository (a monorepo's `docs/`) answers\n * every later `ls-tree`/`show` only under that prefix. Dropping it makes every\n * path miss, and a diff whose base paths all miss reports the whole fleet as\n * added — silently, in exactly the repositories big enough to care.\n */\nexport async function resolveBase(docsDir: DocsDir, ref: string): Promise<BaseResolution> {\n const where = await gitText(docsDir, [\"rev-parse\", \"--show-prefix\"]);\n if (where.spawnError !== undefined || where.code !== 0) {\n return { kind: \"no-git\", detail: declineDetail(where) };\n }\n // A ref spelled like an option would be parsed as one by git, not resolved\n // as a revision — refuse it before it reaches an argv. Same reason the MCP\n // argv boundary refuses '-'-prefixed strings.\n const spelled = ref.trim();\n if (spelled === \"\" || spelled.startsWith(\"-\")) {\n return { kind: \"no-ref\", detail: \"a ref must be a revision name, not empty and not option-shaped.\" };\n }\n // `^{commit}` peels an annotated tag to the commit it tags, and refuses a\n // ref that names any other object kind — a tree or blob sha is not a base\n // state to diff against.\n const verified = await gitText(docsDir, [\"rev-parse\", \"--verify\", `${spelled}^{commit}`]);\n const commit = verified.out.trim();\n if (verified.code !== 0 || !/^[0-9a-f]{40}$/.test(commit)) {\n const said = gitSaid(verified);\n return { kind: \"no-ref\", detail: said === \"\" ? \"\" : `git said: ${said}` };\n }\n return { kind: \"ok\", commit, prefix: where.out.trim() };\n}\n\n/** The resolved base every later question is asked against. */\nexport interface ResolvedBase {\n docsDir: DocsDir;\n commit: string;\n /** The docs repo's path inside its repository, `\"\"` or `\"docs/\"`-shaped — `rev-parse --show-prefix`'s answer. */\n prefix: string;\n}\n\nexport type BaseTreeListing =\n | { kind: \"ok\"; paths: string[] }\n | { kind: \"failed\"; detail: string };\n\n/**\n * Every file path under `services/` at the base commit, DOCS-RELATIVE\n * (`services/...`, prefix already stripped). `--full-tree` anchors both the\n * pathspec and the output at the repository root regardless of cwd, so the\n * one prefix from `resolveBase` is the whole story; `-z` because git quotes\n * non-ASCII paths in line output. An empty answer is git ANSWERING — the base\n * commit simply has no `services/` — and only a non-zero exit is a failure.\n */\nexport async function listBaseTree(base: ResolvedBase): Promise<BaseTreeListing> {\n const answer = await gitText(base.docsDir, [\n \"ls-tree\", \"-r\", \"-z\", \"--name-only\", \"--full-tree\", base.commit, \"--\", `${base.prefix}services`,\n ]);\n if (answer.spawnError !== undefined || answer.code !== 0 || answer.overflowed) {\n return {\n kind: \"failed\",\n detail: answer.overflowed\n ? \"the base tree listing exceeded the output cap.\"\n : declineDetail(answer),\n };\n }\n return {\n kind: \"ok\",\n paths: answer.out\n .split(\"\\0\")\n .filter((p) => p.startsWith(base.prefix) && p !== \"\")\n .map((p) => p.slice(base.prefix.length)),\n };\n}\n\nexport type BaseFileRead =\n | { kind: \"ok\"; bytes: Buffer }\n | { kind: \"failed\"; detail: string };\n\n/**\n * One base file's bytes, by docs-relative path. Callers only ask for paths\n * `listBaseTree` returned, so there is no \"absent\" arm: a decline here is\n * abnormal and travels as `failed` for per-subject containment — that one\n * subject degrades with the detail, never the whole diff.\n */\nexport async function showBaseFile(base: ResolvedBase, rel: string): Promise<BaseFileRead> {\n return new Promise<BaseFileRead>((done) => {\n const child = spawn(\"git\", [\"show\", `${base.commit}:${base.prefix}${rel}`], {\n cwd: base.docsDir,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n timeout: GIT_TIMEOUT_MS,\n });\n // Bytes, not text: `collectStdout` decodes as UTF-8, and a base contract\n // whose bytes are NOT UTF-8 must arrive intact so base-state can grade it\n // unreadable instead of parsing a document nobody wrote.\n const chunks: Buffer[] = [];\n let held = 0;\n let overflowed = false;\n child.stdout.on(\"data\", (chunk: Buffer) => {\n if (overflowed) return;\n if (held + chunk.length > MAX_BASE_FILE_BYTES) {\n overflowed = true;\n child.kill();\n return;\n }\n chunks.push(chunk);\n held += chunk.length;\n });\n const err = collectStderr(child);\n child.on(\"error\", (e) => done({ kind: \"failed\", detail: `git could not be started: ${e.message}.` }));\n child.on(\"close\", (code) => {\n if (code !== 0 || overflowed) {\n done({\n kind: \"failed\",\n detail: overflowed\n ? \"the file exceeds the base-read byte cap.\"\n : declineDetail({ code, out: \"\", err: err(), overflowed }),\n });\n return;\n }\n done({ kind: \"ok\", bytes: Buffer.concat(chunks) });\n });\n });\n}\n"]}
1
+ {"version":3,"file":"base-git.js","sourceRoot":"","sources":["../../../src/core/diff/base-git.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Git-at-a-ref reader for the DOCS repo — the base side of `loam diff`, and\n * the \"what had already shipped\" side of `loam gate --since`.\n *\n * Five questions, all read-only: does this docs directory sit in a git\n * repository and where (`resolveBase`), what did `services/` hold at the base\n * commit (`listBaseTree`), which subdirectories did one docs directory hold\n * there (`listBaseSubdirs` — gate's `features/archive/` question) and which of\n * them differ in the working tree now (`subdirsChangedSince` — its second),\n * and what were one file's bytes there (`showBaseFile`). No checkout, no temp\n * writes — the base state is read straight out of the object store with\n * `rev-parse`/`ls-tree`/`show`, and the comparison with `diff`/`ls-files`.\n *\n * Gate takes diff's doctrine below along with the mechanics: a `--since` ref\n * git cannot answer for is a refusal, never \"nothing was archived since\",\n * because that silence would pass a deploy over every feature it could not\n * see — the same fail-open as a silently empty diff base, one command over.\n *\n * The doctrine here is deliberately NOT provenance's. `../provenance/git.ts`\n * answers every git decline with \"git will not say\" — null, no finding —\n * because provenance has no denominator without git and nothing to report.\n * Diff is the opposite case: without the base there is no honest diff at all,\n * and a silently empty base would report the whole fleet as ADDED — the exact\n * \"green over zero services\" failure `docsRepoReady` exists to prevent, one\n * step later. So every decline travels as a typed outcome the command REFUSES\n * on, carrying git's own words (bounded) so the refusal can say why.\n *\n * What is shared with provenance is the mechanics: the one timeout and the\n * capped stdout reader, so rule 20 (every child spawn bounded) holds by\n * construction.\n */\nimport { spawn } from \"node:child_process\";\nimport { relative, sep } from \"node:path\";\nimport { collectStderr, declineDetail, GIT_TIMEOUT_MS, gitSaid, gitText } from \"../provenance/git.js\";\nimport type { DocsDir } from \"../kernel/ids/dirs.js\";\n\n/**\n * How many bytes one base file may be — the same generosity as\n * `../provenance/git.ts`'s stdout cap, restated here because that constant is\n * private to its module and this one collects BYTES, not text (a base\n * contract's UTF-8-ness is a fact to grade, and `setEncoding(\"utf8\")` would\n * substitute U+FFFD before anyone could check). Past the cap the child is\n * killed and the file reads as unreadable for its subject, never truncated.\n *\n * The question-asking mechanics — `gitText`, `GitAnswer`, `gitSaid`,\n * `declineDetail`, `collectStderr` — are the shared set `../provenance/git.ts`\n * exports: `../provenance/gitq/vouched-ancestor.ts` asks with the\n * same shapes and the opposite consequence (it degrades to \"full read\" where\n * this module's callers REFUSE), and the helpers stay verdict-free so the two\n * doctrines cannot bleed into each other through a shared line.\n */\nconst MAX_BASE_FILE_BYTES = 64 * 1024 * 1024;\n\nexport type BaseResolution =\n | { kind: \"no-git\"; detail: string }\n | { kind: \"no-ref\"; detail: string }\n | { kind: \"ok\"; commit: string; prefix: string };\n\n/**\n * Resolve the base: is `docsDir` inside a git work tree, where does it sit in\n * that repository, and which commit does `ref` name?\n *\n * `--show-prefix` is asked FIRST and is not decoration: a docs repo checked\n * out as a subdirectory of a larger repository (a monorepo's `docs/`) answers\n * every later `ls-tree`/`show` only under that prefix. Dropping it makes every\n * path miss, and a diff whose base paths all miss reports the whole fleet as\n * added — silently, in exactly the repositories big enough to care.\n */\nexport async function resolveBase(docsDir: DocsDir, ref: string): Promise<BaseResolution> {\n const where = await gitText(docsDir, [\"rev-parse\", \"--show-prefix\"]);\n if (where.spawnError !== undefined || where.code !== 0) {\n return { kind: \"no-git\", detail: declineDetail(where) };\n }\n // A ref spelled like an option would be parsed as one by git, not resolved\n // as a revision — refuse it before it reaches an argv. Same reason the MCP\n // argv boundary refuses '-'-prefixed strings.\n const spelled = ref.trim();\n if (spelled === \"\" || spelled.startsWith(\"-\")) {\n return { kind: \"no-ref\", detail: \"a ref must be a revision name, not empty and not option-shaped.\" };\n }\n // `^{commit}` peels an annotated tag to the commit it tags, and refuses a\n // ref that names any other object kind — a tree or blob sha is not a base\n // state to diff against.\n const verified = await gitText(docsDir, [\"rev-parse\", \"--verify\", `${spelled}^{commit}`]);\n const commit = verified.out.trim();\n if (verified.code !== 0 || !/^[0-9a-f]{40}$/.test(commit)) {\n const said = gitSaid(verified);\n return { kind: \"no-ref\", detail: said === \"\" ? \"\" : `git said: ${said}` };\n }\n return { kind: \"ok\", commit, prefix: where.out.trim() };\n}\n\n/** The resolved base every later question is asked against. */\nexport interface ResolvedBase {\n docsDir: DocsDir;\n commit: string;\n /** The docs repo's path inside its repository, `\"\"` or `\"docs/\"`-shaped — `rev-parse --show-prefix`'s answer. */\n prefix: string;\n}\n\nexport type BaseTreeListing =\n | { kind: \"ok\"; paths: string[] }\n | { kind: \"failed\"; detail: string };\n\n/**\n * Every file path under `services/` at the base commit, DOCS-RELATIVE\n * (`services/...`, prefix already stripped — `lsTree` below holds the\n * mechanics both listings share). An empty answer is git ANSWERING — the base\n * commit simply has no `services/` — and only a non-zero exit is a failure.\n */\nexport async function listBaseTree(base: ResolvedBase): Promise<BaseTreeListing> {\n return lsTree(base, \"-r\", \"services\");\n}\n\n/**\n * The NAMES of the immediate subdirectories of one docs directory at the base\n * commit — `loam gate --since`'s question: which `features/archive/<FEAT>/`\n * directories already existed at the ref, so every other archived feature\n * shipped after it. `dir` is the absolute path a `repo/paths.ts` builder\n * spells (`archiveDir`), made docs-relative here, so the layout keeps its one\n * spelling; the separator is forced to `/` because git paths are slash-joined\n * on every host. `-d` lists trees only, and the trailing slash asks for the\n * directory's CHILDREN rather than the directory entry itself. An empty answer\n * is git answering — the base had no such directory — and only a decline is\n * `failed`, which the caller refuses on, as `listBaseTree`'s callers do.\n */\nexport async function listBaseSubdirs(\n base: ResolvedBase,\n dir: string,\n): Promise<{ kind: \"ok\"; names: string[] } | { kind: \"failed\"; detail: string }> {\n const rel = `${relative(base.docsDir, dir).split(sep).join(\"/\")}/`;\n const listing = await lsTree(base, \"-d\", rel);\n if (listing.kind === \"failed\") return listing;\n return {\n kind: \"ok\",\n names: listing.paths.filter((p) => p.startsWith(rel)).map((p) => p.slice(rel.length)),\n };\n}\n\n/**\n * The NAMES of the immediate subdirectories of one docs directory whose\n * contents differ between the base commit and the WORKING TREE — committed\n * since or not — gate's second `features/archive/` question. A directory that\n * was already there at the ref is not thereby old: `loam unarchive`, an edit\n * and `loam archive` again leave a feature at the very path it shipped from\n * before, and reading presence alone graded that re-shipped feature as one an\n * earlier deploy had already answered for.\n *\n * Not a comparison of tree ids, though `ls-tree` has the ref's: the working\n * tree has no id until somebody commits it, and minting one means writing\n * objects — this module asks git, it never writes. The same two questions as\n * `validate --base`'s scope (`./scope/changed-paths.ts`) instead, narrowed to\n * the one directory: `git diff` for every tracked path that differs from the\n * ref, `--no-renames` so a moved file names the place it left as well as the\n * one it reached, and `ls-files --others` for what nobody has committed yet —\n * the re-archive a pipeline is about to ship is exactly that. Both pathspecs\n * are relative to `docsDir`, where git runs; both listings answer\n * root-relative (`git diff` always, `ls-files` under `--full-name`), so the\n * one prefix strip serves both. A decline is `failed`, and the caller decides\n * which way that fails.\n */\nexport async function subdirsChangedSince(\n base: ResolvedBase,\n dir: string,\n): Promise<{ kind: \"ok\"; names: Set<string> } | { kind: \"failed\"; detail: string }> {\n const rel = `${relative(base.docsDir, dir).split(sep).join(\"/\")}/`;\n const tracked = await gitText(base.docsDir, [\"diff\", \"--name-only\", \"--no-renames\", \"--no-ext-diff\", \"-z\", base.commit, \"--\", rel]);\n if (tracked.spawnError !== undefined || tracked.code !== 0 || tracked.overflowed) {\n return { kind: \"failed\", detail: tracked.overflowed ? \"the changed-path listing exceeded the output cap.\" : declineDetail(tracked) };\n }\n const untracked = await gitText(base.docsDir, [\"ls-files\", \"--others\", \"--exclude-standard\", \"--full-name\", \"-z\", \"--\", rel]);\n if (untracked.spawnError !== undefined || untracked.code !== 0 || untracked.overflowed) {\n return { kind: \"failed\", detail: untracked.overflowed ? \"the untracked-path listing exceeded the output cap.\" : declineDetail(untracked) };\n }\n const under = `${base.prefix}${rel}`;\n const names = new Set<string>();\n for (const path of [...tracked.out.split(\"\\0\"), ...untracked.out.split(\"\\0\")]) {\n if (!path.startsWith(under)) continue;\n const name = path.slice(under.length).split(\"/\")[0]!;\n if (name !== \"\") names.add(name);\n }\n return { kind: \"ok\", names };\n}\n\n/**\n * The one `ls-tree` both listings ask, DOCS-RELATIVE (prefix already\n * stripped). `--full-tree` anchors both the pathspec and the output at the\n * repository root regardless of cwd, so the one prefix from `resolveBase` is\n * the whole story; `-z` because git quotes non-ASCII paths in line output.\n */\nasync function lsTree(base: ResolvedBase, mode: \"-r\" | \"-d\", root: string): Promise<BaseTreeListing> {\n const answer = await gitText(base.docsDir, [\n \"ls-tree\", mode, \"-z\", \"--name-only\", \"--full-tree\", base.commit, \"--\", `${base.prefix}${root}`,\n ]);\n if (answer.spawnError !== undefined || answer.code !== 0 || answer.overflowed) {\n return {\n kind: \"failed\",\n detail: answer.overflowed\n ? \"the base tree listing exceeded the output cap.\"\n : declineDetail(answer),\n };\n }\n return {\n kind: \"ok\",\n paths: answer.out\n .split(\"\\0\")\n .filter((p) => p.startsWith(base.prefix) && p !== \"\")\n .map((p) => p.slice(base.prefix.length)),\n };\n}\n\nexport type BaseFileRead =\n | { kind: \"ok\"; bytes: Buffer }\n | { kind: \"failed\"; detail: string };\n\n/**\n * One base file's bytes, by docs-relative path. Callers only ask for paths\n * `listBaseTree` returned, so there is no \"absent\" arm: a decline here is\n * abnormal and travels as `failed` for per-subject containment — that one\n * subject degrades with the detail, never the whole diff.\n */\nexport async function showBaseFile(base: ResolvedBase, rel: string): Promise<BaseFileRead> {\n return new Promise<BaseFileRead>((done) => {\n const child = spawn(\"git\", [\"show\", `${base.commit}:${base.prefix}${rel}`], {\n cwd: base.docsDir,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n timeout: GIT_TIMEOUT_MS,\n });\n // Bytes, not text: `collectStdout` decodes as UTF-8, and a base contract\n // whose bytes are NOT UTF-8 must arrive intact so base-state can grade it\n // unreadable instead of parsing a document nobody wrote.\n const chunks: Buffer[] = [];\n let held = 0;\n let overflowed = false;\n child.stdout.on(\"data\", (chunk: Buffer) => {\n if (overflowed) return;\n if (held + chunk.length > MAX_BASE_FILE_BYTES) {\n overflowed = true;\n child.kill();\n return;\n }\n chunks.push(chunk);\n held += chunk.length;\n });\n const err = collectStderr(child);\n child.on(\"error\", (e) => done({ kind: \"failed\", detail: `git could not be started: ${e.message}.` }));\n child.on(\"close\", (code) => {\n if (code !== 0 || overflowed) {\n done({\n kind: \"failed\",\n detail: overflowed\n ? \"the file exceeds the base-read byte cap.\"\n : declineDetail({ code, out: \"\", err: err(), overflowed }),\n });\n return;\n }\n done({ kind: \"ok\", bytes: Buffer.concat(chunks) });\n });\n });\n}\n"]}
@@ -41,6 +41,8 @@
41
41
  */
42
42
  import { isUtf8 } from "node:buffer";
43
43
  import { inOrder } from "../kernel/concurrency.js";
44
+ import { rawServiceId } from "../kernel/ids/service.js";
45
+ import { decodeDocument } from "../kernel/document-bytes.js";
44
46
  import { parseRequirements } from "../document/parse.js";
45
47
  import { openapiFromText } from "../openapi/doc.js";
46
48
  import { asyncapiFromText } from "../asyncapi/read.js";
@@ -48,6 +50,7 @@ import { ARTIFACT_FILES, isServiceArtifactName } from "../repo/paths.js";
48
50
  import { SUBSYSTEM_MARKER } from "../repo/tree/marker.js";
49
51
  import { RESERVED_INTERIOR } from "../repo/tree/walk.js";
50
52
  import { listBaseTree, showBaseFile } from "./base-git.js";
53
+ import { bytesDigest, collisions } from "../consumers/state.js";
51
54
  /**
52
55
  * Classify the base tree's file listing into service directories — the module
53
56
  * banner's three restated walk branches, in depth order so every ancestor is
@@ -133,44 +136,53 @@ function classifyBaseDirs(paths) {
133
136
  if (interior(segments))
134
137
  continue;
135
138
  acceptedDirs.add(dir);
136
- accepted.push({ id: segments[segments.length - 1], dir, files: fileChildren.get(dir) ?? new Set() });
139
+ accepted.push({ id: rawServiceId(segments[segments.length - 1]), dir, files: fileChildren.get(dir) ?? new Set() });
137
140
  }
138
141
  return { dirs: accepted, ambiguous: collisions(accepted) };
139
142
  }
140
- function collisions(dirs) {
141
- const byId = new Map();
142
- for (const d of dirs)
143
- byId.set(d.id, [...(byId.get(d.id) ?? []), d.dir]);
144
- return new Map([...byId].filter(([, claimants]) => claimants.length > 1));
145
- }
146
143
  /**
147
144
  * Lift one fetched file into an axis state through its parser. Absence is the
148
145
  * listing's answer, a `show` failure is per-subject containment, and only
149
- * then do the bytes reach a parser.
146
+ * then do the bytes reach a parser. A parser's refusal carries the hash of
147
+ * the bytes it refused (`AxisState`'s `bytes`); a `show` failure has none to
148
+ * hash.
150
149
  */
151
150
  function axisFrom(raw, parse) {
152
151
  if (raw === null)
153
152
  return { kind: "absent" };
154
153
  if (raw.read.kind === "failed")
155
154
  return { kind: "unreadable", path: raw.rel, error: raw.read.detail };
156
- return parse(raw.rel, raw.read.bytes);
155
+ const state = parse(raw.rel, raw.read.bytes);
156
+ return state.kind === "unreadable" ? { ...state, bytes: bytesDigest(raw.read.bytes) } : state;
157
157
  }
158
158
  /**
159
- * The same refusal `readOpenapi`/`readAsyncapi`/`decodeDocument` make on the
160
- * living side: non-UTF-8 bytes would be silently rewritten as U+FFFD by
161
- * `toString`, and a document nobody wrote must not be graded.
159
+ * The same refusal `readOpenapi`/`readAsyncapi` make on the living side for a
160
+ * CONTRACT: non-UTF-8 bytes would be silently rewritten as U+FFFD by
161
+ * `toString`, and a document nobody wrote must not be graded. Contracts only:
162
+ * the spec axis decodes through `decodeDocument` instead (`specState`), because
163
+ * that is what its living side does, and each axis must refuse at the base
164
+ * exactly the bytes its living reader refuses.
162
165
  */
163
166
  function decodeBase(path, bytes) {
164
167
  if (!isUtf8(bytes))
165
168
  return { unreadable: { kind: "unreadable", path, error: "file is not valid UTF-8" } };
166
169
  return { text: bytes.toString("utf8") };
167
170
  }
171
+ /**
172
+ * A spec-axis file decodes through `decodeDocument` — the rule the living side
173
+ * applies to the same file (`FleetContext.readText`) — not through
174
+ * `decodeBase`'s `isUtf8`-only test. The difference is the NUL test: UTF-16LE
175
+ * of ASCII saved WITHOUT a byte-order mark is a valid UTF-8 sequence, so
176
+ * `isUtf8` alone let it through here and it parsed as zero requirements while
177
+ * the working tree refused the identical bytes. The base then read as a
178
+ * readable EMPTY spec: an untouched file diffed as one-sided containment, and
179
+ * a branch that re-saved it as UTF-8 reported every requirement in it as
180
+ * added. `decodeDocument`'s `NotUtf8DocumentError` lands in the catch below
181
+ * and becomes the unreadable arm like any parse failure.
182
+ */
168
183
  function specState(path, bytes) {
169
- const decoded = decodeBase(path, bytes);
170
- if ("unreadable" in decoded)
171
- return decoded.unreadable;
172
184
  try {
173
- return { kind: "read", value: parseRequirements(decoded.text) };
185
+ return { kind: "read", value: parseRequirements(decodeDocument(bytes, path)) };
174
186
  }
175
187
  catch (e) {
176
188
  return { kind: "unreadable", path, error: e instanceof Error ? e.message : String(e) };
@@ -1 +1 @@
1
- {"version":3,"file":"base-state.js","sourceRoot":"","sources":["../../../src/core/diff/base-state.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The base side of `loam diff`: the fleet as the base commit held it, read\n * entirely out of git's object store (`./base-git.ts`) and parsed with the\n * SAME parsers the living files go through — `parseRequirements`,\n * `openapiFromText`, `asyncapiFromText` — so a base document can never read\n * differently from the identical bytes sitting on disk.\n *\n * CLASSIFICATION is a second, simplified spelling of `repo/tree/walk.ts`'s\n * rule, and that is a recorded risk, not an accident: the walk classifies a\n * LIVE filesystem (readdir, symlinks, marker files) and cannot walk a git\n * ref. What keeps the two honest is sharing the artifact-name table\n * (`ARTIFACT_FILES`/`isServiceArtifactName` from `repo/paths.ts`), the\n * marker name (`SUBSYSTEM_MARKER`) and the reserved-interior set\n * (`RESERVED_INTERIOR` from `repo/tree/walk.ts`) instead of respelling them, and restating\n * — exactly — the walk's SERVICE branches: (0) a directory named for a\n * service's own interior (`adrs/`, `usecases/`, `ui/`) is classified nowhere,\n * counts as nothing beneath its parent, and hides everything under it, because\n * the walk never descends into one; (1) a directory holding a service\n * artifact is a service, marker present or not (artifacts win); (2) the leaf\n * rule — neither marker nor artifact and nothing beneath (an empty or\n * not-yet-adopted directory, a lone README.md, a lone `.gitkeep` — which is\n * the whole content of a directory `loam seed` creates) is a service too; (3) nothing\n * inside a PLAIN service is classified again (`examples/openapi.yaml` two\n * levels down is interior, the walk stops there), but a service carrying the\n * marker BESIDE its artifacts is still descended — minus artifact-named\n * children like `adrs/` — so the services stranded beneath it stay\n * enumerated, the walk's marker-misplaced branch. Subsystem and unmarked\n * group directories need no restating: a flat file listing descends through\n * them by construction. What is deliberately NOT restated: the walk's\n * findings (`subsystem.marker-misplaced`, `subsystem.unmarked`,\n * `subsystem.name-collision`) — those are validate's live diagnostics; here\n * a name claimed twice suspends the subject (`ambiguous`) instead. If\n * walk.ts's rule ever changes, this module owes the same change;\n * test/diff.test.ts pins the visible agreement (subsystem move, not-adopted\n * leaf, stranded-under-marker).\n *\n * Deliberate consequence of joining by leaf directory name: a service MOVED\n * between subsystems keeps its id and diffs as UNCHANGED. Subsystem placement\n * is navigation, not identity — SCHEMA's own position, and the reason\n * `subsystem move` never touches a join key.\n */\nimport { isUtf8 } from \"node:buffer\";\nimport { inOrder } from \"../kernel/concurrency.js\";\nimport { parseRequirements } from \"../document/parse.js\";\nimport type { Requirement } from \"../document/spec.js\";\nimport { openapiFromText, type Operation } from \"../openapi/doc.js\";\nimport { asyncapiFromText } from \"../asyncapi/read.js\";\nimport type { EventMessage } from \"../asyncapi/model.js\";\nimport { ARTIFACT_FILES, isServiceArtifactName } from \"../repo/paths.js\";\nimport { SUBSYSTEM_MARKER } from \"../repo/tree/marker.js\";\nimport { RESERVED_INTERIOR } from \"../repo/tree/walk.js\";\nimport { listBaseTree, showBaseFile, type ResolvedBase } from \"./base-git.js\";\n\n/**\n * One artifact's state on one side of the diff. `unreadable` is the\n * containment arm: the file exists but could not be read as its kind, so\n * every finding on this axis for this subject is SUSPENDED — \"nobody could\n * look\" must never grade as \"nothing is there\", in either direction.\n */\nexport type AxisState<T> =\n | { kind: \"absent\" }\n | { kind: \"read\"; value: T }\n | { kind: \"unreadable\"; path: string; error: string };\n\n/** The keys of a service state that hold requirement-bearing documents. */\nexport type SpecAxisKey = \"spec\" | \"archSpec\";\n\n/**\n * One service on one side of the diff — the same shape for the base ref and\n * the working tree (`./current-state.ts` builds the other), which is what\n * lets `./semantic.ts` join the two without caring which side came from git.\n */\nexport interface ServiceState {\n id: string;\n /** Docs-relative directory with forward slashes, e.g. `services/billing/payment-service`. */\n dir: string;\n spec: AxisState<Requirement[]>;\n /** Read on both sides for the `Consumes:` join — the outbox requirement lives here as often as in spec.md (core/coherence/events/lookups.ts's lesson). */\n archSpec: AxisState<Requirement[]>;\n /** Operations with `x-loam-remove` markers filtered out: a marker is bookkeeping, never a callable operation. */\n openapi: AxisState<Operation[]>;\n /** Messages with removal markers filtered out, for the same reason. */\n asyncapi: AxisState<EventMessage[]>;\n}\n\nexport interface BaseFleet {\n /** By service id, insertion-sorted by id — deterministic output depends on it. */\n services: Map<string, ServiceState>;\n /**\n * Ids claimed by MORE than one base directory. The live walk grades this\n * `subsystem.name-collision`; here the id's base state is unanswerable, so\n * the subject is carried with every axis suspended rather than one claimant\n * silently winning the join.\n */\n ambiguous: Map<string, string[]>;\n}\n\nexport type BaseFleetRead =\n | { kind: \"ok\"; fleet: BaseFleet }\n | { kind: \"failed\"; detail: string };\n\n/** A classified base service directory and which artifact files the base tree listed in it. */\ninterface BaseDir {\n id: string;\n dir: string;\n files: Set<string>;\n}\n\n/**\n * Classify the base tree's file listing into service directories — the module\n * banner's three restated walk branches, in depth order so every ancestor is\n * decided before anything beneath it.\n */\nfunction classifyBaseDirs(paths: string[]): { dirs: BaseDir[]; ambiguous: Map<string, string[]> } {\n const fileChildren = new Map<string, Set<string>>();\n const dirChildren = new Map<string, Set<string>>();\n // Directories nothing VISIBLE lives in, so neither map above names them.\n const bare = new Set<string>();\n for (const path of paths) {\n const all = path.split(\"/\");\n // Dot-named entries are invisible to the live walk — but the walk still\n // SEES the directory holding them, because readdir discovered the\n // directory itself and only its CONTENTS are filtered (walk.ts's\n // `subdirNames` and `visitDir`). So cut the path at its first dot-named\n // segment rather than discarding the whole path: everything above the cut\n // stays a visible directory, the cut segment and everything beneath it\n // stay invisible. Discarding it outright hid every directory whose only\n // file is dot-named — which is exactly what `loam seed` writes into a\n // service directory it creates (`.gitkeep`, and nothing else), so a seeded\n // service was absent from the base and diffed as `diff.service-added`\n // against the unchanged tree that had just been committed.\n const cut = all.findIndex((s) => s.startsWith(\".\") || s === \"\");\n const segments = cut === -1 ? all : all.slice(0, cut);\n // Only a whole path ends in a file; a cut one is a pure directory chain.\n const fileAt = cut === -1 ? segments.length - 1 : -1;\n for (let i = 1; i < segments.length; i += 1) {\n const dir = segments.slice(0, i).join(\"/\");\n const child = segments[i]!;\n const into = i === fileAt ? fileChildren : dirChildren;\n let set = into.get(dir);\n if (set === undefined) into.set(dir, (set = new Set()));\n set.add(child);\n }\n if (fileAt === -1 && segments.length > 1) bare.add(segments.join(\"/\"));\n }\n const hasMarker = (dir: string): boolean => fileChildren.get(dir)?.has(SUBSYSTEM_MARKER) === true;\n const hasArtifact = (dir: string): boolean =>\n [...(fileChildren.get(dir) ?? [])].some((f) => isServiceArtifactName(f, \"file\")) ||\n [...(dirChildren.get(dir) ?? [])].some((d) => isServiceArtifactName(d, \"dir\"));\n // The walk's `RESERVED_INTERIOR` rule, restated for a flat listing: a\n // directory named for a service's own interior is never classified, and\n // neither is anything beneath it, because the walk never descends into one.\n // Segment 2 onward, matching where the walk applies it — to a directory's\n // CHILDREN, so a directory literally at `services/usecases/` is still\n // classified there.\n const reserved = (dir: string): boolean => dir.split(\"/\").slice(2).some((s) => RESERVED_INTERIOR.has(s));\n // …and, for the same reason, one of them is not the subdirectory that keeps\n // its parent off the leaf rule: the walk filters them out of `dirs` before\n // asking whether anything is beneath.\n const classifiableChildren = (dir: string): boolean =>\n [...(dirChildren.get(dir) ?? [])].some((d) => !RESERVED_INTERIOR.has(d));\n // The walk's leaf rule: neither marker nor artifact and nothing beneath is a\n // service. (A truly EMPTY directory is unrepresentable in a git tree, so at\n // the base this arm means \"no visible entry of ours\" — files none of which\n // are artifacts, or nothing but dot-named entries, the `loam seed` shape.)\n const isLeafService = (dir: string): boolean =>\n !hasMarker(dir) && !hasArtifact(dir) && !classifiableChildren(dir);\n const candidates = [...new Set([...fileChildren.keys(), ...dirChildren.keys(), ...bare])]\n .filter((dir) => dir !== \"services\" && dir.startsWith(\"services/\") && !reserved(dir))\n .filter((dir) => hasArtifact(dir) || isLeafService(dir))\n .sort((a, b) => {\n const depth = a.split(\"/\").length - b.split(\"/\").length;\n return depth !== 0 ? depth : a < b ? -1 : 1;\n });\n const accepted: BaseDir[] = [];\n const acceptedDirs = new Set<string>();\n // Is this candidate INTERIOR to the nearest service already accepted above\n // it? A plain service swallows everything beneath (the walk stops there); a\n // marker-beside-artifacts service is descended except through its\n // artifact-named children (`adrs/`), so a service stranded under it stays.\n const interior = (segments: string[]): boolean => {\n for (let i = segments.length - 1; i >= 2; i -= 1) {\n const ancestor = segments.slice(0, i).join(\"/\");\n if (!acceptedDirs.has(ancestor)) continue;\n return hasMarker(ancestor) ? isServiceArtifactName(segments[i]!, \"dir\") : true;\n }\n return false;\n };\n for (const dir of candidates) {\n const segments = dir.split(\"/\");\n if (interior(segments)) continue;\n acceptedDirs.add(dir);\n accepted.push({ id: segments[segments.length - 1]!, dir, files: fileChildren.get(dir) ?? new Set() });\n }\n return { dirs: accepted, ambiguous: collisions(accepted) };\n}\n\nfunction collisions(dirs: BaseDir[]): Map<string, string[]> {\n const byId = new Map<string, string[]>();\n for (const d of dirs) byId.set(d.id, [...(byId.get(d.id) ?? []), d.dir]);\n return new Map([...byId].filter(([, claimants]) => claimants.length > 1));\n}\n\n/** One shown base file, or `null` for a file the base tree never listed. */\ntype Fetched = { rel: string; read: Awaited<ReturnType<typeof showBaseFile>> } | null;\n\n/**\n * Lift one fetched file into an axis state through its parser. Absence is the\n * listing's answer, a `show` failure is per-subject containment, and only\n * then do the bytes reach a parser.\n */\nfunction axisFrom<T>(raw: Fetched, parse: (path: string, bytes: Buffer) => AxisState<T>): AxisState<T> {\n if (raw === null) return { kind: \"absent\" };\n if (raw.read.kind === \"failed\") return { kind: \"unreadable\", path: raw.rel, error: raw.read.detail };\n return parse(raw.rel, raw.read.bytes);\n}\n\n/**\n * The same refusal `readOpenapi`/`readAsyncapi`/`decodeDocument` make on the\n * living side: non-UTF-8 bytes would be silently rewritten as U+FFFD by\n * `toString`, and a document nobody wrote must not be graded.\n */\nfunction decodeBase(path: string, bytes: Buffer): { text: string } | { unreadable: AxisState<never> } {\n if (!isUtf8(bytes)) return { unreadable: { kind: \"unreadable\", path, error: \"file is not valid UTF-8\" } };\n return { text: bytes.toString(\"utf8\") };\n}\n\nfunction specState(path: string, bytes: Buffer): AxisState<Requirement[]> {\n const decoded = decodeBase(path, bytes);\n if (\"unreadable\" in decoded) return decoded.unreadable;\n try {\n return { kind: \"read\", value: parseRequirements(decoded.text) };\n } catch (e) {\n return { kind: \"unreadable\", path, error: e instanceof Error ? e.message : String(e) };\n }\n}\n\nfunction openapiState(path: string, bytes: Buffer): AxisState<Operation[]> {\n const decoded = decodeBase(path, bytes);\n if (\"unreadable\" in decoded) return decoded.unreadable;\n const doc = openapiFromText(decoded.text);\n return doc.unreadable\n ? { kind: \"unreadable\", path, error: doc.error ?? \"not a readable OpenAPI document\" }\n : { kind: \"read\", value: doc.ops.filter((op) => !op.remove) };\n}\n\nfunction asyncapiState(path: string, bytes: Buffer): AxisState<EventMessage[]> {\n const decoded = decodeBase(path, bytes);\n if (\"unreadable\" in decoded) return decoded.unreadable;\n const doc = asyncapiFromText(decoded.text);\n return doc.unreadable\n ? { kind: \"unreadable\", path, error: doc.error ?? \"not a readable AsyncAPI document\" }\n : { kind: \"read\", value: doc.messages.filter((m) => m.remove !== true) };\n}\n\n/** One base service's four axes. Sequential INSIDE the service on purpose: the pool below rations children at the service grain, and four spawns per in-flight worker would multiply past the cap it exists to hold. */\nasync function readBaseService(base: ResolvedBase, d: BaseDir): Promise<ServiceState> {\n const fetch = async (file: string): Promise<Fetched> =>\n d.files.has(file) ? { rel: `${d.dir}/${file}`, read: await showBaseFile(base, `${d.dir}/${file}`) } : null;\n return {\n id: d.id,\n dir: d.dir,\n spec: axisFrom(await fetch(ARTIFACT_FILES.spec), specState),\n archSpec: axisFrom(await fetch(ARTIFACT_FILES.archSpec), specState),\n openapi: axisFrom(await fetch(ARTIFACT_FILES.openapi), openapiState),\n asyncapi: axisFrom(await fetch(ARTIFACT_FILES.asyncapi), asyncapiState),\n };\n}\n\n/**\n * The whole base fleet. `failed` only when git cannot LIST the tree — the\n * whole-diff refusal; a single file that cannot be shown or parsed degrades\n * its own subject's axis to `unreadable` and nothing else.\n */\nexport async function readBaseFleet(base: ResolvedBase): Promise<BaseFleetRead> {\n const listing = await listBaseTree(base);\n if (listing.kind === \"failed\") return listing;\n const { dirs, ambiguous } = classifyBaseDirs(listing.paths);\n const sorted = [...dirs].sort((a, b) => (a.id < b.id ? -1 : 1)).filter((d) => !ambiguous.has(d.id));\n // Pooled, ordered reads: bounded children in flight, results by input index,\n // so the same base state always assembles the same fleet.\n const states = await inOrder(sorted, (d) => readBaseService(base, d));\n return { kind: \"ok\", fleet: { services: new Map(states.map((s) => [s.id, s])), ambiguous } };\n}\n"]}
1
+ {"version":3,"file":"base-state.js","sourceRoot":"","sources":["../../../src/core/diff/base-state.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The base side of `loam diff`: the fleet as the base commit held it, read\n * entirely out of git's object store (`./base-git.ts`) and parsed with the\n * SAME parsers the living files go through — `parseRequirements`,\n * `openapiFromText`, `asyncapiFromText` — so a base document can never read\n * differently from the identical bytes sitting on disk.\n *\n * CLASSIFICATION is a second, simplified spelling of `repo/tree/walk.ts`'s\n * rule, and that is a recorded risk, not an accident: the walk classifies a\n * LIVE filesystem (readdir, symlinks, marker files) and cannot walk a git\n * ref. What keeps the two honest is sharing the artifact-name table\n * (`ARTIFACT_FILES`/`isServiceArtifactName` from `repo/paths.ts`), the\n * marker name (`SUBSYSTEM_MARKER`) and the reserved-interior set\n * (`RESERVED_INTERIOR` from `repo/tree/walk.ts`) instead of respelling them, and restating\n * — exactly — the walk's SERVICE branches: (0) a directory named for a\n * service's own interior (`adrs/`, `usecases/`, `ui/`) is classified nowhere,\n * counts as nothing beneath its parent, and hides everything under it, because\n * the walk never descends into one; (1) a directory holding a service\n * artifact is a service, marker present or not (artifacts win); (2) the leaf\n * rule — neither marker nor artifact and nothing beneath (an empty or\n * not-yet-adopted directory, a lone README.md, a lone `.gitkeep` — which is\n * the whole content of a directory `loam seed` creates) is a service too; (3) nothing\n * inside a PLAIN service is classified again (`examples/openapi.yaml` two\n * levels down is interior, the walk stops there), but a service carrying the\n * marker BESIDE its artifacts is still descended — minus artifact-named\n * children like `adrs/` — so the services stranded beneath it stay\n * enumerated, the walk's marker-misplaced branch. Subsystem and unmarked\n * group directories need no restating: a flat file listing descends through\n * them by construction. What is deliberately NOT restated: the walk's\n * findings (`subsystem.marker-misplaced`, `subsystem.unmarked`,\n * `subsystem.name-collision`) — those are validate's live diagnostics; here\n * a name claimed twice suspends the subject (`ambiguous`) instead. If\n * walk.ts's rule ever changes, this module owes the same change;\n * test/diff.test.ts pins the visible agreement (subsystem move, not-adopted\n * leaf, stranded-under-marker).\n *\n * Deliberate consequence of joining by leaf directory name: a service MOVED\n * between subsystems keeps its id and diffs as UNCHANGED. Subsystem placement\n * is navigation, not identity — SCHEMA's own position, and the reason\n * `subsystem move` never touches a join key.\n */\nimport { isUtf8 } from \"node:buffer\";\nimport { inOrder } from \"../kernel/concurrency.js\";\nimport { rawServiceId, type RawServiceId } from \"../kernel/ids/service.js\";\nimport { decodeDocument } from \"../kernel/document-bytes.js\";\nimport { parseRequirements } from \"../document/parse.js\";\nimport type { Requirement } from \"../document/spec.js\";\nimport { openapiFromText, type Operation } from \"../openapi/doc.js\";\nimport { asyncapiFromText } from \"../asyncapi/read.js\";\nimport type { EventMessage } from \"../asyncapi/model.js\";\nimport { ARTIFACT_FILES, isServiceArtifactName } from \"../repo/paths.js\";\nimport { SUBSYSTEM_MARKER } from \"../repo/tree/marker.js\";\nimport { RESERVED_INTERIOR } from \"../repo/tree/walk.js\";\nimport { listBaseTree, showBaseFile, type ResolvedBase } from \"./base-git.js\";\nimport { bytesDigest, collisions, type AxisState, type ServiceState } from \"../consumers/state.js\";\n\nexport interface BaseFleet {\n /** By service id, insertion-sorted by id — deterministic output depends on it. */\n services: Map<string, ServiceState>;\n /**\n * Ids claimed by MORE than one base directory. The live walk grades this\n * `subsystem.name-collision`; here the id's base state is unanswerable, so\n * the subject is carried with every axis suspended rather than one claimant\n * silently winning the join.\n */\n ambiguous: Map<string, string[]>;\n}\n\nexport type BaseFleetRead =\n | { kind: \"ok\"; fleet: BaseFleet }\n | { kind: \"failed\"; detail: string };\n\n/** A classified base service directory and which artifact files the base tree listed in it. */\ninterface BaseDir {\n id: RawServiceId;\n dir: string;\n files: Set<string>;\n}\n\n/**\n * Classify the base tree's file listing into service directories — the module\n * banner's three restated walk branches, in depth order so every ancestor is\n * decided before anything beneath it.\n */\nfunction classifyBaseDirs(paths: string[]): { dirs: BaseDir[]; ambiguous: Map<string, string[]> } {\n const fileChildren = new Map<string, Set<string>>();\n const dirChildren = new Map<string, Set<string>>();\n // Directories nothing VISIBLE lives in, so neither map above names them.\n const bare = new Set<string>();\n for (const path of paths) {\n const all = path.split(\"/\");\n // Dot-named entries are invisible to the live walk — but the walk still\n // SEES the directory holding them, because readdir discovered the\n // directory itself and only its CONTENTS are filtered (walk.ts's\n // `subdirNames` and `visitDir`). So cut the path at its first dot-named\n // segment rather than discarding the whole path: everything above the cut\n // stays a visible directory, the cut segment and everything beneath it\n // stay invisible. Discarding it outright hid every directory whose only\n // file is dot-named — which is exactly what `loam seed` writes into a\n // service directory it creates (`.gitkeep`, and nothing else), so a seeded\n // service was absent from the base and diffed as `diff.service-added`\n // against the unchanged tree that had just been committed.\n const cut = all.findIndex((s) => s.startsWith(\".\") || s === \"\");\n const segments = cut === -1 ? all : all.slice(0, cut);\n // Only a whole path ends in a file; a cut one is a pure directory chain.\n const fileAt = cut === -1 ? segments.length - 1 : -1;\n for (let i = 1; i < segments.length; i += 1) {\n const dir = segments.slice(0, i).join(\"/\");\n const child = segments[i]!;\n const into = i === fileAt ? fileChildren : dirChildren;\n let set = into.get(dir);\n if (set === undefined) into.set(dir, (set = new Set()));\n set.add(child);\n }\n if (fileAt === -1 && segments.length > 1) bare.add(segments.join(\"/\"));\n }\n const hasMarker = (dir: string): boolean => fileChildren.get(dir)?.has(SUBSYSTEM_MARKER) === true;\n const hasArtifact = (dir: string): boolean =>\n [...(fileChildren.get(dir) ?? [])].some((f) => isServiceArtifactName(f, \"file\")) ||\n [...(dirChildren.get(dir) ?? [])].some((d) => isServiceArtifactName(d, \"dir\"));\n // The walk's `RESERVED_INTERIOR` rule, restated for a flat listing: a\n // directory named for a service's own interior is never classified, and\n // neither is anything beneath it, because the walk never descends into one.\n // Segment 2 onward, matching where the walk applies it — to a directory's\n // CHILDREN, so a directory literally at `services/usecases/` is still\n // classified there.\n const reserved = (dir: string): boolean => dir.split(\"/\").slice(2).some((s) => RESERVED_INTERIOR.has(s));\n // …and, for the same reason, one of them is not the subdirectory that keeps\n // its parent off the leaf rule: the walk filters them out of `dirs` before\n // asking whether anything is beneath.\n const classifiableChildren = (dir: string): boolean =>\n [...(dirChildren.get(dir) ?? [])].some((d) => !RESERVED_INTERIOR.has(d));\n // The walk's leaf rule: neither marker nor artifact and nothing beneath is a\n // service. (A truly EMPTY directory is unrepresentable in a git tree, so at\n // the base this arm means \"no visible entry of ours\" — files none of which\n // are artifacts, or nothing but dot-named entries, the `loam seed` shape.)\n const isLeafService = (dir: string): boolean =>\n !hasMarker(dir) && !hasArtifact(dir) && !classifiableChildren(dir);\n const candidates = [...new Set([...fileChildren.keys(), ...dirChildren.keys(), ...bare])]\n .filter((dir) => dir !== \"services\" && dir.startsWith(\"services/\") && !reserved(dir))\n .filter((dir) => hasArtifact(dir) || isLeafService(dir))\n .sort((a, b) => {\n const depth = a.split(\"/\").length - b.split(\"/\").length;\n return depth !== 0 ? depth : a < b ? -1 : 1;\n });\n const accepted: BaseDir[] = [];\n const acceptedDirs = new Set<string>();\n // Is this candidate INTERIOR to the nearest service already accepted above\n // it? A plain service swallows everything beneath (the walk stops there); a\n // marker-beside-artifacts service is descended except through its\n // artifact-named children (`adrs/`), so a service stranded under it stays.\n const interior = (segments: string[]): boolean => {\n for (let i = segments.length - 1; i >= 2; i -= 1) {\n const ancestor = segments.slice(0, i).join(\"/\");\n if (!acceptedDirs.has(ancestor)) continue;\n return hasMarker(ancestor) ? isServiceArtifactName(segments[i]!, \"dir\") : true;\n }\n return false;\n };\n for (const dir of candidates) {\n const segments = dir.split(\"/\");\n if (interior(segments)) continue;\n acceptedDirs.add(dir);\n accepted.push({ id: rawServiceId(segments[segments.length - 1]!), dir, files: fileChildren.get(dir) ?? new Set() });\n }\n return { dirs: accepted, ambiguous: collisions(accepted) };\n}\n\n/** One shown base file, or `null` for a file the base tree never listed. */\ntype Fetched = { rel: string; read: Awaited<ReturnType<typeof showBaseFile>> } | null;\n\n/**\n * Lift one fetched file into an axis state through its parser. Absence is the\n * listing's answer, a `show` failure is per-subject containment, and only\n * then do the bytes reach a parser. A parser's refusal carries the hash of\n * the bytes it refused (`AxisState`'s `bytes`); a `show` failure has none to\n * hash.\n */\nfunction axisFrom<T>(raw: Fetched, parse: (path: string, bytes: Buffer) => AxisState<T>): AxisState<T> {\n if (raw === null) return { kind: \"absent\" };\n if (raw.read.kind === \"failed\") return { kind: \"unreadable\", path: raw.rel, error: raw.read.detail };\n const state = parse(raw.rel, raw.read.bytes);\n return state.kind === \"unreadable\" ? { ...state, bytes: bytesDigest(raw.read.bytes) } : state;\n}\n\n/**\n * The same refusal `readOpenapi`/`readAsyncapi` make on the living side for a\n * CONTRACT: non-UTF-8 bytes would be silently rewritten as U+FFFD by\n * `toString`, and a document nobody wrote must not be graded. Contracts only:\n * the spec axis decodes through `decodeDocument` instead (`specState`), because\n * that is what its living side does, and each axis must refuse at the base\n * exactly the bytes its living reader refuses.\n */\nfunction decodeBase(path: string, bytes: Buffer): { text: string } | { unreadable: AxisState<never> } {\n if (!isUtf8(bytes)) return { unreadable: { kind: \"unreadable\", path, error: \"file is not valid UTF-8\" } };\n return { text: bytes.toString(\"utf8\") };\n}\n\n/**\n * A spec-axis file decodes through `decodeDocument` — the rule the living side\n * applies to the same file (`FleetContext.readText`) — not through\n * `decodeBase`'s `isUtf8`-only test. The difference is the NUL test: UTF-16LE\n * of ASCII saved WITHOUT a byte-order mark is a valid UTF-8 sequence, so\n * `isUtf8` alone let it through here and it parsed as zero requirements while\n * the working tree refused the identical bytes. The base then read as a\n * readable EMPTY spec: an untouched file diffed as one-sided containment, and\n * a branch that re-saved it as UTF-8 reported every requirement in it as\n * added. `decodeDocument`'s `NotUtf8DocumentError` lands in the catch below\n * and becomes the unreadable arm like any parse failure.\n */\nfunction specState(path: string, bytes: Buffer): AxisState<Requirement[]> {\n try {\n return { kind: \"read\", value: parseRequirements(decodeDocument(bytes, path)) };\n } catch (e) {\n return { kind: \"unreadable\", path, error: e instanceof Error ? e.message : String(e) };\n }\n}\n\nfunction openapiState(path: string, bytes: Buffer): AxisState<Operation[]> {\n const decoded = decodeBase(path, bytes);\n if (\"unreadable\" in decoded) return decoded.unreadable;\n const doc = openapiFromText(decoded.text);\n return doc.unreadable\n ? { kind: \"unreadable\", path, error: doc.error ?? \"not a readable OpenAPI document\" }\n : { kind: \"read\", value: doc.ops.filter((op) => !op.remove) };\n}\n\nfunction asyncapiState(path: string, bytes: Buffer): AxisState<EventMessage[]> {\n const decoded = decodeBase(path, bytes);\n if (\"unreadable\" in decoded) return decoded.unreadable;\n const doc = asyncapiFromText(decoded.text);\n return doc.unreadable\n ? { kind: \"unreadable\", path, error: doc.error ?? \"not a readable AsyncAPI document\" }\n : { kind: \"read\", value: doc.messages.filter((m) => m.remove !== true) };\n}\n\n/** One base service's four axes. Sequential INSIDE the service on purpose: the pool below rations children at the service grain, and four spawns per in-flight worker would multiply past the cap it exists to hold. */\nasync function readBaseService(base: ResolvedBase, d: BaseDir): Promise<ServiceState> {\n const fetch = async (file: string): Promise<Fetched> =>\n d.files.has(file) ? { rel: `${d.dir}/${file}`, read: await showBaseFile(base, `${d.dir}/${file}`) } : null;\n return {\n id: d.id,\n dir: d.dir,\n spec: axisFrom(await fetch(ARTIFACT_FILES.spec), specState),\n archSpec: axisFrom(await fetch(ARTIFACT_FILES.archSpec), specState),\n openapi: axisFrom(await fetch(ARTIFACT_FILES.openapi), openapiState),\n asyncapi: axisFrom(await fetch(ARTIFACT_FILES.asyncapi), asyncapiState),\n };\n}\n\n/**\n * The whole base fleet. `failed` only when git cannot LIST the tree — the\n * whole-diff refusal; a single file that cannot be shown or parsed degrades\n * its own subject's axis to `unreadable` and nothing else.\n */\nexport async function readBaseFleet(base: ResolvedBase): Promise<BaseFleetRead> {\n const listing = await listBaseTree(base);\n if (listing.kind === \"failed\") return listing;\n const { dirs, ambiguous } = classifyBaseDirs(listing.paths);\n const sorted = [...dirs].sort((a, b) => (a.id < b.id ? -1 : 1)).filter((d) => !ambiguous.has(d.id));\n // Pooled, ordered reads: bounded children in flight, results by input index,\n // so the same base state always assembles the same fleet.\n const states = await inOrder(sorted, (d) => readBaseService(base, d));\n return { kind: \"ok\", fleet: { services: new Map(states.map((s) => [s.id, s])), ambiguous } };\n}\n"]}