@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/SCHEMA.md CHANGED
@@ -62,6 +62,7 @@ docs/
62
62
  architecture/
63
63
  landscape.likec4 global C4 (system landscape)
64
64
  subsystems.likec4 one view per subsystem, views only [GENERATED: loam subsystem sync]
65
+ services.likec4 one overview view per service, views only [GENERATED: loam subsystem sync]
65
66
  permissions.yaml system-wide authorization vocabulary [authored, opt-in]
66
67
  capabilities.yaml system-wide capability vocabulary [authored, opt-in]
67
68
  obligations.yaml system-wide architectural obligations [authored, opt-in]
@@ -71,12 +72,13 @@ docs/
71
72
  spec.md narrative, then `## Requirements` — `Requirement-ID:` REQUIRED
72
73
  glossary/<term>.md the system's domain vocabulary, one file per term; nesting allowed [authored, opt-in]
73
74
  services/<svc>/ at the root (unfiled), or inside subsystem directories at any depth
74
- model.likec4 boundary C4 (containers/components) [adopt]
75
- likec4.config.json one LikeC4 project per service model, create-only [GENERATED: loam subsystem sync]
75
+ model.likec4 boundary C4 (containers/components); extends the map, or stands alone [adopt]
76
+ likec4.config.json one LikeC4 project per STANDALONE service model, create-only [GENERATED: loam subsystem sync]
76
77
  spec.md living requirements (Requirement/Scenario) [adopt]
77
78
  arch.spec.md living ARCHITECTURE requirements (outbox, retries, alerts; Covers:) [authored]
78
79
  openapi.yaml API contract [adopt / authored]
79
80
  asyncapi.yaml async contract (AsyncAPI 3) [adopt / authored]
81
+ usecases/<name>.likec4 optional flows INSIDE this boundary — dynamic views over its containers, graded when tagged #req- (any .likec4 beside model.likec4 is read the same way) [authored, opt-in]
80
82
  adrs/NNNN-*.md MADR decisions [adopt-seed / authored]
81
83
  runbook.md operational runbook [adopt-draft / authored]
82
84
  health.yaml SLI/SLO, checks, critical deps [authored]
@@ -106,6 +108,38 @@ tagged with the capability it realizes. A flow that spans services does not belo
106
108
  service's directory, and the view's steps join to the landscape's own relationships, which is what
107
109
  makes the flow checkable rather than merely drawn.
108
110
 
