@ybotok/loam 0.2.0-alpha.1 → 0.2.0-alpha.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (901) hide show
  1. package/CHANGELOG.md +3448 -0
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +47 -23
  5. package/QUICKSTART.md +189 -12
  6. package/README.md +274 -65
  7. package/ROADMAP.md +572 -31
  8. package/SCHEMA.md +1709 -155
  9. package/WORKFLOW.md +298 -72
  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/adopt/render.js +50 -9
  15. package/dist/commands/adopt/render.js.map +1 -1
  16. package/dist/commands/archive/archive.js +16 -19
  17. package/dist/commands/archive/archive.js.map +1 -1
  18. package/dist/commands/archive/closing.js +42 -0
  19. package/dist/commands/archive/closing.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
  21. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  23. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  24. package/dist/commands/archive/plan/contracts/openapi.js +77 -43
  25. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  26. package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
  27. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  28. package/dist/commands/archive/plan/document/head.js +151 -0
  29. package/dist/commands/archive/plan/document/head.js.map +1 -0
  30. package/dist/commands/archive/plan/document/links.js +284 -0
  31. package/dist/commands/archive/plan/document/links.js.map +1 -0
  32. package/dist/commands/archive/plan/document/run.js +136 -0
  33. package/dist/commands/archive/plan/document/run.js.map +1 -0
  34. package/dist/commands/archive/plan/gate.js +21 -19
  35. package/dist/commands/archive/plan/gate.js.map +1 -1
  36. package/dist/commands/archive/plan/landscape.js +167 -30
  37. package/dist/commands/archive/plan/landscape.js.map +1 -1
  38. package/dist/commands/archive/plan/model/extending.js +85 -0
  39. package/dist/commands/archive/plan/model/extending.js.map +1 -0
  40. package/dist/commands/archive/plan/refusal.js +14 -19
  41. package/dist/commands/archive/plan/refusal.js.map +1 -1
  42. package/dist/commands/archive/plan/specs.js +137 -56
  43. package/dist/commands/archive/plan/specs.js.map +1 -1
  44. package/dist/commands/archive/plan/state.js +3 -0
  45. package/dist/commands/archive/plan/state.js.map +1 -1
  46. package/dist/commands/archive/run.js +134 -30
  47. package/dist/commands/archive/run.js.map +1 -1
  48. package/dist/commands/archive/shipped.js +49 -0
  49. package/dist/commands/archive/shipped.js.map +1 -0
  50. package/dist/commands/context/context.js +85 -18
  51. package/dist/commands/context/context.js.map +1 -1
  52. package/dist/commands/context/print.js +29 -7
  53. package/dist/commands/context/print.js.map +1 -1
  54. package/dist/commands/delta/delta.js +102 -14
  55. package/dist/commands/delta/delta.js.map +1 -1
  56. package/dist/commands/delta/print.js +17 -1
  57. package/dist/commands/delta/print.js.map +1 -1
  58. package/dist/commands/dependencies.js +3 -7
  59. package/dist/commands/dependencies.js.map +1 -1
  60. package/dist/commands/diff/diff.js +20 -27
  61. package/dist/commands/diff/diff.js.map +1 -1
  62. package/dist/commands/doctor.js +60 -1
  63. package/dist/commands/doctor.js.map +1 -1
  64. package/dist/commands/explain/explain.js +89 -13
  65. package/dist/commands/explain/explain.js.map +1 -1
  66. package/dist/commands/explore.js +16 -10
  67. package/dist/commands/explore.js.map +1 -1
  68. package/dist/commands/gate/gate.js +45 -8
  69. package/dist/commands/gate/gate.js.map +1 -1
  70. package/dist/commands/gate/print.js +15 -3
  71. package/dist/commands/gate/print.js.map +1 -1
  72. package/dist/commands/gherkin/commit.js +13 -54
  73. package/dist/commands/gherkin/commit.js.map +1 -1
  74. package/dist/commands/gherkin/gherkin.js +36 -20
  75. package/dist/commands/gherkin/gherkin.js.map +1 -1
  76. package/dist/commands/gherkin/reconcile.js +10 -13
  77. package/dist/commands/gherkin/reconcile.js.map +1 -1
  78. package/dist/commands/init/first-hour.js +44 -0
  79. package/dist/commands/init/first-hour.js.map +1 -1
  80. package/dist/commands/init/init.js +18 -22
  81. package/dist/commands/init/init.js.map +1 -1
  82. package/dist/commands/list/campaign/campaign.js +4 -6
  83. package/dist/commands/list/campaign/campaign.js.map +1 -1
  84. package/dist/commands/list/json.js +5 -7
  85. package/dist/commands/list/json.js.map +1 -1
  86. package/dist/commands/list/list.js +44 -29
  87. package/dist/commands/list/list.js.map +1 -1
  88. package/dist/commands/list/print.js +52 -9
  89. package/dist/commands/list/print.js.map +1 -1
  90. package/dist/commands/list/suites/suites.js +140 -0
  91. package/dist/commands/list/suites/suites.js.map +1 -0
  92. package/dist/commands/list/views.js +37 -34
  93. package/dist/commands/list/views.js.map +1 -1
  94. package/dist/commands/mcp/dispatch.js +91 -36
  95. package/dist/commands/mcp/dispatch.js.map +1 -1
  96. package/dist/commands/mcp/mcp.js +56 -15
  97. package/dist/commands/mcp/mcp.js.map +1 -1
  98. package/dist/commands/mcp/serve.js.map +1 -1
  99. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  100. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  101. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  102. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  103. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  104. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  105. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  106. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  107. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  108. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  109. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  110. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  111. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  112. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  113. package/dist/commands/new/capability-template.js +118 -0
  114. package/dist/commands/new/capability-template.js.map +1 -0
  115. package/dist/commands/new/commit.js +8 -22
  116. package/dist/commands/new/commit.js.map +1 -1
  117. package/dist/commands/new/modifies.js +206 -0
  118. package/dist/commands/new/modifies.js.map +1 -0
  119. package/dist/commands/new/new.js +59 -14
  120. package/dist/commands/new/new.js.map +1 -1
  121. package/dist/commands/new/templates.js +48 -124
  122. package/dist/commands/new/templates.js.map +1 -1
  123. package/dist/commands/open.js +3 -7
  124. package/dist/commands/open.js.map +1 -1
  125. package/dist/commands/policy/gate.js +27 -0
  126. package/dist/commands/policy/gate.js.map +1 -1
  127. package/dist/commands/rebase/plan.js +2 -2
  128. package/dist/commands/rebase/plan.js.map +1 -1
  129. package/dist/commands/rebase/rebase.js +60 -35
  130. package/dist/commands/rebase/rebase.js.map +1 -1
  131. package/dist/commands/seed/commit.js +55 -26
  132. package/dist/commands/seed/commit.js.map +1 -1
  133. package/dist/commands/seed/plan.js +158 -13
  134. package/dist/commands/seed/plan.js.map +1 -1
  135. package/dist/commands/seed/seed.js +13 -11
  136. package/dist/commands/seed/seed.js.map +1 -1
  137. package/dist/commands/show/feature.js +9 -5
  138. package/dist/commands/show/feature.js.map +1 -1
  139. package/dist/commands/show/introduced.js +157 -0
  140. package/dist/commands/show/introduced.js.map +1 -0
  141. package/dist/commands/show/marks.js +35 -6
  142. package/dist/commands/show/marks.js.map +1 -1
  143. package/dist/commands/show/service.js +41 -33
  144. package/dist/commands/show/service.js.map +1 -1
  145. package/dist/commands/show/show.js +17 -7
  146. package/dist/commands/show/show.js.map +1 -1
  147. package/dist/commands/status/fleet-next.js +90 -0
  148. package/dist/commands/status/fleet-next.js.map +1 -0
  149. package/dist/commands/status/json.js +2 -0
  150. package/dist/commands/status/json.js.map +1 -1
  151. package/dist/commands/status/print.js +89 -37
  152. package/dist/commands/status/print.js.map +1 -1
  153. package/dist/commands/status/status.js +3 -7
  154. package/dist/commands/status/status.js.map +1 -1
  155. package/dist/commands/steps/steps.js +103 -23
  156. package/dist/commands/steps/steps.js.map +1 -1
  157. package/dist/commands/subsystem/edit.js +7 -7
  158. package/dist/commands/subsystem/edit.js.map +1 -1
  159. package/dist/commands/subsystem/move.js +106 -46
  160. package/dist/commands/subsystem/move.js.map +1 -1
  161. package/dist/commands/subsystem/query.js +2 -4
  162. package/dist/commands/subsystem/query.js.map +1 -1
  163. package/dist/commands/subsystem/subsystem.js +4 -7
  164. package/dist/commands/subsystem/subsystem.js.map +1 -1
  165. package/dist/commands/subsystem/sync.js +289 -74
  166. package/dist/commands/subsystem/sync.js.map +1 -1
  167. package/dist/commands/subsystem/txn/exclude.js +132 -0
  168. package/dist/commands/subsystem/txn/exclude.js.map +1 -0
  169. package/dist/commands/subsystem/txn/links.js +128 -0
  170. package/dist/commands/subsystem/txn/links.js.map +1 -0
  171. package/dist/commands/subsystem/txn/report.js +174 -0
  172. package/dist/commands/subsystem/txn/report.js.map +1 -0
  173. package/dist/commands/subsystem/txn/txn.js +147 -45
  174. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  175. package/dist/commands/subsystem/txn/views.js +200 -21
  176. package/dist/commands/subsystem/txn/views.js.map +1 -1
  177. package/dist/commands/tasks/actions.js +107 -0
  178. package/dist/commands/tasks/actions.js.map +1 -0
  179. package/dist/commands/tasks/render.js +253 -0
  180. package/dist/commands/tasks/render.js.map +1 -0
  181. package/dist/commands/tasks/tasks.js +119 -0
  182. package/dist/commands/tasks/tasks.js.map +1 -0
  183. package/dist/commands/unarchive/manifest.js +42 -26
  184. package/dist/commands/unarchive/manifest.js.map +1 -1
  185. package/dist/commands/unarchive/restore.js +83 -33
  186. package/dist/commands/unarchive/restore.js.map +1 -1
  187. package/dist/commands/unarchive/unarchive.js +22 -30
  188. package/dist/commands/unarchive/unarchive.js.map +1 -1
  189. package/dist/commands/validate/arch-coverage.js +78 -27
  190. package/dist/commands/validate/arch-coverage.js.map +1 -1
  191. package/dist/commands/validate/checks/fleet-shape.js +34 -126
  192. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  193. package/dist/commands/validate/checks/health-deps.js +9 -6
  194. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  195. package/dist/commands/validate/checks/introduced.js +102 -0
  196. package/dist/commands/validate/checks/introduced.js.map +1 -0
  197. package/dist/commands/validate/checks/requirements.js +18 -8
  198. package/dist/commands/validate/checks/requirements.js.map +1 -1
  199. package/dist/commands/validate/checks/sources.js +11 -2
  200. package/dist/commands/validate/checks/sources.js.map +1 -1
  201. package/dist/commands/validate/coverage/covers-after.js +105 -0
  202. package/dist/commands/validate/coverage/covers-after.js.map +1 -0
  203. package/dist/commands/validate/feature.js +26 -20
  204. package/dist/commands/validate/feature.js.map +1 -1
  205. package/dist/commands/validate/fleet/census.js +37 -13
  206. package/dist/commands/validate/fleet/census.js.map +1 -1
  207. package/dist/commands/validate/fleet/landscape.js +144 -126
  208. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  209. package/dist/commands/validate/fleet/load.js +66 -0
  210. package/dist/commands/validate/fleet/load.js.map +1 -1
  211. package/dist/commands/validate/fleet/map/attest.js +98 -0
  212. package/dist/commands/validate/fleet/map/attest.js.map +1 -0
  213. package/dist/commands/validate/fleet/map/bindings.js +107 -0
  214. package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
  215. package/dist/commands/validate/fleet/map/consumers.js +244 -0
  216. package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
  217. package/dist/commands/validate/fleet/map/isolation.js +116 -0
  218. package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
  219. package/dist/commands/validate/fleet/map/usecases.js +54 -0
  220. package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
  221. package/dist/commands/validate/fleet/obligations.js +44 -14
  222. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  223. package/dist/commands/validate/fleet/scorecard/print.js +10 -2
  224. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  225. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  226. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  227. package/dist/commands/validate/fleet/usecases/place.js.map +1 -1
  228. package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
  229. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
  230. package/dist/commands/validate/fleet/views/ids.js +50 -15
  231. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  232. package/dist/commands/validate/fleet/views/projects.js +278 -0
  233. package/dist/commands/validate/fleet/views/projects.js.map +1 -0
  234. package/dist/commands/validate/fleet/views/root-documents.js +83 -0
  235. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
  236. package/dist/commands/validate/fleet/views/stale.js +40 -47
  237. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  238. package/dist/commands/validate/links/corpus.js +1 -4
  239. package/dist/commands/validate/links/corpus.js.map +1 -1
  240. package/dist/commands/validate/links/glossary.js.map +1 -1
  241. package/dist/commands/validate/render/repeats.js +63 -0
  242. package/dist/commands/validate/render/repeats.js.map +1 -0
  243. package/dist/commands/validate/render/rollups.js +115 -0
  244. package/dist/commands/validate/render/rollups.js.map +1 -0
  245. package/dist/commands/validate/render/text.js +199 -0
  246. package/dist/commands/validate/render/text.js.map +1 -0
  247. package/dist/commands/validate/report.js +36 -160
  248. package/dist/commands/validate/report.js.map +1 -1
  249. package/dist/commands/validate/scope.js +113 -0
  250. package/dist/commands/validate/scope.js.map +1 -0
  251. package/dist/commands/validate/service/api.js +21 -107
  252. package/dist/commands/validate/service/api.js.map +1 -1
  253. package/dist/commands/validate/service/contracts/generated.js +134 -0
  254. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  255. package/dist/commands/validate/service/events/events.js +36 -14
  256. package/dist/commands/validate/service/events/events.js.map +1 -1
  257. package/dist/commands/validate/service/events/message-producers.js +1 -1
  258. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  259. package/dist/commands/validate/service/evidence-pins.js +56 -10
  260. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  261. package/dist/commands/validate/service/model/diverged.js +226 -0
  262. package/dist/commands/validate/service/model/diverged.js.map +1 -0
  263. package/dist/commands/validate/service/model/grade.js +163 -0
  264. package/dist/commands/validate/service/model/grade.js.map +1 -0
  265. package/dist/commands/validate/service/service.js +121 -61
  266. package/dist/commands/validate/service/service.js.map +1 -1
  267. package/dist/commands/validate/service/specs.js +88 -28
  268. package/dist/commands/validate/service/specs.js.map +1 -1
  269. package/dist/commands/validate/service/spine.js +38 -3
  270. package/dist/commands/validate/service/spine.js.map +1 -1
  271. package/dist/commands/validate/service/usecases/tags.js +113 -0
  272. package/dist/commands/validate/service/usecases/tags.js.map +1 -0
  273. package/dist/commands/validate/service/usecases/usecases.js +209 -0
  274. package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
  275. package/dist/commands/validate/validate.js +85 -60
  276. package/dist/commands/validate/validate.js.map +1 -1
  277. package/dist/commands/verify/cross/diff.js +2 -6
  278. package/dist/commands/verify/cross/diff.js.map +1 -1
  279. package/dist/commands/verify/evidence/contract.js +1 -1
  280. package/dist/commands/verify/evidence/contract.js.map +1 -1
  281. package/dist/commands/verify/evidence/read.js +12 -3
  282. package/dist/commands/verify/evidence/read.js.map +1 -1
  283. package/dist/commands/verify/frozen.js +45 -23
  284. package/dist/commands/verify/frozen.js.map +1 -1
  285. package/dist/commands/verify/record.js +69 -17
  286. package/dist/commands/verify/record.js.map +1 -1
  287. package/dist/commands/verify/report.js +27 -44
  288. package/dist/commands/verify/report.js.map +1 -1
  289. package/dist/commands/verify/results.js +112 -30
  290. package/dist/commands/verify/results.js.map +1 -1
  291. package/dist/commands/verify/verify.js +55 -52
  292. package/dist/commands/verify/verify.js.map +1 -1
  293. package/dist/commands/vouch/pack/pack.js +2 -2
  294. package/dist/commands/vouch/pack/pack.js.map +1 -1
  295. package/dist/commands/vouch/pack/print.js +1 -6
  296. package/dist/commands/vouch/pack/print.js.map +1 -1
  297. package/dist/commands/vouch/pack/sections.js +1 -2
  298. package/dist/commands/vouch/pack/sections.js.map +1 -1
  299. package/dist/commands/vouch/run.js +26 -81
  300. package/dist/commands/vouch/run.js.map +1 -1
  301. package/dist/commands/vouch/sample/plan.js +20 -34
  302. package/dist/commands/vouch/sample/plan.js.map +1 -1
  303. package/dist/commands/vouch/vet/verify.js +39 -7
  304. package/dist/commands/vouch/vet/verify.js.map +1 -1
  305. package/dist/commands/vouch/vouch.js +10 -17
  306. package/dist/commands/vouch/vouch.js.map +1 -1
  307. package/dist/core/agent/agents-md/command-map.js +81 -106
  308. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  309. package/dist/core/agent/agents-md/map/explain.js +21 -9
  310. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  311. package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
  312. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  313. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  314. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  315. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  316. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  317. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  318. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  319. package/dist/core/agent/agents-md/map/status.js +24 -8
  320. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  321. package/dist/core/agent/agents-md/map/subsystem.js +80 -8
  322. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  323. package/dist/core/agent/agents-md/map/usecases.js +88 -10
  324. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  325. package/dist/core/agent/agents-md/refusals.js +49 -22
  326. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  327. package/dist/core/agent/agents-md.js +39 -6
  328. package/dist/core/agent/agents-md.js.map +1 -1
  329. package/dist/core/agent/agents-stamp.js +10 -0
  330. package/dist/core/agent/agents-stamp.js.map +1 -1
  331. package/dist/core/agent/protocol.js +1 -1
  332. package/dist/core/agent/protocol.js.map +1 -1
  333. package/dist/core/agent/scaffold.js +10 -15
  334. package/dist/core/agent/scaffold.js.map +1 -1
  335. package/dist/core/agent/support/report.js +46 -5
  336. package/dist/core/agent/support/report.js.map +1 -1
  337. package/dist/core/agent/tools/dialects.js +1 -0
  338. package/dist/core/agent/tools/dialects.js.map +1 -1
  339. package/dist/core/agent/tools/registry.js +41 -76
  340. package/dist/core/agent/tools/registry.js.map +1 -1
  341. package/dist/core/agent/workflows/adopt.js +158 -11
  342. package/dist/core/agent/workflows/adopt.js.map +1 -1
  343. package/dist/core/agent/workflows/check.js +78 -39
  344. package/dist/core/agent/workflows/check.js.map +1 -1
  345. package/dist/core/agent/workflows/closing.js +88 -27
  346. package/dist/core/agent/workflows/closing.js.map +1 -1
  347. package/dist/core/agent/workflows/feature.js +128 -13
  348. package/dist/core/agent/workflows/feature.js.map +1 -1
  349. package/dist/core/agent/workflows/implement.js +96 -13
  350. package/dist/core/agent/workflows/implement.js.map +1 -1
  351. package/dist/core/agent/workflows/reference/authoring.js +106 -4
  352. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  353. package/dist/core/agent/workflows/reference/codes.js +8 -6
  354. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  355. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  356. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  357. package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
  358. package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
  359. package/dist/core/agent/workflows/reference/reference.js +6 -3
  360. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  361. package/dist/core/agent/workflows/reference/spine.js +68 -27
  362. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  363. package/dist/core/asyncapi/baseline/plan.js +14 -81
  364. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  365. package/dist/core/asyncapi/digest.js +30 -25
  366. package/dist/core/asyncapi/digest.js.map +1 -1
  367. package/dist/core/asyncapi/merge/components.js +5 -4
  368. package/dist/core/asyncapi/merge/components.js.map +1 -1
  369. package/dist/core/asyncapi/merge/error.js +7 -2
  370. package/dist/core/asyncapi/merge/error.js.map +1 -1
  371. package/dist/core/asyncapi/merge/markers.js +32 -50
  372. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  373. package/dist/core/asyncapi/merge/merge.js +33 -53
  374. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  375. package/dist/core/asyncapi/merge/pin.js +12 -36
  376. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  377. package/dist/core/asyncapi/model.js.map +1 -1
  378. package/dist/core/asyncapi/read.js +36 -12
  379. package/dist/core/asyncapi/read.js.map +1 -1
  380. package/dist/core/brief/brief.js +96 -36
  381. package/dist/core/brief/brief.js.map +1 -1
  382. package/dist/core/brief/checks.js +138 -3
  383. package/dist/core/brief/checks.js.map +1 -1
  384. package/dist/core/brief/landscape.js +209 -130
  385. package/dist/core/brief/landscape.js.map +1 -1
  386. package/dist/core/brief/map/extend.js +84 -0
  387. package/dist/core/brief/map/extend.js.map +1 -0
  388. package/dist/core/brief/map/owed.js +266 -0
  389. package/dist/core/brief/map/owed.js.map +1 -0
  390. package/dist/core/brief/targets.js +109 -31
  391. package/dist/core/brief/targets.js.map +1 -1
  392. package/dist/core/brief/unchecked.js +14 -3
  393. package/dist/core/brief/unchecked.js.map +1 -1
  394. package/dist/core/c4/arch.js +90 -182
  395. package/dist/core/c4/arch.js.map +1 -1
  396. package/dist/core/c4/direction/direction.js +114 -0
  397. package/dist/core/c4/direction/direction.js.map +1 -0
  398. package/dist/core/c4/likec4.js +169 -14
  399. package/dist/core/c4/likec4.js.map +1 -1
  400. package/dist/core/c4/locate/unlocated.js +150 -0
  401. package/dist/core/c4/locate/unlocated.js.map +1 -0
  402. package/dist/core/c4/parsed/deployment.js +160 -0
  403. package/dist/core/c4/parsed/deployment.js.map +1 -0
  404. package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
  405. package/dist/core/c4/parsed/values.js +54 -1
  406. package/dist/core/c4/parsed/values.js.map +1 -1
  407. package/dist/core/c4/parsed/view-ids.js +39 -1
  408. package/dist/core/c4/parsed/view-ids.js.map +1 -1
  409. package/dist/core/c4/prefetch/fleet.js +134 -0
  410. package/dist/core/c4/prefetch/fleet.js.map +1 -0
  411. package/dist/core/c4/project/architecture.js +123 -7
  412. package/dist/core/c4/project/architecture.js.map +1 -1
  413. package/dist/core/c4/project/batch.js +140 -0
  414. package/dist/core/c4/project/batch.js.map +1 -0
  415. package/dist/core/c4/project/documents.js +43 -12
  416. package/dist/core/c4/project/documents.js.map +1 -1
  417. package/dist/core/c4/project/load.js +79 -43
  418. package/dist/core/c4/project/load.js.map +1 -1
  419. package/dist/core/c4/project/staged.js +277 -0
  420. package/dist/core/c4/project/staged.js.map +1 -0
  421. package/dist/core/c4/resolve/attested.js +0 -0
  422. package/dist/core/c4/resolve/attested.js.map +1 -0
  423. package/dist/core/c4/resolve/external.js +51 -0
  424. package/dist/core/c4/resolve/external.js.map +1 -0
  425. package/dist/core/c4/resolve/service.js +78 -17
  426. package/dist/core/c4/resolve/service.js.map +1 -1
  427. package/dist/core/c4/resolve/steps.js +290 -0
  428. package/dist/core/c4/resolve/steps.js.map +1 -0
  429. package/dist/core/c4/resolve/systems.js +154 -0
  430. package/dist/core/c4/resolve/systems.js.map +1 -0
  431. package/dist/core/c4/root-project/exclude.js +352 -0
  432. package/dist/core/c4/root-project/exclude.js.map +1 -0
  433. package/dist/core/c4/seed/template.js +65 -1
  434. package/dist/core/c4/seed/template.js.map +1 -1
  435. package/dist/core/c4/service-model/created/models.js +96 -0
  436. package/dist/core/c4/service-model/created/models.js.map +1 -0
  437. package/dist/core/c4/service-model/documents.js +47 -0
  438. package/dist/core/c4/service-model/documents.js.map +1 -0
  439. package/dist/core/c4/service-model/fleet/extending.js +82 -0
  440. package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
  441. package/dist/core/c4/service-model/load.js +165 -0
  442. package/dist/core/c4/service-model/load.js.map +1 -0
  443. package/dist/core/c4/service-model/renderer.js +128 -0
  444. package/dist/core/c4/service-model/renderer.js.map +1 -0
  445. package/dist/core/c4/service-model/shape.js +106 -0
  446. package/dist/core/c4/service-model/shape.js.map +1 -0
  447. package/dist/core/c4/service-model/slice.js +188 -0
  448. package/dist/core/c4/service-model/slice.js.map +1 -0
  449. package/dist/core/c4/source-mask.js +297 -10
  450. package/dist/core/c4/source-mask.js.map +1 -1
  451. package/dist/core/c4/source-scan.js +222 -141
  452. package/dist/core/c4/source-scan.js.map +1 -1
  453. package/dist/core/c4/splice/authored-source.js +79 -13
  454. package/dist/core/c4/splice/authored-source.js.map +1 -1
  455. package/dist/core/c4/splice/contract.js +20 -0
  456. package/dist/core/c4/splice/contract.js.map +1 -1
  457. package/dist/core/c4/splice/delta-blocks.js +20 -20
  458. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  459. package/dist/core/c4/splice/identity/declared.js +87 -0
  460. package/dist/core/c4/splice/identity/declared.js.map +1 -0
  461. package/dist/core/c4/splice/identity/edges.js +37 -0
  462. package/dist/core/c4/splice/identity/edges.js.map +1 -1
  463. package/dist/core/c4/splice/identity/existing.js +80 -0
  464. package/dist/core/c4/splice/identity/existing.js.map +1 -0
  465. package/dist/core/c4/splice/identity/interior.js +80 -0
  466. package/dist/core/c4/splice/identity/interior.js.map +1 -0
  467. package/dist/core/c4/splice/identity/owner.js +50 -0
  468. package/dist/core/c4/splice/identity/owner.js.map +1 -0
  469. package/dist/core/c4/splice/landscape-merge.js +182 -114
  470. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  471. package/dist/core/c4/splice/model/merge.js +151 -0
  472. package/dist/core/c4/splice/model/merge.js.map +1 -0
  473. package/dist/core/c4/splice/placement.js +160 -56
  474. package/dist/core/c4/splice/placement.js.map +1 -1
  475. package/dist/core/c4/splice/text/spot.js +131 -0
  476. package/dist/core/c4/splice/text/spot.js.map +1 -0
  477. package/dist/core/c4/workspace.js +13 -19
  478. package/dist/core/c4/workspace.js.map +1 -1
  479. package/dist/core/capabilities/capabilities.js +6 -6
  480. package/dist/core/capabilities/capabilities.js.map +1 -1
  481. package/dist/core/capabilities/delta/doc.js +7 -10
  482. package/dist/core/capabilities/delta/doc.js.map +1 -1
  483. package/dist/core/capabilities/delta/uncovered.js +2 -2
  484. package/dist/core/capabilities/delta/uncovered.js.map +1 -1
  485. package/dist/core/capabilities/findings.js +18 -22
  486. package/dist/core/capabilities/findings.js.map +1 -1
  487. package/dist/core/capabilities/realizes/findings.js +12 -19
  488. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  489. package/dist/core/capabilities/realizes/join.js +0 -20
  490. package/dist/core/capabilities/realizes/join.js.map +1 -1
  491. package/dist/core/capabilities/rollup.js +6 -0
  492. package/dist/core/capabilities/rollup.js.map +1 -1
  493. package/dist/core/capabilities/tree.js +6 -28
  494. package/dist/core/capabilities/tree.js.map +1 -1
  495. package/dist/core/capabilities/usecase-join.js +3 -13
  496. package/dist/core/capabilities/usecase-join.js.map +1 -1
  497. package/dist/core/coherence/authoring/scaffold.js +11 -3
  498. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  499. package/dist/core/coherence/authoring/sentinels.js +2 -1
  500. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  501. package/dist/core/coherence/calls/called.js +52 -0
  502. package/dist/core/coherence/calls/called.js.map +1 -0
  503. package/dist/core/coherence/coherence.js +103 -22
  504. package/dist/core/coherence/coherence.js.map +1 -1
  505. package/dist/core/coherence/consumed/operations.js +70 -0
  506. package/dist/core/coherence/consumed/operations.js.map +1 -0
  507. package/dist/core/coherence/consumed/refusal.js +38 -0
  508. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  509. package/dist/core/coherence/covers/health.js +85 -0
  510. package/dist/core/coherence/covers/health.js.map +1 -0
  511. package/dist/core/coherence/declared.js +60 -34
  512. package/dist/core/coherence/declared.js.map +1 -1
  513. package/dist/core/coherence/events/declared.js +44 -59
  514. package/dist/core/coherence/events/declared.js.map +1 -1
  515. package/dist/core/coherence/events/events.js +147 -16
  516. package/dist/core/coherence/events/events.js.map +1 -1
  517. package/dist/core/coherence/events/grades.js +31 -8
  518. package/dist/core/coherence/events/grades.js.map +1 -1
  519. package/dist/core/coherence/events/lookups.js +28 -87
  520. package/dist/core/coherence/events/lookups.js.map +1 -1
  521. package/dist/core/coherence/events/produced.js +100 -0
  522. package/dist/core/coherence/events/produced.js.map +1 -0
  523. package/dist/core/coherence/lookups.js +18 -78
  524. package/dist/core/coherence/lookups.js.map +1 -1
  525. package/dist/core/coherence/retired/archived.js +285 -0
  526. package/dist/core/coherence/retired/archived.js.map +1 -0
  527. package/dist/core/coherence/retired/event-pair.js +62 -0
  528. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  529. package/dist/core/coherence/retired/merged.js +79 -0
  530. package/dist/core/coherence/retired/merged.js.map +1 -0
  531. package/dist/core/coherence/retired/retirement.js +49 -0
  532. package/dist/core/coherence/retired/retirement.js.map +1 -0
  533. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  534. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  535. package/dist/core/coherence/retired/revival/revived.js +297 -0
  536. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  537. package/dist/core/consumers/after.js +113 -0
  538. package/dist/core/consumers/after.js.map +1 -0
  539. package/dist/core/consumers/consumers.js +296 -0
  540. package/dist/core/consumers/consumers.js.map +1 -0
  541. package/dist/core/consumers/current.js +211 -0
  542. package/dist/core/consumers/current.js.map +1 -0
  543. package/dist/core/consumers/scan.js +46 -0
  544. package/dist/core/consumers/scan.js.map +1 -0
  545. package/dist/core/consumers/state.js +27 -0
  546. package/dist/core/consumers/state.js.map +1 -0
  547. package/dist/core/delta/delta.js +51 -6
  548. package/dist/core/delta/delta.js.map +1 -1
  549. package/dist/core/dependencies/fanin.js +2 -2
  550. package/dist/core/dependencies/fanin.js.map +1 -1
  551. package/dist/core/deployment/delta.js +191 -0
  552. package/dist/core/deployment/delta.js.map +1 -0
  553. package/dist/core/deployment/objects.js +145 -0
  554. package/dist/core/deployment/objects.js.map +1 -0
  555. package/dist/core/diff/base-git.js +94 -12
  556. package/dist/core/diff/base-git.js.map +1 -1
  557. package/dist/core/diff/base-state.js +49 -21
  558. package/dist/core/diff/base-state.js.map +1 -1
  559. package/dist/core/diff/joins.js +136 -0
  560. package/dist/core/diff/joins.js.map +1 -0
  561. package/dist/core/diff/scope/changed-paths.js +22 -6
  562. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  563. package/dist/core/diff/scope/edges.js +82 -0
  564. package/dist/core/diff/scope/edges.js.map +1 -0
  565. package/dist/core/diff/semantic.js +100 -31
  566. package/dist/core/diff/semantic.js.map +1 -1
  567. package/dist/core/docs.js +31 -22
  568. package/dist/core/docs.js.map +1 -1
  569. package/dist/core/doctor/agents.js +34 -18
  570. package/dist/core/doctor/agents.js.map +1 -1
  571. package/dist/core/doctor/config.js +7 -3
  572. package/dist/core/doctor/config.js.map +1 -1
  573. package/dist/core/doctor/doctor.js +91 -11
  574. package/dist/core/doctor/doctor.js.map +1 -1
  575. package/dist/core/doctor/report.js.map +1 -1
  576. package/dist/core/doctor/reports/scan.js +242 -0
  577. package/dist/core/doctor/reports/scan.js.map +1 -0
  578. package/dist/core/document/apply.js +158 -11
  579. package/dist/core/document/apply.js.map +1 -1
  580. package/dist/core/document/frontmatter.js +55 -0
  581. package/dist/core/document/frontmatter.js.map +1 -1
  582. package/dist/core/document/parse.js +29 -6
  583. package/dist/core/document/parse.js.map +1 -1
  584. package/dist/core/document/scenarios.js +17 -0
  585. package/dist/core/document/scenarios.js.map +1 -1
  586. package/dist/core/document/spec.js +4 -11
  587. package/dist/core/document/spec.js.map +1 -1
  588. package/dist/core/envelope/config.js +10 -0
  589. package/dist/core/envelope/config.js.map +1 -1
  590. package/dist/core/envelope/json.js +34 -0
  591. package/dist/core/envelope/json.js.map +1 -1
  592. package/dist/core/explain/families.js +26 -21
  593. package/dist/core/explain/families.js.map +1 -1
  594. package/dist/core/explain/lookup.js +14 -1
  595. package/dist/core/explain/lookup.js.map +1 -1
  596. package/dist/core/explain/refusals.js +5 -5
  597. package/dist/core/explain/refusals.js.map +1 -1
  598. package/dist/core/explain/terms/documents.js +227 -0
  599. package/dist/core/explain/terms/documents.js.map +1 -0
  600. package/dist/core/explain/terms/done-check.js +144 -0
  601. package/dist/core/explain/terms/done-check.js.map +1 -0
  602. package/dist/core/explain/terms/entry.js +14 -0
  603. package/dist/core/explain/terms/entry.js.map +1 -0
  604. package/dist/core/explain/terms/stages.js +71 -0
  605. package/dist/core/explain/terms/stages.js.map +1 -0
  606. package/dist/core/explain/terms/tasks.js +97 -0
  607. package/dist/core/explain/terms/tasks.js.map +1 -0
  608. package/dist/core/explain/terms.js +68 -109
  609. package/dist/core/explain/terms.js.map +1 -1
  610. package/dist/core/explore/explore.js +40 -10
  611. package/dist/core/explore/explore.js.map +1 -1
  612. package/dist/core/fleet-context.js +81 -95
  613. package/dist/core/fleet-context.js.map +1 -1
  614. package/dist/core/gate/checks.js +11 -4
  615. package/dist/core/gate/checks.js.map +1 -1
  616. package/dist/core/gate/partners.js +69 -28
  617. package/dist/core/gate/partners.js.map +1 -1
  618. package/dist/core/gate/report.js.map +1 -1
  619. package/dist/core/gate/verification.js +75 -12
  620. package/dist/core/gate/verification.js.map +1 -1
  621. package/dist/core/gherkin/stale.js +13 -37
  622. package/dist/core/gherkin/stale.js.map +1 -1
  623. package/dist/core/glossary/delta.js +10 -7
  624. package/dist/core/glossary/delta.js.map +1 -1
  625. package/dist/core/glossary/tree.js +6 -19
  626. package/dist/core/glossary/tree.js.map +1 -1
  627. package/dist/core/kernel/concurrency.js +19 -0
  628. package/dist/core/kernel/concurrency.js.map +1 -1
  629. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  630. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  631. package/dist/core/kernel/path-safety.js +38 -0
  632. package/dist/core/kernel/path-safety.js.map +1 -1
  633. package/dist/core/kernel/records.js +11 -0
  634. package/dist/core/kernel/records.js.map +1 -1
  635. package/dist/core/links/backlinks.js +1 -3
  636. package/dist/core/links/backlinks.js.map +1 -1
  637. package/dist/core/links/corpus.js +51 -37
  638. package/dist/core/links/corpus.js.map +1 -1
  639. package/dist/core/links/findings.js.map +1 -1
  640. package/dist/core/links/parse.js +38 -9
  641. package/dist/core/links/parse.js.map +1 -1
  642. package/dist/core/mcp/argv.js +101 -0
  643. package/dist/core/mcp/argv.js.map +1 -1
  644. package/dist/core/mcp/author-tools.js +2 -1
  645. package/dist/core/mcp/author-tools.js.map +1 -1
  646. package/dist/core/mcp/protocol.js +2 -2
  647. package/dist/core/mcp/protocol.js.map +1 -1
  648. package/dist/core/mcp/tools.js +43 -33
  649. package/dist/core/mcp/tools.js.map +1 -1
  650. package/dist/core/openapi/baseline/gate.js +53 -12
  651. package/dist/core/openapi/baseline/gate.js.map +1 -1
  652. package/dist/core/openapi/baseline/plan.js +40 -10
  653. package/dist/core/openapi/baseline/plan.js.map +1 -1
  654. package/dist/core/openapi/baseline/record.js +0 -1
  655. package/dist/core/openapi/baseline/record.js.map +1 -1
  656. package/dist/core/openapi/contract-text.js +33 -0
  657. package/dist/core/openapi/contract-text.js.map +1 -0
  658. package/dist/core/openapi/contract-tree.js +301 -0
  659. package/dist/core/openapi/contract-tree.js.map +1 -0
  660. package/dist/core/openapi/depth.js +38 -24
  661. package/dist/core/openapi/depth.js.map +1 -1
  662. package/dist/core/openapi/digest.js +52 -2
  663. package/dist/core/openapi/digest.js.map +1 -1
  664. package/dist/core/openapi/doc.js +12 -4
  665. package/dist/core/openapi/doc.js.map +1 -1
  666. package/dist/core/openapi/merge/components.js +25 -60
  667. package/dist/core/openapi/merge/components.js.map +1 -1
  668. package/dist/core/openapi/merge/error.js +3 -13
  669. package/dist/core/openapi/merge/error.js.map +1 -1
  670. package/dist/core/openapi/merge/markers.js +26 -30
  671. package/dist/core/openapi/merge/markers.js.map +1 -1
  672. package/dist/core/openapi/merge/merge.js +28 -52
  673. package/dist/core/openapi/merge/merge.js.map +1 -1
  674. package/dist/core/openapi/merge/pin.js +17 -35
  675. package/dist/core/openapi/merge/pin.js.map +1 -1
  676. package/dist/core/openspec/decide/verdict.js.map +1 -1
  677. package/dist/core/openspec/inventory.js +4 -5
  678. package/dist/core/openspec/inventory.js.map +1 -1
  679. package/dist/core/openspec/model/mapping.js +30 -28
  680. package/dist/core/openspec/model/mapping.js.map +1 -1
  681. package/dist/core/openspec/model/model.js +12 -7
  682. package/dist/core/openspec/model/model.js.map +1 -1
  683. package/dist/core/openspec/scan/changes.js +10 -4
  684. package/dist/core/openspec/scan/changes.js.map +1 -1
  685. package/dist/core/openspec/scan/config.js +7 -16
  686. package/dist/core/openspec/scan/config.js.map +1 -1
  687. package/dist/core/openspec/scan/shape.js +63 -19
  688. package/dist/core/openspec/scan/shape.js.map +1 -1
  689. package/dist/core/openspec/scan/walk.js +21 -38
  690. package/dist/core/openspec/scan/walk.js.map +1 -1
  691. package/dist/core/pack/living.js +43 -4
  692. package/dist/core/pack/living.js.map +1 -1
  693. package/dist/core/pack/pack.js +50 -6
  694. package/dist/core/pack/pack.js.map +1 -1
  695. package/dist/core/projection/arch-slice.js +2 -2
  696. package/dist/core/projection/arch-slice.js.map +1 -1
  697. package/dist/core/projection/introduced.js +14 -0
  698. package/dist/core/projection/introduced.js.map +1 -0
  699. package/dist/core/provenance/findings.js +48 -12
  700. package/dist/core/provenance/findings.js.map +1 -1
  701. package/dist/core/provenance/git.js +33 -57
  702. package/dist/core/provenance/git.js.map +1 -1
  703. package/dist/core/provenance/gitq/moves.js +71 -114
  704. package/dist/core/provenance/gitq/moves.js.map +1 -1
  705. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  706. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  707. package/dist/core/provenance/sources.js +23 -6
  708. package/dist/core/provenance/sources.js.map +1 -1
  709. package/dist/core/provenance/stamp.js +5 -17
  710. package/dist/core/provenance/stamp.js.map +1 -1
  711. package/dist/core/repo/authored/paths.js +18 -0
  712. package/dist/core/repo/authored/paths.js.map +1 -1
  713. package/dist/core/repo/entries.js.map +1 -1
  714. package/dist/core/repo/paths.js +92 -6
  715. package/dist/core/repo/paths.js.map +1 -1
  716. package/dist/core/repo/repo.js +74 -14
  717. package/dist/core/repo/repo.js.map +1 -1
  718. package/dist/core/repo/tree/find.js +12 -3
  719. package/dist/core/repo/tree/find.js.map +1 -1
  720. package/dist/core/repo/tree/fs.js +27 -1
  721. package/dist/core/repo/tree/fs.js.map +1 -1
  722. package/dist/core/repo/tree/render/body.js +258 -0
  723. package/dist/core/repo/tree/render/body.js.map +1 -0
  724. package/dist/core/repo/tree/render/projects.js +182 -0
  725. package/dist/core/repo/tree/render/projects.js.map +1 -0
  726. package/dist/core/repo/tree/render/service/header.js +125 -0
  727. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  728. package/dist/core/repo/tree/render/service/peers.js +28 -0
  729. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  730. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  731. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  732. package/dist/core/repo/tree/render/service/views.js +78 -0
  733. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  734. package/dist/core/repo/tree/render/stale.js +42 -0
  735. package/dist/core/repo/tree/render/stale.js.map +1 -0
  736. package/dist/core/repo/tree/render/text.js +84 -0
  737. package/dist/core/repo/tree/render/text.js.map +1 -0
  738. package/dist/core/repo/tree/render/views.js +293 -0
  739. package/dist/core/repo/tree/render/views.js.map +1 -0
  740. package/dist/core/repo/tree/walk.js +53 -32
  741. package/dist/core/repo/tree/walk.js.map +1 -1
  742. package/dist/core/results.js +47 -12
  743. package/dist/core/results.js.map +1 -1
  744. package/dist/core/scaffold/landscape.js +121 -13
  745. package/dist/core/scaffold/landscape.js.map +1 -1
  746. package/dist/core/scaffold/legacy.js +271 -0
  747. package/dist/core/scaffold/legacy.js.map +1 -0
  748. package/dist/core/staging/commit.js +26 -15
  749. package/dist/core/staging/commit.js.map +1 -1
  750. package/dist/core/staging/lock.js +74 -12
  751. package/dist/core/staging/lock.js.map +1 -1
  752. package/dist/core/staging/recovery/intent.js +2 -5
  753. package/dist/core/staging/recovery/intent.js.map +1 -1
  754. package/dist/core/staging/recovery/recover.js +84 -6
  755. package/dist/core/staging/recovery/recover.js.map +1 -1
  756. package/dist/core/staging/snapshot/manifest.js +188 -0
  757. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  758. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
  759. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  760. package/dist/core/staging/txn/forward.js +24 -14
  761. package/dist/core/staging/txn/forward.js.map +1 -1
  762. package/dist/core/staging/writes.js.map +1 -1
  763. package/dist/core/status/actions/execution.js +11 -0
  764. package/dist/core/status/actions/execution.js.map +1 -1
  765. package/dist/core/status/contracts.js +39 -15
  766. package/dist/core/status/contracts.js.map +1 -1
  767. package/dist/core/status/feature/artifacts.js +33 -8
  768. package/dist/core/status/feature/artifacts.js.map +1 -1
  769. package/dist/core/status/feature/feature.js +24 -17
  770. package/dist/core/status/feature/feature.js.map +1 -1
  771. package/dist/core/status/feature/next.js +20 -7
  772. package/dist/core/status/feature/next.js.map +1 -1
  773. package/dist/core/status/feature/words.js +96 -0
  774. package/dist/core/status/feature/words.js.map +1 -0
  775. package/dist/core/status/fleet/fleet.js +43 -14
  776. package/dist/core/status/fleet/fleet.js.map +1 -1
  777. package/dist/core/status/fleet/next.js +153 -58
  778. package/dist/core/status/fleet/next.js.map +1 -1
  779. package/dist/core/status/report.js.map +1 -1
  780. package/dist/core/status/scan.js.map +1 -1
  781. package/dist/core/status/verification.js +30 -15
  782. package/dist/core/status/verification.js.map +1 -1
  783. package/dist/core/tasks/packet.js +199 -0
  784. package/dist/core/tasks/packet.js.map +1 -0
  785. package/dist/core/tasks/record.js +43 -0
  786. package/dist/core/tasks/record.js.map +1 -0
  787. package/dist/core/tasks/sources.js +181 -0
  788. package/dist/core/tasks/sources.js.map +1 -0
  789. package/dist/core/tasks/stamps.js +76 -0
  790. package/dist/core/tasks/stamps.js.map +1 -0
  791. package/dist/core/tasks/unit.js +0 -0
  792. package/dist/core/tasks/unit.js.map +1 -0
  793. package/dist/core/usecases/capability.js +11 -14
  794. package/dist/core/usecases/capability.js.map +1 -1
  795. package/dist/core/usecases/delta/claims.js +31 -7
  796. package/dist/core/usecases/delta/claims.js.map +1 -1
  797. package/dist/core/usecases/delta/flows.js +5 -7
  798. package/dist/core/usecases/delta/flows.js.map +1 -1
  799. package/dist/core/usecases/delta/modified.js +315 -0
  800. package/dist/core/usecases/delta/modified.js.map +1 -0
  801. package/dist/core/usecases/delta/overlay.js +32 -191
  802. package/dist/core/usecases/delta/overlay.js.map +1 -1
  803. package/dist/core/usecases/fleet.js +195 -36
  804. package/dist/core/usecases/fleet.js.map +1 -1
  805. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
  806. package/dist/core/usecases/grade/capability-tag.js.map +1 -0
  807. package/dist/core/usecases/grade/feature.js +88 -0
  808. package/dist/core/usecases/grade/feature.js.map +1 -0
  809. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
  810. package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
  811. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
  812. package/dist/core/usecases/grade/steps.js.map +1 -0
  813. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
  814. package/dist/core/usecases/grade/usecases.js.map +1 -0
  815. package/dist/core/usecases/operations.js +26 -14
  816. package/dist/core/usecases/operations.js.map +1 -1
  817. package/dist/core/usecases/place.js.map +1 -1
  818. package/dist/core/usecases/service/exclude.js +119 -0
  819. package/dist/core/usecases/service/exclude.js.map +1 -0
  820. package/dist/core/usecases/service/flows.js +164 -0
  821. package/dist/core/usecases/service/flows.js.map +1 -0
  822. package/dist/core/usecases/suites/participants.js +83 -0
  823. package/dist/core/usecases/suites/participants.js.map +1 -0
  824. package/dist/core/usecases/suites/suites.js +45 -0
  825. package/dist/core/usecases/suites/suites.js.map +1 -0
  826. package/dist/core/verify/answers.js +12 -5
  827. package/dist/core/verify/answers.js.map +1 -1
  828. package/dist/core/verify/build.js +102 -82
  829. package/dist/core/verify/build.js.map +1 -1
  830. package/dist/core/verify/checklist.js +70 -12
  831. package/dist/core/verify/checklist.js.map +1 -1
  832. package/dist/core/verify/location.js +67 -0
  833. package/dist/core/verify/location.js.map +1 -0
  834. package/dist/core/verify/pins/lint.js +3 -2
  835. package/dist/core/verify/pins/lint.js.map +1 -1
  836. package/dist/core/verify/pins/pin.js +1 -1
  837. package/dist/core/verify/pins/pin.js.map +1 -1
  838. package/dist/core/verify/read/file.js +224 -0
  839. package/dist/core/verify/read/file.js.map +1 -0
  840. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  841. package/dist/core/verify/read/shape.js.map +1 -0
  842. package/dist/core/verify/read/union.js +105 -0
  843. package/dist/core/verify/read/union.js.map +1 -0
  844. package/dist/core/verify/record.js +35 -12
  845. package/dist/core/verify/record.js.map +1 -1
  846. package/dist/core/verify/store/commit.js +40 -30
  847. package/dist/core/verify/store/commit.js.map +1 -1
  848. package/dist/core/verify/store/render.js +8 -4
  849. package/dist/core/verify/store/render.js.map +1 -1
  850. package/dist/core/vocabulary/issue.js +4 -0
  851. package/dist/core/vocabulary/issue.js.map +1 -1
  852. package/dist/core/vocabulary/maturity.js +75 -4
  853. package/dist/core/vocabulary/maturity.js.map +1 -1
  854. package/dist/core/vocabulary/report.js +16 -1
  855. package/dist/core/vocabulary/report.js.map +1 -1
  856. package/examples/README.md +54 -11
  857. package/examples/docs/architecture/landscape.likec4 +62 -6
  858. package/examples/docs/architecture/services.likec4 +70 -0
  859. package/examples/docs/architecture/subsystems.likec4 +7 -0
  860. package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
  861. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  862. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  863. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
  864. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  865. package/examples/docs/likec4.config.json +0 -1
  866. package/examples/docs/services/checkout-web/model.likec4 +39 -56
  867. package/examples/docs/services/order-service/arch.spec.md +13 -6
  868. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  869. package/examples/docs/services/order-service/model.likec4 +58 -70
  870. package/examples/docs/services/payment-service/arch.spec.md +7 -4
  871. package/examples/docs/services/payment-service/model.likec4 +38 -37
  872. package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
  873. package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
  874. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  875. package/examples/docs/services/platform/notification-service/health.yaml +8 -3
  876. package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
  877. package/package.json +1 -1
  878. package/dist/commands/validate/checks/vocabulary.js +0 -31
  879. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  880. package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
  881. package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
  882. package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
  883. package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
  884. package/dist/core/agent/agents-md/artifacts.js +0 -220
  885. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  886. package/dist/core/agent/agents-md/cycle.js +0 -165
  887. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  888. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  889. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  890. package/dist/core/agent/agents-md/spine.js +0 -40
  891. package/dist/core/agent/agents-md/spine.js.map +0 -1
  892. package/dist/core/coherence/events/merged.js +0 -40
  893. package/dist/core/coherence/events/merged.js.map +0 -1
  894. package/dist/core/diff/current-state.js +0 -96
  895. package/dist/core/diff/current-state.js.map +0 -1
  896. package/dist/core/diff/victims.js +0 -247
  897. package/dist/core/diff/victims.js.map +0 -1
  898. package/dist/core/repo/tree/views.js +0 -172
  899. package/dist/core/repo/tree/views.js.map +0 -1
  900. package/dist/core/staging/snapshot.js.map +0 -1
  901. package/dist/core/verify/file.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -10,6 +10,3454 @@ case for a change — the alternative that was rejected, the defect it came from
10
10
  generalises — lives where it is maintained: [SCHEMA.md](SCHEMA.md) for the rule,
11
11
  [ROADMAP.md](ROADMAP.md) for the priority and its exit criteria, and the commit that landed it._
12
12
 
13
+ ## [0.2.0-alpha.13] - 2026-10-22
14
+
15
+ Everything on `main` since `0.2.0-alpha.12`. Still a prerelease on the `alpha` line. When this
16
+ version was cut, `0.2.0-alpha.10`, `0.2.0-alpha.11` and `0.2.0-alpha.12` were tagged but none of
17
+ their publishes had run (`alpha` and `latest` both named `0.2.0-alpha.9`), so a repository
18
+ upgrading from alpha.9 takes alpha.10's, alpha.11's and alpha.12's sections below as well as this
19
+ one; `npm view @ybotok/loam dist-tags` is the record of what installs.
20
+
21
+ Almost all of it changes what an agent reads before it writes, not what a check grades. A fifth
22
+ reference page, `loam instructions loam-drawing`, teaches seven relationship kinds with one arrow
23
+ direction each, a direction only from evidence (`connects` otherwise), a `legend` view drawn from
24
+ real edges, and overview views that answer one question at one altitude, measured at the LikeC4
25
+ 1.59.2 pin. `loam instructions loam-authoring` gains "Writing for a person": an intent.md that says
26
+ how to read the feature, a plain-language `## Purpose` for each spec delta, and requirement bodies a
27
+ person can read. The workflow pages point at both, and `loam explain` defines `kind`, `legend`,
28
+ `overview` and `purpose`. The map `loam init --create` scaffolds and the one `loam seed` writes now
29
+ declare the seven kinds in their `specification` block. A map an earlier loam scaffolded and nobody
30
+ touched still reads as the stub, but it declares no kind: add the block from the drawing page before
31
+ a feature draws a kinded edge, because `loam archive` refuses `merge-failed` on a kind the living
32
+ map lacks and `validate --feature` and `loam status` do not predict that refusal yet.
33
+
34
+ No document changes its exit code. The only runs that do are lookups of the new names:
35
+ `loam instructions loam-drawing`, and `loam explain` on `kind`, `legend`, `overview`, `purpose` and
36
+ their aliases (`publishes`, `delivers`, `arrow` and `direction` among them), exit 0 where they
37
+ refused `unknown-target`.
38
+
39
+ No stable code was added, renamed or retired — 360 in all, as at alpha.12, counted with
40
+ `loam explain --codes --json` — and no flag, `--json` key or frontmatter field was added. The new
41
+ page is a fifth entry of `loam instructions --json`'s `references[]` and the MCP resource
42
+ `loam://instructions/loam-drawing`, and the four terms are new entries of `loam explain --json`'s
43
+ `terms[]`.
44
+
45
+ ### Added — `loam instructions loam-drawing`, and agent pages that teach a picture and a feature a person can read
46
+
47
+ No code, flag or exit code was added or changed. The new page holds the convention.
48
+ SCHEMA.md, "Which way an edge points", states the one part of it loam itself depends on.
49
+
50
+ - **A fifth reference page, `loam instructions loam-drawing`**, also served over MCP as
51
+ `loam://instructions/loam-drawing` and listed as a fifth entry of `loam instructions --json`'s
52
+ `references[]`. It teaches how to draw the C4 model so a person can read it. It names seven
53
+ relationship kinds, each with one direction: `publishes` (producer to topic), `delivers` (topic
54
+ to consumer), `reads`, `writes` and `readsWrites` (always written service `->` store, with the
55
+ kind's style placing the arrowhead), `calls`, and `connects` for a connection whose direction
56
+ no evidence establishes. Kinds are declared once in the map's `specification` and again in a
57
+ delta that draws one, never carry a tag, and never use the dotted `a .kind b` form. A
58
+ `legend` view is drawn from real edges. The page also covers overview views, measured at the
59
+ LikeC4 1.59.2 pin: one question at one altitude; a single positive `where kind is …` filter,
60
+ because relationships of different kinds merged into one line render default gray; the
61
+ `include <parent>.* -> * where …` form, which renders an empty view at subsystem altitude;
62
+ `rank` rows; `tail odot` on a direction-less edge; numbered titles; and no authored
63
+ `view … of` a subsystem or service element. `AGENTS.md`'s "Reference pages" section lists the
64
+ page, and loam-adopt, loam-feature, loam-implement, loam-check and loam-ship point at it
65
+ wherever an edge is drawn or read.
66
+ - **Adoption resolves directions, and a feature draws messages through the broker.** Step 3 of
67
+ loam-adopt gives every edge a kind, and a direction only from evidence: the code, a
68
+ `*_PRODUCER_*` or `*_CONSUMER_*` configuration name, or the service's own `asyncapi.yaml`.
69
+ Configuration that only names an address is drawn `connects`, and the adoption turns the
70
+ service's `connects` edges into real kinds. Its hand-back now says eight things; the new one
71
+ is every edge still drawn `connects`. loam-feature's delta step draws producer
72
+ `-[publishes]->` topic `-[delivers]->` consumer, and draws a producer outside the fleet as an
73
+ `#external` element. loam-implement redraws a `connects` edge once the code shows its kind.
74
+ - **`loam instructions loam-authoring` gains "Writing for a person".** It covers intent.md's
75
+ `## How to read this feature`, a business-requirement coverage table, and `## Open questions`
76
+ as a table (ID, question, blocks, owner, current default). Each spec delta gets a
77
+ plain-language `## Purpose`, which is copied into a document the archive creates and stays
78
+ with the archived delta otherwise. A long requirement body becomes a one-line lead plus one
79
+ bullet per SHALL sentence, and abbreviations are expanded on first use. Wording such as
80
+ "this delta" or "this feature" stays out of what the archive copies. It also covers feature
81
+ glossary terms, feature ADRs, use cases tagged `#cap-`, and `Covers:` edges spelled by
82
+ service name. loam-feature's step 4 and loam-ship's dry-run step say the same in brief, and
83
+ loam-feature's analyze step writes open questions as table rows.
84
+ - **`loam explain` knows the new words.** A fifth listing heading, "the picture — drawing the
85
+ map and writing for a person", holds `kind` (which also answers `publishes`, `delivers`,
86
+ `reads`, `writes`, `readsWrites`, `calls`, `connects`, `arrow` and `direction`), `legend`,
87
+ `overview` and `purpose`. `loam explain --json`'s `terms[]` gains the four.
88
+ - **loam-report** rebuilds a reproduction on neutral stand-ins when the fleet's own names are
89
+ confidential, and its `Sanitization` section says when `Actual` quotes the fleet.
90
+
91
+ ### Changed — a new fleet map declares the seven relationship kinds
92
+
93
+ - The `architecture/landscape.likec4` that `loam init --create` scaffolds, and the map
94
+ `loam seed` writes, now declare `publishes`, `delivers`, `reads`, `writes`, `readsWrites`,
95
+ `calls` and `connects` in their `specification` block, with the styles the drawing page
96
+ teaches. The stub's comment shows each kind in use. Seed's edges stay plain: `fleet.yaml`
97
+ says who calls whom and nothing about what an edge carries.
98
+ - No `legend` view is scaffolded. A view names only elements the model declares, and a new map
99
+ declares none.
100
+ - A map an earlier loam scaffolded and nobody touched still reads as the untouched stub:
101
+ `loam status` keeps its `next.author-landscape` step and `loam seed` still replaces it. Such a
102
+ map declares no kind, so add the block from `loam instructions loam-drawing` before a feature
103
+ draws a kinded edge. `loam archive` copies an edge and never a declaration, and refuses with
104
+ `merge-failed` on a kind the living map lacks. `loam validate --feature` and `loam status` do
105
+ not predict that refusal yet.
106
+
107
+ ## [0.2.0-alpha.12] - 2026-10-22
108
+
109
+ Everything on `main` since `0.2.0-alpha.11`. Still a prerelease on the `alpha` line. When this
110
+ version was cut, `0.2.0-alpha.10` and `0.2.0-alpha.11` were tagged but neither publish had run
111
+ (`alpha` and `latest` both named `0.2.0-alpha.9`), so a repository upgrading from alpha.9 takes
112
+ alpha.10's and alpha.11's sections below as well as this one; `npm view @ybotok/loam dist-tags` is
113
+ the record of what installs.
114
+
115
+ Both entries close a gap between what a feature's checks say and what the archive then does. A
116
+ feature that REMOVES a requirement a living flow's `#req-` tag claims, or MODIFIES one and adds an
117
+ id that flattens to the same tag, is refused at feature scope with
118
+ `usecase.requirement-unresolved` — the error `validate --all` used to report only after the
119
+ archive — and that walk reads a service-local tag against both of the service's documents, as the
120
+ fleet does. `loam rebase` writes a contract's `x-loam-based-on` pin and `x-loam-baselines` record
121
+ without re-printing the rest of the feature's `openapi.yaml` or `asyncapi.yaml`.
122
+
123
+ One entry changes an exit code. The refusal is new: `loam validate --feature`, `validate --all` for
124
+ an active feature and `loam archive` (`not-coherent`, which `--approve` overrides) exit 1 for a
125
+ feature whose merge breaks a living flow's `#req-` tag where they exited 0, and `loam status` no
126
+ longer calls that feature ready. Under `--strict`, a `usecase.requirement-modified` warning that
127
+ reading both of a service's documents takes away can let a run pass that failed. `loam rebase`
128
+ changes no exit code.
129
+
130
+ No stable code was added, renamed or retired — 360 in all, as at alpha.11, counted with
131
+ `loam explain --codes --json` — and no flag, `--json` key or frontmatter field was added.
132
+
133
+ ### Fixed — a feature that removes a promise a living flow claims is refused before the archive
134
+
135
+ No code, flag or payload key was added. SCHEMA.md "A promise a living flow keeps, removed" states
136
+ the rule.
137
+
138
+ - **`usecase.requirement-unresolved` is raised at feature scope for a living flow.** A feature that
139
+ REMOVES a requirement a living flow's `#req-` tag claims passed `validate --feature`, `loam status`
140
+ and the archive gate, archived at exit 0, and the next `validate --all` reported
141
+ `usecase.requirement-unresolved` against the flow. The walk that raises
142
+ `usecase.requirement-modified` now raises that error too, at both of its altitudes: an
143
+ `architecture/` flow scoped by its `#cap-` tag to a capability requirement the feature's capability
144
+ delta removes, and a flow beside a service's model naming one of that service's requirements the
145
+ feature's `specs/` delta removes. The message names the living flow file and the requirement, and
146
+ the subject is the view, as `validate --all` names it. `--approve` overrides it. The tag is graded
147
+ against what the merge leaves: a removal the same delta re-spells under an id that flattens to the
148
+ same tag raises nothing, and a tag that does not resolve today is left to `validate --all`, which
149
+ already reports it. **Exit code:** `loam validate --feature`, `validate --all` (per active feature)
150
+ and `loam archive` (`not-coherent`) now exit 1 for such a feature where they exited 0, and
151
+ `loam status` no longer calls it ready.
152
+ - **A MODIFIED that leaves a living flow's tag ambiguous is the same error, not the warning.** A
153
+ feature that MODIFIES a requirement a living flow claims and ADDS an id that flattens to the same
154
+ tag (`PAY-AUTH` beside `PAY.AUTH`) got only `usecase.requirement-modified`, whose message said the
155
+ tag would still resolve; after the archive `validate --all` reported `usecase.requirement-unresolved`.
156
+ It now gets that error, naming the colliding ids, with the same exit codes as above.
157
+ - **A service-local `#req-` tag is read against both of the service's documents in this walk, as the
158
+ fleet reads it.** When the feature carried a delta for only one of spec.md and arch.spec.md, the
159
+ other was not read, so an id it declared did not count; and a requirement under a living document's
160
+ `## REMOVED Requirements` heading counted, though the fleet skips it. Both now match
161
+ `validate --all`. For `usecase.requirement-modified` this can only remove a warning, from a tag the
162
+ two documents already make ambiguous.
163
+
164
+ ### Fixed — `loam rebase` pins a feature's contract without re-printing it
165
+
166
+ No code, flag, exit code or `--json` key was added or removed.
167
+
168
+ - **A rebased `openapi.yaml` or `asyncapi.yaml` changes only where loam pins it.** Writing an
169
+ `x-loam-based-on` pin, or writing or removing the root `x-loam-baselines` record, re-printed the
170
+ feature's whole contract: every long scalar re-folded at 80 columns, every flow sequence
171
+ re-padded (`[a, b]` became `[ a, b ]`) and CRLF turned into LF. Now only the pin lines and the
172
+ record change and the rest of the file stays byte-for-byte as written, with the same fallback to
173
+ the whole-file write that the archive's merges use for a shape they cannot splice safely. A
174
+ contract an earlier rebase already re-printed stays as it is: rebasing it again does not bring
175
+ back the author's layout.
176
+
177
+ ## [0.2.0-alpha.11] - 2026-10-22
178
+
179
+ Everything on `main` since `0.2.0-alpha.10`. Still a prerelease on the `alpha` line. When this
180
+ version was cut, `0.2.0-alpha.10` was tagged but its publish had not run (`alpha` and `latest` both
181
+ named `0.2.0-alpha.9`), so a repository upgrading from alpha.9 takes alpha.10's section below as
182
+ well as this one; `npm view @ybotok/loam dist-tags` is the record of what installs.
183
+
184
+ Most of it makes a grade or an archive say what it used to decide in silence. An event key on an
185
+ edge drawn the wrong way round is named instead of binding nothing, the consumer census counts a
186
+ service at either end of an edge into a store or a topic, and a `Covers:` line may name a kinded
187
+ edge. `loam archive` merges an edge whose relationship kind carries a tag, reads an extending
188
+ model's dotted edges and kind-first declarations, keeps a comment with the statement under it, and
189
+ gives a service it creates its own `services/<svc>/model.likec4`. A feature's new services are the
190
+ ones the archived fleet will call services, and `loam status` owes an `openapi.yaml` only where
191
+ something calls the service. The archive names every scenario and join-line entry a MODIFIED or
192
+ REMOVED requirement takes out of the living docs, marks a first spec.md it writes into a
193
+ never-adopted service `adoption: partial`, and writes only the entries a contract merge changes. A
194
+ use-case step's title can say which of several relationships its hop means.
195
+
196
+ Several entries change an exit code, listed in each. Refusals that are gone: `loam archive` and
197
+ `archive --dry-run` on an edge whose relationship kind carries a tag or on a child of a kind-first
198
+ container in an extending model, and the `usecase.flow-invalid` / `deployment.doc-invalid` that
199
+ `validate --feature`, `validate --all` and `loam status` reported for the same shapes. Refusals that
200
+ are new: a feature's use-case hop or deployment document naming a container the delta draws inside
201
+ a service the feature introduces is `usecase.flow-invalid` / `deployment.doc-invalid` from
202
+ `validate --feature`, `validate --all` for an active feature and `loam archive` (`not-coherent`,
203
+ which `--approve` does not override); on the tagged-kind shape `validate --feature` can now report
204
+ `spine.message-unproduced`; and `loam gate --service <id>` exits 1 for a service whose first
205
+ spec.md an archive wrote as `adoption: partial` (`gate.service-undocumented`). Under `--strict` the
206
+ new warnings can fail a run that passed — `spine.message-unbound`, `usecase.requirement-modified`,
207
+ `openapi.info-not-merged`, `asyncapi.info-not-merged`, `gate.partner-undocumented`, and
208
+ `landscape.datastore-shared` / `landscape.platform-candidate` now that the census counts both ends
209
+ — and the warnings that are gone (`service.no-requirement-delta` on an `#external` producer or a
210
+ living element's stub, `c4.uncovered` on a grouping, a `usecase.step-contested` hop its title
211
+ resolves) can let one pass that failed.
212
+
213
+ Four stable codes were added, all warnings that never gate — `spine.message-unbound`,
214
+ `usecase.requirement-modified`, `openapi.info-not-merged` and `asyncapi.info-not-merged` — 360 in
215
+ all, 356 at alpha.10; none was renamed or retired, and no flag was added. New `--json` keys, all
216
+ additive: `dropped` on `loam archive` and on `loam delta`, and `partial: true` on the spec.md entry
217
+ of `loam adopt`'s `targets[]`. `loam archive --json`'s `plan` gains a `create` row for a service
218
+ model it writes, and `loam status` can read a new service's `openapi` as `done` with
219
+ `required: false`. One frontmatter field is new, written by loam: `adoption: partial`, which stays
220
+ until the service's baseline is written and the line is deleted.
221
+
222
+ ### Fixed — which way an edge points no longer decides a grade in silence
223
+
224
+ One finding code was added: `spine.message-unbound` (warn). It is a new member of the frozen
225
+ `IssueCode` union. No flag or payload key changed, and no exit code changed except under `--strict`,
226
+ where every new warning below fails the run. SCHEMA.md has a new section, *Which way an edge
227
+ points*, that lists every reader of a relationship's direction and the drawing each one expects.
228
+ Until now the event rule was written only in the examples README.
229
+
230
+ - **An event key bound to a topic is reported.** `metadata { publishes }` binds the edge's SOURCE
231
+ and `metadata { consumes }` binds its TARGET. A consume edge drawn from the consumer to the topic,
232
+ or a publish edge drawn from the topic to the producer, bound nothing: the document validated, and
233
+ the event spine graded one link fewer without a word. `loam validate` (`--service`, and `--all`
234
+ per service) now raises `spine.message-unbound` on the service at the other end when the bound end
235
+ is infrastructure: a `topic`, `queue`, `database`, `cache` or `bucket`, by its own kind or an
236
+ ancestor's. `validate --feature`, `loam status <FEAT>` and the archive gate raise it for a delta's
237
+ tagged edges. The message spells the edge turned round. For an edge that carries both keys it says
238
+ to move the key at fault onto an edge of its own, since turning the arrow round would only move
239
+ the warning. The warning does not gate. While it stands, the service's `event.covered` line does
240
+ not appear. An `#external` system at the bound end does not raise it: `crm -> billingService`
241
+ carrying `publishes` is an outside producer, and a `consumes` on an outside system is an outside
242
+ consumer, both drawn correctly. Neither does an edge with no fleet service at either end.
243
+ - **The consumer census counts a service at either end of an edge into infrastructure.** For an
244
+ infrastructure element that stands for no service, `store -> service` now counts that service as
245
+ a consumer, as `service -> store` already did, and so does an extending model's inbound call from
246
+ such an element. Two drawings now warn that did not. A store a second service reads, drawn
247
+ `store -> service`, raises `landscape.datastore-shared`. An `#external` broker not tagged
248
+ `#platform` raises `landscape.platform-candidate` once three services reach its topics, counting
249
+ services that only consume, drawn `topic -> service`. Before, only publishers were counted. **This
250
+ can add a warning to a fleet that had none, and fail `--strict` with it.** While either warning
251
+ stands, `validate --all` does not print `landscape.matched`. For any other element the arrow is
252
+ still read, so a system calling into the fleet is not consumed by the services it calls.
253
+ - **A `Covers:` edge entry may carry the relationship's kind**, in either of LikeC4's spellings.
254
+ `a -[kind]-> b` used to split at the `->` after the `]`, and `a .kind b` was read as one element
255
+ id, so both were always `covers.unknown` while `a -> b` matched the same kinded edge. Both now
256
+ resolve exactly as `a -> b` does, at service and feature scope, and satisfy `c4.uncovered` the
257
+ same way. `node:a -[kind]-> node:b` reads as a deployment edge. The kind is read past, not
258
+ compared, because loam identifies an edge by its two endpoints.
259
+
260
+ ### Fixed — `loam archive` splices what LikeC4 reads, where a reader expects it, and gives a service it creates its model
261
+
262
+ No code, flag or payload key was added. SCHEMA.md "How archive writes, and how unarchive undoes it"
263
+ states the rules.
264
+
265
+ - **An edge whose relationship kind carries a tag archives.** With `relationship publishes { #async }`
266
+ in the specification, every `-[publishes]->` (or `.publishes`) edge the delta added was refused as
267
+ `merge-failed` ("cannot locate the '…' relationship in delta.likec4") by an archive
268
+ `validate --feature` and `loam status` had called ready, and a feature with flows got
269
+ `usecase.flow-invalid` from `validate --feature` instead. The splice now matches a statement with
270
+ its kind's tags. **Exit code:** `loam archive` and `archive --dry-run` exit 0 where they exited 1
271
+ on that shape; `validate --feature`, `validate --all` (per active feature) and `loam status` no
272
+ longer carry the `usecase.flow-invalid` — or, for a feature with `deployment/`, the
273
+ `deployment.doc-invalid` — and exit 0 where they exited 1. With the merge preview readable again,
274
+ `validate --feature` also checks a consumed message against the map the archive leaves, so on that
275
+ shape it can now report `spine.message-unproduced` where it said nothing.
276
+ - **An extending model is read with the kinds it borrows from the map.** In a service's extending
277
+ `model.likec4`, a dotted edge (`a .delivers b`) and a kind-first declaration (`container outbox …`)
278
+ were invisible to the splice: an edge the archive routed into the model landed above the dotted
279
+ edge and its comment, a delta restating a dotted edge wrote a second copy at exit 0, and a child of
280
+ a kind-first container was refused (`merge-failed`, "nests under … which is neither in the living
281
+ landscape nor added by this delta"). **Exit code:** that last shape exits 0 where it exited 1 —
282
+ `loam archive`, and `validate --feature` and `loam status` for a feature with flows or
283
+ `deployment/`, which reported the same refusal as `usecase.flow-invalid` / `deployment.doc-invalid`.
284
+ - **An addition with no anchor lands above the comment written over the statement after it, and
285
+ where it lands no longer depends on unrelated edges.** On a hand-written map, an edge from a
286
+ service the same archive adds was spliced between a section comment and the edge it explains, and
287
+ turning one unrelated edge round moved it to another section. The relationship walk now sorts only
288
+ within the region's ascending tail, and a comment directly above a statement or the model's
289
+ closing brace stays with what follows it — in the landscape and in a service model alike. The bytes
290
+ an archive writes change only for those shapes; placement stays order-independent.
291
+ - **A service the archive creates gets `services/<svc>/model.likec4`.** For a service the feature
292
+ carries `specs/<svc>/` for, the fleet does not enumerate and the merged map draws (the living map,
293
+ or an element the delta adds, bound to it or titled with its name), the archive writes an empty extending
294
+ model (a header comment and `model { }`) and routes whatever the delta draws inside the service
295
+ into it, where those containers used to land on the map. `loam archive --json`'s `plan` gains a
296
+ `create` row for that path, the run prints `architecture: created services/<svc>/model.likec4 …`,
297
+ and `warnings[]` no longer carries `service.no-model` for that service — so the closing "complete
298
+ + current" line is no longer withheld for it, and `validate --all` reports no `service.no-model`
299
+ after it. `service.no-model` is still filed (error, `gates: false`) for a service the map gains with
300
+ no `specs/<svc>/`, which `validate --all` then reports as `landscape.binding-unknown`, and for a
301
+ `specs/<svc>/` the merged map never draws (an unknown service only `--approve` merges), when the
302
+ merge creates its directory anyway. The archive
303
+ never picks a subsystem — the service lands unfiled at `services/<svc>/` unless its directory
304
+ already exists — and when the tree has a subsystem it prints
305
+ `loam subsystem move <svc> --into <subsystem>`. `unarchive` deletes the created model. The
306
+ shipped example's FEAT-101 now archives with no warning and a fleet that is clean after
307
+ `loam subsystem sync`.
308
+ - **The feature gate previews that routing.** A `features/<FEAT>/usecases/` hop into a container
309
+ the delta draws inside a service the feature introduces resolved against the preview's map, passed,
310
+ and was copied into `architecture/usecases/`, naming a container that now lives in the service's
311
+ model. **Exit code:** `loam validate --feature`, `validate --all` (per active feature) and
312
+ `loam archive` (`not-coherent`; `--approve` does not override it) now refuse that shape with
313
+ `usecase.flow-invalid` where they passed, and `loam status` no longer calls that feature ready. A
314
+ `features/<FEAT>/deployment/` document naming such a container is refused the same way, with
315
+ `deployment.doc-invalid`.
316
+
317
+ ### Fixed — what a feature introduces is graded the way the archived fleet grades it
318
+
319
+ No code, flag or `--json` key was added. A feature's delta was read with a narrower idea of "a
320
+ service" than the fleet census `validate --all` applies once the archive lands, and `loam status`
321
+ owed a contract to every service a feature introduces whether or not anything could ever call it.
322
+
323
+ - **`service.no-requirement-delta` (warn) fires only for a new service of ours**: a tagged
324
+ `softwareSystem` at service level that is not `#external`, not a grouping (an element standing
325
+ for no service that contains one), has no `services/<id>/` and is not already drawn by the living
326
+ map. A delta's `#external` producer, and the stub of a living element — read with the living
327
+ element's tags, so a re-declared `#external` system stays external — no longer raise it.
328
+ - **The verification checklist's `service.exists` claims follow the same reading**, so
329
+ `loam verify` and `loam tasks` lose the claim and unit for an `#external` element, a grouping, and the
330
+ stub of a living `#external` system — and GAIN one for a new service drawn inside a group the
331
+ delta declares (the shape the shipped `FEAT-101` uses for `payment-split-service`), which a
332
+ dotted id used to hide. Only a grouping is looked through: what a delta draws inside any other
333
+ element (a container of a system with no directory yet, an endpoint inside an `#external`
334
+ vendor) is still not a service, and the stub of a service that already has a directory is asked
335
+ about only when tagged at top level, as before. A record answering the old checklist of such a
336
+ feature reads stale and is answered again.
337
+ - **`c4.uncovered` (warn) no longer asks a grouping for a `Covers:` line**; the service inside it
338
+ is graded as before.
339
+ - **`loam status` owes an `openapi.yaml` on evidence, in both forms.** A service with no living
340
+ contract, that no other feature in flight carries one for, owes it when the feature writes an
341
+ `Operations:` line for it or a tagged delta edge calling an operation on it — the links
342
+ `spec-api.op-undefined` and `c4-api.op-undefined` grade. A new service nothing calls now reads
343
+ `openapi` `done` (`required: false`), drops `next.author-openapi`, and leaves the fleet form's
344
+ `missing[]`, so the feature's stage can move from `missing` to `ready`. An adopted service with
345
+ no contract that a tagged edge calls with an op now owes one (`required: true`, `missing`,
346
+ `next.author-openapi`, `<svc>/openapi` in `missing[]`) where status said "none owed" beside the
347
+ gate's error. When the delta does not parse, the old answer stands. The evidence is the
348
+ feature's own: another feature's edge into a service this one introduces is that feature's
349
+ `c4-api.op-undefined` / `c4-api.op-pending`, not a contract this one owes.
350
+ - **`loam archive` does not create a living `openapi.yaml` from a contract that describes nothing**
351
+ (no path, webhook or component): the plan prints `openapi: <svc> — nothing to merge …, no living
352
+ openapi.yaml created` instead of `created ()`, and `archive --json`'s plan carries no write for it.
353
+
354
+ Exit codes: `validate --strict`, which escalates warnings, can exit 0 where one of the warnings
355
+ above was the only one.
356
+
357
+ ### Fixed — the archive says what it replaces, creates and rewrites
358
+
359
+ Three new stable codes, all warnings that never gate: `usecase.requirement-modified`,
360
+ `openapi.info-not-merged`, `asyncapi.info-not-merged` (360 in all). No flag and no refusal changed;
361
+ the exit codes that move are `loam gate`'s and, under `--strict`, `validate`'s, both below. New
362
+ `--json` keys, all additive: `dropped` on `loam archive` and on `loam delta`, and `partial: true` on
363
+ the spec.md entry of `loam adopt`'s `targets[]`. One new frontmatter
364
+ field, written by loam: `adoption: partial`.
365
+
366
+ - **A MODIFIED or REMOVED requirement names what it takes away.** A MODIFIED replaces the living
367
+ requirement whole, so every scenario and join-line entry (`Operations:`, `Covers:`, `Requires:`,
368
+ `Capability:`, `Realizes:`, `Publishes:`, `Consumes:`) it did not restate vanished at archive
369
+ under a bare `~1`. The plan now prints one line per replaced or removed requirement naming the
370
+ dropped scenarios and entries, `loam archive --json` carries them as `dropped`
371
+ (`[{ path, kind, requirement, id?, scenarios, directives }]`), and `loam delta` carries the same
372
+ rows as `dropped` and prints them under *Taken out of the living docs by the merge* — on the
373
+ service's spec.md and arch.spec.md and on every capability document the feature changes. `path`
374
+ is the living document; `kind` is `MODIFIED`, `REMOVED` or `ADDED` (an ADDED that lands on a living
375
+ requirement of the same id or name); `id` is absent for a requirement with no `Requirement-ID:`;
376
+ `directives` is keyed `operations`, `covers`, `requires`, `capabilities`, `realizes`, `publishes`,
377
+ `consumes`, each present only for a line that loses an entry (a `Realizes:` entry is compared by
378
+ its target, so a pin is never a drop).
379
+ - **A living flow claiming a requirement the feature MODIFIES is named.** Its `#req-` tag still
380
+ resolves after the archive and every grade stays green, whatever the new wording says.
381
+ `usecase.requirement-modified` (warn) now fires wherever the feature is graded — `validate
382
+ --feature`, `validate --all` per active feature, `loam status <FEAT>`, and the archive's
383
+ `warnings[]` (it never refuses the archive) — for an `architecture/` flow whose `#cap-`-scoped `#req-` tag claims a capability
384
+ requirement the feature modifies, and for a flow beside a service's model whose `#req-` tag
385
+ names one of that service's requirements the feature's `specs/` delta modifies. **Exit code:**
386
+ unchanged, except under `--strict`, which fails on any warning and so now exits 1 on this shape.
387
+ - **A first spec.md archived into a never-adopted service is marked `adoption: partial`.** When
388
+ `loam archive` creates `services/<id>/spec.md` for a service that already existed and that the
389
+ feature does not introduce, the file carries `adoption: partial` and the plan says why.
390
+ `loam list`, `explore`, the context pack and `validate --all`'s scorecard keep the service at
391
+ `partial` (`missing` names `loam adopt --service <id>`) instead of `documented`, and `loam status`
392
+ lists it under `next.adopt`, with a sentence saying the spec.md holds one feature's slice, instead
393
+ of `next.complete-service` even when it has no model.likec4. **Exit code: `loam gate --service
394
+ <id>` now exits 1 for such a service** — it sits at `partial`, so `gate.service-undocumented`
395
+ fires where the same archive used to leave it `documented` and the gate passing; every service
396
+ joined to it gets `gate.partner-undocumented` (warn: exit unchanged, except under `--strict`).
397
+ `loam adopt --service <id>` — the command all of those name — flags that spec.md `PARTIAL` and
398
+ counts it as owed (`targets[]` carries `partial: true`) where it said every required artifact was
399
+ already there. Delete the line once the service's baseline is written — the brief and the
400
+ `loam-adopt` protocol now say so — and all of it clears.
401
+ - **The contract merges keep the bytes they did not change.** Archiving into a living
402
+ `openapi.yaml` or `asyncapi.yaml` re-folded every long scalar at 80 columns, re-padded flow
403
+ sequences and turned CRLF into LF across the whole file; only the entries the merge adds, replaces
404
+ or removes are now written, and the rest of the file is byte-identical (falling back to the old
405
+ whole-file write for a shape it cannot splice safely). A living contract the archive CREATES from
406
+ the feature's file keeps that file's bytes too, minus the `x-loam-*` keys it strips.
407
+ - **The delta's `info` is not merged, and the plan now says so.** `openapi.info-not-merged` /
408
+ `asyncapi.info-not-merged` (warn) name every `info` key the feature's contract spells differently
409
+ from the living one — including for a delta whose only change is its `info` — since the merge
410
+ keeps the living `info`. Edit the living contract in the same change if it should move.
411
+
412
+ ### Fixed — a use-case hop can say which of several relationships it means
413
+
414
+ No code, flag or `--json` key was added or removed. Before this, a hop between two elements the map
415
+ joins by edges naming different operations was `usecase.step-contested` (warn) whatever the flow
416
+ said. On a fleet flow (`architecture/usecases/*.likec4`) between two services the map draws as
417
+ single elements, no edit to the flow could clear it: the repair the warning offered — draw the hop
418
+ between the elements that name exactly one candidate — needs containers, and a container an
419
+ extending model declares does not resolve on a fleet flow.
420
+
421
+ - **A step's title now picks the candidate it opens with.** Where the candidates disagree, a title
422
+ that STARTS with one candidate's `metadata { op }` — or with the message it `publishes` or
423
+ `consumes` — and goes on only after punctuation, or not at all, attributes the hop to that
424
+ relationship: `orders -> payments 'authorizePayment — authorizes the payment'`,
425
+ `'getOrder(orderId)'`, `'refundPayment'`. The name is matched exactly, case included, as a whole
426
+ name: a letter, digit, `_`, `.` or `-` after it means it is part of a longer name and picks
427
+ nothing. A name that is the first word of a sentence (`'read the order'`), a name later in the
428
+ title, a relationship's own title, a name the pair's candidates do not declare, or one name
429
+ declared by candidates that still disagree picks nothing, and the hop stays contested over every
430
+ candidate. A title never backs an unbacked hop and never overrules candidates that agree.
431
+ - **Which findings change.** A hop the title resolves is no longer `usecase.step-contested`.
432
+ Picked by an operation, it is attributed to that operation. Picked by a message, it is an event
433
+ hop, which `usecase.step-unlinked` exempts. This holds on the fleet target, on a service's own
434
+ flows (`loam validate --service`) and on a feature's `usecases/` flows. **Titles written before
435
+ this release are read the same way**: a step title that already opens with a candidate's
436
+ operation id and punctuation (`'createOrder: places the order'`) now picks it with no edit, its
437
+ next `loam validate` stops warning on that hop, and `--strict` can exit 0 where it exited 1.
438
+ - **Contract removals read the same pick.** `loam diff --base` and the contract-removal gate list a
439
+ picked hop among the flows that break when its operation is removed, and drop the "could not be
440
+ answered" line they printed for the other candidates' operations. A hop picked by an operation is
441
+ no longer listed among the flows a removed message breaks, even where another candidate consumes
442
+ that message; that edge itself still is. Which code fires, and the exit code, do not change: the
443
+ picked edge was already named as a consumer.
444
+ - **The `usecase.step-contested` message** now leads with how to pick, giving as the example the
445
+ step's own title behind a candidate's name, and only a title that would clear the warning. Drawing
446
+ the hop between other elements comes last, for a project that declares those elements. Its
447
+ `details[]` lines print every name an edge declares — `(op: <id>)`,
448
+ `(publishes: <message>)`, `(consumes: <message>)`, or `(no op)` when it declares none — where a
449
+ message edge used to print `(no op)`, and an edge declaring both an operation and a message
450
+ prints both, `(op: <id>, publishes: <message>)`, where it used to print only `(op: <id>)`.
451
+
452
+ ## [0.2.0-alpha.10] - 2026-10-21
453
+
454
+ Everything on `main` since `0.2.0-alpha.9`. Still a prerelease on the `alpha` line.
455
+
456
+ Most of it closes the gap between a feature called clean and the fleet its archive leaves:
457
+ `validate --feature`, `loam status <FEAT>` and the archive gate now raise, before the archive, what
458
+ `validate --all` raises after it. `loam archive` keeps a created document's title, owner and
459
+ `## Purpose`, re-expresses every link it moves — out of the archived feature, and into it from any
460
+ other document — and its undo snapshot no longer breaks a feature's LikeC4 project;
461
+ `loam subsystem move` and `rename` rewrite the links their renames would break. A repository bound
462
+ to a service a feature introduces is pointed at that feature instead of being told to adopt it, by
463
+ `loam status`, `loam doctor`, `loam validate`, `loam show`, `loam context` and `loam steps` alike.
464
+ `loam status`, `loam tasks` and `loam validate` say in words where work stands, `loam explain`
465
+ defines the words they print, and on Windows a rolled-back write takes back the directories it
466
+ created.
467
+
468
+ Several entries change an exit code, listed in each. Refusals that are new: four feature-scope
469
+ errors (`spine.message-unproduced`, `usecase.step-unbacked`, `usecase.requirement-unresolved`,
470
+ `usecase.capability-unresolved`) fail `validate --feature`, `validate --all` for an active feature
471
+ carrying one, and `loam archive` (`not-coherent`, unless `--approve`); `loam unarchive` refuses
472
+ `snapshot-stale` over a relinked document edited since the archive; `move-uncommitted` also covers
473
+ an uncommitted document outside the moved directories that the move would relink. Refusals that are
474
+ gone: `loam explain` on the seventeen new terms, and `show`, `context`, `steps` and a bare
475
+ `validate` for a service a feature introduces. `loam archive` files `service.no-model` at
476
+ error severity, the severity `validate --all` gives that directory next, and it still does not gate
477
+ (`gates: false`): count what blocks by `gates`, never by `severity`. `loam context` with two
478
+ introducing features refuses `invalid-option` where it said `unknown-service`, and
479
+ `loam tasks <FEAT>` in a repository bound to a service the feature touches lists that service's
480
+ slice. The undo snapshot is now version 4, and an older loam cannot unarchive a feature this version
481
+ archived: upgrade every checkout that may run `loam unarchive`.
482
+
483
+ No stable code was added or renamed — 356 in all, as at alpha.9 — and no flag.
484
+ `loam archive --json`'s `issues[]` may now carry seven codes `validate` already had
485
+ (`spine.message-unproduced`, `health.uncovered` and five `usecase.*`). New `--json` keys, all
486
+ additive: `staleViews` on `loam unarchive`, `relinked` on `subsystem move`/`rename`, `title`,
487
+ `checklist` and `serviceSteps[]` on `loam status`, `title` on `show` and `list`,
488
+ `currentService.introducedBy` on `loam doctor`, `boundService` on `loam validate`, and `living` /
489
+ `introducedBy[]` on `show`, `context` and `steps` for a service a feature introduces.
490
+
491
+ ### Fixed — the feature gate predicts the fleet its archive leaves
492
+
493
+ No code or flag was added. A feature `validate --feature` called clean — `loam status` "ready", the
494
+ archive gate agreeing — could archive at exit 0 under "living spec + landscape are now complete +
495
+ current." into a fleet `validate --all` then reported red. The feature scope (`validate --feature`,
496
+ `loam status <FEAT>`, the archive gate and `archive --dry-run`) now raises, before the archive,
497
+ what `validate --all` raises after it, under the same codes — except the two `Covers:` findings
498
+ below, which are `validate`'s alone (`--feature`, and `--all` per active feature); `loam status`
499
+ and the archive do not raise them:
500
+
501
+ - **A consumer with no producer is refused.** A delta `Consumes:` line or tagged `consumes` edge
502
+ naming a message nothing would send after the merge — no service's contract as the merge leaves
503
+ it, no `#external` publisher on the living map or in the delta — is `spine.message-unproduced`
504
+ (error) at feature scope. When only another in-flight feature's delta sends the message it is
505
+ `spec-event.message-pending` / `c4-event.message-pending` (warn) instead.
506
+ - **A feature's own flows are graded as the fleet grades them.** A hop of a
507
+ `features/<FEAT>/usecases/*.likec4` view that no relationship on the post-merge map backs is
508
+ `usecase.step-unbacked`; a `#req-` tag no `#cap-` tag scopes, or one naming no requirement as the
509
+ merge leaves the capability document, is `usecase.requirement-unresolved`; an unresolving `#cap-`
510
+ tag is `usecase.capability-unresolved` — errors. `usecase.step-contested` and
511
+ `usecase.step-unlinked` arrive as warnings. The messages open with the feature file's path.
512
+ - **A MODIFIED arch requirement that drops a `Covers: sli:` / `alert:` line** the living
513
+ `arch.spec.md` had is `health.uncovered` (warn) at feature scope, naming the signal.
514
+ - **A `Covers:` entry spelled in the delta's own ids** — a stub the delta declares where the living
515
+ map nests it, or a child under one — is `covers.unknown` (warn) at feature scope, offering the
516
+ spelling that resolves in both. An entry spelled the way the merged map will spell an element
517
+ this feature adds now resolves at feature scope too.
518
+ - **`c4.uncovered`'s hint** offers that same surviving spelling: an edge by its endpoints' service
519
+ names (`checkout-web -> payment-split-service`), an element by its service or the id the merge
520
+ gives it — the delta's own id only when the merge preview cannot be computed.
521
+ - **`loam archive` files `service.no-model` at error severity**, because `validate --all` reports an
522
+ error next — `service.no-model` for the directory the archive creates, `landscape.binding-unknown`
523
+ for a binding it lands with no directory, which the message now names; it still does not gate (`gates: false` in `--json` `warnings[]`). The text
524
+ `--dry-run` now prints it, as `--json` and the real run did, before "dry run — nothing was
525
+ written.", and both runs print in place of the closing line when this archive lands ahead of an
526
+ in-flight change it waits on (a `spec-api.op-pending`, `c4-api.op-pending`,
527
+ `spec-event.message-pending` or `c4-event.message-pending` warning).
528
+ - **A `spine.message-unproduced` producer may be an `#external` system the delta draws**, including
529
+ a living one it re-declares as an untagged stub: the question is asked of the map the merge leaves.
530
+ - **Exit code:** on any of the four errors above, `loam validate --feature`, `loam validate <FEAT>`
531
+ and `loam validate --all` — for every active feature carrying one, before anything is archived —
532
+ now exit 1 where they exited 0; `loam archive` and `archive --dry-run` exit 1 (`not-coherent`)
533
+ without `--approve`, which overrides all four; `loam status` counts them as gating, and
534
+ `loam show <FEAT> --json` reports `review.readyToArchive: false`. The warnings above
535
+ (`health.uncovered`, `covers.unknown`, `usecase.step-contested`, `usecase.step-unlinked`, the two
536
+ `*-pending`) leave the exit code alone except under `--strict`, which fails on any warning and so
537
+ now exits 1 on these shapes.
538
+ - **`service.no-model` rides at error severity in a `not-coherent` refusal's `error.issues[]`** too,
539
+ beside the issues that refused — count what blocks by `gates`, never by `severity`.
540
+
541
+ `loam archive --json`'s `issues[]` may carry the seven codes above (`spine.message-unproduced`,
542
+ `health.uncovered`, the five `usecase.*`), which the `IssueCode` union now admits; each was already
543
+ a stable `validate` code.
544
+
545
+ ### Fixed — `loam archive` keeps what makes the living docs readable, and a link it moves still resolves
546
+
547
+ No code or flag was added. Two additive `--json` keys (`unarchive`'s `staleViews`, `subsystem
548
+ move`/`rename`'s `relinked`), one new snapshot layout (version 4) and one refusal that is new
549
+ (`move-uncommitted` over a document outside the move). Archive's `plan` gains `update` rows for the
550
+ feature's own documents it relinks and for every document outside the feature whose links into it
551
+ it rewrites, its `revouchOwed` also names a vouched spec that rewrite moves, and `unarchive`'s
552
+ existing `snapshot-stale` refusal now covers all of those documents too. SCHEMA.md "Conventions"
553
+ states the rules.
554
+
555
+ - **A living document archive CREATES carries what the delta marked as its own.** A new
556
+ `services/<svc>/spec.md` or `arch.spec.md` used to be a fixed header — `service`,
557
+ `status: draft`, `# <svc>` — and the requirements; a new `capabilities/<cap>/spec.md` was
558
+ `# <cap>` and the requirements. The delta's title, its narrative, its frontmatter `owner` and
559
+ every section around the delta sections were dropped in silence. Now a created document gets the
560
+ delta's `owner` (service specs), its H1 unless that title is about the change (it names the
561
+ feature, or calls itself a delta — the scaffold's own title does both, so a scaffolded delta keeps
562
+ the heading it always had), its `## Purpose` section verbatim, and one sentence linking back to
563
+ the archived delta ("Created when FEAT-1 was archived; its delta keeps the prose written for that
564
+ change", with "its delta" the link). The plan prints one note per created document, under that
565
+ file's line, naming what it carried and where the rest stays. A `## Purpose` in a delta against a
566
+ document that already exists is not merged, and the plan now says so under that file's line.
567
+ - **A link into the feature lands on the file's new home.** A delta link to the feature's own
568
+ `glossary/<term>.md`, `adrs/…` or any other file was re-expressed against the file's old place,
569
+ which the same archive then moved — `link.unresolved` on the first `validate --all` after a clean
570
+ archive. A link to a file the archive copies — a glossary term, a flow, a topology document — now
571
+ lands on its living copy (`glossary/<term>.md`, `architecture/usecases/…`, `architecture/…`); a
572
+ link to any other file under the feature lands in `features/archive/<FEAT>/`. A feature ADR is
573
+ never copied into the living tree: it stays with the archived feature, and living documents that
574
+ cite it point there. A glossary term the archive copies has its own relative links re-expressed
575
+ for `glossary/` too, so a term citing a service spec or the feature's ADR no longer lands broken;
576
+ the copy is otherwise byte-for-byte, as before. A link whose text still reaches its target from
577
+ where it lands keeps its exact spelling.
578
+ - **The documents that stay in the feature keep links that resolve.** The archive moves
579
+ `features/<FEAT>/` to `features/archive/<FEAT>/` as a plain directory rename, so every relative
580
+ link out of the feature in the intent, the feature's ADRs, its deltas — any markdown document the
581
+ rename carried — was one directory short afterwards, and a reader who followed a living
582
+ document's link into an archived ADR landed on a decision whose own links led nowhere (nothing
583
+ grades archived features, so nothing said so). The archive now re-expresses those links for the
584
+ move, in the same commit; a link to another file of the same feature, and any link whose text
585
+ still reaches its target, is left exactly as written. Each relinked document is an `update` row
586
+ in `plan`, at its path before the move (`features/<FEAT>/…`), ahead of the `move` row
587
+ (`--dry-run` included); the text output adds `feature documents: N relinked for
588
+ features/archive/<FEAT>/` with one line per document, and the snapshot parks each pre-image where
589
+ the move leaves it (`features/archive/<FEAT>/…`), so `loam unarchive` puts the authored bytes
590
+ back — archive then unarchive is still a byte-for-byte round trip — and lists them in `restored`
591
+ (and `discarded`, under `--force`) where they land (`features/<FEAT>/…`). A retried archive and the crash repair find those
592
+ pre-images across the move. No code, flag or payload key was added, and the snapshot stays
593
+ version 4: the new rows are literal paths every version-4 reader already restores. **Exit
594
+ code:** `loam unarchive` now also refuses with `snapshot-stale` (exit 1) when a document the
595
+ archive relinked was edited under `features/archive/<FEAT>/` since; `--force` puts the authored
596
+ text back over that edit. Those documents used to move back as they stood.
597
+ - **A document outside the feature that links into it still resolves once the feature is
598
+ archived.** Another change in flight citing the feature's ADR (`../<FEAT>/adrs/0001-x.md` from its
599
+ intent), or a living service or fleet ADR citing a decision taken for a change still open, was
600
+ correct the moment before the archive and `link.unresolved` (an error) on the first
601
+ `validate --all` after it: the rename took the directory away from under the link, and the
602
+ archived path the author would have needed did not exist until the archive did. The archive now
603
+ rewrites those links in the same commit, across the whole link corpus — the fleet's own documents,
604
+ every service's, every other in-flight feature's, the documents `subsystem move` rewrites for its
605
+ renames: a term the archive copies into `glossary/` is cited at its living copy, everything else
606
+ in the feature where it now lives (`features/archive/<FEAT>/…`), and every other link keeps its
607
+ spelling. A living spec the merge rewrites is relinked from its merged text, so it is still one
608
+ `plan` row. Each relinked document is an `update` row in `plan` (`--dry-run` included); the text
609
+ output adds `links into the feature: N document(s) relinked for features/archive/<FEAT>/`, one
610
+ line per document. The costs are said, not softened: a vouched `spec.md` or `arch.spec.md` the
611
+ rewrite moves off its stamp is on `revouchOwed` like one the merge moves (`content.stale` follows
612
+ until its owner re-vouches), and a rewritten route inside a living requirement adds a `note:` line
613
+ saying a feature that pins it with `Based-On:` reports `delta.baseline-stale` until `loam rebase`.
614
+ No code, flag or payload key was added. The undo is the snapshot's — each relinked document is an
615
+ ordinary row, so `loam unarchive` puts the authored bytes back and archive-then-unarchive stays a
616
+ byte-for-byte round trip. **Exit code:** `loam unarchive` now also refuses with `snapshot-stale`
617
+ (exit 1) when such a document — another in-flight feature's intent among them — was edited since
618
+ the archive; `--force` puts the pre-archive text back over that edit. A document whose own feature
619
+ has been archived or removed since is not drift: `unarchive` leaves it as it is, with a `left:`
620
+ line in the text output (no `--json` key), rather than recreating a one-file `features/<other>/`.
621
+ - **The undo snapshot no longer breaks a feature's LikeC4 project.** `.loam-before/` stored each
622
+ pre-image under its living name, so the archived feature held a second `landscape.likec4`: a
623
+ feature with its own `likec4.config.json`, or rendered alone, failed on duplicate elements, and so
624
+ did a fleet project that did not exclude `features/`. Snapshot manifests are now **version 4** and
625
+ store each pre-image as `files/<path>.before`. Version-2 and version-3 snapshots are still read
626
+ and restored, each under its own layout, and are never rewritten. **An older loam cannot unarchive
627
+ a feature this version archived** — it refuses with `snapshot-missing`, as it does any layout it
628
+ does not know — and it cannot repair an archive, unarchive or subsystem move this version was
629
+ killed in the middle of; finish those with this version, and upgrade every checkout that may run
630
+ `loam unarchive`.
631
+ - **`loam unarchive` names the generated views it left behind.** It restores what the archive
632
+ wrote, and `architecture/subsystems.likec4` / `architecture/services.likec4` are not among it, so
633
+ after `loam subsystem sync` had run on the merged map the undo closed on "the living docs are back
634
+ to what they said before the archive" over a fleet the next `validate --all` failed
635
+ (`subsystem.views-stale`). It now prints one `⚠ <path> no longer matches the tree and this map`
636
+ line per stale file, naming `loam subsystem sync`, under a narrower closing line, and `--json`
637
+ carries the list as `staleViews` — the key `archive --json` already has, same meaning, absent when
638
+ nothing is stale.
639
+ - **`loam subsystem move` and `rename` rewrite the relative links the renames would break** — out of
640
+ every markdown document under a moved directory, and into a moved directory from every other
641
+ authored document (fleet documents, services, in-flight features; archived features are left as
642
+ history) — in the same transaction as the renames, and stage them in git with the renames. Each
643
+ rewritten document is printed as `relinked <path>` and listed in `--json` as `relinked` (always
644
+ present; `[]` when no link needed rewriting). A rewritten route is still a changed byte, and the
645
+ move says so in its `warnings` (text: `note:` lines): a `verified` document it relinks no longer
646
+ hashes to its `content_digest`, so `content.stale` follows (exit 1 under `--strict`) until its
647
+ owner re-vouches, and a living requirement it relinks moves the digest a feature in flight may
648
+ have pinned with `Based-On:`, so that feature reports `delta.baseline-stale` until `loam rebase`.
649
+ Before this release the same move left those links `link.unresolved` instead. **Exit code:**
650
+ `move-uncommitted` now also refuses (exit 1) when git reports an uncommitted or untracked
651
+ document OUTSIDE the moved directories that the move would relink — an in-flight feature's work
652
+ in progress included; it used to proceed and leave the link broken. A commit killed after its
653
+ renames now recovers a relinked document from where the rename took it instead of refusing it as
654
+ edited.
655
+
656
+ ### Changed — `loam status` says where a feature stands in words, with the claims counted
657
+
658
+ No code was added or renamed, and no exit code changed. The `stage` values (`missing`, `blocked`,
659
+ `draft`, `ready`, `done`) are untouched; what changed is that the human view no longer prints them
660
+ bare. `ready` means the documents are written and pass the shared checks — nothing about code or
661
+ evidence — and `loam status FEAT-101 — ready` over a feature nobody had built or verified read as
662
+ "ready to ship"; `done (archived)` over a feature that was never verified read as "shipped and
663
+ checked", when archive checks documents and nothing else.
664
+
665
+ - **The header is a sentence:** `loam status FEAT-101 (Payment splitting): docs ready — nothing
666
+ verified yet (0 of 19 claim(s) answered)`; for an archived feature `archived — never verified (no
667
+ verification record)`, `archived — verified (…)` or `archived — attested, not verified: …`. The
668
+ machine value moves to its own `stage` row, which names `loam explain <stage>`.
669
+ - **The artifact table says what each row means:** `written`, `not owed`, `missing`, `has errors`,
670
+ `blocked`; the verification row `none yet`, `open`, `attested`, `verified`, `stale`,
671
+ `unreadable`. The `verification` row with no record says how many claims there are to answer.
672
+ `coherence` ends with `N stop it (validate --feature or archive refuses on each)` — every error
673
+ and every gating warning — instead of `N gate archive`, which counted only findings carrying
674
+ `gates` and so read 0 over a feature archive refuses for an unknown service. The `checks.gating`
675
+ payload value is unchanged.
676
+ - **The fleet form says less than the feature form, on purpose:** it grades nothing, so a verified
677
+ feature reads "its own status says whether archive will take it", not "ready to archive"; and a
678
+ feature behind an interrupted commit reads as blocked by the commit even when it also waits on
679
+ another feature.
680
+ - **`next[]` statements (prose, not codes):** `next.archive` no longer says a feature "ships once
681
+ everything above is done" — it says archive checks only the documents, not the code and not the
682
+ verification record; its clean form says "archive it" instead of "ship it". `next.archived` no
683
+ longer says the feature "shipped": it says what archiving did and whether it was verified. The
684
+ fleet form's `next.feature` says the stage in the same words (`FEAT-101 (Payment splitting): docs
685
+ ready — nothing verified yet (no verification record).`) instead of `FEAT-101 is at 'ready'.`, and
686
+ `next.elided` counts what it left out by code.
687
+ - **Additive payload keys:** `feature.title` and `checklist` (`{claims}` — how many claims the
688
+ feature's checklist asks now; `null` for an archived feature) on `loam status <FEAT> --json`;
689
+ `features[].title` and `serviceSteps[]` on `loam status --json`; `title` on
690
+ `loam show <FEAT> --json` and on `loam list --json`'s `features[]`. The title is intent.md's
691
+ `title:`, else its first `# ` heading without the feature id, else `null`.
692
+ - **Feature rows name the change:** `loam status`, `loam list` and `loam show <FEAT>` print the
693
+ intent title beside the id. An intent.md loam cannot read is no title, never a refusal.
694
+ - **Help text:** `loam verify` is described as "Check a feature against its own promises: derive the
695
+ claims, record the answers with evidence" and `loam archive` as "Merge a finished feature's deltas
696
+ into the living specs + model, then archive it (checks the documents, not the code)" — neither
697
+ says "shipped" any more. The `validate --all` scorecard's features row glosses `ready` and `done`.
698
+
699
+ ### Changed — `loam status` lists the features in flight before any service's adoption work
700
+
701
+ No code, flag or exit code changed; the ORDER of the fleet form's `next[]` did, and one payload
702
+ key was added. The per-service rungs (`next.adopt`, `next.complete-service`, `next.revouch`) used
703
+ to lead the list, so on a fleet mid-adoption the cap of ten filled with them and elided the one
704
+ feature in flight as "more step(s) of the same kinds".
705
+
706
+ - **Order:** `next.recover-commit` (outside the cap), then `next.adopt-bound` or the first-hour
707
+ ladder, then every feature (`next.feature`, `next.archive`), then the per-service rungs — the
708
+ bound service's own still first among them — then `next.elided` and `next.fleet-gate`. An agent
709
+ reading `next[0]` in a docs repo with a feature in flight now gets that feature, not an adopt.
710
+ - **`serviceSteps[]`** (additive, `loam status --json`): every per-service rung, uncapped, grouped
711
+ by code — `{code, services, command}`, `command` being the first service's.
712
+ - **The human view folds each per-service code into one counted line** under the features —
713
+ `next.adopt ×12: 12 services with no spec.md — …: a, b, c, …, +6 more.` — and only says
714
+ something was left out when features were.
715
+
716
+ ### Changed — a repository bound to a service its feature introduces is not told to adopt it
717
+
718
+ No code was added. In a service repository whose `loam.json` names a service that exists only in
719
+ an active feature's `specs/<service>/`, `loam status` led with `next.adopt-bound` →
720
+ `loam adopt --service <id>` — adopting code that does not exist yet. It now leads with that
721
+ feature's `next.feature` (`loam status <FEAT> --json`) — one per feature that introduces it —
722
+ saying the service joins the living docs when one of them archives, and does not list those
723
+ features again. A bound service no feature
724
+ introduces gets `next.adopt-bound` as before.
725
+
726
+ ### Changed — `loam doctor` agrees: a service a feature introduces is not something to adopt
727
+
728
+ No code was added and no exit code changed. In the same state — a service repository whose
729
+ `loam.json` binds a service that exists only in an active feature's `specs/<service>/` — `loam
730
+ doctor` still filed `doctor.service-unknown` with "Run `loam adopt --service <id>` to onboard it".
731
+ The finding keeps its code and its `warning` severity (the binding still names nothing under
732
+ `services/`, and `healthy` does not move), and now says which feature introduces the service and
733
+ that it is not in the living docs until that feature archives. Its `fix` names `loam status <FEAT>`
734
+ and no longer tells anyone to adopt.
735
+
736
+ - **Human view:** the `binding` row reads `payment-split-service · introduced by FEAT-101 (in
737
+ flight)` instead of `· unknown`, listing every introducer when more than one feature introduces
738
+ the service.
739
+ - **`--json` (additive):** `currentService.introducedBy` — the introducing features' ids, ordered
740
+ by id — present only when an active feature introduces the configured service.
741
+ `currentService.status` stays `unknown`.
742
+ - `loam explain doctor.service-unknown` and the `loam-codes` page of `loam instructions` describe
743
+ both states and both fixes.
744
+ - A bound service no feature introduces gets the adopt advice, byte for byte, as before.
745
+
746
+ ### Changed — `loam validate` in that repository validates the feature that introduces its service
747
+
748
+ No code was added. In the same state, a bare `loam validate` refused with `✗ No service directory
749
+ at services/payment-split-service. Did you mean: payment-service? (service.unknown)` and exited 1.
750
+ It treated the newest service in the fleet as a typo of an older one, so that repository's CI could
751
+ not pass until the feature archived. A service a feature introduces has no `services/<id>/` yet.
752
+ Its documents are the feature's `specs/<id>/`, so the run now validates each introducing feature
753
+ instead. That is the same grade `loam validate --feature <FEAT>` gives, target for target, and the
754
+ exit code follows it: `0` when the feature is valid, `1` when it has errors. `--strict` and
755
+ `--errors-only` work as before.
756
+
757
+ - **Human view:** two lines above the report say what was validated and why —
758
+ `payment-split-service introduced by FEAT-101 (in flight) — not in the living docs until it
759
+ archives` (the header `loam show payment-split-service` prints), then `validating FEAT-101
760
+ instead: its specs/payment-split-service/ are this service's documents until then`.
761
+ - **`--json` (additive):** `boundService` — `{ id, living: false, introducedBy }`, the same keys
762
+ `show` and `steps` use for this state — present only on such a run. `targets` then holds the
763
+ features and no service, and the `summary` counts no services.
764
+ - **Naming the service** with `--service <id>` or the positional still targets a service, so it is
765
+ still `service.unknown` at error and exits 1. The message now names the feature in place of a
766
+ near-miss: ``No service directory at services/payment-split-service: FEAT-101 introduces it, and
767
+ it is not in the living docs until FEAT-101 archives. Until then its documents are FEAT-101's
768
+ specs/payment-split-service/ — run `loam validate --feature FEAT-101` to validate them.``
769
+ - `loam explain service.unknown`, its row in the `loam-check` fix table (the text `--json`'s
770
+ `fixes` carries) and the `loam-codes` page describe both states.
771
+ - A name no active feature introduces keeps today's `service.unknown` message and near-miss,
772
+ unchanged.
773
+
774
+ ### Added — `loam explain` defines the words loam prints, and a finding page says which command reports it
775
+
776
+ No code was added. `loam explain` knew six concept terms and answered `unknown-target` for the
777
+ words a person meets first in loam's own output.
778
+
779
+ - **Seventeen new concept terms** (23 in all), with aliases: the stages — `stage` (`missing`,
780
+ `blocked`, `draft`), `ready`, `done`, each saying what it does NOT mean (`ready` is documents
781
+ only; an archived feature is `done` whether or not anybody verified it); `claim` and each claim
782
+ kind — `service.exists`, `api.exposes`, `event.declares`, `c4.calls`, `scenario.tested`; the
783
+ verdicts — `verified` (`unverified`), beside the existing `attested`; the `loam tasks` words —
784
+ `unit`, `frontier`, `describe`, `generate`, `implement`; `coherence` (`gates`, `gating`); and the
785
+ maturity ladder — `maturity` (`empty`, `partial`, `documented`, `sourced`). An alias names the entry
786
+ that answered it. **Exit code:** 0 where it was 1 (`unknown-target`) for each new term and alias.
787
+ - **The bare `loam explain` listing groups the terms** under four headings (feature stages, the
788
+ done-check, the work packet, the documents). `--json` `terms[]` keeps its row shape; it has 23
789
+ rows instead of 6, in the listing's grouped order, so the six existing terms are at new positions.
790
+ `loam explain --help` and the MCP `explain` tool's `subject` description list the new terms.
791
+ - **A finding page's header says which command reports the code** — "when `loam validate --feature
792
+ <FEAT-id>` reports it — warn" — instead of `[--feature <FEAT-id>] (warn)`, which read as an
793
+ optional flag. `--json` `entries[].scope` is unchanged. The `next.*` entries say what the steps
794
+ now say: archive checks documents only, and the fleet list leads with the features.
795
+
796
+ ### Changed — `loam show`, `loam context` and `loam steps` answer for a service a feature introduces
797
+
798
+ No code was added. A service an active feature introduces (`features/<FEAT>/specs/<svc>/` exists,
799
+ `services/<svc>/` does not yet) was refused by all three as unknown, while `loam tasks` listed work
800
+ for it on the same tree.
801
+
802
+ - **`loam show <svc>`** prints the introduced-service view: which feature introduces it, its delta
803
+ documents, requirement counts, operations, and the `loam show`/`delta`/`tasks` commands to run.
804
+ `--json` is `{type: "service", id, living: false, introducedBy[], introductions[]}`, each
805
+ introduction carrying the keys of `show <FEAT>`'s `services[]` plus `requirements`, `archSpec` and
806
+ `next[]`. **Exit code:** 0 where it was 1 (`unknown-target`).
807
+ - **`loam context <svc>`** briefs from the introducing feature's delta, saying the service is not in
808
+ the living docs yet. `--json` adds `living: false` and `introducedBy[]`, and `path` is the
809
+ feature's `specs/<svc>/`. `--feature` naming a feature that does not introduce it refuses
810
+ `unknown-service`. **Exit code:** 0 where it was 1 (`unknown-service`, or `invalid-option` when the
811
+ feature's `specs/` directory name is not a legal service id). **Code change:** with two
812
+ introducing features and no `--feature`, the refusal is now `invalid-option` naming both, where it
813
+ was `unknown-service` — a script that read `unknown-service` as "no such service" sees the new
814
+ code for this one case. Its "did you mean" hint now includes introduced services.
815
+ - **`loam steps --service <svc>`** (or the bound service) inventories the step phrases of the
816
+ feature's spec deltas. `--json` adds `living: false`, `introducedBy[]` and `specs[]`. **Exit
817
+ code:** 0 where it was 1 (`unknown-target`, or `invalid-option` when the feature's `specs/`
818
+ directory name is not a legal service id).
819
+ - Payloads for living services, and the refusals for a name nothing introduces, are unchanged.
820
+
821
+ ### Changed — `loam tasks` reads as a to-do list, and a service repository sees its own slice
822
+
823
+ No code or flag was added.
824
+
825
+ - **The human view** groups units by service and then by requirement (its file printed once), and
826
+ says each unit as an action — "make scenario '…' pass, then record the run", "generate the test
827
+ for scenario '…' (loam gherkin <FEAT>)", "expose operation '…'", "call '…' on <svc>" — keeping the
828
+ unit id on every row. The header says in words what `record` and `stamps` mean, and the frontier
829
+ line leads with the next unit's action. `--next` leads with the action and warns when this
830
+ repository cannot see whether the scenario's test was generated; the Given/When/Then lines are
831
+ unchanged.
832
+ - **`--service` lists every unit**; the whole-feature view still caps each service at 12, and now
833
+ points at `loam tasks <FEAT> --service <id>` instead of `--json`.
834
+ - **Behaviour change:** in a repository whose `loam.json` names a `service`, `loam tasks <FEAT>`
835
+ without `--service` narrows to that service when the feature touches it. Under `--json`, `service`
836
+ is the bound id; `units`, `counts`, `frontier`/`frontierElided`, `contracts` and `size.calls`
837
+ cover that service only; and `waitingOn` appears. The generated-suite stamps that tell `generate`
838
+ from `implement` are read from this repository alone, so other services' steps in a whole-feature
839
+ view were never about their repositories. Pass `--service <id>` for another service; a docs repo,
840
+ and a feature that does not touch the bound service, get the whole feature as before.
841
+
842
+ ### Changed — `loam validate`'s human report marks every error, names files relatively, and leads with what matters
843
+
844
+ No code, severity, subject, payload key or exit code changed; `--strict` and the summary counts are
845
+ the same.
846
+
847
+ - **`service.no-model` and `service.unknown` print with ✗** — they printed with no marker.
848
+ - **Messages name files by repo-relative `/` paths** instead of the absolute path of the machine
849
+ that ran the check: `service.unknown`, no-model, no-spec, no-openapi, no-asyncapi, an unreadable artifact
850
+ (`service.unreadable` / `feature.unreadable`) and `health.dependency-unmodelled`. The same
851
+ `message` strings in `--json` change with them. A path outside the docs repo keeps its absolute
852
+ spelling.
853
+ - **`sources.unverifiable-from-here` prints ○, not ✓** — that check did not run here. It stays
854
+ severity `ok`.
855
+ - **On `--all`, a warning or error more than three services raise once each prints once**,
856
+ followed by one line counting them (`same code ×N on M services: …`, capped, `+K more`). A
857
+ service raising the code more than once, and a finding carrying details, still print in full;
858
+ every finding is still in `--json`.
859
+ - **`--all` opens with the counts and an "errors by code" index, then the feature blocks**, then the
860
+ landscape and per-service findings; an interrupted-commit finding still leads and the footer still
861
+ closes the report.
862
+
863
+ ### Fixed — on Windows, a rolled-back write takes back the directories it created
864
+
865
+ No code, flag or payload key was added. On Windows under Node 24, a command that failed and rolled
866
+ back — `loam archive` most visibly — answered `merge-failed` ("rolled back, nothing was merged")
867
+ and still left the directories it had made standing: an empty `features/archive/`, or an empty
868
+ `services/<new-service>/` that names a service the merge never added. Node reports the directory
869
+ `mkdir` created in the `\\?\C:\…` spelling, and the rollback, comparing that with the plain
870
+ spelling, found nothing inside it to remove. Those directories are now removed on Windows as they
871
+ already were on Linux and macOS; a directory something else has put a file in is still left alone.
872
+
873
+ ## [0.2.0-alpha.9] - 2026-10-20
874
+
875
+ The first publish since `0.2.0-alpha.7`. `0.2.0-alpha.8` was tagged but never reached npm (its
876
+ release run failed at `npm ci` on a stripped lockfile), so a repository upgrading from alpha.7 takes
877
+ alpha.8's section below as well as this one. Still a prerelease on the `alpha` line.
878
+
879
+ Most of it is fixes. `loam archive` merges into a landscape written in any form LikeC4 parses —
880
+ several model blocks, markdown strings, split heads, hyphenated ids — and no longer writes a wrong
881
+ merge at exit 0 for the shapes listed under "Fixed — `loam archive` merges into a landscape…",
882
+ which alpha.7 did. Consumers are counted once, by one index, so `loam archive`, `loam validate` and
883
+ `loam diff` agree; the PR gate sees a changed schema, an edited `op` on an edge and a caller drawn
884
+ with no `op`; `loam gate --since` keeps an archived feature in view; the trust readouts show stale
885
+ vouches; a waiting writer no longer deletes the next writer's lock.
886
+
887
+ Several entries change an exit code, listed in each: refusals that are new (an overwrite or
888
+ retirement a consumer still names, a delta reviving a retired operation, an unreadable consumer
889
+ document) and refusals that were false and are gone. A federated verification record is now one
890
+ file per service, and an older loam does not read it: upgrade every repository that records or
891
+ gates a feature together. New: `loam list suites`, `loam new --modifies`, `loam gate --since`,
892
+ `loam_adopt` on MCP; six new stable codes — `openapi.op-revived`, `asyncapi.slot-revived`,
893
+ `next.revouch`, `frontmatter.owner-mismatch`, `diff.op-modified` and `diff.message-modified`.
894
+
895
+ ### Changed — one consumer index: `loam archive`, `loam validate` and `loam diff` agree about who still consumes an operation or a message
896
+
897
+ No code or flag was added; one additive `--json` key on two existing codes. Who still consumes what
898
+ a change takes out was answered three ways — the coherence gate (`openapi.remove-op-consumed`,
899
+ `asyncapi.remove-message-consumed`), `loam diff`'s victims (`diff.op-removed-consumed`,
900
+ `diff.message-removed-consumed`, the `-deprecated`/`-modified` details, `diff.consumer-added` /
901
+ `diff.consumer-removed`) and `loam archive`'s `openapi.op-modified` / `asyncapi.message-modified`
902
+ warnings — and the three copies read different documents. They are one index now; SCHEMA.md's
903
+ "Who still consumes it" states the rules.
904
+
905
+ - **More consumers are found, so more removals are refused:**
906
+ - **Edges come from the whole `architecture/` project**, not `architecture/landscape.likec4`
907
+ alone, for the gate as for `loam diff` and the archive's overwrite warnings: an edge with `metadata { op }` (or `consumes`) drawn in
908
+ `architecture/calls.likec4` or in a use-case document counts. The gate passed such a removal
909
+ and the next `validate --all` reported `spine.op-undefined` on the edge. Where only a sibling
910
+ document fails to parse, the map file's own edges still count.
911
+ - **The gate names use-case hops** attributed to the operation (or backed by a `consumes` edge),
912
+ as `loam diff` already did.
913
+ - **An `arch.spec.md` `Operations:` line** of another service counts, as a spec.md one does —
914
+ for the gate, `loam diff`'s victims, `diff.consumer-added` / `diff.consumer-removed` and the
915
+ archive's overwrite warnings. So an
916
+ operation's `diff.consumer-added` / `-removed` is not reported while the consumer's
917
+ `arch.spec.md` cannot be read on the other side, as it never was over an unreadable `spec.md`.
918
+ The consumer's own `openapi.yaml` does not suppress it: an unreadable contract proves no
919
+ ownership, so the line counts on that side too. The exception is a line whose join exists only
920
+ because that contract did not parse on one side, while on the other side it reads and defines
921
+ the id: there the line names the consumer's own operation, and only the contract's readability
922
+ changed. No `diff.consumer-added` / `diff.consumer-removed` is reported for it — before, one per
923
+ provider of the id, each counted in the `--json` summary's `added` / `removed`. The contract's own
924
+ `unreadable[]` entry is still reported, and `loam diff` still exits 1 on it.
925
+ - **The provider's own `spec.md` requirements count for a marker retirement too**, as they do for
926
+ the overwrite of the entry below: one still naming the retired operation after the merge (a
927
+ second one beside the REMOVED requirement) archived at exit 0 and left `spec-api.op-undefined`.
928
+ Its `arch.spec.md` `Operations:` lines count for neither shape: `validate` grades such a line in
929
+ spec.md only, so one in arch.spec.md strands nothing. An edge the provider draws to itself
930
+ counted already, and still does — the spine grades it like any other.
931
+ `loam diff` and the archive's overwrite warnings still leave the provider's own documents out:
932
+ they name who ELSE is affected.
933
+ - **On the event axis the provider's own `Publishes:` lines count**, as the merge would leave
934
+ them. A message slot re-keyed to another name, or a send operation turned to receive, while the
935
+ provider's requirement still publishes the message, archived at exit 0 and the next
936
+ `validate --all` reported `spec-event.message-undefined`; it is `asyncapi.remove-message-consumed`
937
+ now. So do its own `publishes` edges, which the spine grades on their source: a full retirement
938
+ over a living `paymentService -> billingService { metadata { publishes '…' } }` archived at exit
939
+ 0 and left `spine.message-undefined` on the provider. The remedy names "drop or redraw its
940
+ publishes edge".
941
+ - **Fail closed**: a spec the merge refuses to apply — a malformed `Requirement-ID:` in the
942
+ living spec or the delta — no longer answers "no consumers". The living requirements count,
943
+ plus every requirement the delta ADDS or MODIFIES.
944
+ - **A consumer document loam cannot read refuses the removal**, as the gate did before it asked
945
+ one index: another service's `spec.md` or `arch.spec.md` that does not decode as UTF-8, the
946
+ provider's own `spec.md` (either spec file, for a message), or a service id two directories
947
+ claim. `openapi.remove-op-consumed` /
948
+ `asyncapi.remove-message-consumed` name each in `details[]` and say the gate could not check —
949
+ never that the fleet still consumes it. An edge drawn with no `metadata { op }` and a contested
950
+ use-case hop still never refuse; `loam diff` lists them as doubts, as before.
951
+ - **`loam diff` counts only a service that SENDS a message as still producing it.** A removed
952
+ message another service declares only with `action: receive` — a consumer's own declaration,
953
+ which validate requires — was `diff.message-removed` (warn), "still declared by" that consumer,
954
+ while the gate refused the same removal and the next `validate --all` reported
955
+ `spine.message-unproduced` there. It is `diff.message-removed-consumed` now, naming the gate's
956
+ consumers; the warning for a message another service does send reads "still sent by".
957
+ - **Fewer refusals where the consumer was false or already retired:**
958
+ - **Another service's `Operations:` line naming an id its own contract defines** names its own
959
+ operation, and is no consumer (gate, `loam diff`, `diff.consumer-added` / `-removed`, the
960
+ archive's overwrite warnings): shared
961
+ ids such as `healthCheck` no longer refuse a rename, and `--approve` is no longer the only way
962
+ past a fleet `validate --all` calls clean.
963
+ - **Read after the feature's merge**: a consumer requirement the same feature MODIFIES to drop
964
+ the id, or REMOVES, no longer counts — another service's included, which is the remedy the
965
+ refusal itself names.
966
+ - **A message another service would still send after the merge strands no consumer.**
967
+ `asyncapi.remove-message-consumed` counted every `consumes` edge, `Consumes:` line and use-case
968
+ hop of a message whoever sent it, so retiring it from one of two senders was refused over the
969
+ other sender's consumers. Its remedy said to drop that sender's own `consumes` edge. Meanwhile
970
+ `loam diff` reported the same removal as `diff.message-removed` (warn), "still sent by …", and
971
+ the next `validate --all` was clean. Now a consumer refuses only while no service would send the
972
+ message after this feature's merge. The senders are read as the merge leaves them: a service
973
+ the feature carries an event delta for sends what its merged contract would (the delta itself
974
+ where it creates that contract), so moving production to another service in the same feature
975
+ passes, and a feature that retires the message from every sender is still refused. An unreadable contract never counts as
976
+ a sender. Beside another sender, the provider's own `Publishes:` lines, its own `publishes`
977
+ edges and its own unreadable spec files still refuse; another service's unreadable spec file,
978
+ or an id two directories claim, does not. This predates the one index: the gate behaved the
979
+ same before it. The message is reworded to say which case applies. With no sender left it reads
980
+ "no readable contract would still send it after this merge" and adds the route "keep another
981
+ service sending it". With a sender left it names the senders, lists only the provider's own
982
+ documents, and gives only their remedy.
983
+ - **An `#external` element that publishes the message keeps its consumers produced too**, for
984
+ the gate and for `loam diff` as for `validate`. Both counted only senders inside the fleet, so
985
+ retiring a message from its only fleet sender was refused while an `#external` element still
986
+ published it (an edge from it with `metadata { publishes }`), and after `--approve` `loam diff`
987
+ reported `diff.message-removed-consumed` (error) — while `spine.message-unproduced` accepts such
988
+ a producer and the next `validate --all` was clean. Now the outside producer counts as a
989
+ sender, read off the living `architecture/` project with validate's own rule: only while the
990
+ project parses, and not from an edge the feature's own C4 delta draws. The gate asks the rule
991
+ over the service directories as the merge leaves them, so an element the same feature makes a
992
+ service — a `specs/<id>/` directory for the id the element, or the element it is nested in,
993
+ resolves to, which the archive turns into `services/<id>/` — no longer counts, and the
994
+ retirement is refused, as the next `loam diff` and `validate --all` would read it; `loam diff`
995
+ asks over the directories as they stand. `loam diff` reports
996
+ `diff.message-removed` (warn), "still sent by `<title>` (#external)", and a refusal for the
997
+ provider's own documents, which it still does not excuse, names it the same way. With no sender
998
+ left, the refusal now also says "nor does an #external element publish it", and its route
999
+ reads "keep another service sending it (…) or an #external element publishing it (a publishes
1000
+ edge from it in the living architecture/ project; one whose element this feature's
1001
+ specs/<service>/ makes a service no longer counts)". This predates the one index too.
1002
+ - **`openapi.remove-marker-missing` is owed only while there is a living operation to retire**:
1003
+ not for an id the service's living contract does not define (a consumer's REMOVED requirement
1004
+ naming another service's operation), and not for one the delta already replaces at its method
1005
+ and path (no marker could be written there). A living `openapi.yaml` that does not parse proves
1006
+ neither, so the marker is still owed there, as before. The overwrite's "REMOVE the requirement"
1007
+ route can now be followed; its message says a REMOVED requirement owes no marker there.
1008
+ `asyncapi.remove-marker-missing` likewise skips a message the merge already takes out.
1009
+ - **`details[]`** (additive) on `openapi.remove-op-consumed` and `asyncapi.remove-message-consumed`:
1010
+ the consumers, one per line, in the order `loam diff` lists the victims of the same removal.
1011
+ - **Messages reworded**: the remedies name the `architecture/` project instead of
1012
+ `architecture/landscape.likec4`, and say a requirement is retired in the feature's
1013
+ `specs/<service>/` delta; `diff.op-removed`'s no-consumer sentence reads "no current edge,
1014
+ requirement or use-case hop names it".
1015
+ - **Exit code:** where a consumer is newly found, `loam archive` exits 1 (`not-coherent`) without
1016
+ `--approve`; `loam validate --feature`, `loam validate <FEAT>` and `loam validate --all` (for every
1017
+ active feature in that shape) exit 1; `loam status` counts the finding as gating; `loam show
1018
+ <FEAT> --json` reports `review.readyToArchive: false`; and `loam diff` exits 1
1019
+ (`diff.op-removed-consumed` / `diff.message-removed-consumed`, `breaking: true`) where a removal
1020
+ now has a victim — a removed message whose only other declarer receives it included, which exited
1021
+ 0. Where another service's spec file does not decode, `loam validate --feature`
1022
+ reports `openapi.remove-op-consumed` / `asyncapi.remove-message-consumed` naming the file where it
1023
+ reported `feature.unreadable`, at exit 1 as before, and `loam archive` exits 1 as before. Where
1024
+ two directories claim a service id, or the provider's own `spec.md` does not decode (either spec
1025
+ file, for a message), `loam
1026
+ archive` and `loam validate --feature` exit 1 for the removal whatever those documents say —
1027
+ before, 0 unless a copy the gate could read named it — until the document reads or `--approve` is
1028
+ passed. Where a refusal was false or the consumer is retired in the same feature, each
1029
+ of those exits 0 for that shape unless something else gates. That includes a message retired from
1030
+ one sender while another service would still send it after the merge, or while an `#external`
1031
+ element still publishes it and the feature does not make that element a service, where only
1032
+ consumers were named: `loam archive`, `loam validate --feature`, `loam validate <FEAT>` and `loam validate
1033
+ --all` exited 1 and now exit 0, `loam status` no longer counts it as gating, and `loam show
1034
+ <FEAT> --json` no longer reports `review.readyToArchive: false` for it. `loam diff` reports
1035
+ `diff.op-removed` (warn) where its only victim was a same-id requirement, and
1036
+ `diff.message-removed` (warn) where an `#external` element still publishes the removed message —
1037
+ it exited 1 (`diff.message-removed-consumed`, `breaking: true`) and now exits 0 unless something
1038
+ else fails. `--approve` overrides
1039
+ every one of these refusals as before. `--strict` changes nothing here beyond that: every finding
1040
+ in this entry is an error, or is unchanged.
1041
+
1042
+ ### Fixed — `loam instructions loam-spine` says what `loam rebase` pins for a revived quote
1043
+
1044
+ The page said `loam rebase` "pins neither shape". That has been false since rebase began grading
1045
+ the delta it will write. Rebase never pins a retired operation as new, but it does repin a quote
1046
+ of an address a move vacated over the operation that lives there now, and it reports the revival
1047
+ that edit makes. The page now says so, in line with the `loam-codes` command map, WORKFLOW.md
1048
+ and SCHEMA.md.
1049
+
1050
+ ### Fixed — an operation with no `operationId` is a slot like any other
1051
+
1052
+ No code, flag or payload key was added. The OpenAPI reader used to drop an operation written
1053
+ without an `operationId`. It now keeps it by its method and path, never by an id: requirements,
1054
+ edges and removal markers still cannot name it, and it gets no finding of its own. The checks that
1055
+ work by slot now see it:
1056
+
1057
+ - **`openapi.op-revived` names it.** When a quote lands where such an operation lives, the message
1058
+ says `<METHOD /path> now holds an operation with no operationId, and archiving this delta
1059
+ replaces it there`. Its move-back remedy adds that, if that operation is to stay, the same delta
1060
+ must define it at another address. The message no longer calls that address one "the living
1061
+ contract no longer has". Followed as it used to read, the remedy led to an archive that deleted
1062
+ the operation with nothing naming it.
1063
+ - **`loam rebase` and `loam validate` agree about a pin over it.** Rebase always pinned against
1064
+ whatever stood at the slot, but validate refused that pin with `openapi.baseline-invalid` ("no
1065
+ operation at that slot").
1066
+ - **Pins are now required at these slots.** An unpinned quote over such an operation counts into
1067
+ `openapi.baseline-missing` (warn, gates archive), and so does an unpinned delta operation without
1068
+ an `operationId` over a living one. Their pins are graded like any other (`-stale`, `-invalid`).
1069
+ **Exit code:** `loam archive` now exits 1 (`not-coherent`) on either shape until `loam rebase`
1070
+ pins it, or `--approve` merges unpinned. `openapi.baseline-missing` is a warning that gates:
1071
+ `loam validate` exits as before on it, except `--strict` (and `--all --strict`), which fails on
1072
+ any warning and so now exits 1; `loam status` counts it as gating, and `loam show <FEAT> --json`
1073
+ reports `review.readyToArchive: false` until the pin is written.
1074
+ - **A pin on such an operation where nothing lives is `openapi.baseline-invalid` (error)**, as it is
1075
+ for a named operation (rebase never writes one; a malformed pin on such an operation is refused
1076
+ the same way). **Exit code:** `loam validate --feature`, `loam validate <FEAT>` and `loam validate
1077
+ --all` (for every active feature carrying one) now exit 1 on it, where they exited 0 with no
1078
+ finding; `loam archive` exits 1 (`not-coherent`) without `--approve`; `loam status` counts it as
1079
+ gating; `loam show <FEAT> --json` reports `review.readyToArchive: false`.
1080
+ - **`openapi.remove-op-consumed`** also fires when the delta defines an operation with no
1081
+ `operationId` where a consumed one lives. Before, a delta holding only such an operation upserted
1082
+ over the consumed one with no finding of any kind.
1083
+
1084
+ ### Changed — `loam archive` refuses an overwrite that takes a consumed operation out of the contract
1085
+
1086
+ No code, flag or payload key was added: an existing code covers a second shape.
1087
+
1088
+ - **`openapi.remove-op-consumed` (error, `--approve` overrides) now also fires for an overwrite.**
1089
+ A feature's `openapi.yaml` defines another operationId at the method and path where a living
1090
+ operation lives, and the contract the archive would write then defines the living one nowhere.
1091
+ The merge replaces it there with no removal marker naming it, so before this only the advisory
1092
+ `api.op-unconsumed` fired, the archive passed without `--approve`, and the next `validate`
1093
+ reported `spine.op-undefined` / `spec-api.op-undefined`. The consumers are a removal's — the one
1094
+ consumer index of the entry above — including the provider's own `spec.md` requirements as the
1095
+ merge would leave them: one the feature MODIFIES to name the new operation, or REMOVES, no longer counts. The
1096
+ message names what replaces the operation and where, and says to define it at another address in
1097
+ the same delta if it is to stay.
1098
+ - **Exit code:** on this shape `loam archive` now exits 1 (`not-coherent`) without `--approve`,
1099
+ and `loam validate --feature`, `loam validate <FEAT>` and `loam validate --all` (for every active
1100
+ feature in this shape) exit 1. `loam status` counts it as gating, and `loam show <FEAT> --json`
1101
+ reports `review.readyToArchive: false` until it is fixed or archived past with `--approve`. With
1102
+ `--approve` the archive merges as before and lists the finding in `overridden[]`.
1103
+ - **`c4-api.op-removing`** now also fires for a new tagged edge onto an operation the delta
1104
+ replaces that way.
1105
+ - An overwrite of an operation nothing names is unchanged: `api.op-unconsumed` stays advisory. On
1106
+ the event axis `asyncapi.remove-message-consumed` already graded the simulated merge's sent
1107
+ messages, so a channel, operation or message slot redefined under another identity was caught
1108
+ like a retirement for its OTHER consumers — but not for the provider's own `Publishes:` line,
1109
+ which the consumer-index entry above adds.
1110
+
1111
+ ### Fixed — `loam archive` merges into a landscape written in any form LikeC4 parses
1112
+
1113
+ No code, flag or `--json` key was added or changed. Each case below is a landscape (or a delta)
1114
+ that `loam validate --all` called valid and that `loam archive` refused with `merge-failed`, nothing
1115
+ written — or, where the case says "exit 0", merged wrong and wrote; each now merges as LikeC4
1116
+ reads it.
1117
+
1118
+ - **A landscape with more than one top-level `model { }` block.** The splice map read only the
1119
+ first block, so every declaration in a later one was invisible and archive refused with
1120
+ "declares '<id>' in a form placement cannot locate — most often two declarations sharing one
1121
+ line" about a declaration on a line of its own. Every block is read now. An addition anchored to
1122
+ a living element — nested in it, joining its service's neighborhood, or joining its source's
1123
+ edges — lands in that element's block; an unanchored one lands in the first block's trailing
1124
+ region, where it landed before. A one-block map is placed byte for byte as before, and archive
1125
+ order still does not change the bytes. The same reading fixes three neighbours: a delta written
1126
+ in two `model { }` blocks (its second-block additions were "cannot locate … in delta.likec4"), a
1127
+ service `model.likec4` whose `extend <fqn> { }` frame sits in a later block (the merge minted a
1128
+ second frame in the first block, or re-added a container the model already declares there), and
1129
+ `loam validate --all --base`, which did not grade a call edge the branch edited in a later block.
1130
+ - **A declaration split across lines.** LikeC4's newlines are hidden tokens, so `name =` with its
1131
+ kind on the next line, a bare name above `= kind`, a kind above the name it declares, a title on
1132
+ the next line, a `/* … */` comment spanning a line break, an edge broken around its arrow, and an
1133
+ edge's `#tag` on the next line are all one statement — and the splice map, reading one line per
1134
+ statement, could not see them. Worse, `name = kind 'title'` with its `{` on the NEXT line read as
1135
+ a bodyless element: an addition anchored after it landed between the head and its body and took
1136
+ that body — description, children, `metadata { service }` binding — for its own, in a merge that
1137
+ parsed and was written (exit 0). The body stays with its element now.
1138
+ - **An `extend <fqn> { }` frame written in the landscape itself, with an edge inside it.** An
1139
+ edge the feature added, anchored to the frame's edge, was spliced INSIDE the frame — a scope, so
1140
+ its names re-resolved there: the delta's `zulu -> beta` landed in `extend alpha { beta = … }` as
1141
+ an edge into alpha's own `beta` container, in a merge that parsed and was written (exit 0). It
1142
+ lands outside the frame now. Elements inside such a frame were already read correctly.
1143
+ - **A hyphenated id (`entity-service = …`) and a dot-kind edge (`a .async b`).** LikeC4's
1144
+ identifiers may carry hyphens and a declared relationship kind may be written as `.kind`; the
1145
+ splice map read neither, so such an element was "in a form placement cannot locate" and such an
1146
+ edge made the relationship count disagree.
1147
+ - **A `link` (or `icon`) URI followed by more code on its line.** LikeC4 reads `https://…` and a
1148
+ relative `./…` path as one token; the splice map read its `//` as a comment that ran to the end
1149
+ of the line, so `x = kind 'X' { link https://… }` lost its closing brace and archive refused with
1150
+ `landscape.likec4 has no model block`. The URI is a token wherever it sits: a `link` or `icon`
1151
+ alone on its line ends there, and the child or edge declared on the line below is its own.
1152
+ - **A markdown string (`'''…'''` or `"""…"""`) whose prose holds an apostrophe or an odd number
1153
+ of quotes.** The splice map read the triple as three ordinary quotes, so the first `it's` in a
1154
+ long `description` ended the string and the rest of the file was read inside out — a map with
1155
+ one such description above its model had no model block at all, and archive refused with
1156
+ `landscape.likec4 has no model block`.
1157
+ - **A delta edge whose body holds a key or tag containing `op`, `publishes` or `consumes`.** The
1158
+ splice map read those keys anywhere in the edge's body, and took a hyphen for the end of a word,
1159
+ so `op-version 'v2'` above `op 'cancelOrder'` read as op `v2`, and a `#op-critical` or `#op` tag
1160
+ as an op naming the description below it. The edge then matched none of the delta's statements
1161
+ and archive refused with "cannot locate the '<a> -> <b>' relationship in delta.likec4". A key is
1162
+ read whole now, and `op`, `publishes` and `consumes` only from `metadata { }`, which is where
1163
+ LikeC4 reads them. The same misreading hid such an edge from `validate --all --base` (below).
1164
+ - **A bare carriage return (`\r` with no `\n`) between two statements.** LikeC4 breaks a line at
1165
+ `\r` as well as `\n`, and the splice map broke only at `\n`. A statement therefore ran on over
1166
+ a bare `\r` into the next one, and a `// comment` ran on over it and hid the next declaration.
1167
+ Two elements, two edges or a property and a child on either side of a bare `\r` were refused as
1168
+ "in a form placement cannot locate". A bare `\r` ends the statement and the comment now. A CRLF
1169
+ map reads exactly as before.
1170
+ - **An addition anchored to a declaration whose line goes on with more code.** Placement put an
1171
+ addition on the line after its anchor. When the next declaration opened a multi-line body on the
1172
+ anchor's own line (`alpha = … { … } beta = … {`), that next line was inside beta. An element
1173
+ bound to alpha's service was then written inside beta as its child, in a merge that parsed and
1174
+ was written (exit 0). A block comment opened on the anchor's line and closed on a later one
1175
+ swallowed the addition the same way. The addition now lands right after its anchor whenever
1176
+ code, or a block comment the line does not close, follows the anchor on its line. Two archives
1177
+ anchored to the two declarations on such a line also write the same bytes in either order now;
1178
+ before, whichever ran second landed above the other. A comment trailing the anchor changes
1179
+ nothing: the addition lands on the next line, as before — unless a bare `\r` ends that `// comment`
1180
+ and the next declaration follows it on the same line, which counts as code following the anchor.
1181
+ - **A bodyless declaration with a comment trailing it on its line.** A parent such as
1182
+ `api = container 'API' // the api` that gained its first child got its `{` inside the comment,
1183
+ and archive refused: the merged landscape "would not parse". The body now opens before the
1184
+ comment, which then trails the new closing brace. A block comment that closes on a LATER line
1185
+ was worse: the splice map ended the declaration inside it, so a child it gained, or an element
1186
+ or edge anchored after it, was written inside the comment, in a merge that parsed and was
1187
+ written (exit 0). The declaration now ends before such a comment. A comment that closes on the
1188
+ declaration's own line keeps its place, and the bytes come out as before.
1189
+ - **A delta whose feature tag sits in a tag list with a separator.** LikeC4 reads a tag list as
1190
+ `#a #b, #c;`: one or more tags, then comma groups, then an optional `;`. Stripping the feature's
1191
+ own tag left `#FEAT-1, #x` as `, #x` and `#FEAT-1;` as a bare `;`, and archive refused because
1192
+ the merged landscape "would not parse". The tag now takes the comma after it with it (or else
1193
+ the comma before it), and takes a `;` when no tag is left in front of it. A tag after a comma no
1194
+ longer leaves that comma behind either: `#x, #FEAT-1` lands as `#x`, where it used to land as
1195
+ `#x,`.
1196
+
1197
+ What the splice map still cannot locate is still refused (`merge-failed`, nothing written), and
1198
+ the message no longer guesses. It used to say "most often two declarations sharing one line"
1199
+ whatever the map held — over the reported declaration, which sat on a line of its own. It names the
1200
+ declaration (a relationship by its endpoints, where it used to give only a count) and the line
1201
+ LikeC4 parsed it on, then the cause the text shows: "it shares that line with '<other>'" only when
1202
+ other code precedes it on that line (its own kind keyword, or an edge's own source, is not other
1203
+ code); "the splice map read it as part of '<other>', which starts at line
1204
+ <n>" when the scan ran a statement on past a line break LikeC4 makes (a bare carriage return was
1205
+ the one such break until the splice map learnt to break there too, above); and otherwise that the
1206
+ splice map read nothing there, with the rewrite to make and a request to report the shape. Wording
1207
+ only; the code is unchanged.
1208
+
1209
+ ### Changed — `loam archive` refuses a delta that would bring back an operation another feature retired
1210
+
1211
+ Two codes, both added to the `IssueCode` union, and one additive `--json` key were added.
1212
+
1213
+ - **New `openapi.op-revived` (warn, gates archive).** A feature's `openapi.yaml` spells an
1214
+ operation (by operationId) that its service's living contract no longer holds, and an ARCHIVED
1215
+ feature retired it with an `x-loam-remove: true` marker — any archived feature, including one
1216
+ whose directory carries the same feature id as the one being graded. Before this, the
1217
+ quote read as an addition: a delta that had only quoted `createOrderV1` archived after the
1218
+ feature retiring it and put it back into the living contract at exit 0, with the advisory
1219
+ `api.op-unconsumed` the only finding. The message names the operation, the retiring feature(s)
1220
+ and both remedies — delete the quote if it only restated the operation; archive with `--approve`
1221
+ if the feature brings it back on purpose.
1222
+ - **New `asyncapi.slot-revived` (warn, gates archive)** — the same on the event axis: a `channels`
1223
+ or `operations` slot by its key, or a message by its name, that an archived feature retired. A
1224
+ channel-slot removal's inline messages are named from that feature's `.loam-before/` snapshot
1225
+ pre-image, read only while it still hashes to the digest its manifest recorded.
1226
+ - **Both codes also cover an address an archived feature VACATED.** An archived feature that
1227
+ relocated an operation (a marker on the old slot, the same operationId defined at a new one)
1228
+ vacated the old address — and so did one that retired it there, once a later feature re-added it
1229
+ somewhere else. A later delta quoting the operation at that address — unpinned — archived at
1230
+ exit 0 and put the id back beside where it lives, one operationId at two slots (only the
1231
+ advisory `openapi.duplicate-operationid` said so afterwards): the slot-keyed baseline gate saw
1232
+ nothing living at the slot, and the id-keyed check saw the id living. That quote is now
1233
+ `openapi.op-revived` too. Both codes are judged on the contract the archive would write: the
1234
+ check runs the archive's own merge of the delta over the living contract and compares where each
1235
+ operationId (each message name) stands there with where it stands now. One the merged contract
1236
+ answers at two or more addresses — one of them new, and vacated by an archived feature — is the
1237
+ finding; one the living contract still holds that it answers at exactly one address is not,
1238
+ whether the delta moves it back with its own removal marker where it lives now or swaps another
1239
+ operation in there (with or without an operationId — the merge replaces what stands at that
1240
+ method and path). A removal marker standing where the id does not live deletes nothing, so it
1241
+ no longer excuses the quote. The message says which archived feature moved the operation and
1242
+ where to, or retired it there, and — apart from
1243
+ that, since anything landed after can have moved it again — where it lives now; its remedy names
1244
+ the address to drop: delete the quote, or, to move it back on purpose, add a removal marker
1245
+ where it lives now — and where the delta already spells that address, so no marker can be
1246
+ written there, archive with `--approve` instead. Where another operation lives at the quoted
1247
+ address now (on the event axis, another message is declared at the quoted key), the message
1248
+ names it and says archiving replaces it there — the merge writes one entry per address —
1249
+ instead of calling the address one the living contract no longer has, and the move-back remedy
1250
+ says to define it at another address (key) in the same delta if it is to stay: followed as it
1251
+ read before, the remedy reached a clean gate and an archive that deleted the other operation
1252
+ with nothing naming it. The retired-shape message names such an entry too. A quote of an
1253
+ address an archived feature moved the operation away from is the finding even where the living
1254
+ contract holds the operation nowhere (taken out of its new address by a hand edit, which leaves
1255
+ no marker): the move is on the record, and the message reads as a retirement brought back,
1256
+ naming the move. On the event axis the vacated addresses are `components.messages` keys (a
1257
+ marker there) and the inline declarations a channel removal took; a delta that restates the
1258
+ channel a name stands inline in with a `$ref`, or retires it, takes the name out of there and
1259
+ is not graded. For an inline declaration the remedy names the channel (restate it with a
1260
+ `$ref`, or retire it), never a marker nested on the inline message, which retires nothing. For
1261
+ a `components.messages` key the remedy also names every channel (or operation) of the merged
1262
+ contract still `$ref`-ing that key, to restate with the `$ref` pointed at the quoted key: told
1263
+ only to retire the key, an author left the channel pointing at nothing, and its send stopped
1264
+ sending the message — `asyncapi.remove-message-consumed`, which gates. `asyncapi.baseline-invalid`'s
1265
+ move-back remedy says the same. A channel or operation slot is its key, so a "moved" one is a
1266
+ retirement, as before. A message inline under a channel the delta brings back is reported by
1267
+ the channel's finding alone only while the merged contract declares the name nowhere else; where
1268
+ an archived feature re-declared it under `components` as it retired the channel, the inline
1269
+ declaration is a finding of its own (one message name in two places) — it was skipped, and
1270
+ `--approve` archived `asyncapi.duplicate-message`.
1271
+ - **Exit code: `loam archive` now exits 1 (`not-coherent`) on this shape** without `--approve`;
1272
+ with `--approve` it merges as before, and the operation lands in `overridden[]` and the text
1273
+ output's "archiving despite" list, naming what came back and whose retirement it reverses.
1274
+ `loam validate --feature` and `--all` report both codes as warnings: exit unchanged, except
1275
+ under `--strict`, which fails on any warning and so now exits 1 on this shape. `loam status`
1276
+ counts them as gating like every warning that gates, and `loam show <FEAT> --json` reports
1277
+ `review.readyToArchive: false` for such a feature until the quote is deleted (or the feature is
1278
+ archived past it with `--approve`).
1279
+ - **`loam rebase` no longer calls a retired operation one "this feature is adding".** It pins
1280
+ nothing for it either way, and now prints the same finding after its pin lines; the additive
1281
+ `--json` key `revived[]` carries the findings (`severity`, `code`, `gates`, `subject`,
1282
+ `message`). The operation's `pins[]` row still reads `unresolved`. The check runs over the
1283
+ delta as rebase will write it, pins included, under `--dry-run` too: a quote pinned to its
1284
+ own content is skipped by the merge, and repinned over another operation that now lives at an
1285
+ address an archived feature moved it away from it becomes an edit that would put it back
1286
+ there — that finding is in `revived[]` as well, and its `pins[]` row reads `repinned`. The
1287
+ text output's revival banner no longer says "nothing was pinned" for such items.
1288
+ - **`openapi.baseline-invalid` and `asyncapi.baseline-invalid` reworded** for a pin whose living
1289
+ operation or slot is gone: they used to say "drop the marker", which turned the quote into the
1290
+ revival above. They now say to re-read the living contract and delete the quote unless the
1291
+ feature deliberately brings it back. When the living contract still holds the operationId (or,
1292
+ for a `components.messages` key, the message name) elsewhere, the message instead says a
1293
+ parallel feature MOVED it there (or retired it here, and another re-added it there) and where it
1294
+ lives now, and to make any change there — never to archive past the revival finding, which would
1295
+ answer it at both addresses. The `/loam-check` rows and `loam explain` say the same. `loam
1296
+ rebase`'s text says such items were retired by an archived feature "or moved to another address
1297
+ by one".
1298
+ - **What it reads, and what it cannot see.** Only files under `features/archive/` — the archived
1299
+ deltas' removal markers and each feature's `.loam-before/` snapshot; no git. A marker counts only
1300
+ if the merge carried it out: the snapshot's pre-image must hold the marker's operationId at its
1301
+ method and path (an event marker: its channel or operation key, or its message name at its
1302
+ `components.messages` key), and a contract the archive created or never wrote lost nothing. A
1303
+ marker archived past `openapi.remove-target-missing` / `-mismatch` (or the `asyncapi.*` twins)
1304
+ under `--approve` deleted nothing, so a genuinely new operation it happened to name is not a
1305
+ revival. A feature with no intact snapshot cannot be checked, and its markers count as written.
1306
+ A removal made by hand-editing a living contract, or an archived feature deleted from disk,
1307
+ leaves nothing to read, and a quote of such an operation still grades as an addition. A marker
1308
+ with no `operationId` or one written at path level retired nothing when its feature archived, so
1309
+ it is never named as the retirer.
1310
+
1311
+ ### Added — `#suite-<name>` tags and `loam list suites`: which services a test suite needs up
1312
+
1313
+ No code was added. One reserved tag prefix, one list section and one positional argument were.
1314
+
1315
+ - **A `dynamic view` may carry `#suite-<name>` tags** to say which test suites the journey runs in —
1316
+ a separate prefix from `#cap-`, many-to-many (one view may carry several), each declared as
1317
+ `tag suite-<name>` in the landscape's `specification` block like every LikeC4 tag, so a mistyped
1318
+ suite is a parse error.
1319
+ - **A view carrying only `#suite-` tags is now a use case.** The one opt-in predicate takes three
1320
+ prefixes (`cap-`, `req-`, `suite-`), and so does the byte scan that lets a fleet with no use cases
1321
+ skip the LikeC4 load. Such a view's hops are graded by `loam validate` like any other use case's
1322
+ (`usecase.step-unbacked`, `usecase.step-contested`, `usecase.step-unlinked` — at fleet scope, and
1323
+ per service for a flow beside a service's model), and every reader that names use cases sees it:
1324
+ `loam diff`'s removed-operation victims, `loam delta` and `loam status`'s use-case blast radius,
1325
+ and the context packs. A view that already carried `#cap-` or `#req-` is unaffected, and an
1326
+ untagged view is still never graded. On a fleet that had drawn `#suite-`-tagged views before this
1327
+ release, those views can now earn findings.
1328
+ - **New section `loam list suites [<name>] [--json]`** — the answer to "which services must be up
1329
+ for suite X". `[section]` accepts `suites`, and a new optional positional `[name]` narrows the
1330
+ listing to one suite. Additive `--json` keys, emitted only with this section: `suites[]` (`name`,
1331
+ `views[]` of `{id, file}`, `services[]` — enumerated service ids, sorted — and `unresolved[]` of
1332
+ `{element, external}`: the participants that resolve to no service, `external` true when the
1333
+ element or anything it is drawn inside carries `#external`, the walk stopping at the first
1334
+ element bound to or titled for a service as `loam gate`'s partner reading does), `ungrouped[]` (`{id, file}` of every
1335
+ use case carrying no `#suite-` tag, never narrowed by `[name]`), and `useCases` (`state`:
1336
+ `absent` | `ok` | `unreadable`, beside `unreadable` and, when unreadable, `error`, spelled as
1337
+ `loam list capabilities` spells them). Over an `architecture/` that does not parse the section
1338
+ degrades instead of refusing, and `suites` / `ungrouped` are absent rather than `[]`. Only the
1339
+ fleet map's use cases are listed. The text view prints one block per suite — its services, the
1340
+ unresolved participants marked `!`, its use cases — then the use cases in no suite.
1341
+ - **Refusals use existing codes.** An unknown suite name is `unknown-target` with close names; a
1342
+ `[name]` after any other section, and `--needs-work` with `suites`, are `invalid-option` (a name
1343
+ after another section used to be commander's "too many arguments", also `invalid-option` under
1344
+ `--json`). The campaign flags (`--subsystem`, `--owners`) refuse `suites` as they refuse every
1345
+ section but `services`.
1346
+ - The MCP tool `loam_list` gains the matching optional `name` argument, and its `section`
1347
+ description now lists `glossary` and `suites`. `name` without `section` is refused as invalid
1348
+ params (`-32602`) rather than read as a section.
1349
+ - `examples/docs` tags its one use case `#suite-smoke` and `#suite-notifications`, so
1350
+ `loam list suites` there answers checkout-web, notification-service and order-service, with
1351
+ `customer` and the `#external` topic `kafka.orderEvents` named as unresolved.
1352
+
1353
+ ### Changed — the use-case byte scan opens on a `#` tag reference, not on a bare prefix
1354
+
1355
+ No code, flag or payload key was added, changed or retired.
1356
+
1357
+ - **loam starts LikeC4 for use cases only when a document references a reserved tag.** The scan in
1358
+ front of every reader of the fleet's use cases, and of a service's own flows, used to open on the
1359
+ bare text `cap-`, `req-` or `suite-` anywhere in a file. It now opens on `#cap-`, `#req-` or
1360
+ `#suite-`, with only whitespace, line breaks or comments between the `#` and the name, as LikeC4
1361
+ allows. That is the only way a view carries a tag, so no use case is missed.
1362
+ - **A broken `.likec4` beside a service's model that names a prefix only in prose no longer fails
1363
+ the service.** In `0.2.0-alpha.8`, a file with no tagged view that mentioned `cap-` or `req-`
1364
+ anywhere — a title, a comment — was loaded, and if it did not parse, `validate --service` (and
1365
+ `--all`, per service) reported `usecase.flow-invalid`, an error, exit 1. This release's `suite-`
1366
+ prefix would have added a third such substring (an id like `test-suite-runner`). Now the file is
1367
+ skipped, like any untagged diagram.
1368
+ - **The same holds at fleet scope.** Suppose `architecture/` does not parse, carries no tagged view,
1369
+ and only declares `tag cap-…` / `tag req-…` or names a prefix in prose. It now reads as declaring
1370
+ no use case. `useCases.unreadable` is then `false` where it used to be `true` in
1371
+ `loam status --json`, `loam delta --json` and `loam list capabilities --json`, and so is
1372
+ `useCaseScan.unreadable` in `loam context --json`. `validate --all` still reports that map as
1373
+ `landscape.invalid`.
1374
+ - A `#` followed by a reserved prefix still opens the scan wherever it is written, a comment
1375
+ included.
1376
+
1377
+ ### Changed — `loam-feature` asks whether a feature is owed, and analyzes before it pins
1378
+
1379
+ No code, flag or payload key was added, changed or retired. What changes is the protocol text an
1380
+ agent is handed, the `intent.md` a new feature starts from (and which of its text `intent.empty`
1381
+ counts), and what the documentation says a feature's contract delta holds.
1382
+
1383
+ - **`loam instructions loam-feature` opens with a step 0.** A change that touches no requirement,
1384
+ operation or fleet edge — a refactor, a dependency bump, a fix back to behaviour the living spec
1385
+ already documents — needs no feature: a code PR, then `loam validate --service <id>` and
1386
+ `loam vouch --pack --service <id>` in the service repo, and a person's vouch. WORKFLOW.md's "The
1387
+ honestly-small change" now opens with the same lane.
1388
+ - **A new, ungraded step 4b, "Analyze before you pin", sits between authoring and `loam rebase`.**
1389
+ The agent reads `loam show <FEAT> --json`, `loam context <service> --feature <FEAT> --json` and the
1390
+ `explore` neighbours, then writes questions under `## Open questions` in `intent.md`: ambiguous
1391
+ SHALLs, values, limits and windows two services state differently (and which side refuses),
1392
+ `Realizes:` lines that do not deliver their capability, each edge's timeout, retry, partial
1393
+ failure and compensation, and neighbours left out of `--touches`. A person answers by editing the
1394
+ file. `loam-implement` sends an ambiguity it meets to the same section.
1395
+ - **`loam new` scaffolds an optional `## Open questions` heading in `intent.md`**, over a comment
1396
+ only. **`intent.empty` now reads past that section:** questions an agent writes there are not the
1397
+ feature's Why, so an intent whose only prose is open questions still gates the archive. The generated `loam-feature` command and skill pointers
1398
+ list both new steps in their spine; an unedited pointer refreshes on the next `loam init`.
1399
+ - **A feature's `openapi.yaml` / `asyncapi.yaml` is documented as holding only what the feature
1400
+ adds, changes or removes.** SCHEMA.md, WORKFLOW.md, the `loam-spine` and `loam-feature` pages,
1401
+ the `spec.md` scaffold's comment and the example deltas taught "a complete document, not a
1402
+ patch". The merge never needed one: it walks only the paths, methods and slots a delta spells, a
1403
+ local `$ref` resolves into the living contract, and an add-only delta needs no `loam rebase`.
1404
+ Each needless quote is one more pin and, once a parallel feature removes the quoted operation,
1405
+ either a blocking `openapi.baseline-invalid` (pinned) or an archive that puts the retired operation
1406
+ back (unpinned); `loam-check`'s fix for the first now says to delete the quote. The
1407
+ `loam-spine` page also no longer says path-level keys and components go unpinned —
1408
+ `x-loam-baselines` covers them.
1409
+ - **WORKFLOW.md gains "Build on the envelope":** a tracker export from `loam tasks <FEAT> --json`
1410
+ keyed on unit ids (and what rewording re-keys one), a rollup of the per-service CI summaries
1411
+ that reads a missing or old report as unknown, and `npx likec4 mcp` beside `loam mcp` on the same
1412
+ docs repo.
1413
+ - **The `loam-codes` page points at AGENTS.md's "Reference pages — fetch when needed" section** —
1414
+ it named a "The reference pages" section that does not exist — and lists `loam-report` among the
1415
+ pages `loam instructions` prints.
1416
+
1417
+ ### Added — `loam new --modifies <Req-ID>`: a small change is new → edit → archive
1418
+
1419
+ - **New repeatable flag `--modifies <Req-ID>` on `loam new`.** Each value names a living
1420
+ requirement, by its `Requirement-ID:`, in a service the feature `--touches`. The scaffold copies
1421
+ that requirement into the service's delta, uncommented, under `## MODIFIED Requirements`, with
1422
+ its `Based-On:` pin already set. It is the same pin `loam rebase` writes, so a rebase right after
1423
+ reports it `unchanged`, and the living text moving before archive is still `delta.baseline-stale`.
1424
+ An `arch.spec.md` requirement lands in a scaffolded `specs/<svc>/arch.spec.md`. Before this, the
1425
+ author copied the requirement by hand and ran `loam rebase` to pin it.
1426
+ - **When every touched service has a `--modifies` and no `--new-service` or `--capability` is
1427
+ given, no `delta.likec4` is written.** A feature that only rewrites requirements changes no
1428
+ architecture, and the scaffold already told authors to delete the file in that case. `created`
1429
+ in `--json` simply omits it. Any other combination scaffolds `delta.likec4` as before.
1430
+ - **Refusals use existing codes.** An id that no touched service's living spec carries is
1431
+ `unknown-target`. The message names the close ids in those specs, or the untouched service that
1432
+ does carry the id (`add --touches <svc>`). A value that is not a Requirement-ID, or an id two
1433
+ touched documents both carry, is `invalid-option`. A living spec loam cannot decode is
1434
+ `merge-failed`, as `loam rebase` refuses it. Every refusal writes nothing.
1435
+ - The opt-in MCP author tool `loam_new` gains the matching `modifies` argument. No code or payload
1436
+ key was added.
1437
+
1438
+ ### Added — `loam_adopt`: the adopt brief on the MCP server
1439
+
1440
+ - **`loam mcp` serves a sixteenth read tool, `loam_adopt`**, mirroring `loam adopt [--service <id>]
1441
+ [--subsystem <name>] [--targets] --json` (JSON arguments `service`, `subsystem`, `targets`). Its
1442
+ result is the CLI's `adopt` envelope, verbatim. It is advertised `readOnlyHint: true` like every
1443
+ base tool, because `adopt` writes nothing: it hands the agent the brief, and the agent writes the
1444
+ baseline. Until now an MCP-only host could fetch the loam-adopt protocol through
1445
+ `loam_instructions` and could not run its first step, `loam adopt --service <id> --json`. No
1446
+ code, flag or payload key was added to the CLI; `tools/list` returns one more tool.
1447
+
1448
+ ### Changed — `loam init --create` with no service prints the docs repo's first hour
1449
+
1450
+ - **A run that creates a docs repo and binds no service now ends with `next — the docs repo's first
1451
+ hour:`**: `loam seed --from fleet.yaml` (or draw the map by hand), `loam init --docs <docs-repo>
1452
+ --service <id> --agent-profile service` in each service's own repo, `loam adopt --service <id>
1453
+ --json` there, and `loam status`. It used to print nothing after the scaffold list. These are
1454
+ WORKFLOW.md's Day zero steps and the rungs `loam status` already names on an empty fleet. A run
1455
+ that binds a service still prints the single-repo trial's hour, and a join still prints neither.
1456
+ Human output only: no code, flag or payload key was added, and the `--json` envelope is unchanged
1457
+ for both variants.
1458
+
1459
+ ### Changed — every service bind loam prints names `--agent-profile service`
1460
+
1461
+ No code, flag or payload key was added, changed or retired. What changes is printed text and one
1462
+ `--json` value.
1463
+
1464
+ - **The bind command now reads `loam init --docs <path> --service <id> --agent-profile service`**
1465
+ in `loam status`'s `next.bind-service` step, its `loam explain` entry, the docs repo's first hour,
1466
+ the generated AGENTS.md (`## The cycle`, step 0), and the `loam-adopt` and `loam-implement`
1467
+ protocols. README, QUICKSTART and docs/SERVICE-TEAMS.md already told a fleet to bind each service
1468
+ checkout this way, because `--agent-profile` defaults to `full`, and `full` writes `loam-feature`
1469
+ and `loam-ship` into the service repo. But every bind command loam printed left the flag out, and
1470
+ an agent runs the command loam prints. WORKFLOW.md's Day zero step 3 now spells it too.
1471
+ - **`next.bind-service`'s `command` in `status --json` `next[]` carries the new text.** Its
1472
+ `execution` block still names the same two placeholders.
1473
+ - **Two binds keep their old form on purpose.**
1474
+ - `loam-adopt` names the single-repo trial (`--create`, used only when the docs repo does not
1475
+ exist yet) as the one bind that keeps the full profile.
1476
+ - `doctor.service-unbound`'s fix is a re-run in a checkout that is already wired. An omitted
1477
+ `--agent-profile` there keeps the profile that checkout recorded.
1478
+
1479
+ ### Changed — the OpenSpec compatibility pin moves to v1.14.1, and a weekly canary watches the newest release
1480
+
1481
+ No code, CLI flag or payload key was added, changed or retired. The `audit-openspec` /
1482
+ `migrate-openspec` payload shape is unchanged, but the value it reports as the certified release moves.
1483
+
1484
+ - **`audit-openspec` and `migrate-openspec` now report OpenSpec v1.14.1 as the certified release.**
1485
+ The banner line and `baselines.release` under `--json` carry `1.14.1` / `v1.14.1` /
1486
+ `87c3595ace6a2e22957f39ebe5b74c2f2316e0cb` (released 2026-10-05) instead of `1.9.0` / `2826b88`.
1487
+ `baselines.mainCanary` is unchanged. The corpus gate's `--baseline release` is now the v1.14.1
1488
+ corpus: 223 Markdown files, 765 requirements and 2475 scenarios, every file round-tripped. v1.9.0
1489
+ stays as the new `--baseline previous` regression pin beside `legacy` (v1.7.0) and `canary`, and
1490
+ the scheduled `ci.yml` matrix runs all four. The sweep found no parser gap. The grammar OpenSpec
1491
+ 1.13 added — a closing `#` run, `*`/`+` bullets on `REMOVED` and `RENAMED`, repeated delta
1492
+ sections — was already read by the migration, and a new test pins the repeated sections.
1493
+ `audit-openspec` and `migrate-openspec --apply`, run on OpenSpec's own v1.14.1 workspace, staged
1494
+ all 251 living requirements and 747 scenarios across 36 services. The seven vendored fixtures stay
1495
+ at v1.9.0, since v1.14.1 has no shape they lack. MIGRATING-from-OpenSpec.md's version matrix
1496
+ records all of this.
1497
+ - **The corpus sweep reads each file through the migration's own reader.** That reader is the one
1498
+ `audit-openspec` and `--apply` use: loam's parser plus the grammar only OpenSpec accepts. The
1499
+ sweep used to read with loam's parser alone. Every pinned corpus gives the same totals either way.
1500
+ - **A new scheduled workflow, `.github/workflows/openspec-canary.yml`, watches the newest OpenSpec
1501
+ release.** It runs weekly and on manual dispatch. It resolves the newest `vX.Y.Z` tag with
1502
+ `git ls-remote` and sweeps it with the new `--baseline latest` mode of `npm run
1503
+ test:openspec-corpus`. That mode reports how the totals moved from the certified pin instead of
1504
+ refusing an unpinned commit, and still fails on a file that does not round-trip. The workflow also
1505
+ audits OpenSpec's own workspace and diffs upstream's parser sources against the pin, in its step
1506
+ summary. It has no push or pull-request trigger and blocks nothing; CONTRIBUTING.md's "The
1507
+ OpenSpec canary" is the runbook. The README's note that `--baseline` defaults to `canary` was
1508
+ wrong — it defaults to `release` — and now says so.
1509
+
1510
+ ### Added — documentation: running loam where service work runs, a CLI-only first ten minutes, and what loam costs an agent
1511
+
1512
+ No code, flag, command or payload key was added, changed or retired; the binary is unchanged. The
1513
+ new paths are `docs/SERVICE-TEAMS.md` and `scripts/payload-sizes.ts`.
1514
+
1515
+ - **README has a service-repo CI recipe** beside the docs repo's fleet-edges one. The workflow
1516
+ checks out the service and the docs repo as sibling subdirectories of the workspace, so a
1517
+ committed relative `docsDir` such as `../docs` resolves. It does not use `path: ../docs`, which
1518
+ `actions/checkout` refuses. It runs `loam validate --service <id> --json` at the same pinned
1519
+ version, and two `jq` programs turn the findings, or a refusal, into `::error` / `::warning`
1520
+ annotations and a `$GITHUB_STEP_SUMMARY` table. A deploy pipeline can add
1521
+ `loam gate --service <id> --since <ref>`. It is followed by setup for cloud agents in a service
1522
+ repo: a `copilot-setup-steps.yml` and a Codex environment setup script, each cloning the docs
1523
+ repo into the committed `docsDir` with a read token from a secret.
1524
+ - **README's day-zero binding passes `--agent-profile service`**, and says why: the profile
1525
+ defaults to `full`, which puts `loam-feature` and `loam-ship` into every service checkout.
1526
+ QUICKSTART's command for joining a shared docs repo passes it too.
1527
+ - **QUICKSTART opens with ten minutes of CLI only.** A two-service `fleet.yaml`, `loam seed`, one
1528
+ provider `openapi.yaml` and one misspelled `op` on an edge give `spine.op-undefined`, with no
1529
+ agent and no adoption. A requirements-only `loam new --touches … --touches …` then validates and
1530
+ plans its archive before either service is adopted. Every output shown was produced by running
1531
+ the commands. The single-project path moves to sections 3–6.
1532
+ - **`docs/SERVICE-TEAMS.md`, one screen for a team whose repository is one service in a fleet:**
1533
+ four commands, five slash commands, what a person signs when they vouch, and the fifteen codes
1534
+ they will actually meet. README, QUICKSTART and the Docs list link to it.
1535
+ - **COMPARISON.md names OpenSpec, Spec Kit and Kiro again, in one dated section.** "Spec-driven
1536
+ change tools, as of October 2026" sets out what each offers for a change that crosses
1537
+ repositories: OpenSpec Stores and worksets, `show --diff` and the Cloud Agent; Spec Kit's
1538
+ contract-driven guide, `/speckit.converge` and MCP server; Kiro's Design-First specs and Kiro
1539
+ Web. It records what is still open upstream (OpenSpec #725, Spec Kit #4583) and where loam
1540
+ differs, including the two gaps it has against them. Each claim links its source. The rest of
1541
+ the page stays an ecosystem map, and `test/docs-drift.test.ts` now allows the three names only
1542
+ inside that dated section.
1543
+ - **`docs/BENCHMARKS.md` has a table of agent-facing payload sizes**, in bytes and ~tokens:
1544
+ the generated `AGENTS.md`, `loam adopt --service <id> --json` with and without `--targets`,
1545
+ every `loam instructions` page with and without `--no-fix-tables`, and `loam explain <code>
1546
+ --json` across the whole vocabulary. `npx tsx scripts/payload-sizes.ts --write` generates it from
1547
+ the tree, and `test/docs-facts.test.ts` regenerates it and fails when the committed table
1548
+ differs. README's adopt row now points at that table instead of quoting a dated figure.
1549
+ `test/docs-drift.test.ts` now also requires every exact `@ybotok/loam@<version>` in README to be
1550
+ this checkout's version.
1551
+
1552
+ ### Changed — `loam archive`'s overwrite warnings name the current consumers
1553
+
1554
+ No code or flag was added. Existing warnings now fill a payload key that was always empty.
1555
+
1556
+ - **`openapi.op-modified` and `asyncapi.message-modified` now say who the overwrite affects.**
1557
+ Each message ends with one of three phrases: `N current consumer(s) still name it`,
1558
+ `whether current consumers name it could not be fully answered`, or
1559
+ `no current consumer names it`. The warning's `details` (until now always `[]`) list the
1560
+ consumers, in `archive --json` `warnings[]` and, when the contract merge is refused as
1561
+ `not-coherent`, in the same warning's entry in `issues[]`:
1562
+ - edges of the `architecture/` project;
1563
+ - other services' living requirements, in either spec file;
1564
+ - use-case hops;
1565
+ - and last, any consumer whose documents could not be read, or any caller drawn without an
1566
+ `op`.
1567
+
1568
+ The human plan prints the same lines under the warning. This is the scan `loam diff` runs for
1569
+ `diff.op-modified` and `diff.message-modified`. Here it runs over the living fleet as it stands
1570
+ before the merge, and only when one of these warnings fires. On the 120-service benchmark fleet
1571
+ that costs about 130 ms. Severity is unchanged, and neither warning gates the archive. Whether a
1572
+ change breaks a consumer is still not graded (`oasdiff breaking` / `asyncapi diff` do that).
1573
+ - `openapi.component-modified` and `asyncapi.component-modified` do not name consumers. A
1574
+ component's consumers are the operations or messages whose `$ref` closure reaches it, and loam
1575
+ has no index of those.
1576
+
1577
+ ### Fixed — an MCP tool argument is never read in another argument's slot
1578
+
1579
+ No code, flag or payload key was added, changed or retired. Two MCP input schemas each gained one
1580
+ JSON Schema keyword, named below.
1581
+
1582
+ - **A `tools/call` that supplies an optional positional argument while leaving out an earlier one is
1583
+ refused as invalid params (`-32602`)**, naming the argument left out, as a missing required
1584
+ argument already is. Positionals reach the command by position, so the later value used to be read
1585
+ as the earlier one: `loam_instructions` with `args` and no `workflow` took the first arg as the
1586
+ workflow name. An empty `args` still passes. The defect is present in `0.2.0-alpha.8`.
1587
+ - **The MCP tools' advertised input schemas now say that `name` needs `section` and a non-empty
1588
+ `args` needs `workflow`**, the rule a tool call already enforces, so a client that validates a
1589
+ call against `tools/list` is no longer refused `-32602` for a call the schema allowed.
1590
+ `loam_list`'s schema gains `dependentRequired` (`name` → `section`); `loam_instructions`' gains
1591
+ `dependentSchemas`, requiring `workflow` once `args` has a member, because an empty `args` still
1592
+ passes. Both are standard JSON Schema keywords; no tool, argument or other schema key changed.
1593
+
1594
+ ### Fixed — `loam validate` reports an unreadable verification record from the docs repo
1595
+
1596
+ No code, flag or payload key was added. An existing code fires in one more place.
1597
+
1598
+ - **`evidence.record-unreadable` (warn) now fires on the feature target too**, under
1599
+ `validate --feature <FEAT>`, `validate <FEAT>` and every active feature under `validate --all`, run
1600
+ from anywhere. A `features/<FEAT>/verification.yaml` or `verification/<svc>.yaml` with git
1601
+ conflict markers, broken YAML, a shape that is not a record, or a summary that contradicts its
1602
+ claims used to pass `validate --all` in the docs repo with no finding. `verify` refused the file,
1603
+ and `status`, `gate` and `tasks` reported it. Until now only the service target raised this code,
1604
+ and only from the service's own repository. The finding names the file and gives the same repair
1605
+ as the service-target finding. Its `locations` hold that file as `primary`. It is a warning, as
1606
+ it is on the service target. `valid`, the exit code and `loam archive` are unchanged, and
1607
+ `--strict` fails on it. In a service repository's `validate --all` the same file is reported
1608
+ twice: once by the service target and once by the feature target.
1609
+
1610
+ ### Fixed — `loam diff` reads a spec saved as UTF-16 without a byte-order mark the same way on both sides
1611
+
1612
+ No code, flag, path or payload key was added, changed or retired.
1613
+
1614
+ - On disk, a `spec.md` or `arch.spec.md` saved as UTF-16 without a byte-order mark was refused as
1615
+ unreadable. At the base ref, the same bytes were read as a readable spec with no requirements.
1616
+ That happened because only the working-tree side checked for NUL characters. So an untouched file
1617
+ was suspended on one side only, and a branch that re-saved the file as UTF-8 reported every
1618
+ requirement in it as `diff.requirement-added`. The base side now applies the same decoding rule as
1619
+ the working tree. Both sides report the file as unreadable, and the spec axis is suspended. The
1620
+ contract axes are unchanged: their readers on both sides already applied the same rule.
1621
+
1622
+ ### Fixed — a writer waiting on the lock no longer deletes the next writer's live lock
1623
+
1624
+ No code, flag, path or payload key was added, changed or retired. What changes is that concurrent
1625
+ writers no longer fail, or share one commit window, when a lock holder exits while another writer
1626
+ waits for it.
1627
+
1628
+ - **A stale `.loam-lock` is broken only while the holder judged dead still holds it.** A writer
1629
+ that finds the lock held reads the holder, and if that pid no longer exists on this host, it
1630
+ breaks the lock. Under CPU load the holder could release normally and exit between that read and
1631
+ the pid check, and a third writer could take the lock in the same gap. The waiter then deleted
1632
+ the third writer's live lock and took one of its own, so two writers were inside one commit window.
1633
+ One of four concurrent `loam gherkin --json` runs failed as `merge-failed` (`ENOENT … rename
1634
+ '….feature.loam-<pid>-…tmp'`): the second writer's recovery read the first's in-flight journal as
1635
+ an interrupted commit and swept its staged files. The code is shared by every writer that takes
1636
+ the lock (`archive`, `unarchive`, `new`, `seed`, `rebase`, `subsystem`, `verify --record`, `vouch`,
1637
+ `gherkin`), and the defect is present in `0.2.0-alpha.8`. The waiter now re-reads the lock after
1638
+ its verdict, and moves it to a private name before removing it. Anything it moved that is not the
1639
+ lock it judged goes back in place. A crashed holder's lock is still reclaimed as before.
1640
+
1641
+ ### Fixed — `validate --all --base` grades the landscape by what the branch changed in it
1642
+
1643
+ No code, flag, path or payload key was added, changed or retired; the unscoped `validate --all` is
1644
+ unchanged.
1645
+
1646
+ - **A call edge the branch adds or edits in `architecture/` is graded against its provider's
1647
+ contract**, even when the provider is not in scope. README's own example — `authorisePayment` on
1648
+ an edge into a contract spelling it `authorizePayment` — used to pass both PR gates on the example
1649
+ fleet and, even under `--strict`, on a freshly seeded spine-first fleet. It now fails as
1650
+ `spine.op-undefined` (and `spine.op-deprecated`, `spine.op-link-missing` and `spine.resolved` read
1651
+ the same way), filed on the `landscape` target. Only those edges are graded: the provider is not
1652
+ pulled into scope, so its own documents — on a seeded fleet, its `service.no-model` — stay out of
1653
+ the PR's verdict. An edge is new when its (source, target, op) is; a reformat grades nothing.
1654
+ - **An edge whose metadata or tags hold a word containing `op` is graded on the op it names.**
1655
+ `--base` finds the branch's edges by reading the map's text, and that reading took
1656
+ `op-version 'v2'` above `op 'cancelOrdr'` for op `v2`, and a `#op-critical` tag for an op naming
1657
+ the description below it. The edge it read matched no edge in the parsed map, so the branch's
1658
+ misspelt `cancelOrdr` was graded nowhere: exit 0 and `valid: true`, where the unscoped run
1659
+ reports `spine.op-undefined`. A key is read whole now, and the op only from `metadata { }`.
1660
+ - **An edge written after a bare carriage return is graded on its own source.** After a `link`,
1661
+ `description` or `technology` property ending in a bare `\r`, the parent's sourceless
1662
+ `-> target` edge was read as an edge FROM `link` (or `description`, or `technology`), and an edge
1663
+ after a `// comment` ending in a bare `\r` was not read at all. Either way the branch's op on that
1664
+ edge was graded nowhere, with the same exit 0. LikeC4 breaks a line at a bare `\r`, and the
1665
+ reading does too now.
1666
+ - **The landscape re-grades a fleet-wide vocabulary only when the file that carries it changed.**
1667
+ `permissions.*` rides a scoped run only when `architecture/permissions.yaml` changed,
1668
+ `capability.*` only when `architecture/capabilities.yaml` or `capabilities/` did,
1669
+ `obligation.invalid` and `obligation.unapplied` only when `architecture/obligations.yaml` did,
1670
+ `glossary.*` only when `glossary/` did, and `agents.stale` / `docs.binary-behind` only when
1671
+ `AGENTS.md` did. A one-line map edit on the example fleet is no longer red under `--strict` on four
1672
+ warnings that were all there at the base.
1673
+ - README's spine-first section now says that under the `--base` gate a PR adding a service's
1674
+ `openapi.yaml` stays red until that service has a `model.likec4` — `service.no-model` is the
1675
+ ratchet working as designed.
1676
+ - **Each service target in scope reports a committed map that does not parse, even when the branch
1677
+ left `architecture/` alone.** On such a branch the scoped run emits no `landscape` target, yet
1678
+ each service in scope still said `spine.landscape-invalid … the parser output is reported once,
1679
+ on the landscape target`, with empty `details`. So the parser output was reported nowhere. Each
1680
+ service's `spine.landscape-invalid` now carries the parser output in `details` whenever the run
1681
+ has no `landscape` target, as `validate --service` always has. A feature target does not report
1682
+ the map, so a run whose only targets are features still says nothing about it. The defect
1683
+ predates this release's `--base` changes and is present in `0.2.0-alpha.8`.
1684
+
1685
+ ### Fixed — one LikeC4 typo no longer floods agent-facing output
1686
+
1687
+ No code, flag, path or payload key was added, changed or removed; what moves is the TEXT of the
1688
+ diagnostics LikeC4 hands loam, and one warning that stops cascading.
1689
+
1690
+ - **A parser's "Expecting: one of these possible Token sequences" message is cut to its first four
1691
+ alternatives plus `… (+N more)`**, keeping the `but found: '…'` line — on every surface that
1692
+ prints a LikeC4 diagnostic (`validate`, `context`, `show`, `tasks`, `doctor`, `gate`,
1693
+ `archive`). One dropped quote in the example fleet's order-service model made `loam validate
1694
+ --service order-service` print 18,503 lines (434 KB; 458 KB as `--json`); it now prints 27 lines
1695
+ (1.7 KB; 5.5 KB as `--json`).
1696
+ - **Each LikeC4 diagnostic is reported once, in line order.** The same message at the same line of
1697
+ the same file used to be listed twice, and the lexer's error led the parser's even when it sat a
1698
+ dozen lines below the fault. The `N error(s)` count in `c4.invalid`, `delta.invalid` and the other
1699
+ parse findings now counts distinct diagnostics.
1700
+ - **`covers.unknown` is no longer raised for a service whose own `model.likec4` does not parse.**
1701
+ `c4.invalid` is the one finding; the six `covers.unknown` warnings the example fleet showed for one
1702
+ dropped quote named ids that were spelled right. The same suspension already applied to a model
1703
+ extending a map that does not parse.
1704
+ - **A broken document no longer bloats anybody else's briefing.** A dropped quote in a feature's
1705
+ `delta.likec4` made `loam context order-service --json` 626 KB instead of 24 KB (now 27 KB). The
1706
+ fleet map's parse error reached `useCaseScan.error` on every service's `context --json` and the
1707
+ `loam gate` "landscape cannot be used" message raw — 173 KB and 152 KB — and now goes through the
1708
+ same spelling as every other diagnostic, which adds its 1-based `L<n>:` line (23 KB and 2 KB).
1709
+
1710
+ ### Fixed — `doctor.agent-files-stale` fires on a changed body, not on every release
1711
+
1712
+ No code, flag, path or payload key was added, changed or retired; what a repo NOTICES is that
1713
+ `loam doctor` stops warning about generated command and skill files whose only difference is the
1714
+ version stamp.
1715
+
1716
+ - The stamp sits inside every generated command and skill file, so every release used to turn every
1717
+ one of them stale in every repo, even when the release had not changed a word of the pointer — a
1718
+ warning that is always on, which then hid the release where a pointer really did change. A file is
1719
+ now stale only when its body, the stamp line set aside, is not what this loam writes, AND its stamp
1720
+ is missing or older. Line endings do not count (a Windows checkout that converts them to CRLF is
1721
+ not a change). A customized body under a current stamp stays quiet, as before. Same code, same severity;
1722
+ the `agents.staleFiles` list in `loam doctor --json` simply stops naming stamp-only files.
1723
+
1724
+ ### Fixed — relationship order is the same on every run
1725
+
1726
+ No code, flag, path or payload key was added, changed or retired; lists built from relationships
1727
+ come back in one stable order.
1728
+
1729
+ - LikeC4 indexes relationships by a hash of the document's path, and loam stages documents under a
1730
+ fresh random folder per run, so an edge whose hash happened to come out all digits (about one in
1731
+ 2,200) jumped to the front of the list on that run only; inside a project of several documents the
1732
+ base order was whichever file finished reading first. A scheduled CI run failed on it. Every loader
1733
+ now returns relationships sorted by source, target, title, `op`, `publishes`, `consumes` and tags,
1734
+ so anything listed from edges — `loam explore`'s neighbours, the candidates a
1735
+ `usecase.step-contested` finding names, findings emitted per edge — keeps one order between runs
1736
+ and machines. That order is no longer the declaration order.
1737
+
1738
+ ### Fixed — the repository no longer claims a version is published
1739
+
1740
+ No code, flag, path or payload key was added, changed or retired; only documentation moved.
1741
+
1742
+ - README called the version in `package.json` "the newest published version", by construction of
1743
+ the test that pinned it — and kept saying so for `0.2.0-alpha.8`, whose publish failed. It now
1744
+ names that version as the version of the checkout and points at the npm dist-tags as the source of
1745
+ truth; the drift test forbids the publication claim in README and in the CHANGELOG section of the
1746
+ checkout's own version. The `0.2.0-alpha.8` section below carries a dated note that it was never
1747
+ published.
1748
+ - The `0.2.0-alpha.3` section promised that installing `@ybotok/loam@alpha` gets it. That release was tagged
1749
+ and withdrawn before its publish step, which only the `0.2.0-alpha.4` section said. It now carries
1750
+ the same kind of dated note.
1751
+ - README said Codex reads `.codex/skills/`. That is the folder the `codex` target writes, and Codex
1752
+ still loads it, but Codex's skills documentation names only `.agents/skills`. README now says so
1753
+ and points at the `agents` target for the documented root. The `codex` target's path is
1754
+ unchanged.
1755
+ - README and WORKFLOW no longer quote byte sizes and code counts that had gone stale (the
1756
+ `loam-check` page, the AGENTS.md orientation, the finding-code total). A finding- or refusal-code
1757
+ count quoted on a shipped page is now graded against `loam explain --codes`.
1758
+
1759
+ ### Changed — `loam archive` moves the delta's bytes and nothing else's
1760
+
1761
+ - **The requirements merge splices instead of re-serializing.** A living requirement the delta does
1762
+ not touch is written back byte for byte; only ADDED requirements and the ones a MODIFIED replaces
1763
+ are serialized. Before, the whole `## Requirements` run was re-serialized, so a living spec
1764
+ written without a blank line under `### Requirement:` had one inserted under every untouched
1765
+ requirement by its first archive — the diff, `loam vouch --pack` and the re-vouch all widened to
1766
+ the whole section. `loam unarchive` is unchanged and still restores every byte. Each kept slice
1767
+ is matched to its requirement by `Requirement-ID:` (the heading when there is none), never by
1768
+ position; if the slices and the living requirements do not match one to one, the whole run is
1769
+ re-serialized as before rather than one requirement's text landing in another's place.
1770
+ - **`archive --json` gains the additive `revouchOwed` key** (`[{service, path, owner}]`, on the
1771
+ dry run and the real merge alike): every vouched living `spec.md` / `arch.spec.md` the merge
1772
+ moves under its vouch stamp, with the frontmatter's `owner` (`null` when none is recorded).
1773
+ `content.stale` follows on each — unchanged and still a warning; no new code, nothing `--strict`
1774
+ lets through. The human view prints the same list under a `re-vouch owed` line, naming
1775
+ `loam vouch --service <id>` and saying the re-vouches belong in the archive's own PR, which
1776
+ `loam validate --all --base <ref> --strict` keeps red until they land. The `/loam-ship` protocol
1777
+ gains that step, and README's docs-repo PR recipe says so.
1778
+ - The human view of `loam archive` no longer prints its `archive <id>` header twice.
1779
+
1780
+ ### Fixed — the adopt brief's spec examples
1781
+
1782
+ - The `spec.md` and `arch.spec.md` examples in `loam adopt`'s brief name the service being adopted
1783
+ in their frontmatter `service:` and title, where they used to say `payment-service` for every
1784
+ service — a verbatim paste failed `frontmatter.field-mismatch`. Their requirements now carry the
1785
+ canonical blank line under `### Requirement:`, so a spec copied from them is not reflowed by its
1786
+ first archive.
1787
+
1788
+ ### Fixed — fleet readouts tell the truth about trust
1789
+
1790
+ A vouched spec edited after the vouch (`content.stale`, computable from the docs repo) was reported
1791
+ by `loam validate` and by no fleet readout; `sources.unwalked` fired in every service repo straight
1792
+ after a fresh vouch; and the human `status`/`list` views printed a verification count without
1793
+ saying who answered it. Additive throughout — no code, flag or key changed or was removed, and the
1794
+ maturity rung of every service is unchanged.
1795
+
1796
+ - **New stable code `next.revouch`** in `loam status`'s fleet `next[]` (and under `--service`): one
1797
+ step per vouched service whose `spec.md` or `arch.spec.md` changed since the vouch, naming the
1798
+ documents. Its `command` is `loam vouch --service <id>` and its `execution` is
1799
+ `kind: "human-review"`, `runnable: false`, `cwd: "service-repo"` — a re-vouch is a person's act.
1800
+ While one stands, `status` no longer reports `next.fleet-clean`.
1801
+ - **New payload key `services.contentStale`** on `loam status --json` (fleet form): of `vouched`, the
1802
+ services with a document moved under its stamp — a subset, like `sampledVouched`. The human line
1803
+ reads `N vouched (M stale)`.
1804
+ - **New payload key `scorecard.provenance.contentStale`** on `loam validate --all --json`: services
1805
+ with a `content.stale` finding in that run. The text row reads `N vouched — M stale content ·
1806
+ K stale digest(s) · …` (it used to say `0 stale digest(s)` beside the warning).
1807
+ - **New payload key `answered` inside `verification`** on `loam status --json` (the feature form, and each
1808
+ `features[].verification` of the fleet form) and on each `features[]` row of `loam gate --json`,
1809
+ which spreads the same record: the confirmed claims per `answered_by`
1810
+ (`runner`, `external-runner`, `agent`), the split `list --json` already carried. The human views
1811
+ print it beside the verdict — `status <FEAT>` as `verified 19/19 confirmed (13 runner · 6 agent)`,
1812
+ `list`'s verification column as `7/7 (5 runner · 2 agent)`. The verdict and its definition are
1813
+ unchanged.
1814
+ - **`loam list`** shows such a service as `vouched (stale)` (or `vouched (sampled, stale)`) and counts
1815
+ it in the maturity line; its `missing` — in text and in `--json`, and likewise in `explore` and the
1816
+ context pack, which share it — now reads `re-vouch (<file> changed since the vouch)`, so
1817
+ `list --needs-work` (with `--review-order` and `--owners`) lists it instead of answering
1818
+ "nothing to do". The `maturity` value stays `vouched`.
1819
+ - **`sources.unwalked`** no longer counts loam's own files — `loam.json` and the agent files recorded
1820
+ in its `agentFiles` — or any `.gitignore` toward its denominator. `.gitignore` is the file that
1821
+ defines what git tracks, so it is the measuring instrument, never a part of the service a spec
1822
+ owes; `package.json`, READMEs and the like stay in. A freshly vouched service repo is no longer
1823
+ red under `validate --service --strict` for files it cannot honestly list in `sources`.
1824
+ - **`loam list` legend** names the `e` (events / AsyncAPI) column: `[e]vents`.
1825
+ - **SCHEMA.md**: the per-service CI summary shape gains `contentStale` (the count of `content.stale`
1826
+ findings), and its jq derivation counts it. **WORKFLOW.md** states that `loam gate --strict` also
1827
+ fails on a join partner's `content.stale`.
1828
+
1829
+ ### Added
1830
+
1831
+ - **New warning `frontmatter.owner-mismatch`** from `loam validate` (`--service` and `--all`) when
1832
+ one service's `spec.md` and `arch.spec.md` both name an `owner` and the two differ. `loam show`
1833
+ and `loam context` print only `spec.md`'s owner, so the other value was a second answer nobody
1834
+ saw. Its `details` list each file's value (`spec.md: <owner>`, `arch.spec.md: <owner>`). Silent
1835
+ when either file names no owner (that stays `frontmatter.field-missing`). It compares the two
1836
+ files only, never CODEOWNERS. As a warning it fails `loam validate --strict`, so a service that
1837
+ passed `--strict` with two different owners now exits 1 there until they agree.
1838
+ - **`openapi.generated-stale` now says what moved.** The finding gains `details` naming each
1839
+ operationId that differs between the build's contract and the committed copy — `served, not in
1840
+ the committed copy: <id>` and `committed, no longer served: <id>` — and its message counts both
1841
+ sides, or says every operationId matches when the difference is inside an operation or outside
1842
+ `paths`. The digest still decides whether it fires; same code, same severity.
1843
+ - **`diff.op-modified` (warn)**: an operationId present at the base ref and now, whose operation
1844
+ changed — its own definition, its method or path, or anything its internal `$ref`s reach, so a
1845
+ field renamed in a shared `components` schema counts. **`diff.message-modified` (warn)** is the
1846
+ same for an AsyncAPI message: its declaration or anything its payload `$ref`s reach. Both name
1847
+ the authored consumers in `details[]` (the same scan `diff.op-deprecated` runs) and point at
1848
+ oasdiff or `asyncapi diff`; neither grades whether the change is breaking, so neither sets
1849
+ `breaking` or changes the exit code. A PR that renamed a required field used to print "no
1850
+ fleet-meaningful changes" and exit 0 with nothing else.
1851
+ - **The `modified` count in `loam diff --json`'s `summary` now counts both new codes** beside
1852
+ `diff.requirement-modified`. The key is unchanged; a diff over a changed contract reports a
1853
+ higher number than before.
1854
+ - A newly deprecated operation is still reported once, as `diff.op-deprecated`, not also as
1855
+ `diff.op-modified`.
1856
+
1857
+ ### Changed
1858
+
1859
+ - **An AsyncAPI 1.x or 2.x document is `asyncapi.invalid`, once.** loam reads AsyncAPI 3.0 only,
1860
+ and a document whose `asyncapi:` field says `1.` or `2.` used to read as a 3.0 contract with no
1861
+ messages: one `spine.message-undefined` per landscape edge, one `spec-event.message-undefined` per
1862
+ requirement and a `spine.message-unproduced` on every consumer, none naming the version. It now
1863
+ takes the unreadable path every broken contract takes — one `asyncapi.invalid` whose `details`
1864
+ name the version and `asyncapi convert --target-version=3.0.0`, and nothing on the event axis
1865
+ graded against it. The same applies to a feature's own `asyncapi.yaml` under `--feature`. The
1866
+ `asyncapi.invalid` message, and the `context` / `delta` lines for an unreadable event contract,
1867
+ now say "does not read as AsyncAPI 3.0" rather than "does not parse". **It is an error:
1868
+ `loam validate` now exits 1 on such a document even where no edge or requirement names its
1869
+ messages** — a document that used to pass. `asyncapi convert --target-version=3.0.0` upgrades it.
1870
+ - **`loam diff --base <ref>` exits 1 while a CHANGED `asyncapi.yaml` says 1.x or 2.x.** The
1871
+ document is listed in that service's `unreadable[]` once for each side on which it says 1.x or
1872
+ 2.x, counted in the `unreadable` total of `summary`, and the service reads
1873
+ `change: "changed"`; it used to read as a contract with no messages and exit 0. One the branch
1874
+ did not touch does not do this — see the next entry. `asyncapi convert --target-version=3.0.0`
1875
+ upgrades it.
1876
+ - **`loam diff --base <ref>` skips an unreadable `openapi.yaml` or `asyncapi.yaml` that the base
1877
+ ref holds byte for byte.** Such a contract is no longer listed in `unreadable[]`, counted in
1878
+ `summary`, or the reason a service reads `change: "changed"`, so an unconverted
1879
+ AsyncAPI 2.x file, or a broken OpenAPI file, that the branch never touched no longer fails every
1880
+ PR's diff with exit 1. A contract the branch changed, or one only one side cannot read, still
1881
+ suspends its axis and exits 1. `spec.md` and `arch.spec.md` are not skipped: an unreadable one
1882
+ stays suspended even when untouched, because another service's removal may be the one that
1883
+ could not be checked against it. No key is added or removed.
1884
+ - **Suggested element ids follow `loam seed`'s convention.** For a service the fleet map does not
1885
+ draw yet, the `loam adopt` brief and the `loam new` delta scaffold now suggest `svc_<id>` (dots
1886
+ and dashes folded to `_`, e.g. `svc_payment_service`) instead of a camel-cased name, and
1887
+ `loam new --touches <id>` reuses the element id the map already draws the service as. A service
1888
+ the map nests inside another element is quoted by its last segment with a note naming the parent,
1889
+ since a dotted id is no top-level declaration. The commented example in a freshly seeded
1890
+ `architecture/landscape.likec4` uses the seeded ids too. Element ids stay cosmetic —
1891
+ `metadata { service '<id>' }` is the join — so nothing already written needs to change.
1892
+ - **`seed-landscape-edited` gives advice that can be followed.** The refusal and `loam explain
1893
+ seed-landscape-edited` used to say "fold the edits into fleet.yaml and delete the file", which is
1894
+ impossible once an edge carries `metadata { op }` (fleet.yaml cannot). They now say seed runs
1895
+ once, before any operation is drawn, and that the map grows from then on through
1896
+ `loam adopt --service <id>` and feature C4 deltas. Same code, same exit code.
1897
+ - **`migrate-openspec` and `audit-openspec` read the OpenSpec 1.13 requirement grammar.** A
1898
+ `RENAMED` pair written with a `*` or `+` bullet, or with no bullet, is a pair (it was
1899
+ `openspec.renamed-malformed`); a `REMOVED` section written as a bullet list (`-`, `*` or `+`) of
1900
+ requirement headings is read as those removals (it was `openspec.change-empty`); and a closing
1901
+ `#` run on a `### Requirement:` or `#### Scenario:` heading is dropped at ingest, so
1902
+ `### Requirement: Invoice totals ###` stages as `Invoice totals` and a MODIFIED delta naming it
1903
+ no longer fails `loam validate` as `delta.modified-unknown`. loam's own parser is unchanged.
1904
+ - **An inbound landscape edge into the provider with no `metadata { op }`** (and no
1905
+ `publishes`/`consumes`) is now listed in the `details[]` of `diff.op-removed`,
1906
+ `diff.op-removed-consumed`, `diff.op-deprecated` and `diff.op-modified` as a consumer whose
1907
+ operation is unknown. A removal with such a caller and no named one now says "could NOT be fully
1908
+ answered" instead of "no current landscape edge or living requirement names it". It stays a
1909
+ warning, exit 0: a map `loam seed` drew, every edge without an op, does not fail every removal.
1910
+ - **Without `--json`, a commander usage error** (a missing `--base` on `loam diff`, an unknown flag
1911
+ or command, a missing argument) now prints a fix line naming `loam <command> --help` and the code
1912
+ line `invalid-option · loam explain invalid-option` under commander's own message, like every
1913
+ other refusal. stdout stays empty, exit 1; the `--json` envelope is unchanged.
1914
+ - README's `.github/workflows/fleet-edges.yml` **pins loam to an exact version** (`LOAM`) and says
1915
+ how to move the pin, **adds an oasdiff step** per changed `services/*openapi.yaml`, and no longer
1916
+ labels the `diff` step "did you break a consumer?". `test/docs-drift.test.ts` keeps the pin equal
1917
+ to the version README names.
1918
+
1919
+ ### Contributors — release preflight and test harness
1920
+
1921
+ - `npm run release:check` checks that every dependency a `package-lock.json` entry names resolves
1922
+ to another entry, the way Node looks it up, and then runs `npm ci --dry-run --offline` on a
1923
+ scratch copy of `package.json` and `package-lock.json` (new `scripts/lockfile-check.mjs`): the
1924
+ check that would have refused `0.2.0-alpha.8`'s stripped lockfile before the tag instead of after
1925
+ it. The structural walk comes first because npm's own verdict depends on the npm asked: npm 11
1926
+ refuses that lockfile, npm 10 (Node 22.22.3) installs it.
1927
+ - The test harness creates its temporary directories under the canonical (symlink-resolved) temp
1928
+ root, so the suite passes on macOS without a `TMPDIR` override.
1929
+
1930
+ ### Changed — a federated verification record is one file per service (a file-format event)
1931
+
1932
+ - **New path written: `features/<FEAT>/verification/<svc>.yaml`.** `loam verify <FEAT> --service
1933
+ <svc> --record` (and `--results` / `--contract-results` under `--service`, and the bound form that
1934
+ substitutes `--service` from loam.json) now writes that service's attestation to a file of its
1935
+ own instead of into one shared `verification.yaml`. Each file is a `schema: 2` record holding
1936
+ exactly one attestation and that service's answers; the directory is the record, and the verdict
1937
+ and counts are computed from the union of its files when anything reads it. What a repo notices:
1938
+ two teams recording two different services on two docs-repo branches no longer conflict when
1939
+ their PRs merge — the shared file conflicted every time (add/add from an empty start, content
1940
+ conflicts on `summary:`, `claims:` and `attestations:` once one attestation had landed).
1941
+ - **Old path still read, forever: `features/<FEAT>/verification.yaml`**, active and archived, in
1942
+ every shape loam has written. A one-file federated record reads exactly as before until a service
1943
+ records again; that service's new file then supersedes its share of the old one (the old file is
1944
+ not rewritten), and the write that leaves nothing in it counting removes it. An all-at-once
1945
+ (schema-1) record is removed by the first `--service` write, which lists its answers in
1946
+ `discarded` — the same answers the old overwrite listed.
1947
+ - **The all-at-once form is unchanged on disk.** `loam verify <FEAT> --record` without `--service`
1948
+ still writes the one file `verification.yaml` — it answers the whole checklist from one place, so
1949
+ there is no second writer to collide with and no service to name a file after — and is still
1950
+ refused `record-federated` over any record carrying attestations, now including per-service files.
1951
+ - **When services answered different versions of the checklist**, a record kept as per-service files
1952
+ counts the answers of the files that answered the version the feature asks NOW — `loam verify`
1953
+ (read and `--record`), `loam tasks`, and `status`, `list` and `gate` for an in-flight feature all
1954
+ derive it — and falls back to the newest file (by `recorded` date, a later service id breaking a
1955
+ same-day tie) only for an archived feature's frozen view or when no file answered the current
1956
+ version. The others stay in `attestations` and are named by `verify.checklist-forked` until they
1957
+ record again — now on the recording run's own output (`notices` under `--json`) as well as the
1958
+ read view, so the service whose answers a tally leaves out is named where the tally is printed. A
1959
+ same-day record from a service with a later id against an older checklist no longer drops the
1960
+ current answers and makes every reader call the record stale. A one-file record keeps its old
1961
+ reading. The `verify.checklist-forked` message no longer suggests accepting the split.
1962
+ - **`record-unreadable` names the file that failed and the repair that is safe for it.** Git conflict
1963
+ markers in any record file are detected before the YAML is parsed and named with their lines. A
1964
+ service's own `verification/<svc>.yaml` is that service's word alone, so its advice is to repair
1965
+ it or delete it and record that service again; the one-file `verification.yaml` may hold other
1966
+ services' attestations, so its advice is to repair it or restore it from version control (after a
1967
+ merge, the copy on the branch you merged into) — never to delete it. The old advice ("delete it
1968
+ and record again") erased other teams' attestations when followed on a shared record, and a
1969
+ `verify.record-miscounted` record's "record the answers again" looped straight back into the same
1970
+ refusal. The same per-file advice reaches `evidence.record-unreadable`, and the `status` and
1971
+ `gate` sentences for an unreadable record no longer say "delete it". A per-service file holding
1972
+ anything but its own service's attestation is unreadable too.
1973
+ - **`path` in a recording `verify --json` payload** is the file this run wrote — the service's own
1974
+ `verification/<svc>.yaml` under `--service`. The `path` inside `recorded` on the read and frozen views, and the
1975
+ `verification` artifact's `path` in `status`, name the `verification/` directory once it exists,
1976
+ the one file otherwise. `archive` and `unarchive` carry the directory with the feature they move.
1977
+ No code or flag was added, changed or retired, and no payload key was added or removed. What
1978
+ changes is the VALUE of four `path` keys, and each may now name a DIRECTORY: `path` on a recording
1979
+ `verify --json`, the `path` inside `recorded` on the read and frozen views, the `verification` artifact's
1980
+ `path` in `status --json`, and the `path` on a `next.verify` step in `status --json` (which follows
1981
+ the artifact's). A script that opens one of them as a file must now expect
1982
+ `features/<FEAT>/verification` — a directory — once any service has recorded under `--service`.
1983
+ - **A loam older than this release does not read `verification/<svc>.yaml`.** It sees only
1984
+ `verification.yaml`, so `status`, `gate` and `verify` under an older pin report a feature recorded
1985
+ this way as having no record (`gate --strict` then exits 1 on `gate.feature-unverified`), and an
1986
+ older all-at-once `--record` writes a `verification.yaml` that this release ignores beside the
1987
+ directory (its answers carry no attestation). Upgrade every repository that records or gates a
1988
+ feature together — the docs repo's pipelines and each service repo's pin.
1989
+ - **`features/<FEAT>/verification/` is loam's from this release.** Every non-dot `*.yaml` in it is
1990
+ read as one service's attestation file, and one that is not (a hand-made `k6.yaml`, say) is
1991
+ `record-unreadable`: `verify` refuses and `status` shows the record as unreadable. Keep anything
1992
+ else beside the feature, not inside that directory.
1993
+
1994
+ ### Changed — `verified` means a run of the stamped scenario, and the runner-neutral path is findable
1995
+
1996
+ - **A cucumber run must have run the stamped scenario's step count.** `verify --results` used to
1997
+ confirm a `scenario.tested` claim from any report element carrying its `@loam-digest-…` tag whose
1998
+ steps passed — a one-step stub tagged by hand confirmed five-step scenarios. A matched cucumber
1999
+ element must now have run exactly as many steps as `loam gherkin` emits for the scenario (`And` /
2000
+ `But` included); otherwise the claim is `unconfirmed`, noted `ran N step(s) where the stamped
2001
+ scenario has M`. The step text is not compared (outlines fill their placeholders in, dialects
2002
+ differ on keyword spacing). cucumber-js's hook entries inside `steps[]` (`hidden`, keyword
2003
+ `Before`/`After`) are read as hooks, not steps: they no longer count toward the steps, and a
2004
+ failing one is reported `failed in a before/after hook`. `loamScenarioReport` entries carry no
2005
+ steps and are held to their status, as before. What a repo notices: a report whose elements do
2006
+ not run every stamped step stops confirming.
2007
+ - **A `--results` file that is not JSON is refused naming both accepted shapes.** A JUnit XML report
2008
+ died with the parser's raw "Unexpected token '<'". It is still `answers-unreadable`, and the
2009
+ message now says the file is XML or not JSON and names what `--results` reads: cucumber JSON
2010
+ (cucumber-js, cucumber-jvm, behave, SpecFlow/Reqnroll, pytest-bdd `--cucumberjson`, godog) or
2011
+ loam's runner-neutral `{"loamScenarioReport": 1, …}`. A `--contract-results` file that is not JSON
2012
+ names its one shape the same way.
2013
+ - **`loamScenarioReport` is named wherever cucumber is.** The `--results` description in
2014
+ `loam verify --help` now reads "cucumber JSON, or loam's runner-neutral loamScenarioReport JSON for
2015
+ any other runner"; the `check` in the `done` block `loam tasks` prints for a `scenario.tested` unit names both
2016
+ report shapes; the `verify.scenario-attested` message's suggested command reads
2017
+ `--results <report.json>` and names both; WORKFLOW, the verify, implement and done-check protocols
2018
+ and the cycle `loam explain` teaches say the same. So does the `next.verify-attested` step in
2019
+ `status --json` — its `statement` names both shapes and its `command` now reads
2020
+ `loam verify <FEAT> --results <report.json>` (it was `--results <cucumber-report.json>`) — and
2021
+ `loam explain next.verify-attested`.
2022
+ - **The all-at-once `--record` checks each evidence entry shaped like a `file:line`.** Without
2023
+ `--service` it accepted any non-empty evidence — `src/foo.py:1` for a file that does not exist —
2024
+ and printed `verified`, while the federated form refused the same citation. An evidence entry of
2025
+ an agent-confirmed answer that is shaped like a `file:line` (one token ending in `:<digits>`, not
2026
+ a URL) must now be a canonical relative path to a regular file inside the repository loam.json was
2027
+ found in, with that line in it; otherwise `answers-unevidenced`, exit 1, where it used to be
2028
+ recorded. Every other entry — prose ("manual QA by Alice"), a URL, a path with no line — is
2029
+ recorded exactly as before. The form still binds to no commit and stamps no pins. Evidence that
2030
+ lives in another service's repository is recorded from there, with `--service`. A missing cited
2031
+ file is now named in the repository's own spelling ("does not exist in this repository") in both
2032
+ forms, never through the machine's absolute path.
2033
+ - No code, flag, path or payload key was added, changed or retired.
2034
+
2035
+ ### Added — `loam gate --since <ref>`: the deploy gate no longer forgets a feature at archive
2036
+
2037
+ - **`loam gate` used to grade in-flight features only**, so a feature left the verification check
2038
+ the moment it was archived — and `loam archive` reads no verification record, by design. On the
2039
+ example fleet `gate --service order-service --strict` went from exit 1 (FEAT-112 unverified) to
2040
+ exit 0 after `loam archive FEAT-112`; a contract-only feature escaped even a vouched service,
2041
+ because `content_digest` covers the spec bodies and not `openapi.yaml`/`asyncapi.yaml`. Without
2042
+ the new flag that is still exactly what the gate does, and `--help`, WORKFLOW.md and
2043
+ `loam explain gate.feature-unverified` now say so.
2044
+ - **New flag `--since <ref>`** on `loam gate`: a docs-repo git ref — the docs commit the pipeline's
2045
+ last deploy recorded. Every feature archived after it that carries a `specs/<service>/` delta is
2046
+ graded too, by its frozen verification record, under the existing warn-only
2047
+ `gate.feature-unverified` (or the reused `verify.scenario-attested`). "Archived after" means its
2048
+ `features/archive/<FEAT>/` directory is absent at the ref, or differs from what it held there —
2049
+ committed since or not — so a feature unarchived, edited and archived again is graded as the new
2050
+ ship it is, not skipped as one an earlier deploy answered for. When git will not say what changed
2051
+ under `features/archive/`, every archived feature touching the service is graded. Its message
2052
+ names the ref and points at `loam unarchive <FEAT>` rather than a `loam verify --record` an
2053
+ archived feature refuses. The ref is read from the docs repo's own git exactly as
2054
+ `loam diff --base` reads its base, and refused the same way: `repository-unavailable` when git
2055
+ cannot answer from the docs repo (or cannot list `features/archive/` at the ref), `unknown-target`
2056
+ when the ref names no commit. No new code; no deployment state is stored.
2057
+ - **An archived record is held to the checklist its feature shipped with.** A record gone stale
2058
+ before the archive (a scenario appended after it was written) read `stale`/`unverified` in flight
2059
+ and `verified` with no finding once archived, because an archived record is never judged against
2060
+ a checklist re-derived after the merge. `loam archive` now writes the digest of the checklist a
2061
+ fresh `loam verify` derives just before the merge — when the feature carries a readable record —
2062
+ into its snapshot manifest as a new key, **`checklist`**, in
2063
+ `features/archive/<FEAT>/.loam-before/manifest.json` (additive: the manifest stays version 3,
2064
+ `unarchive` ignores the key, and an older loam reading the manifest is unaffected). `gate --since`
2065
+ grades the frozen record against it: a record answering an older checklist is `state: "stale"`,
2066
+ `verdict: "unverified"`, with a `gate.feature-unverified` saying the feature moved after its
2067
+ record was written. A manifest without the key — an archive made by an older loam — is graded as
2068
+ before, as frozen history, and the row says so. `list`, `status` and `verify` keep reading an
2069
+ archived record as frozen history, never stale.
2070
+ - **New `--json` payload keys:** top-level `since` — `{ ref, commit }` under `--since`, `null`
2071
+ without it — and, only on a row `--since` added, `features[].archived` (`true`) and
2072
+ `features[].checklistAtArchive` (the captured digest, or `null` when the archive captured none).
2073
+ An in-flight row carries neither key, so a payload without the flag is unchanged apart from
2074
+ `since: null`. The text view titles the block "verification — in flight, and archived since
2075
+ <ref>", marks those rows `· archived`, and adds a line under a recorded row whose archive
2076
+ captured no checklist.
2077
+ - **The MCP `loam_gate` tool** gains the matching `since` string argument.
2078
+
2079
+ ## [0.2.0-alpha.8] - 2026-09-21
2080
+
2081
+ The work packet: the verify checklist read forwards. Still a prerelease on the `alpha` line.
2082
+ **Not published (noted 2026-10-10):** the `v0.2.0-alpha.8` tag was cut, but its release run failed at
2083
+ `npm ci` — npm on Windows had stripped other platforms' optional packages from the tagged commit's
2084
+ `package-lock.json` during the version bump (restored on `main` the same day) — so the publish never
2085
+ ran. `npm i @ybotok/loam@alpha` does not install this version, and `npm view @ybotok/loam dist-tags`
2086
+ is the record of what does. Everything in it is additive — no stable code was added,
2087
+ changed or retired, no existing flag, path or payload key moved, and nothing a repository has
2088
+ committed needs to change. What a repo NOTICES after upgrading: one new read-only command
2089
+ (`loam tasks`), new keys on two `--json` payloads it may already consume, and a corrected SCHEMA
2090
+ sentence about how scenario digests are salted — the code did not move, the sentence did.
2091
+
2092
+ ### Added — `loam tasks`, the derived answer to "what do I do next, and what closes it"
2093
+
2094
+ The question an authored task file answers is a good one; the file is the wrong place to answer it,
2095
+ because a second mutable state drifts from the spec it was copied out of. So this is that answer
2096
+ DERIVED — the `loam verify` checklist read forwards, one work unit per promise the feature makes,
2097
+ each carrying the step it is waiting on and the fact that retires it, recomputed on every run from
2098
+ the delta, the generated suite and the verification record. SCHEMA's "Deliberate limits of the
2099
+ workflow surface" row refusing an authored `tasks.md` as authoritative state **stands unchanged**;
2100
+ this is the projection that row has always pointed at, and it is a projection precisely so that
2101
+ nothing here can disagree with the documents it was derived from.
2102
+
2103
+ - **New read-only command `loam tasks <FEAT> [--service <id>] [--next] [--json]`.** It writes
2104
+ nothing, anywhere, ever, and creates no artifact. Every unit it reports is one claim of the same
2105
+ checklist `loam verify` derives, **under the same id** — a unit and the answer that closes it are
2106
+ one identity on both commands. The one unit with no claim behind it is minted for an ADDED or
2107
+ MODIFIED requirement carrying no scenario at all (kind requirement.described): nothing to test
2108
+ means nothing for the checklist to ask, and a promise with no acceptance criteria is still work.
2109
+ - **`units[]` is the projection**, one entry per unit, carrying `id`, `kind`, `subject`, `at`,
2110
+ `claim`, `dependsOn[]`, a `done` block, and — where the ledger has them — `digest`, `operation`,
2111
+ `token` and a `source` naming the document, the 1-based line and, for a scenario, its body
2112
+ verbatim.
2113
+ - **`at` is the step the unit is waiting at**, one of `describe`, `generate`, `implement` or
2114
+ `attested` — never a percentage. It is spelled `at` rather than `stage` or `verdict` on purpose:
2115
+ both of those already mean something else on payloads an agent reads beside this one.
2116
+ - **Done is a fact, not a tick.** A closed unit stops appearing, and exactly one thing closes one: a
2117
+ claim recorded `confirmed` in a `verification.yaml` that is **not stale** against the current
2118
+ checklist. A stale record closes nothing — the words moved under the answers. An agent-attested
2119
+ scenario never vanishes: it stays visible at `attested`, because attested is not verified and a
2120
+ work list that hid the difference would be the gate that teaches everyone to say yes.
2121
+ - **`frontier` is the ids of the first ten open units in derived order**, providers before consumers
2122
+ on the exact token join `c4.calls.token == api.exposes.operation` — never a reading of prose —
2123
+ with `frontierElided` saying how many open units it left off. **The order is advice and never a
2124
+ lock**: nothing is assigned, nothing is reserved, and working out of order is legal and invisible
2125
+ to every other command.
2126
+ - **`--next` is the leaf packet**: the same payload with `units` narrowed to the frontier head, and
2127
+ what an orchestrator hands one coding agent. Everything else is left whole on purpose, so a cheap
2128
+ model handed one unit cannot read it as the whole job. The text view prints that leaf in full —
2129
+ the scenario's Given/When/Then verbatim and never reflowed (those are the bytes the digest was
2130
+ taken over), its digest, the `done` block whose `check` is the fact that closes the unit, and the
2131
+ literal follow-up command. Literal rather than a shape to fill in, because "the appropriate verify
2132
+ invocation" is a guess the reader will get wrong.
2133
+ - **`--service <id>` is a pure lens** over the same derivation, and adds `waitingOn[]`: the open
2134
+ units in OTHER services that this service's units depend on.
2135
+ - **`stamps` is `read` or `unknown`.** `unknown` means the bound service repo's generated suite could
2136
+ not be read — could-not-look, and never nothing-wrong.
2137
+ - **The rest of the payload**: `feature`, `service` (the lens, or null), `services[]`, `path`,
2138
+ `intent`, `checklist` (the digest of the question set the units derive from), `record` (`absent`,
2139
+ `recorded`, `stale` or `unreadable`), `size{requirements,scenarios,operations,messages,calls}` —
2140
+ the size of the DELTA and never of the open work, so that "this feature is small" and "this
2141
+ feature is nearly done" cannot become one sentence — `contracts{expose[],call[]}`,
2142
+ `counts{open,done,attested}`, and `review[]`, the four instructions for re-deriving this answer
2143
+ and reviewing it.
2144
+ - **A broken `delta.likec4` or contract axis exits 1 with `ok: true`**, naming what it could not read
2145
+ in `unreadable` (`architecture`, `contracts[]`). That is `loam delta`'s own rule rather than a new
2146
+ one: a feeder that could not be read empties the packet, an agent consuming an empty packet reads
2147
+ "nothing to do", so the envelope stays informative and true while the exit code stops a pipeline
2148
+ building on it.
2149
+ - **New MCP read-only tool `loam_tasks`**, declaring `readOnlyHint` like every other read tool; the
2150
+ base table is fifteen tools where it was fourteen.
2151
+ - **No stable code was added, changed or retired, and no existing flag, path or payload key moved.**
2152
+ The change is purely additive: everything `loam tasks` reports is a projection of state other
2153
+ commands already derive and grade.
2154
+
2155
+ ### Added — `loam delta --json` carries the joins and the lines it had been dropping
2156
+
2157
+ - **New additive keys per requirement**: `line` (where it sits in the document it was parsed from, so
2158
+ a consumer can send an author to the line rather than to the file), `basedOn`, `requires`,
2159
+ `capabilities`, `realizes`, `publishes` and `consumes`. Every one of them is already written on the
2160
+ requirement and every one names work — which permission gates it, which capability it belongs to,
2161
+ which named promise it realizes, which messages it produces and consumes — and an implementer
2162
+ handed this projection had to go and read the Markdown it claimed to have projected to find them.
2163
+ - **New additive keys per scenario**: `line`, and `digest`, the 16-hex scenario body hash. That
2164
+ digest is the exact join key the rest of the toolchain already uses — the `@loam-digest-<16hex>`
2165
+ tag `loam gherkin` stamps and the string `loam verify --results` matches a cucumber report on — so
2166
+ a consumer of this brief can now name the test that answers an acceptance criterion instead of
2167
+ regenerating the suite to find out. It is salted by the owning service and the axis, like every
2168
+ other spelling of it, so an arch requirement's scenarios digest differently from identically
2169
+ worded business ones.
2170
+ - **Nothing existing moved.** Every key this payload already carried is untouched, the text output is
2171
+ unchanged, and no stable code was added or changed.
2172
+
2173
+ ### Added — `loam gherkin --json` says which stamp belongs to which scenario
2174
+
2175
+ - **New additive key `files[].stamped`**, a list of `{scenario, digest}` pairs. `digests` was a bare
2176
+ array and `scenarios` a bare count, so the one thing a consumer needed — which stamp belongs to
2177
+ which scenario — was the one thing this payload could not say, and the only way to learn it was to
2178
+ read the generated file back off disk. A `kept` row reads its pairs off the file that is really on
2179
+ disk, because its numbers describe the in-flight emission that stayed rather than the one that was
2180
+ withheld.
2181
+ - **`digests` and `scenarios` are unchanged beside it**, and no stable code was added or changed: the
2182
+ envelope permits adding a key and never changing one.
2183
+
2184
+ ## [0.2.0-alpha.7] - 2026-09-07
2185
+
2186
+ Infrastructure belongs on the fleet map, and the render is navigable at fleet scale. Still a
2187
+ prerelease on the `alpha` line: `npm i @ybotok/loam@alpha` installs it. One code is REMOVED —
2188
+ `landscape.datastore-private`, the first code this project has ever retired — and no flag, path or
2189
+ payload key is. What a repo NOTICES on its first `validate --all` after upgrading: a
2190
+ `subsystem.views-stale` error against the new `architecture/services.likec4`, in every fleet whose
2191
+ map draws a service, cleared by one `loam subsystem sync`; a second one against
2192
+ `architecture/subsystems.likec4` wherever subsystems exist, because those views gained boundary
2193
+ edges and an `of` subject; and silence where a datastore drawn beside the services used to be told
2194
+ to move.
2195
+
2196
+ **Upgrading: one `loam subsystem sync` per docs repo, once.** `loam validate --all` reports
2197
+ `subsystem.views-stale` (error) against the new `architecture/services.likec4` in EVERY existing
2198
+ docs repo whose fleet map draws a service — flat fleets with no subsystems included, because the
2199
+ file is new everywhere and an absent generated file is a state of its own. This is not a
2200
+ subsystems-only upgrade: a fleet that has never run `loam subsystem new` gets the finding too. A
2201
+ repo that DOES have subsystems gets a second finding on the same run, against
2202
+ `architecture/subsystems.likec4`, because the subsystem views gained boundary edges and an `of`
2203
+ subject and every existing copy therefore re-renders differently. One `loam subsystem sync` writes
2204
+ both files and clears both findings. `loam archive` names the same repair rather than claiming
2205
+ "complete + current". Nothing else in a repository has to change, and no document a team wrote is
2206
+ touched — and a document a team wrote that is already standing at one of the two generated paths is
2207
+ refused by name rather than overwritten (below).
2208
+
2209
+ ### Removed — `landscape.datastore-private`, because where a store is drawn is the team's choice
2210
+
2211
+ - **`landscape.datastore-private` (warn) is retired.** It fired on a `database` element drawn at
2212
+ fleet level with exactly ONE consumer, and its whole message prescribed deleting the peer and
2213
+ writing the store inside its owner. The identical store nested was silent, so the check graded the
2214
+ DRAWING and not the architecture — a restriction rather than a fact about the fleet. A store one
2215
+ service owns may now be nested under that service's element or drawn beside the services, and loam
2216
+ grades neither. The code is not in the frozen `ErrorCode` or `IssueCode` unions, so removing it
2217
+ breaks no contract; `loam explain landscape.datastore-private` no longer answers, and the row is
2218
+ gone from the agent-facing check table.
2219
+ - **`landscape.datastore-shared` (warn) is unchanged**, both arms. Two or more services on one store
2220
+ is a fact about coupling, not a restriction, and it fires at two consumers for both placements
2221
+ alike. What survives from the retired code's message is the one clause that was a fact about
2222
+ LikeC4 rather than a preference, and SCHEMA's "Private stores" now leads with it: a second
2223
+ consumer's edge resolves only against a declaration the MAP holds, because a per-service project
2224
+ is `architecture/` plus one model.
2225
+ - **Infrastructure is not a service.** A new `INFRA_KINDS` set in `core/vocabulary/maturity.ts` —
2226
+ `database`, `queue`, `topic`, `cache`, `bucket`, compared case-insensitively — takes such an
2227
+ element out of four surfaces at once: the drawn-service census behind
2228
+ `landscape.service-undocumented`, the `loam gate` partner set (an infra endpoint is dropped from
2229
+ the `partners` payload, exactly as an actor already was), the `loam verify` checklist (a
2230
+ fleet-level store no longer mints a `service.exists` claim no repository can answer), and the
2231
+ scorecard's element denominator, whose `c4` row now counts services rather than services plus
2232
+ their stores. The hook is the kind NAME, with the same limitation `ACTOR_KINDS` lives with: a
2233
+ fleet spelling its kind `postgres` is not covered, and a tag can be added later as a second
2234
+ spelling. A store BOUND to a directory with `metadata { service }`, or titled like one, stays an
2235
+ adopted service on every one of the four surfaces — the census predicate wins over the kind.
2236
+ `landscape.service-undocumented`'s message gained a THIRD remedy beside "bind it" and "tag it
2237
+ `#external`", spelled from the same set: neither of the old two was true of a store the fleet
2238
+ owns, and that message is the only place an author learns which kind spellings loam reads.
2239
+ - `c4.uncovered` was left alone, and the measurement is the reason: a delta drawing
2240
+ `splitLedger = database 'split-ledger'` earns it, and adding `Covers: splitLedger` to the
2241
+ feature's `arch.spec.md` delta silences it exactly, with no `covers.unknown`. The remedy the
2242
+ message names is true, so this is not a residual restriction — and the code fires only on elements
2243
+ a feature DELTA adds, never from `validate --all` over a living tree.
2244
+ - The acceptance criterion, run for real: a `database` drawn at fleet level, owned by the fleet, with
2245
+ one consumer, no `#external` tag and no binding, now earns ZERO findings from `loam validate --all`
2246
+ and from `loam gate`.
2247
+
2248
+ ### Added — `architecture/services.likec4`: one generated overview view per service
2249
+
2250
+ - **New generated path `architecture/services.likec4`**, written by `loam subsystem sync` beside
2251
+ `architecture/subsystems.likec4`, removed by it when the fleet map draws no service, and owned
2252
+ outright by loam: views only, byte-reproducible, one view per service the map resolves, sorted by
2253
+ service directory id. Each view is declared `of` its service's element, which is what makes the
2254
+ fleet map, every subsystem view and every other service view click-through into it with no
2255
+ authored link — measured at likec4 1.59.2. Peers are NAMED one by one rather than wildcarded,
2256
+ because on a map that groups its services the wildcard form collapses every peer to its group box
2257
+ and yields no drill-down at all (measured on a 120-service fleet: 0 of 10 nodes navigable). Median
2258
+ at 58 services in 11 groups: 10 nodes, 8 edges per view, about 7 ms of layout each.
2259
+ - **Titles are folders.** A per-service view is titled `<subsystem path> / <service directory id>`,
2260
+ built from the same path helper the subsystem views use, so both generated files fold into one
2261
+ browser folder tree mirroring `services/`. LikeC4 has no view-folder syntax — `views { group … }`
2262
+ is a parse error at the pin — and splitting the title on `/` is the whole mechanism.
2263
+ - **An authored view of the same element keeps precedence.** Two views `of` one element are legal;
2264
+ the first document alphabetically wins the default navigation target and both stay reachable,
2265
+ because the subject's own node cycles to the other. `architecture/landscape.likec4` sorts before
2266
+ `architecture/services.likec4`. loam does not detect the authored view and never will:
2267
+ docs/DESIGN.md rule 26 forbids reading what a view SHOWS.
2268
+ - **Subsystem views gained two things** in the same generator: `include <member> -> *, * -> <member>`
2269
+ per member, so a subsystem view answers "what does this group touch" and not only "who is in it",
2270
+ and `view subsystem_<name> of <ancestor>` when the map holds an element owning exactly that
2271
+ subsystem's members. Measured at 58/11: ~14 nodes, ~38 edges, 12 of 14 nodes navigable. Every
2272
+ existing `architecture/subsystems.likec4` therefore re-renders differently and is stale once.
2273
+ - **`subsystem.views-stale` now grades two files**, one finding per file, each naming its own path in
2274
+ `subject`, `locations[0]` and the message. No new code was minted: both files are written by one
2275
+ command in one commit and repaired by one run, which is what makes one code honest.
2276
+ - **`subsystem.view-id-collision` reserves `service_` as well as `subsystem_`.** A `service_` id is
2277
+ reserved only for a service the fleet map actually DRAWS, so an unmodelled `services/<id>/`
2278
+ reserves nothing; the message names which generated file and which prefix.
2279
+ - `deployment.doc-reserved` refuses a feature deployment document named `services.likec4` for the
2280
+ same reason it already refused `subsystems.likec4`: both names are generated, overwritten
2281
+ wholesale, and parsed by nothing.
2282
+ - **No writer overwrites a generated views file it did not write, and refusing is now an exit 1.**
2283
+ Both generated paths are filenames loam RESERVES inside `architecture/`, a directory the team
2284
+ owns, and every writer of them rewrites the file whole — so a repository that already kept a
2285
+ hand-authored diagram at one of those paths would have lost it, silently, on the first run after
2286
+ the release that reserved the name. Reproduced before the guard existed: with a hand-authored
2287
+ `architecture/services.likec4` in the tree, `loam subsystem new platform` printed
2288
+ `architecture/services.likec4 updated` and destroyed it, first line and all.
2289
+ - **What makes a file loam's is its FIRST LINE**, `// generated by loam`, with a leading byte-order
2290
+ mark stripped before the test — a Windows editor adds one without changing a fact, and without the
2291
+ strip loam would call its own output authored and refuse to regenerate it for good. A hand EDIT of
2292
+ a generated file keeps that line, so it is still loam's to repair and one `loam subsystem sync`
2293
+ puts it back: that has always been what `subsystem.views-stale` says. Only a file that never
2294
+ carried the mark is somebody else's. **A 0-byte file at either path counts as somebody else's** —
2295
+ it carries no mark — so an empty `architecture/subsystems.likec4` or
2296
+ `architecture/services.likec4` is no longer filled in; delete it and re-run.
2297
+ - **`loam subsystem sync` declines the one file and finishes the rest.** `--json` reports the new
2298
+ `action` value **`refused`** — on `action` for `architecture/subsystems.likec4`, on
2299
+ `serviceViews.action` for `architecture/services.likec4`, one predicate behind both — while the
2300
+ other generated file, the per-service project files and the root `exclude` are still done and
2301
+ still reported, and the **exit code is 1**. That is `loam validate`'s shape rather than a refusal
2302
+ envelope, and the non-zero exit is the point: `loam validate --all` goes on reporting
2303
+ `subsystem.views-stale` for that path with `loam subsystem sync` named as the repair, so a run in
2304
+ a hook or a CI step must not report success over a file it can never fix.
2305
+ - **`loam subsystem new` / `rm` / `move` / `rename` and `loam seed` refuse WHOLE**, before a byte is
2306
+ staged: `error.code` `already-exists`, exit 1, nothing written and — for `move` and `rename` —
2307
+ nothing renamed. No new code was minted, because that is what `already-exists` has always meant on
2308
+ these verbs; `loam seed` can answer it for the first time. They refuse whole rather than per-file
2309
+ because their entire contract is that the generated files land in the SAME commit as the change,
2310
+ so completing the structural half would manufacture the very `subsystem.views-stale` state that
2311
+ commit window exists never to leave behind — irreversibly, for a `move` that also stages renames.
2312
+ - **The refusal sentence is one sentence, verbatim from all five writers.** It names the mark loam
2313
+ looked for, says that not one byte changed, and gives the way out: rename the file to a name loam
2314
+ does not reserve — every other `.likec4` under `architecture/` renders in the same project, so the
2315
+ diagram is not lost — or delete it, then re-run.
2316
+ - **Payload keys, all additive.** `loam subsystem sync --json` keeps `path` and `action` meaning the
2317
+ subsystem file and adds `serviceViews: { path, action }`; `loam subsystem new` / `move` / `rm` /
2318
+ `rename --json` keep `views` and add `serviceViews`; `loam archive --json` keeps
2319
+ `subsystemViewsStale` and adds `staleViews: string[]`, the generated files it left behind. Nothing
2320
+ existing changed meaning.
2321
+ - The generated-file SET is now one table (`GENERATED_ARCHITECTURE_FILES`), read by the path
2322
+ builders, the name reservation, both project loads that must EXCLUDE these files (the fleet map's
2323
+ and `c4.fleet-project-invalid`'s), archive's staging and `loam seed`'s. Four readers that must
2324
+ never disagree about which files are generated, and a name in one but not another failed silently
2325
+ both ways while there was only one.
2326
+
2327
+ ### Added — the intra-service use-case slot, published rather than graded
2328
+
2329
+ - **`loam adopt` briefs `services/<…>/<id>/usecases/<name>.likec4` as an OPTIONAL target**, with the
2330
+ tag grammar and a worked example: a `dynamic view` tagged `#req-<Requirement-ID>` as the first
2331
+ thing in the view body, the tag declared once (in the landscape's `specification` for an extending
2332
+ model, in the model's own for a standalone one), every hop backed by a relationship the model
2333
+ declares, and the view id namespaced by the service because an extending model's views share the
2334
+ root project's flat id namespace. Optional-and-absent prints lowercase `missing` and is not a
2335
+ finding — the existing mechanism for an optional artifact, not a new one.
2336
+ - **The generated AGENTS.md names the slot and its title convention**,
2337
+ `'<subsystem path> / <service id> / <use case>'`, so an authored use-case view lands in the same
2338
+ browser folder as its service's generated overview.
2339
+ - **No new finding code, and no check on absence.** docs/DESIGN.md rule 26 — presence is never owed,
2340
+ and no check may grade a view's absence — is enforced by `scripts/arch-check.mjs`, so there is no
2341
+ service.usecases-missing and there never will be. The grader (`c4.valid`'s
2342
+ ` · K tagged flow(s) graded`) has existed since 2026-09-03; all that was missing was anyone being
2343
+ told. A usecase.flow-untagged check, which would grade a view that EXISTS, is recorded on
2344
+ [ROADMAP.md](ROADMAP.md) with its promotion bar rather than built.
2345
+
2346
+ ## [0.2.0-alpha.6] - 2026-09-04
2347
+
2348
+ A service model may extend the fleet map, and the nine problem reports one fleet filed against the
2349
+ previous release are closed. Still a prerelease on the `alpha` line: `npm i @ybotok/loam@alpha`
2350
+ installs it. Nothing below removes a code, a flag or a payload key; two things a script may have
2351
+ read do move: every LikeC4 line number loam prints is 1-based now, and seven feature-delta codes
2352
+ plus the feature arm of `usecase.flow-invalid` answer `--feature <FEAT-id>` in
2353
+ `loam explain --json` where they answered `--service <id>`. What a repo NOTICES on its first
2354
+ `validate --all` after upgrading: a model written in the extending shape earns the renderer-wiring
2355
+ warnings until one `loam subsystem sync` — which now also deletes a stray per-service
2356
+ `likec4.config.json` beside such a model — a root `exclude` that hides the map is
2357
+ `landscape.excluded`, and a standalone model whose copies of the map's elements disagree with it
2358
+ is `c4.declaration-diverged`; a repository whose models all stand alone is asked to migrate
2359
+ nothing, and its root `exclude` is not touched.
2360
+
2361
+ ### Fixed — one rule for a service's own edge, and the document that actually failed, on every surface
2362
+
2363
+ - `loam context` and `loam explore` name the `architecture/` document that ACTUALLY failed in their
2364
+ printed warning, off the `landscape.broken` key their `--json` payloads already carried. `parses`
2365
+ answers for the PROJECT, so with a broken sibling the landscape file has no errors at all — and
2366
+ both printers still said `architecture/landscape.likec4 does not parse`, a claim about bytes loam
2367
+ had read fine. `explore`'s absent arm keeps the file name: `present` is about the FILE, which is
2368
+ the write target every brief asks for edits to.
2369
+ - An edge whose two endpoints resolve to the SAME service is that service's own internal wiring,
2370
+ and no surface files it as an inbound call any more. `loam adopt --json` already dropped it; the
2371
+ one shared derivation does now, so `loam context`, `loam explore`, `loam show` and
2372
+ `loam vouch --pack` stopped reporting a service as its own caller (`← <id> "reads"`), and
2373
+ `loam list`'s fleet-wide spelling of the same rule stopped reading an `op`-carrying self-edge as
2374
+ proof that somebody calls the service — a service nobody calls was told it owed an OpenAPI
2375
+ contract, grading `partial` in `list` while `context` said `documented`. `landscape.touched` and
2376
+ `landscape.service-isolated` are unchanged: they read the relationships, so the map still draws
2377
+ an edge on the service.
2378
+ - The adopt brief's `service.model-excluded` row describes both entry shapes: one covering the
2379
+ DIRECTORY, which `loam subsystem sync` rewrites, and one shaped like a FILE
2380
+ (`services/**/model.likec4`), which sync cannot repair and has to be removed or narrowed by hand.
2381
+ The finding's own message already said which one you have; the brief promised sync for both.
2382
+ - `loam diff` classifies a base ref's tree with the same reserved-directory rule the live walk uses
2383
+ (`adrs/`, `usecases/`, `ui/` — shared as one set, not respelled). A service directory holding only
2384
+ the `services/<id>/usecases/` slot used to be absent from the base AND to contribute a phantom
2385
+ service called `usecases`, so committing it and diffing the unchanged tree reported one addition
2386
+ and one removal.
2387
+
2388
+ ### Added — one model, many documents: a service model may extend the fleet map
2389
+
2390
+ - `services/<…>/<svc>/model.likec4` now comes in two shapes, and the FILE's own grammar decides
2391
+ which — never a config key, never a flag. A model declaring no `element` kind inside a top-level
2392
+ `specification { }` block is an **extending model**: loam parses it inside the `architecture/`
2393
+ project and it says what is inside the service with `extend <fully-qualified id> { … }` on the
2394
+ element the map already binds to the directory, re-declaring no kind, no tag and no partner. A
2395
+ model that declares one is a **standalone model**, parsed alone exactly as every model was
2396
+ before — legal forever, and not what `loam adopt` briefs any more. SCHEMA.md's new
2397
+ "Two shapes of a service model" carries both, the grades below, and the ordered migration recipe.
2398
+ - New `c4.declaration-diverged` (warn, service target, subject the service, one finding per element):
2399
+ a STANDALONE model and the fleet map (`architecture/`) both declare one element, and the
2400
+ two copies disagree about `kind`, `title`, the tag SET or the `metadata { service }` binding. The
2401
+ message prints both declarations, names which fields differ, and offers the two repairs — copy the
2402
+ map's declaration verbatim, or migrate to the extending shape. `description` is deliberately not
2403
+ compared: it is prose nothing grades, while the other four each change what a view shows or what
2404
+ loam resolves. Silent while the map does not parse. This is the check that had no code: a fleet's
2405
+ copies of `kafka` and its identity provider had already lost `#platform` on every copy but the
2406
+ map's, and nothing in loam read two documents together.
2407
+ - New `c4.element-unowned` (warn, service target, subject the service, one per element): an
2408
+ EXTENDING model declares an element outside the element that resolves to its own service. A system
2409
+ the service reaches belongs in `architecture/landscape.likec4`, declared once; another service's
2410
+ internals belong in that service's model.
2411
+ - New `c4.fleet-project-invalid` (warn, `landscape` target, subject the service whose directory
2412
+ holds the file): `--all` only, only when at least one extending model exists, the map itself
2413
+ parses, and no root `exclude` entry hides the map (`landscape.excluded` below is then the whole
2414
+ answer). loam loads every `.likec4` the root LikeC4 project reads except the generated
2415
+ `architecture/subsystems.likec4` — the map, its siblings, every extending model and every
2416
+ `.likec4` beside one — and reports each error that is NOT already present in the map alone, in a
2417
+ per-service project, or in a service-flow project the same run made. That difference is the class
2418
+ no per-service grade can see: two services each declaring `tag req-AUTH` locally, two models
2419
+ adding the same child id under one element, a broken untagged sibling nobody opted in. All three
2420
+ are renderer-fatal and all three were silent.
2421
+ - Three new renderer-wiring warnings on the `landscape` target, root-gated like
2422
+ `service.likec4-config-missing` and repaired by the same `loam subsystem sync`:
2423
+ `service.likec4-config-stray` (a `likec4.config.json` beside an EXTENDING model — the renderer
2424
+ registers a project holding nothing, or holding the model alone where it cannot parse),
2425
+ `service.model-excluded` (the root `likec4.config.json`'s `exclude` covers an extending model's
2426
+ directory, naming the entry that does it, so the renderer never loads the model) and
2427
+ `service.model-unexcluded` (it does NOT cover a STANDALONE model's directory, so the renderer
2428
+ merges that model into the map and reports every kind and element it declares as a duplicate —
2429
+ blanking the whole root project, not one service).
2430
+ - New `landscape.excluded` (warn, `landscape` target, `loam validate --all`) — the root
2431
+ `likec4.config.json`'s `exclude` covers `architecture/landscape.likec4` itself. loam keeps
2432
+ grading the map whatever that list says, so every finding in the run is right; the renderer does
2433
+ not, and until now nothing said so. A repo notices it as one line at the top of `validate --all`
2434
+ naming the entry verbatim — typically an `architecture/*.likec4` written for a palette — INSTEAD OF
2435
+ the `c4.fleet-project-invalid` cascade the same entry used to produce, and it is the only line in
2436
+ the run that names the cause. `c4.fleet-project-invalid` is not graded while the entry stands: that
2437
+ check loads the fleet project the way the renderer does, with no floor under the map, so the one
2438
+ line came back as an unresolved `extend` in every extending model — 161 warnings on the packaged
2439
+ five-service example. Its message says the fleet project is unread rather than clean, and grading
2440
+ resumes the moment the entry goes. Never gating: with the map kept by the loader, every fact loam grades holds
2441
+ identically either way. The repair is the team's own edit — `loam subsystem sync` maintains only
2442
+ the `services/` entries and will not touch it, and the message says so rather than sending its
2443
+ reader to a command that reports success and changes nothing.
2444
+ - `loam subsystem sync` now maintains the root `likec4.config.json`'s `exclude` as well as the
2445
+ per-service files. It rewrites the list to hold `services/<tree>/**` for exactly the models that
2446
+ stand alone: the wholesale `services/**` and every `services/<tree>/**` naming a directory that IS
2447
+ a service are removed, every other entry is kept in the order the team wrote it (a
2448
+ `services/legacy/**` for a directory that is no service survives), and the standalone entries are
2449
+ appended sorted. It runs when at least one extending model exists or when a standalone model's
2450
+ directory is not covered, so a repository whose models all stand alone behind `services/**` is
2451
+ never touched. Same staged transaction as the rest of `sync`; `--json` gains the additive
2452
+ `projects.exclude` (whether it rewrote, the resulting entries, and what was added and removed) and
2453
+ the text view prints one line when it did. A root config that is not JSON is left alone with a
2454
+ printed note.
2455
+ - `loam init --docs <path> --create` now scaffolds `likec4.config.json` with
2456
+ `exclude: ["**/node_modules/**", "features/**"]`. `services/` is deliberately not excluded — the
2457
+ models a fresh repository writes extend the map, and the root project is the only place they
2458
+ parse. A feature's `delta.likec4` keeps its own `specification` block and stays excluded.
2459
+ - The landscape the scaffold writes teaches the extending shape. Its `views` paragraph told a fresh
2460
+ repository that a service model "renders from its OWN directory" and that "each declares its own
2461
+ `specification` block, so the renderer can only be given one of them at a time" — written before
2462
+ an extending model belonged to the root project, and printed in the same `loam init --docs .
2463
+ --create` run that now excludes `features/**` precisely because it does. And the check that says
2464
+ a map is still untouched scaffolding now accepts EVERY generation loam has shipped, not just the
2465
+ current text: it is a byte-suffix match, so rewording the comment reclassified every older
2466
+ untouched scaffold as hand-authored — `loam seed` refused one its own scaffold had written
2467
+ (`seed-landscape-edited`, nothing written) and `loam status` dropped the teaching rung from the
2468
+ empty-fleet ladder. The two `v0.1.0-beta` stubs (no `views` block, no `tag platform`) and the
2469
+ `v0.2.0-alpha.1`-through-`.5` stub are all recognised, frozen beside the writer.
2470
+ - `loam adopt --service <id>` briefs the extending shape: all four `shape` rules of the
2471
+ `model.likec4` target are rewritten, and the `example` is a `model { extend <id> { … } }` document
2472
+ with the fleet element's real id substituted in where the brief resolves exactly one. The brief's
2473
+ `checks[]` gains `c4.declaration-diverged`, `c4.element-unowned`, `service.likec4-config-stray`,
2474
+ `service.model-excluded`, `service.model-unexcluded` and `c4.fleet-project-invalid`, and its
2475
+ renderer statement in `unchecked[]` says the root project now holds the map and every extending
2476
+ model. `/loam-adopt` steps 3, 4 and 5 and `/loam-check`'s three fix tables carry the same.
2477
+ - The packaged example fleet (`examples/docs`, what `loam init --example` copies out) is migrated:
2478
+ all five models extend the map, the five `services/**/likec4.config.json` are gone, the root
2479
+ `exclude` is the scaffold's two entries, `architecture/landscape.likec4` gains `element database`
2480
+ and the `notification-provider` system that notification-service's model used to declare for
2481
+ itself, and every `Covers:` entry and `views` block names the map's fully-qualified ids. Its
2482
+ `loam validate --all` result is unchanged: 0 errors and the same ten deliberate warnings.
2483
+ - `loam archive` now writes a service's OWN `model.likec4`, not just the fleet map. A delta's
2484
+ additions nested under a service whose model EXTENDS the map — a container, a store, a
2485
+ container-level edge — are spliced into that model's `extend <fqn> { … }` block instead of into
2486
+ `architecture/landscape.likec4`, and a model with no `extend` block for that fqn gains one. A
2487
+ relationship goes to the model of the service owning its SOURCE, else its TARGET, else stays on
2488
+ the map: a service-level edge is where every fleet check reads it. A service-level element, a
2489
+ STANDALONE model and a service with no model.likec4 keep the previous behaviour byte for byte.
2490
+ What a repo notices: `loam archive` prints a second
2491
+ `architecture: merged into services/<…>/model.likec4 — +N element(s), +M relationship(s)` line
2492
+ beside the landscape one, the model path appears in the `--json` plan (and in `--dry-run`) as an
2493
+ ordinary write, `loam unarchive` restores it from the same `.loam-before` snapshot as any other
2494
+ overwritten file, and the service is no longer `c4.invalid` (duplicate declaration) the moment its
2495
+ model declares the container the adopt brief told it to declare. No new payload key and no new
2496
+ error code. Re-archiving adds nothing a second time — existence is checked against the models too
2497
+ — and two archives touching one model's `extend` block land adjacently.
2498
+ - The archive's existence check now reads the extending models by TITLE as well as by id, so a
2499
+ container a service already draws under a different identifier is recognised instead of being
2500
+ spliced in beside itself: a delta adding `cache = container 'Order cache'` under a service whose
2501
+ model already declares `orderCache = container 'Order cache'` adds nothing and leaves the model
2502
+ byte for byte, which is what the same delta against the same title on the fleet map has always
2503
+ done. Nothing is routed to that model, so the plan names only the landscape — the second
2504
+ `merged into services/<…>/model.likec4` line appears when a model actually receives something.
2505
+ The models' title join is scoped to one service — it fires only when both the model's
2506
+ declaration and the addition resolve to the SAME explicitly bound service — because every
2507
+ extending model in a fleet names a container `api`, and the map's global title index is a
2508
+ pre-binding fallback, not a rule to spread.
2509
+ - An extending model with no `model { }` block — an empty file, a `views { }`-only file, a tags-only
2510
+ `specification { }`, all three shapes loam calls extending on purpose — gains one when a feature's
2511
+ addition is routed into it, instead of refusing the whole archive with `merge-failed`. It is the
2512
+ same call the merge already makes one level down, where a model that has never named a container's
2513
+ parent gains the `extend <fqn> { }` block rather than refusing.
2514
+ - `loam archive` proves the model it would write. The merged model is loaded as the project the
2515
+ per-service grade reads it in — the `architecture/` documents with this archive's own merged
2516
+ landscape in place, plus the model — and an archive whose model would not parse is refused at plan
2517
+ time (`merge-failed`) with nothing written, naming the model path and the first three errors. Two
2518
+ causes actually occur, and the refusal names both: a kind or tag the map's `specification` block
2519
+ does not declare, and a reference to ANOTHER service's interior, which lives in that service's own
2520
+ model and is in no project this one can see. A container-level edge whose two ends belong to two
2521
+ different services is the case that changes: it used to merge, because both containers landed on
2522
+ the map beside it, and it is now refused — there is no legal placement for it, and the answer is
2523
+ to draw that call service to service.
2524
+ - What a repo notices: nothing, until a model is written in the new shape or `loam subsystem sync`
2525
+ is run. A repository whose models all stand alone keeps every finding it had, and its root
2526
+ `exclude` is not touched. Write one extending model — or upgrade a repo whose scaffold predates
2527
+ `services/**` being excluded — and the next `loam validate --all` reports the renderer-wiring
2528
+ warnings above until one `sync`; that one sync rewrites the root `exclude`, writes the project
2529
+ file a standalone model is owed and DELETES the stray one beside every extending model, so after
2530
+ it the renderer's project picker holds one entry instead
2531
+ of one per adopted service, and a fleet-wide duplicate that used to blank the whole picture is
2532
+ reported as `c4.fleet-project-invalid` before anybody opens the renderer.
2533
+
2534
+ ### Changed — a model's grades follow its shape, and `--service` reads the whole map project
2535
+
2536
+ - `service.likec4-config-missing` is narrowed to STANDALONE models. A model that extends the map
2537
+ belongs to the root project, so it is owed no per-service `likec4.config.json` and never earns
2538
+ this warning; the same directories earn `service.likec4-config-stray` instead while a file left
2539
+ over from before the migration is still sitting there, and `loam subsystem sync` — which now also
2540
+ stops writing the file for extending models — is the repair for both. The message is reworded to
2541
+ say which shape it is about.
2542
+ - `loam validate --service <id>` now reads the whole `architecture/` project, exactly as `--all`
2543
+ does, instead of `architecture/landscape.likec4` alone. A use-case file under `architecture/` that
2544
+ does not parse therefore reports `spine.landscape-invalid` on a single-service run too, where it
2545
+ used to be visible only under `--all`. `c4.declaration-diverged` compares against that same
2546
+ project.
2547
+ - `c4.invalid` and `c4.valid` keep their codes and change scope with the shape. For an extending
2548
+ model every error the per-service project raises is the MODEL's — including one the parser blames
2549
+ on `architecture/landscape.likec4`, because the map parses clean on its own — and `c4.valid`'s
2550
+ message reads `extends the fleet map — N elements · M relationships`. When the map itself does not
2551
+ parse, an extending model cannot be read at all: neither code is emitted for it, and
2552
+ `spine.landscape-invalid` carries one clause more saying the model extends the map and so cannot
2553
+ be read either.
2554
+ - `landscape.service-unmodelled` is raised on the `--service` target too, and stays an error. An
2555
+ EXTENDING `model.likec4` says what is inside an element the fleet map binds, so a service whose
2556
+ directory no map element resolves to has nothing to be inside and no count in the model means
2557
+ anything: `loam validate --service <id>` reports it and exits 1 where the same tree used to come
2558
+ back `c4.invalid` for a model read alone. `/loam-check`'s `--service` table carries a row of its
2559
+ own for it and `loam explain landscape.service-unmodelled` answers for both scopes. A standalone
2560
+ model is unaffected — the fleet run still owns that state.
2561
+ - `subsystem.view-id-collision`'s census covers every extending model's authored view ids under
2562
+ `--all`, not the landscape's alone: those documents are in the same root project now, and a view
2563
+ id claimed twice there makes the renderer refuse the whole project.
2564
+ - `spine.landscape-invalid` names the document that actually broke. The finding is raised against
2565
+ the whole `architecture/` PROJECT and the message hardcoded `architecture/landscape.likec4`, so an
2566
+ `architecture/usecases/x.likec4` with a typo sent every service's reader to a file that parses and
2567
+ the real path appeared nowhere in the payload. The message now lists the broken documents
2568
+ repo-relative and comma-separated — the spelling `landscape.invalid` already used under `--all` —
2569
+ each `details` line carries its own file once two or more are broken, and `locations` carries the
2570
+ first broken file as `primary` beside the service directory as `scope`. `loam explain`'s "fix the
2571
+ file the message names" is true now.
2572
+ - `c4.declaration-diverged` finds the copies it was written for. It joined on the literal element
2573
+ id, so in a GROUPED map — one nesting its services under a parent, as loam's own example does — a
2574
+ standalone model's `paymentService` never met the map's `marketplace.paymentService` and the check
2575
+ was silent on the shape it exists for. The join is now the id; then the `metadata { service }`
2576
+ binding when both documents wrote one; then the map element whose id ends with the model's as a
2577
+ dotted tail. A tail two map elements answer to joins nothing — a divergence reported against the
2578
+ wrong peer is worse than the silence. The message names the other side as `the fleet map
2579
+ (architecture/)` rather than a file it never read, and names the map's own id when it differs.
2580
+ - `c4.declaration-diverged` no longer joins a standalone model's `db`, `api` or `agent` to a
2581
+ container the map draws INSIDE another service. The dotted-tail rung stands, and a tail that
2582
+ carries a dot (`paymentService.api` ↔ `marketplace.paymentService.api`) is joined as before; the
2583
+ BARE last segment is now a key only for a FLEET-LEVEL map element — one no ancestor of which
2584
+ stands for a service, which is a top-level element and equally a box the map draws inside a plain
2585
+ grouping element, by the same fleet-census predicate every other fleet check asks. Grouping is a
2586
+ drawing convenience, not ownership: a `cache` the map draws inside an `infra` group is the FLEET's
2587
+ cache, so a standalone model that declares its own top-level `cache` gives one name two meanings
2588
+ and is reported. Only a SERVICE owns an interior. What a repo notices: a private cache called
2589
+ `db` beside another service's `db` was warned about and told to copy that service's title
2590
+ verbatim; it is now silent. loam's own `meta/docs` carried the workaround for it, and no longer
2591
+ does — its `coding agent` element is spelled `agent` again.
2592
+ - `c4.declaration-diverged` stops asking for a copy that is already there — in both directions.
2593
+ Since LikeC4 1.59.0 a kind can carry tags and every element of that kind arrives with them, so a
2594
+ tag difference can be one no element declaration writes and no copy can fix. loam now decides that
2595
+ by SIMULATING the copy rather than by matching one shape of it: when the tag sets are all that
2596
+ differ and copying the map's literal tags across would leave them still differing, the message
2597
+ says so, names `specification { element <kind> { … } }` as where they come apart, and says WHICH
2598
+ document declares tags on that kind, so an author knows which file to open. Both shapes reach it
2599
+ now: the model writing `#external` on an element whose map twin inherits `#external #platform`
2600
+ from `element topic { … }` used to be told to copy the map's bare line, and doing exactly that
2601
+ produced the other arm of the same message (measured on `examples/docs`' order-service). Grammar:
2602
+ "the kind differs" / "the title differs" / "the tags differ".
2603
+ - Three messages say what they could not say before. `c4.invalid` on a model written in BOTH shapes
2604
+ at once — an element kind declared in `specification { }` beside an `extend` — gains one `details`
2605
+ line saying the kind is what makes the file standalone, so the `extend` resolves nothing, and
2606
+ which of the two lines to drop. `usecase.flow-invalid` whose project refuses an undeclared
2607
+ `#req-`/`#cap-` tag gains one line naming the `tag <name>` declaration that is missing and the
2608
+ `specification { }` block it belongs in. And `c4.valid` gains ` · K tagged flow(s) graded` when
2609
+ the service's own project declared any, so a HEALTHY intra-service flow is visible on one surface
2610
+ instead of none. Both hints are the FIRST `details` line rather than the last: the cap is ten
2611
+ lines in text and in `--json`, and a model written in both shapes at once produces dozens of
2612
+ cascading errors, so the one line that diagnosed it was truncated away in exactly the case it
2613
+ exists for.
2614
+ - A `.likec4` beside `model.likec4` that the service's own `likec4.config.json` `exclude` covers is
2615
+ no longer read, graded or reported. loam staged every sibling regardless, so a file the renderer
2616
+ never loads earned `usecase.flow-invalid` under a message claiming the project is read the way the
2617
+ renderer reads it. Standalone models only — that file IS the project config there; a model that
2618
+ extends the map is read in the root project, and a per-service config beside it is
2619
+ `service.likec4-config-stray`. The RECURSIVE spelling counts too: a leading `**/` compiled to a
2620
+ required `/`, so `**/usecases/**`, `**/views.likec4` and `**/*.likec4` matched nothing while the
2621
+ renderer (measured, likec4 1.59.2) hides exactly what they name — `usecase.flow-invalid` gated the
2622
+ run at exit 1 over a file in no project loam was reading.
2623
+ - `spine.landscape-invalid` says "a, b have N error(s)" when it names more than one document; it
2624
+ read "a, b has N error(s)".
2625
+ - `c4.element-unowned` leads with the remedy that fits a private store. The message listed the two
2626
+ remedies that move an element OUT of the service — the map, or another service's model — and
2627
+ omitted the one an author usually wants: a store or component this service owns goes INSIDE the
2628
+ `extend <fqn> { }` block, where its id becomes `<fqn>.<name>`. Following what the tool printed
2629
+ moved a private store to fleet level and earned `landscape.service-undocumented`.
2630
+
2631
+ ### Added — problem reports carry an ordinal and a status, and `loam doctor` counts them
2632
+
2633
+ - The `loam-report` protocol (`loam instructions loam-report`) names its file
2634
+ `loam-reports/NNN-YYYY-MM-DD-<short-slug>.md`: `NNN` is an ordinal of at least three digits,
2635
+ zero-padded, one
2636
+ more than the highest already in the directory, `001` over an empty or absent one, widening past
2637
+ three digits rather than reusing a number, and the
2638
+ protocol tells the agent to read it from `loam doctor --json` rather than count the directory
2639
+ itself. A path that already exists is a concurrent report claiming that ordinal — take the next
2640
+ one; the collision suffix is gone. That check is on the PATH while the promise is about the
2641
+ ordinal, so the protocol also tells the agent to re-run `doctor` after writing: two entries
2642
+ sharing an ordinal means the later `Recorded` renames to the next free one.
2643
+ The directory is `loam-reports/` beside the `loam.json` that resolved
2644
+ (else beside the directory the agent runs in) — `reports.dir` carries it WITH that segment on the
2645
+ end, so it is written into rather than joined onto — the ordinal is unique within it, and a
2646
+ report is cited by
2647
+ ordinal — in a commit message, an issue or another report's cross-references — never by filename.
2648
+ The document template gains `- Status: open` directly after `- Classification:`, with the
2649
+ vocabulary `open` / `sent` (handed to the author — the person forwarding a report sets it, never
2650
+ the agent writing it) / `fixed in <version>` / `superseded by <NNN>`; a report is closed by
2651
+ editing its own `Status` line, and there is no index to keep in step with it. Every safety
2652
+ sentence is unchanged: `<redacted>`, "Never re-run" a writer, "do not upload", and the section
2653
+ headings.
2654
+ - `loam doctor --json` gains one additive key, `reports`: `reports.dir` (the absolute path of the
2655
+ `loam-reports` directory beside the `loam.json` that resolved — and, when a `loam.json` was FOUND
2656
+ but could not be parsed, beside that file, which is the same root `agents` is inspected against;
2657
+ only where no config file exists at or above the run directory does it fall back to the run
2658
+ directory. The last segment is ON it, so the protocol writes inside the path rather than
2659
+ joining the name a second time), `reports.present`, `reports.total`, `reports.next` (the ordinal
2660
+ the next report takes, padded
2661
+ to at least three digits and widening past them, `001` when there is none), and `entries[]` —
2662
+ every `*.md` in the directory
2663
+ sorted by the ordinal its name claims, numerically, with unnumbered legacy names last (byte order
2664
+ stopped being report order at exactly the width the ordinal widens to: `1000-…` sorts before
2665
+ `998-…`), each carrying `file`, `ordinal` (the leading three-or-more-digit ordinal,
2666
+ `null` for a name written before the numbering, including the `YYYY-MM-DD-…` shape) and
2667
+ `status` (`open` | `sent` | `fixed` | `superseded` | `unstated`, read as the first word of the
2668
+ `- Status:` line in the report's HEADER FIELD BLOCK — the lines above the first `##` heading,
2669
+ quoted code skipped in BOTH markdown spellings, fenced and indented, so a report quoting the
2670
+ protocol's own template counts as whatever its own header says — and `fixed in 0.2.0-alpha.5`
2671
+ counts as `fixed`). The
2672
+ human view prints one row after `agents`:
2673
+ `reports loam-reports/ 12 · open 3 · sent 5 · fixed 3 · superseded 1 · next 013` — zero
2674
+ counts omitted, the total always shown, `reports (none)` when the directory is absent or
2675
+ empty.
2676
+ - Reading it is best-effort and emits no finding: an unreadable file is an entry with status
2677
+ `unstated`, an unreadable directory is `present: true` with `total: 0`, and a status word this
2678
+ vocabulary does not know is `unstated` rather than a sixth bucket. No new code is emitted and
2679
+ `doctor`'s `healthy` does not move for anything in the directory — an open report is not a
2680
+ defect in the repository holding it. Reading stays local: nothing is transmitted, which is the
2681
+ constraint the protocol is built around.
2682
+ - AGENTS.md's support paragraph now says reports are numbered and that `loam doctor` counts the
2683
+ directory and says which ordinal is next.
2684
+ - What a repo notices: `loam doctor` prints a `reports` row it never printed before — `(none)`
2685
+ on a repository that has never written one — and `loam doctor --json` carries a `reports` object
2686
+ beside `agents`. Existing reports keep their names and are counted as `unstated` with no
2687
+ ordinal until someone renames them and adds a `Status:` line; the next report an agent writes
2688
+ lands as `loam-reports/001-…` in a directory of legacy names, because `next` is one past the
2689
+ highest ordinal present and a `YYYY-MM-DD-…` name has none.
2690
+
2691
+ ### Added — the adopt brief on an edgeless landscape element
2692
+
2693
+ - `loam adopt --service <id> --json` gains two additive keys under `landscape`: `landscape.touched`
2694
+ — `true` when at least one edge in the `architecture/` project has an endpoint resolving to
2695
+ the service (an intra-service edge counts: the predicate `loam context` prints as "(modelled, no
2696
+ edges touch it)"), `null` whenever `landscape.modelled` is `null` or `false` — and
2697
+ `landscape.attested`, the calls the service's own `model.likec4` declares across its boundary
2698
+ (`direction`, `counterpartId`, `counterpart`, and `title` / `op` / `publishes` / `consumes` where
2699
+ the edge carries them), sorted and de-duplicated, `[]` when the model is absent, does not parse,
2700
+ or declares none, and read only in the edgeless state. `landscape.modelled` and
2701
+ `landscape.elements` are now decided by the same element→service resolver `validate --all` grades
2702
+ `landscape.service-unmodelled` on: a container titled like a sibling service, nested inside an
2703
+ element bound to another service, resolves to its ancestor — so `loam adopt` reports such a
2704
+ service `modelled: false` where it reported `true`, which is the answer the fleet run was already
2705
+ giving.
2706
+ `landscape.instruction` is no longer `null` on element existence alone: while an element resolves
2707
+ to the service and no edge touches it, the brief names each attested call and asks for it as ONE
2708
+ edge on the existing element, collapsed to the service and naming the other party as the map
2709
+ already spells it — or, with nothing attested, points at the walk's outbound and message stops and
2710
+ says draw NOTHING rather than invent an edge — and names the element's tags loam does not read (a
2711
+ placeholder such as `#provisional`, and the view written `exclude element.tag = #<that>` that
2712
+ keeps an adopted service hidden). The `architecture/landscape.likec4` target is back in that state
2713
+ with `action: "edit"`, `required: true`, a shape refusing a second element and an edge-only
2714
+ example. The text view prints the instruction under "what the fleet already says about this
2715
+ service" and flags the row `EDGELESS` ("an element exists, no edge does") instead of `UNDRAWN`.
2716
+ Until now `loam seed --from fleet.yaml` left every service absent from `calls:` as a bound,
2717
+ edgeless element the brief then declared finished.
2718
+ - New `landscape.service-isolated` (warn, never gating, `--all` only, on the landscape target,
2719
+ `subject` the service id): an element resolves to `services/<…>/<svc>/`, no edge anywhere in the
2720
+ `architecture/` project touches it, and that service's `model.likec4` parses and declares at least
2721
+ one call across its
2722
+ boundary — the message names the first three. Evidence-gated on purpose: silent for a service with
2723
+ no model (a seeded fleet), an unparseable model (`c4.invalid` owns it), a model reaching nothing,
2724
+ an unmodelled service (`landscape.service-unmodelled` owns it), and a service the map already
2725
+ touches with ONE edge while its model attests five — the check is touched/untouched, not a set
2726
+ difference; `#external` elements are never
2727
+ subjects. It suppresses `landscape.matched` like every finding. The brief's `checks[]`, the
2728
+ `/loam-check` `--all` table and AGENTS.md's adopt step carry it; `/loam-adopt` step 5 states
2729
+ the silent cases and points at `landscape.touched` in step 1.
2730
+ - One more statement in the adoption brief's unchecked list, now seventeen: whether an adopted
2731
+ service is VISIBLE on the fleet map — a placeholder tag that survives adoption, a curated view
2732
+ that lists its members by id, and `include *` collapsing to a boundary element
2733
+ (`include <boundary>.**`). `/loam-adopt` step 3 names that housekeeping as part of the landscape
2734
+ edit.
2735
+ - `loam adopt --service <id>` reads the fleet map as the `architecture/` PROJECT — the landscape
2736
+ plus every other `.likec4` under `architecture/`, minus the generated `subsystems.likec4` — where
2737
+ it read `architecture/landscape.likec4` alone. It is the document set `landscape.service-isolated`
2738
+ already grades `touched` over, so `landscape.modelled`, `landscape.elements`, `landscape.inbound`,
2739
+ `landscape.outbound`, `landscape.expects`, `landscape.touched`, `landscape.attested` and
2740
+ `landscape.instruction` now answer about the same map `loam validate --all` does. What a repo
2741
+ notices: a fleet that keeps a second `model { }` block, a use case or a palette in a sibling
2742
+ `architecture/*.likec4` no longer gets a brief demanding edges it has already drawn —
2743
+ `touched: true`, `instruction: null`, and no eighth `architecture/landscape.likec4` target — where
2744
+ the fleet run on the same tree reported `landscape.matched`. The same widening makes
2745
+ `landscape.parses` false, and `landscape.modelled` `null`, when any document under `architecture/`
2746
+ fails to parse: the landscape file parsing on its own does not rescue a map the renderer cannot
2747
+ load. No key added, no key removed. The edgeless instruction names the element's home as "the
2748
+ fleet map (architecture/)" rather than a file it did not read — the WRITE still lands in
2749
+ `architecture/landscape.likec4`, and the target's `path` says so.
2750
+ - The adopt brief's `model.likec4` example is headed with the service being adopted
2751
+ (`// order-service's own C4 …`) instead of the literal `payment-service` it carried for every
2752
+ service — one line above an `extend <fqn>` the brief already substituted, so the personalised half
2753
+ taught an agent the rest of the header had been personalised too. `<svc>` joins `<fqn>` as a
2754
+ placeholder the shape rules leave literal and `serviceBrief` fills in; the example never leaves
2755
+ either in the payload.
2756
+ - What a repo notices: a drawn-but-edgeless service whose model reaches other systems gains one
2757
+ `landscape.service-isolated` warning per service on its next `loam validate --all` — under
2758
+ `--strict`, CI is red until an edge lands or the element is genuinely edgeless and stays so (a
2759
+ model attesting no cross-boundary call is silent); a seeded service nobody listed under `calls:`
2760
+ is briefed the edges instead of nothing.
2761
+
2762
+ ### Added — a home for an intra-service use case
2763
+
2764
+ - A `dynamic view` over one service's own containers now has a slot loam reads and grades: any
2765
+ `.likec4` beside `services/<…>/<svc>/model.likec4` (`usecases/<name>.likec4` by convention,
2766
+ `views.likec4` read the same) or a tagged view inside model.likec4 itself — exactly the document
2767
+ set the per-service renderer project registers, read whether or not `likec4.config.json` exists.
2768
+ `loam validate --service <id>`, and `--all` per service, grade every view tagged
2769
+ `#req-<Requirement-ID>` with the codes the fleet target already uses: `usecase.step-unbacked`
2770
+ (error), `usecase.step-contested` (warn), `usecase.requirement-unresolved` (error — resolved
2771
+ against THIS service's spec.md and arch.spec.md ids, four arms), `usecase.capability-unresolved`
2772
+ (error — a `#cap-` tag beside a model is refused by placement, vocabulary or not) and
2773
+ `usecase.flow-invalid` (error — a sibling that breaks the service project; model.likec4 keeps
2774
+ grading alone, never `c4.invalid`). Untagged views are never graded, and an untagged sibling is
2775
+ not even loaded while no view in the project opts in and no sibling mentions either reserved
2776
+ prefix anywhere in its bytes; once loaded — a tagged view inside model.likec4 itself, or `req-` /
2777
+ `cap-` in a sibling's comment or title — a project that does not read is one
2778
+ `usecase.flow-invalid` whether or not any view in it opted in. A resolved service-local `#req-`
2779
+ feeds nothing else yet. The feature slot for such a flow is deferred (ROADMAP).
2780
+ - Every surface that names a code for a `#req-` tag declared TWICE — once in the map's
2781
+ `specification` and once in a tags-only block beside the `extend` — now names the one loam
2782
+ actually emits. SCHEMA.md, `/loam-adopt` step 3 and `loam instructions loam-codes` all said
2783
+ `c4.fleet-project-invalid` (a warning); measured, the finding lands where the second declaration's
2784
+ FILE is: `c4.invalid` — an error that gates, suspends the model's grading and cascades six
2785
+ `covers.unknown` — when the tags-only block is inside model.likec4, and `usecase.flow-invalid`
2786
+ when it is in a sibling. `c4.fleet-project-invalid` is the fleet-scope class one step out: two
2787
+ SERVICES each declaring the same tag locally, which no per-service project can see. No behaviour
2788
+ changed; an agent routing on the printed code was being sent to the wrong severity and the wrong
2789
+ repair.
2790
+ - Two corrections to the shared step graders, at fleet altitude too: a hop whose two endpoints
2791
+ resolve to ONE service is no longer backed by that service's internal edges on the service tier
2792
+ (measured: every internal relationship was a candidate, so an unbacked hop between two containers
2793
+ of one service graded attributed and could never earn `usecase.step-unbacked`); and
2794
+ `usecase.step-unlinked` no longer fires when the caller resolves to the provider — a service owes
2795
+ no operationId to itself, and that is the guard's whole scope: a hop from a service-local flow
2796
+ into another service's element still warns, exactly as it does on the fleet map. What a repo
2797
+ notices: a landscape that draws a service as containers with a tagged hop between two of them
2798
+ loses a wrong `usecase.step-unlinked` warning and may gain a right `usecase.step-unbacked` error;
2799
+ a service directory holding a tagged `dynamic view` beside its model starts being graded.
2800
+ - `loam diff` no longer counts a service as a consumer of its own operation, on either half of the
2801
+ question. A landscape edge whose SOURCE and TARGET both resolve to the provider is the provider's
2802
+ own internal call — the rule `usecase.step-unlinked` already states — and so is a use-case hop
2803
+ whose two endpoints resolve to it; the edge half excluded nothing while the message half already
2804
+ did, so the downgrade this list promised could not be observed in the configuration it described.
2805
+ Both are excluded now, and it is: a removal whose only trace in the fleet is the provider calling
2806
+ itself reports `diff.op-removed` (warn) and the run exits 0, where it reported
2807
+ `diff.op-removed-consumed` (error, `breaking: true`, exit 1). A second service's edge naming the
2808
+ operation is still `diff.op-removed-consumed`.
2809
+ - The adopt brief's `checks[]` gains six `--service` rows: the five above and
2810
+ `usecase.step-unlinked`, which the service target reports for a hop into a sibling's stand-in, and
2811
+ which `/loam-check`'s `--service` table now carries a row of its own for. The arch.spec.md shape
2812
+ names the hop-sequence slot; AGENTS.md's layout paragraph names the slot itself
2813
+ (`services/<…>/<svc>/usecases/*.likec4`) and the styling opt-in beside it.
2814
+ - `loam explain` answers the right scope for the eight codes only a feature run raises. A markdown
2815
+ fix table ends at the first line that is not a `|` row, and nothing separated `/loam-check`'s
2816
+ `--feature <FEAT-id>` rows from the `--service <id>` ones sitting above them, so
2817
+ `capability.uncovered`, `capability.remove-requirement-realized`, `glossary.term-exists`,
2818
+ `usecase.flow-exists`, `deployment.doc-exists`, `deployment.doc-invalid`,
2819
+ `deployment.doc-reserved` and the feature/archive arm of `usecase.flow-invalid` all came back
2820
+ `scope: "--service <id>"` — a service run can raise none of them. They now read
2821
+ `--feature <FEAT-id>`. What a consumer notices: `entries[].scope` changes value for those eight
2822
+ in `loam explain <code> --json` and in `loam explain --codes --json`; the meanings and the fixes
2823
+ are unchanged, and no code was added or removed.
2824
+
2825
+ ### Changed — the generated subsystem views join the repository's styling
2826
+
2827
+ Every view in the generated `architecture/subsystems.likec4` now carries `global style subsystems`
2828
+ — written after its `title` and `description`, before its `include`/`group` — exactly when the
2829
+ `architecture/` project (the landscape merged with every other `.likec4` under `architecture/` the
2830
+ root `exclude` does not cover — a `palette.likec4` beside the map counts, and one under an excluded
2831
+ path is loaded by nobody; the one
2832
+ load `loam subsystem sync`, `validate --all` and `archive` share) declares a global style with the
2833
+ id `subsystems`, in either form: `global { styleGroup subsystems { … } }` or
2834
+ `global { style subsystems … }`. loam reads only that the id is declared — a census of the keys of
2835
+ `$data.globals.styles` beside the view-id census in `core/c4/parsed/view-ids.ts` — and never what
2836
+ the group says (docs/DESIGN.md rule 26). Nothing is written otherwise, and the file is
2837
+ byte-identical to before. When the project declares style groups under other names only, one
2838
+ comment line between the header and `views {` names them and says none is `subsystems`; there is
2839
+ no single-group fallback, because a second group declared for a hand-drawn view would otherwise
2840
+ switch the generated views' styling off on a change that touched no subsystem. Styling therefore
2841
+ restales the generated file on two triggers, and `subsystem.views-stale`, `loam explain` and the
2842
+ fix tables name both: declaring or removing the group named `subsystems` changes whether the views
2843
+ carry the `global style subsystems` line, and declaring or removing a group under ANY OTHER name
2844
+ changes the disclosure comment. What a repo notices:
2845
+ a fleet that already declares a group named `subsystems` (or any group, for the disclosure line)
2846
+ sees one `subsystem.views-stale` on its next `validate --all`, cleared by one `loam subsystem sync`.
2847
+ `loam seed` renders the file from that same project — the landscape it is about to write, staged
2848
+ beside every other `architecture/` document — so a palette kept in a sibling such as
2849
+ `architecture/usecases/palette.likec4` is referenced by the seeded views and never a stale file
2850
+ straight after a seed. No new code, flag or `--json` key. `LoadedDoc` gains the additive field
2851
+ `globalStyles` (the sorted declared ids), produced alike by every loader and pinned by the three
2852
+ parity suites; seven tripwire assertions in `test/likec4-view-shape.test.ts` pin the parsed shape
2853
+ at likec4@1.59.2.
2854
+
2855
+ ### Changed — the fleet's consumer census reads the models, not the map alone
2856
+
2857
+ - `landscape.datastore-private`, `landscape.datastore-shared` and `landscape.platform-candidate`
2858
+ count a consumer that a service's own EXTENDING `model.likec4` attests — an outbound call at the
2859
+ element or a descendant — beside the edges `architecture/` draws. Under the extending shape the
2860
+ container-level call lives in the model and only the service-level twin reaches the map, so a
2861
+ store whose consuming edges were all drawn in models had zero consumers and earned nothing at all,
2862
+ and a store reached by three models plus one map edge was reported `landscape.datastore-private`
2863
+ naming the single service the map happened to draw. Extending models only: their ids are the map's
2864
+ fully-qualified ids, so the join is exact, while a standalone model's ids are its own file's and
2865
+ are never matched to a landscape element. A model that does not parse, or cannot be read,
2866
+ contributes nothing.
2867
+ - `landscape.datastore-shared` now fires for a NESTED store. A store sitting inside the element that
2868
+ resolves to its owner — declared there on the map, or added by the owner's extending model — is
2869
+ private by construction and silent at one consumer, and is graded at two: `subject` is the store's
2870
+ title as before, and the message adds ` — <owner>'s own store, written as '<fqn>'` beside the
2871
+ consuming services it names — the store's OWN id, since `<fqn>` IS the store and the earlier
2872
+ "nested under" sent a reader looking for a containing element of that name. The message then
2873
+ states the reversibility directionally: the two placements give the store the same id, but with a
2874
+ second consumer the move is one-way — out of the `extend` block onto the map is always safe, and
2875
+ back into it breaks every other consumer's edge.
2876
+ Until now the
2877
+ strongest coupling two services can have rendered perfectly and was graded nowhere, because the
2878
+ census only ever looked at elements drawn at fleet level.
2879
+ - `landscape.datastore-private`'s remedy names the placement instead of a directory, and names it
2880
+ for the consumer's own model SHAPE rather than assuming one. For an EXTENDING consumer it says to
2881
+ write the store in that model's `extend <fqn> { … }` block, or inside `<fqn>` on the map when the
2882
+ map draws that service's containers. For a consumer whose model STANDS ALONE — or that loam could
2883
+ not read as extending — it says the map placement and offers the migration to the extending shape
2884
+ as the other arm, because `extend <fqn>` in a standalone model resolves nothing (such a model is
2885
+ parsed alone) and following the old sentence there turned a run with 0 errors into `c4.invalid`.
2886
+ Either way it states what happens next: a second service
2887
+ reaching the same data draws a container-level edge to `<fqn>.<name>` in its own model plus a
2888
+ service-level edge on the map, and loam counts that consumer — so the state is
2889
+ `landscape.datastore-shared`, never silence. It also states which of the two placements keeps that
2890
+ door open: the edge resolves only against a store the MAP declares, because a per-service project
2891
+ is `architecture/` plus one model. The old "or add the second consumer's edge" is gone as an equal
2892
+ remedy and survives as "if another service reaches the same data today, draw its edge and this
2893
+ becomes that warning now". It also states the measured reversibility with its condition — the two
2894
+ placements give the store the same id, `<fqn>.<store>`, and with one consumer either move is free:
2895
+ out of the `extend` block onto the map always is, and back into it while no other service's model
2896
+ or map edge names the store (measured on `examples/docs`: moving `db` out of order-service's `extend`
2897
+ block into `orderService` on the map left the model at 12 elements · 8 relationships, left
2898
+ `Covers: marketplace.orderService.db` in its `arch.spec.md` resolving, and left checkout-web's
2899
+ container-level edge to it resolving) — and it qualifies the "never silence" promise: a consumer
2900
+ whose model stands alone is not counted, because its ids are its own file's, so its edge belongs
2901
+ on the map.
2902
+ - `landscape.datastore-private` and `landscape.datastore-shared` never take an element that stands
2903
+ for a `services/<id>/` as their subject, at ANY depth. The fleet-level half has always skipped
2904
+ one (a datastore bound to a directory is the binding checks' subject); the NESTED half did not,
2905
+ so a `database` carrying `metadata { service '<id>' }` and drawn inside another service's
2906
+ element became a store "owned" by its own binding, and one more reader made it
2907
+ `landscape.datastore-shared` — advising the team to give each service a private copy of a
2908
+ service. Both halves and the model-declared store census now share one `standsForService`
2909
+ predicate, handed down on the input record because a `fleet/map/` module may not import `fleet/`.
2910
+ - `landscape.invalid`'s message generalises its hint. It spoke about use-case flows alone, so an
2911
+ author whose MODEL EDGE named a container was told to move a flow that does not exist. It now says
2912
+ that an unresolved name an extending model declares — a use-case hop or an edge naming
2913
+ `<service-fqn>.<container>` — renders for the renderer and resolves for nobody here, and that the
2914
+ repair is to name the SERVICE on the map and the container in that service's own model.
2915
+ - `landscape.service-isolated` never takes an `#external` element as its subject. The claim rested
2916
+ on "an `#external` element is not an enumerated service", which is false for an element carrying
2917
+ the tag AND a `metadata { service '<id>' }` binding to a directory that exists: the input set had
2918
+ no tag filter, so a foreign box answered for one of our services. The isolation input SET now
2919
+ skips `#external`, matching `landscape.binding-duplicate` and
2920
+ `landscape.binding-unknown`. `landscape.service-unmodelled` keeps the unfiltered set — the
2921
+ directory IS drawn. The `touched` walk applies NO tag filter, deliberately: it answers a different
2922
+ question — does the map draw any edge on this service — and LikeC4 puts a kind tag on every
2923
+ element of the kind, so `element topic { #external }` (the idiom
2924
+ `examples/docs/architecture/landscape.likec4` documents) made an edge drawn from a service's OWN
2925
+ topic stop touching that service. The warning fired on a service the map demonstrably reaches
2926
+ while `loam adopt` reported `touched: true` on the same tree; the walk is the brief's
2927
+ `landscape.touched` predicate exactly, tag and all.
2928
+
2929
+ ### Changed — the renderer wiring, measured rather than assumed
2930
+
2931
+ - `loam subsystem sync` now DELETES a `likec4.config.json` that sits beside a model which EXTENDS
2932
+ the fleet map, in the same journaled commit as everything else it writes, and `--json` `projects`
2933
+ gains `projects.removed` (POSIX paths relative to the docs root, sorted by path (not by service
2934
+ id — the survey's own order); `[]` on a run that removed none)
2935
+ beside `projects.created`. The text view prints one `removed <path>` line per file and one
2936
+ sentence saying why. The note this corrects said such a nested project was registered EMPTY and
2937
+ therefore harmless; measured on likec4 1.59.2 it CLAIMS the model — the workspace loads two
2938
+ projects, the model is Invalid inside its own one (the map's kinds are not in there with it) and
2939
+ `export json --project fleet` loses the service's containers wherever that nested project claims
2940
+ the model (measured both ways at the 1.59.2 pin: it did on a seeded fleet, and did not on
2941
+ `examples/docs`, where the root project kept the model — what happens in every state is that the
2942
+ docs root stops being a one-project workspace and `likec4 validate .` refuses without
2943
+ `--project`). A repo that migrated 56 models to
2944
+ `extend` and ran one `sync` kept 56 stray files and 57 projects; it now keeps one. A leftover
2945
+ beside a model that is GONE still stays — that one really is the team's, and a nested project
2946
+ holding no `.likec4` is silently absent from the renderer's list. `service.likec4-config-stray`'s
2947
+ message is rewritten to the measured consequence and now names a repair `sync` actually performs.
2948
+ `projects.current` narrows with it: it counts the services owed a project file that already had
2949
+ one, and a model that extends the map is owed none — so on the run that deletes the strays it
2950
+ drops by exactly the length of `projects.removed`, and every run after that reports the same
2951
+ number both spellings would have.
2952
+ - `loam subsystem sync` no longer rewrites the generated views file when the map cannot be read.
2953
+ `action` gains the value `blocked`: with `architecture/` unreadable and
2954
+ `architecture/subsystems.likec4` already present, the run writes nothing about that file, prints
2955
+ the reason, and `--json` carries that action plus `blocked.reason` reading `landscape-invalid` —
2956
+ exit 0, because `loam validate --all`'s `landscape.invalid` is the gating surface. Before this,
2957
+ any load error rendered the tolerant shape: the file was rewritten down to title and description,
2958
+ losing the `global style subsystems` line, every `include` and the boundary comment, while `sync`
2959
+ reported `updated … 1 subsystem view(s)` and answered `current` ever after. The render also now
2960
+ falls back to the landscape FILE when only a SIBLING document breaks the project, so a duplicate
2961
+ `subsystems` group declared in two `architecture/` documents costs nothing. With no generated file
2962
+ yet, and inside a tree-changing verb's commit window (`new`/`move`/`rm`/`rename`, whose contract is
2963
+ that the file lands with the change), the tolerant shape is still written and the same one-line
2964
+ note is printed.
2965
+ - The `architecture/` document set every reader shares now honours the root `likec4.config.json`'s
2966
+ `exclude`. A `.likec4` under `architecture/` the renderer never loads is no longer loaded by loam
2967
+ either: a palette under an excluded path used to yield a `global style subsystems` reference the
2968
+ fleet project could not resolve — `likec4 validate` Invalid on every run while `loam validate
2969
+ --all` reported 0 errors, `doctor` was healthy and `sync` said "current" — and a broken excluded
2970
+ document blanked the whole map. A root config loam cannot read an `exclude` list out of filters
2971
+ nothing.
2972
+ - An `exclude` entry is now read the way the renderer reads it, measured rather than reasoned about.
2973
+ loam interpreted only a literal `/**` suffix, so a root config written `services/**/*`,
2974
+ `services/*`, `services/` or the bare `services/<tree>` hid every extending model with zero
2975
+ findings; the first repair of that read the entry as a walk over path SEGMENTS, which is not what
2976
+ LikeC4 does either. Measured at the 1.59.2 pin on a scratch fleet of five documents, by putting
2977
+ one entry in the root config and counting the "N files" `npx likec4 validate .` reports: a bare
2978
+ entry is a raw PREFIX with no end anchor (`services/pay` -> 3 files, hiding BOTH
2979
+ `services/payment-service` and `services/payments/payment-service`; `architect` -> 3, hiding all of
2980
+ `architecture/`), it must start on a `/` boundary (`ervices` and `ces/payment-service` -> 5, that
2981
+ is, nothing), `*` stays inside one segment while `**` crosses (`architecture/*.likec4` -> 4, so
2982
+ the nested use case survives; `*.likec4` -> 0), and a glob form does NOT prefix-match
2983
+ (`services/pay/**` -> 5). LikeC4 prefixes an entry that does not begin with a doubled star with
2984
+ one, joins it onto the project folder and matches with picomatch `contains: true` — the missing
2985
+ end anchor is that option. So `service.model-excluded` fires now, naming the entry verbatim, on
2986
+ every spelling including a starred middle segment; `service.model-unexcluded` no longer
2987
+ false-positives on a standalone model whose directory such an entry already covers; and
2988
+ `subsystem sync` takes back an entry that hides an extending model whatever its spelling — the
2989
+ write side asked "which directory does this entry name", which answers nothing for a starred
2990
+ segment, so `services/*/svc-b` hid a model forever while `service.model-excluded` named that same
2991
+ command as the repair — and appends nothing for a directory the surviving list already covers.
2992
+ - The root `exclude` filter on the `architecture/` document set now has a floor:
2993
+ `architecture/landscape.likec4` stays in the set whatever the list says. Without one, an entry a
2994
+ team writes for a palette (`architecture/*.likec4` covers both) left the architecture project
2995
+ holding ZERO documents — and an empty project parses with zero errors, so every reader took it for
2996
+ "the map declares nothing". On a seeded two-service fleet that produced four errors, all false:
2997
+ `landscape.service-unmodelled` twice, naming the very file that models both services and telling
2998
+ the reader to add an element already in it, plus `c4.invalid` on the extending model; and
2999
+ `loam subsystem sync` answered `updated` and rewrote a good generated file down to a title, with
3000
+ no line in either run mentioning the `exclude` entry that caused it. loam now grades one document
3001
+ the renderer may skip rather than asserting a fleet's whole architecture says nothing.
3002
+ - `loam subsystem sync` answers `blocked` whenever the `architecture/` PROJECT does not parse, not
3003
+ only when `architecture/landscape.likec4` itself is unreadable. The landscape-file fallback that
3004
+ saves the element join says nothing about the STYLE CENSUS — a palette may live in any `.likec4`
3005
+ under `architecture/` — so a run made while an unrelated sibling was broken reported `updated`,
3006
+ deleted the `global style subsystems` line from a good generated file, answered `current` ever
3007
+ after, and never earned `subsystem.views-stale` while the map stayed invalid. The file is now left
3008
+ byte-identical in that state and the note names what was unavailable.
3009
+ - `loam subsystem sync --json` carries `projects.exclude.unreadable` (boolean, emitted on every
3010
+ run). `entries: []` alone collapsed the two answers loam insists must stay apart: "the root
3011
+ project excludes nothing" and "loam could not read an `exclude` list out of the root file". The
3012
+ text view has always had a note for the second; a machine consumer reading `entries: []` got the
3013
+ opposite of the truth about what the renderer will load. Additive, beside `projects.exclude.removed`.
3014
+ - `loam subsystem sync`'s "— nothing to write." tail is dropped when the run rewrote the root
3015
+ `likec4.config.json`'s `exclude`. It counted only the per-service project files created and
3016
+ removed, so a run with a current views file printed "nothing to write." immediately above the line
3017
+ reporting the rewrite.
3018
+ - `c4.fleet-project-invalid` now enumerates what the renderer actually loads. The document set is a
3019
+ walk of the docs root — everything the root `exclude` leaves, minus the generated
3020
+ `architecture/subsystems.likec4`, minus every standalone service directory (owned by
3021
+ `service.model-unexcluded`), minus `features/` (a delta carries its own `specification` and the
3022
+ directory is transient). A `.likec4` in a SUBSYSTEM directory — the parent of service directories
3023
+ — was in none of the three roots the check used to merge: `validate --all` came back
3024
+ byte-identical to baseline and never named the file while `npx likec4 validate --project fleet .`
3025
+ reported Invalid over the same tree. The finding also gains `locations` (the file the error is IN,
3026
+ not `architecture/landscape.likec4` on every one of them) and `details`, and its tail now names
3027
+ the failure it actually is — a double declaration, an unresolved reference, or neither — instead
3028
+ of always blaming a double declaration.
3029
+ - Every LikeC4 line number loam prints is 1-based, the way an editor counts — and deliberately NOT
3030
+ the way `likec4 validate` prints it, which passes the LSP range's start line through and so says
3031
+ `Line 8` for a fault on line 9.
3032
+ `c4.invalid`'s details said `L3` for an error on line 4; `c4.fleet-project-invalid` printed
3033
+ `model.likec4:0` for a declaration on line 1; and the feature arm of `usecase.flow-invalid`
3034
+ printed no line at all and an ABSOLUTE Windows path into loam's own staging tree — it now spells
3035
+ the authored file docs-relative with forward slashes and an `L<n>`, exactly as the service arm of
3036
+ the same code does, in the text view, in `--json` and in the `loam archive` block message.
3037
+
3038
+ ### Fixed — the adoption brief answers off the document it actually read
3039
+
3040
+ - `loam adopt` no longer briefs a write to the shared fleet map when the map could not be READ.
3041
+ The eighth target (`architecture/landscape.likec4`) is emitted on `landscape.modelled === false`
3042
+ — a read that succeeded and found nothing — and on the edgeless state; `modelled: null` ("loam
3043
+ could not read the map", which since the `architecture/`-project widening includes a broken
3044
+ palette or use case beside a landscape that parses perfectly) now emits no target at all.
3045
+ It used to fall through the same gate as "the map draws nothing for this service", so the brief
3046
+ handed an agent the UNDRAWN block — "add one top-level element bound to its directory" — for a
3047
+ service the map already declares and binds, and splicing it earned `landscape.binding-duplicate`.
3048
+ `landscape.instruction` carries the whole answer instead and names the documents that failed.
3049
+ - `loam adopt`'s fleet-map instruction and the text view's landscape headline no longer name
3050
+ `architecture/landscape.likec4` as the broken file when the document that failed is a SIBLING
3051
+ under `architecture/`. The instruction now names the failing documents docs-relative with their
3052
+ error count — the same spelling `validate --all`'s `landscape.invalid` prints — and the headline
3053
+ says `The fleet map (architecture/) does not parse — fix that first; the files are named below.`
3054
+ The brief and the fleet run on the same tree used to blame two different files.
3055
+ - `loam adopt` no longer marks `openapi.yaml` required because a document beside the fleet map
3056
+ broke. The API question is answered off the landscape FILE (`landscape.callersKnown`, added to
3057
+ `--json`: true when the `architecture/` project parses, and true when it does not but the
3058
+ landscape file does), which is the evidence `loam list` and `loam context` already read: a broken
3059
+ `architecture/palette.likec4` used to flip `openapi.yaml` from `create missing` to
3060
+ `create MISSING` for a browser UI the map proves nobody calls, and told the agent to write an API
3061
+ contract for it. With the landscape file itself unreadable the contract is still owed.
3062
+ - `loam adopt`'s model.likec4 example prints a short `<your-element-id>` where loam has no id to
3063
+ substitute, and carries the reason once as a `//` comment above the block. The placeholder used
3064
+ to be a 103-character English sentence spliced into all three code positions `<fqn>` occupies —
3065
+ the `extend` line, the source of a container edge and `view of` — on the command's most common
3066
+ invocation, a brand-new adoption.
3067
+ - `loam adopt`'s arch.spec.md shape rule named `c4.fleet-project-invalid` (warn, `--all`) for a
3068
+ `#req-` tag declared both in the map and beside the model. Measured, that is `c4.invalid` (error,
3069
+ and the model's grading is suspended behind it) when the tags-only block is in model.likec4 and
3070
+ `usecase.flow-invalid` when it is in a sibling; `c4.fleet-project-invalid` is the fleet-scope
3071
+ class one step out, two SERVICES each declaring the same tag locally. The rule now says so, and
3072
+ matches the same statement in SCHEMA.md.
3073
+ - `loam adopt`'s model.likec4 shape rule for `c4.element-unowned` leads with the nested remedy —
3074
+ a store or component this service OWNS goes INSIDE the `extend <fqn> { }` block, where its id
3075
+ becomes `<fqn>.<name>` — before the two existing arms, matching the `--service` fix table and
3076
+ SCHEMA's grades table.
3077
+ - `loam adopt`'s `landscape.invalid` check description said "architecture/landscape.likec4 exists
3078
+ but does not parse". It now says the `architecture/` PROJECT does not parse — the landscape or
3079
+ any `.likec4` beside it — and that the message names the document that broke.
3080
+
3081
+ ### Fixed — one tree, one answer: the gate's preview, and the map's other readers
3082
+
3083
+ - The merge preview every feature-side gate reads is now routed exactly as `loam archive` routes:
3084
+ the fleet's extending models reach it, so an addition the delta nests inside a service is
3085
+ previewed in that service's `model.likec4` and not on the map. Without them the preview held
3086
+ interior the archive puts somewhere else, and `usecase.flow-invalid` — the gate `--approve`
3087
+ cannot override — failed open on the one shape it exists for. Measured on a copy of
3088
+ `examples/docs`: a feature nesting `cache` under `marketplace.orderService` and hopping
3089
+ `checkoutWeb -> marketplace.orderService.cache` from `features/<FEAT>/usecases/` passed
3090
+ `validate --feature`, archived at exit 0, and left a fleet whose next `validate --all` reported 7
3091
+ errors — one `landscape.invalid` plus one `spine.landscape-invalid` per service — against a file
3092
+ its author never wrote. The gate now refuses before anything is written, `validate --feature`
3093
+ gives the same verdict, and a flow hopping between elements the map itself draws still archives.
3094
+ `deployment.doc-invalid` is wired the same way and had the same hole.
3095
+ - `loam context <service>` reads the fleet map as the `architecture/` PROJECT — the landscape
3096
+ merged with every use case, palette and second `model { }` block beside it — never
3097
+ `architecture/landscape.likec4` alone. Two commands answered opposite things about one tree:
3098
+ measured on a two-service fleet with a broken `architecture/palette.likec4` beside a landscape
3099
+ that parses, `loam adopt --service order-service` reported `parses: false` and refused to say
3100
+ anything about the map, while `loam context order-service` reported `parses: true`,
3101
+ `modelled: true` and listed the map's edges — and an agent handed both has no way to tell which is
3102
+ right. `landscape.present` is still a fact about the FILE; `landscape.parses` is now a fact about
3103
+ the project the renderer loads, so a broken sibling reads here as "nobody could look", the answer
3104
+ `validate --all` and `adopt` already gave. The second load of that same project the deployment
3105
+ slice used to make when the landscape file declared no `deployment { }` block is gone with it: one
3106
+ parse, one answer.
3107
+ - `loam seed` computes the generated views file's style census from the `architecture/` documents
3108
+ the root `likec4.config.json`'s `exclude` leaves, not from a raw walk of the directory. A
3109
+ `palette.likec4` under an excluded path is loaded by neither the renderer nor `validate --all`,
3110
+ yet seed read it and wrote `global style subsystems` into `architecture/subsystems.likec4` — a
3111
+ name the fleet project cannot resolve, written by the command whose own `next` line is
3112
+ `loam validate --all`. The same defect one command over from the one the exclude filter fixed in
3113
+ the loader. The fleet use-case scan's cheap tag gate was corrected with it: it walked
3114
+ `architecture/` for itself, which could only over-scan (it is a prefilter before a load that does
3115
+ apply the exclude), but two lists that disagree about what the project is are two answers waiting
3116
+ to drift.
3117
+ - `landscape.invalid`'s head reads `<documents> have N error(s)` once more than one document broke,
3118
+ matching `spine.landscape-invalid`. The named list is comma-joined when the `architecture/`
3119
+ project has errors in two files, and the fleet line said "a, b has 4 error(s)" directly above the
3120
+ service line saying "a, b have 4 error(s)" about the same two files — one report, two spellings of
3121
+ one fact.
3122
+
3123
+ ### Fixed — a second verification round: what the archive routes, and what every message names
3124
+
3125
+ - `loam archive` no longer refuses a delta written in the legacy title-join spelling inside a group
3126
+ the map declares. Routing an addition into a service's extending model now requires the delta to
3127
+ spell the whole parent chain in the MAP's own ids — a living element, a container an extending
3128
+ model already declares, or a declaration this same delta adds — instead of only requiring that
3129
+ *some* ancestor be living. A grouped map (`marketplace = group { … }`, the shape
3130
+ `loam init --example` ships) made the old guard pass on `marketplace.orderSvc`, the addition was
3131
+ written as `extend marketplace.orderSvc` — an id the map has never declared — and the model's
3132
+ parse net refused the whole archive with `merge-failed` and nothing written. What a repo notices:
3133
+ such a delta merges onto `architecture/landscape.likec4` under the map's own id and exits 0 again,
3134
+ exactly as it does when the service's model is standalone or absent. The documented remainder is
3135
+ unchanged: a title-joined parent keeps the map path.
3136
+ - A service a feature INTRODUCES now keeps its interior out of the fleet map when its directory
3137
+ already holds an extending model. The new element's containers arrive inside its authored bytes and
3138
+ carry no feature tag of their own — LikeC4 does not inherit tags, so nothing in the addition set
3139
+ could see them — and they rode onto the map: `loam adopt --service <svc>` then told the team to
3140
+ write the same container into `extend <fqn> { }`, and the renderer reported a duplicate element
3141
+ blamed on both files while the service graded `c4.invalid`. Now the box lands on the map without
3142
+ its interior, the children are spliced into the model's `extend <fqn> { }` block (created if
3143
+ absent), and `loam archive` prints the second
3144
+ `architecture: merged into services/<…>/model.likec4` line for it. A service arriving with no
3145
+ directory at all, and one whose block also draws an internal edge between its own containers, keep
3146
+ the previous behaviour byte for byte — an edge written inside the block names its endpoints by
3147
+ their local names and there is no legal split, so the block rides whole. That is ONE decision and it covers the whole block: a child or an edge
3148
+ the delta tags with the feature rides with a block that rides and is spliced with a block that
3149
+ splits, never placed twice. While the two were decided separately the tagged child landed in both
3150
+ documents and the merge refused itself, `merge-failed`, on a delta neither half was wrong about.
3151
+ - Both C4 merges now splice with the target document's own line endings. A CRLF
3152
+ `architecture/landscape.likec4` or `services/<…>/model.likec4` came back with a handful of bare-LF
3153
+ lines in the spliced region, and a CRLF delta's blank lines arrived carrying the target indent as
3154
+ trailing whitespace. A document with one convention keeps it; a document that is already mixed is
3155
+ left alone, because there is no convention to match.
3156
+ - `landscape.datastore-shared` and `landscape.datastore-private` now state the reversibility of a
3157
+ private store's placement **directionally**, because the unconditional clause was false in every
3158
+ state `datastore-shared` prints in. The two placements do give the store the same id
3159
+ (`<owner-fqn>.<store>`), but the move is only free one way: out of the owner's `extend` block onto
3160
+ the owner's element on the map is always safe, and back INTO the `extend` block breaks every other
3161
+ consumer's edge — `c4.invalid` on that service's model, or `landscape.invalid` when the edge is
3162
+ drawn on the map — because a per-service project is `architecture/` plus one model, so an `extend`
3163
+ block is visible to its own service's project alone. `datastore-shared` needs a second consumer to
3164
+ fire, so the state it prints in is exactly the state in which map→extend breaks; `datastore-private`
3165
+ has one consumer, so it says both moves are free *today* and names the condition that ends that.
3166
+ - `landscape.datastore-private`'s no-model arm offers a model to WRITE, not one to migrate: with no
3167
+ `model.likec4` at all it now says "write an extending model at `<path>`" one sentence after saying
3168
+ loam read no extending model there. The STANDALONE arm still says "migrate the model", which is
3169
+ correct for it.
3170
+ - `landscape.service-isolated`'s message names the fleet map rather than one file: "no edge in the
3171
+ fleet map (architecture/) touches it". The check's input is the `architecture/` PROJECT — an edge
3172
+ added to `architecture/extra.likec4` and never to `landscape.likec4` silences it, measured — so the
3173
+ message now agrees with SCHEMA, with `loam explain landscape.service-isolated` and with the adopt
3174
+ brief, all three of which already said "anywhere in `architecture/`".
3175
+ - `loam doctor` prints LikeC4 line numbers 1-based, through the same `errorText` helper every other
3176
+ surface uses. `doctor.landscape-invalid` hand-rolled its own `(line <n>)` spelling and passed the
3177
+ LSP range through raw, so it named the line above the fault and disagreed with `validate --all`
3178
+ about one error on one tree. The message now reads
3179
+ `architecture/landscape.likec4 does not parse: L<n>: <message> — and N more`.
3180
+ - `loam context --json` and `loam explore --json` gain `landscape.broken`: the `architecture/`
3181
+ documents that failed, docs-relative POSIX, deduped and sorted, `[]` while `landscape.parses` is
3182
+ true. Spelled exactly as `landscape.invalid` and the adopt brief spell them, so the three surfaces
3183
+ name one file one way. Both commands read the `architecture/` PROJECT, so the document at fault is
3184
+ very often a sibling beside a landscape that reads perfectly.
3185
+ - The adopt brief no longer promises a warning that cannot fire. On an element carrying `#external`,
3186
+ the edgeless instruction and the `landscape.likec4` target's shape rule say that
3187
+ `landscape.service-isolated` will NOT fire (the check removes every `#external` element from its
3188
+ subject set), that this brief is the only place the state is named, and that dropping `#external`
3189
+ makes the check apply again. Every other tree keeps the previous sentence.
3190
+ - The adopt brief's `checks[]` row for `c4.declaration-diverged` names "the fleet map
3191
+ (`architecture/`)" instead of `architecture/landscape.likec4`. The map is a project; LikeC4 hands
3192
+ back no source document for an element, and the finding's own message and `loam explain` already
3193
+ refused to name a file.
3194
+ - `loam adopt --json`'s `landscape.inbound`/`landscape.outbound` drop an edge whose two endpoints
3195
+ resolve to the SAME service — a service's own internal wiring drawn on the map made it its own
3196
+ caller. `landscape.touched` is unchanged: it is now read off the map's relationships rather than
3197
+ off those two lists, so it still counts an intra-service edge and still matches
3198
+ `landscape.service-isolated`'s predicate exactly.
3199
+ - The service arm's use-case findings (`usecase.step-unbacked`, `usecase.step-unlinked`,
3200
+ `usecase.requirement-unresolved`, `usecase.capability-unresolved`) carry the flow FILE as
3201
+ `locations[0]` with role `primary` and the service directory beside it as `scope`. They carried the
3202
+ service directory alone, so a `--json` consumer had to parse the prose to learn which `.likec4` to
3203
+ open. `usecase.flow-invalid` on the same arm already named its file.
3204
+ - `usecase.flow-invalid`'s undeclared-tag hint tells a `cap-` author the placement rule instead of a
3205
+ declaration that cannot help: a capability is claimed at fleet altitude, so declaring `tag cap-…`
3206
+ beside a model makes the file parse and earns `usecase.capability-unresolved` on the next run. The
3207
+ `req-` hint is unchanged.
3208
+ - The services tree walk never classifies a service's own reserved subdirectory (`adrs`, `usecases`,
3209
+ `ui` — SCHEMA's service layout) as a service or a subsystem. Writing the documented
3210
+ `services/<id>/usecases/<name>.likec4` slot into a directory holding no artifact FILE used to make
3211
+ the walk read `usecases` as a service and its parent as an unmarked group: three errors
3212
+ (`subsystem.unmarked`, `landscape.service-unmodelled` for `services/<id>/usecases/`,
3213
+ `landscape.binding-unknown` for the real service) earned by following the instruction `loam init`
3214
+ had just scaffolded. Such a directory is also not counted when deciding whether a directory is a
3215
+ group, so a real service filed beside a reserved slot still refuses with `subsystem.unmarked` and
3216
+ is still named.
3217
+ - `loam` now strips a leading UTF-8 byte-order mark before parsing the root `likec4.config.json`.
3218
+ PowerShell's `Out-File` writes one by default, so a Windows shell saving the file loam itself wrote
3219
+ left every `exclude` grade silent while the renderer went on applying the list (measured:
3220
+ `npx likec4 validate .` at 3 source files of 8 with `services/**` in force, `loam validate --all`
3221
+ at 0 errors and `doctor` healthy). A rewrite through `loam subsystem sync` drops the mark.
3222
+ - `service.model-excluded` asks the FILE question too. An `exclude` entry shaped like a file
3223
+ (`services/**/model.likec4`, `**/model.likec4`) hides a model as thoroughly as one naming its
3224
+ directory, and nothing said so: on `examples/docs` such a line took the renderer to 3 source files
3225
+ of 8 while `loam validate --all` printed the string "exclude" not once. It is reported now, with
3226
+ the repair that fits it — `loam subsystem sync` maintains only the `services/` directory entries,
3227
+ so the file shape has to be narrowed or deleted by hand, and the message says which shape you have.
3228
+ - `loam seed` no longer overwrites a hand edit written ABOVE the scaffold's landscape stub.
3229
+ `isLandscapeStub` matched with `endsWith`, so anything a person put before the stub was invisible
3230
+ to the guard: a two-line "OWNED BY THE ARCHITECTURE GUILD — do not regenerate" header was replaced
3231
+ without a word while seed printed "replaced the scaffold's untouched stub", against its own refusal
3232
+ text ("Seed never overwrites human work"). The suffix match existed for exactly one prefix — the
3233
+ comment preamble `migrate-openspec` stages above the identical stub, closed by a bare `//` line —
3234
+ so that is now the only prefix accepted, at every scaffold generation. What a repo notices:
3235
+ `loam seed` refuses with `seed-landscape-edited` and writes nothing, and `loam status` calls such a
3236
+ map `authored` rather than `stub`. A repo staged by `migrate-openspec` still seeds.
3237
+ - `loam doctor` counts the report corpus that is there when the config is the thing that is broken.
3238
+ Read from a SUBDIRECTORY of a repo whose `loam.json` does not parse, it used to report
3239
+ `total 0, next 001` while naming the broken config's own path in the same envelope — handing out an
3240
+ ordinal five existing reports already used. `doctor` is the preflight that runs precisely when the
3241
+ config may not resolve, so `reports.dir` now resolves beside the `loam.json` it FOUND.
3242
+ - `/loam-check`'s `c4.fleet-project-invalid` row spells the `details` format the finding emits:
3243
+ `<path>: L<n>: <error>`, with the colon after the path that the message's own head has always
3244
+ carried. The row promised `<path> L<n>: <error>`, so an agent splitting on the documented separator
3245
+ split in the wrong place.
3246
+
3247
+ ## [0.2.0-alpha.5] - 2026-09-03
3248
+
3249
+ An adopted service's model renders from the docs root. Still a prerelease on the `alpha` line:
3250
+ `npm i @ybotok/loam@alpha` installs it. Nothing below is breaking. What a repo with adopted services
3251
+ NOTICES: one `service.likec4-config-missing` warning per service on its first `validate --all`
3252
+ until one `loam subsystem sync`, and from that sync on `--project` for `likec4 validate` at the
3253
+ docs root.
3254
+
3255
+ ### Added — an adopted service's model renders from the docs root
3256
+
3257
+ The root `likec4.config.json` excludes `services/**`, and has to; what that cost was never stated:
3258
+ an adopted service's `model.likec4` — the artifact `loam adopt` spends the most effort on — was a
3259
+ box on the fleet map with nothing renderable inside it in the renderer opened at the docs root, and
3260
+ the only documented way to see its containers was one `npx likec4 start services/<id>` per service.
3261
+ One problem report; one file per service closes it.
3262
+
3263
+ - `loam subsystem sync` now writes `services/<…>/<svc>/likec4.config.json` beside every service
3264
+ `model.likec4` that has none, whenever the root `likec4.config.json` exists — **create-only**: a
3265
+ file that exists is yours and is never compared, rewritten or removed. Measured at the pinned
3266
+ likec4, a project file inside a service directory registers the model as a project of its own
3267
+ regardless of the root's exclusion, so `npx likec4 start` at the docs root shows `fleet` plus one
3268
+ project per service. Without the root file nothing is written and the text says `loam doctor`
3269
+ prints it. `subsystem sync --json` carries an additive `projects` key, `{ root, created, current }`
3270
+ — whether the root file exists, the repo-relative paths written this run (sorted by id), and how
3271
+ many model-bearing services already had one. `action` still describes
3272
+ `architecture/subsystems.likec4` alone.
3273
+ - `loam validate --all` gains one warning per service, `service.likec4-config-missing`, on the
3274
+ `landscape` target with the service's tree path as its `scope`: `model.likec4` exists and the
3275
+ project file does not. Never an error, never gating, never in `--service`, `status` or `doctor`;
3276
+ the fix is `loam subsystem sync`, never a hand-written file. `doctor.likec4-config-missing`'s fix
3277
+ text now hands over to `sync` once the root file is written.
3278
+ - The project name: the id with every `.` folded to `-` (LikeC4's name grammar refuses the byte),
3279
+ prefixed `service-` when that would equal the root project's `fleet` or LikeC4's reserved
3280
+ `default`; `title` is the id verbatim, and the title is what the renderer's picker shows. Not
3281
+ injective — an id spelled billing.core beside one spelled billing-core, or `service-fleet` beside
3282
+ `fleet` — and LikeC4 suffixes the later one `-1`; disclosed, never graded.
3283
+
3284
+ What a repo NOTICES on upgrade: one warning per adopted service on the first `validate --all` until
3285
+ one `loam subsystem sync` — under `--strict`, CI is red until then; after that `sync`, N untracked
3286
+ files (`sync` does not `git add` them) and `move-uncommitted` on the next `loam subsystem move` of
3287
+ such a service until they are committed; from the first `sync` on, `likec4 validate` at the docs
3288
+ root needs `--project <name>` (measured: with more than one project it refuses bare, while
3289
+ `likec4 build` and `likec4 export` take every project and need no flag); and the renderer's project
3290
+ list grows by one per adopted service, beside `fleet`. No command, flag, exit code or envelope key
3291
+ changes; `projects` is additive.
3292
+
3293
+ ## [0.2.0-alpha.4] - 2026-09-03
3294
+
3295
+ The view title becomes the subsystem's path. `0.2.0-alpha.3` was tagged and **never published** — it
3296
+ was withdrawn before its publish step for a reason unrelated to the code — so this is the release
3297
+ that carries both it and the entry below, and `npm i @ybotok/loam@alpha` moves from `0.2.0-alpha.2`
3298
+ straight to this one. A repo upgrading from either reports one `subsystem.views-stale` until one
3299
+ `loam subsystem sync`.
3300
+
3301
+ ### Changed — a generated subsystem view is titled by its path
3302
+
3303
+ `0.2.0-alpha.3` gave each view the subsystem's own label. It now carries the whole path — every
3304
+ marked ancestor's label and its own, joined with ` / `, so `services/platform/order-flow` reads
3305
+ `Platform / Order flow`. Measured against the pinned likec4: the renderer reads that as a folder
3306
+ path, so the view list mirrors the directory tree a human already navigates — one folder per
3307
+ ancestor, the leaf's own label as the title — and two subsystems whose labels are equal are told
3308
+ apart by their parents instead of appearing twice under one name. The box a view draws keeps the
3309
+ subsystem's own label, unprefixed.
3310
+
3311
+ Because loam is now the one composing with `/`, it owns what a `/` inside an authored label would
3312
+ do: measured, LikeC4 would show only the last segment and add a folder level the tree does not have,
3313
+ so a slash in a marker's `title` becomes `∕` (U+2215) and the whole authored label stays visible as
3314
+ one folder name. The alternative — a fixed `loam subsystems /` prefix — bought the same folder
3315
+ without the churn and put loam's own name in a reader's UI; the tree's own words won.
3316
+
3317
+ Two costs, both real: the generated bytes move again, so a repo upgrading from `0.2.0-alpha.3`
3318
+ reports one `subsystem.views-stale` until one `loam subsystem sync`; and renaming a PARENT's title
3319
+ now rewrites the bytes of every view beneath it.
3320
+
3321
+ ## [0.2.0-alpha.3] - 2026-09-03
3322
+
3323
+ The generated subsystem views, made readable — and the defect that hunt found underneath them.
3324
+ Still a prerelease on the `alpha` line. **Not published (noted 2026-10-10):** the `v0.2.0-alpha.3`
3325
+ tag was cut, but it was withdrawn before its publish step — the `0.2.0-alpha.4` section above says
3326
+ so, and that release carries everything here — so `npm i @ybotok/loam@alpha` never installed this
3327
+ version, and `npm view @ybotok/loam dist-tags` is the record of what does. Nothing below is
3328
+ breaking, and one entry is a change every repo NOTICES: the generated file's bytes move, so the
3329
+ first `loam validate --all` after upgrading asks for one `loam subsystem sync`.
3330
+
3331
+ ### Fixed — a `subsystem.views-stale` no command could clear
3332
+
3333
+ `loam validate --all` grades `architecture/subsystems.likec4` against the whole `architecture/`
3334
+ project — the landscape merged with every `architecture/usecases/*.likec4` — while `loam subsystem
3335
+ sync` and the write verbs rendered it from `landscape.likec4` alone. One use-case document declaring
3336
+ an element bound to a filed service was therefore enough to make `sync` answer `current` while
3337
+ `validate --all` exited 1 with `subsystem.views-stale`, whose message names `sync` as the repair.
3338
+ Reproduced on `examples/docs`; the two now read the same documents. No code, flag or payload key
3339
+ changes.
3340
+
3341
+ ### Changed — the generated subsystem views carry a label and a boundary
3342
+
3343
+ `architecture/subsystems.likec4` exists only to be rendered, and in the renderer its views were the
3344
+ least readable objects in a docs repo: no title, so LikeC4 showed the hex-escaped id
3345
+ (`subsystem_platform__order_2dflow`), and leaf includes only, so a fleet drawn as ordinary
3346
+ grouped C4 rendered flat. Every view now carries `title` (the marker's, or the directory name) and
3347
+ `description` when the marker authors one, and draws a boundary: the model's own containing element
3348
+ when one holds exactly the subsystem's members, a `group '<title>'` mirroring the directory subtree
3349
+ when the landscape nests nothing, and — when the members are nested but no element owns exactly them
3350
+ — no box plus one comment line, `// model: no boundary — …`, which is loam's whole answer to a tree
3351
+ and a map that disagree. Nothing grades that comment: placement still carries no policy.
3352
+
3353
+ Marker text is normalized to one line and escaped before it is written (measured: a raw `'` in a
3354
+ title is two parse errors and blanks the fleet's whole map in the renderer; `Ops' } view evil {` is
3355
+ zero errors and injects a view). [SCHEMA.md](SCHEMA.md) states the rules.
3356
+
3357
+ What a repo notices, and what it costs:
3358
+
3359
+ - the first `loam validate --all` after upgrading reports exactly one `subsystem.views-stale` error
3360
+ (exit 1) until one `loam subsystem sync` runs. `sync` does not `git add`, so the repair leaves one
3361
+ unstaged one-file diff to commit;
3362
+ - the boundary depends on which OTHER services sit under an element, so an unrelated adoption or
3363
+ archive can flip a view from boxed to flat and restale the file. `loam archive` now withholds its
3364
+ "living spec + landscape are now complete + current" line, prints one warning naming
3365
+ `loam subsystem sync`, and adds `subsystemViewsStale: true` to its `--json` payload when it left
3366
+ the file behind;
3367
+ - `subsystem.views-stale` and `subsystem.view-id-collision` now carry `locations`: the generated
3368
+ file (or `architecture/` when it does not exist yet) and the claimant document respectively. Both
3369
+ used to fall back to the landscape, sending an editor to a file with nothing to fix in it.
3370
+
3371
+ No command, flag, exit code, envelope key or stable code string changes; `subsystem.views-stale` is
3372
+ not in the `IssueCode` union and carries no `gates` key, so `loam archive` remains unaffected by it.
3373
+
3374
+ ## [0.2.0-alpha.2] - 2026-09-02
3375
+
3376
+ The deployment axis, and the two defects the axis surfaced on its way in. Still a prerelease on the
3377
+ `alpha` line: `npm i @ybotok/loam@alpha` installs it. Nothing below is breaking, but two entries are
3378
+ changes a repo can NOTICE — a fleet that had already placed a `#obl-` tag on a `deploymentNode`
3379
+ starts seeing findings it was never shown, and `loam status` stops asking an architecture-only
3380
+ feature for a business spec.
3381
+
3382
+ ### Added — the deployment axis: topology joined to requirements
3383
+
3384
+ A LikeC4 `deployment { }` block has always been legal in a docs repo and was completely unread. The
3385
+ parser resolved every `instanceOf`, and after that no requirement could name a node, no `#obl-` tag
3386
+ on a datacenter was graded, and the context pack an agent implements from did not mention topology
3387
+ at all. Five things change, and a fleet that draws no topology is unaffected by every one of them —
3388
+ the axis opts in on the block's existence, the way the obligation vocabulary opts in on its file.
3389
+
3390
+ - **`Covers:` gains a fourth entry form.** `node:<id>` names a deployment node or an instance, and
3391
+ `node:a -> node:b` names an edge between them; both match literally. This is the form that lets an
3392
+ architecture requirement about replication name the two clusters it is written about, and a
3393
+ topology change that renames one now reports `covers.unknown` with the real ids offered — spelled
3394
+ with the prefix, because that is the line the author has to type. Both sides of an edge carry the
3395
+ prefix or neither; a bare id stays an element entry.
3396
+ - **An obligation on a deployment object is graded.** `obligation.unknown`, `obligation.unapplied`
3397
+ and `obligation.uncovered` now walk the topology as well as the logical model. This is a
3398
+ behaviour change a repo can notice: a `#obl-` tag on a `deploymentNode` used to be invisible to
3399
+ every check, so a fleet that had placed one starts seeing findings it was never shown. A
3400
+ datacenter is owned by no service, so `obligation.uncovered` drops `subject` there and names the
3401
+ requirement's home in general terms rather than picking a team.
3402
+ - **A feature can bring topology.** `features/<FEAT>/deployment/<name>.likec4` is a create-only
3403
+ document `loam archive` copies into `architecture/` and `loam unarchive` takes back — the third
3404
+ whole-file slot, beside `usecases/` and `glossary/`. Say `extend` in it to add to a region the
3405
+ living map already declares; two features may extend one region from documents of their own and
3406
+ both archive. Two new codes, both errors and neither overridable: **`deployment.doc-exists`** when
3407
+ the feature's document names a file the living tree already holds, and **`deployment.doc-invalid`**
3408
+ when it cannot be read against the map the feature's own merge would leave behind. The second is
3409
+ what makes the ordinary case legal — a document instancing a service only this feature's
3410
+ `delta.likec4` adds resolves, because the corpus inside a feature window is feature ∪ living — and
3411
+ refuses the case where the merge does not land what the document names. `delta.likec4` still
3412
+ refuses a `deployment { }` block and now names this slot instead of the living landscape.
3413
+ A third, **`deployment.doc-reserved`**, refuses a name loam owns inside `architecture/` —
3414
+ `subsystems.likec4`, `landscape.likec4`, or a path under `usecases/` — whether or not the target
3415
+ exists, because two of the three fail silently when it does not: nothing parses the generated
3416
+ subsystem views and `loam subsystem sync` overwrites them wholesale.
3417
+ - **`loam context` carries the topology.** `--json` gains `living.deployment` — the service's
3418
+ instances, and the deployment edges touching them from either end.
3419
+ - **One more statement in the adoption brief's unchecked list**, now sixteen: nothing here knows
3420
+ whether the second cluster exists, is reachable, or holds the data a requirement claims, and no
3421
+ check counts datacenters — how many a service needs is a decision the fleet writes down as a
3422
+ requirement, never one loam evaluates.
3423
+
3424
+ ### Fixed — the never-overridable archive refusal names what actually refused
3425
+
3426
+ It said `N element binding(s) name an illegal service id` for every code in that register, and had
3427
+ done since the use-case slot put a second one there — about features that bind nothing. The headline
3428
+ now counts the refusals and the closing line names their codes; each issue keeps its own sentence.
3429
+ The `--json` envelope is unchanged: consumers branch on `issues[].code` and `overridable`, which is
3430
+ what that split exists for.
3431
+
3432
+ ### Fixed — `loam status` no longer invents work for an architecture-only feature
3433
+
3434
+ A feature whose only requirement delta is `specs/<svc>/arch.spec.md` reported two things that were
3435
+ not true, and both are corrected. The `spec` row read `missing` and `next.author-spec` told the
3436
+ author to write business requirements the change does not have — while `loam validate --feature`
3437
+ accepted the same tree at exit 0 and `coherence`'s `service.no-requirement-delta` stayed silent,
3438
+ because every other reader counts the service DIRECTORY and not which corpus is in it. And the
3439
+ `verification` row read `blocked` with `blockedBy: ["delta", "spec", "openapi", "asyncapi"]`,
3440
+ telling the reader there was nothing to derive a checklist from — while `featureChecklist` reads
3441
+ `arch.spec.md` and `loam verify` on that feature returns a real checklist.
3442
+
3443
+ The fleet form carried the same defect independently, and it broke the rule stated one line below
3444
+ it in its own source: `loam status` with no feature reported `missing <svc>/spec` and staged the
3445
+ feature `missing`, over a feature `loam status <FEAT>` called `ready`. Both forms now accept either
3446
+ corpus as the service's requirement delta, and `<svc>/spec` is named only when neither exists.
3447
+
3448
+ Three payload-visible consequences, all in `loam status --json`: `artifacts[]`'s `spec` row is
3449
+ `required: false` when the service's `arch.spec.md` delta exists (it was unconditionally `true`),
3450
+ `arch-spec` now counts as a verification feeder, so `blockedBy` gained `"arch-spec"` in the
3451
+ one case that is still genuinely blocked — a feature with no requirement delta of either kind —
3452
+ and `features[]`'s `missing` and `stage` no longer report an architecture-only feature as
3453
+ incomplete. No finding code, flag or exit code changes. This is the change to read if a script
3454
+ branched on `artifacts[].required` or on `features[].missing`.
3455
+
3456
+ The case is the one `arch.spec.md` exists for: an outbox, a retry policy, an alert — obligations no
3457
+ business scenario was ever going to carry, so the feature that adds one has no business requirement
3458
+ to write. Found by authoring loam's own architecture change through loam's own lifecycle, which is
3459
+ the trigger ROADMAP's self-hosting section records for that axis.
3460
+
13
3461
  ## [0.2.0-alpha.1] - 2026-09-01
14
3462
 
15
3463
  The first release of the 0.2 line, and the whole accumulated body since `0.1.0-beta.3` — 116