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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (691) hide show
  1. package/CHANGELOG.md +1477 -1
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +40 -22
  5. package/QUICKSTART.md +181 -9
  6. package/README.md +256 -57
  7. package/ROADMAP.md +108 -11
  8. package/SCHEMA.md +721 -130
  9. package/WORKFLOW.md +272 -62
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/archive/archive.js +15 -18
  15. package/dist/commands/archive/archive.js.map +1 -1
  16. package/dist/commands/archive/plan/contracts/asyncapi.js +27 -41
  17. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  18. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  19. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/openapi.js +25 -42
  21. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/verdict-lines.js +41 -0
  23. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  24. package/dist/commands/archive/plan/gate.js +3 -7
  25. package/dist/commands/archive/plan/gate.js.map +1 -1
  26. package/dist/commands/archive/plan/landscape.js +4 -4
  27. package/dist/commands/archive/plan/landscape.js.map +1 -1
  28. package/dist/commands/archive/plan/refusal.js +8 -18
  29. package/dist/commands/archive/plan/refusal.js.map +1 -1
  30. package/dist/commands/archive/plan/specs.js +134 -25
  31. package/dist/commands/archive/plan/specs.js.map +1 -1
  32. package/dist/commands/archive/plan/state.js +1 -0
  33. package/dist/commands/archive/plan/state.js.map +1 -1
  34. package/dist/commands/archive/run.js +82 -33
  35. package/dist/commands/archive/run.js.map +1 -1
  36. package/dist/commands/archive/shipped.js +49 -0
  37. package/dist/commands/archive/shipped.js.map +1 -0
  38. package/dist/commands/context/context.js +4 -7
  39. package/dist/commands/context/context.js.map +1 -1
  40. package/dist/commands/context/print.js +2 -2
  41. package/dist/commands/context/print.js.map +1 -1
  42. package/dist/commands/delta/delta.js +44 -12
  43. package/dist/commands/delta/delta.js.map +1 -1
  44. package/dist/commands/delta/print.js +1 -1
  45. package/dist/commands/delta/print.js.map +1 -1
  46. package/dist/commands/dependencies.js +3 -7
  47. package/dist/commands/dependencies.js.map +1 -1
  48. package/dist/commands/diff/diff.js +20 -27
  49. package/dist/commands/diff/diff.js.map +1 -1
  50. package/dist/commands/explore.js +4 -8
  51. package/dist/commands/explore.js.map +1 -1
  52. package/dist/commands/gate/gate.js +45 -8
  53. package/dist/commands/gate/gate.js.map +1 -1
  54. package/dist/commands/gate/print.js +15 -3
  55. package/dist/commands/gate/print.js.map +1 -1
  56. package/dist/commands/gherkin/commit.js +13 -54
  57. package/dist/commands/gherkin/commit.js.map +1 -1
  58. package/dist/commands/gherkin/gherkin.js +36 -20
  59. package/dist/commands/gherkin/gherkin.js.map +1 -1
  60. package/dist/commands/gherkin/reconcile.js +10 -13
  61. package/dist/commands/gherkin/reconcile.js.map +1 -1
  62. package/dist/commands/init/first-hour.js +44 -0
  63. package/dist/commands/init/first-hour.js.map +1 -1
  64. package/dist/commands/init/init.js +18 -22
  65. package/dist/commands/init/init.js.map +1 -1
  66. package/dist/commands/list/campaign/campaign.js +4 -6
  67. package/dist/commands/list/campaign/campaign.js.map +1 -1
  68. package/dist/commands/list/json.js +0 -4
  69. package/dist/commands/list/json.js.map +1 -1
  70. package/dist/commands/list/list.js +38 -23
  71. package/dist/commands/list/list.js.map +1 -1
  72. package/dist/commands/list/print.js +38 -6
  73. package/dist/commands/list/print.js.map +1 -1
  74. package/dist/commands/list/suites/suites.js +140 -0
  75. package/dist/commands/list/suites/suites.js.map +1 -0
  76. package/dist/commands/list/views.js +28 -33
  77. package/dist/commands/list/views.js.map +1 -1
  78. package/dist/commands/mcp/dispatch.js +91 -36
  79. package/dist/commands/mcp/dispatch.js.map +1 -1
  80. package/dist/commands/mcp/mcp.js +56 -15
  81. package/dist/commands/mcp/mcp.js.map +1 -1
  82. package/dist/commands/mcp/serve.js.map +1 -1
  83. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  84. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  85. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  86. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  87. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  88. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  89. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  90. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  91. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  92. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  93. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  94. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  95. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  96. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  97. package/dist/commands/new/capability-template.js +118 -0
  98. package/dist/commands/new/capability-template.js.map +1 -0
  99. package/dist/commands/new/commit.js +8 -22
  100. package/dist/commands/new/commit.js.map +1 -1
  101. package/dist/commands/new/modifies.js +206 -0
  102. package/dist/commands/new/modifies.js.map +1 -0
  103. package/dist/commands/new/new.js +59 -14
  104. package/dist/commands/new/new.js.map +1 -1
  105. package/dist/commands/new/templates.js +48 -124
  106. package/dist/commands/new/templates.js.map +1 -1
  107. package/dist/commands/open.js +3 -7
  108. package/dist/commands/open.js.map +1 -1
  109. package/dist/commands/policy/gate.js +27 -0
  110. package/dist/commands/policy/gate.js.map +1 -1
  111. package/dist/commands/rebase/plan.js +2 -2
  112. package/dist/commands/rebase/plan.js.map +1 -1
  113. package/dist/commands/rebase/rebase.js +60 -35
  114. package/dist/commands/rebase/rebase.js.map +1 -1
  115. package/dist/commands/seed/commit.js +55 -26
  116. package/dist/commands/seed/commit.js.map +1 -1
  117. package/dist/commands/seed/plan.js +64 -13
  118. package/dist/commands/seed/plan.js.map +1 -1
  119. package/dist/commands/seed/seed.js +6 -9
  120. package/dist/commands/seed/seed.js.map +1 -1
  121. package/dist/commands/show/show.js +3 -7
  122. package/dist/commands/show/show.js.map +1 -1
  123. package/dist/commands/status/print.js +13 -4
  124. package/dist/commands/status/print.js.map +1 -1
  125. package/dist/commands/status/status.js +3 -7
  126. package/dist/commands/status/status.js.map +1 -1
  127. package/dist/commands/steps/steps.js +3 -7
  128. package/dist/commands/steps/steps.js.map +1 -1
  129. package/dist/commands/subsystem/edit.js +7 -7
  130. package/dist/commands/subsystem/edit.js.map +1 -1
  131. package/dist/commands/subsystem/move.js +60 -25
  132. package/dist/commands/subsystem/move.js.map +1 -1
  133. package/dist/commands/subsystem/query.js +2 -4
  134. package/dist/commands/subsystem/query.js.map +1 -1
  135. package/dist/commands/subsystem/subsystem.js +4 -7
  136. package/dist/commands/subsystem/subsystem.js.map +1 -1
  137. package/dist/commands/subsystem/sync.js +141 -71
  138. package/dist/commands/subsystem/sync.js.map +1 -1
  139. package/dist/commands/subsystem/txn/report.js +55 -5
  140. package/dist/commands/subsystem/txn/report.js.map +1 -1
  141. package/dist/commands/subsystem/txn/txn.js +121 -45
  142. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  143. package/dist/commands/subsystem/txn/views.js +82 -12
  144. package/dist/commands/subsystem/txn/views.js.map +1 -1
  145. package/dist/commands/tasks/render.js +116 -0
  146. package/dist/commands/tasks/render.js.map +1 -0
  147. package/dist/commands/tasks/tasks.js +113 -0
  148. package/dist/commands/tasks/tasks.js.map +1 -0
  149. package/dist/commands/unarchive/manifest.js +6 -9
  150. package/dist/commands/unarchive/manifest.js.map +1 -1
  151. package/dist/commands/unarchive/restore.js +17 -28
  152. package/dist/commands/unarchive/restore.js.map +1 -1
  153. package/dist/commands/unarchive/unarchive.js +22 -30
  154. package/dist/commands/unarchive/unarchive.js.map +1 -1
  155. package/dist/commands/validate/arch-coverage.js +6 -15
  156. package/dist/commands/validate/arch-coverage.js.map +1 -1
  157. package/dist/commands/validate/checks/fleet-shape.js +4 -49
  158. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  159. package/dist/commands/validate/checks/health-deps.js +9 -6
  160. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  161. package/dist/commands/validate/checks/requirements.js +18 -8
  162. package/dist/commands/validate/checks/requirements.js.map +1 -1
  163. package/dist/commands/validate/checks/sources.js +6 -2
  164. package/dist/commands/validate/checks/sources.js.map +1 -1
  165. package/dist/commands/validate/feature.js +23 -19
  166. package/dist/commands/validate/feature.js.map +1 -1
  167. package/dist/commands/validate/fleet/census.js +26 -3
  168. package/dist/commands/validate/fleet/census.js.map +1 -1
  169. package/dist/commands/validate/fleet/landscape.js +35 -32
  170. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  171. package/dist/commands/validate/fleet/map/attest.js +7 -12
  172. package/dist/commands/validate/fleet/map/attest.js.map +1 -1
  173. package/dist/commands/validate/fleet/map/bindings.js +16 -5
  174. package/dist/commands/validate/fleet/map/bindings.js.map +1 -1
  175. package/dist/commands/validate/fleet/map/consumers.js +43 -122
  176. package/dist/commands/validate/fleet/map/consumers.js.map +1 -1
  177. package/dist/commands/validate/fleet/map/usecases.js +5 -11
  178. package/dist/commands/validate/fleet/map/usecases.js.map +1 -1
  179. package/dist/commands/validate/fleet/obligations.js +1 -5
  180. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  181. package/dist/commands/validate/fleet/scorecard/print.js +5 -1
  182. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  183. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  184. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  185. package/dist/commands/validate/fleet/usecases/usecases.js +6 -4
  186. package/dist/commands/validate/fleet/usecases/usecases.js.map +1 -1
  187. package/dist/commands/validate/fleet/views/fleet-project.js +1 -1
  188. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -1
  189. package/dist/commands/validate/fleet/views/ids.js +33 -7
  190. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  191. package/dist/commands/validate/fleet/views/root-documents.js +17 -35
  192. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -1
  193. package/dist/commands/validate/fleet/views/stale.js +31 -14
  194. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  195. package/dist/commands/validate/links/corpus.js +1 -4
  196. package/dist/commands/validate/links/corpus.js.map +1 -1
  197. package/dist/commands/validate/links/glossary.js.map +1 -1
  198. package/dist/commands/validate/report.js +5 -4
  199. package/dist/commands/validate/report.js.map +1 -1
  200. package/dist/commands/validate/scope.js +113 -0
  201. package/dist/commands/validate/scope.js.map +1 -0
  202. package/dist/commands/validate/service/api.js +16 -104
  203. package/dist/commands/validate/service/api.js.map +1 -1
  204. package/dist/commands/validate/service/contracts/generated.js +134 -0
  205. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  206. package/dist/commands/validate/service/events/events.js +1 -1
  207. package/dist/commands/validate/service/events/events.js.map +1 -1
  208. package/dist/commands/validate/service/events/message-producers.js +1 -1
  209. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  210. package/dist/commands/validate/service/evidence-pins.js +56 -10
  211. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  212. package/dist/commands/validate/service/service.js +16 -26
  213. package/dist/commands/validate/service/service.js.map +1 -1
  214. package/dist/commands/validate/service/specs.js +31 -38
  215. package/dist/commands/validate/service/specs.js.map +1 -1
  216. package/dist/commands/validate/service/spine.js +8 -11
  217. package/dist/commands/validate/service/spine.js.map +1 -1
  218. package/dist/commands/validate/service/usecases/usecases.js +3 -3
  219. package/dist/commands/validate/service/usecases/usecases.js.map +1 -1
  220. package/dist/commands/validate/validate.js +44 -26
  221. package/dist/commands/validate/validate.js.map +1 -1
  222. package/dist/commands/verify/cross/diff.js +2 -6
  223. package/dist/commands/verify/cross/diff.js.map +1 -1
  224. package/dist/commands/verify/evidence/contract.js +1 -1
  225. package/dist/commands/verify/evidence/contract.js.map +1 -1
  226. package/dist/commands/verify/evidence/read.js +12 -3
  227. package/dist/commands/verify/evidence/read.js.map +1 -1
  228. package/dist/commands/verify/frozen.js +45 -23
  229. package/dist/commands/verify/frozen.js.map +1 -1
  230. package/dist/commands/verify/record.js +69 -17
  231. package/dist/commands/verify/record.js.map +1 -1
  232. package/dist/commands/verify/report.js +27 -44
  233. package/dist/commands/verify/report.js.map +1 -1
  234. package/dist/commands/verify/results.js +112 -30
  235. package/dist/commands/verify/results.js.map +1 -1
  236. package/dist/commands/verify/verify.js +54 -51
  237. package/dist/commands/verify/verify.js.map +1 -1
  238. package/dist/commands/vouch/pack/pack.js +2 -2
  239. package/dist/commands/vouch/pack/pack.js.map +1 -1
  240. package/dist/commands/vouch/pack/print.js +1 -6
  241. package/dist/commands/vouch/pack/print.js.map +1 -1
  242. package/dist/commands/vouch/pack/sections.js +1 -2
  243. package/dist/commands/vouch/pack/sections.js.map +1 -1
  244. package/dist/commands/vouch/run.js +26 -81
  245. package/dist/commands/vouch/run.js.map +1 -1
  246. package/dist/commands/vouch/sample/plan.js +20 -34
  247. package/dist/commands/vouch/sample/plan.js.map +1 -1
  248. package/dist/commands/vouch/vet/verify.js +39 -7
  249. package/dist/commands/vouch/vet/verify.js.map +1 -1
  250. package/dist/commands/vouch/vouch.js +10 -17
  251. package/dist/commands/vouch/vouch.js.map +1 -1
  252. package/dist/core/agent/agents-md/command-map.js +57 -102
  253. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  254. package/dist/core/agent/agents-md/map/explain.js +4 -3
  255. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  256. package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
  257. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  258. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  259. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  260. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  261. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  262. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  263. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  264. package/dist/core/agent/agents-md/map/status.js +9 -3
  265. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  266. package/dist/core/agent/agents-md/map/subsystem.js +31 -17
  267. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  268. package/dist/core/agent/agents-md/map/usecases.js +24 -6
  269. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  270. package/dist/core/agent/agents-md/refusals.js +37 -21
  271. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  272. package/dist/core/agent/agents-md.js +25 -10
  273. package/dist/core/agent/agents-md.js.map +1 -1
  274. package/dist/core/agent/agents-stamp.js +10 -0
  275. package/dist/core/agent/agents-stamp.js.map +1 -1
  276. package/dist/core/agent/scaffold.js +10 -15
  277. package/dist/core/agent/scaffold.js.map +1 -1
  278. package/dist/core/agent/tools/dialects.js +1 -0
  279. package/dist/core/agent/tools/dialects.js.map +1 -1
  280. package/dist/core/agent/tools/registry.js +41 -76
  281. package/dist/core/agent/tools/registry.js.map +1 -1
  282. package/dist/core/agent/workflows/adopt.js +5 -3
  283. package/dist/core/agent/workflows/adopt.js.map +1 -1
  284. package/dist/core/agent/workflows/check.js +25 -22
  285. package/dist/core/agent/workflows/check.js.map +1 -1
  286. package/dist/core/agent/workflows/closing.js +54 -21
  287. package/dist/core/agent/workflows/closing.js.map +1 -1
  288. package/dist/core/agent/workflows/feature.js +79 -11
  289. package/dist/core/agent/workflows/feature.js.map +1 -1
  290. package/dist/core/agent/workflows/implement.js +91 -13
  291. package/dist/core/agent/workflows/implement.js.map +1 -1
  292. package/dist/core/agent/workflows/reference/authoring.js +3 -1
  293. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  294. package/dist/core/agent/workflows/reference/codes.js +8 -6
  295. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  296. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  297. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  298. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  299. package/dist/core/agent/workflows/reference/spine.js +62 -24
  300. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  301. package/dist/core/asyncapi/baseline/plan.js +14 -81
  302. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  303. package/dist/core/asyncapi/digest.js +30 -25
  304. package/dist/core/asyncapi/digest.js.map +1 -1
  305. package/dist/core/asyncapi/merge/components.js +5 -4
  306. package/dist/core/asyncapi/merge/components.js.map +1 -1
  307. package/dist/core/asyncapi/merge/error.js +7 -2
  308. package/dist/core/asyncapi/merge/error.js.map +1 -1
  309. package/dist/core/asyncapi/merge/markers.js +30 -50
  310. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  311. package/dist/core/asyncapi/merge/merge.js +31 -53
  312. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  313. package/dist/core/asyncapi/merge/pin.js +10 -36
  314. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  315. package/dist/core/asyncapi/model.js.map +1 -1
  316. package/dist/core/asyncapi/read.js +36 -12
  317. package/dist/core/asyncapi/read.js.map +1 -1
  318. package/dist/core/brief/brief.js +30 -25
  319. package/dist/core/brief/brief.js.map +1 -1
  320. package/dist/core/brief/landscape.js +8 -12
  321. package/dist/core/brief/landscape.js.map +1 -1
  322. package/dist/core/brief/map/extend.js +8 -5
  323. package/dist/core/brief/map/extend.js.map +1 -1
  324. package/dist/core/brief/map/owed.js +8 -26
  325. package/dist/core/brief/map/owed.js.map +1 -1
  326. package/dist/core/brief/targets.js +79 -5
  327. package/dist/core/brief/targets.js.map +1 -1
  328. package/dist/core/brief/unchecked.js +1 -1
  329. package/dist/core/brief/unchecked.js.map +1 -1
  330. package/dist/core/c4/likec4.js +147 -16
  331. package/dist/core/c4/likec4.js.map +1 -1
  332. package/dist/core/c4/locate/unlocated.js +150 -0
  333. package/dist/core/c4/locate/unlocated.js.map +1 -0
  334. package/dist/core/c4/parsed/values.js +54 -1
  335. package/dist/core/c4/parsed/values.js.map +1 -1
  336. package/dist/core/c4/prefetch/fleet.js +1 -9
  337. package/dist/core/c4/prefetch/fleet.js.map +1 -1
  338. package/dist/core/c4/project/architecture.js +31 -9
  339. package/dist/core/c4/project/architecture.js.map +1 -1
  340. package/dist/core/c4/project/documents.js +36 -10
  341. package/dist/core/c4/project/documents.js.map +1 -1
  342. package/dist/core/c4/project/load.js +14 -21
  343. package/dist/core/c4/project/load.js.map +1 -1
  344. package/dist/core/c4/project/staged.js +7 -3
  345. package/dist/core/c4/project/staged.js.map +1 -1
  346. package/dist/core/c4/resolve/external.js +51 -0
  347. package/dist/core/c4/resolve/external.js.map +1 -0
  348. package/dist/core/c4/resolve/service.js +25 -1
  349. package/dist/core/c4/resolve/service.js.map +1 -1
  350. package/dist/core/c4/seed/template.js +15 -1
  351. package/dist/core/c4/seed/template.js.map +1 -1
  352. package/dist/core/c4/service-model/shape.js +2 -22
  353. package/dist/core/c4/service-model/shape.js.map +1 -1
  354. package/dist/core/c4/source-mask.js +288 -4
  355. package/dist/core/c4/source-mask.js.map +1 -1
  356. package/dist/core/c4/source-scan.js +203 -147
  357. package/dist/core/c4/source-scan.js.map +1 -1
  358. package/dist/core/c4/splice/authored-source.js +19 -0
  359. package/dist/core/c4/splice/authored-source.js.map +1 -1
  360. package/dist/core/c4/splice/delta-blocks.js +5 -18
  361. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  362. package/dist/core/c4/splice/landscape-merge.js +13 -8
  363. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  364. package/dist/core/c4/splice/model/merge.js +10 -6
  365. package/dist/core/c4/splice/model/merge.js.map +1 -1
  366. package/dist/core/c4/splice/placement.js +108 -18
  367. package/dist/core/c4/splice/placement.js.map +1 -1
  368. package/dist/core/c4/workspace.js +13 -22
  369. package/dist/core/c4/workspace.js.map +1 -1
  370. package/dist/core/capabilities/capabilities.js +6 -6
  371. package/dist/core/capabilities/capabilities.js.map +1 -1
  372. package/dist/core/capabilities/delta/doc.js +7 -10
  373. package/dist/core/capabilities/delta/doc.js.map +1 -1
  374. package/dist/core/capabilities/findings.js +15 -20
  375. package/dist/core/capabilities/findings.js.map +1 -1
  376. package/dist/core/capabilities/realizes/findings.js +12 -19
  377. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  378. package/dist/core/capabilities/realizes/join.js +0 -20
  379. package/dist/core/capabilities/realizes/join.js.map +1 -1
  380. package/dist/core/capabilities/rollup.js +6 -0
  381. package/dist/core/capabilities/rollup.js.map +1 -1
  382. package/dist/core/capabilities/tree.js +6 -28
  383. package/dist/core/capabilities/tree.js.map +1 -1
  384. package/dist/core/capabilities/usecase-join.js +3 -13
  385. package/dist/core/capabilities/usecase-join.js.map +1 -1
  386. package/dist/core/coherence/authoring/scaffold.js +11 -3
  387. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  388. package/dist/core/coherence/authoring/sentinels.js +2 -1
  389. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  390. package/dist/core/coherence/coherence.js +30 -16
  391. package/dist/core/coherence/coherence.js.map +1 -1
  392. package/dist/core/coherence/consumed/operations.js +70 -0
  393. package/dist/core/coherence/consumed/operations.js.map +1 -0
  394. package/dist/core/coherence/consumed/refusal.js +38 -0
  395. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  396. package/dist/core/coherence/declared.js +60 -34
  397. package/dist/core/coherence/declared.js.map +1 -1
  398. package/dist/core/coherence/events/declared.js +44 -59
  399. package/dist/core/coherence/events/declared.js.map +1 -1
  400. package/dist/core/coherence/events/events.js +119 -8
  401. package/dist/core/coherence/events/events.js.map +1 -1
  402. package/dist/core/coherence/events/grades.js +31 -8
  403. package/dist/core/coherence/events/grades.js.map +1 -1
  404. package/dist/core/coherence/events/lookups.js +14 -82
  405. package/dist/core/coherence/events/lookups.js.map +1 -1
  406. package/dist/core/coherence/lookups.js +18 -78
  407. package/dist/core/coherence/lookups.js.map +1 -1
  408. package/dist/core/coherence/retired/archived.js +285 -0
  409. package/dist/core/coherence/retired/archived.js.map +1 -0
  410. package/dist/core/coherence/retired/event-pair.js +62 -0
  411. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  412. package/dist/core/coherence/retired/merged.js +79 -0
  413. package/dist/core/coherence/retired/merged.js.map +1 -0
  414. package/dist/core/coherence/retired/retirement.js +49 -0
  415. package/dist/core/coherence/retired/retirement.js.map +1 -0
  416. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  417. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  418. package/dist/core/coherence/retired/revival/revived.js +297 -0
  419. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  420. package/dist/core/consumers/after.js +113 -0
  421. package/dist/core/consumers/after.js.map +1 -0
  422. package/dist/core/consumers/consumers.js +296 -0
  423. package/dist/core/consumers/consumers.js.map +1 -0
  424. package/dist/core/consumers/current.js +211 -0
  425. package/dist/core/consumers/current.js.map +1 -0
  426. package/dist/core/consumers/scan.js +46 -0
  427. package/dist/core/consumers/scan.js.map +1 -0
  428. package/dist/core/consumers/state.js +27 -0
  429. package/dist/core/consumers/state.js.map +1 -0
  430. package/dist/core/delta/delta.js +1 -1
  431. package/dist/core/delta/delta.js.map +1 -1
  432. package/dist/core/deployment/delta.js +15 -16
  433. package/dist/core/deployment/delta.js.map +1 -1
  434. package/dist/core/diff/base-git.js +94 -12
  435. package/dist/core/diff/base-git.js.map +1 -1
  436. package/dist/core/diff/base-state.js +28 -16
  437. package/dist/core/diff/base-state.js.map +1 -1
  438. package/dist/core/diff/joins.js +136 -0
  439. package/dist/core/diff/joins.js.map +1 -0
  440. package/dist/core/diff/scope/changed-paths.js +22 -6
  441. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  442. package/dist/core/diff/scope/edges.js +82 -0
  443. package/dist/core/diff/scope/edges.js.map +1 -0
  444. package/dist/core/diff/semantic.js +100 -31
  445. package/dist/core/diff/semantic.js.map +1 -1
  446. package/dist/core/doctor/agents.js +34 -18
  447. package/dist/core/doctor/agents.js.map +1 -1
  448. package/dist/core/doctor/report.js.map +1 -1
  449. package/dist/core/document/apply.js +10 -2
  450. package/dist/core/document/apply.js.map +1 -1
  451. package/dist/core/document/frontmatter.js +55 -0
  452. package/dist/core/document/frontmatter.js.map +1 -1
  453. package/dist/core/document/parse.js +29 -6
  454. package/dist/core/document/parse.js.map +1 -1
  455. package/dist/core/document/scenarios.js +17 -0
  456. package/dist/core/document/scenarios.js.map +1 -1
  457. package/dist/core/document/spec.js +4 -11
  458. package/dist/core/document/spec.js.map +1 -1
  459. package/dist/core/envelope/config.js +10 -0
  460. package/dist/core/envelope/config.js.map +1 -1
  461. package/dist/core/envelope/json.js +34 -0
  462. package/dist/core/envelope/json.js.map +1 -1
  463. package/dist/core/explain/families.js +17 -12
  464. package/dist/core/explain/families.js.map +1 -1
  465. package/dist/core/explain/refusals.js +3 -3
  466. package/dist/core/explain/refusals.js.map +1 -1
  467. package/dist/core/explain/terms.js +18 -9
  468. package/dist/core/explain/terms.js.map +1 -1
  469. package/dist/core/explore/explore.js +2 -7
  470. package/dist/core/explore/explore.js.map +1 -1
  471. package/dist/core/fleet-context.js +34 -95
  472. package/dist/core/fleet-context.js.map +1 -1
  473. package/dist/core/gate/checks.js +10 -3
  474. package/dist/core/gate/checks.js.map +1 -1
  475. package/dist/core/gate/partners.js +64 -19
  476. package/dist/core/gate/partners.js.map +1 -1
  477. package/dist/core/gate/report.js.map +1 -1
  478. package/dist/core/gate/verification.js +75 -12
  479. package/dist/core/gate/verification.js.map +1 -1
  480. package/dist/core/gherkin/stale.js +13 -37
  481. package/dist/core/gherkin/stale.js.map +1 -1
  482. package/dist/core/glossary/tree.js +6 -19
  483. package/dist/core/glossary/tree.js.map +1 -1
  484. package/dist/core/kernel/concurrency.js +19 -0
  485. package/dist/core/kernel/concurrency.js.map +1 -1
  486. package/dist/core/kernel/records.js +11 -0
  487. package/dist/core/kernel/records.js.map +1 -1
  488. package/dist/core/links/backlinks.js +1 -3
  489. package/dist/core/links/backlinks.js.map +1 -1
  490. package/dist/core/links/corpus.js +5 -36
  491. package/dist/core/links/corpus.js.map +1 -1
  492. package/dist/core/mcp/argv.js +101 -0
  493. package/dist/core/mcp/argv.js.map +1 -1
  494. package/dist/core/mcp/author-tools.js +2 -1
  495. package/dist/core/mcp/author-tools.js.map +1 -1
  496. package/dist/core/mcp/protocol.js +2 -2
  497. package/dist/core/mcp/protocol.js.map +1 -1
  498. package/dist/core/mcp/tools.js +43 -33
  499. package/dist/core/mcp/tools.js.map +1 -1
  500. package/dist/core/openapi/baseline/gate.js +53 -12
  501. package/dist/core/openapi/baseline/gate.js.map +1 -1
  502. package/dist/core/openapi/baseline/plan.js +22 -8
  503. package/dist/core/openapi/baseline/plan.js.map +1 -1
  504. package/dist/core/openapi/baseline/record.js +0 -1
  505. package/dist/core/openapi/baseline/record.js.map +1 -1
  506. package/dist/core/openapi/contract-text.js +33 -0
  507. package/dist/core/openapi/contract-text.js.map +1 -0
  508. package/dist/core/openapi/contract-tree.js +56 -0
  509. package/dist/core/openapi/contract-tree.js.map +1 -0
  510. package/dist/core/openapi/depth.js +38 -24
  511. package/dist/core/openapi/depth.js.map +1 -1
  512. package/dist/core/openapi/digest.js +52 -2
  513. package/dist/core/openapi/digest.js.map +1 -1
  514. package/dist/core/openapi/doc.js +12 -4
  515. package/dist/core/openapi/doc.js.map +1 -1
  516. package/dist/core/openapi/merge/components.js +25 -60
  517. package/dist/core/openapi/merge/components.js.map +1 -1
  518. package/dist/core/openapi/merge/error.js +3 -13
  519. package/dist/core/openapi/merge/error.js.map +1 -1
  520. package/dist/core/openapi/merge/markers.js +26 -30
  521. package/dist/core/openapi/merge/markers.js.map +1 -1
  522. package/dist/core/openapi/merge/merge.js +26 -52
  523. package/dist/core/openapi/merge/merge.js.map +1 -1
  524. package/dist/core/openapi/merge/pin.js +15 -35
  525. package/dist/core/openapi/merge/pin.js.map +1 -1
  526. package/dist/core/openspec/decide/verdict.js.map +1 -1
  527. package/dist/core/openspec/inventory.js +4 -5
  528. package/dist/core/openspec/inventory.js.map +1 -1
  529. package/dist/core/openspec/model/mapping.js +30 -28
  530. package/dist/core/openspec/model/mapping.js.map +1 -1
  531. package/dist/core/openspec/model/model.js +12 -7
  532. package/dist/core/openspec/model/model.js.map +1 -1
  533. package/dist/core/openspec/scan/changes.js +10 -4
  534. package/dist/core/openspec/scan/changes.js.map +1 -1
  535. package/dist/core/openspec/scan/config.js +7 -16
  536. package/dist/core/openspec/scan/config.js.map +1 -1
  537. package/dist/core/openspec/scan/shape.js +63 -19
  538. package/dist/core/openspec/scan/shape.js.map +1 -1
  539. package/dist/core/openspec/scan/walk.js +21 -38
  540. package/dist/core/openspec/scan/walk.js.map +1 -1
  541. package/dist/core/pack/living.js +2 -16
  542. package/dist/core/pack/living.js.map +1 -1
  543. package/dist/core/provenance/findings.js +48 -12
  544. package/dist/core/provenance/findings.js.map +1 -1
  545. package/dist/core/provenance/git.js +33 -57
  546. package/dist/core/provenance/git.js.map +1 -1
  547. package/dist/core/provenance/gitq/moves.js +71 -114
  548. package/dist/core/provenance/gitq/moves.js.map +1 -1
  549. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  550. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  551. package/dist/core/provenance/sources.js +23 -6
  552. package/dist/core/provenance/sources.js.map +1 -1
  553. package/dist/core/provenance/stamp.js +5 -17
  554. package/dist/core/provenance/stamp.js.map +1 -1
  555. package/dist/core/repo/entries.js.map +1 -1
  556. package/dist/core/repo/paths.js +34 -29
  557. package/dist/core/repo/paths.js.map +1 -1
  558. package/dist/core/repo/repo.js +24 -14
  559. package/dist/core/repo/repo.js.map +1 -1
  560. package/dist/core/repo/tree/find.js +12 -3
  561. package/dist/core/repo/tree/find.js.map +1 -1
  562. package/dist/core/repo/tree/fs.js +27 -1
  563. package/dist/core/repo/tree/fs.js.map +1 -1
  564. package/dist/core/repo/tree/render/body.js +45 -6
  565. package/dist/core/repo/tree/render/body.js.map +1 -1
  566. package/dist/core/repo/tree/render/service/header.js +125 -0
  567. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  568. package/dist/core/repo/tree/render/service/peers.js +28 -0
  569. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  570. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  571. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  572. package/dist/core/repo/tree/render/service/views.js +78 -0
  573. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  574. package/dist/core/repo/tree/render/stale.js +19 -2
  575. package/dist/core/repo/tree/render/stale.js.map +1 -1
  576. package/dist/core/repo/tree/render/views.js +102 -33
  577. package/dist/core/repo/tree/render/views.js.map +1 -1
  578. package/dist/core/repo/tree/walk.js +8 -30
  579. package/dist/core/repo/tree/walk.js.map +1 -1
  580. package/dist/core/results.js +47 -12
  581. package/dist/core/results.js.map +1 -1
  582. package/dist/core/staging/commit.js +10 -12
  583. package/dist/core/staging/commit.js.map +1 -1
  584. package/dist/core/staging/lock.js +74 -12
  585. package/dist/core/staging/lock.js.map +1 -1
  586. package/dist/core/staging/recovery/intent.js +2 -5
  587. package/dist/core/staging/recovery/intent.js.map +1 -1
  588. package/dist/core/staging/recovery/recover.js +60 -1
  589. package/dist/core/staging/recovery/recover.js.map +1 -1
  590. package/dist/core/staging/snapshot/manifest.js +152 -0
  591. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  592. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +10 -34
  593. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  594. package/dist/core/status/actions/execution.js +11 -0
  595. package/dist/core/status/actions/execution.js.map +1 -1
  596. package/dist/core/status/feature/artifacts.js +2 -2
  597. package/dist/core/status/feature/artifacts.js.map +1 -1
  598. package/dist/core/status/feature/feature.js +5 -11
  599. package/dist/core/status/feature/feature.js.map +1 -1
  600. package/dist/core/status/fleet/fleet.js +6 -2
  601. package/dist/core/status/fleet/fleet.js.map +1 -1
  602. package/dist/core/status/fleet/next.js +26 -3
  603. package/dist/core/status/fleet/next.js.map +1 -1
  604. package/dist/core/status/report.js.map +1 -1
  605. package/dist/core/status/verification.js +30 -15
  606. package/dist/core/status/verification.js.map +1 -1
  607. package/dist/core/tasks/packet.js +174 -0
  608. package/dist/core/tasks/packet.js.map +1 -0
  609. package/dist/core/tasks/record.js +43 -0
  610. package/dist/core/tasks/record.js.map +1 -0
  611. package/dist/core/tasks/sources.js +181 -0
  612. package/dist/core/tasks/sources.js.map +1 -0
  613. package/dist/core/tasks/stamps.js +76 -0
  614. package/dist/core/tasks/stamps.js.map +1 -0
  615. package/dist/core/tasks/unit.js +0 -0
  616. package/dist/core/tasks/unit.js.map +1 -0
  617. package/dist/core/usecases/capability.js +11 -14
  618. package/dist/core/usecases/capability.js.map +1 -1
  619. package/dist/core/usecases/delta/flows.js +5 -7
  620. package/dist/core/usecases/delta/flows.js.map +1 -1
  621. package/dist/core/usecases/fleet.js +178 -31
  622. package/dist/core/usecases/fleet.js.map +1 -1
  623. package/dist/core/usecases/operations.js +5 -4
  624. package/dist/core/usecases/operations.js.map +1 -1
  625. package/dist/core/usecases/service/flows.js +9 -6
  626. package/dist/core/usecases/service/flows.js.map +1 -1
  627. package/dist/core/usecases/suites/participants.js +83 -0
  628. package/dist/core/usecases/suites/participants.js.map +1 -0
  629. package/dist/core/usecases/suites/suites.js +45 -0
  630. package/dist/core/usecases/suites/suites.js.map +1 -0
  631. package/dist/core/verify/answers.js +12 -5
  632. package/dist/core/verify/answers.js.map +1 -1
  633. package/dist/core/verify/build.js +102 -82
  634. package/dist/core/verify/build.js.map +1 -1
  635. package/dist/core/verify/checklist.js +45 -10
  636. package/dist/core/verify/checklist.js.map +1 -1
  637. package/dist/core/verify/location.js +67 -0
  638. package/dist/core/verify/location.js.map +1 -0
  639. package/dist/core/verify/pins/lint.js +3 -2
  640. package/dist/core/verify/pins/lint.js.map +1 -1
  641. package/dist/core/verify/pins/pin.js +1 -1
  642. package/dist/core/verify/pins/pin.js.map +1 -1
  643. package/dist/core/verify/read/file.js +224 -0
  644. package/dist/core/verify/read/file.js.map +1 -0
  645. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  646. package/dist/core/verify/read/shape.js.map +1 -0
  647. package/dist/core/verify/read/union.js +105 -0
  648. package/dist/core/verify/read/union.js.map +1 -0
  649. package/dist/core/verify/record.js +35 -12
  650. package/dist/core/verify/record.js.map +1 -1
  651. package/dist/core/verify/store/commit.js +40 -30
  652. package/dist/core/verify/store/commit.js.map +1 -1
  653. package/dist/core/verify/store/render.js +8 -4
  654. package/dist/core/verify/store/render.js.map +1 -1
  655. package/dist/core/vocabulary/issue.js +1 -0
  656. package/dist/core/vocabulary/issue.js.map +1 -1
  657. package/dist/core/vocabulary/maturity.js +53 -3
  658. package/dist/core/vocabulary/maturity.js.map +1 -1
  659. package/examples/README.md +28 -9
  660. package/examples/docs/architecture/landscape.likec4 +21 -7
  661. package/examples/docs/architecture/services.likec4 +70 -0
  662. package/examples/docs/architecture/subsystems.likec4 +3 -0
  663. package/examples/docs/architecture/usecases/order-notification.likec4 +6 -2
  664. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  665. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  666. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  667. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  668. package/examples/docs/services/order-service/model.likec4 +10 -8
  669. package/examples/docs/services/payment-service/model.likec4 +6 -4
  670. package/examples/docs/services/platform/identity-service/model.likec4 +9 -7
  671. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  672. package/examples/docs/services/platform/notification-service/model.likec4 +4 -5
  673. package/package.json +1 -1
  674. package/dist/commands/validate/checks/vocabulary.js +0 -16
  675. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  676. package/dist/core/agent/agents-md/artifacts.js +0 -221
  677. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  678. package/dist/core/agent/agents-md/cycle.js +0 -180
  679. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  680. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  681. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  682. package/dist/core/agent/agents-md/spine.js +0 -40
  683. package/dist/core/agent/agents-md/spine.js.map +0 -1
  684. package/dist/core/coherence/events/merged.js +0 -40
  685. package/dist/core/coherence/events/merged.js.map +0 -1
  686. package/dist/core/diff/current-state.js +0 -96
  687. package/dist/core/diff/current-state.js.map +0 -1
  688. package/dist/core/diff/victims.js +0 -256
  689. package/dist/core/diff/victims.js.map +0 -1
  690. package/dist/core/staging/snapshot.js.map +0 -1
  691. package/dist/core/verify/file.js.map +0 -1