111
+ A flow that stays INSIDE one service — the hop sequence behind an arch.spec.md requirement whose
112
+ `Covers:` names containers — cannot live under `architecture/`: loam parses that directory as its
113
+ OWN document set — the map plus its siblings, and nothing under `services/` — so a step naming a
114
+ container does not resolve there, and one such file takes the whole fleet map down
115
+ (`landscape.invalid` plus one `spine.landscape-invalid` per service). That holds however the models
116
+ are shaped: the renderer's root project may also hold every extending model and draw such a step
117
+ perfectly, and loam still grades the map alone, because the map is the document every cross-service
118
+ check reads. It lives beside
119
+ the model instead: every `.likec4` under `services/<…>/<svc>/` other than model.likec4, plus any
120
+ tagged `dynamic view` inside model.likec4 itself, is that service's own LikeC4 project — for an
121
+ extending model, the map plus those documents; for a standalone one, the set
122
+ `services/<svc>/likec4.config.json` registers for the renderer — and `loam validate --service <id>`
123
+ (and `--all`, per service) reads exactly that set, whether or not the project file exists.
124
+ `usecases/<name>.likec4` is the convention; `views.likec4` reads the same. Tag the view
125
+ `#req-<Requirement-ID>` of a requirement in that service's spec.md or arch.spec.md. The tag has to
126
+ be DECLARED somewhere the project can see: for a standalone model that is model.likec4's own
127
+ `specification` — the one block that project has; for an extending model it is
128
+ `architecture/landscape.likec4`'s block, or a tags-only `specification { tag req-… }` beside the
129
+ `extend` (legal, and it lands the tag in the project's specification). Declaring it in BOTH is a
130
+ duplicate, and the code depends on which document carries the second declaration — the finding
131
+ lands where the file is, not at fleet altitude: `c4.invalid` (an ERROR, and the model's grading is
132
+ suspended behind it) when the tags-only block is in model.likec4, `usecase.flow-invalid` when it is
133
+ in a sibling. `c4.fleet-project-invalid` is the fleet-scope class one step out: TWO SERVICES each
134
+ declaring the same tag locally, where no per-service project sees the collision.
135
+ model.likec4 stays THE model:
136
+ `c4.valid`, `Covers:` and every other model check read it alone, and a sibling that does not parse
137
+ is one `usecase.flow-invalid` while the model keeps grading. Files loam does not read: a `.likec4`
138
+ in a subsystem directory, in a feature directory outside `delta.likec4`/`usecases/`/`deployment/`,
139
+ the generated `subsystems.likec4` and `services.likec4`, a sibling the service's own `likec4.config.json` `exclude`
140
+ covers (it is in no project the renderer loads either), and every UNTAGGED `dynamic view`
141
+ anywhere — a diagram, never a use case.
142
+
109
143
  A feature **brings** one the way it brings every other axis: `features/<FEAT>/usecases/<name>.likec4`
110
144
  is a views-only document that `loam archive` copies into `architecture/usecases/` and
111
145
  `loam unarchive` takes back. Create-only, and graded before the copy against the map the same merge
@@ -113,6 +147,189 @@ would leave behind — so a flow may draw a hop into a service the feature's own
113
147
  and a hop into an element the merge does NOT land is refused at plan time rather than archived into
114
148
  a map the next reader's `loam validate --all` fails on.
115
149
 
150
+ ### Two shapes of a service model
151
+
152
+ `services/<…>/<svc>/model.likec4` comes in two shapes, and **the file's own grammar decides which**
153
+ — never a config key, never a flag. loam scans the bytes (comments and strings masked) for a
154
+ top-level `specification { }` block containing an `element` declaration:
155
+
156
+ - an **extending model** declares no element kind. It is parsed INSIDE the fleet's `architecture/`
157
+ project — the landscape plus every other `architecture/**/*.likec4` except the generated views
158
+ files (`subsystems.likec4`, `services.likec4`) — and says what is inside the service with `extend <fqn> { … }` on the element
159
+ the map already binds to the directory. `<fqn>` is the FULLY-QUALIFIED id: `extend orderService`
160
+ resolves to nothing when the element is `marketplace.orderService`. No `specification` is needed,
161
+ no partner is re-declared, and `model {}` or no model at all are both fine. `extend` accepts
162
+ `#tags` and a `metadata { }` block on the element it opens (merged into it — the map's `service`
163
+ binding survives) and REFUSES `description`, which belongs on the map's declaration. This is the
164
+ shape `loam adopt` briefs, and the shape `loam init --create` scaffolds a repository for.
165
+ - a **standalone model** declares at least one `element <kind>` inside a `specification { }` block.
166
+ It is parsed ALONE, exactly as every model was before this shape existed; it may re-declare the
167
+ partners it names; and it renders only as a LikeC4 project of its own. Legal forever — a service
168
+ whose docs are reviewed from its own repository, a model adopted before the fleet had a map — and
169
+ not what a new model is written as.
170
+
171
+ **Who declares kinds and tags.** In the extending shape, `architecture/landscape.likec4` is the one
172
+ `specification` block: a kind a model needs and the map does not declare (`element database`,
173
+ `element queue`) is added THERE, once, and a missing one is a single parse error on the model. A
174
+ tags-only `specification { tag req-CANCEL }` beside an `extend` is legal and lands the tag in the
175
+ project's specification — but a tag the map already declares, declared again there, is a duplicate
176
+ blamed on both files. In the standalone shape the model declares everything it uses, which is
177
+ exactly why its copies of the map's elements can drift.
178
+
179
+ **Seven grades come with the pair**, and one existing grade is narrowed:
180
+
181
+ | code | shape | what it says |
182
+ |---|---|---|
183
+ | `c4.declaration-diverged` (warn, service) | standalone | an element this model and the fleet map (`architecture/`) both declare, whose `kind`, `title`, tag SET or `metadata { service }` binding differ. The two are joined by id, then by the `metadata { service }` binding both wrote, then by the map element whose id ends with the model's as a dotted tail (`paymentService` ↔ `marketplace.paymentService`) — a tail two map elements answer to joins nothing, and a tail that is a BARE last segment (`db`, `api`) is a key only for a FLEET-LEVEL map element: one no ancestor of which stands for a service, so a top-level element or a box the map draws inside a plain grouping element, never an element drawn inside a service's own. `description` is deliberately not compared — it is prose, nothing grades it, and the other four each change what a view shows or what loam resolves. When the tag sets are all that differ and copying the map's declaration would not clear them — because the difference comes off `specification { element <kind> { … } }` on one side or the other — the message says so and names the document whose specification declares them, rather than asking for a copy. One finding per divergent element; silent while the map does not parse |
184
+ | `c4.element-unowned` (warn, service) | extending | the model declares an element OUTSIDE the element that resolves to this service. Three remedies, first first: a store or component this service owns goes INSIDE the `extend <fqn> { }` block, where its id becomes `<fqn>.<name>`; a system the service reaches belongs in the map, declared once; another service's internals belong in that service's model |
185
+ | `c4.fleet-project-invalid` (warn, landscape) | extending | `--all` only, and not while `landscape.excluded` holds — this load reads what the renderer reads and so has no floor under the map, and one `exclude` entry covering the map turns every extending model's `extend` into an unresolved reference (measured on `examples/docs`: 161 warnings from one line). loam loads the fleet project the way the renderer does — a walk of the docs root, minus everything the root `exclude` covers, minus the generated views files (`subsystems.likec4`, `services.likec4`), minus every STANDALONE service directory (`service.model-unexcluded` owns that state), minus `features/` (a delta carries its own `specification` and the directory is transient) — and reports each error NOT already present in the map alone or in any per-service project the same run made. A `.likec4` in a SUBSYSTEM directory, above every service, is in that set. This is the class no per-service grade can see: two services declaring the same tag locally, two models adding the same child id, a broken untagged sibling nobody opted in. All of them are renderer-fatal. `locations[0]` is the file the error is in and the message tail names the failure it actually is |
186
+ | `service.likec4-config-stray` (warn, landscape) | extending | a `likec4.config.json` beside an extending model. The renderer registers it as a project of its own rooted at that directory, so the docs root stops being a one-project workspace (`likec4 validate .` then refuses without `--project`), and wherever that nested project claims the model it parses alone, out of the root project where the map's kinds are: the model does not resolve and `export json --project fleet` loses the service's containers (measured, likec4 1.59.2 — which of the two happens depends on the workspace; on `examples/docs` the fleet export was byte-identical with and without the file, on a seeded fleet the containers went). Run `loam subsystem sync`: it deletes the file. Deleting it by hand works too. loam never writes one beside an extending model |
187
+ | `service.model-excluded` (warn, landscape) | extending | the root `likec4.config.json`'s `exclude` covers this model's directory, so the renderer never loads it and the service is a box with nothing inside it. An entry is read the way the renderer reads it (measured, likec4 1.59.2): a bare entry is a raw path PREFIX starting on a `/` boundary, so `services`, `services/`, `services/*`, `services/**`, `services/**/*`, a wildcard segment in the middle and the bare `services/pay` all cover `services/payment-service` — while the glob form `services/pay/**` wants the literal separator and covers nothing there. An entry shaped like a FILE rather than a directory (`services/**/model.likec4`, `**/model.likec4`) is reported too, with a different repair: `loam subsystem sync` maintains only the `services/` directory entries, so that shape has to be narrowed or deleted by hand. The finding quotes the entry verbatim, because the repair is to edit that line; `loam subsystem sync` is the repair for the directory shape |
188
+ | `service.model-unexcluded` (warn, landscape) | standalone | the mirror, and the one that costs most: the root `exclude` does NOT cover a standalone model's directory, so the renderer merges it into the root project and reports every kind and element it declares as a duplicate — blanking the whole project, not just this service. An entry already covering the directory in any of the spellings above satisfies it, and `sync` appends no second glob for one. Same repair |
189
+ | `service.likec4-config-missing` (warn, landscape) | standalone **only** | a standalone model with no project file beside it. It renders nowhere from the docs root until `loam subsystem sync` writes one. An extending model never earns this |
190
+ | `landscape.excluded` (warn, landscape) | either | `--all` only. The root `likec4.config.json`'s `exclude` covers `architecture/landscape.likec4` itself — an `architecture/*.likec4` written for a palette is the spelling that does it. The renderer then loads NO fleet map: it draws nothing, and every extending model in the root project resolves against nothing. loam is unaffected — the architecture loader keeps the map whatever the list says — which is exactly why the state was invisible. `c4.fleet-project-invalid` is suspended behind it, on the cascade rule `permissions.invalid` states: that check reads the renderer's project, so the same entry would come back once per unresolved reference instead of once as the cause, and the message says the fleet project is not graded until the entry goes. The finding quotes the entry verbatim, and `loam subsystem sync` is NOT the repair: it maintains only the `services/` entries and leaves every other line as the team wrote it |
191
+
192
+ `c4.invalid` and `c4.valid` are unchanged codes with adjusted scope: for an extending model every
193
+ error the per-service project raises is THIS model's, including one the parser blames on the
194
+ landscape, because the map parsed clean on its own; and `c4.valid`'s message reads
195
+ `extends the fleet map — N elements · M relationships`, followed by ` · K tagged flow(s) graded`
196
+ when the service's own project declared any (the one surface a healthy intra-service flow appears
197
+ on). When the map itself does not parse, an
198
+ extending model cannot be read at all: neither code is emitted, and `spine.landscape-invalid` says
199
+ so with the clause ` — and model.likec4 extends it, so the model cannot be read either`.
200
+
201
+ Every other model-dependent check reads the same slice it always read — the service's OWN elements
202
+ and relationships. For an extending model that is what the project holds minus what the map alone
203
+ holds, plus the **partners** its own edges point at and their ancestors, so `health.yaml`'s
204
+ `kafka` still resolves when the only edge names `kafka.orderEvents`. Nothing reads the map's edges
205
+ as the model's.
206
+
207
+ **What loam grades is still the `architecture/` project alone.** The renderer's root project is
208
+ WIDER than the fleet map: it also holds every extending model. loam's fleet checks — the spine, the
209
+ bindings, the fleet-shape advisories, and the steps of every `architecture/usecases/` flow — read
210
+ the landscape plus its `architecture/` siblings and nothing else, whatever shape the models take.
211
+ So a cross-service flow names SERVICE elements: a hop naming a container an extending model
212
+ declares renders perfectly and is `landscape.invalid` for loam. That is deliberate, and the reason
213
+ is blast radius — a service's model is graded on its own merits, and one broken model must never
214
+ blank the fleet's own map. A container-level flow has its own home beside the model that declares
215
+ the container. The same holds for a feature's `features/<FEAT>/usecases/` flow, and the gate says
216
+ so before the merge rather than after it: the preview stages no service model, and it ROUTES a
217
+ nested addition into the model that owns that service's interior exactly as `loam archive` would.
218
+ So a hop naming `<service-fqn>.<container>` resolves in neither the preview nor the tree the archive
219
+ leaves, and `usecase.flow-invalid` refuses — where the un-routed preview resolved it, passed the
220
+ gate `--approve` cannot override, and landed a flow the next `loam validate --all` failed on.
221
+
222
+ **Private stores.** **Where a store is drawn is the team's choice.** A store one service owns may be
223
+ nested UNDER that service's element — written in the service's `extend <fqn> { store = database … }`
224
+ block, or inside the service's element on the map when the map draws that service's containers — or
225
+ drawn beside the services as an element of its own. Both are first-class and loam grades neither as
226
+ wrong. Until 2026-09-07 the second earned `landscape.datastore-private` at one consumer, a warning
227
+ whose whole message prescribed deleting the peer while the identical store nested was silent: it
228
+ graded the drawing rather than the architecture, and it is retired. Infrastructure is not a service
229
+ either — an element whose kind is `database`, `queue`, `topic`, `cache` or `bucket` is out of the
230
+ service census, so an owned store drawn at fleet level is not `landscape.service-undocumented`, is
231
+ not a `loam gate` partner, and mints no `service.exists` verification claim. The `extend` placement
232
+ needs an EXTENDING model: a model that declares its own `specification` is parsed alone, so
233
+ `extend <fqn>` resolves nothing there, and for such a service the map is the only nesting site (or
234
+ migrate the model — see "Two shapes of a service model" above).
235
+
236
+ What the choice DOES decide is one thing, and it is a fact about LikeC4 rather than a preference: **a
237
+ second consumer's edge resolves only against a declaration the MAP holds**, because a per-service
238
+ project is `architecture/` plus one model. Draw a container-level edge to `<owner-fqn>.<store>` in
239
+ another service's model while the store lives in the owner's `extend` block and that model is
240
+ `c4.invalid` ("Could not resolve reference to Referenceable named '<store>'"). So a store a second
241
+ consumer will name has to be on the map — nested inside the owner's element there, or beside the
242
+ services. That was always the load-bearing half; the prescription around it was not.
243
+
244
+ Nesting closes no door in the other direction: loam counts a
245
+ consumer a service's EXTENDING model attests exactly as it counts one the map draws, so a nested
246
+ store two services reach is `landscape.datastore-shared` — the store's owner plus every service
247
+ whose model draws an outbound call at it or below it. The second consumer draws that call as a
248
+ container-level edge to `<owner-fqn>.<store>` in its OWN model, and the service-level twin on the
249
+ map as usual. The two placements give the store the same id,
250
+ `<owner-fqn>.<store>`, so moving the declaration OUT of the owner's `extend` block into the owner's
251
+ element on the map rewrites no edge and no `Covers:` line — measured on `examples/docs`, where
252
+ moving `db` out of order-service's `extend` block into `orderService` on the map left the model at
253
+ 12 elements · 8 relationships, left `Covers: marketplace.orderService.db` in its `arch.spec.md`
254
+ resolving, and left checkout-web's container-level edge to it resolving. The move BACK is free only
255
+ while no other service names the store: with checkout-web's edge drawn, putting `db` back into
256
+ order-service's `extend` block is `c4.invalid` on checkout-web's model ("Could not resolve
257
+ reference to Referenceable named 'db'"), or `landscape.invalid` plus one `spine.landscape-invalid`
258
+ per service when that second edge is drawn on the map. `landscape.datastore-shared` needs a second
259
+ consumer to fire, so it always prints in the state where map→extend breaks, and its message says
260
+ so. It is the one datastore grade left, it fires at two consumers for both placements alike, and it
261
+ states a coupling rather than prescribing a move: shared means the same DATA, and if two services
262
+ only share a host or a cluster the honest model is one private store each. The consumer census is
263
+ fleet-scope: it reads
264
+ EXTENDING models only, because their ids are the map's fully-qualified ids and the join is exact,
265
+ while a standalone model's ids are its own file's and are never matched to a landscape element.
266
+
267
+ **Renderer consequences.** The root project holds the map **and** every extending model, so one
268
+ `npx likec4 start` at the docs root draws the fleet and the inside of every adopted service, and
269
+ the project picker holds one entry. A standalone model is the exception, and it costs two files:
270
+ ONE entry in the root `likec4.config.json`'s `exclude` covering its directory (its kinds would be
271
+ duplicates of the map's inside one project), plus its own `likec4.config.json`, which is what makes
272
+ it a project of its own.
273
+ `loam subsystem sync` maintains both halves — it writes the per-service file for standalone models
274
+ only, and rewrites the root `exclude` so that it holds `services/<tree>/**` for exactly the models
275
+ that stand alone. The rewrite removes the wholesale `services/**` and every `services/<tree>/**`
276
+ naming a directory that IS a service, keeps every other entry in the order the team wrote it (a
277
+ `services/legacy/**` for a directory that is no service survives untouched), and appends the
278
+ standalone entries sorted. It runs when at least one extending model exists, or when a standalone
279
+ model's directory is not covered — so a repository whose models all stand alone behind `services/**`
280
+ is never touched, and a fresh scaffold that gains its first standalone model earns the entry on the
281
+ next sync. `--json` reports it as `projects.exclude`, with what was added and removed; the text view
282
+ prints one line when it rewrote. An entry covers a directory when it names that directory —
283
+ `<dir>`, `<dir>/`, `<dir>/*`, `<dir>/**` and `<dir>/**/*` all do, and a wildcard segment in the
284
+ middle (`services/*/legacy`) matches one level — or when it is a raw path prefix of it from a `/`
285
+ boundary, which is what the renderer measures: `services/pay` covers `services/payment-service`,
286
+ while `services/pay/**` covers nothing there. An entry is normalised the way the renderer
287
+ normalises it before anything is matched: a leading `./`, an inner `/./` and an inner `//` are
288
+ resolved away, a TRAILING `/` is not (bare `architecture` also hides `architecture-old/`,
289
+ `architecture/` does not), `.//` leaves a literal `.` directory and hides nothing, and a BACKSLASH
290
+ is a literal character to the matcher, never a separator — `services\order-service\**` hides
291
+ nothing at all, so loam neither grades on it nor rewrites it. Measured at the 1.59.2 pin against
292
+ `npx likec4 validate .` file counts; the table is in `src/core/c4/root-project/exclude.ts` and
293
+ pinned in `test/root-exclude.test.ts`. A directory the list already covers in
294
+ any of those spellings earns no second entry. `projects.exclude` carries `updated`, `entries`,
295
+ `added`, `removed` and `unreadable` — the last is `true` when the root file exists and loam could
296
+ not read an `exclude` list out of it, which is never the same answer as `entries: []`, and the list
297
+ is then reported untouched. Beside it, `--json` carries
298
+ `projects.created` (the per-service project files written) and `projects.removed` (POSIX paths
299
+ relative to the DOCS ROOT, deleted this run, sorted by path; `[]` otherwise) — and
300
+ `projects.created` is spelled the same way. Both are relative to the docs root, not to the
301
+ repository root, which differ whenever `docsDir` is a subdirectory. A fresh repository is
302
+ scaffolded with
303
+ `exclude: ["**/node_modules/**", "features/**"]`.
304
+
305
+ **Migrating a standalone model to the extending shape** is a documented recipe an agent performs;
306
+ loam does not rewrite a model, because the comments in a real one are somebody's and not loam's to
307
+ reflow. In order:
308
+
309
+ 1. Write into `architecture/landscape.likec4` any element kind the model uses that the map does not
310
+ declare (`element database`, `element queue`), and any element the model reaches that the map
311
+ does not draw — an external system, a broker's topic — as the fleet's own declaration.
312
+ 2. Delete the model's `specification` block.
313
+ 3. Delete every element the map declares: the service's own element, and every re-declared partner.
314
+ 4. Wrap what is left in `model { extend <fqn> { … } }`, `<fqn>` being the fully-qualified id of the
315
+ map's element for this service. Child ids inside the block stay relative; internal edges may be
316
+ written inside it.
317
+ 5. Move the cross-boundary edges outside the `extend` block and spell both endpoints in full —
318
+ `marketplace.orderService.outbox -> kafka.orderEvents`.
319
+ 6. Requalify every id that names one of those elements from outside the file: the `Covers:` lines in
320
+ `arch.spec.md`, the `of <element>` and `include` lines of any `views` block, and the hops of any
321
+ `#req-`-tagged flow beside the model. There is no suffix matching anywhere — `orderService.db`
322
+ does not answer for `marketplace.orderService.db` — so every model-local nested id has to be
323
+ rewritten. `health.yaml`'s `dependencies:` are the one place that usually needs no edit, because
324
+ a dependency resolves when an element of the own slice answers to it **by id, `metadata { service }`
325
+ binding or title**, and the slice holds the service's own elements plus every endpoint of the
326
+ model's own edges and each endpoint's ANCESTORS: `kafka` still answers through an edge naming
327
+ `kafka.orderEvents`, and a peer service still answers through the binding on the map's element.
328
+ Prefer the service id or a top-level system id there — those two spellings resolve under either
329
+ model shape, and a model-local nested id does not.
330
+ 7. Delete `services/<…>/<svc>/likec4.config.json`.
331
+ 8. Run `loam subsystem sync` (it rewrites the root `exclude`), then `loam validate --all`.
332
+
116
333
  ## Subsystems — a navigable tree under `services/`
117
334
 
118
335
  `services/` may be one flat level (the compatibility case, and forever legal) or an unbounded tree
@@ -135,6 +352,13 @@ A directory under `services/` is classified **three ways, never two**:
135
352
  subsystem's marker while another moves a service in, and the group directory would otherwise be
136
353
  read as one empty service. The fleet is never reported smaller than it is.
137
354
 
355
+ A directory named for one of the service's own subdirectories (`adrs/`, `usecases/`, `ui/`) is
356
+ service interior wherever it sits: the walk never reads one as a service or a subsystem, and never
357
+ counts one as the subdirectory that makes its parent a group. Writing the documented
358
+ `services/<id>/usecases/<name>.likec4` slot into a directory that holds no artifact FILE yet used
359
+ to make the walk read `usecases` as a service and its parent as an unmarked group — three errors
360
+ earned by following the instruction `loam init` had just scaffolded.
361
+
138
362
  `subsystem.yaml` is a **marker, not a manifest**:
139
363
 
140
364
  ```yaml
@@ -166,6 +390,30 @@ reports). The output is deterministic and line-oriented — subsystems sorted by
166
390
  one `include` per line, no timestamps, no absolute paths — so it is byte-reproducible on any machine
167
391
  and two concurrent moves into different groups merge in git without intervention.
168
392
 
393
+ **Styling.** Each generated view carries `global style subsystems` — after its `title` and
394
+ `description`, before its `include`/`group` — exactly when the `architecture/` project declares a
395
+ global style with that id: `global { styleGroup subsystems { style element.tag = #external { color gray } } }`
396
+ (or the single-rule `global { style subsystems … }`), in the landscape or in ANY `.likec4` under
397
+ `architecture/` the root `likec4.config.json`'s `exclude` does not cover — a `palette.likec4` beside
398
+ the map works, and one under an excluded path is loaded by neither the renderer nor loam.
399
+ `architecture/landscape.likec4` is the one exception: loam keeps the map in that set however the
400
+ list is written, because an architecture project with no documents in it reads as a fleet that
401
+ models nothing. With the targeted tag declared in the `specification` block.
402
+ Declaring it is the whole opt-in. loam reads only that the id is declared, never what the group
403
+ says, and writes nothing when it is not — a reference to an undeclared style is a parse error that
404
+ blanks the whole `architecture/` project in the renderer. When the project declares style groups
405
+ under other names only, the file opens with one comment line naming them and saying none is
406
+ `subsystems`, and the views carry the renderer's defaults; there is no single-group fallback.
407
+ Both halves restale the file once (`subsystem.views-stale`) until the next `loam subsystem sync`:
408
+ declaring or removing the group named `subsystems` changes whether the views carry the
409
+ `global style subsystems` line, and declaring or removing a group under ANY OTHER name changes the
410
+ disclosure comment that lists what is available. Whether a service's own views can borrow that group depends on the shape of
411
+ its model: a model that EXTENDS the map is in the root project, so a `global styleGroup` declared
412
+ under `architecture/` reaches the views beside it and referencing the id there is legal; a model
413
+ that STANDS ALONE is a project of its own, and a group declared under `architecture/` does not reach
414
+ it — those views need a declaration of their own, or the reference is the parse error that blanks
415
+ that project.
416
+
169
417
  Each view carries the **label a human reads**: `title`, and `description` when the marker authors
170
418
  one. The title is the subsystem's **path** — every marked ancestor's label and its own, joined with
171
419
  ` / `, so `services/platform/order-flow` reads `Platform / Order flow` — and each label is the
@@ -199,23 +447,161 @@ the file behind.
199
447
  `loam subsystem sync` is the one regenerator (it also *removes* the file when the tree has no
200
448
  subsystems), every `loam subsystem` write verb regenerates it inside the same transaction, and
201
449
  `validate --all` grades staleness by **content comparison** (line endings normalized; a CRLF
202
- checkout changes no fact) — exactly one error,
203
- `subsystem.views-stale`, on exactly that file; nothing in loam ever parses it: a views-only document
204
- does not parse standalone (its `include` lines reference elements the landscape defines), and it
205
- holds **static** views, whose contents loam never reads in any document. Only the LikeC4 renderer,
206
- which merges the whole `architecture/` project, resolves its includes.
207
-
208
- The tree's **second renderer file** is per service — `services/<…>/<svc>/likec4.config.json`,
209
- which `loam subsystem sync` also writes, beside every `model.likec4` that has none. The root
210
- project is scoped to `architecture/` and excludes `services/**`, and must be, since each model
211
- declares its own `specification` block; what that exclusion cost went unstated for a release: an
450
+ checkout changes no fact) — one error,
451
+ `subsystem.views-stale`, per stale generated file, each naming its own path (there are two of them;
452
+ `architecture/services.likec4` is below); nothing in loam ever parses either: a views-only document
453
+ does not parse standalone (its `include` lines reference elements the landscape defines), and both
454
+ hold **static** views, whose contents loam never reads in any document. Only the LikeC4 renderer,
455
+ which merges the whole `architecture/` project, resolves their includes.
456
+
457
+ `sync` refuses to rewrite this file while the `architecture/` project cannot be read. The render
458
+ needs the map's elements and its global-style census, and a tolerant "no elements" answer wrote a
459
+ file holding title and description alone — the `global style subsystems` line, every `include` and
460
+ the boundary comment gone — while reporting `updated … 1 subsystem view(s)` and answering `current`
461
+ ever after. So: a load error falls back to the landscape FILE when that parses on its own (a
462
+ duplicate `subsystems` group declared in two `architecture/` documents costs nothing), and when
463
+ that fails too the facts are UNKNOWN. With an existing views file, `sync` then writes nothing about
464
+ it, prints why, and reports the `--json` action `blocked`, with `blocked.reason` reading
465
+ `landscape-invalid` — exit 0, because `loam validate --all`'s `landscape.invalid` is the gating
466
+ surface. With no file yet, and inside a tree-changing verb's commit window (`new`/`move`/`rm`/
467
+ `rename`, whose contract is that the file lands with the change), the tolerant shape is written as
468
+ before and the same note is printed.
469
+
470
+ ### `architecture/services.likec4` — one overview view per service
471
+
472
+ The **second generated views file**, written by the same `loam subsystem sync`, in the same commit,
473
+ removed by it when the map draws no service, and graded by the same `subsystem.views-stale` on its
474
+ own path. Views only, one per service the fleet map draws, sorted by service directory id:
475
+
476
+ ```
477
+ view service_payment_2dservice of marketplace.paymentService {
478
+ title 'Payments / payment-service'
479
+ global style services
480
+ include *
481
+ include kafka.orderEvents
482
+ include marketplace.checkoutWeb
483
+ include stripe
484
+ exclude * -> *
485
+ include -> marketplace.paymentService
486
+ include marketplace.paymentService ->
487
+ include marketplace.paymentService.* -> marketplace.paymentService.*
488
+ }
489
+ ```
490
+
491
+ **Why the peers are named one by one, measured at the 1.59.2 pin on a 120-service and a 58-service
492
+ fleet.** The obvious recipe — `include <fqn> -> *` and `include * -> <fqn>` — is wrong at fleet
493
+ scale and looks perfect on a small flat fixture, which is how it would have shipped. LikeC4 draws a
494
+ counterpart at the highest ancestor that is not also an ancestor of the subject, so on a map that
495
+ GROUPS its services (which is exactly what a 100-service fleet needs at fleet level) every peer
496
+ collapses to its group box: the naive view of one service in the 120-service fleet drew six group
497
+ boxes and **0 of its 10 nodes had drill-down**. loam already reads elements and relationships, so it
498
+ names each peer itself — the far endpoint of every edge touching the service or anything inside it,
499
+ spelled as the map declares it, so an edge at a topic names the topic and not the broker, which is
500
+ the truer picture and the one the map already draws. `exclude * -> *` then drops the peer-to-peer
501
+ cross traffic (23 edges down to 7 on the same fixture), and the three lines after it put back
502
+ exactly the subject's own: the UNARY `include -> <fqn>` / `include <fqn> ->`, never the binary
503
+ wildcard, which re-introduces the parent group boxes as extra nodes (16 instead of 10). `include
504
+ <fqn>.* -> <fqn>.*` is the interior wiring, and it is legal on a childless element — it simply
505
+ matches nothing — which is what makes one line work for both model shapes: an extending model's
506
+ containers are in the root project and appear, a standalone model's are not and the line is silent.
507
+
508
+ Measured at 58 services in 11 groups: a median of **10 nodes and 8 edges** per generated view, every
509
+ peer that is a service carrying drill-down into its own generated view, and `npx likec4 export json`
510
+ — which layouts every view — costing about **7 ms per generated view** (1.3 s with 120 of them
511
+ against 451 ms without). `likec4 start` layouts on demand, so a reader pays for the one view they
512
+ open, and the generated views are all small BY CONSTRUCTION; the expensive view is the hand-authored
513
+ fleet one, and it is unaffected.
514
+
515
+ **Drill-down is the `of`, and it costs one keyword.** `view <id> of <element-fqn>` names the view's
516
+ subject, and LikeC4 then computes `navigateTo` on that element's node in **every other view that
517
+ draws it** — no authored links anywhere. So one generated view per service makes the fleet map,
518
+ every subsystem view and every other service view click-through into the service. The subsystem
519
+ views take the same keyword when the map holds an element owning exactly that subsystem's members,
520
+ which closes the mesh — `fleet → payments → payments/service-22 → identity/service-45 → identity`,
521
+ every hop generated, none authored.
522
+
523
+ **Two views of one element are legal, and the FIRST DOCUMENT ALPHABETICALLY wins** the default
524
+ navigation target — measured by renaming the files, LikeC4's load order being alphabetical by path.
525
+ Both stay reachable: inside a view of the subject, the subject's own node navigates to the OTHER
526
+ view of the same element, so the renderer cycles between siblings and nothing is orphaned.
527
+ `architecture/landscape.likec4` sorts before `architecture/services.likec4`, so a team's authored
528
+ `view of <element>` written in the map keeps precedence over the generated one, and neither is lost.
529
+ loam does not detect this and never will: **docs/DESIGN.md rule 26 forbids loam reading what a view
530
+ SHOWS**, in any document, so it may not look at an authored `view of` to decide whether to emit. It
531
+ emits for every service the map draws, and the alphabet settles the rest.
532
+
533
+ **The first line is the deed, and every writer checks it.** Both generated views files open with a
534
+ `// generated by loam` mark, and that mark is what makes the path loam's rather than the team's.
535
+ These are the only two filenames loam RESERVES inside `architecture/`, a directory that is otherwise
536
+ entirely authored, so a repository that already had a file at one of those paths must not lose it to
537
+ an upgrade — the archive's promise that authored bytes stay authored holds here too. Every writer of
538
+ these paths reads the first line first: `loam subsystem sync` reports that one file `refused`,
539
+ writes nothing for it, still does the rest of its work, and exits 1; `loam subsystem new`, `rm`,
540
+ `move` and `rename` and `loam seed` refuse the WHOLE operation with `already-exists`, writing,
541
+ renaming and staging nothing, because their commit window exists precisely to land the generated
542
+ file in the same commit as the change to the tree — completing half of that would manufacture the
543
+ `subsystem.views-stale` state the window exists never to leave behind. The consequence to know: a
544
+ file with no mark is never regenerated, and an EMPTY file carries no mark either, so a truncated one
545
+ must be deleted by hand before sync will write it again. loam's own staging is atomic and cannot
546
+ produce one.
547
+
548
+ **Titles are folders, because there is nothing else.** LikeC4 has **no view-folder syntax** —
549
+ `views { group 'X' { … } }` is a parse error at the pinned version and the AST carries no view-group
550
+ type at all. What it has is the title: a view title is split on `/` into browser folders and only
551
+ the last segment is shown. So a per-service view is titled `<subsystem path> / <service directory
552
+ id>`, built from the same path helper the subsystem views use, and the two generated files fold into
553
+ ONE folder tree mirroring `services/`. An unfiled service is its own id at the root. The service
554
+ **directory id** is used rather than the map element's title: it is the name every loam command
555
+ addresses the service by, it cannot drift from the map, and a directory id cannot contain `/`. A
556
+ `dynamic view` you author under `services/<…>/<id>/usecases/` lands in the same folder when you
557
+ title it `'<subsystem path> / <service id> / <use case>'` — a convention loam states and never
558
+ grades.
559
+
560
+ **Which services appear.** Exactly those the element→service join resolves, the same join the
561
+ subsystem views use. A service the map does not draw is omitted — its absence is already
562
+ `landscape.service-unmodelled`, and rendering only what the committed map resolves is what keeps the
563
+ output a function of committed bytes. **View ids** are `service_` plus the same hex escaping the
564
+ subsystem view ids use, so the two generated files cannot collide with each other, and an authored
565
+ view claiming one is `subsystem.view-id-collision` — which names the file and the reserved prefix. A
566
+ `service_` id is reserved only for a service the map actually draws. **Style**: `global style
567
+ services`, presence-detected off the parsed map's declared style ids exactly as `subsystems` is, and
568
+ written only when a group of that name is declared — a reference to an undeclared group is a parse
569
+ error that blanks the whole project.
570
+
571
+ ### The per-service LikeC4 project file
572
+
573
+ A different artifact from the two generated views files above, and the tree's other renderer
574
+ concern: `services/<…>/<svc>/likec4.config.json`,
575
+ which `loam subsystem sync` also writes, beside every **standalone** `model.likec4` that has none.
576
+ An extending model needs no such file and never gets one: it belongs to the root project, which is
577
+ where it parses, and a project file beside it is `service.likec4-config-stray` — which `sync`
578
+ DELETES, in the same journaled commit as everything else it writes. A standalone model
579
+ needs one because it cannot share a project with the map — it declares its own kinds, and inside
580
+ one project every declaration is a duplicate blamed on the map too — so `sync` also keeps the root
581
+ `exclude` covering exactly the directories of the models that stand alone. Before extending models
582
+ existed, `services/**` was excluded wholesale and every model needed the file; what that exclusion
583
+ cost went unstated for a release: an
212
584
  adopted service was a box on the fleet map with nothing renderable inside it. Measured at the
213
585
  pinned likec4, a project file INSIDE a service directory is registered as a project of its own
214
- regardless of that exclusion, so one `npx likec4 start` at the docs root loads `fleet` plus one
215
- project per service, each carrying LikeC4's own `index` view of the containers beside the authored
216
- one, and a model that does not parse breaks only its own project. The file is **create-only** —
217
- never compared, never rewritten, never removed — under the root config's ownership rule, not the
218
- views file's: there is no `-stale` grade because nothing can make it stale. Its bytes are a
586
+ regardless of the exclusion, so a docs root holding standalone models loads `fleet` plus one
587
+ project per such service, each carrying LikeC4's own `index` view of the containers beside the
588
+ authored one, and a model that does not parse breaks only its own project. (An extending model
589
+ is inside `fleet`, so a model that does not parse breaks the root project instead — the trade the
590
+ shape makes, and `c4.fleet-project-invalid` is what names it before a reader meets it in the
591
+ renderer.) The file is **create-only for a standalone model** —
592
+ never compared, never rewritten — under the root config's ownership rule, not the
593
+ views file's: there is no `-stale` grade because nothing can make it stale. It is removed in
594
+ exactly one state, and only there: beside a model that EXTENDS the map. That nested project claims
595
+ the model out of the root project — the workspace loads two projects, the model is Invalid inside
596
+ its own one (the map's kinds are not in there with it) and `export json --project fleet` then loses
597
+ the service's containers. Which of the two the workspace does was measured both ways at the 1.59.2
598
+ pin: on a seeded fleet whose model extends a top-level id the containers went; on `examples/docs`
599
+ the root project kept the model and the export was byte-identical. What is true in every state is
600
+ the second project itself — `likec4 validate .` at the docs root refuses without `--project` from
601
+ the moment that file exists. So it is not a harmless leftover, and `loam subsystem sync` deletes
602
+ it. One beside a model that is GONE stays: that one really is the team's — though the same second
603
+ project it registers is there too, so `likec4 validate .` and `likec4 build` at the docs root need
604
+ `--project` while it stands (ROADMAP's "Known and carried, 2026-09-04"). Its bytes are a
219
605
  function of the id alone (two keys, `name` and `title`), so placement never changes them and a
220
606
  `subsystem move` carries the file with no write of its own — it rides inside the rename the move's
221
607
  transaction already journals (in a git-backed repo, commit the files `sync` wrote first: `move`
@@ -234,11 +620,19 @@ nothing is written and the text output says so. No model, no file: a spec-only s
234
620
  nothing and gets its file on the first `sync` after its model lands. No legal id, no file either: a
235
621
  directory whose name breaks the service-id grammar (`service.id-invalid`) would get a `name` the
236
622
  renderer silently drops, and a create-only file the renderer ignores would silence the grade for
237
- good, so `sync` skips it and the rename that clears `service.id-invalid` earns the file. A file left behind by a
238
- deleted model is the team's and harmless (measured: a nested project holding no `.likec4` file is
239
- absent from the project list). `validate --all` grades presence only — one warning per service,
240
- `service.likec4-config-missing`, on the fleet `landscape` target, never an error, never in
241
- `--service`, `status` or `doctor`; the fix is `loam subsystem sync`, never a hand-written file. A
623
+ good, so `sync` skips it and the rename that clears `service.id-invalid` earns the file.
624
+ `validate --all` grades presence only — one warning per service with
625
+ a STANDALONE model and no file, `service.likec4-config-missing`, on the fleet `landscape` target,
626
+ never an error, never in `--service`, `status` or `doctor`; the fix is `loam subsystem sync`, never
627
+ a hand-written file. Its two siblings grade the extending shape's version of the same question —
628
+ `service.likec4-config-stray` for a project file that should not be there, `service.model-excluded`
629
+ for a root `exclude` entry that hides the model from the renderer — and `sync` is the repair for
630
+ both: it removes the stray and rewrites the entry. A third reads the same list and is nobody's to
631
+ rewrite: `landscape.excluded` says an entry covers `architecture/landscape.likec4` itself, so the
632
+ renderer loads no map at all — `sync` maintains only the `services/` entries and will not touch that
633
+ line, and the finding quotes it so its author can. It also suspends
634
+ `c4.fleet-project-invalid` for that run, because the renderer's project is exactly what the entry
635
+ broke and grading it would report the one line once per extending model. A
242
636
  feature's `delta.likec4` is deliberately out of scope: the directory is transient and
243
637
  `loam archive` moves it where a project file would still be registered, so every shipped feature
244
638
  would keep a dead project in the list forever.
@@ -264,6 +658,7 @@ lives *today*.
264
658
  | Capability | requirement `Capability:` (list; also `Capabilities:`) | `architecture/capabilities.yaml` OR `capabilities/<cap>/spec.md` | declared capability id, `/` allowed for nesting |
265
659
  | Capability requirement | requirement `Realizes:` (list) | a requirement in `capabilities/<cap>/spec.md` | `<capability-id>#<Requirement-ID>`, split at the LAST `#`, with an optional `@<digest>` pin |
266
660
  | Capability requirement (flow) | a `dynamic view` tag `#req-<slug>` | a requirement of the capability the view's `#cap-` tag names | the `Requirement-ID` with every character outside `[A-Za-z0-9_-]` flattened to `-` |
661
+ | Service requirement (flow) | a `dynamic view` tag `#req-<slug>` in a `.likec4` beside model.likec4, or inside it | a requirement of the service whose directory holds the file, from either spec axis | the `Requirement-ID` flattened as above |
267
662
 
268
663
  These keys are independent: an operation id is not a permission and a service id is not inferred
269
664
  from a feature directory. A file can therefore be structurally valid while a join is broken; the
@@ -686,7 +1081,21 @@ There is no manifest of services: the directories under `services/` **are** the
686
1081
  why the two are graded differently;
687
1082
  - two elements resolving to the **same** `services/<svc>/` → `landscape.binding-duplicate`
688
1083
  (**warning**): every element→service join picks one of them arbitrarily, so the other's edges are
689
- quietly filed under a service that does not own them.
1084
+ quietly filed under a service that does not own them;
1085
+ - an element resolving to a `services/<svc>/` that no edge in the map touches, while that service's
1086
+ `model.likec4` parses and declares at least one call across its own boundary →
1087
+ `landscape.service-isolated` (**warning**, never gating): the service is drawn and invisible to
1088
+ every cross-service check, and the finding names the attested calls to draw as edges on the
1089
+ existing element, collapsed to the service. Evidence-gated on purpose — a service with no model,
1090
+ an unparseable one (`c4.invalid`), or a model reaching nothing is silent here, because an edgeless
1091
+ element that is TRUE is a fact and an invented edge is a dependency the fleet then plans against;
1092
+ the adopt brief's `landscape.touched` is the only place that silent state is named. And BINARY:
1093
+ one edge closes it, so a map drawing one of five attested calls is silent too — the check is
1094
+ touched/untouched, never a set difference over the attested calls, because loam cannot tell a
1095
+ call the map forgot from one the fleet deliberately does not draw at its own altitude. "Edge in
1096
+ the map" means an edge in the `architecture/` PROJECT — the landscape plus every other `.likec4`
1097
+ under it, minus the generated views files (`subsystems.likec4`, `services.likec4`) — and `loam adopt` reads the same set. An
1098
+ `#external` element is never the subject, whatever directory it is bound to.
690
1099
 
691
1100
  **`architecture/landscape.likec4` is required, not optional.** Its absence is a finding, never a
692
1101
  skipped check: an **error** as soon as `services/` holds at least one service directory, and a
@@ -697,7 +1106,7 @@ it cannot be silence: with no map, the whole cross-service layer — the C4↔AP
697
1106
  `landscape.service-unmodelled`, every derived view — computes nothing and *reports* nothing, which
698
1107
  reads exactly like a clean fleet. `loam init` scaffolds the file (empty, commented, parsing clean)
699
1108
  so a fresh docs repo starts from a real one rather than from that ambiguity, and `loam adopt` briefs
700
- the block each service still owes it.
1109
+ the write each service still owes it — the block, or the edges on an element no edge touches.
701
1110
 
702
1111
  Absences inside a service directory are graded too. A directory with no `spec.md` is a **warning**
703
1112
  (`service.no-spec`) — partial adoption is a supported state, but the checks that file feeds are
@@ -985,7 +1394,9 @@ the source for tests.
985
1394
  diff, tagged to the feature.
986
1395
  - **`loam archive <FEAT>`** merges the delta into the living state on three axes — **requirements**
987
1396
  (`spec.md`, and `arch.spec.md` beside it: the two files ride one merge code path, parameterized by
988
- filename), **API** (`openapi.yaml`), **architecture** (`landscape.likec4`) — then archives the
1397
+ filename), **API** (`openapi.yaml`), **architecture** (`landscape.likec4`, plus the
1398
+ `services/<…>/<svc>/model.likec4` of each service whose EXTENDING model owns an addition nested
1399
+ inside it) — then archives the
989
1400
  feature, so the living state stays complete. Archived deltas are the evolution history (like
990
1401
  `git log`). `--dry-run` prints the whole plan and writes nothing.
991
1402
  - **Coherence gate:** `loam validate --feature` checks the three axes agree (C4 edge `op` ↔ OpenAPI
@@ -1145,10 +1556,16 @@ the CI escalation.
1145
1556
  such entry into a false `covers.unknown` "typo" pointing at the spec, when the truth was the file.
1146
1557
  - `health.dependency-unmodelled` (service scope): a `dependencies:` id that nothing in the service's
1147
1558
  OWN `model.likec4` answers to — by element id, `metadata { service }` binding, or title; exact
1148
- match, with a did-you-mean hint. Deliberately the service model and never the landscape: the
1559
+ match, with a did-you-mean hint. Deliberately the service model and never the landscape read on
1560
+ its own: the
1149
1561
  landscape carries only what crosses the service boundary, the model carries everything the service
1150
- touches (a private store as a nested container — `landscape.datastore-private` is the check
1151
- pushing it there), and that is the set the on-call file must agree with.
1562
+ touches (a private store nested inside the service's own element — one of the two placements
1563
+ "Private stores" above leaves to the team, and `landscape.datastore-shared` is what a second
1564
+ consumer of that nested store earns), and that is the set the on-call file must agree with. For an extending model
1565
+ the set is its own SLICE: the elements it adds, plus the partners its own edges point at and their
1566
+ ancestors — so a dependency named `kafka` resolves through an edge that names `kafka.orderEvents`,
1567
+ and a peer service resolves through the `metadata { service }` binding on the map's element the
1568
+ model points at. A partner the model never reaches still does not answer, which is the check.
1152
1569
 
1153
1570
  The rest of the machinery treats the axis as what it is — requirements. `loam archive` merges an
1154
1571
  `arch.spec.md` delta into the living one through the same code path as `spec.md` (same delta
@@ -1536,6 +1953,21 @@ turn a mistake into a green fleet. And a run that could not READ the flows (a si
1536
1953
  an `architecture/` that did not parse) suspends `capability.requirement-unrealized` entirely: loam
1537
1954
  did not look, which is never the same answer as "there is nothing there".
1538
1955
 
1956
+ **A `#req-` tag inside a service's own project is scoped by the SERVICE, not by a capability.** A
1957
+ `dynamic view` beside model.likec4 carrying `#req-<slug>` resolves against the `Requirement-ID`s of
1958
+ that service's spec.md and arch.spec.md together — flattened by the same rule, an id both declare
1959
+ resolving once, and `many` when the two documents declare ids that flatten alike, because a
1960
+ Requirement-ID is unique inside ONE document and a flow beside the model has two.
1961
+ `usecase.requirement-unresolved` there has four arms: no document at all, no identified requirement,
1962
+ none flattening to the slug (close ids offered), or two that do. No `#cap-` tag belongs there
1963
+ (`usecase.capability-unresolved` says so, vocabulary or not); `usecase.step-unlinked` never fires
1964
+ on a hop whose caller and provider resolve to the same service — a service owes no operationId to
1965
+ itself — and still fires on a hop from such a flow into another service's element (a stand-in the
1966
+ model declares for a sibling, with no `metadata { op }`), exactly as on the fleet map; and a
1967
+ resolved service-local claim is the join being checked and nothing more today: it
1968
+ enters no capability `keptBy`, counts toward no `Covers:` or `c4.uncovered`, appears in no pack,
1969
+ `loam list capabilities`, `loam diff` or `loam delta`, and no feature carries one yet.
1970
+
1539
1971
  **Both corpora are reported together, and neither is derived from the other.**
1540
1972
  `loam list capabilities --json` carries `keptBy` (the flows keeping a promise) beside `realizedBy`
1541
1973
  (the service requirements realizing it) on every `requirements[]` row, with the same three-state
@@ -1809,6 +2241,44 @@ unparseable delta. A living declaration sharing its line with another statement
1809
2241
  splice map cannot see around — its statement scan runs to the end of the line) refuses the same way:
1810
2242
  give each top-level declaration its own line, then re-run.
1811
2243
 
2244
+ **Which document an addition lands in.** The C4 merge asks it per declaration, not per feature. An
2245
+ element whose PARENT resolves to a service whose `model.likec4` extends the map is spliced into that
2246
+ model — and so is the interior a delta writes inside a service element the same archive introduces,
2247
+ since those containers carry no feature tag and would otherwise ride onto the map inside their
2248
+ parent's bytes. The parent chain has to be spelled in the map's own ids for either to happen. The
2249
+ splice goes inside the `extend <fqn> { … }` block for the parent — appended when the model has none, as
2250
+ the `model { }` block itself is appended to a model that has none (an empty file, a `views { }`-only
2251
+ file and a tags-only `specification { }` are all extending models, and all three are legal to
2252
+ archive into). A
2253
+ relationship goes to the model of the service owning its SOURCE when the source is interior to such
2254
+ a service, else to the model owning its TARGET, else to the map: a service-level edge stays where
2255
+ every fleet check reads it. A service with a STANDALONE model, or none yet, owns nothing — the map
2256
+ draws its containers, which is legal and is what every repository did before the extending shape.
2257
+ The merged model is proved the way the grader reads it (the `architecture/` documents with this
2258
+ archive's own merged landscape in place, plus the model) before anything is written, and it appears
2259
+ in the `--json` plan as an ordinary write, so `loam unarchive` restores it from the same
2260
+ `.loam-before` snapshot as any other overwritten file. Two limits are deliberate and each refuses at
2261
+ plan time with nothing written: a merged model that does not parse beside the map (`merge-failed`),
2262
+ and a container-level edge whose other end lives in a THIRD document — another service's interior
2263
+ resolves in neither this model nor the map, so that call is drawn service to service on the map. One
2264
+ more is silent: a delta that spells the parent under its own name rather than the map's (the legacy
2265
+ title-join) keeps landing on the map, because splicing its bytes into a document that shares the
2266
+ map's id namespace would carry ids nothing there resolves. It is the WHOLE chain that decides, not
2267
+ the outermost box — `marketplace.orderSvc.cache` under a map that spells the service
2268
+ `marketplace.orderService` is a title join and stays on the map, even though the group
2269
+ `marketplace` is the map's own. A block a delta writes for a NEW service is split — the box to the
2270
+ map, its containers to the model — unless the block also draws a relationship between those
2271
+ containers. Such an edge names its endpoints by their local names inside the block, so splitting it
2272
+ would leave the map naming children it no longer declares; the block rides whole instead, as it did
2273
+ before routing existed. The block decides once for everything written inside it: a container or an
2274
+ edge the delta tags with the feature follows its block rather than its own tag, so it is placed
2275
+ exactly once whichever way the block goes. Re-archiving is idempotent in the models
2276
+ too: an addition whose id, or whose title inside the same bound service, an extending model already
2277
+ declares is skipped exactly as one the map declares is. The title half is service-scoped in a model
2278
+ and global on the map — the map's global join is the fallback for documents written before
2279
+ `metadata { service }` existed, and a service's interior is where two boxes called `api` would
2280
+ collide.
2281
+
1812
2282
  The OpenAPI merge carries components along with operations, and since the path-item/component
1813
2283
  baselines it decides each one **by verdict rather than by reachability**: a component the feature
1814
2284
  declares with a pin equal to its own content is a quote and is skipped (the living document keeps
@@ -2335,21 +2805,27 @@ through the forge, not through loam features:
2335
2805
  tags and ordered steps, already materialized by the parse loam pays for) and never what a view
2336
2806
  **shows**. It renders nothing, and view *computation* is what is superlinear in edges. (If you
2337
2807
  want diagrams, add a `views` block and render it with LikeC4's own tooling — `npx likec4 start`
2338
- at the docs root, which renders the fleet map AND every adopted service's model, each as the
2339
- project its own `services/<svc>/likec4.config.json` declares (written by `loam subsystem sync`;
2340
- the Layout section has the rule), and `npx likec4 start features/<FEAT>` for a delta, which is
2808
+ at the docs root, which renders the fleet map AND every adopted service's model: an extending
2809
+ model is part of the root `fleet` project, beside the map, and a standalone one is the project its
2810
+ own `services/<svc>/likec4.config.json` declares (written by `loam subsystem sync`; the Layout
2811
+ section, "Two shapes of a service model", has the rule), and `npx likec4 start features/<FEAT>`
2812
+ for a delta, which is
2341
2813
  transient and gets no project — and scope the views, because an `include *` over a fleet-sized
2342
- landscape takes minutes. Two costs arrive with the first service project. Measured at the
2814
+ landscape takes minutes. Two costs arrive with the first STANDALONE service project, and only
2815
+ with it. Measured at the
2343
2816
  pinned likec4: with more than one project, `likec4 validate` at the docs root refuses without
2344
2817
  `--project <name>`, so a CI step that ran it bare changes (`likec4 build` and `likec4 export` take
2345
- every project and need no flag). And the renderer's project list grows by one per adopted service
2818
+ every project and need no flag). And the renderer's project list grows by one per such service
2346
2819
  beside `fleet` — LikeC4 projects are separate models, so a box on the fleet map is not a link into
2347
- that service's project. `loam init`
2820
+ that service's project. A fleet whose models all extend the map pays neither: one project, one
2821
+ picker entry, and the containers of every adopted service reachable from the map's own boxes.
2822
+ `loam init`
2348
2823
  scaffolds the landscape with no `views` block for that reason: loam should not plant a diagram it
2349
2824
  never draws **— and never asks for one: a fleet that draws nothing owes no views block.** It also
2350
- scaffolds a `likec4.config.json` that scopes the root LikeC4 project to `architecture/`: loam
2351
- parses every `.likec4` file **alone**, so each one declares its own `specification` block, and a
2352
- renderer given the whole tree at once merges them and reports every declaration as a duplicate.)
2825
+ scaffolds a `likec4.config.json` whose `exclude` is `["**/node_modules/**", "features/**"]`: a
2826
+ feature's `delta.likec4` re-declares the landscape's own identifiers and carries a `specification`
2827
+ block of its own, so it is read alone, and `services/` is deliberately NOT excluded — the models a
2828
+ fresh repository writes extend the map and the root project is the only place they parse.)
2353
2829
  `loam validate --all --json` over the same 120 services used to be the outlier at ~13–14 s — one
