@ybotok/loam 0.2.0-alpha.5 → 0.2.0-alpha.7

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 (303) hide show
  1. package/CHANGELOG.md +1063 -0
  2. package/MIGRATING-from-OpenSpec.md +7 -4
  3. package/QUICKSTART.md +8 -3
  4. package/README.md +13 -7
  5. package/ROADMAP.md +285 -11
  6. package/SCHEMA.md +566 -45
  7. package/WORKFLOW.md +23 -10
  8. package/dist/commands/adopt/render.js +47 -8
  9. package/dist/commands/adopt/render.js.map +1 -1
  10. package/dist/commands/archive/plan/landscape.js +45 -1
  11. package/dist/commands/archive/plan/landscape.js.map +1 -1
  12. package/dist/commands/archive/plan/model/extending.js +85 -0
  13. package/dist/commands/archive/plan/model/extending.js.map +1 -0
  14. package/dist/commands/archive/run.js +53 -23
  15. package/dist/commands/archive/run.js.map +1 -1
  16. package/dist/commands/context/print.js +11 -1
  17. package/dist/commands/context/print.js.map +1 -1
  18. package/dist/commands/doctor.js +46 -0
  19. package/dist/commands/doctor.js.map +1 -1
  20. package/dist/commands/explore.js +12 -2
  21. package/dist/commands/explore.js.map +1 -1
  22. package/dist/commands/list/views.js +5 -1
  23. package/dist/commands/list/views.js.map +1 -1
  24. package/dist/commands/seed/commit.js +34 -1
  25. package/dist/commands/seed/commit.js.map +1 -1
  26. package/dist/commands/seed/plan.js +158 -13
  27. package/dist/commands/seed/plan.js.map +1 -1
  28. package/dist/commands/seed/seed.js +7 -2
  29. package/dist/commands/seed/seed.js.map +1 -1
  30. package/dist/commands/show/feature.js +2 -1
  31. package/dist/commands/show/feature.js.map +1 -1
  32. package/dist/commands/show/marks.js +3 -5
  33. package/dist/commands/show/marks.js.map +1 -1
  34. package/dist/commands/show/service.js +39 -18
  35. package/dist/commands/show/service.js.map +1 -1
  36. package/dist/commands/subsystem/edit.js +4 -2
  37. package/dist/commands/subsystem/edit.js.map +1 -1
  38. package/dist/commands/subsystem/move.js +55 -17
  39. package/dist/commands/subsystem/move.js.map +1 -1
  40. package/dist/commands/subsystem/sync.js +238 -87
  41. package/dist/commands/subsystem/sync.js.map +1 -1
  42. package/dist/commands/subsystem/txn/exclude.js +132 -0
  43. package/dist/commands/subsystem/txn/exclude.js.map +1 -0
  44. package/dist/commands/subsystem/txn/report.js +174 -0
  45. package/dist/commands/subsystem/txn/report.js.map +1 -0
  46. package/dist/commands/subsystem/txn/txn.js +126 -22
  47. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  48. package/dist/commands/subsystem/txn/views.js +159 -19
  49. package/dist/commands/subsystem/txn/views.js.map +1 -1
  50. package/dist/commands/validate/arch-coverage.js +16 -9
  51. package/dist/commands/validate/arch-coverage.js.map +1 -1
  52. package/dist/commands/validate/checks/fleet-shape.js +30 -77
  53. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  54. package/dist/commands/validate/checks/health-deps.js +9 -6
  55. package/dist/commands/validate/checks/health-deps.js.map +1 -1
  56. package/dist/commands/validate/checks/vocabulary.js +4 -19
  57. package/dist/commands/validate/checks/vocabulary.js.map +1 -1
  58. package/dist/commands/validate/feature.js +1 -1
  59. package/dist/commands/validate/feature.js.map +1 -1
  60. package/dist/commands/validate/fleet/census.js +37 -13
  61. package/dist/commands/validate/fleet/census.js.map +1 -1
  62. package/dist/commands/validate/fleet/landscape.js +131 -120
  63. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  64. package/dist/commands/validate/fleet/load.js +66 -0
  65. package/dist/commands/validate/fleet/load.js.map +1 -1
  66. package/dist/commands/validate/fleet/map/attest.js +102 -0
  67. package/dist/commands/validate/fleet/map/attest.js.map +1 -0
  68. package/dist/commands/validate/fleet/map/bindings.js +109 -0
  69. package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
  70. package/dist/commands/validate/fleet/map/consumers.js +219 -0
  71. package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
  72. package/dist/commands/validate/fleet/map/isolation.js +116 -0
  73. package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
  74. package/dist/commands/validate/fleet/map/usecases.js +56 -0
  75. package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
  76. package/dist/commands/validate/fleet/scorecard/scorecard.js.map +1 -1
  77. package/dist/commands/validate/fleet/usecases/steps.js +49 -17
  78. package/dist/commands/validate/fleet/usecases/steps.js.map +1 -1
  79. package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
  80. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
  81. package/dist/commands/validate/fleet/views/ids.js +45 -15
  82. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  83. package/dist/commands/validate/fleet/views/projects.js +258 -70
  84. package/dist/commands/validate/fleet/views/projects.js.map +1 -1
  85. package/dist/commands/validate/fleet/views/root-documents.js +99 -0
  86. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
  87. package/dist/commands/validate/fleet/views/stale.js +32 -15
  88. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  89. package/dist/commands/validate/service/model/diverged.js +226 -0
  90. package/dist/commands/validate/service/model/diverged.js.map +1 -0
  91. package/dist/commands/validate/service/model/grade.js +163 -0
  92. package/dist/commands/validate/service/model/grade.js.map +1 -0
  93. package/dist/commands/validate/service/service.js +110 -34
  94. package/dist/commands/validate/service/service.js.map +1 -1
  95. package/dist/commands/validate/service/specs.js +39 -5
  96. package/dist/commands/validate/service/specs.js.map +1 -1
  97. package/dist/commands/validate/service/spine.js +41 -3
  98. package/dist/commands/validate/service/spine.js.map +1 -1
  99. package/dist/commands/validate/service/usecases/tags.js +113 -0
  100. package/dist/commands/validate/service/usecases/tags.js.map +1 -0
  101. package/dist/commands/validate/service/usecases/usecases.js +209 -0
  102. package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
  103. package/dist/commands/validate/validate.js +10 -24
  104. package/dist/commands/validate/validate.js.map +1 -1
  105. package/dist/core/agent/agents-md/artifacts.js +1 -0
  106. package/dist/core/agent/agents-md/artifacts.js.map +1 -1
  107. package/dist/core/agent/agents-md/command-map.js +19 -5
  108. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  109. package/dist/core/agent/agents-md/cycle.js +24 -9
  110. package/dist/core/agent/agents-md/cycle.js.map +1 -1
  111. package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
  112. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  113. package/dist/core/agent/agents-md/map/subsystem.js +70 -17
  114. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  115. package/dist/core/agent/agents-md/map/usecases.js +45 -3
  116. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  117. package/dist/core/agent/agents-md/refusals.js +5 -3
  118. package/dist/core/agent/agents-md/refusals.js.map +1 -1
  119. package/dist/core/agent/agents-md.js +26 -7
  120. package/dist/core/agent/agents-md.js.map +1 -1
  121. package/dist/core/agent/support/report.js +39 -5
  122. package/dist/core/agent/support/report.js.map +1 -1
  123. package/dist/core/agent/workflows/adopt.js +123 -14
  124. package/dist/core/agent/workflows/adopt.js.map +1 -1
  125. package/dist/core/agent/workflows/check.js +34 -20
  126. package/dist/core/agent/workflows/check.js.map +1 -1
  127. package/dist/core/agent/workflows/closing.js +3 -1
  128. package/dist/core/agent/workflows/closing.js.map +1 -1
  129. package/dist/core/agent/workflows/reference/spine.js +12 -6
  130. package/dist/core/agent/workflows/reference/spine.js.map +1 -1
  131. package/dist/core/brief/brief.js +88 -19
  132. package/dist/core/brief/brief.js.map +1 -1
  133. package/dist/core/brief/checks.js +133 -7
  134. package/dist/core/brief/checks.js.map +1 -1
  135. package/dist/core/brief/landscape.js +212 -129
  136. package/dist/core/brief/landscape.js.map +1 -1
  137. package/dist/core/brief/map/extend.js +81 -0
  138. package/dist/core/brief/map/extend.js.map +1 -0
  139. package/dist/core/brief/map/owed.js +284 -0
  140. package/dist/core/brief/map/owed.js.map +1 -0
  141. package/dist/core/brief/targets.js +94 -29
  142. package/dist/core/brief/targets.js.map +1 -1
  143. package/dist/core/brief/unchecked.js +13 -3
  144. package/dist/core/brief/unchecked.js.map +1 -1
  145. package/dist/core/c4/likec4.js +23 -1
  146. package/dist/core/c4/likec4.js.map +1 -1
  147. package/dist/core/c4/parsed/deployment.js +5 -3
  148. package/dist/core/c4/parsed/deployment.js.map +1 -1
  149. package/dist/core/c4/parsed/view-ids.js +39 -1
  150. package/dist/core/c4/parsed/view-ids.js.map +1 -1
  151. package/dist/core/c4/prefetch/fleet.js +142 -0
  152. package/dist/core/c4/prefetch/fleet.js.map +1 -0
  153. package/dist/core/c4/project/architecture.js +77 -7
  154. package/dist/core/c4/project/architecture.js.map +1 -1
  155. package/dist/core/c4/project/batch.js +140 -0
  156. package/dist/core/c4/project/batch.js.map +1 -0
  157. package/dist/core/c4/project/documents.js +12 -6
  158. package/dist/core/c4/project/documents.js.map +1 -1
  159. package/dist/core/c4/project/load.js +85 -42
  160. package/dist/core/c4/project/load.js.map +1 -1
  161. package/dist/core/c4/project/staged.js +53 -15
  162. package/dist/core/c4/project/staged.js.map +1 -1
  163. package/dist/core/c4/resolve/attested.js +0 -0
  164. package/dist/core/c4/resolve/attested.js.map +1 -0
  165. package/dist/core/c4/resolve/service.js +8 -17
  166. package/dist/core/c4/resolve/service.js.map +1 -1
  167. package/dist/core/c4/resolve/steps.js +18 -0
  168. package/dist/core/c4/resolve/steps.js.map +1 -1
  169. package/dist/core/c4/root-project/exclude.js +352 -0
  170. package/dist/core/c4/root-project/exclude.js.map +1 -0
  171. package/dist/core/c4/service-model/documents.js +47 -0
  172. package/dist/core/c4/service-model/documents.js.map +1 -0
  173. package/dist/core/c4/service-model/fleet/extending.js +82 -0
  174. package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
  175. package/dist/core/c4/service-model/load.js +165 -0
  176. package/dist/core/c4/service-model/load.js.map +1 -0
  177. package/dist/core/c4/service-model/renderer.js +128 -0
  178. package/dist/core/c4/service-model/renderer.js.map +1 -0
  179. package/dist/core/c4/service-model/shape.js +126 -0
  180. package/dist/core/c4/service-model/shape.js.map +1 -0
  181. package/dist/core/c4/service-model/slice.js +188 -0
  182. package/dist/core/c4/service-model/slice.js.map +1 -0
  183. package/dist/core/c4/source-scan.js +26 -0
  184. package/dist/core/c4/source-scan.js.map +1 -1
  185. package/dist/core/c4/splice/authored-source.js +60 -13
  186. package/dist/core/c4/splice/authored-source.js.map +1 -1
  187. package/dist/core/c4/splice/contract.js.map +1 -1
  188. package/dist/core/c4/splice/identity/declared.js +87 -0
  189. package/dist/core/c4/splice/identity/declared.js.map +1 -0
  190. package/dist/core/c4/splice/identity/edges.js +17 -0
  191. package/dist/core/c4/splice/identity/edges.js.map +1 -1
  192. package/dist/core/c4/splice/identity/existing.js +80 -0
  193. package/dist/core/c4/splice/identity/existing.js.map +1 -0
  194. package/dist/core/c4/splice/identity/interior.js +80 -0
  195. package/dist/core/c4/splice/identity/interior.js.map +1 -0
  196. package/dist/core/c4/splice/identity/owner.js +50 -0
  197. package/dist/core/c4/splice/identity/owner.js.map +1 -0
  198. package/dist/core/c4/splice/landscape-merge.js +154 -100
  199. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  200. package/dist/core/c4/splice/model/merge.js +140 -0
  201. package/dist/core/c4/splice/model/merge.js.map +1 -0
  202. package/dist/core/c4/splice/placement.js +14 -4
  203. package/dist/core/c4/splice/placement.js.map +1 -1
  204. package/dist/core/c4/workspace.js +2 -1
  205. package/dist/core/c4/workspace.js.map +1 -1
  206. package/dist/core/coherence/coherence.js +7 -0
  207. package/dist/core/coherence/coherence.js.map +1 -1
  208. package/dist/core/delta/delta.js +9 -0
  209. package/dist/core/delta/delta.js.map +1 -1
  210. package/dist/core/deployment/delta.js +16 -10
  211. package/dist/core/deployment/delta.js.map +1 -1
  212. package/dist/core/diff/base-state.js +21 -5
  213. package/dist/core/diff/base-state.js.map +1 -1
  214. package/dist/core/diff/victims.js +10 -1
  215. package/dist/core/diff/victims.js.map +1 -1
  216. package/dist/core/docs.js +29 -24
  217. package/dist/core/docs.js.map +1 -1
  218. package/dist/core/doctor/config.js +7 -3
  219. package/dist/core/doctor/config.js.map +1 -1
  220. package/dist/core/doctor/doctor.js +36 -8
  221. package/dist/core/doctor/doctor.js.map +1 -1
  222. package/dist/core/doctor/report.js.map +1 -1
  223. package/dist/core/doctor/reports/scan.js +242 -0
  224. package/dist/core/doctor/reports/scan.js.map +1 -0
  225. package/dist/core/explain/refusals.js +1 -1
  226. package/dist/core/explain/refusals.js.map +1 -1
  227. package/dist/core/explore/explore.js +44 -9
  228. package/dist/core/explore/explore.js.map +1 -1
  229. package/dist/core/fleet-context.js +47 -0
  230. package/dist/core/fleet-context.js.map +1 -1
  231. package/dist/core/gate/checks.js +5 -1
  232. package/dist/core/gate/checks.js.map +1 -1
  233. package/dist/core/gate/partners.js +65 -26
  234. package/dist/core/gate/partners.js.map +1 -1
  235. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  236. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  237. package/dist/core/pack/living.js +47 -13
  238. package/dist/core/pack/living.js.map +1 -1
  239. package/dist/core/pack/pack.js +9 -6
  240. package/dist/core/pack/pack.js.map +1 -1
  241. package/dist/core/projection/arch-slice.js +2 -2
  242. package/dist/core/projection/arch-slice.js.map +1 -1
  243. package/dist/core/repo/paths.js +45 -38
  244. package/dist/core/repo/paths.js.map +1 -1
  245. package/dist/core/repo/tree/render/body.js +45 -6
  246. package/dist/core/repo/tree/render/body.js.map +1 -1
  247. package/dist/core/repo/tree/render/projects.js +93 -35
  248. package/dist/core/repo/tree/render/projects.js.map +1 -1
  249. package/dist/core/repo/tree/render/service/header.js +125 -0
  250. package/dist/core/repo/tree/render/service/header.js.map +1 -0
  251. package/dist/core/repo/tree/render/service/peers.js +28 -0
  252. package/dist/core/repo/tree/render/service/peers.js.map +1 -0
  253. package/dist/core/repo/tree/render/service/view-id.js +67 -0
  254. package/dist/core/repo/tree/render/service/view-id.js.map +1 -0
  255. package/dist/core/repo/tree/render/service/views.js +78 -0
  256. package/dist/core/repo/tree/render/service/views.js.map +1 -0
  257. package/dist/core/repo/tree/render/stale.js +20 -4
  258. package/dist/core/repo/tree/render/stale.js.map +1 -1
  259. package/dist/core/repo/tree/render/views.js +149 -32
  260. package/dist/core/repo/tree/render/views.js.map +1 -1
  261. package/dist/core/repo/tree/walk.js +45 -2
  262. package/dist/core/repo/tree/walk.js.map +1 -1
  263. package/dist/core/scaffold/landscape.js +219 -9
  264. package/dist/core/scaffold/landscape.js.map +1 -1
  265. package/dist/core/staging/writes.js.map +1 -1
  266. package/dist/core/usecases/delta/claims.js +1 -0
  267. package/dist/core/usecases/delta/claims.js.map +1 -1
  268. package/dist/core/usecases/delta/overlay.js +4 -0
  269. package/dist/core/usecases/delta/overlay.js.map +1 -1
  270. package/dist/core/usecases/fleet.js +18 -6
  271. package/dist/core/usecases/fleet.js.map +1 -1
  272. package/dist/core/usecases/operations.js +7 -0
  273. package/dist/core/usecases/operations.js.map +1 -1
  274. package/dist/core/usecases/service/exclude.js +119 -0
  275. package/dist/core/usecases/service/exclude.js.map +1 -0
  276. package/dist/core/usecases/service/flows.js +161 -0
  277. package/dist/core/usecases/service/flows.js.map +1 -0
  278. package/dist/core/verify/checklist.js +42 -10
  279. package/dist/core/verify/checklist.js.map +1 -1
  280. package/dist/core/vocabulary/issue.js.map +1 -1
  281. package/dist/core/vocabulary/maturity.js +51 -1
  282. package/dist/core/vocabulary/maturity.js.map +1 -1
  283. package/examples/README.md +30 -8
  284. package/examples/docs/architecture/landscape.likec4 +47 -0
  285. package/examples/docs/architecture/services.likec4 +70 -0
  286. package/examples/docs/architecture/subsystems.likec4 +3 -0
  287. package/examples/docs/architecture/usecases/order-notification.likec4 +5 -2
  288. package/examples/docs/likec4.config.json +0 -1
  289. package/examples/docs/services/checkout-web/model.likec4 +39 -56
  290. package/examples/docs/services/order-service/arch.spec.md +13 -6
  291. package/examples/docs/services/order-service/model.likec4 +58 -70
  292. package/examples/docs/services/payment-service/arch.spec.md +7 -4
  293. package/examples/docs/services/payment-service/model.likec4 +38 -39
  294. package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
  295. package/examples/docs/services/platform/identity-service/model.likec4 +35 -37
  296. package/examples/docs/services/platform/notification-service/health.yaml +8 -3
  297. package/examples/docs/services/platform/notification-service/model.likec4 +43 -64
  298. package/package.json +1 -1
  299. package/examples/docs/services/checkout-web/likec4.config.json +0 -4
  300. package/examples/docs/services/order-service/likec4.config.json +0 -4
  301. package/examples/docs/services/payment-service/likec4.config.json +0 -4
  302. package/examples/docs/services/platform/identity-service/likec4.config.json +0 -4
  303. package/examples/docs/services/platform/notification-service/likec4.config.json +0 -4
