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

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