package/SCHEMA.md CHANGED
@@ -62,6 +62,7 @@ docs/
62
62
  architecture/
63
63
  landscape.likec4 global C4 (system landscape)
64
64
  subsystems.likec4 one view per subsystem, views only [GENERATED: loam subsystem sync]
65
+ services.likec4 one overview view per service, views only [GENERATED: loam subsystem sync]
65
66
  permissions.yaml system-wide authorization vocabulary [authored, opt-in]
66
67
  capabilities.yaml system-wide capability vocabulary [authored, opt-in]
67
68
  obligations.yaml system-wide architectural obligations [authored, opt-in]
@@ -88,24 +89,25 @@ docs/
88
89
  delta.likec4 C4 delta (architecture) [authored]
89
90
  specs/<svc>/spec.md requirement delta (ADDED/MODIFIED/REMOVED + scenarios) [authored]
90
91
  specs/<svc>/arch.spec.md architectural requirement delta, same algebra [authored]
91
- specs/<svc>/openapi.yaml the endpoints this feature adds [authored]
92
- specs/<svc>/asyncapi.yaml the event-contract delta, slot-pinned [authored]
92
+ specs/<svc>/openapi.yaml only the endpoints it adds/changes/removes [authored]
93
+ specs/<svc>/asyncapi.yaml only the event slots it adds/changes/removes [authored]
93
94
  capabilities/<cap>/spec.md capability delta, same requirement grammar [authored]