package/CHANGELOG.md CHANGED
@@ -10,6 +10,1069 @@ case for a change — the alternative that was rejected, the defect it came from
10
10
  generalises — lives where it is maintained: [SCHEMA.md](SCHEMA.md) for the rule,
11
11
  [ROADMAP.md](ROADMAP.md) for the priority and its exit criteria, and the commit that landed it._
12
12
 
13
+ ## [0.2.0-alpha.7] - 2026-09-07
14
+
15
+ Infrastructure belongs on the fleet map, and the render is navigable at fleet scale. Still a
16
+ prerelease on the `alpha` line: `npm i @ybotok/loam@alpha` installs it. One code is REMOVED —
17
+ `landscape.datastore-private`, the first code this project has ever retired — and no flag, path or
18
+ payload key is. What a repo NOTICES on its first `validate --all` after upgrading: a
19
+ `subsystem.views-stale` error against the new `architecture/services.likec4`, in every fleet whose
20
+ map draws a service, cleared by one `loam subsystem sync`; a second one against
21
+ `architecture/subsystems.likec4` wherever subsystems exist, because those views gained boundary
22
+ edges and an `of` subject; and silence where a datastore drawn beside the services used to be told
23
+ to move.
24
+
25
+ **Upgrading: one `loam subsystem sync` per docs repo, once.** `loam validate --all` reports
26
+ `subsystem.views-stale` (error) against the new `architecture/services.likec4` in EVERY existing
27
+ docs repo whose fleet map draws a service — flat fleets with no subsystems included, because the
28
+ file is new everywhere and an absent generated file is a state of its own. This is not a
29
+ subsystems-only upgrade: a fleet that has never run `loam subsystem new` gets the finding too. A
30
+ repo that DOES have subsystems gets a second finding on the same run, against
31
+ `architecture/subsystems.likec4`, because the subsystem views gained boundary edges and an `of`
32
+ subject and every existing copy therefore re-renders differently. One `loam subsystem sync` writes
33
+ both files and clears both findings. `loam archive` names the same repair rather than claiming
34
+ "complete + current". Nothing else in a repository has to change, and no document a team wrote is
35
+ touched — and a document a team wrote that is already standing at one of the two generated paths is
36
+ refused by name rather than overwritten (below).
37
+
38
+ ### Removed — `landscape.datastore-private`, because where a store is drawn is the team's choice
39
+
40
+ - **`landscape.datastore-private` (warn) is retired.** It fired on a `database` element drawn at
41
+ fleet level with exactly ONE consumer, and its whole message prescribed deleting the peer and
42
+ writing the store inside its owner. The identical store nested was silent, so the check graded the
43
+ DRAWING and not the architecture — a restriction rather than a fact about the fleet. A store one
44
+ service owns may now be nested under that service's element or drawn beside the services, and loam
45
+ grades neither. The code is not in the frozen `ErrorCode` or `IssueCode` unions, so removing it
46
+ breaks no contract; `loam explain landscape.datastore-private` no longer answers, and the row is
47
+ gone from the agent-facing check table.
48
+ - **`landscape.datastore-shared` (warn) is unchanged**, both arms. Two or more services on one store
49
+ is a fact about coupling, not a restriction, and it fires at two consumers for both placements
50
+ alike. What survives from the retired code's message is the one clause that was a fact about
51
+ LikeC4 rather than a preference, and SCHEMA's "Private stores" now leads with it: a second
52
+ consumer's edge resolves only against a declaration the MAP holds, because a per-service project
53
+ is `architecture/` plus one model.
54
+ - **Infrastructure is not a service.** A new `INFRA_KINDS` set in `core/vocabulary/maturity.ts` —
55
+ `database`, `queue`, `topic`, `cache`, `bucket`, compared case-insensitively — takes such an
56
+ element out of four surfaces at once: the drawn-service census behind
57
+ `landscape.service-undocumented`, the `loam gate` partner set (an infra endpoint is dropped from
58
+ the `partners` payload, exactly as an actor already was), the `loam verify` checklist (a
59
+ fleet-level store no longer mints a `service.exists` claim no repository can answer), and the
60
+ scorecard's element denominator, whose `c4` row now counts services rather than services plus
61
+ their stores. The hook is the kind NAME, with the same limitation `ACTOR_KINDS` lives with: a
62
+ fleet spelling its kind `postgres` is not covered, and a tag can be added later as a second
63
+ spelling. A store BOUND to a directory with `metadata { service }`, or titled like one, stays an
64
+ adopted service on every one of the four surfaces — the census predicate wins over the kind.
65
+ `landscape.service-undocumented`'s message gained a THIRD remedy beside "bind it" and "tag it
66
+ `#external`", spelled from the same set: neither of the old two was true of a store the fleet
67
+ owns, and that message is the only place an author learns which kind spellings loam reads.
68
+ - `c4.uncovered` was left alone, and the measurement is the reason: a delta drawing
69
+ `splitLedger = database 'split-ledger'` earns it, and adding `Covers: splitLedger` to the
70
+ feature's `arch.spec.md` delta silences it exactly, with no `covers.unknown`. The remedy the
71
+ message names is true, so this is not a residual restriction — and the code fires only on elements
72
+ a feature DELTA adds, never from `validate --all` over a living tree.
73
+ - The acceptance criterion, run for real: a `database` drawn at fleet level, owned by the fleet, with
74
+ one consumer, no `#external` tag and no binding, now earns ZERO findings from `loam validate --all`
75
+ and from `loam gate`.
76
+
77
+ ### Added — `architecture/services.likec4`: one generated overview view per service
78
+
79
+ - **New generated path `architecture/services.likec4`**, written by `loam subsystem sync` beside
80
+ `architecture/subsystems.likec4`, removed by it when the fleet map draws no service, and owned
81
+ outright by loam: views only, byte-reproducible, one view per service the map resolves, sorted by
82
+ service directory id. Each view is declared `of` its service's element, which is what makes the
83
+ fleet map, every subsystem view and every other service view click-through into it with no
84
+ authored link — measured at likec4 1.59.2. Peers are NAMED one by one rather than wildcarded,
85
+ because on a map that groups its services the wildcard form collapses every peer to its group box
86
+ and yields no drill-down at all (measured on a 120-service fleet: 0 of 10 nodes navigable). Median
87
+ at 58 services in 11 groups: 10 nodes, 8 edges per view, about 7 ms of layout each.
88
+ - **Titles are folders.** A per-service view is titled `<subsystem path> / <service directory id>`,
89
+ built from the same path helper the subsystem views use, so both generated files fold into one
90
+ browser folder tree mirroring `services/`. LikeC4 has no view-folder syntax — `views { group … }`
91
+ is a parse error at the pin — and splitting the title on `/` is the whole mechanism.
92
+ - **An authored view of the same element keeps precedence.** Two views `of` one element are legal;
93
+ the first document alphabetically wins the default navigation target and both stay reachable,
94
+ because the subject's own node cycles to the other. `architecture/landscape.likec4` sorts before
95
+ `architecture/services.likec4`. loam does not detect the authored view and never will:
96
+ docs/DESIGN.md rule 26 forbids reading what a view SHOWS.
97
+ - **Subsystem views gained two things** in the same generator: `include <member> -> *, * -> <member>`
98
+ per member, so a subsystem view answers "what does this group touch" and not only "who is in it",
99
+ and `view subsystem_<name> of <ancestor>` when the map holds an element owning exactly that
100
+ subsystem's members. Measured at 58/11: ~14 nodes, ~38 edges, 12 of 14 nodes navigable. Every
101
+ existing `architecture/subsystems.likec4` therefore re-renders differently and is stale once.
102
+ - **`subsystem.views-stale` now grades two files**, one finding per file, each naming its own path in
103
+ `subject`, `locations[0]` and the message. No new code was minted: both files are written by one
104
+ command in one commit and repaired by one run, which is what makes one code honest.
105
+ - **`subsystem.view-id-collision` reserves `service_` as well as `subsystem_`.** A `service_` id is
106
+ reserved only for a service the fleet map actually DRAWS, so an unmodelled `services/<id>/`
107
+ reserves nothing; the message names which generated file and which prefix.
108
+ - `deployment.doc-reserved` refuses a feature deployment document named `services.likec4` for the
109
+ same reason it already refused `subsystems.likec4`: both names are generated, overwritten
110
+ wholesale, and parsed by nothing.
111
+ - **No writer overwrites a generated views file it did not write, and refusing is now an exit 1.**
112
+ Both generated paths are filenames loam RESERVES inside `architecture/`, a directory the team
113
+ owns, and every writer of them rewrites the file whole — so a repository that already kept a
114
+ hand-authored diagram at one of those paths would have lost it, silently, on the first run after
115
+ the release that reserved the name. Reproduced before the guard existed: with a hand-authored
116
+ `architecture/services.likec4` in the tree, `loam subsystem new platform` printed
117
+ `architecture/services.likec4 updated` and destroyed it, first line and all.
118
+ - **What makes a file loam's is its FIRST LINE**, `// generated by loam`, with a leading byte-order
119
+ mark stripped before the test — a Windows editor adds one without changing a fact, and without the
120
+ strip loam would call its own output authored and refuse to regenerate it for good. A hand EDIT of
121
+ a generated file keeps that line, so it is still loam's to repair and one `loam subsystem sync`
122
+ puts it back: that has always been what `subsystem.views-stale` says. Only a file that never
123
+ carried the mark is somebody else's. **A 0-byte file at either path counts as somebody else's** —
124
+ it carries no mark — so an empty `architecture/subsystems.likec4` or
125
+ `architecture/services.likec4` is no longer filled in; delete it and re-run.
126
+ - **`loam subsystem sync` declines the one file and finishes the rest.** `--json` reports the new
127
+ `action` value **`refused`** — on `action` for `architecture/subsystems.likec4`, on
128
+ `serviceViews.action` for `architecture/services.likec4`, one predicate behind both — while the
129
+ other generated file, the per-service project files and the root `exclude` are still done and
130
+ still reported, and the **exit code is 1**. That is `loam validate`'s shape rather than a refusal
131
+ envelope, and the non-zero exit is the point: `loam validate --all` goes on reporting
132
+ `subsystem.views-stale` for that path with `loam subsystem sync` named as the repair, so a run in
133
+ a hook or a CI step must not report success over a file it can never fix.
134
+ - **`loam subsystem new` / `rm` / `move` / `rename` and `loam seed` refuse WHOLE**, before a byte is
135
+ staged: `error.code` `already-exists`, exit 1, nothing written and — for `move` and `rename` —
136
+ nothing renamed. No new code was minted, because that is what `already-exists` has always meant on
137
+ these verbs; `loam seed` can answer it for the first time. They refuse whole rather than per-file
138
+ because their entire contract is that the generated files land in the SAME commit as the change,
139
+ so completing the structural half would manufacture the very `subsystem.views-stale` state that
140
+ commit window exists never to leave behind — irreversibly, for a `move` that also stages renames.
141
+ - **The refusal sentence is one sentence, verbatim from all five writers.** It names the mark loam
142
+ looked for, says that not one byte changed, and gives the way out: rename the file to a name loam
143
+ does not reserve — every other `.likec4` under `architecture/` renders in the same project, so the
144
+ diagram is not lost — or delete it, then re-run.
145
+ - **Payload keys, all additive.** `loam subsystem sync --json` keeps `path` and `action` meaning the
146
+ subsystem file and adds `serviceViews: { path, action }`; `loam subsystem new` / `move` / `rm` /
147
+ `rename --json` keep `views` and add `serviceViews`; `loam archive --json` keeps
148
+ `subsystemViewsStale` and adds `staleViews: string[]`, the generated files it left behind. Nothing
149
+ existing changed meaning.
150
+ - The generated-file SET is now one table (`GENERATED_ARCHITECTURE_FILES`), read by the path
151
+ builders, the name reservation, both project loads that must EXCLUDE these files (the fleet map's
152
+ and `c4.fleet-project-invalid`'s), archive's staging and `loam seed`'s. Four readers that must
153
+ never disagree about which files are generated, and a name in one but not another failed silently
154
+ both ways while there was only one.
155
+
156
+ ### Added — the intra-service use-case slot, published rather than graded
157
+
158
+ - **`loam adopt` briefs `services/<…>/<id>/usecases/<name>.likec4` as an OPTIONAL target**, with the
159
+ tag grammar and a worked example: a `dynamic view` tagged `#req-<Requirement-ID>` as the first
160
+ thing in the view body, the tag declared once (in the landscape's `specification` for an extending
161
+ model, in the model's own for a standalone one), every hop backed by a relationship the model
162
+ declares, and the view id namespaced by the service because an extending model's views share the
163
+ root project's flat id namespace. Optional-and-absent prints lowercase `missing` and is not a
164
+ finding — the existing mechanism for an optional artifact, not a new one.
165
+ - **The generated AGENTS.md names the slot and its title convention**,
166
+ `'<subsystem path> / <service id> / <use case>'`, so an authored use-case view lands in the same
167
+ browser folder as its service's generated overview.
168
+ - **No new finding code, and no check on absence.** docs/DESIGN.md rule 26 — presence is never owed,
169
+ and no check may grade a view's absence — is enforced by `scripts/arch-check.mjs`, so there is no
170
+ service.usecases-missing and there never will be. The grader (`c4.valid`'s
171
+ ` · K tagged flow(s) graded`) has existed since 2026-09-03; all that was missing was anyone being
172
+ told. A usecase.flow-untagged check, which would grade a view that EXISTS, is recorded on
173
+ [ROADMAP.md](ROADMAP.md) with its promotion bar rather than built.
174
+
175
+ ## [0.2.0-alpha.6] - 2026-09-04
176
+
177
+ A service model may extend the fleet map, and the nine problem reports one fleet filed against the
178
+ previous release are closed. Still a prerelease on the `alpha` line: `npm i @ybotok/loam@alpha`
179
+ installs it. Nothing below removes a code, a flag or a payload key; two things a script may have
180
+ read do move: every LikeC4 line number loam prints is 1-based now, and seven feature-delta codes
181
+ plus the feature arm of `usecase.flow-invalid` answer `--feature <FEAT-id>` in
182
+ `loam explain --json` where they answered `--service <id>`. What a repo NOTICES on its first
183
+ `validate --all` after upgrading: a model written in the extending shape earns the renderer-wiring
184
+ warnings until one `loam subsystem sync` — which now also deletes a stray per-service
185
+ `likec4.config.json` beside such a model — a root `exclude` that hides the map is
186
+ `landscape.excluded`, and a standalone model whose copies of the map's elements disagree with it
187
+ is `c4.declaration-diverged`; a repository whose models all stand alone is asked to migrate
188
+ nothing, and its root `exclude` is not touched.
189
+
190
+ ### Fixed — one rule for a service's own edge, and the document that actually failed, on every surface
191
+
192
+ - `loam context` and `loam explore` name the `architecture/` document that ACTUALLY failed in their
193
+ printed warning, off the `landscape.broken` key their `--json` payloads already carried. `parses`
194
+ answers for the PROJECT, so with a broken sibling the landscape file has no errors at all — and
195
+ both printers still said `architecture/landscape.likec4 does not parse`, a claim about bytes loam
196
+ had read fine. `explore`'s absent arm keeps the file name: `present` is about the FILE, which is
197
+ the write target every brief asks for edits to.
198
+ - An edge whose two endpoints resolve to the SAME service is that service's own internal wiring,
199
+ and no surface files it as an inbound call any more. `loam adopt --json` already dropped it; the
200
+ one shared derivation does now, so `loam context`, `loam explore`, `loam show` and
201
+ `loam vouch --pack` stopped reporting a service as its own caller (`← <id> "reads"`), and
202
+ `loam list`'s fleet-wide spelling of the same rule stopped reading an `op`-carrying self-edge as
203
+ proof that somebody calls the service — a service nobody calls was told it owed an OpenAPI
204
+ contract, grading `partial` in `list` while `context` said `documented`. `landscape.touched` and
205
+ `landscape.service-isolated` are unchanged: they read the relationships, so the map still draws
206
+ an edge on the service.
207
+ - The adopt brief's `service.model-excluded` row describes both entry shapes: one covering the
208
+ DIRECTORY, which `loam subsystem sync` rewrites, and one shaped like a FILE
209
+ (`services/**/model.likec4`), which sync cannot repair and has to be removed or narrowed by hand.
210
+ The finding's own message already said which one you have; the brief promised sync for both.
211
+ - `loam diff` classifies a base ref's tree with the same reserved-directory rule the live walk uses
212
+ (`adrs/`, `usecases/`, `ui/` — shared as one set, not respelled). A service directory holding only
213
+ the `services/<id>/usecases/` slot used to be absent from the base AND to contribute a phantom
214
+ service called `usecases`, so committing it and diffing the unchanged tree reported one addition
215
+ and one removal.
216
+
217
+ ### Added — one model, many documents: a service model may extend the fleet map
218
+
219
+ - `services/<…>/<svc>/model.likec4` now comes in two shapes, and the FILE's own grammar decides
220
+ which — never a config key, never a flag. A model declaring no `element` kind inside a top-level
221
+ `specification { }` block is an **extending model**: loam parses it inside the `architecture/`
222
+ project and it says what is inside the service with `extend <fully-qualified id> { … }` on the
223
+ element the map already binds to the directory, re-declaring no kind, no tag and no partner. A
224
+ model that declares one is a **standalone model**, parsed alone exactly as every model was
225
+ before — legal forever, and not what `loam adopt` briefs any more. SCHEMA.md's new
226
+ "Two shapes of a service model" carries both, the grades below, and the ordered migration recipe.
227
+ - New `c4.declaration-diverged` (warn, service target, subject the service, one finding per element):
228
+ a STANDALONE model and the fleet map (`architecture/`) both declare one element, and the
229
+ two copies disagree about `kind`, `title`, the tag SET or the `metadata { service }` binding. The
230
+ message prints both declarations, names which fields differ, and offers the two repairs — copy the
231
+ map's declaration verbatim, or migrate to the extending shape. `description` is deliberately not
232
+ compared: it is prose nothing grades, while the other four each change what a view shows or what
233
+ loam resolves. Silent while the map does not parse. This is the check that had no code: a fleet's
234
+ copies of `kafka` and its identity provider had already lost `#platform` on every copy but the
235
+ map's, and nothing in loam read two documents together.
236
+ - New `c4.element-unowned` (warn, service target, subject the service, one per element): an
237
+ EXTENDING model declares an element outside the element that resolves to its own service. A system
238
+ the service reaches belongs in `architecture/landscape.likec4`, declared once; another service's
239
+ internals belong in that service's model.
240
+ - New `c4.fleet-project-invalid` (warn, `landscape` target, subject the service whose directory
241
+ holds the file): `--all` only, only when at least one extending model exists, the map itself
242
+ parses, and no root `exclude` entry hides the map (`landscape.excluded` below is then the whole
243
+ answer). loam loads every `.likec4` the root LikeC4 project reads except the generated
244
+ `architecture/subsystems.likec4` — the map, its siblings, every extending model and every
245
+ `.likec4` beside one — and reports each error that is NOT already present in the map alone, in a
246
+ per-service project, or in a service-flow project the same run made. That difference is the class
247
+ no per-service grade can see: two services each declaring `tag req-AUTH` locally, two models
248
+ adding the same child id under one element, a broken untagged sibling nobody opted in. All three
249
+ are renderer-fatal and all three were silent.
250
+ - Three new renderer-wiring warnings on the `landscape` target, root-gated like
251
+ `service.likec4-config-missing` and repaired by the same `loam subsystem sync`:
252
+ `service.likec4-config-stray` (a `likec4.config.json` beside an EXTENDING model — the renderer
253
+ registers a project holding nothing, or holding the model alone where it cannot parse),
254
+ `service.model-excluded` (the root `likec4.config.json`'s `exclude` covers an extending model's
255
+ directory, naming the entry that does it, so the renderer never loads the model) and
256
+ `service.model-unexcluded` (it does NOT cover a STANDALONE model's directory, so the renderer
257
+ merges that model into the map and reports every kind and element it declares as a duplicate —
258
+ blanking the whole root project, not one service).
259
+ - New `landscape.excluded` (warn, `landscape` target, `loam validate --all`) — the root
260
+ `likec4.config.json`'s `exclude` covers `architecture/landscape.likec4` itself. loam keeps
261
+ grading the map whatever that list says, so every finding in the run is right; the renderer does
262
+ not, and until now nothing said so. A repo notices it as one line at the top of `validate --all`
263
+ naming the entry verbatim — typically an `architecture/*.likec4` written for a palette — INSTEAD OF
264
+ the `c4.fleet-project-invalid` cascade the same entry used to produce, and it is the only line in
265
+ the run that names the cause. `c4.fleet-project-invalid` is not graded while the entry stands: that
266
+ check loads the fleet project the way the renderer does, with no floor under the map, so the one
267
+ line came back as an unresolved `extend` in every extending model — 161 warnings on the packaged
268
+ five-service example. Its message says the fleet project is unread rather than clean, and grading
269
+ resumes the moment the entry goes. Never gating: with the map kept by the loader, every fact loam grades holds
270
+ identically either way. The repair is the team's own edit — `loam subsystem sync` maintains only
271
+ the `services/` entries and will not touch it, and the message says so rather than sending its
272
+ reader to a command that reports success and changes nothing.
273
+ - `loam subsystem sync` now maintains the root `likec4.config.json`'s `exclude` as well as the
274
+ per-service files. It rewrites the list to hold `services/<tree>/**` for exactly the models that
275
+ stand alone: the wholesale `services/**` and every `services/<tree>/**` naming a directory that IS
276
+ a service are removed, every other entry is kept in the order the team wrote it (a
277
+ `services/legacy/**` for a directory that is no service survives), and the standalone entries are
278
+ appended sorted. It runs when at least one extending model exists or when a standalone model's
279
+ directory is not covered, so a repository whose models all stand alone behind `services/**` is
280
+ never touched. Same staged transaction as the rest of `sync`; `--json` gains the additive
281
+ `projects.exclude` (whether it rewrote, the resulting entries, and what was added and removed) and
282
+ the text view prints one line when it did. A root config that is not JSON is left alone with a
283
+ printed note.
284
+ - `loam init --docs <path> --create` now scaffolds `likec4.config.json` with
285
+ `exclude: ["**/node_modules/**", "features/**"]`. `services/` is deliberately not excluded — the
286
+ models a fresh repository writes extend the map, and the root project is the only place they
287
+ parse. A feature's `delta.likec4` keeps its own `specification` block and stays excluded.
288
+ - The landscape the scaffold writes teaches the extending shape. Its `views` paragraph told a fresh
289
+ repository that a service model "renders from its OWN directory" and that "each declares its own
290
+ `specification` block, so the renderer can only be given one of them at a time" — written before
291
+ an extending model belonged to the root project, and printed in the same `loam init --docs .
292
+ --create` run that now excludes `features/**` precisely because it does. And the check that says
293
+ a map is still untouched scaffolding now accepts EVERY generation loam has shipped, not just the
294
+ current text: it is a byte-suffix match, so rewording the comment reclassified every older
295
+ untouched scaffold as hand-authored — `loam seed` refused one its own scaffold had written
296
+ (`seed-landscape-edited`, nothing written) and `loam status` dropped the teaching rung from the
297
+ empty-fleet ladder. The two `v0.1.0-beta` stubs (no `views` block, no `tag platform`) and the
298
+ `v0.2.0-alpha.1`-through-`.5` stub are all recognised, frozen beside the writer.
299
+ - `loam adopt --service <id>` briefs the extending shape: all four `shape` rules of the
300
+ `model.likec4` target are rewritten, and the `example` is a `model { extend <id> { … } }` document
301
+ with the fleet element's real id substituted in where the brief resolves exactly one. The brief's
302
+ `checks[]` gains `c4.declaration-diverged`, `c4.element-unowned`, `service.likec4-config-stray`,
303
+ `service.model-excluded`, `service.model-unexcluded` and `c4.fleet-project-invalid`, and its
304
+ renderer statement in `unchecked[]` says the root project now holds the map and every extending
305
+ model. `/loam-adopt` steps 3, 4 and 5 and `/loam-check`'s three fix tables carry the same.
306
+ - The packaged example fleet (`examples/docs`, what `loam init --example` copies out) is migrated:
307
+ all five models extend the map, the five `services/**/likec4.config.json` are gone, the root
308
+ `exclude` is the scaffold's two entries, `architecture/landscape.likec4` gains `element database`
309
+ and the `notification-provider` system that notification-service's model used to declare for
310
+ itself, and every `Covers:` entry and `views` block names the map's fully-qualified ids. Its
311
+ `loam validate --all` result is unchanged: 0 errors and the same ten deliberate warnings.
312
+ - `loam archive` now writes a service's OWN `model.likec4`, not just the fleet map. A delta's
313
+ additions nested under a service whose model EXTENDS the map — a container, a store, a
314
+ container-level edge — are spliced into that model's `extend <fqn> { … }` block instead of into
315
+ `architecture/landscape.likec4`, and a model with no `extend` block for that fqn gains one. A
316
+ relationship goes to the model of the service owning its SOURCE, else its TARGET, else stays on
317
+ the map: a service-level edge is where every fleet check reads it. A service-level element, a
318
+ STANDALONE model and a service with no model.likec4 keep the previous behaviour byte for byte.
319
+ What a repo notices: `loam archive` prints a second
320
+ `architecture: merged into services/<…>/model.likec4 — +N element(s), +M relationship(s)` line
321
+ beside the landscape one, the model path appears in the `--json` plan (and in `--dry-run`) as an
322
+ ordinary write, `loam unarchive` restores it from the same `.loam-before` snapshot as any other
323
+ overwritten file, and the service is no longer `c4.invalid` (duplicate declaration) the moment its
324
+ model declares the container the adopt brief told it to declare. No new payload key and no new
325
+ error code. Re-archiving adds nothing a second time — existence is checked against the models too
326
+ — and two archives touching one model's `extend` block land adjacently.
327
+ - The archive's existence check now reads the extending models by TITLE as well as by id, so a
328
+ container a service already draws under a different identifier is recognised instead of being
329
+ spliced in beside itself: a delta adding `cache = container 'Order cache'` under a service whose
330
+ model already declares `orderCache = container 'Order cache'` adds nothing and leaves the model
331
+ byte for byte, which is what the same delta against the same title on the fleet map has always
332
+ done. Nothing is routed to that model, so the plan names only the landscape — the second
333
+ `merged into services/<…>/model.likec4` line appears when a model actually receives something.
334
+ The models' title join is scoped to one service — it fires only when both the model's
335
+ declaration and the addition resolve to the SAME explicitly bound service — because every
336
+ extending model in a fleet names a container `api`, and the map's global title index is a
337
+ pre-binding fallback, not a rule to spread.
338
+ - An extending model with no `model { }` block — an empty file, a `views { }`-only file, a tags-only
339
+ `specification { }`, all three shapes loam calls extending on purpose — gains one when a feature's
340
+ addition is routed into it, instead of refusing the whole archive with `merge-failed`. It is the
341
+ same call the merge already makes one level down, where a model that has never named a container's
342
+ parent gains the `extend <fqn> { }` block rather than refusing.
343
+ - `loam archive` proves the model it would write. The merged model is loaded as the project the
344
+ per-service grade reads it in — the `architecture/` documents with this archive's own merged
345
+ landscape in place, plus the model — and an archive whose model would not parse is refused at plan
346
+ time (`merge-failed`) with nothing written, naming the model path and the first three errors. Two
347
+ causes actually occur, and the refusal names both: a kind or tag the map's `specification` block
348
+ does not declare, and a reference to ANOTHER service's interior, which lives in that service's own
349
+ model and is in no project this one can see. A container-level edge whose two ends belong to two
350
+ different services is the case that changes: it used to merge, because both containers landed on
351
+ the map beside it, and it is now refused — there is no legal placement for it, and the answer is
352
+ to draw that call service to service.
353
+ - What a repo notices: nothing, until a model is written in the new shape or `loam subsystem sync`
354
+ is run. A repository whose models all stand alone keeps every finding it had, and its root
355
+ `exclude` is not touched. Write one extending model — or upgrade a repo whose scaffold predates
356
+ `services/**` being excluded — and the next `loam validate --all` reports the renderer-wiring
357
+ warnings above until one `sync`; that one sync rewrites the root `exclude`, writes the project
358
+ file a standalone model is owed and DELETES the stray one beside every extending model, so after
359
+ it the renderer's project picker holds one entry instead
360
+ of one per adopted service, and a fleet-wide duplicate that used to blank the whole picture is
361
+ reported as `c4.fleet-project-invalid` before anybody opens the renderer.
362
+
363
+ ### Changed — a model's grades follow its shape, and `--service` reads the whole map project
364
+
365
+ - `service.likec4-config-missing` is narrowed to STANDALONE models. A model that extends the map
366
+ belongs to the root project, so it is owed no per-service `likec4.config.json` and never earns
367
+ this warning; the same directories earn `service.likec4-config-stray` instead while a file left
368
+ over from before the migration is still sitting there, and `loam subsystem sync` — which now also
369
+ stops writing the file for extending models — is the repair for both. The message is reworded to
370
+ say which shape it is about.
371
+ - `loam validate --service <id>` now reads the whole `architecture/` project, exactly as `--all`
372
+ does, instead of `architecture/landscape.likec4` alone. A use-case file under `architecture/` that
373
+ does not parse therefore reports `spine.landscape-invalid` on a single-service run too, where it
374
+ used to be visible only under `--all`. `c4.declaration-diverged` compares against that same
375
+ project.
376
+ - `c4.invalid` and `c4.valid` keep their codes and change scope with the shape. For an extending
377
+ model every error the per-service project raises is the MODEL's — including one the parser blames
378
+ on `architecture/landscape.likec4`, because the map parses clean on its own — and `c4.valid`'s
379
+ message reads `extends the fleet map — N elements · M relationships`. When the map itself does not
380
+ parse, an extending model cannot be read at all: neither code is emitted for it, and
381
+ `spine.landscape-invalid` carries one clause more saying the model extends the map and so cannot
382
+ be read either.
383
+ - `landscape.service-unmodelled` is raised on the `--service` target too, and stays an error. An
384
+ EXTENDING `model.likec4` says what is inside an element the fleet map binds, so a service whose
385
+ directory no map element resolves to has nothing to be inside and no count in the model means
386
+ anything: `loam validate --service <id>` reports it and exits 1 where the same tree used to come
387
+ back `c4.invalid` for a model read alone. `/loam-check`'s `--service` table carries a row of its
388
+ own for it and `loam explain landscape.service-unmodelled` answers for both scopes. A standalone
389
+ model is unaffected — the fleet run still owns that state.
390
+ - `subsystem.view-id-collision`'s census covers every extending model's authored view ids under
391
+ `--all`, not the landscape's alone: those documents are in the same root project now, and a view
392
+ id claimed twice there makes the renderer refuse the whole project.
393
+ - `spine.landscape-invalid` names the document that actually broke. The finding is raised against
394
+ the whole `architecture/` PROJECT and the message hardcoded `architecture/landscape.likec4`, so an
395
+ `architecture/usecases/x.likec4` with a typo sent every service's reader to a file that parses and
396
+ the real path appeared nowhere in the payload. The message now lists the broken documents
397
+ repo-relative and comma-separated — the spelling `landscape.invalid` already used under `--all` —
398
+ each `details` line carries its own file once two or more are broken, and `locations` carries the
399
+ first broken file as `primary` beside the service directory as `scope`. `loam explain`'s "fix the
400
+ file the message names" is true now.
401
+ - `c4.declaration-diverged` finds the copies it was written for. It joined on the literal element
402
+ id, so in a GROUPED map — one nesting its services under a parent, as loam's own example does — a
403
+ standalone model's `paymentService` never met the map's `marketplace.paymentService` and the check
404
+ was silent on the shape it exists for. The join is now the id; then the `metadata { service }`
405
+ binding when both documents wrote one; then the map element whose id ends with the model's as a
406
+ dotted tail. A tail two map elements answer to joins nothing — a divergence reported against the
407
+ wrong peer is worse than the silence. The message names the other side as `the fleet map
408
+ (architecture/)` rather than a file it never read, and names the map's own id when it differs.
409
+ - `c4.declaration-diverged` no longer joins a standalone model's `db`, `api` or `agent` to a
410
+ container the map draws INSIDE another service. The dotted-tail rung stands, and a tail that
411
+ carries a dot (`paymentService.api` ↔ `marketplace.paymentService.api`) is joined as before; the
412
+ BARE last segment is now a key only for a FLEET-LEVEL map element — one no ancestor of which
413
+ stands for a service, which is a top-level element and equally a box the map draws inside a plain
414
+ grouping element, by the same fleet-census predicate every other fleet check asks. Grouping is a
415
+ drawing convenience, not ownership: a `cache` the map draws inside an `infra` group is the FLEET's
416
+ cache, so a standalone model that declares its own top-level `cache` gives one name two meanings
417
+ and is reported. Only a SERVICE owns an interior. What a repo notices: a private cache called
418
+ `db` beside another service's `db` was warned about and told to copy that service's title
419
+ verbatim; it is now silent. loam's own `meta/docs` carried the workaround for it, and no longer
420
+ does — its `coding agent` element is spelled `agent` again.
421
+ - `c4.declaration-diverged` stops asking for a copy that is already there — in both directions.
422
+ Since LikeC4 1.59.0 a kind can carry tags and every element of that kind arrives with them, so a
423
+ tag difference can be one no element declaration writes and no copy can fix. loam now decides that
424
+ by SIMULATING the copy rather than by matching one shape of it: when the tag sets are all that
425
+ differ and copying the map's literal tags across would leave them still differing, the message
426
+ says so, names `specification { element <kind> { … } }` as where they come apart, and says WHICH
427
+ document declares tags on that kind, so an author knows which file to open. Both shapes reach it
428
+ now: the model writing `#external` on an element whose map twin inherits `#external #platform`
429
+ from `element topic { … }` used to be told to copy the map's bare line, and doing exactly that
430
+ produced the other arm of the same message (measured on `examples/docs`' order-service). Grammar:
431
+ "the kind differs" / "the title differs" / "the tags differ".
432
+ - Three messages say what they could not say before. `c4.invalid` on a model written in BOTH shapes
433
+ at once — an element kind declared in `specification { }` beside an `extend` — gains one `details`
434
+ line saying the kind is what makes the file standalone, so the `extend` resolves nothing, and
435
+ which of the two lines to drop. `usecase.flow-invalid` whose project refuses an undeclared
436
+ `#req-`/`#cap-` tag gains one line naming the `tag <name>` declaration that is missing and the
437
+ `specification { }` block it belongs in. And `c4.valid` gains ` · K tagged flow(s) graded` when
438
+ the service's own project declared any, so a HEALTHY intra-service flow is visible on one surface
439
+ instead of none. Both hints are the FIRST `details` line rather than the last: the cap is ten
440
+ lines in text and in `--json`, and a model written in both shapes at once produces dozens of
441
+ cascading errors, so the one line that diagnosed it was truncated away in exactly the case it
442
+ exists for.
443
+ - A `.likec4` beside `model.likec4` that the service's own `likec4.config.json` `exclude` covers is
444
+ no longer read, graded or reported. loam staged every sibling regardless, so a file the renderer
445
+ never loads earned `usecase.flow-invalid` under a message claiming the project is read the way the
446
+ renderer reads it. Standalone models only — that file IS the project config there; a model that
447
+ extends the map is read in the root project, and a per-service config beside it is
448
+ `service.likec4-config-stray`. The RECURSIVE spelling counts too: a leading `**/` compiled to a
449
+ required `/`, so `**/usecases/**`, `**/views.likec4` and `**/*.likec4` matched nothing while the
450
+ renderer (measured, likec4 1.59.2) hides exactly what they name — `usecase.flow-invalid` gated the
451
+ run at exit 1 over a file in no project loam was reading.
452
+ - `spine.landscape-invalid` says "a, b have N error(s)" when it names more than one document; it
453
+ read "a, b has N error(s)".
454
+ - `c4.element-unowned` leads with the remedy that fits a private store. The message listed the two
455
+ remedies that move an element OUT of the service — the map, or another service's model — and
456
+ omitted the one an author usually wants: a store or component this service owns goes INSIDE the
457
+ `extend <fqn> { }` block, where its id becomes `<fqn>.<name>`. Following what the tool printed
458
+ moved a private store to fleet level and earned `landscape.service-undocumented`.
459
+
460
+ ### Added — problem reports carry an ordinal and a status, and `loam doctor` counts them
461
+
462
+ - The `loam-report` protocol (`loam instructions loam-report`) names its file
463
+ `loam-reports/NNN-YYYY-MM-DD-<short-slug>.md`: `NNN` is an ordinal of at least three digits,
464
+ zero-padded, one
465
+ more than the highest already in the directory, `001` over an empty or absent one, widening past
466
+ three digits rather than reusing a number, and the
467
+ protocol tells the agent to read it from `loam doctor --json` rather than count the directory
468
+ itself. A path that already exists is a concurrent report claiming that ordinal — take the next
469
+ one; the collision suffix is gone. That check is on the PATH while the promise is about the
470
+ ordinal, so the protocol also tells the agent to re-run `doctor` after writing: two entries
471
+ sharing an ordinal means the later `Recorded` renames to the next free one.
472
+ The directory is `loam-reports/` beside the `loam.json` that resolved
473
+ (else beside the directory the agent runs in) — `reports.dir` carries it WITH that segment on the
474
+ end, so it is written into rather than joined onto — the ordinal is unique within it, and a
475
+ report is cited by
476
+ ordinal — in a commit message, an issue or another report's cross-references — never by filename.
477
+ The document template gains `- Status: open` directly after `- Classification:`, with the
478
+ vocabulary `open` / `sent` (handed to the author — the person forwarding a report sets it, never
479
+ the agent writing it) / `fixed in <version>` / `superseded by <NNN>`; a report is closed by
480
+ editing its own `Status` line, and there is no index to keep in step with it. Every safety
481
+ sentence is unchanged: `<redacted>`, "Never re-run" a writer, "do not upload", and the section
482
+ headings.
483
+ - `loam doctor --json` gains one additive key, `reports`: `reports.dir` (the absolute path of the
484
+ `loam-reports` directory beside the `loam.json` that resolved — and, when a `loam.json` was FOUND
485
+ but could not be parsed, beside that file, which is the same root `agents` is inspected against;
486
+ only where no config file exists at or above the run directory does it fall back to the run
487
+ directory. The last segment is ON it, so the protocol writes inside the path rather than
488
+ joining the name a second time), `reports.present`, `reports.total`, `reports.next` (the ordinal
489
+ the next report takes, padded
490
+ to at least three digits and widening past them, `001` when there is none), and `entries[]` —
491
+ every `*.md` in the directory
492
+ sorted by the ordinal its name claims, numerically, with unnumbered legacy names last (byte order
493
+ stopped being report order at exactly the width the ordinal widens to: `1000-…` sorts before
494
+ `998-…`), each carrying `file`, `ordinal` (the leading three-or-more-digit ordinal,
495
+ `null` for a name written before the numbering, including the `YYYY-MM-DD-…` shape) and
496
+ `status` (`open` | `sent` | `fixed` | `superseded` | `unstated`, read as the first word of the
497
+ `- Status:` line in the report's HEADER FIELD BLOCK — the lines above the first `##` heading,
498
+ quoted code skipped in BOTH markdown spellings, fenced and indented, so a report quoting the
499
+ protocol's own template counts as whatever its own header says — and `fixed in 0.2.0-alpha.5`
500
+ counts as `fixed`). The
501
+ human view prints one row after `agents`:
502
+ `reports loam-reports/ 12 · open 3 · sent 5 · fixed 3 · superseded 1 · next 013` — zero
503
+ counts omitted, the total always shown, `reports (none)` when the directory is absent or
504
+ empty.
505
+ - Reading it is best-effort and emits no finding: an unreadable file is an entry with status
506
+ `unstated`, an unreadable directory is `present: true` with `total: 0`, and a status word this
507
+ vocabulary does not know is `unstated` rather than a sixth bucket. No new code is emitted and
508
+ `doctor`'s `healthy` does not move for anything in the directory — an open report is not a
509
+ defect in the repository holding it. Reading stays local: nothing is transmitted, which is the
510
+ constraint the protocol is built around.
511
+ - AGENTS.md's support paragraph now says reports are numbered and that `loam doctor` counts the
512
+ directory and says which ordinal is next.
513
+ - What a repo notices: `loam doctor` prints a `reports` row it never printed before — `(none)`
514
+ on a repository that has never written one — and `loam doctor --json` carries a `reports` object
515
+ beside `agents`. Existing reports keep their names and are counted as `unstated` with no
516
+ ordinal until someone renames them and adds a `Status:` line; the next report an agent writes
517
+ lands as `loam-reports/001-…` in a directory of legacy names, because `next` is one past the
518
+ highest ordinal present and a `YYYY-MM-DD-…` name has none.
519
+
520
+ ### Added — the adopt brief on an edgeless landscape element
521
+
522
+ - `loam adopt --service <id> --json` gains two additive keys under `landscape`: `landscape.touched`
523
+ — `true` when at least one edge in the `architecture/` project has an endpoint resolving to
524
+ the service (an intra-service edge counts: the predicate `loam context` prints as "(modelled, no
525
+ edges touch it)"), `null` whenever `landscape.modelled` is `null` or `false` — and
526
+ `landscape.attested`, the calls the service's own `model.likec4` declares across its boundary
527
+ (`direction`, `counterpartId`, `counterpart`, and `title` / `op` / `publishes` / `consumes` where
528
+ the edge carries them), sorted and de-duplicated, `[]` when the model is absent, does not parse,
529
+ or declares none, and read only in the edgeless state. `landscape.modelled` and
530
+ `landscape.elements` are now decided by the same element→service resolver `validate --all` grades
531
+ `landscape.service-unmodelled` on: a container titled like a sibling service, nested inside an
532
+ element bound to another service, resolves to its ancestor — so `loam adopt` reports such a
533
+ service `modelled: false` where it reported `true`, which is the answer the fleet run was already
534
+ giving.
535
+ `landscape.instruction` is no longer `null` on element existence alone: while an element resolves
536
+ to the service and no edge touches it, the brief names each attested call and asks for it as ONE
537
+ edge on the existing element, collapsed to the service and naming the other party as the map
538
+ already spells it — or, with nothing attested, points at the walk's outbound and message stops and
539
+ says draw NOTHING rather than invent an edge — and names the element's tags loam does not read (a
540
+ placeholder such as `#provisional`, and the view written `exclude element.tag = #<that>` that
541
+ keeps an adopted service hidden). The `architecture/landscape.likec4` target is back in that state
542
+ with `action: "edit"`, `required: true`, a shape refusing a second element and an edge-only
543
+ example. The text view prints the instruction under "what the fleet already says about this
544
+ service" and flags the row `EDGELESS` ("an element exists, no edge does") instead of `UNDRAWN`.
545
+ Until now `loam seed --from fleet.yaml` left every service absent from `calls:` as a bound,
546
+ edgeless element the brief then declared finished.
547
+ - New `landscape.service-isolated` (warn, never gating, `--all` only, on the landscape target,
548
+ `subject` the service id): an element resolves to `services/<…>/<svc>/`, no edge anywhere in the
549
+ `architecture/` project touches it, and that service's `model.likec4` parses and declares at least
550
+ one call across its
551
+ boundary — the message names the first three. Evidence-gated on purpose: silent for a service with
552
+ no model (a seeded fleet), an unparseable model (`c4.invalid` owns it), a model reaching nothing,
553
+ an unmodelled service (`landscape.service-unmodelled` owns it), and a service the map already
554
+ touches with ONE edge while its model attests five — the check is touched/untouched, not a set
555
+ difference; `#external` elements are never
556
+ subjects. It suppresses `landscape.matched` like every finding. The brief's `checks[]`, the
557
+ `/loam-check` `--all` table and AGENTS.md's adopt step carry it; `/loam-adopt` step 5 states
558
+ the silent cases and points at `landscape.touched` in step 1.
559
+ - One more statement in the adoption brief's unchecked list, now seventeen: whether an adopted
560
+ service is VISIBLE on the fleet map — a placeholder tag that survives adoption, a curated view
561
+ that lists its members by id, and `include *` collapsing to a boundary element
562
+ (`include <boundary>.**`). `/loam-adopt` step 3 names that housekeeping as part of the landscape
563
+ edit.
564
+ - `loam adopt --service <id>` reads the fleet map as the `architecture/` PROJECT — the landscape
565
+ plus every other `.likec4` under `architecture/`, minus the generated `subsystems.likec4` — where
566
+ it read `architecture/landscape.likec4` alone. It is the document set `landscape.service-isolated`
567
+ already grades `touched` over, so `landscape.modelled`, `landscape.elements`, `landscape.inbound`,
568
+ `landscape.outbound`, `landscape.expects`, `landscape.touched`, `landscape.attested` and
569
+ `landscape.instruction` now answer about the same map `loam validate --all` does. What a repo
570
+ notices: a fleet that keeps a second `model { }` block, a use case or a palette in a sibling
571
+ `architecture/*.likec4` no longer gets a brief demanding edges it has already drawn —
572
+ `touched: true`, `instruction: null`, and no eighth `architecture/landscape.likec4` target — where
573
+ the fleet run on the same tree reported `landscape.matched`. The same widening makes
574
+ `landscape.parses` false, and `landscape.modelled` `null`, when any document under `architecture/`
575
+ fails to parse: the landscape file parsing on its own does not rescue a map the renderer cannot
576
+ load. No key added, no key removed. The edgeless instruction names the element's home as "the
577
+ fleet map (architecture/)" rather than a file it did not read — the WRITE still lands in
578
+ `architecture/landscape.likec4`, and the target's `path` says so.
579
+ - The adopt brief's `model.likec4` example is headed with the service being adopted
580
+ (`// order-service's own C4 …`) instead of the literal `payment-service` it carried for every
581
+ service — one line above an `extend <fqn>` the brief already substituted, so the personalised half
582
+ taught an agent the rest of the header had been personalised too. `<svc>` joins `<fqn>` as a
583
+ placeholder the shape rules leave literal and `serviceBrief` fills in; the example never leaves
584
+ either in the payload.
585
+ - What a repo notices: a drawn-but-edgeless service whose model reaches other systems gains one
586
+ `landscape.service-isolated` warning per service on its next `loam validate --all` — under
587
+ `--strict`, CI is red until an edge lands or the element is genuinely edgeless and stays so (a
588
+ model attesting no cross-boundary call is silent); a seeded service nobody listed under `calls:`
589
+ is briefed the edges instead of nothing.
590
+
591
+ ### Added — a home for an intra-service use case
592
+
593
+ - A `dynamic view` over one service's own containers now has a slot loam reads and grades: any
594
+ `.likec4` beside `services/<…>/<svc>/model.likec4` (`usecases/<name>.likec4` by convention,
595
+ `views.likec4` read the same) or a tagged view inside model.likec4 itself — exactly the document
596
+ set the per-service renderer project registers, read whether or not `likec4.config.json` exists.
597
+ `loam validate --service <id>`, and `--all` per service, grade every view tagged
598
+ `#req-<Requirement-ID>` with the codes the fleet target already uses: `usecase.step-unbacked`
599
+ (error), `usecase.step-contested` (warn), `usecase.requirement-unresolved` (error — resolved
600
+ against THIS service's spec.md and arch.spec.md ids, four arms), `usecase.capability-unresolved`
601
+ (error — a `#cap-` tag beside a model is refused by placement, vocabulary or not) and
602
+ `usecase.flow-invalid` (error — a sibling that breaks the service project; model.likec4 keeps
603
+ grading alone, never `c4.invalid`). Untagged views are never graded, and an untagged sibling is
604
+ not even loaded while no view in the project opts in and no sibling mentions either reserved
605
+ prefix anywhere in its bytes; once loaded — a tagged view inside model.likec4 itself, or `req-` /
606
+ `cap-` in a sibling's comment or title — a project that does not read is one
607
+ `usecase.flow-invalid` whether or not any view in it opted in. A resolved service-local `#req-`
608
+ feeds nothing else yet. The feature slot for such a flow is deferred (ROADMAP).
609
+ - Every surface that names a code for a `#req-` tag declared TWICE — once in the map's
610
+ `specification` and once in a tags-only block beside the `extend` — now names the one loam
611
+ actually emits. SCHEMA.md, `/loam-adopt` step 3 and `loam instructions loam-codes` all said
612
+ `c4.fleet-project-invalid` (a warning); measured, the finding lands where the second declaration's
613
+ FILE is: `c4.invalid` — an error that gates, suspends the model's grading and cascades six
614
+ `covers.unknown` — when the tags-only block is inside model.likec4, and `usecase.flow-invalid`
615
+ when it is in a sibling. `c4.fleet-project-invalid` is the fleet-scope class one step out: two
616
+ SERVICES each declaring the same tag locally, which no per-service project can see. No behaviour
617
+ changed; an agent routing on the printed code was being sent to the wrong severity and the wrong
618
+ repair.
619
+ - Two corrections to the shared step graders, at fleet altitude too: a hop whose two endpoints
620
+ resolve to ONE service is no longer backed by that service's internal edges on the service tier
621
+ (measured: every internal relationship was a candidate, so an unbacked hop between two containers
622
+ of one service graded attributed and could never earn `usecase.step-unbacked`); and
623
+ `usecase.step-unlinked` no longer fires when the caller resolves to the provider — a service owes
624
+ no operationId to itself, and that is the guard's whole scope: a hop from a service-local flow
625
+ into another service's element still warns, exactly as it does on the fleet map. What a repo
626
+ notices: a landscape that draws a service as containers with a tagged hop between two of them
627
+ loses a wrong `usecase.step-unlinked` warning and may gain a right `usecase.step-unbacked` error;
628
+ a service directory holding a tagged `dynamic view` beside its model starts being graded.
629
+ - `loam diff` no longer counts a service as a consumer of its own operation, on either half of the
630
+ question. A landscape edge whose SOURCE and TARGET both resolve to the provider is the provider's
631
+ own internal call — the rule `usecase.step-unlinked` already states — and so is a use-case hop
632
+ whose two endpoints resolve to it; the edge half excluded nothing while the message half already
633
+ did, so the downgrade this list promised could not be observed in the configuration it described.
634
+ Both are excluded now, and it is: a removal whose only trace in the fleet is the provider calling
635
+ itself reports `diff.op-removed` (warn) and the run exits 0, where it reported
636
+ `diff.op-removed-consumed` (error, `breaking: true`, exit 1). A second service's edge naming the
637
+ operation is still `diff.op-removed-consumed`.
638
+ - The adopt brief's `checks[]` gains six `--service` rows: the five above and
639
+ `usecase.step-unlinked`, which the service target reports for a hop into a sibling's stand-in, and
640
+ which `/loam-check`'s `--service` table now carries a row of its own for. The arch.spec.md shape
641
+ names the hop-sequence slot; AGENTS.md's layout paragraph names the slot itself
642
+ (`services/<…>/<svc>/usecases/*.likec4`) and the styling opt-in beside it.
643
+ - `loam explain` answers the right scope for the eight codes only a feature run raises. A markdown
644
+ fix table ends at the first line that is not a `|` row, and nothing separated `/loam-check`'s
645
+ `--feature <FEAT-id>` rows from the `--service <id>` ones sitting above them, so
646
+ `capability.uncovered`, `capability.remove-requirement-realized`, `glossary.term-exists`,
647
+ `usecase.flow-exists`, `deployment.doc-exists`, `deployment.doc-invalid`,
648
+ `deployment.doc-reserved` and the feature/archive arm of `usecase.flow-invalid` all came back
649
+ `scope: "--service <id>"` — a service run can raise none of them. They now read
650
+ `--feature <FEAT-id>`. What a consumer notices: `entries[].scope` changes value for those eight
651
+ in `loam explain <code> --json` and in `loam explain --codes --json`; the meanings and the fixes
652
+ are unchanged, and no code was added or removed.
653
+
654
+ ### Changed — the generated subsystem views join the repository's styling
655
+
656
+ Every view in the generated `architecture/subsystems.likec4` now carries `global style subsystems`
657
+ — written after its `title` and `description`, before its `include`/`group` — exactly when the
658
+ `architecture/` project (the landscape merged with every other `.likec4` under `architecture/` the
659
+ root `exclude` does not cover — a `palette.likec4` beside the map counts, and one under an excluded
660
+ path is loaded by nobody; the one
661
+ load `loam subsystem sync`, `validate --all` and `archive` share) declares a global style with the
662
+ id `subsystems`, in either form: `global { styleGroup subsystems { … } }` or
663
+ `global { style subsystems … }`. loam reads only that the id is declared — a census of the keys of
664
+ `$data.globals.styles` beside the view-id census in `core/c4/parsed/view-ids.ts` — and never what
665
+ the group says (docs/DESIGN.md rule 26). Nothing is written otherwise, and the file is
666
+ byte-identical to before. When the project declares style groups under other names only, one
667
+ comment line between the header and `views {` names them and says none is `subsystems`; there is
668
+ no single-group fallback, because a second group declared for a hand-drawn view would otherwise
669
+ switch the generated views' styling off on a change that touched no subsystem. Styling therefore
670
+ restales the generated file on two triggers, and `subsystem.views-stale`, `loam explain` and the
671
+ fix tables name both: declaring or removing the group named `subsystems` changes whether the views
672
+ carry the `global style subsystems` line, and declaring or removing a group under ANY OTHER name
673
+ changes the disclosure comment. What a repo notices:
674
+ a fleet that already declares a group named `subsystems` (or any group, for the disclosure line)
675
+ sees one `subsystem.views-stale` on its next `validate --all`, cleared by one `loam subsystem sync`.
676
+ `loam seed` renders the file from that same project — the landscape it is about to write, staged
677
+ beside every other `architecture/` document — so a palette kept in a sibling such as
678
+ `architecture/usecases/palette.likec4` is referenced by the seeded views and never a stale file
679
+ straight after a seed. No new code, flag or `--json` key. `LoadedDoc` gains the additive field
680
+ `globalStyles` (the sorted declared ids), produced alike by every loader and pinned by the three
681
+ parity suites; seven tripwire assertions in `test/likec4-view-shape.test.ts` pin the parsed shape
682
+ at likec4@1.59.2.
683
+
684
+ ### Changed — the fleet's consumer census reads the models, not the map alone
685
+
686
+ - `landscape.datastore-private`, `landscape.datastore-shared` and `landscape.platform-candidate`
687
+ count a consumer that a service's own EXTENDING `model.likec4` attests — an outbound call at the
688
+ element or a descendant — beside the edges `architecture/` draws. Under the extending shape the
689
+ container-level call lives in the model and only the service-level twin reaches the map, so a
690
+ store whose consuming edges were all drawn in models had zero consumers and earned nothing at all,
691
+ and a store reached by three models plus one map edge was reported `landscape.datastore-private`
692
+ naming the single service the map happened to draw. Extending models only: their ids are the map's
693
+ fully-qualified ids, so the join is exact, while a standalone model's ids are its own file's and
694
+ are never matched to a landscape element. A model that does not parse, or cannot be read,
695
+ contributes nothing.
696
+ - `landscape.datastore-shared` now fires for a NESTED store. A store sitting inside the element that
697
+ resolves to its owner — declared there on the map, or added by the owner's extending model — is
698
+ private by construction and silent at one consumer, and is graded at two: `subject` is the store's
699
+ title as before, and the message adds ` — <owner>'s own store, written as '<fqn>'` beside the
700
+ consuming services it names — the store's OWN id, since `<fqn>` IS the store and the earlier
701
+ "nested under" sent a reader looking for a containing element of that name. The message then
702
+ states the reversibility directionally: the two placements give the store the same id, but with a
703
+ second consumer the move is one-way — out of the `extend` block onto the map is always safe, and
704
+ back into it breaks every other consumer's edge.
705
+ Until now the
706
+ strongest coupling two services can have rendered perfectly and was graded nowhere, because the
707
+ census only ever looked at elements drawn at fleet level.
708
+ - `landscape.datastore-private`'s remedy names the placement instead of a directory, and names it
709
+ for the consumer's own model SHAPE rather than assuming one. For an EXTENDING consumer it says to
710
+ write the store in that model's `extend <fqn> { … }` block, or inside `<fqn>` on the map when the
711
+ map draws that service's containers. For a consumer whose model STANDS ALONE — or that loam could
712
+ not read as extending — it says the map placement and offers the migration to the extending shape
713
+ as the other arm, because `extend <fqn>` in a standalone model resolves nothing (such a model is
714
+ parsed alone) and following the old sentence there turned a run with 0 errors into `c4.invalid`.
715
+ Either way it states what happens next: a second service
716
+ reaching the same data draws a container-level edge to `<fqn>.<name>` in its own model plus a
717
+ service-level edge on the map, and loam counts that consumer — so the state is
718
+ `landscape.datastore-shared`, never silence. It also states which of the two placements keeps that
719
+ door open: the edge resolves only against a store the MAP declares, because a per-service project
720
+ is `architecture/` plus one model. The old "or add the second consumer's edge" is gone as an equal
721
+ remedy and survives as "if another service reaches the same data today, draw its edge and this
722
+ becomes that warning now". It also states the measured reversibility with its condition — the two
723
+ placements give the store the same id, `<fqn>.<store>`, and with one consumer either move is free:
724
+ out of the `extend` block onto the map always is, and back into it while no other service's model
725
+ or map edge names the store (measured on `examples/docs`: moving `db` out of order-service's `extend`
726
+ block into `orderService` on the map left the model at 12 elements · 8 relationships, left
727
+ `Covers: marketplace.orderService.db` in its `arch.spec.md` resolving, and left checkout-web's
728
+ container-level edge to it resolving) — and it qualifies the "never silence" promise: a consumer
729
+ whose model stands alone is not counted, because its ids are its own file's, so its edge belongs
730
+ on the map.
731
+ - `landscape.datastore-private` and `landscape.datastore-shared` never take an element that stands
732
+ for a `services/<id>/` as their subject, at ANY depth. The fleet-level half has always skipped
733
+ one (a datastore bound to a directory is the binding checks' subject); the NESTED half did not,
734
+ so a `database` carrying `metadata { service '<id>' }` and drawn inside another service's
735
+ element became a store "owned" by its own binding, and one more reader made it
736
+ `landscape.datastore-shared` — advising the team to give each service a private copy of a
737
+ service. Both halves and the model-declared store census now share one `standsForService`
738
+ predicate, handed down on the input record because a `fleet/map/` module may not import `fleet/`.
739
+ - `landscape.invalid`'s message generalises its hint. It spoke about use-case flows alone, so an
740
+ author whose MODEL EDGE named a container was told to move a flow that does not exist. It now says
741
+ that an unresolved name an extending model declares — a use-case hop or an edge naming
742
+ `<service-fqn>.<container>` — renders for the renderer and resolves for nobody here, and that the
743
+ repair is to name the SERVICE on the map and the container in that service's own model.
744
+ - `landscape.service-isolated` never takes an `#external` element as its subject. The claim rested
745
+ on "an `#external` element is not an enumerated service", which is false for an element carrying
746
+ the tag AND a `metadata { service '<id>' }` binding to a directory that exists: the input set had
747
+ no tag filter, so a foreign box answered for one of our services. The isolation input SET now
748
+ skips `#external`, matching `landscape.binding-duplicate` and
749
+ `landscape.binding-unknown`. `landscape.service-unmodelled` keeps the unfiltered set — the
750
+ directory IS drawn. The `touched` walk applies NO tag filter, deliberately: it answers a different
751
+ question — does the map draw any edge on this service — and LikeC4 puts a kind tag on every
752
+ element of the kind, so `element topic { #external }` (the idiom
753
+ `examples/docs/architecture/landscape.likec4` documents) made an edge drawn from a service's OWN
754
+ topic stop touching that service. The warning fired on a service the map demonstrably reaches
755
+ while `loam adopt` reported `touched: true` on the same tree; the walk is the brief's
756
+ `landscape.touched` predicate exactly, tag and all.
757
+
758
+ ### Changed — the renderer wiring, measured rather than assumed
759
+
760
+ - `loam subsystem sync` now DELETES a `likec4.config.json` that sits beside a model which EXTENDS
761
+ the fleet map, in the same journaled commit as everything else it writes, and `--json` `projects`
762
+ gains `projects.removed` (POSIX paths relative to the docs root, sorted by path (not by service
763
+ id — the survey's own order); `[]` on a run that removed none)
764
+ beside `projects.created`. The text view prints one `removed <path>` line per file and one
765
+ sentence saying why. The note this corrects said such a nested project was registered EMPTY and
766
+ therefore harmless; measured on likec4 1.59.2 it CLAIMS the model — the workspace loads two
767
+ projects, the model is Invalid inside its own one (the map's kinds are not in there with it) and
768
+ `export json --project fleet` loses the service's containers wherever that nested project claims
769
+ the model (measured both ways at the 1.59.2 pin: it did on a seeded fleet, and did not on
770
+ `examples/docs`, where the root project kept the model — what happens in every state is that the
771
+ docs root stops being a one-project workspace and `likec4 validate .` refuses without
772
+ `--project`). A repo that migrated 56 models to
773
+ `extend` and ran one `sync` kept 56 stray files and 57 projects; it now keeps one. A leftover
774
+ beside a model that is GONE still stays — that one really is the team's, and a nested project
775
+ holding no `.likec4` is silently absent from the renderer's list. `service.likec4-config-stray`'s
776
+ message is rewritten to the measured consequence and now names a repair `sync` actually performs.
777
+ `projects.current` narrows with it: it counts the services owed a project file that already had
778
+ one, and a model that extends the map is owed none — so on the run that deletes the strays it
779
+ drops by exactly the length of `projects.removed`, and every run after that reports the same
780
+ number both spellings would have.
781
+ - `loam subsystem sync` no longer rewrites the generated views file when the map cannot be read.
782
+ `action` gains the value `blocked`: with `architecture/` unreadable and
783
+ `architecture/subsystems.likec4` already present, the run writes nothing about that file, prints
784
+ the reason, and `--json` carries that action plus `blocked.reason` reading `landscape-invalid` —
785
+ exit 0, because `loam validate --all`'s `landscape.invalid` is the gating surface. Before this,
786
+ any load error rendered the tolerant shape: the file was rewritten down to title and description,
787
+ losing the `global style subsystems` line, every `include` and the boundary comment, while `sync`
788
+ reported `updated … 1 subsystem view(s)` and answered `current` ever after. The render also now
789
+ falls back to the landscape FILE when only a SIBLING document breaks the project, so a duplicate
790
+ `subsystems` group declared in two `architecture/` documents costs nothing. With no generated file
791
+ yet, and inside a tree-changing verb's commit window (`new`/`move`/`rm`/`rename`, whose contract is
792
+ that the file lands with the change), the tolerant shape is still written and the same one-line
793
+ note is printed.
794
+ - The `architecture/` document set every reader shares now honours the root `likec4.config.json`'s
795
+ `exclude`. A `.likec4` under `architecture/` the renderer never loads is no longer loaded by loam
796
+ either: a palette under an excluded path used to yield a `global style subsystems` reference the
797
+ fleet project could not resolve — `likec4 validate` Invalid on every run while `loam validate
798
+ --all` reported 0 errors, `doctor` was healthy and `sync` said "current" — and a broken excluded
799
+ document blanked the whole map. A root config loam cannot read an `exclude` list out of filters
800
+ nothing.
801
+ - An `exclude` entry is now read the way the renderer reads it, measured rather than reasoned about.
802
+ loam interpreted only a literal `/**` suffix, so a root config written `services/**/*`,
803
+ `services/*`, `services/` or the bare `services/<tree>` hid every extending model with zero
804
+ findings; the first repair of that read the entry as a walk over path SEGMENTS, which is not what
805
+ LikeC4 does either. Measured at the 1.59.2 pin on a scratch fleet of five documents, by putting
806
+ one entry in the root config and counting the "N files" `npx likec4 validate .` reports: a bare
807
+ entry is a raw PREFIX with no end anchor (`services/pay` -> 3 files, hiding BOTH
808
+ `services/payment-service` and `services/payments/payment-service`; `architect` -> 3, hiding all of
809
+ `architecture/`), it must start on a `/` boundary (`ervices` and `ces/payment-service` -> 5, that
810
+ is, nothing), `*` stays inside one segment while `**` crosses (`architecture/*.likec4` -> 4, so
811
+ the nested use case survives; `*.likec4` -> 0), and a glob form does NOT prefix-match
812
+ (`services/pay/**` -> 5). LikeC4 prefixes an entry that does not begin with a doubled star with
813
+ one, joins it onto the project folder and matches with picomatch `contains: true` — the missing
814
+ end anchor is that option. So `service.model-excluded` fires now, naming the entry verbatim, on
815
+ every spelling including a starred middle segment; `service.model-unexcluded` no longer
816
+ false-positives on a standalone model whose directory such an entry already covers; and
817
+ `subsystem sync` takes back an entry that hides an extending model whatever its spelling — the
818
+ write side asked "which directory does this entry name", which answers nothing for a starred
819
+ segment, so `services/*/svc-b` hid a model forever while `service.model-excluded` named that same
820
+ command as the repair — and appends nothing for a directory the surviving list already covers.
821
+ - The root `exclude` filter on the `architecture/` document set now has a floor:
822
+ `architecture/landscape.likec4` stays in the set whatever the list says. Without one, an entry a
823
+ team writes for a palette (`architecture/*.likec4` covers both) left the architecture project
824
+ holding ZERO documents — and an empty project parses with zero errors, so every reader took it for
825
+ "the map declares nothing". On a seeded two-service fleet that produced four errors, all false:
826
+ `landscape.service-unmodelled` twice, naming the very file that models both services and telling
827
+ the reader to add an element already in it, plus `c4.invalid` on the extending model; and
828
+ `loam subsystem sync` answered `updated` and rewrote a good generated file down to a title, with
829
+ no line in either run mentioning the `exclude` entry that caused it. loam now grades one document
830
+ the renderer may skip rather than asserting a fleet's whole architecture says nothing.
831
+ - `loam subsystem sync` answers `blocked` whenever the `architecture/` PROJECT does not parse, not
832
+ only when `architecture/landscape.likec4` itself is unreadable. The landscape-file fallback that
833
+ saves the element join says nothing about the STYLE CENSUS — a palette may live in any `.likec4`
834
+ under `architecture/` — so a run made while an unrelated sibling was broken reported `updated`,
835
+ deleted the `global style subsystems` line from a good generated file, answered `current` ever
836
+ after, and never earned `subsystem.views-stale` while the map stayed invalid. The file is now left
837
+ byte-identical in that state and the note names what was unavailable.
838
+ - `loam subsystem sync --json` carries `projects.exclude.unreadable` (boolean, emitted on every
839
+ run). `entries: []` alone collapsed the two answers loam insists must stay apart: "the root
840
+ project excludes nothing" and "loam could not read an `exclude` list out of the root file". The
841
+ text view has always had a note for the second; a machine consumer reading `entries: []` got the
842
+ opposite of the truth about what the renderer will load. Additive, beside `projects.exclude.removed`.
843
+ - `loam subsystem sync`'s "— nothing to write." tail is dropped when the run rewrote the root
844
+ `likec4.config.json`'s `exclude`. It counted only the per-service project files created and
845
+ removed, so a run with a current views file printed "nothing to write." immediately above the line
846
+ reporting the rewrite.
847
+ - `c4.fleet-project-invalid` now enumerates what the renderer actually loads. The document set is a
848
+ walk of the docs root — everything the root `exclude` leaves, minus the generated
849
+ `architecture/subsystems.likec4`, minus every standalone service directory (owned by
850
+ `service.model-unexcluded`), minus `features/` (a delta carries its own `specification` and the
851
+ directory is transient). A `.likec4` in a SUBSYSTEM directory — the parent of service directories
852
+ — was in none of the three roots the check used to merge: `validate --all` came back
853
+ byte-identical to baseline and never named the file while `npx likec4 validate --project fleet .`
854
+ reported Invalid over the same tree. The finding also gains `locations` (the file the error is IN,
855
+ not `architecture/landscape.likec4` on every one of them) and `details`, and its tail now names
856
+ the failure it actually is — a double declaration, an unresolved reference, or neither — instead
857
+ of always blaming a double declaration.
858
+ - Every LikeC4 line number loam prints is 1-based, the way an editor counts — and deliberately NOT
859
+ the way `likec4 validate` prints it, which passes the LSP range's start line through and so says
860
+ `Line 8` for a fault on line 9.
861
+ `c4.invalid`'s details said `L3` for an error on line 4; `c4.fleet-project-invalid` printed
862
+ `model.likec4:0` for a declaration on line 1; and the feature arm of `usecase.flow-invalid`
863
+ printed no line at all and an ABSOLUTE Windows path into loam's own staging tree — it now spells
864
+ the authored file docs-relative with forward slashes and an `L<n>`, exactly as the service arm of
865
+ the same code does, in the text view, in `--json` and in the `loam archive` block message.
866
+
867
+ ### Fixed — the adoption brief answers off the document it actually read
868
+
869
+ - `loam adopt` no longer briefs a write to the shared fleet map when the map could not be READ.
870
+ The eighth target (`architecture/landscape.likec4`) is emitted on `landscape.modelled === false`
871
+ — a read that succeeded and found nothing — and on the edgeless state; `modelled: null` ("loam
872
+ could not read the map", which since the `architecture/`-project widening includes a broken
873
+ palette or use case beside a landscape that parses perfectly) now emits no target at all.
874
+ It used to fall through the same gate as "the map draws nothing for this service", so the brief
875
+ handed an agent the UNDRAWN block — "add one top-level element bound to its directory" — for a
876
+ service the map already declares and binds, and splicing it earned `landscape.binding-duplicate`.
877
+ `landscape.instruction` carries the whole answer instead and names the documents that failed.
878
+ - `loam adopt`'s fleet-map instruction and the text view's landscape headline no longer name
879
+ `architecture/landscape.likec4` as the broken file when the document that failed is a SIBLING
880
+ under `architecture/`. The instruction now names the failing documents docs-relative with their
881
+ error count — the same spelling `validate --all`'s `landscape.invalid` prints — and the headline
882
+ says `The fleet map (architecture/) does not parse — fix that first; the files are named below.`
883
+ The brief and the fleet run on the same tree used to blame two different files.
884
+ - `loam adopt` no longer marks `openapi.yaml` required because a document beside the fleet map
885
+ broke. The API question is answered off the landscape FILE (`landscape.callersKnown`, added to
886
+ `--json`: true when the `architecture/` project parses, and true when it does not but the
887
+ landscape file does), which is the evidence `loam list` and `loam context` already read: a broken
888
+ `architecture/palette.likec4` used to flip `openapi.yaml` from `create missing` to
889
+ `create MISSING` for a browser UI the map proves nobody calls, and told the agent to write an API
890
+ contract for it. With the landscape file itself unreadable the contract is still owed.
891
+ - `loam adopt`'s model.likec4 example prints a short `<your-element-id>` where loam has no id to
892
+ substitute, and carries the reason once as a `//` comment above the block. The placeholder used
893
+ to be a 103-character English sentence spliced into all three code positions `<fqn>` occupies —
894
+ the `extend` line, the source of a container edge and `view of` — on the command's most common
895
+ invocation, a brand-new adoption.
896
+ - `loam adopt`'s arch.spec.md shape rule named `c4.fleet-project-invalid` (warn, `--all`) for a
897
+ `#req-` tag declared both in the map and beside the model. Measured, that is `c4.invalid` (error,
898
+ and the model's grading is suspended behind it) when the tags-only block is in model.likec4 and
899
+ `usecase.flow-invalid` when it is in a sibling; `c4.fleet-project-invalid` is the fleet-scope
900
+ class one step out, two SERVICES each declaring the same tag locally. The rule now says so, and
901
+ matches the same statement in SCHEMA.md.
902
+ - `loam adopt`'s model.likec4 shape rule for `c4.element-unowned` leads with the nested remedy —
903
+ a store or component this service OWNS goes INSIDE the `extend <fqn> { }` block, where its id
904
+ becomes `<fqn>.<name>` — before the two existing arms, matching the `--service` fix table and
905
+ SCHEMA's grades table.
906
+ - `loam adopt`'s `landscape.invalid` check description said "architecture/landscape.likec4 exists
907
+ but does not parse". It now says the `architecture/` PROJECT does not parse — the landscape or
908
+ any `.likec4` beside it — and that the message names the document that broke.
909
+
910
+ ### Fixed — one tree, one answer: the gate's preview, and the map's other readers
911
+
912
+ - The merge preview every feature-side gate reads is now routed exactly as `loam archive` routes:
913
+ the fleet's extending models reach it, so an addition the delta nests inside a service is
914
+ previewed in that service's `model.likec4` and not on the map. Without them the preview held
915
+ interior the archive puts somewhere else, and `usecase.flow-invalid` — the gate `--approve`
916
+ cannot override — failed open on the one shape it exists for. Measured on a copy of
917
+ `examples/docs`: a feature nesting `cache` under `marketplace.orderService` and hopping
918
+ `checkoutWeb -> marketplace.orderService.cache` from `features/<FEAT>/usecases/` passed
919
+ `validate --feature`, archived at exit 0, and left a fleet whose next `validate --all` reported 7
920
+ errors — one `landscape.invalid` plus one `spine.landscape-invalid` per service — against a file
921
+ its author never wrote. The gate now refuses before anything is written, `validate --feature`
922
+ gives the same verdict, and a flow hopping between elements the map itself draws still archives.
923
+ `deployment.doc-invalid` is wired the same way and had the same hole.
924
+ - `loam context <service>` reads the fleet map as the `architecture/` PROJECT — the landscape
925
+ merged with every use case, palette and second `model { }` block beside it — never
926
+ `architecture/landscape.likec4` alone. Two commands answered opposite things about one tree:
927
+ measured on a two-service fleet with a broken `architecture/palette.likec4` beside a landscape
928
+ that parses, `loam adopt --service order-service` reported `parses: false` and refused to say
929
+ anything about the map, while `loam context order-service` reported `parses: true`,
930
+ `modelled: true` and listed the map's edges — and an agent handed both has no way to tell which is
931
+ right. `landscape.present` is still a fact about the FILE; `landscape.parses` is now a fact about
932
+ the project the renderer loads, so a broken sibling reads here as "nobody could look", the answer
933
+ `validate --all` and `adopt` already gave. The second load of that same project the deployment
934
+ slice used to make when the landscape file declared no `deployment { }` block is gone with it: one
935
+ parse, one answer.
936
+ - `loam seed` computes the generated views file's style census from the `architecture/` documents
937
+ the root `likec4.config.json`'s `exclude` leaves, not from a raw walk of the directory. A
938
+ `palette.likec4` under an excluded path is loaded by neither the renderer nor `validate --all`,
939
+ yet seed read it and wrote `global style subsystems` into `architecture/subsystems.likec4` — a
940
+ name the fleet project cannot resolve, written by the command whose own `next` line is
941
+ `loam validate --all`. The same defect one command over from the one the exclude filter fixed in
942
+ the loader. The fleet use-case scan's cheap tag gate was corrected with it: it walked
943
+ `architecture/` for itself, which could only over-scan (it is a prefilter before a load that does
944
+ apply the exclude), but two lists that disagree about what the project is are two answers waiting
945
+ to drift.
946
+ - `landscape.invalid`'s head reads `<documents> have N error(s)` once more than one document broke,
947
+ matching `spine.landscape-invalid`. The named list is comma-joined when the `architecture/`
948
+ project has errors in two files, and the fleet line said "a, b has 4 error(s)" directly above the
949
+ service line saying "a, b have 4 error(s)" about the same two files — one report, two spellings of
950
+ one fact.
951
+
952
+ ### Fixed — a second verification round: what the archive routes, and what every message names
953
+
954
+ - `loam archive` no longer refuses a delta written in the legacy title-join spelling inside a group
955
+ the map declares. Routing an addition into a service's extending model now requires the delta to
956
+ spell the whole parent chain in the MAP's own ids — a living element, a container an extending
957
+ model already declares, or a declaration this same delta adds — instead of only requiring that
958
+ *some* ancestor be living. A grouped map (`marketplace = group { … }`, the shape
959
+ `loam init --example` ships) made the old guard pass on `marketplace.orderSvc`, the addition was
960
+ written as `extend marketplace.orderSvc` — an id the map has never declared — and the model's
961
+ parse net refused the whole archive with `merge-failed` and nothing written. What a repo notices:
962
+ such a delta merges onto `architecture/landscape.likec4` under the map's own id and exits 0 again,
963
+ exactly as it does when the service's model is standalone or absent. The documented remainder is
964
+ unchanged: a title-joined parent keeps the map path.
965
+ - A service a feature INTRODUCES now keeps its interior out of the fleet map when its directory
966
+ already holds an extending model. The new element's containers arrive inside its authored bytes and
967
+ carry no feature tag of their own — LikeC4 does not inherit tags, so nothing in the addition set
968
+ could see them — and they rode onto the map: `loam adopt --service <svc>` then told the team to
969
+ write the same container into `extend <fqn> { }`, and the renderer reported a duplicate element
970
+ blamed on both files while the service graded `c4.invalid`. Now the box lands on the map without
971
+ its interior, the children are spliced into the model's `extend <fqn> { }` block (created if
972
+ absent), and `loam archive` prints the second
973
+ `architecture: merged into services/<…>/model.likec4` line for it. A service arriving with no
974
+ directory at all, and one whose block also draws an internal edge between its own containers, keep
975
+ the previous behaviour byte for byte — an edge written inside the block names its endpoints by
976
+ their local names and there is no legal split, so the block rides whole. That is ONE decision and it covers the whole block: a child or an edge
977
+ the delta tags with the feature rides with a block that rides and is spliced with a block that
978
+ splits, never placed twice. While the two were decided separately the tagged child landed in both
979
+ documents and the merge refused itself, `merge-failed`, on a delta neither half was wrong about.
980
+ - Both C4 merges now splice with the target document's own line endings. A CRLF
981
+ `architecture/landscape.likec4` or `services/<…>/model.likec4` came back with a handful of bare-LF
982
+ lines in the spliced region, and a CRLF delta's blank lines arrived carrying the target indent as
983
+ trailing whitespace. A document with one convention keeps it; a document that is already mixed is
984
+ left alone, because there is no convention to match.
985
+ - `landscape.datastore-shared` and `landscape.datastore-private` now state the reversibility of a
986
+ private store's placement **directionally**, because the unconditional clause was false in every
987
+ state `datastore-shared` prints in. The two placements do give the store the same id
988
+ (`<owner-fqn>.<store>`), but the move is only free one way: out of the owner's `extend` block onto
989
+ the owner's element on the map is always safe, and back INTO the `extend` block breaks every other
990
+ consumer's edge — `c4.invalid` on that service's model, or `landscape.invalid` when the edge is
991
+ drawn on the map — because a per-service project is `architecture/` plus one model, so an `extend`
992
+ block is visible to its own service's project alone. `datastore-shared` needs a second consumer to
993
+ fire, so the state it prints in is exactly the state in which map→extend breaks; `datastore-private`
994
+ has one consumer, so it says both moves are free *today* and names the condition that ends that.
995
+ - `landscape.datastore-private`'s no-model arm offers a model to WRITE, not one to migrate: with no
996
+ `model.likec4` at all it now says "write an extending model at `<path>`" one sentence after saying
997
+ loam read no extending model there. The STANDALONE arm still says "migrate the model", which is
998
+ correct for it.
999
+ - `landscape.service-isolated`'s message names the fleet map rather than one file: "no edge in the
1000
+ fleet map (architecture/) touches it". The check's input is the `architecture/` PROJECT — an edge
1001
+ added to `architecture/extra.likec4` and never to `landscape.likec4` silences it, measured — so the
1002
+ message now agrees with SCHEMA, with `loam explain landscape.service-isolated` and with the adopt
1003
+ brief, all three of which already said "anywhere in `architecture/`".
1004
+ - `loam doctor` prints LikeC4 line numbers 1-based, through the same `errorText` helper every other
1005
+ surface uses. `doctor.landscape-invalid` hand-rolled its own `(line <n>)` spelling and passed the
1006
+ LSP range through raw, so it named the line above the fault and disagreed with `validate --all`
1007
+ about one error on one tree. The message now reads
1008
+ `architecture/landscape.likec4 does not parse: L<n>: <message> — and N more`.
1009
+ - `loam context --json` and `loam explore --json` gain `landscape.broken`: the `architecture/`
1010
+ documents that failed, docs-relative POSIX, deduped and sorted, `[]` while `landscape.parses` is
1011
+ true. Spelled exactly as `landscape.invalid` and the adopt brief spell them, so the three surfaces
1012
+ name one file one way. Both commands read the `architecture/` PROJECT, so the document at fault is
1013
+ very often a sibling beside a landscape that reads perfectly.
1014
+ - The adopt brief no longer promises a warning that cannot fire. On an element carrying `#external`,
1015
+ the edgeless instruction and the `landscape.likec4` target's shape rule say that
1016
+ `landscape.service-isolated` will NOT fire (the check removes every `#external` element from its
1017
+ subject set), that this brief is the only place the state is named, and that dropping `#external`
1018
+ makes the check apply again. Every other tree keeps the previous sentence.
1019
+ - The adopt brief's `checks[]` row for `c4.declaration-diverged` names "the fleet map
1020
+ (`architecture/`)" instead of `architecture/landscape.likec4`. The map is a project; LikeC4 hands
1021
+ back no source document for an element, and the finding's own message and `loam explain` already
1022
+ refused to name a file.
1023
+ - `loam adopt --json`'s `landscape.inbound`/`landscape.outbound` drop an edge whose two endpoints
1024
+ resolve to the SAME service — a service's own internal wiring drawn on the map made it its own
1025
+ caller. `landscape.touched` is unchanged: it is now read off the map's relationships rather than
1026
+ off those two lists, so it still counts an intra-service edge and still matches
1027
+ `landscape.service-isolated`'s predicate exactly.
1028
+ - The service arm's use-case findings (`usecase.step-unbacked`, `usecase.step-unlinked`,
1029
+ `usecase.requirement-unresolved`, `usecase.capability-unresolved`) carry the flow FILE as
1030
+ `locations[0]` with role `primary` and the service directory beside it as `scope`. They carried the
1031
+ service directory alone, so a `--json` consumer had to parse the prose to learn which `.likec4` to
1032
+ open. `usecase.flow-invalid` on the same arm already named its file.
1033
+ - `usecase.flow-invalid`'s undeclared-tag hint tells a `cap-` author the placement rule instead of a
1034
+ declaration that cannot help: a capability is claimed at fleet altitude, so declaring `tag cap-…`
1035
+ beside a model makes the file parse and earns `usecase.capability-unresolved` on the next run. The
1036
+ `req-` hint is unchanged.
1037
+ - The services tree walk never classifies a service's own reserved subdirectory (`adrs`, `usecases`,
1038
+ `ui` — SCHEMA's service layout) as a service or a subsystem. Writing the documented
1039
+ `services/<id>/usecases/<name>.likec4` slot into a directory holding no artifact FILE used to make
1040
+ the walk read `usecases` as a service and its parent as an unmarked group: three errors
1041
+ (`subsystem.unmarked`, `landscape.service-unmodelled` for `services/<id>/usecases/`,
1042
+ `landscape.binding-unknown` for the real service) earned by following the instruction `loam init`
1043
+ had just scaffolded. Such a directory is also not counted when deciding whether a directory is a
1044
+ group, so a real service filed beside a reserved slot still refuses with `subsystem.unmarked` and
1045
+ is still named.
1046
+ - `loam` now strips a leading UTF-8 byte-order mark before parsing the root `likec4.config.json`.
1047
+ PowerShell's `Out-File` writes one by default, so a Windows shell saving the file loam itself wrote
1048
+ left every `exclude` grade silent while the renderer went on applying the list (measured:
1049
+ `npx likec4 validate .` at 3 source files of 8 with `services/**` in force, `loam validate --all`
1050
+ at 0 errors and `doctor` healthy). A rewrite through `loam subsystem sync` drops the mark.
1051
+ - `service.model-excluded` asks the FILE question too. An `exclude` entry shaped like a file
1052
+ (`services/**/model.likec4`, `**/model.likec4`) hides a model as thoroughly as one naming its
1053
+ directory, and nothing said so: on `examples/docs` such a line took the renderer to 3 source files
1054
+ of 8 while `loam validate --all` printed the string "exclude" not once. It is reported now, with
1055
+ the repair that fits it — `loam subsystem sync` maintains only the `services/` directory entries,
1056
+ so the file shape has to be narrowed or deleted by hand, and the message says which shape you have.
1057
+ - `loam seed` no longer overwrites a hand edit written ABOVE the scaffold's landscape stub.
1058
+ `isLandscapeStub` matched with `endsWith`, so anything a person put before the stub was invisible
1059
+ to the guard: a two-line "OWNED BY THE ARCHITECTURE GUILD — do not regenerate" header was replaced
1060
+ without a word while seed printed "replaced the scaffold's untouched stub", against its own refusal
1061
+ text ("Seed never overwrites human work"). The suffix match existed for exactly one prefix — the
1062
+ comment preamble `migrate-openspec` stages above the identical stub, closed by a bare `//` line —
1063
+ so that is now the only prefix accepted, at every scaffold generation. What a repo notices:
1064
+ `loam seed` refuses with `seed-landscape-edited` and writes nothing, and `loam status` calls such a
1065
+ map `authored` rather than `stub`. A repo staged by `migrate-openspec` still seeds.
1066
+ - `loam doctor` counts the report corpus that is there when the config is the thing that is broken.
1067
+ Read from a SUBDIRECTORY of a repo whose `loam.json` does not parse, it used to report
1068
+ `total 0, next 001` while naming the broken config's own path in the same envelope — handing out an
1069
+ ordinal five existing reports already used. `doctor` is the preflight that runs precisely when the
1070
+ config may not resolve, so `reports.dir` now resolves beside the `loam.json` it FOUND.
1071
+ - `/loam-check`'s `c4.fleet-project-invalid` row spells the `details` format the finding emits:
1072
+ `<path>: L<n>: <error>`, with the colon after the path that the message's own head has always
1073
+ carried. The row promised `<path> L<n>: <error>`, so an agent splitting on the documented separator
1074
+ split in the wrong place.
1075
+
13
1076
  ## [0.2.0-alpha.5] - 2026-09-03
14
1077
 
15
1078
  An adopted service's model renders from the docs root. Still a prerelease on the `alpha` line: