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

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 (273) hide show
  1. package/CHANGELOG.md +901 -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 +264 -11
  6. package/SCHEMA.md +439 -38
  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 +5 -2
  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/plan.js +97 -3
  25. package/dist/commands/seed/plan.js.map +1 -1
  26. package/dist/commands/seed/seed.js +7 -2
  27. package/dist/commands/seed/seed.js.map +1 -1
  28. package/dist/commands/show/feature.js +2 -1
  29. package/dist/commands/show/feature.js.map +1 -1
  30. package/dist/commands/show/marks.js +3 -5
  31. package/dist/commands/show/marks.js.map +1 -1
  32. package/dist/commands/show/service.js +39 -18
  33. package/dist/commands/show/service.js.map +1 -1
  34. package/dist/commands/subsystem/sync.js +128 -74
  35. package/dist/commands/subsystem/sync.js.map +1 -1
  36. package/dist/commands/subsystem/txn/exclude.js +132 -0
  37. package/dist/commands/subsystem/txn/exclude.js.map +1 -0
  38. package/dist/commands/subsystem/txn/report.js +124 -0
  39. package/dist/commands/subsystem/txn/report.js.map +1 -0
  40. package/dist/commands/subsystem/txn/txn.js +10 -3
  41. package/dist/commands/subsystem/txn/txn.js.map +1 -1
  42. package/dist/commands/subsystem/txn/views.js +113 -16
  43. package/dist/commands/subsystem/txn/views.js.map +1 -1
  44. package/dist/commands/validate/arch-coverage.js +16 -9
  45. package/dist/commands/validate/arch-coverage.js.map +1 -1
  46. package/dist/commands/validate/checks/fleet-shape.js +30 -77
  47. package/dist/commands/validate/checks/fleet-shape.js.map +1 -1
  48. package/dist/commands/validate/checks/vocabulary.js +4 -19
  49. package/dist/commands/validate/checks/vocabulary.js.map +1 -1
  50. package/dist/commands/validate/feature.js +1 -1
  51. package/dist/commands/validate/feature.js.map +1 -1
  52. package/dist/commands/validate/fleet/census.js +11 -10
  53. package/dist/commands/validate/fleet/census.js.map +1 -1
  54. package/dist/commands/validate/fleet/landscape.js +127 -119
  55. package/dist/commands/validate/fleet/landscape.js.map +1 -1
  56. package/dist/commands/validate/fleet/load.js +66 -0
  57. package/dist/commands/validate/fleet/load.js.map +1 -1
  58. package/dist/commands/validate/fleet/map/attest.js +103 -0
  59. package/dist/commands/validate/fleet/map/attest.js.map +1 -0
  60. package/dist/commands/validate/fleet/map/bindings.js +96 -0
  61. package/dist/commands/validate/fleet/map/bindings.js.map +1 -0
  62. package/dist/commands/validate/fleet/map/consumers.js +298 -0
  63. package/dist/commands/validate/fleet/map/consumers.js.map +1 -0
  64. package/dist/commands/validate/fleet/map/isolation.js +116 -0
  65. package/dist/commands/validate/fleet/map/isolation.js.map +1 -0
  66. package/dist/commands/validate/fleet/map/usecases.js +56 -0
  67. package/dist/commands/validate/fleet/map/usecases.js.map +1 -0
  68. package/dist/commands/validate/fleet/usecases/steps.js +49 -17
  69. package/dist/commands/validate/fleet/usecases/steps.js.map +1 -1
  70. package/dist/commands/validate/fleet/views/fleet-project.js +217 -0
  71. package/dist/commands/validate/fleet/views/fleet-project.js.map +1 -0
  72. package/dist/commands/validate/fleet/views/ids.js +13 -9
  73. package/dist/commands/validate/fleet/views/ids.js.map +1 -1
  74. package/dist/commands/validate/fleet/views/projects.js +258 -70
  75. package/dist/commands/validate/fleet/views/projects.js.map +1 -1
  76. package/dist/commands/validate/fleet/views/root-documents.js +97 -0
  77. package/dist/commands/validate/fleet/views/root-documents.js.map +1 -0
  78. package/dist/commands/validate/fleet/views/stale.js +2 -2
  79. package/dist/commands/validate/fleet/views/stale.js.map +1 -1
  80. package/dist/commands/validate/service/model/diverged.js +226 -0
  81. package/dist/commands/validate/service/model/diverged.js.map +1 -0
  82. package/dist/commands/validate/service/model/grade.js +163 -0
  83. package/dist/commands/validate/service/model/grade.js.map +1 -0
  84. package/dist/commands/validate/service/service.js +110 -34
  85. package/dist/commands/validate/service/service.js.map +1 -1
  86. package/dist/commands/validate/service/specs.js +39 -5
  87. package/dist/commands/validate/service/specs.js.map +1 -1
  88. package/dist/commands/validate/service/spine.js +41 -3
  89. package/dist/commands/validate/service/spine.js.map +1 -1
  90. package/dist/commands/validate/service/usecases/tags.js +113 -0
  91. package/dist/commands/validate/service/usecases/tags.js.map +1 -0
  92. package/dist/commands/validate/service/usecases/usecases.js +209 -0
  93. package/dist/commands/validate/service/usecases/usecases.js.map +1 -0
  94. package/dist/commands/validate/validate.js +10 -24
  95. package/dist/commands/validate/validate.js.map +1 -1
  96. package/dist/core/agent/agents-md/artifacts.js +1 -0
  97. package/dist/core/agent/agents-md/artifacts.js.map +1 -1
  98. package/dist/core/agent/agents-md/command-map.js +18 -4
  99. package/dist/core/agent/agents-md/command-map.js.map +1 -1
  100. package/dist/core/agent/agents-md/cycle.js +24 -9
  101. package/dist/core/agent/agents-md/cycle.js.map +1 -1
  102. package/dist/core/agent/agents-md/map/lenses/context.js +4 -3
  103. package/dist/core/agent/agents-md/map/lenses/context.js.map +1 -1
  104. package/dist/core/agent/agents-md/map/subsystem.js +51 -11
  105. package/dist/core/agent/agents-md/map/subsystem.js.map +1 -1
  106. package/dist/core/agent/agents-md/map/usecases.js +45 -3
  107. package/dist/core/agent/agents-md/map/usecases.js.map +1 -1
  108. package/dist/core/agent/agents-md.js +14 -4
  109. package/dist/core/agent/agents-md.js.map +1 -1
  110. package/dist/core/agent/support/report.js +39 -5
  111. package/dist/core/agent/support/report.js.map +1 -1
  112. package/dist/core/agent/workflows/adopt.js +123 -14
  113. package/dist/core/agent/workflows/adopt.js.map +1 -1
  114. package/dist/core/agent/workflows/check.js +35 -20
  115. package/dist/core/agent/workflows/check.js.map +1 -1
  116. package/dist/core/agent/workflows/closing.js +3 -1
  117. package/dist/core/agent/workflows/closing.js.map +1 -1
  118. package/dist/core/brief/brief.js +63 -11
  119. package/dist/core/brief/brief.js.map +1 -1
  120. package/dist/core/brief/checks.js +133 -7
  121. package/dist/core/brief/checks.js.map +1 -1
  122. package/dist/core/brief/landscape.js +212 -129
  123. package/dist/core/brief/landscape.js.map +1 -1
  124. package/dist/core/brief/map/extend.js +81 -0
  125. package/dist/core/brief/map/extend.js.map +1 -0
  126. package/dist/core/brief/map/owed.js +284 -0
  127. package/dist/core/brief/map/owed.js.map +1 -0
  128. package/dist/core/brief/targets.js +30 -29
  129. package/dist/core/brief/targets.js.map +1 -1
  130. package/dist/core/brief/unchecked.js +12 -2
  131. package/dist/core/brief/unchecked.js.map +1 -1
  132. package/dist/core/c4/likec4.js +23 -1
  133. package/dist/core/c4/likec4.js.map +1 -1
  134. package/dist/core/c4/parsed/deployment.js +5 -3
  135. package/dist/core/c4/parsed/deployment.js.map +1 -1
  136. package/dist/core/c4/parsed/view-ids.js +39 -1
  137. package/dist/core/c4/parsed/view-ids.js.map +1 -1
  138. package/dist/core/c4/prefetch/fleet.js +142 -0
  139. package/dist/core/c4/prefetch/fleet.js.map +1 -0
  140. package/dist/core/c4/project/architecture.js +52 -4
  141. package/dist/core/c4/project/architecture.js.map +1 -1
  142. package/dist/core/c4/project/batch.js +140 -0
  143. package/dist/core/c4/project/batch.js.map +1 -0
  144. package/dist/core/c4/project/documents.js +9 -4
  145. package/dist/core/c4/project/documents.js.map +1 -1
  146. package/dist/core/c4/project/load.js +77 -38
  147. package/dist/core/c4/project/load.js.map +1 -1
  148. package/dist/core/c4/project/staged.js +46 -12
  149. package/dist/core/c4/project/staged.js.map +1 -1
  150. package/dist/core/c4/resolve/attested.js +0 -0
  151. package/dist/core/c4/resolve/attested.js.map +1 -0
  152. package/dist/core/c4/resolve/service.js +8 -17
  153. package/dist/core/c4/resolve/service.js.map +1 -1
  154. package/dist/core/c4/resolve/steps.js +18 -0
  155. package/dist/core/c4/resolve/steps.js.map +1 -1
  156. package/dist/core/c4/root-project/exclude.js +352 -0
  157. package/dist/core/c4/root-project/exclude.js.map +1 -0
  158. package/dist/core/c4/service-model/documents.js +47 -0
  159. package/dist/core/c4/service-model/documents.js.map +1 -0
  160. package/dist/core/c4/service-model/fleet/extending.js +82 -0
  161. package/dist/core/c4/service-model/fleet/extending.js.map +1 -0
  162. package/dist/core/c4/service-model/load.js +165 -0
  163. package/dist/core/c4/service-model/load.js.map +1 -0
  164. package/dist/core/c4/service-model/renderer.js +128 -0
  165. package/dist/core/c4/service-model/renderer.js.map +1 -0
  166. package/dist/core/c4/service-model/shape.js +126 -0
  167. package/dist/core/c4/service-model/shape.js.map +1 -0
  168. package/dist/core/c4/service-model/slice.js +188 -0
  169. package/dist/core/c4/service-model/slice.js.map +1 -0
  170. package/dist/core/c4/source-scan.js +26 -0
  171. package/dist/core/c4/source-scan.js.map +1 -1
  172. package/dist/core/c4/splice/authored-source.js +60 -13
  173. package/dist/core/c4/splice/authored-source.js.map +1 -1
  174. package/dist/core/c4/splice/contract.js.map +1 -1
  175. package/dist/core/c4/splice/identity/declared.js +87 -0
  176. package/dist/core/c4/splice/identity/declared.js.map +1 -0
  177. package/dist/core/c4/splice/identity/edges.js +17 -0
  178. package/dist/core/c4/splice/identity/edges.js.map +1 -1
  179. package/dist/core/c4/splice/identity/existing.js +80 -0
  180. package/dist/core/c4/splice/identity/existing.js.map +1 -0
  181. package/dist/core/c4/splice/identity/interior.js +80 -0
  182. package/dist/core/c4/splice/identity/interior.js.map +1 -0
  183. package/dist/core/c4/splice/identity/owner.js +50 -0
  184. package/dist/core/c4/splice/identity/owner.js.map +1 -0
  185. package/dist/core/c4/splice/landscape-merge.js +154 -100
  186. package/dist/core/c4/splice/landscape-merge.js.map +1 -1
  187. package/dist/core/c4/splice/model/merge.js +140 -0
  188. package/dist/core/c4/splice/model/merge.js.map +1 -0
  189. package/dist/core/c4/splice/placement.js +14 -4
  190. package/dist/core/c4/splice/placement.js.map +1 -1
  191. package/dist/core/c4/workspace.js +2 -1
  192. package/dist/core/c4/workspace.js.map +1 -1
  193. package/dist/core/coherence/coherence.js +7 -0
  194. package/dist/core/coherence/coherence.js.map +1 -1
  195. package/dist/core/delta/delta.js +9 -0
  196. package/dist/core/delta/delta.js.map +1 -1
  197. package/dist/core/deployment/delta.js +4 -0
  198. package/dist/core/deployment/delta.js.map +1 -1
  199. package/dist/core/diff/base-state.js +21 -5
  200. package/dist/core/diff/base-state.js.map +1 -1
  201. package/dist/core/diff/victims.js +10 -1
  202. package/dist/core/diff/victims.js.map +1 -1
  203. package/dist/core/docs.js +29 -24
  204. package/dist/core/docs.js.map +1 -1
  205. package/dist/core/doctor/config.js +7 -3
  206. package/dist/core/doctor/config.js.map +1 -1
  207. package/dist/core/doctor/doctor.js +36 -8
  208. package/dist/core/doctor/doctor.js.map +1 -1
  209. package/dist/core/doctor/report.js.map +1 -1
  210. package/dist/core/doctor/reports/scan.js +242 -0
  211. package/dist/core/doctor/reports/scan.js.map +1 -0
  212. package/dist/core/explain/refusals.js +1 -1
  213. package/dist/core/explain/refusals.js.map +1 -1
  214. package/dist/core/explore/explore.js +44 -9
  215. package/dist/core/explore/explore.js.map +1 -1
  216. package/dist/core/fleet-context.js +47 -0
  217. package/dist/core/fleet-context.js.map +1 -1
  218. package/dist/core/gate/partners.js +5 -9
  219. package/dist/core/gate/partners.js.map +1 -1
  220. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  221. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  222. package/dist/core/pack/living.js +47 -13
  223. package/dist/core/pack/living.js.map +1 -1
  224. package/dist/core/pack/pack.js +9 -6
  225. package/dist/core/pack/pack.js.map +1 -1
  226. package/dist/core/projection/arch-slice.js +2 -2
  227. package/dist/core/projection/arch-slice.js.map +1 -1
  228. package/dist/core/repo/paths.js +11 -9
  229. package/dist/core/repo/paths.js.map +1 -1
  230. package/dist/core/repo/tree/render/projects.js +93 -35
  231. package/dist/core/repo/tree/render/projects.js.map +1 -1
  232. package/dist/core/repo/tree/render/stale.js +2 -3
  233. package/dist/core/repo/tree/render/stale.js.map +1 -1
  234. package/dist/core/repo/tree/render/views.js +51 -3
  235. package/dist/core/repo/tree/render/views.js.map +1 -1
  236. package/dist/core/repo/tree/walk.js +45 -2
  237. package/dist/core/repo/tree/walk.js.map +1 -1
  238. package/dist/core/scaffold/landscape.js +219 -9
  239. package/dist/core/scaffold/landscape.js.map +1 -1
  240. package/dist/core/staging/writes.js.map +1 -1
  241. package/dist/core/usecases/delta/claims.js +1 -0
  242. package/dist/core/usecases/delta/claims.js.map +1 -1
  243. package/dist/core/usecases/delta/overlay.js +4 -0
  244. package/dist/core/usecases/delta/overlay.js.map +1 -1
  245. package/dist/core/usecases/fleet.js +18 -6
  246. package/dist/core/usecases/fleet.js.map +1 -1
  247. package/dist/core/usecases/operations.js +7 -0
  248. package/dist/core/usecases/operations.js.map +1 -1
  249. package/dist/core/usecases/service/exclude.js +119 -0
  250. package/dist/core/usecases/service/exclude.js.map +1 -0
  251. package/dist/core/usecases/service/flows.js +161 -0
  252. package/dist/core/usecases/service/flows.js.map +1 -0
  253. package/dist/core/vocabulary/maturity.js +10 -0
  254. package/dist/core/vocabulary/maturity.js.map +1 -1
  255. package/examples/README.md +24 -6
  256. package/examples/docs/architecture/landscape.likec4 +41 -0
  257. package/examples/docs/architecture/usecases/order-notification.likec4 +5 -2
  258. package/examples/docs/likec4.config.json +0 -1
  259. package/examples/docs/services/checkout-web/model.likec4 +39 -56
  260. package/examples/docs/services/order-service/arch.spec.md +13 -6
  261. package/examples/docs/services/order-service/model.likec4 +53 -67
  262. package/examples/docs/services/payment-service/arch.spec.md +7 -4
  263. package/examples/docs/services/payment-service/model.likec4 +32 -35
  264. package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
  265. package/examples/docs/services/platform/identity-service/model.likec4 +29 -33
  266. package/examples/docs/services/platform/notification-service/health.yaml +8 -3
  267. package/examples/docs/services/platform/notification-service/model.likec4 +39 -59
  268. package/package.json +1 -1
  269. package/examples/docs/services/checkout-web/likec4.config.json +0 -4
  270. package/examples/docs/services/order-service/likec4.config.json +0 -4
  271. package/examples/docs/services/payment-service/likec4.config.json +0 -4
  272. package/examples/docs/services/platform/identity-service/likec4.config.json +0 -4
  273. package/examples/docs/services/platform/notification-service/likec4.config.json +0 -4
@@ -5,7 +5,7 @@
5
5
  * one a later check depends on, and test/agent-contract.test.ts pins the
6
6
  * mapping. This module is the list alone — `serviceBrief` (brief.ts) resolves
7
7
  * it against a docs repo, and the fleet map's own target, whose block names
8
- * the service, is assembled per service in landscape.ts.
8
+ * the service, is assembled per service in map/owed.ts.
9
9
  */
10
10
  /** The artifacts, in the order a baseline is best written in. */
11
11
  export const ARTIFACTS = [
@@ -21,41 +21,41 @@ export const ARTIFACTS = [
21
21
  // `loam validate` passing means more than it does. They now live in
22
22
  // UNCHECKED, where their status is the point.
23
23
  shape: [
24
- "A `specification { ... }` block declaring every element kind you use (`element softwareSystem`, `element container`). LikeC4 rejects an undeclared kind, and `loam validate --service <id>` reports it as `c4.invalid` with line numbers.",
25
- "A `model { ... }` block holding the service's element, with containers and components nested inside it. The whole file has to parse — `loam validate` runs LikeC4 in-process and reports `c4.invalid` with line numbers.",
26
- "The service element binds to this directory: `metadata { service '<id>' }`. Without a binding the element's TITLE has to equal the directory name — and then renaming the box silently unlinks every check that joined the two. The same binding is what makes the FLEET map resolve an element to `services/<id>/`; until one does, `loam validate --all` reports `landscape.service-unmodelled`.",
27
- "A call to another service is an edge that names the operation it uses: `a -> b 'Calls createSplit' { metadata { op 'createSplit' } }`. Cross-service edges belong in `architecture/landscape.likec4` (a target of its own, below) — this file cannot even resolve the other service's element — and there the `op` must be an operationId the TARGET's openapi.yaml defines, or `spine.op-undefined` fires.",
24
+ "No `specification` block of its own: the kinds and tags come from `architecture/landscape.likec4` (a kind this model needs that the map does not declare — `element database`, `element queue` — is added there, once). A model that declares its own element kinds is the standalone shape: it still parses, alone, and loam then reports where its copies of the map's elements diverge (`c4.declaration-diverged`). New models never use it.",
25
+ "A `model { ... }` block whose body is `extend <id> { ... }` — the fully-qualified id of the element the fleet map already binds to this directory (the landscape section of this brief names it; write the element into the map first when none resolves) — with containers and components nested inside. The binding and the description live on the map's element; this file may add `#tags` and metadata to it and never a description. The whole file has to parse: `loam validate` runs LikeC4 in-process over the map plus this file and reports `c4.invalid` with line numbers.",
26
+ "A store or component this service OWNS goes INSIDE the `extend <fqn> { }` block, where its id becomes `<fqn>.<name>`: that is the only place an extending model may declare an element, and one declared anywhere else is `c4.element-unowned` (warn) — the fleet project then carries this service's private copy of somebody else's box. A call to another system is an edge from the container that makes it to the other party's element as the map spells it — `marketplace.paymentService.api -> stripe 'Authorizes cards'` — never a copy of that element: the map declares a system this service merely REACHES once, and another service's internals belong in that service's own model. The map still carries the service-level edge the fleet checks read (`metadata { op '<operationId>' }` graded by `spine.op-undefined`); draw it there too.",
27
+ "The map's element is the binding: `metadata { service '<id>' }` on the element you extend (the landscape target below). Without it the element's title has to equal the directory name — and then renaming the box silently unlinks every check that joined the two. Until something in the map resolves to `services/<id>/`, `loam validate --all` reports `landscape.service-unmodelled` and there is no element for this file to extend.",
28
28
  ],
29
- example: `specification {
30
- element softwareSystem
31
- element container
32
- }
33
-
29
+ // `<fqn>` and `<svc>` are PLACEHOLDERS this module leaves literal:
30
+ // `serviceBrief` (brief.ts) substitutes the landscape element's real id and
31
+ // the service being adopted, and for `<fqn>` the "the element's id in
32
+ // architecture/landscape.likec4" wording where the map holds none. ARTIFACTS
33
+ // stays a static constant — a target list that varied per repository would
34
+ // be a second thing to keep true. The header carried the literal
35
+ // `payment-service` for every service, beside an `extend` line that WAS
36
+ // substituted, so the half of the example that was personalised taught an
37
+ // agent the other half had been too.
38
+ example: `// <svc>'s own C4 — what is INSIDE the element the fleet map binds to
39
+ // this directory. The map declares the kinds and the element; this
40
+ // file extends it and re-declares nothing.
34
41
  model {
35
- paymentService = softwareSystem 'payment-service' {
36
- description 'Owns payment authorization and capture'
37
- metadata { service 'payment-service' }
38
-
42
+ extend <fqn> {
39
43
  api = container 'HTTP API'
40
- ledger = container 'Ledger store'
44
+ ledger = database 'Ledger store'
45
+ api -> ledger 'Writes every authorization'
41
46
  }
47
+ // 'stripe' stands for the OTHER party, spelled as the fleet map already
48
+ // declares it — loam has no id to substitute here, and inventing one is
49
+ // the edge that makes the model unresolvable. The containers above are
50
+ // this example's own: name yours after what they are.
51
+ <fqn>.api -> stripe 'Authorizes cards'
42
52
  }
43
53
 
44
- // Views are LikeC4's, not loam's: loam parses this file and computes no view,
45
- // and it never reads a STATIC view's contents in any document, so nothing below
46
- // is read by any check (docs/DESIGN.md rule 26 — the one thing loam does read is
47
- // a dynamic view's declared steps, and only in the fleet landscape).
48
- // Keep it for the renderer, which does draw it — 'npx likec4 start' from the docs
49
- // repo ROOT, once 'loam subsystem sync' has written services/<id>/likec4.config.json:
50
- // that file is loam's, never yours, and it is what registers this model as a
51
- // LikeC4 project of its own beside the fleet map. The root project stays scoped
52
- // to architecture/ (the root likec4.config.json) because every model declares
53
- // its own 'specification' block, and a root that merged them would read each
54
- // declaration as a duplicate.
55
- // Scoped to one service 'include *' is cheap; the same line in
56
- // architecture/landscape.likec4 walks every call in the fleet and takes minutes.
54
+ // Views are LikeC4's, not loam's (docs/DESIGN.md rule 26). Render with
55
+ // 'npx likec4 start' from the docs repo root: this file is part of the root
56
+ // project, beside the map — nothing else to write.
57
57
  views {
58
- view of paymentService {
58
+ view of <fqn> {
59
59
  include *
60
60
  }
61
61
  }
@@ -127,6 +127,7 @@ Operations: refundPayment
127
127
  shape: [
128
128
  "Same grammar and frontmatter as spec.md: `## Requirements`, `### Requirement:` + `#### Scenario:` with Given/When/Then. The same checks read it.",
129
129
  "A `Covers:` line per requirement names what its scenarios exercise: a C4 element id (`paymentService.db`), an edge (`paymentService -> kafka`), or a health signal from health.yaml (`alert:<id>` / `sli:<id>`). Every entry must resolve, or `covers.unknown` (warn) flags the typo.",
130
+ "A requirement whose `Covers:` names containers may have its hop sequence drawn as a `dynamic view` in a `.likec4` beside model.likec4 (`usecases/<name>.likec4` by convention; the renderer's per-service project reads every `.likec4` under the directory, and loam reads the same set), tagged `#req-<Requirement-ID>` — with the tag declared in `architecture/landscape.likec4`'s `specification` where the model extends the map, or in model.likec4's own block where it declares one; declaring it in BOTH is a duplicate, and the code lands where the second declaration's FILE is, not at fleet altitude: `c4.invalid` (an ERROR, and the model's grading is suspended behind it) when the tags-only block is in model.likec4, `usecase.flow-invalid` when it is in a sibling. `c4.fleet-project-invalid` is the fleet-scope class one step out — TWO SERVICES each declaring the same tag locally, which no per-service project sees. Graded by `loam validate --service <id>` (`usecase.step-unbacked` when a hop names a call the model does not declare, `usecase.requirement-unresolved` when the tag names no `Requirement-ID` here or in spec.md). Optional; nothing grades its absence. Never `architecture/usecases/`, which cannot resolve a container.",
130
131
  "Optional, but expected for a service with real architecture: every alert/SLI health.yaml declares wants a covering requirement here (`health.uncovered`, warn) — a signal nothing tests is dashboard decoration.",
131
132
  "Write the obligations no business requirement states — the transactional outbox, what a caller's retry may assume, what pages whom. An arch scenario becomes an integration/ops test, not an acceptance test.",
132
133
  ],
@@ -1 +1 @@
1
- {"version":3,"file":"targets.js","sourceRoot":"","sources":["../../../src/core/brief/targets.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAiDH,iEAAiE;AACjE,MAAM,CAAC,MAAM,SAAS,GAA8E;IAClG;QACE,QAAQ,EAAE,cAAc;QACxB,GAAG,EAAE,OAAO;QACZ,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,oEAAoE;QAC7E,uEAAuE;QACvE,2EAA2E;QAC3E,4EAA4E;QAC5E,qEAAqE;QACrE,oEAAoE;QACpE,8CAA8C;QAC9C,KAAK,EAAE;YACL,2OAA2O;YAC3O,0NAA0N;YAC1N,oYAAoY;YACpY,6YAA6Y;SAC9Y;QACD,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCZ;KACE;IACD;QACE,QAAQ,EAAE,SAAS;QACnB,GAAG,EAAE,MAAM;QACX,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,mFAAmF;QAC5F,KAAK,EAAE;YACL,wDAAwD;YACxD,kQAAkQ;YAClQ,wMAAwM;YACxM,wtBAAwtB;YACxtB,o0BAAo0B;YACp0B,wjBAAwjB;YACxjB,4fAA4f;YAC5f,slBAAslB;YACtlB,6RAA6R;YAC7R,6HAA6H;SAC9H;QACD,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCZ;KACE;IACD;QACE,QAAQ,EAAE,cAAc;QACxB,GAAG,EAAE,UAAU;QACf,QAAQ,EAAE,KAAK;QACf,OAAO,EACL,uIAAuI;QACzI,KAAK,EAAE;YACL,kJAAkJ;YAClJ,uRAAuR;YACvR,kNAAkN;YAClN,+MAA+M;SAChN;QACD,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;CAsBZ;KACE;IACD;QACE,QAAQ,EAAE,cAAc;QACxB,GAAG,EAAE,SAAS;QACd,0EAA0E;QAC1E,6DAA6D;QAC7D,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,wDAAwD;QACjE,KAAK,EAAE;YACL,iCAAiC;YACjC,+NAA+N;YAC/N,yXAAyX;YACzX,kKAAkK;YAClK,wEAAwE;YACxE,uEAAuE;YACvE,0EAA0E;YAC1E,wEAAwE;YACxE,uEAAuE;YACvE,kCAAkC;YAClC,kXAAkX;SACnX;KACF;IACD;QACE,QAAQ,EAAE,eAAe;QACzB,GAAG,EAAE,UAAU;QACf,0EAA0E;QAC1E,6EAA6E;QAC7E,uEAAuE;QACvE,kDAAkD;QAClD,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,8EAA8E;QACvF,KAAK,EAAE;YACL,8SAA8S;YAC9S,qRAAqR;YACrR,wPAAwP;YACxP,4bAA4b;YAC5b,gQAAgQ;YAChQ,+FAA+F;SAChG;KACF;IACD;QACE,QAAQ,EAAE,OAAO;QACjB,GAAG,EAAE,SAAS;QACd,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,+CAA+C;QACxD,KAAK,EAAE;YACL,+FAA+F;YAC/F,2MAA2M;YAC3M,oJAAoJ;SACrJ;KACF;IACD;QACE,QAAQ,EAAE,YAAY;QACtB,GAAG,EAAE,SAAS;QACd,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,iEAAiE;QAC1E,KAAK,EAAE;YACL,kIAAkI;YAClI,2JAA2J;YAC3J,4PAA4P;SAC7P;KACF;IACD;QACE,QAAQ,EAAE,aAAa;QACvB,GAAG,EAAE,QAAQ;QACb,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,2DAA2D;QACpE,KAAK,EAAE;YACL,uFAAuF;YACvF,uHAAuH;SACxH;KACF;CACF,CAAC","sourcesContent":["/**\n * The artifacts of the adoption brief: what each file under `services/<svc>/`\n * is for, the grammar it has to be written in, and a minimal valid example\n * where showing one is shorter than describing it. Every `shape` rule here is\n * one a later check depends on, and test/agent-contract.test.ts pins the\n * mapping. This module is the list alone — `serviceBrief` (brief.ts) resolves\n * it against a docs repo, and the fleet map's own target, whose block names\n * the service, is assembled per service in landscape.ts.\n */\n\n/* ------------------------------------------------------------------ */\n/* The artifacts */\n/* ------------------------------------------------------------------ */\n\nexport interface BriefTarget {\n /** File name as it appears under `services/<svc>/`. */\n artifact: string;\n /** Repo-relative path to write. */\n path: string;\n exists: boolean;\n /**\n * `create` a missing artifact; `diff` an existing one — never overwrite it.\n *\n * `edit` is the third case and belongs to exactly one target: the fleet map.\n * `architecture/landscape.likec4` is a SHARED document that already holds\n * every other service, so \"diff it and report what disagrees\" is the wrong\n * instruction — the file is not this service's baseline, it is the fleet's,\n * and what this service owes it is an addition inside the existing\n * `model { … }` block. Spelling that as `diff` taught agents to leave the\n * fleet map alone; spelling it as `create` would have taught them to\n * overwrite ten other services.\n */\n action: \"create\" | \"diff\" | \"edit\";\n /**\n * False for artifacts a baseline can legitimately ship without. True means\n * required for a COMPLETE baseline — validate grades a missing spec.md or\n * openapi.yaml as a warn (`service.no-spec` / `service.no-openapi`), not an\n * error: partial adoption is a supported state, and the warns are the\n * progress meter.\n *\n * Constant per artifact except one: `openapi.yaml` is required only where the\n * fleet map shows an API is expected (see `apiExpected` in brief.ts), because\n * the brief is read as an instruction and \"MISSING openapi.yaml\" told an agent\n * to write a contract for a service that serves no requests.\n */\n required: boolean;\n /** What the artifact is for, in one line. */\n purpose: string;\n /** The grammar. Every rule here is one a later check depends on. */\n shape: string[];\n /** A minimal valid instance, where showing one is shorter than describing it. */\n example?: string;\n}\n\n/** Where an artifact lives — `repo/paths.ts` spells the filenames, this only points at them. */\ntype PathKey = \"model\" | \"spec\" | \"archSpec\" | \"openapi\" | \"asyncapi\" | \"adrsDir\" | \"runbook\" | \"health\";\n\n/** The artifacts, in the order a baseline is best written in. */\nexport const ARTIFACTS: Array<Omit<BriefTarget, \"path\" | \"exists\" | \"action\"> & { key: PathKey }> = [\n {\n artifact: \"model.likec4\",\n key: \"model\",\n required: true,\n purpose: \"the service's C4 — what it is, what is inside it, what it talks to\",\n // Every rule below is one a later check depends on, and the mapping is\n // pinned in test/agent-contract.test.ts. Rules nobody checks — the `views`\n // block, \"exactly one top-level element\" — used to sit here reading exactly\n // like the enforced ones, which is how a brief teaches an agent that\n // `loam validate` passing means more than it does. They now live in\n // UNCHECKED, where their status is the point.\n shape: [\n \"A `specification { ... }` block declaring every element kind you use (`element softwareSystem`, `element container`). LikeC4 rejects an undeclared kind, and `loam validate --service <id>` reports it as `c4.invalid` with line numbers.\",\n \"A `model { ... }` block holding the service's element, with containers and components nested inside it. The whole file has to parse — `loam validate` runs LikeC4 in-process and reports `c4.invalid` with line numbers.\",\n \"The service element binds to this directory: `metadata { service '<id>' }`. Without a binding the element's TITLE has to equal the directory name — and then renaming the box silently unlinks every check that joined the two. The same binding is what makes the FLEET map resolve an element to `services/<id>/`; until one does, `loam validate --all` reports `landscape.service-unmodelled`.\",\n \"A call to another service is an edge that names the operation it uses: `a -> b 'Calls createSplit' { metadata { op 'createSplit' } }`. Cross-service edges belong in `architecture/landscape.likec4` (a target of its own, below) — this file cannot even resolve the other service's element — and there the `op` must be an operationId the TARGET's openapi.yaml defines, or `spine.op-undefined` fires.\",\n ],\n example: `specification {\n element softwareSystem\n element container\n}\n\nmodel {\n paymentService = softwareSystem 'payment-service' {\n description 'Owns payment authorization and capture'\n metadata { service 'payment-service' }\n\n api = container 'HTTP API'\n ledger = container 'Ledger store'\n }\n}\n\n// Views are LikeC4's, not loam's: loam parses this file and computes no view,\n// and it never reads a STATIC view's contents in any document, so nothing below\n// is read by any check (docs/DESIGN.md rule 26 — the one thing loam does read is\n// a dynamic view's declared steps, and only in the fleet landscape).\n// Keep it for the renderer, which does draw it — 'npx likec4 start' from the docs\n// repo ROOT, once 'loam subsystem sync' has written services/<id>/likec4.config.json:\n// that file is loam's, never yours, and it is what registers this model as a\n// LikeC4 project of its own beside the fleet map. The root project stays scoped\n// to architecture/ (the root likec4.config.json) because every model declares\n// its own 'specification' block, and a root that merged them would read each\n// declaration as a duplicate.\n// Scoped to one service 'include *' is cheap; the same line in\n// architecture/landscape.likec4 walks every call in the fleet and takes minutes.\nviews {\n view of paymentService {\n include *\n }\n}\n`,\n },\n {\n artifact: \"spec.md\",\n key: \"spec\",\n required: true,\n purpose: \"the living requirements — what this service is required to do, as it stands today\",\n shape: [\n \"Frontmatter (below), then a `## Requirements` heading.\",\n \"`### Requirement: <name>` — ONE observable behaviour, written with SHALL, testable without reading the code. Add `Requirement-ID: <id>` in its body for stable identity across heading renames; legacy requirements without one continue to match by exact name.\",\n \"At least one `#### Scenario: <name>` per requirement, with Given/When/Then bullets. This is gated (`requirements.missing-scenarios`) — scenarios are the acceptance criteria and the source for tests.\",\n \"A markdown TABLE in a scenario body becomes the `Examples` of a `Scenario Outline`: the header names the placeholders the steps use (`<permission>`), one row per case. This is how a matrix is written — a permission matrix, a validation matrix, a status-code table is ONE scenario with twenty rows, never twenty scenarios. `loam gherkin` emits the outline, cucumber reports one result per row carrying that scenario's digest tag, and `loam verify --results` confirms the claim only when EVERY row passed. Every row must have the header's column count and a header needs at least one row: a table loam cannot read stays in the description, the scenario then runs once instead of once per case, and the emission says so out loud.\",\n \"A step takes an ARGUMENT by carrying it INDENTED under the bullet, and this is what lets one scenario assert a whole data pass rather than a sentence. An indented table becomes that step's Gherkin DATA TABLE — `- **Then** the outbox holds:` followed by the rows it must hold; a fenced block becomes its DOCSTRING, indentation intact — `- **When** the caller posts:` followed by the JSON body. Indentation is the entire disambiguation: indented under a step means this step's rows, at the margin means the scenario's cases, and both ride into the scenario digest. Write the ASSERTIONS OUT, one bullet each, including the negative ones (`no row is created in ...`, `... is not invoked`) and each meter separately: a scenario that summarises them reads complete, emits valid Gherkin, grades green, and tests a fraction of what it claims.\",\n \"`Requires: <subject>/<permission>[, ...]` in the requirement body names the authorization it is gated on, resolved against the FLEET vocabulary in `architecture/permissions.yaml`. An entry that is not declared there is `permissions.unknown` (error) — the same grade as `Operations:`, because an invented permission reads exactly like a real one everywhere downstream. Add the permission to that file when this service enforces one nobody has written down yet; the subject is the entity it is checked ON (`user`, `profile`, `service`), which is not always the caller.\",\n \"`Capability: <id>[, ...]` in the requirement body names the fleet capabilities this requirement realizes part of, declared in `architecture/capabilities.yaml`. A LIST, because the relation is many-to-many: one requirement commonly closes part of two capabilities. The FILE is this axis's opt-in — a fleet without it gets no capability findings — and once it exists an undeclared name is `capability.unknown` (error). Write the line only where the requirement genuinely carries part of the named capability.\",\n \"Write the requirement body in EARS form — `WHEN <trigger> THE SYSTEM SHALL <response>`, `IF <precondition> THEN THE SYSTEM SHALL <response>`, `WHERE <feature is included> THE SYSTEM SHALL <response>`, or a bare `THE SYSTEM SHALL <response>` when it is unconditional. **Nothing checks this** — it is a writing convention, and it is here because the grammar forces the trigger and the response to be named separately, while free prose lets a requirement read as complete while stating no condition at all. That is the exact shape a guard collapses into when it is summarised rather than enumerated.\",\n \"`Operations: <operationId>[, <operationId>]` in the requirement body names the API operations it governs. Every one must exist in this service's openapi.yaml — a name that does not is `spec-api.op-undefined` (error), reported against the LIVING spec, not only inside a feature delta.\",\n \"Document what the service DOES today, not what it should do. This is the baseline; changes to it belong in a feature delta.\",\n ],\n example: `---\nservice: payment-service\nstatus: draft\nowner: payments-team\nsources:\n - src/main/java/com/shop/payment/\n---\n\n# payment-service\n\n## Requirements\n\n### Requirement: Authorize a payment\nWHEN authorization is requested for a captured-eligible order THE SYSTEM SHALL\nauthorize it against the issuer before any capture.\n\nOperations: authorizePayment\n\n#### Scenario: Successful authorization\n- **Given** a valid card and an amount within its limit\n- **When** authorization is requested\n- **Then** the payment is authorized and an authorization id is returned\n\n### Requirement: Refunding is permission-gated\nIF the caller lacks the refund permission THEN THE SYSTEM SHALL refuse the\nrequest and leave the payment untouched.\n\nOperations: refundPayment\n\n#### Scenario: Refund by permission\n- **Given** a captured payment\n- **When** a caller holding <permission> requests a refund\n- **Then** the response is <status> and the payment is <state>\n\n| permission | status | state |\n|-----------------|--------|----------|\n| payments:refund | 200 | refunded |\n| payments:read | 403 | captured |\n| (none) | 401 | captured |\n`,\n },\n {\n artifact: \"arch.spec.md\",\n key: \"archSpec\",\n required: false,\n purpose:\n \"the living architecture requirements — the obligations the business spec never carries: outbox, retries, idempotency, metrics, alerts\",\n shape: [\n \"Same grammar and frontmatter as spec.md: `## Requirements`, `### Requirement:` + `#### Scenario:` with Given/When/Then. The same checks read it.\",\n \"A `Covers:` line per requirement names what its scenarios exercise: a C4 element id (`paymentService.db`), an edge (`paymentService -> kafka`), or a health signal from health.yaml (`alert:<id>` / `sli:<id>`). Every entry must resolve, or `covers.unknown` (warn) flags the typo.\",\n \"Optional, but expected for a service with real architecture: every alert/SLI health.yaml declares wants a covering requirement here (`health.uncovered`, warn) — a signal nothing tests is dashboard decoration.\",\n \"Write the obligations no business requirement states — the transactional outbox, what a caller's retry may assume, what pages whom. An arch scenario becomes an integration/ops test, not an acceptance test.\",\n ],\n example: `---\nservice: payment-service\nstatus: draft\nowner: payments-team\nsources:\n - src/main/java/com/shop/payment/\n---\n\n# payment-service — architecture\n\n## Requirements\n\n### Requirement: Events leave through the transactional outbox\nThe service SHALL write a domain event and its state change in one transaction,\npublished by an outbox relay — never a dual write.\n\nCovers: paymentService.db, paymentService -> kafka\n\n#### Scenario: Broker down at commit time\n- **Given** an event in the outbox\n- **When** the broker is unavailable\n- **Then** the state stays committed and the event is published once it returns\n`,\n },\n {\n artifact: \"openapi.yaml\",\n key: \"openapi\",\n // Overridden per service in `serviceBrief` (brief.ts): an API is required\n // only where the fleet expects one. See `apiExpected` there.\n required: true,\n purpose: \"the API contract — the operations this service exposes\",\n shape: [\n \"OpenAPI 3.x with a `paths` map.\",\n \"EVERY operation carries an `operationId`. That token is the spine: the C4 edge's `metadata { op }`, the requirement's `Operations:` line and this `operationId` are one name spelled three times, and they must be identical.\",\n \"loam reads operationIds, and probes the rest by PRESENCE only: an operation whose responses declare no schema warns (`openapi.response-undescribed`), an internal `$ref` to nothing warns (`openapi.ref-unresolved`). What a declared schema SAYS is still never checked — the schemas are for humans, codegen and review, so getting them wrong is invisible here and expensive later.\",\n \"Document the endpoints that exist. An operation the fleet already calls (see `landscape.expects`) must be in here, or the contract breaks the moment this lands.\",\n // The instruction the brief was missing, and the one that matters most:\n // it used to mark this artifact required unconditionally, in capitals,\n // which for a UI, a worker or a cron told the agent to invent an API into\n // the source of truth. `required` is derived now; this says so in prose\n // as well, because an agent reading the shape rules must not re-derive\n // \"MISSING\" as \"write something\".\n \"If this service exposes no HTTP API, there is no file to write. Do not invent one — an empty or imagined contract is a claim the whole fleet then joins against. `required` above is derived from the fleet map: it is false exactly when the landscape parses and no edge calls an operation on this service, which is the same evidence that keeps `service.no-openapi` quiet.\",\n ],\n },\n {\n artifact: \"asyncapi.yaml\",\n key: \"asyncapi\",\n // Never required. Unlike the HTTP contract, whose necessity the fleet map\n // can PROVE (an op-linked edge points here), most services in a legacy fleet\n // touch no topic at all — and a required artifact nobody owes is how a\n // whole adoption wave learns to ignore the brief.\n required: false,\n purpose: \"the async contract — the messages this service puts on and takes off the bus\",\n shape: [\n \"AsyncAPI **3.0** with `channels`, `operations` and `components.messages`. Only 3.0 is read: its operations are named top-level objects carrying `action: send|receive`, which is what makes a message's direction legible. A 2.x document declares no `operations` and reads as a contract with no messages.\",\n \"EVERY message carries a `name` (or is declared under the key you intend to reference). That token is the spine: the C4 edge's `metadata { publishes }` / `metadata { consumes }`, the requirement's `Publishes:` / `Consumes:` line and this name are one name spelled three times.\",\n \"Namespace the name by the domain that owns it — `payment.PaymentAuthorized`. One message has exactly one producer; two services declaring they send one name is `asyncapi.message-contested`, and then which contract a consumer reads is a coin flip.\",\n \"loam reads message names and the `action` of the operation carrying them. **It never joins on anything inside `payload`** — the one look inside is a presence probe: a payload declaring no shape at all warns (`asyncapi.payload-undescribed`), and a payload with a non-JSON `schemaFormat` is skipped outright. Write the payload as JSON Schema today; if the fleet later adopts Avro, that is a `schemaFormat` line here and nothing in loam changes.\",\n \"Keep payloads in `components.schemas` in THIS file rather than `$ref`-ing an external `.avsc`. External references are out of scope for the merge, exactly as on the OpenAPI axis, so a schema in another file will not travel with the message that needs it.\",\n \"If this service publishes and consumes nothing, there is no file to write. Do not invent one.\",\n ],\n },\n {\n artifact: \"adrs/\",\n key: \"adrsDir\",\n required: false,\n purpose: \"the decisions behind the shape of the service\",\n shape: [\n \"One file per decision: `adrs/NNNN-<slug>.md`, MADR-style (context · decision · consequences).\",\n \"Only decisions whose consequences are still visible in the code. A reconstructed rationale for something nobody decided is fiction, and it is the kind that survives review because it sounds reasonable.\",\n \"If a decision is evident but its reasoning is not, write the decision and say the reasoning is unknown. That is a fact; a plausible motive is not.\",\n ],\n },\n {\n artifact: \"runbook.md\",\n key: \"runbook\",\n required: false,\n purpose: \"how it is run — deploys, dependencies, what to do when it pages\",\n shape: [\n \"Frontmatter, then how it is deployed and configured, what it depends on, and what to do about the failures that actually happen.\",\n \"Written from what is in the repo (pipelines, manifests, alert definitions). Whoever carries the pager verifies it; leave `status: draft` until they have.\",\n \"A 'configured but not a dependency, and why' list: every capability the configuration wires up that the deploy manifest switches off. Without it, the next reader re-derives the dependency from the config and re-adds what the manifest already removed.\",\n ],\n },\n {\n artifact: \"health.yaml\",\n key: \"health\",\n required: false,\n purpose: \"SLIs, SLOs, checks and the dependencies that are critical\",\n shape: [\n \"SLIs and SLOs as they have been AGREED, plus health checks and critical dependencies.\",\n \"If no SLO has ever been agreed, say so and leave it out. An invented 99.9% becomes a number other teams plan against.\",\n ],\n },\n];\n"]}
1
+ {"version":3,"file":"targets.js","sourceRoot":"","sources":["../../../src/core/brief/targets.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAiDH,iEAAiE;AACjE,MAAM,CAAC,MAAM,SAAS,GAA8E;IAClG;QACE,QAAQ,EAAE,cAAc;QACxB,GAAG,EAAE,OAAO;QACZ,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,oEAAoE;QAC7E,uEAAuE;QACvE,2EAA2E;QAC3E,4EAA4E;QAC5E,qEAAqE;QACrE,oEAAoE;QACpE,8CAA8C;QAC9C,KAAK,EAAE;YACL,ibAAib;YACjb,wjBAAwjB;YACxjB,6zBAA6zB;YAC7zB,6aAA6a;SAC9a;QACD,mEAAmE;QACnE,4EAA4E;QAC5E,sEAAsE;QACtE,6EAA6E;QAC7E,2EAA2E;QAC3E,iEAAiE;QACjE,wEAAwE;QACxE,0EAA0E;QAC1E,qCAAqC;QACrC,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;CAwBZ;KACE;IACD;QACE,QAAQ,EAAE,SAAS;QACnB,GAAG,EAAE,MAAM;QACX,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,mFAAmF;QAC5F,KAAK,EAAE;YACL,wDAAwD;YACxD,kQAAkQ;YAClQ,wMAAwM;YACxM,wtBAAwtB;YACxtB,o0BAAo0B;YACp0B,wjBAAwjB;YACxjB,4fAA4f;YAC5f,slBAAslB;YACtlB,6RAA6R;YAC7R,6HAA6H;SAC9H;QACD,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCZ;KACE;IACD;QACE,QAAQ,EAAE,cAAc;QACxB,GAAG,EAAE,UAAU;QACf,QAAQ,EAAE,KAAK;QACf,OAAO,EACL,uIAAuI;QACzI,KAAK,EAAE;YACL,kJAAkJ;YAClJ,uRAAuR;YACvR,8sCAA8sC;YAC9sC,kNAAkN;YAClN,+MAA+M;SAChN;QACD,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;;;CAsBZ;KACE;IACD;QACE,QAAQ,EAAE,cAAc;QACxB,GAAG,EAAE,SAAS;QACd,0EAA0E;QAC1E,6DAA6D;QAC7D,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,wDAAwD;QACjE,KAAK,EAAE;YACL,iCAAiC;YACjC,+NAA+N;YAC/N,yXAAyX;YACzX,kKAAkK;YAClK,wEAAwE;YACxE,uEAAuE;YACvE,0EAA0E;YAC1E,wEAAwE;YACxE,uEAAuE;YACvE,kCAAkC;YAClC,kXAAkX;SACnX;KACF;IACD;QACE,QAAQ,EAAE,eAAe;QACzB,GAAG,EAAE,UAAU;QACf,0EAA0E;QAC1E,6EAA6E;QAC7E,uEAAuE;QACvE,kDAAkD;QAClD,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,8EAA8E;QACvF,KAAK,EAAE;YACL,8SAA8S;YAC9S,qRAAqR;YACrR,wPAAwP;YACxP,4bAA4b;YAC5b,gQAAgQ;YAChQ,+FAA+F;SAChG;KACF;IACD;QACE,QAAQ,EAAE,OAAO;QACjB,GAAG,EAAE,SAAS;QACd,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,+CAA+C;QACxD,KAAK,EAAE;YACL,+FAA+F;YAC/F,2MAA2M;YAC3M,oJAAoJ;SACrJ;KACF;IACD;QACE,QAAQ,EAAE,YAAY;QACtB,GAAG,EAAE,SAAS;QACd,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,iEAAiE;QAC1E,KAAK,EAAE;YACL,kIAAkI;YAClI,2JAA2J;YAC3J,4PAA4P;SAC7P;KACF;IACD;QACE,QAAQ,EAAE,aAAa;QACvB,GAAG,EAAE,QAAQ;QACb,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,2DAA2D;QACpE,KAAK,EAAE;YACL,uFAAuF;YACvF,uHAAuH;SACxH;KACF;CACF,CAAC","sourcesContent":["/**\n * The artifacts of the adoption brief: what each file under `services/<svc>/`\n * is for, the grammar it has to be written in, and a minimal valid example\n * where showing one is shorter than describing it. Every `shape` rule here is\n * one a later check depends on, and test/agent-contract.test.ts pins the\n * mapping. This module is the list alone — `serviceBrief` (brief.ts) resolves\n * it against a docs repo, and the fleet map's own target, whose block names\n * the service, is assembled per service in map/owed.ts.\n */\n\n/* ------------------------------------------------------------------ */\n/* The artifacts */\n/* ------------------------------------------------------------------ */\n\nexport interface BriefTarget {\n /** File name as it appears under `services/<svc>/`. */\n artifact: string;\n /** Repo-relative path to write. */\n path: string;\n exists: boolean;\n /**\n * `create` a missing artifact; `diff` an existing one — never overwrite it.\n *\n * `edit` is the third case and belongs to exactly one target: the fleet map.\n * `architecture/landscape.likec4` is a SHARED document that already holds\n * every other service, so \"diff it and report what disagrees\" is the wrong\n * instruction — the file is not this service's baseline, it is the fleet's,\n * and what this service owes it is an addition inside the existing\n * `model { … }` block. Spelling that as `diff` taught agents to leave the\n * fleet map alone; spelling it as `create` would have taught them to\n * overwrite ten other services.\n */\n action: \"create\" | \"diff\" | \"edit\";\n /**\n * False for artifacts a baseline can legitimately ship without. True means\n * required for a COMPLETE baseline — validate grades a missing spec.md or\n * openapi.yaml as a warn (`service.no-spec` / `service.no-openapi`), not an\n * error: partial adoption is a supported state, and the warns are the\n * progress meter.\n *\n * Constant per artifact except one: `openapi.yaml` is required only where the\n * fleet map shows an API is expected (see `apiExpected` in brief.ts), because\n * the brief is read as an instruction and \"MISSING openapi.yaml\" told an agent\n * to write a contract for a service that serves no requests.\n */\n required: boolean;\n /** What the artifact is for, in one line. */\n purpose: string;\n /** The grammar. Every rule here is one a later check depends on. */\n shape: string[];\n /** A minimal valid instance, where showing one is shorter than describing it. */\n example?: string;\n}\n\n/** Where an artifact lives — `repo/paths.ts` spells the filenames, this only points at them. */\ntype PathKey = \"model\" | \"spec\" | \"archSpec\" | \"openapi\" | \"asyncapi\" | \"adrsDir\" | \"runbook\" | \"health\";\n\n/** The artifacts, in the order a baseline is best written in. */\nexport const ARTIFACTS: Array<Omit<BriefTarget, \"path\" | \"exists\" | \"action\"> & { key: PathKey }> = [\n {\n artifact: \"model.likec4\",\n key: \"model\",\n required: true,\n purpose: \"the service's C4 — what it is, what is inside it, what it talks to\",\n // Every rule below is one a later check depends on, and the mapping is\n // pinned in test/agent-contract.test.ts. Rules nobody checks — the `views`\n // block, \"exactly one top-level element\" — used to sit here reading exactly\n // like the enforced ones, which is how a brief teaches an agent that\n // `loam validate` passing means more than it does. They now live in\n // UNCHECKED, where their status is the point.\n shape: [\n \"No `specification` block of its own: the kinds and tags come from `architecture/landscape.likec4` (a kind this model needs that the map does not declare — `element database`, `element queue` — is added there, once). A model that declares its own element kinds is the standalone shape: it still parses, alone, and loam then reports where its copies of the map's elements diverge (`c4.declaration-diverged`). New models never use it.\",\n \"A `model { ... }` block whose body is `extend <id> { ... }` — the fully-qualified id of the element the fleet map already binds to this directory (the landscape section of this brief names it; write the element into the map first when none resolves) — with containers and components nested inside. The binding and the description live on the map's element; this file may add `#tags` and metadata to it and never a description. The whole file has to parse: `loam validate` runs LikeC4 in-process over the map plus this file and reports `c4.invalid` with line numbers.\",\n \"A store or component this service OWNS goes INSIDE the `extend <fqn> { }` block, where its id becomes `<fqn>.<name>`: that is the only place an extending model may declare an element, and one declared anywhere else is `c4.element-unowned` (warn) — the fleet project then carries this service's private copy of somebody else's box. A call to another system is an edge from the container that makes it to the other party's element as the map spells it — `marketplace.paymentService.api -> stripe 'Authorizes cards'` — never a copy of that element: the map declares a system this service merely REACHES once, and another service's internals belong in that service's own model. The map still carries the service-level edge the fleet checks read (`metadata { op '<operationId>' }` graded by `spine.op-undefined`); draw it there too.\",\n \"The map's element is the binding: `metadata { service '<id>' }` on the element you extend (the landscape target below). Without it the element's title has to equal the directory name — and then renaming the box silently unlinks every check that joined the two. Until something in the map resolves to `services/<id>/`, `loam validate --all` reports `landscape.service-unmodelled` and there is no element for this file to extend.\",\n ],\n // `<fqn>` and `<svc>` are PLACEHOLDERS this module leaves literal:\n // `serviceBrief` (brief.ts) substitutes the landscape element's real id and\n // the service being adopted, and for `<fqn>` the \"the element's id in\n // architecture/landscape.likec4\" wording where the map holds none. ARTIFACTS\n // stays a static constant — a target list that varied per repository would\n // be a second thing to keep true. The header carried the literal\n // `payment-service` for every service, beside an `extend` line that WAS\n // substituted, so the half of the example that was personalised taught an\n // agent the other half had been too.\n example: `// <svc>'s own C4 — what is INSIDE the element the fleet map binds to\n// this directory. The map declares the kinds and the element; this\n// file extends it and re-declares nothing.\nmodel {\n extend <fqn> {\n api = container 'HTTP API'\n ledger = database 'Ledger store'\n api -> ledger 'Writes every authorization'\n }\n // 'stripe' stands for the OTHER party, spelled as the fleet map already\n // declares it — loam has no id to substitute here, and inventing one is\n // the edge that makes the model unresolvable. The containers above are\n // this example's own: name yours after what they are.\n <fqn>.api -> stripe 'Authorizes cards'\n}\n\n// Views are LikeC4's, not loam's (docs/DESIGN.md rule 26). Render with\n// 'npx likec4 start' from the docs repo root: this file is part of the root\n// project, beside the map — nothing else to write.\nviews {\n view of <fqn> {\n include *\n }\n}\n`,\n },\n {\n artifact: \"spec.md\",\n key: \"spec\",\n required: true,\n purpose: \"the living requirements — what this service is required to do, as it stands today\",\n shape: [\n \"Frontmatter (below), then a `## Requirements` heading.\",\n \"`### Requirement: <name>` — ONE observable behaviour, written with SHALL, testable without reading the code. Add `Requirement-ID: <id>` in its body for stable identity across heading renames; legacy requirements without one continue to match by exact name.\",\n \"At least one `#### Scenario: <name>` per requirement, with Given/When/Then bullets. This is gated (`requirements.missing-scenarios`) — scenarios are the acceptance criteria and the source for tests.\",\n \"A markdown TABLE in a scenario body becomes the `Examples` of a `Scenario Outline`: the header names the placeholders the steps use (`<permission>`), one row per case. This is how a matrix is written — a permission matrix, a validation matrix, a status-code table is ONE scenario with twenty rows, never twenty scenarios. `loam gherkin` emits the outline, cucumber reports one result per row carrying that scenario's digest tag, and `loam verify --results` confirms the claim only when EVERY row passed. Every row must have the header's column count and a header needs at least one row: a table loam cannot read stays in the description, the scenario then runs once instead of once per case, and the emission says so out loud.\",\n \"A step takes an ARGUMENT by carrying it INDENTED under the bullet, and this is what lets one scenario assert a whole data pass rather than a sentence. An indented table becomes that step's Gherkin DATA TABLE — `- **Then** the outbox holds:` followed by the rows it must hold; a fenced block becomes its DOCSTRING, indentation intact — `- **When** the caller posts:` followed by the JSON body. Indentation is the entire disambiguation: indented under a step means this step's rows, at the margin means the scenario's cases, and both ride into the scenario digest. Write the ASSERTIONS OUT, one bullet each, including the negative ones (`no row is created in ...`, `... is not invoked`) and each meter separately: a scenario that summarises them reads complete, emits valid Gherkin, grades green, and tests a fraction of what it claims.\",\n \"`Requires: <subject>/<permission>[, ...]` in the requirement body names the authorization it is gated on, resolved against the FLEET vocabulary in `architecture/permissions.yaml`. An entry that is not declared there is `permissions.unknown` (error) — the same grade as `Operations:`, because an invented permission reads exactly like a real one everywhere downstream. Add the permission to that file when this service enforces one nobody has written down yet; the subject is the entity it is checked ON (`user`, `profile`, `service`), which is not always the caller.\",\n \"`Capability: <id>[, ...]` in the requirement body names the fleet capabilities this requirement realizes part of, declared in `architecture/capabilities.yaml`. A LIST, because the relation is many-to-many: one requirement commonly closes part of two capabilities. The FILE is this axis's opt-in — a fleet without it gets no capability findings — and once it exists an undeclared name is `capability.unknown` (error). Write the line only where the requirement genuinely carries part of the named capability.\",\n \"Write the requirement body in EARS form — `WHEN <trigger> THE SYSTEM SHALL <response>`, `IF <precondition> THEN THE SYSTEM SHALL <response>`, `WHERE <feature is included> THE SYSTEM SHALL <response>`, or a bare `THE SYSTEM SHALL <response>` when it is unconditional. **Nothing checks this** — it is a writing convention, and it is here because the grammar forces the trigger and the response to be named separately, while free prose lets a requirement read as complete while stating no condition at all. That is the exact shape a guard collapses into when it is summarised rather than enumerated.\",\n \"`Operations: <operationId>[, <operationId>]` in the requirement body names the API operations it governs. Every one must exist in this service's openapi.yaml — a name that does not is `spec-api.op-undefined` (error), reported against the LIVING spec, not only inside a feature delta.\",\n \"Document what the service DOES today, not what it should do. This is the baseline; changes to it belong in a feature delta.\",\n ],\n example: `---\nservice: payment-service\nstatus: draft\nowner: payments-team\nsources:\n - src/main/java/com/shop/payment/\n---\n\n# payment-service\n\n## Requirements\n\n### Requirement: Authorize a payment\nWHEN authorization is requested for a captured-eligible order THE SYSTEM SHALL\nauthorize it against the issuer before any capture.\n\nOperations: authorizePayment\n\n#### Scenario: Successful authorization\n- **Given** a valid card and an amount within its limit\n- **When** authorization is requested\n- **Then** the payment is authorized and an authorization id is returned\n\n### Requirement: Refunding is permission-gated\nIF the caller lacks the refund permission THEN THE SYSTEM SHALL refuse the\nrequest and leave the payment untouched.\n\nOperations: refundPayment\n\n#### Scenario: Refund by permission\n- **Given** a captured payment\n- **When** a caller holding <permission> requests a refund\n- **Then** the response is <status> and the payment is <state>\n\n| permission | status | state |\n|-----------------|--------|----------|\n| payments:refund | 200 | refunded |\n| payments:read | 403 | captured |\n| (none) | 401 | captured |\n`,\n },\n {\n artifact: \"arch.spec.md\",\n key: \"archSpec\",\n required: false,\n purpose:\n \"the living architecture requirements — the obligations the business spec never carries: outbox, retries, idempotency, metrics, alerts\",\n shape: [\n \"Same grammar and frontmatter as spec.md: `## Requirements`, `### Requirement:` + `#### Scenario:` with Given/When/Then. The same checks read it.\",\n \"A `Covers:` line per requirement names what its scenarios exercise: a C4 element id (`paymentService.db`), an edge (`paymentService -> kafka`), or a health signal from health.yaml (`alert:<id>` / `sli:<id>`). Every entry must resolve, or `covers.unknown` (warn) flags the typo.\",\n \"A requirement whose `Covers:` names containers may have its hop sequence drawn as a `dynamic view` in a `.likec4` beside model.likec4 (`usecases/<name>.likec4` by convention; the renderer's per-service project reads every `.likec4` under the directory, and loam reads the same set), tagged `#req-<Requirement-ID>` — with the tag declared in `architecture/landscape.likec4`'s `specification` where the model extends the map, or in model.likec4's own block where it declares one; declaring it in BOTH is a duplicate, and the code lands where the second declaration's FILE is, not at fleet altitude: `c4.invalid` (an ERROR, and the model's grading is suspended behind it) when the tags-only block is in model.likec4, `usecase.flow-invalid` when it is in a sibling. `c4.fleet-project-invalid` is the fleet-scope class one step out — TWO SERVICES each declaring the same tag locally, which no per-service project sees. Graded by `loam validate --service <id>` (`usecase.step-unbacked` when a hop names a call the model does not declare, `usecase.requirement-unresolved` when the tag names no `Requirement-ID` here or in spec.md). Optional; nothing grades its absence. Never `architecture/usecases/`, which cannot resolve a container.\",\n \"Optional, but expected for a service with real architecture: every alert/SLI health.yaml declares wants a covering requirement here (`health.uncovered`, warn) — a signal nothing tests is dashboard decoration.\",\n \"Write the obligations no business requirement states — the transactional outbox, what a caller's retry may assume, what pages whom. An arch scenario becomes an integration/ops test, not an acceptance test.\",\n ],\n example: `---\nservice: payment-service\nstatus: draft\nowner: payments-team\nsources:\n - src/main/java/com/shop/payment/\n---\n\n# payment-service — architecture\n\n## Requirements\n\n### Requirement: Events leave through the transactional outbox\nThe service SHALL write a domain event and its state change in one transaction,\npublished by an outbox relay — never a dual write.\n\nCovers: paymentService.db, paymentService -> kafka\n\n#### Scenario: Broker down at commit time\n- **Given** an event in the outbox\n- **When** the broker is unavailable\n- **Then** the state stays committed and the event is published once it returns\n`,\n },\n {\n artifact: \"openapi.yaml\",\n key: \"openapi\",\n // Overridden per service in `serviceBrief` (brief.ts): an API is required\n // only where the fleet expects one. See `apiExpected` there.\n required: true,\n purpose: \"the API contract — the operations this service exposes\",\n shape: [\n \"OpenAPI 3.x with a `paths` map.\",\n \"EVERY operation carries an `operationId`. That token is the spine: the C4 edge's `metadata { op }`, the requirement's `Operations:` line and this `operationId` are one name spelled three times, and they must be identical.\",\n \"loam reads operationIds, and probes the rest by PRESENCE only: an operation whose responses declare no schema warns (`openapi.response-undescribed`), an internal `$ref` to nothing warns (`openapi.ref-unresolved`). What a declared schema SAYS is still never checked — the schemas are for humans, codegen and review, so getting them wrong is invisible here and expensive later.\",\n \"Document the endpoints that exist. An operation the fleet already calls (see `landscape.expects`) must be in here, or the contract breaks the moment this lands.\",\n // The instruction the brief was missing, and the one that matters most:\n // it used to mark this artifact required unconditionally, in capitals,\n // which for a UI, a worker or a cron told the agent to invent an API into\n // the source of truth. `required` is derived now; this says so in prose\n // as well, because an agent reading the shape rules must not re-derive\n // \"MISSING\" as \"write something\".\n \"If this service exposes no HTTP API, there is no file to write. Do not invent one — an empty or imagined contract is a claim the whole fleet then joins against. `required` above is derived from the fleet map: it is false exactly when the landscape parses and no edge calls an operation on this service, which is the same evidence that keeps `service.no-openapi` quiet.\",\n ],\n },\n {\n artifact: \"asyncapi.yaml\",\n key: \"asyncapi\",\n // Never required. Unlike the HTTP contract, whose necessity the fleet map\n // can PROVE (an op-linked edge points here), most services in a legacy fleet\n // touch no topic at all — and a required artifact nobody owes is how a\n // whole adoption wave learns to ignore the brief.\n required: false,\n purpose: \"the async contract — the messages this service puts on and takes off the bus\",\n shape: [\n \"AsyncAPI **3.0** with `channels`, `operations` and `components.messages`. Only 3.0 is read: its operations are named top-level objects carrying `action: send|receive`, which is what makes a message's direction legible. A 2.x document declares no `operations` and reads as a contract with no messages.\",\n \"EVERY message carries a `name` (or is declared under the key you intend to reference). That token is the spine: the C4 edge's `metadata { publishes }` / `metadata { consumes }`, the requirement's `Publishes:` / `Consumes:` line and this name are one name spelled three times.\",\n \"Namespace the name by the domain that owns it — `payment.PaymentAuthorized`. One message has exactly one producer; two services declaring they send one name is `asyncapi.message-contested`, and then which contract a consumer reads is a coin flip.\",\n \"loam reads message names and the `action` of the operation carrying them. **It never joins on anything inside `payload`** — the one look inside is a presence probe: a payload declaring no shape at all warns (`asyncapi.payload-undescribed`), and a payload with a non-JSON `schemaFormat` is skipped outright. Write the payload as JSON Schema today; if the fleet later adopts Avro, that is a `schemaFormat` line here and nothing in loam changes.\",\n \"Keep payloads in `components.schemas` in THIS file rather than `$ref`-ing an external `.avsc`. External references are out of scope for the merge, exactly as on the OpenAPI axis, so a schema in another file will not travel with the message that needs it.\",\n \"If this service publishes and consumes nothing, there is no file to write. Do not invent one.\",\n ],\n },\n {\n artifact: \"adrs/\",\n key: \"adrsDir\",\n required: false,\n purpose: \"the decisions behind the shape of the service\",\n shape: [\n \"One file per decision: `adrs/NNNN-<slug>.md`, MADR-style (context · decision · consequences).\",\n \"Only decisions whose consequences are still visible in the code. A reconstructed rationale for something nobody decided is fiction, and it is the kind that survives review because it sounds reasonable.\",\n \"If a decision is evident but its reasoning is not, write the decision and say the reasoning is unknown. That is a fact; a plausible motive is not.\",\n ],\n },\n {\n artifact: \"runbook.md\",\n key: \"runbook\",\n required: false,\n purpose: \"how it is run — deploys, dependencies, what to do when it pages\",\n shape: [\n \"Frontmatter, then how it is deployed and configured, what it depends on, and what to do about the failures that actually happen.\",\n \"Written from what is in the repo (pipelines, manifests, alert definitions). Whoever carries the pager verifies it; leave `status: draft` until they have.\",\n \"A 'configured but not a dependency, and why' list: every capability the configuration wires up that the deploy manifest switches off. Without it, the next reader re-derives the dependency from the config and re-adds what the manifest already removed.\",\n ],\n },\n {\n artifact: \"health.yaml\",\n key: \"health\",\n required: false,\n purpose: \"SLIs, SLOs, checks and the dependencies that are critical\",\n shape: [\n \"SLIs and SLOs as they have been AGREED, plus health checks and critical dependencies.\",\n \"If no SLO has ever been agreed, say so and leave it out. An invented 99.9% becomes a number other teams plan against.\",\n ],\n },\n];\n"]}
@@ -22,7 +22,7 @@ export const UNCHECKED = [
22
22
  // grades their absence, and the failure this comment records — agents
23
23
  // learning that a views block is owed to loam — is the one rule 26 is
24
24
  // written to prevent recurring.
25
- "Whether model.likec4 declares a `views { ... }` block, or any view at all — and nothing in loam ever will. What loam reads, when a `dynamic view` IS present, is only what its author declared: the view's tags and its ordered steps. It computes no view and renders nothing, and it never grades a model for lacking one — a fleet that draws no diagrams owes loam no views block, today or ever. Views belong to LikeC4's own renderer: write them if you want diagrams, and read them with `npx likec4 start` at the docs repo root, which shows the fleet map (the root project, which `likec4.config.json` scopes to `architecture/`) and one project per service model — the `services/<id>/likec4.config.json` that `loam subsystem sync` writes beside each model.likec4, loam's file and never yours. A feature's delta.likec4 is the one document still pointed at alone (`npx likec4 start features/<FEAT>`). The renderer merges every `.likec4` file a project holds into one model, and loam parses each of them alone, so each declares its own `specification` block: that is why the root project excludes `services/**`, and why a model with no project file of its own renders nowhere from the docs root. Scope your views too — computing a view is superlinear in the number of edges, and an `include *` over `architecture/landscape.likec4` is the expensive one, because that file holds every call in the fleet.",
25
+ "Whether model.likec4 declares a `views { ... }` block, or any view at all — and nothing in loam ever will. What loam reads, when a `dynamic view` IS present — in the fleet map, or in this service's own project once a view there carries `#req-` — is only what its author declared: the view's tags and its ordered steps. It computes no view and renders nothing, and it never grades a model for lacking one — a fleet that draws no diagrams owes loam no views block, today or ever. Views belong to LikeC4's own renderer: write them if you want diagrams, and read them with `npx likec4 start` at the docs repo root. The root project holds `architecture/` AND every model that EXTENDS the map, so a model written the way this brief asks for it renders there beside the fleet map with no second file to write — one project, one entry in the renderer's picker. The exception is a model that declares its own `specification` block and is therefore parsed alone: its kinds would be duplicates inside the root project, so its directory is excluded there and its own `services/<id>/likec4.config.json` — written by `loam subsystem sync`, loam's file and never yours — makes it a project of its own. A feature's delta.likec4 is the one document always pointed at alone (`npx likec4 start features/<FEAT>`), because it re-declares the landscape's identifiers and carries a `specification` block of its own. Scope your views too — computing a view is superlinear in the number of edges, and an `include *` over `architecture/landscape.likec4` is the expensive one, because that file holds every call in the fleet.",
26
26
  // The readability half of the entry above. They are separate entries because
27
27
  // the failures are: one is a view that takes minutes to compute, the other is
28
28
  // a view that computes instantly and cannot be read. A fleet acquires the
@@ -30,7 +30,17 @@ export const UNCHECKED = [
30
30
  // after that makes it worse by exactly one edge — which is why it arrives as
31
31
  // a surprise on a map that was fine last quarter.
32
32
  "Whether the fleet map is LEGIBLE — nothing in loam draws it, so nothing in loam notices. The shape that goes first is a shared broker: Kafka, a bus, a gateway, drawn as ONE element, is the node every service in the fleet points at, and a view over it is a star with sixty spokes. Model the TOPIC rather than the broker — `paymentEvents = topic 'payment.events'` nested inside the broker's element, with the edges pointing at `kafka.paymentEvents` — and one hub of degree sixty becomes twelve of degree five, which is also the truer model: the contract a producer and a consumer share is the topic, never the broker. Declare `element topic { #external, style { shape queue } }` in the `specification` block and give the nested elements that kind: LikeC4 does not inherit tags, so a topic under an `#external` broker is NOT external itself and `loam validate --all` asks for a `services/payment.events/` nobody owes (`landscape.service-undocumented`). Then scope the views, which is where the rest of the legibility lives: `exclude element.tag = #external` keeps the fleet overview to the calls between our own services, and a second view over the broker draws the event spine on its own.",
33
- "Whether the model has exactly ONE top-level element for the service, with its containers nested inside. Five top-level boxes for one service parse, validate and bind exactly as well as one — and then the fleet map, which joins elements to directories, has five candidates for the same service and no way to say which is wrong.",
33
+ // The third view entry, and the one docs/DESIGN.md rule 26 makes permanent:
34
+ // what a rendered view SHOWS is never computed, so whether an adopted service
35
+ // is on the picture is a fact loam cannot have. It is here rather than a
36
+ // check because each of the three ways to vanish is either the fleet's own
37
+ // vocabulary (a placeholder tag `loam seed` never writes), a static view's
38
+ // `include` predicates (unread by rule), or LikeC4's semantics — and a
39
+ // warning that is right once and noise forty times is the warning somebody
40
+ // silences. The brief NAMES the element's unread tags (landscape.ts); this
41
+ // entry says what nothing will ever do with them.
42
+ "Whether an adopted service is VISIBLE on the fleet map, which is a different state from being drawn on it. Three things make a documented, bound, edged service disappear, and no check names any of them. A placeholder tag somebody put on the element before adoption — `#provisional`, or whatever your fleet uses to mean drawn-ahead-of-adoption — survives adoption untouched: loam's `seed` writes no such tag, loam reads none, and a fleet view written `exclude element.tag = #<that>` keeps hiding the service until a human removes it, so clearing it is part of the landscape edit. A curated view that lists its members by id (`include paymentService, billingService`) gains nothing by itself. And `include *` shows top-level elements only: once services nest inside a boundary element — the shape loam's own generated subsystem views draw a box around — a fleet view written that way collapses to the boundary and shows no service at all, and needs `include <boundary>.**` (LikeC4's semantics, not loam's). Adopted and visible are two states; only the second is yours to make, and nothing here can tell you which one you are in.",
43
+ "Whether the model has exactly ONE top-level element for the service, with its containers nested inside — which is a question only for a model that declares its own `specification` and is parsed alone. Five top-level boxes for one service parse, validate and bind exactly as well as one there, and then the fleet map, which joins elements to directories, has five candidates for the same service and no way to say which is wrong. A model that EXTENDS the map cannot get into that state by accident: it opens one element, the map's, and anything it declares beside that element is `c4.element-unowned` — which is the same rule enforced instead of stated.",
34
44
  "Whether the model is the architecture the code actually has. loam parses model.likec4; it never reads a line of the service.",
35
45
  "Whether a requirement is TRUE of the service. `loam validate` checks that a requirement has a scenario, never that either one describes real behaviour.",
36
46
  "Whether the scenarios are acceptance criteria somebody could test, or the requirement restated in Given/When/Then.",
@@ -1 +1 @@
1
- {"version":3,"file":"unchecked.js","sourceRoot":"","sources":["../../../src/core/brief/unchecked.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,SAAS,GAAa;IACjC,4EAA4E;IAC5E,uEAAuE;IACvE,4EAA4E;IAC5E,2EAA2E;IAC3E,qDAAqD;IACrD,EAAE;IACF,6EAA6E;IAC7E,2EAA2E;IAC3E,sEAAsE;IACtE,sEAAsE;IACtE,gCAAgC;IAChC,i3CAAi3C;IACj3C,6EAA6E;IAC7E,8EAA8E;IAC9E,0EAA0E;IAC1E,6EAA6E;IAC7E,6EAA6E;IAC7E,kDAAkD;IAClD,oqCAAoqC;IACpqC,wUAAwU;IACxU,8HAA8H;IAC9H,yJAAyJ;IACzJ,oHAAoH;IACpH,wUAAwU;IACxU,yFAAyF;IACzF,6LAA6L;IAC7L,uGAAuG;IACvG,gnBAAgnB;IAChnB,yJAAyJ;IACzJ,mXAAmX;IACnX,2RAA2R;IAC3R,mjBAAmjB;IACnjB,gIAAgI;CACjI,CAAC","sourcesContent":["/**\n * What no check will ever tell you. Read it as the list of ways to be wrong\n * quietly. The checks that do exist — the table an adoption baseline actually\n * meets — live in checks.ts; this module is that table's counterpart, split\n * out so each half can grow without crowding the other.\n *\n * The first two entries are here because they used to be `shape` rules on\n * model.likec4, phrased exactly like the enforced ones. A brief that states an\n * unenforced rule beside four enforced ones is not merely useless — it is the\n * way an agent learns that a green `loam validate` means more than it does.\n * Anything nothing checks belongs on this list, where its status IS the point.\n */\nexport const UNCHECKED: string[] = [\n // Not merely unchecked — unread. This entry used to end \"write one\", on the\n // theory that a view is what makes a model legible. It is, to LikeC4's\n // renderer; loam computes none. Saying otherwise taught agents that a views\n // block was owed to loam, and an `include *` over the FLEET map is the one\n // shape that costs minutes rather than milliseconds.\n //\n // Still exactly true of PRESENCE, which is what this entry is about. Rule 26\n // lets loam read a dynamic view's declared steps WHERE ONE EXISTS; nothing\n // grades their absence, and the failure this comment records — agents\n // learning that a views block is owed to loam — is the one rule 26 is\n // written to prevent recurring.\n \"Whether model.likec4 declares a `views { ... }` block, or any view at all — and nothing in loam ever will. What loam reads, when a `dynamic view` IS present, is only what its author declared: the view's tags and its ordered steps. It computes no view and renders nothing, and it never grades a model for lacking one — a fleet that draws no diagrams owes loam no views block, today or ever. Views belong to LikeC4's own renderer: write them if you want diagrams, and read them with `npx likec4 start` at the docs repo root, which shows the fleet map (the root project, which `likec4.config.json` scopes to `architecture/`) and one project per service model — the `services/<id>/likec4.config.json` that `loam subsystem sync` writes beside each model.likec4, loam's file and never yours. A feature's delta.likec4 is the one document still pointed at alone (`npx likec4 start features/<FEAT>`). The renderer merges every `.likec4` file a project holds into one model, and loam parses each of them alone, so each declares its own `specification` block: that is why the root project excludes `services/**`, and why a model with no project file of its own renders nowhere from the docs root. Scope your views too — computing a view is superlinear in the number of edges, and an `include *` over `architecture/landscape.likec4` is the expensive one, because that file holds every call in the fleet.\",\n // The readability half of the entry above. They are separate entries because\n // the failures are: one is a view that takes minutes to compute, the other is\n // a view that computes instantly and cannot be read. A fleet acquires the\n // second one the day its second service starts publishing, and every service\n // after that makes it worse by exactly one edge — which is why it arrives as\n // a surprise on a map that was fine last quarter.\n \"Whether the fleet map is LEGIBLE — nothing in loam draws it, so nothing in loam notices. The shape that goes first is a shared broker: Kafka, a bus, a gateway, drawn as ONE element, is the node every service in the fleet points at, and a view over it is a star with sixty spokes. Model the TOPIC rather than the broker — `paymentEvents = topic 'payment.events'` nested inside the broker's element, with the edges pointing at `kafka.paymentEvents` — and one hub of degree sixty becomes twelve of degree five, which is also the truer model: the contract a producer and a consumer share is the topic, never the broker. Declare `element topic { #external, style { shape queue } }` in the `specification` block and give the nested elements that kind: LikeC4 does not inherit tags, so a topic under an `#external` broker is NOT external itself and `loam validate --all` asks for a `services/payment.events/` nobody owes (`landscape.service-undocumented`). Then scope the views, which is where the rest of the legibility lives: `exclude element.tag = #external` keeps the fleet overview to the calls between our own services, and a second view over the broker draws the event spine on its own.\",\n \"Whether the model has exactly ONE top-level element for the service, with its containers nested inside. Five top-level boxes for one service parse, validate and bind exactly as well as one — and then the fleet map, which joins elements to directories, has five candidates for the same service and no way to say which is wrong.\",\n \"Whether the model is the architecture the code actually has. loam parses model.likec4; it never reads a line of the service.\",\n \"Whether a requirement is TRUE of the service. `loam validate` checks that a requirement has a scenario, never that either one describes real behaviour.\",\n \"Whether the scenarios are acceptance criteria somebody could test, or the requirement restated in Given/When/Then.\",\n \"Whether an operationId is the one the code serves, and whether the request and response schemas resemble what the endpoint really accepts and returns. Presence is probed — an operation with no response schema at all is warned about — but what a declared schema SAYS is never compared with anything; only the operationId joins.\",\n \"Whether the runbook's steps work, or the health.yaml SLOs are numbers anyone agreed to.\",\n \"Whether an arch scenario really exercises what its `Covers:` line names. The entries are resolved against the model and health.yaml — never against code, tests, dashboards or alert rules.\",\n \"Whether an ADR records a decision that was made, or one reconstructed afterwards to justify the code.\",\n \"Whether the DEPLOYMENT model is the topology the fleet actually runs. Every join over it is between documents: `instanceOf` resolves against the C4 model, a `#obl-` tag resolves against the obligation vocabulary, and a `Covers: node:` entry resolves against the nodes the map declares. Nothing here knows whether the second cluster exists, whether it is reachable, whether replication is configured, or whether it holds the data a requirement claims — and no check counts datacenters, because how many a service needs is an architectural decision the fleet makes and writes down as a requirement, never one loam evaluates.\",\n \"COMPLETENESS. Forty behaviours documented as one requirement passes every check loam has. So does a service with one endpoint documented out of thirty.\",\n \"REPRODUCIBILITY — the bar the artifact set aims at: could a reader answer, without the code, what the service exposes, what it reaches, what shapes it exchanges, how it is run and what pages whom. The depth probes catch an EMPTY contract; nothing measures a shallow one against the service, so green means the files agree with each other, never that they reach the bar.\",\n \"Whether an arch requirement recording effective configuration or a library's semantics ('retries: -1 is unbounded, proven from the bytecode') is still true of the chart and the classpath. The convention names arch.spec.md as the home for such facts; loam never verifies the values.\",\n \"Whether a permission in `architecture/permissions.yaml` is one the system actually has. `Requires:` is checked against that file and the file against nothing: a vocabulary that agrees with every requirement citing it can still name three permissions the identity provider retired last year, and `owned_by` / `enforced_by` are read as prose — neither is resolved against `services/`, and a realm is never opened. The check is a spelling guard between documents, exactly as `Operations:` is; whether the word is real is a question only the running system answers.\",\n \"Whether `sources` names the files you read. loam checks those paths exist — not that they are the ones the document came from.\",\n];\n"]}
1
+ {"version":3,"file":"unchecked.js","sourceRoot":"","sources":["../../../src/core/brief/unchecked.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,SAAS,GAAa;IACjC,4EAA4E;IAC5E,uEAAuE;IACvE,4EAA4E;IAC5E,2EAA2E;IAC3E,qDAAqD;IACrD,EAAE;IACF,6EAA6E;IAC7E,2EAA2E;IAC3E,sEAAsE;IACtE,sEAAsE;IACtE,gCAAgC;IAChC,gkDAAgkD;IAChkD,6EAA6E;IAC7E,8EAA8E;IAC9E,0EAA0E;IAC1E,6EAA6E;IAC7E,6EAA6E;IAC7E,kDAAkD;IAClD,oqCAAoqC;IACpqC,4EAA4E;IAC5E,8EAA8E;IAC9E,yEAAyE;IACzE,2EAA2E;IAC3E,2EAA2E;IAC3E,uEAAuE;IACvE,2EAA2E;IAC3E,2EAA2E;IAC3E,kDAAkD;IAClD,wmCAAwmC;IACxmC,8oBAA8oB;IAC9oB,8HAA8H;IAC9H,yJAAyJ;IACzJ,oHAAoH;IACpH,wUAAwU;IACxU,yFAAyF;IACzF,6LAA6L;IAC7L,uGAAuG;IACvG,gnBAAgnB;IAChnB,yJAAyJ;IACzJ,mXAAmX;IACnX,2RAA2R;IAC3R,mjBAAmjB;IACnjB,gIAAgI;CACjI,CAAC","sourcesContent":["/**\n * What no check will ever tell you. Read it as the list of ways to be wrong\n * quietly. The checks that do exist — the table an adoption baseline actually\n * meets — live in checks.ts; this module is that table's counterpart, split\n * out so each half can grow without crowding the other.\n *\n * The first two entries are here because they used to be `shape` rules on\n * model.likec4, phrased exactly like the enforced ones. A brief that states an\n * unenforced rule beside four enforced ones is not merely useless — it is the\n * way an agent learns that a green `loam validate` means more than it does.\n * Anything nothing checks belongs on this list, where its status IS the point.\n */\nexport const UNCHECKED: string[] = [\n // Not merely unchecked — unread. This entry used to end \"write one\", on the\n // theory that a view is what makes a model legible. It is, to LikeC4's\n // renderer; loam computes none. Saying otherwise taught agents that a views\n // block was owed to loam, and an `include *` over the FLEET map is the one\n // shape that costs minutes rather than milliseconds.\n //\n // Still exactly true of PRESENCE, which is what this entry is about. Rule 26\n // lets loam read a dynamic view's declared steps WHERE ONE EXISTS; nothing\n // grades their absence, and the failure this comment records — agents\n // learning that a views block is owed to loam — is the one rule 26 is\n // written to prevent recurring.\n \"Whether model.likec4 declares a `views { ... }` block, or any view at all — and nothing in loam ever will. What loam reads, when a `dynamic view` IS present — in the fleet map, or in this service's own project once a view there carries `#req-` — is only what its author declared: the view's tags and its ordered steps. It computes no view and renders nothing, and it never grades a model for lacking one — a fleet that draws no diagrams owes loam no views block, today or ever. Views belong to LikeC4's own renderer: write them if you want diagrams, and read them with `npx likec4 start` at the docs repo root. The root project holds `architecture/` AND every model that EXTENDS the map, so a model written the way this brief asks for it renders there beside the fleet map with no second file to write — one project, one entry in the renderer's picker. The exception is a model that declares its own `specification` block and is therefore parsed alone: its kinds would be duplicates inside the root project, so its directory is excluded there and its own `services/<id>/likec4.config.json` — written by `loam subsystem sync`, loam's file and never yours — makes it a project of its own. A feature's delta.likec4 is the one document always pointed at alone (`npx likec4 start features/<FEAT>`), because it re-declares the landscape's identifiers and carries a `specification` block of its own. Scope your views too — computing a view is superlinear in the number of edges, and an `include *` over `architecture/landscape.likec4` is the expensive one, because that file holds every call in the fleet.\",\n // The readability half of the entry above. They are separate entries because\n // the failures are: one is a view that takes minutes to compute, the other is\n // a view that computes instantly and cannot be read. A fleet acquires the\n // second one the day its second service starts publishing, and every service\n // after that makes it worse by exactly one edge — which is why it arrives as\n // a surprise on a map that was fine last quarter.\n \"Whether the fleet map is LEGIBLE — nothing in loam draws it, so nothing in loam notices. The shape that goes first is a shared broker: Kafka, a bus, a gateway, drawn as ONE element, is the node every service in the fleet points at, and a view over it is a star with sixty spokes. Model the TOPIC rather than the broker — `paymentEvents = topic 'payment.events'` nested inside the broker's element, with the edges pointing at `kafka.paymentEvents` — and one hub of degree sixty becomes twelve of degree five, which is also the truer model: the contract a producer and a consumer share is the topic, never the broker. Declare `element topic { #external, style { shape queue } }` in the `specification` block and give the nested elements that kind: LikeC4 does not inherit tags, so a topic under an `#external` broker is NOT external itself and `loam validate --all` asks for a `services/payment.events/` nobody owes (`landscape.service-undocumented`). Then scope the views, which is where the rest of the legibility lives: `exclude element.tag = #external` keeps the fleet overview to the calls between our own services, and a second view over the broker draws the event spine on its own.\",\n // The third view entry, and the one docs/DESIGN.md rule 26 makes permanent:\n // what a rendered view SHOWS is never computed, so whether an adopted service\n // is on the picture is a fact loam cannot have. It is here rather than a\n // check because each of the three ways to vanish is either the fleet's own\n // vocabulary (a placeholder tag `loam seed` never writes), a static view's\n // `include` predicates (unread by rule), or LikeC4's semantics — and a\n // warning that is right once and noise forty times is the warning somebody\n // silences. The brief NAMES the element's unread tags (landscape.ts); this\n // entry says what nothing will ever do with them.\n \"Whether an adopted service is VISIBLE on the fleet map, which is a different state from being drawn on it. Three things make a documented, bound, edged service disappear, and no check names any of them. A placeholder tag somebody put on the element before adoption — `#provisional`, or whatever your fleet uses to mean drawn-ahead-of-adoption — survives adoption untouched: loam's `seed` writes no such tag, loam reads none, and a fleet view written `exclude element.tag = #<that>` keeps hiding the service until a human removes it, so clearing it is part of the landscape edit. A curated view that lists its members by id (`include paymentService, billingService`) gains nothing by itself. And `include *` shows top-level elements only: once services nest inside a boundary element — the shape loam's own generated subsystem views draw a box around — a fleet view written that way collapses to the boundary and shows no service at all, and needs `include <boundary>.**` (LikeC4's semantics, not loam's). Adopted and visible are two states; only the second is yours to make, and nothing here can tell you which one you are in.\",\n \"Whether the model has exactly ONE top-level element for the service, with its containers nested inside — which is a question only for a model that declares its own `specification` and is parsed alone. Five top-level boxes for one service parse, validate and bind exactly as well as one there, and then the fleet map, which joins elements to directories, has five candidates for the same service and no way to say which is wrong. A model that EXTENDS the map cannot get into that state by accident: it opens one element, the map's, and anything it declares beside that element is `c4.element-unowned` — which is the same rule enforced instead of stated.\",\n \"Whether the model is the architecture the code actually has. loam parses model.likec4; it never reads a line of the service.\",\n \"Whether a requirement is TRUE of the service. `loam validate` checks that a requirement has a scenario, never that either one describes real behaviour.\",\n \"Whether the scenarios are acceptance criteria somebody could test, or the requirement restated in Given/When/Then.\",\n \"Whether an operationId is the one the code serves, and whether the request and response schemas resemble what the endpoint really accepts and returns. Presence is probed — an operation with no response schema at all is warned about — but what a declared schema SAYS is never compared with anything; only the operationId joins.\",\n \"Whether the runbook's steps work, or the health.yaml SLOs are numbers anyone agreed to.\",\n \"Whether an arch scenario really exercises what its `Covers:` line names. The entries are resolved against the model and health.yaml — never against code, tests, dashboards or alert rules.\",\n \"Whether an ADR records a decision that was made, or one reconstructed afterwards to justify the code.\",\n \"Whether the DEPLOYMENT model is the topology the fleet actually runs. Every join over it is between documents: `instanceOf` resolves against the C4 model, a `#obl-` tag resolves against the obligation vocabulary, and a `Covers: node:` entry resolves against the nodes the map declares. Nothing here knows whether the second cluster exists, whether it is reachable, whether replication is configured, or whether it holds the data a requirement claims — and no check counts datacenters, because how many a service needs is an architectural decision the fleet makes and writes down as a requirement, never one loam evaluates.\",\n \"COMPLETENESS. Forty behaviours documented as one requirement passes every check loam has. So does a service with one endpoint documented out of thirty.\",\n \"REPRODUCIBILITY — the bar the artifact set aims at: could a reader answer, without the code, what the service exposes, what it reaches, what shapes it exchanges, how it is run and what pages whom. The depth probes catch an EMPTY contract; nothing measures a shallow one against the service, so green means the files agree with each other, never that they reach the bar.\",\n \"Whether an arch requirement recording effective configuration or a library's semantics ('retries: -1 is unbounded, proven from the bytecode') is still true of the chart and the classpath. The convention names arch.spec.md as the home for such facts; loam never verifies the values.\",\n \"Whether a permission in `architecture/permissions.yaml` is one the system actually has. `Requires:` is checked against that file and the file against nothing: a vocabulary that agrees with every requirement citing it can still name three permissions the identity provider retired last year, and `owned_by` / `enforced_by` are read as prose — neither is resolved against `services/`, and a realm is never opened. The check is a spelling guard between documents, exactly as `Operations:` is; whether the word is real is a question only the running system answers.\",\n \"Whether `sources` names the files you read. loam checks those paths exist — not that they are the ones the document came from.\",\n];\n"]}
@@ -2,10 +2,31 @@ import { readFile } from "node:fs/promises";
2
2
  import { LikeC4 } from "likec4";
3
3
  import { declaredService } from "../kernel/ids/service.js";
4
4
  import { readDynamicViews } from "./parsed/dynamic-views.js";
5
- import { readViewIds } from "./parsed/view-ids.js";
5
+ import { readGlobalStyleIds, readViewIds } from "./parsed/view-ids.js";
6
6
  import { readSpecification } from "./parsed/specification.js";
7
7
  import { readDeployment } from "./parsed/deployment.js";
8
8
  import { descText, metaKey } from "./parsed/values.js";
9
+ /**
10
+ * The one spelling of a diagnostic loam prints — `L<n>: <message>`, with the
11
+ * line 1-BASED: the number an editor's gutter shows, so an agent told to open
12
+ * it opens the line the fault is on. LikeC4's `getErrors()` copies the LSP
13
+ * range's start line, which counts from 0, and every message used to print that
14
+ * raw number: the named line landed one above the fault every time
15
+ * (verification 2026-09-04, W6).
16
+ *
17
+ * `likec4 validate` DOES NOT AGREE, and that is deliberate rather than
18
+ * unnoticed. Measured at the 1.59.2 pin on a document whose fault is on file
19
+ * line 10: the CLI prints `Line 9:` and loam prints `L10:` for the same
20
+ * diagnostic — it renders the LSP index raw. Copying that would make loam's
21
+ * output wrong in the way the fix above repaired, so the two spellings differ
22
+ * by exactly one and a reader comparing them should expect it.
23
+ *
24
+ * Four modules used to hand-roll this ternary; they all read this one now, so
25
+ * the base cannot drift between them again.
26
+ */
27
+ export function errorText(e) {
28
+ return typeof e.line === "number" ? `L${e.line + 1}: ${e.message}` : e.message;
29
+ }
9
30
  /** Flatten a LikeC4 model into loam's neutral `Elem`/`Rel` view. */
10
31
  export function flattenModel(model) {
11
32
  const elements = [...model.elements()].map((e) => {
@@ -79,6 +100,7 @@ export async function loadSource(src) {
79
100
  specification: readSpecification(model.specification),
80
101
  views: readDynamicViews(model),
81
102
  viewIds: readViewIds(model),
103
+ globalStyles: readGlobalStyleIds(model),
82
104
  deployment: readDeployment(model),
83
105
  ...flattenModel(model),
84
106
  };
@@ -1 +1 @@
1
- {"version":3,"file":"likec4.js","sourceRoot":"","sources":["../../../src/core/c4/likec4.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { LikeC4 } from \"likec4\";\nimport { declaredService, type DeclaredService } from \"../kernel/ids/service.js\";\nimport { readDynamicViews, type ParsedView } from \"./parsed/dynamic-views.js\";\nimport { readViewIds, type ViewIdClaim } from \"./parsed/view-ids.js\";\nimport { readSpecification, type DocSpecification } from \"./parsed/specification.js\";\nimport { readDeployment, type DeploymentModel } from \"./parsed/deployment.js\";\nimport { descText, metaKey } from \"./parsed/values.js\";\n\n/** A parse/validation issue reported by LikeC4. */\nexport interface LikeC4Error {\n message: string;\n line?: number;\n sourceFsPath?: string;\n}\n\n/** loam-neutral element view (flattened from the LikeC4 parsed model). */\nexport interface Elem {\n id: string;\n kind: string;\n title: string;\n description?: string;\n /**\n * The `services/<id>` directory this element stands for, from the element's\n * `metadata { service '...' }`. Absent on the elements nobody has bound —\n * see `elementService` for the fallback.\n */\n service?: DeclaredService;\n tags: string[];\n}\n\n/** loam-neutral relationship view. */\nexport interface Rel {\n source: string;\n target: string;\n title?: string;\n /** OpenAPI operationId this call uses, from the relationship's `metadata { op '...' }`. */\n op?: string;\n /**\n * AsyncAPI message this edge PRODUCES, from `metadata { publishes '...' }`.\n * Resolved against the edge SOURCE's own contract: a service publishes what it\n * declares an `action: send` for.\n */\n publishes?: string;\n /**\n * AsyncAPI message this edge CONSUMES, from `metadata { consumes '...' }`.\n * Two keys rather than one directional `message`, because the async spine is\n * not symmetric with the HTTP one: there the PROVIDER owns the contract and\n * every consumer is checked against it, while here the PRODUCER owns the\n * message and the consumer lives in another repository entirely. Which side of\n * an edge owes the declaration has to be readable from the edge itself — the\n * arrow cannot say it, because a Kafka edge points at the broker from both\n * ends.\n */\n consumes?: string;\n tags: string[];\n}\n\nexport interface LoadedDoc {\n errors: LikeC4Error[];\n elements: Elem[];\n relationships: Rel[];\n /**\n * The document's `specification { }` block — what its KINDS declare, as\n * opposed to what its elements do. Optional because a document that did not\n * parse has none, and because the several `LoadedDoc` literals standing in for\n * an absent file (`show`'s missing model.likec4, `unreadableLandscape`) have\n * nothing to put here: a reader must treat \"absent\" and \"declares nothing\" the\n * same way. See `./parsed/specification.ts` for why loam reads it at all —\n * short version: since LikeC4 1.59.0 a kind can carry tags, and only the\n * specification can tell an inherited tag from an authored one.\n */\n specification?: DocSpecification;\n /**\n * The `dynamic view`s the document DECLARES — the ordered steps of a use\n * case, never a rendering (docs/DESIGN.md rule 26). Optional for exactly the\n * reason `specification` is: a document that did not parse has none, and the\n * several `LoadedDoc` literals standing in for an absent file have nothing to\n * put here. Absent and empty mean the same thing to every reader — no views\n * were declared, or none could be read — and neither is a finding: a fleet\n * that draws no diagrams owes loam no views block.\n */\n views?: ParsedView[];\n /**\n * The ids of every view the document AUTHORS, static ones included — a census,\n * not a read of what any of them shows. Separate from `views` above because\n * the two answer different questions off different filters (see\n * `./parsed/view-ids.ts`), and because a static view contributes an id and\n * nothing else loam may look at.\n */\n viewIds?: ViewIdClaim[];\n /**\n * The `deployment { }` model the document declares — nodes, instances and the\n * edges between them (`./parsed/deployment.ts`). Optional for the reason\n * `views` is, and read the same way by every consumer: absent and empty mean\n * \"no topology\", and neither is a finding. A fleet that draws no deployment\n * owes loam none, which is also the whole opt-in the axis has.\n */\n deployment?: DeploymentModel;\n}\n\n/**\n * The slice of a LikeC4 model loam reads: elements and relationships, and on\n * each of those only id/kind/title/description/tags/metadata.\n *\n * It is a type of its own, rather than an inline cast at the one call site,\n * because BOTH of LikeC4's model stages expose exactly this slice — which is\n * what makes the cheap stage a safe substitute for the expensive one. The\n * substitution is pinned in test/likec4-model-parity.test.ts, which flattens\n * both stages through `flattenModel` below and compares the results.\n *\n * Views are deliberately NOT in this interface, even though loam does read them.\n * What this interface pins is the two-stage PARITY that makes the cheap stage a\n * safe substitute for the expensive one — and the stages are not claimed to\n * agree about views, nor need to: the parsed stage is the only one rule 26\n * permits. That read has its own adapter, `./parsed/dynamic-views.ts`.\n */\nexport interface ReadableModel {\n elements: () => Iterable<{\n id: string;\n kind: string;\n title: string;\n description?: unknown;\n tags?: readonly string[];\n metadata?: unknown;\n }>;\n relationships: () => Iterable<{\n source: { id: string };\n target: { id: string };\n title?: string | null;\n tags?: readonly string[];\n metadata?: unknown;\n }>;\n /**\n * The parsed `specification { }` block. Typed `unknown` and normalized by\n * `readSpecification` rather than described here, because it is NOT part of\n * the two-stage parity this interface pins: the shape is LikeC4's internal\n * record (1.59.2 spells element-kind tags and relationship-kind tags\n * differently), and writing it out here would be a structural claim about a\n * dependency's internals that the compiler would then enforce against us.\n */\n specification?: unknown;\n}\n\n/** Flatten a LikeC4 model into loam's neutral `Elem`/`Rel` view. */\nexport function flattenModel(model: ReadableModel): { elements: Elem[]; relationships: Rel[] } {\n const elements: Elem[] = [...model.elements()].map((e) => {\n // The one crossing between LikeC4's parse output and loam's type system:\n // a `metadata { service '...' }` binding is text somebody wrote, and this\n // is where it acquires a type that says so.\n const bound = metaKey(e.metadata, \"service\");\n return {\n id: e.id,\n kind: e.kind,\n title: e.title,\n description: descText(e.description),\n ...(bound === undefined ? {} : { service: declaredService(bound) }),\n tags: [...(e.tags ?? [])],\n };\n });\n const relationships: Rel[] = [...model.relationships()].map((r) => ({\n source: r.source.id,\n target: r.target.id,\n // LikeC4 reports an untitled edge as title: null — normalize to the declared `title?: string`.\n title: r.title ?? undefined,\n op: metaKey(r.metadata, \"op\"),\n publishes: metaKey(r.metadata, \"publishes\"),\n consumes: metaKey(r.metadata, \"consumes\"),\n tags: [...(r.tags ?? [])],\n }));\n return { elements, relationships };\n}\n\n/**\n * Load and validate a single self-contained `.likec4` document, in-process\n * (no external tool, no JVM). Returns validation errors and, if clean, the\n * flattened elements + relationships.\n */\nexport async function loadFile(path: string): Promise<LoadedDoc> {\n return loadSource(await readFile(path, \"utf8\"));\n}\n\n/**\n * `loadFile` for text that is not (yet) on disk — what the archive merge uses\n * to prove a computed landscape parses BEFORE anything is written.\n */\nexport async function loadSource(src: string): Promise<LoadedDoc> {\n // LikeC4 owns a Langium workspace and registers a process-exit listener for\n // it. Validation creates many short-lived instances, so every acquired\n // instance must be disposed even on an invalid document or a failed model\n // computation. Its logger is disabled because diagnostics are returned in\n // LoadedDoc instead of being written out-of-band to stderr.\n const likec4 = await LikeC4.fromSource(src, { logger: false });\n try {\n // Assigned, not cast: LikeC4 declares `getErrors()` as a non-nullable array\n // whose element type is structurally wider than ours, so the compiler can\n // check the shape here. The cast was the only thing standing between a\n // dependency renaming `message` and a build that still succeeded — and the\n // `?? []` it carried could never fire.\n const errors: LikeC4Error[] = likec4.getErrors();\n if (errors.length > 0) {\n return { errors, elements: [], relationships: [] };\n }\n\n // `parsedModel`, not `computedModel`: the computed stage RESOLVES every\n // view — expanding its predicates against the model and deriving the\n // ancestor edges a diagram needs (and computing a default view when the\n // document declares none). loam reads what a view DECLARES and never what a\n // view SHOWS (docs/DESIGN.md rule 26), so none of that resolution is ever\n // wanted; it renders nothing. That work is superlinear in the number of\n // RELATIONSHIPS, so a landscape at fleet shape turned `loam list` from under\n // a second into minutes. The elements and relationships below are identical\n // either way. The `await` is load-bearing for anything reading `$data`: it is\n // undefined on the unresolved promise.\n const model = (await likec4.parsedModel()) as ReadableModel;\n return {\n errors,\n specification: readSpecification(model.specification),\n views: readDynamicViews(model),\n viewIds: readViewIds(model),\n deployment: readDeployment(model),\n ...flattenModel(model),\n };\n } finally {\n await likec4.dispose();\n }\n}\n"]}
1
+ {"version":3,"file":"likec4.js","sourceRoot":"","sources":["../../../src/core/c4/likec4.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { LikeC4 } from \"likec4\";\nimport { declaredService, type DeclaredService } from \"../kernel/ids/service.js\";\nimport { readDynamicViews, type ParsedView } from \"./parsed/dynamic-views.js\";\nimport { readGlobalStyleIds, readViewIds, type ViewIdClaim } from \"./parsed/view-ids.js\";\nimport { readSpecification, type DocSpecification } from \"./parsed/specification.js\";\nimport { readDeployment, type DeploymentModel } from \"./parsed/deployment.js\";\nimport { descText, metaKey } from \"./parsed/values.js\";\n\n/** A parse/validation issue reported by LikeC4. */\nexport interface LikeC4Error {\n message: string;\n /** As LikeC4 hands it over: an LSP range line, which starts at 0. Print it through `errorText`. */\n line?: number;\n sourceFsPath?: string;\n}\n\n/**\n * The one spelling of a diagnostic loam prints — `L<n>: <message>`, with the\n * line 1-BASED: the number an editor's gutter shows, so an agent told to open\n * it opens the line the fault is on. LikeC4's `getErrors()` copies the LSP\n * range's start line, which counts from 0, and every message used to print that\n * raw number: the named line landed one above the fault every time\n * (verification 2026-09-04, W6).\n *\n * `likec4 validate` DOES NOT AGREE, and that is deliberate rather than\n * unnoticed. Measured at the 1.59.2 pin on a document whose fault is on file\n * line 10: the CLI prints `Line 9:` and loam prints `L10:` for the same\n * diagnostic — it renders the LSP index raw. Copying that would make loam's\n * output wrong in the way the fix above repaired, so the two spellings differ\n * by exactly one and a reader comparing them should expect it.\n *\n * Four modules used to hand-roll this ternary; they all read this one now, so\n * the base cannot drift between them again.\n */\nexport function errorText(e: LikeC4Error): string {\n return typeof e.line === \"number\" ? `L${e.line + 1}: ${e.message}` : e.message;\n}\n\n/** loam-neutral element view (flattened from the LikeC4 parsed model). */\nexport interface Elem {\n id: string;\n kind: string;\n title: string;\n description?: string;\n /**\n * The `services/<id>` directory this element stands for, from the element's\n * `metadata { service '...' }`. Absent on the elements nobody has bound —\n * see `elementService` for the fallback.\n */\n service?: DeclaredService;\n tags: string[];\n}\n\n/** loam-neutral relationship view. */\nexport interface Rel {\n source: string;\n target: string;\n title?: string;\n /** OpenAPI operationId this call uses, from the relationship's `metadata { op '...' }`. */\n op?: string;\n /**\n * AsyncAPI message this edge PRODUCES, from `metadata { publishes '...' }`.\n * Resolved against the edge SOURCE's own contract: a service publishes what it\n * declares an `action: send` for.\n */\n publishes?: string;\n /**\n * AsyncAPI message this edge CONSUMES, from `metadata { consumes '...' }`.\n * Two keys rather than one directional `message`, because the async spine is\n * not symmetric with the HTTP one: there the PROVIDER owns the contract and\n * every consumer is checked against it, while here the PRODUCER owns the\n * message and the consumer lives in another repository entirely. Which side of\n * an edge owes the declaration has to be readable from the edge itself — the\n * arrow cannot say it, because a Kafka edge points at the broker from both\n * ends.\n */\n consumes?: string;\n tags: string[];\n}\n\nexport interface LoadedDoc {\n errors: LikeC4Error[];\n elements: Elem[];\n relationships: Rel[];\n /**\n * The document's `specification { }` block — what its KINDS declare, as\n * opposed to what its elements do. Optional because a document that did not\n * parse has none, and because the several `LoadedDoc` literals standing in for\n * an absent file (`show`'s missing model.likec4, `unreadableLandscape`) have\n * nothing to put here: a reader must treat \"absent\" and \"declares nothing\" the\n * same way. See `./parsed/specification.ts` for why loam reads it at all —\n * short version: since LikeC4 1.59.0 a kind can carry tags, and only the\n * specification can tell an inherited tag from an authored one.\n */\n specification?: DocSpecification;\n /**\n * The `dynamic view`s the document DECLARES — the ordered steps of a use\n * case, never a rendering (docs/DESIGN.md rule 26). Optional for exactly the\n * reason `specification` is: a document that did not parse has none, and the\n * several `LoadedDoc` literals standing in for an absent file have nothing to\n * put here. Absent and empty mean the same thing to every reader — no views\n * were declared, or none could be read — and neither is a finding: a fleet\n * that draws no diagrams owes loam no views block.\n */\n views?: ParsedView[];\n /**\n * The ids of every view the document AUTHORS, static ones included — a census,\n * not a read of what any of them shows. Separate from `views` above because\n * the two answer different questions off different filters (see\n * `./parsed/view-ids.ts`), and because a static view contributes an id and\n * nothing else loam may look at.\n */\n viewIds?: ViewIdClaim[];\n /**\n * The ids of every GLOBAL STYLE the document declares — `global { styleGroup\n * <id> { … } }` and the single-rule `global { style <id> … }` alike, since\n * LikeC4 files both under one id table (hence not `styleGroups`). A\n * declaration census exactly as `viewIds` is: the ids, sorted, and never a\n * rule inside one (docs/DESIGN.md rule 26 says why a style is a rendering\n * instruction loam may not read). One consumer — the generated subsystem\n * views, which reference the group named `subsystems` when it is declared\n * and nothing otherwise, because a reference to an undeclared id is a parse\n * error that blanks the whole `architecture/` project in the renderer.\n * Optional for the reason `views` is: a document that did not parse has\n * none, and the `LoadedDoc` literals standing in for an absent file have\n * nothing to put here; absent and empty mean the same thing to every reader.\n */\n globalStyles?: string[];\n /**\n * The `deployment { }` model the document declares — nodes, instances and the\n * edges between them (`./parsed/deployment.ts`). Optional for the reason\n * `views` is, and read the same way by every consumer: absent and empty mean\n * \"no topology\", and neither is a finding. A fleet that draws no deployment\n * owes loam none, which is also the whole opt-in the axis has.\n */\n deployment?: DeploymentModel;\n}\n\n/**\n * The slice of a LikeC4 model loam reads: elements and relationships, and on\n * each of those only id/kind/title/description/tags/metadata.\n *\n * It is a type of its own, rather than an inline cast at the one call site,\n * because BOTH of LikeC4's model stages expose exactly this slice — which is\n * what makes the cheap stage a safe substitute for the expensive one. The\n * substitution is pinned in test/likec4-model-parity.test.ts, which flattens\n * both stages through `flattenModel` below and compares the results.\n *\n * Views are deliberately NOT in this interface, even though loam does read them.\n * What this interface pins is the two-stage PARITY that makes the cheap stage a\n * safe substitute for the expensive one — and the stages are not claimed to\n * agree about views, nor need to: the parsed stage is the only one rule 26\n * permits. That read has its own adapter, `./parsed/dynamic-views.ts`.\n */\nexport interface ReadableModel {\n elements: () => Iterable<{\n id: string;\n kind: string;\n title: string;\n description?: unknown;\n tags?: readonly string[];\n metadata?: unknown;\n }>;\n relationships: () => Iterable<{\n source: { id: string };\n target: { id: string };\n title?: string | null;\n tags?: readonly string[];\n metadata?: unknown;\n }>;\n /**\n * The parsed `specification { }` block. Typed `unknown` and normalized by\n * `readSpecification` rather than described here, because it is NOT part of\n * the two-stage parity this interface pins: the shape is LikeC4's internal\n * record (1.59.2 spells element-kind tags and relationship-kind tags\n * differently), and writing it out here would be a structural claim about a\n * dependency's internals that the compiler would then enforce against us.\n */\n specification?: unknown;\n}\n\n/** Flatten a LikeC4 model into loam's neutral `Elem`/`Rel` view. */\nexport function flattenModel(model: ReadableModel): { elements: Elem[]; relationships: Rel[] } {\n const elements: Elem[] = [...model.elements()].map((e) => {\n // The one crossing between LikeC4's parse output and loam's type system:\n // a `metadata { service '...' }` binding is text somebody wrote, and this\n // is where it acquires a type that says so.\n const bound = metaKey(e.metadata, \"service\");\n return {\n id: e.id,\n kind: e.kind,\n title: e.title,\n description: descText(e.description),\n ...(bound === undefined ? {} : { service: declaredService(bound) }),\n tags: [...(e.tags ?? [])],\n };\n });\n const relationships: Rel[] = [...model.relationships()].map((r) => ({\n source: r.source.id,\n target: r.target.id,\n // LikeC4 reports an untitled edge as title: null — normalize to the declared `title?: string`.\n title: r.title ?? undefined,\n op: metaKey(r.metadata, \"op\"),\n publishes: metaKey(r.metadata, \"publishes\"),\n consumes: metaKey(r.metadata, \"consumes\"),\n tags: [...(r.tags ?? [])],\n }));\n return { elements, relationships };\n}\n\n/**\n * Load and validate a single self-contained `.likec4` document, in-process\n * (no external tool, no JVM). Returns validation errors and, if clean, the\n * flattened elements + relationships.\n */\nexport async function loadFile(path: string): Promise<LoadedDoc> {\n return loadSource(await readFile(path, \"utf8\"));\n}\n\n/**\n * `loadFile` for text that is not (yet) on disk — what the archive merge uses\n * to prove a computed landscape parses BEFORE anything is written.\n */\nexport async function loadSource(src: string): Promise<LoadedDoc> {\n // LikeC4 owns a Langium workspace and registers a process-exit listener for\n // it. Validation creates many short-lived instances, so every acquired\n // instance must be disposed even on an invalid document or a failed model\n // computation. Its logger is disabled because diagnostics are returned in\n // LoadedDoc instead of being written out-of-band to stderr.\n const likec4 = await LikeC4.fromSource(src, { logger: false });\n try {\n // Assigned, not cast: LikeC4 declares `getErrors()` as a non-nullable array\n // whose element type is structurally wider than ours, so the compiler can\n // check the shape here. The cast was the only thing standing between a\n // dependency renaming `message` and a build that still succeeded — and the\n // `?? []` it carried could never fire.\n const errors: LikeC4Error[] = likec4.getErrors();\n if (errors.length > 0) {\n return { errors, elements: [], relationships: [] };\n }\n\n // `parsedModel`, not `computedModel`: the computed stage RESOLVES every\n // view — expanding its predicates against the model and deriving the\n // ancestor edges a diagram needs (and computing a default view when the\n // document declares none). loam reads what a view DECLARES and never what a\n // view SHOWS (docs/DESIGN.md rule 26), so none of that resolution is ever\n // wanted; it renders nothing. That work is superlinear in the number of\n // RELATIONSHIPS, so a landscape at fleet shape turned `loam list` from under\n // a second into minutes. The elements and relationships below are identical\n // either way. The `await` is load-bearing for anything reading `$data`: it is\n // undefined on the unresolved promise.\n const model = (await likec4.parsedModel()) as ReadableModel;\n return {\n errors,\n specification: readSpecification(model.specification),\n views: readDynamicViews(model),\n viewIds: readViewIds(model),\n globalStyles: readGlobalStyleIds(model),\n deployment: readDeployment(model),\n ...flattenModel(model),\n };\n } finally {\n await likec4.dispose();\n }\n}\n"]}
@@ -23,9 +23,11 @@
23
23
  * pretending to be a subject (docs/DESIGN.md), and the delta was corrected
24
24
  * rather than the code bent to it — which is what an unarchived feature is for.
25
25
  *
26
- * **`parsed/` is now AT its five-file limit.** The next reader here needs a
27
- * split first, and the seam to split on is the one this file does not share:
28
- * `dynamic-views.ts` and `view-ids.ts` read `$data`, this one and
26
+ * **`parsed/` is still AT its five-file limit.** The global style-id census
27
+ * rode into `view-ids.ts` because it IS that module's subject — one more
28
+ * table asked the same question — and the next reader that is not a census
29
+ * still needs the split first. The seam to split on is the one this file does
30
+ * not share: `dynamic-views.ts` and `view-ids.ts` read `$data`, this one and
29
31
  * `specification.ts` read the model surface.
30
32
  *
31
33
  * ## What is read, and what is deliberately left
@@ -1 +1 @@
1
- {"version":3,"file":"deployment.js","sourceRoot":"","sources":["../../../../src/core/c4/parsed/deployment.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The DEPLOYMENT model a document declares — which physical nodes exist, which\n * containers are instanced where, and what talks to what across them.\n *\n * A `deployment { }` block has been legal in a docs repo since LikeC4 could\n * parse one, and until this reader existed loam saw none of it. The parser\n * resolved every `instanceOf` — so a container renamed out from under a\n * deployment node already failed the gate — and after that no requirement could\n * name a node, no `#obl-` tag on a datacenter was graded, and the context pack\n * an agent implements from did not mention topology at all. Integrity held at\n * the level of syntax and was absent at the level of requirements. ROADMAP's\n * deployment axis and `meta/docs/features/FEAT-1-the-deployment-axis/` are the\n * item and the specification; this module is its first half.\n *\n * ## Why this is a `parsed/` reader and not a package of its own\n *\n * It is the same job the three modules beside it do: take one corner of\n * LikeC4's parse output and hand back a loam-neutral record, defensively, with\n * its shape assumptions owned outright. `readSpecification`, `readDynamicViews`\n * and `readViewIds` are all called on the line above `flattenModel(model)` in\n * each of loam's three loaders, and so is this. FEAT-1's delta said\n * `src/core/c4/deploy/`; a package holding this alone would be a directory\n * pretending to be a subject (docs/DESIGN.md), and the delta was corrected\n * rather than the code bent to it — which is what an unarchived feature is for.\n *\n * **`parsed/` is now AT its five-file limit.** The next reader here needs a\n * split first, and the seam to split on is the one this file does not share:\n * `dynamic-views.ts` and `view-ids.ts` read `$data`, this one and\n * `specification.ts` read the model surface.\n *\n * ## What is read, and what is deliberately left\n *\n * Nodes, instances and relationships, with the ids, titles and tags each\n * carries. MEASURED at the 1.59.2 pin, and every line of it needed measuring\n * rather than assuming:\n *\n * - `nodes()` returns deployment NODES only — an `instanceOf` is not among\n * them, which is why instances are a second list rather than a filter.\n * - an instance carries the LOGICAL element it deploys as `element.id`, and\n * that is the whole join back to the C4 model: `eu.a.dbA -> orders.db`.\n * - both nodes and instances carry `tags`, so `#obl-geo` on a datacenter is\n * readable — the fail-open case that made this an item.\n * - a relationship carries `tags` and `metadata` DIRECTLY (`r.metadata`, no\n * `$relationship` indirection), and both model stages return byte-identical\n * results, which is what lets `test/likec4-model-parity.test.ts` cover this\n * read with the substitution it already pins.\n * - a document with NO `deployment { }` block still returns a deployment\n * object whose three iterators are empty. Absent and empty are therefore the\n * same answer here, deliberately: no reader may distinguish \"declares none\"\n * from \"could not be read\", because the second is what the defensive returns\n * below produce and a fleet that draws no topology owes loam nothing.\n *\n * `metadata` is NOT carried on the records below, and neither is an instance's\n * `kind`. Both are readable; no check reads them yet, and a field carried\n * before a caller needs it is a field nobody notices going wrong. Add one with\n * the check that consumes it.\n */\n\n/** One deployment node — a region, a datacenter, a cluster; the kinds are the fleet's own. */\nexport interface DeployNode {\n /** The dotted path LikeC4 gives it: `eu.dcA.k8sA`. */\n id: string;\n /** The `deploymentNode` kind the specification declares. */\n kind: string;\n title: string;\n tags: string[];\n}\n\n/** One deployed instance of a logical element. */\nexport interface DeployInstance {\n id: string;\n /**\n * The id of the C4 element this instance deploys — the join back to the\n * logical model, and the only reason an instance is worth reading at all.\n */\n element: string;\n title: string;\n tags: string[];\n}\n\n/** One edge between deployment objects — replication, failover, a network hop. */\nexport interface DeployRel {\n source: string;\n target: string;\n title?: string;\n tags: string[];\n}\n\n/** A document's whole deployment model, flattened. */\nexport interface DeploymentModel {\n nodes: DeployNode[];\n instances: DeployInstance[];\n relationships: DeployRel[];\n}\n\n/** No topology — what an absent block and an unreadable one both come back as. */\nexport const NO_DEPLOYMENT: DeploymentModel = { nodes: [], instances: [], relationships: [] };\n\n/** Is there anything here? The opt-in every deployment check is gated on. */\nexport function hasDeployment(d: DeploymentModel): boolean {\n return d.nodes.length > 0 || d.instances.length > 0 || d.relationships.length > 0;\n}\n\n/** A record, or nothing — the parse surface hands back `unknown`. */\nfunction record(v: unknown): Record<string, unknown> | undefined {\n return v && typeof v === \"object\" && !Array.isArray(v) ? (v as Record<string, unknown>) : undefined;\n}\n\n/** An iterable produced by `name()`, or an empty one when the surface is not what we measured. */\nfunction iterate(source: Record<string, unknown>, name: string): unknown[] {\n const fn = source[name];\n if (typeof fn !== \"function\") return [];\n try {\n const out: unknown = (fn as () => unknown).call(source);\n // A generator satisfies this; so does an array. Anything else is a shape\n // change upstream, and the honest answer to one is \"no topology read\".\n return out !== null && typeof out === \"object\" && Symbol.iterator in (out as object)\n ? [...(out as Iterable<unknown>)]\n : [];\n } catch {\n return [];\n }\n}\n\n/** A string field, or the empty string — never `undefined` leaking into a record. */\nfunction str(o: Record<string, unknown>, key: string): string {\n const v = o[key];\n return typeof v === \"string\" ? v : \"\";\n}\n\n/** Tags as loam models them: a plain array of strings, always present. */\nfunction tags(o: Record<string, unknown>): string[] {\n const v = o[\"tags\"];\n return Array.isArray(v) ? v.filter((t): t is string => typeof t === \"string\") : [];\n}\n\n/**\n * Read the deployment model off an awaited `parsedModel()`.\n *\n * Defensive throughout, in the shape `readViewIds` established: an entry\n * missing the one field that identifies it is DROPPED rather than reported with\n * a hole, and a surface that is not what was measured yields an empty model\n * instead of throwing. A check that cannot see the topology must say\n * could-not-look through its own silence, never fail the fleet's parse.\n */\nexport function readDeployment(model: unknown): DeploymentModel {\n const root = record(model);\n if (root === undefined) return NO_DEPLOYMENT;\n const deployment = record(root[\"deployment\"]);\n if (deployment === undefined) return NO_DEPLOYMENT;\n\n const nodes: DeployNode[] = [];\n for (const raw of iterate(deployment, \"nodes\")) {\n const n = record(raw);\n if (n === undefined) continue;\n const id = str(n, \"id\");\n if (id === \"\") continue;\n nodes.push({ id, kind: str(n, \"kind\"), title: str(n, \"title\"), tags: tags(n) });\n }\n\n const instances: DeployInstance[] = [];\n for (const raw of iterate(deployment, \"instances\")) {\n const i = record(raw);\n if (i === undefined) continue;\n const id = str(i, \"id\");\n // The logical element is the whole point of the record. An instance whose\n // element cannot be read joins to nothing, so reporting it would put a\n // deployed thing on the map that no requirement could ever cover.\n const element = record(i[\"element\"]);\n if (id === \"\" || element === undefined) continue;\n const elementId = str(element, \"id\");\n if (elementId === \"\") continue;\n instances.push({ id, element: elementId, title: str(i, \"title\"), tags: tags(i) });\n }\n\n const relationships: DeployRel[] = [];\n for (const raw of iterate(deployment, \"relationships\")) {\n const r = record(raw);\n if (r === undefined) continue;\n const source = record(r[\"source\"]);\n const target = record(r[\"target\"]);\n if (source === undefined || target === undefined) continue;\n const from = str(source, \"id\");\n const to = str(target, \"id\");\n if (from === \"\" || to === \"\") continue;\n // LikeC4 reports an untitled edge as `title: null` — normalized to the\n // declared optional exactly as `flattenModel` does for the logical model.\n const title = typeof r[\"title\"] === \"string\" ? r[\"title\"] : undefined;\n relationships.push({ source: from, target: to, ...(title === undefined ? {} : { title }), tags: tags(r) });\n }\n\n return { nodes, instances, relationships };\n}\n"]}
1
+ {"version":3,"file":"deployment.js","sourceRoot":"","sources":["../../../../src/core/c4/parsed/deployment.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The DEPLOYMENT model a document declares — which physical nodes exist, which\n * containers are instanced where, and what talks to what across them.\n *\n * A `deployment { }` block has been legal in a docs repo since LikeC4 could\n * parse one, and until this reader existed loam saw none of it. The parser\n * resolved every `instanceOf` — so a container renamed out from under a\n * deployment node already failed the gate — and after that no requirement could\n * name a node, no `#obl-` tag on a datacenter was graded, and the context pack\n * an agent implements from did not mention topology at all. Integrity held at\n * the level of syntax and was absent at the level of requirements. ROADMAP's\n * deployment axis and `meta/docs/features/FEAT-1-the-deployment-axis/` are the\n * item and the specification; this module is its first half.\n *\n * ## Why this is a `parsed/` reader and not a package of its own\n *\n * It is the same job the three modules beside it do: take one corner of\n * LikeC4's parse output and hand back a loam-neutral record, defensively, with\n * its shape assumptions owned outright. `readSpecification`, `readDynamicViews`\n * and `readViewIds` are all called on the line above `flattenModel(model)` in\n * each of loam's three loaders, and so is this. FEAT-1's delta said\n * `src/core/c4/deploy/`; a package holding this alone would be a directory\n * pretending to be a subject (docs/DESIGN.md), and the delta was corrected\n * rather than the code bent to it — which is what an unarchived feature is for.\n *\n * **`parsed/` is still AT its five-file limit.** The global style-id census\n * rode into `view-ids.ts` because it IS that module's subject — one more\n * table asked the same question — and the next reader that is not a census\n * still needs the split first. The seam to split on is the one this file does\n * not share: `dynamic-views.ts` and `view-ids.ts` read `$data`, this one and\n * `specification.ts` read the model surface.\n *\n * ## What is read, and what is deliberately left\n *\n * Nodes, instances and relationships, with the ids, titles and tags each\n * carries. MEASURED at the 1.59.2 pin, and every line of it needed measuring\n * rather than assuming:\n *\n * - `nodes()` returns deployment NODES only — an `instanceOf` is not among\n * them, which is why instances are a second list rather than a filter.\n * - an instance carries the LOGICAL element it deploys as `element.id`, and\n * that is the whole join back to the C4 model: `eu.a.dbA -> orders.db`.\n * - both nodes and instances carry `tags`, so `#obl-geo` on a datacenter is\n * readable — the fail-open case that made this an item.\n * - a relationship carries `tags` and `metadata` DIRECTLY (`r.metadata`, no\n * `$relationship` indirection), and both model stages return byte-identical\n * results, which is what lets `test/likec4-model-parity.test.ts` cover this\n * read with the substitution it already pins.\n * - a document with NO `deployment { }` block still returns a deployment\n * object whose three iterators are empty. Absent and empty are therefore the\n * same answer here, deliberately: no reader may distinguish \"declares none\"\n * from \"could not be read\", because the second is what the defensive returns\n * below produce and a fleet that draws no topology owes loam nothing.\n *\n * `metadata` is NOT carried on the records below, and neither is an instance's\n * `kind`. Both are readable; no check reads them yet, and a field carried\n * before a caller needs it is a field nobody notices going wrong. Add one with\n * the check that consumes it.\n */\n\n/** One deployment node — a region, a datacenter, a cluster; the kinds are the fleet's own. */\nexport interface DeployNode {\n /** The dotted path LikeC4 gives it: `eu.dcA.k8sA`. */\n id: string;\n /** The `deploymentNode` kind the specification declares. */\n kind: string;\n title: string;\n tags: string[];\n}\n\n/** One deployed instance of a logical element. */\nexport interface DeployInstance {\n id: string;\n /**\n * The id of the C4 element this instance deploys — the join back to the\n * logical model, and the only reason an instance is worth reading at all.\n */\n element: string;\n title: string;\n tags: string[];\n}\n\n/** One edge between deployment objects — replication, failover, a network hop. */\nexport interface DeployRel {\n source: string;\n target: string;\n title?: string;\n tags: string[];\n}\n\n/** A document's whole deployment model, flattened. */\nexport interface DeploymentModel {\n nodes: DeployNode[];\n instances: DeployInstance[];\n relationships: DeployRel[];\n}\n\n/** No topology — what an absent block and an unreadable one both come back as. */\nexport const NO_DEPLOYMENT: DeploymentModel = { nodes: [], instances: [], relationships: [] };\n\n/** Is there anything here? The opt-in every deployment check is gated on. */\nexport function hasDeployment(d: DeploymentModel): boolean {\n return d.nodes.length > 0 || d.instances.length > 0 || d.relationships.length > 0;\n}\n\n/** A record, or nothing — the parse surface hands back `unknown`. */\nfunction record(v: unknown): Record<string, unknown> | undefined {\n return v && typeof v === \"object\" && !Array.isArray(v) ? (v as Record<string, unknown>) : undefined;\n}\n\n/** An iterable produced by `name()`, or an empty one when the surface is not what we measured. */\nfunction iterate(source: Record<string, unknown>, name: string): unknown[] {\n const fn = source[name];\n if (typeof fn !== \"function\") return [];\n try {\n const out: unknown = (fn as () => unknown).call(source);\n // A generator satisfies this; so does an array. Anything else is a shape\n // change upstream, and the honest answer to one is \"no topology read\".\n return out !== null && typeof out === \"object\" && Symbol.iterator in (out as object)\n ? [...(out as Iterable<unknown>)]\n : [];\n } catch {\n return [];\n }\n}\n\n/** A string field, or the empty string — never `undefined` leaking into a record. */\nfunction str(o: Record<string, unknown>, key: string): string {\n const v = o[key];\n return typeof v === \"string\" ? v : \"\";\n}\n\n/** Tags as loam models them: a plain array of strings, always present. */\nfunction tags(o: Record<string, unknown>): string[] {\n const v = o[\"tags\"];\n return Array.isArray(v) ? v.filter((t): t is string => typeof t === \"string\") : [];\n}\n\n/**\n * Read the deployment model off an awaited `parsedModel()`.\n *\n * Defensive throughout, in the shape `readViewIds` established: an entry\n * missing the one field that identifies it is DROPPED rather than reported with\n * a hole, and a surface that is not what was measured yields an empty model\n * instead of throwing. A check that cannot see the topology must say\n * could-not-look through its own silence, never fail the fleet's parse.\n */\nexport function readDeployment(model: unknown): DeploymentModel {\n const root = record(model);\n if (root === undefined) return NO_DEPLOYMENT;\n const deployment = record(root[\"deployment\"]);\n if (deployment === undefined) return NO_DEPLOYMENT;\n\n const nodes: DeployNode[] = [];\n for (const raw of iterate(deployment, \"nodes\")) {\n const n = record(raw);\n if (n === undefined) continue;\n const id = str(n, \"id\");\n if (id === \"\") continue;\n nodes.push({ id, kind: str(n, \"kind\"), title: str(n, \"title\"), tags: tags(n) });\n }\n\n const instances: DeployInstance[] = [];\n for (const raw of iterate(deployment, \"instances\")) {\n const i = record(raw);\n if (i === undefined) continue;\n const id = str(i, \"id\");\n // The logical element is the whole point of the record. An instance whose\n // element cannot be read joins to nothing, so reporting it would put a\n // deployed thing on the map that no requirement could ever cover.\n const element = record(i[\"element\"]);\n if (id === \"\" || element === undefined) continue;\n const elementId = str(element, \"id\");\n if (elementId === \"\") continue;\n instances.push({ id, element: elementId, title: str(i, \"title\"), tags: tags(i) });\n }\n\n const relationships: DeployRel[] = [];\n for (const raw of iterate(deployment, \"relationships\")) {\n const r = record(raw);\n if (r === undefined) continue;\n const source = record(r[\"source\"]);\n const target = record(r[\"target\"]);\n if (source === undefined || target === undefined) continue;\n const from = str(source, \"id\");\n const to = str(target, \"id\");\n if (from === \"\" || to === \"\") continue;\n // LikeC4 reports an untitled edge as `title: null` — normalized to the\n // declared optional exactly as `flattenModel` does for the logical model.\n const title = typeof r[\"title\"] === \"string\" ? r[\"title\"] : undefined;\n relationships.push({ source: from, target: to, ...(title === undefined ? {} : { title }), tags: tags(r) });\n }\n\n return { nodes, instances, relationships };\n}\n"]}
@@ -16,8 +16,17 @@
16
16
  * safer when each one owns its shape assumptions outright — the same reason
17
17
  * `./specification.ts` states.
18
18
  *
19
+ * Two censuses over one record now: view ids off `$data.views`, and since
20
+ * 2026-09-03 the global style ids off `$data.globals.styles` — the same
21
+ * question (which authored ids exist, for a collision or a reference) asked of
22
+ * a second corner, so the generated subsystem views can reference a style
23
+ * group only when the project actually declares it. The second rode into this
24
+ * module rather than a file of its own because `parsed/` sits AT its five-file
25
+ * cap and the census IS this module's subject; `./deployment.ts`'s banner
26
+ * records the seam the next non-census reader still needs.
27
+ *
19
28
  * Ids only, each with the file that claims it. What the view SHOWS stays
20
- * unread, in any document, forever.
29
+ * unread, in any document, forever — and so does what a style group SAYS.
21
30
  */
22
31
  /**
23
32
  * The shape LikeC4 mints for a view its author never named — `view of svc { }`
@@ -72,4 +81,33 @@ export function readViewIds(model) {
72
81
  }
73
82
  return out;
74
83
  }
84
+ /**
85
+ * Every global style id the document declares, sorted.
86
+ *
87
+ * `$data.globals.styles` is keyed by id and holds one entry per `styleGroup`
88
+ * OR single-rule `style` declared under `global { }` — both forms land in the
89
+ * one table, measured at the 1.59.2 pin and pinned in
90
+ * test/likec4-view-shape.test.ts. Keys only: what a group SAYS — its targets
91
+ * and colours — is a rendering instruction, and docs/DESIGN.md rule 26 keeps
92
+ * loam out of those on purpose; the one consumer (the generated subsystem
93
+ * views) asks whether an id is declared, nothing more. Sorted so a document
94
+ * declaring two groups yields one order under every loader — the parity rule
95
+ * `readViewIds` states for the minted-id case, applied here to `Object.keys`
96
+ * order, which is the record's and not the author's.
97
+ *
98
+ * Defensive like everything here: a shape this cannot read is ZERO ids, so the
99
+ * generated views then reference nothing — the same file they rendered before
100
+ * the read existed, never a reference to a name loam guessed. That direction
101
+ * is the safe one: a missed declaration costs a palette, while an invented
102
+ * reference is a parse error that blanks the whole `architecture/` project in
103
+ * the renderer.
104
+ */
105
+ export function readGlobalStyleIds(model) {
106
+ const styles = record(record(record(record(model)?.["$data"])?.["globals"])?.["styles"]);
107
+ if (!styles)
108
+ return [];
109
+ return Object.keys(styles)
110
+ .filter((id) => id.length > 0)
111
+ .sort();
112
+ }
75
113
  //# sourceMappingURL=view-ids.js.map