2354
2830
  fresh LikeC4 workspace per service; since the batched loader it parses the run's C4 documents in
2355
2831
  one shared workspace and answers in **~0.7 s median** on the committed 120-service benchmark
@@ -2455,7 +2931,28 @@ repairing anything:
2455
2931
 
2456
2932
  `loam-report` is a separate support protocol, generated as a command and skill in every profile
2457
2933
  but never appended to the lifecycle. It writes
2458
- `loam-reports/YYYY-MM-DD-<short-slug>.md` in the current repository with expected/actual behavior,
2934
+ `loam-reports/NNN-YYYY-MM-DD-<short-slug>.md` — `NNN` an ORDINAL of at least three digits,
2935
+ zero-padded, one more
2936
+ than the highest already in the directory (`001` when it is empty or absent, and widening past
2937
+ three digits rather than reusing a number once a corpus gets there), so a report has a
2938
+ short handle a commit message, an issue or the next report can cite. The document carries a
2939
+ `- Status: open` line beside its classification, whose vocabulary is `open` / `sent` (handed to
2940
+ the author, set by whoever forwards it) / `fixed in <version>` / `superseded by <NNN>`, and
2941
+ `loam doctor` reads the directory: how many reports it holds, how many stand at each status, and
2942
+ which ordinal comes next. `doctor --json` carries that as the `reports` object — `reports.dir`
2943
+ (the ABSOLUTE path of the directory, `loam-reports/` included, so an agent writes inside it rather
2944
+ than joining the name on a second time), resolved beside the `loam.json` that parsed, or beside
2945
+ the one loam FOUND when it did not parse, and only beside the run directory when there is no
2946
+ config file at or above it — `doctor` is the preflight that runs precisely when the config may not
2947
+ resolve, so it counts the corpus that is there rather than handing out an ordinal five existing
2948
+ reports already used — `reports.present`, `reports.total`, `reports.next`, and
2949
+ `reports.entries[]` with a `file`, an `ordinal` (`null` for a name written before the numbering)
2950
+ and a `status`, in ordinal order, numerically, with unnumbered names last (byte order stopped
2951
+ being report order at exactly the width the ordinal widens to: `1000-…` sorts before `998-…`). The status is read from the report's HEADER FIELD BLOCK alone — the lines above
2952
+ the first `##` heading, quoted code skipped in both markdown spellings (a fence, closed only by a
2953
+ run of the same character at least as long as the one that opened it, and an indented block,
2954
+ which is how the protocol prints its own template) — so a report quoting this protocol's own
2955
+ template still counts as whatever its own header says. The body carries expected/actual behavior,
2459
2956
  version, stable envelope and finding codes, relevant locations, write state, a minimal safe
2460
2957
  reproduction, classification and missing evidence. Before writing, the agent redacts secrets and
2461
2958
  home paths and reduces command output to the excerpt that proves the symptom. It never retries a
@@ -2564,20 +3061,44 @@ hand-merges the block. That resolution has a documented cost, not a surprise: a
2564
3061
  landscape moves the file away from the bytes the archive's `.loam-before/` snapshot recorded, so a
2565
3062
  later `loam unarchive` of that feature refuses with `snapshot-stale` and needs `--force` — the
2566
3063
  resolver is trading undo precision for the merge, and should know it. The trigger for revisiting is
2567
- written down so nobody relitigates it early: when landscape merge conflicts become routine — weekly,
2568
- not monthly — move per-service internals fully into `services/<svc>/model.likec4` and thin the
2569
- landscape down to top-level elements plus cross-service edges. Until then, one file keeps the fleet
2570
- map one diffable document.
3064
+ written down so nobody relitigates it early — and half of what it once asked for has already
3065
+ arrived. Per-service internals now live in `services/<svc>/model.likec4` **by default**, and that is
3066
+ where a second team adopting a service writes, so it adds nothing to this file. It is a default and
3067
+ not a prohibition: the map MAY hold a service's interior, and sometimes must. A store a second
3068
+ service will reach has to be declared where that consumer's project can see it — a per-service
3069
+ project is `architecture/` plus one model — so it is drawn ON THE MAP, nested inside the owner's
3070
+ element or beside the services, whichever the team prefers. Which of the two is nobody's business
3071
+ but the team's: "Private stores" above leaves the placement open and loam grades neither, so nothing
3072
+ here says a store must move. A delta spelling a parent in the legacy
3073
+ title-join lands its container on the map too. What an extending model owns is what it DECLARES,
3074
+ not everything below the service's element. `loam archive` routes rather than forbids — a delta's
3075
+ additions nested under a service whose model extends the map, with the whole parent chain spelled
3076
+ in the map's own ids, are spliced into that model's `extend <fqn> { … }` block instead of this
3077
+ file, and the merged model is proved to parse beside the merged map before anything is written. What is left of the
3078
+ trigger is the CROSS-SERVICE EDGE, which still exists twice — container-level in the model, service
3079
+ level here — because the spine, the platform census and every other fleet check read this file
3080
+ alone. So: when landscape merge conflicts become routine — weekly, not monthly — thin the map to
3081
+ elements plus cross-service edges and teach the fleet checks to read the edges out of the models
3082
+ that attest them. Until then, one file keeps the fleet map one diffable document, and
3083
+ `landscape.service-isolated` remains the nudge that carries an attested call up into it.
2571
3084
 
