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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (901) hide show
  1. package/CHANGELOG.md +3448 -0
  2. package/COMPARISON.md +91 -3
  3. package/CONTRIBUTING.md +37 -0
  4. package/MIGRATING-from-OpenSpec.md +47 -23
  5. package/QUICKSTART.md +189 -12
  6. package/README.md +274 -65
  7. package/ROADMAP.md +572 -31
  8. package/SCHEMA.md +1709 -155
  9. package/WORKFLOW.md +298 -72
  10. package/dist/cli.js +26 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/adopt/adopt.js +5 -10
  13. package/dist/commands/adopt/adopt.js.map +1 -1
  14. package/dist/commands/adopt/render.js +50 -9
  15. package/dist/commands/adopt/render.js.map +1 -1
  16. package/dist/commands/archive/archive.js +16 -19
  17. package/dist/commands/archive/archive.js.map +1 -1
  18. package/dist/commands/archive/closing.js +42 -0
  19. package/dist/commands/archive/closing.js.map +1 -0
  20. package/dist/commands/archive/plan/contracts/asyncapi.js +36 -42
  21. package/dist/commands/archive/plan/contracts/asyncapi.js.map +1 -1
  22. package/dist/commands/archive/plan/contracts/consumers.js +56 -0
  23. package/dist/commands/archive/plan/contracts/consumers.js.map +1 -0
  24. package/dist/commands/archive/plan/contracts/openapi.js +77 -43
  25. package/dist/commands/archive/plan/contracts/openapi.js.map +1 -1
  26. package/dist/commands/archive/plan/contracts/verdict-lines.js +103 -0
  27. package/dist/commands/archive/plan/contracts/verdict-lines.js.map +1 -0
  28. package/dist/commands/archive/plan/document/head.js +151 -0
  29. package/dist/commands/archive/plan/document/head.js.map +1 -0
  30. package/dist/commands/archive/plan/document/links.js +284 -0
  31. package/dist/commands/archive/plan/document/links.js.map +1 -0
  32. package/dist/commands/archive/plan/document/run.js +136 -0
  33. package/dist/commands/archive/plan/document/run.js.map +1 -0
  34. package/dist/commands/archive/plan/gate.js +21 -19
  35. package/dist/commands/archive/plan/gate.js.map +1 -1
  36. package/dist/commands/archive/plan/landscape.js +167 -30
  37. package/dist/commands/archive/plan/landscape.js.map +1 -1
  38. package/dist/commands/archive/plan/model/extending.js +85 -0
  39. package/dist/commands/archive/plan/model/extending.js.map +1 -0
  40. package/dist/commands/archive/plan/refusal.js +14 -19
  41. package/dist/commands/archive/plan/refusal.js.map +1 -1
  42. package/dist/commands/archive/plan/specs.js +137 -56
  43. package/dist/commands/archive/plan/specs.js.map +1 -1
  44. package/dist/commands/archive/plan/state.js +3 -0
  45. package/dist/commands/archive/plan/state.js.map +1 -1
  46. package/dist/commands/archive/run.js +134 -30
  47. package/dist/commands/archive/run.js.map +1 -1
  48. package/dist/commands/archive/shipped.js +49 -0
  49. package/dist/commands/archive/shipped.js.map +1 -0
  50. package/dist/commands/context/context.js +85 -18
  51. package/dist/commands/context/context.js.map +1 -1
  52. package/dist/commands/context/print.js +29 -7
  53. package/dist/commands/context/print.js.map +1 -1
  54. package/dist/commands/delta/delta.js +102 -14
  55. package/dist/commands/delta/delta.js.map +1 -1
  56. package/dist/commands/delta/print.js +17 -1
  57. package/dist/commands/delta/print.js.map +1 -1
  58. package/dist/commands/dependencies.js +3 -7
  59. package/dist/commands/dependencies.js.map +1 -1
  60. package/dist/commands/diff/diff.js +20 -27
  61. package/dist/commands/diff/diff.js.map +1 -1
  62. package/dist/commands/doctor.js +60 -1
  63. package/dist/commands/doctor.js.map +1 -1
  64. package/dist/commands/explain/explain.js +89 -13
  65. package/dist/commands/explain/explain.js.map +1 -1
  66. package/dist/commands/explore.js +16 -10
  67. package/dist/commands/explore.js.map +1 -1
  68. package/dist/commands/gate/gate.js +45 -8
  69. package/dist/commands/gate/gate.js.map +1 -1
  70. package/dist/commands/gate/print.js +15 -3
  71. package/dist/commands/gate/print.js.map +1 -1
  72. package/dist/commands/gherkin/commit.js +13 -54
  73. package/dist/commands/gherkin/commit.js.map +1 -1
  74. package/dist/commands/gherkin/gherkin.js +36 -20
  75. package/dist/commands/gherkin/gherkin.js.map +1 -1
  76. package/dist/commands/gherkin/reconcile.js +10 -13
  77. package/dist/commands/gherkin/reconcile.js.map +1 -1
  78. package/dist/commands/init/first-hour.js +44 -0
  79. package/dist/commands/init/first-hour.js.map +1 -1
  80. package/dist/commands/init/init.js +18 -22
  81. package/dist/commands/init/init.js.map +1 -1
  82. package/dist/commands/list/campaign/campaign.js +4 -6
  83. package/dist/commands/list/campaign/campaign.js.map +1 -1
  84. package/dist/commands/list/json.js +5 -7
  85. package/dist/commands/list/json.js.map +1 -1
  86. package/dist/commands/list/list.js +44 -29
  87. package/dist/commands/list/list.js.map +1 -1
  88. package/dist/commands/list/print.js +52 -9
  89. package/dist/commands/list/print.js.map +1 -1
  90. package/dist/commands/list/suites/suites.js +140 -0
  91. package/dist/commands/list/suites/suites.js.map +1 -0
  92. package/dist/commands/list/views.js +37 -34
  93. package/dist/commands/list/views.js.map +1 -1
  94. package/dist/commands/mcp/dispatch.js +91 -36
  95. package/dist/commands/mcp/dispatch.js.map +1 -1
  96. package/dist/commands/mcp/mcp.js +56 -15
  97. package/dist/commands/mcp/mcp.js.map +1 -1
  98. package/dist/commands/mcp/serve.js.map +1 -1
  99. package/dist/commands/migrate-openspec/materialize/active.js +27 -22
  100. package/dist/commands/migrate-openspec/materialize/active.js.map +1 -1
  101. package/dist/commands/migrate-openspec/materialize/stage.js +4 -3
  102. package/dist/commands/migrate-openspec/materialize/stage.js.map +1 -1
  103. package/dist/commands/migrate-openspec/materialize/target.js +2 -2
  104. package/dist/commands/migrate-openspec/materialize/target.js.map +1 -1
  105. package/dist/commands/migrate-openspec/openspec/decisions.js +9 -14
  106. package/dist/commands/migrate-openspec/openspec/decisions.js.map +1 -1
  107. package/dist/commands/migrate-openspec/openspec/error.js +7 -8
  108. package/dist/commands/migrate-openspec/openspec/error.js.map +1 -1
  109. package/dist/commands/migrate-openspec/openspec/mapping.js +1 -9
  110. package/dist/commands/migrate-openspec/openspec/mapping.js.map +1 -1
  111. package/dist/commands/migrate-openspec/openspec/paths.js +3 -6
  112. package/dist/commands/migrate-openspec/openspec/paths.js.map +1 -1
  113. package/dist/commands/new/capability-template.js +118 -0
  114. package/dist/commands/new/capability-template.js.map +1 -0
  115. package/dist/commands/new/commit.js +8 -22
  116. package/dist/commands/new/commit.js.map +1 -1
  117. package/dist/commands/new/modifies.js +206 -0
  118. package/dist/commands/new/modifies.js.map +1 -0
  119. package/dist/commands/new/new.js +59 -14
  120. package/dist/commands/new/new.js.map +1 -1
  121. package/dist/commands/new/templates.js +48 -124
  122. package/dist/commands/new/templates.js.map +1 -1
  123. package/dist/commands/open.js +3 -7
  124. package/dist/commands/open.js.map +1 -1
  125. package/dist/commands/policy/gate.js +27 -0
  126. package/dist/commands/policy/gate.js.map +1 -1
  127. package/dist/commands/rebase/plan.js +2 -2
  128. package/dist/commands/rebase/plan.js.map +1 -1
  129. package/dist/commands/rebase/rebase.js +60 -35
  130. package/dist/commands/rebase/rebase.js.map +1 -1
  131. package/dist/commands/seed/commit.js +55 -26
  132. package/dist/commands/seed/commit.js.map +1 -1
  133. package/dist/commands/seed/plan.js +158 -13
  134. package/dist/commands/seed/plan.js.map +1 -1
  135. package/dist/commands/seed/seed.js +13 -11
  136. package/dist/commands/seed/seed.js.map +1 -1
  137. package/dist/commands/show/feature.js +9 -5
  138. package/dist/commands/show/feature.js.map +1 -1
  139. package/dist/commands/show/introduced.js +157 -0
  140. package/dist/commands/show/introduced.js.map +1 -0
  141. package/dist/commands/show/marks.js +35 -6
  142. package/dist/commands/show/marks.js.map +1 -1
  143. package/dist/commands/show/service.js +41 -33
  144. package/dist/commands/show/service.js.map +1 -1
  145. package/dist/commands/show/show.js +17 -7
  146. package/dist/commands/show/show.js.map +1 -1
  147. package/dist/commands/status/fleet-next.js +90 -0
  148. package/dist/commands/status/fleet-next.js.map +1 -0
  149. package/dist/commands/status/json.js +2 -0
  150. package/dist/commands/status/json.js.map +1 -1
  151. package/dist/commands/status/print.js +89 -37
  152. package/dist/commands/status/print.js.map +1 -1
  153. package/dist/commands/status/status.js +3 -7
  154. package/dist/commands/status/status.js.map +1 -1
  155. package/dist/commands/steps/steps.js +103 -23
  156. package/dist/commands/steps/steps.js.map +1 -1
  157. package/dist/commands/subsystem/edit.js +7 -7
  158. package/dist/commands/subsystem/edit.js.map +1 -1
  159. package/dist/commands/subsystem/move.js +106 -46
  160. package/dist/commands/subsystem/move.js.map +1 -1
  161. package/dist/commands/subsystem/query.js +2 -4
  162. package/dist/commands/subsystem/query.js.map +1 -1
  163. package/dist/commands/subsystem/subsystem.js +4 -7
  164. package/dist/commands/subsystem/subsystem.js.map +1 -1
  165. package/dist/commands/subsystem/sync.js +289 -74
  166. package/dist/commands/subsystem/sync.js.map +1 -1
  167. package/dist/commands/subsystem/txn/exclude.js +132 -0
  168. package/dist/commands/subsystem/txn/exclude.js.map +1 -0
  169. package/dist/commands/subsystem/txn/links.js +128 -0
  170. package/dist/commands/subsystem/txn/links.js.map +1 -0
  171. package/dist/commands/subsystem/txn/report.js +174 -0
  172. package/dist/commands/subsystem/txn/report.js.map +1 -0
  173. package/dist/commands/subsystem/txn/txn.js +147 -45
  174. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  175. package/dist/commands/subsystem/txn/views.js +200 -21
  176. package/dist/commands/subsystem/txn/views.js.map +1 -1
  177. package/dist/commands/tasks/actions.js +107 -0
  178. package/dist/commands/tasks/actions.js.map +1 -0
  179. package/dist/commands/tasks/render.js +253 -0
  180. package/dist/commands/tasks/render.js.map +1 -0
  181. package/dist/commands/tasks/tasks.js +119 -0
  182. package/dist/commands/tasks/tasks.js.map +1 -0
  183. package/dist/commands/unarchive/manifest.js +42 -26
  184. package/dist/commands/unarchive/manifest.js.map +1 -1
  185. package/dist/commands/unarchive/restore.js +83 -33
  186. package/dist/commands/unarchive/restore.js.map +1 -1
  187. package/dist/commands/unarchive/unarchive.js +22 -30
  188. package/dist/commands/unarchive/unarchive.js.map +1 -1
  189. package/dist/commands/validate/arch-coverage.js +78 -27
  190. package/dist/commands/validate/arch-coverage.js.map +1 -1
  191. package/dist/commands/validate/checks/fleet-shape.js +34 -126
  192. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  193. package/dist/commands/validate/checks/health-deps.js +9 -6
  194. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  195. package/dist/commands/validate/checks/introduced.js +102 -0
  196. package/dist/commands/validate/checks/introduced.js.map +1 -0
  197. package/dist/commands/validate/checks/requirements.js +18 -8
  198. package/dist/commands/validate/checks/requirements.js.map +1 -1
  199. package/dist/commands/validate/checks/sources.js +11 -2
  200. package/dist/commands/validate/checks/sources.js.map +1 -1
  201. package/dist/commands/validate/coverage/covers-after.js +105 -0
  202. package/dist/commands/validate/coverage/covers-after.js.map +1 -0
  203. package/dist/commands/validate/feature.js +26 -20
  204. package/dist/commands/validate/feature.js.map +1 -1
  205. package/dist/commands/validate/fleet/census.js +37 -13
  206. package/dist/commands/validate/fleet/census.js.map +1 -1
  207. package/dist/commands/validate/fleet/landscape.js +144 -126
  208. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  209. package/dist/commands/validate/fleet/load.js +66 -0
  210. package/dist/commands/validate/fleet/load.js.map +1 -1
  211. package/dist/commands/validate/fleet/map/attest.js +98 -0
  212. package/dist/commands/validate/fleet/map/attest.js.map +1 -0
  213. package/dist/commands/validate/fleet/map/bindings.js +107 -0
  214. package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
  215. package/dist/commands/validate/fleet/map/consumers.js +244 -0
  216. package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
  217. package/dist/commands/validate/fleet/map/isolation.js +116 -0
  218. package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
  219. package/dist/commands/validate/fleet/map/usecases.js +54 -0
  220. package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
  221. package/dist/commands/validate/fleet/obligations.js +44 -14
  222. package/dist/commands/validate/fleet/obligations.js.map +1 -1
  223. package/dist/commands/validate/fleet/scorecard/print.js +10 -2
  224. package/dist/commands/validate/fleet/scorecard/print.js.map +1 -1
  225. package/dist/commands/validate/fleet/scorecard/scorecard.js +17 -11
  226. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  227. package/dist/commands/validate/fleet/usecases/place.js.map +1 -1
  228. package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
  229. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
  230. package/dist/commands/validate/fleet/views/ids.js +50 -15
  231. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  232. package/dist/commands/validate/fleet/views/projects.js +278 -0
  233. package/dist/commands/validate/fleet/views/projects.js.map +1 -0
  234. package/dist/commands/validate/fleet/views/root-documents.js +83 -0
  235. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
  236. package/dist/commands/validate/fleet/views/stale.js +40 -47
  237. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  238. package/dist/commands/validate/links/corpus.js +1 -4
  239. package/dist/commands/validate/links/corpus.js.map +1 -1
  240. package/dist/commands/validate/links/glossary.js.map +1 -1
  241. package/dist/commands/validate/render/repeats.js +63 -0
  242. package/dist/commands/validate/render/repeats.js.map +1 -0
  243. package/dist/commands/validate/render/rollups.js +115 -0
  244. package/dist/commands/validate/render/rollups.js.map +1 -0
  245. package/dist/commands/validate/render/text.js +199 -0
  246. package/dist/commands/validate/render/text.js.map +1 -0
  247. package/dist/commands/validate/report.js +36 -160
  248. package/dist/commands/validate/report.js.map +1 -1
  249. package/dist/commands/validate/scope.js +113 -0
  250. package/dist/commands/validate/scope.js.map +1 -0
  251. package/dist/commands/validate/service/api.js +21 -107
  252. package/dist/commands/validate/service/api.js.map +1 -1
  253. package/dist/commands/validate/service/contracts/generated.js +134 -0
  254. package/dist/commands/validate/service/contracts/generated.js.map +1 -0
  255. package/dist/commands/validate/service/events/events.js +36 -14
  256. package/dist/commands/validate/service/events/events.js.map +1 -1
  257. package/dist/commands/validate/service/events/message-producers.js +1 -1
  258. package/dist/commands/validate/service/events/message-producers.js.map +1 -1
  259. package/dist/commands/validate/service/evidence-pins.js +56 -10
  260. package/dist/commands/validate/service/evidence-pins.js.map +1 -1
  261. package/dist/commands/validate/service/model/diverged.js +226 -0
  262. package/dist/commands/validate/service/model/diverged.js.map +1 -0
  263. package/dist/commands/validate/service/model/grade.js +163 -0
  264. package/dist/commands/validate/service/model/grade.js.map +1 -0
  265. package/dist/commands/validate/service/service.js +121 -61
  266. package/dist/commands/validate/service/service.js.map +1 -1
  267. package/dist/commands/validate/service/specs.js +88 -28
  268. package/dist/commands/validate/service/specs.js.map +1 -1
  269. package/dist/commands/validate/service/spine.js +38 -3
  270. package/dist/commands/validate/service/spine.js.map +1 -1
  271. package/dist/commands/validate/service/usecases/tags.js +113 -0
  272. package/dist/commands/validate/service/usecases/tags.js.map +1 -0
  273. package/dist/commands/validate/service/usecases/usecases.js +209 -0
  274. package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
  275. package/dist/commands/validate/validate.js +85 -60
  276. package/dist/commands/validate/validate.js.map +1 -1
  277. package/dist/commands/verify/cross/diff.js +2 -6
  278. package/dist/commands/verify/cross/diff.js.map +1 -1
  279. package/dist/commands/verify/evidence/contract.js +1 -1
  280. package/dist/commands/verify/evidence/contract.js.map +1 -1
  281. package/dist/commands/verify/evidence/read.js +12 -3
  282. package/dist/commands/verify/evidence/read.js.map +1 -1
  283. package/dist/commands/verify/frozen.js +45 -23
  284. package/dist/commands/verify/frozen.js.map +1 -1
  285. package/dist/commands/verify/record.js +69 -17
  286. package/dist/commands/verify/record.js.map +1 -1
  287. package/dist/commands/verify/report.js +27 -44
  288. package/dist/commands/verify/report.js.map +1 -1
  289. package/dist/commands/verify/results.js +112 -30
  290. package/dist/commands/verify/results.js.map +1 -1
  291. package/dist/commands/verify/verify.js +55 -52
  292. package/dist/commands/verify/verify.js.map +1 -1
  293. package/dist/commands/vouch/pack/pack.js +2 -2
  294. package/dist/commands/vouch/pack/pack.js.map +1 -1
  295. package/dist/commands/vouch/pack/print.js +1 -6
  296. package/dist/commands/vouch/pack/print.js.map +1 -1
  297. package/dist/commands/vouch/pack/sections.js +1 -2
  298. package/dist/commands/vouch/pack/sections.js.map +1 -1
  299. package/dist/commands/vouch/run.js +26 -81
  300. package/dist/commands/vouch/run.js.map +1 -1
  301. package/dist/commands/vouch/sample/plan.js +20 -34
  302. package/dist/commands/vouch/sample/plan.js.map +1 -1
  303. package/dist/commands/vouch/vet/verify.js +39 -7
  304. package/dist/commands/vouch/vet/verify.js.map +1 -1
  305. package/dist/commands/vouch/vouch.js +10 -17
  306. package/dist/commands/vouch/vouch.js.map +1 -1
  307. package/dist/core/agent/agents-md/command-map.js +81 -106
  308. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  309. package/dist/core/agent/agents-md/map/explain.js +21 -9
  310. package/dist/core/agent/agents-md/map/explain.js.map +1 -1
  311. package/dist/core/agent/agents-md/map/lenses/context.js +17 -8
  312. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  313. package/dist/core/agent/agents-md/map/lenses/diff.js +33 -20
  314. package/dist/core/agent/agents-md/map/lenses/diff.js.map +1 -1
  315. package/dist/core/agent/agents-md/map/lenses/gate.js +13 -5
  316. package/dist/core/agent/agents-md/map/lenses/gate.js.map +1 -1
  317. package/dist/core/agent/agents-md/map/mcp.js +10 -7
  318. package/dist/core/agent/agents-md/map/mcp.js.map +1 -1
  319. package/dist/core/agent/agents-md/map/status.js +24 -8
  320. package/dist/core/agent/agents-md/map/status.js.map +1 -1
  321. package/dist/core/agent/agents-md/map/subsystem.js +80 -8
  322. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  323. package/dist/core/agent/agents-md/map/usecases.js +88 -10
  324. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  325. package/dist/core/agent/agents-md/refusals.js +49 -22
  326. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  327. package/dist/core/agent/agents-md.js +39 -6
  328. package/dist/core/agent/agents-md.js.map +1 -1
  329. package/dist/core/agent/agents-stamp.js +10 -0
  330. package/dist/core/agent/agents-stamp.js.map +1 -1
  331. package/dist/core/agent/protocol.js +1 -1
  332. package/dist/core/agent/protocol.js.map +1 -1
  333. package/dist/core/agent/scaffold.js +10 -15
  334. package/dist/core/agent/scaffold.js.map +1 -1
  335. package/dist/core/agent/support/report.js +46 -5
  336. package/dist/core/agent/support/report.js.map +1 -1
  337. package/dist/core/agent/tools/dialects.js +1 -0
  338. package/dist/core/agent/tools/dialects.js.map +1 -1
  339. package/dist/core/agent/tools/registry.js +41 -76
  340. package/dist/core/agent/tools/registry.js.map +1 -1
  341. package/dist/core/agent/workflows/adopt.js +158 -11
  342. package/dist/core/agent/workflows/adopt.js.map +1 -1
  343. package/dist/core/agent/workflows/check.js +78 -39
  344. package/dist/core/agent/workflows/check.js.map +1 -1
  345. package/dist/core/agent/workflows/closing.js +88 -27
  346. package/dist/core/agent/workflows/closing.js.map +1 -1
  347. package/dist/core/agent/workflows/feature.js +128 -13
  348. package/dist/core/agent/workflows/feature.js.map +1 -1
  349. package/dist/core/agent/workflows/implement.js +96 -13
  350. package/dist/core/agent/workflows/implement.js.map +1 -1
  351. package/dist/core/agent/workflows/reference/authoring.js +106 -4
  352. package/dist/core/agent/workflows/reference/authoring.js.map +1 -1
  353. package/dist/core/agent/workflows/reference/codes.js +8 -6
  354. package/dist/core/agent/workflows/reference/codes.js.map +1 -1
  355. package/dist/core/agent/workflows/reference/done-check.js +37 -15
  356. package/dist/core/agent/workflows/reference/done-check.js.map +1 -1
  357. package/dist/core/agent/workflows/reference/drawing/drawing.js +253 -0
  358. package/dist/core/agent/workflows/reference/drawing/drawing.js.map +1 -0
  359. package/dist/core/agent/workflows/reference/reference.js +6 -3
  360. package/dist/core/agent/workflows/reference/reference.js.map +1 -1
  361. package/dist/core/agent/workflows/reference/spine.js +68 -27
  362. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  363. package/dist/core/asyncapi/baseline/plan.js +14 -81
  364. package/dist/core/asyncapi/baseline/plan.js.map +1 -1
  365. package/dist/core/asyncapi/digest.js +30 -25
  366. package/dist/core/asyncapi/digest.js.map +1 -1
  367. package/dist/core/asyncapi/merge/components.js +5 -4
  368. package/dist/core/asyncapi/merge/components.js.map +1 -1
  369. package/dist/core/asyncapi/merge/error.js +7 -2
  370. package/dist/core/asyncapi/merge/error.js.map +1 -1
  371. package/dist/core/asyncapi/merge/markers.js +32 -50
  372. package/dist/core/asyncapi/merge/markers.js.map +1 -1
  373. package/dist/core/asyncapi/merge/merge.js +33 -53
  374. package/dist/core/asyncapi/merge/merge.js.map +1 -1
  375. package/dist/core/asyncapi/merge/pin.js +12 -36
  376. package/dist/core/asyncapi/merge/pin.js.map +1 -1
  377. package/dist/core/asyncapi/model.js.map +1 -1
  378. package/dist/core/asyncapi/read.js +36 -12
  379. package/dist/core/asyncapi/read.js.map +1 -1
  380. package/dist/core/brief/brief.js +96 -36
  381. package/dist/core/brief/brief.js.map +1 -1
  382. package/dist/core/brief/checks.js +138 -3
  383. package/dist/core/brief/checks.js.map +1 -1
  384. package/dist/core/brief/landscape.js +209 -130
  385. package/dist/core/brief/landscape.js.map +1 -1
  386. package/dist/core/brief/map/extend.js +84 -0
  387. package/dist/core/brief/map/extend.js.map +1 -0
  388. package/dist/core/brief/map/owed.js +266 -0
  389. package/dist/core/brief/map/owed.js.map +1 -0
  390. package/dist/core/brief/targets.js +109 -31
  391. package/dist/core/brief/targets.js.map +1 -1
  392. package/dist/core/brief/unchecked.js +14 -3
  393. package/dist/core/brief/unchecked.js.map +1 -1
  394. package/dist/core/c4/arch.js +90 -182
  395. package/dist/core/c4/arch.js.map +1 -1
  396. package/dist/core/c4/direction/direction.js +114 -0
  397. package/dist/core/c4/direction/direction.js.map +1 -0
  398. package/dist/core/c4/likec4.js +169 -14
  399. package/dist/core/c4/likec4.js.map +1 -1
  400. package/dist/core/c4/locate/unlocated.js +150 -0
  401. package/dist/core/c4/locate/unlocated.js.map +1 -0
  402. package/dist/core/c4/parsed/deployment.js +160 -0
  403. package/dist/core/c4/parsed/deployment.js.map +1 -0
  404. package/dist/core/c4/parsed/dynamic-views.js.map +1 -1
  405. package/dist/core/c4/parsed/values.js +54 -1
  406. package/dist/core/c4/parsed/values.js.map +1 -1
  407. package/dist/core/c4/parsed/view-ids.js +39 -1
  408. package/dist/core/c4/parsed/view-ids.js.map +1 -1
  409. package/dist/core/c4/prefetch/fleet.js +134 -0
  410. package/dist/core/c4/prefetch/fleet.js.map +1 -0
  411. package/dist/core/c4/project/architecture.js +123 -7
  412. package/dist/core/c4/project/architecture.js.map +1 -1
  413. package/dist/core/c4/project/batch.js +140 -0
  414. package/dist/core/c4/project/batch.js.map +1 -0
  415. package/dist/core/c4/project/documents.js +43 -12
  416. package/dist/core/c4/project/documents.js.map +1 -1
  417. package/dist/core/c4/project/load.js +79 -43
  418. package/dist/core/c4/project/load.js.map +1 -1
  419. package/dist/core/c4/project/staged.js +277 -0
  420. package/dist/core/c4/project/staged.js.map +1 -0
  421. package/dist/core/c4/resolve/attested.js +0 -0
  422. package/dist/core/c4/resolve/attested.js.map +1 -0
  423. package/dist/core/c4/resolve/external.js +51 -0
  424. package/dist/core/c4/resolve/external.js.map +1 -0
  425. package/dist/core/c4/resolve/service.js +78 -17
  426. package/dist/core/c4/resolve/service.js.map +1 -1
  427. package/dist/core/c4/resolve/steps.js +290 -0
  428. package/dist/core/c4/resolve/steps.js.map +1 -0
  429. package/dist/core/c4/resolve/systems.js +154 -0
  430. package/dist/core/c4/resolve/systems.js.map +1 -0
  431. package/dist/core/c4/root-project/exclude.js +352 -0
  432. package/dist/core/c4/root-project/exclude.js.map +1 -0
  433. package/dist/core/c4/seed/template.js +65 -1
  434. package/dist/core/c4/seed/template.js.map +1 -1
  435. package/dist/core/c4/service-model/created/models.js +96 -0
  436. package/dist/core/c4/service-model/created/models.js.map +1 -0
  437. package/dist/core/c4/service-model/documents.js +47 -0
  438. package/dist/core/c4/service-model/documents.js.map +1 -0
  439. package/dist/core/c4/service-model/fleet/extending.js +82 -0
  440. package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
  441. package/dist/core/c4/service-model/load.js +165 -0
  442. package/dist/core/c4/service-model/load.js.map +1 -0
  443. package/dist/core/c4/service-model/renderer.js +128 -0
  444. package/dist/core/c4/service-model/renderer.js.map +1 -0
  445. package/dist/core/c4/service-model/shape.js +106 -0
  446. package/dist/core/c4/service-model/shape.js.map +1 -0
  447. package/dist/core/c4/service-model/slice.js +188 -0
  448. package/dist/core/c4/service-model/slice.js.map +1 -0
  449. package/dist/core/c4/source-mask.js +297 -10
  450. package/dist/core/c4/source-mask.js.map +1 -1
  451. package/dist/core/c4/source-scan.js +222 -141
  452. package/dist/core/c4/source-scan.js.map +1 -1
  453. package/dist/core/c4/splice/authored-source.js +79 -13
  454. package/dist/core/c4/splice/authored-source.js.map +1 -1
  455. package/dist/core/c4/splice/contract.js +20 -0
  456. package/dist/core/c4/splice/contract.js.map +1 -1
  457. package/dist/core/c4/splice/delta-blocks.js +20 -20
  458. package/dist/core/c4/splice/delta-blocks.js.map +1 -1
  459. package/dist/core/c4/splice/identity/declared.js +87 -0
  460. package/dist/core/c4/splice/identity/declared.js.map +1 -0
  461. package/dist/core/c4/splice/identity/edges.js +37 -0
  462. package/dist/core/c4/splice/identity/edges.js.map +1 -1
  463. package/dist/core/c4/splice/identity/existing.js +80 -0
  464. package/dist/core/c4/splice/identity/existing.js.map +1 -0
  465. package/dist/core/c4/splice/identity/interior.js +80 -0
  466. package/dist/core/c4/splice/identity/interior.js.map +1 -0
  467. package/dist/core/c4/splice/identity/owner.js +50 -0
  468. package/dist/core/c4/splice/identity/owner.js.map +1 -0
  469. package/dist/core/c4/splice/landscape-merge.js +182 -114
  470. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  471. package/dist/core/c4/splice/model/merge.js +151 -0
  472. package/dist/core/c4/splice/model/merge.js.map +1 -0
  473. package/dist/core/c4/splice/placement.js +160 -56
  474. package/dist/core/c4/splice/placement.js.map +1 -1
  475. package/dist/core/c4/splice/text/spot.js +131 -0
  476. package/dist/core/c4/splice/text/spot.js.map +1 -0
  477. package/dist/core/c4/workspace.js +13 -19
  478. package/dist/core/c4/workspace.js.map +1 -1
  479. package/dist/core/capabilities/capabilities.js +6 -6
  480. package/dist/core/capabilities/capabilities.js.map +1 -1
  481. package/dist/core/capabilities/delta/doc.js +7 -10
  482. package/dist/core/capabilities/delta/doc.js.map +1 -1
  483. package/dist/core/capabilities/delta/uncovered.js +2 -2
  484. package/dist/core/capabilities/delta/uncovered.js.map +1 -1
  485. package/dist/core/capabilities/findings.js +18 -22
  486. package/dist/core/capabilities/findings.js.map +1 -1
  487. package/dist/core/capabilities/realizes/findings.js +12 -19
  488. package/dist/core/capabilities/realizes/findings.js.map +1 -1
  489. package/dist/core/capabilities/realizes/join.js +0 -20
  490. package/dist/core/capabilities/realizes/join.js.map +1 -1
  491. package/dist/core/capabilities/rollup.js +6 -0
  492. package/dist/core/capabilities/rollup.js.map +1 -1
  493. package/dist/core/capabilities/tree.js +6 -28
  494. package/dist/core/capabilities/tree.js.map +1 -1
  495. package/dist/core/capabilities/usecase-join.js +3 -13
  496. package/dist/core/capabilities/usecase-join.js.map +1 -1
  497. package/dist/core/coherence/authoring/scaffold.js +11 -3
  498. package/dist/core/coherence/authoring/scaffold.js.map +1 -1
  499. package/dist/core/coherence/authoring/sentinels.js +2 -1
  500. package/dist/core/coherence/authoring/sentinels.js.map +1 -1
  501. package/dist/core/coherence/calls/called.js +52 -0
  502. package/dist/core/coherence/calls/called.js.map +1 -0
  503. package/dist/core/coherence/coherence.js +103 -22
  504. package/dist/core/coherence/coherence.js.map +1 -1
  505. package/dist/core/coherence/consumed/operations.js +70 -0
  506. package/dist/core/coherence/consumed/operations.js.map +1 -0
  507. package/dist/core/coherence/consumed/refusal.js +38 -0
  508. package/dist/core/coherence/consumed/refusal.js.map +1 -0
  509. package/dist/core/coherence/covers/health.js +85 -0
  510. package/dist/core/coherence/covers/health.js.map +1 -0
  511. package/dist/core/coherence/declared.js +60 -34
  512. package/dist/core/coherence/declared.js.map +1 -1
  513. package/dist/core/coherence/events/declared.js +44 -59
  514. package/dist/core/coherence/events/declared.js.map +1 -1
  515. package/dist/core/coherence/events/events.js +147 -16
  516. package/dist/core/coherence/events/events.js.map +1 -1
  517. package/dist/core/coherence/events/grades.js +31 -8
  518. package/dist/core/coherence/events/grades.js.map +1 -1
  519. package/dist/core/coherence/events/lookups.js +28 -87
  520. package/dist/core/coherence/events/lookups.js.map +1 -1
  521. package/dist/core/coherence/events/produced.js +100 -0
  522. package/dist/core/coherence/events/produced.js.map +1 -0
  523. package/dist/core/coherence/lookups.js +18 -78
  524. package/dist/core/coherence/lookups.js.map +1 -1
  525. package/dist/core/coherence/retired/archived.js +285 -0
  526. package/dist/core/coherence/retired/archived.js.map +1 -0
  527. package/dist/core/coherence/retired/event-pair.js +62 -0
  528. package/dist/core/coherence/retired/event-pair.js.map +1 -0
  529. package/dist/core/coherence/retired/merged.js +79 -0
  530. package/dist/core/coherence/retired/merged.js.map +1 -0
  531. package/dist/core/coherence/retired/retirement.js +49 -0
  532. package/dist/core/coherence/retired/retirement.js.map +1 -0
  533. package/dist/core/coherence/retired/revival/arrivals.js +131 -0
  534. package/dist/core/coherence/retired/revival/arrivals.js.map +1 -0
  535. package/dist/core/coherence/retired/revival/revived.js +297 -0
  536. package/dist/core/coherence/retired/revival/revived.js.map +1 -0
  537. package/dist/core/consumers/after.js +113 -0
  538. package/dist/core/consumers/after.js.map +1 -0
  539. package/dist/core/consumers/consumers.js +296 -0
  540. package/dist/core/consumers/consumers.js.map +1 -0
  541. package/dist/core/consumers/current.js +211 -0
  542. package/dist/core/consumers/current.js.map +1 -0
  543. package/dist/core/consumers/scan.js +46 -0
  544. package/dist/core/consumers/scan.js.map +1 -0
  545. package/dist/core/consumers/state.js +27 -0
  546. package/dist/core/consumers/state.js.map +1 -0
  547. package/dist/core/delta/delta.js +51 -6
  548. package/dist/core/delta/delta.js.map +1 -1
  549. package/dist/core/dependencies/fanin.js +2 -2
  550. package/dist/core/dependencies/fanin.js.map +1 -1
  551. package/dist/core/deployment/delta.js +191 -0
  552. package/dist/core/deployment/delta.js.map +1 -0
  553. package/dist/core/deployment/objects.js +145 -0
  554. package/dist/core/deployment/objects.js.map +1 -0
  555. package/dist/core/diff/base-git.js +94 -12
  556. package/dist/core/diff/base-git.js.map +1 -1
  557. package/dist/core/diff/base-state.js +49 -21
  558. package/dist/core/diff/base-state.js.map +1 -1
  559. package/dist/core/diff/joins.js +136 -0
  560. package/dist/core/diff/joins.js.map +1 -0
  561. package/dist/core/diff/scope/changed-paths.js +22 -6
  562. package/dist/core/diff/scope/changed-paths.js.map +1 -1
  563. package/dist/core/diff/scope/edges.js +82 -0
  564. package/dist/core/diff/scope/edges.js.map +1 -0
  565. package/dist/core/diff/semantic.js +100 -31
  566. package/dist/core/diff/semantic.js.map +1 -1
  567. package/dist/core/docs.js +31 -22
  568. package/dist/core/docs.js.map +1 -1
  569. package/dist/core/doctor/agents.js +34 -18
  570. package/dist/core/doctor/agents.js.map +1 -1
  571. package/dist/core/doctor/config.js +7 -3
  572. package/dist/core/doctor/config.js.map +1 -1
  573. package/dist/core/doctor/doctor.js +91 -11
  574. package/dist/core/doctor/doctor.js.map +1 -1
  575. package/dist/core/doctor/report.js.map +1 -1
  576. package/dist/core/doctor/reports/scan.js +242 -0
  577. package/dist/core/doctor/reports/scan.js.map +1 -0
  578. package/dist/core/document/apply.js +158 -11
  579. package/dist/core/document/apply.js.map +1 -1
  580. package/dist/core/document/frontmatter.js +55 -0
  581. package/dist/core/document/frontmatter.js.map +1 -1
  582. package/dist/core/document/parse.js +29 -6
  583. package/dist/core/document/parse.js.map +1 -1
  584. package/dist/core/document/scenarios.js +17 -0
  585. package/dist/core/document/scenarios.js.map +1 -1
  586. package/dist/core/document/spec.js +4 -11
  587. package/dist/core/document/spec.js.map +1 -1
  588. package/dist/core/envelope/config.js +10 -0
  589. package/dist/core/envelope/config.js.map +1 -1
  590. package/dist/core/envelope/json.js +34 -0
  591. package/dist/core/envelope/json.js.map +1 -1
  592. package/dist/core/explain/families.js +26 -21
  593. package/dist/core/explain/families.js.map +1 -1
  594. package/dist/core/explain/lookup.js +14 -1
  595. package/dist/core/explain/lookup.js.map +1 -1
  596. package/dist/core/explain/refusals.js +5 -5
  597. package/dist/core/explain/refusals.js.map +1 -1
  598. package/dist/core/explain/terms/documents.js +227 -0
  599. package/dist/core/explain/terms/documents.js.map +1 -0
  600. package/dist/core/explain/terms/done-check.js +144 -0
  601. package/dist/core/explain/terms/done-check.js.map +1 -0
  602. package/dist/core/explain/terms/entry.js +14 -0
  603. package/dist/core/explain/terms/entry.js.map +1 -0
  604. package/dist/core/explain/terms/stages.js +71 -0
  605. package/dist/core/explain/terms/stages.js.map +1 -0
  606. package/dist/core/explain/terms/tasks.js +97 -0
  607. package/dist/core/explain/terms/tasks.js.map +1 -0
  608. package/dist/core/explain/terms.js +68 -109
  609. package/dist/core/explain/terms.js.map +1 -1
  610. package/dist/core/explore/explore.js +40 -10
  611. package/dist/core/explore/explore.js.map +1 -1
  612. package/dist/core/fleet-context.js +81 -95
  613. package/dist/core/fleet-context.js.map +1 -1
  614. package/dist/core/gate/checks.js +11 -4
  615. package/dist/core/gate/checks.js.map +1 -1
  616. package/dist/core/gate/partners.js +69 -28
  617. package/dist/core/gate/partners.js.map +1 -1
  618. package/dist/core/gate/report.js.map +1 -1
  619. package/dist/core/gate/verification.js +75 -12
  620. package/dist/core/gate/verification.js.map +1 -1
  621. package/dist/core/gherkin/stale.js +13 -37
  622. package/dist/core/gherkin/stale.js.map +1 -1
  623. package/dist/core/glossary/delta.js +10 -7
  624. package/dist/core/glossary/delta.js.map +1 -1
  625. package/dist/core/glossary/tree.js +6 -19
  626. package/dist/core/glossary/tree.js.map +1 -1
  627. package/dist/core/kernel/concurrency.js +19 -0
  628. package/dist/core/kernel/concurrency.js.map +1 -1
  629. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  630. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  631. package/dist/core/kernel/path-safety.js +38 -0
  632. package/dist/core/kernel/path-safety.js.map +1 -1
  633. package/dist/core/kernel/records.js +11 -0
  634. package/dist/core/kernel/records.js.map +1 -1
  635. package/dist/core/links/backlinks.js +1 -3
  636. package/dist/core/links/backlinks.js.map +1 -1
  637. package/dist/core/links/corpus.js +51 -37
  638. package/dist/core/links/corpus.js.map +1 -1
  639. package/dist/core/links/findings.js.map +1 -1
  640. package/dist/core/links/parse.js +38 -9
  641. package/dist/core/links/parse.js.map +1 -1
  642. package/dist/core/mcp/argv.js +101 -0
  643. package/dist/core/mcp/argv.js.map +1 -1
  644. package/dist/core/mcp/author-tools.js +2 -1
  645. package/dist/core/mcp/author-tools.js.map +1 -1
  646. package/dist/core/mcp/protocol.js +2 -2
  647. package/dist/core/mcp/protocol.js.map +1 -1
  648. package/dist/core/mcp/tools.js +43 -33
  649. package/dist/core/mcp/tools.js.map +1 -1
  650. package/dist/core/openapi/baseline/gate.js +53 -12
  651. package/dist/core/openapi/baseline/gate.js.map +1 -1
  652. package/dist/core/openapi/baseline/plan.js +40 -10
  653. package/dist/core/openapi/baseline/plan.js.map +1 -1
  654. package/dist/core/openapi/baseline/record.js +0 -1
  655. package/dist/core/openapi/baseline/record.js.map +1 -1
  656. package/dist/core/openapi/contract-text.js +33 -0
  657. package/dist/core/openapi/contract-text.js.map +1 -0
  658. package/dist/core/openapi/contract-tree.js +301 -0
  659. package/dist/core/openapi/contract-tree.js.map +1 -0
  660. package/dist/core/openapi/depth.js +38 -24
  661. package/dist/core/openapi/depth.js.map +1 -1
  662. package/dist/core/openapi/digest.js +52 -2
  663. package/dist/core/openapi/digest.js.map +1 -1
  664. package/dist/core/openapi/doc.js +12 -4
  665. package/dist/core/openapi/doc.js.map +1 -1
  666. package/dist/core/openapi/merge/components.js +25 -60
  667. package/dist/core/openapi/merge/components.js.map +1 -1
  668. package/dist/core/openapi/merge/error.js +3 -13
  669. package/dist/core/openapi/merge/error.js.map +1 -1
  670. package/dist/core/openapi/merge/markers.js +26 -30
  671. package/dist/core/openapi/merge/markers.js.map +1 -1
  672. package/dist/core/openapi/merge/merge.js +28 -52
  673. package/dist/core/openapi/merge/merge.js.map +1 -1
  674. package/dist/core/openapi/merge/pin.js +17 -35
  675. package/dist/core/openapi/merge/pin.js.map +1 -1
  676. package/dist/core/openspec/decide/verdict.js.map +1 -1
  677. package/dist/core/openspec/inventory.js +4 -5
  678. package/dist/core/openspec/inventory.js.map +1 -1
  679. package/dist/core/openspec/model/mapping.js +30 -28
  680. package/dist/core/openspec/model/mapping.js.map +1 -1
  681. package/dist/core/openspec/model/model.js +12 -7
  682. package/dist/core/openspec/model/model.js.map +1 -1
  683. package/dist/core/openspec/scan/changes.js +10 -4
  684. package/dist/core/openspec/scan/changes.js.map +1 -1
  685. package/dist/core/openspec/scan/config.js +7 -16
  686. package/dist/core/openspec/scan/config.js.map +1 -1
  687. package/dist/core/openspec/scan/shape.js +63 -19
  688. package/dist/core/openspec/scan/shape.js.map +1 -1
  689. package/dist/core/openspec/scan/walk.js +21 -38
  690. package/dist/core/openspec/scan/walk.js.map +1 -1
  691. package/dist/core/pack/living.js +43 -4
  692. package/dist/core/pack/living.js.map +1 -1
  693. package/dist/core/pack/pack.js +50 -6
  694. package/dist/core/pack/pack.js.map +1 -1
  695. package/dist/core/projection/arch-slice.js +2 -2
  696. package/dist/core/projection/arch-slice.js.map +1 -1
  697. package/dist/core/projection/introduced.js +14 -0
  698. package/dist/core/projection/introduced.js.map +1 -0
  699. package/dist/core/provenance/findings.js +48 -12
  700. package/dist/core/provenance/findings.js.map +1 -1
  701. package/dist/core/provenance/git.js +33 -57
  702. package/dist/core/provenance/git.js.map +1 -1
  703. package/dist/core/provenance/gitq/moves.js +71 -114
  704. package/dist/core/provenance/gitq/moves.js.map +1 -1
  705. package/dist/core/provenance/gitq/vouched-ancestor.js +2 -2
  706. package/dist/core/provenance/gitq/vouched-ancestor.js.map +1 -1
  707. package/dist/core/provenance/sources.js +23 -6
  708. package/dist/core/provenance/sources.js.map +1 -1
  709. package/dist/core/provenance/stamp.js +5 -17
  710. package/dist/core/provenance/stamp.js.map +1 -1
  711. package/dist/core/repo/authored/paths.js +18 -0
  712. package/dist/core/repo/authored/paths.js.map +1 -1
  713. package/dist/core/repo/entries.js.map +1 -1
  714. package/dist/core/repo/paths.js +92 -6
  715. package/dist/core/repo/paths.js.map +1 -1
  716. package/dist/core/repo/repo.js +74 -14
  717. package/dist/core/repo/repo.js.map +1 -1
  718. package/dist/core/repo/tree/find.js +12 -3
  719. package/dist/core/repo/tree/find.js.map +1 -1
  720. package/dist/core/repo/tree/fs.js +27 -1
  721. package/dist/core/repo/tree/fs.js.map +1 -1
  722. package/dist/core/repo/tree/render/body.js +258 -0
  723. package/dist/core/repo/tree/render/body.js.map +1 -0
  724. package/dist/core/repo/tree/render/projects.js +182 -0
  725. package/dist/core/repo/tree/render/projects.js.map +1 -0
  726. package/dist/core/repo/tree/render/service/header.js +125 -0
  727. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  728. package/dist/core/repo/tree/render/service/peers.js +28 -0
  729. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  730. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  731. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  732. package/dist/core/repo/tree/render/service/views.js +78 -0
  733. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  734. package/dist/core/repo/tree/render/stale.js +42 -0
  735. package/dist/core/repo/tree/render/stale.js.map +1 -0
  736. package/dist/core/repo/tree/render/text.js +84 -0
  737. package/dist/core/repo/tree/render/text.js.map +1 -0
  738. package/dist/core/repo/tree/render/views.js +293 -0
  739. package/dist/core/repo/tree/render/views.js.map +1 -0
  740. package/dist/core/repo/tree/walk.js +53 -32
  741. package/dist/core/repo/tree/walk.js.map +1 -1
  742. package/dist/core/results.js +47 -12
  743. package/dist/core/results.js.map +1 -1
  744. package/dist/core/scaffold/landscape.js +121 -13
  745. package/dist/core/scaffold/landscape.js.map +1 -1
  746. package/dist/core/scaffold/legacy.js +271 -0
  747. package/dist/core/scaffold/legacy.js.map +1 -0
  748. package/dist/core/staging/commit.js +26 -15
  749. package/dist/core/staging/commit.js.map +1 -1
  750. package/dist/core/staging/lock.js +74 -12
  751. package/dist/core/staging/lock.js.map +1 -1
  752. package/dist/core/staging/recovery/intent.js +2 -5
  753. package/dist/core/staging/recovery/intent.js.map +1 -1
  754. package/dist/core/staging/recovery/recover.js +84 -6
  755. package/dist/core/staging/recovery/recover.js.map +1 -1
  756. package/dist/core/staging/snapshot/manifest.js +188 -0
  757. package/dist/core/staging/snapshot/manifest.js.map +1 -0
  758. package/dist/core/staging/{snapshot.js → snapshot/snapshot.js} +39 -45
  759. package/dist/core/staging/snapshot/snapshot.js.map +1 -0
  760. package/dist/core/staging/txn/forward.js +24 -14
  761. package/dist/core/staging/txn/forward.js.map +1 -1
  762. package/dist/core/staging/writes.js.map +1 -1
  763. package/dist/core/status/actions/execution.js +11 -0
  764. package/dist/core/status/actions/execution.js.map +1 -1
  765. package/dist/core/status/contracts.js +39 -15
  766. package/dist/core/status/contracts.js.map +1 -1
  767. package/dist/core/status/feature/artifacts.js +33 -8
  768. package/dist/core/status/feature/artifacts.js.map +1 -1
  769. package/dist/core/status/feature/feature.js +24 -17
  770. package/dist/core/status/feature/feature.js.map +1 -1
  771. package/dist/core/status/feature/next.js +20 -7
  772. package/dist/core/status/feature/next.js.map +1 -1
  773. package/dist/core/status/feature/words.js +96 -0
  774. package/dist/core/status/feature/words.js.map +1 -0
  775. package/dist/core/status/fleet/fleet.js +43 -14
  776. package/dist/core/status/fleet/fleet.js.map +1 -1
  777. package/dist/core/status/fleet/next.js +153 -58
  778. package/dist/core/status/fleet/next.js.map +1 -1
  779. package/dist/core/status/report.js.map +1 -1
  780. package/dist/core/status/scan.js.map +1 -1
  781. package/dist/core/status/verification.js +30 -15
  782. package/dist/core/status/verification.js.map +1 -1
  783. package/dist/core/tasks/packet.js +199 -0
  784. package/dist/core/tasks/packet.js.map +1 -0
  785. package/dist/core/tasks/record.js +43 -0
  786. package/dist/core/tasks/record.js.map +1 -0
  787. package/dist/core/tasks/sources.js +181 -0
  788. package/dist/core/tasks/sources.js.map +1 -0
  789. package/dist/core/tasks/stamps.js +76 -0
  790. package/dist/core/tasks/stamps.js.map +1 -0
  791. package/dist/core/tasks/unit.js +0 -0
  792. package/dist/core/tasks/unit.js.map +1 -0
  793. package/dist/core/usecases/capability.js +11 -14
  794. package/dist/core/usecases/capability.js.map +1 -1
  795. package/dist/core/usecases/delta/claims.js +31 -7
  796. package/dist/core/usecases/delta/claims.js.map +1 -1
  797. package/dist/core/usecases/delta/flows.js +5 -7
  798. package/dist/core/usecases/delta/flows.js.map +1 -1
  799. package/dist/core/usecases/delta/modified.js +315 -0
  800. package/dist/core/usecases/delta/modified.js.map +1 -0
  801. package/dist/core/usecases/delta/overlay.js +32 -191
  802. package/dist/core/usecases/delta/overlay.js.map +1 -1
  803. package/dist/core/usecases/fleet.js +195 -36
  804. package/dist/core/usecases/fleet.js.map +1 -1
  805. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/capability-tag.js +8 -9
  806. package/dist/core/usecases/grade/capability-tag.js.map +1 -0
  807. package/dist/core/usecases/grade/feature.js +88 -0
  808. package/dist/core/usecases/grade/feature.js.map +1 -0
  809. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/requirement-tag.js +3 -4
  810. package/dist/core/usecases/grade/requirement-tag.js.map +1 -0
  811. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/steps.js +102 -33
  812. package/dist/core/usecases/grade/steps.js.map +1 -0
  813. package/dist/{commands/validate/fleet/usecases → core/usecases/grade}/usecases.js +23 -10
  814. package/dist/core/usecases/grade/usecases.js.map +1 -0
  815. package/dist/core/usecases/operations.js +26 -14
  816. package/dist/core/usecases/operations.js.map +1 -1
  817. package/dist/core/usecases/place.js.map +1 -1
  818. package/dist/core/usecases/service/exclude.js +119 -0
  819. package/dist/core/usecases/service/exclude.js.map +1 -0
  820. package/dist/core/usecases/service/flows.js +164 -0
  821. package/dist/core/usecases/service/flows.js.map +1 -0
  822. package/dist/core/usecases/suites/participants.js +83 -0
  823. package/dist/core/usecases/suites/participants.js.map +1 -0
  824. package/dist/core/usecases/suites/suites.js +45 -0
  825. package/dist/core/usecases/suites/suites.js.map +1 -0
  826. package/dist/core/verify/answers.js +12 -5
  827. package/dist/core/verify/answers.js.map +1 -1
  828. package/dist/core/verify/build.js +102 -82
  829. package/dist/core/verify/build.js.map +1 -1
  830. package/dist/core/verify/checklist.js +70 -12
  831. package/dist/core/verify/checklist.js.map +1 -1
  832. package/dist/core/verify/location.js +67 -0
  833. package/dist/core/verify/location.js.map +1 -0
  834. package/dist/core/verify/pins/lint.js +3 -2
  835. package/dist/core/verify/pins/lint.js.map +1 -1
  836. package/dist/core/verify/pins/pin.js +1 -1
  837. package/dist/core/verify/pins/pin.js.map +1 -1
  838. package/dist/core/verify/read/file.js +224 -0
  839. package/dist/core/verify/read/file.js.map +1 -0
  840. package/dist/core/verify/{file.js → read/shape.js} +16 -104
  841. package/dist/core/verify/read/shape.js.map +1 -0
  842. package/dist/core/verify/read/union.js +105 -0
  843. package/dist/core/verify/read/union.js.map +1 -0
  844. package/dist/core/verify/record.js +35 -12
  845. package/dist/core/verify/record.js.map +1 -1
  846. package/dist/core/verify/store/commit.js +40 -30
  847. package/dist/core/verify/store/commit.js.map +1 -1
  848. package/dist/core/verify/store/render.js +8 -4
  849. package/dist/core/verify/store/render.js.map +1 -1
  850. package/dist/core/vocabulary/issue.js +4 -0
  851. package/dist/core/vocabulary/issue.js.map +1 -1
  852. package/dist/core/vocabulary/maturity.js +75 -4
  853. package/dist/core/vocabulary/maturity.js.map +1 -1
  854. package/dist/core/vocabulary/report.js +16 -1
  855. package/dist/core/vocabulary/report.js.map +1 -1
  856. package/examples/README.md +54 -11
  857. package/examples/docs/architecture/landscape.likec4 +62 -6
  858. package/examples/docs/architecture/services.likec4 +70 -0
  859. package/examples/docs/architecture/subsystems.likec4 +7 -0
  860. package/examples/docs/architecture/usecases/order-notification.likec4 +16 -7
  861. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-service/asyncapi.yaml +11 -8
  862. package/examples/docs/features/FEAT-101-payment-splitting/specs/payment-split-service/openapi.yaml +6 -6
  863. package/examples/docs/features/FEAT-112-retire-order-v1/specs/order-service/openapi.yaml +4 -1
  864. package/examples/docs/features/archive/FEAT-088-refunds/specs/payment-service/openapi.yaml +13 -10
  865. package/examples/docs/likec4.config.json +0 -1
  866. package/examples/docs/services/checkout-web/model.likec4 +39 -56
  867. package/examples/docs/services/order-service/arch.spec.md +13 -6
  868. package/examples/docs/services/order-service/asyncapi.yaml +3 -2
  869. package/examples/docs/services/order-service/model.likec4 +58 -70
  870. package/examples/docs/services/payment-service/arch.spec.md +7 -4
  871. package/examples/docs/services/payment-service/model.likec4 +38 -37
  872. package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
  873. package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
  874. package/examples/docs/services/platform/notification-service/asyncapi.yaml +2 -1
  875. package/examples/docs/services/platform/notification-service/health.yaml +8 -3
  876. package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
  877. package/package.json +1 -1
  878. package/dist/commands/validate/checks/vocabulary.js +0 -31
  879. package/dist/commands/validate/checks/vocabulary.js.map +0 -1
  880. package/dist/commands/validate/fleet/usecases/capability-tag.js.map +0 -1
  881. package/dist/commands/validate/fleet/usecases/requirement-tag.js.map +0 -1
  882. package/dist/commands/validate/fleet/usecases/steps.js.map +0 -1
  883. package/dist/commands/validate/fleet/usecases/usecases.js.map +0 -1
  884. package/dist/core/agent/agents-md/artifacts.js +0 -220
  885. package/dist/core/agent/agents-md/artifacts.js.map +0 -1
  886. package/dist/core/agent/agents-md/cycle.js +0 -165
  887. package/dist/core/agent/agents-md/cycle.js.map +0 -1
  888. package/dist/core/agent/agents-md/shipped/archive-gate.js +0 -98
  889. package/dist/core/agent/agents-md/shipped/archive-gate.js.map +0 -1
  890. package/dist/core/agent/agents-md/spine.js +0 -40
  891. package/dist/core/agent/agents-md/spine.js.map +0 -1
  892. package/dist/core/coherence/events/merged.js +0 -40
  893. package/dist/core/coherence/events/merged.js.map +0 -1
  894. package/dist/core/diff/current-state.js +0 -96
  895. package/dist/core/diff/current-state.js.map +0 -1
  896. package/dist/core/diff/victims.js +0 -247
  897. package/dist/core/diff/victims.js.map +0 -1
  898. package/dist/core/repo/tree/views.js +0 -172
  899. package/dist/core/repo/tree/views.js.map +0 -1
  900. package/dist/core/staging/snapshot.js.map +0 -1
  901. package/dist/core/verify/file.js.map +0 -1