94
95
  glossary/<term>.md a word this feature brings with it; create-only [authored]
95
96
  usecases/<name>.likec4 a FLOW this feature brings; views-only, create-only [authored]
96
97
  adrs/NNNN-*.md feature-level decisions [authored]
97
- verification.yaml the done-check: claims + verdicts + evidence, one attestation per service [loam verify --service <id> --record]
98
+ verification/<svc>.yaml the done-check, one file per attesting service: claims + verdicts + evidence [loam verify --service <id> --record]
99
+ verification.yaml the same answered all at once, and every record from before the directory — read forever [loam verify --record]
98
100
  features/archive/<FEAT>/ a shipped change — the same files, plus:
99
- .loam-before/ the bytes the merge overwrote [written by loam]
101
+ .loam-before/ the bytes the merge overwrote, and the checklist it shipped with [written by loam]
100
102
  .loam-lock transient: one writer at a time [archive/unarchive only]
101
103
  .loam-commit transient: the commit intent journal [archive/unarchive only]
102
104
  ```
103
105
 
104
106
  There is no `flows/` tree, and there will not be one. A cross-service **interaction flow** is a
105
107
  `dynamic view` under `architecture/` — the one place that already holds every cross-service edge —
106
- tagged with the capability it realizes. A flow that spans services does not belong under one
107
- service's directory, and the view's steps join to the landscape's own relationships, which is what
108
- makes the flow checkable rather than merely drawn.
108
+ tagged with the capability it realizes, or with the suites it runs in (below). A flow that spans
109
+ services does not belong under one service's directory, and the view's steps join to the
110
+ landscape's own relationships, which is what makes the flow checkable rather than merely drawn.
109
111
 
110
112
  A flow that stays INSIDE one service — the hop sequence behind an arch.spec.md requirement whose
111
113
  `Covers:` names containers — cannot live under `architecture/`: loam parses that directory as its
@@ -135,7 +137,7 @@ model.likec4 stays THE model:
135
137
  `c4.valid`, `Covers:` and every other model check read it alone, and a sibling that does not parse
136
138
  is one `usecase.flow-invalid` while the model keeps grading. Files loam does not read: a `.likec4`
137
139
  in a subsystem directory, in a feature directory outside `delta.likec4`/`usecases/`/`deployment/`,
138
- the generated `subsystems.likec4`, a sibling the service's own `likec4.config.json` `exclude`
140
+ the generated `subsystems.likec4` and `services.likec4`, a sibling the service's own `likec4.config.json` `exclude`
139
141
  covers (it is in no project the renderer loads either), and every UNTAGGED `dynamic view`
140
142
  anywhere — a diagram, never a use case.
141
143
 
@@ -146,6 +148,41 @@ would leave behind — so a flow may draw a hop into a service the feature's own
146
148
  and a hop into an element the merge does NOT land is refused at plan time rather than archived into
147
149
  a map the next reader's `loam validate --all` fails on.
148
150
 
151
+ **A fleet flow can also say which SUITE it runs with: `#suite-<name>`.** A suite is a set of
152
+ journeys a test run exercises together — a smoke suite, a payments suite — and a journey joins one
153
+ by carrying the tag on its `dynamic view`, declared like every LikeC4 tag in the landscape's one
154
+ `specification` block (`tag suite-smoke`), so a mistyped suite is a parse error rather than a second,
155
+ empty suite. It is a SEPARATE prefix, not `#cap-`: a capability says what a flow is FOR, a suite says
156
+ what it is RUN WITH, and membership is **many-to-many** — one view may carry several `#suite-` tags,
157
+ and a suite holds as many views as carry its tag, which is why it is a tag and not a directory.
158
+
159
+ **Tagging is the opt-in, and it grades.** A view carrying only `#suite-` tags — no `#cap-`, no
160
+ `#req-` — IS a use case: every reader takes it (the predicate and the byte gate below now read three
161
+ prefixes), and `loam validate --all` grades its hops like any other's (`usecase.step-unbacked`,
162
+ `usecase.step-contested`, `usecase.step-unlinked`). A journey a test stand is provisioned from must
163
+ be held to the model it is drawn over. The suite tag claims no capability and no requirement, so it
164
+ earns nothing from the capability axis. An UNTAGGED view stays ungraded, exactly as before.
165
+
166
+ `loam list suites [<name>] [--json]` is the answer the tag exists for: **which services must be up
167
+ for suite X.** For each suite it lists the views carrying the tag, the enumerated services their
168
+ steps resolve to — through the same element→service resolver the step grades use, so a step into
169
+ one of order-service's containers counts order-service — and every participant that resolves to NO
170
+ service, by element id, with whether it (or anything it is drawn inside) carries `#external`: the
171
+ customer who drives the flow, a topic, a partner's API. The walk up stops at the first element
172
+ bound to or titled for a service, as `loam gate`'s partner reading does, so a service drawn inside
173
+ an `#external` grouping is still ours. Those are named and never dropped, because an environment
174
+ list that is silently short comes up, and the journey then fails against something nobody was
175
+ told to start. The payload is `suites[]` (`name`, `views[]` of `{id, file}`, `services[]`, `unresolved[]`
176
+ of `{element, external}`), `ungrouped[]` — the use cases carrying no `#suite-` tag, which every
177
+ per-suite line would otherwise leave out — and `useCases` (`state`: `absent` when the fleet
178
+ declares no use case, `ok`, or `unreadable` with `error`, beside the `unreadable` boolean
179
+ `list capabilities` spells). Over an unreadable `architecture/` the section degrades rather than
180
+ refusing, and `suites` and `ungrouped` are ABSENT rather than `[]`. A `<name>` narrows `suites[]`
181
+ only; an unknown one refuses `unknown-target` with the close names. The listing reads the FLEET
182
+ map's use cases; a `#suite-` tag on a flow inside one service's own project is graded there like any
183
+ other use-case tag, and is not listed — that flow runs inside one service, whose environment is the
184
+ service itself.
185
+
149
186
  ### Two shapes of a service model
150
187
 
151
188
  `services/<…>/<svc>/model.likec4` comes in two shapes, and **the file's own grammar decides which**
@@ -153,8 +190,8 @@ a map the next reader's `loam validate --all` fails on.
153
190
  top-level `specification { }` block containing an `element` declaration:
154
191
 
155
192
  - an **extending model** declares no element kind. It is parsed INSIDE the fleet's `architecture/`
156
- project — the landscape plus every other `architecture/**/*.likec4` except the generated
157
- `subsystems.likec4` — and says what is inside the service with `extend <fqn> { … }` on the element
193
+ project — the landscape plus every other `architecture/**/*.likec4` except the generated views
194
+ files (`subsystems.likec4`, `services.likec4`) — and says what is inside the service with `extend <fqn> { … }` on the element
158
195
  the map already binds to the directory. `<fqn>` is the FULLY-QUALIFIED id: `extend orderService`
159
196
  resolves to nothing when the element is `marketplace.orderService`. No `specification` is needed,
160
197
  no partner is re-declared, and `model {}` or no model at all are both fine. `extend` accepts
@@ -181,7 +218,7 @@ exactly why its copies of the map's elements can drift.
181
218
  |---|---|---|
182
219
  | `c4.declaration-diverged` (warn, service) | standalone | an element this model and the fleet map (`architecture/`) both declare, whose `kind`, `title`, tag SET or `metadata { service }` binding differ. The two are joined by id, then by the `metadata { service }` binding both wrote, then by the map element whose id ends with the model's as a dotted tail (`paymentService` ↔ `marketplace.paymentService`) — a tail two map elements answer to joins nothing, and a tail that is a BARE last segment (`db`, `api`) is a key only for a FLEET-LEVEL map element: one no ancestor of which stands for a service, so a top-level element or a box the map draws inside a plain grouping element, never an element drawn inside a service's own. `description` is deliberately not compared — it is prose, nothing grades it, and the other four each change what a view shows or what loam resolves. When the tag sets are all that differ and copying the map's declaration would not clear them — because the difference comes off `specification { element <kind> { … } }` on one side or the other — the message says so and names the document whose specification declares them, rather than asking for a copy. One finding per divergent element; silent while the map does not parse |
183
220
  | `c4.element-unowned` (warn, service) | extending | the model declares an element OUTSIDE the element that resolves to this service. Three remedies, first first: a store or component this service owns goes INSIDE the `extend <fqn> { }` block, where its id becomes `<fqn>.<name>`; a system the service reaches belongs in the map, declared once; another service's internals belong in that service's model |
184
- | `c4.fleet-project-invalid` (warn, landscape) | extending | `--all` only, and not while `landscape.excluded` holds — this load reads what the renderer reads and so has no floor under the map, and one `exclude` entry covering the map turns every extending model's `extend` into an unresolved reference (measured on `examples/docs`: 161 warnings from one line). loam loads the fleet project the way the renderer does — a walk of the docs root, minus everything the root `exclude` covers, minus the generated `subsystems.likec4`, minus every STANDALONE service directory (`service.model-unexcluded` owns that state), minus `features/` (a delta carries its own `specification` and the directory is transient) — and reports each error NOT already present in the map alone or in any per-service project the same run made. A `.likec4` in a SUBSYSTEM directory, above every service, is in that set. This is the class no per-service grade can see: two services declaring the same tag locally, two models adding the same child id, a broken untagged sibling nobody opted in. All of them are renderer-fatal. `locations[0]` is the file the error is in and the message tail names the failure it actually is |
221
+ | `c4.fleet-project-invalid` (warn, landscape) | extending | `--all` only, and not while `landscape.excluded` holds — this load reads what the renderer reads and so has no floor under the map, and one `exclude` entry covering the map turns every extending model's `extend` into an unresolved reference (measured on `examples/docs`: 161 warnings from one line). loam loads the fleet project the way the renderer does — a walk of the docs root, minus everything the root `exclude` covers, minus the generated views files (`subsystems.likec4`, `services.likec4`), minus every STANDALONE service directory (`service.model-unexcluded` owns that state), minus `features/` (a delta carries its own `specification` and the directory is transient) — and reports each error NOT already present in the map alone or in any per-service project the same run made. A `.likec4` in a SUBSYSTEM directory, above every service, is in that set. This is the class no per-service grade can see: two services declaring the same tag locally, two models adding the same child id, a broken untagged sibling nobody opted in. All of them are renderer-fatal. `locations[0]` is the file the error is in and the message tail names the failure it actually is |
185
222
  | `service.likec4-config-stray` (warn, landscape) | extending | a `likec4.config.json` beside an extending model. The renderer registers it as a project of its own rooted at that directory, so the docs root stops being a one-project workspace (`likec4 validate .` then refuses without `--project`), and wherever that nested project claims the model it parses alone, out of the root project where the map's kinds are: the model does not resolve and `export json --project fleet` loses the service's containers (measured, likec4 1.59.2 — which of the two happens depends on the workspace; on `examples/docs` the fleet export was byte-identical with and without the file, on a seeded fleet the containers went). Run `loam subsystem sync`: it deletes the file. Deleting it by hand works too. loam never writes one beside an extending model |
186
223
  | `service.model-excluded` (warn, landscape) | extending | the root `likec4.config.json`'s `exclude` covers this model's directory, so the renderer never loads it and the service is a box with nothing inside it. An entry is read the way the renderer reads it (measured, likec4 1.59.2): a bare entry is a raw path PREFIX starting on a `/` boundary, so `services`, `services/`, `services/*`, `services/**`, `services/**/*`, a wildcard segment in the middle and the bare `services/pay` all cover `services/payment-service` — while the glob form `services/pay/**` wants the literal separator and covers nothing there. An entry shaped like a FILE rather than a directory (`services/**/model.likec4`, `**/model.likec4`) is reported too, with a different repair: `loam subsystem sync` maintains only the `services/` directory entries, so that shape has to be narrowed or deleted by hand. The finding quotes the entry verbatim, because the repair is to edit that line; `loam subsystem sync` is the repair for the directory shape |
187
224
  | `service.model-unexcluded` (warn, landscape) | standalone | the mirror, and the one that costs most: the root `exclude` does NOT cover a standalone model's directory, so the renderer merges it into the root project and reports every kind and element it declares as a duplicate — blanking the whole project, not just this service. An entry already covering the directory in any of the spellings above satisfies it, and `sync` appends no second glob for one. Same repair |
@@ -218,24 +255,34 @@ So a hop naming `<service-fqn>.<container>` resolves in neither the preview nor
218
255
  leaves, and `usecase.flow-invalid` refuses — where the un-routed preview resolved it, passed the
219
256
  gate `--approve` cannot override, and landed a flow the next `loam validate --all` failed on.
220
257
 
221
- **Private stores.** A store one service owns is nested UNDER that service's element — written in
222
- the service's `extend <fqn> { store = database … }` block, or inside the service's element on the
223
- map when the map draws that service's containers. The `extend` placement needs an EXTENDING model:
224
- a model that declares its own `specification` is parsed alone, so `extend <fqn>` resolves nothing
225
- there, and for such a service the map is the only placement (or migrate the model — see "Two
226
- shapes of a service model" above). `landscape.datastore-private` names whichever of the two the
227
- consumer's model shape allows. Drawn beside the services instead it reads as a
228
- system in its own right (`landscape.datastore-private`). Nesting closes no door: loam counts a
258
+ **Private stores.** **Where a store is drawn is the team's choice.** A store one service owns may be
259
+ nested UNDER that service's element — written in the service's `extend <fqn> { store = database … }`
260
+ block, or inside the service's element on the map when the map draws that service's containers — or
261
+ drawn beside the services as an element of its own. Both are first-class and loam grades neither as
262
+ wrong. Until 2026-09-07 the second earned `landscape.datastore-private` at one consumer, a warning
263
+ whose whole message prescribed deleting the peer while the identical store nested was silent: it
264
+ graded the drawing rather than the architecture, and it is retired. Infrastructure is not a service
265
+ either — an element whose kind is `database`, `queue`, `topic`, `cache` or `bucket` is out of the
266
+ service census, so an owned store drawn at fleet level is not `landscape.service-undocumented`, is
267
+ not a `loam gate` partner, and mints no `service.exists` verification claim. The `extend` placement
268
+ needs an EXTENDING model: a model that declares its own `specification` is parsed alone, so
269
+ `extend <fqn>` resolves nothing there, and for such a service the map is the only nesting site (or
270
+ migrate the model — see "Two shapes of a service model" above).
271
+
272
+ What the choice DOES decide is one thing, and it is a fact about LikeC4 rather than a preference: **a
273
+ second consumer's edge resolves only against a declaration the MAP holds**, because a per-service
274
+ project is `architecture/` plus one model. Draw a container-level edge to `<owner-fqn>.<store>` in
275
+ another service's model while the store lives in the owner's `extend` block and that model is
276
+ `c4.invalid` ("Could not resolve reference to Referenceable named '<store>'"). So a store a second
277
+ consumer will name has to be on the map — nested inside the owner's element there, or beside the
278
+ services. That was always the load-bearing half; the prescription around it was not.
279
+
280
+ Nesting closes no door in the other direction: loam counts a
229
281
  consumer a service's EXTENDING model attests exactly as it counts one the map draws, so a nested
230
282
  store two services reach is `landscape.datastore-shared` — the store's owner plus every service
231
283
  whose model draws an outbound call at it or below it. The second consumer draws that call as a
232
284
  container-level edge to `<owner-fqn>.<store>` in its OWN model, and the service-level twin on the
233
- map as usual. Which of the two placements to choose is decided by that edge: a per-service project
234
- is `architecture/` plus one model, so the edge resolves only when the MAP declares the store —
235
- written in the owner's `extend` block, the same edge is `c4.invalid` on the consumer ("Could not
236
- resolve reference to Referenceable named '<store>'"). Nest it on the map when a second consumer is
237
- foreseeable, and in the model when it is not. The choice is cheap in ONE direction, and the
238
- direction is the whole of the rule. The two placements give the store the same id,
285
+ map as usual. The two placements give the store the same id,
239
286
  `<owner-fqn>.<store>`, so moving the declaration OUT of the owner's `extend` block into the owner's
240
287
  element on the map rewrites no edge and no `Covers:` line — measured on `examples/docs`, where
241
288
  moving `db` out of order-service's `extend` block into `orderService` on the map left the model at
@@ -246,7 +293,10 @@ order-service's `extend` block is `c4.invalid` on checkout-web's model ("Could n
246
293
  reference to Referenceable named 'db'"), or `landscape.invalid` plus one `spine.landscape-invalid`
247
294
  per service when that second edge is drawn on the map. `landscape.datastore-shared` needs a second
248
295
  consumer to fire, so it always prints in the state where map→extend breaks, and its message says
249
- so. The consumer census is fleet-scope: it reads
296
+ so. It is the one datastore grade left, it fires at two consumers for both placements alike, and it
297
+ states a coupling rather than prescribing a move: shared means the same DATA, and if two services
298
+ only share a host or a cluster the honest model is one private store each. The consumer census is
299
+ fleet-scope: it reads
250
300
  EXTENDING models only, because their ids are the map's fully-qualified ids and the join is exact,
251
301
  while a standalone model's ids are its own file's and are never matched to a landscape element.
252
302
 
@@ -433,11 +483,12 @@ the file behind.
433
483
  `loam subsystem sync` is the one regenerator (it also *removes* the file when the tree has no
434
484
  subsystems), every `loam subsystem` write verb regenerates it inside the same transaction, and
435
485
  `validate --all` grades staleness by **content comparison** (line endings normalized; a CRLF
436
- checkout changes no fact) — exactly one error,
437
- `subsystem.views-stale`, on exactly that file; nothing in loam ever parses it: a views-only document
438
- does not parse standalone (its `include` lines reference elements the landscape defines), and it
439
- holds **static** views, whose contents loam never reads in any document. Only the LikeC4 renderer,
440
- which merges the whole `architecture/` project, resolves its includes.
486
+ checkout changes no fact) — one error,
487
+ `subsystem.views-stale`, per stale generated file, each naming its own path (there are two of them;
488
+ `architecture/services.likec4` is below); nothing in loam ever parses either: a views-only document
489
+ does not parse standalone (its `include` lines reference elements the landscape defines), and both
490
+ hold **static** views, whose contents loam never reads in any document. Only the LikeC4 renderer,
491
+ which merges the whole `architecture/` project, resolves their includes.
441
492
 
442
493
  `sync` refuses to rewrite this file while the `architecture/` project cannot be read. The render
443
494
  needs the map's elements and its global-style census, and a tolerant "no elements" answer wrote a
@@ -452,7 +503,111 @@ surface. With no file yet, and inside a tree-changing verb's commit window (`new
452
503
  `rename`, whose contract is that the file lands with the change), the tolerant shape is written as
453
504
  before and the same note is printed.
454
505
 
455
- The tree's **second renderer file** is per service — `services/<…>/<svc>/likec4.config.json`,
506
+ ### `architecture/services.likec4` — one overview view per service
507
+
508
+ The **second generated views file**, written by the same `loam subsystem sync`, in the same commit,
509
+ removed by it when the map draws no service, and graded by the same `subsystem.views-stale` on its
510
+ own path. Views only, one per service the fleet map draws, sorted by service directory id:
511
+
512
+ ```
513
+ view service_payment_2dservice of marketplace.paymentService {
514
+ title 'Payments / payment-service'
515
+ global style services
516
+ include *
517
+ include kafka.orderEvents
518
+ include marketplace.checkoutWeb
519
+ include stripe
520
+ exclude * -> *
521
+ include -> marketplace.paymentService
522
+ include marketplace.paymentService ->
523
+ include marketplace.paymentService.* -> marketplace.paymentService.*
524
+ }
525
+ ```
526
+
527
+ **Why the peers are named one by one, measured at the 1.59.2 pin on a 120-service and a 58-service
528
+ fleet.** The obvious recipe — `include <fqn> -> *` and `include * -> <fqn>` — is wrong at fleet
529
+ scale and looks perfect on a small flat fixture, which is how it would have shipped. LikeC4 draws a
530
+ counterpart at the highest ancestor that is not also an ancestor of the subject, so on a map that
531
+ GROUPS its services (which is exactly what a 100-service fleet needs at fleet level) every peer
532
+ collapses to its group box: the naive view of one service in the 120-service fleet drew six group
533
+ boxes and **0 of its 10 nodes had drill-down**. loam already reads elements and relationships, so it
534
+ names each peer itself — the far endpoint of every edge touching the service or anything inside it,
535
+ spelled as the map declares it, so an edge at a topic names the topic and not the broker, which is
536
+ the truer picture and the one the map already draws. `exclude * -> *` then drops the peer-to-peer
537
+ cross traffic (23 edges down to 7 on the same fixture), and the three lines after it put back
538
+ exactly the subject's own: the UNARY `include -> <fqn>` / `include <fqn> ->`, never the binary
539
+ wildcard, which re-introduces the parent group boxes as extra nodes (16 instead of 10). `include
540
+ <fqn>.* -> <fqn>.*` is the interior wiring, and it is legal on a childless element — it simply
541
+ matches nothing — which is what makes one line work for both model shapes: an extending model's
542
+ containers are in the root project and appear, a standalone model's are not and the line is silent.
543
+
544
+ Measured at 58 services in 11 groups: a median of **10 nodes and 8 edges** per generated view, every
545
+ peer that is a service carrying drill-down into its own generated view, and `npx likec4 export json`
546
+ — which layouts every view — costing about **7 ms per generated view** (1.3 s with 120 of them
547
+ against 451 ms without). `likec4 start` layouts on demand, so a reader pays for the one view they
548
+ open, and the generated views are all small BY CONSTRUCTION; the expensive view is the hand-authored
549
+ fleet one, and it is unaffected.
550
+
551
+ **Drill-down is the `of`, and it costs one keyword.** `view <id> of <element-fqn>` names the view's
552
+ subject, and LikeC4 then computes `navigateTo` on that element's node in **every other view that
553
+ draws it** — no authored links anywhere. So one generated view per service makes the fleet map,
554
+ every subsystem view and every other service view click-through into the service. The subsystem
555
+ views take the same keyword when the map holds an element owning exactly that subsystem's members,
556
+ which closes the mesh — `fleet → payments → payments/service-22 → identity/service-45 → identity`,
557
+ every hop generated, none authored.
558
+
559
+ **Two views of one element are legal, and the FIRST DOCUMENT ALPHABETICALLY wins** the default
560
+ navigation target — measured by renaming the files, LikeC4's load order being alphabetical by path.
561
+ Both stay reachable: inside a view of the subject, the subject's own node navigates to the OTHER
562
+ view of the same element, so the renderer cycles between siblings and nothing is orphaned.
563
+ `architecture/landscape.likec4` sorts before `architecture/services.likec4`, so a team's authored
564
+ `view of <element>` written in the map keeps precedence over the generated one, and neither is lost.
565
+ loam does not detect this and never will: **docs/DESIGN.md rule 26 forbids loam reading what a view
566
+ SHOWS**, in any document, so it may not look at an authored `view of` to decide whether to emit. It
567
+ emits for every service the map draws, and the alphabet settles the rest.
568
+
569
+ **The first line is the deed, and every writer checks it.** Both generated views files open with a
570
+ `// generated by loam` mark, and that mark is what makes the path loam's rather than the team's.
571
+ These are the only two filenames loam RESERVES inside `architecture/`, a directory that is otherwise
572
+ entirely authored, so a repository that already had a file at one of those paths must not lose it to
573
+ an upgrade — the archive's promise that authored bytes stay authored holds here too. Every writer of
574
+ these paths reads the first line first: `loam subsystem sync` reports that one file `refused`,
575
+ writes nothing for it, still does the rest of its work, and exits 1; `loam subsystem new`, `rm`,
576
+ `move` and `rename` and `loam seed` refuse the WHOLE operation with `already-exists`, writing,
577
+ renaming and staging nothing, because their commit window exists precisely to land the generated
578
+ file in the same commit as the change to the tree — completing half of that would manufacture the
579
+ `subsystem.views-stale` state the window exists never to leave behind. The consequence to know: a
580
+ file with no mark is never regenerated, and an EMPTY file carries no mark either, so a truncated one
581
+ must be deleted by hand before sync will write it again. loam's own staging is atomic and cannot
582
+ produce one.
583
+
584
+ **Titles are folders, because there is nothing else.** LikeC4 has **no view-folder syntax** —
585
+ `views { group 'X' { … } }` is a parse error at the pinned version and the AST carries no view-group
586
+ type at all. What it has is the title: a view title is split on `/` into browser folders and only
587
+ the last segment is shown. So a per-service view is titled `<subsystem path> / <service directory
588
+ id>`, built from the same path helper the subsystem views use, and the two generated files fold into
589
+ ONE folder tree mirroring `services/`. An unfiled service is its own id at the root. The service
590
+ **directory id** is used rather than the map element's title: it is the name every loam command
591
+ addresses the service by, it cannot drift from the map, and a directory id cannot contain `/`. A
592
+ `dynamic view` you author under `services/<…>/<id>/usecases/` lands in the same folder when you
593
+ title it `'<subsystem path> / <service id> / <use case>'` — a convention loam states and never
594
+ grades.
595
+
596
+ **Which services appear.** Exactly those the element→service join resolves, the same join the
597
+ subsystem views use. A service the map does not draw is omitted — its absence is already
598
+ `landscape.service-unmodelled`, and rendering only what the committed map resolves is what keeps the
599
+ output a function of committed bytes. **View ids** are `service_` plus the same hex escaping the
600
+ subsystem view ids use, so the two generated files cannot collide with each other, and an authored
601
+ view claiming one is `subsystem.view-id-collision` — which names the file and the reserved prefix. A
602
+ `service_` id is reserved only for a service the map actually draws. **Style**: `global style
603
+ services`, presence-detected off the parsed map's declared style ids exactly as `subsystems` is, and
604
+ written only when a group of that name is declared — a reference to an undeclared group is a parse
605
+ error that blanks the whole project.
606
+
607
+ ### The per-service LikeC4 project file
608
+
609
+ A different artifact from the two generated views files above, and the tree's other renderer
610
+ concern: `services/<…>/<svc>/likec4.config.json`,
456
611
  which `loam subsystem sync` also writes, beside every **standalone** `model.likec4` that has none.
457
612
  An extending model needs no such file and never gets one: it belongs to the root project, which is
458
613
  where it parses, and a project file beside it is `service.likec4-config-stray` — which `sync`
@@ -589,7 +744,10 @@ so running loam from a subdirectory works; `loam init` writes the file where it
589
744
  this repo, digests that document and compares it with the committed
590
745
  `services/<id>/openapi.yaml`: `openapi.generated-stale` (warn) when they differ,
591
746
  `contracts.source-missing` (error) when nothing is there, `contracts.source-invalid` (error) when
592
- the path escapes the repository or the file does not parse.
747
+ the path escapes the repository or the file does not parse. The stale warning's `details` name
748
+ the operationIds that differ (served but not in the committed copy, committed but no longer
749
+ served), and its message says when the two sets agree, so a reworded description no longer reads
750
+ the same as twenty vanished endpoints.
593
751
 
594
752
  **loam reads it and never writes it.** The copy into the docs repo stays a human `cp` reviewed in
595
753
  a pull request — that is what keeps the committed contract a document somebody agreed to rather
@@ -803,7 +961,12 @@ two checked files:
803
961
  `--all` alike, from the docs repo included. A `verified` spec whose current body hash mismatches
804
962
  gets `content.stale` (**warning**, the `sources.stale` grading: the doc changed since it was
805
963
  vouched, and only a person can say whether verified still holds). A verified doc with no
806
- `content_digest` predates the stamp and stays quiet — the fix is re-vouching.
964
+ `content_digest` predates the stamp and stays quiet — the fix is re-vouching. Every fleet readout
965
+ carries the same fact, because it is computable from the docs repo: `loam list` shows
966
+ `vouched (stale)` and owes the row `re-vouch` in `missing` (so `--needs-work` lists it), `loam
967
+ status` counts it under `services.contentStale` and emits `next.revouch` instead of
968
+ `next.fleet-clean`, and `validate --all`'s scorecard counts it under `provenance.contentStale`.
969
+ The rung stays `vouched` — a person did stamp the document — exactly as for `vouch_scope` below.
807
970
  - `vouch_scope` — written by `loam vouch --sample <n>`, never by hand, and present only when the
808
971
  person who vouched read a recorded SAMPLE of the document rather than all of it. One flat string,
809
972
  `sampled <k>/<n> seed=<16 hex>`: k sections read of n, and the seed that chose them. `status`
@@ -975,7 +1138,7 @@ There is no manifest of services: the directories under `services/` **are** the
975
1138
  touched/untouched, never a set difference over the attested calls, because loam cannot tell a
976
1139
  call the map forgot from one the fleet deliberately does not draw at its own altitude. "Edge in
977
1140
  the map" means an edge in the `architecture/` PROJECT — the landscape plus every other `.likec4`
978
- under it, minus the generated `subsystems.likec4` — and `loam adopt` reads the same set. An
1141
+ under it, minus the generated views files (`subsystems.likec4`, `services.likec4`) — and `loam adopt` reads the same set. An
979
1142
  `#external` element is never the subject, whatever directory it is bound to.
980
1143
 
981
1144
  **`architecture/landscape.likec4` is required, not optional.** Its absence is a finding, never a
@@ -1078,9 +1241,64 @@ under any flag — `x-loam-remove` and `x-loam-based-on` are stripped at path le
1078
1241
  level alike, on every merge branch. A path item consisting of nothing but a marker publishes no
1079
1242
  phantom path. The same feature must carry a `REMOVED` requirement whose `Operations:` line names
1080
1243
  that id. Coherence gates an absent path+method, an id mismatch, an unjustified marker, a removed
1081
- requirement with no marker, and a new tagged C4 edge consuming the operation being removed.
1244
+ requirement with no marker, a new tagged C4 edge consuming the operation being removed, and a
1245
+ removal anything would still consume after the merge (`openapi.remove-op-consumed`, error,
1246
+ `--approve` overrides — **Who still consumes it** below says what counts). A `REMOVED`
1247
+ requirement owes a marker only while there is a living operation for it to retire: not for an id
1248
+ the service's living contract does not define (a consumer's `REMOVED` requirement naming another
1249
+ service's operation), and not for one this delta already replaces at its address (the overwrite
1250
+ below — no marker could be written there, since the replacement holds the slot). A living
1251
+ contract that does not parse proves neither exemption, so the marker stays owed.
1082
1252
  `serviceOperationIds` evaluates the feature as living upserts minus removals, so other same-feature
1083
- checks see the post-archive contract. Archive deletes only the matching method, reports removals in
1253
+ checks see the post-archive contract.
1254
+
1255
+ **An overwrite can remove an operation too, and is graded as one.** The merge writes per method and
1256
+ path, so a delta defining ANOTHER operationId where a living operation lives replaces it there — and
1257
+ when the contract the merge would write then defines the living one nowhere, it has left the
1258
+ contract with no marker for any of the checks above to read. Coherence asks the removal's own
1259
+ consumer question of it, on the merged contract: `openapi.remove-op-consumed` (error, `--approve`
1260
+ overrides, exactly as for a marker) while anything would still consume it after the merge. With no
1261
+ marker there is no `REMOVED` requirement either, so the provider's own requirement naming the old id
1262
+ stays unless the feature `MODIFIES` it to name the new operation or `REMOVES` it — and a `REMOVED`
1263
+ one owes no marker here. A new tagged edge onto the replaced operation is `c4-api.op-removing`. The
1264
+ fix is the delta's choice: define the replaced operation at another address in the same delta if it
1265
+ is to stay; for a rename, redraw the living edge and restate the governing requirement. An
1266
+ overwrite of an operation nothing names stays the advisory `api.op-unconsumed` it always was.
1267
+
1268
+ **Who still consumes it — one answer for the gate, `loam diff` and `loam archive`.** The consumers
1269
+ of an operation are read once, the same way for every reader (`core/consumers/`):
1270
+
1271
+ - every edge of the `architecture/` **project** whose `metadata { op }` names it and whose target
1272
+ resolves to the provider — any document the spine grades, never `landscape.likec4` alone (when
1273
+ only a sibling document fails to parse, the map file's own edges still count);
1274
+ - another service's requirement — in `spec.md` **or** `arch.spec.md` — whose `Operations:` line
1275
+ names it, **unless that service's own contract defines the same id** — then the line names that
1276
+ service's own operation: `validate` resolves a spec.md `Operations:` line against its own
1277
+ service's contract, and `loam dependencies` reads a line in either file naming an operation its
1278
+ own service provides as no dependency on anybody (an unreadable or absent contract proves no
1279
+ ownership, and the line counts);
1280
+ - the use-case hops the model attributes to it (a contested hop is a doubt, never a consumer);
1281
+ - the provider's OWN documents still naming it — its `spec.md` requirements, and edges it draws to
1282
+ itself. Not its `arch.spec.md` lines: `validate` grades an `Operations:` line in spec.md only, so
1283
+ one in arch.spec.md strands nothing (another service's does count — it is a dependency on this
1284
+ contract, as `loam dependencies` reads it). The gate counts these, because the merge strands them as surely; `loam diff` and the archive's
1285
+ `openapi.op-modified` warnings answer who ELSE is affected, and leave them out (a service owes no
1286
+ operationId to itself).
1287
+
1288
+ The gate reads all of it **after the feature's merge**: a requirement the same feature `MODIFIES`
1289
+ to drop the id, or `REMOVES`, no longer counts — another service's too, so retiring the consumer in
1290
+ the same feature is a fix the gate sees — and one of its own `ADDED`/`MODIFIED` requirements naming
1291
+ the id does. A delta the merge would refuse to apply fails closed: the living requirements count,
1292
+ plus every one the delta adds or modifies. Edges and hops are read as they stand: the landscape
1293
+ merge only ADDS edges and a feature's flows are create-only, so no feature document can take one
1294
+ away — drop or redraw a living edge by editing the living map in the same change. The refusal lists
1295
+ the consumers in its `details[]`, in the order `loam diff` lists the victims of the same removal.
1296
+ A consumer document nobody could read refuses too, and is named there: a spec file that does not
1297
+ decode (another service's, or the provider's own), or a service id two directories claim — the gate
1298
+ cannot prove such a document does not name what is leaving, so it says it could not check, never
1299
+ that the fleet still consumes it. An edge drawn with no `metadata { op }` and a contested use-case
1300
+ hop are doubts about a drawing: `loam diff` lists them, and they never refuse, since `loam seed`
1301
+ draws every edge without an op. Archive deletes only the matching method, reports removals in
1084
1302
  text and `openapiRemovals` JSON, never copies the marker into living OpenAPI, and deliberately does
1085
1303
  not garbage-collect components. A removal marker in a living `openapi.yaml` is
1086
1304
  `openapi.remove-marker-living` (error). The usual staged-write rollback and `unarchive` snapshot
@@ -1092,9 +1310,11 @@ A `paymentService -> kafka 'Publishes PaymentAuthorized'` edge used to read as f
1092
1310
  while naming no contract anybody could check. The event axis is the API axis's mirror, in **AsyncAPI
1093
1311
  3.0** — only 3.0, because its operations are named top-level objects carrying
1094
1312
  `action: send|receive`, the exact analog of an `operationId`, where 2.x spells the same thing as
1095
- `publish`/`subscribe` under a channel and leaves whose perspective they take ambiguous. A 2.x
1096
- document declares no `operations` and reads as a contract with no messages rather than as a
1097
- mis-parsed one. Like OpenAPI it is read with a shallow structural walk over the `yaml` parse, never
1313
+ `publish`/`subscribe` under a channel and leaves whose perspective they take ambiguous. A document
1314
+ whose `asyncapi:` field says 1.x or 2.x is refused as one `asyncapi.invalid` naming the version, and
1315
+ nothing on the event axis is graded against it: reading it as a 3.0 contract with no messages used
1316
+ to turn every edge and requirement into a misleading `spine.message-undefined`. The AsyncAPI CLI's
1317
+ `asyncapi convert --target-version=3.0.0` upgrades it. Like OpenAPI it is read with a shallow structural walk over the `yaml` parse, never
1098
1318
  a real AsyncAPI parser: the slice loam joins on is small enough that the parser's model would be
1099
1319
  dead weight, and adding it would be a fourth runtime dependency.
1100
1320
 
@@ -1157,9 +1377,14 @@ the edges point at channels it is the channels that collect three consumers each
1157
1377
  broker leaves a row of untagged hubs behind it.
1158
1378
 
1159
1379
  **The feature lifecycle, mirrored from the OpenAPI axis.** A feature changes this contract through
1160
- `features/<FEAT>/specs/<svc>/asyncapi.yaml` — a **complete AsyncAPI 3.0 document**, exactly as an
1161
- openapi delta is a complete document: the author restates the living contract around the part they
1162
- are changing, and the machinery tells restatement from edit. Identity is the **slot** — the pair
1380
+ `features/<FEAT>/specs/<svc>/asyncapi.yaml` — a self-contained AsyncAPI 3.0 document holding **only
1381
+ the slots the feature adds, changes or removes**, exactly as an openapi delta holds only its own
1382
+ operations: the merge walks only the slots the delta declares, so a slot it leaves out stays as the
1383
+ living contract has it, and an edited slot is written out whole. A slot restated without being
1384
+ changed is legal and the machinery tells it from an edit, but it buys nothing and costs a pin,
1385
+ which a parallel feature's removal turns into `asyncapi.baseline-invalid` and `asyncapi.slot-revived`
1386
+ — or, unpinned, into `asyncapi.slot-revived` alone, the contract axis's revival check (under *Living
1387
+ spec vs delta* below) on this axis's identity. Identity is the **slot** — the pair
1163
1388
  (section, key) over the three named sections `channels.<key>`, `operations.<key>` and
1164
1389
  `components.messages.<key>` — and the join token stays the message name. Three feature-only keys,
1165
1390
  spelled and shaped exactly as on the OpenAPI axis: `x-loam-based-on` inside a slot's value (written
@@ -1211,7 +1436,36 @@ decision that reverses that stance: the removal family exists, a message-slot re
1211
1436
  justified by a `REMOVED` requirement's `Publishes:`/`Consumes:` line (channel and operation slots
1212
1437
  need exactness only — requirements join on message names and never on channel or operation keys, so
1213
1438
  demanding a justifying requirement there would invent a join the corpus cannot express), and
1214
- `asyncapi.remove-message-consumed` gates a removal the living fleet still consumes. The lag argument
1439
+ `asyncapi.remove-message-consumed` gates a removal the fleet would still consume after the merge —
1440
+ a `consumes` edge of the `architecture/` project, another service's `Consumes:` line in either spec
1441
+ file (no ownership rule here: a consumer declares the message in its own contract as `receive` by
1442
+ construction), a use-case hop backed by such an edge — or whose provider's own `Publishes:` line the
1443
+ merge would leave behind, or its own `publishes` edge (the spine grades that edge on its source,
1444
+ `spine.message-undefined`; drop or redraw it in the living map), which covers a message slot re-keyed to another name or a send operation
1445
+ turned around as surely as a marker: read after the merge, as on the HTTP axis, and a `REMOVED`
1446
+ requirement owes no marker for a message the merge already takes out. **A consumer counts only
1447
+ while nothing would still send the message after the merge** — no service, and no `#external`
1448
+ element whose edge publishes it: the join is fleet-global, and a consumer with another producer is
1449
+ not stranded — `spine.message-unproduced` grades a consumer by exactly that, an outside producer
1450
+ included (*The direction is not decoration* above), and `loam diff` reports such a removal as
1451
+ `diff.message-removed`, "still sent by …", naming an outside producer as `<title> (#external)`.
1452
+ The senders are read after the merge too: a service this feature carries an event delta for sends
1453
+ what the merged contract would (the delta itself, where it creates the contract), and vouches for
1454
+ nothing where the merge cannot be simulated, so a feature retiring the message from every sender
1455
+ is still refused; an unreadable contract never vouches. The outside producer is read off the
1456
+ living `architecture/` project with validate's own rule, and only while that project parses. The
1457
+ landscape merge only adds to the map, so no feature takes its edge or tag away — but the rule also
1458
+ asks that no `services/<id>/` directory stand for the element, and the merge creates one from each
1459
+ `specs/<id>/` the feature carries. So the rule is asked over the directories as the merge leaves
1460
+ them: a feature that onboards the outside system as a service (`specs/legacy-gateway/`, its element
1461
+ titled or bound `legacy-gateway`, or a topic nested inside it) takes the producer away, and a
1462
+ retirement in it is refused unless something else still sends the message — the new service's own
1463
+ contract, in its `specs/legacy-gateway/` delta, for one. A `publishes` edge
1464
+ from an `#external` element that only the feature's own C4 delta draws does not count either —
1465
+ draw it in the living map, or archive with `--approve`. Another sender does not excuse the
1466
+ provider's own `Publishes:` lines and `publishes` edges — those are graded against its own
1467
+ contract, whoever else sends the message — nor its own unreadable spec files; another service's
1468
+ unreadable spec file, or an id two directories claim, refuses only while nothing would send. The lag argument
1215
1469
  survives as an **operational warning**, not a veto: passing the gate proves the *documents* agree
1216
1470
  nobody consumes the message — it does not prove no consumer is still reading the topic with lag, so
1217
1471
  sequence the runtime retirement accordingly.
@@ -1325,19 +1579,35 @@ the source for tests.
1325
1579
  never the resolution itself — a pin says "I read this version", so `loam rebase` reports every
1326
1580
  requirement that moved and says plainly that restamping without re-reading merges your text over
1327
1581
  theirs. There is deliberately no three-way merge: requirement prose does not merge.
1328
- - **The same baseline on the contract axis, where it matters more.** A feature's `openapi.yaml` is a
1329
- **complete document, not a patch** — authors restate the living contract around the slot they
1330
- change — and the merge upserts every operation the document spells. The collision therefore needs
1331
- no overlap between the two features at all: two deltas over one service, editing *different*
1332
- operations, destroy each other, because whichever archives second pushes its authoring-time copy
1333
- of the operation it never meant to touch back over what landed in between. On a service with
1334
- thirty operations a delta quotes twenty-nine of them, and each is a candidate. So every operation
1335
- in a feature contract carries **`x-loam-based-on`** — the same 16-hex digest, in the
1582
+ - **The same baseline on the contract axis.** A feature's `openapi.yaml` is a **self-contained
1583
+ document holding only what the feature adds, changes or removes** — not a copy of the living
1584
+ contract. The merge walks only the paths and methods the delta spells: an operation it leaves out
1585
+ stays exactly as the living contract has it, a local `$ref` (`#/components/schemas/Order`)
1586
+ resolves into the living document, and a brand-new operation, path-level key or component has no
1587
+ living version to pin, so an add-only delta never trips `openapi.baseline-missing` and needs no
1588
+ `loam rebase` at all. What the merge does upsert is every operation the document *does* spell,
1589
+ so an operation quoted without being changed — copied for context, or a whole contract pasted in
1590
+ — is a collision waiting for somebody else's edit: two deltas over one service, each quoting the
1591
+ operation the other edits, destroy each other, because whichever archives second pushes its
1592
+ authoring-time copy back over what landed in between. A quote also costs something even pinned:
1593
+ it is one more pin, and a pinned quote whose living operation a parallel feature removes is
1594
+ `openapi.baseline-invalid` — the living slot is gone, so the pin is founded on nothing — an
1595
+ error that blocks the archive until the quote is deleted. Unpinned, the same quote has no living
1596
+ operation behind it, so the baseline axis cannot tell it from an addition — and it used to
1597
+ archive as one, at exit 0, putting the retired operation back. The **revival check** below is
1598
+ what refuses it now. So a delta quotes nothing it does not change, and every operation in it that
1599
+ the living contract already holds carries
1600
+ **`x-loam-based-on`** — the same 16-hex digest, in the
1336
1601
  vendor-extension shape this axis already uses for `x-loam-remove`, written by `loam rebase` and
1337
1602
  stripped on merge exactly like `Based-On:`. The digest is taken over the operation's *canonical*
1338
1603
  form (object keys sorted), which is the same comparison `isDeepStrictEqual` makes when the merge
1339
1604
  decides an operation "differs" — so a pin goes stale exactly when the merge would say something
1340
- moved, never over a reordered key. The pin is always the digest of the **living** operation, and
1605
+ moved, never over a reordered key. The pin is keyed by the slot, method and path, on both sides,
1606
+ so an operation written with no `operationId` — living or in the delta — is pinned, graded and
1607
+ quoted exactly as a named one: `loam rebase` always pinned against whatever stood at the slot,
1608
+ and the gate once read named operations alone, so it refused the pin rebase had just written as
1609
+ one over "no operation at that slot" and never asked an unnamed quote for a pin. The pin is
1610
+ always the digest of the **living** operation, and
1341
1611
  that one rule yields both merge verdicts by itself: an operation the author only quoted is
1342
1612
  byte-equal to living, so its pin equals its own content and **the merge skips it entirely**