2572
3085
  ## Two flows
2573
3086
 
2574
3087
  - **Bootstrap (reverse):** `loam adopt --service <id>` emits a **brief**, not an extraction — the
2575
3088
  target paths (and which already exist, to be diffed and never overwritten), the grammar of each
2576
- artifact, what the living landscape already says about the service, the frontmatter to write, the
3089
+ artifact, what the living fleet map — the `architecture/` project, not the landscape file alone —
3090
+ already says about the service, the frontmatter to write, the
2577
3091
  checks `loam validate --service <id>` will then run, and the ones that do not exist. One target is
2578
3092
  not under `services/<id>/`: `architecture/landscape.likec4`, carried with `action: "edit"` and a
2579
- `landscape.instruction` naming the block this service owes the fleet map, and present only while
2580
- nothing in the map resolves to the service. An agent reads the code and writes draft
3093
+ `landscape.instruction` naming the write this service owes the fleet map — the element while
3094
+ nothing in the map resolves to the service, the EDGES while an element does and no edge touches it
3095
+ (`landscape.touched: false`, with `landscape.attested` listing the calls the service's own
3096
+ model.likec4 declares across its boundary); absent, and the instruction `null`, only once an
3097
+ element resolves and an edge touches it. `landscape.callersKnown` says whether loam could read WHO
3098
+ CALLS this service — true when the `architecture/` project parses, and true when it does not but
3099
+ `architecture/landscape.likec4` does. It is what an empty `landscape.expects` has to be read
3100
+ against: only a readable landscape makes "nothing calls this service" evidence, and it is the one
3101
+ fact `openapi.yaml`'s `required` is derived from. An agent reads the code and writes draft
2581
3102
  `model.likec4` + `spec.md` + `openapi.yaml` + `adrs/`, `runbook.md`, `health.yaml`, and makes that
2582
3103
  one addition to the shared map. A human promotes `draft` -> `verified` with `loam vouch`.
2583
3104
  - **Forward (generative):** author `features/<FEAT>/delta.likec4` -> `loam delta <FEAT>` projects it