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

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 (1036) hide show
  1. package/CHANGELOG.md +2635 -86
  2. package/COMPARISON.md +80 -63
  3. package/CONTRIBUTING.md +84 -0
  4. package/MIGRATING-from-OpenSpec.md +213 -54
  5. package/QUICKSTART.md +125 -0
  6. package/README.md +450 -150
  7. package/ROADMAP.md +599 -0
  8. package/SCHEMA.md +2355 -256
  9. package/SECURITY.md +21 -3
  10. package/WORKFLOW.md +701 -0
  11. package/dist/cli.js +113 -19
  12. package/dist/cli.js.map +1 -1
  13. package/dist/commands/adopt/adopt.js +209 -0
  14. package/dist/commands/adopt/adopt.js.map +1 -0
  15. package/dist/commands/adopt/render.js +268 -0
  16. package/dist/commands/adopt/render.js.map +1 -0
  17. package/dist/commands/archive/archive.js +77 -0
  18. package/dist/commands/archive/archive.js.map +1 -0
  19. package/dist/commands/archive/plan/contracts/asyncapi.js +178 -0
  20. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -0
  21. package/dist/commands/archive/plan/contracts/openapi.js +159 -0
  22. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -0
  23. package/dist/commands/archive/plan/gate.js +323 -0
  24. package/dist/commands/archive/plan/gate.js.map +1 -0
  25. package/dist/commands/archive/plan/landscape.js +180 -0
  26. package/dist/commands/archive/plan/landscape.js.map +1 -0
  27. package/dist/commands/archive/plan/refusal.js +111 -0
  28. package/dist/commands/archive/plan/refusal.js.map +1 -0
  29. package/dist/commands/archive/plan/specs.js +237 -0
  30. package/dist/commands/archive/plan/specs.js.map +1 -0
  31. package/dist/commands/archive/plan/state.js +27 -0
  32. package/dist/commands/archive/plan/state.js.map +1 -0
  33. package/dist/commands/archive/run.js +252 -0
  34. package/dist/commands/archive/run.js.map +1 -0
  35. package/dist/commands/context/context.js +99 -0
  36. package/dist/commands/context/context.js.map +1 -0
  37. package/dist/commands/context/print.js +283 -0
  38. package/dist/commands/context/print.js.map +1 -0
  39. package/dist/commands/delta/delta.js +279 -0
  40. package/dist/commands/delta/delta.js.map +1 -0
  41. package/dist/commands/delta/print.js +205 -0
  42. package/dist/commands/delta/print.js.map +1 -0
  43. package/dist/commands/dependencies.js +9 -7
  44. package/dist/commands/dependencies.js.map +1 -1
  45. package/dist/commands/diff/diff.js +93 -0
  46. package/dist/commands/diff/diff.js.map +1 -0
  47. package/dist/commands/diff/print.js +38 -0
  48. package/dist/commands/diff/print.js.map +1 -0
  49. package/dist/commands/doctor.js +5 -4
  50. package/dist/commands/doctor.js.map +1 -1
  51. package/dist/commands/explain/explain.js +151 -0
  52. package/dist/commands/explain/explain.js.map +1 -0
  53. package/dist/commands/explore.js +29 -24
  54. package/dist/commands/explore.js.map +1 -1
  55. package/dist/commands/gate/gate.js +115 -0
  56. package/dist/commands/gate/gate.js.map +1 -0
  57. package/dist/commands/gate/print.js +54 -0
  58. package/dist/commands/gate/print.js.map +1 -0
  59. package/dist/commands/gherkin/commit.js +146 -0
  60. package/dist/commands/gherkin/commit.js.map +1 -0
  61. package/dist/commands/gherkin/gherkin.js +246 -0
  62. package/dist/commands/gherkin/gherkin.js.map +1 -0
  63. package/dist/commands/gherkin/reconcile.js +149 -0
  64. package/dist/commands/gherkin/reconcile.js.map +1 -0
  65. package/dist/commands/gherkin/render.js +77 -0
  66. package/dist/commands/gherkin/render.js.map +1 -0
  67. package/dist/commands/init/example.js +204 -0
  68. package/dist/commands/init/example.js.map +1 -0
  69. package/dist/commands/init/first-hour.js +28 -0
  70. package/dist/commands/init/first-hour.js.map +1 -0
  71. package/dist/commands/{init.js → init/init.js} +146 -71
  72. package/dist/commands/init/init.js.map +1 -0
  73. package/dist/commands/init/mcp.js +131 -0
  74. package/dist/commands/init/mcp.js.map +1 -0
  75. package/dist/commands/init/options.js +78 -0
  76. package/dist/commands/init/options.js.map +1 -0
  77. package/dist/commands/instructions.js +72 -14
  78. package/dist/commands/instructions.js.map +1 -1
  79. package/dist/commands/list/campaign/campaign.js +65 -0
  80. package/dist/commands/list/campaign/campaign.js.map +1 -0
  81. package/dist/commands/list/campaign/owners.js +65 -0
  82. package/dist/commands/list/campaign/owners.js.map +1 -0
  83. package/dist/commands/list/json.js +157 -0
  84. package/dist/commands/list/json.js.map +1 -0
  85. package/dist/commands/list/list.js +343 -0
  86. package/dist/commands/list/list.js.map +1 -0
  87. package/dist/commands/list/print.js +301 -0
  88. package/dist/commands/list/print.js.map +1 -0
  89. package/dist/commands/list/review.js +120 -0
  90. package/dist/commands/list/review.js.map +1 -0
  91. package/dist/commands/list/views.js +105 -0
  92. package/dist/commands/list/views.js.map +1 -0
  93. package/dist/commands/mcp/dispatch.js +183 -0
  94. package/dist/commands/mcp/dispatch.js.map +1 -0
  95. package/dist/commands/mcp/mcp.js +34 -0
  96. package/dist/commands/mcp/mcp.js.map +1 -0
  97. package/dist/commands/mcp/serve.js +55 -0
  98. package/dist/commands/mcp/serve.js.map +1 -0
  99. package/dist/commands/migrate-openspec/materialize/active.js +170 -0
  100. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -0
  101. package/dist/commands/migrate-openspec/materialize/feature.js +41 -0
  102. package/dist/commands/migrate-openspec/materialize/feature.js.map +1 -0
  103. package/dist/commands/migrate-openspec/materialize/living.js +62 -0
  104. package/dist/commands/migrate-openspec/materialize/living.js.map +1 -0
  105. package/dist/commands/migrate-openspec/materialize/stage.js +142 -0
  106. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -0
  107. package/dist/commands/migrate-openspec/materialize/target.js +219 -0
  108. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -0
  109. package/dist/commands/migrate-openspec/migrate-openspec.js +142 -0
  110. package/dist/commands/migrate-openspec/migrate-openspec.js.map +1 -0
  111. package/dist/commands/migrate-openspec/openspec/decisions.js +91 -0
  112. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -0
  113. package/dist/commands/migrate-openspec/openspec/error.js +19 -0
  114. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -0
  115. package/dist/commands/migrate-openspec/openspec/mapping.js +170 -0
  116. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -0
  117. package/dist/commands/migrate-openspec/openspec/paths.js +43 -0
  118. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -0
  119. package/dist/commands/migrate-openspec/openspec/read.js +49 -0
  120. package/dist/commands/migrate-openspec/openspec/read.js.map +1 -0
  121. package/dist/commands/migrate-openspec/print.js +89 -0
  122. package/dist/commands/migrate-openspec/print.js.map +1 -0
  123. package/dist/commands/new/commit.js +76 -0
  124. package/dist/commands/new/commit.js.map +1 -0
  125. package/dist/commands/new/new.js +283 -0
  126. package/dist/commands/new/new.js.map +1 -0
  127. package/dist/commands/new/templates.js +355 -0
  128. package/dist/commands/new/templates.js.map +1 -0
  129. package/dist/commands/open.js +203 -0
  130. package/dist/commands/open.js.map +1 -0
  131. package/dist/commands/policy/format.js +152 -0
  132. package/dist/commands/policy/format.js.map +1 -0
  133. package/dist/commands/{docs-repo-gate.js → policy/gate.js} +11 -11
  134. package/dist/commands/policy/gate.js.map +1 -0
  135. package/dist/commands/rebase/edit.js +106 -0
  136. package/dist/commands/rebase/edit.js.map +1 -0
  137. package/dist/commands/rebase/living.js +185 -0
  138. package/dist/commands/rebase/living.js.map +1 -0
  139. package/dist/commands/rebase/plan.js +245 -0
  140. package/dist/commands/rebase/plan.js.map +1 -0
  141. package/dist/commands/rebase/rebase.js +275 -0
  142. package/dist/commands/rebase/rebase.js.map +1 -0
  143. package/dist/commands/seed/commit.js +192 -0
  144. package/dist/commands/seed/commit.js.map +1 -0
  145. package/dist/commands/seed/plan.js +133 -0
  146. package/dist/commands/seed/plan.js.map +1 -0
  147. package/dist/commands/seed/seed.js +172 -0
  148. package/dist/commands/seed/seed.js.map +1 -0
  149. package/dist/commands/show/feature.js +178 -0
  150. package/dist/commands/show/feature.js.map +1 -0
  151. package/dist/commands/show/marks.js +24 -0
  152. package/dist/commands/show/marks.js.map +1 -0
  153. package/dist/commands/{show.js → show/service.js} +63 -152
  154. package/dist/commands/show/service.js.map +1 -0
  155. package/dist/commands/show/show.js +77 -0
  156. package/dist/commands/show/show.js.map +1 -0
  157. package/dist/commands/status/json.js +45 -0
  158. package/dist/commands/status/json.js.map +1 -0
  159. package/dist/commands/status/print.js +157 -0
  160. package/dist/commands/status/print.js.map +1 -0
  161. package/dist/commands/status/status.js +119 -0
  162. package/dist/commands/status/status.js.map +1 -0
  163. package/dist/commands/steps/steps.js +189 -0
  164. package/dist/commands/steps/steps.js.map +1 -0
  165. package/dist/commands/subsystem/edit.js +192 -0
  166. package/dist/commands/subsystem/edit.js.map +1 -0
  167. package/dist/commands/subsystem/move.js +265 -0
  168. package/dist/commands/subsystem/move.js.map +1 -0
  169. package/dist/commands/subsystem/query.js +115 -0
  170. package/dist/commands/subsystem/query.js.map +1 -0
  171. package/dist/commands/subsystem/subsystem.js +101 -0
  172. package/dist/commands/subsystem/subsystem.js.map +1 -0
  173. package/dist/commands/subsystem/sync.js +103 -0
  174. package/dist/commands/subsystem/sync.js.map +1 -0
  175. package/dist/commands/subsystem/txn/txn.js +94 -0
  176. package/dist/commands/subsystem/txn/txn.js.map +1 -0
  177. package/dist/commands/subsystem/txn/views.js +70 -0
  178. package/dist/commands/subsystem/txn/views.js.map +1 -0
  179. package/dist/commands/unarchive/manifest.js +160 -0
  180. package/dist/commands/unarchive/manifest.js.map +1 -0
  181. package/dist/commands/{unarchive.js → unarchive/restore.js} +47 -160
  182. package/dist/commands/unarchive/restore.js.map +1 -0
  183. package/dist/commands/unarchive/unarchive.js +67 -0
  184. package/dist/commands/unarchive/unarchive.js.map +1 -0
  185. package/dist/commands/validate/arch-coverage.js +204 -0
  186. package/dist/commands/validate/arch-coverage.js.map +1 -0
  187. package/dist/commands/validate/checks/fleet-shape.js +296 -0
  188. package/dist/commands/validate/checks/fleet-shape.js.map +1 -0
  189. package/dist/commands/validate/checks/health-deps.js +55 -0
  190. package/dist/commands/validate/checks/health-deps.js.map +1 -0
  191. package/dist/commands/validate/checks/requirements.js +254 -0
  192. package/dist/commands/validate/checks/requirements.js.map +1 -0
  193. package/dist/commands/validate/checks/sources.js +76 -0
  194. package/dist/commands/validate/checks/sources.js.map +1 -0
  195. package/dist/commands/validate/checks/vocabulary.js +31 -0
  196. package/dist/commands/validate/checks/vocabulary.js.map +1 -0
  197. package/dist/commands/validate/feature.js +216 -0
  198. package/dist/commands/validate/feature.js.map +1 -0
  199. package/dist/commands/validate/fleet/census.js +69 -0
  200. package/dist/commands/validate/fleet/census.js.map +1 -0
  201. package/dist/commands/validate/fleet/kind-tags.js +39 -0
  202. package/dist/commands/validate/fleet/kind-tags.js.map +1 -0
  203. package/dist/commands/validate/fleet/landscape.js +353 -0
  204. package/dist/commands/validate/fleet/landscape.js.map +1 -0
  205. package/dist/commands/validate/fleet/load.js +52 -0
  206. package/dist/commands/validate/fleet/load.js.map +1 -0
  207. package/dist/commands/validate/fleet/obligations.js +270 -0
  208. package/dist/commands/validate/fleet/obligations.js.map +1 -0
  209. package/dist/commands/validate/fleet/scorecard/adoption.js +102 -0
  210. package/dist/commands/validate/fleet/scorecard/adoption.js.map +1 -0
  211. package/dist/commands/validate/fleet/scorecard/contracts.js +98 -0
  212. package/dist/commands/validate/fleet/scorecard/contracts.js.map +1 -0
  213. package/dist/commands/validate/fleet/scorecard/print.js +63 -0
  214. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -0
  215. package/dist/commands/validate/fleet/scorecard/scorecard.js +156 -0
  216. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -0
  217. package/dist/commands/validate/fleet/usecases/capability-tag.js +94 -0
  218. package/dist/commands/validate/fleet/usecases/capability-tag.js.map +1 -0
  219. package/dist/commands/validate/fleet/usecases/place.js +13 -0
  220. package/dist/commands/validate/fleet/usecases/place.js.map +1 -0
  221. package/dist/commands/validate/fleet/usecases/requirement-tag.js +95 -0
  222. package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +1 -0
  223. package/dist/commands/validate/fleet/usecases/steps.js +197 -0
  224. package/dist/commands/validate/fleet/usecases/steps.js.map +1 -0
  225. package/dist/commands/validate/fleet/usecases/usecases.js +99 -0
  226. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -0
  227. package/dist/commands/validate/fleet/views/ids.js +40 -0
  228. package/dist/commands/validate/fleet/views/ids.js.map +1 -0
  229. package/dist/commands/validate/fleet/views/stale.js +59 -0
  230. package/dist/commands/validate/fleet/views/stale.js.map +1 -0
  231. package/dist/commands/validate/links/corpus.js +92 -0
  232. package/dist/commands/validate/links/corpus.js.map +1 -0
  233. package/dist/commands/validate/links/glossary.js +75 -0
  234. package/dist/commands/validate/links/glossary.js.map +1 -0
  235. package/dist/commands/validate/report.js +269 -0
  236. package/dist/commands/validate/report.js.map +1 -0
  237. package/dist/commands/validate/service/api.js +338 -0
  238. package/dist/commands/validate/service/api.js.map +1 -0
  239. package/dist/commands/validate/service/events/events.js +219 -0
  240. package/dist/commands/validate/service/events/events.js.map +1 -0
  241. package/dist/commands/validate/service/events/message-producers.js +77 -0
  242. package/dist/commands/validate/service/events/message-producers.js.map +1 -0
  243. package/dist/commands/validate/service/evidence-pins.js +131 -0
  244. package/dist/commands/validate/service/evidence-pins.js.map +1 -0
  245. package/dist/commands/validate/service/service.js +251 -0
  246. package/dist/commands/validate/service/service.js.map +1 -0
  247. package/dist/commands/validate/service/specs.js +251 -0
  248. package/dist/commands/validate/service/specs.js.map +1 -0
  249. package/dist/commands/validate/service/spine.js +113 -0
  250. package/dist/commands/validate/service/spine.js.map +1 -0
  251. package/dist/commands/validate/validate.js +338 -0
  252. package/dist/commands/validate/validate.js.map +1 -0
  253. package/dist/commands/verify/cross/diff.js +182 -0
  254. package/dist/commands/verify/cross/diff.js.map +1 -0
  255. package/dist/commands/verify/evidence/contract.js +32 -0
  256. package/dist/commands/verify/evidence/contract.js.map +1 -0
  257. package/dist/commands/verify/evidence/pins.js +52 -0
  258. package/dist/commands/verify/evidence/pins.js.map +1 -0
  259. package/dist/commands/verify/evidence/read.js +72 -0
  260. package/dist/commands/verify/evidence/read.js.map +1 -0
  261. package/dist/commands/verify/frozen.js +241 -0
  262. package/dist/commands/verify/frozen.js.map +1 -0
  263. package/dist/commands/verify/record.js +226 -0
  264. package/dist/commands/verify/record.js.map +1 -0
  265. package/dist/commands/verify/report.js +223 -0
  266. package/dist/commands/verify/report.js.map +1 -0
  267. package/dist/commands/verify/results.js +246 -0
  268. package/dist/commands/verify/results.js.map +1 -0
  269. package/dist/commands/verify/verify.js +217 -0
  270. package/dist/commands/verify/verify.js.map +1 -0
  271. package/dist/commands/vouch/contract.js +11 -0
  272. package/dist/commands/vouch/contract.js.map +1 -0
  273. package/dist/commands/vouch/pack/contract.js +2 -0
  274. package/dist/commands/vouch/pack/contract.js.map +1 -0
  275. package/dist/commands/vouch/pack/pack.js +266 -0
  276. package/dist/commands/vouch/pack/pack.js.map +1 -0
  277. package/dist/commands/vouch/pack/print.js +271 -0
  278. package/dist/commands/vouch/pack/print.js.map +1 -0
  279. package/dist/commands/vouch/pack/sections.js +142 -0
  280. package/dist/commands/vouch/pack/sections.js.map +1 -0
  281. package/dist/commands/vouch/prompt/confirm.js +46 -0
  282. package/dist/commands/vouch/prompt/confirm.js.map +1 -0
  283. package/dist/commands/vouch/report.js +101 -0
  284. package/dist/commands/vouch/report.js.map +1 -0
  285. package/dist/commands/vouch/run.js +285 -0
  286. package/dist/commands/vouch/run.js.map +1 -0
  287. package/dist/commands/vouch/sample/plan.js +125 -0
  288. package/dist/commands/vouch/sample/plan.js.map +1 -0
  289. package/dist/commands/vouch/sample/print.js +109 -0
  290. package/dist/commands/vouch/sample/print.js.map +1 -0
  291. package/dist/commands/vouch/vet/verify.js +123 -0
  292. package/dist/commands/vouch/vet/verify.js.map +1 -0
  293. package/dist/commands/vouch/vouch.js +176 -0
  294. package/dist/commands/vouch/vouch.js.map +1 -0
  295. package/dist/core/agent/agents-md/artifacts.js +220 -0
  296. package/dist/core/agent/agents-md/artifacts.js.map +1 -0
  297. package/dist/core/agent/agents-md/command-map.js +324 -0
  298. package/dist/core/agent/agents-md/command-map.js.map +1 -0
  299. package/dist/core/agent/agents-md/cycle.js +165 -0
  300. package/dist/core/agent/agents-md/cycle.js.map +1 -0
  301. package/dist/core/agent/agents-md/map/explain.js +34 -0
  302. package/dist/core/agent/agents-md/map/explain.js.map +1 -0
  303. package/dist/core/agent/agents-md/map/lenses/context.js +41 -0
  304. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -0
  305. package/dist/core/agent/agents-md/map/lenses/diff.js +46 -0
  306. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -0
  307. package/dist/core/agent/agents-md/map/lenses/gate.js +57 -0
  308. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -0
  309. package/dist/core/agent/agents-md/map/mcp.js +41 -0
  310. package/dist/core/agent/agents-md/map/mcp.js.map +1 -0
  311. package/dist/core/agent/agents-md/map/status.js +62 -0
  312. package/dist/core/agent/agents-md/map/status.js.map +1 -0
  313. package/dist/core/agent/agents-md/map/subsystem.js +32 -0
  314. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -0
  315. package/dist/core/agent/agents-md/map/usecases.js +60 -0
  316. package/dist/core/agent/agents-md/map/usecases.js.map +1 -0
  317. package/dist/core/agent/agents-md/refusals.js +246 -0
  318. package/dist/core/agent/agents-md/refusals.js.map +1 -0
  319. package/dist/core/agent/agents-md/shipped/archive-gate.js +98 -0
  320. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +1 -0
  321. package/dist/core/agent/agents-md/spine.js +40 -0
  322. package/dist/core/agent/agents-md/spine.js.map +1 -0
  323. package/dist/core/agent/agents-md.js +170 -0
  324. package/dist/core/agent/agents-md.js.map +1 -0
  325. package/dist/core/{agents-stamp.js → agent/agents-stamp.js} +50 -0
  326. package/dist/core/agent/agents-stamp.js.map +1 -0
  327. package/dist/core/agent/contract.js +2 -0
  328. package/dist/core/agent/contract.js.map +1 -0
  329. package/dist/core/agent/protocol.js +193 -0
  330. package/dist/core/agent/protocol.js.map +1 -0
  331. package/dist/core/agent/scaffold.js +259 -0
  332. package/dist/core/agent/scaffold.js.map +1 -0
  333. package/dist/core/agent/support/report.js +103 -0
  334. package/dist/core/agent/support/report.js.map +1 -0
  335. package/dist/core/agent/tools/dialects.js +150 -0
  336. package/dist/core/agent/tools/dialects.js.map +1 -0
  337. package/dist/core/agent/tools/registry.js +228 -0
  338. package/dist/core/agent/tools/registry.js.map +1 -0
  339. package/dist/core/agent/workflows/adopt.js +133 -0
  340. package/dist/core/agent/workflows/adopt.js.map +1 -0
  341. package/dist/core/agent/workflows/check.js +337 -0
  342. package/dist/core/agent/workflows/check.js.map +1 -0
  343. package/dist/core/agent/workflows/closing.js +262 -0
  344. package/dist/core/agent/workflows/closing.js.map +1 -0
  345. package/dist/core/agent/workflows/feature.js +105 -0
  346. package/dist/core/agent/workflows/feature.js.map +1 -0
  347. package/dist/core/agent/workflows/implement.js +105 -0
  348. package/dist/core/agent/workflows/implement.js.map +1 -0
  349. package/dist/core/agent/workflows/reference/authoring.js +209 -0
  350. package/dist/core/agent/workflows/reference/authoring.js.map +1 -0
  351. package/dist/core/agent/workflows/reference/codes.js +65 -0
  352. package/dist/core/agent/workflows/reference/codes.js.map +1 -0
  353. package/dist/core/agent/workflows/reference/done-check.js +188 -0
  354. package/dist/core/agent/workflows/reference/done-check.js.map +1 -0
  355. package/dist/core/agent/workflows/reference/reference.js +16 -0
  356. package/dist/core/agent/workflows/reference/reference.js.map +1 -0
  357. package/dist/core/agent/workflows/reference/spine.js +297 -0
  358. package/dist/core/agent/workflows/reference/spine.js.map +1 -0
  359. package/dist/core/asyncapi/baseline/plan.js +110 -0
  360. package/dist/core/asyncapi/baseline/plan.js.map +1 -0
  361. package/dist/core/asyncapi/baseline/surfaces.js +54 -0
  362. package/dist/core/asyncapi/baseline/surfaces.js.map +1 -0
  363. package/dist/core/asyncapi/depth.js +164 -0
  364. package/dist/core/asyncapi/depth.js.map +1 -0
  365. package/dist/core/asyncapi/digest.js +141 -0
  366. package/dist/core/asyncapi/digest.js.map +1 -0
  367. package/dist/core/asyncapi/merge/components.js +91 -0
  368. package/dist/core/asyncapi/merge/components.js.map +1 -0
  369. package/dist/core/asyncapi/merge/error.js +33 -0
  370. package/dist/core/asyncapi/merge/error.js.map +1 -0
  371. package/dist/core/asyncapi/merge/markers.js +308 -0
  372. package/dist/core/asyncapi/merge/markers.js.map +1 -0
  373. package/dist/core/asyncapi/merge/merge.js +215 -0
  374. package/dist/core/asyncapi/merge/merge.js.map +1 -0
  375. package/dist/core/asyncapi/merge/pin.js +107 -0
  376. package/dist/core/asyncapi/merge/pin.js.map +1 -0
  377. package/dist/core/asyncapi/model.js +11 -0
  378. package/dist/core/asyncapi/model.js.map +1 -0
  379. package/dist/core/asyncapi/producers.js +50 -0
  380. package/dist/core/asyncapi/producers.js.map +1 -0
  381. package/dist/core/{asyncapi.js → asyncapi/read.js} +95 -109
  382. package/dist/core/asyncapi/read.js.map +1 -0
  383. package/dist/core/brief/brief.js +225 -0
  384. package/dist/core/brief/brief.js.map +1 -0
  385. package/dist/core/brief/checks.js +250 -0
  386. package/dist/core/brief/checks.js.map +1 -0
  387. package/dist/core/brief/landscape.js +194 -0
  388. package/dist/core/brief/landscape.js.map +1 -0
  389. package/dist/core/brief/targets.js +226 -0
  390. package/dist/core/brief/targets.js.map +1 -0
  391. package/dist/core/brief/unchecked.js +48 -0
  392. package/dist/core/brief/unchecked.js.map +1 -0
  393. package/dist/core/c4/arch.js +174 -0
  394. package/dist/core/c4/arch.js.map +1 -0
  395. package/dist/core/c4/likec4.js +90 -0
  396. package/dist/core/c4/likec4.js.map +1 -0
  397. package/dist/core/c4/parsed/deployment.js +158 -0
  398. package/dist/core/c4/parsed/deployment.js.map +1 -0
  399. package/dist/core/c4/parsed/dynamic-views.js +132 -0
  400. package/dist/core/c4/parsed/dynamic-views.js.map +1 -0
  401. package/dist/core/c4/parsed/specification.js +78 -0
  402. package/dist/core/c4/parsed/specification.js.map +1 -0
  403. package/dist/core/c4/parsed/values.js +57 -0
  404. package/dist/core/c4/parsed/values.js.map +1 -0
  405. package/dist/core/c4/parsed/view-ids.js +75 -0
  406. package/dist/core/c4/parsed/view-ids.js.map +1 -0
  407. package/dist/core/c4/project/architecture.js +45 -0
  408. package/dist/core/c4/project/architecture.js.map +1 -0
  409. package/dist/core/c4/project/documents.js +58 -0
  410. package/dist/core/c4/project/documents.js.map +1 -0
  411. package/dist/core/c4/project/load.js +187 -0
  412. package/dist/core/c4/project/load.js.map +1 -0
  413. package/dist/core/c4/project/staged.js +220 -0
  414. package/dist/core/c4/project/staged.js.map +1 -0
  415. package/dist/core/c4/resolve/service.js +159 -0
  416. package/dist/core/c4/resolve/service.js.map +1 -0
  417. package/dist/core/c4/resolve/steps.js +144 -0
  418. package/dist/core/c4/resolve/steps.js.map +1 -0
  419. package/dist/core/c4/seed/fleet-file.js +201 -0
  420. package/dist/core/c4/seed/fleet-file.js.map +1 -0
  421. package/dist/core/c4/seed/items.js +91 -0
  422. package/dist/core/c4/seed/items.js.map +1 -0
  423. package/dist/core/c4/seed/stamp.js +48 -0
  424. package/dist/core/c4/seed/stamp.js.map +1 -0
  425. package/dist/core/c4/seed/template.js +109 -0
  426. package/dist/core/c4/seed/template.js.map +1 -0
  427. package/dist/core/c4/source-mask.js +82 -0
  428. package/dist/core/c4/source-mask.js.map +1 -0
  429. package/dist/core/c4/source-scan.js +227 -0
  430. package/dist/core/c4/source-scan.js.map +1 -0
  431. package/dist/core/c4/splice/authored-source.js +101 -0
  432. package/dist/core/c4/splice/authored-source.js.map +1 -0
  433. package/dist/core/c4/splice/contract.js +10 -0
  434. package/dist/core/c4/splice/contract.js.map +1 -0
  435. package/dist/core/c4/splice/delta-blocks.js +139 -0
  436. package/dist/core/c4/splice/delta-blocks.js.map +1 -0
  437. package/dist/core/c4/splice/identity/edges.js +36 -0
  438. package/dist/core/c4/splice/identity/edges.js.map +1 -0
  439. package/dist/core/c4/splice/landscape-merge.js +269 -0
  440. package/dist/core/c4/splice/landscape-merge.js.map +1 -0
  441. package/dist/core/c4/splice/placement.js +194 -0
  442. package/dist/core/c4/splice/placement.js.map +1 -0
  443. package/dist/core/c4/workspace.js +174 -0
  444. package/dist/core/c4/workspace.js.map +1 -0
  445. package/dist/core/capabilities/authoring/notes.js +141 -0
  446. package/dist/core/capabilities/authoring/notes.js.map +1 -0
  447. package/dist/core/capabilities/capabilities.js +124 -0
  448. package/dist/core/capabilities/capabilities.js.map +1 -0
  449. package/dist/core/capabilities/delta/doc.js +50 -0
  450. package/dist/core/capabilities/delta/doc.js.map +1 -0
  451. package/dist/core/capabilities/delta/overlay.js +129 -0
  452. package/dist/core/capabilities/delta/overlay.js.map +1 -0
  453. package/dist/core/capabilities/delta/summary.js +57 -0
  454. package/dist/core/capabilities/delta/summary.js.map +1 -0
  455. package/dist/core/capabilities/delta/tree.js +43 -0
  456. package/dist/core/capabilities/delta/tree.js.map +1 -0
  457. package/dist/core/capabilities/delta/uncovered.js +222 -0
  458. package/dist/core/capabilities/delta/uncovered.js.map +1 -0
  459. package/dist/core/capabilities/findings.js +342 -0
  460. package/dist/core/capabilities/findings.js.map +1 -0
  461. package/dist/core/capabilities/realizes/findings.js +181 -0
  462. package/dist/core/capabilities/realizes/findings.js.map +1 -0
  463. package/dist/core/capabilities/realizes/join.js +143 -0
  464. package/dist/core/capabilities/realizes/join.js.map +1 -0
  465. package/dist/core/capabilities/rollup.js +199 -0
  466. package/dist/core/capabilities/rollup.js.map +1 -0
  467. package/dist/core/capabilities/tree.js +126 -0
  468. package/dist/core/capabilities/tree.js.map +1 -0
  469. package/dist/core/capabilities/usecase-join.js +277 -0
  470. package/dist/core/capabilities/usecase-join.js.map +1 -0
  471. package/dist/core/coherence/authoring/scaffold.js +177 -0
  472. package/dist/core/coherence/authoring/scaffold.js.map +1 -0
  473. package/dist/core/coherence/authoring/sentinels.js +74 -0
  474. package/dist/core/coherence/authoring/sentinels.js.map +1 -0
  475. package/dist/core/coherence/coherence.js +287 -0
  476. package/dist/core/coherence/coherence.js.map +1 -0
  477. package/dist/core/coherence/declared.js +269 -0
  478. package/dist/core/coherence/declared.js.map +1 -0
  479. package/dist/core/coherence/events/declared.js +206 -0
  480. package/dist/core/coherence/events/declared.js.map +1 -0
  481. package/dist/core/coherence/events/events.js +204 -0
  482. package/dist/core/coherence/events/events.js.map +1 -0
  483. package/dist/core/coherence/events/grades.js +166 -0
  484. package/dist/core/coherence/events/grades.js.map +1 -0
  485. package/dist/core/coherence/events/lookups.js +122 -0
  486. package/dist/core/coherence/events/lookups.js.map +1 -0
  487. package/dist/core/coherence/events/merged.js +40 -0
  488. package/dist/core/coherence/events/merged.js.map +1 -0
  489. package/dist/core/coherence/living.js +198 -0
  490. package/dist/core/coherence/living.js.map +1 -0
  491. package/dist/core/coherence/lookups.js +155 -0
  492. package/dist/core/coherence/lookups.js.map +1 -0
  493. package/dist/core/coherence/pending.js +39 -0
  494. package/dist/core/coherence/pending.js.map +1 -0
  495. package/dist/core/conflict-markers.js +60 -0
  496. package/dist/core/conflict-markers.js.map +1 -0
  497. package/dist/core/delta/claims.js +73 -0
  498. package/dist/core/delta/claims.js.map +1 -0
  499. package/dist/core/delta/delta.js +296 -0
  500. package/dist/core/delta/delta.js.map +1 -0
  501. package/dist/core/delta/document.js +177 -0
  502. package/dist/core/delta/document.js.map +1 -0
  503. package/dist/core/delta/scope.js +48 -0
  504. package/dist/core/delta/scope.js.map +1 -0
  505. package/dist/core/delta/select.js +237 -0
  506. package/dist/core/delta/select.js.map +1 -0
  507. package/dist/core/dependencies/dependencies.js +141 -0
  508. package/dist/core/dependencies/dependencies.js.map +1 -0
  509. package/dist/core/dependencies/facts.js +165 -0
  510. package/dist/core/dependencies/facts.js.map +1 -0
  511. package/dist/core/dependencies/fanin.js +88 -0
  512. package/dist/core/dependencies/fanin.js.map +1 -0
  513. package/dist/core/dependencies/graph.js +106 -0
  514. package/dist/core/dependencies/graph.js.map +1 -0
  515. package/dist/core/deployment/delta.js +188 -0
  516. package/dist/core/deployment/delta.js.map +1 -0
  517. package/dist/core/deployment/objects.js +145 -0
  518. package/dist/core/deployment/objects.js.map +1 -0
  519. package/dist/core/diff/base-git.js +148 -0
  520. package/dist/core/diff/base-git.js.map +1 -0
  521. package/dist/core/diff/base-state.js +209 -0
  522. package/dist/core/diff/base-state.js.map +1 -0
  523. package/dist/core/diff/current-state.js +96 -0
  524. package/dist/core/diff/current-state.js.map +1 -0
  525. package/dist/core/diff/scope/changed-paths.js +188 -0
  526. package/dist/core/diff/scope/changed-paths.js.map +1 -0
  527. package/dist/core/diff/semantic.js +243 -0
  528. package/dist/core/diff/semantic.js.map +1 -0
  529. package/dist/core/diff/victims.js +247 -0
  530. package/dist/core/diff/victims.js.map +1 -0
  531. package/dist/core/docs.js +7 -64
  532. package/dist/core/docs.js.map +1 -1
  533. package/dist/core/doctor/agents.js +222 -0
  534. package/dist/core/doctor/agents.js.map +1 -0
  535. package/dist/core/doctor/config.js +135 -0
  536. package/dist/core/doctor/config.js.map +1 -0
  537. package/dist/core/doctor/doctor.js +275 -0
  538. package/dist/core/doctor/doctor.js.map +1 -0
  539. package/dist/core/doctor/report.js +13 -0
  540. package/dist/core/doctor/report.js.map +1 -0
  541. package/dist/core/doctor/residue.js +110 -0
  542. package/dist/core/doctor/residue.js.map +1 -0
  543. package/dist/core/document/apply.js +77 -0
  544. package/dist/core/document/apply.js.map +1 -0
  545. package/dist/core/{frontmatter.js → document/frontmatter.js} +48 -2
  546. package/dist/core/document/frontmatter.js.map +1 -0
  547. package/dist/core/document/grammar/directives.js +143 -0
  548. package/dist/core/document/grammar/directives.js.map +1 -0
  549. package/dist/core/document/parse.js +263 -0
  550. package/dist/core/document/parse.js.map +1 -0
  551. package/dist/core/document/scenarios.js +221 -0
  552. package/dist/core/document/scenarios.js.map +1 -0
  553. package/dist/core/document/spec.js +241 -0
  554. package/dist/core/document/spec.js.map +1 -0
  555. package/dist/core/{config.js → envelope/config.js} +79 -18
  556. package/dist/core/envelope/config.js.map +1 -0
  557. package/dist/core/envelope/json.js +175 -0
  558. package/dist/core/envelope/json.js.map +1 -0
  559. package/dist/core/{version.js → envelope/version.js} +3 -3
  560. package/dist/core/envelope/version.js.map +1 -0
  561. package/dist/core/explain/families.js +175 -0
  562. package/dist/core/explain/families.js.map +1 -0
  563. package/dist/core/explain/fix-tables.js +178 -0
  564. package/dist/core/explain/fix-tables.js.map +1 -0
  565. package/dist/core/explain/lookup.js +181 -0
  566. package/dist/core/explain/lookup.js.map +1 -0
  567. package/dist/core/explain/refusals.js +49 -0
  568. package/dist/core/explain/refusals.js.map +1 -0
  569. package/dist/core/explain/terms.js +144 -0
  570. package/dist/core/explain/terms.js.map +1 -0
  571. package/dist/core/explore/describe.js +105 -0
  572. package/dist/core/explore/describe.js.map +1 -0
  573. package/dist/core/{explore.js → explore/explore.js} +50 -101
  574. package/dist/core/explore/explore.js.map +1 -0
  575. package/dist/core/fleet-context.js +120 -78
  576. package/dist/core/fleet-context.js.map +1 -1
  577. package/dist/core/gate/checks.js +201 -0
  578. package/dist/core/gate/checks.js.map +1 -0
  579. package/dist/core/gate/partners.js +205 -0
  580. package/dist/core/gate/partners.js.map +1 -0
  581. package/dist/core/gate/report.js +47 -0
  582. package/dist/core/gate/report.js.map +1 -0
  583. package/dist/core/gate/verification.js +113 -0
  584. package/dist/core/gate/verification.js.map +1 -0
  585. package/dist/core/gherkin/digest.js +60 -0
  586. package/dist/core/gherkin/digest.js.map +1 -0
  587. package/dist/core/gherkin/emit.js +177 -0
  588. package/dist/core/gherkin/emit.js.map +1 -0
  589. package/dist/core/gherkin/read.js +76 -0
  590. package/dist/core/gherkin/read.js.map +1 -0
  591. package/dist/core/gherkin/stale.js +234 -0
  592. package/dist/core/gherkin/stale.js.map +1 -0
  593. package/dist/core/gherkin/stamp.js +77 -0
  594. package/dist/core/gherkin/stamp.js.map +1 -0
  595. package/dist/core/gherkin/steps/catalogue.js +154 -0
  596. package/dist/core/gherkin/steps/catalogue.js.map +1 -0
  597. package/dist/core/gherkin/steps/inventory.js +98 -0
  598. package/dist/core/gherkin/steps/inventory.js.map +1 -0
  599. package/dist/core/gherkin/steps/phrase.js +72 -0
  600. package/dist/core/gherkin/steps/phrase.js.map +1 -0
  601. package/dist/core/glossary/delta.js +106 -0
  602. package/dist/core/glossary/delta.js.map +1 -0
  603. package/dist/core/glossary/tree.js +86 -0
  604. package/dist/core/glossary/tree.js.map +1 -0
  605. package/dist/core/kernel/concurrency.js.map +1 -0
  606. package/dist/core/{document-bytes.js → kernel/document-bytes.js} +1 -1
  607. package/dist/core/kernel/document-bytes.js.map +1 -0
  608. package/dist/core/kernel/fences.js +42 -0
  609. package/dist/core/kernel/fences.js.map +1 -0
  610. package/dist/core/kernel/ids/capability.js +72 -0
  611. package/dist/core/kernel/ids/capability.js.map +1 -0
  612. package/dist/core/kernel/ids/dirs.js +33 -0
  613. package/dist/core/kernel/ids/dirs.js.map +1 -0
  614. package/dist/core/kernel/ids/feature.js +75 -0
  615. package/dist/core/kernel/ids/feature.js.map +1 -0
  616. package/dist/core/kernel/ids/service.js +156 -0
  617. package/dist/core/kernel/ids/service.js.map +1 -0
  618. package/dist/core/kernel/ids/subsystem.js +63 -0
  619. package/dist/core/kernel/ids/subsystem.js.map +1 -0
  620. package/dist/core/{path-safety.js → kernel/path-safety.js} +18 -4
  621. package/dist/core/kernel/path-safety.js.map +1 -0
  622. package/dist/core/kernel/records.js.map +1 -0
  623. package/dist/core/links/backlinks.js +68 -0
  624. package/dist/core/links/backlinks.js.map +1 -0
  625. package/dist/core/links/case.js +74 -0
  626. package/dist/core/links/case.js.map +1 -0
  627. package/dist/core/links/corpus.js +104 -0
  628. package/dist/core/links/corpus.js.map +1 -0
  629. package/dist/core/links/findings.js +110 -0
  630. package/dist/core/links/findings.js.map +1 -0
  631. package/dist/core/links/parse.js +236 -0
  632. package/dist/core/links/parse.js.map +1 -0
  633. package/dist/core/mcp/argv.js +111 -0
  634. package/dist/core/mcp/argv.js.map +1 -0
  635. package/dist/core/mcp/author-tools.js +72 -0
  636. package/dist/core/mcp/author-tools.js.map +1 -0
  637. package/dist/core/mcp/framing.js +46 -0
  638. package/dist/core/mcp/framing.js.map +1 -0
  639. package/dist/core/mcp/protocol.js +280 -0
  640. package/dist/core/mcp/protocol.js.map +1 -0
  641. package/dist/core/mcp/tools.js +314 -0
  642. package/dist/core/mcp/tools.js.map +1 -0
  643. package/dist/core/obligations/obligations.js +106 -0
  644. package/dist/core/obligations/obligations.js.map +1 -0
  645. package/dist/core/openapi/baseline/gate.js +166 -0
  646. package/dist/core/openapi/baseline/gate.js.map +1 -0
  647. package/dist/core/openapi/baseline/plan.js +112 -0
  648. package/dist/core/openapi/baseline/plan.js.map +1 -0
  649. package/dist/core/openapi/baseline/record.js +175 -0
  650. package/dist/core/openapi/baseline/record.js.map +1 -0
  651. package/dist/core/openapi/depth.js +122 -0
  652. package/dist/core/openapi/depth.js.map +1 -0
  653. package/dist/core/openapi/digest.js +189 -0
  654. package/dist/core/openapi/digest.js.map +1 -0
  655. package/dist/core/{openapi.js → openapi/doc.js} +44 -106
  656. package/dist/core/openapi/doc.js.map +1 -0
  657. package/dist/core/openapi/merge/components.js +234 -0
  658. package/dist/core/openapi/merge/components.js.map +1 -0
  659. package/dist/core/openapi/merge/error.js +26 -0
  660. package/dist/core/openapi/merge/error.js.map +1 -0
  661. package/dist/core/openapi/merge/markers.js +208 -0
  662. package/dist/core/openapi/merge/markers.js.map +1 -0
  663. package/dist/core/openapi/merge/merge.js +256 -0
  664. package/dist/core/openapi/merge/merge.js.map +1 -0
  665. package/dist/core/openapi/merge/pin.js +128 -0
  666. package/dist/core/openapi/merge/pin.js.map +1 -0
  667. package/dist/core/openspec/decide/capabilities.js +244 -0
  668. package/dist/core/openspec/decide/capabilities.js.map +1 -0
  669. package/dist/core/openspec/decide/dispositions.js +63 -0
  670. package/dist/core/openspec/decide/dispositions.js.map +1 -0
  671. package/dist/core/openspec/decide/renames.js +179 -0
  672. package/dist/core/openspec/decide/renames.js.map +1 -0
  673. package/dist/core/openspec/decide/verdict.js +84 -0
  674. package/dist/core/openspec/decide/verdict.js.map +1 -0
  675. package/dist/core/openspec/inventory.js +221 -0
  676. package/dist/core/openspec/inventory.js.map +1 -0
  677. package/dist/core/openspec/model/mapping.js +76 -0
  678. package/dist/core/openspec/model/mapping.js.map +1 -0
  679. package/dist/core/openspec/model/model.js +65 -0
  680. package/dist/core/openspec/model/model.js.map +1 -0
  681. package/dist/core/openspec/scan/changes.js +235 -0
  682. package/dist/core/openspec/scan/changes.js.map +1 -0
  683. package/dist/core/openspec/scan/config.js +200 -0
  684. package/dist/core/openspec/scan/config.js.map +1 -0
  685. package/dist/core/openspec/scan/shape.js +260 -0
  686. package/dist/core/openspec/scan/shape.js.map +1 -0
  687. package/dist/core/openspec/scan/walk.js +133 -0
  688. package/dist/core/openspec/scan/walk.js.map +1 -0
  689. package/dist/core/openspec/scan/workspace.js +71 -0
  690. package/dist/core/openspec/scan/workspace.js.map +1 -0
  691. package/dist/core/owners/codeowners.js +141 -0
  692. package/dist/core/owners/codeowners.js.map +1 -0
  693. package/dist/core/pack/features.js +120 -0
  694. package/dist/core/pack/features.js.map +1 -0
  695. package/dist/core/pack/joins.js +112 -0
  696. package/dist/core/pack/joins.js.map +1 -0
  697. package/dist/core/pack/living.js +137 -0
  698. package/dist/core/pack/living.js.map +1 -0
  699. package/dist/core/pack/pack.js +76 -0
  700. package/dist/core/pack/pack.js.map +1 -0
  701. package/dist/core/permissions/permissions.js +87 -0
  702. package/dist/core/permissions/permissions.js.map +1 -0
  703. package/dist/core/projection/api.js +84 -0
  704. package/dist/core/projection/api.js.map +1 -0
  705. package/dist/core/projection/arch-slice.js +63 -0
  706. package/dist/core/projection/arch-slice.js.map +1 -0
  707. package/dist/core/projection/events.js +34 -0
  708. package/dist/core/projection/events.js.map +1 -0
  709. package/dist/core/provenance/findings.js +208 -0
  710. package/dist/core/provenance/findings.js.map +1 -0
  711. package/dist/core/provenance/git.js +225 -0
  712. package/dist/core/provenance/git.js.map +1 -0
  713. package/dist/core/provenance/gitq/moves.js +182 -0
  714. package/dist/core/provenance/gitq/moves.js.map +1 -0
  715. package/dist/core/provenance/gitq/vouched-ancestor.js +179 -0
  716. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -0
  717. package/dist/core/provenance/sample/scope.js +238 -0
  718. package/dist/core/provenance/sample/scope.js.map +1 -0
  719. package/dist/core/provenance/sample/sections.js +61 -0
  720. package/dist/core/provenance/sample/sections.js.map +1 -0
  721. package/dist/core/provenance/sources.js +263 -0
  722. package/dist/core/provenance/sources.js.map +1 -0
  723. package/dist/core/provenance/stamp.js +232 -0
  724. package/dist/core/provenance/stamp.js.map +1 -0
  725. package/dist/core/provenance/walk.js +157 -0
  726. package/dist/core/provenance/walk.js.map +1 -0
  727. package/dist/core/repo/authored/paths.js +210 -0
  728. package/dist/core/repo/authored/paths.js.map +1 -0
  729. package/dist/core/repo/entries.js +126 -0
  730. package/dist/core/repo/entries.js.map +1 -0
  731. package/dist/core/repo/paths.js +277 -0
  732. package/dist/core/repo/paths.js.map +1 -0
  733. package/dist/core/repo/repo.js +248 -0
  734. package/dist/core/repo/repo.js.map +1 -0
  735. package/dist/core/repo/service-target.js +153 -0
  736. package/dist/core/repo/service-target.js.map +1 -0
  737. package/dist/core/repo/state.js +58 -0
  738. package/dist/core/repo/state.js.map +1 -0
  739. package/dist/core/repo/tree/find.js +52 -0
  740. package/dist/core/repo/tree/find.js.map +1 -0
  741. package/dist/core/repo/tree/fs.js +64 -0
  742. package/dist/core/repo/tree/fs.js.map +1 -0
  743. package/dist/core/repo/tree/marker.js +61 -0
  744. package/dist/core/repo/tree/marker.js.map +1 -0
  745. package/dist/core/repo/tree/views.js +172 -0
  746. package/dist/core/repo/tree/views.js.map +1 -0
  747. package/dist/core/repo/tree/walk.js +234 -0
  748. package/dist/core/repo/tree/walk.js.map +1 -0
  749. package/dist/core/results.js +99 -2
  750. package/dist/core/results.js.map +1 -1
  751. package/dist/core/scaffold/landscape.js +131 -0
  752. package/dist/core/scaffold/landscape.js.map +1 -0
  753. package/dist/core/scaffold/readme.js +84 -0
  754. package/dist/core/scaffold/readme.js.map +1 -0
  755. package/dist/core/staging/commit.js +241 -0
  756. package/dist/core/staging/commit.js.map +1 -0
  757. package/dist/core/staging/interrupted.js +96 -0
  758. package/dist/core/staging/interrupted.js.map +1 -0
  759. package/dist/core/staging/lock.js +206 -0
  760. package/dist/core/staging/lock.js.map +1 -0
  761. package/dist/core/staging/recovery/finding.js +35 -0
  762. package/dist/core/staging/recovery/finding.js.map +1 -0
  763. package/dist/core/staging/recovery/intent.js +98 -0
  764. package/dist/core/staging/recovery/intent.js.map +1 -0
  765. package/dist/core/staging/recovery/recover.js +126 -0
  766. package/dist/core/staging/recovery/recover.js.map +1 -0
  767. package/dist/core/staging/recovery/residue.js +62 -0
  768. package/dist/core/staging/recovery/residue.js.map +1 -0
  769. package/dist/core/staging/snapshot.js +202 -0
  770. package/dist/core/staging/snapshot.js.map +1 -0
  771. package/dist/core/staging/txn/forward.js +171 -0
  772. package/dist/core/staging/txn/forward.js.map +1 -0
  773. package/dist/core/staging/txn/journal.js +139 -0
  774. package/dist/core/staging/txn/journal.js.map +1 -0
  775. package/dist/core/staging/txn/transaction.js +75 -0
  776. package/dist/core/staging/txn/transaction.js.map +1 -0
  777. package/dist/core/staging/writes.js +113 -0
  778. package/dist/core/staging/writes.js.map +1 -0
  779. package/dist/core/status/actions/execution.js +49 -0
  780. package/dist/core/status/actions/execution.js.map +1 -0
  781. package/dist/core/status/contracts.js +102 -0
  782. package/dist/core/status/contracts.js.map +1 -0
  783. package/dist/core/status/feature/artifacts.js +196 -0
  784. package/dist/core/status/feature/artifacts.js.map +1 -0
  785. package/dist/core/status/feature/feature.js +207 -0
  786. package/dist/core/status/feature/feature.js.map +1 -0
  787. package/dist/core/status/feature/next.js +150 -0
  788. package/dist/core/status/feature/next.js.map +1 -0
  789. package/dist/core/status/feature/state.js +21 -0
  790. package/dist/core/status/feature/state.js.map +1 -0
  791. package/dist/core/status/fleet/fleet.js +197 -0
  792. package/dist/core/status/fleet/fleet.js.map +1 -0
  793. package/dist/core/status/fleet/next.js +207 -0
  794. package/dist/core/status/fleet/next.js.map +1 -0
  795. package/dist/core/status/interrupted.js +97 -0
  796. package/dist/core/status/interrupted.js.map +1 -0
  797. package/dist/core/status/report.js +31 -0
  798. package/dist/core/status/report.js.map +1 -0
  799. package/dist/core/status/scan.js +41 -0
  800. package/dist/core/status/scan.js.map +1 -0
  801. package/dist/core/status/verification.js +201 -0
  802. package/dist/core/status/verification.js.map +1 -0
  803. package/dist/core/usecases/capability.js +169 -0
  804. package/dist/core/usecases/capability.js.map +1 -0
  805. package/dist/core/usecases/delta/claims.js +116 -0
  806. package/dist/core/usecases/delta/claims.js.map +1 -0
  807. package/dist/core/usecases/delta/flows.js +103 -0
  808. package/dist/core/usecases/delta/flows.js.map +1 -0
  809. package/dist/core/usecases/delta/overlay.js +72 -0
  810. package/dist/core/usecases/delta/overlay.js.map +1 -0
  811. package/dist/core/usecases/fleet.js +137 -0
  812. package/dist/core/usecases/fleet.js.map +1 -0
  813. package/dist/core/usecases/operations.js +111 -0
  814. package/dist/core/usecases/operations.js.map +1 -0
  815. package/dist/core/usecases/place.js +44 -0
  816. package/dist/core/usecases/place.js.map +1 -0
  817. package/dist/core/usecases/touch.js +101 -0
  818. package/dist/core/usecases/touch.js.map +1 -0
  819. package/dist/core/verify/answers.js +173 -0
  820. package/dist/core/verify/answers.js.map +1 -0
  821. package/dist/core/verify/build.js +150 -0
  822. package/dist/core/verify/build.js.map +1 -0
  823. package/dist/core/verify/checklist.js +201 -0
  824. package/dist/core/verify/checklist.js.map +1 -0
  825. package/dist/core/verify/claims/identity.js +61 -0
  826. package/dist/core/verify/claims/identity.js.map +1 -0
  827. package/dist/core/verify/cross/diff.js +97 -0
  828. package/dist/core/verify/cross/diff.js.map +1 -0
  829. package/dist/core/verify/evidence/contract.js +169 -0
  830. package/dist/core/verify/evidence/contract.js.map +1 -0
  831. package/dist/core/verify/file.js +257 -0
  832. package/dist/core/verify/file.js.map +1 -0
  833. package/dist/core/verify/pins/lint.js +157 -0
  834. package/dist/core/verify/pins/lint.js.map +1 -0
  835. package/dist/core/verify/pins/pin.js +95 -0
  836. package/dist/core/verify/pins/pin.js.map +1 -0
  837. package/dist/core/verify/record.js +142 -0
  838. package/dist/core/verify/record.js.map +1 -0
  839. package/dist/core/verify/store/commit.js +89 -0
  840. package/dist/core/verify/store/commit.js.map +1 -0
  841. package/dist/core/verify/store/render.js +57 -0
  842. package/dist/core/verify/store/render.js.map +1 -0
  843. package/dist/core/vocabulary/health.js +91 -0
  844. package/dist/core/vocabulary/health.js.map +1 -0
  845. package/dist/core/vocabulary/issue.js +58 -0
  846. package/dist/core/vocabulary/issue.js.map +1 -0
  847. package/dist/core/vocabulary/maturity.js +145 -0
  848. package/dist/core/vocabulary/maturity.js.map +1 -0
  849. package/dist/core/{report.js → vocabulary/report.js} +30 -2
  850. package/dist/core/vocabulary/report.js.map +1 -0
  851. package/dist/core/vocabulary/steps.js +266 -0
  852. package/dist/core/vocabulary/steps.js.map +1 -0
  853. package/dist/core/workspace/discover.js +239 -0
  854. package/dist/core/workspace/discover.js.map +1 -0
  855. package/dist/core/workspace/render.js +52 -0
  856. package/dist/core/workspace/render.js.map +1 -0
  857. package/examples/README.md +189 -0
  858. package/examples/docs/architecture/adrs/0001-transactional-outbox.md +33 -0
  859. package/examples/docs/architecture/capabilities.yaml +31 -0
  860. package/examples/docs/architecture/landscape.likec4 +280 -0
  861. package/examples/docs/architecture/obligations.yaml +23 -0
  862. package/examples/docs/architecture/permissions.yaml +50 -0
  863. package/examples/docs/architecture/subsystems.likec4 +11 -0
  864. package/examples/docs/architecture/usecases/order-notification.likec4 +65 -0
  865. package/examples/docs/capabilities/checkout/spec.md +55 -0
  866. package/examples/docs/capabilities/identity/tokens/spec.md +42 -0
  867. package/examples/docs/capabilities/order-notifications/spec.md +43 -0
  868. package/examples/docs/features/FEAT-101-payment-splitting/adrs/0001-dedicated-split-service.md +18 -0
  869. package/examples/docs/features/FEAT-101-payment-splitting/delta.likec4 +94 -0
  870. package/examples/docs/features/FEAT-101-payment-splitting/intent.md +44 -0
  871. package/examples/docs/features/FEAT-101-payment-splitting/specs/checkout-web/spec.md +21 -0
  872. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +129 -0
  873. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/spec.md +36 -0
  874. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/arch.spec.md +40 -0
  875. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +116 -0
  876. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/spec.md +55 -0
  877. package/examples/docs/features/FEAT-112-retire-order-v1/intent.md +35 -0
  878. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +28 -0
  879. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/spec.md +17 -0
  880. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/architecture/landscape.likec4 +253 -0
  881. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/order-service/spec.md +121 -0
  882. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/openapi.yaml +111 -0
  883. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/files/services/payment-service/spec.md +58 -0
  884. package/examples/docs/features/archive/FEAT-088-refunds/.loam-before/manifest.json +32 -0
  885. package/examples/docs/features/archive/FEAT-088-refunds/adrs/0001-refunds-stay-in-payment-service.md +26 -0
  886. package/examples/docs/features/archive/FEAT-088-refunds/delta.likec4 +28 -0
  887. package/examples/docs/features/archive/FEAT-088-refunds/intent.md +26 -0
  888. package/examples/docs/features/archive/FEAT-088-refunds/specs/order-service/spec.md +37 -0
  889. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +70 -0
  890. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/spec.md +23 -0
  891. package/examples/docs/features/archive/FEAT-088-refunds/verification.yaml +76 -0
  892. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/architecture/landscape.likec4 +275 -0
  893. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/asyncapi.yaml +96 -0
  894. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/payment-service/spec.md +82 -0
  895. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/asyncapi.yaml +164 -0
  896. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/files/services/platform/notification-service/spec.md +91 -0
  897. package/examples/docs/features/archive/FEAT-120-refund-notification/.loam-before/manifest.json +46 -0
  898. package/examples/docs/features/archive/FEAT-120-refund-notification/delta.likec4 +36 -0
  899. package/examples/docs/features/archive/FEAT-120-refund-notification/intent.md +22 -0
  900. package/examples/docs/features/archive/FEAT-120-refund-notification/scenario-report.json +10 -0
  901. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/asyncapi.yaml +208 -0
  902. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/notification-service/spec.md +24 -0
  903. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/asyncapi.yaml +137 -0
  904. package/examples/docs/features/archive/FEAT-120-refund-notification/specs/payment-service/spec.md +31 -0
  905. package/examples/docs/features/archive/FEAT-120-refund-notification/verification.yaml +84 -0
  906. package/examples/docs/glossary/order.md +15 -0
  907. package/examples/docs/glossary/payments/authorization.md +14 -0
  908. package/examples/docs/likec4.config.json +9 -0
  909. package/examples/docs/loam.json +3 -0
  910. package/examples/docs/services/checkout-web/model.likec4 +109 -0
  911. package/examples/docs/services/checkout-web/spec.md +91 -0
  912. package/examples/docs/services/checkout-web/ui/pages/checkout.page.yaml +40 -0
  913. package/examples/docs/services/checkout-web/ui/pages/order-status.page.yaml +24 -0
  914. package/examples/docs/services/checkout-web/ui/pages/split-payment.page.yaml +26 -0
  915. package/examples/docs/services/order-service/adrs/0001-orchestrated-order-lifecycle.md +52 -0
  916. package/examples/docs/services/order-service/arch.spec.md +127 -0
  917. package/examples/docs/services/order-service/asyncapi.yaml +108 -0
  918. package/examples/docs/services/order-service/health.yaml +57 -0
  919. package/examples/docs/services/order-service/model.likec4 +138 -0
  920. package/examples/docs/services/order-service/openapi.yaml +257 -0
  921. package/examples/docs/services/order-service/runbook.md +62 -0
  922. package/examples/docs/services/order-service/spec.md +134 -0
  923. package/examples/docs/services/payment-service/adrs/0001-payments-outbox.md +18 -0
  924. package/examples/docs/services/payment-service/adrs/0002-idempotency-keys-on-authorization.md +27 -0
  925. package/examples/docs/services/payment-service/arch.spec.md +73 -0
  926. package/examples/docs/services/payment-service/asyncapi.yaml +130 -0
  927. package/examples/docs/services/payment-service/health.yaml +39 -0
  928. package/examples/docs/services/payment-service/model.likec4 +67 -0
  929. package/examples/docs/services/payment-service/openapi.yaml +151 -0
  930. package/examples/docs/services/payment-service/runbook.md +37 -0
  931. package/examples/docs/services/payment-service/spec.md +88 -0
  932. package/examples/docs/services/platform/identity-service/adrs/0001-token-introspection-over-local-validation.md +60 -0
  933. package/examples/docs/services/platform/identity-service/arch.spec.md +117 -0
  934. package/examples/docs/services/platform/identity-service/health.yaml +70 -0
  935. package/examples/docs/services/platform/identity-service/model.likec4 +83 -0
  936. package/examples/docs/services/platform/identity-service/openapi.yaml +197 -0
  937. package/examples/docs/services/platform/identity-service/runbook.md +95 -0
  938. package/examples/docs/services/platform/identity-service/spec.md +119 -0
  939. package/examples/docs/services/platform/notification-service/asyncapi.yaml +199 -0
  940. package/examples/docs/services/platform/notification-service/health.yaml +35 -0
  941. package/examples/docs/services/platform/notification-service/model.likec4 +118 -0
  942. package/examples/docs/services/platform/notification-service/runbook.md +48 -0
  943. package/examples/docs/services/platform/notification-service/spec.md +112 -0
  944. package/examples/docs/services/platform/subsystem.yaml +2 -0
  945. package/examples/fleet.yaml +57 -0
  946. package/package.json +19 -6
  947. package/dist/commands/adopt.js +0 -240
  948. package/dist/commands/adopt.js.map +0 -1
  949. package/dist/commands/archive.js +0 -1313
  950. package/dist/commands/archive.js.map +0 -1
  951. package/dist/commands/delta.js +0 -388
  952. package/dist/commands/delta.js.map +0 -1
  953. package/dist/commands/docs-repo-gate.js.map +0 -1
  954. package/dist/commands/format.js +0 -17
  955. package/dist/commands/format.js.map +0 -1
  956. package/dist/commands/gherkin.js +0 -309
  957. package/dist/commands/gherkin.js.map +0 -1
  958. package/dist/commands/init.js.map +0 -1
  959. package/dist/commands/list.js +0 -295
  960. package/dist/commands/list.js.map +0 -1
  961. package/dist/commands/migrate-openspec.js +0 -950
  962. package/dist/commands/migrate-openspec.js.map +0 -1
  963. package/dist/commands/new.js +0 -413
  964. package/dist/commands/new.js.map +0 -1
  965. package/dist/commands/rebase.js +0 -356
  966. package/dist/commands/rebase.js.map +0 -1
  967. package/dist/commands/show.js.map +0 -1
  968. package/dist/commands/status.js +0 -262
  969. package/dist/commands/status.js.map +0 -1
  970. package/dist/commands/unarchive.js.map +0 -1
  971. package/dist/commands/validate.js +0 -1773
  972. package/dist/commands/validate.js.map +0 -1
  973. package/dist/commands/verify.js +0 -775
  974. package/dist/commands/verify.js.map +0 -1
  975. package/dist/commands/vouch.js +0 -421
  976. package/dist/commands/vouch.js.map +0 -1
  977. package/dist/core/agent.js +0 -2173
  978. package/dist/core/agent.js.map +0 -1
  979. package/dist/core/agents-stamp.js.map +0 -1
  980. package/dist/core/arch.js +0 -130
  981. package/dist/core/arch.js.map +0 -1
  982. package/dist/core/asyncapi.js.map +0 -1
  983. package/dist/core/brief.js +0 -755
  984. package/dist/core/brief.js.map +0 -1
  985. package/dist/core/coherence.js +0 -577
  986. package/dist/core/coherence.js.map +0 -1
  987. package/dist/core/concurrency.js.map +0 -1
  988. package/dist/core/config.js.map +0 -1
  989. package/dist/core/delta.js +0 -476
  990. package/dist/core/delta.js.map +0 -1
  991. package/dist/core/dependencies.js +0 -356
  992. package/dist/core/dependencies.js.map +0 -1
  993. package/dist/core/doctor.js +0 -596
  994. package/dist/core/doctor.js.map +0 -1
  995. package/dist/core/document-bytes.js.map +0 -1
  996. package/dist/core/explore.js.map +0 -1
  997. package/dist/core/frontmatter.js.map +0 -1
  998. package/dist/core/gherkin.js +0 -463
  999. package/dist/core/gherkin.js.map +0 -1
  1000. package/dist/core/health.js +0 -67
  1001. package/dist/core/health.js.map +0 -1
  1002. package/dist/core/ids.js +0 -122
  1003. package/dist/core/ids.js.map +0 -1
  1004. package/dist/core/issue.js +0 -9
  1005. package/dist/core/issue.js.map +0 -1
  1006. package/dist/core/json.js +0 -74
  1007. package/dist/core/json.js.map +0 -1
  1008. package/dist/core/likec4.js +0 -489
  1009. package/dist/core/likec4.js.map +0 -1
  1010. package/dist/core/maturity.js +0 -66
  1011. package/dist/core/maturity.js.map +0 -1
  1012. package/dist/core/openapi-merge.js +0 -552
  1013. package/dist/core/openapi-merge.js.map +0 -1
  1014. package/dist/core/openapi.js.map +0 -1
  1015. package/dist/core/openspec-inventory.js +0 -1440
  1016. package/dist/core/openspec-inventory.js.map +0 -1
  1017. package/dist/core/path-safety.js.map +0 -1
  1018. package/dist/core/provenance.js +0 -787
  1019. package/dist/core/provenance.js.map +0 -1
  1020. package/dist/core/records.js.map +0 -1
  1021. package/dist/core/repo.js +0 -438
  1022. package/dist/core/repo.js.map +0 -1
  1023. package/dist/core/report.js.map +0 -1
  1024. package/dist/core/spec.js +0 -505
  1025. package/dist/core/spec.js.map +0 -1
  1026. package/dist/core/staging.js +0 -890
  1027. package/dist/core/staging.js.map +0 -1
  1028. package/dist/core/status.js +0 -1132
  1029. package/dist/core/status.js.map +0 -1
  1030. package/dist/core/steps.js +0 -72
  1031. package/dist/core/steps.js.map +0 -1
  1032. package/dist/core/verify.js +0 -807
  1033. package/dist/core/verify.js.map +0 -1
  1034. package/dist/core/version.js.map +0 -1
  1035. /package/dist/core/{concurrency.js → kernel/concurrency.js} +0 -0
  1036. /package/dist/core/{records.js → kernel/records.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,175 +1,2719 @@
1
1
  # Changelog
2
2
 
3
- All notable project changes are recorded here. The format follows Keep a Changelog, and release versions follow Semantic Versioning.
3
+ All notable project changes are recorded here. The format follows Keep a Changelog, and release
4
+ versions follow Semantic Versioning.
4
5
 
5
6
  ## [Unreleased]
6
7
 
8
+ _Each entry states the change and names every code, flag, path and payload key it introduced. The
9
+ case for a change — the alternative that was rejected, the defect it came from, the rule it
10
+ generalises — lives where it is maintained: [SCHEMA.md](SCHEMA.md) for the rule,
11
+ [ROADMAP.md](ROADMAP.md) for the priority and its exit criteria, and the commit that landed it._
12
+
13
+ ## [0.2.0-alpha.2] - 2026-09-02
14
+
15
+ The deployment axis, and the two defects the axis surfaced on its way in. Still a prerelease on the
16
+ `alpha` line: `npm i @ybotok/loam@alpha` installs it. Nothing below is breaking, but two entries are
17
+ changes a repo can NOTICE — a fleet that had already placed a `#obl-` tag on a `deploymentNode`
18
+ starts seeing findings it was never shown, and `loam status` stops asking an architecture-only
19
+ feature for a business spec.
20
+
21
+ ### Added — the deployment axis: topology joined to requirements
22
+
23
+ A LikeC4 `deployment { }` block has always been legal in a docs repo and was completely unread. The
24
+ parser resolved every `instanceOf`, and after that no requirement could name a node, no `#obl-` tag
25
+ on a datacenter was graded, and the context pack an agent implements from did not mention topology
26
+ at all. Five things change, and a fleet that draws no topology is unaffected by every one of them —
27
+ the axis opts in on the block's existence, the way the obligation vocabulary opts in on its file.
28
+
29
+ - **`Covers:` gains a fourth entry form.** `node:<id>` names a deployment node or an instance, and
30
+ `node:a -> node:b` names an edge between them; both match literally. This is the form that lets an
31
+ architecture requirement about replication name the two clusters it is written about, and a
32
+ topology change that renames one now reports `covers.unknown` with the real ids offered — spelled
33
+ with the prefix, because that is the line the author has to type. Both sides of an edge carry the
34
+ prefix or neither; a bare id stays an element entry.
35
+ - **An obligation on a deployment object is graded.** `obligation.unknown`, `obligation.unapplied`
36
+ and `obligation.uncovered` now walk the topology as well as the logical model. This is a
37
+ behaviour change a repo can notice: a `#obl-` tag on a `deploymentNode` used to be invisible to
38
+ every check, so a fleet that had placed one starts seeing findings it was never shown. A
39
+ datacenter is owned by no service, so `obligation.uncovered` drops `subject` there and names the
40
+ requirement's home in general terms rather than picking a team.
41
+ - **A feature can bring topology.** `features/<FEAT>/deployment/<name>.likec4` is a create-only
42
+ document `loam archive` copies into `architecture/` and `loam unarchive` takes back — the third
43
+ whole-file slot, beside `usecases/` and `glossary/`. Say `extend` in it to add to a region the
44
+ living map already declares; two features may extend one region from documents of their own and
45
+ both archive. Two new codes, both errors and neither overridable: **`deployment.doc-exists`** when
46
+ the feature's document names a file the living tree already holds, and **`deployment.doc-invalid`**
47
+ when it cannot be read against the map the feature's own merge would leave behind. The second is
48
+ what makes the ordinary case legal — a document instancing a service only this feature's
49
+ `delta.likec4` adds resolves, because the corpus inside a feature window is feature ∪ living — and
50
+ refuses the case where the merge does not land what the document names. `delta.likec4` still
51
+ refuses a `deployment { }` block and now names this slot instead of the living landscape.
52
+ A third, **`deployment.doc-reserved`**, refuses a name loam owns inside `architecture/` —
53
+ `subsystems.likec4`, `landscape.likec4`, or a path under `usecases/` — whether or not the target
54
+ exists, because two of the three fail silently when it does not: nothing parses the generated
55
+ subsystem views and `loam subsystem sync` overwrites them wholesale.
56
+ - **`loam context` carries the topology.** `--json` gains `living.deployment` — the service's
57
+ instances, and the deployment edges touching them from either end.
58
+ - **One more statement in the adoption brief's unchecked list**, now sixteen: nothing here knows
59
+ whether the second cluster exists, is reachable, or holds the data a requirement claims, and no
60
+ check counts datacenters — how many a service needs is a decision the fleet writes down as a
61
+ requirement, never one loam evaluates.
62
+
63
+ ### Fixed — the never-overridable archive refusal names what actually refused
64
+
65
+ It said `N element binding(s) name an illegal service id` for every code in that register, and had
66
+ done since the use-case slot put a second one there — about features that bind nothing. The headline
67
+ now counts the refusals and the closing line names their codes; each issue keeps its own sentence.
68
+ The `--json` envelope is unchanged: consumers branch on `issues[].code` and `overridable`, which is
69
+ what that split exists for.
70
+
71
+ ### Fixed — `loam status` no longer invents work for an architecture-only feature
72
+
73
+ A feature whose only requirement delta is `specs/<svc>/arch.spec.md` reported two things that were
74
+ not true, and both are corrected. The `spec` row read `missing` and `next.author-spec` told the
75
+ author to write business requirements the change does not have — while `loam validate --feature`
76
+ accepted the same tree at exit 0 and `coherence`'s `service.no-requirement-delta` stayed silent,
77
+ because every other reader counts the service DIRECTORY and not which corpus is in it. And the
78
+ `verification` row read `blocked` with `blockedBy: ["delta", "spec", "openapi", "asyncapi"]`,
79
+ telling the reader there was nothing to derive a checklist from — while `featureChecklist` reads
80
+ `arch.spec.md` and `loam verify` on that feature returns a real checklist.
81
+
82
+ The fleet form carried the same defect independently, and it broke the rule stated one line below
83
+ it in its own source: `loam status` with no feature reported `missing <svc>/spec` and staged the
84
+ feature `missing`, over a feature `loam status <FEAT>` called `ready`. Both forms now accept either
85
+ corpus as the service's requirement delta, and `<svc>/spec` is named only when neither exists.
86
+
87
+ Three payload-visible consequences, all in `loam status --json`: `artifacts[]`'s `spec` row is
88
+ `required: false` when the service's `arch.spec.md` delta exists (it was unconditionally `true`),
89
+ `arch-spec` now counts as a verification feeder, so `blockedBy` gained `"arch-spec"` in the
90
+ one case that is still genuinely blocked — a feature with no requirement delta of either kind —
91
+ and `features[]`'s `missing` and `stage` no longer report an architecture-only feature as
92
+ incomplete. No finding code, flag or exit code changes. This is the change to read if a script
93
+ branched on `artifacts[].required` or on `features[].missing`.
94
+
95
+ The case is the one `arch.spec.md` exists for: an outbox, a retry policy, an alert — obligations no
96
+ business scenario was ever going to carry, so the feature that adds one has no business requirement
97
+ to write. Found by authoring loam's own architecture change through loam's own lifecycle, which is
98
+ the trigger ROADMAP's self-hosting section records for that axis.
99
+
100
+ ## [0.2.0-alpha.1] - 2026-09-01
101
+
102
+ The first release of the 0.2 line, and the whole accumulated body since `0.1.0-beta.3` — 116
103
+ entries, 53 of them additions. It opens a new line at `alpha` rather than continuing `0.1.0-beta.4`
104
+ because the surface moved that far: the use-case, business, glossary and obligations axes all
105
+ landed, and the agent interface became a dual-entry contract of generated commands and skills.
106
+
107
+ Still a prerelease, and the 5–10 service pilot still has not run. The entries below describe
108
+ implementation behavior and repository-owned synthetic fixtures; they claim no production-fleet or
109
+ external execution evidence.
110
+
111
+ **It publishes under the `alpha` npm dist-tag**, so `npm i @ybotok/loam@alpha` — or the exact
112
+ version — installs this one. `latest` is moved onto a release by hand once it has published, and
113
+ until it is, a plain `npm i @ybotok/loam` still resolves to `0.1.0-beta.3`. No
114
+ entry below is marked breaking, but a repository upgrading from beta.3 should read the `Changed`
115
+ sections first: the view doctrine narrowed, a use case became a consumer (`loam diff` refuses where
116
+ it warned), and the scaffolded `AGENTS.md` became a pointer. Every generated file stamped by an
117
+ earlier loam now reports against this binary — `doctor.agent-files-stale` for the command and skill
118
+ files, `agents.stale` for a docs repo's `AGENTS.md`. Both are detection, not damage: a file whose
119
+ recorded digest still matches is refreshed by re-running `loam init`, and a customized one stays
120
+ yours to review and re-stamp by hand.
121
+
122
+ ### Security — the private reporting route is live, and every document says so
123
+
124
+ GitHub Private Vulnerability Reporting is enabled on `ybotok/loam`, verified from the repository's
125
+ PUBLIC advisories page — the same view an outside researcher gets — rather than taken on trust.
126
+ `SECURITY.md` now names [the advisory form](https://github.com/ybotok/loam/security/advisories/new),
127
+ says what to include and what to leave out, and keeps the one warning the old text was right about:
128
+ a GitHub issue, pull request or discussion is public the moment it is opened. The
129
+ `private-security-route` release blocker and the detail-free-issue fallback are gone — the fallback
130
+ existed only while the private form did not, and leaving it standing would offer a public route
131
+ beside a private one. `test/docs-drift.test.ts` inverts and keeps the blocker constant, so absence
132
+ is now the invariant.
133
+
134
+ ### Added — the agent interface is progressive, executable and ownership-aware
135
+
136
+ - **`QUICKSTART.md` now ships in the npm package.** It gives the shortest complete path from
137
+ install through adoption and the first explicit agent-driven feature, with the host-specific
138
+ command spellings and the human-only vouch/archive boundaries in one place.
139
+ - **`loam-report` is a generated command and Agent Skill in every profile.** It collects an
140
+ unexpected binary or agent-integration run into
141
+ `loam-reports/YYYY-MM-DD-<short-slug>.md`: version, expected/actual behavior, stable codes and
142
+ locations, doctor/status and write-state evidence, minimal safe reproduction, classification and
143
+ missing evidence. The protocol redacts secrets and home paths, never retries a writer merely to
144
+ reproduce it, and never repairs, uploads or submits anything automatically. Bare
145
+ `loam instructions --json` adds the support entry under the additive `support` key; the six-item
146
+ `workflows` array remains the lifecycle contract.
147
+ - **The documented chat contract is explicitly dual-entry.** The generated command is the
148
+ recommended predictable path (`loam-feature` → `loam-implement` → `loam-check` → `loam-verify`
149
+ → `loam-ship`), while natural language may load the matching Agent Skill. Both deliveries retain
150
+ one shared body and defer to the same binary-owned `loam instructions` page; no command or skill
151
+ name changed.
152
+ - **The always-loaded generated `AGENTS.md` is now 8,793 bytes**, measured on the file `loam init
153
+ --create` actually writes. Detailed workflow, authoring and
154
+ code material stays behind `loam instructions`; `loam-check` pointers start with
155
+ `--no-fix-tables` and fetch individual fixes through `loam explain <code>`.
156
+ - **`loam status --json` adds `next[].execution`.** It distinguishes runnable commands from edits,
157
+ external-repository work and human review; carries `cwd`; and never presents a command containing
158
+ unresolved `<placeholders>` as runnable. The original `command` field remains compatible.
159
+ - **Serialized findings add `locations[]`, and validate targets add `path`.** Producers may emit
160
+ exact file/line/pointer locations; otherwise the shared serializer supplies the narrowest proved
161
+ service, feature or landscape scope. Codes and message text are unchanged.
162
+ - **`loam show <FEAT> --json` adds `review`.** The pack joins the intent excerpt, tagged C4 objects,
163
+ API/event changes, dependencies, artifact and verification state, use cases, blockers, advisories
164
+ and executable next actions from the existing semantic projections.
165
+ - **`loam init` adds `--agent-profile <full|service|docs>` and safe refresh.** `loam.json` gains
166
+ additive `agentProfile` and `agentFiles` fields. An unchanged generated command/skill pointer is
167
+ refreshed when its recorded sha256 still matches; a customized file is never overwritten.
168
+ - **`loam mcp` adds resources, a common output schema and opt-in `--author`.** Orientation and every
169
+ workflow/support/reference page are readable at `loam://orientation` and
170
+ `loam://instructions/<name>`. `--author` adds `loam_new`, `loam_rebase`, `loam_gherkin` and
171
+ `loam_archive_plan` (server-enforced `--dry-run`); `vouch`, verification recording and a committing
172
+ archive remain excluded. `loam init --mcp-author` writes this launch mode.
173
+
174
+ ### Changed — the product boundary is a governed system, not a microservice topology
175
+
176
+ - **The public position is now “architecture-first semantic integrity and change governance for
177
+ evolving software systems.”** README and npm metadata no longer define loam as a microservice-only
178
+ framework. Distributed service fleets remain the richest contract-spine case; modular monoliths
179
+ and hybrid systems take the same lifecycle without pretending their internal modules are network
180
+ services.
181
+ - **The existing `service` vocabulary is defined, not renamed.** In flags, paths, config and stable
182
+ codes it remains the frozen term for one governed implementation boundary. A modular monolith uses
183
+ one `services/<app>/` directory and one bound C4 root; nested modules participate in feature
184
+ deltas and `Covers:` while fleet maturity, dependency and scorecard views continue to aggregate at
185
+ the bound service level. No command, flag, path, envelope field or stable code changed.
186
+ - **README, WORKFLOW, SCHEMA, the ecosystem overview and the generated docs-repo README and agent
187
+ contract carry the same topology contract.** They distinguish modular, distributed and hybrid
188
+ shapes, show the nested C4 spelling, and state the current module-level reporting limit rather
189
+ than turning the broader position into a claim the binary does not make.
190
+ - **The ecosystem page no longer defines loam against OpenSpec.** It now gives short, non-adversarial
191
+ boundaries with architecture-as-code tools, catalogs, contract tooling, traceability systems and
192
+ code-level architecture checks. OpenSpec remains documented only where the product has a real
193
+ compatibility concern: the one-way migration guide and its pinned parser corpus.
194
+
195
+ ### Fixed — a Windows checkout read the generated subsystem views file as stale
196
+
197
+ - **The generated `architecture/subsystems.likec4` is now compared by content, not by bytes, in all
198
+ four places that ask the question.** `renderSubsystemViews` only ever emits LF; `core.autocrlf=true`
199
+ is Git for Windows' installer default and a docs repo ships no `.gitattributes`, so an ordinary
200
+ Windows clone got the file back with CRLF and not one fact in it changed. Under a byte compare
201
+ `loam validate --all` reported one error, and the repair the finding advertises made it worse:
202
+ `loam subsystem sync` answered `updated`, rewrote with LF, git then showed the file permanently
203
+ modified, and the next checkout restored the CRLF — a loop with no stable exit. The rule now lives
204
+ once, as `viewsAgree` beside the generator that mints the bytes, and `loam validate`,
205
+ `loam seed`, `loam subsystem sync` and every subsystem transaction take it. The compare is still
206
+ never a parse, absent / must-be-absent / mismatched are still three distinct messages, and a real
207
+ content edit under CRLF is still `subsystem.views-stale`. Measured on this repository's own
208
+ `examples/docs`: 1 error to 0, with no other check moving.
209
+ - **`loam seed` and `loam subsystem move` no longer journal a no-op rewrite of that file** on such a
210
+ clone. What gets written when the file really has moved is unchanged.
211
+
212
+ ### Fixed — `loam status`'s first rung sent the agent to the gate
213
+
214
+ - **`next.author-landscape` now carries the command its own statement names** — `loam init --create`
215
+ when `architecture/landscape.likec4` is absent, `loam seed --from fleet.yaml` when it is still the
216
+ scaffold's untouched stub — where both arms used to carry `loam validate --all --json`. Over a
217
+ repository with no services `validate --all` answers `ok: true` with `landscape.matched`, so an
218
+ agent running the documented loop (WORKFLOW.md: read `next[0]`, run the command it names, repeat)
219
+ changed nothing and read back the identical `next[0]` on every pass: a livelock on the first rung
220
+ of the first hour, on the one command built to answer "what now". No new `next.*` code and no new
221
+ payload key.
222
+ - **`next.fleet-gate`'s statement writes `` `loam validate --all` `` where it wrote
223
+ `` `validate --all` ``**, so the sentence is visible to the checks that read a backticked
224
+ `loam <verb>` span as an instruction rather than exempt from them.
225
+
226
+ ### Fixed — `loam gherkin` and `loam vouch` said `invalid-option` when the flags were fine
227
+
228
+ - **A wrong-repo refusal now carries a wrong-repo code.** `loam gherkin --service <id>` and
229
+ `loam vouch --service <id>` run from the docs repo, or from a repo bound to a different service,
230
+ both refused under `invalid-option` — whose `loam explain` row says the invocation itself is
231
+ wrong, which was false for both and sent a user whose flags were correct off to re-read their
232
+ flags. Each now emits the pair `loam verify --record --service` has always emitted for the
233
+ identical condition: **`repository-unavailable`** when loam.json declares no `service` at all, and
234
+ **`service-mismatch`** when it declares a different one. Both human-facing messages are unchanged,
235
+ exit stays 1, the `--json` envelope is unchanged, and no code is new. Refusing with no service
236
+ named anywhere is still `invalid-option`; that one really is a bad invocation.
237
+ - **`loam explain service-mismatch` and `loam explain repository-unavailable` name all three
238
+ commands** that can now answer with them, where both rows described only `loam verify`.
239
+ - **The generated `AGENTS.md` lists the two codes on the `gherkin` and `vouch` rows.**
240
+
241
+ ### Fixed — the example fleet could not be run
242
+
243
+ - **`examples/docs/` now carries its own `loam.json`**, holding the same `{ "docsDir": "." }` that
244
+ `loam init --docs . --create` writes for a docs repo that governs itself. `cd examples/docs &&
245
+ loam status` used to answer "No loam.json found" and exit 1, and the workaround both READMEs
246
+ printed — a throwaway `loam.json` at the loam repository root — then governed every directory
247
+ under it, so `loam init` from a sibling refused with "loam.json already governs this directory"
248
+ and forgetting to delete it silently redirected later commands at the example fleet.
249
+ - **`examples/README.md`'s runnable block is now `cd examples/docs` plus the installed binary**,
250
+ with no file to write first and none to delete afterwards, and it names the from-source
251
+ substitute (`npx tsx ../../src/cli.ts`) explicitly. `test/examples.test.ts` pins the file the
252
+ walkthrough depends on; neither archive dry-run plan enumerates it.
253
+
254
+ ### Fixed — three README claims the tree had stopped supporting
255
+
256
+ - **The Windows caveat under "Explore the example fleet" is gone.** It advised a re-clone with
257
+ `core.autocrlf=false` — advice that never transferred to the reader's own docs repo, where the
258
+ same defect lived and no re-clone was on offer. The comparison is by content now, so the caveat is
259
+ simply false.
260
+ - **"Explore the example fleet" is `cd examples/docs` plus the installed binary**, the same commands
261
+ in the same order. ROADMAP's claim that the throwaway root config is the documented first
262
+ experience is dropped with it.
263
+ - **The commands table said `--errors-only` "prints just the errors". It never did.** The report
264
+ drops only the `ok` confirmations, so every warning survives the filter. The row now carries the
265
+ sentence the workflow protocol and the generated `AGENTS.md` already use, and
266
+ `test/agent-contract.test.ts` pins it against both the corrected wording and the old claim. No
267
+ flag changed and no behaviour moved.
268
+
269
+ ### Added — three narrowing flags, because the agent-facing payloads had outgrown the work they describe
270
+
271
+ - **`loam instructions [<workflow>] --no-fix-tables`.** Every generated command and skill file says
272
+ "run this first" and points at `loam instructions loam-check`, which was **83,731 bytes** — about
273
+ 21k tokens, 223 fix-table rows — with no way to narrow it. The flag collapses each
274
+ `| code | what it means | what to do |` block to one line naming how many rows went and how to get
275
+ them back, and the page becomes **3,541 bytes**; `--json` goes 84,201 to 3,769, the narrowed text
276
+ riding in the existing `body` field so no envelope key is added or renamed. `/loam-verify`'s
277
+ notice table strips through the same path (12,439 to 8,839); the four protocols carrying no table
278
+ come back untouched. **The paragraph that introduces each table stays** — it is what says which
279
+ scope graded a code, and `spec.merge-conflict` means different things in two of them. Off by
280
+ default, so all six protocols are byte-identical without it, asserted against the shipped bodies
281
+ rather than claimed.
282
+ - **The stripper shares the parser's block detection** (`withoutFixTables` beside `parseFixRows` in
283
+ `src/core/explain/fix-tables.ts`) rather than carrying a regex of its own. A stripper that
284
+ disagreed with the parser about where a table begins would ship a page that reads perfectly and
285
+ sends an agent to look up a code nothing explains; a test asserts every code the narrowing removes
286
+ still resolves through `explainSubject`.
287
+ - **A generated command and skill file now says how to READ the protocol, not only to run it** — add
288
+ `--no-fix-tables`, then `loam explain <code>` for each code the run actually reports. One
289
+ sentence; the stub stays under 2.3 KB. Existing generated files are never rewritten, so the clause
290
+ reaches repositories scaffolded from this release on.
291
+ - **`loam adopt --targets`.** The brief was **42,873 bytes** per service and had no narrowing flag,
292
+ while `walk`, `checks` and `unchecked` are the same bytes for every service — a twelve-service
293
+ adoption paid that invariant text twelve times. `--targets` emits only what varies by service and
294
+ comes back at **17,805 bytes**. It carries one required extra key, **`full`**, naming the run that
295
+ returns the rest, because a narrowing flag that made
296
+ [the fifteen statements of what nothing checks](https://github.com/ybotok/loam/blob/main/src/core/brief/unchecked.ts)
297
+ invisible would quietly undo the one promise the brief exists to keep. The default payload is
298
+ unchanged.
299
+ - **`loam explain --codes`.** The code vocabulary could not be enumerated through the machine
300
+ contract: `loam explain --json` with no subject returned six concept terms, and the ~100 issue
301
+ codes and 46 refusal codes were reachable only from TypeScript source or from a 223-row prose
302
+ table inside an 84 KB body. `--codes` lists **273 codes — 227 findings and 46 refusals** — each row
303
+ the same answer `explain <code> --json` already gives for that one, so there is no second shape to
304
+ keep in sync. No derived `severity` or `gates` field: those have one authority in
305
+ `core/vocabulary/issue.ts` and a second answer here could disagree with it. Like every other
306
+ `explain` form it reads no config, so it works in an unfamiliar directory before anything is
307
+ wired.
308
+ - **A named backlog rather than a silent gap.** The coverage test that holds `--codes` against every
309
+ stable code loam emits ships with an explicit list of the families that have no `explain` row yet —
310
+ `doctor.*`, `next.*`, `diff.*`, `gate.*` and the `openspec.*` / `mapping.*` migration surface —
311
+ with a written reason per family. It can only shrink.
312
+
313
+ ### Added — `loam explain` answers the four families it has never been able to
314
+
315
+ - **`doctor.*`, `next.*`, `diff.*` and `gate.*` — 65 codes no fix table anywhere graded.**
316
+ `loam explain`'s finding content is parsed at runtime out of the `/loam-check` tables, and those
317
+ grade documents; these families grade wiring, a recommendation, a change between two refs and a
318
+ deploy question, so `explain` returned nothing for any of them. `loam explain <code>`,
319
+ `<code> --json` and `--codes` now answer them in the shape they already returned — no new key, no
320
+ second envelope. The listing goes from 274 codes to **339** (293 finding, 46 refusal).
321
+ - **A registry, and it is not the duplication `fix-tables.ts` argues against.** That argument is
322
+ about a second copy of prose the `/loam-check` bodies already ship, which would silently trail
323
+ them. These 65 have no row anywhere, so the registry is the only copy — the same shape, and the
324
+ same reason, as the sibling `refusals.ts`. Every meaning and fix is written from the emitter:
325
+ `doctor`'s own `fix` fields, each `next[]` step's `statement` and `command`, the `DiffCode` union's
326
+ doc comments. Where the "what to do" column is genuinely empty — a `next.*` step's action IS the
327
+ step — it says so instead of inventing a second instruction.
328
+ - **A test keeps the two sources disjoint**: the registry may not answer a code a fix table already
329
+ answers, so the parsed and hand-written halves can never drift into two answers a reader cannot
330
+ tell apart. The backlog of unanswerable codes shrinks from 116 to **51** — the OpenSpec migration
331
+ surface and two `ok`-severity confirmations, each with its written reason.
332
+
333
+ ### Changed — the scaffolded `AGENTS.md` becomes a pointer, and the reference ships with the binary
334
+
335
+ - **109,399 B to 29,720 B (−73%), under the 32,768 bytes Codex truncates the AGENTS.md chain at for
336
+ the first time.** Every agents.md-aware host auto-loads that file from the working directory, every
337
+ session, and it is never regenerated — so for those hosts roughly 70% of it was being silently
338
+ dropped, with no error and no way for the reader to tell which half it got.
339
+ - **This is loam's own doctrine, finally applied to the file that never got it.** `scaffold.ts`
340
+ argues it for the command files in its own words — *"Thinning the file is the third option. What
341
+ remains is what does not move between releases"* — and README's "a generated file is a pointer,
342
+ not a protocol" makes the same case. Nothing new was decided here.
343
+ - **`loam instructions` now serves four reference pages beside the six workflow protocols**:
344
+ `loam-codes` (44,132 B — which codes each invocation can raise), `loam-spine` (15,590 — the ID
345
+ spine and the `Based-On:` pins), `loam-authoring` (12,021 — `arch.spec.md`, the generated Gherkin
346
+ suite, frontmatter) and `loam-done-check` (11,126 — how `loam verify` derives its claims, and
347
+ verified versus attested). Each takes no arguments and prints whole. Bare `loam instructions`
348
+ lists them under their own heading — **not** flattened in with the workflows, because a list of
349
+ ten names would read as a ten-step process, which is the one thing the six-step cycle must not be
350
+ confused with. `--json` carries them in an additive `references` key beside `workflows`.
351
+ - **`loam init` writes no new file for a page.** They feed `PROTOCOLS`, never `SLASH_COMMANDS` or
352
+ the scaffold's file list, so moving 44 KB out of `AGENTS.md` did not put it back as four new
353
+ artifacts in every repository loam touches. Printed by the binary, they describe the loam you are
354
+ about to run rather than the one that scaffolded the repository — which is strictly better than
355
+ what they replaced, since `AGENTS.md` is written once and never refreshed.
356
+ - **What stays is what a reader needs to form a question at all**: the layout, the cycle, which
357
+ element is which service, `loam.json`, linking, the glossary, obligations, the validator's rules,
358
+ the archive gate and its undo. A new `## The reference pages` section names each page with the
359
+ exact command that prints it, and the cycle's steps 4 and 7 name the command where they used to
360
+ name a heading that has left the file.
361
+ - **The move was verbatim, and that was checked rather than assumed**: zero headings and zero stable
362
+ codes lost, audited against the index. `codes-drift` is untouched and green by construction — the
363
+ codes moved from one half of the `AGENTS_MD + PROTOCOLS` corpus into the other.
364
+ - **Still over Windsurf's 12,000-character cap**, and no further move fixes that: what remains is
365
+ the teaching, not duplication.
366
+
367
+ ### Changed — the scaffolded `AGENTS.md` stops storing the code vocabulary twice
368
+
369
+ - **124,587 B to 109,399 B (−12%).** `loam init` writes that file into the docs repo and every
370
+ agents.md-aware host auto-loads it from the working directory. Its `## Reading loam's output`
371
+ section is a per-invocation inventory — which codes each way of running loam can raise — and each
372
+ code carried a gloss of what it means. The `/loam-check` tables carry the same codes with
373
+ meaning-and-fix. **143 codes were explained twice, in two independently worded places, in a file
374
+ nothing regenerates** — so the two drift and nothing says which copy rotted.
375
+ - **The inventory stays; the gloss goes.** All 335 codes the file named are still named, still
376
+ backticked, still grouped by the invocation that raises them — that is unique content and
377
+ `codes-drift` reads this file as half its corpus. What a code MEANS now comes from
378
+ `loam explain <code>`, out of the running binary. `## Reading loam's output` falls from 59,621 B
379
+ to 44,433 B and every other section is byte-identical. A parenthetical that carried something
380
+ other than the meaning — a cross-reference, a severity that differs by scope — was kept.
381
+ - **The OpenSpec migration codes keep their notes inline**, because `loam explain` does not answer
382
+ them. **A test fails by name if any other code this file mentions cannot be explained**, so the
383
+ pointer can never outrun the catalogue.
384
+ - **A size assertion** now stands beside the file, because the number is load-bearing: Codex
385
+ truncates the AGENTS.md chain at 32,768 bytes by default and Windsurf caps a workspace rule file
386
+ at 12,000 characters, so a file over the cap loses its tail with no error at all. **This change
387
+ does not reach that cap and does not claim to** — the twenty-two remaining sections are 64,548 B
388
+ of teaching, not duplication, and cutting them is a separate decision about what a scaffolded
389
+ `AGENTS.md` is for.
390
+
391
+ ### Added — `loam validate --all --base <ref>`, the ratchet a partly-adopted system had no setting for
392
+
393
+ - **Grade only the targets changed since a base git ref of the docs repo.** `--all` is green over
394
+ undocumented boundaries because warnings do not gate, and `--all --strict` is red from the first
395
+ minute of adoption until the last boundary is written — so a team could install a gate that never
396
+ notices adoption stalling at 8%, or one they could not turn on until the work everyone hoped CI
397
+ would motivate was already done. Measured on a twelve-boundary system with one documented:
398
+ `--all --strict` exits 1 on thirteen warnings, eleven of them on boundaries nobody touched;
399
+ `--all --base HEAD~1 --strict` exits 1 on two, both on the branch's own boundary.
400
+ - **Spelled `--base`, not `--since`** — already loam's word for "a base git ref of the docs repo" on
401
+ `loam diff` and `loam vouch --pack`. It requires `--all` and refuses `invalid-option` beside a
402
+ positional target, `--service` or `--feature`: two scopes is a contradiction, not a narrowing.
403
+ - **Changed paths map to targets through the service and feature enumerations, never a path split.**
404
+ A service filed under a subsystem does not live at `services/<id>/`, and a `split("/")` would
405
+ return the subsystem name, match no id, and print a green over nothing. Untracked files count:
406
+ `validate` grades the working tree, so a boundary adopted five minutes ago and not yet committed
407
+ is in scope.
408
+ - **The run says what it looked at, in both views.** The summary reads "N of M services, K of L
409
+ features in scope since `<ref>`", and the payload gains an additive `scope` object naming the base,
410
+ the resolved commit and the targets. **A run with zero targets in scope prints that it graded
411
+ nothing and exits 0 — never the bare word "valid".** The fleet scorecard is omitted from a scoped
412
+ run: its denominators are the graded targets, so over a narrowed set it would describe adoption the
413
+ run never measured.
414
+ - No new stable code: an unreachable repository or an unresolvable ref refuse with the codes
415
+ `loam diff` already uses.
416
+ - **The honest limit, stated in the docs beside the recipe:** a `--base`-scoped validate cannot see a
417
+ victim boundary the change did not touch. `loam diff --base` remains the gate for that.
418
+
419
+ ### Added — one vendor-neutral skills root, and the machine surface wired at `init`
420
+
421
+ - **`loam init --tools` gains `agents`: the `.agents/skills/<name>/SKILL.md` root**, read by Cursor,
422
+ GitHub Copilot, Codex, Gemini CLI, Zed and Roo Code, each cited per its own documentation. Skills
423
+ only — none of the six documents a command file under that root, so none is written. Purely
424
+ additive: no existing tool's skill path moved. Detection is `.agents/skills`, never a bare
425
+ `.agents/` — the same rule that keeps a bare `.github/` from meaning Copilot.
426
+ - **`loam init --mcp` writes a repo-root `.mcp.json`** pointing an MCP host at `loam mcp` for this
427
+ repository. The MCP facade is the only typed, schema-carrying delivery loam has, and it was the
428
+ one that required a human to hand-edit JSON — so in practice scaffolded repositories got the prose
429
+ deliveries and never the machine one. Opt-in and default off: the file arranges for a process to be
430
+ launched on every machine that checks the repository out.
431
+ - **The launch form follows the install.** Where `node_modules/.bin/loam` exists — the per-repo
432
+ devDependency install the README documents — the entry is `npx --no loam mcp`; otherwise the bare
433
+ binary. A fixed `"loam"` would have written an unlaunchable entry into exactly those repositories.
434
+ - **Only the repo-root `.mcp.json`.** `.github/.mcp.json` is already a Copilot detection marker, so
435
+ writing it would make the next scan detect a tool loam itself installed; `.cursor/mcp.json` and
436
+ `.vscode/mcp.json` carry different schemas; `.gemini/settings.json` is a general settings file that
437
+ could only be merged into. An existing file is reported skipped and left byte for byte alone.
438
+
439
+ ### Added — an evaluator can see loam working without modelling anything first
440
+
441
+ - **`loam init --example <dir>`** copies the packaged example fleet — five services, four features
442
+ at four points in their life, and its own `loam.json` — into a fresh directory through the same
443
+ journaled transaction every other loam writer uses, and prints the three commands that make the
444
+ point. It binds no repository, records no `docsDir` and generates no agent files, so it refuses
445
+ `invalid-option` beside `--docs`, `--service`, `--create` or `--tools`, and `already-exists` when
446
+ the target is occupied. No new stable code.
447
+ - **The `examples/` tree now ships in the published package.** `npm i -g @ybotok/loam` delivered no
448
+ example at all, so the first question an evaluating team asks — "show me a working one" — was
449
+ answered with "model your own boundaries first, then come back". The tarball grows by 89 files and
450
+ about 77 kB packed.
451
+ - **Fixed with it: the shipped-page link audit resolved relative targets against the package root
452
+ rather than against the page carrying them.** Every audited page sat at the root until `examples/`
453
+ shipped, so the two were the same string. For a page in a subdirectory they are not, and the audit
454
+ would have called correct links broken and missed broken ones.
455
+
456
+ ### Added — `loam --help` says where to start, and what to run when lost
457
+
458
+ - **The help page ends with two lines: `loam init --create` for a first run, `loam doctor` for a
459
+ lost one.** The seven headings already put thirty commands in the order of the work, but nothing
460
+ on the page said that `init` must precede all of them or which command answers "what now" — the
461
+ last thing a newcomer read was `help [command]`. Both lines name a command that parses:
462
+ `test/agent-commands-runnable.test.ts` takes `HELP_EPILOG` by import and hands both invocations to
463
+ the real program, exactly as it takes the `init` first-hour lines.
464
+
465
+ ### Changed — `loam explain` is reachable from the message that needs it
466
+
467
+ - **Every text-mode refusal now prints its stable code and the lookup on stderr**, as one line after
468
+ the message: `<code> · loam explain <code>`. Measured over eight wrong first-hour invocations,
469
+ all eight printed prose and no code, and only `loam validate` mentioned `loam explain` at all — so
470
+ reaching an explanation meant re-running the whole command with `--json` and reading `error.code`,
471
+ which is the knowledge a new user does not yet have. `vouch-declined` is the one refusal that
472
+ stays silent: loam asked, a person said no, and there is nothing to look up.
473
+ - **`loam validate`'s text report carries each finding's code**, appended to every line whose
474
+ severity is not `ok`; the `ok` confirmations stay clean, so a mostly-green report keeps its
475
+ contrast. A code is printed only where `loam explain` can answer it — a pointer to an explanation
476
+ that does not exist is worse than none. `gherkin.path-outside` gained the fix-table row it had
477
+ been missing, which is what made that true rather than nearly true.
478
+ - **The explain footer was reworded to match**: the old "codes ride in `--json`" clause existed only
479
+ because no code was ever on screen.
480
+ - **`--json` stdout is byte-identical on every path**: the refusal pointer is a stderr line, and the
481
+ finding codes are the ones the envelope already carried.
482
+
483
+ ### Fixed — `loam adopt`'s human view printed 650 lines with nothing telling a reader what they were
484
+
485
+ - **The default view opens with a three-line orientation block**: how many briefed artifacts are
486
+ required and still outstanding and which, a decode of the artifact table's flags (`MISSING` in
487
+ capitals is required and absent, lowercase `missing` optional, `present` means diff it, `UNDRAWN`
488
+ is the shared map this boundary owes an element), and what the remaining four hundred lines are.
489
+ This is step 1 of the five-minute trial, and the two rows that orient a person landed around line
490
+ 470 of 646.
491
+ - **The outstanding count includes the fleet-map edit, which `required && !exists` misses.**
492
+ `architecture/landscape.likec4` exists for every docs repo after its first service, so the file is
493
+ present while nothing in it resolves to the new boundary — an error the moment `validate --all`
494
+ runs. A fresh boundary is briefed as three artifacts owed, not two.
495
+ - Every number is computed from the brief just assembled. No new flag, nothing moved behind one, and
496
+ no change to `--json` or to `--targets`.
497
+
498
+ ### Changed — `loam status` leads with the boundary you are standing in
499
+
500
+ - **The fleet form now leads its `next[]` with the service this repository's `loam.json` binds.**
501
+ The binding was visible to the worklist only while that service was *un*adopted; once adopted, the
502
+ repository you are standing in became invisible again and the list reverted to every service,
503
+ alphabetically. In a ten-repo system that opened every developer's `status` with nine other
504
+ boundaries' adoption work, and past ten services the ten-entry cap elided their own step entirely
505
+ — so working down the list as documented never reached it.
506
+ - **A stable partition applied before the cap, not a sort**: the bound service's steps move to the
507
+ front and every other service keeps the order it had, so an unbound reader sees exactly the list
508
+ it saw before. `--service <id>` is unaffected — that flag is an explicit question about one
509
+ service. No `next.*` code added, removed or changed.
510
+
511
+ ### Added — a `--json` envelope now says which loam wrote it, and which command answered
512
+
513
+ - **Every envelope carries `version`, the binary's own, beside `contractVersion`.** The two are
514
+ separate on purpose: `contractVersion` versions the SHAPE, `version` versions the BUILD, so a
515
+ release never reads as a contract break. Only `instructions` and `explain` said which loam
516
+ produced their output; `validate --all`, `status`, `list`, `context` and `gate` did not — so a
517
+ consumer holding a payload could not apply the caution `docs.binary-behind` exists to express, and
518
+ could not detect a mixed-version system from the contract at all. It is stamped by the two
519
+ emitters in `src/core/envelope/json.ts`, not by each command, which is why no command can be added
520
+ without it. **`version` is on the refusal envelope too**: a refusal is what a caller is most
521
+ likely to be holding when the build is the question.
522
+ - **Every success payload now carries `command`, the CLI command name.** `adopt`, `archive`,
523
+ `delta`, `gherkin`, `init`, `list`, `new`, `rebase`, `seed`, `show`, `subsystem`, `unarchive`,
524
+ `validate`, `verify` and `vouch`'s stamp payload gained it, so an MCP host multiplexing tool
525
+ results branches on one field instead of sniffing for the presence of `valid` or `services`. All
526
+ seven `subsystem` verbs emit `command: "subsystem"` — it is one registered command with a verb
527
+ positional. **A refusal deliberately carries no `command`, and a test now says so**: the error
528
+ emitter has no command in scope, and threading one would cost module-level mutable state or an
529
+ extra argument through every deep caller of `fail()`.
530
+ - Every key here is additive: no existing key changed name, type, value or relative order.
531
+
532
+ ### Added — `loam validate --json` now says what to do about the codes it raised
533
+
534
+ - **The payload carries `fixes`, a top-level map from each raised code to its fix.**
535
+ `loam validate --all --json` is the command every generated protocol names at step 1, and it was
536
+ the one that answered "what do I do next" in prose only. An agent holding the payload either had
537
+ the fix tables in context already or spawned a separate `loam explain` process per code — having
538
+ first guessed that it could.
539
+ - **A map keyed by code, not a field on each finding** — the argument `report.ts` already makes
540
+ about its `DETAIL_LIMIT`: ten services raising `sources.absent` produce ONE entry, and the whole
541
+ map costs 398 bytes there. On `examples/docs`, 51 findings become 9 entries and the payload grows
542
+ 16,433 → 17,731 bytes.
543
+ - **Only codes a non-`ok` finding raised**, **resolved through the same function
544
+ `loam explain <code>` calls** so the two surfaces cannot disagree (a test asserts every value
545
+ equals what `explain --json` returns), and **a code no fix table grades is absent, not a
546
+ placeholder** — the named backlog in `test/explain.test.ts` is where that gap is supposed to itch.
547
+ Nine codes that two tables grade differently carry both, each prefixed by its scope.
548
+ - Not behind a flag: a flag would mean an agent has to already know the fixes exist in order to ask
549
+ for them, which is the gap being closed. `{}` on a green run, so no consumer branches on the key.
550
+
551
+ ### Added — `loam mcp`: read-only annotations, and `instructions` + `steps` as tools
552
+
553
+ - **Every MCP tool advertises `annotations: { readOnlyHint: true, openWorldHint: false }`.**
554
+ `tools/list` emitted exactly `{name, description, inputSchema}`, so a host had no machine signal
555
+ that `loam_validate` or `loam_status` only reads files, and every call fell into the same approval
556
+ path as a mutating tool — which is the friction the read-only-only table was built to avoid.
557
+ `idempotentHint` and `destructiveHint` are deliberately absent: MCP defines both as meaningful
558
+ only when `readOnlyHint` is false. `outputSchema` was considered and deferred, with the reason
559
+ recorded beside the annotations.
560
+ - **The read-only claim is derived, not attested.** `test/mcp-protocol.test.ts` computes the
561
+ transitive value-import closure of every served command from `scripts/source-graph.mjs` and fails
562
+ if one reaches a module that commits. Adding a writer to the table now goes red instead of
563
+ shipping a hint that tells every host it is safe to auto-approve; a negative control asserts nine
564
+ known writers still DO reach that seam, so the guard cannot pass for everything if the seam moves.
565
+ - **`loam_instructions` and `loam_steps` join the table — twelve read tools become fourteen.** Both
566
+ read nothing and write nothing, and both were unreachable over MCP, so an MCP-only host could not
567
+ fetch the protocol its own generated skill points at. `--no-fix-tables` rides as the JSON property
568
+ `noFixTables`, named for what setting it true DOES, because commander's own attribute for a
569
+ negated option is `fixTables` and the two would otherwise mean opposite things.
570
+ - **`tools/call` can actually run them, and a test now proves that for every advertised tool.** The
571
+ dispatcher builds its own program from a second, hand-written roster — `core/` may not import
572
+ `commands/`, so the two lists cannot be one — and nothing counted them against each other. Landing
573
+ the tools without the dispatcher made `tools/list` promise fourteen while two of them answered
574
+ `{ ok: false, error: { code: "invalid-option", message: "unknown command 'instructions'" } }`, an
575
+ envelope that reads as the CALLER's mistake. `test/mcp-serve.test.ts` now calls every tool in
576
+ `MCP_TOOLS` and fails on `unknown command`.
577
+
578
+ ### Fixed — the generated allowlist stopped short of the verbs the protocols instruct
579
+
580
+ - **`allowed-tools:` in every generated skill file now names 23 of loam's 29 verbs, and the six it
581
+ omits carry a written reason.** It named 17, and four of the missing were verbs loam's own
582
+ protocols call mandatory: `/loam-implement` opens with `loam context` and requires `loam steps`,
583
+ `/loam-check`'s fix table says to run `loam subsystem sync`, and every generated file now tells
584
+ the agent to read the protocol narrow and run `loam explain <code>` per reported code. Each
585
+ stalled on a permission prompt at exactly the step its own protocol calls required — worst for
586
+ `loam explain`, the documented escape from an 84 KB page. Added: `context`, `diff`, `explain`,
587
+ `gate`, `steps`, `subsystem`.
588
+ - **The rule is now "every verb the CLI registers except the named exclusions", and it admits no
589
+ silent third category.** `UNAPPROVED` maps each excluded verb to its reason — `vouch` (an agent
590
+ must not be able to say a spec matches the code), `mcp` (a long-running server, not a call that
591
+ returns), `seed` and `open` (a human's statement and a human's editor), `audit-openspec` and
592
+ `migrate-openspec` (a one-time on-ramp against a foreign workspace). Still an enumeration, never
593
+ `Bash(loam:*)`: the wildcard is what would let an agent promote its own draft to `verified`.
594
+ - **`test/agents.test.ts` grades both halves against the binary** instead of spot-checking six
595
+ verbs: one test requires every backticked `loam <verb>` the protocols teach to be pre-approved or
596
+ named in `UNAPPROVED`; the other asserts the emitted list equals the registered commands minus
597
+ `UNAPPROVED`'s keys.
598
+
599
+ ### Fixed — a tool that does not substitute `$1` got a refusal instead of the protocol
600
+
601
+ - **`loam instructions <workflow> $1` prints the protocol instead of refusing.** Every generated
602
+ command file carries Claude's positional `$1`/`$2` spelling into all twenty dialects, and Cline,
603
+ Kilo Code and Roo Code substitute nothing — so those three sent loam the literal `$1` and got
604
+ `invalid-option` on the first instruction of the workflow. A literal `$1`…`$9` now reads as an
605
+ unsupplied argument and the placeholder is left standing, exactly as passing no argument does. An
606
+ argument that merely contains one (`FEAT-$1`) is still checked and still refused.
607
+
608
+ ### Fixed — `loam delta` printed `Covers:` twice
609
+
610
+ - **The text briefing printed each requirement's body verbatim and then re-printed `Operations:`
611
+ and `Covers:` from the parsed fields**, so a requirement carrying either line showed it twice
612
+ while the five other directives showed it once. A directive line is a body line by design — it
613
+ stays in the requirement's text so the document round-trips and its digest is stable — so the
614
+ re-print was always redundant. The duplicated lines are gone; `--json` is unchanged, and so is
615
+ every other command. Found by running `loam delta` over loam's own self-model.
616
+
617
+ ### Added — a feature can bring a use case
618
+
619
+ - **New feature-delta slot: `features/<FEAT>/usecases/<name>.likec4`.** A views-only LikeC4 document
620
+ holding the `dynamic view`s a change brings with it. `loam archive` copies each one into
621
+ `architecture/usecases/<name>.likec4` and `loam unarchive` removes it again, through the same
622
+ transaction and the same `.loam-before/` snapshot as every other axis. The route is CREATE-ONLY:
623
+ to change a living flow, edit `architecture/usecases/<name>.likec4` directly in the same pull
624
+ request.
625
+ - **New issue code `usecase.flow-exists`** (error, archive-gating, **not** `--approve`-overridable):
626
+ the feature introduces a flow the living `architecture/usecases/` already holds, and the merge is a
627
+ whole-file copy that would replace an authored hop sequence wholesale. The same shape and severity
628
+ as `glossary.term-exists`.
629
+ - **New issue code `usecase.flow-invalid`** (error, archive-gating, **not** `--approve`-overridable):
630
+ the feature's flows could not be read against the map its own merge would leave behind — a parse
631
+ error, a hop naming an element the merge does not land, or a landscape merge that itself refuses.
632
+ Nothing is written; the archive stops at plan time rather than copying a flow into `architecture/`
633
+ for the next reader's `loam validate --all` to fail on.
634
+ - **`capability.uncovered` now counts a feature-local flow as cover.** A `dynamic view` under the
635
+ feature's own `usecases/` whose `#cap-` and `#req-` tags both resolve keeps the promise, resolving
636
+ against the capability vocabulary and requirement ids the same feature's merge would leave behind —
637
+ the both-corpora rule `Realizes:` already follows. Only a resolved claim counts, so a broken tag
638
+ still earns its own error and silences nothing. **Its message changed**: it now names
639
+ `features/<FEAT>/usecases/<name>.likec4` and the two tags to write, where it used to say the flow
640
+ route opened only after the promise was living. When the flow half could not be evaluated — the
641
+ flows unreadable, or `architecture/capabilities.yaml` not readable as a vocabulary so no `#cap-`
642
+ tag resolves — the message says so and makes no claim about the flows, rather than reporting that
643
+ none of them keeps the promise. The `Realizes:` half is still checked and still gates.
644
+ - **`delta.likec4` still refuses a `dynamic view`, and now says where one goes.** The refusal is
645
+ unchanged and mechanical — that document re-declares the landscape's identifiers and carries its
646
+ own `specification` block, so it cannot be read beside the map in one LikeC4 project — but its
647
+ message names the new slot instead of sending the author to `architecture/landscape.likec4`. A
648
+ `deployment view` still goes to the living map.
649
+ - **New path in the layout: `features/<FEAT>/usecases/`** (`SCHEMA.md`, the shipped `AGENTS.md`).
650
+
651
+ ### Added — one predicate decides what a use case is
652
+
653
+ - **A `dynamic view` tagged only `#req-<slug>` is now visible to every reader**, not just to
654
+ `loam validate --all`. `loam diff`'s victim walk, `loam delta` and `loam status`'s blast radius,
655
+ the context and explore packs and `loam list capabilities` all take the same opt-in, which is
656
+ exported as `isUseCase`; no caller spells the prefix test itself any more. The cheap byte scan that
657
+ lets a fleet with no use cases skip the LikeC4 load was widened with it — a fleet whose only flow
658
+ was `#req-`-tagged declared `tag req-…` and no `cap-` anywhere, so the scan answered "no use cases"
659
+ and nothing loaded the project at all.
660
+ - No code, flag, exit code or payload key changed. A fleet whose flows all carry `#cap-` sees no
661
+ difference; a fleet with a `#req-`-only flow sees it appear in reports that previously omitted it.
662
+
663
+ ### Added — the step catalogue
664
+
665
+ - **New authored document: `services/<svc>/steps.yaml`** — a `steps:` list of step TEXTS recording
666
+ which phrases a team has decided its suite defines. Optional; the file's existence is the opt-in.
667
+ - **`loam steps` reports written phrases against catalogued ones.** New additive `--json` key
668
+ `catalogue` carrying `present`, `path`, `entries`, and — when the file exists and parses —
669
+ `uncatalogued` (written, not catalogued: the work-list, in the inventory's own order), `unwritten`
670
+ (catalogued, nothing writes it) and `duplicated` (two entries collapsing onto one phrase). An
671
+ absent catalogue and an unreadable one are different answers and neither reports a work-list:
672
+ `unreadable` carries the reason instead. Existing keys are untouched.
673
+ - **No new stable code, and `loam validate` does not read the catalogue.** A phrase written before
674
+ its glue is the normal order of work; the near-duplicate groups stay a report, and no
675
+ phrase-similarity finding ships. The exit code is unchanged in every case — `loam steps` remains a
676
+ read.
677
+
678
+ ### Fixed — a slot-less AsyncAPI delta merges instead of archiving silently
679
+
680
+ - **A feature `asyncapi.yaml` that declares component surfaces and no channel, operation or message
681
+ slot now merges them.** It archived at exit 0 having written nothing, and unlike its OpenAPI twin
682
+ the early return was the symptom rather than the cause: the only write loop ran over the slots, so there was no writer for anything else, no enumeration of what lay outside the three slot
683
+ sections, and no pin that could reach a `components.schemas` value — the event pin is written INTO
684
+ a slot value, which works because every slot value is a mapping and a JSON Schema is not.
685
+ - **`loam rebase` writes a root `x-loam-baselines` record into a feature `asyncapi.yaml`**, pinning
686
+ each component surface to the digest of the living value, ordered after the slot pins so a surface
687
+ digest never contains a slot pin and the record never enters a slot digest.
688
+ `stripAsyncapiMarkers` removes that record on every branch, including the early one — otherwise
689
+ the archive's create path published it into the living contract and nothing caught it, because the
690
+ removal-marker walk only looks for `x-loam-remove`.
691
+ - **New code `asyncapi.component-modified`** (warn, archive plan), the event-axis sibling of
692
+ `openapi.component-modified`. `asyncapi.baseline-missing`, `-stale` and `-invalid` now grade
693
+ component surfaces as well as slots, with unpinned surfaces folded into the SAME per-service
694
+ counter rather than a second warning.
695
+ - **`asyncapi.ref-unresolved` no longer fires for a target the delta itself declares.** A feature
696
+ that adds `components.messages.Refunded` alongside the `components.schemas.RefundPayload` its
697
+ payload references now merges both and resolves; one whose target nothing anywhere defines still
698
+ gates, and that case is the negative control in the suite.
699
+
700
+ ### loam models its own architecture in loam
701
+
702
+ - **`meta/docs/` is a docs repo describing loam itself.** Its landscape holds 70 containers — the
703
+ top-level subjects of `src/` — and 403 relationships, the real value imports;
704
+ `services/loam/arch.spec.md` carries fourteen of `docs/DESIGN.md`'s numbered rules as requirements
705
+ with `Covers:` lines onto real model objects, and opens by naming the twelve it cannot cover,
706
+ because `Covers:` can only point at an object that EXISTS and roughly a third of loam's own rules
707
+ prohibit edges that do not.
708
+ - **`npm run meta:check` (`scripts/self-model.mjs`) runs in CI beside `npm run arch:check`.** It
709
+ fails on a package with no box, a box with no package, an import with no relationship, a
710
+ relationship with no import, and a mutual dependency between top-level subjects outside its named
711
+ baseline. It prints the fix and never applies it: loam reads no source tree, by standing non-goal,
712
+ so this script CONVICTS the model and does not generate it. `scripts/source-graph.mjs` is the one
713
+ definition of an edge, shared with `scripts/package-graph.mjs` so `import type` cannot be exempt
714
+ on one side and not the other.
715
+ - **`.gitignore`'s `loam.json` is anchored to `/loam.json`.** Unanchored, it ignored a file of that
716
+ name at any depth — which is why a self-model directory could not be committed at all. The
717
+ throwaway root config the Quick start writes is still ignored; a committed one in a subdirectory
718
+ is not.
719
+ - No command, flag, exit code, envelope field or stable code changed.
720
+
721
+ ### Fixed — `docs/DESIGN.md` had stopped describing the tree, and its pin had never fired
722
+
723
+ - **The package-layout table had no row for nine `core/` packages** (`brief`, `explain`, `glossary`,
724
+ `links`, `obligations`, `owners`, `provenance`, `scaffold`, `usecases`) and a false `Depends on`
725
+ cell in eleven more — `core/c4/` claimed to depend on nothing while importing `repo` and
726
+ `kernel`. The column is recomputed from the derived graph and now has a machine-checked
727
+ counterpart. The DAG-levels table placed `verify/` below `gherkin/`, which it imports, and the
728
+ "7-level DAG" is ten levels per package and eleven per file.
729
+ - **DESIGN said loam makes 27 `register*` calls producing 28 commands**; it is 28 and 29, and the
730
+ page contradicted itself two rows later. **The pin that should have caught it had never matched**:
731
+ it required a literal space before `commands` and the page wraps at exactly that point. That loop
732
+ in `test/docs-facts.test.ts` now runs over the flattened text — every other pattern in it had the
733
+ same hole — and a new assertion requires the sentence to be PRESENT before anything grades it. A
734
+ pin that can pass vacuously is worse than none, which is the doctrine that file already states.
735
+ - **"Every edge points up the row order" is now qualified**, because it was false at a granularity
736
+ nothing checked: `core/c4` ↔ `core/repo` and `core/repo` ↔ `core/provenance` are mutual, and
737
+ `scripts/package-graph.mjs` cannot see either — it keys on the full relative directory, so
738
+ `core/c4/project` and `core/c4` are different nodes. Both are held in an `ACCEPTED_CYCLES`
739
+ baseline that may only shrink; breaking them is a hub move and a follow-up.
740
+
741
+ ### Fixed — a components-only OpenAPI delta merges instead of archiving silently
742
+
743
+ - **A feature `openapi.yaml` that declares `components` and no `paths` now merges those components
744
+ into the living contract.** It used to archive at exit 0 having written nothing: `mergeOpenapiPaths`
745
+ answered no-op on the absence of `paths` before it had even parsed the living document, and the
746
+ component closure is the last thing the merge runs — so components merged only as a side effect of
747
+ merging a path. The no-op now consults the surface enumeration the baseline gate and `loam rebase`
748
+ already share, so a contract delta whose entire content is a component is a merge, not silence.
749
+ - **A genuinely new component in such a delta is promoted before the ref fixpoint is seeded**, not
750
+ after, so its own `$ref`s ride the same sweep: a components-only delta pointing at a schema neither
751
+ document defines still gates `openapi.ref-unresolved`, naming `components/<kind>/<name>` as the
752
+ source. Reachability still decides the new-component question whenever the delta merges paths at
753
+ all — an unreferenced schema riding in beside a path change stays behind, as before.
754
+ - The archive plan names what it merged (`openapi: <svc> — merged (component schemas/Split)`) and no
755
+ longer prints an empty `merged ()`. No command, flag, exit code, envelope key or stable code
756
+ changed.
757
+
758
+ ### The two-fleet pilot is removed
759
+
760
+ - **Removed `docs/pilot/`, `scripts/pilot-harness.mjs`, the `pilot:run` and `pilot:check` npm
761
+ scripts, and the CI step that ran them.** The pilot was a plan for evidence, not evidence, and it
762
+ had become the one `## Now` item that no work inside this repository could advance. Nothing in
763
+ the shipped binary referenced it: no command, flag, code or payload key changes, and no page loam
764
+ writes into a docs repo mentioned it.
765
+ - **`docs/pilot/RELEASE-READINESS.md` moved to `docs/RELEASE-READINESS.md`.** It is a release
766
+ document — npm trusted publishing, tag discipline, provenance, post-publication verification — and
767
+ it outlives the pilot that happened to file it. Its one pilot-specific checklist item is now the
768
+ security review alone.
769
+
770
+ ### `loam steps` — how many step definitions a suite actually needs
771
+
772
+ - **New read-only command `loam steps`** (`--service <id>`, `--duplicates`, `--json`). It collapses
773
+ every written step in a service's living `spec.md` and `arch.spec.md` onto the phrase a step
774
+ definition matches, and reports each phrase with its count, the keywords it appears under and the
775
+ requirements it comes from — plus the two numbers a team plans from: how many steps are written,
776
+ and how few phrases cover 80% of them.
777
+ - **Why it is a command and not a check.** The glue layer is where a generated suite dies: the step
778
+ text is free prose from a spec bullet, so one precondition gets three spellings and the registry
779
+ grows without bound until people go back to hand-written tests. Nothing in loam could previously
780
+ say how bad that was. It emits **no finding and no stable code** — a near-duplicate is a judgement
781
+ about writing, and a phrase-similarity warning is the kind of check a fleet turns off, taking the
782
+ real findings beside it.
783
+ - **The phrase key is a contract** and `test/steps-inventory.test.ts` pins it case by case: the
784
+ Gherkin keyword is dropped (every runner resolves Given/When/Then/And/But against one registry, so
785
+ `Given the ledger is open` and `And the ledger is open` are one definition); quoted strings,
786
+ `<placeholders>` and standalone numbers collapse to `{s}` / `{p}` / `{n}`, because those three are
787
+ exactly what a definition captures as an argument; `v1` and `oauth2` keep their digits. Nothing
788
+ measures edit distance.
789
+ - **`nearDuplicates`** groups phrases that differ only by a leading article or a trailing
790
+ `because …` / `while …` clause — two definitions where one was meant, and the fix is the spec's
791
+ wording rather than a second definition.
792
+ - **It reads the living specs, not the emitted files**, so it answers the same way in a repo that has
793
+ never run `loam gherkin`, and needs no service repo to stand in. `/loam-implement` and the
794
+ generated `AGENTS.md` now both send an agent here before it writes any glue.
795
+
796
+ ### An outline's header and its placeholders must agree
797
+
798
+ - **New `requirements.examples-unbound` (error)**: a scenario with an `Examples` table uses a
799
+ `<placeholder>` no column supplies. Cucumber substitutes nothing, so the step definition receives
800
+ the literal angle-bracket text and either fails obscurely or matches and asserts against a string
801
+ nobody meant.
802
+ - **New `requirements.examples-unreferenced` (warn)**: an `Examples` column nothing refers to —
803
+ usually the fossil of a rename. The column still lists six cases, the step that used it now names
804
+ something else, and the outline runs six identical passes that read as six cases of coverage.
805
+ - **Steps, docstrings and data-table cells all count as references**, because cucumber substitutes
806
+ into all three: `<fee>` inside a request payload is a real use, and a check that read only the step
807
+ lines would call its column dead and invite the author to delete it.
808
+ - **Scoped to scenarios that HAVE an `Examples` table.** A plain scenario's angle brackets are prose
809
+ — a URL template, a generic, one of loam's own scaffold sentinels — and a fleet that writes no
810
+ outline earns nothing from either code.
811
+
812
+ ### A scenario that asserts nothing is a finding
813
+
814
+ - **New `requirements.assertionless-scenario` (error)**: a scenario whose body has steps and no
815
+ `Then` anywhere in it. It arranges and acts and asserts nothing, so the run confirms only that the
816
+ setup did not throw — and answers its `scenario.tested` claim on that. `And`/`But` are
817
+ continuations, so with no `Then` above them they continue the setup.
818
+ - **Disjoint from `requirements.stepless-scenario` by construction** (that one has no steps at all),
819
+ and graded in the same four places: living `spec.md` and `arch.spec.md`, and a feature's deltas of
820
+ both — a delta merges into the living spec and is called covered from then on.
821
+ - It matters most where the least is left to check: under the runner-neutral answer sheet there are
822
+ no step results to count, so a document-side check is the only thing that can tell a dense scenario
823
+ from a mute one.
824
+
825
+ ### A step can carry an argument: payload docstrings and expected-state tables
826
+
827
+ - **A fenced block written under a step becomes that step's Gherkin docstring**, info string carried
828
+ through as the content type and **indentation preserved rather than trimmed** — a request or
829
+ response payload whose leading whitespace has been eaten is one no step definition can compare
830
+ against. **An indented Markdown table under a step becomes that step's data table** (the markdown
831
+ separator row is dropped; Gherkin has none), which is how an expected-state assertion — the rows an
832
+ outbox, a topic or a table must hold after one data pass — can be written at all.
833
+ - **Indentation is the whole disambiguation**, and it is the one behaviour change to existing
834
+ documents: a table INDENTED under a step is now that step's rows, while a table at the scenario
835
+ body's left margin is still the `Examples:` of a `Scenario Outline`, unchanged. A spec that wrote
836
+ its expected rows indented under a `Then` was previously emitting them as the scenario's case
837
+ matrix — the same scenario run once per expected row, with the step itself receiving nothing.
838
+ - **No digest moves.** The scenario digest hashes the markdown source, not the rendering, so every
839
+ `@loam-digest-…` stamp, every `scenario.tested` claim and every recorded answer is unchanged by
840
+ this. A suite generated by an earlier loam keeps its stamps and still grades `gherkin.current`;
841
+ regenerate to pick up the new rendering.
842
+ - **New per-scenario emission loss `strandedBlocks`** (text, and a new `files[].strandedBlocks` key
843
+ under `loam gherkin --json` — additive): a fenced block or an indented table that no step precedes,
844
+ or an indented table loam cannot read. It stays in the description, so the document still reads
845
+ correctly and the file is still valid Gherkin; what is lost is the ARGUMENT, and nothing downstream
846
+ can otherwise notice.
847
+ - **Two defects fixed on the way in.** A Markdown table inside a fenced payload was hoisted out and
848
+ emitted as the scenario's `Examples:`, turning one data pass into an outline parameterized by the
849
+ contents of its own request body — fences are now recognised before tables. And a table cell
850
+ holding a literal pipe written `\|` was read as two cells, so the row's width stopped matching the
851
+ header and the WHOLE table was reported unreadable against the one row that needed the escape;
852
+ `\|` and `\\` now unescape on the way in and are re-escaped on the way out, along with `|`, `\` and
853
+ newline in every emitted cell.
854
+ - `parseStampedFeature` now skips docstring bodies. Every staleness verdict rests on that parse, so a
855
+ payload containing a line reading `Scenario: …` or beginning with `@` would otherwise have been
856
+ deciding what the suite promises.
857
+
858
+ ### `loam --help` is grouped
859
+
860
+ - The twenty-eight commands now print under seven headings — **Set up · Read the fleet · Adopt what
861
+ exists · Change it · Check it · Ship it · Migrate** — in the order the work happens, mirroring the
862
+ six shipped workflow protocols rather than inventing a second taxonomy.
863
+ - **Nothing is hidden and nothing is renamed.** Every command still parses, still appears, and still
864
+ carries the same flags; `test/help-groups.test.ts` asserts that no command is hidden, that every
865
+ one has a heading, and that no stray heading exists. `src/cli.ts` throws on a command with no
866
+ heading, so a twenty-ninth added without one fails the first time anybody runs the binary rather
867
+ than landing quietly at the bottom of the page.
868
+ - [AGENTS.md](https://github.com/ybotok/loam/blob/main/AGENTS.md) now states that **help layout is
869
+ not part of the frozen surface**: names,
870
+ flags, exit codes, the envelope and the codes are frozen; which heading a command prints under is
871
+ typography, and regrouping is not a compatibility event.
872
+
873
+ ### The build's own contract, read as a check
874
+
875
+ - **New `contracts` block in `loam.json`**: `{ "contracts": { "openapi": "build/openapi.yaml" } }`
876
+ names where this repository's BUILD writes its contract. `loam validate --service` digests that
877
+ document and compares it with the committed `services/<id>/openapi.yaml` — **new finding
878
+ `openapi.generated-stale` (warn)** when they differ, **`contracts.source-missing` (error)** when
879
+ the path holds nothing (CI that validates before it builds), and **`contracts.source-invalid`
880
+ (error)** when it escapes the repository or does not parse.
881
+ - **It reads, and never writes.** The copy into the docs repo stays a human `cp` reviewed in a pull
882
+ request, which is what keeps the committed contract a document somebody agreed to rather than a
883
+ cache of the build. This is the same category as `verify --results` ingesting a cucumber report:
884
+ loam parses a standard document the team's build emitted, at a path a human named, and derives no
885
+ meaning from it — no line of service code is read.
886
+ - **The digest is over canonical JSON, not bytes**, so two generator versions ordering keys
887
+ differently, or a dumper re-wrapping a description, produce silence rather than a permanent
888
+ warning nobody can clear. Service-repo only, and entirely silent for a repo with no `contracts`
889
+ block — which is every existing repo.
890
+ - The premise this closes is SCHEMA's most load-bearing untested one: that the committed contract is
891
+ what the service actually serves. Most fleets generate OpenAPI and copy it by hand, and until now
892
+ nothing in the product could notice when the copy stopped being current.
893
+
894
+ ### The example fleet now demonstrates both verdicts
895
+
896
+ - **New archived feature `FEAT-120-refund-notification`** in `examples/docs/`, and it reads
897
+ **`verified`**. It is the sequel FEAT-088 left open — the money went back and nobody told the
898
+ customer — so it publishes `payment.PaymentRefunded` from payment-service and has
899
+ notification-service turn it into the customer's message: the event spine end to end, across two
900
+ services, with no HTTP anywhere in it.
901
+ - **The two archived records are a matched pair, and that is the point.** FEAT-088's scenario claims
902
+ rest on an agent's word (`attested`); FEAT-120's on a digest-matched runner report (`verified`).
903
+ A showcase demonstrating only the lesser verdict teaches that the distinction is decorative —
904
+ which is the one thing this product may not teach, since keeping those two answers apart is most
905
+ of what it claims to be for.
906
+ - **Every artifact was produced by the real commands**, not hand-written: `loam new`, `loam rebase`,
907
+ `loam gherkin` (which is where the five scenario digests came from), `loam verify --results`, then
908
+ `loam archive` — snapshot, manifest and all. The `scenario-report.json` beside the record is a
909
+ real `loamScenarioReport` file, and the record's `report:` block carries that file's true sha256.
910
+ - Its two `event.declares` claims still rest on an agent's word, and it is `verified` anyway — the
911
+ verdict turns on the scenario claims alone. That mix is the honest common case: a suite that
912
+ answers the scenarios, and wiring claims a human vouches for.
913
+ - The fleet still reports **0 errors and exactly the same ten deliberate warnings**: the merge
914
+ disturbed nothing. `test/examples.test.ts` now pins both verdicts and asserts they stay different,
915
+ so "fixing" the example by making both sides agree fails the suite.
916
+ - `examples/README.md` also separates two words that were being read as one: a SERVICE's ladder rung
917
+ (`draft` → … → `vouched`) is not a feature's verdict. FEAT-120 is `verified` in a fleet whose
918
+ services are all `draft`, which is exactly where a team stands the day its first suite goes green.
919
+
920
+ ### `docs.binary-behind` — the run that reports green without having graded the corpus
921
+
922
+ - **New finding `docs.binary-behind` (warn)**, on `loam validate --all` and `loam doctor`: the docs
923
+ repo's `AGENTS.md` stamp names a loam NEWER than the binary reading it.
924
+ - **It is the only finding that changes what a PASS means rather than reporting a defect.** Every
925
+ check loam has is an existence constraint over a value the parser recognised, so a binary that
926
+ predates a grammar addition does not fail on the newer directive — it reads it as prose, produces
927
+ no join, and reports green. Until now nothing said so: `agents.stale` grades only the opposite
928
+ direction, and explicitly declined this case because the fix is different (upgrade the binary, do
929
+ not edit the file).
930
+ - **Warn, not error**, on the `sources.stale` doctrine: a mixed-version fleet is ordinary, and
931
+ failing every command in that repo aims a refusal at the wrong person. `--strict` is the lever
932
+ for a fleet that wants it to gate. The message says plainly that a pass from this binary is
933
+ incomplete rather than clean, and never sends anyone to edit `AGENTS.md`.
934
+ - Prerelease identifiers are compared, not ignored — in a 0.x product a prerelease is exactly where
935
+ a generated file's form and a corpus grammar actually move, which `agents.stale` already learned
936
+ the hard way at beta.2.
937
+
938
+ ### Any test runner can answer a scenario claim
939
+
940
+ - **`--results` now also accepts `{"loamScenarioReport": 1, "results": [{"digest": "…", "status":
941
+ "passed", "test": "…"}]}`**, chosen by its marker key, beside the cucumber JSON it already read.
942
+ `digest` is the `@loam-digest-…` tag `loam gherkin` stamped; `status` is `passed` or `failed`;
943
+ `test` is optional free text used as the evidence string.
944
+ - **This is a widened input dialect, not a weakened standard of proof.** The contract was never
945
+ cucumber's JSON — it is the content-derived digest plus a status saying a real run reported it
946
+ green, and both halves are present here. An answer from this shape is `answered_by: runner`,
947
+ identical to a cucumber one, because it is the same claim answered to the same standard by the
948
+ same identity. A fleet on JUnit, pytest, Playwright, Vitest or a house runner was previously held
949
+ at `attested` **by a file format**, which is the one thing that distinction was never meant to
950
+ mean.
951
+ - It adds no forgeability: loam cannot prove any JSON came from executing a commit — which is why
952
+ the record stores the file's sha256 and mtime and claims nothing more — and a hand-written
953
+ cucumber array was always exactly as easy to write.
954
+ - **Parsing is strict, and a `skipped` status is refused rather than guessed.** A scenario that did
955
+ not run has no place in an answer sheet; omitting it leaves the claim unanswered rather than
956
+ confirmed. A file carrying the marker is always graded as that shape even when malformed, so an
957
+ author is told the real mistake instead of "this is not a cucumber array".
958
+
959
+ ### Two corrections from review, before either shipped in a release
960
+
961
+ - **`spec.unknown-directive` no longer fires inside a fenced block.** A requirement body
962
+ legitimately holds a fenced example, and inside one `Realises:` is a sample value rather than a
963
+ misspelled directive — the check convicted a document that was exactly right, which is the one
964
+ thing a near-miss guard may never do. Backtick and tilde fences both toggle; a real typo after a
965
+ closed fence is still caught. Table cells never fired (a pipe-led line does not match the
966
+ candidate pattern) and there is now a test saying so.
967
+ - **`verify.checklist-forked` is measured against the current checklist, not pairwise between
968
+ attestations.** The pairwise reading answers the wrong question: three services that all attested
969
+ against a since-rewritten delta agree with each other perfectly and are every one of them stale,
970
+ which pairwise called clean. The notice now names the services whose answers are not about the
971
+ questions being asked now. Differing `docsCommit` values remain **not** a finding — recording a
972
+ week apart is normal for a feature in flight.
973
+
974
+ ### A grammar guard: `spec.unknown-directive`
975
+
976
+ - **New finding `spec.unknown-directive` (warn)** — a requirement body line whose key is one or two
977
+ edits from a directive and is not one: `Realises:`, `Capabilties:`, `Opertaions:`, `Publsihes:`.
978
+ - This was the one place the corpus was quieter than it looked. Every join is an existence
979
+ constraint over a *parsed* value, so a key the parser does not recognise yields no value, no join
980
+ and therefore no finding — the requirement claims to realize a promise, gets nothing, and validates
981
+ clean. It is the judgement `obligation.unknown` already makes one axis over ("a mistyped tag reads
982
+ exactly like a rule"), applied to the keys themselves.
983
+ - **Measured by edit distance, not by shared prefix**, deliberately: `closeIds`'s three-character
984
+ prefix rule would report `Context:` as a near miss for `Consumes:`, in a check whose entire value
985
+ is that it does not cry wolf. `Note:`, `Owner:`, `Rationale:`, `Status:` and `See:` are silent,
986
+ and the example fleet's warning count is unchanged at ten.
987
+
988
+ ### An attestation says what it answered, and against what
989
+
990
+ - **Two optional keys on each `ServiceAttestation`**: `checklist` (the digest that attestation
991
+ answered) and `docsCommit` (the docs-repo HEAD it was derived from, when the docs repo is a git
992
+ checkout). `commit` already pinned the service repo — the code the evidence points into — and
993
+ nothing pinned the side the *question* came from.
994
+ - **New finding `verify.checklist-forked` (warn)** — two or more services on one record answered
995
+ different versions of the feature's question set. The record carried a single top-level
996
+ `checklist` digest, so this state could not be represented at all: the staleness check flagged
997
+ both services or neither, and never said which answers went stale.
998
+ - **An attestation with no `checklist` field is not counted as a third version.** Silence is not
999
+ disagreement, and reading it as such would fire this on every federated record written before the
1000
+ field existed. `docsCommit` is omitted rather than refused when the docs repo is not a git
1001
+ checkout — a docs repo is not obliged to be one.
1002
+
1003
+ ### The pages say where loam is behind, not only where it is ahead
1004
+
1005
+ - **README's "Why" now carries what green does *not* mean**, pointing at
1006
+ `src/core/brief/unchecked.ts` — the fifteen statements of what no check will ever tell you, which
1007
+ the binary already prints into the adoption brief and no page had ever quoted. Completeness is the
1008
+ one worth naming out loud: forty behaviours documented as one requirement passes every check loam
1009
+ has. The `verified`/`attested` distinction moved up beside it.
1010
+ - **COMPARISON.md gains "The wider field, and who is actually nearest"** — OpenFastTrace, Doorstop,
1011
+ Pact Broker, Backstage, EventCatalog, oasdiff/buf, StrictDoc/Sphinx-Needs and ArchUnit, each with
1012
+ the mechanism it shares and **who is ahead at it**. The page compared loam only to OpenSpec, which
1013
+ is the nearest tool on one of loam's seven joins and on none of the other six. Two entries are
1014
+ direct credit: OpenFastTrace put the version inside the coverage token years ago, and Doorstop
1015
+ stored a parent's fingerprint on a link thirteen years before `capability.realizes-stale`.
1016
+
1017
+ ### A living `Realizes:` pin, so a moved promise stops being invisible
1018
+
1019
+ - **New finding `capability.realizes-stale` (warn).** A `Realizes:` entry may now carry the digest of
1020
+ the capability requirement it was written against — `Realizes: checkout#CHK-1@9f2c1a4b` — and
1021
+ `loam validate` reports one warning per pinned entry whose target has been rewritten since. Until
1022
+ now every capability check was an EXISTENCE constraint: `capability.realizes-unknown` fires when
1023
+ the target is *gone* and stays silent when the target merely *changed*, so an analyst could narrow a
1024
+ promise and every requirement claiming to keep it went on claiming so, in a corpus loam rewrote
1025
+ itself. It is a warn and not an error because re-reading a moved promise has three legitimate
1026
+ outcomes and loam cannot tell which; it never gates `loam archive`.
1027
+ - **New flag `loam rebase --living`**, which writes and refreshes those pins across the living
1028
+ corpus and takes no feature id — so `rebase`'s `<FEAT>` argument is now optional, and every
1029
+ existing invocation still parses. It commits through the same journaled transaction as every other
1030
+ writer, under the same docs lock, and rewrites exactly one `Realizes:` line per requirement that
1031
+ already has one: it never invents a join on an author's behalf. Its stored rerun is
1032
+ `loam rebase --living`. `--service <id>` and `--dry-run` apply.
1033
+ - **Nothing changes for a corpus that has not been pinned.** An entry without a pin grades exactly as
1034
+ it did before, forever, which is what lets this land with no migration.
1035
+ - **`requirementDigest` now strips `Realizes:` pins as well as `Based-On:` lines.** Both are
1036
+ bookkeeping a requirement carries about its own joins, and a digest that moved when bookkeeping
1037
+ moved would make every pin self-invalidating — one `rebase --living` would go on to invalidate
1038
+ every `Based-On:` baseline in the fleet. The pinned and unpinned spellings of a requirement
1039
+ therefore digest identically, which `test/capability-realizes-pin.test.ts` asserts directly.
1040
+
1041
+ ### The first hour starts from the fleet spine
1042
+
1043
+ - **`examples/fleet.yaml`** ships as a real `loam seed` input for the five-service example fleet, and
1044
+ **WORKFLOW.md's day zero step 2 now names `loam seed --from fleet.yaml`** instead of telling a
1045
+ reader to draw `architecture/landscape.likec4` by hand. `seed` has shipped since beta.1 and
1046
+ appeared in exactly one place in the prose: a command-table row.
1047
+ - **README leads with two entry points that pay off before adoption starts** — "Start from the fleet
1048
+ spine" (seed, drop in the OpenAPI you already have, name the operation on each call edge, then
1049
+ `validate --all` convicts `spine.op-undefined` with no requirement Markdown anywhere) and "Guard
1050
+ the fleet's edges on every PR" (a copy-pasteable GitHub Actions job around `loam diff --base`,
1051
+ which exits 1 on a removal the fleet still consumes). Both state plainly that the run is red:
1052
+ `service.no-model` and `service.no-spec` are what a fleet at the bottom rung honestly looks like.
1053
+ - **`test/spine-first.test.ts`** pins that sequence against the shipped `examples/fleet.yaml`,
1054
+ including the exact counts of the expected `service.no-model` and `service.no-spec` findings, so
1055
+ the documented first hour cannot drift from the binary.
1056
+ - One stale cross-reference fixed: WORKFLOW.md sent readers to "the README's `loam diff` command
1057
+ note" for the oasdiff invocation, which lives in WORKFLOW.md's own command notes.
1058
+
1059
+ ### Documentation — the pages say what the binary does, and four more claims are checked
1060
+
1061
+ - **README is the front door again, not the manual.** It carries the pitch, the install, the
1062
+ five-minute trial, one table row per command **with every flag the binary registers**, and links.
1063
+ Day zero, the per-command notes, the archive-protection walkthrough and the MCP server moved to
1064
+ [WORKFLOW.md](WORKFLOW.md); the fleet scorecard to [SCHEMA.md](SCHEMA.md); the measured fleet-run
1065
+ costs to `docs/BENCHMARKS.md`. Nothing was deleted, and the page is 53% shorter.
1066
+ - **Five drift fixes, each one a claim that had stopped being true.** `loam list glossary` was
1067
+ missing from the command table; thirteen shipped flags were named on no page; the example fleet's
1068
+ headline read "0 errors, 7 deliberate warnings" against a fixture that has reported ten since the
1069
+ glossary and obligation axes landed; `loam mcp`'s prose listed eleven read commands after
1070
+ `loam_explain` made twelve; and SCHEMA's layout block — the tree readers take for the whole map —
1071
+ never drew `architecture/obligations.yaml`, `glossary/<term>.md`, `features/<FEAT>/glossary/`,
1072
+ `features/<FEAT>/capabilities/<cap>/spec.md` or `features/<FEAT>/specs/<svc>/asyncapi.yaml`.
1073
+ - **Four new pins in `test/docs-facts.test.ts`, placed where the drift actually happened**: every
1074
+ registered long flag is named in README, the `loam list` row's sections equal the CLI's, SCHEMA's
1075
+ layout block names every artifact path `src/core/repo/` still builds, and the example fleet's
1076
+ counts are read out of `test/examples.test.ts` rather than carried as a fourth copy. Each was
1077
+ checked by breaking the fact it guards and watching it fail.
1078
+ - **A derivable count no longer has a dated exemption.** ROADMAP's assessment claimed 261 modules
1079
+ and 21 commands for nine days against a tree holding 394 and 28, legally, because a dated heading
1080
+ covered counts that derive from one readdir. Only a passed/total test count keeps the exemption
1081
+ now; module, package, test-file and command counts are graded live on every page.
1082
+ - **Every Markdown page wraps at 100 columns**, fences, tables and headings excepted, so a prose
1083
+ change reviews as a line-sized diff. The phrase assertions in `docs-drift`, `docs-facts` and
1084
+ `agent-contract` match through a whitespace flattener, which is what makes the wrap a formatting
1085
+ decision rather than a test-breaking one.
1086
+ - **`[Unreleased]` states its changes rather than arguing them**, under a contract that is checked
1087
+ rather than promised: every code, flag, path and command an entry introduced — every one written
1088
+ in bold, which is how this file marks its subjects — is still named by the entry. What left is the
1089
+ argument, 31,342 words down to 12,572. One `ALLOWED_PROSE_TOKENS` entry was pruned with it,
1090
+ because the sentence carrying it was the case for a change, not the change.
1091
+
1092
+ ### Added — architectural obligations: what the architect hands the team, for the rules that vary
1093
+
1094
+ - **`architecture/obligations.yaml`** declares the fleet's architectural obligations —
1095
+ `obligations: {<id>: {description, adr}}` — and a **`#obl-<name>` tag** on an element or an edge
1096
+ in `architecture/landscape.likec4` says where each one applies. loam had exactly one checked
1097
+ architect→team channel before this, and it works: an edge carrying `metadata { op }` obliges the
1098
+ provider to define that operationId or `spine.op-undefined` fails the gate.
1099
+ - **`obligation.uncovered` (warn) is the prerequisite the roadmap named, discharged.**
1100
+ `c4.uncovered` could always say "this architecture object owes a requirement" — but only about a
1101
+ NEW tagged element in a feature's `delta.likec4`, never about the map the fleet actually runs on.
1102
+ - **`obligation.unknown` (error) and `obligation.unapplied` (warn)** are the same join read in both
1103
+ directions, with the asymmetry `permissions.unknown`/`permissions.unenforced` already carry: a tag
1104
+ that resolves to nothing reads exactly like a rule the fleet keeps, while a declaration nobody has
1105
+ placed yet is an honest state.
1106
+ - **`obligation.adr-missing` (error)** — an `adr:` field naming no file.
1107
+ - **`obligation.invalid` (error)** suspends the family, as the permission and capability
1108
+ vocabularies' invalid states do.
1109
+ - **The file is the opt-in**: a fleet without `architecture/obligations.yaml` produces no obligation
1110
+ finding at all, however many tags its map already carries.
1111
+
1112
+ ### Added — a feature brings its own words, and the merge keeps their links pointing at them
1113
+
1114
+ - **`features/<FEAT>/glossary/<term>.md`** is copied into `glossary/` by `loam archive` and removed
1115
+ again by `loam unarchive`, so the vocabulary a change introduces ships and unships with it and is
1116
+ reviewed beside the requirements that use it.
1117
+ - **The route is CREATE-ONLY**, and that is a decision rather than an unfinished half.
1118
+ **`glossary.term-exists` (error)** refuses a term the living glossary already defines, and
1119
+ **`--approve` does not override it** — approving it would be approving a deletion nobody
1120
+ described.
1121
+ - **A feature may cite the word it is introducing** at that word's future living path.
1122
+ - **`loam archive` now re-expresses relative links in the text it moves**, and this closes a defect
1123
+ that predates the glossary.
1124
+
1125
+ ### Added — the domain glossary: `glossary/<term>.md`, checked through the links that cite it
1126
+
1127
+ - **`glossary/<term>.md`** is the fleet's domain vocabulary — one FILE per term, nesting allowed
1128
+ (`glossary/payments/authorization.md` is the term `payments/authorization`), and **no
1129
+ `glossary.yaml` anywhere**.
1130
+ - **`glossary.unlinked` (warn)** — a term no document outside `glossary/` cites: a word the fleet
1131
+ never adopted, or a definition left behind by a rename.
1132
+ - **A whole glossary nobody cites is ONE finding**, not one per term.
1133
+ - **`loam list glossary`** — a fourth explicit-only section beside `capabilities`.
1134
+
1135
+ ### Added — a markdown link is a join, and `loam validate` now resolves it
1136
+
1137
+ - **`link.unresolved` (error)** — a relative markdown link in an authored document whose target does
1138
+ not exist.
1139
+ - **The corpus is every authored markdown document**: both spec axes living and delta, `runbook.md`,
1140
+ every `adrs/` file at all three altitudes, `intent.md`, and each capability document.
1141
+ - **`link.unreadable` (error)** — a document in that corpus that could not be decoded, almost always
1142
+ UTF-16 (PowerShell's `>` and `Out-File` write it unasked).
1143
+
1144
+ ### Added — `loam archive` refuses a business promise nothing keeps, in both directions
1145
+
1146
+ - **`capability.uncovered`** — a feature ADDS a capability requirement and no `Realizes:` line in
1147
+ the same feature's service deltas names it.
1148
+ - **`capability.remove-requirement-realized`** — the same join taken in the removal direction, and
1149
+ it closes a hole that used to archive at exit 0: a feature removing a capability requirement that
1150
+ a living service requirement still realizes merged cleanly, and the next `loam validate --all`
1151
+ then failed with `capability.realizes-unknown` against a service document nobody had touched.
1152
+ - **`loam status --json` gains `capabilities` artifact rows** (additive; present only when the
1153
+ feature carries the directory, with an optional `capability` key).
1154
+
1155
+ ### Added — a feature can change a capability, and `loam archive` merges it
1156
+
1157
+ - **`features/<FEAT>/capabilities/<cap>/spec.md`** carries a delta against the living
1158
+ `capabilities/<cap>/spec.md`, in the requirement grammar and delta algebra every service spec
1159
+ already uses: `## ADDED|MODIFIED|REMOVED Requirements`, `Requirement-ID:` identity, `Based-On:`
1160
+ pins.
1161
+ - **`loam rebase` learned the capability axis**, because `delta.baseline-missing`'s own message
1162
+ tells you to run it. Additive `--json` keys: `capabilities` and `capabilityPins` (a `PinOutcome`
1163
+ carrying `capability` instead of `service`).
1164
+ - **The three capability-document rules now gate archive**, not just `loam validate`:
1165
+ `capability.requirement-unidentified`, `capability.requirement-service-scoped` and
1166
+ `capability.requirement-inert-join` are graded on the DELTA as well as on the living document.
1167
+ - **A `--json` nuance worth knowing:** `Issue.subject` on a `delta.*` code no longer always names a
1168
+ service.
1169
+ - **A fleet with no `features/*/capabilities/` sees no change and pays nothing** — one `existsSync`
1170
+ per feature, no read, no finding.
1171
+
1172
+ ### Changed — `loam list capabilities` and `loam validate` now give the SAME answer to "is this promise kept"
1173
+
1174
+ - **`loam list capabilities --json` carries both corpora.** Each `requirements[]` row gains `keptBy`
1175
+ (additive): the ids of the `#cap-`/`#req-` tagged flows that keep that promise, beside the
1176
+ `realizedBy` service requirements that realize it.
1177
+ - **An unreadable `architecture/` degrades rather than refusing.** The rows themselves come from
1178
+ `capabilities.yaml` and `capabilities/`, both readable, and `validate --all` already reports the
1179
+ broken document as `landscape.invalid` — so the listing prints what it has and marks the flows
1180
+ unread.
1181
+ - **`capability.unrealized` counts flows too.** A capability whose every promise is kept by a tagged
1182
+ flow, named by no service requirement at all, was being told it was "a promise nobody implemented
1183
+ or a word nobody adopted" while loam's own report said the promise was kept.
1184
+ - **Bare `loam list --json` is byte-identical**, and pays nothing: the capabilities section stays
1185
+ explicit-opt-in, and the flow read keeps its byte-scan gate in front of any LikeC4 load.
1186
+
1187
+ ### Added — `#req-`: a use case can keep a business promise, which is the only way a cross-service one gets kept
1188
+
1189
+ - **A `dynamic view` already tagged `#cap-<slug>` may now carry `#req-<slug>`**, naming one of that
1190
+ capability's requirements.
1191
+ - **`usecase.requirement-unresolved` (error)** — six failures with six fixes, and the message says
1192
+ which happened: no resolved `#cap-` tag, two of them, a capability with no document, a document
1193
+ with no requirements yet, an id that document does not declare (close ids offered, each already
1194
+ spelled as the tag to write), or two ids flattening to one slug.
1195
+ - **A tag name accepts exactly `[A-Za-z0-9_-]`, measured at the `likec4@1.59.2` pin**, so the slug
1196
+ rule is now a whitelist rather than "flatten a slash": a `Requirement-ID` may legally contain a
1197
+ `.`, and a capability id its `/`.
1198
+ - **Only a RESOLVED claim keeps a promise.** A broken `#req-` tag suppresses nothing, so
1199
+ `capability.requirement-unrealized` goes on firing beside the error — a typo that silenced it
1200
+ would turn a mistake into a green fleet.
1201
+
1202
+ ### Added — `Realizes:`, the join that makes the business tree checkable
1203
+
1204
+ - **A service requirement says which promise it serves**: `Realizes: checkout#CHECKOUT-CHARGE-ONCE`,
1205
+ a list line resolving against a requirement in `capabilities/<cap>/spec.md`.
1206
+ - **The separator is the LAST `#`, not the first.** The requirement half's grammar excludes `#`
1207
+ while a capability id is a YAML key and a directory name and is constrained nowhere, so splitting
1208
+ at the last one is unambiguous for every id there is.
1209
+ - **`capability.realizes-unknown` (error)** — the entry names no capability requirement.
1210
+ - **`capability.requirement-unrealized` (warn, fleet scope)** — a capability requirement no living
1211
+ `Realizes:` line names, one warning per requirement, subject `<capability>#<id>`.
1212
+ - **`capability.requirement-inert-join` (error)** — a requirement in a capability document carrying
1213
+ `Capability:` or `Realizes:`.
1214
+ - **Realizing a REQUIREMENT realizes its capability.** A requirement carrying only `Realizes:`
1215
+ counts toward `capability.unrealized`, and one carrying both joins is counted once.
1216
+ - **`loam list capabilities --json` gains `requirements[]` per capability** (additive), each with
1217
+ what realizes it.
1218
+
1219
+ ### Added — `capabilities/<cap>/spec.md`: the business tree an analyst can actually write in
1220
+
1221
+ - **A capability may now be declared by a DOCUMENT, not only by a name in
1222
+ `architecture/capabilities.yaml`, and the vocabulary is the union of the two.** Nothing is removed
1223
+ and nothing is deprecated: a fleet that declares twenty names in YAML and has written four
1224
+ documents is the normal state of an adoption, and both sides grade.
1225
+ - **The directory IS the list.** A directory under `capabilities/` is a capability if and only if it
1226
+ holds `spec.md`; nesting is spelled by the tree, so the id `payments/refunds` lives at
1227
+ `capabilities/payments/refunds/spec.md` and `payments` may be a capability in its own right beside
1228
+ it. There is no manifest, for the reason `loam init`'s removed `loam.docs.json` was removed: a
1229
+ second list is a second thing that can disagree.
1230
+ - **The opt-in widened, so a fleet can start grading capabilities without touching the YAML.**
1231
+ `architecture/capabilities.yaml` was this axis's only opt-in; now either file is.
1232
+ - **`capability.doc-missing` (warn)** — a directory under `capabilities/` holding neither the
1233
+ document nor a capability beneath it: what `mkdir` leaves behind halfway through creating one.
1234
+ - **`capability.requirement-unidentified` (error)** — a requirement in a capability document with no
1235
+ `Requirement-ID:`.
1236
+ - **`capability.requirement-service-scoped` (error)** — a capability requirement carrying
1237
+ `Operations:`, `Covers:`, `Publishes:` or `Consumes:`.
1238
+ - **The authored tree's own grades are not suppressed behind `capability.invalid`.** The suppression
1239
+ exists to stop a cascade of grades resolved against a file nobody can read; a directory holding no
1240
+ document, and a requirement with no stable id, consult no vocabulary at all.
1241
+ - **What is NOT in this release, each owned by the roadmap:** the `Realizes:` join from a service
1242
+ requirement back to a capability requirement, the feature-local
1243
+ `features/<FEAT>/capabilities/<cap>/` delta merged by the transactional archive, and
1244
+ `loam new <FEAT> --capability <cap>`.
1245
+
1246
+ ### Changed — a use case is now a CONSUMER, so `loam diff` can refuse where it used to warn
1247
+
1248
+ - **Read this one before upgrading CI.** A use-case hop is now a consumer, so a removal whose ONLY
1249
+ consumer is a step in a `#cap-`-tagged flow moves from `diff.op-removed` (warn, exit 0) to
1250
+ `diff.op-removed-consumed` (error, exit 1) — the same removal, the same command, a different
1251
+ verdict. A repository with tagged use cases may see `loam diff` start failing a pipeline that
1252
+ passed.
1253
+ - **A CONTESTED hop is never a victim.** When the relationships backing a hop disagree about the
1254
+ operation, a removal touching it is reported as a suspension rather than as a broken flow: silence
1255
+ would claim nothing depends on it, and a victim would claim something does.
1256
+ - **`loam context` gains one new exit-1 condition**: `architecture/` failing to parse as a LikeC4
1257
+ project is now a hole in the pack.
1258
+ - **`loam diff` spends no new code for any of this.** Use-case victims ride the `details[]` of the
1259
+ two codes above, naming the flow, the hop number, the step title and the file:
1260
+ `use case 'uc_checkout' step 4 'authorizes the payment' (architecture/usecases/checkout.likec4)`.
1261
+
1262
+ ### Added — the blast radius of a feature, in hops
1263
+
1264
+ - **`loam delta` and `loam status <FEAT>` gain a `useCases` key** (additive; nothing removed): the
1265
+ flows whose hops touch a service the feature addresses, each with its ordered steps and the
1266
+ services each step resolves to.
1267
+ - **The landscape is loaded lazily**, because `loam delta` sits in `/loam-implement`'s hot path and
1268
+ a LikeC4 workspace spin there is felt on every iteration.
1269
+ - **`loam context` and `loam explore --capability` see flows too.** The pack carries `useCaseSteps`
1270
+ for the service it is packing and `capabilities[].useCases` fleet-wide, and an `architecture/`
1271
+ that will not parse becomes a **hole** rather than an absence — the pack says it could not look,
1272
+ never that there are none.
1273
+
1274
+ ### Added — a home for fleet-level decisions, and one way to link between documents
1275
+
1276
+ - **`architecture/adrs/NNNN-*.md` — ADRs about the fleet.** They existed at exactly two altitudes
1277
+ before this: a service's own decisions and a feature's.
1278
+
1279
+ ### Added — `loam validate --all` grades a business use case, hop by hop
1280
+
1281
+ - **A `dynamic view` tagged `#cap-<slug>` is now a checked use case**, and four new stable codes
1282
+ grade it.
1283
+ - **`usecase.step-unbacked` (error)** — a hop the model does not declare.
1284
+ - **`usecase.capability-unresolved` (error)** — a `#cap-` tag that names no declared capability, or
1285
+ two.
1286
+ - **`usecase.step-contested` (warn)** — the relationships backing one hop disagree about the
1287
+ operation, listed as candidates. It suppresses `usecase.step-unlinked` for the same step,
1288
+ structurally rather than by rule — the warn is derived from the attributed verdict, and the two
1289
+ verdicts are disjoint.
1290
+ - **`usecase.step-unlinked` (warn)** — a backed hop that names no operation of the provider's
1291
+ contract, grouped under the OpenAPI adoption axis.
1292
+
1293
+ ### Changed — the source-file limit is 400 lines, up from 300
1294
+
1295
+ - **A house limit, not a published contract — but it is in `CONTRIBUTING.md` and `AGENTS.md`, so it
1296
+ is stated here.** `test/code-limits.test.ts` now fails a `src/` file over **400** lines rather
1297
+ than 300.
1298
+ - **What moved it was a measurement.** Across 359 files in `src/` the median was 150 and p90 was 283
1299
+ — and **thirteen files sat at exactly 300**, with sixteen more between 290 and 300.
1300
+ - **400 rather than 500 or 600, because a ceiling has to keep asking.** The case for having one is
1301
+ that `src/core/agent.ts` reached 2,387 lines with nothing asking; at p90 283 a 500-line ceiling
1302
+ would be inert for years.
1303
+
1304
+ ### Changed — `architecture/` is read as one LikeC4 project, so a use case can live in its own file
1305
+
1306
+ - **`validate --all` now loads `architecture/landscape.likec4` together with every
1307
+ `architecture/usecases/*.likec4` as ONE project**, the way the LikeC4 renderer has always read
1308
+ that directory.
1309
+ - **Why a file of its own, rather than the landscape's `views { }` block.** Readability is the
1310
+ visible half — an analyst opens `usecases/checkout.likec4` and reads one business flow instead of
1311
+ scrolling a fleet-sized map.
1312
+ - **Diagnosis improves rather than widening.** A typo'd element in a use-case file is attributed to
1313
+ THAT file: `landscape: architecture/usecases/checkout.likec4 has 2 error(s)`, and when more than
1314
+ one document is broken every detail line carries its own file.
1315
+ - **Two authoring rules invert inside `architecture/`, and both are enforced by LikeC4 rather than
1316
+ by loam.** Exactly one document declares the `specification` block: every `.likec4` file loam
1317
+ parses alone must declare its own, while a second declaration inside one project is a duplicate
1318
+ error blamed on BOTH files (measured).
1319
+ - **The generated `architecture/subsystems.likec4` is excluded from that project**, and
1320
+ docs/DESIGN.md rule 26 already said why: it stays a byte compare, and loam reads its contents in
1321
+ no document.
1322
+ - **No new command, no new flag and no `--json` key for THIS change** — the loader is a mechanism,
1323
+ and the codes that grade on it are the use-case section above.
1324
+
1325
+ ### Fixed — a service filed under a subsystem was named at a directory that does not exist
1326
+
1327
+ - **Every message that names an existing service's directory now spells it from the enumeration.**
1328
+ `services/<id>/` is right for an unfiled fleet and wrong for every filed one: a service under
1329
+ `services/platform/` lives at `services/platform/<id>/`, and eight separate messages joined the
1330
+ root form anyway — so `loam status` told a reader to go to `services/checkout-web/` when the
1331
+ directory was `services/commerce/checkout-web/`, and `loam gate --service identity-service`
1332
+ reported on `services/identity-service/` for a service at `services/platform/identity-service/`.
1333
+ - **Eight sites, one rule.** `loam status`'s two adoption rungs, `loam gate`'s target and partner
1334
+ rungs, `loam validate --all`'s `landscape.service-unmodelled` and the datastore-placement fix in
1335
+ `fleet-shape`, the `loam adopt` brief's landscape instruction and artifact shape, `loam vouch`'s
1336
+ confirmation prompt, and `loam seed`'s "left exactly where it is" line — which was the worst of
1337
+ them, because that sentence is ABOUT placement.
1338
+ - **`serviceTreePath` in `core/kernel/ids/dirs.ts` is that implementation**, with
1339
+ `serviceTreePathOf(docsDir, id)` beside `locateServicePaths` for callers that hold only an id.
1340
+ - No new code, no new flag, no `--json` key: every change is message text and the internals that
1341
+ feed it.
1342
+
1343
+ ### Added — a docs repo now scaffolds a README, the one file in it addressed to a person
1344
+
1345
+ - **`loam init --create` writes `README.md` first.** Everything else a docs repo starts with is
1346
+ either a document about the fleet or `AGENTS.md`, which runs to some 1500 generated lines and is
1347
+ named so that a human does not open it.
1348
+ - `migrate-openspec` stages the same file, because its target is meant to be the docs repo
1349
+ `loam init` makes.
1350
+
1351
+ ### Fixed — an authored view id could take the whole fleet map down while the gate printed green
1352
+
1353
+ - **`validate --all` now refuses a landscape view whose id loam generates**
1354
+ (`subsystem.view-id-collision`, **error**, one finding per colliding id, fleet scope).
1355
+ - **Exactly one pair is compared, and that is the measured scope rather than a simplification.** A
1356
+ duplicate view id *inside* one document is already two LikeC4 errors, so `landscape.invalid` has
1357
+ had it all along (measured).
1358
+ - **The check asks the generator what it mints.** `core/repo/tree/views.ts` now exports its id
1359
+ function and the `subsystem_` prefix instead of keeping them private, because a check carrying its
1360
+ own copy of that escaping would go quiet the day the encoding changed — and going quiet is the
1361
+ failure this whole family exists to prevent.
1362
+
1363
+ ### Changed — the view doctrine narrows: loam reads what a view DECLARES, never what a view SHOWS
1364
+
1365
+ - **`docs/DESIGN.md` gains rule 26, and the written doctrine finally matches the enforced one.**
1366
+ loam's prose said it "never parses views"; what it actually never does is COMPUTE one.
1367
+ - **Nothing is asked of any fleet, and nothing grades a views block's absence.** A repo that
1368
+ declares no dynamic view owes loam nothing, today or ever; `loam init` still scaffolds no `views`
1369
+ block — and that stays true after the use-case section above, whose four codes grade only a view
1370
+ somebody deliberately tagged. **Rule 26 itself adds no command, no flag, no stable code, no
1371
+ `--json` key and no severity change** — it records a boundary, not a behaviour.
1372
+ - **`npm run arch:check` gains a `view-stage` scan, so the doctrine is a failing build rather than a
1373
+ paragraph.** `computedModel` and `layoutedModel` may not appear in `src/` at all (zero
1374
+ occurrences, so no whitelist — the one mention is inside a comment, which the scan's `codeOnly`
1375
+ blanks), and `$data` may appear only under `src/core/c4/parsed/`, which confines the blast radius
1376
+ of an upstream shape change to one module.
1377
+ - **`test/likec4-view-shape.test.ts` pins the parsed record's shape at the `likec4@1.59.2` pin,
1378
+ written before any reader exists.** Ten measured facts, each load-bearing: LikeC4 synthesises an
1379
+ `index` view into every document (`_type: "element"`, no `sourcePath`) that a reader must ignore;
1380
+ a `<-` reply is recorded reversed AND flagged `isBackward`, while a forward step carries no such
1381
+ key; a `loop` is a nested entry with no endpoints and no `astPath` of its own; an untagged view
1382
+ reads `tags: null`; a step carrying `metadata` is a parse error, and a `#cap-` tag written after
1383
+ `title` is five of them.
1384
+ - **loam now READS a declared `dynamic view`, and grades nothing on it yet.**
1385
+ `src/core/c4/parsed/dynamic-views.ts` normalizes the parsed record into `ParsedView`/`ParsedStep`
1386
+ — id, tags, title, description, and ordered steps carrying `source`, `target`, `title`, `notes`,
1387
+ `isBackward` and `astPath` — and it is the only module permitted to touch `$data`.
1388
+ - **`astPath` is carried but optional, and that is a refusal rather than an oversight.** Every leaf
1389
+ carries one at the pin, so requiring it would look free — and would mean that an upstream release
1390
+ which stopped emitting the field turned every use case in the fleet into a view with zero steps at
1391
+ exit 0.
1392
+ - **Two modules split, no behaviour changed.** `core/c4/likec4.ts` reached the 300-line limit, and
1393
+ the seam was already in it: resolving an element id to the `services/<id>/` directory it stands
1394
+ for is a distinct phase from loading a document, and runs over loam's own records rather than
1395
+ LikeC4's — it moved verbatim to `core/c4/resolve/service.ts` (28 importers rewritten, 22 of them
1396
+ one line longer, which is what splitting a widely-imported module costs and is not payable by
1397
+ squeezing a comment).
1398
+ - **`SCHEMA.md`'s planned `services/<svc>/flows/` tree is retired rather than built.** A
1399
+ cross-service interaction flow is a `dynamic view` in `architecture/landscape.likec4` — the one
1400
+ document that already holds every cross-service edge.
1401
+
1402
+ ### Fixed — a tag declared on a LikeC4 specification KIND could switch the fleet gate off
1403
+
1404
+ - **`validate --all` no longer reports a fleet it has stopped checking as agreeing.** LikeC4 1.59.0
1405
+ added tags on specification kinds, and 1.59.2 applies them to every element of the kind before
1406
+ loam sees anything: `specification { element softwareSystem { #external } }` puts `#external` on
1407
+ every `softwareSystem` in the document. loam reads `#external` as "deliberately not ours — stop
1408
+ grading it", so those six words silenced the landscape↔services reconciliation for the whole fleet
1409
+ at once, and the run ended on `landscape.matched` — *"N service(s) modelled —
1410
+ architecture/landscape.likec4 and services/ agree"* — over a map nothing had checked.
1411
+ - **One new stable finding code, `landscape.kind-tag-graded` (error).** It fires when the
1412
+ landscape's `specification` block declares `#external` or `#platform` on an element kind **and**
1413
+ at least one element of that kind stands for a real `services/<id>/` directory — by an explicit
1414
+ `metadata { service }` binding, or by a title naming one.
1415
+ - **A graded tag on a kind is still legal where it exempts nothing of ours** — and it has to be:
1416
+ `examples/docs/architecture/landscape.likec4` declares `element topic { #external #platform }` so
1417
+ that the Kafka topics nested inside an external broker do not each demand a
1418
+ `services/payment.events/` nobody owes, and that spelling is the one that scales.
1419
+ - **`loam archive` now refuses a delta that declares its own feature tag on a kind**
1420
+ (`merge-failed`, exit 1, nothing written).
1421
+
1422
+ ### Fixed — a feature delta's non-model blocks were silently discarded on archive
1423
+
1424
+ - **`loam archive` now refuses a delta whose `deployment { }` or `global { }` block, or whose
1425
+ `dynamic view`, the landscape merge cannot carry** (`merge-failed`, exit 1, nothing written;
1426
+ `--approve` does not override — the loss is mechanical, not a judgement about coherence).
1427
+ - **A static `views { }` block is still accepted and still not merged** — `loam new` scaffolds one
1428
+ into every delta, and it is meant to be rendered while the feature is in flight rather than to
1429
+ travel.
1430
+
1431
+ ### Added — `loam seed`: the fleet map templated from human-stated facts
1432
+
1433
+ - **New command `loam seed [--from <file>] [--json]`** — a tiny human-authored `fleet.yaml` (a
1434
+ `services:` list of ids, optional `subsystems:`, `externals:` and `calls:` lines like
1435
+ `checkout -> payments`) templated mechanically into `architecture/landscape.likec4` — one
1436
+ `softwareSystem` per service bound with `metadata { service '<id>' }`, `#external` systems, plain
1437
+ edges, the scaffold's own two views — plus one `services/<id>/` directory per service (under its
1438
+ subsystem where one is declared, with the marker and the regenerated
1439
+ `architecture/subsystems.likec4` in the same commit), all in one journaled transaction under the
1440
+ docs lock.
1441
+ - **Seed never overwrites human work.** It writes the landscape only when the file is absent, is the
1442
+ scaffold's untouched stub, or carries seed's own line-1 stamp (`// loam-seed sha256:<digest of the
1443
+ rest>`) with a matching digest — a self-verifying record, no state anywhere else.
1444
+ - **`loam status`'s empty-fleet teaching ladder now names the mechanical path**: the
1445
+ `next.author-landscape` rung over an untouched scaffold stub teaches `loam seed --from fleet.yaml`
1446
+ alongside authoring by hand.
1447
+
1448
+ ### Added — the single-repo trial: `loam init` teaches the first hour
1449
+
1450
+ - **`loam init`'s human output now ends with the first-hour sequence** —
1451
+ `loam adopt --service <id> --json` → `loam validate --service <id>` → `loam vouch --service <id>`
1452
+ → `loam status`, each with a one-line reason — printed exactly when a single run both creates the
1453
+ docs repo (`--create`) and ends with a service bound (`--service`, or a binding the repo's
1454
+ existing `loam.json` already carried): the composition of the README's new five-minute trial.
1455
+
1456
+ ### Added — `loam explain`: the vocabulary wall, answered from the binary
1457
+
1458
+ - **New command `loam explain [<subject>] [--json]`** — what a finding code, a refusal code or a
1459
+ loam concept means, without a docs repo and without wiring: like `loam instructions` it reads no
1460
+ `loam.json`, because the vocabulary wall is hit in the first minute in an unfamiliar repository. A
1461
+ finding code (`spine.op-undefined`) answers with the /loam-check fix table's own row — severity
1462
+ note, "what it means" and "what to do" verbatim, plus the `loam validate` invocation that surfaces
1463
+ it where the adoption brief knows one — **parsed at runtime from the same workflow body the binary
1464
+ ships**, so the explanation cannot drift from the documentation; a code graded in more than one
1465
+ table (service scope and `loam archive` plan time, say) explains every context.
1466
+ - **`loam validate` and `loam verify` text reports gain a one-line footer** — `` → codes ride in
1467
+ `--json`; `loam explain <code>` says what one means and how to fix it `` — printed only when a
1468
+ non-ok finding (validate) or a notice (verify) was printed, appended as the report's last line,
1469
+ and only in text mode: every `--json` payload is byte-identical to before.
1470
+ - **`loam mcp` gains `loam_explain`** — the read-tool table grows to twelve; one optional `subject`
1471
+ argument, read-only like the rest.
1472
+
1473
+ ### Added — `loam list --subsystem` and `--owners`: the adoption campaign, sliced by group and by team
1474
+
1475
+ - **`loam list` gains `--subsystem <name>`** — the services section limited to one subsystem's
1476
+ services, at any depth, resolved against the same flat namespace every `loam subsystem` verb
1477
+ reads.
1478
+ - **`loam list` gains `--owners <path>`** — a mechanical, read-only join of each listed service's
1479
+ directory to the owning teams in the user-named CODEOWNERS file (loam still has no permission
1480
+ model of its own; the forge's file stays the authority). `--json` gains the additive `owners` key:
1481
+ `path`, `teams[]` (owner plus that team's services in the listing's own filtered-and-ordered row
1482
+ order — the per-team campaign worklists), `unowned[]` (rows no rule matched, listed explicitly),
1483
+ `skippedRules[]`.
1484
+ - **One new stable refusal, `owners-unreadable`** (exit 1): the CODEOWNERS path cannot be read, or a
1485
+ line in it cannot be parsed as `pattern owner…` — the message names the path and line.
1486
+ - Considered and deferred: joining `features/**` to teams (the campaign worklist is services-only
1487
+ until someone asks), and any wider CODEOWNERS pattern grammar — implementing the fleet-wide `*`
1488
+ default above all — a half-implemented glob would attribute services the forge never did, so the
1489
+ subset grows only deliberately, with its own CHANGELOG line.
1490
+
1491
+ ### Added — the axis-adoption rollup on `validate --all`
1492
+
1493
+ - **`loam validate --all --json` gains the additive `adoption` key on the `scorecard` payload** —
1494
+ services participating in each contract axis (`requirements`, `arch`, `openapi`, `asyncapi`,
1495
+ `permissions`, `capabilities`), one mechanical rule per axis (a requirement block for the two spec
1496
+ axes, file presence for the two contract files, a non-REMOVED `Requires:`/`Capability:` entry for
1497
+ the two vocabularies), the denominator carried once as the payload's existing `services` count.
1498
+ - **Text mode groups the warnings a fleet-wide not-started axis alone causes under one banner per
1499
+ axis** — e.g. a freshly declared capability vocabulary's per-capability `capability.unrealized`
1500
+ warns collapse to
1501
+ `⚠ capabilities axis not started fleet-wide (0 of N services), expected during staged adoption — k warning(s) grouped: …`.
1502
+
1503
+ ### Added — `loam vouch --sample <n>`: a partial read, recorded as one
1504
+
1505
+ - **New flag `loam vouch --sample <n> [--service <id>] [--yes] [--json]`** — vouch after reading a
1506
+ deterministic sample of `<n>` sections per spec-axis file, instead of the whole document.
1507
+ `--sample` never loosens attendance — no TTY and no `--yes` is still `vouch-unattended`, `--json`
1508
+ without `--yes` still refuses, and vouch stays off the generated agent allowlist.
1509
+ - **The scope is stamped beside `vouched_by`, never inside `status`** — a new frontmatter field
1510
+ `vouch_scope: sampled <k>/<n> seed=<16 hex>`, written by `loam vouch` and never by hand.
1511
+ - **A sampled vouch is distinguishable from a full one on every surface that reports trust.**
1512
+ `loam list` shows `vouched (sampled)` in text and adds `vouchScope: "sampled"` to the row in
1513
+ `--json` (additive, omitted for a full vouch; `maturity` stays `vouched`). `loam validate` reports
1514
+ the new warning `sources.sampled-vouch`.
1515
+ - **One new stable code, `sources.sampled-vouch` (warn)** — one finding per sampled spec file,
1516
+ naming k of n, the seed and `last_verified`, from the docs repo as well (it needs no service repo,
1517
+ like `content.stale`).
1518
+ - **Both spec axes count, and the fleet summaries say so.** The sample is per file, so a service
1519
+ whose `arch.spec.md` alone was sampled is one part of whose documentation nobody read:
1520
+ `loam list`, `loam show` and `loam status` grade the service from EITHER axis, and `show` prints
1521
+ the scope per file (with `archSpec.vouch_scope` additive in `--json`).
1522
+ - **A later full vouch clears it.** `loam vouch` with no `--sample` deletes the `vouch_scope` field
1523
+ (the shared frontmatter writer gained an explicit list of keys to remove — deletion is something a
1524
+ call site names, never a null value it happens to hold — leaving the body byte-identical and every
1525
+ neighbouring line in place), so a document that has since been read in full stops reporting as
1526
+ sampled.
1527
+ - **`--sample` composes with `--pack`.** `loam vouch --pack --sample <n>` prints the reading list
1528
+ for the sampled vouch that follows, through the same seeded derivation — so the pack and the stamp
1529
+ can never prescribe different sections — and prints only those sections in place of the full
1530
+ heading list.
1531
+
1532
+ ### Added — `loam vouch --pack`: the re-vouch reading pack
1533
+
1534
+ - **New flag `loam vouch --pack [--service <id>] [--json]`** — a re-vouch now starts from a printed
1535
+ delta instead of a full re-read: for each spec-axis file (spec.md, and arch.spec.md when present)
1536
+ the pack prints (a) the file's git diff from the last commit in the DOCS repo's history whose body
1537
+ hashes to the stamped `content_digest`, with a section-level summary (changed/added/removed
1538
+ headings), (b) the source files that moved against the stamped `sources_files` index —
1539
+ added/changed/removed paths in the stale finding's own one-column layout — and (c) the sections
1540
+ whose text is identical to the vouched ancestor's, listed under `vouched_by`/`last_verified` so
1541
+ the read can stop where it should.
1542
+ - **`--pack --yes` is refused** (`invalid-option`, exit 1): `--pack` is the reading list and `--yes`
1543
+ the unattended stamp, and a pack that immediately stamped would defeat the read it just
1544
+ prescribed.
1545
+
1546
+ ### Added — `loam verify --diff-answers`: mechanical cross-examination of two blind answer sets
1547
+
1548
+ - **New flag `loam verify <FEAT> --diff-answers a.json b.json [--service <id>] [--json]`** — a
1549
+ read-only lens that validates BOTH answer sets with the same `checkAnswers` discipline the record
1550
+ path uses, against the same derived checklist, then reports per claim who agrees with whom:
1551
+ `cross.agree-confirmed`, `cross.agree-unconfirmed`, `cross.disagree` — joined by deterministic
1552
+ claim id only, never by claim text — plus the `cross.evidence-disjoint` warn notice naming
1553
+ agreed-confirmed claims whose cited FILE sets do not overlap (a trailing `:line` or `:start-end`
1554
+ suffix is normalized away, so two citations into one file never read as disjoint).
1555
+ - **Agreement is not verification, structurally**: the payload deliberately carries no `verified`,
1556
+ `verdict` or `attested` key, `verification.yaml` is untouched (byte-identical before and after, no
1557
+ docs lock taken, works while a writer holds one), and nothing this flag does can upgrade
1558
+ `attested` to `verified` — two agents agreeing (usually two contexts of the same model, so
1559
+ correlated) ranks where a reviewer reads first, it proves nothing.
1560
+ - **Refusals reuse existing codes and name WHICH file**: `answers-unreadable` / `answers-mismatch` /
1561
+ `answers-unevidenced` prefixed by "first/second answer set (`<file>`)"; `invalid-option` for a
1562
+ combination with `--record`/`--results`/`--contract-results`, for an arity other than exactly two
1563
+ files, and for an archived feature (frozen history has no current checklist to answer).
1564
+
1565
+ ### Added — evidence pins: federated records stamp what was cited, and `loam validate` convicts drift
1566
+
1567
+ - **`loam verify <FEAT> --service <id> --record` now stamps `evidence_pins` under each
1568
+ agent-confirmed claim** — one pin per `file:line` citation: the cited file's sha256 at the
1569
+ attested commit (CRLF-normalized, so a Windows checkout's line endings can never read as drift),
1570
+ the cited line's text (trimmed, capped at 200 characters), and the literal token the claim asserts
1571
+ (the operationId, message name or edge op), carried structurally so nothing ever parses claim
1572
+ prose — stamped only when the attested blob actually contains it, so the validate-side token lint
1573
+ can only ever convict a disappearance, never re-litigate an absence the record-time notice already
1574
+ reported once.
1575
+ - **One new record-time notice, `verify.evidence-token-missing`** (warn, in the existing
1576
+ `notices[]`, text and `--json` alike): a just-confirmed citation's file at the attested commit
1577
+ does not contain the claim's own token.
1578
+ - **`loam validate`, run in a service's own repository, re-checks the pins** of every ACTIVE feature
1579
+ record that service has attested and reports the new `evidence.*` finding family: ok
1580
+ `evidence.checked` / `evidence.unpinned`, warn `evidence.unresolved` / `evidence.moved` /
1581
+ `evidence.line-changed` / `evidence.token-missing`, plus warn `evidence.record-unreadable` for a
1582
+ verification.yaml that exists but cannot be read — a hand edit that breaks the record must surface
1583
+ here as a finding, not as a silently green run.
1584
+ - **No new command, no new flag, no new `ErrorCode`, no exit-code change**; the `--json` additions —
1585
+ `evidence_pins` inside the read and frozen views' `claims[]`, the notice, the findings — are
1586
+ additive payload keys.
1587
+
1588
+ ### Added — `loam list --needs-work --review-order`: the blast-radius review queue
1589
+
1590
+ - **`loam list --needs-work` gains `--review-order`** — the adoption worklist sorted by blast
1591
+ radius: for each service, the number of DISTINCT other services that depend on it — drawing a call
1592
+ edge into it (with or without an `op`; a delivery edge carrying `consumes` is not a call, because
1593
+ on the event spine the arrow follows the message), subscribing to a message it produces via a
1594
+ drawn `consumes` edge, or carrying a living `Consumes:` requirement line naming a message its
1595
+ asyncapi.yaml declares `action: send` for.
1596
+
1597
+ ### Changed — `loam status` walks the first hour of an empty docs repo
1598
+
1599
+ - **`loam status` in a docs repo with zero services and zero features now teaches the first hour
1600
+ instead of reporting it clean.** Three new stable `next[]` codes, in order:
1601
+ `next.author-landscape` (also emitted when `architecture/landscape.likec4` is still the scaffold's
1602
+ untouched bytes, not only when it is absent — the step names the file and the literal command),
1603
+ `next.bind-service` (wire a service repo's `loam.json` to this fleet) and `next.adopt-first`
1604
+ (write the first baseline).
1605
+
1606
+ ### Added — `loam diff`: semantic branch diff of the docs repo, with the victims named
1607
+
1608
+ - **New command `loam diff --base <ref> [--json]`** — the fleet-meaningful delta between the living
1609
+ docs and a base git ref of the docs repo (a branch, `origin/main`, a commit sha): services
1610
+ added/removed, living requirements added/removed/modified (by `Requirement-ID:`/heading identity
1611
+ and content digest, so a `loam rebase` pin never reads as a change), OpenAPI operations
1612
+ added/removed/newly-deprecated, AsyncAPI messages added/removed, and cross-service joins that
1613
+ appeared or went away.
1614
+ - **Fourteen new stable finding codes, zero new `ErrorCode` union members**:
1615
+ `diff.service-added`/`-removed`, `diff.requirement-added`/`-removed`/`-modified`,
1616
+ `diff.op-added`/`-removed`/`-removed-consumed`/`-deprecated`,
1617
+ `diff.message-added`/`-removed`/`-removed-consumed`, `diff.consumer-added`/`-removed` — documented
1618
+ in the generated AGENTS.md's command map.
1619
+ - **loam now asks read-only git questions in the DOCS repo** (`rev-parse`/`ls-tree`/`show` — no
1620
+ checkout, no temp writes, every child process bounded by the same timeout and output caps
1621
+ provenance uses).
1622
+ - **`loam mcp` gains `loam_diff`** — the read-tool table grows to eleven; the tool schema marks
1623
+ `base` required (the table now models required flags, mirrored against commander's own
1624
+ `requiredOption` by the shape test), and a call without it is refused as invalid params before any
1625
+ argv is built.
1626
+
1627
+ ### Added — `loam verify --contract-results`: contract-test reports answer the `api.exposes` claims
1628
+
1629
+ - **New flag `loam verify <FEAT> --contract-results <file>`** — answer the `api.exposes` claims
1630
+ mechanically from an API contract-test report, the way `--results` answers the `scenario.tested`
1631
+ claims from a cucumber one.
1632
+ - **Records gain a `contractReport:` pin and `answered_by: external-runner`** — the consumed
1633
+ report's path, sha256, mtime, distinct-operation count and format land on the record (all-at-once)
1634
+ or inside the recording service's attestation (federated, pruned with its answers, resolved inside
1635
+ the attesting repository and bound to the attested commit exactly as `report:` is), and each
1636
+ contract-confirmed claim says an external runner answered it (`[contract]` on the printed line).
1637
+ - **The verdict ladder does not move** — a contract report can never answer a `scenario.tested`
1638
+ claim, so `attested` versus `verified` still turns on scenario claims alone; a contract-confirmed
1639
+ `api.exposes` claim counts toward `verified` exactly as an agent-confirmed one always has, with
1640
+ strictly more provenance behind it.
1641
+ - **Ownership mirrors `--results`, contested operations included**: under the flag the contract
1642
+ report owns every `api.exposes` claim in scope — an answers-file entry for one refuses
1643
+ `answers-mismatch`, a mechanical flag alone refuses while other claims are outstanding, an
1644
+ unreadable or unrecognizable report refuses `answers-unreadable` naming the accepted shape, and
1645
+ the flag refuses on archived features exactly as `--record` does. An operationId that more than
1646
+ one service on the checklist exposes is `verify.digest-contested`'s case on this axis — a report
1647
+ entry names no service, so those claims stay `unconfirmed` and the run carries the new
1648
+ **`verify.operation-contested`** notice (warn, gating nothing, an open-vocabulary notice code
1649
+ exactly as `verify.claims-open` was); `--service` resolves it.
1650
+
1651
+ ### Added — `loam mcp`: the read commands as MCP tools over stdio
1652
+
1653
+ - **New command `loam mcp`** — a hand-rolled MCP stdio server (JSON-RPC 2.0, newline-delimited,
1654
+ protocol revision 2025-06-18 negotiated down to 2025-03-26/2024-11-05) for hosts that reach tools
1655
+ through MCP rather than a shell.
1656
+ - **One machine contract, not two**: every tool result carries the command's `--json` envelope
1657
+ VERBATIM in `content[0].text` (and parsed again as `structuredContent`), with `isError` following
1658
+ the envelope's `ok` — so `ok`, `error.code` and every payload key mean exactly what they mean at
1659
+ the CLI, and a refusal (`no-config`, `unknown-target`, …) arrives as a readable envelope with
1660
+ `isError: true`, not as a dead call.
1661
+ - **No new stable codes, no envelope changes, no new dependency** — the server is hand-rolled on
1662
+ `node:stream` and the existing commander wiring; `@modelcontextprotocol/sdk` was considered and
1663
+ declined (three runtime dependencies is a product decision).
1664
+
1665
+ ### Added — `loam gate`: can-i-deploy, answered from recorded evidence
1666
+
1667
+ - **New command `loam gate [--service <id>] [--strict] [--json]`** — a can-i-deploy-shaped PURE
1668
+ QUERY for deploy pipelines outside loam's lifecycle: it executes nothing, writes nothing, takes no
1669
+ lock, and answers only from evidence previous runs recorded.
1670
+ - **Four new stable finding codes, all in the open finding vocabulary — zero new `ErrorCode` union
1671
+ members.** `gate.service-undocumented` (error): the gated service itself sits below `documented`,
1672
+ so the docs cannot say what its joins are; re-running succeeds once `loam adopt`'s required
1673
+ artifact set exists — and under a map that cannot be read it fires only when the service is below
1674
+ `documented` regardless of whether an API is owed, because the api question is then unanswerable
1675
+ and one unanswerable fact yields one finding.
1676
+ - **The `--json` payload** carries `verdict`, `strict`, `landscape` (`read`/`absent`/`invalid`),
1677
+ `partners[]` (maturity rung, consumer/provider role, the joining operations and messages,
1678
+ `external`), `features[]` (the record's own tallies and three-valued verdict per touching feature)
1679
+ and `checks[]` (findings by check, in the shared finding shape) — so a pipeline wanting a stricter
1680
+ bar (vouched partners, no attested scenarios) branches on data rather than waiting for a flag.
1681
+ - **No new ErrorCode strings, no changed envelope fields, and no change to what gates the archive**:
1682
+ verify still never gates the merge, archive still reads no verification record — `loam gate`'s
1683
+ verdict is advice to a deploy pipeline loam does not own (WORKFLOW.md's "What actually gates" now
1684
+ says so beside the other two gates).
1685
+
1686
+ ### Added — `loam context`: one service's docs slice as one deterministic briefing
1687
+
1688
+ - **New command `loam context <service>`** — assemble, for one service, the exact docs slice bound
1689
+ to it as one payload an agent loads before working in that service's repository: the living
1690
+ requirements and arch requirements VERBATIM (bodies and Given/When/Then lines, with the
1691
+ `Operations:`/`Covers:`/`Requires:`/`Capability:`/`Publishes:`/`Consumes:` joins parsed out beside
1692
+ them), the OpenAPI operations with the requirements governing each (`x-loam-remove` filtered), the
1693
+ AsyncAPI messages with their send/receive direction, the fleet edges one hop out with the map's
1694
+ own health beside them (the `landscape` key carries `present` and `parses`, so silence is evidence
1695
+ rather than absence), the `Requires:` permissions resolved against `architecture/permissions.yaml`
1696
+ (an undeclared entry is carried with `declared: false`, never refused), the capabilities the
1697
+ service realizes — with both fleet vocabularies' own health beside their sections, so
1698
+ `declared: false` under an unreadable vocabulary reads as "nobody could look" — maturity and
1699
+ frontmatter provenance, presence pointers for runbook/health/ADRs, and every ACTIVE feature's
1700
+ delta over the service in `loam delta`'s own payload shapes, each ending with the runnable
1701
+ `loam delta <FEAT> --service <id>` line.
1702
+ - **Exit 1 with `ok: true` when the pack has a silent hole** — a landscape that is present but does
1703
+ not parse, an unreadable living `openapi.yaml`/`asyncapi.yaml`, an unreadable fleet vocabulary
1704
+ (permissions or capabilities), another service's spec file the capability rollup could not read
1705
+ (degraded per file into the `capabilitiesUnread` payload key, never a refusal — one service's
1706
+ briefing is not hostage to a sibling's encoding), or an included feature whose `delta.likec4` has
1707
+ errors or whose contract delta does not parse — and a feature whose delta does not parse is still
1708
+ INCLUDED in every service's pack with its errors in the payload, because a change nobody can read
1709
+ is in flight, not absent.
1710
+ - **Internal relocation**: the delta projection helpers (`apiChanges`, `eventChanges`, `archSlice`,
1711
+ `introducedServices`, `livingServices`) moved from `src/commands/delta/slices.ts` to the new
1712
+ `src/core/projection/` package, and `stripFrontmatter` to `src/core/document/frontmatter.ts`, the
1713
+ day the pack became their second caller — commands do not import commands, so the shared
1714
+ projection now lives in core.
1715
+ - **`/loam-implement` gains the pack as its load step** — `loam context $2 --feature $1 --json`
1716
+ right after `loam status`, before `loam delta`: the delta is what changes, the pack is what it
1717
+ changes into, and reading it first is how a MODIFIED requirement lands as an edit rather than a
1718
+ rewrite.
1719
+
1720
+ ### Added — `loam open`: an editor workspace derived from the committed bindings
1721
+
1722
+ - **New command `loam open`** — writes a `.code-workspace` file joining the docs repo and every
1723
+ service checkout whose committed `loam.json` binds to it.
1724
+ - A sibling whose `loam.json` exists but cannot be read is reported in `skipped[]` — never fatal,
1725
+ and never a member, because an unreadable binding proves nothing; a sibling bound to a different
1726
+ docs repo is silently not a member.
1727
+
1728
+ ### Added — the fleet scorecard
1729
+
1730
+ - **`loam validate --all --json` gains one additive payload key, `scorecard`** — ceiling-vs-actual
1731
+ aggregates for the whole fleet, recomputed per invocation from the reads the run already makes and
1732
+ never stored: `services` with the maturity rollup (every rung present), `provenance` (vouched
1733
+ services · stale source digests · sources unverifiable from here — the latter two counted off this
1734
+ run's own findings by subject, so each is the same number the report prints beside them),
1735
+ `verification` (recorded records — the features axis's `active` count is their one shared
1736
+ denominator — the three-valued verdicts, and `claims.answered` — the confirmed claims split by
1737
+ their own `answered_by`, one count per provenance with all three keys always present: `runner` (a
1738
+ digest-matched cucumber run), `external-runner` (an operationId-matched contract report) and
1739
+ `agent` (somebody's word, and every record too old to carry the field)), `operations` (defined →
1740
+ governed · deprecated, plus deprecated operations something still joins to), `messages` (defined →
1741
+ linked), `c4` (the fleet map's drawn systems, by the map's own census — actors, externals,
1742
+ groupings and containers excluded — → covered by an arch requirement) and `features` (active, with
1743
+ each feature's fleet stage over all five stage keys).
1744
+ - **`loam validate --all` text output appends a "fleet scorecard" table** after the summary footer —
1745
+ one line per axis, ceiling before actual.
1746
+
1747
+ ### Added — inverse coverage on the event axis, and the partial-verification honesty line
1748
+
1749
+ - **New stable finding code `event.ungoverned`** (warn) — `loam validate --service` / `--all` now
1750
+ warns, per living asyncapi.yaml, when declared message(s) are named by no requirement's
1751
+ `Publishes:`/`Consumes:` line, listing the orphaned names.
1752
+ - **New verify notice `verify.claims-open`** (warn) — `loam verify`'s read view and the frozen
1753
+ post-archive view now carry a one-line honesty summary whenever a record EXISTS and leaves claims
1754
+ unconfirmed or unanswered: "not a clean result", with all four counts and the agent's-word share
1755
+ of the confirmed.
1756
+ - **WORKFLOW.md gains "The honestly-small change"** — the walkthrough for a one-service,
1757
+ requirements-only change: scaffold with `--touches`, delete the scaffolded C4 delta on the
1758
+ scaffold's own printed advice, author intent.md and one spec delta, and ship down the three-step
1759
+ `next[]` (`next.generate-tests`, `next.verify`, `next.archive`), with `loam status` printing "(not
1760
+ written — none owed)" beside every artifact the change legitimately skips.
1761
+ - No new command, flag, exit code, or envelope key; both new codes are open-vocabulary
1762
+ finding/notice codes, exactly as `api.ungoverned` and `verify.scenario-attested` were before them.
1763
+
1764
+ ### Added — the LikeC4 canary
1765
+
1766
+ - **A weekly scheduled workflow** (`.github/workflows/likec4-canary.yml`, manual dispatch included)
1767
+ **installs `likec4@latest` over the exact 1.59.2 lockfile pin and runs the LikeC4-touching suites
1768
+ against it**, then the committed 120-service benchmark as informational evidence in the run's step
1769
+ summary.
1770
+
1771
+ ### Documentation
1772
+
1773
+ - **The product comparison now follows OpenSpec v1.10.0** (released 2026-08-19) while the certified
1774
+ parser/migration corpus stays pinned at the v1.9.0 commit
1775
+ `2826b8889e5223a9a8095d4428b60b56597e1020` — v1.10.0 changed no requirement-Markdown format
1776
+ upstream, and COMPARISON.md, README.md and MIGRATING-from-OpenSpec.md now name the divergence as
1777
+ deliberate.
1778
+
1779
+ ### Added — the shipped documentation is now guarded like a contract
1780
+
1781
+ - **package.json, the release preflight, and the smoke share one reviewed package-file list**
1782
+ (`scripts/package-docs.mjs`): `files[]` set-inequality with the reviewed list is now a release
1783
+ blocker and an `npm test` failure, and every reviewed page must be relatively linked from README —
1784
+ adding a shipped document is deliberately a three-place edit, and the blocker names all three
1785
+ places.
1786
+ - **New drift guards run inside `npm test`** (test/docs-facts.test.ts, test/package-docs.test.ts):
1787
+ counted facts in the shipped pages must match the live tree or sit under ROADMAP's dated
1788
+ assessment snapshot; the README command table is set-equal to the built program's commands;
1789
+ SCHEMA's `architecture/permissions.yaml` example is parsed through the real vocabulary reader,
1790
+ pinning the documented `owned_by`/`enforced_by`/`description` spellings; every dotted backticked
1791
+ token in the shipped pages must be a stable code loam actually emits, a verify claim kind, or a
1792
+ reasoned allowlist entry; and each documented known gap is paired with the roadmap sentence that
1793
+ owns closing it, so shipping a roadmap item fails the gate until the gap prose leaves with it.
1794
+ - No new command, flag, exit code, envelope key, or stable code; nothing in `dist/` changed.
1795
+
1796
+ ### Added — the capability axis
1797
+
1798
+ - **New fleet document `architecture/capabilities.yaml`** — shape
1799
+ `capabilities: {<id>: {description, owner}}`, both leaf fields optional (`{}` is a legal
1800
+ declaration); ids are flat keys with `/` allowed for nesting (`payments/refunds` stays one key,
1801
+ never collapsed).
1802
+ - **New requirement-grammar line `Capability:`** (also accepted as `Capabilities:`) — a
1803
+ comma-separated LIST, legal in both spec files, living and delta alike, parsed beside
1804
+ `Operations:`/`Covers:`/`Requires:` with the same keep-last quirk.
1805
+ - **New stable finding code `capability.unknown`** (error) — a `Capability:` entry that the
1806
+ vocabulary does not declare, with close-name suggestions; graded on living spec.md/arch.spec.md at
1807
+ the service target and on a feature's deltas of both via coherence.
1808
+ - **New IssueCode `capability.unknown`** — the delta-side shape of the same rule **gates
1809
+ `loam archive`** (not-coherent), overridable with `--approve`; the only new archive gate in this
1810
+ item.
1811
+ - **New stable finding code `capability.invalid`** (error) — the file exists but does not read as a
1812
+ vocabulary; reported exactly once per `validate --all` run (single-target runs stay silent about
1813
+ the file), suppressing the rest of the family, because a hundred findings about one broken file is
1814
+ a cascade rather than a diagnosis.
1815
+ - **New stable finding code `capability.unrealized`** (warn) — a declared capability no living
1816
+ non-`REMOVED` requirement names; one finding per capability, subject = the id.
1817
+ - **`loam list capabilities`** — new explicit-only section (the no-argument default and every
1818
+ existing section's payload are byte-unchanged); new additive `--json` payload key `capabilities`
1819
+ with deterministic, diff-stable ordering (rows by id, `realizedBy` by service/file/requirement,
1820
+ `statuses` keys sorted).
1821
+ - **`loam explore --capability <id>`** — new repeatable flag seeding the exploration from a declared
1822
+ capability's realizing services; every miss (undeclared, or declared and realized by nothing)
1823
+ lands in the new additive payload field `unresolvedCapabilities` — explore still refuses nothing.
1824
+ - **`migrate-openspec --apply` preserves capability identity**: the staged target declares the union
1825
+ of living and active-horizon OpenSpec capability ids in `architecture/capabilities.yaml` (empty
1826
+ bodies — no description is invented, the authored `## Purpose` prose stays verbatim under
1827
+ `legacy/`), and every routed requirement — living, delta and rename-materialized alike — carries a
1828
+ `Capability: <id>` line.
1829
+
1830
+ ### Added — subsystems: `services/` may now be a tree, and no identity notices
1831
+
1832
+ Nested directories under `services/` are now legal: a directory marked by a `subsystem.yaml` is a
1833
+ **subsystem** and is walked, at any depth.
1834
+ - **Three-way classification, the third branch a refusal.** A directory under `services/` holding
1835
+ `subsystem.yaml` is a subsystem (walked); one holding any service artifact is a service (not
1836
+ walked deeper); one holding **neither** while containing subdirectories is the new error
1837
+ `subsystem.unmarked`, naming every service stranded beneath it — those services stay enumerated
1838
+ and counted, so the fleet is never silently smaller (the deleted-marker merge race).
1839
+ - **`subsystem.yaml` is a marker, not a manifest** — optional `title`, `description`, `owner`; never
1840
+ members (SCHEMA.md, "Subsystems").
1841
+ - **The tree is mirrored into a GENERATED views file, never into the authored landscape.**
1842
+ `architecture/subsystems.likec4` holds LikeC4 views only — no model, no tags, no `specification` —
1843
+ one view per subsystem enumerating every service beneath it (members resolved to landscape
1844
+ *element* ids through the same `metadata { service }`-then-title join every check uses; a member
1845
+ nothing models is omitted, which `landscape.service-unmodelled` already reports). New fleet-scope
1846
+ error **`subsystem.views-stale`** in `validate --all`: the file's bytes do not match the tree —
1847
+ exactly one finding on exactly one file, graded by byte comparison (nothing in loam ever parses
1848
+ the generated file), fixed by the one command.
1849
+ - **New command `loam subsystem <verb>`** — `new`, `move`, `rename`, `rm`, `list`, `history`,
1850
+ `sync`; options `--into`, `--under`, `--title`, `--description`, `--owner`, `--json`.
1851
+ - **`loam subsystem move <name>... --into <sub|.>` is ONE transaction over N directory renames plus
1852
+ the generated views file** — the first consumer of the crash journal's new moves half.
1853
+ - **`loam adopt --subsystem <name>`** — the brief's artifact paths land inside the named group, so
1854
+ an adoption need not land unfiled and cost a second command.
1855
+ - **`loam list` gains additive `--json` keys**: `services[].subsystem` (the path as a string array,
1856
+ `[]` = unfiled), top-level `subsystems[]` (`name`, `path`, `title`, transitive `memberCount`) and
1857
+ `unfiledServices` (a count — unfiled is permanent and normal, so it is never a finding).
1858
+ - **`subsystem move`/`rename` notice old snapshots.** When an archived feature holds a version-2
1859
+ snapshot addressing a directory being moved by its literal pre-move path, the move prints a notice
1860
+ (JSON: additive `warnings` key) naming the feature — `loam unarchive <FEAT> --force` after the
1861
+ move would restore into the old location, resurrecting the pre-move directory beside the moved
1862
+ one.
1863
+
1864
+ ### Changed — archive snapshots are re-keyed by service id (manifest version 2 → 3)
1865
+
1866
+ The undo snapshot's manifest (`features/archive/<dir>/.loam-before/manifest.json`) bumps to
1867
+ **version 3**: every entry under `services/` now carries `(service, artifact)` — the service id and
1868
+ the artifact's path inside the service directory — beside the as-archived literal `path`, and
1869
+ `loam unarchive` resolves the write target through the **current** services enumeration, falling
1870
+ back to the literal path when the id is not enumerated.
1871
+
1872
+ ### Added — the AsyncAPI feature lifecycle
1873
+
1874
+ - **File-format contract** (SCHEMA.md): a feature changes an async contract through
1875
+ `features/<FEAT>/specs/<svc>/asyncapi.yaml` — a complete AsyncAPI 3.0 document.
1876
+ - **`loam rebase` pins asyncapi slots**: every slot a feature's asyncapi.yaml restates is pinned
1877
+ against the living contract; `rebase --json`'s `pins[]` entries may now carry
1878
+ `file: "asyncapi.yaml"` with `kind` = the section name and `target` = the slot key (new values in
1879
+ existing fields — no envelope key changes).
1880
+ - **New validate finding `asyncapi.remove-marker-living`** (error): a living asyncapi.yaml carrying
1881
+ `x-loam-remove: true` — at ANY depth: a slot, an inline channel message, or anywhere else one
1882
+ leaked (the document root, `info`), the sweep exactly as deep as the strip — names where it sits,
1883
+ the `openapi.remove-marker-living` discipline on the event axis.
1884
+ - **The coherence gate grades the event axis** (`validate --feature` and the archive gate alike,
1885
+ since the gate consumes coherence issues wholesale). Three strings validate already emitted joined
1886
+ the IssueCode union unchanged: `asyncapi.invalid` (now also fired for a FEATURE's own
1887
+ asyncapi.yaml, suspending that service's event checks and gating archive),
1888
+ `asyncapi.ref-unresolved`, and `spec-event.message-undefined` (now also graded on delta
1889
+ requirement lines in feature scope). **What a `--json` consumer notices.** The event axis grades
1890
+ two things that need no asyncapi.yaml at all: a feature delta's tagged edges carrying
1891
+ `metadata { publishes }` / `{ consumes }` (`c4-event.message-undefined`) and a delta requirement's
1892
+ `Publishes:`/`Consumes:` lines (`spec-event.message-undefined`).
1893
+ - **The merge is verdict-driven, per slot** (`channels.<key>` / `operations.<key>` /
1894
+ `components.messages.<key>`): a pinned QUOTE is never a merge input — not even under `--approve` —
1895
+ so a change that landed on the living contract after the delta was written survives an archive
1896
+ that merely restates the slot (the lost-update case); an EDIT overwrites; a `stale` pin still
1897
+ writes, because reaching the merge at all means `--approve` said to, and the plan names what it
1898
+ cost.
1899
+ - **New stable plan-warn codes**, each with a /loam-check fix-table row:
1900
+ `asyncapi.message-modified`, `asyncapi.channel-modified`, `asyncapi.operation-modified` — the
1901
+ delta redefines a slot the living AsyncAPI already has and the merge overwrites it wholesale, the
1902
+ event axis's mirror of `openapi.op-modified`'s family.
1903
+ - **`asyncapi.ref-unresolved` is now also a gating plan issue** (it stays a validate warn on living
1904
+ contracts): a `$ref` the MERGED document would carry that resolves in neither the feature's
1905
+ asyncapi.yaml nor the living one — including a removal that deletes a slot the living document
1906
+ still references — refuses the archive at `not-coherent` unless `--approve`.
1907
+ - **New gating plan code `asyncapi.remove-marker-inline`** (error, `--approve` overrides, with a
1908
+ /loam-check fix-table row): an `x-loam-remove: true` nested on an INLINE channel message
1909
+ (`channels.<ck>.messages.<mk>`).
1910
+ - **`archive --json` gains the additive `asyncapiRemovals` key** (`[{service, slots}]`, the labels
1911
+ of living slots the plan deletes), beside `openapiRemovals`.
1912
+ - **New verify claim kind `event.declares`** in `CLAIM_KINDS` (the `verification.yaml` value
1913
+ vocabulary), after `api.exposes` in the checklist's story order: one claim per genuinely NEW
1914
+ (direction, message) a feature's asyncapi delta declares relative to the living contract's
1915
+ send/receive sets — `"<svc> declares it <sends|receives> message '<name>'"`.
1916
+ - **`delta --json` gains the additive per-service `events` key** —
1917
+ `{changes: [{slot, message, direction, remove}], unreadable, error?}`, the event-axis mirror of
1918
+ the `api`/`openapi` pair: every message declaration in the feature's asyncapi.yaml with the slot
1919
+ it lives in, the direction the document's own operations give it (`send`/`receive`/null), and its
1920
+ removal marker; the text view prints the same rows. **An unreadable feature asyncapi.yaml now
1921
+ makes `loam delta` exit 1** exactly as the openapi path does — the brief is an implementation
1922
+ task, and "no event work here" over a YAML error is work silently dropped.
1923
+ - **`loam status` sees the event axis.** The feature artifact table gains an `asyncapi` row per
1924
+ touched service (additive `--json` value in the `ARTIFACT_IDS` vocabulary; never required — an
1925
+ event contract is genuinely optional, so absence is `done` like arch.spec.md), the `asyncapi.*` /
1926
+ `c4-event.*` / `spec-event.*` finding families now turn their responsible artifact row `draft`
1927
+ (asyncapi.yaml, delta.likec4 and the spec delta respectively), a feature whose findings include
1928
+ `asyncapi.baseline-missing` gets a `next.rebase` step naming `loam rebase`, and a blocked
1929
+ verification's `blockedBy` list names `asyncapi` among the feeders it waits on.
1930
+ - **The authoring surfaces teach the axis**: `loam new`'s spec template comment documents the
1931
+ `Publishes:`/`Consumes:` requirement lines and when to hand-create `specs/<svc>/asyncapi.yaml`
1932
+ (nothing is scaffolded — the axis's absence-grading rests on the contract being optional), and the
1933
+ `/loam-feature` workflow carries the event-axis authoring step beside the openapi one, rebase
1934
+ pinning included.
1935
+
1936
+ ### Changed — `validate --all` parses the fleet's C4 documents in one workspace
1937
+
1938
+ `loam validate --all` now parses the run's C4 documents — every service model, every active
1939
+ feature's delta, and the landscape — in **one** LikeC4 workspace instead of spinning up one
1940
+ workspace per document: measured **~18.8x faster** (13.7s → 0.73s median) on the committed
1941
+ 120-service benchmark, with peak memory roughly halved (docs/BENCHMARKS.md).
1942
+
1943
+ ### Changed — a restated component or path-level key can no longer silently revert a landed change
1944
+
1945
+ The operation pin's discipline now reaches the two surfaces it deliberately did not: path-item
1946
+ non-method keys (`parameters`, `servers`, `summary`, `x-*`) and every `components/<kind>/<name>`.
1947
+ The merge skips quoted surfaces exactly as it skips quoted operations, and component copying is
1948
+ verdict-driven: **archives that used to exit 0 while a restated component or path-level key
1949
+ overwrote another feature's landed change now refuse until `loam rebase <FEAT>` runs or `--approve`
1950
+ says the overwrite is meant.** `loam validate` moves with it: a feature whose delta restates a
1951
+ path-level key or a component another change has since moved now fails where it passed, on the same
1952
+ `openapi.baseline-stale`, before any archive is attempted.
1953
+
1954
+ ### Changed — the architecture rules became one executable gate
1955
+
1956
+ - **New contributor gate `npm run arch:check`** runs every architecture check the design docs state
1957
+ — file-level import cycles, the package graph, the core→commands ban (named and type-only imports;
1958
+ a bare side-effect or dynamic import is not seen), the barrel ban, the console/process boundary
1959
+ with `core/envelope/json.ts` as the one named exception, the child-process timeout/`maxBuffer`
1960
+ policy, and brand-cast containment — each with a negative self-test in `test/arch-gate.test.ts`.
1961
+ - **Under `--json`, an unreadable loam.json no longer prints a stray line on stderr.** The
1962
+ `config-invalid` envelope message now carries the actual parse problem (stdout stays the single
1963
+ machine-readable stream); in text mode the same sentence prints from the command layer.
1964
+ - **`loam verify --record`'s git questions are bounded** — a 10-second deadline whose refusal says
1965
+ the deadline fired (a blocking credential helper used to hang the record forever), and a
1966
+ deliberate 64 MiB output cap in place of Node's implicit 1 MiB that refused sound evidence over 1
1967
+ MiB.
1968
+ - **Validated identities are branded types end to end.** Feature ids, docs directories and feature
1969
+ directories join service ids: the smart constructors in `core/kernel/ids/` are the only bridge,
1970
+ the path builders in `core/repo/paths.ts` refuse raw strings at compile time, and `arch:check`
1971
+ fails a brand cast outside the constructor modules.
1972
+
1973
+ ### Changed — every multi-file writer now commits through a journaled, crash-consistent transaction
1974
+
1975
+ `loam archive` and `loam unarchive` have long committed through a lock, a byte-level
1976
+ compare-and-swap, a snapshot and a fsynced `.loam-commit` journal.
1977
+ - **`loam rebase`** journals its pin writes.
1978
+ - **`loam vouch`** now takes the docs lock for its commit window (the roadmap called this lock
1979
+ missing) — the slow half, reading specs and hashing sources, stays unlocked so independent
1980
+ services' vouches do not serialise — and journals the spec.md/arch.spec.md pair.
1981
+ - **`loam new`** builds its entire scaffold in memory, then commits it under the lock as one set of
1982
+ exclusive creates.
1983
+ - **`loam gherkin`** — the writer the roadmap named, a plain write/delete loop in the service repo —
1984
+ commits through staging, the lock and the journal at **`<gherkinDir>/loam/.loam-lock`** /
1985
+ **`.loam-commit`**, two new transient dotfiles in the directory loam already owns.
1986
+ - **`loam verify --record`** recovers (or refuses **`commit-interrupted`**) under its held lock
1987
+ before the authoritative read — the last docs-repo writer that could still silently write over a
1988
+ half-commit.
1989
+ - **`loam validate` leads every mode with the new error finding `docs.commit-interrupted`** while a
1990
+ `.loam-commit` sits in the docs repo: a half-merged tree graded green would certify bytes no run
1991
+ produced, and in CI that green merges.
1992
+ - **`loam doctor` covers the service repo too:** when loam.json names a service, the same residue
1993
+ scan runs over `<gherkinDir>/loam/` and grades with the same `doctor.docs-locked` /
1994
+ `doctor.commit-interrupted` / `doctor.commit-unreadable` / `doctor.staging-temps` codes; the
1995
+ report gains an additive `serviceWritePath` key.
1996
+ - **Recovery is reported, not silent:** any journaled writer that first rolls a predecessor's commit
1997
+ forward carries an additive `recovered` key in its `--json` payload and says so in the human view
1998
+ — docs changing beyond the command's own writes would otherwise read as its doing.
1999
+ - **`recovered.feature` and `status`'s `interrupted.feature` no longer always name a feature.** When
2000
+ the journal being recovered was left by `loam vouch` or `loam gherkin`, the field holds the
2001
+ **service** id those commands were committing for, and `recovered.command` / `interrupted.command`
2002
+ can now read `rebase`, `vouch`, `new` or `gherkin` where they only ever read `archive` or
2003
+ `unarchive`.
2004
+ - **`loam doctor --json`'s `writePath.intent` now has two shapes.** A journal from the smaller
2005
+ transaction reads `{version: 2, command, rerun, target, …}` — no `feature`, and each file carries
2006
+ the staged `tmp` beside its digests.
2007
+ - **`loam gherkin --dry-run` recovers before it plans** — archive's own dry-run precedent.
2008
+ - A journal written by this loam and found by an older one grades as `doctor.commit-unreadable`
2009
+ (fail-safe, human-directed) — worth knowing where fleets pin loam per repo.
2010
+
2011
+ ### Changed — recording a verification is now atomic, locked, and compare-and-swap
2012
+
2013
+ - **`loam verify --record` / `--results` takes the docs repo's advisory lock and commits the record
2014
+ atomically.** The record used to be one plain `writeFile` from an unlocked read, which lost work
2015
+ three ways: two services recording one feature concurrently could read the same pre-image and the
2016
+ last writer silently discarded the other's attestation; a process killed mid-write left the only
2017
+ record truncated (and every later `--record` refusing on `record-unreadable`); and an edit landing
2018
+ between the read and the write was buried. A kill mid-record leaves the previous record whole plus
2019
+ a staging temp that `loam doctor` already names under its write-path residue.
2020
+ - **The git questions `verify --record` asks now have a deadline and an output cap.** Every `git`
2021
+ call on the record path (`rev-parse`, `diff`, `show`) runs while the docs lock is held, and none
2022
+ of them was bounded: a git blocked on a credential-helper prompt hung the record forever with the
2023
+ lock in hand, wedging every `archive`, `rebase` and other `--record` in the fleet behind a live
2024
+ pid that stale-lock breaking rightly refuses to touch.
2025
+ - **A staging failure while recording answers `merge-failed`, not `internal`.** A read-only feature
2026
+ directory or a full disk at the staging step escaped as the one code with no stable meaning; it is
2027
+ now the same refusal archive gives the identical condition — nothing was recorded, the record is
2028
+ as the last writer left it.
2029
+ - **`loam doctor` grades a `.loam-lock` that cannot name its holder as damage, and exits 1 where it
2030
+ exited 0.** An empty or unparseable lock file — what a crash inside a lock create used to leave,
2031
+ or an editor's accident — got the warning "wait for it to finish", advice that can never work:
2032
+ stale-lock breaking rightly refuses to interpret a lock it cannot read, so nothing was ever going
2033
+ to release it while every writer refused `docs-busy` forever. **What a `--json` consumer
2034
+ notices:** the finding keeps its `doctor.docs-locked` code but arrives with `severity: "blocker"`,
2035
+ so `healthy` is `false` and a preflight that passed over such a lock now fails.
2036
+ - **A `verification.yaml` that is a symlink to a target that does not exist now refuses
2037
+ `record-unreadable`, naming the broken link.** The read side used to call it absent while the
2038
+ commit's exclusive create saw the link itself and refused — so every `--record` answered
2039
+ `record-raced` with advice (re-run) that could never work.
2040
+
2041
+ ### Changed — the OpenSpec compatibility baseline moves to v1.9.0, and the gate that backs it runs again
2042
+
2043
+ - **`audit-openspec` and `migrate-openspec` now report v1.9.0 as the certified release baseline.**
2044
+ The banner line and `baselines.release` under `--json` carry `1.9.0` /
2045
+ `2826b8889e5223a9a8095d4428b60b56597e1020` (2026-08-13, and the tip of `main` when pinned) instead
2046
+ of `1.7.0` / `4e16790`.
2047
+
2048
+ ### Fixed
2049
+
2050
+ - **The OpenSpec corpus gate had not run for eight days: it failed at module resolution, not at a
2051
+ corpus.** `scripts/check-openspec-corpus.ts` imported `../src/core/spec.js`, which moved into
2052
+ `src/core/document/` when core's leaves became packages, so every scheduled and manual invocation
2053
+ died with `ERR_MODULE_NOT_FOUND` while three documents pointed at it as the evidence for the
2054
+ compatibility claim.
2055
+
2056
+ ### Documentation
2057
+
2058
+ - Added the evidence-backed `ROADMAP.md`, updated the product comparison to OpenSpec v1.9 while
2059
+ preserving the exact v1.7 compatibility baseline, documented
2060
+ AsyncAPI/authorization/response-governance joins, and reconciled release, contributor,
2061
+ security and generated-agent guidance with the implemented contracts.
2062
+
2063
+ ### Added — the decision layer gets somewhere to live: outlines, an authorization vocabulary, and a walk that asks for both
2064
+
2065
+ - **A markdown table in a scenario body is now a `Scenario Outline`.** `loam gherkin` emits the
2066
+ table as `Examples` — one row per case, columns padded, deterministic to the byte — instead of
2067
+ dropping it into the scenario description where cucumber ran it as one vacuous scenario.
2068
+ - **`architecture/permissions.yaml` — the fleet's authorization vocabulary — and the `Requires:`
2069
+ line that joins to it.** A third spine beside `Operations:` and `Covers:`, because neither can
2070
+ carry a permission: the cardinality is wrong for an operation, a permission is not a C4 element,
2071
+ and OpenAPI's `security` carries permission NAMES only for oauth2/openIdConnect — the spec
2072
+ requires an empty scope array for `http bearer` and `apiKey`, which is what most of a legacy fleet
2073
+ authenticates with. Three codes: **`permissions.unknown`** (error — an entry the vocabulary does
2074
+ not declare; an ERROR where its `Covers:` sibling is a warning, because an invented permission
2075
+ reads exactly like a real one in the requirement, in the generated scenario and in the test
2076
+ somebody then writes from it), **`permissions.invalid`** (the file exists and does not read as a
2077
+ vocabulary — reported alone, since grading every `Requires:` line against it would be a cascade
2078
+ rather than a diagnosis), and **`permissions.unenforced`** (warn — a declared permission no
2079
+ requirement anywhere in the fleet names, the mirror of `api.ungoverned`).
2080
+ - **`api.response-ungoverned` (warn) — a declared 4xx/5xx response no scenario reaches.**
2081
+ `api.ungoverned` grades whole OPERATIONS against requirements, so an endpoint with one happy-path
2082
+ requirement and twelve declared failure codes was fully governed by every check loam had — and the
2083
+ refusals are precisely where a service's decision layer surfaces.
2084
+ - **`sources` is described as digest input rather than a reading list.** Nobody follows those paths
2085
+ and nothing asks them to — the value is that `vouch` hashes their content so a later `validate`
2086
+ can say the code moved.
2087
+
2088
+ ### Fixed
2089
+
2090
+ - **An optional `arch.spec.md` could refuse an entire `--all` run.** The new fleet-wide `Requires:`
2091
+ sweep read both requirement documents of every service without checking existence first, and
2092
+ `FleetContext.readRequirements` throws ENOENT — which surfaced as `repository-unavailable` with no
2093
+ targets and nothing validated.
2094
+
2095
+ ### Changed — three archives that used to exit 0 now refuse, because each one was a silent loss
2096
+
2097
+ - **Unpinned deltas gate the archive.** `delta.baseline-missing` and `openapi.baseline-missing` keep
2098
+ their warn severity — `loam validate` stays green, the documents are legal — but both now carry
2099
+ `gates`, so `loam archive` (dry run included) refuses instead of merging.
2100
+ - **A scaffold nobody edited cannot archive.** Two new gating warnings from
2101
+ `loam validate --feature`, `loam status` and the archive gate: **`scaffold.placeholder`** — a
2102
+ feature document still carries `loam new`'s exact template text (a `TODO — name the behaviour`
2103
+ requirement, a `TODO — name the case` scenario, an `<angle-bracket>` fill-in, a
2104
+ `TODO — what this service owns` description) — and **`intent.empty`** — intent.md is missing or
2105
+ says nothing outside the scaffold's own comments.
2106
+ - **A feature's own broken openapi.yaml is now named.** `validate --feature` reports
2107
+ **`openapi.invalid`** (error — the same code the living contract gets) when a feature's
2108
+ `specs/<svc>/openapi.yaml` exists but does not parse, and suspends the service's contract-axis
2109
+ checks instead of grading against the empty parse — which used to skip every baseline pin and
2110
+ removal marker in the broken file silently and false-fire `spec-api.op-undefined` against
2111
+ requirements whose operations the unreadable file defined.
2112
+
2113
+ ### Fixed — the fleet map's containers are visible to the checks that guard removals and ordering
2114
+
2115
+ - The element→service resolver was called without the enumerated fleet in exactly the places that
2116
+ are repository-aware: the removal gate's consumer scan, the feature dependency graph, coherence's
2117
+ edge grading, arch coverage and its `Covers:` matcher, the verify checklist, and `loam delta`'s
2118
+ projection.
2119
+
2120
+ ### Fixed — edge metadata loam parsed but did not keep
2121
+
2122
+ - **`loam archive` dropped an event binding at exit 0.** What makes two C4 edges the same edge
2123
+ accounted for `metadata { op }` and ignored `metadata { publishes }` and `metadata { consumes }`
2124
+ entirely.
2125
+
2126
+ ### Added — fleet-shape and contract-depth checks
2127
+
2128
+ These checks are specified with repository-owned synthetic docs fixtures and compare artifacts loam
2129
+ already parses.
2130
+ - **`landscape.platform-candidate` (warn)** — an `#external` element consumed by three or more
2131
+ distinct services and not tagged `#platform`.
2132
+ - **`landscape.datastore-private` / `landscape.datastore-shared` (warn)** — a fleet-level `database`
2133
+ element graded by its consumer count.
2134
+ - **`c4.no-relationships` (warn)** — a parsed model with elements and zero relationships, when its
2135
+ own evidence says it should reach something: more than one nested element, or dependencies
2136
+ declared in this service's health.yaml.
2137
+ - **`health.dependency-unmodelled` (warn)** — every `dependencies:` id in health.yaml must resolve
2138
+ in the service's OWN `model.likec4`, by element id, `metadata { service }` binding, or title
2139
+ (exact match, did-you-mean hints, no case folding).
2140
+ - **The contract-depth probes** — form validated and depth did not: an operation whose one response
2141
+ is `description: OK`, a message whose payload is bare `type: object`, and a `$ref` to a schema
2142
+ nobody wrote all validated exactly as cleanly as complete contracts. Four warns close it:
2143
+ **`openapi.response-undescribed`** (no response declares a schema while at least one should —
2144
+ 204/304 excepted), **`asyncapi.payload-undescribed`** (a payload declaring no shape at all; a
2145
+ non-JSON `schemaFormat` is never judged, so the Avro migration stays a document change), and
2146
+ **`openapi.ref-unresolved` / `asyncapi.ref-unresolved`** (internal `$ref`s to nothing — on the
2147
+ async axis these used to vanish silently).
2148
+ - **`spine.message-external` (warn), and `spine.message-unproduced` narrows to mean what it says** —
2149
+ a message produced outside the fleet was inexpressible: the landscape said the producer was
2150
+ `#external` and the error fired anyway, so a truthful map cost a red build and the exit ramp was
2151
+ deleting the link.
2152
+
2153
+ ### Changed — the adopt protocol makes deployment evidence boundaries explicit
2154
+
2155
+ - **The reproducibility bar is stated**, beside the honest half: `loam validate` grades form and
2156
+ joins, never depth, so green means the files agree with each other — and `unchecked[]` now says
2157
+ nothing measures a shallow baseline against the service.
2158
+ - **`api.covered` carries both counts** — operations governed and living requirements — because the
2159
+ ratio is the one signal that lets a reviewer smell a thin baseline from the summary line.
2160
+
2161
+ ### What a `--json` consumer notices, upgrading to this release
2162
+
2163
+ - **Repos that were green can newly show warnings** — the eight codes above, plus `agents.stale`
2164
+ from the version bump itself (and this time the AGENTS.md review is real work: the code tables
2165
+ materially changed). `valid` stays true and plain exit codes stay 0, but **a `--strict` CI job
2166
+ that exited 0 yesterday can exit 1 today**. A freshly migrated OpenSpec repo now reports
2167
+ `openapi.response-undescribed` as its honest state — OpenSpec never carried response schemas.
2168
+
2169
+ - **One red case turns green**: a consumed message whose only producer is an `#external` `publishes`
2170
+ edge no longer errors. Fleets that took the old exit ramp — deleting the link to get green — get
2171
+ no signal to restore it; re-add the edge and the consumption, the map is allowed to be truthful
2172
+ now.
2173
+
2174
+ - **`landscape.matched` no longer appears** on a fleet where any fleet-shape warning fires — a map
2175
+ with a shape warning did not fully agree.
2176
+
2177
+ - No envelope keys changed shape; the new findings ride the existing `findings[]`/`details`
2178
+ contract, and prose (including `api.covered`'s message) was reworded freely as always. A feature's
2179
+ per-service deltas live in `features/<FEAT>/specs/<svc>/`, and that `<svc>` is a directory name
2180
+ somebody typed — but unlike `--service`, nothing ever asked the id grammar about it.
2181
+ `specs/Payment Service/` (with the space) validated green —
2182
+ `✓ Payment Service: requirements covered`, even, because a tagged element whose *title* matched
2183
+ the directory counted as introducing the service — and `loam archive` then exited 0 and
2184
+ materialised `services/Payment Service/`. The very next `loam validate --all` failed it with
2185
+ `service.id-invalid`: a directory loam itself wrote, and one no loam command can address or
2186
+ re-create. New finding: **`delta.service-id-invalid`** (error), from `loam validate --feature`,
2187
+ `loam status`, and the archive gate, naming the `specs/<svc>/` directory and the exact rule the
2188
+ name breaks — the same sentence `service.id-invalid` uses, Windows-reserved stems (`NUL`) and
2189
+ trailing dots (`payments.`) included. `loam archive` refuses on it before anything joins the name
2190
+ into a path, `--dry-run` included, and **`--approve` does not override it**: which service you
2191
+ meant is a judgment call, but a name the grammar refuses is a directory loam can never address,
2192
+ mechanically. The fix is a rename of the specs/ directory. The same guarantee on the architecture
2193
+ axis. A tagged element's explicit `metadata { service '…' }` binding is a name the landscape merge
2194
+ splices into the living `architecture/landscape.likec4` verbatim, and the archive probes
2195
+ `services/<binding>/` with it — `metadata { service '../outside-svc' }` parses in LikeC4 without a
2196
+ single error, validated with nothing but a warning, archived at exit 0, and the `../` collapsed
2197
+ the probe right out of `services/`. The next `validate --all` then failed the very map archive had
2198
+ just written (`landscape.binding-unknown`). New coherence issue: **`c4.service-binding-invalid`**
2199
+ (error), from `loam validate --feature`, `loam status`, and the archive gate, naming the element
2200
+ and the rule its binding breaks; **`--approve` does not override it either**, for the same reason.
2201
+ The check's scope is the splice's, not the tag's: the merge carries a tagged element's authored
2202
+ block into the living landscape byte for byte, untagged children included, so an untagged
2203
+ `container` nested inside a tagged system — `metadata { service '../outside-svc' }` on the child —
2204
+ is held to the same grammar, because its binding reaches the living map exactly as its parent's
2205
+ does. A prose title with no binding stays legal C4 — a title becomes a path only through
2206
+ `specs/<svc>/`, which the finding above now guards. Together the two codes close every text→path
2207
+ route into the archive merge. **What a `--json` consumer notices.** `loam validate --feature` can
2208
+ now exit 1 on repos that used to pass, with `delta.service-id-invalid` or
2209
+ `c4.service-binding-invalid` in `findings[]` — and the same two findings reach
2210
+ `loam validate --all` and the bare positional `loam validate <FEAT>`, which grade every feature
2211
+ through the same check: a repo that exited 0 yesterday exits 1 today, and a CI job running
2212
+ `validate --all` fails naming the feature. The fix is renaming the `specs/<svc>/` directory or
2213
+ editing the `metadata { service }` binding, never a flag. `loam archive` refuses both with the
2214
+ envelope code `not-coherent` and the offending finding in `issues[]`, whether or not `--approve`
2215
+ was passed; a refusal `--approve` cannot move now also says "--approve does not override this" in
2216
+ `error.message`, and every issue `archive --json` emits (`issues[]`, `warnings[]`, `overridden[]`)
2217
+ carries a new additive `overridable` key — whether `--approve` can move it — so a consumer
2218
+ branches on data instead of keeping its own code list. One ordering change rides along: the
2219
+ illegal `specs/<svc>/` name is refused ahead of the conflict-marker scan, so a feature carrying
2220
+ both defects reports `not-coherent` where it used to report `merge-failed`. A `services/<bad>/` a
2221
+ previous archive already created is repaired by renaming — the `service.id-invalid` message spells
2222
+ the three places the old name is written.
2223
+
2224
+ ### Fixed — coherence grading resolves an edge's target before it builds a path
2225
+
2226
+ The coherence check behind `loam validate --feature`, `loam status` and the archive gate joined an
2227
+ op-edge's *declared* target — the element's `metadata { service '…' }` binding, or its title —
2228
+ straight into `services/<target>/` to read the living spec, the living OpenAPI and the deprecation
2229
+ flags. **What a `--json` consumer notices.** Nothing, unless a feature's delta declared a target
2230
+ that never matched a `services/` or `specs/` directory *and* the joined path landed on a real file:
2231
+ those repos can newly see `c4-api.op-undefined` (error, gates archive) and `c4.op-ungoverned` (warn)
2232
+ where the out-of-repo file used to satisfy the check.
2233
+
2234
+ ### Fixed — `loam validate` resolves a service name before it builds a path
2235
+
2236
+ `loam validate --service <name>` and `loam validate <name>` both spelled
2237
+ `<docsDir>/services/<name>/` out of whatever the caller typed, and neither ever checked it.
2238
+ `loam validate --service ../../outside/services/x` therefore resolved **above the docs repo**, and
2239
+ where a `spec.md` happened to sit at that path loam opened it, graded it, and reported its
2240
+ frontmatter back through `--json`.
2241
+
2242
+ ### Added — `loam adopt` states the walk, and `validate` grades it
2243
+
2244
+ - **`walk[]` in the brief** — nine ordered stops, each naming what to open, what to take from it,
2245
+ and which artifacts it feeds (`lands`).
2246
+ - **`sources.unwalked` (warn) / `sources.walked` (ok)** — the walk graded against the repository,
2247
+ reported by `loam validate --service <id>` run inside the service's own repo.
2248
+
2249
+ ### Added — how to draw a shared broker, before the fleet map becomes a star
2250
+
2251
+ A broker modelled as ONE element is the node every service in the fleet points at: a map that read
2252
+ fine at five services is a star with sixty spokes at sixty, and nothing warns on the way there,
2253
+ because loam parses the model and renders no view.
2254
+
7
2255
  ## [0.1.0-beta.3] - 2026-08-08
8
2256
 
9
- Still a prerelease under the `beta` dist-tag; the 5–10 service pilot has not run yet. Everything below came out of the first real fleet adoption — two Java Spring Boot services into a fresh docs repo — and every item was reproduced there, not inferred. One breaking change, in `loam vouch`; read that section before upgrading a CI job.
2257
+ Still a prerelease under the `beta` dist-tag; the 5–10 service pilot has not run yet. The entries
2258
+ below describe implementation behavior and repository-owned synthetic fixtures; they claim no
2259
+ production-fleet or external execution evidence. One breaking change, in `loam vouch`; read that
2260
+ section before upgrading a CI job.
10
2261
 
11
2262
  ### Changed — `loam vouch` now records a person, and refuses when it cannot
12
2263
 
13
2264
  **This is a breaking change for any script or CI job that runs `loam vouch`: add `--yes`.**
14
2265
 
15
- `vouch` is the one command in loam whose output is a claim about a human act — everything else it checks is internal consistency, which fluent prose satisfies on its own. Its own docblock called it "the one command that records a person", and it recorded none: no identity of any kind, no interactive gate. Run twice, unattended, from a script, it stamped `status: verified` both times. Meanwhile every generated command and skill file pre-approved `Bash(loam:*)`, which handed the agent that wrote a draft the power to promote its own draft. That inverts the argument loam makes about test evidence everywhere else — an agent must not be able to *say* a scenario is tested — by letting one say a spec matches the code.
2266
+ `vouch` is the one command in loam whose output is a claim about a human act — everything else it
2267
+ checks is internal consistency, which fluent prose satisfies on its own. Its own docblock called it
2268
+ "the one command that records a person", and it recorded none: no identity of any kind, no
2269
+ interactive gate. Run twice, unattended, from a script, it stamped `status: verified` both times.
2270
+ Meanwhile every generated command and skill file pre-approved `Bash(loam:*)`, which handed the agent
2271
+ that wrote a draft the power to promote its own draft. That inverts the argument loam makes about
2272
+ test evidence everywhere else — an agent must not be able to *say* a scenario is tested — by letting
2273
+ one say a spec matches the code.
16
2274
 
17
2275
  Three changes close it, and none of them is a signature:
18
2276
 
19
- - **`vouched_by` is stamped beside `status` and `last_verified`**, taken from git — `GIT_COMMITTER_*`/`GIT_AUTHOR_*` first and then `user.name`/`user.email`, which is git's own precedence, so a CI job and a laptop resolve it the same way. It is a name, not a proof: git config is a text file. What it buys is that a reviewer can ask a *specific person* what they read, which is the question `status: verified` was silently answering with nobody. It rides `--json` as `vouched_by`, and a re-vouch refreshes it.
20
- - **`--yes`, and a confirmation when there is a terminal.** Without `--yes`, `vouch` refuses `vouch-unattended` when stdin is not a TTY, and in `--json` mode regardless (a question cannot be asked on a stream whose contract is one JSON document). On a terminal it states what is about to be claimed and takes only an explicit yes; `vouch-declined` is the answer when the person says no. `vouch-unattributable` is the refusal when git can name nobody at all — better than stamping an anonymous claim.
21
- - **The generated `allowed-tools` allowlist names loam's verbs one by one** — `Bash(loam adopt:*), Bash(loam validate:*), …` — and deliberately not `vouch`. A verb missing from the list is not forbidden, only unapproved: the agent asks, and a person answers. That is the correct cost for this one, and it is what makes the refusal something a human sees rather than something an agent routes around.
22
-
23
- New error codes: `vouch-unattended`, `vouch-unattributable`, `vouch-declined`. Existing specs carrying `status: verified` with no `vouched_by` are not re-graded — nothing new fires on them — but the next vouch stamps a name.
2277
+ - **`vouched_by` is stamped beside `status` and `last_verified`**, taken from git —
2278
+ `GIT_COMMITTER_*`/`GIT_AUTHOR_*` first and then `user.name`/`user.email`, which is git's own
2279
+ precedence, so a CI job and a laptop resolve it the same way. It is a name, not a proof: git
2280
+ config is a text file. What it buys is that a reviewer can ask a *specific person* what they read,
2281
+ which is the question `status: verified` was silently answering with nobody. It rides `--json` as
2282
+ `vouched_by`, and a re-vouch refreshes it.
2283
+ - **`--yes`, and a confirmation when there is a terminal.** Without `--yes`, `vouch` refuses
2284
+ `vouch-unattended` when stdin is not a TTY, and in `--json` mode regardless (a question cannot be
2285
+ asked on a stream whose contract is one JSON document). On a terminal it states what is about to
2286
+ be claimed and takes only an explicit yes; `vouch-declined` is the answer when the person says no.
2287
+ `vouch-unattributable` is the refusal when git can name nobody at all — better than stamping an
2288
+ anonymous claim.
2289
+ - **The generated `allowed-tools` allowlist names loam's verbs one by one** —
2290
+ `Bash(loam adopt:*), Bash(loam validate:*), …` — and deliberately not `vouch`. A verb missing from
2291
+ the list is not forbidden, only unapproved: the agent asks, and a person answers. That is the
2292
+ correct cost for this one, and it is what makes the refusal something a human sees rather than
2293
+ something an agent routes around.
2294
+
2295
+ New error codes: `vouch-unattended`, `vouch-unattributable`, `vouch-declined`. Existing specs
2296
+ carrying `status: verified` with no `vouched_by` are not re-graded — nothing new fires on them — but
2297
+ the next vouch stamps a name.
24
2298
 
25
2299
  ### Fixed — a docs repo that could be cloned, and rendered
26
2300
 
27
- - **`loam init --create` writes `services/.gitkeep` and `features/.gitkeep`.** Both directories were created empty; git tracks files, not directories, so after the first push neither existed for anyone who cloned. A missing `services/` is a *blocker* in `doctor`, so the second person to touch the repo got a red preflight on a repository the first person left green.
28
- - **`loam init --create` writes `likec4.config.json`, and the docs repo is a loadable LikeC4 workspace again.** loam parses every `.likec4` file *alone*, so the landscape, each `services/<id>/model.likec4` and each `features/<FEAT>/delta.likec4` legitimately declares its own `specification { … }` block and re-declares the elements it names. LikeC4's own loader merges the whole tree into one model, so pointing `npx likec4 start` at the repo root — the command loam's own brief recommends — reported every one of those declarations as a duplicate: on loam's four-file `examples/docs`, 16 errors from the renderer and 0 from `loam validate --all`. The scaffolded config declares the root as one project scoped to `architecture/`; a service model or feature delta renders from its own directory (`npx likec4 start services/<id>`), which is what parsing them in isolation meant all along. `doctor` reports `doctor.likec4-config-missing` (warning) on a docs repo created before this, with the exact file to write in its `fix`.
2301
+ - **`loam init --create` writes `services/.gitkeep` and `features/.gitkeep`.** Both directories were
2302
+ created empty; git tracks files, not directories, so after the first push neither existed for
2303
+ anyone who cloned. A missing `services/` is a *blocker* in `doctor`, so the second person to touch
2304
+ the repo got a red preflight on a repository the first person left green.
2305
+ - **`loam init --create` writes `likec4.config.json`, and the docs repo is a loadable LikeC4
2306
+ workspace again.** loam parses every `.likec4` file *alone*, so the landscape, each
2307
+ `services/<id>/model.likec4` and each `features/<FEAT>/delta.likec4` legitimately declares its own
2308
+ `specification { … }` block and re-declares the elements it names. LikeC4's own loader merges the
2309
+ whole tree into one model, so pointing `npx likec4 start` at the repo root — the command loam's
2310
+ own brief recommends — reported every one of those declarations as a duplicate: on loam's
2311
+ four-file `examples/docs`, 16 errors from the renderer and 0 from `loam validate --all`. The
2312
+ scaffolded config declares the root as one project scoped to `architecture/`; a service model or
2313
+ feature delta renders from its own directory (`npx likec4 start services/<id>`), which is what
2314
+ parsing them in isolation meant all along. `doctor` reports `doctor.likec4-config-missing`
2315
+ (warning) on a docs repo created before this, with the exact file to write in its `fix`.
29
2316
 
30
2317
  ### Fixed — three commands that answered the wrong question
31
2318
 
32
- - **`loam status` now sees the repository it is standing in.** With `loam.json` naming a service the docs repo has no directory for — a freshly wired service repo, the most common repo there is — the fleet count made "nothing is in flight and every service is written down" vacuously true, and `next.fleet-clean` came out on top. `doctor` had the right answer (`doctor.service-unknown`) at the same moment, and the documented agent loop reads `status --json` and runs `next[0]`: an agent following it was sent to start a feature instead of adopting the service under its feet. The new step is **`next.adopt-bound`**, first in `next[]` (behind only `next.recover-commit`), and suppressed under an explicit `--service`, which is a question about a different service.
33
- - **`doctor.service-unbound` is no longer raised inside the docs repo.** Having no service binding there is the correct state — the docs repo is the fleet, not any one service — and the `fix` it printed (`loam init --service <id>` here) would have made the repo wrong. `currentService` still reports `unbound`; only the advice is gone.
34
- - **`loam instructions` checks the arguments it substitutes.** `loam instructions loam-adopt "$PWD"` rendered a protocol reading `services//Users/someone/work/svc/` — a page of confident instructions built around a value no loam command accepts. Each workflow now declares what its `$1`, `$2`, … stand for, and a service-id or feature-id placeholder is checked against the same grammar `loam adopt` and `loam new` use before anything is printed (`invalid-option`). An *unsupplied* placeholder is still left standing, and a free-text one (a feature title) is still free.
2319
+ - **`loam status` now sees the repository it is standing in.** With `loam.json` naming a service the
2320
+ docs repo has no directory for — a freshly wired service repo, the most common repo there is — the
2321
+ fleet count made "nothing is in flight and every service is written down" vacuously true, and
2322
+ `next.fleet-clean` came out on top. `doctor` had the right answer (`doctor.service-unknown`) at
2323
+ the same moment, and the documented agent loop reads `status --json` and runs `next[0]`: an agent
2324
+ following it was sent to start a feature instead of adopting the service under its feet. The new
2325
+ step is **`next.adopt-bound`**, first in `next[]` (behind only `next.recover-commit`), and
2326
+ suppressed under an explicit `--service`, which is a question about a different service.
2327
+ - **`doctor.service-unbound` is no longer raised inside the docs repo.** Having no service binding
2328
+ there is the correct state — the docs repo is the fleet, not any one service — and the `fix` it
2329
+ printed (`loam init --service <id>` here) would have made the repo wrong. `currentService` still
2330
+ reports `unbound`; only the advice is gone.
2331
+ - **`loam instructions` checks the arguments it substitutes.** `loam instructions loam-adopt "$PWD"`
2332
+ rendered a protocol reading `services//Users/someone/work/svc/` — a page of confident instructions
2333
+ built around a value no loam command accepts. Each workflow now declares what its `$1`, `$2`, …
2334
+ stand for, and a service-id or feature-id placeholder is checked against the same grammar
2335
+ `loam adopt` and `loam new` use before anything is printed (`invalid-option`). An *unsupplied*
2336
+ placeholder is still left standing, and a free-text one (a feature title) is still free.
35
2337
 
36
2338
  ### Fixed — `doctor.agent-files-stale` and `agents.stale` across a prerelease
37
2339
 
38
- `versionTrails` compared only the numeric triple, so `0.1.0-beta.1` neither trailed nor outranked `0.1.0-beta.2`. But beta.2 is precisely the release that changed the *form* of every generated file — embedded protocol text became a pointer at `loam instructions` — and its own changelog told readers to delete the files and re-run `loam init`. The one upgrade that most needed the warning was the one bump shape that could never raise it. Prerelease identifiers are now compared per semver: numerically where they are numbers, a prerelease behind its own final release, and a longer identifier list ahead of a prefix of itself. A stamp whose version is not valid semver still reads as no stamp at all, unchanged.
2340
+ `versionTrails` compared only the numeric triple, so `0.1.0-beta.1` neither trailed nor outranked
2341
+ `0.1.0-beta.2`. But beta.2 is precisely the release that changed the *form* of every generated file
2342
+ — embedded protocol text became a pointer at `loam instructions` — and its own changelog told
2343
+ readers to delete the files and re-run `loam init`. The one upgrade that most needed the warning was
2344
+ the one bump shape that could never raise it. Prerelease identifiers are now compared per semver:
2345
+ numerically where they are numbers, a prerelease behind its own final release, and a longer
2346
+ identifier list ahead of a prefix of itself. A stamp whose version is not valid semver still reads
2347
+ as no stamp at all, unchanged.
39
2348
 
40
2349
  ### Added — the async contract axis (AsyncAPI 3), phase one
41
2350
 
42
- Kafka was already drawable in the fleet map and describable in prose, and that was all: a `paymentService -> kafka 'Publishes PaymentAuthorized'` edge read as fully documented while naming no contract anybody could check. The event axis gives it the treatment the HTTP axis has — an artifact, a join token, and findings — in a first slice that reads and grades but does not yet merge.
43
-
44
- - **`services/<svc>/asyncapi.yaml`** — the async contract, sibling of `openapi.yaml`, optional and adopt-seeded. **AsyncAPI 3.0 only**: its operations are named top-level objects carrying `action: send|receive`, which is the exact analog of an `operationId`, where 2.x spells the same thing as `publish`/`subscribe` under a channel and leaves whose perspective they take ambiguous. A 2.x document declares no `operations` and reads as a contract with no messages rather than as a mis-parsed one. No new runtime dependency: the document is read with the `yaml` parser already present, by the same shallow structural walk `core/openapi.ts` uses.
45
- - **The join token is a message name**, spelled three times and required to be identical: a landscape edge's `metadata { publishes '...' }` / `metadata { consumes '...' }`, a requirement's `Publishes:` / `Consumes:` line, and the message's `name` in `asyncapi.yaml` (its declaration key when it has no `name`).
46
- - **loam never reads inside a message's `payload`.** It joins on names and on the `action` of the operation carrying them, and nothing else. Write payloads as JSON Schema; a fleet that later adopts Avro changes a `schemaFormat` line in its documents and nothing in loam. Keep payloads inside the document rather than `$ref`-ing an external `.avsc` — external references are out of scope here exactly as on the OpenAPI axis.
47
- - **New finding codes**, all from `loam validate --service <id>`: `service.no-asyncapi`, `asyncapi.invalid`, `asyncapi.duplicate-message`, `spine.message-undefined`, `spec-event.message-undefined`, `spine.message-unproduced`, `asyncapi.message-contested`, `event.messages-unlinked`, and the `ok` confirmation `event.covered`. No existing code changed, and no refusal that used to succeed now fails: every one of these fires only on a file or a `metadata` key that did not exist before this release.
48
- - **`spine.message-unproduced` is the check with no HTTP analog**, and it is why the axis exists. On the API axis the provider owns the contract, so "does this operation exist" is answered inside one service's directory. An event's schema lives in the *producer's* repository, so "does anybody publish what I consume" is a fleet question — and a consumer joined to nothing was previously indistinguishable from a healthy one. `asyncapi.message-contested` is its other half: two services claiming to send one name means every consumer's join picks one arbitrarily.
49
- - **`service.no-asyncapi` has two grades, not three.** Its `service.no-openapi` sibling warns when the landscape cannot prove nobody calls a service, because most services expose HTTP. An event contract is genuinely optional and most services touch no topic, so the absent file is an **error** when something already joins into it — the stranded message names ride in `details` — and **silence** otherwise.
50
- - `asyncapi.yaml` presence rides in `loam list`/`loam show` (`has.asyncapi`, and a lowercase `e` in the text flags, beside the arch spec's `a`). It is deliberately **not** part of the maturity ladder: making it a fourth required artifact would demote every already-`documented` service in the fleet without one byte of their files changing.
51
-
52
- **Not in this slice, and stated so nobody reads the absence as a bug:** a feature cannot yet add a message. There is no `features/<FEAT>/specs/<svc>/asyncapi.yaml`, no merge, no baseline pin, and no `verify` claim on this axis — a message is retired, for now, by editing the living contract in a reviewed PR. Deprecation markers and a removal family are deliberately absent rather than pending: an async contract that only ever grows has nothing to mark, because a consumer reading a topic with lag breaks on a removed field regardless of what the document says about it.
2351
+ Kafka was already drawable in the fleet map and describable in prose, and that was all: a
2352
+ `paymentService -> kafka 'Publishes PaymentAuthorized'` edge read as fully documented while naming
2353
+ no contract anybody could check. The event axis gives it the treatment the HTTP axis has — an
2354
+ artifact, a join token, and findings — in a first slice that reads and grades but does not yet
2355
+ merge.
2356
+
2357
+ - **`services/<svc>/asyncapi.yaml`** — the async contract, sibling of `openapi.yaml`, optional and
2358
+ adopt-seeded. **AsyncAPI 3.0 only**: its operations are named top-level objects carrying
2359
+ `action: send|receive`, which is the exact analog of an `operationId`, where 2.x spells the same
2360
+ thing as `publish`/`subscribe` under a channel and leaves whose perspective they take ambiguous. A
2361
+ 2.x document declares no `operations` and reads as a contract with no messages rather than as a
2362
+ mis-parsed one. No new runtime dependency: the document is read with the `yaml` parser already
2363
+ present, by the same shallow structural walk `core/openapi.ts` uses.
2364
+ - **The join token is a message name**, spelled three times and required to be identical: a
2365
+ landscape edge's `metadata { publishes '...' }` / `metadata { consumes '...' }`, a requirement's
2366
+ `Publishes:` / `Consumes:` line, and the message's `name` in `asyncapi.yaml` (its declaration key
2367
+ when it has no `name`).
2368
+ - **loam never reads inside a message's `payload`.** It joins on names and on the `action` of the
2369
+ operation carrying them, and nothing else. Write payloads as JSON Schema; a fleet that later
2370
+ adopts Avro changes a `schemaFormat` line in its documents and nothing in loam. Keep payloads
2371
+ inside the document rather than `$ref`-ing an external `.avsc` — external references are out of
2372
+ scope here exactly as on the OpenAPI axis.
2373
+ - **New finding codes**, all from `loam validate --service <id>`: `service.no-asyncapi`,
2374
+ `asyncapi.invalid`, `asyncapi.duplicate-message`, `spine.message-undefined`,
2375
+ `spec-event.message-undefined`, `spine.message-unproduced`, `asyncapi.message-contested`,
2376
+ `event.messages-unlinked`, and the `ok` confirmation `event.covered`. No existing code changed,
2377
+ and no refusal that used to succeed now fails: every one of these fires only on a file or a
2378
+ `metadata` key that did not exist before this release.
2379
+ - **`spine.message-unproduced` is the check with no HTTP analog**, and it is why the axis exists. On
2380
+ the API axis the provider owns the contract, so "does this operation exist" is answered inside one
2381
+ service's directory. An event's schema lives in the *producer's* repository, so "does anybody
2382
+ publish what I consume" is a fleet question — and a consumer joined to nothing was previously
2383
+ indistinguishable from a healthy one. `asyncapi.message-contested` is its other half: two services
2384
+ claiming to send one name means every consumer's join picks one arbitrarily.
2385
+ - **`service.no-asyncapi` has two grades, not three.** Its `service.no-openapi` sibling warns when
2386
+ the landscape cannot prove nobody calls a service, because most services expose HTTP. An event
2387
+ contract is genuinely optional and most services touch no topic, so the absent file is an
2388
+ **error** when something already joins into it — the stranded message names ride in `details` —
2389
+ and **silence** otherwise.
2390
+ - `asyncapi.yaml` presence rides in `loam list`/`loam show` (`has.asyncapi`, and a lowercase `e` in
2391
+ the text flags, beside the arch spec's `a`). It is deliberately **not** part of the maturity
2392
+ ladder: making it a fourth required artifact would demote every already-`documented` service in
2393
+ the fleet without one byte of their files changing.
2394
+
2395
+ **Not in this slice, and stated so nobody reads the absence as a bug:** a feature cannot yet add a
2396
+ message. There is no `features/<FEAT>/specs/<svc>/asyncapi.yaml`, no merge, no baseline pin, and no
2397
+ `verify` claim on this axis — a message is retired, for now, by editing the living contract in a
2398
+ reviewed PR. Deprecation markers and a removal family are deliberately absent rather than pending:
2399
+ an async contract that only ever grows has nothing to mark, because a consumer reading a topic with
2400
+ lag breaks on a removed field regardless of what the document says about it.
53
2401
 
54
2402
  ## [0.1.0-beta.2] - 2026-08-07
55
2403
 
56
- Still a prerelease, still under the `beta` dist-tag: the 5–10 service pilot has not run yet, and this release changes what `loam init` leaves in a repository, which is exactly the kind of thing a pilot is for. Two new commands, that change to the generated files, and a code-quality pass over `src/` with the defects it turned up. The refactoring half is invisible by construction; what follows is only the part a user can observe.
2404
+ Still a prerelease, still under the `beta` dist-tag: the 5–10 service pilot has not run yet, and
2405
+ this release changes what `loam init` leaves in a repository, which is exactly the kind of thing a
2406
+ pilot is for. Two new commands, that change to the generated files, and a code-quality pass over
2407
+ `src/` with the defects it turned up. The refactoring half is invisible by construction; what
2408
+ follows is only the part a user can observe.
57
2409
 
58
2410
  ### Added
59
2411
 
60
- - **`loam explore [<service>...]`** — read the fleet around a change nobody has written down yet, and write nothing. It answers the one question `loam new` takes as an argument: which services a feature touches. For each seed it reports the ring one hop out in the fleet map, each service's maturity rung and living operations, who already calls whom, the active features already covering the same services, and the literal `loam new` line the seeds imply. `--op <operationId>` seeds from an operation when you know the call but not who owns it; `--as <FEAT>` names the feature in the suggested line. A seed naming no `services/<id>/` is reported with its near-misses rather than refused — a feature may be introducing that service, and a typo looks identical until you read the list. The neighbours are deliberately **not** folded into the suggested command: loam knows those services are connected, not whether you change them.
61
- - **`loam instructions [<workflow>] [args...]`** — print one of the six workflow protocols (`loam-adopt`, `loam-feature`, `loam-implement`, `loam-check`, `loam-verify`, `loam-ship`) with `$1`, `$2` filled in from the arguments given. With no argument it lists them. It is the only command that reads no `loam.json` and no docs repo, deliberately: `loam-adopt`'s own first step is to run `loam init` when there is no config, so it cannot be the step that requires one.
62
- - **[WORKFLOW.md](WORKFLOW.md)** — the working protocol as a document: the artifact graph and its five derived states, what actually gates and what only advises, the six workflows, how an agent drives the cycle from `--json`, and why there is no task-list artifact.
2412
+ - **`loam explore [<service>...]`** — read the fleet around a change nobody has written down yet,
2413
+ and write nothing. It answers the one question `loam new` takes as an argument: which services a
2414
+ feature touches. For each seed it reports the ring one hop out in the fleet map, each service's
2415
+ maturity rung and living operations, who already calls whom, the active features already covering
2416
+ the same services, and the literal `loam new` line the seeds imply. `--op <operationId>` seeds
2417
+ from an operation when you know the call but not who owns it; `--as <FEAT>` names the feature in
2418
+ the suggested line. A seed naming no `services/<id>/` is reported with its near-misses rather than
2419
+ refused — a feature may be introducing that service, and a typo looks identical until you read the
2420
+ list. The neighbours are deliberately **not** folded into the suggested command: loam knows those
2421
+ services are connected, not whether you change them.
2422
+ - **`loam instructions [<workflow>] [args...]`** — print one of the six workflow protocols
2423
+ (`loam-adopt`, `loam-feature`, `loam-implement`, `loam-check`, `loam-verify`, `loam-ship`) with
2424
+ `$1`, `$2` filled in from the arguments given. With no argument it lists them. It is the only
2425
+ command that reads no `loam.json` and no docs repo, deliberately: `loam-adopt`'s own first step is
2426
+ to run `loam init` when there is no config, so it cannot be the step that requires one.
2427
+ - **[WORKFLOW.md](WORKFLOW.md)** — the working protocol as a document: the artifact graph and its
2428
+ five derived states, what actually gates and what only advises, the six workflows, how an agent
2429
+ drives the cycle from `--json`, and why there is no task-list artifact.
63
2430
 
64
2431
  ### Changed — what `loam init` writes into a command or skill file
65
2432
 
66
- - **A generated file is now a pointer at `loam instructions`, not a copy of the protocol.** It carries the workflow's purpose and its spine — the verbs, in order — and defers this release's flags, finding codes and fix tables to the binary; a fresh `.claude/commands/` drops from about 53 KB to about 11 KB, and the finding-code tables move with it. The protocol itself is unchanged and complete; only its delivery moved. The reason is that the two go stale in opposite directions and only one is fixable: loam never regenerates a generated file (your edits outrank the template, and that is not changing), so a protocol copied into a repository will eventually describe a different loam with total confidence, while a pointer cannot. `doctor.agent-files-stale` still reports a file whose version stamp has fallen behind, and now has almost nothing to be wrong about.
67
- - **Nothing rewrites an existing file.** A repository scaffolded by an earlier loam keeps exactly the files it was given, full protocol text and all — re-running `loam init` over it leaves them byte-unchanged. The version stamp is how you tell, and `loam doctor` raises `doctor.agent-files-stale` against them only once this binary's stamp is ahead of theirs. To take the new form, delete the files and re-run `loam init`.
68
- - Both new commands reuse the existing refusal codes (`invalid-option`, `unknown-target`). No stable code was added, changed or removed.
69
- - **The feature-id grammar has one home.** It was spelled privately in `loam new` and again in the OpenSpec migration; `core/ids.ts` owns it now, so an id one command accepts cannot be an id the other refuses. The only user-visible edge is wording: `migrate-openspec`'s `mapping.feature-id-invalid` message now ends with the same "e.g. FEAT-101 or BUG-42" example every other refusal gives. The code is unchanged.
2433
+ - **A generated file is now a pointer at `loam instructions`, not a copy of the protocol.** It
2434
+ carries the workflow's purpose and its spine — the verbs, in order — and defers this release's
2435
+ flags, finding codes and fix tables to the binary; a fresh `.claude/commands/` drops from about 53
2436
+ KB to about 11 KB, and the finding-code tables move with it. The protocol itself is unchanged and
2437
+ complete; only its delivery moved. The reason is that the two go stale in opposite directions and
2438
+ only one is fixable: loam never regenerates a generated file (your edits outrank the template, and
2439
+ that is not changing), so a protocol copied into a repository will eventually describe a different
2440
+ loam with total confidence, while a pointer cannot. `doctor.agent-files-stale` still reports a
2441
+ file whose version stamp has fallen behind, and now has almost nothing to be wrong about.
2442
+ - **Nothing rewrites an existing file.** A repository scaffolded by an earlier loam keeps exactly
2443
+ the files it was given, full protocol text and all — re-running `loam init` over it leaves them
2444
+ byte-unchanged. The version stamp is how you tell, and `loam doctor` raises
2445
+ `doctor.agent-files-stale` against them only once this binary's stamp is ahead of theirs. To take
2446
+ the new form, delete the files and re-run `loam init`.
2447
+ - Both new commands reuse the existing refusal codes (`invalid-option`, `unknown-target`). No stable
2448
+ code was added, changed or removed.
2449
+ - **The feature-id grammar has one home.** It was spelled privately in `loam new` and again in the
2450
+ OpenSpec migration; `core/ids.ts` owns it now, so an id one command accepts cannot be an id the
2451
+ other refuses. The only user-visible edge is wording: `migrate-openspec`'s
2452
+ `mapping.feature-id-invalid` message now ends with the same "e.g. FEAT-101 or BUG-42" example
2453
+ every other refusal gives. The code is unchanged.
70
2454
 
71
2455
  ### Fixed — commands that wrote or attested on evidence they had not checked
72
2456
 
73
- - **`archive` no longer installs an unreadable feature contract as the living one.** A feature `openapi.yaml` that does not parse as an OpenAPI document was planned verbatim into `services/<svc>/openapi.yaml` and reported `ok: true`; every other command reads `readOpenapi`'s `unreadable` flag, and the one that writes was the one that did not. It now refuses with `merge-failed`, naming the file. The same code now answers when the contract is being created rather than merged — that case used to escape as `internal`.
74
- - **`verify --record` no longer attests when it could not check the report.** The committed-report binding check tested for git's exit code 1 and nothing else, so a `git diff` killed by a signal — a CI timeout, the OOM killer, a fork failure — skipped the check and wrote the attestation anyway. A run that cannot reach an exit status is now a refusal that says git could not be run to completion.
75
- - **An interrupted `archive` can be retried on a docs repo composed of symlinks.** Checking whether the snapshot was stale resolved the manifest's paths through a symlink test meant for writes, so a `services/<svc>/` mounted from a sibling checkout — a layout every walk of the repo follows on purpose — turned every retry into a permanent refusal. That check only reads a file and compares a digest; containment is now judged without resolving the mount. `unarchive`, which writes the pre-image back, deliberately keeps the stricter test.
76
- - **A malformed `.loam-before/manifest.json` or `.loam-lock` is answered, not assumed.** Both were read through a cast: well-formed JSON that was not an object slipped past as "a different loam", and a bare `null` threw a `TypeError` out of the refusal itself. They now answer `commit-interrupted` and `docs-busy` respectively — the codes that case always deserved.
77
- - **A `spec.md` whose bytes are UTF-16LE is refused on the write path.** The write path's UTF-8 guard was a weaker copy of the read path's and missed exactly the file the read path had been hardened against: such a document parsed as zero requirements, so a merge computed over an empty baseline and wrote that back.
2457
+ - **`archive` no longer installs an unreadable feature contract as the living one.** A feature
2458
+ `openapi.yaml` that does not parse as an OpenAPI document was planned verbatim into
2459
+ `services/<svc>/openapi.yaml` and reported `ok: true`; every other command reads `readOpenapi`'s
2460
+ `unreadable` flag, and the one that writes was the one that did not. It now refuses with
2461
+ `merge-failed`, naming the file. The same code now answers when the contract is being created
2462
+ rather than merged — that case used to escape as `internal`.
2463
+ - **`verify --record` no longer attests when it could not check the report.** The committed-report
2464
+ binding check tested for git's exit code 1 and nothing else, so a `git diff` killed by a signal —
2465
+ a CI timeout, the OOM killer, a fork failure — skipped the check and wrote the attestation anyway.
2466
+ A run that cannot reach an exit status is now a refusal that says git could not be run to
2467
+ completion.
2468
+ - **An interrupted `archive` can be retried on a docs repo composed of symlinks.** Checking whether
2469
+ the snapshot was stale resolved the manifest's paths through a symlink test meant for writes, so a
2470
+ `services/<svc>/` mounted from a sibling checkout — a layout every walk of the repo follows on
2471
+ purpose — turned every retry into a permanent refusal. That check only reads a file and compares a
2472
+ digest; containment is now judged without resolving the mount. `unarchive`, which writes the
2473
+ pre-image back, deliberately keeps the stricter test.
2474
+ - **A malformed `.loam-before/manifest.json` or `.loam-lock` is answered, not assumed.** Both were
2475
+ read through a cast: well-formed JSON that was not an object slipped past as "a different loam",
2476
+ and a bare `null` threw a `TypeError` out of the refusal itself. They now answer
2477
+ `commit-interrupted` and `docs-busy` respectively — the codes that case always deserved.
2478
+ - **A `spec.md` whose bytes are UTF-16LE is refused on the write path.** The write path's UTF-8
2479
+ guard was a weaker copy of the read path's and missed exactly the file the read path had been
2480
+ hardened against: such a document parsed as zero requirements, so a merge computed over an empty
2481
+ baseline and wrote that back.
78
2482
 
79
2483
  ### Fixed — user errors reported as `internal`
80
2484
 
81
- - **`list`, `show` and `validate` report `repository-unavailable`** where a filesystem failure carries no path — a directory where `spec.md` belongs reports EISDIR with no `path`, which defeated the guard. `status` had this fix; the other three copies of the same catch block did not.
82
- - **`loam.json` that cannot be read reports `config-invalid`.** The read sat one line above its own `try`, so a config file that was a directory, or carried a restrictive permission bit, crashed every command in the CLI.
83
- - **`loam new` refuses instead of scaffolding into a directory that is not a docs repo**, and **`loam adopt` refuses (`docs-missing`)** instead of briefing an agent to write a baseline into a docs repo that does not exist. Both previously exited 0.
2485
+ - **`list`, `show` and `validate` report `repository-unavailable`** where a filesystem failure
2486
+ carries no path — a directory where `spec.md` belongs reports EISDIR with no `path`, which
2487
+ defeated the guard. `status` had this fix; the other three copies of the same catch block did not.
2488
+ - **`loam.json` that cannot be read reports `config-invalid`.** The read sat one line above its own
2489
+ `try`, so a config file that was a directory, or carried a restrictive permission bit, crashed
2490
+ every command in the CLI.
2491
+ - **`loam new` refuses instead of scaffolding into a directory that is not a docs repo**, and
2492
+ **`loam adopt` refuses (`docs-missing`)** instead of briefing an agent to write a baseline into a
2493
+ docs repo that does not exist. Both previously exited 0.
84
2494
 
85
2495
  ### Fixed — checks that answered differently depending on where they were asked
86
2496
 
87
- - **A relocated operation grades the same under `validate`, `status` and `archive`.** `FleetContext` carried a second implementation of `serviceOperationIds` that interleaved removals with upserts, so an operation removed at one path and defined at another answered "undefined" or "defined" depending on which the author spelled first — and `spec-api.op-undefined` is a gating error.
88
- - **`c4.op-ungoverned` joins per service.** Operation ids are unique only within a contract, so an operation governed on one service suppressed the warning for an unrelated service that shared its name.
89
- - **`api.covered` no longer counts a REMOVED requirement as governing.** An operation whose only governing requirement was removed now reports `api.ungoverned` (a warning; nothing about gating or exit codes changes).
90
- - **`loam status` (fleet form) reports `blocked` for a repo holding an interrupted commit journal**, agreeing with the per-feature form, which already did. It previously said `done` and "ship it" over a tree the other form called blocked.
91
- - **`loam delta` exits 1 when the feature's `openapi.yaml` does not parse**, matching the rule the architecture axis already had, and its `--json` payload carries an `openapi` key saying so. An unparseable contract used to project as "no operations".
2497
+ - **A relocated operation grades the same under `validate`, `status` and `archive`.** `FleetContext`
2498
+ carried a second implementation of `serviceOperationIds` that interleaved removals with upserts,
2499
+ so an operation removed at one path and defined at another answered "undefined" or "defined"
2500
+ depending on which the author spelled first — and `spec-api.op-undefined` is a gating error.
2501
+ - **`c4.op-ungoverned` joins per service.** Operation ids are unique only within a contract, so an
2502
+ operation governed on one service suppressed the warning for an unrelated service that shared its
2503
+ name.
2504
+ - **`api.covered` no longer counts a REMOVED requirement as governing.** An operation whose only
2505
+ governing requirement was removed now reports `api.ungoverned` (a warning; nothing about gating or
2506
+ exit codes changes).
2507
+ - **`loam status` (fleet form) reports `blocked` for a repo holding an interrupted commit journal**,
2508
+ agreeing with the per-feature form, which already did. It previously said `done` and "ship it"
2509
+ over a tree the other form called blocked.
2510
+ - **`loam delta` exits 1 when the feature's `openapi.yaml` does not parse**, matching the rule the
2511
+ architecture axis already had, and its `--json` payload carries an `openapi` key saying so. An
2512
+ unparseable contract used to project as "no operations".
92
2513
 
93
2514
  ### Fixed — deletion that reached outside the repository
94
2515
 
95
- - **`loam gherkin` orphan cleanup no longer follows a symlinked directory out of the repo.** Enumeration follows symlinks by design, so a planted `<gherkinDir>/loam/sub -> /outside` produced orphan paths that `unlink` resolved and deleted elsewhere. A symlinked `.feature` sitting directly in `loam/` is still removed — the link goes, its target survives — which is the distinction the guard now draws.
2516
+ - **`loam gherkin` orphan cleanup no longer follows a symlinked directory out of the repo.**
2517
+ Enumeration follows symlinks by design, so a planted `<gherkinDir>/loam/sub -> /outside` produced
2518
+ orphan paths that `unlink` resolved and deleted elsewhere. A symlinked `.feature` sitting directly
2519
+ in `loam/` is still removed — the link goes, its target survives — which is the distinction the
2520
+ guard now draws.
96
2521
 
97
2522
  ### Changed — internal structure
98
2523
 
99
- No behaviour depends on this, and it is recorded because the next contributor will notice it: the eight runtime import cycles in `src/core/` are gone, five helper modules were extracted from the modules that happened to hold them (`document-bytes`, `records`, `steps`, `concurrency`, and the docs-repo gate), and the duplicated spellings of six shared rules now have one home each. `docs/CODE-STYLE.md` records the conventions and the defect behind each one.
2524
+ No behaviour depends on this, and it is recorded because the next contributor will notice it: the
2525
+ eight runtime import cycles in `src/core/` are gone, five helper modules were extracted from the
2526
+ modules that happened to hold them (`document-bytes`, `records`, `steps`, `concurrency`, and the
2527
+ docs-repo gate), and the duplicated spellings of six shared rules now have one home each.
2528
+ `docs/CODE-STYLE.md` records the conventions and the defect behind each one.
100
2529
 
101
2530
  ## [0.1.0-beta.1] - 2026-08-06
102
2531
 
103
- The first published release, and a prerelease on purpose: it ships under the `beta` dist-tag, not `latest`. This entry covers the pre-publication hardening pass. Most of it is not new capability — it is the difference between a command that reports a problem and one that was quietly blind to it, and in four places it is the difference between an undo that works and one that certifies text nobody wrote. Grouped by what a user would notice.
2532
+ The first published release, and a prerelease on purpose: it ships under the `beta` dist-tag, not
2533
+ `latest`. This entry covers the pre-publication hardening pass. Most of it is not new capability —
2534
+ it is the difference between a command that reports a problem and one that was quietly blind to it,
2535
+ and in four places it is the difference between an undo that works and one that certifies text
2536
+ nobody wrote. Grouped by what a user would notice.
104
2537
 
105
2538
  ### Fixed — data loss and silent failure
106
2539
 
107
- - **A file whose bytes are not UTF-8 is no longer rewritten.** The whole write path moves bytes rather than strings; text is produced only where a parser needs it. Undecodable content refuses the merge (`merge-failed`, naming the file) instead of replacing every such byte with U+FFFD — in the living document *and* in the snapshot meant to undo it, which left nothing to restore from. A non-UTF-8 `openapi.yaml` now grades `openapi.invalid` instead of being handed to the YAML parser with the damage already done.
108
- - **`unarchive` no longer restores an edited snapshot and certifies it.** The snapshot manifest is version 2: every entry records `before`, a sha256 of the pre-image it will restore, alongside the existing `after`. `unarchive` hashes each pre-image before staging anything and refuses on a mismatch (`snapshot-corrupt`) — and `--force` deliberately does not override it, because `--force` discards later changes to the living docs while the damage here is to the undo itself. A version-1 snapshot is refused as `snapshot-missing`.
109
- - **A commit killed halfway can now be repaired.** `archive` and `unarchive` fsync an intent journal (`.loam-commit`) before the first swap and recover from it on the next run, under the lock: an archive is undone, an unarchive is finished (the merged text it was replacing is recorded nowhere else). Where the files no longer permit a safe repair the answer is `commit-interrupted`, a refusal. Previously a SIGKILL between two renames left a half-merged repo that `doctor` called healthy, `validate` blamed on the author's delta, and nothing could roll back.
110
- - **A removal marker can no longer be published into a living contract.** `x-loam-remove` written at path level, beside the methods, names no operation and retires nothing; `archive` gates it (`openapi.remove-marker-path-level`) and the marker is stripped from every merge branch, so it never reaches the fleet's living OpenAPI under any flag.
111
- - **Symlinked service and feature directories are enumerated.** A `services/<svc>` or `features/<FEAT>` reached through a symlink used to vanish from every listing, which also produced a false `landscape.binding-unknown` for the service that was right there. Dangling links are skipped exactly as an absent directory is.
2540
+ - **A file whose bytes are not UTF-8 is no longer rewritten.** The whole write path moves bytes
2541
+ rather than strings; text is produced only where a parser needs it. Undecodable content refuses
2542
+ the merge (`merge-failed`, naming the file) instead of replacing every such byte with U+FFFD — in
2543
+ the living document *and* in the snapshot meant to undo it, which left nothing to restore from. A
2544
+ non-UTF-8 `openapi.yaml` now grades `openapi.invalid` instead of being handed to the YAML parser
2545
+ with the damage already done.
2546
+ - **`unarchive` no longer restores an edited snapshot and certifies it.** The snapshot manifest is
2547
+ version 2: every entry records `before`, a sha256 of the pre-image it will restore, alongside the
2548
+ existing `after`. `unarchive` hashes each pre-image before staging anything and refuses on a
2549
+ mismatch (`snapshot-corrupt`) — and `--force` deliberately does not override it, because `--force`
2550
+ discards later changes to the living docs while the damage here is to the undo itself. A version-1
2551
+ snapshot is refused as `snapshot-missing`.
2552
+ - **A commit killed halfway can now be repaired.** `archive` and `unarchive` fsync an intent journal
2553
+ (`.loam-commit`) before the first swap and recover from it on the next run, under the lock: an
2554
+ archive is undone, an unarchive is finished (the merged text it was replacing is recorded nowhere
2555
+ else). Where the files no longer permit a safe repair the answer is `commit-interrupted`, a
2556
+ refusal. Previously a SIGKILL between two renames left a half-merged repo that `doctor` called
2557
+ healthy, `validate` blamed on the author's delta, and nothing could roll back.
2558
+ - **A removal marker can no longer be published into a living contract.** `x-loam-remove` written at
2559
+ path level, beside the methods, names no operation and retires nothing; `archive` gates it
2560
+ (`openapi.remove-marker-path-level`) and the marker is stripped from every merge branch, so it
2561
+ never reaches the fleet's living OpenAPI under any flag.
2562
+ - **Symlinked service and feature directories are enumerated.** A `services/<svc>` or
2563
+ `features/<FEAT>` reached through a symlink used to vanish from every listing, which also produced
2564
+ a false `landscape.binding-unknown` for the service that was right there. Dangling links are
2565
+ skipped exactly as an absent directory is.
112
2566
 
113
2567
  ### Fixed — checks that were blind
114
2568
 
115
- - **Nested landscape elements are graded.** The landscape cross-check kept only top-level elements, so ordinary grouped C4 — services under an `enterprise`, `group` or `boundary` — reported *every* service as unmodelled. The tree is walked instead: an element is at service level when no ancestor already stands for a service, an element that contains a service is a grouping, and `landscape.binding-unknown` is now graded at any depth.
116
- - **A missing `openapi.yaml` no longer silences the API axis.** It is an **error** when a living non-`REMOVED` `Operations:` line or an op-linked landscape edge already points into the absent file, with the stranded operationIds in `details`; a warning when nothing does and the landscape cannot prove nobody calls the service; silent when it can. Correspondingly, one absent contract is one finding rather than one `spine.op-undefined` per inbound consumer — a file that is not there proves nothing about an edge.
117
- - **A feature delta addressed to a nonexistent service is refused** (`delta.service-unknown`, naming close ids). A typo in `--touches` used to validate green and materialise a phantom service directory on archive.
118
- - **`openapi.duplicate-operationid` fires in service scope**, not only through an unrelated feature's delta — so `validate --all` sees it on a fleet with no feature in flight.
119
- - **`c4.uncovered` no longer fires on a re-declared edge.** A delta that restates a living edge to hang a requirement on it was told to write a decorative architecture requirement.
120
- - **`loam doctor` reports what it could not see before**: a held or stale `.loam-lock`, an interrupted commit, orphaned staging temp files, and generated command/skill files that have fallen behind (below).
2569
+ - **Nested landscape elements are graded.** The landscape cross-check kept only top-level elements,
2570
+ so ordinary grouped C4 — services under an `enterprise`, `group` or `boundary` — reported *every*
2571
+ service as unmodelled. The tree is walked instead: an element is at service level when no ancestor
2572
+ already stands for a service, an element that contains a service is a grouping, and
2573
+ `landscape.binding-unknown` is now graded at any depth.
2574
+ - **A missing `openapi.yaml` no longer silences the API axis.** It is an **error** when a living
2575
+ non-`REMOVED` `Operations:` line or an op-linked landscape edge already points into the absent
2576
+ file, with the stranded operationIds in `details`; a warning when nothing does and the landscape
2577
+ cannot prove nobody calls the service; silent when it can. Correspondingly, one absent contract is
2578
+ one finding rather than one `spine.op-undefined` per inbound consumer — a file that is not there
2579
+ proves nothing about an edge.
2580
+ - **A feature delta addressed to a nonexistent service is refused** (`delta.service-unknown`, naming
2581
+ close ids). A typo in `--touches` used to validate green and materialise a phantom service
2582
+ directory on archive.
2583
+ - **`openapi.duplicate-operationid` fires in service scope**, not only through an unrelated
2584
+ feature's delta — so `validate --all` sees it on a fleet with no feature in flight.
2585
+ - **`c4.uncovered` no longer fires on a re-declared edge.** A delta that restates a living edge to
2586
+ hang a requirement on it was told to write a decorative architecture requirement.
2587
+ - **`loam doctor` reports what it could not see before**: a held or stale `.loam-lock`, an
2588
+ interrupted commit, orphaned staging temp files, and generated command/skill files that have
2589
+ fallen behind (below).
121
2590
 
122
2591
  ### Changed — verification says which answers came from a test run
123
2592
 
124
- `--record` may still confirm a `scenario.tested` claim on an agent's word; a service with no runnable suite yet has to be able to record its answers. What changed is that the record now says so, everywhere:
125
-
126
- - a three-valued **`verdict`** — `verified`, `attested`, `unverified` — recomputed from `claims[]` on every read and never taken from the record's own `summary:` block. `verified` in `--json` is exactly `verdict === "verified"`, so a record with zero claims no longer reads as verified by arithmetic;
127
- - `verify.scenario-attested` (warning, gating nothing) naming each claim, on the read view, the recording view and the frozen post-archive view alike; `loam status` re-reports it and offers `next.verify-attested`, and such a feature reaches `stage: ready`, never `done`;
128
- - a `summary:` block contradicting its own `claims[]` makes the whole record `record-unreadable` (`verify.record-miscounted`) — neither half can be believed, so neither is reported as fact;
129
- - `--results` writes down **which report it read** (path, sha256, mtime, tagged-scenario count), must resolve inside the attesting repository in federated mode, and refuses a committed report that differs from the attested commit. That says which file was consumed; it does not say the file came from executing that commit, and no digest can;
130
- - two services wording a scenario identically share one digest, so a single report cannot say whose suite ran it: those claims are left unconfirmed under `verify.digest-contested` rather than confirming both.
2593
+ `--record` may still confirm a `scenario.tested` claim on an agent's word; a service with no
2594
+ runnable suite yet has to be able to record its answers. What changed is that the record now says
2595
+ so, everywhere:
2596
+
2597
+ - a three-valued **`verdict`** — `verified`, `attested`, `unverified` — recomputed from `claims[]`
2598
+ on every read and never taken from the record's own `summary:` block. `verified` in `--json` is
2599
+ exactly `verdict === "verified"`, so a record with zero claims no longer reads as verified by
2600
+ arithmetic;
2601
+ - `verify.scenario-attested` (warning, gating nothing) naming each claim, on the read view, the
2602
+ recording view and the frozen post-archive view alike; `loam status` re-reports it and offers
2603
+ `next.verify-attested`, and such a feature reaches `stage: ready`, never `done`;
2604
+ - a `summary:` block contradicting its own `claims[]` makes the whole record `record-unreadable`
2605
+ (`verify.record-miscounted`) — neither half can be believed, so neither is reported as fact;
2606
+ - `--results` writes down **which report it read** (path, sha256, mtime, tagged-scenario count),
2607
+ must resolve inside the attesting repository in federated mode, and refuses a committed report
2608
+ that differs from the attested commit. That says which file was consumed; it does not say the file
2609
+ came from executing that commit, and no digest can;
2610
+ - two services wording a scenario identically share one digest, so a single report cannot say whose
2611
+ suite ran it: those claims are left unconfirmed under `verify.digest-contested` rather than
2612
+ confirming both.
131
2613
 
132
2614
  ### Changed — `loam status` is a projection over the gates
133
2615
 
134
- `status` used to say "ship it" on trees `archive` refused. It now takes `stage` and `next[]` from the union of what `validate --feature` errors on and what `archive` refuses to merge, so it may be more pessimistic than either and is never greener than both — an invariant with a test behind it. Also: `checks.coherent` runs the same functions `validate` does (coherence, provenance, missing scenarios); `owesContract` keys on the contract *file* rather than the service directory; `next[]` names `loam delta`, `loam gherkin` and `loam rebase`; the fleet form is capped at ten steps plus a `next.elided` notice and always ends on `next.fleet-gate`; and a filesystem failure that carries no path (a directory where a file belongs) is reported rather than crashing the command.
2616
+ `status` used to say "ship it" on trees `archive` refused. It now takes `stage` and `next[]` from
2617
+ the union of what `validate --feature` errors on and what `archive` refuses to merge, so it may be
2618
+ more pessimistic than either and is never greener than both — an invariant with a test behind it.
2619
+ Also: `checks.coherent` runs the same functions `validate` does (coherence, provenance, missing
2620
+ scenarios); `owesContract` keys on the contract *file* rather than the service directory; `next[]`
2621
+ names `loam delta`, `loam gherkin` and `loam rebase`; the fleet form is capped at ten steps plus a
2622
+ `next.elided` notice and always ends on `next.fleet-gate`; and a filesystem failure that carries no
2623
+ path (a directory where a file belongs) is reported rather than crashing the command.
135
2624
 
136
2625
  ### Changed — generated agent files carry a version stamp
137
2626
 
138
- Every generated command and skill body now opens with `<!-- generated by loam vX.Y.Z -->`, and `loam doctor` raises `doctor.agent-files-stale` for a file with no stamp or an older one. loam still never rewrites a generated file — the fix is a human's — but drift is now detectable, which it previously was not: a command file mangled to one line left `doctor: healthy: true`. Doctor also stops reporting a repo initialized `--no-skills` as missing its skills.
2627
+ Every generated command and skill body now opens with `<!-- generated by loam vX.Y.Z -->`, and
2628
+ `loam doctor` raises `doctor.agent-files-stale` for a file with no stamp or an older one. loam still
2629
+ never rewrites a generated file — the fix is a human's — but drift is now detectable, which it
2630
+ previously was not: a command file mangled to one line left `doctor: healthy: true`. Doctor also
2631
+ stops reporting a repo initialized `--no-skills` as missing its skills.
139
2632
 
140
- `loam rebase` and `loam status` were added to the shipped workflow bodies, which is where the mechanism that prevents two in-flight features from overwriting each other actually gets run. `doctor`'s fix line now spells `loam adopt --service <id>`; the positional form it used to print is refused by the CLI, and a new test parses every command loam prints against the real CLI so that class of defect cannot return silently.
2633
+ `loam rebase` and `loam status` were added to the shipped workflow bodies, which is where the
2634
+ mechanism that prevents two in-flight features from overwriting each other actually gets run.
2635
+ `doctor`'s fix line now spells `loam adopt --service <id>`; the positional form it used to print is
2636
+ refused by the CLI, and a new test parses every command loam prints against the real CLI so that
2637
+ class of defect cannot return silently.
141
2638
 
142
2639
  ### Changed — `loam init` keeps the pointer it was given
143
2640
 
144
- `--docs` wins only when it is actually passed. A re-run in a wired repository keeps the `docsDir` its committed `loam.json` already names, `--create` included, and spreads the rest of the config forward; `--json` reports `docsDirSource` as `flag`, `config` or `default`. Following `doctor`'s advice to re-run `init` used to repoint the repo at an empty decoy over which `validate --all` went green.
2641
+ `--docs` wins only when it is actually passed. A re-run in a wired repository keeps the `docsDir`
2642
+ its committed `loam.json` already names, `--create` included, and spreads the rest of the config
2643
+ forward; `--json` reports `docsDirSource` as `flag`, `config` or `default`. Following `doctor`'s
2644
+ advice to re-run `init` used to repoint the repo at an empty decoy over which `validate --all` went
2645
+ green.
145
2646
 
146
2647
  ### Fixed — the OpenSpec on-ramp
147
2648
 
148
- - **A Store checkout is audited by its planning shape, not by its marker.** A `.openspec-store/store.yaml` beside real `specs/` used to make audit look for `<checkout>/openspec`, find nothing, and report `ready: true, capabilities: 0` — then apply a target holding no requirements at all. The shape picks the root; the marker picks only the kind; a checkout with planning content in neither place is refused by name.
2649
+ - **A Store checkout is audited by its planning shape, not by its marker.** A
2650
+ `.openspec-store/store.yaml` beside real `specs/` used to make audit look for
2651
+ `<checkout>/openspec`, find nothing, and report `ready: true, capabilities: 0` — then apply a
2652
+ target holding no requirements at all. The shape picks the root; the marker picks only the kind; a
2653
+ checkout with planning content in neither place is refused by name.
149
2654
  - **A workspace nobody could read is never `ready`** (`openspec.workspace-empty`).
150
- - New blockers for shapes that silently migrated nothing: `openspec.change-quoted-requirements` (requirements under `## Requirements` inside an active delta — the shape OpenSpec's own living template mandates, which stages nothing in a change), `openspec.nonstandard-living-spec` (markdown under `specs/` named neither `spec.md` nor `design.md`), `openspec.hidden-change-directory` (a dot-prefixed directory under `changes/`, which enumeration skips).
151
- - **The source digest no longer covers archived changes.** Frozen history never gates, so it must not be able to invalidate a completed mapping either; a living or active edit still does.
152
- - **The staged target is a real docs repo** — its own `loam.json`, `AGENTS.md` and an empty `architecture/landscape.likec4` — so `FOLLOW-UP.md`'s instructions run where they are written, and `FOLLOW-UP.md` now ends with the cutover procedure: `services/` and `features/` move, everything else is review residue.
153
- - **A target inside a live loam fleet is refused**, instead of producing phantom features in every `loam list`.
154
- - The living capability tree is copied verbatim under `legacy/openspec/specs/`, so `## Purpose` prose, section prose and capability `design.md` are preserved rather than dropped; an ISO-8601 `created` timestamp is a valid date; fenced FROM/TO examples inside a `## RENAMED Requirements` block are no longer parsed as renames; and a rename's TO name no longer demands a service allocation the router never asks for.
2655
+ - New blockers for shapes that silently migrated nothing: `openspec.change-quoted-requirements`
2656
+ (requirements under `## Requirements` inside an active delta — the shape OpenSpec's own living
2657
+ template mandates, which stages nothing in a change), `openspec.nonstandard-living-spec` (markdown
2658
+ under `specs/` named neither `spec.md` nor `design.md`), `openspec.hidden-change-directory` (a
2659
+ dot-prefixed directory under `changes/`, which enumeration skips).
2660
+ - **The source digest no longer covers archived changes.** Frozen history never gates, so it must
2661
+ not be able to invalidate a completed mapping either; a living or active edit still does.
2662
+ - **The staged target is a real docs repo** — its own `loam.json`, `AGENTS.md` and an empty
2663
+ `architecture/landscape.likec4` — so `FOLLOW-UP.md`'s instructions run where they are written, and
2664
+ `FOLLOW-UP.md` now ends with the cutover procedure: `services/` and `features/` move, everything
2665
+ else is review residue.
2666
+ - **A target inside a live loam fleet is refused**, instead of producing phantom features in every
2667
+ `loam list`.
2668
+ - The living capability tree is copied verbatim under `legacy/openspec/specs/`, so `## Purpose`
2669
+ prose, section prose and capability `design.md` are preserved rather than dropped; an ISO-8601
2670
+ `created` timestamp is a valid date; fenced FROM/TO examples inside a `## RENAMED Requirements`
2671
+ block are no longer parsed as renames; and a rename's TO name no longer demands a service
2672
+ allocation the router never asks for.
155
2673
 
156
2674
  ### Performance
157
2675
 
158
- `loam list` and `loam validate --service` are now flat in landscape edge count: loam reads LikeC4's parsed model and never computes a view, which it had no use for. On a generated 120-service fleet the same commands previously did not finish above roughly 200 edges. Measured at 120 services and 400/800 edges on an 8-core laptop: `list --json` 1.1–1.5 s, `validate --service <id> --json` 1.4–1.6 s. `loam init` no longer scaffolds a `views { view index { include * } }` block it never reads.
2676
+ `loam list` and `loam validate --service` are now flat in landscape edge count: loam reads LikeC4's
2677
+ parsed model and never computes a view, which it had no use for. On a generated 120-service fleet
2678
+ the same commands previously did not finish above roughly 200 edges. Measured at 120 services and
2679
+ 400/800 edges on an 8-core laptop: `list --json` 1.1–1.5 s, `validate --service <id> --json` 1.4–1.6
2680
+ s. `loam init` no longer scaffolds a `views { view index { include * } }` block it never reads.
159
2681
 
160
- `loam validate --all` remains ~30 s on that fleet — it parses a fresh LikeC4 workspace per service — and no speedup is claimed for it. Its target loop is now a bounded pool rather than a serial `await`, which is byte-for-byte output-identical and deterministic in ordering, but measured as a wash on the hardware available; the lever that moved this command was the parsed model, not the loop shape.
2682
+ `loam validate --all` remains ~30 s on that fleet — it parses a fresh LikeC4 workspace per service —
2683
+ and no speedup is claimed for it. Its target loop is now a bounded pool rather than a serial
2684
+ `await`, which is byte-for-byte output-identical and deterministic in ordering, but measured as a
2685
+ wash on the hardware available; the lever that moved this command was the parsed model, not the loop
2686
+ shape.
161
2687
 
162
2688
  ### Fixed — release engineering
163
2689
 
164
- - The CI `package` job could never pass: `release-check.mjs` refused `--fixture-ready` whenever `GITHUB_ACTIONS` was set. The guard now keys on a tag ref, which is what it was actually protecting against. Any claim that CI had validated release readiness was false before this.
165
- - `npm run test:package` failed against the current tarball — the smoke test called `loam init --docs docs` without `--create`.
166
- - Five release scripts spawned `npm.cmd`/`loam.cmd` without a shell, which Node ≥ 20.12 rejects on Windows; they now resolve npm's CLI and run it through `process.execPath`. Windows itself is unexercised here — there is no Windows host — so this is verified structurally and by the first real CI run.
167
- - `pack-release` and `verify-release-artifact` compared an unpeeled `GITHUB_SHA` against `HEAD`, so an annotated or signed tag — the `npm version` and `git tag -a/-s` default — could never pass. Both peel now; `git tag -s v<version>` works.
168
- - Release artifact retention raised 1 → 14 days, so a candidate awaiting human approval outlives a weekend; CI narrowed to pushes on `main` with a concurrency group, so tags no longer run both workflows in parallel.
2690
+ - The CI `package` job could never pass: `release-check.mjs` refused `--fixture-ready` whenever
2691
+ `GITHUB_ACTIONS` was set. The guard now keys on a tag ref, which is what it was actually
2692
+ protecting against. Any claim that CI had validated release readiness was false before this.
2693
+ - `npm run test:package` failed against the current tarball — the smoke test called
2694
+ `loam init --docs docs` without `--create`.
2695
+ - Five release scripts spawned `npm.cmd`/`loam.cmd` without a shell, which Node ≥ 20.12 rejects on
2696
+ Windows; they now resolve npm's CLI and run it through `process.execPath`. Windows itself is
2697
+ unexercised here — there is no Windows host — so this is verified structurally and a platform
2698
+ runtime check remains outstanding.
2699
+ - `pack-release` and `verify-release-artifact` compared an unpeeled `GITHUB_SHA` against `HEAD`, so
2700
+ an annotated or signed tag — the `npm version` and `git tag -a/-s` default — could never pass.
2701
+ Both peel now; `git tag -s v<version>` works.
2702
+ - Release artifact retention raised 1 → 14 days, so a candidate awaiting human approval outlives a
2703
+ weekend; CI narrowed to pushes on `main` with a concurrency group, so tags no longer run both
2704
+ workflows in parallel.
169
2705
 
170
2706
  ### Documentation
171
2707
 
172
- README, COMPARISON, SCHEMA and MIGRATING were re-checked claim by claim against the code and against upstream OpenSpec live on 2026-08-05. Corrected: the documented migration entry point (`migrate-openspec <root>` returns `invalid-option`; `audit-openspec` is the real first step and appeared nowhere in the README), the corpus and test counts, the `--baseline` flag the reproduction recipe needs, the Executability and agent-surface comparison rows, and the claim that OpenSpec has no multi-repo story — Stores shipped in v1.5.0, and the honest argument is their documented "No sync, ever — by design" plus the absence of any lockfile or commit pin. Removed: the argument that upstream's generated instructions are broken, which upstream fixed inside 48 hours and which loam had in its own surface.
2708
+ README, COMPARISON, SCHEMA and MIGRATING were re-checked claim by claim against the code and against
2709
+ upstream OpenSpec live on 2026-08-05. Corrected: the documented migration entry point
2710
+ (`migrate-openspec <root>` returns `invalid-option`; `audit-openspec` is the real first step and
2711
+ appeared nowhere in the README), the corpus and test counts, the `--baseline` flag the reproduction
2712
+ recipe needs, the Executability and agent-surface comparison rows, and the claim that OpenSpec has
2713
+ no multi-repo story — Stores shipped in v1.5.0, and the honest argument is their documented "No
2714
+ sync, ever — by design" plus the absence of any lockfile or commit pin. Removed: the argument that
2715
+ upstream's generated instructions are broken, which upstream fixed inside 48 hours and which loam
2716
+ had in its own surface.
173
2717
 
174
2718
  ### Added
175
2719
 
@@ -182,4 +2726,9 @@ README, COMPARISON, SCHEMA and MIGRATING were re-checked claim by claim against
182
2726
 
183
2727
  ## Upgrading to this release
184
2728
 
185
- Nothing to upgrade from — this is the first published version. One note for a repository initialized from a pre-release build: the first `loam doctor` after installing this one reports `doctor.agent-files-stale` for **every** generated command and skill file. Earlier builds wrote no version stamp, so "no stamp" and "an old stamp" are indistinguishable, and loam refuses to assume the instructions on disk describe this binary. Read each file, then bump its stamp by hand, or delete it and re-run `loam init` — the drift is reported and never repaired for you.
2729
+ Nothing to upgrade from — this is the first published version. One note for a repository initialized
2730
+ from a pre-release build: the first `loam doctor` after installing this one reports
2731
+ `doctor.agent-files-stale` for **every** generated command and skill file. Earlier builds wrote no
2732
+ version stamp, so "no stamp" and "an old stamp" are indistinguishable, and loam refuses to assume
2733
+ the instructions on disk describe this binary. Read each file, then bump its stamp by hand, or
2734
+ delete it and re-run `loam init` — the drift is reported and never repaired for you.