1343
1613
  (reported as `· quotes …` in the plan, so the plan never silently writes less than the delta
@@ -1347,7 +1617,8 @@ the source for tests.
1347
1617
  decision, reverting an operation nobody edited is a bug. `openapi.baseline-missing` is a warning
1348
1618
  that gates the archive, exactly like its requirement-axis sibling — every unpinned restatement is
1349
1619
  a candidate rollback — and it is reported **once per service** rather than per operation: the fix
1350
- is one command, and twenty-nine identical findings teach people to filter the code out. **Covered
1620
+ is one command, and the twenty-nine identical findings a contract pasted whole from a
1621
+ thirty-operation service would raise teach people to filter the code out. **Covered
1351
1622
  since the path-item/component baselines landed:** the same discipline now reaches the two surfaces
1352
1623
  the operation pin cannot — path-level keys (`parameters`, `servers`, `summary`, `x-*`) and every
1353
1624
  `components/<kind>/<name>` — through a single feature-only root record **`x-loam-baselines`** (`{
@@ -1362,6 +1633,93 @@ the source for tests.
1362
1633
  component copying is verdict-driven, with genuinely new components copied only when reachable from
1363
1634
  content actually written. Removal markers still carry no pin, since
1364
1635
  `openapi.remove-target-mismatch` already checks that exact slot.
1636
+ - **A retired operation does not come back by accident.** An operation the living contract does
1637
+ not hold is either new or *retired*, and the living contract alone cannot say which: both are
1638
+ simply absent. So the feature gate also reads what every **archived** feature took out — the
1639
+ `x-loam-remove: true` markers left in `features/archive/<FEAT>/specs/<svc>/openapi.yaml` and
1640
+ `asyncapi.yaml`, through the same relocation rule the gate applies to an in-flight delta (a
1641
+ marker on an id the same delta redefines elsewhere moved it, and retired nothing). A delta entry
1642
+ that is not itself a removal, that its service's living contract does not hold, and that an
1643
+ archived feature retired is **`openapi.op-revived`** (by operationId — a
1644
+ retired operation quoted at a new path is the same operation back) or **`asyncapi.slot-revived`**
1645
+ (a `channels` or `operations` slot by its key; a message by its *name*, wherever it is declared).
1646
+ A relocation retires nothing, but it VACATES an address — and so does a retirement, once a later
1647
+ feature re-adds the id somewhere else — so the index records, per id, every address an archived
1648
+ feature's executed marker vacated, which feature, and for a move where its delta put the id. The
1649
+ finding is judged on the contract the archive would WRITE: the check runs the archive's own
1650
+ merge of the delta over the living contract and compares where each operationId stands there with
1651
+ where it stands now. An (operationId, address) the merged contract has and the living one lacks
1652
+ is `openapi.op-revived` when the living contract holds the id nowhere and an archived feature
1653
+ retired it (anywhere — by identity) or moved it away from that very address, or when the merged
1654
+ contract answers the id at two or more addresses and an archived feature retired it at, or moved
1655
+ it away from, that very address: archived, it would answer the id at both. An id that the living
1656
+ contract still holds and the merged contract answers at exactly one address is never a revival —
1657
+ a delta moving it back with its own removal marker where it lives now, or swapping another
1658
+ operation in there (named or not: the merge writes per method and path), leaves it at one
1659
+ address — while a marker standing where the id does not live deletes nothing, and excuses
1660
+ nothing. One address is no exemption for an id the living contract holds nowhere: that is the
1661
+ first case, so a quote of an address a move vacated is the finding even after a hand edit took
1662
+ the id out of where the move put it. Reading the merged contract replaced a
1663
+ case-by-case re-derivation of the merge that each review found a new hole in.
1664
+ On the event axis the same rule runs on the message name, over the event merge: the addresses a
1665
+ feature vacates are `components.messages` keys (a marker there) and the inline declarations a
1666
+ channel removal took. A delta that restates the channel an inline declaration stands in (with
1667
+ the message as a `$ref`) or retires that channel takes the name out, since a channel upsert
1668
+ replaces the channel's interior; a marker nested on the inline message does not
1669
+ (`asyncapi.remove-marker-inline`), so the finding names the channel as the remedy. A channel or
1670
+ operation slot's identity is its key, so moving one is a retirement plus an addition, graded as
1671
+ such. A message declared inline under a channel the delta brings back is that channel's
1672
+ interior, and the channel's finding stands for it — unless the merged contract declares the
1673
+ name somewhere else too (an archived feature re-declared it under `components` when it retired
1674
+ the channel): then the inline declaration is a second place of its own, and gets its own
1675
+ finding, since the channel's says nothing of the duplicate `--approve` would archive.
1676
+ Both are warnings that **gate** the archive, `openapi.baseline-missing`'s doctrine: the document
1677
+ is legal, and re-adding a retired operation can be exactly what a feature means — a retirement
1678
+ rolled back — so `--approve` carries the call, and the archive output then names what came back
1679
+ and whose retirement it reverses. The message offers both remedies: delete the quote if it only
1680
+ restated the operation; archive with `--approve` if the feature brings it back on purpose (a pin
1681
+ over nothing is `openapi.baseline-invalid`, so a deliberate re-add drops it first). At a vacated
1682
+ address beside a living one it names the address to drop instead: delete the quote, or, to move
1683
+ the id back on purpose, take it out of where it lives now in the same delta (a removal marker
1684
+ there; for a message declared inline, restate or retire its channel; for a `components.messages`
1685
+ key, also restate every channel or operation of the merged contract still `$ref`-ing it, with
1686
+ the `$ref` pointed at the quoted key — the message names them, since a marker alone would leave
1687
+ them pointing at nothing and their send would stop sending the message) — and where the delta
1688
+ already spells that address, so no marker can stand there, archive with `--approve`. A slot
1689
+ holds one entry, so where the living contract has ANOTHER operation (or message) at the address
1690
+ the delta spells the id at, the merge replaces it there: the message names it ("now holds
1691
+ 'requestCancellation', and archiving this delta replaces it there"), and the move-back remedy
1692
+ says to define it at another address (another key, for a message) in the same delta if it is
1693
+ to stay — never an address
1694
+ "the living contract no longer has", a remedy that once led to a clean gate and an archive that
1695
+ deleted the other entry unnamed. `loam rebase`
1696
+ never pins such an entry as new: it prints the same finding (`revived[]` in `--json`) and leaves
1697
+ the decision to the author. It asks the question of the delta as it will WRITE it, pins
1698
+ included: a quote pinned to its own content is skipped by the merge, and the same quote
1699
+ repinned to whatever now lives at its address is an edit the merge writes — so a revival
1700
+ rebase's own repin creates is in that list, `--dry-run` included, and the next validate agrees. A channel-slot removal retires the messages its living channel
1701
+ declared inline, which the delta never spells, so those names are read from the archived
1702
+ feature's undo snapshot (`.loam-before/`, the pre-image of the contract the merge ran against) —
1703
+ only while that pre-image still hashes to what the manifest recorded. A marker is a request, not a
1704
+ record of what happened: the OpenAPI merge deletes a living operation only when the one at the
1705
+ marker's own method and path carries the marker's `operationId`, and the event merge deletes a
1706
+ `channels` or `operations` slot only when the living contract has that key, and a
1707
+ `components.messages` slot only when the message declared there carries the marker's name. A
1708
+ marker that missed — archived past `remove-target-missing` or `remove-target-mismatch` under
1709
+ `--approve` — deleted nothing, so each marker counts only if the same pre-image held its target
1710
+ (a contract the archive created, or never wrote, lost nothing to it); a feature with no intact
1711
+ snapshot cannot be checked, and its markers count as written. Two marker
1712
+ shapes are never sources, because neither can match anything: a marker with no `operationId`
1713
+ (`openapi.remove-marker-anonymous`) and one written at path level
1714
+ (`openapi.remove-marker-path-level`) — a feature archived past either under `--approve` left the
1715
+ operation living.
1716
+ **The honest limit:** the check knows only what the files under `features/archive/` record. A
1717
+ removal made by hand-editing a living contract leaves no marker anywhere, and an archived feature
1718
+ deleted from disk takes its markers with it; both read as "never retired", and a quote of such an
1719
+ operation grades as an addition exactly as it did before the check existed. A channel's inline
1720
+ messages drop out the same way when its feature carries no intact pre-image (archived before the
1721
+ snapshot existed, or the snapshot edited or removed since) — the channel slot itself is still
1722
+ caught. No git history is read: the archive directory is the record.
1365
1723
 
1366
1724
  A requirement may carry a stable identity in its body: `Requirement-ID: <id>`, where the ID matches
1367
1725
  `[A-Za-z][A-Za-z0-9._-]{0,127}`. It is optional for backward compatibility. When present in a delta,
@@ -1414,7 +1772,10 @@ scenarios exercise. Comma-separated, same keep-last-line quirk (and the same rep
1414
1772
 
1415
1773
  An entry that resolves to nothing is `covers.unknown` (**warning**, with close ids offered where
1416
1774
  they exist): the typo guard, because a mistyped entry silently costs exactly the coverage it was
1417
- written for.
1775
+ written for. It is suspended — every form at once — for a service whose `model.likec4` could not be
1776
+ read, whether the model itself does not parse (`c4.invalid`) or it extends a map that does not
1777
+ (`spine.landscape-invalid`): an entry naming a container of an unread model is not a typo, and the
1778
+ one repair brings the whole grade back on the next run.
1418
1779
 
1419
1780
  **Coverage obligations** — both warnings, deliberately: they never gate `archive`, and `--strict` is
1420
1781
  the CI escalation.
@@ -1440,9 +1801,9 @@ the CI escalation.
1440
1801
  match, with a did-you-mean hint. Deliberately the service model and never the landscape read on
1441
1802
  its own: the
1442
1803
  landscape carries only what crosses the service boundary, the model carries everything the service
1443
- touches (a private store nested inside the service's own element — `landscape.datastore-private`
1444
- is the check pushing it there, and `landscape.datastore-shared` is what a second consumer of that
1445
- nested store now earns), and that is the set the on-call file must agree with. For an extending model
1804
+ touches (a private store nested inside the service's own element — one of the two placements
1805
+ "Private stores" above leaves to the team, and `landscape.datastore-shared` is what a second
1806
+ consumer of that nested store earns), and that is the set the on-call file must agree with. For an extending model
1446
1807
  the set is its own SLICE: the elements it adds, plus the partners its own edges point at and their
1447
1808
  ancestors — so a dependency named `kafka` resolves through an edge that names `kafka.orderEvents`,
1448
1809
  and a peer service resolves through the `metadata { service }` binding on the map's element the
@@ -1530,12 +1891,15 @@ digest:
1530
1891
  - stamps: line 1 is `# generated by loam v<version> — …` (the AGENTS.md stamp pattern — the file is
1531
1892
  never to be hand-edited), and each scenario carries the tag `@loam-digest-<16hex>` on the line
1532
1893
  above its `Scenario:` keyword: the first 16 hex characters of `loam verify`'s scenario body hash —
1533
- sha256 of the body lines joined and edge-trimmed, salted with the file name on the arch axis, the
1534
- exact recipe `scenario.tested` claim ids fold in — so the stamp, the claim and the spec can never
1535
- disagree about what a scenario says, and identically-worded scenarios across `spec.md` and
1536
- `arch.spec.md` can never share a digest (a business-axis test run cannot answer for an arch
1537
- scenario). A tag rather than a comment because cucumber's JSON report carries tags per scenario
1538
- (`elements[].tags`): the stamp rides through the runner untouched, which is what
1894
+ sha256 of the body lines joined and edge-trimmed, salted with the owning SERVICE and, on the arch
1895
+ axis, with that axis's file name — the exact recipe `scenario.tested` claim ids fold in — so the
1896
+ stamp, the claim and the spec can never disagree about what a scenario says. Both salts are there
1897
+ for one reason: a digest that spanned two namespaces would let a green run answer a question
1898
+ nobody ran a test for. Identically-worded scenarios across `spec.md` and `arch.spec.md` can never
1899
+ share a digest (a business-axis test run cannot answer for an arch scenario), and neither can two
1900
+ services wording a scenario the same way (one repository's green run cannot answer another
1901
+ repository's claim). A tag rather than a comment because cucumber's JSON report carries tags per
1902
+ scenario (`elements[].tags`): the stamp rides through the runner untouched, which is what
1539
1903
  `loam verify --results` matches on.
1540
1904
 
1541
1905
  **Staleness** (`loam validate --service <id>`, service-repo-scoped like the `sources.*` chain):
@@ -1608,6 +1972,12 @@ requirement also needs a current `Based-On:` pin before archive; `loam rebase <F
1608
1972
  Omitting or leaving the intent as scaffold text raises the gating `intent.empty` /
1609
1973
  `scaffold.placeholder` warning, so the old one-file shape is not archive-ready.
1610
1974
 
1975
+ Below that floor is no feature at all. A change that touches no requirement, no operation and
1976
+ no fleet edge — a refactor, a dependency bump, a fix that brings the code back to behaviour the
1977
+ living spec already documents — leaves the docs repo untouched: it is a code PR, then
1978
+ `loam validate --service <id>` in the service repo, where `sources.stale` names the moved paths, and
1979
+ the re-vouch they owe (`loam vouch --pack` is the reading list; the vouch is a person's).
1980
+
1611
1981
  Each axis may be legitimately empty, and the checks grade absence differently from
1612
1982
  emptiness-by-accident:
1613
1983
 
@@ -1618,7 +1988,8 @@ emptiness-by-accident:
1618
1988
  declared-but-untagged is almost always a forgotten tag, while declaring nothing is a legible
1619
1989
  statement that the architecture is unchanged.
1620
1990
  - **openapi delta** — absent whenever the feature adds no operations; a requirement change that
1621
- stays inside the existing contract has no API axis to speak on.
1991
+ stays inside the existing contract has no API axis to speak on. Present, it holds only the
1992
+ operations the feature adds, changes or removes.
1622
1993
  - **requirement delta** — the axis that is nearly always present, because a feature that changes no
1623
1994
  requirement changes no promised behaviour. The exception is architecture-only work: a new service
1624
1995
  in the C4 delta with no `specs/<svc>/spec.md` gets `service.no-requirement-delta` as a warning,
@@ -1631,6 +2002,16 @@ a feature cannot fold into the living docs with its reason unwritten. Its siblin
1631
2002
  `scaffold.placeholder` refuses the rest of an unedited `loam new` scaffold the same way — the exact
1632
2003
  template strings, a requirement or description nobody authored, must not become living truth.
1633
2004
 
2005
+ The scaffolded `intent.md` also carries an optional `## Open questions` heading over a comment.
2006
+ It is where `loam-feature`'s analyze step — between authoring and `loam rebase` — writes what the
2007
+ documents leave open: a SHALL two engineers could build two ways, a value or window two services
2008
+ state differently and which side refuses, a `Realizes:` that does not deliver its capability, an
2009
+ edge with no stated timeout, retry, partial-failure or compensation answer, a neighbour left out of
2010
+ `--touches`. A person answers by editing the file. Nothing grades the section: `intent.empty` reads
2011
+ past it, so questions written there are never mistaken for the Why, and they never gate the archive —
2012
+ every check loam has is a join between exact tokens, and whether two services MEAN the same thing by
2013
+ one is the part no join reads.
2014
+
1634
2015
  ### Where a capability lives (a declared name, and the document behind it)
1635
2016
 
1636
2017
  A business capability — "payment splitting" — is spread across the living specs of every service
@@ -1797,13 +2178,19 @@ refusing it, which is why the flattening is a whitelist and not a list of substi
1797
2178
  document declares no requirements yet, it declares none flattening to this slug (close ids
1798
2179
  offered, each already spelled as the tag to write), or two of its ids flatten to one slug.
1799
2180
 
1800
- **Either reserved prefix opts a view in.** A view carrying `#req-` and no `#cap-` is an author who
2181
+ **Any reserved prefix opts a view in.** A view carrying `#req-` and no `#cap-` is an author who
1801
2182
  asked to be graded and forgot half the claim; it earns the `unscoped` message rather than silence.
1802
- That opt-in is **one predicate**, and every reader takes it: `loam validate`, `loam diff`,
1803
- `loam delta`, `loam status`, the context and explore packs and `loam list capabilities` all decide
1804
- "is this a use case" the same way, so a flow one of them can see is a flow all of them can see. The
1805
- byte scan that lets a fleet with no use cases skip the LikeC4 load looks for both prefixes for the
1806
- same reason — a gate narrower than the predicate is silence, not a smaller answer.
2183
+ A view carrying only `#suite-<name>` is a use case too (see the layout section above). That opt-in
2184
+ is **one predicate**, and every reader takes it: `loam validate`, `loam diff`, `loam delta`,
2185
+ `loam status`, the context and explore packs, `loam list capabilities` and `loam list suites` all
2186
+ decide "is this a use case" the same way, so a flow one of them can see is a flow all of them can
2187
+ see. The byte scan that lets a fleet with no use cases skip the LikeC4 load looks for every prefix
2188
+ — `cap-`, `req-` and `suite-` — for the same reason: a gate narrower than the predicate is silence,
2189
+ not a smaller answer. It looks for each written as a tag REFERENCE: a `#`, then only what LikeC4
2190
+ skips between the `#` and a tag's name (whitespace, line breaks, comments), then the prefix — the
2191
+ only way a view carries a tag. A prefix in prose, a title or a `tag cap-…` declaration does not open
2192
+ it, so a hand-drawn `.likec4` beside a service's model that names `suite-` in its title, carries no
2193
+ tagged view and does not parse is not loaded and earns no `usecase.flow-invalid`.
1807
2194
 
1808
2195
  Being SEEN is not the same as claiming something, and the difference is worth stating because it
1809
2196
  looks like a gap and is not. A view with no resolved `#cap-` tag claims no capability, so it seeds
@@ -1988,7 +2375,10 @@ ready. Several useful product shapes remain outside that boundary on purpose:
1988
2375
  a third thing to keep truthful.
1989
2376
  - **Authored `tasks.md` as authoritative state** — loam derives its actionable checklist (`loam
1990
2377
  delta`, `loam verify`), so it cannot drift from the delta. Imported task lists are preserved as
1991
- non-authoritative migration evidence until a human chooses their disposition.
2378
+ non-authoritative migration evidence until a human chooses their disposition. The refusal stands,
2379
+ and the derived projection now exists as `loam tasks`, which answers the same question the file
2380
+ would have — recomputed from the delta, the generated suite and the verification record on every
2381
+ run, so there is nothing to tick and nothing to go stale.
1992
2382
  - **Per-tool workflow forks** — `AGENTS.md` is the tool-agnostic contract. Generated adapters wrap
1993
2383
  shared bodies and are never independent process definitions.
1994
2384
 
@@ -2079,6 +2469,19 @@ which carry them across loam upgrades. Correspondingly, `archive` refuses to rep
2079
2469
  version-3 claim graded at its service's current directory: the previous archive's writes are still
2080
2470
  in the docs and those files are the only way back.
2081
2471
 
2472
+ The manifest also carries one key that is not about bytes, added without a version bump because
2473
+ nothing that restores reads it: **`checklist`**, the digest of the checklist a fresh `loam verify`
2474
+ derived for the feature just before the merge — written when the feature carried a readable
2475
+ verification record then, absent otherwise and in every manifest an older loam wrote. It is the
2476
+ question the feature asked as it shipped. Once archived, a record is frozen history and the checklist
2477
+ cannot be re-derived (the merge moved the feature's operations into the living contract, so a
2478
+ derivation after it can only mismatch), so this is the one thing an archived record can still be
2479
+ held to: `loam gate --since` grades the record against it, and a record that answered an older
2480
+ checklist reads `stale`. `unarchive` takes the snapshot away with the rest of it, so a feature
2481
+ archived again gets a fresh capture. `list`, `status` and `verify` keep reading an archived record as
2482
+ frozen history; a manifest with no `checklist` leaves the gate reading it that way too, and saying
2483
+ so.
2484
+
2082
2485
  Some checks run at plan time rather than in the gate, because only the computed merge can see them.
2083
2486
  A **living** requirement sitting outside `## Requirements` refuses the whole archive
2084
2487
  (`living.requirement-outside-requirements`, error): the merge rewrites only that section, so the
@@ -2086,7 +2489,9 @@ requirement would land in the file twice — and `--approve` does not override i
2086
2489
  duplication is mechanical, not a judgment call; re-home the requirement first. A delta that
2087
2490
  redefines a path+method the living OpenAPI already has (`openapi.op-modified`, warning) merges
2088
2491
  anyway, but the plan says so — the overwrite is wholesale, and the living definition it replaces
2089
- deserved an eye. An unparseable `delta.likec4` fails the archive at plan time regardless of
2492
+ deserved an eye. One that defines a different operationId there, leaving a consumed operation
2493
+ defined nowhere, never reaches the plan without `--approve`: the gate refuses it first
2494
+ (`openapi.remove-op-consumed`, the overwrite shape). An unparseable `delta.likec4` fails the archive at plan time regardless of
2090
2495
  `--approve`: the alternative was archiving with the architecture axis silently dropped, which is the
2091
2496
  data loss this tool exists to prevent.
2092
2497
 
@@ -2094,7 +2499,8 @@ The landscape merge **splices authored source, it does not re-serialize**. Each
2094
2499
  relationship is copied into the living landscape byte-for-byte from `delta.likec4` — technology,
2095
2500
  style, icons, links, metadata, nested children, every tag — with exactly one edit: the feature's own
2096
2501
  tag is stripped (the additions are baseline now — the drop above is why unarchive restores bytes),
2097
- and a construct the strip empties goes with it (`x = kind 'y' { #FEAT-1 }` lands as `x = kind 'y'`).
2502
+ together with the `,` or `;` that set it apart in a tag list (`#FEAT-1, #x` lands as `#x`), and a
2503
+ construct the strip empties goes with it (`x = kind 'y' { #FEAT-1 }` lands as `x = kind 'y'`).
2098
2504
  Placement is deterministic and **service-grouped**, and never moves an authored byte — it only
2099
2505
  chooses where the new blocks go. A top-level element whose service the model already draws (the
2100
2506
  `metadata { service }` binding first, the title as fallback) lands immediately after the last
@@ -2112,22 +2518,32 @@ services) yields the byte-identical landscape to B then A, so concurrent archive
2112
2518
  different lines and git merges them without help. The insertions carry no marker comment — which
2113
2519
  archive added what is the git history's to say. A **nested** element lands inside its parent's block
2114
2520
  — inside the living parent when the landscape already has it, riding verbatim inside the spliced
2115
- parent when the parent is new — never as a flat dotted id, which LikeC4 rejects at top level. LikeC4
2521
+ parent when the parent is new — never as a flat dotted id, which LikeC4 rejects at top level. A
2522
+ landscape may be written in **several top-level `model { }` blocks**, which LikeC4 reads as one
2523
+ model, and the merge reads all of them: an ANCHORED addition — nested in a parent, joining its
2524
+ service's neighborhood, or joining its source's edges — lands in its anchor's block, and an
2525
+ unanchored one lands in the trailing region of the **first** block, the one every addition went to
2526
+ before a second block was readable. The walks never cross a block boundary, so the scheme stays
2527
+ order-independent, and a one-block map places exactly as before. LikeC4
2116
2528
  reference resolution is not order-dependent, so a relationship placed before an element it names
2117
2529
  still parses — and because splicing is text surgery, the computed landscape is **parsed in memory
2118
2530
  before anything is written**: a result LikeC4 rejects — a splice bug, or legal inputs the living
2119
2531
  document cannot absorb, like an element kind or tag its `specification` block never declares —
2120
2532
  refuses the archive at plan time (`merge-failed`, nothing written), the same discipline as the
2121
- unparseable delta. A living declaration sharing its line with another statement (legal LikeC4 the
2122
- splice map cannot see around — its statement scan runs to the end of the line) refuses the same way:
2123
- give each top-level declaration its own line, then re-run.
2533
+ unparseable delta. A living declaration the splice map cannot locate refuses the same way rather
2534
+ than be spliced around blind — most often one sharing its line with another statement, which is
2535
+ legal LikeC4 the splice map cannot see around, since it reads one statement per line. The refusal
2536
+ names the line LikeC4 parsed the declaration on and what the splice map read there instead, and
2537
+ says "shares that line" only when the line shows it: give each top-level declaration its own line,
2538
+ then re-run.
2124
2539
 
2125
2540
  **Which document an addition lands in.** The C4 merge asks it per declaration, not per feature. An
2126
2541
  element whose PARENT resolves to a service whose `model.likec4` extends the map is spliced into that
2127
2542
  model — and so is the interior a delta writes inside a service element the same archive introduces,
2128
2543
  since those containers carry no feature tag and would otherwise ride onto the map inside their
2129
2544
  parent's bytes. The parent chain has to be spelled in the map's own ids for either to happen. The
2130
- splice goes inside the `extend <fqn> { … }` block for the parent — appended when the model has none, as
2545
+ splice goes inside the `extend <fqn> { … }` block for the parent — in whichever `model { }` block
2546
+ holds it, and appended to the first one when the model has none, as
2131
2547
  the `model { }` block itself is appended to a model that has none (an empty file, a `views { }`-only
2132
2548
  file and a tags-only `specification { }` are all extending models, and all three are legal to
2133
2549
  archive into). A
@@ -2230,7 +2646,9 @@ design. The heading must be reachable, too — a leading UTF-8 BOM used to hide
2230
2646
 
2231
2647
  ## The verification record
2232
2648
 
2233
- `features/<FEAT>/verification.yaml` is the done-check written down. `loam verify <FEAT>` derives a
2649
+ The done-check is written down inside the feature: one `features/<FEAT>/verification/<svc>.yaml` per
2650
+ attesting service, or — for the all-at-once form, and every record written before the directory —
2651
+ the one file `features/<FEAT>/verification.yaml`. `loam verify <FEAT>` derives a
2234
2652
  **checklist** from the feature's own artifacts — the same files `validate` reads, never the code:
2235
2653
 
2236
2654
  | kind | one claim per | reads |
@@ -2254,18 +2672,26 @@ nobody wrote must not carry over as if it still applied.
2254
2672
  anything that does not answer the *current* checklist, each under its own `--json` `error.code`:
2255
2673
  `answers-unreadable` (not JSON, or a verdict outside `confirmed` / `unconfirmed`),
2256
2674
  `answers-mismatch` (an id nobody asked about, a claim with no answer, or one answered twice),
2257
- `answers-unevidenced` (a `confirmed` with no `file:line` behind it). An unchecked claim must never
2258
- be able to masquerade as checked.
2675
+ `answers-unevidenced` (a `confirmed` with no `file:line` behind it, or one citing a file or a line
2676
+ that does not exist in the repository the record is written from — the all-at-once form included,
2677
+ which binds to no commit but no longer takes a citation on trust). The all-at-once form checks what
2678
+ is shaped like a citation and only that: an evidence entry that is one token ending in `:<digits>`
2679
+ and is not a URL must name a regular file inside the repository with that line, while prose ("manual
2680
+ QA by Alice"), a URL or a path with no line is recorded as it always was. The federated form keeps
2681
+ its stricter contract — every agent entry a `file:line` bound to the attested commit. An unchecked
2682
+ claim must never be able to masquerade as checked.
2259
2683
 
2260
2684
  **`--results <report.json>` answers the `scenario.tested` claims mechanically, from a cucumber JSON
2261
- test report** — the format `cucumber-js --format json` emits, and cucumber-jvm, behave and SpecFlow
2262
- speak too. The contract is exactly this and nothing more: a top-level array of features, each
2685
+ test report** — the format `cucumber-js --format json` emits, and cucumber-jvm, behave,
2686
+ SpecFlow/Reqnroll, pytest-bdd (`--cucumberjson`) and godog's cucumber formatter speak too. The contract is exactly this and nothing more: a top-level array of features, each
2263
2687
  `elements[]` (scenarios) with `name`, `tags[] {name}` and `steps[] {result {status}}` — plus the two
2264
2688
  places those dialects put a failure the steps never see: cucumber-jvm's per-element
2265
2689
  `before[]`/`after[]` hook results and behave's element-level `status`. Every other field is ignored,
2266
2690
  tagless elements (backgrounds, hand-written scenarios) are invisible, and a file that is not that
2267
2691
  array at all refuses under `answers-unreadable` rather than quietly answering every claim "not
2268
- found".
2692
+ found". A file that is not JSON at all — a JUnit XML report is the usual one — refuses the same way,
2693
+ and the refusal names both shapes `--results` reads, because the second one exists for exactly that
2694
+ runner.
2269
2695
 
2270
2696
  Matching is by digest and nothing else: a claim is answered by the report scenarios carrying its
2271
2697
  `@loam-digest-<16hex>` tag — the tag `loam gherkin` stamped, the same 16 hex of the same body hash
@@ -2274,12 +2700,21 @@ name), so a business run can never answer an arch claim however identically the
2274
2700
  worded. Names never match anything: a reworded spec scenario matches nothing until the suite is
2275
2701
  regenerated and re-run — an agent must not be able to SAY a scenario is tested; only a green run
2276
2702
  may. The verdicts: every matching occurrence (a digest the report holds twice is a re-run, and all
2277
- occurrences count) ran at least one step, every step `passed`, every before/after hook `passed`, and
2278
- the element-level status (when the dialect carries one) is `passed` → `confirmed`, with evidence
2703
+ occurrences count) ran at least one step, every step `passed`, every before/after hook `passed`, the
2704
+ element-level status (when the dialect carries one) is `passed`, and **it ran exactly as many steps
2705
+ as the stamped scenario has** — the count `loam gherkin` emits for it, `And`/`But` included →
2706
+ `confirmed`, with evidence
2279
2707
  `<report-path>: <feature uri or name> › <scenario name>` per occurrence; a failed / undefined /
2280
2708
  pending / ambiguous step (`failed at step N`), a failing hook (`failed in a before/after hook`), a
2281
2709
  failed element status, a skipped-only run (`skipped`), or no match at all (`not found in report`) →
2282
- `unconfirmed` with the reason as the note.
2710
+ `unconfirmed` with the reason as the note — and a run whose step count differs from the stamped
2711
+ scenario's is `unconfirmed` too (`ran N step(s) where the stamped scenario has M`). The digest is
2712
+ only a tag, and a tag is text: a one-step stub carrying the right digest used to confirm five-step
2713
+ scenarios, because nothing compared what ran with what was stamped. The count is what every dialect
2714
+ can be held to; the step TEXT is not compared, because an outline's steps reach the report with
2715
+ their placeholders filled in and dialects disagree about keyword spacing and localisation. The hook
2716
+ entries cucumber-js lists among `steps[]` (marked `hidden`, keyword `Before`/`After`) are read as
2717
+ hooks, never counted as steps — and a failing one still sinks the run.
2283
2718
 
2284
2719
  `--results` OWNS every `scenario.tested` claim **in the same run**: an answers-file entry for one is
2285
2720
  refused (`answers-mismatch` — the runner owns it), `--record` alongside must answer exactly the
@@ -2311,6 +2746,8 @@ own runner-neutral shape, chosen by its marker key:
2311
2746
  ```
2312
2747
 
2313
2748
  `digest` is the 16 lowercase hex of the `@loam-digest-…` tag `loam gherkin` stamped on the scenario.
2749
+ An entry carries no steps, so it is held to its `status` alone — the step-count check above is a
2750
+ property of the cucumber shape.
2314
2751
  `status` is `passed` or `failed` — nothing else. `test` is optional free text: the runner's own name
2315
2752
  for the run, used as the evidence string a reader follows. `loamScenarioReport: 1` is the format
2316
2753
  version this loam reads; any other value refuses rather than being partly understood.
@@ -2336,12 +2773,18 @@ carrying the marker is always graded as this shape, even when malformed: falling
2336
2773
  cucumber parser would tell an author their report is not a cucumber array, which is true, useless,
2337
2774
  and hides the real mistake.
2338
2775
 
2339
- **Two services wording a scenario identically share one digest**, and a single report then cannot
2340
- say whose suite ran it. Those claims are left `unconfirmed` with `verify.digest-contested` (warn)
2341
- naming both services and the affected claim ids, rather than confirming both from one run. Record
2342
- each service's claims from its own repository with `--service`, which narrows the checklist before
2343
- matching. Scenario digests are salted by *axis* (business versus arch) but **not** by service, so a
2344
- digest identifies scenario words; `--service` is what gives a report an owner.
2776
+ **Scenario digests are salted by both the owning service and the axis** (business versus arch), so
2777
+ two services wording a scenario identically do not share one: the hash folds in the service the
2778
+ claim is filed under. The service salt is what makes ordinary repeated wording CORRECT rather than
2779
+ merely refused — "the service returns 404 for an unknown id" is a sentence a fleet writes many
2780
+ times, and before the salt those scenarios shared a digest, so one repository's green run could
2781
+ appear to confirm claims from suites that never ran each other's tests, and the only defence was to
2782
+ decline to answer. `verify.digest-contested` (warn) survives as the guard on that invariant rather
2783
+ than as a diagnosis of ordinary authoring: if a digest is ever claimed by two services it is a
2784
+ truncated-hash collision, a single report still cannot say whose suite ran it, and those claims are
2785
+ left `unconfirmed` naming both services and the affected claim ids rather than confirmed from one
2786
+ run. Record each service's claims from its own repository with `--service`, which narrows the
2787
+ checklist before matching.
2345
2788
 
2346
2789
  ### Contract-test reports answer the `api.exposes` claims
2347
2790
 
@@ -2435,7 +2878,7 @@ service's as a pure lens — no repository binding, usable from the docs repo, r
2435
2878
  `unknown-service` when the checklist has no claims owned by that id — and both agents must have
2436
2879
  answered the same scope. Combining the flag with `--record`, `--results` or `--contract-results`
2437
2880
  refuses `invalid-option`, as does any arity other than exactly two files and an archived feature
2438
- (frozen history has no current checklist to answer). A `verification.yaml` too damaged to read does
2881
+ (frozen history has no current checklist to answer). A verification record too damaged to read does
2439
2882
  **not** block the diff — the lens never reads the record, and the moment a cross-examination is most
2440
2883
  needed is before re-recording over a broken one.
2441
2884
 
@@ -2455,7 +2898,7 @@ block pins each file's path and the sha256 of the exact bytes read.
2455
2898
  confirmed/unconfirmed/unanswered. The payload also deliberately carries **no `verified`, `verdict`
2456
2899
  or `attested` key at all**: two answer sets agreeing — usually two contexts of the same model, so
2457
2900
  correlated — ranks where a reviewer reads first; it is not verification, it upgrades nothing, and it
2458
- leaves `verification.yaml` byte-identical (no docs lock is taken, and the diff runs while a writer
2901
+ leaves the verification record byte-identical (no docs lock is taken, and the diff runs while a writer
2459
2902
  holds one).
2460
2903
 
2461
2904
  ### Attested is not verified
@@ -2490,28 +2933,79 @@ the decision:
2490
2933
  So "a green run is the only thing that confirms a scenario" is a claim about the **record**, not
2491
2934
  about the CLI's flags: the short path still works, and it is legible afterwards as the short path.
2492
2935
 
2493
- ### The record is federated: one file, one attestation per service
2936
+ ### The record is federated: one attestation per service, one file each
2494
2937
 
2495
2938
  A cross-service feature's claims belong to several services, and each service's code lives in its
2496
2939
  own repository — so no single place can honestly answer all of them.
2497
2940
  `loam verify <FEAT> --service <id> --record answers.json`, run **in that service's own repo**,
2498
2941
  narrows the checklist to that service's claims and binds the answers to this repository's commit.
2499
- The result is one `schema: 2` `verification.yaml` holding a list of **`attestations`**, one per
2500
- service: the service id, the commit the answers were bound to, and that service's answered claims.
2501
-
2502
- The merge semantics are additive and non-destructive. Recording for service B keeps service A's
2503
- attestation exactly as it stood — the new record is the old attestations minus B's previous one,
2504
- plus B's new one. Answers the new record does *not* carry are reported in `discarded`, tagged
2505
- `unattested` (a schema-1 answer, or one whose attestation is gone) or `off-checklist` (a claim the
2506
- current checklist no longer asks). Nothing is dropped silently.
2507
-
2508
- The write behind that merge holds the same advisory lock archive does, and holds it from before the
2509
- feature is resolved to after the record lands — two `--record` runs for different services serialize
2510
- (the later one waits briefly, then merges over what the first left, so **both** attestations
2511
- survive), and a record can no longer be written into a path an `archive` is renaming out from under
2512
- it. The commit itself is staged beside the target and swapped in by one rename over the exact bytes
2513
- the merge was computed from, so a kill mid-record leaves the previous record whole — never truncated
2514
- YAML — and an edit that landed in between refuses instead of being buried:
2942
+ The result is that service's **attestation**, written to a file of its own:
2943
+ `features/<FEAT>/verification/<id>.yaml` — a `schema: 2` record holding exactly that service's
2944
+ attestation (the service id, the commit the answers were bound to, the checklist digest it answered,
2945
+ and its answered claims) and nothing of anybody else's. **The directory is the record**, the rule
2946
+ `glossary/` and `capabilities/` already follow: loam reads every file in it and adds them up, and
2947
+ the verdict and the counts are computed from the union at read time — exactly as they always were,
2948
+ since nothing ever trusted a stored `summary`. Each file's own `summary` counts the whole checklist,
2949
+ with the claims other services own honestly `unanswered` in it.
2950
+
2951
+ **Why one file per service.** The record used to be ONE `verification.yaml` for every service, and
2952
+ every attestation rewrote its `summary`, its `recorded` date and its checklist-ordered `claims`. Two
2953
+ teams recording two different services on two docs-repo branches therefore always conflicted when
2954
+ the PRs merged — add/add from an empty start, content conflicts on the summary, the claims and the
2955
+ attestations once one had landed — and sorting the attestations did not help, because two
2956
+ insertions at one anchor are a conflict whatever the order. The advisory lock below serializes
2957
+ writers on one checkout only; a fleet records from many. With a file per service, two teams' records
2958
+ touch two different paths and merge cleanly. Recording a service again replaces that service's file
2959
+ and nothing else; another service's file is never rewritten, and its pins never touched.
2960
+
2961
+ The record answers **one version of the checklist**: the one the feature asks now — `loam verify`
2962
+ (read and record alike), `tasks`, and `status`, `list` and `gate` for an in-flight feature all
2963
+ derive it, and the files that answered it are the ones that count. Only where no reader knows the
2964
+ current version (the frozen view of an archived feature) or no file answered it does the newest
2965
+ file decide (by `recorded`, a later service id breaking a same-day tie). Newest-by-date used to
2966
+ decide always, and `recorded` is a date: a service that recorded the current checklist lost a
2967
+ same-day tie to a service with a later id that had recorded an older one, so the current answers
2968
+ were dropped and every reader called the record stale. A file that answered a different version
2969
+ keeps its attestation in the list — `verify.checklist-forked` names it, on the recording run's
2970
+ output as well as the read view — but its answers do not count until that service records again.
2971
+ The one shared file used to settle this by rewriting: the last writer pruned everybody's answers to
2972
+ its own checklist. Per-service files are never rewritten by anyone else, so nothing can prune them,
2973
+ and counting an answer filed against another question set would let an id the feature no longer
2974
+ asks fill the slot of the question that replaced it.
2975
+
2976
+ **`features/<FEAT>/verification/` is loam's.** Every non-dot `*.yaml` in it is read as one
2977
+ service's attestation file — the file name is the claim about whose word it holds — so a file there
2978
+ that is not one (a hand-made `k6.yaml`, say) makes the record `record-unreadable`, exactly like a
2979
+ damaged attestation. Keep anything else beside the feature, not inside that directory.
2980
+
2981
+ **The one-file record is read forever.** A `verification.yaml` written before the directory existed
2982
+ — a federated record with several attestations in it, active or archived — reads exactly as it
2983
+ always did while no service has a file of its own. Once services do, it is read beside them: a
2984
+ service that records again supersedes its share of the one-file record (that attestation and its
2985
+ answers drop out of the union; the file itself is not rewritten), and the write that leaves nothing
2986
+ in it counting removes it. An all-at-once record (no `schema`, no attestations) never counts once a
2987
+ service has a file — its answers carry no commit attestation — so the first `--service` write
2988
+ removes it in the same commit and lists its answers in `discarded`. Answers a write takes off the
2989
+ disk that its new file does not carry are reported in `discarded`, tagged `unattested` (an
2990
+ all-at-once answer) or `off-checklist` (a claim the current checklist no longer asks). Nothing is
2991
+ dropped silently.
2992
+
2993
+ **The all-at-once form keeps the one file, by decision.** `--record` without `--service` answers the
2994
+ whole checklist from one place on one word, with no commit and no service — so there is no second
2995
+ writer for it to collide with and no service to name a file after, and every record it ever wrote
2996
+ (the example fleet's included) stays byte-for-byte what it is. There is one reader either way:
2997
+ `loam verify`, `status`, `list`, `tasks`, `gate` and `validate` all read the record through the same
2998
+ union, whichever files it is made of.
2999
+
3000
+ The write holds the same advisory lock archive does, and holds it from before the feature is
3001
+ resolved to after the record lands — two `--record` runs for different services serialize (the
3002
+ later one waits briefly, then writes its own file, so **both** attestations survive), and a record
3003
+ can no longer be written into a path an `archive` is renaming out from under it. The commit itself
3004
+ is staged beside the target and swapped in by one rename over the exact bytes the read found, so a
3005
+ kill mid-record leaves the previous record whole — never truncated YAML — and an edit that landed in
3006
+ between refuses instead of being buried. A write that also removes a superseded one-file record
3007
+ holds that removal to the same compare, and lands the service's own file first, so a kill between
3008
+ the two leaves a record that still reads whole:
2515
3009
 
2516
3010
  - `docs-busy` — another writer held the lock longer than the record was willing to wait (5 s).
2517
3011
  Nothing was read or written; re-run once it finishes. The contention runs both ways: `archive`,
@@ -2532,9 +3026,10 @@ Recording refuses rather than guesses, each under its own `--json` `error.code`:
2532
3026
  - `repository-unavailable` — `--service` from a repo that declares none, or one whose commit cannot
2533
3027
  be read. An attestation with nothing to bind to is not an attestation.
2534
3028
  - **`record-federated`** — the legacy all-at-once form (`--record` with **no** `--service`) over a
2535
- record that already carries attestations. That form writes the whole file from one place, so it
2536
- would erase every other repository's evidence; loam refuses outright and names the attestors, and
2537
- never silently migrates or merges. Re-run with `--service`.
3029
+ record that already carries attestations, in per-service files or in a one-file record. That form
3030
+ writes the whole record from one place, so it would put one unattested answer set over every
3031
+ other repository's evidence; loam refuses outright and names the attestors, and never silently
3032
+ migrates or merges. Re-run with `--service`.
2538
3033
  - `record-unreadable` — see below.
2539
3034
 
2540
3035
  Reading is unrestricted: `loam verify <FEAT> --service <id>` **without** `--record` is a pure lens
@@ -2543,12 +3038,26 @@ in `services`, which is how you find out which repositories still owe an attesta
2543
3038
 
2544
3039
  The record is YAML, like the other data artifacts, and holds the claim text next to each verdict so
2545
3040
  it reads without loam. Because it has to survive without loam, human edits are expected — and read
2546
- back defensively. A file that exists but does not read as a record (broken YAML, or a shape missing
3041
+ back defensively. A record file that exists but does not read as a record (git conflict markers a
3042
+ merge left in it — named before the YAML parser sees them, since the parser either fails on a line
3043
+ that is not the problem or reads both sides as one plausible document; broken YAML; a shape missing
2547
3044
  what the readers use — `summary` with its counts, `recorded`, `checklist`, claims each with an `id`
2548
- and a known `verdict`) is **`record-unreadable`**: reported with the YAML line, and **never
3045
+ and a known `verdict`; or a `verification/<svc>.yaml` holding anything but that one service's
3046
+ attestation and answers) is **`record-unreadable`**: reported with the file and the line, and **never
2549
3047
  overwritten**. It used to count as *no record at all*, which was the wrong direction on the one
2550
3048
  artifact loam cannot reconstruct — "no record" invites writing a fresh one over evidence somebody's
2551
- edit merely broke. Only a genuinely absent file is "no verification record". Extra keys a human adds
3049
+ edit merely broke. Only a genuinely absent file is "no verification record".
3050
+
3051
+ The refusal names the ONE file that failed and the repair that is safe for it, because the two kinds
3052
+ of file call for opposite advice. A service's own `verification/<svc>.yaml` holds that service's
3053
+ word and nobody else's — after a merge, both sides of a conflict in it are that service's own
3054
+ attestations — so repairing it by hand, or deleting it and recording that service again from its
3055
+ repository, loses no other team's evidence. The one-file `verification.yaml` may hold every other
3056
+ service's attestation, so its advice is to repair it or **restore it from version control** (after a
3057
+ merge, the copy on the branch you merged into) and then record again — **never to delete it**. The
3058
+ old one-size advice, "delete it and record again", erased other teams' attestations when followed on
3059
+ a shared record, and the miscount advice, "record the answers again", sent the reader straight back
3060
+ into this refusal: `--record` will not write over a file it could not read. Both now end in a record. Extra keys a human adds
2552
3061
  still pass through untouched. Every verdict names who answered it — `answered_by: runner` (a
2553
3062
  cucumber report's green run, mechanical), `answered_by: external-runner` (a contract-test report's
2554
3063
  operationId-matched green entry, mechanical too), or `answered_by: agent` (somebody's word about the
@@ -2618,7 +3127,7 @@ next `--record` makes it drift-checkable), warn `evidence.unresolved` (the cited
2618
3127
  unsafe, not a regular file, or the cited line is past its end), `evidence.moved` (the file's digest
2619
3128
  differs while the cited line still reads as pinned), `evidence.line-changed` (the cited line no
2620
3129
  longer says what was recorded), `evidence.token-missing` (the file contained the token at the
2621
- attested commit and no longer does) and `evidence.record-unreadable` (a verification.yaml that
3130
+ attested commit and no longer does) and `evidence.record-unreadable` (a verification record file that
2622
3131
  exists but cannot be read — none of its evidence was checked, and a silent skip would read as clean,
2623
3132
  on the very file a hand edit to the pins most plausibly breaks). Archived records are deliberately
2624
3133
  not linted — frozen history, and post-ship code drift is normal evolution. From the docs repo the
@@ -2628,13 +3137,18 @@ verdict in either direction, `attested` never silently becomes `verified`, and t
2628
3137
  fail `validate`; `--strict` remains the CI lever it always was.
2629
3138
 
2630
3139
  It lives inside the feature, so `archive` carries it into `features/archive/<FEAT>/` with everything
2631
- else. `loam verify` re-run later compares `checklist` against the current digest and reports the
3140
+ else — the `verification/` directory moves with the feature it sits in, and `unarchive` brings it
3141
+ back the same way; the frozen view reads it through the same union. `loam verify` re-run later compares `checklist` against the current digest and reports the
2632
3142
  record as **stale** if the feature moved under it — while the feature is active. Once archived there
2633
3143
  is no current checklist to be stale against (the merge itself moved the feature's operations into
2634
3144
  the living OpenAPI, so a re-derived checklist could only disagree): `loam verify` on an archived
2635
3145
  feature renders the record verbatim as **frozen history** (`frozen: true` under `--json`, no
2636
3146
  staleness judgment), and `--record` refuses (`invalid-option` — there is no current checklist for
2637
- the answers to answer): the record is about the words that shipped, and it stays that way.
3147
+ the answers to answer): the record is about the words that shipped, and it stays that way. One
3148
+ reader holds it to something all the same: `loam gate --since <ref>` grades an archived record
3149
+ against the checklist digest `archive` captured as the feature merged (the snapshot manifest's
3150
+ `checklist`, above), because the deploy question is whether what shipped was what was checked — a
3151
+ record that had gone stale before the archive is `stale` there, never `verified` again.
2638
3152
 
2639
3153
  **Verification does not gate `archive`.** Coherence gates because loam computed it from the
2640
3154
  documents; a verdict is an agent's word about code loam never read, and a gate in front of shipping
@@ -2643,6 +3157,72 @@ comes later, and an `unconfirmed` claim with a note is worth more there than a `
2643
3157
  can back up. `loam verify` exits 0 either way; branch on `verified` in the `--json` payload, or on
2644
3158
  `verdict` when you need to tell `attested` from `unverified`.
2645
3159
 
3160
+ ### The same checklist read forwards (`loam tasks`)
3161
+
3162
+ `loam verify` answers *answered, and by whom*. `loam tasks <FEAT> [--service <id>] [--next]
3163
+ [--json]` answers the other half of the same question — *still open, at which step, and what fact
3164
+ closes it* — and it is the identical checklist read in the other direction. It is derived on every
3165
+ run from the feature's deltas, the generated suite and the verification record; it writes nothing,
3166
+ anywhere, ever, and it adds no artifact for a later run to disagree with.
3167
+
3168
+ **One unit per promise, and a unit's id is the claim's id.** Every claim the checklist derives is
3169
+ one work unit, under the same identity on both commands, so the thing to do and the answer that
3170
+ closes it can never be two different objects. One kind of unit has no claim behind it: an ADDED or
3171
+ MODIFIED requirement with no scenario under it asks the checklist nothing — there is no scenario to
3172
+ test — so `loam tasks` mints a unit of its own for it, of the kind requirement.described. That is
3173
+ the promise nobody has written acceptance criteria for yet, and it is where the loop starts.
3174
+
3175
+ **Where a unit stands** is its `at` field, and the four values are the steps of the loop this
3176
+ document already describes rather than a percentage: `describe` (the requirement carries no scenario
3177
+ at all, so there is nothing to generate a test from), `generate` (the scenario exists and this
3178
+ repository's generated suite does not carry its stamp yet), `implement` (everything else still
3179
+ open), `attested` (an agent's word confirmed the claim and no run has). It is spelled `at`
3180
+ deliberately, and not `stage` or `verdict`: both of those already mean something else on payloads an
3181
+ agent reads beside this one, and a third meaning for either word is how a consumer comes to branch
3182
+ on the wrong field.
3183
+
3184
+ **Done is a fact, not a tick.** A closed unit simply stops appearing, and exactly one thing closes
3185
+ one: a claim recorded `confirmed` in a verification record that is **not stale** against the current
3186
+ checklist. A stale record closes nothing — the words moved under the answers, which is the state
3187
+ `loam verify` already reports. An agent-attested scenario never vanishes either: it stays visible at
3188
+ `attested`, because attested is not verified, and a work list that hid the difference would be
3189
+ exactly the gate that teaches everyone the cheapest way past it is to say yes.
3190
+
3191
+ **The frontier** is the first ten open units in derived order, providers before consumers — ordered
3192
+ on the exact token join `c4.calls.token == api.exposes.operation`, never on a reading of prose.
3193
+ **The order is advice and never a lock**: nothing is assigned, nothing is reserved, and working out
3194
+ of order is legal and invisible to every other command.
3195
+
3196
+ **`--next` is the leaf**: the same packet with `units` narrowed to the frontier head, which is what
3197
+ an orchestrator hands a coding agent. Everything else — the counts, the frontier, the review
3198
+ notes — is left whole on purpose, so a cheap model handed one unit cannot read it as the whole job.
3199
+ The text view prints that leaf in full: the scenario's body verbatim and never reflowed (the bytes
3200
+ the digest was taken over), the digest itself — the same `@loam-digest-<16hex>` join key
3201
+ `loam gherkin` stamps and `loam verify --results` matches on — the `done` block whose `check` is the
3202
+ fact that closes the unit, and the literal follow-up command to run next, spelled out rather than
3203
+ left as a shape to fill in.
3204
+
3205
+ **`--service <id>` is a pure lens.** It narrows the units to one service's without binding a
3206
+ repository, and adds `waitingOn[]`: the open units in OTHER services that this service's units
3207
+ depend on. A team learns what it is blocked by from the same run that tells it what to do.
3208
+
3209
+ **What the payload says about what it could not see.** `stamps` is `read` or `unknown`, and
3210
+ `unknown` means the bound service repo's generated suite could not be read — could-not-look, never
3211
+ nothing-wrong, because a question loam was unable to ask must not read as one that came back clean.
3212
+ Beside it the payload carries `checklist` (the digest of the question set these units derive from),
3213
+ `record` (`absent`, `recorded`, `stale` or `unreadable`), `intent`, `services[]`,
3214
+ `size{requirements,scenarios,operations,messages,calls}` — the size of the DELTA and never of the
3215
+ open work, so that "this feature is small" and "this feature is nearly done" cannot become one
3216
+ sentence — `contracts{expose[],call[]}`, `counts{open,done,attested}` and `review[]`, the four
3217
+ instructions for re-deriving this answer and reviewing it, because a projection that says what to do
3218
+ must also say how to check it was read correctly. A broken `delta.likec4` or contract axis names
3219
+ what it could not read in `unreadable` and exits 1 with `ok: true` — `loam delta`'s own rule rather
3220
+ than a new one: a feeder nothing could read empties the packet, an empty packet reads as "nothing to
3221
+ do", so the envelope stays true while the exit code stops a pipeline building on it.
3222
+
3223
+ The command mints no finding or refusal code of its own, and `loam_tasks` serves the same projection
3224
+ read-only over MCP.
3225
+
2646
3226
  ## Operating at fleet scale
2647
3227
 
2648
3228
  One docs repo, a hundred services, many teams. The repo scales the way any shared codebase does —
@@ -2794,11 +3374,14 @@ repairing anything:
2794
3374
 
2795
3375
  And it reports the **agent surface**: which tool and profile the repo is wired for, how many command and skill
2796
3376
  files it holds, how many a newer loam would lay down (`doctor.agent-files-missing`), and how many
2797
- have fallen behind (`doctor.agent-files-stale`). The staleness check reads exactly one thing — the
2798
- `<!-- generated by loam vX.Y.Z -->` line every generated body opens with — and nothing else.
3377
+ have fallen behind (`doctor.agent-files-stale`). A file has fallen behind when BOTH hold: its
3378
+ bytes, the `<!-- generated by loam vX.Y.Z -->` line every generated body opens with set aside, are
3379
+ not what this binary writes; and that stamp is missing or older than this binary. A stamp-only
3380
+ difference is not staleness — the stamp moves in every file at every release, and a warning that
3381
+ fires on every upgrade hides the release where a pointer's body really changed. A customized body
3382
+ under a current stamp is not staleness either: the stamp is the record that somebody reviewed it.
2799
3383
  The digest manifest is what authorizes `init` to refresh an unchanged pointer; a different body is
2800
- treated as human-owned and never overwritten. A stamp is a separate claim and can be stale while
2801
- every edit around it is legitimate. Which deliveries a repo holds is asked of the files rather
3384
+ treated as human-owned and never overwritten. Which deliveries a repo holds is asked of the files rather
2802
3385
  than of a record: `loam.json` stores tools and profile, not deliveries, and a repo initialized
2803
3386
  `--no-skills` is not reported as missing the selected skills.
2804
3387
 
@@ -2901,6 +3484,7 @@ and publishes it wherever the fleet view reads:
2901
3484
  "errors": 0,
2902
3485
  "warnings": 2,
2903
3486
  "sources": { "current": 1, "stale": 0, "unvouched": 0, "absent": 0 },
3487
+ "contentStale": 0,
2904
3488
  "generatedAt": "2026-08-03T12:00:00Z"
2905
3489
  }
2906
3490
  ```
@@ -2919,10 +3503,16 @@ loam validate --service "$SVC" --json | jq '{
2919
3503
  unvouched: [.targets[0].findings[] | select(.code == "sources.unvouched")] | length,
2920
3504
  absent: [.targets[0].findings[] | select(.code == "sources.absent")] | length
2921
3505
  },
3506
+ contentStale: [.targets[0].findings[] | select(.code == "content.stale")] | length,
2922
3507
  generatedAt: (now | todate)
2923
3508
  }'
2924
3509
  ```
2925
3510
 
3511
+ `contentStale` counts the spec documents whose words moved after their vouch — the doc-side half of
3512
+ staleness, beside the code-side `sources.stale`. Unlike that half it is not owed to the service repo
3513
+ alone (the docs repo's own `validate --all` scorecard counts it too), but a summary that left it out
3514
+ would publish "0 stale" over a document nobody re-read.
3515
+
2926
3516
  `generatedAt` is the CI's clock, never loam's — loam output carries no timestamps, by design, so the
2927
3517
  same repo state always yields the same bytes. That is also what makes the timestamp the liveness
2928
3518
  signal: the rule is that every service repo's CI publishes this summary on every run, so a summary
@@ -2947,9 +3537,10 @@ arrived. Per-service internals now live in `services/<svc>/model.likec4` **by de
2947
3537
  where a second team adopting a service writes, so it adds nothing to this file. It is a default and
2948
3538
  not a prohibition: the map MAY hold a service's interior, and sometimes must. A store a second
2949
3539
  service will reach has to be declared where that consumer's project can see it — a per-service
2950
- project is `architecture/` plus one model — so it is nested inside the owner's element ON THE MAP,
2951
- which is exactly what "Private stores" above prescribes and what `landscape.datastore-private` and
2952
- `landscape.datastore-shared` offer as a placement. A delta spelling a parent in the legacy
3540
+ project is `architecture/` plus one model — so it is drawn ON THE MAP, nested inside the owner's
3541
+ element or beside the services, whichever the team prefers. Which of the two is nobody's business
3542
+ but the team's: "Private stores" above leaves the placement open and loam grades neither, so nothing
3543
+ here says a store must move. A delta spelling a parent in the legacy
2953
3544
  title-join lands its container on the map too. What an extending model owns is what it DECLARES,
2954
3545
  not everything below the service's element. `loam archive` routes rather than forbids — a delta's
2955
3546
  additions nested under a service whose model extends the map, with the whole parent chain spelled
@@ -2991,7 +3582,7 @@ says what its architecture means, and two generated models of the same code disa
2991
3582
  every run — a done-check built on diffing them would flap until somebody turned it off. What is
2992
3583
  deterministic is the **question**: which files, in which grammar, bound to which existing elements;
2993
3584
  which operations, which edges, which scenarios. loam owns the questions and the checking; an agent
2994
- owns the reading. `sources` and `verification.yaml` are where the answers are written down.
3585
+ owns the reading. `sources` and the verification record are where the answers are written down.
2995
3586
 
2996
3587
  ## Status
2997
3588