@@ -56,13 +56,22 @@ with \`gates: true\` will stop \`loam archive\` even when it is a warning. Fix e
56
56
  error and every gating finding. Leave an advisory warning only if you can say why,
57
57
  and say it.
58
58
 
59
+ A clean run grades the joins and never the picture. No check reads a view or a
60
+ relationship's kind, so a fleet can validate with its arrows guessed and every
61
+ overview unreadable. When the work drew edges or views, open them
62
+ (\`npx likec4 start\` in the docs repo) and hold them against
63
+ \`loam instructions loam-drawing\` before calling it done.
64
+
59
65
  \`--service <id>\` — one service's own axes (\`--all\` runs these for every service):
60
66
 
61
67
  | code | what it means | what to do |
62
68
  |---|---|---|
63
- | \`service.unknown\` | no \`services/<id>/\` at all — the id is a typo until proven otherwise | use one of the ids the message offers, or \`loam list services\`; never \`loam adopt\` a misspelling |
69
+ | \`service.unknown\` | no \`services/<id>/\` at all — the id is a typo until proven otherwise. The proof otherwise is an active feature that INTRODUCES the service: its only documents are that feature's \`specs/<id>/\` until it archives, and the message names the feature instead of a near-miss. A bare \`loam validate\` in a repository bound to such a service never raises it — it validates the introducing features instead, says so first, and \`--json\` carries \`boundService\` | use one of the ids the message offers, or \`loam list services\`; never \`loam adopt\` a misspelling. When the message names a feature that introduces the service there is nothing to adopt — adopting would write \`services/<id>/\` ahead of the merge: run \`loam validate --feature <FEAT-id>\` |
64
70
  | \`service.no-model\` | the directory is real but there is no model.likec4 | run \`loam adopt\` for it — without the C4 center nothing else has anywhere to hang |
65
- | \`c4.invalid\` | model.likec4 does not parse | fix this first — an unreadable axis makes every other check meaningless |
71
+ | \`c4.invalid\` | model.likec4 does not parse. How it was READ depends on the file's own grammar (SCHEMA.md, "Two shapes of a service model"): a model declaring its own \`specification\` is parsed ALONE, exactly as before; a model with none EXTENDS the fleet map and is parsed together with \`architecture/landscape.likec4\`. In the second case every error belongs to the model even when the message names the map — the map parsed clean by itself, or \`spine.landscape-invalid\` would be standing here instead and this model could not be read at all | fix this first — an unreadable axis makes every other check meaningless. For an extending model the usual causes are a kind the map does not declare (add \`element database\` to the map's one \`specification\` block), an \`extend\` naming a partial id (it takes the FULLY-QUALIFIED id — \`marketplace.orderService\`, never \`orderService\`), or a \`description\` written inside the \`extend\` block, which belongs on the map's declaration. A model that declares an element kind AND writes \`extend\` is the standalone shape with an \`extend\` that can never resolve; the details say so and name the two lines, one of which has to go |
72
+ | \`landscape.service-unmodelled\` | the same code as the fleet row below, raised on the SERVICE target when this service's model.likec4 EXTENDS the fleet map and no element in \`architecture/\` resolves to \`services/<id>/\`. An extending model says what is INSIDE the element the map binds; with no such element there is nothing to be inside, so no count in the model means anything and neither \`c4.valid\` nor \`c4.invalid\` is claimed. A model that STANDS ALONE never reaches it here — it answers for its own elements, and the fleet run owns that state | draw the element, or bind an existing one with \`metadata { service '<id>' }\`; then re-run |
73
+ | \`c4.declaration-diverged\` (warn) | a model that STANDS ALONE declares an element the fleet map (\`architecture/\`) also declares, and the two copies disagree about \`kind\`, \`title\`, the tag SET or the \`metadata { service }\` binding. The two declarations 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 — so a map that GROUPS its services under a parent is compared like any other, and an ambiguous tail is compared with nothing. A tail that is a BARE last segment (\`db\`, \`api\`, \`agent\`) joins only 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 — because one service's private store and another service's store are two elements owned by two services, not one element declared twice, while a box the map draws at fleet level and a model re-declares at top level is one name given two meanings. Two documents are two authorities on one element, and until now nothing read them together: a copy losing \`#platform\` changes what every view filtering on that tag shows, and no check said so. One finding per divergent element, naming both declarations and which fields differ; \`description\` is deliberately NOT compared, because it is prose nothing grades, while the other four each change what a view shows or what loam resolves. Silent while the map does not parse, and impossible for a model that extends the map — there is one declaration | copy the map's declaration verbatim into the model, treating the map as the authority; or migrate the model to the extending shape (the ordered recipe is in SCHEMA.md, "Two shapes of a service model"), after which the element is declared once and cannot diverge. When the message says copying the map's declaration would not clear it, the difference comes off \`specification { element <kind> { … } }\` and no copy can fix it — the message names the document whose specification declares tags on that kind, and the repair is to match the two blocks there, or to migrate to the extending shape so there is one specification and one declaration |
74
+ | \`c4.element-unowned\` (warn) | a model that EXTENDS the fleet map declares an element outside the element that resolves to this service — one finding per element, with its id and kind. An extending model adds elements under its own element and nowhere else: what it adds elsewhere lands in the fleet project as this service's private copy of somebody else's box | a store or component this service OWNS goes inside the \`extend <fqn> { }\` block, where its id becomes \`<fqn>.<name>\` — that is the usual answer and the one the old wording omitted. Otherwise: declare a system this service merely REACHES in \`architecture/landscape.likec4\`, once, and point an edge at it from the container that reaches it; put another service's internals in that service's own model |
66
75
  | \`c4.no-relationships\` (warn) | the model parses, declares elements, and no relationship joins anything — while its own evidence says it should reach something: more than one nested element, or dependencies declared in this service's health.yaml. A model that reaches nothing is almost never true; without this check, a 2-element model with 0 relationships and a 19-element model with 19 relationships could receive the same green verdict | draw the edges the service actually has — what its containers call, what reads and writes its stores, what it depends on at runtime. A bare root-plus-one-container baseline with no other evidence stays silent on purpose: nothing proves it thin |
67
76
  | \`requirements.missing-scenarios\` | a requirement with no scenario | add the acceptance criteria; do not delete the requirement |
68
77
  | \`requirements.stepless-scenario\` | a scenario with a heading and no recognizable steps — it satisfies the coverage rule and tests nothing: cucumber runs it vacuously green and \`loam verify --results\` can never confirm it. Graded on spec.md and arch.spec.md alike, in \`--service\` and \`--feature\` scope both, because a stepless scenario in a delta merges into the living spec and is called covered forever after | write the body as \`- **Given/When/Then**\` bullets |
@@ -74,7 +83,7 @@ and say it.
74
83
  | \`spec.requirement-id-invalid\` | a \`Requirement-ID\` violates \`[A-Za-z][A-Za-z0-9._-]{0,127}\` | replace it with a portable stable ID; do not remove an established ID merely to silence the check |
75
84
  | \`spec.requirement-id-repeated\` | one requirement declares \`Requirement-ID\` more than once | keep exactly one identity line |
76
85
  | \`spec.requirement-id-duplicate\` | one ID identifies multiple requirements in the same file | give each requirement its own stable ID |
77
- | \`openapi.generated-stale\` (warn) | \`loam.json\` names \`contracts.openapi\` — where this repo's BUILD writes its contract — and that document differs from the committed \`services/<id>/openapi.yaml\`. The docs-repo copy is what every operation join is graded against, so the fleet is being checked against a contract the service no longer serves. Compared by a digest over canonical JSON, so key order and formatting never fire it. Service-repo only; silent with no \`contracts\` block | review the difference and copy the generated document across — loam never writes it for you, because the committed contract is one somebody agreed to, not a cache of the build |
86
+ | \`openapi.generated-stale\` (warn) | \`loam.json\` names \`contracts.openapi\` — where this repo's BUILD writes its contract — and that document differs from the committed \`services/<id>/openapi.yaml\`. The docs-repo copy is what every operation join is graded against, so the fleet is being checked against a contract the service no longer serves. Compared by a digest over canonical JSON, so key order and formatting never fire it. The \`details\` name the operationIds that differ — served but not in the committed copy, committed but no longer served — and the message says when the sets agree, so a reworded description reads apart from vanished endpoints. Service-repo only; silent with no \`contracts\` block | review the difference and copy the generated document across — loam never writes it for you, because the committed contract is one somebody agreed to, not a cache of the build |
78
87
  | \`contracts.source-missing\` (error) | \`contracts.openapi\` names a path and nothing is there — usually CI validating before it builds | run the build first, or correct the path |
79
88
  | \`contracts.source-invalid\` (error) | \`contracts.openapi\` resolves outside the repository, or names a file that does not parse as YAML or JSON | correct the path, or fix the document the build emitted |
80
89
  | \`spec.unknown-directive\` (warn) | a requirement body line whose key is one or two edits from a directive and is not one — \`Realises:\`, \`Capabilties:\`, \`Opertaions:\`. This is the one case where the corpus is quieter than it looks: an unrecognised key is prose, so the join it resembles simply does not exist and every check over it stays green for want of anything to fail. Measured by edit distance, not by prefix, so ordinary prose keys (\`Note:\`, \`Context:\`, \`Owner:\`) do not fire it; fenced blocks and table cells are skipped, because a directive spelling inside an example is a sample value | correct the spelling, or reword the line so it does not read as a directive |
@@ -91,17 +100,17 @@ and say it.
91
100
  | \`openapi.ref-unresolved\` (warn here; error at archive plan time) | internal \`$ref\`(s) in the living contract that resolve to nothing in the document — the operation validates while its schema is silently absent. External refs (not starting \`#/\`) are never checked | define the missing component or fix the pointer the \`details\` name |
92
101
  | \`openapi.baseline-stale\` | the living operation — or a pinned path-level key or component — changed after this delta edited it | somebody landed a change to it in between. Re-read the living value, fold in what you still mean, then \`loam rebase <FEAT>\` |
93
102
  | \`openapi.baseline-missing\` (warn, gates archive; one per service) | operations, path-level keys or components in this feature's openapi.yaml carry no baseline (\`x-loam-based-on\` / \`x-loam-baselines\`) | run \`loam rebase <FEAT>\`. Until you do, archive refuses: the merge cannot tell the surfaces you EDITED from the ones you merely restated, so it would upsert all of them — and every restated one reverts whatever landed on it. \`--approve\` merges unpinned deliberately |
94
- | \`openapi.baseline-invalid\` | an \`x-loam-based-on\` that is not a digest, one on an operation the living contract has no slot for, a malformed \`x-loam-baselines\` record, or a record entry naming a surface the delta does not restate (or whose living value is gone) | \`loam rebase\` writes both; a new value has no living version to be based on, so it carries no entry |
103
+ | \`openapi.baseline-invalid\` | an \`x-loam-based-on\` that is not a digest, one on an operation the living contract has no slot for, a malformed \`x-loam-baselines\` record, or a record entry naming a surface the delta does not restate (or whose living value is gone) | \`loam rebase\` writes both; a new value has no living version to be based on, so it carries no entry. A pinned operation whose living one a parallel feature has since REMOVED lands here too, and \`openapi.op-revived\` fires beside it — the pinned quote reports both: re-read the living contract and delete the whole operation from the delta — dropping only the pin clears this one and leaves \`openapi.op-revived\`, because unpinned it reads as an addition. Keep it only if this feature deliberately brings the operation back: then drop the pin and archive with \`--approve\` past \`openapi.op-revived\`. One a parallel feature MOVED to another slot (or retired here, before another re-added it elsewhere) lands here as well, and the message names where it lives now: delete the quote and make any change there — archiving past it while it still lives there would answer the operationId at both addresses; to move it back on purpose, drop the pin and add a removal marker at that address, or define another operation there. If you edited it, settle with that feature first |
95
104
  | \`spec-api.op-undefined\` | a LIVING requirement's \`Operations:\` line names an operationId this service's openapi.yaml does not define (the same code fires inside a feature delta) | define the endpoint, or correct the \`Operations:\` line — the two axes disagree about what the service exposes |
96
105
  | \`api.ungoverned\` (warn) | operation(s) no requirement's \`Operations:\` line names | write the requirement, or link an existing one |
97
106
  | \`api.ops-unlinked\` (warn) | operations AND requirements exist but zero \`Operations:\` lines join them — the API axis is vacuously green | link each requirement to the operations it governs |
98
107
  | \`api.requirement-deprecated\` (warn) | a requirement's \`Operations:\` list resolves only to operations the OpenAPI marks \`deprecated: true\` — the behaviour it governs is on its way out | migrate the requirement to the replacement operation, or retire it with the ops it governs |
99
- | \`spine.landscape-invalid\` | the living landscape does not parse, so the C4↔API spine cannot be checked | fix architecture/landscape.likec4 first |
108
+ | \`spine.landscape-invalid\` | the \`architecture/\` project does not parse, so the C4↔API spine cannot be checked. The whole project, not the landscape file alone: a \`architecture/usecases/x.likec4\` that does not read reports this on a single-service run exactly as under \`--all\`. For a service whose model EXTENDS the map the message carries one clause more — the model cannot be read either, so neither \`c4.valid\` nor \`c4.invalid\` is emitted for it and every model-dependent check on that service is suspended rather than failing. The message names the broken document(s) repo-relative and \`locations[0]\` points at the first of them | fix the file the message names, under \`architecture/\`, before anything else — one unreadable document there suspends the cross-service layer for the whole fleet |
100
109
  | \`spine.op-undefined\` | a landscape edge calls an operation this service's OpenAPI does not define | a broken contract between services — fix the edge or add the endpoint |
101
110
  | \`spine.op-link-missing\` (warn) | a landscape "Calls" edge into this service with no \`metadata { op }\` | link it to the operationId |
102
111
  | \`spine.op-deprecated\` (warn) | a landscape edge calls an operation this service's OpenAPI marks \`deprecated: true\` — the consumer is standing on a contract being retired | migrate the consumer to the replacement operation; deprecation is the first step of retiring an op, and the op stays defined until a human removes it |
103
112
  | \`service.no-asyncapi\` | no asyncapi.yaml, and message link(s) already point into it — a landscape edge's \`metadata { publishes }\` / \`metadata { consumes }\`, or a LIVING requirement's \`Publishes:\` / \`Consumes:\` line. The \`details\` list the stranded message names. Silent when nothing joins into it: an async contract is optional, and most services touch no topic | put the file back — every link the \`details\` name resolves to nothing until it is there |
104
- | \`asyncapi.invalid\` | asyncapi.yaml exists but does not parse — an unreadable contract proves nothing, so no \`event.*\` or message-spine finding is graded against it. The same code fires for a FEATURE's own asyncapi.yaml under \`--feature\`, where it gates the archive and suspends the service's event-axis checks the same way | fix the YAML first — the event axis is unchecked until it reads |
113
+ | \`asyncapi.invalid\` | asyncapi.yaml exists but does not read as AsyncAPI 3.0: it does not parse, or its \`asyncapi:\` field says 1.x or 2.x (whose \`publish\`/\`subscribe\` loam does not read — the \`details\` name the version). An unreadable contract proves nothing, so no \`event.*\` or message-spine finding is graded against it. The same code fires for a FEATURE's own asyncapi.yaml under \`--feature\`, where it gates the archive and suspends the service's event-axis checks the same way | fix the YAML first, or upgrade an older document with \`asyncapi convert --target-version=3.0.0\` (the AsyncAPI CLI) — the event axis is unchecked until it reads |
105
114
  | \`asyncapi.remove-marker-living\` | a living asyncapi.yaml carries feature-only \`x-loam-remove: true\` — at a \`channels.<key>\` / \`operations.<key>\` / \`components.messages.<key>\` slot, or nested on an inline channel message | remove the marker from the living contract; retire a slot through a feature delta's asyncapi.yaml instead |
106
115
  | \`asyncapi.duplicate-message\` (warn) | one message name is declared in two slots — every join on the name (an edge's \`metadata { publishes }\`, a requirement's \`Publishes:\` line) then picks one of them arbitrarily | give each declaration its own name, or factor the second into a \`$ref\` at the first |
107
116
  | \`asyncapi.payload-undescribed\` (warn) | message(s) whose payload declares no shape at all — absent, or bare \`type: object\` with not one property. The name joins the spine while nothing defines what a consumer would read. A presence probe: a payload declaring a non-JSON \`schemaFormat\` (Avro) is never judged | declare \`properties\` on the payload, or the \`schemaFormat\` of its non-JSON schema |
@@ -110,10 +119,17 @@ and say it.
110
119
  | \`spec-event.message-undefined\` | a LIVING requirement's \`Publishes:\` / \`Consumes:\` line names a message this service's asyncapi.yaml does not declare in that direction | declare it, or correct the line |
111
120
  | \`spine.message-unproduced\` | this service consumes a message that NO service in the fleet and NO \`#external\` element declares is sent — no producer anywhere. The inverse of \`spine.op-undefined\`, and it has no HTTP analog: on the API axis the provider owns the contract and the check is local, while an event's schema lives in the producer's repo | find the producer and have it declare the message (an outside-the-fleet producer is declared on its \`#external\` element: an edge with \`metadata { publishes '<msg>' }\`), or correct the name — a consumer joined to nothing reads a payload nothing defines |
112
121
  | \`spine.message-external\` (warn) | this service consumes a message whose only declared producer is an \`#external\` element — produced outside the fleet, so no contract will ever appear in a \`services/<id>/\`, and this service's own asyncapi.yaml currently defines no shape for it. Fires on positive evidence (the \`publishes\` edge), so unreadable contracts elsewhere do not suspend it | declare the message in THIS service's asyncapi.yaml with its payload — the consumer's copy is the only contract the fleet can hold; once it declares a shape, the finding stops |
122
+ | \`spine.message-unbound\` (warn) | a landscape edge this service drew carries an event key on the wrong end: \`publishes\` binds the edge's SOURCE and \`consumes\` its TARGET, and that end is infrastructure (a \`topic\`, \`queue\`, \`database\`, \`cache\` or \`bucket\`, itself or by an ancestor), which neither publishes nor consumes, while this service sits at the other one. The key binds nothing, so no contract is checked against it and the spine counts one link fewer — it used to say nothing at all. An \`#external\` system at that end is an outside producer or consumer, drawn correctly, and stays silent | turn the edge round, as the message spells it: the arrow follows the message, producer \`-[publishes]->\` topic and topic \`-[delivers]->\` consumer (\`loam instructions loam-drawing\`). An edge carrying both keys cannot serve both: move the key the message names onto an edge of its own |
113
123
  | \`asyncapi.message-contested\` (warn) | two or more services declare they send one message name — every consumer's join picks one of them arbitrarily | namespace the message by its owning domain; one message has one producer, and which contract a consumer reads must not be a coin flip |
114
124
  | \`event.messages-unlinked\` (warn) | messages AND requirements exist but zero \`Publishes:\`/\`Consumes:\` lines join them — the event axis is vacuously green | link each requirement to the messages it governs |
115
125
  | \`event.ungoverned\` (warn) | message(s) declared in asyncapi.yaml that no requirement's \`Publishes:\`/\`Consumes:\` line names — \`api.ungoverned\`'s event twin, and the per-message half \`event.messages-unlinked\` cannot see: that code stays silent once even one message is linked | write the requirement that governs each named message, or drop the declaration |
116
- | \`covers.unknown\` (warn) | a \`Covers:\` entry in arch.spec.md resolves to no element, edge, alert or SLI — living and feature deltas alike | fix the id (the message offers close ones); a mistyped entry silently costs the coverage it was written for |
126
+ | \`covers.unknown\` (warn) | a \`Covers:\` entry in arch.spec.md resolves to no element, edge, alert or SLI — living and feature deltas alike. An edge entry may carry the relationship's kind as LikeC4 writes the edge, \`a -[kind]-> b\` or \`a .kind b\`: the kind is read past, not compared, so it covers what \`a -> b\` covers. Not raised for a service whose model.likec4 could not be read (\`c4.invalid\`, or \`spine.landscape-invalid\` for a model that extends the map): nothing is graded against a model nobody could read | fix the id (the message offers close ones); a mistyped entry silently costs the coverage it was written for |
127
+ | \`usecase.step-unbacked\` | a hop of a \`#req-\`-tagged \`dynamic view\` in this service's own LikeC4 project — any \`.likec4\` beside model.likec4, or a view inside it — that NO relationship in model.likec4 backs. LikeC4 reports no error for it, so the diagram renders and the renderer's project parses; only this convicts it. The message reads \`<svc>: services/<tree>/<file> — dynamic view '<id>' step N\` | draw the edge in \`model { }\` (with \`metadata { op '<operationId>' }\` where it is a call), or, if the hop is a return step, write it as \`<target> <- <source>\` |
128
+ | \`usecase.step-contested\` (warn) | two or more relationships in model.likec4 back one hop of a service-local flow and name DIFFERENT operations — an absent \`metadata { op }\` counts as a value — so nothing can say which one the hop exercises; \`details\` lists every candidate | open the step's title with the operation id (or message name) the hop means, then punctuation — \`'createOrder — places the order'\` — or give the candidates one operation, or draw the hop between the elements that name exactly one |
129
+ | \`usecase.step-unlinked\` (warn) | the same code as the fleet row below, raised on a service-local flow: the relationship backing a hop of a \`#req-\`-tagged \`dynamic view\` beside model.likec4 carries no \`metadata { op }\` and no \`publishes\`/\`consumes\`, while the hop's caller is not a person and its target resolves to a real \`services/<id>/\` — the stand-in a model declares for a sibling service is the case that reaches it here. Never between two containers of THIS service: a service owes no operationId to itself. The \`--all\` row spells out all four mechanical guards | add \`metadata { op '<operationId>' }\` to the edge in \`model { }\`, or \`metadata { publishes '<message>' }\` / \`consumes\` where the hop is an event |
130
+ | \`usecase.requirement-unresolved\` | a \`#req-<slug>\` tag on a flow beside model.likec4 whose slug names no \`Requirement-ID\` of THIS service — scoped by the directory, never by a \`#cap-\` tag: the ids of spec.md and arch.spec.md together. Four arms: neither document exists, neither declares a \`Requirement-ID:\`, none flattens to the slug (close ids offered as the tag to write), or two flatten alike — a Requirement-ID is unique inside ONE document and this service has two | add \`Requirement-ID: <id>\` to the requirement this flow keeps, or fix the slug; for two colliding ids, rename one |
131
+ | \`usecase.capability-unresolved\` | a \`#cap-\` tag on a \`dynamic view\` inside this service's own project, where no capability can be claimed — a capability is a fleet promise realized by a hop sequence across the fleet map, and this flow's hops are this service's containers. Fires whether or not \`architecture/capabilities.yaml\` exists: the reason is placement, not vocabulary. The view stays opted in and its steps are graded | move the flow to \`architecture/usecases/\` if it crosses services, else drop the \`#cap-\` tag and keep the \`#req-\` one |
132
+ | \`usecase.flow-invalid\` | a \`.likec4\` beside model.likec4 that does not parse as part of the service's LikeC4 project — an unresolved element, a syntax error, or a \`specification\` block the project already has (that is a duplicate whether the model declares one or the fleet map does). Every \`.likec4\` under the service directory is read as ONE project the way the renderer reads it — for an extending model that project is the fleet map plus those documents — and the renderer refuses the whole project over one broken file; no flow in it was graded. model.likec4 itself still parses and is graded (\`c4.valid\`, \`Covers:\`, health) — this is never \`c4.invalid\`. A sibling the service's OWN \`likec4.config.json\` \`exclude\` covers is not in that project and is not graded | fix the file the finding names (\`locations[0]\`, and one \`details\` line per error prefixed by its file) — the renderer refuses the same project until it reads. An undeclared reserved tag adds one further \`details\` line naming the \`tag <name>\` declaration to write and the \`specification { }\` block it belongs in |
117
133
  | \`api.response-ungoverned\` (warn) | an operation declares a 4xx/5xx response that no scenario of any requirement governing it reaches. \`api.ungoverned\` grades whole OPERATIONS, so an endpoint with one happy-path requirement and twelve declared failure codes was fully governed by every other check — and the refusals are where the decision layer lives | write the refusal as a scenario, or as a row in an existing one. A scenario reaches a code when the number appears in its body, \`Examples\` rows included |
118
134
  | \`obligation.unknown\` | a \`#obl-<name>\` tag on the LIVING fleet map that \`architecture/obligations.yaml\` does not declare. An ERROR, and the asymmetry with its sibling below is the point: a tag that resolves to nothing reads exactly like a rule the fleet keeps, in review and in every derived view. \`details\` names every object carrying it | fix the tag (the message offers close names), or declare the obligation |
119
135
  | \`obligation.unapplied\` (warn) | a declared obligation that no \`#obl-\` tag applies anywhere on the map — the mirror of \`permissions.unenforced\`, and it earns its place for the same reason: a vocabulary is worth what cites it. Either a decision that was reversed and left its word behind, or one nobody has placed yet | tag the elements and edges it governs, or drop the declaration |
@@ -133,29 +149,24 @@ and say it.
133
149
  | \`capability.realizes-unknown\` | a \`Realizes:\` entry in spec.md or arch.spec.md — living at the service target, or a feature's delta of either, where it gates \`loam archive\` (\`--approve\` overrides) — that names no capability requirement. Five distinct failures, each with its own fix in the message: the entry is not spelled \`<capability-id>#<Requirement-ID>\`; the capability is undeclared; it is declared but has no document; the document declares no requirements yet; or it declares requirements but not this id. Silent under the same ladder as \`capability.unknown\` | follow the message — it names which of the five happened, and offers close names where there are any |
134
150
  | \`capability.realizes-stale\` (warn) | a PINNED \`Realizes:\` entry whose capability requirement has been rewritten since the pin was stamped. The join still resolves — the address is fine and the promise behind it moved, which no existence check can see. Only pinned entries are graded, so a corpus that has never run \`loam rebase --living\` produces none of these, ever | re-read \`capabilities/<id>/spec.md\` and decide which side is now wrong: change this requirement, change the promise, or — when this requirement is still right as it stands — \`loam rebase --living\` to record the re-read |
135
151
  | \`capability.requirement-unrealized\` (warn) | a requirement in a capability document that no living service requirement's \`Realizes:\` line names, one warning PER requirement, subject = the \`<capability>#<id>\` entry an author would type to fix it. The sharper sibling of \`capability.unrealized\`, and the one that survives a healthy-looking row: a capability with four requirements and three realized reports nothing at all through the other code | add the line to each service requirement that carries part of the promise — or leave it, if the promise is genuinely not implemented yet: writing the business document ahead of the fleet is the intended use, which is why this warns and never gates |
136
- | \`capability.uncovered\` (warn, GATES archive) | a requirement a feature's \`features/<FEAT>/capabilities/<id>/spec.md\` ADDS that no ADDED or MODIFIED requirement in the same feature's \`specs/\` deltas names with \`Realizes: <capability-id>#<Requirement-ID>\`, subject = the capability id. A WARNING because the document is legal — writing a promise ahead of the fleet is the intended use — that GATES because the merge is what is unsafe: once it lands, the only thing that will ever mention the promise again is \`capability.requirement-unrealized\`, a fleet-scope warning nobody reads. MODIFIED is exempt (the promise may already be realized by living text), and a requirement with no \`Requirement-ID:\` is left to \`capability.requirement-unidentified\` alone | add the \`Realizes:\` line to the service requirement that carries part of the promise; or, if the promise CROSSES services, write the flow that keeps it as \`features/<FEAT>/usecases/<name>.likec4\` — a views-only document holding one \`dynamic view\` tagged \`#cap-<capability>\` and \`#req-<requirement>\`, which \`loam archive\` merges into \`architecture/usecases/\` and \`loam unarchive\` takes back; or \`loam archive <FEAT> --approve\` to land the promise ahead of the fleet and let \`capability.requirement-unrealized\` carry it |
137
- | \`usecase.flow-invalid\` | a \`features/<FEAT>/usecases/*.likec4\` loam could not read against the map this feature's own merge would leave behind — a parse error, a hop naming an element the merge does not land, or a landscape merge that itself refuses. The flows were NOT graded, and the archive would copy them into \`architecture/\` for the next reader's \`loam validate --all\` to fail on. \`--approve\` does NOT move it (\`overridable: false\`): that flag overrides loam's judgment about coherence, never its ability to read an axis | fix what the message names. A hop into an element the feature adds is legal — the flow is graded against the POST-MERGE map — so the usual cause is a spelling that matches neither the living landscape nor \`delta.likec4\` |
138
- | \`capability.remove-requirement-realized\` | the same join in the removal direction: a feature's capability delta REMOVES a requirement that something the merge leaves behind still names with \`Realizes:\` — a living service requirement this feature does not also change, or one of its own ADDED/MODIFIED requirements. Without it the archive succeeded and the very next \`loam validate --all\` failed with \`capability.realizes-unknown\` against a service document nobody in the feature had touched. An ERROR, exactly as \`openapi.remove-op-consumed\` is for the identical shape one axis over; \`--approve\` overrides | retire the realizing requirement in the same feature, or drop its \`Realizes:\` line (a MODIFIED does that by restating the requirement without it), or \`loam archive <FEAT> --approve\` to break the join deliberately |
139
- | \`glossary.term-exists\` | a \`features/<FEAT>/glossary/<term>.md\` whose term the LIVING glossary already defines. The merge is a whole-file copy, so it would replace an authored definition wholesale — and \`--approve\` does NOT move this one (\`overridable: false\` in the \`--json\` refusal), because approving it is approving a deletion nobody described. A feature-local glossary document INTRODUCES a term; it does not rewrite one | delete the feature's copy and edit \`glossary/<term>.md\` directly in the same change, where git produces an ordinary conflict if somebody else is editing it too |
140
- | \`usecase.flow-exists\` | a \`features/<FEAT>/usecases/<name>.likec4\` whose flow the LIVING \`architecture/usecases/\` already holds. The merge is a whole-file copy, so it would replace an authored hop sequence wholesale — and \`--approve\` does NOT move this one (\`overridable: false\` in the \`--json\` refusal), because a \`dynamic view\`'s ordered steps are written down in no other document the fleet keeps. A feature-local flow INTRODUCES a use case; it does not rewrite one | delete the feature's copy and edit \`architecture/usecases/<name>.likec4\` directly in the same change, where git produces an ordinary conflict if somebody else is editing it too |
141
152
  | \`glossary.unlinked\` (warn) | a term defined in \`glossary/<term>.md\` that no document OUTSIDE \`glossary/\` links to — the mirror of \`permissions.unenforced\` and \`capability.unrealized\`, and it earns its place for their reason: a vocabulary is worth what cites it. A term cited only by OTHER TERMS is still unlinked and the citing terms are listed in \`details\` — a glossary is a network of definitions, so \`order-line\` linking to \`order\` says the glossary is consistent, not that the fleet uses the word. When NOTHING outside cites anything it is one finding for the whole glossary rather than one per term, because that is one afternoon's work and not N breaches. Warn and never a gate: writing the vocabulary ahead of the fleet is the intended use | cite the term from the requirement, capability or intent that uses it — \`[Order](../../glossary/order.md)\` — or delete the definition. \`loam list glossary\` shows every term with the documents that cite it |
142
153
  | \`link.unresolved\` | a markdown link in an authored document whose relative target names nothing. ONE finding per document, listing every broken link with its line, because a rotted set of links is one editing session rather than ten separate breaches. The corpus is every authored markdown document: both spec axes living and delta, \`runbook.md\`, every \`adrs/\` file at all three altitudes, \`intent.md\`, and each capability document — the fleet's own ADRs and living capability tree are graded once, on the \`landscape\` target, and never repeated per service. Three things are deliberately NOT graded, each of them a question loam has no answer to: a target outside the docs repo (that tree may not be checked out beside these documents), a bare \`#section\` anchor, and anything inside a fenced block or an inline code span. CASE **is** graded — a link resolves only when each path segment is spelled the way the directory stores it, and when only the case is wrong the finding names the stored spelling, so a link that works on a Windows laptop and 404s on GitHub is caught here rather than in review | fix the path, or write the document it points at. A link here is a join of the same kind as \`Operations:\` and \`Covers:\` — write it as a relative path, which is the only form that can resolve |
143
154
  | \`link.unreadable\` | a document in that corpus could not be decoded — almost always UTF-16, which PowerShell's \`>\` and \`Out-File\` write unasked. Its links were not checked; everything else about the service or feature still was, which is exactly why this is not \`service.unreadable\` | re-save the file as UTF-8 (\`Out-File -Encoding utf8\`) |
144
155
  | \`docs.commit-interrupted\` | a \`.loam-commit\` journal sits in the docs repo: a writer's commit never finished, so the living docs may be half-merged — nothing graded in the same run can be trusted to describe one state, and a green here would certify bytes no run produced | run the command the finding names (it recovers first), or \`loam doctor\` for the file-by-file account; the finding leads every validate mode until the journal is gone |
145
156
  | \`health.invalid\` (warn) | health.yaml exists but does not parse — alert/SLI ids are unreadable, so \`Covers: alert:/sli:\` entries and health coverage are unchecked (a missing health.yaml is legal and silent; an unreadable one used to masquerade as \`covers.unknown\` typos) | fix the YAML — the health axis resumes once it reads |
146
157
  | \`health.uncovered\` (warn) | health.yaml declares an alert or SLI no arch.spec.md requirement covers | write the arch requirement with \`Covers: alert:<id>\` / \`Covers: sli:<id>\` — a signal nothing tests is dashboard decoration |
147
- | \`health.dependency-unmodelled\` (warn) | a \`dependencies:\` id in health.yaml that nothing in this service's OWN model.likec4 answers to — by element id, \`metadata { service }\` binding, or title. Resolved against the service model, never the landscape: the model carries everything the service touches (private stores included, as nested containers), and the on-call file must name the same world. Either a dependency nobody modelled, or one that no longer exists — for example, a disabled optional service registry left as \`critical: startup\` under a green validator | model it (an external system as its own element with the real edge; a private store as a nested container), correct the id to one of the element's three names, or delete the entry |
158
+ | \`health.dependency-unmodelled\` (warn) | a \`dependencies:\` id in health.yaml that nothing in this service's OWN model.likec4 answers to — by element id, \`metadata { service }\` binding, or title, matched exactly and never by suffix. Resolved against the service model, never the fleet map read on its own: the model carries everything the service touches (private stores included, as nested containers), and the on-call file must name the same world. For a model that EXTENDS the map the set is its own SLICE — the elements it adds, plus every endpoint of its own edges and each endpoint's ANCESTORS — so \`kafka\` answers through an edge that names \`kafka.orderEvents\`, and a peer service answers through the binding on the map's element the model points at. Either a dependency nobody modelled, or one that no longer exists — for example, a disabled optional service registry left as \`critical: startup\` under a green validator | model it (a system the service reaches as an element of the FLEET MAP with the real edge drawn from the container that reaches it; a private store as a nested container), correct the id to one of the element's three names — the service id or a top-level system id is the spelling that survives either model shape — or delete the entry |
148
159
  | \`gherkin.missing\` (warn) | a living scenario no generated .feature scenario carries a digest for — the suite has no test for these words. Fires only in the service's repo, once \`<gherkinDir>/loam/\` exists | \`loam gherkin --service <id>\` regenerates the suite |
149
160
  | \`gherkin.stale\` (warn) | a generated scenario's digest matches no living scenario while its requirement still exists — the spec moved under the file (a reworded scenario reports stale + missing together) | regenerate; never edit a generated file to catch it up |
150
161
  | \`gherkin.orphaned\` (warn) | a generated file whose requirement no longer exists in the living spec (a file for a feature still in flight is exempt — it answers to its feature until it archives) | regenerate; the orphaned file is deleted and reported |
151
162
  | \`gherkin.path-outside\` | this repo's \`gherkinDir\` resolves outside the repository — a \`..\` segment, an absolute path, or a symlink pointing away — so generating would write where nothing here can review it. Refused before any file is read, not repaired | point \`gherkinDir\` in loam.json at a directory INSIDE this repository; it is the suite's own home, and a path outside it is a write nobody's diff would show |
152
- | \`evidence.checked\` (ok) | every evidence pin on the federated verification records this service attested re-resolved clean against this working tree. Fires only in the service's own repo, like \`sources.*\` — from the docs repo the whole \`evidence\` family is silent | nothing — it is the confirmation |
163
+ | \`evidence.checked\` (ok) | every evidence pin on the federated verification records this service attested re-resolved clean against this working tree. Fires only in the service's own repo, like \`sources.*\` — from the docs repo the service target's whole \`evidence\` family is silent, and only \`evidence.record-unreadable\` still fires there, on the feature target | nothing — it is the confirmation |
153
164
  | \`evidence.unpinned\` (ok) | agent-confirmed citations recorded before evidence pins existed, so nothing can grade their drift | the next \`loam verify <FEAT> --service <id> --record\` stamps pins; not work on its own |
154
165
  | \`evidence.unresolved\` (warn) | a pinned citation cannot be re-checked: the cited file is gone, unreadable, unsafe, not a regular file, or the cited line is past its end | re-read the evidence in this tree, re-answer, and re-record from this repo — a pin lint never moves the verdict by itself |
155
166
  | \`evidence.moved\` (warn) | a cited file's normalized sha256 differs from the pin while the cited line's text survives — the mildest drift: the file changed around the citation | re-read the cited file; re-record once the answers are re-checked |
156
167
  | \`evidence.line-changed\` (warn) | the cited line no longer says what the pin recorded — the citation no longer shows what it showed | re-read the evidence and re-answer; the claim may still be true, but this line no longer backs it |
157
168
  | \`evidence.token-missing\` (warn) | the cited file contained the literal the claim asserts (the operationId, message name or edge op the pin carries as \`token\`) at the attested commit, and no longer does — a token the commit never contained is warned once at record time and never stamped, so this grade always convicts a disappearance | check whether the code moved or the claim stopped being true; re-answer and re-record from this repo |
158
- | \`evidence.record-unreadable\` (warn) | a feature's verification.yaml exists but cannot be read as a record (broken YAML, a malformed \`evidence_pins\` block, a summary contradicting its claims) — none of its evidence was checked, and a silent skip would read as clean | repair the YAML by hand, or re-record from this repo; loam never overwrites a record it could not read |
169
+ | \`evidence.record-unreadable\` (warn) | a file of a feature's verification record (its \`verification.yaml\`, or a \`verification/<svc>.yaml\`) exists but cannot be read as a record (broken YAML, git conflict markers, a malformed \`evidence_pins\` block, a summary contradicting its claims) — none of its evidence was checked, and a silent skip would read as clean | follow the repair the finding names for that file: this service's own \`verification/<svc>.yaml\` may be repaired, or deleted and re-recorded from this repo; the one-file \`verification.yaml\` may hold other services' attestations, so it is repaired or restored from version control, never deleted. loam never overwrites a record it could not read |
159
170
 
160
171
  \`--feature <FEAT-id>\` — a change's three axes against each other. The SAME checks
161
172
  gate \`loam archive\` (errors block it; warnings never do), so a clean run here is
@@ -176,35 +187,53 @@ what lets a feature ship:
176
187
  | \`c4-api.op-undefined\` | an edge calls an operation the target does not expose | a broken contract between services — fix the caller or add the endpoint |
177
188
  | \`c4-api.op-pending\` (warn) | the called operation is defined by another feature in flight | archive that feature first |
178
189
  | \`c4-api.op-deprecated\` (warn) | a NEW tagged edge consumes an operation the living provider's OpenAPI marks \`deprecated: true\` — building new consumption on a dying op (quiet when this feature's own openapi delta drops the flag: that IS the un-deprecation) | point the edge at the replacement operation, or say why the deprecated one is right; never gates archive |
179
- | \`c4-api.op-removing\` | a NEW tagged edge consumes an operation this feature removes | remove or redirect the new edge; one feature cannot retire and add consumption of the same operation |
190
+ | \`c4-api.op-removing\` | a NEW tagged edge consumes an operation this feature removes — with a removal marker, or by defining another operation at its method and path so the merge leaves it defined nowhere | remove or redirect the new edge; one feature cannot retire and add consumption of the same operation |
180
191
  | \`openapi.remove-target-missing\` | an \`x-loam-remove: true\` marker's exact path+method does not exist in living OpenAPI | update the stale marker to the current slot, or drop it if the operation is already gone |
181
192
  | \`openapi.remove-target-mismatch\` | the marker's operationId differs from the living operation at that path+method | name the living operation exactly; loam never deletes the different operation occupying the slot |
182
- | \`openapi.remove-marker-missing\` | a REMOVED requirement governs an operation but the feature has no removal marker | add the exact path/method operation with its operationId and \`x-loam-remove: true\` to the feature openapi.yaml |
193
+ | \`openapi.remove-marker-missing\` | a REMOVED requirement governs an operation the service's living contract still defines, but the feature has no removal marker for it. Owed only while there is a living operation to retire: not for an id the service's own contract does not define (a consumer's REMOVED requirement naming another service's operation), and not for one this delta already replaces at its method and path. A living contract that does not parse proves neither, so the marker is still owed there | add the exact path/method operation with its operationId and \`x-loam-remove: true\` to the feature openapi.yaml |
183
194
  | \`openapi.remove-marker-unjustified\` | a removal marker is not named by any REMOVED requirement's \`Operations:\` line | remove the marker or retire the governing requirement in the same feature |
184
195
  | \`openapi.remove-marker-anonymous\` | an \`x-loam-remove: true\` marker with no \`operationId\` — loam cannot tell which operation it retires, and the marker itself would be written into the living contract | name the operation the marker retires, exactly as the living contract spells it |
185
- | \`openapi.remove-op-consumed\` | the feature retires an operation the LIVING fleet still consumes — a landscape edge's \`metadata { op }\`, or another service's living requirement naming it | fix the consumer in the same feature (redirect the edge, retire its requirement), or archive with \`--approve\` if the consumer is already gone in code |
196
+ | \`openapi.remove-op-consumed\` | the feature takes out of the contract an operation the fleet would still consume after this merge — read once for the gate and \`loam diff\` alike: an edge of the \`architecture/\` project with \`metadata { op }\` into the provider; another service's requirement (spec.md or arch.spec.md) whose \`Operations:\` line names it, unless that service's own contract defines the same id (then it names its own); a use-case hop attributed to it; and the provider's OWN spec.md requirements (an arch.spec.md \`Operations:\` line is never graded against its own contract, so the provider's strands nothing) and self-edges still naming it. Every requirement is read as the merge would leave it — one this feature MODIFIES to drop the id or REMOVES no longer counts, another service's included, and an unappliable spec delta fails closed. Two shapes: a removal marker retires it, or the delta defines ANOTHER operation at its method and path, and the contract the merge would write then defines it nowhere (the merge writes per method and path, so it replaces it there). The message names what replaces it and where; \`details[]\` lists the consumers. A consumer document loam cannot read — a spec.md or arch.spec.md that is not UTF-8, the provider's own spec.md included, or a service id two directories claim — refuses too, named in \`details[]\`: nobody could prove it does not name the operation. An edge into the provider drawn with no \`metadata { op }\`, or a use-case hop whose edges name different operations, does not refuse (\`loam diff\` lists them as doubts) | make each document listed readable (re-save it as UTF-8, or leave one directory per service id); fix each consumer in the same change — drop or redraw the edge by editing the living map (the landscape merge only adds edges, so a \`delta.likec4\` can never take one away), MODIFY or REMOVE the requirement in this feature's \`specs/<service>/\` delta (a REMOVED one owes no marker where the overwrite already takes the operation out) — or archive with \`--approve\` if the consumer is already gone in code. An overwrite: define the replaced operation at another address in the same delta if it is to stay; for a rename, redraw the living edge and restate the governing requirement as a MODIFIED naming the new operation |
197
+ | \`openapi.op-revived\` (warn, gates archive) | the feature's openapi.yaml spells an operation the living contract no longer has, and an ARCHIVED feature's removal marker retired it — or spells it at an address an archived feature MOVED it away from (or retired it from, before another re-added it elsewhere), so the merge would answer it at both addresses (one operationId at two slots). Judged on the contract the merge would write: an operation the living contract still holds that ends up at one address is not graded — moved back with its own removal marker where it lives now, or swapped out there by another operation, with or without an operationId — and a removal marker where the operation does not live removes nothing, so it excuses nothing. The message names the operation, the archived feature(s), what each did there (moved it, and where to, or retired it) and, apart from that, where it lives now — and, when another operation lives at the address the delta spells it at, names that one too (one written with no operationId by what it is): the merge writes per method and path, so archiving replaces it there. Absent from living reads like an addition, so the merge would put it back. Read from the markers under \`features/archive/\`, counted only where that feature's snapshot shows the merge carried them out: an operation removed by hand-editing a living contract, or retired by an archive since deleted from disk, is invisible to it | retired: delete the quote if you only restated it; archive with \`--approve\` if the feature brings it back on purpose (drop its \`x-loam-based-on\` first — a pin over nothing is \`openapi.baseline-invalid\`). Two addresses: delete the quote and make any change at the address the message names; to move it back on purpose, add a removal marker for it at that address in the same delta (or define another operation there) — and where another operation lives at the quoted address now, the delta replaces it, so define that one at another address in it if it is to stay. Where the delta already spells that address, no marker can stand beside what it holds there, and the message offers \`--approve\` instead |
186
198
  | \`c4-event.message-undefined\` | a tagged edge \`publishes\`/\`consumes\` a message its bound service's asyncapi contract — as this feature's merge would leave it; feature ∪ living where no delta exists — declares no matching \`action: send\`/\`receive\` operation for | declare the operation in the feature's asyncapi.yaml, or correct the edge |
187
199
  | \`c4-event.message-pending\` (warn) | the named message is introduced by another feature in flight | archive that feature first |
188
200
  | \`spec-event.message-undefined\` | a delta requirement's \`Publishes:\`/\`Consumes:\` line names a message the service's asyncapi contract — as this feature's merge would leave it, so a retired declaration stops answering and a leftover line on a surviving requirement is caught before it breaks the post-archive fleet — does not declare in that direction; the same code validate gives the living-side breach | declare it in the feature's asyncapi.yaml, retire the line with its requirement, or correct it |
189
- | \`spec-event.message-pending\` (warn) | the named message is introduced by another feature in flight | archive that feature first |
201
+ | \`spec-event.message-pending\` (warn) | the named message is introduced by another feature in flight — or a delta \`Consumes:\` line (and, as \`c4-event.message-pending\`, a tagged \`consumes\` edge) names a message whose only sender is another feature's asyncapi delta. Archived first, this feature leaves \`spine.message-unproduced\` for \`validate --all\`, and the archive prints that in place of its closing line | archive that feature first |
202
+ | \`spine.message-unproduced\` | a delta's \`Consumes:\` line or tagged \`consumes\` edge names a message nothing would send after this merge — no service's contract as the merge leaves it, no \`#external\` element publishing it on the living map or in this delta. Validate's own code, raised before the archive lands the consumer it would report. Suspended while any contract in the fleet does not read, as at service scope | add the producer's \`action: send\` operation (its asyncapi.yaml, living or in this feature's \`specs/<service>/\` delta), draw the \`#external\` system publishing it, or drop the consumption |
203
+ | \`spine.message-unbound\` (warn) | a tagged edge in delta.likec4 carries an event key on the wrong end — \`publishes\` binds the SOURCE, \`consumes\` the TARGET — and that end is infrastructure (a topic or store, by its kind in the delta) while a service sits at the other one. Nothing grades the key here, and once archived the living spine reports the same code. An \`#external\` system at that end stays silent, as at service scope | turn the edge round, or split a two-key edge, as the message spells it, before the archive splices it — producer \`-[publishes]->\` topic, topic \`-[delivers]->\` consumer |
190
204
  | \`asyncapi.baseline-missing\` (warn, gates archive; one per service) | slots or component surfaces in this feature's asyncapi.yaml carry no baseline pin (\`x-loam-based-on\` / \`x-loam-baselines\`) | run \`loam rebase <FEAT>\` — until then the merge cannot tell the slots and surfaces you EDITED from the ones you merely restated, and every restated one would revert whatever landed on it. \`--approve\` merges unpinned deliberately |
191
205
  | \`asyncapi.baseline-stale\` | the living slot, or the living component surface, changed after this delta pinned it | somebody landed a change in between. Re-read the living value, fold in what you still mean, then \`loam rebase <FEAT>\` |
192
- | \`asyncapi.baseline-invalid\` | an \`x-loam-based-on\` that is not a digest or one on a slot the living contract has no counterpart for; a malformed \`x-loam-baselines\` record, or an entry of it naming a component surface the delta does not declare (or whose living value is gone) | \`loam rebase\` writes both; a new channel, operation, message or component has no living version to be based on, so it carries no pin |
206
+ | \`asyncapi.baseline-invalid\` | an \`x-loam-based-on\` that is not a digest or one on a slot the living contract has no counterpart for; a malformed \`x-loam-baselines\` record, or an entry of it naming a component surface the delta does not declare (or whose living value is gone) | \`loam rebase\` writes both; a new channel, operation, message or component has no living version to be based on, so it carries no pin. A pinned slot whose living one a parallel feature has since REMOVED lands here too: re-read the living contract and delete the slot from the delta, unless this feature deliberately brings it back — then drop the pin and archive with \`--approve\` past \`asyncapi.slot-revived\`. A pinned \`components.messages\` key whose message a parallel feature MOVED elsewhere (another key, or inline under a channel) — or retired there, before another re-declared it elsewhere — lands here as well, and the message names where it is declared now: delete the quote and make any change there — archiving past it would declare the name in two places; to move it back on purpose, drop the pin and take the name out of there as \`asyncapi.slot-revived\`'s row says |
207
+ | \`asyncapi.slot-revived\` (warn, gates archive) | the feature's asyncapi.yaml declares a \`channels\`/\`operations\` slot, or a message by name, that the living contract no longer has, and an ARCHIVED feature retired it — a slot marker, or a channel removal that took the channel's inline messages (named from that feature's \`.loam-before/\` snapshot) — or declares a message at a key an archived feature MOVED it away from (or retired it from, before another re-declared it elsewhere), so the merge would declare its name in two places (the message says what that feature did there, where the name is declared now, and any other message declared at that key now, which archiving replaces there) — judged on the contract the merge would write, so a delta that also takes the name out of where it is declared now (restating or retiring the channel it stands inline in, a marker at its \`components.messages\` key, or another message declared at that key) is moving it back, and is not graded. Absent from living reads like an addition, so the merge would put it back. A message inline under a channel the delta brings back is that channel's finding, unless the merge would also declare its name elsewhere — then it is a two-places finding of its own. A removal made by hand-editing a living contract, or an archive since deleted from disk, is invisible to it | retired: delete the quote if you only restated it; archive with \`--approve\` if the feature brings it back on purpose. Two places: delete the quote and make any change where the message says it is declared now; to move it back on purpose, take it out of there in the same delta — a removal marker at a \`components.messages\` key, AND every channel (or operation) the message names as still \`$ref\`-ing that key restated with the \`$ref\` pointed at the quoted key (retired alone, the key leaves them pointing at nothing, and their send stops sending the message: \`asyncapi.remove-message-consumed\`); for a declaration inline under a channel, restate channel \`<ck>\` with a \`$ref\`, or retire channel \`<ck>\` (a marker nested on the inline message retires nothing: \`asyncapi.remove-marker-inline\`); where another message is declared at the quoted key now, define it at another key to keep it. Where the delta already spells that key, the message offers \`--approve\` instead |
193
208
  | \`asyncapi.remove-target-missing\` | an \`x-loam-remove: true\` marker addresses a slot the living asyncapi does not have | update the stale marker to the current key, or drop it if the slot is already gone |
194
209
  | \`asyncapi.remove-target-mismatch\` | the marker's message name differs from the living declaration at that key | name the living message exactly; loam never deletes a different message occupying the slot |
195
- | \`asyncapi.remove-marker-missing\` | a REMOVED requirement's \`Publishes:\`/\`Consumes:\` line names a message the living contract still declares, with no matching marker in the feature's asyncapi.yaml | add the \`components.messages\` entry with the message's name and \`x-loam-remove: true\`; a message the living contract declares only INLINE under a channel is retired by that channel's own removal marker (\`channels.<key>: {x-loam-remove: true}\`) instead |
210
+ | \`asyncapi.remove-marker-missing\` | a REMOVED requirement's \`Publishes:\`/\`Consumes:\` line names a message the living contract still declares, and the contract the merge would write still declares it, with no matching marker in the feature's asyncapi.yaml (a message this delta already takes out — a slot re-keyed to another name — owes none) | add the \`components.messages\` entry with the message's name and \`x-loam-remove: true\`; a message the living contract declares only INLINE under a channel is retired by that channel's own removal marker (\`channels.<key>: {x-loam-remove: true}\`) instead |
196
211
  | \`asyncapi.remove-marker-unjustified\` | a message-removal marker no REMOVED requirement's \`Publishes:\`/\`Consumes:\` line justifies (channel and operation slots need exactness only — requirements never join on their keys) | remove the marker, or retire the governing requirement in the same feature |
197
- | \`asyncapi.remove-message-consumed\` | the feature retires a message the LIVING fleet still consumes — a landscape \`consumes\` edge, or another service's living \`Consumes:\` requirement | fix the consumer in the same feature (drop the edge, retire its requirement), or archive with \`--approve\` if it is already gone in code |
212
+ | \`asyncapi.remove-message-consumed\` | the feature retires a message — a marker, or a removal or edit that stops the service sending it (a slot re-keyed, a send turned to receive) — that the fleet would still name after this merge: a \`consumes\` edge of the \`architecture/\` project, another service's \`Consumes:\` line in either spec file, or a use-case hop backed by such an edge, while nothing would still send the message after this merge (another service's contract, as the merge leaves it, keeps them served, and so does an \`#external\` element whose \`publishes\` edge is in the living map — the outside producer \`spine.message-unproduced\` accepts — unless this feature's \`specs/<service>/\` makes that element a service); or the provider's OWN \`Publishes:\` line or \`publishes\` edge (the spine grades that edge on its source), whoever else sends it — each read as the merge would leave it; \`details[]\` lists them. A consumer document loam cannot read (a spec file that is not UTF-8, the provider's own included) or a service id two directories claim refuses too, named in \`details[]\` — another service's only while nothing would still send the message | make each document listed readable (re-save it as UTF-8, or leave one directory per service id); fix each in the same change (drop the consumes edge, or drop or redraw the provider's publishes edge, in the living map, MODIFY or REMOVE the requirement in this feature's \`specs/<service>/\` delta — a REMOVED one owes no marker for a message the merge already takes out), keep it sent — by an operation in this service, or, for the consumers, by one in another service's asyncapi.yaml (living or in this feature's delta) or by an \`#external\` element's \`publishes\` edge in the living map — or archive with \`--approve\` if it is already gone in code |
198
213
  | \`asyncapi.message-conflict\` (warn) | two features in flight add or edit the same (service, message) | whichever archives second replaces the other's declaration wholesale — coordinate before either ships |
199
214
  | \`c4.op-ungoverned\` (warn) | an operation is called but no requirement governs it | write the requirement |
200
215
  | \`c4.op-link-missing\` (warn) | a "Calls" edge in the delta with no \`metadata { op }\` | link it to the operationId |
201
216
  | \`c4.service-binding-invalid\` | an explicit \`metadata { service }\` binding — a tagged element's, or an untagged child's anywhere inside its block, since the merge splices the whole authored block verbatim — is not a legal \`services/<id>/\` name (a space, a \`../\`, a Windows-reserved stem); archive would write the name into the living landscape, and a \`../\` collapses its \`services/\` probe out of the docs repo | fix the binding to the directory the element means; \`--approve\` does not override this one — the path the name becomes is mechanical, not a judgment call |
202
217
  | \`api.op-unconsumed\` (warn) | an added operation no edge consumes | model the caller, or say why it is provider-only |
203
- | \`service.no-requirement-delta\` (warn) | a new service with no spec delta | write \`specs/<svc>/spec.md\` |
218
+ | \`service.no-requirement-delta\` (warn) | a new service with no spec delta — a tagged \`softwareSystem\` of ours that no \`services/<id>/\` and no living map element already stands for. An \`#external\` element, a grouping that contains a service, and the stub of a living element are not new services and never raise it | write \`specs/<svc>/spec.md\`; or, if the system is somebody else's, tag it \`#external\` |
204
219
  | \`scaffold.placeholder\` (warn, gates archive) | a feature document still carries \`loam new\`'s exact placeholder text — a \`TODO — name the behaviour\` requirement, a \`TODO — name the case\` scenario, an \`<angle-bracket>\` fill-in, or a \`TODO — what this service owns\` description | write the real content; the merge would otherwise publish a requirement or description nobody authored. \`--approve\` overrides |
205
- | \`intent.empty\` (warn, gates archive) | intent.md is missing, or its Why/Scope hold nothing outside the scaffold's own comments | write why the feature exists — it is the first thing a reviewer reads. \`--approve\` overrides |
220
+ | \`intent.empty\` (warn, gates archive) | intent.md is missing, or its Why/Scope hold nothing outside the scaffold's own comments — questions under \`## Open questions\` do not count | write why the feature exists — it is the first thing a reviewer reads. \`--approve\` overrides |
206
221
  | \`archedge.uncovered\` (warn) | no scenario names a tagged edge (a heuristic) | write the scenario, or say why the edge needs none |
207
- | \`c4.uncovered\` (warn) | a NEW tagged element or edge in delta.likec4 that no arch requirement covers via \`Covers:\` — the mechanical check, not the heuristic above | add the requirement to \`specs/<svc>/arch.spec.md\` (outbox? retries? alerts?), or the architectural obligations ship unchecked |
222
+ | \`c4.uncovered\` (warn) | a NEW tagged element or edge in delta.likec4 that no arch requirement covers via \`Covers:\` — the mechanical check, not the heuristic above. The line the message offers is spelled as it will resolve once living: an edge by its endpoints' service names, an element by the service it stands for or the id the merge gives it — the delta's own id only when the merge preview cannot be computed | add the requirement to \`specs/<svc>/arch.spec.md\` (outbox? retries? alerts?), or the architectural obligations ship unchecked |
223
+ | \`covers.unknown\` (warn) | at feature scope, besides the typo: a \`Covers:\` entry that resolves ONLY through delta.likec4's own ids — a stub the delta declares where the living map nests it (\`orderService\` for \`marketplace.orderService\`), or a child under such a stub, which the merge lands under the living parent. It resolves here and is \`covers.unknown\` the moment the archive lands it | write the spelling the message offers — an edge by service names (\`a-service -> b-service\`), an element by the id the merged map gives it; both resolve before and after the archive |
224
+ | \`health.uncovered\` (warn) | at feature scope: an alert or SLI in the service's health.yaml that a LIVING arch.spec.md requirement covers today and nothing would after this merge — a MODIFIED requirement restates its whole block, so a \`Covers: sli:<id>\` line not carried over is a line the merge deletes. Only what the merge takes away; a signal already uncovered is the service target's warning | keep the entry on the MODIFIED requirement, or cover the signal from another requirement in the delta |
225
+ | \`capability.uncovered\` (warn, GATES archive) | a requirement a feature's \`features/<FEAT>/capabilities/<id>/spec.md\` ADDS that no ADDED or MODIFIED requirement in the same feature's \`specs/\` deltas names with \`Realizes: <capability-id>#<Requirement-ID>\`, subject = the capability id. A WARNING because the document is legal — writing a promise ahead of the fleet is the intended use — that GATES because the merge is what is unsafe: once it lands, the only thing that will ever mention the promise again is \`capability.requirement-unrealized\`, a fleet-scope warning nobody reads. MODIFIED is exempt (the promise may already be realized by living text), and a requirement with no \`Requirement-ID:\` is left to \`capability.requirement-unidentified\` alone | add the \`Realizes:\` line to the service requirement that carries part of the promise; or, if the promise CROSSES services, write the flow that keeps it as \`features/<FEAT>/usecases/<name>.likec4\` — a views-only document holding one \`dynamic view\` tagged \`#cap-<capability>\` and \`#req-<requirement>\`, which \`loam archive\` merges into \`architecture/usecases/\` and \`loam unarchive\` takes back; or \`loam archive <FEAT> --approve\` to land the promise ahead of the fleet and let \`capability.requirement-unrealized\` carry it |
226
+ | \`usecase.flow-invalid\` | a \`features/<FEAT>/usecases/*.likec4\` loam could not read against the map this feature's own merge would leave behind — a parse error, a hop naming an element the merge does not land, or a landscape merge that itself refuses. The flows were NOT graded, and the archive would copy them into \`architecture/\` for the next reader's \`loam validate --all\` to fail on. \`--approve\` does NOT move it (\`overridable: false\`): that flag overrides loam's judgment about coherence, never its ability to read an axis. Also fires on the SERVICE target, under \`--service\` and per service under \`--all\`, for a \`.likec4\` beside model.likec4 that does not read as part of that service's own project — there the flows beside the model were not graded while model.likec4 itself still was | fix what the message names. A hop into an element the feature adds is legal — the flow is graded against the POST-MERGE map — so the usual cause is a spelling that matches neither the living landscape nor \`delta.likec4\` |
227
+ | \`usecase.step-unbacked\` / \`usecase.requirement-unresolved\` / \`usecase.capability-unresolved\` | a feature's own flow (\`features/<FEAT>/usecases/*.likec4\`) graded as \`validate --all\` grades it once archived: a hop no relationship on the POST-MERGE map backs, a \`#req-\` tag no \`#cap-\` tag scopes or that names no requirement of that capability as the merge leaves it (an id this feature's capability delta ADDS resolves; one it REMOVES does not), or a \`#cap-\` tag naming no capability. Same codes, same sentences, opened by the feature file's path. Errors, so they gate archive | the fixes the fleet-scope rows give: draw the edge (in delta.likec4, tagged, or in the living map), add the \`#cap-\` tag, or correct the id |
228
+ | \`usecase.requirement-modified\` (warn, never gates) | a LIVING flow whose \`#req-\` tag claims a requirement this feature MODIFIES — an \`architecture/\` flow scoped by its \`#cap-\` tag to a capability requirement the feature's capability delta rewrites, or a flow beside a service's model naming one of that service's spec.md / arch.spec.md requirements the feature's \`specs/\` delta rewrites. A MODIFIED keeps the id and replaces the whole text, so the tag still resolves after the archive and every grade stays green while the flow may draw what the new wording forbids. When an id the same feature ADDS flattens to that tag, the tag would not resolve, and the flow gets \`usecase.requirement-unresolved\` instead of this warning | read the named flow against the replacement; if it no longer keeps the promise, redraw it (or drop the tag) in the same change. The warning names the flow and the requirement and stays until the feature archives — loam cannot read a flow's meaning, only that the promise moved under it |
229
+ | \`usecase.requirement-unresolved\` (error, gates archive, \`--approve\`-overridable) | a LIVING flow whose \`#req-\` tag claims a requirement this feature REMOVES — the same two altitudes as \`usecase.requirement-modified\`, a service-local tag read against BOTH of that service's documents. The merge deletes the promise and leaves the tag, so the next \`validate --all\` reports this same error against the living flow; it is raised here first, with the view as its subject as the fleet names it, and only when the tag resolves today and would not after the merge (a removal the delta re-spells under an id flattening to the same tag earns nothing). A MODIFIED earns it too, instead of \`usecase.requirement-modified\`, when an id the feature adds flattens to the same tag | drop the tag from the named living flow (or point it at the promise that replaces this one) in the same change, or MODIFY the requirement instead of removing it; for colliding ids, rename one. \`--approve\` merges past it deliberately, and the living docs then carry exactly this error |
230
+ | \`usecase.step-contested\` / \`usecase.step-unlinked\` (warn) | the same two hop warnings the fleet gives, raised before the archive on a feature's own flow over the post-merge map | as at fleet scope — open the step's title with the operation id the hop means, then punctuation; give the candidates one operation; or \`metadata { op }\` on the edge |
231
+ | \`capability.remove-requirement-realized\` | the same join in the removal direction: a feature's capability delta REMOVES a requirement that something the merge leaves behind still names with \`Realizes:\` — a living service requirement this feature does not also change, or one of its own ADDED/MODIFIED requirements. Without it the archive succeeded and the very next \`loam validate --all\` failed with \`capability.realizes-unknown\` against a service document nobody in the feature had touched. An ERROR, exactly as \`openapi.remove-op-consumed\` is for the identical shape one axis over; \`--approve\` overrides | retire the realizing requirement in the same feature, or drop its \`Realizes:\` line (a MODIFIED does that by restating the requirement without it), or \`loam archive <FEAT> --approve\` to break the join deliberately |
232
+ | \`glossary.term-exists\` | a \`features/<FEAT>/glossary/<term>.md\` whose term the LIVING glossary already defines. The merge is a whole-file copy, so it would replace an authored definition wholesale — and \`--approve\` does NOT move this one (\`overridable: false\` in the \`--json\` refusal), because approving it is approving a deletion nobody described. A feature-local glossary document INTRODUCES a term; it does not rewrite one | delete the feature's copy and edit \`glossary/<term>.md\` directly in the same change, where git produces an ordinary conflict if somebody else is editing it too |
233
+ | \`usecase.flow-exists\` | a \`features/<FEAT>/usecases/<name>.likec4\` whose flow the LIVING \`architecture/usecases/\` already holds. The merge is a whole-file copy, so it would replace an authored hop sequence wholesale — and \`--approve\` does NOT move this one (\`overridable: false\` in the \`--json\` refusal), because a \`dynamic view\`'s ordered steps are written down in no other document the fleet keeps. A feature-local flow INTRODUCES a use case; it does not rewrite one | delete the feature's copy and edit \`architecture/usecases/<name>.likec4\` directly in the same change, where git produces an ordinary conflict if somebody else is editing it too |
234
+ | \`deployment.doc-exists\` | a \`features/<FEAT>/deployment/<name>.likec4\` whose file the LIVING \`architecture/\` already holds. The merge is a whole-file copy, so it would replace an authored topology wholesale, and \`--approve\` does NOT move it — the same act, and the same severity, as \`usecase.flow-exists\` one axis over. The collision is on the FILE, never on what is inside it: two features may both \`extend\` one living region from documents of their own and both archive | delete the feature's copy and edit \`architecture/<name>.likec4\` directly in the same change — or, to ADD to what that file declares, rename the feature's document and say \`extend\` in it |
235
+ | \`deployment.doc-invalid\` | a \`features/<FEAT>/deployment/*.likec4\` loam could not read against the map this feature's own merge would leave behind — a parse error, an unresolved element, or a landscape merge that itself refuses. The corpus is the feature's \`deployment/\` PLUS the living \`architecture/\` documents it \`extend\`s, so the fault may be in either and every message names its file. \`--approve\` does not move it: the flag overrides loam's judgement about coherence, never its ability to read an axis | fix what the messages name. A document instancing an element only this feature's \`delta.likec4\` adds is LEGAL and resolves — that is what the merge preview is for; one naming an element the merge does not land is the case this refuses |
236
+ | \`deployment.doc-reserved\` | a \`features/<FEAT>/deployment/<name>.likec4\` whose name loam owns inside \`architecture/\`: \`subsystems.likec4\` and \`services.likec4\` (both generated, overwritten wholesale by \`loam subsystem sync\`, and parsed by nothing — so topology landing in either is invisible from the moment it archives), \`landscape.likec4\` (spliced into, never replaced), or any path under \`usecases/\` (the flow tree). Refused whether or not the target exists, which is the point: three of the four fail silently when it does not | rename the feature's document — its name is yours to choose, and \`extend\` is how it adds to what a living document already declares |
208
237
  | \`delta.unknown-section\` | a heading that nearly matches the delta grammar | fix it — everything under it merges as NOTHING today, silently |
209
238
  | \`delta.no-delta-sections\` | requirements, but no \`## ADDED/MODIFIED/REMOVED Requirements\` section anywhere — the whole delta would merge nothing | put every changed requirement under its delta section |
210
239
  | \`delta.requirement-not-merged\` (warn, gates archive) | a requirement under a prose heading (\`## Behavior\`) instead of a delta section | move it under \`## ADDED\`/\`## MODIFIED\`/\`## REMOVED Requirements\` — as written, archive drops it. If it really is documentation, quote it under \`## Requirements\`, which is exempt |
@@ -223,35 +252,42 @@ what lets a feature ship:
223
252
  | \`delta.baseline-stale\` | the living requirement changed after this delta was written against it | someone landed a change in between and this MODIFIED would replace it outright. Re-read the living requirement, fold in what you still mean, then \`loam rebase <FEAT>\`. Re-pinning without re-reading is how you overwrite them on purpose |
224
253
  | \`delta.baseline-missing\` (warn, gates archive) | a MODIFIED/REMOVED requirement with no \`Based-On:\` | run \`loam rebase <FEAT>\` — until then nothing can say whether the living text moved under this delta, and archive refuses to merge it. Expected on deltas adopted from OpenSpec, which never carried the line: one rebase pins the whole feature, and \`--approve\` merges unpinned deliberately |
225
254
  | \`delta.baseline-invalid\` | a \`Based-On:\` that is not a digest, declared twice, or sitting on an ADDED requirement | \`loam rebase\` writes the value; an ADDED requirement has no living version to be based on, so drop the line or make it MODIFIED |
255
+ | \`evidence.record-unreadable\` (warn) | a file of this feature's verification record (\`verification.yaml\`, or a \`verification/<svc>.yaml\`) exists but cannot be read as a record — broken YAML, git conflict markers after a merge, a shape that is not a record, a summary contradicting its claims. The same code and sentence the service target raises from a service repo; here it fires from anywhere, the docs repo included, because the record lives there. Readability only: the verdict stays \`loam verify\`'s and \`loam status\`'s, and as a warning it never stops \`loam archive\` | repair the file the finding names, following the recipe it carries: a \`verification/<svc>.yaml\` holds one service's word and may be deleted and recorded again from that service's repo; the one-file \`verification.yaml\` may hold other services' attestations, so it is repaired or restored from version control, never deleted. loam never overwrites a record it could not read |
226
256
 
227
257
  \`--all\` — everything above for every target, plus the fleet cross-check:
228
258
 
229
259
  | code | what it means | what to do |
230
260
  |---|---|---|
231
261
  | \`landscape.missing\` | \`architecture/landscape.likec4\` does not exist — an ERROR as soon as \`services/\` holds one service, a warning only in an empty docs repo | create it: a \`specification { ... }\` block declaring the kinds, then a \`model { ... }\` block with one bound element per service (\`metadata { service '<id>' }\`) and one edge per cross-service call carrying \`metadata { op '<operationId>' }\`. With no fleet map every cross-service check is blind, not passing |
232
- | \`landscape.invalid\` | the living landscape does not parse | fix it first — the fleet cross-check cannot run against a document nobody can read |
262
+ | \`landscape.invalid\` | the living landscape does not parse. The fleet map is graded as the \`architecture/\` project and NOTHING ELSE — the landscape plus every \`architecture/**/*.likec4\` except the generated views files (\`subsystems.likec4\` and \`services.likec4\`) — whatever shape the service models take. That is deliberate: the renderer's root project is wider (it also holds every extending model), so a hop in \`architecture/usecases/\` naming a CONTAINER an extending model declares renders perfectly and is \`landscape.invalid\` here. A cross-service flow names service elements; a container-level flow lives beside the model that declares the container. One broken service model must never blank the fleet's own map | fix it first — the fleet cross-check cannot run against a document nobody can read. If the unresolved name is a container an extending model declares, the reference belongs one altitude down whatever carries it: a use-case hop moves beside that service's model.likec4 and is tagged \`#req-\`; a model edge names the SERVICE on the map and the container in the service's own model |
233
263
  | \`service.id-invalid\` | a \`services/<id>/\` directory whose name is not a legal service id — every authoring command refuses it, so nothing in that directory can be changed through loam. Fleet scope only: the SET of service directories is what makes the id a question, and a \`--service\` run is already refused by its own guard before it gets this far (the guard refuses only a name that neither exists as a directory nor is a legal id — a badly-named directory that DOES exist resolves through the enumeration and grades as an ordinary target, still without this finding). Graded before the map is opened, so it stands even when the landscape is missing or unreadable | rename the directory, then update its \`service:\` frontmatter, its \`metadata { service }\` binding in the landscape, and any \`features/<FEAT>/specs/<id>/\` naming it |
234
264
  | \`subsystem.unmarked\` | a directory under \`services/\` that is neither a subsystem (no \`subsystem.yaml\`) nor a service (no service artifact), with directories beneath it — the shape an ordinary merge produces when one branch deletes an emptied subsystem's marker while another moves a service in. The services beneath STAY enumerated and the finding names every one of them: the fleet is never reported smaller than it is | restore \`subsystem.yaml\` in that directory (an empty file is a valid marker), or move the directories out |
235
265
  | \`subsystem.marker-misplaced\` | \`subsystem.yaml\` sitting BESIDE service artifacts (spec.md, model.likec4, …) — a directory is a subsystem or a service, never both; it stays classified as a service | delete the marker, or move the artifacts into a service directory of their own inside the group |
236
266
  | \`subsystem.name-collision\` | one name claimed by two or more directories in the \`services/\` tree — service ids and subsystem names share one flat namespace, unique at any depth, because a service's identity is its leaf directory name alone and placement never disambiguates it. The message names every claimant | rename all but one, then update whichever frontmatter, landscape bindings and \`features/<FEAT>/specs/<id>/\` name the renamed service |
237
267
  | \`subsystem.name-invalid\` | a subsystem directory name that breaks the id grammar (a space, a \`..\`, a Windows-reserved stem, a trailing dot) — subsystem names take the service-id grammar because both become directory names in one shared tree. Its services stay enumerated meanwhile | rename the directory to a legal name |
238
268
  | \`subsystem.invalid\` | a \`subsystem.yaml\` that exists but cannot be read as subsystem metadata — presence still classifies the directory as a subsystem (content is metadata, never identity), and exactly one finding is emitted per file | fix the file: a YAML mapping with optional \`title\`, \`description\` and \`owner\`; never \`members\` — the directory itself is the membership record. An empty file is also a valid marker |
239
- | \`subsystem.views-stale\` | \`architecture/subsystems.likec4\` — the GENERATED file holding one LikeC4 view per subsystem — does not match the tree: it drifted after a move, it is absent while subsystems exist, or it lingers after the last subsystem was removed. One error on one file, graded by byte comparison against the deterministic re-render (no check ever reads the file's content) | run \`loam subsystem sync\` — the single regenerator. Never edit the file by hand; authored views belong in the landscape or a file of your own |
240
- | \`subsystem.view-id-collision\` | \`architecture/landscape.likec4\` declares a view whose id is one loam GENERATES into \`architecture/subsystems.likec4\`. LikeC4 merges every file in \`architecture/\` into one project and view ids are a flat namespace there, so the renderer refuses the WHOLE project and draws nothing — while every other check here stays green. Only a real collision is reported: a \`subsystem_\`-looking id matching no current subsystem breaks nothing yet, and fires on the run after somebody creates that subsystem | rename the view in \`architecture/landscape.likec4\`. Ids beginning \`subsystem_\` are loam's to mint; \`architecture/subsystems.likec4\` is generated (\`loam subsystem sync\`) and is never hand-edited |
269
+ | \`subsystem.views-stale\` | a GENERATED views file under \`architecture/\` does not match the tree and the map. There are TWO, written by the same command in the same commit, and each earns its own finding naming its own path: \`architecture/subsystems.likec4\` (one view per subsystem) and \`architecture/services.likec4\` (one overview view per service the fleet map draws — the service, its own interior, every peer the map gives it, and \`of <element>\`, which is what makes every other view drill into it). A file is stale when it drifted after a move, is absent while the thing it mirrors exists, lingers after the last of them was removed, or the map moved under it — a service added, an edge drawn, a subsystem renamed, or the global style groups changed. Styling restales the subsystem file twice over: declaring or removing a group named \`subsystems\` changes whether its views carry the \`global style subsystems\` line, and declaring a group under ANY other name changes the disclosure comment that names what is available; the per-service file reads a group named \`services\` the same way. The census reads every \`.likec4\` under \`architecture/\` the root \`likec4.config.json\` does not exclude, so a palette in a sibling counts. One error per stale file, graded by content comparison against the deterministic re-render — never by reading what a view SHOWS (docs/DESIGN.md rule 26) | run \`loam subsystem sync\` — the single regenerator for both files. Never edit either by hand; authored views belong in the landscape or a file of your own, and an authored view \`of\` the same element keeps precedence whenever its file sorts first (\`landscape.likec4\` does, ahead of \`services.likec4\`), with both still reachable. \`sync\` refuses to rewrite a generated file while \`architecture/\` does not parse (\`action: "blocked"\`) — fix the map first; \`validate\` already skips the staleness question in that state. A docs repo that has not run \`sync\` since \`architecture/services.likec4\` existed sees this once for that file; one run clears it |
270
+ | \`service.likec4-config-missing\` (warn) | a model that STANDS ALONE — it declares its own \`specification\` block, so it is parsed alone and cannot share a project with the map — with no \`likec4.config.json\` beside it. It renders only as a project of its own and this one has none: a box on the fleet map with nothing renderable inside it. One warning per service, on the fleet target, only when the root \`likec4.config.json\` exists (\`loam doctor\` prints that one); never gating, because no loam check reads the model any differently. A model that EXTENDS the map never earns this — it belongs to the root project | run \`loam subsystem sync\` — it creates the file (create-only: a file that already exists is yours and is never touched). Never hand-write it; the bytes are a function of the service id, and the writer spells them once |
271
+ | \`service.likec4-config-stray\` (warn) | the mirror: a \`likec4.config.json\` beside a model that EXTENDS the fleet map. The renderer registers that file as a project of its own rooted at that directory, so \`likec4 validate .\` at the docs root 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 — it happened on a seeded fleet and not on \`examples/docs\`). Fleet target, root-gated like its siblings, never gating | run \`loam subsystem sync\` — it deletes the file (or delete it yourself). loam never writes one beside an extending model, so this one is a leftover from before the model migrated, or was hand-written |
272
+ | \`service.model-excluded\` (warn) | a model that EXTENDS the fleet map whose directory the root \`likec4.config.json\`'s \`exclude\` covers — or whose \`model.likec4\` a FILE-shaped entry (\`services/**/model.likec4\`) names; the message says which shape it found. An entry is read the way the renderer reads it: a bare entry is a raw path PREFIX from a \`/\` boundary, so \`services\`, \`services/\`, \`services/*\`, \`services/**\`, \`services/**/*\`, a wildcard segment in the middle (\`services/*/legacy\`) and the bare \`services/pay\` all cover \`services/payment-service\` — while \`services/pay/**\` wants the literal separator and covers nothing there. The renderer never loads the model, so the service is a box with nothing inside it from the docs root, while every loam check still grades it perfectly: the gap is invisible except here. The finding quotes the entry verbatim, because the repair is to edit that line | for a DIRECTORY entry, run \`loam subsystem sync\` — it rewrites the root project's \`exclude\` to cover exactly the models that stand alone, keeping every entry that is not about \`services/\` in the order the team wrote it. For a FILE-shaped entry sync repairs nothing: remove or narrow that line by hand, then re-run |
273
+ | \`service.model-unexcluded\` (warn) | the mirror of that one: a model that STANDS ALONE whose directory the root \`exclude\` does NOT cover. The renderer merges it into the root project and reports every kind and element it declares as a duplicate of the map's — which blanks the whole root project, not just this service. The most damaging of the four, and the one a fresh scaffold walks into the moment somebody writes a model in the old shape. An entry already covering the directory in any of its spellings satisfies it — no second glob is wanted, and none is appended | run \`loam subsystem sync\` — it adds \`services/<tree>/**\` to the root project's \`exclude\`. Or migrate the model to extend the map (SCHEMA.md, "Two shapes of a service model"), after which no exclusion is wanted |
274
+ | \`landscape.excluded\` (warn) | the same root \`exclude\` list, one level up: an entry covering \`architecture/landscape.likec4\` itself. The renderer then loads NO fleet map — it draws nothing, and every model that EXTENDS the map resolves against nothing in the root project, which is also what WOULD turn \`c4.fleet-project-invalid\` into a page of unresolved references — measured on the packaged example fleet, one authored line came back as 161 of them. So that check is not graded at all while this entry stands, and this finding is the only line in the run that names the cause; remove the entry and the fleet project is graded again. loam is otherwise unaffected: the architecture loader keeps the map whatever the list says, so every grade in the run is right, and that is exactly why the state was invisible until this code existed. An entry is matched as a FILE path here, not as a directory, so \`architecture/*.likec4\` — a line somebody wrote for a palette — covers it while \`services/**\` and \`features/**\` do not. Quoted verbatim, because the repair is to edit that line | remove the entry, or narrow it to the paths it was written for. NOT \`loam subsystem sync\`: that command maintains only the \`services/\` entries in the list and leaves every other line exactly as the team wrote it, so it would report success and change nothing |
275
+ | \`c4.fleet-project-invalid\` (warn) | every document reads clean where loam grades it, and the ONE project the renderer builds out of them does not parse. \`--all\` only, only when at least one extending model exists, the map itself parses, and the root \`exclude\` does not cover the map (\`landscape.excluded\` then names that one entry and this check stands down behind it, because this load reads what the renderer reads and has no floor under the map): loam walks the docs root and loads what the renderer's root project loads — everything the root \`exclude\` leaves, minus the generated views files (\`subsystems.likec4\` and \`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 that is NOT already present in the map alone, in some per-service project, or in a service-flow project the same run made. A \`.likec4\` in a SUBSYSTEM directory, above every service, is in that set. That difference is exactly the class no per-service grade can see: two services each declaring \`tag req-AUTH\` locally, two models adding the same child id under one element, a broken untagged sibling nobody opted in. All of them are renderer-fatal and all of them were silent. \`locations[0]\` is the file the error is IN, \`details\` carry \`<path>: L<n>: <error>\` 1-based — path, colon, space, then the line, exactly as the message's own head spells it, so an agent splitting on the separator gets the same two halves from either — and the message tail names the failure it actually is — a double declaration, an unresolved reference, or neither. \`subject\` is the service whose directory holds the file, where there is one | declare it once — in \`architecture/landscape.likec4\`, or in the single service that owns it — at the file and line the message names. Never by excluding the directory: that hides the model from the renderer instead (\`service.model-excluded\`) |
276
+ | \`subsystem.view-id-collision\` | a document the root project reads declares a view whose id is one loam GENERATES — \`subsystem_<name>\` into \`architecture/subsystems.likec4\`, or \`service_<id>\` into \`architecture/services.likec4\`. Two reserved prefixes, one code, because the failure is identical: LikeC4 merges every file of a project into one model and view ids are a flat namespace there, so the renderer refuses the WHOLE project and draws nothing — while every other check here stays green. Under \`--all\` the census covers every EXTENDING model's authored view ids as well as the landscape's, because those documents are in the same root project now; a standalone model's are not, since its views live in a project of its own. Only a real collision is reported: a \`subsystem_\`-looking id matching no current subsystem breaks nothing yet and fires on the run after somebody creates it, and a \`service_\`-looking id is reserved only for a service the fleet MAP DRAWS — an unmodelled \`services/<id>/\` mints nothing until an element resolves to it | rename the view in the file the finding names — the message names which file and which prefix. Ids beginning \`subsystem_\` or \`service_\` are loam's to mint; both generated files are written by \`loam subsystem sync\` and are never hand-edited |
241
277
  | \`landscape.merge-conflict\` | the fleet map still holds conflict markers — it is two halves of two different maps, and every cross-service check reads it. Checked BEFORE the parse and returns like \`landscape.invalid\`: nothing is concluded from the file, so no \`landscape.service-unmodelled\` fires behind it | resolve it keeping BOTH sides' elements and edges; ten people adopting ten services into one landscape.likec4 in the same week is how it happens. \`loam archive\` refuses on it too (\`merge-failed\`, not overridable with \`--approve\`) |
242
278
  | \`landscape.service-unmodelled\` | a \`services/<svc>/\` no element in the landscape resolves to | draw it, or bind an existing element with \`metadata { service '<svc>' }\` — the fleet map is incomplete until you do |
243
279
  | \`landscape.service-undocumented\` (warn) | a landscape element with no \`services/<id>/\` | document the service, bind the element to the directory it means, or tag it \`#external\` if it is not ours |
244
280
  | \`landscape.binding-unknown\` | an element's \`metadata { service }\` names a directory that does not exist | fix the id or create the service — a binding is a claim, and this one is false |
245
281
  | \`landscape.binding-duplicate\` (warn) | two elements resolve to the same \`services/<id>/\` | every element→service join picks one of them arbitrarily, so the other's edges are filed under a service that does not own them — bind each element to the directory it actually is, or tag the stray one \`#external\` |
282
+ | \`landscape.service-isolated\` (warn) | an element resolves to a \`services/<id>/\` and no edge anywhere in \`architecture/\` touches it, while that service's own \`model.likec4\` declares a call across its boundary. The map draws the service and nothing reaches it, so every cross-service check is vacuous over it — and \`loam validate --service\` cannot see it. Evidence-gated: a service whose model declares no cross-boundary call is silent here, and the adopt brief's \`landscape.touched\` is the only place that state is named. Binary, too: ONE edge closes it, so a map drawing one of five attested calls is silent — the check is touched/untouched, not a set difference, and the calls left undrawn are named nowhere. An \`#external\` element is never the subject, whatever it is bound to; the touched side applies no tag filter — an edge on an \`#external\` element the service owns (a kind-tagged topic, say) still touches it, exactly as the adopt brief's \`landscape.touched\` counts it | draw each attested call as one edge on the element, collapsed to the service, carrying \`metadata { op '<operationId>' }\` for a call or \`publishes\`/\`consumes\` for an event, naming the other party as the map already spells it — never a second element |
246
283
  | \`landscape.kind-tag-graded\` | the \`specification\` block declares \`#external\` or \`#platform\` on an element KIND, and at least one element of that kind stands for a real \`services/<id>/\` — bound with \`metadata { service }\`, or titled like one. Since LikeC4 1.59.0 a kind's tags land on every element of it, and loam reads both tags as "not ours, stop grading", so the exemption is silencing the checks for a service of yours; the run would otherwise end on \`landscape.matched\` over a map nothing checked | remove the tag from the kind and write \`#external\` / \`#platform\` on the individual elements that really are foreign — or stop drawing your own services as that kind. A graded tag on a kind whose elements stand for no directory of yours (the \`element topic { #external }\` idiom for the topics inside a broker) is correct and stays silent, and any OTHER tag is safe on any kind — loam does not read it |
247
- | \`landscape.platform-candidate\` (warn) | an \`#external\` element consumed by three or more services and not tagged \`#platform\` — a hub on its way to making the fleet view unreadable | declare \`tag platform\` in the \`specification\` block and tag the element (LikeC4 refuses an undeclared tag, so both steps are needed): the fleet view then excludes it, and a platform view over \`include * -> element.tag = #platform\` keeps "who depends on it" answerable |
248
- | \`landscape.datastore-private\` (warn) | a datastore element with exactly one consumer service at fleet level — drawn as a peer, it reads as a system in its own right | it is that service's internals: move it into \`services/<id>/model.likec4\` as a nested container and delete it from the landscape, or add the second consumer's edge if another service really reaches the same data |
249
- | \`landscape.datastore-shared\` (warn) | a datastore element consumed by two or more services — the strongest coupling in the fleet, stated rather than inferred | shared means the same DATA: if they read the same tables or keys, keep it drawn and let the warning state the coupling; if they only share a host or cluster (two schemas, two lock paths), model one private store per service and put the blast radius in the runbook |
250
- | \`usecase.capability-unresolved\` | a \`dynamic view\` tagged \`#cap-<slug>\` whose slug matches no declared capability id, or more than one. The tag spells the id with every character outside \`[A-Za-z0-9_-]\` flattened to \`-\` (that is all a LikeC4 tag name accepts) and that flattening is not injective, so \`payments/refunds\` and \`payments-refunds\` both answer to one tag. Silent while \`architecture/capabilities.yaml\` is absent or invalid, like the rest of that family — the FILE is the capability axis's opt-in | zero matches: fix the tag, or declare the capability in the fleet vocabulary (the message offers close ids, each already spelled as the tag you would write). More than one: rename one of the colliding ids — nothing in the tag can say which the flow realizes, and a guess files every hop of it under an owner who never claimed it |
284
+ | \`landscape.platform-candidate\` (warn) | an \`#external\` element consumed by three or more services and not tagged \`#platform\` — a hub on its way to making the fleet view unreadable. A service counts at the source of an edge into the element, and at EITHER end where the element's end is infrastructure (a \`database\`, \`queue\`, \`topic\`, \`cache\` or \`bucket\`), so a broker's consumers drawn \`topic -> service\` count | declare \`tag platform\` in the \`specification\` block and tag the element (LikeC4 refuses an undeclared tag, so both steps are needed): the fleet view then excludes it, and a platform view over \`include * -> element.tag = #platform\` keeps "who depends on it" answerable |
285
+ | \`landscape.datastore-shared\` (warn) | a datastore consumed by two or more services — the strongest coupling in the fleet, stated rather than inferred. Two shapes: a store drawn as a fleet-level peer, and a store NESTED inside its owner's element that another service reaches — the message adds \` — <owner>'s own store, written as '<store-fqn>'\` for the second, \`<store-fqn>\` being the store's own id, and then says the move is ONE-WAY at this point: the other consumers' edges resolve only against a declaration the MAP holds, so out of the owner's \`extend\` block onto the map is always safe while moving it back INTO the \`extend\` block breaks them (\`c4.invalid\` on that service's model, or \`landscape.invalid\` when the edge is drawn on the map). Consumers are counted from the map's edges AND from every extending model's attested calls, in EITHER direction: a store never initiates, so \`store -> service\` (a read drawn as the data's flow) counts that service exactly as \`service -> store\` does | shared means the same DATA: if they read the same tables or keys, keep it drawn and let the warning state the coupling; if they only share a host or cluster (two schemas, two lock paths), model one private store per service and put the blast radius in the runbook |
286
+ | \`usecase.capability-unresolved\` | a \`dynamic view\` tagged \`#cap-<slug>\` whose slug matches no declared capability id, or more than one. The tag spells the id with every character outside \`[A-Za-z0-9_-]\` flattened to \`-\` (that is all a LikeC4 tag name accepts) and that flattening is not injective, so \`payments/refunds\` and \`payments-refunds\` both answer to one tag. Silent while \`architecture/capabilities.yaml\` is absent or invalid, like the rest of that family — the FILE is the capability axis's opt-in, or carried by a flow inside a service's own project (a \`dynamic view\` beside model.likec4), where no capability can be claimed — that arm fires whether or not the vocabulary exists, and the fix is to drop the tag or move the flow to \`architecture/usecases/\` | zero matches: fix the tag, or declare the capability in the fleet vocabulary (the message offers close ids, each already spelled as the tag you would write). More than one: rename one of the colliding ids — nothing in the tag can say which the flow realizes, and a guess files every hop of it under an owner who never claimed it |
251
287
  | \`usecase.requirement-unresolved\` | a \`dynamic view\` tagged \`#req-<slug>\` whose slug names no requirement of the capability that view is about. Six failures with six fixes, and the message says which: the view resolves no \`#cap-\` tag (a \`Requirement-ID\` is unique only inside its own document, so the id has no scope), it resolves two (nothing says which document), the capability has no \`capabilities/<id>/spec.md\`, that document declares no requirements yet, it declares none flattening to this slug, or two of its ids flatten to one slug. It suppresses NOTHING: only a RESOLVED claim counts as a promise kept, so a broken tag leaves \`capability.requirement-unrealized\` firing beside it | follow the message. Several \`#req-\` tags on one view are legal and normal — a flow commonly keeps two of a capability's promises |
252
288
  | \`usecase.step-unbacked\` | a hop of a capability-tagged \`dynamic view\` that NO relationship in the model backs, on either tier. LikeC4 reports no error for it — a dynamic view is allowed to show a call the model never declared — so the diagram renders, the project parses, and every other check stays green. The finding names the file the view was written in, the step's ordinal and title, and the oriented pair | draw the edge in \`model { }\` (with \`metadata { op '<operationId>' }\` where it is a call), or, if the hop is a return step, write it as \`<target> <- <source>\`: a reply is attributed to the CALL it answers, so the \`<-\` spelling looks the hop up under the reversed pair instead |
253
- | \`usecase.step-contested\` (warn) | two or more relationships back one hop and they name DIFFERENT operations — an absent \`metadata { op }\` counts as a value there, not as a gap — so nothing can say which one the hop exercises. \`details\` lists every candidate as \`<source> -> <target> "<title>" (op: <id>)\` | give the candidates one operation, or draw the hop between the elements that name exactly one. loam names them rather than picking, because a guessed operation reads exactly like a right one in every rollup built over the flow. It suppresses \`usecase.step-unlinked\` on the same hop: one breach, one finding |
254
- | \`usecase.step-unlinked\` (warn) | the relationship backing a hop carries no \`metadata { op }\` and no \`publishes\`/\`consumes\`, while the hop's CALLER is not a person and its target resolves to a real \`services/<id>/\` — the flow reaches the fleet map and stops short of the contract it drives. \`spine.op-link-missing\` at use-case altitude. A hop FROM a person never fires it — the first hop of almost every sequence diagram is somebody using the app, and an app owes no operationId for a click — and neither does a hop INTO an \`#external\` system, an actor, a database, a cache or a queue: none of those owns a contract to name. A \`publishes\`/\`consumes\` edge already reaches an API — an AsyncAPI one | add \`metadata { op '<operationId>' }\` to the edge in \`model { }\`, or \`metadata { publishes '<message>' }\` / \`consumes\` where the hop is an event |
289
+ | \`usecase.step-contested\` (warn) | two or more relationships back one hop and they name DIFFERENT operations — an absent \`metadata { op }\` counts as a value there, not as a gap — so nothing can say which one the hop exercises. \`details\` lists every candidate as \`<source> -> <target> "<title>" (op: <id>)\` — \`(publishes: <message>)\`, \`(consumes: <message>)\` or \`(no op)\` where that is what the edge declares | say which one the hop means by opening the step's title with that candidate's operation id or message name — \`orders -> payments 'authorizePayment — authorizes the payment'\` — or give the candidates one operation. Only the title's OPENING counts, matched exactly against a name an edge declares, followed by the end of the title or by punctuation, and only among the candidates: the name as the first word of a sentence (\`'read the order'\`), a name later in it, or a relationship's own title picks nothing. Drawing the hop between containers instead works only where the map itself draws them — a container an extending model declares does not resolve on a fleet flow. loam names them rather than picking, because a guessed operation reads exactly like a right one in every rollup built over the flow. It suppresses \`usecase.step-unlinked\` on the same hop: one breach, one finding |
290
+ | \`usecase.step-unlinked\` (warn) | the relationship backing a hop carries no \`metadata { op }\` and no \`publishes\`/\`consumes\`, while the hop's CALLER is not a person and its target resolves to a real \`services/<id>/\` — the flow reaches the fleet map and stops short of the contract it drives. \`spine.op-link-missing\` at use-case altitude. A hop FROM a person never fires it — the first hop of almost every sequence diagram is somebody using the app, and an app owes no operationId for a click — and neither does a hop INTO an \`#external\` system, an actor, a database, a cache or a queue: none of those owns a contract to name. A \`publishes\`/\`consumes\` edge already reaches an API — an AsyncAPI one. Never on a hop whose caller and provider resolve to the SAME service — two containers of one service, inside its own project or drawn on the landscape — because a service owes no operationId to itself; that is the fourth mechanical guard, and its whole scope. A hop from a service-local flow into ANOTHER service's element (a stand-in model.likec4 declares for a sibling, with no \`metadata { op }\`) still warns, on the service target, exactly as it does on the fleet map | add \`metadata { op '<operationId>' }\` to the edge in \`model { }\`, or \`metadata { publishes '<message>' }\` / \`consumes\` where the hop is an event |
255
291
  | \`feature.unreadable\` | an artifact of a feature in flight could not be read — nothing about that feature was graded, and the rest of the fleet still was | fix the file the finding names; a feature silently skipped would have read as a clean one |
256
292
  | \`sources.unverifiable-from-here\` (ok) | one finding PER SERVICE whose \`sources\` only its own repo can check — a confirmation, not work, so it never trips \`--strict\`, and it is present in \`--service\` runs from the docs repo too | run \`loam validate --service <id>\` from inside that service's repo to actually grade its provenance; the fleet rollup line is derived from these findings |
257
293
  | \`docs.binary-behind\` (warn) | the docs repo's AGENTS.md stamp names a loam NEWER than the one running. This is the one finding that changes what a PASS means: a binary predating a grammar addition does not fail on the newer directive, it reads it as prose, has no join to fail, and reports green — so the corpus was checked less than the run implies. Warn rather than error because a mixed-version fleet is ordinary; \`--strict\` is the lever if it should gate | upgrade loam and re-run. Do NOT edit AGENTS.md — its stamp is correct and this binary is the older half |
@@ -267,6 +303,7 @@ intent.md, both modes:
267
303
  | \`frontmatter.field-mismatch\` | the doc names a different service/feature than the one it lives under | fix the frontmatter, or move the file |
268
304
  | \`frontmatter.status-unknown\` | a status nobody defined (\`verifed\`) | use the documented vocabulary — a typo here reads as unverified forever |
269
305
  | \`frontmatter.field-missing\` (warn) | owner, status or the identity field is absent | fill them in |
306
+ | \`frontmatter.owner-mismatch\` (warn) | one service's spec.md and arch.spec.md both name an \`owner\` and the two differ — \`loam show\` and \`loam context\` print only spec.md's, so the other is a second answer nobody sees. The \`details\` list each file's value. Compares the two files only, never CODEOWNERS | make the two agree; only a person knows which one is current |
270
307
  | \`sources.absent\` (warn) | the doc names no sources | nothing ties it to the code, so nothing can tell you when it goes stale |
271
308
  | \`sources.path-missing\` | a listed source no longer exists, or is a glob pattern (no longer supported) | the code moved — re-read it and update the doc, do not just fix the path. For a pattern, name files or directories instead |
272
309
  | \`sources.path-outside\` | a listed source resolves OUTSIDE the service's repository — an absolute path, a \`..\` escape, or a path that only exists on one machine. loam will not hash bytes beyond the repo boundary, so the tie the entry claims cannot be checked | name the path repo-relative, inside the repository; if the document was really written from another repo's files, it belongs to that repo |
@@ -274,7 +311,7 @@ intent.md, both modes:
274
311
  | \`sources.empty\` (warn) | the declared \`sources\` exist but expand to no files at all — an empty directory, or a tree the repository ignores. A digest over nothing reads as current forever | point \`sources\` at the files the document was actually written from; \`loam vouch\` refuses to stamp this state with the same sentence |
275
312
  | \`sources.skipped\` (warn) | a path under a listed source was found but NOT hashed: a symlink loam will not follow (its target is outside the repository, dangling, or not a file or directory) — the digest cannot go stale over bytes it never read | not yours to close by editing the document: either the symlink should not be under a listed source, or \`sources\` should name the real path. Report it |
276
313
  | \`sources.unvouched\` (warn) | \`sources\` with no \`sources_digest\` — nobody ever stamped it | leave it: vouching is a human's reading, not yours |
277
- | \`sources.unwalked\` (warn) | \`sources\` leaves whole top-level paths of the service repo untouched — the \`details\` name them, measured against the files git tracks. The one completeness signal loam can compute: every other check compares the documents with each other, and a corpus agrees with itself perfectly while describing a third of a service. Silent where git cannot answer (not a repository, not installed) | open them. Each one is either a part of the service nobody read — go back to the walk \`loam adopt\` printed — or something this document legitimately does not owe, and then say which and why in the hand-back. Do NOT add paths to \`sources\` that you did not read: the list is a record of what was opened, and padding it destroys the only tie the document has to the code |
314
+ | \`sources.unwalked\` (warn) | \`sources\` leaves whole top-level paths of the service repo untouched — the \`details\` name them, measured against the files git tracks — minus loam's own (\`loam.json\` and the agent files it recorded) and \`.gitignore\`, which no document owes a reading of. The one completeness signal loam can compute: every other check compares the documents with each other, and a corpus agrees with itself perfectly while describing a third of a service. Silent where git cannot answer (not a repository, not installed) | open them. Each one is either a part of the service nobody read — go back to the walk \`loam adopt\` printed — or something this document legitimately does not owe, and then say which and why in the hand-back. Do NOT add paths to \`sources\` that you did not read: the list is a record of what was opened, and padding it destroys the only tie the document has to the code |
278
315
  | \`content.stale\` (warn) | the spec's body changed since it was vouched — \`status: verified\` is standing over words nobody has read. Unlike \`sources.*\` it needs no service repo, so it fires from the docs repo too | if you edited the doc, that is the point: report it and ask a human to re-vouch. Never revert the doc or touch the digest just to silence it |
279
316
  | \`sources.sampled-vouch\` (warn) | a person vouched after reading a recorded SAMPLE of the document — the stamp carries \`vouch_scope: sampled k/n seed=…\`, so \`status: verified\` stands over sections nobody read. The status and the \`vouched\` rung are unchanged and correct; this is what says the reading behind them was partial. A \`vouch_scope\` that does not decode fires it too, deliberately: an unreadable scope must never read as a full vouch. Doc-side, so it fires from the docs repo as well | not yours to clear. NEVER edit the document, and never touch or delete the \`vouch_scope\` field, to silence it — deleting it would forge a full vouch out of a partial one. A human runs a full \`loam vouch --service <id>\` (no \`--sample\`) once they have read the rest, which removes the field; \`loam vouch --pack --service <id>\` lists exactly what that sample left unread |
280
317
  | \`sources.resolved\` (ok) | every listed source still exists in the service repo | nothing — it is the confirmation |
@@ -291,17 +328,19 @@ contract too):
291
328
  | code | what it means | what to do |
292
329
  |---|---|---|
293
330
  | \`living.requirement-outside-requirements\` | the LIVING spec holds a requirement outside \`## Requirements\`, and the merge rewrites only that section — it would land in the file twice | re-home the requirement under \`## Requirements\`, then re-run; \`--approve\` does not override this |
294
- | \`openapi.op-modified\` (warn) | the feature redefines an operation the living OpenAPI already has — the merge overwrites the living definition wholesale | make sure the redefinition is intended; if not, align the feature's openapi.yaml with the living one |
331
+ | \`openapi.op-modified\` (warn) | the feature redefines an operation the living OpenAPI already has — the merge overwrites the living definition wholesale. The message ends with whether the CURRENT fleet names the operation, and \`details\` lists who does — landscape edges, other services' living requirements, use-case hops — then any consumer that could not be checked; the same scan \`loam diff\` runs. Whether the change breaks them is not graded | make sure the redefinition is intended and safe for every consumer listed (\`oasdiff breaking\` on the two contract states tells a breaking change from an additive one); if not, align the feature's openapi.yaml with the living one |
295
332
  | \`openapi.path-item-modified\` (warn) | the delta redefines a PATH-level key (\`parameters\`, \`servers\`, \`summary\`, an \`x-\`) the living OpenAPI already has — the overwrite applies to EVERY operation on that path, including ones this feature never mentions | keep path-level keys out of the delta unless changing them for every operation on the path is the intent |
296
- | \`service.no-model\` (warn) | the archive creates \`services/<id>/\` but nothing writes its \`model.likec4\` | run \`loam adopt --service <id>\` after the archive lands, or the new service ships without a C4 center and \`validate --all\` grades it incomplete |
333
+ | \`service.no-model\` (error, never gates archive) | the archive brings a service into the fleet without writing its \`model.likec4\` — a binding the map gains with no \`specs/<id>/\` in the feature, so no directory either, or (under \`--approve\`) a \`specs/<id>/\` the merged map never draws. A service whose \`services/<id>/\` the archive creates and the merged map draws gets an empty extending model written beside its documents, with whatever the delta drew inside the service spliced into it, and raises nothing. An error because \`validate --all\` reports one next (\`landscape.binding-unknown\` for a binding with no directory, \`service.no-model\` for a directory with no model; the message names which), in \`--json\` \`warnings[]\` with \`gates: false\`, and printed by the dry run and the real run in place of the closing line | give the service its documents — \`loam adopt --service <id>\` from its repo writes the directory and the model — or write them by hand; until then \`validate --all\` grades the fleet incomplete |
297
334
  | \`openapi.component-modified\` (warn) | a component this delta declares — whether the merged operations reference it or the components ARE the delta — already exists in the living OpenAPI with different content, and the merge copies the feature's version over it wholesale | make sure the redefinition is intended; if not, align the feature's component with the living one |
298
335
  | \`openapi.ref-unresolved\` | a \`$ref\` reachable from the merged operations or a merged component resolves in neither the feature's OpenAPI nor the living one — the merge would write a dangling reference | define the missing component or fix the ref; \`--approve\` merges the dangling reference anyway. External refs (not starting \`#/\`) are never checked |
299
336
  | \`openapi.remove-marker-path-level\` | an \`x-loam-remove: true\` written at PATH level, beside the methods instead of inside the operation being retired — it names no operation, so it retires nothing, and it is not a contract key either | move the marker inside the operation (with its \`operationId\`), or delete it. The merge is safe either way — a feature-only key is never published into the living contract — but the removal you asked for will not happen |
300
- | \`asyncapi.message-modified\` (warn) | the delta redefines a \`components.messages\` slot the living AsyncAPI already has — the merge overwrites the living message wholesale | make sure the redefinition is intended; if not, align the feature's asyncapi.yaml with the living one, then \`loam rebase\` so the restatement classifies as a quote |
337
+ | \`asyncapi.message-modified\` (warn) | the delta redefines a \`components.messages\` slot the living AsyncAPI already has — the merge overwrites the living message wholesale. The message ends with whether the CURRENT fleet consumes it, and \`details\` lists who does — \`consumes\` edges, other services' \`Consumes:\` lines, use-case hops — then any consumer that could not be checked; the same scan \`loam diff\` runs. Whether the change breaks them is not graded | make sure the redefinition is intended and safe for every consumer listed; if not, align the feature's asyncapi.yaml with the living one, then \`loam rebase\` so the restatement classifies as a quote |
301
338
  | \`asyncapi.channel-modified\` (warn) | the delta redefines a \`channels\` slot the living AsyncAPI already has — the merge overwrites it wholesale, inline channel messages included (they are channel interior, never slots of their own) | make sure the redefinition is intended; if not, align the feature's channel with the living one |
302
339
  | \`asyncapi.operation-modified\` (warn) | the delta redefines an \`operations\` slot the living AsyncAPI already has — the merge overwrites it wholesale | make sure the redefinition is intended; if not, align the feature's operation with the living one |
303
340
  | \`asyncapi.component-modified\` (warn) | the delta declares a component SURFACE outside \`components.messages\` — a \`components.schemas\` entry, a security scheme — that the living AsyncAPI already has with different content, and the merge copies the feature's version over it wholesale | make sure the redefinition is intended; if not, align the feature's component with the living one, then \`loam rebase\` so the restatement classifies as a quote |
304
341
  | \`asyncapi.ref-unresolved\` | a \`$ref\` the MERGED asyncapi.yaml would carry resolves in neither the feature's document nor the living one — including a removal that deletes a slot the living document still references. A target the feature's own document DECLARES no longer fires: the merge now copies component surfaces too, so the pointer resolves in the document it lands in | define the missing target, fix the pointer, or retire the referrer in the same delta; \`--approve\` merges the dangling reference anyway. External refs (not starting \`#/\`) are never checked |
342
+ | \`openapi.info-not-merged\` (warn) | the feature's openapi.yaml spells an \`info\` key — a \`version\`, a \`description\` — differently from the living contract, and the merge carries paths and components only, never \`info\`: the living contract keeps its own, so the delta's change would be lost with exit 0. Only keys the delta declares are compared | if the \`info\` should move, edit the living \`services/<id>/openapi.yaml\` in the same change; if it should not, drop \`info\` from the delta or restate the living values |
343
+ | \`asyncapi.info-not-merged\` (warn) | the event-axis twin: the feature's asyncapi.yaml spells an \`info\` key differently from the living contract, and the merge carries channels, operations and components only | edit the living \`services/<id>/asyncapi.yaml\` in the same change if it should move; otherwise drop or restate \`info\` in the delta |
305
344
  | \`asyncapi.remove-marker-inline\` | an \`x-loam-remove: true\` nested on an INLINE channel message (\`channels.<ck>.messages.<mk>\`) — inline messages are channel interior, never slots, so the marker retires nothing; when the channel is otherwise unchanged it does not even surface as an edit | retire the whole channel (\`x-loam-remove: true\` at \`channels.<ck>\`), or declare the message under \`components.messages\` (the channel aliasing it by \`$ref\`) and put the marker there. The merge is safe either way — the deep strip keeps the key out of living — but the removal you asked for will not happen |
306
345
 
307
346
  \`loam validate <target>\` — the positional form's own finding, both modes:
@@ -316,7 +355,7 @@ always carries them):
316
355
 
317
356
  | code | what it means | what to do |
318
357
  |---|---|---|
319
- | \`c4.valid\` (ok) | the service's model.likec4 parses and the C4 axis was actually graded | nothing — it is the confirmation |
358
+ | \`c4.valid\` (ok) | the service's model.likec4 parses and the C4 axis was actually graded. Its message says HOW the file was read: "extends the fleet map — N elements · M relationships" for a model with no \`specification\` block of its own, parsed together with \`architecture/landscape.likec4\`; the plain wording for one that stands alone. A trailing \` · K tagged flow(s) graded\` says the service's own project declared use cases and they were graded — the one surface a HEALTHY intra-service flow appears on | nothing — it is the confirmation |
320
359
  | \`delta.valid\` (ok) | the feature's delta.likec4 parses and its tagged change is well-formed | nothing — it is the confirmation |
321
360
  | \`coherence.ok\` (ok) | the feature's axes agree with each other and with the fleet in flight — nothing gates its archive | nothing — it is the confirmation |
322
361
  | \`requirements.covered\` (ok) | every requirement carries at least one scenario | nothing — it is the confirmation |