@ybotok/loam 0.2.0-alpha.4 → 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 (270) hide show
  1. package/CHANGELOG.md +947 -0
  2. package/MIGRATING-from-OpenSpec.md +7 -1
  3. package/QUICKSTART.md +8 -3
  4. package/README.md +20 -13
  5. package/ROADMAP.md +329 -22
  6. package/SCHEMA.md +472 -24
  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 +184 -39
  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 +29 -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 +128 -114
  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 +278 -0
  75. package/dist/commands/validate/fleet/views/projects.js.map +1 -0
  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 -2
  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 -2
  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 +125 -6
  113. package/dist/core/agent/workflows/adopt.js.map +1 -1
  114. package/dist/core/agent/workflows/check.js +35 -19
  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 +138 -3
  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 -26
  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 +31 -22
  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 +48 -9
  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/families.js +1 -1
  213. package/dist/core/explain/families.js.map +1 -1
  214. package/dist/core/explain/refusals.js +1 -1
  215. package/dist/core/explain/refusals.js.map +1 -1
  216. package/dist/core/explore/explore.js +44 -9
  217. package/dist/core/explore/explore.js.map +1 -1
  218. package/dist/core/fleet-context.js +47 -0
  219. package/dist/core/fleet-context.js.map +1 -1
  220. package/dist/core/gate/partners.js +5 -9
  221. package/dist/core/gate/partners.js.map +1 -1
  222. package/dist/core/kernel/ids/fqn/ancestors.js +65 -0
  223. package/dist/core/kernel/ids/fqn/ancestors.js.map +1 -0
  224. package/dist/core/pack/living.js +47 -13
  225. package/dist/core/pack/living.js.map +1 -1
  226. package/dist/core/pack/pack.js +9 -6
  227. package/dist/core/pack/pack.js.map +1 -1
  228. package/dist/core/projection/arch-slice.js +2 -2
  229. package/dist/core/projection/arch-slice.js.map +1 -1
  230. package/dist/core/repo/paths.js +40 -0
  231. package/dist/core/repo/paths.js.map +1 -1
  232. package/dist/core/repo/tree/render/projects.js +182 -0
  233. package/dist/core/repo/tree/render/projects.js.map +1 -0
  234. package/dist/core/repo/tree/render/stale.js +2 -3
  235. package/dist/core/repo/tree/render/stale.js.map +1 -1
  236. package/dist/core/repo/tree/render/views.js +51 -3
  237. package/dist/core/repo/tree/render/views.js.map +1 -1
  238. package/dist/core/repo/tree/walk.js +45 -2
  239. package/dist/core/repo/tree/walk.js.map +1 -1
  240. package/dist/core/scaffold/landscape.js +219 -9
  241. package/dist/core/scaffold/landscape.js.map +1 -1
  242. package/dist/core/staging/writes.js.map +1 -1
  243. package/dist/core/usecases/delta/claims.js +1 -0
  244. package/dist/core/usecases/delta/claims.js.map +1 -1
  245. package/dist/core/usecases/delta/overlay.js +4 -0
  246. package/dist/core/usecases/delta/overlay.js.map +1 -1
  247. package/dist/core/usecases/fleet.js +18 -6
  248. package/dist/core/usecases/fleet.js.map +1 -1
  249. package/dist/core/usecases/operations.js +7 -0
  250. package/dist/core/usecases/operations.js.map +1 -1
  251. package/dist/core/usecases/service/exclude.js +119 -0
  252. package/dist/core/usecases/service/exclude.js.map +1 -0
  253. package/dist/core/usecases/service/flows.js +161 -0
  254. package/dist/core/usecases/service/flows.js.map +1 -0
  255. package/dist/core/vocabulary/maturity.js +10 -0
  256. package/dist/core/vocabulary/maturity.js.map +1 -1
  257. package/examples/README.md +23 -0
  258. package/examples/docs/architecture/landscape.likec4 +41 -0
  259. package/examples/docs/architecture/usecases/order-notification.likec4 +5 -2
  260. package/examples/docs/likec4.config.json +0 -1
  261. package/examples/docs/services/checkout-web/model.likec4 +39 -56
  262. package/examples/docs/services/order-service/arch.spec.md +13 -6
  263. package/examples/docs/services/order-service/model.likec4 +53 -67
  264. package/examples/docs/services/payment-service/arch.spec.md +7 -4
  265. package/examples/docs/services/payment-service/model.likec4 +32 -33
  266. package/examples/docs/services/platform/identity-service/arch.spec.md +7 -4
  267. package/examples/docs/services/platform/identity-service/model.likec4 +29 -33
  268. package/examples/docs/services/platform/notification-service/health.yaml +8 -3
  269. package/examples/docs/services/platform/notification-service/model.likec4 +39 -59
  270. package/package.json +1 -1
@@ -1,30 +1,27 @@
1
- // order-service's own C4 — the model behind `services/order-service/`.
1
+ // order-service's own C4 — what is INSIDE the element the fleet map binds to
2
+ // services/order-service/.
2
3
  //
3
- // Every .likec4 file loam reads is parsed ON ITS OWN; nothing is concatenated and
4
- // nothing is inherited from architecture/landscape.likec4. So this document declares
5
- // the kinds it uses even though the landscape declares the same names: leaving them
6
- // out does not borrow them, it fails to parse (`c4.invalid`, an error), and the arch
7
- // axis, the `Covers:` resolution and health.yaml's dependency reconciliation all go
8
- // quiet behind that one failure.
9
- specification {
10
- element softwareSystem
11
- element container
12
- element database
13
- element topic
14
- }
15
-
4
+ // This is an EXTENDING model: it declares no `specification` block, so every
5
+ // kind and tag it uses comes from architecture/landscape.likec4, and it declares
6
+ // no partner of its own. The map already says that order-service exists, what it
7
+ // is called and which directory it binds to; this file says what is inside it,
8
+ // with `extend` on the fully-qualified id the map gave that element. The id has
9
+ // to be the full path — `extend orderService` resolves to nothing, because the
10
+ // element is nested in the `marketplace` group.
11
+ //
12
+ // Nothing here re-declares kafka, payment-service or identity-service. That is
13
+ // the whole point of the shape: the root likec4.config.json does not exclude
14
+ // `services/**`, so the map and every model are ONE LikeC4 project, an element
15
+ // is declared exactly once, and two documents cannot disagree about its tags or
16
+ // its description. A PARTNER is what this file refers to and never owns — the
17
+ // map's element, reached by the id the map spells. An element declared here
18
+ // outside `marketplace.orderService` would be `c4.element-unowned`.
19
+ //
20
+ // SCHEMA.md, "Two shapes of a service model", has the other shape (a model that
21
+ // declares its own `specification` and is parsed alone) and the recipe for
22
+ // migrating between them.
16
23
  model {
17
- orderService = softwareSystem 'order-service' {
18
- description 'Owns the order lifecycle and orchestrates the payment behind it'
19
- // The binding, not the title, is what ties this element to services/order-service/,
20
- // and it is what every join downstream resolves through: health.yaml's dependency
21
- // ids, an `arch.spec.md` edge entry written as `order-service -> payment-service`,
22
- // the landscape's twin element. Rename the box without it and each of those stops
23
- // matching in silence.
24
- metadata {
25
- service 'order-service'
26
- }
27
-
24
+ extend marketplace.orderService {
28
25
  api = container 'api' {
29
26
  description 'REST API: createOrder, getOrder, cancelOrder, and the deprecated createOrderV1'
30
27
  technology 'Spring Boot'
@@ -36,7 +33,8 @@ model {
36
33
  // The two background containers exist in this model because arch.spec.md has to
37
34
  // be able to point at them: ARCH-ORD-OUTBOX and ARCH-ORD-DEDUPE cover them by id,
38
35
  // and an obligation whose subject is not modelled is an obligation no `Covers:`
39
- // line can name.
36
+ // line can name. Written the way the map spells them —
37
+ // `marketplace.orderService.outbox` — because that is the id this file makes.
40
38
  outbox = container 'outbox-relay' {
41
39
  description 'Publishes committed outbox rows to order.events.v1 — at-least-once'
42
40
  technology 'Kotlin'
@@ -48,55 +46,40 @@ model {
48
46
  // The store is a NESTED container of this service, never a fleet-level peer: it
49
47
  // has exactly one consumer, so drawn beside the services in the landscape it would
50
48
  // read as a system in its own right (`landscape.datastore-private` is the check
51
- // that pushes it back in here).
49
+ // that pushes it back in here). Nesting hides nothing — a second service reaching
50
+ // the same data earns `landscape.datastore-shared`, counted from that service's own
51
+ // model — but it does decide where the store may be written: an edge naming
52
+ // `marketplace.orderService.db` resolves only for a project that holds this file,
53
+ // so a store two services will share belongs on the map inside `marketplace.orderService`.
54
+ // The `database` kind is the landscape's, declared there once for every model that needs it.
52
55
  db = database 'db' {
53
56
  description 'Orders, idempotency keys, the applied-payment ledger and the outbox table'
54
57
  technology 'PostgreSQL'
55
58
  }
56
59
 
60
+ // The internal edges, written INSIDE the extend block, where the children
61
+ // resolve by their short names.
57
62
  api -> domain 'Places, reads and cancels'
58
63
  domain -> db 'Writes the order, its idempotency key and its outbox row in one transaction'
59
64
  outbox -> db 'Polls unpublished outbox rows and marks them sent'
60
65
  consumer -> db 'Records the applied paymentId, then advances the order'
61
66
  }
62
67
 
63
- // Everything the service touches from outside its own boundary. The landscape
64
- // carries only the boundary crossing; THIS file carries the whole reach, and it is
65
- // the file `health.yaml`'s `dependencies:` are reconciled against — an id nothing
66
- // here answers to is `health.dependency-unmodelled`, the check that catches a
67
- // dependency the on-call file still swears by after the code stopped calling it.
68
- paymentService = softwareSystem 'payment-service' {
69
- description 'Authorizes and captures cards'
70
- metadata {
71
- service 'payment-service'
72
- }
73
- }
74
- identityService = softwareSystem 'identity-service' {
75
- description 'Issues and introspects access tokens'
76
- metadata {
77
- service 'identity-service'
78
- }
79
- }
80
- kafka = softwareSystem 'kafka' {
81
- description 'Event backbone (platform)'
82
- // A topic per channel rather than one broker box. What a producer and a consumer
83
- // actually share is the channel, and pointing every edge at the broker turns the
84
- // fleet map into a star with one node in the middle the moment the fleet is
85
- // bigger than this example.
86
- orderEvents = topic 'order.events'
87
- paymentEvents = topic 'payment.events'
88
- }
89
-
90
- // The edges, drawn from the container that really makes each call — which is the
91
- // half of the truth the landscape cannot hold. A model with elements and no
92
- // relationships beside a health.yaml declaring two dependencies is
93
- // `c4.no-relationships`: a service that reaches nothing is almost never true.
68
+ // The cross-boundary edges, drawn OUTSIDE the extend block, from the container
69
+ // that really makes each call to the other party's element as the MAP spells
70
+ // it — which is the half of the truth the landscape cannot hold, since the map
71
+ // carries only the service-level crossing. This file names no `kafka` of its
72
+ // own: `kafka.orderEvents` is the map's topic, and pointing at it is what makes
73
+ // health.yaml's `kafka` dependency resolve (a partner brings its ancestors with
74
+ // it, so the broker answers even though only its topic is named).
94
75
  //
95
76
  // The `metadata` blocks below are not read for a service model — only landscape
96
77
  // edges feed `spine.op-*` and the event spine — but they are written anyway so the
97
78
  // two documents cannot drift into disagreeing about which operation and which
98
- // message each edge stands for.
99
- orderService.domain -> paymentService 'Calls authorizePayment' {
79
+ // message each edge stands for. A model with elements and no relationships beside
80
+ // a health.yaml declaring two dependencies is `c4.no-relationships`: a service
81
+ // that reaches nothing is almost never true.
82
+ marketplace.orderService.domain -> marketplace.paymentService 'Calls authorizePayment' {
100
83
  metadata {
101
84
  op 'authorizePayment'
102
85
  }
@@ -104,12 +87,12 @@ model {
104
87
  // ORD-CANCEL's `Requires: user/orders:cancel` has to be enforced somewhere, and this
105
88
  // is that edge: the API introspects the caller's token before it will cancel anything.
106
89
  // Drawn here and absent from health.yaml, which states the asymmetry and the reason.
107
- orderService.api -> identityService 'Calls introspectToken' {
90
+ marketplace.orderService.api -> marketplace.identityService 'Calls introspectToken' {
108
91
  metadata {
109
92
  op 'introspectToken'
110
93
  }
111
94
  }
112
- orderService.outbox -> kafka.orderEvents 'Publishes OrderPlaced' {
95
+ marketplace.orderService.outbox -> kafka.orderEvents 'Publishes OrderPlaced' {
113
96
  metadata {
114
97
  publishes 'order.OrderPlaced'
115
98
  }
@@ -119,7 +102,7 @@ model {
119
102
  // edge drawn the other way round binds to nothing. Nothing grades it in a
120
103
  // service model, which is exactly why it follows the same convention — a
121
104
  // reader who learns the arrow here should not have to unlearn it upstairs.
122
- kafka.paymentEvents -> orderService.consumer 'PaymentAuthorized advances the order' {
105
+ kafka.paymentEvents -> marketplace.orderService.consumer 'PaymentAuthorized advances the order' {
123
106
  metadata {
124
107
  consumes 'payment.PaymentAuthorized'
125
108
  }
@@ -127,12 +110,15 @@ model {
127
110
  }
128
111
 
129
112
  // Views are the LikeC4 renderer's, never loam's — no check reads this block. Scoped
130
- // to the system, because "what does an order stand on" is this file's question and
131
- // the fleet-wide one is already answered in architecture/landscape.likec4.
113
+ // to the service's element, because "what does an order stand on" is this file's
114
+ // question and the fleet-wide one is already answered in architecture/landscape.likec4.
115
+ // This file is part of the ROOT project, beside the map: `npx likec4 start` at the
116
+ // docs root draws it, and there is no per-service likec4.config.json to write.
117
+ // View ids are flat across the whole project, so this one is spelled to be unique.
132
118
  views {
133
- view order_service of orderService {
119
+ view order_service of marketplace.orderService {
134
120
  title 'order-service — containers, its store, and what it reaches'
135
121
  include *
136
- include paymentService, identityService, kafka, kafka.*
122
+ include marketplace.paymentService, marketplace.identityService, kafka, kafka.*
137
123
  }
138
124
  }
@@ -15,9 +15,12 @@ its scenarios exercise, and `loam validate` checks every entry resolves
15
15
  (`covers.unknown`) and every declared alert/SLI is covered (`health.uncovered`).
16
16
 
17
17
  An edge entry resolves by exact element id **or** by the service an endpoint stands for, which
18
- is why the entries below name `payment-service` rather than `marketplace.paymentService`: the
19
- landscape draws this service inside a grouping element, and a `Covers:` line written against a
20
- group path would have to be rewritten the day somebody moves the box.
18
+ is why the edge entries below name `payment-service` rather than `marketplace.paymentService`:
19
+ the landscape draws this service inside a grouping element, and an edge written against a group
20
+ path would have to be rewritten the day somebody moves the box. A CONTAINER has no such coarser
21
+ name, so `marketplace.paymentService.outbox` is spelled in full — `model.likec4` extends the
22
+ element the map declares inside `marketplace`, so that IS the container's id. SCHEMA.md, "Two
23
+ shapes of a service model", has the rule and the migration step that requalifies these lines.
21
24
 
22
25
  ## Requirements
23
26
 
@@ -27,7 +30,7 @@ Requirement-ID: ARCH-PAY-OUTBOX
27
30
  The service SHALL write a domain event and the state change it reports in one
28
31
  database transaction, published to kafka by an outbox relay — never a dual write.
29
32
 
30
- Covers: paymentService.outbox, payment-service -> kafka.paymentEvents
33
+ Covers: marketplace.paymentService.outbox, payment-service -> kafka.paymentEvents
31
34
  Publishes: payment.PaymentAuthorized, payment.PaymentCaptured
32
35
 
33
36
  #### Scenario: Broker down at commit time
@@ -1,19 +1,21 @@
1
- // Per-service C4. Written by an agent from the `loam adopt` brief, then curated.
1
+ // payment-service's own C4 — what is INSIDE the element the fleet map binds to
2
+ // services/payment-service/. Written by an agent from the `loam adopt` brief,
3
+ // then curated.
2
4
  //
3
- // loam parses every `.likec4` file ALONE, so this document declares its own
4
- // `specification` block; a renderer handed the whole tree at once would merge
5
- // them and call every declaration a duplicate, which is what
6
- // `likec4.config.json` scopes the root project to `architecture/` for.
7
- specification {
8
- element softwareSystem
9
- element container
10
- element database
11
- }
12
-
5
+ // An EXTENDING model: no `specification` block of its own, no copy of any
6
+ // partner. The kinds (`container`, `database`) and every element this file
7
+ // points at are declared once in architecture/landscape.likec4, and the root
8
+ // `likec4.config.json` does not exclude `services/**`, so the map and every
9
+ // model are one LikeC4 project. `extend` takes the FULLY-QUALIFIED id — the
10
+ // element is nested in the `marketplace` group, so `extend paymentService`
11
+ // would resolve to nothing.
12
+ //
13
+ // There is no likec4.config.json beside this file and loam never writes one for
14
+ // an extending model: it belongs to the root project, so `npx likec4 start` at
15
+ // the docs root already draws it beside the map. SCHEMA.md, "Two shapes of a
16
+ // service model", has the other shape and the migration recipe.
13
17
  model {
14
- paymentService = softwareSystem 'payment-service' {
15
- description 'Owns payment authorization/capture'
16
-
18
+ extend marketplace.paymentService {
17
19
  api = container 'api' {
18
20
  description 'REST API'
19
21
  technology 'Spring Boot'
@@ -40,28 +42,25 @@ model {
40
42
  domain -> outbox 'Appends events in the state-change transaction'
41
43
  }
42
44
 
43
- // The service model carries everything the service touches — cross-boundary
44
- // dependencies included, drawn with the edge the service actually has. The
45
- // landscape carries only the boundary crossing; health.yaml's dependencies
46
- // must resolve HERE (`health.dependency-unmodelled` otherwise), which is why
47
- // all three of this service's declared dependencies appear below.
48
- kafka = softwareSystem 'kafka' {
49
- description 'Event backbone (platform)'
50
- }
51
- identityService = softwareSystem 'identity-service' {
52
- description 'Token validation (degradable: cached decisions carry a request)'
53
- }
54
- stripe = softwareSystem 'stripe' {
55
- description 'Card acquirer'
56
- }
57
-
58
- paymentService.outbox -> kafka 'Relay publishes payment events'
59
- paymentService.domain -> identityService 'Validates the caller token'
60
- paymentService.domain -> stripe 'Authorizes and captures cards'
45
+ // Everything the service touches across its own boundary, drawn from the
46
+ // container that really touches it to the other party's element AS THE MAP
47
+ // SPELLS IT — never a copy of that element. The landscape carries only the
48
+ // service-level crossing; these three edges say which container makes it.
49
+ // health.yaml's three dependencies resolve through them
50
+ // (`health.dependency-unmodelled` otherwise): `identity-service` through the
51
+ // binding on the map's element, `stripe` by its id, and `kafka` because a
52
+ // partner brings its ancestors — the edge names the TOPIC and the broker
53
+ // answers for it.
54
+ marketplace.paymentService.outbox -> kafka.paymentEvents 'Relay publishes payment events'
55
+ marketplace.paymentService.domain -> marketplace.identityService 'Validates the caller token'
56
+ marketplace.paymentService.domain -> stripe 'Authorizes and captures cards'
61
57
  }
62
58
 
59
+ // Views are the LikeC4 renderer's, never loam's. This file is part of the root
60
+ // project, so `npx likec4 start` at the docs root draws this view beside the
61
+ // fleet map with nothing else to write.
63
62
  views {
64
- view payment_service of paymentService {
63
+ view payment_service of marketplace.paymentService {
65
64
  include *
66
65
  }
67
66
  }
@@ -23,8 +23,11 @@ element id, an edge `source -> target`, or a health signal `sli:<id>` / `alert:<
23
23
  is `covers.unknown` (a mistyped id silently costs exactly the coverage it was written
24
24
  for), and an SLI or alert in `health.yaml` that no requirement here covers is
25
25
  `health.uncovered`. Every id below comes from `model.likec4` or `health.yaml` in this
26
- directory; nothing here reaches for the landscape, because everything this service
27
- touches is inside its own boundary.
26
+ directory; no entry names another service, because everything this service touches is
27
+ inside its own boundary. The element ids carry the full `marketplace.identityService.`
28
+ path because that is what the ids ARE: `model.likec4` extends the element the fleet map
29
+ declares inside the `marketplace` group, so a container it adds has no shorter spelling.
30
+ SCHEMA.md, "Two shapes of a service model", has the rule.
28
31
 
29
32
  ## Requirements
30
33
 
@@ -41,7 +44,7 @@ one a single-instance test never catches: the signer and the verifier are differ
41
44
  processes reading the same store, and a key promoted before it lands there makes
42
45
  one instance mint tokens the others report inactive.
43
46
 
44
- Covers: identityService.keyStore, identityService.tokens -> identityService.keyStore
47
+ Covers: marketplace.identityService.keyStore, marketplace.identityService.tokens -> marketplace.identityService.keyStore
45
48
 
46
49
  #### Scenario: A rotation keeps tokens minted a minute earlier verifiable
47
50
  - **Given** the active key K1 and tokens issued under it with thirty minutes of life left
@@ -71,7 +74,7 @@ cache holds derived state only: losing it costs latency, never correctness, and
71
74
  design where a cache miss can produce a *different verdict* is the bug this
72
75
  requirement exists to prevent.
73
76
 
74
- Covers: identityService.introspectionCache, identityService.tokens -> identityService.introspectionCache
77
+ Covers: marketplace.identityService.introspectionCache, marketplace.identityService.tokens -> marketplace.identityService.introspectionCache
75
78
 
76
79
  #### Scenario: A revocation invalidates the cached verdict immediately
77
80
  - **Given** a token whose active verdict is cached
@@ -1,30 +1,22 @@
1
- // The identity-service C4 model: the containers behind the token API and the
2
- // two stores they reach.
1
+ // identity-service's own C4 — what is INSIDE the element the fleet map binds to
2
+ // services/platform/identity-service/: the containers behind the token API and
3
+ // the two stores they reach.
3
4
  //
4
- // The `specification` block is repeated in every .likec4 file on purpose. loam
5
- // parses each document ALONE — it never stitches this file to
6
- // architecture/landscape.likec4 before reading it — so a kind declared only in
7
- // the landscape would resolve in the LikeC4 workspace and fail here, and the
8
- // failure surfaces as `c4.invalid` on a file whose text looks perfect.
9
- specification {
10
- element softwareSystem
11
- element container
12
- element database
13
- }
14
-
5
+ // An EXTENDING model. There is no `specification` block here and there must not
6
+ // be one: the kinds come from architecture/landscape.likec4, which is read as
7
+ // part of the SAME LikeC4 project (the root `likec4.config.json` excludes
8
+ // `**/node_modules/**` and `features/**`, and nothing under `services/`). A
9
+ // second declaration of a kind the map already declares is a duplicate error
10
+ // blamed on both files. `extend` takes the fully-qualified id the map minted —
11
+ // `marketplace.identityService` — because the element is nested in the
12
+ // `marketplace` group; `extend identityService` resolves to nothing.
13
+ //
14
+ // Where the service sits in the SUBSYSTEM tree changes none of this: the model
15
+ // is found by walking `services/`, and `services/platform/` is a grouping
16
+ // directory, never part of any id. SCHEMA.md, "Two shapes of a service model",
17
+ // has the standalone shape and the recipe for migrating a model to this one.
15
18
  model {
16
- identityService = softwareSystem 'identity-service' {
17
- description 'Issues the access tokens the rest of the fleet presents, and answers whether one is still good'
18
- // The binding — not the title — is what ties this element to
19
- // services/identity-service/. With a title alone, renaming the box to
20
- // 'Identity' would silently stop every join that reads this file (health
21
- // dependencies, `Covers:` entries, the landscape spine) while `loam
22
- // validate` stayed green, because a title that fails to match is only a
23
- // guess at a binding and is graded as one.
24
- metadata {
25
- service 'identity-service'
26
- }
27
-
19
+ extend marketplace.identityService {
28
20
  api = container 'api' {
29
21
  description 'HTTP surface: POST /tokens, POST /tokens/introspect, GET /tokens/{token}/valid'
30
22
  technology 'Go'
@@ -51,7 +43,9 @@ model {
51
43
  // architecture/landscape.likec4 as a peer is `landscape.datastore-private`:
52
44
  // drawn at fleet level a store reads as a system in its own right, available
53
45
  // for somebody else's service to depend on, which is the one coupling that is
54
- // hardest to undo once a second team believes the picture.
46
+ // hardest to undo once a second team believes the picture. The extending
47
+ // shape makes that the default rather than a discipline — internals are
48
+ // written where they belong because there is nowhere else to write them.
55
49
  introspectionCache = container 'introspection-cache' {
56
50
  description 'Short-TTL introspection verdicts, keyed by token id'
57
51
  technology 'Redis'
@@ -61,11 +55,12 @@ model {
61
55
  // These three edges are the WHOLE runtime graph of this service:
62
56
  // identity-service is the fleet's root of trust and calls nothing outside
63
57
  // its own boundary, which is why health.yaml carries no `dependencies:`
64
- // key. They still have to be drawn — with four nested elements and no edge
65
- // joining any of them this model is `c4.no-relationships`, on the grounds
66
- // that parts which reach nothing are almost never the truth. (The check is
67
- // evidence-gated: one lone container proves nothing and stays silent, which
68
- // is why the warning is about a model this shape and not about any model.)
58
+ // key, and why this model draws no cross-boundary edge at all. They still
59
+ // have to be drawn — with four nested elements and no edge joining any of
60
+ // them this model is `c4.no-relationships`, on the grounds that parts which
61
+ // reach nothing are almost never the truth. (The check is evidence-gated:
62
+ // one lone container proves nothing and stays silent, which is why the
63
+ // warning is about a model this shape and not about any model.)
69
64
  tokens -> keyStore 'Signs with the active key; rotation promotes the next; introspection reads keys and revocations back'
70
65
  tokens -> introspectionCache 'Caches introspection verdicts, invalidates on revocation'
71
66
  }
@@ -74,9 +69,10 @@ model {
74
69
  // Views belong to LikeC4's renderer; no loam check reads anything below. One
75
70
  // view is enough here because the whole model fits inside one boundary — the
76
71
  // landscape splits its views only because a fleet-wide `include *` is the page
77
- // nobody can read.
72
+ // nobody can read. View ids are flat across the whole project now that the map
73
+ // and every model share one, so this one is spelled to be unique.
78
74
  views {
79
- view identity_service of identityService {
75
+ view identity_service of marketplace.identityService {
80
76
  title 'identity-service — containers'
81
77
  include *
82
78
  }
@@ -25,9 +25,14 @@ checks:
25
25
  liveness: GET /internal/health/liveness
26
26
  readiness: GET /internal/health/readiness # consumer group joined + both stores reachable
27
27
  dependencies:
28
- # Resolved against services/notification-service/model.likec4 and never the
29
- # landscape (`health.dependency-unmodelled` when nothing there answers to the
30
- # name). The delivery log and the contact store are absent on purpose: both
28
+ # Resolved against services/platform/notification-service/model.likec4 — this
29
+ # service's own model, never the fleet map read on its own
30
+ # (`health.dependency-unmodelled` when nothing there answers to the name).
31
+ # That model EXTENDS the map, so what answers is its own slice: the elements
32
+ # it adds, plus the partners it points at and their ancestors. `kafka` below
33
+ # resolves through the second half — the edges name `kafka.orderEvents` and
34
+ # the broker that owns the topic comes with it.
35
+ # The delivery log and the contact store are absent on purpose: both
31
36
  # are nested inside the service element and share its lifecycle, so they are
32
37
  # blast radius rather than dependency — identity-service's health.yaml states
33
38
  # the same rule for its two stores.
@@ -1,35 +1,21 @@
1
- // Per-service C4. Written by an agent from the `loam adopt` brief, then curated.
1
+ // notification-service's own C4 — what is INSIDE the element the fleet map binds
2
+ // to services/platform/notification-service/. Written by an agent from the
3
+ // `loam adopt` brief, then curated.
2
4
  //
3
- // Every .likec4 file is parsed ALONE, so the kinds this document uses have to be
4
- // declared in this document: a `specification` block here is not a duplicate of
5
- // the landscape's, it is what makes this file readable without it.
6
- specification {
7
- element softwareSystem
8
- element container
9
- element database
10
- // Declared because the consumption edges below point at CHANNELS rather than
11
- // at the broker. Three edges into one `kafka` box would say only "talks to
12
- // kafka"; three edges into three topics say which contracts this service is
13
- // standing on, and they line up one-for-one with the channels in
14
- // asyncapi.yaml.
15
- element topic {
16
- style {
17
- shape queue
18
- }
19
- }
20
- }
21
-
5
+ // An EXTENDING model: no `specification` block, no partner copies. The topics
6
+ // this service consumes — `kafka.orderEvents`, `kafka.paymentEvents`,
7
+ // `kafka.crmEvents` — are the MAP's, declared there once with the `topic` kind
8
+ // that carries `#external` and `#platform`; this file points at them. So is
9
+ // `notificationProvider`, the third-party gateway: a system this service reaches
10
+ // belongs in architecture/landscape.likec4, and declaring a copy of it here
11
+ // would be `c4.element-unowned`. `extend` takes the fully-qualified id because
12
+ // the element is nested in the `marketplace` group.
13
+ //
14
+ // SCHEMA.md, "Two shapes of a service model", has the standalone shape — a model
15
+ // that declares its own `specification` and is therefore parsed alone — and the
16
+ // ordered recipe for migrating one to this shape.
22
17
  model {
23
- notificationService = softwareSystem 'notification-service' {
24
- description 'Turns fleet events into customer email and SMS'
25
- // The binding, not the title, is what ties this element to
26
- // services/notification-service/. With only a title, renaming the box in a
27
- // diagram silently detaches every join that runs through it — health.yaml's
28
- // dependencies, `Covers:` entries, the landscape's service checks.
29
- metadata {
30
- service 'notification-service'
31
- }
32
-
18
+ extend marketplace.notificationService {
33
19
  consumer = container 'consumer' {
34
20
  description 'Kafka consumer group; one handler per message name'
35
21
  technology 'Go'
@@ -67,30 +53,12 @@ model {
67
53
  sender -> deliveryLog 'Records delivered / failed'
68
54
  }
69
55
 
70
- // Everything the service touches belongs in its own model, cross-boundary
71
- // dependencies included — the landscape carries only the boundary crossing.
72
- // health.yaml names `kafka` as a critical dependency and that id has to resolve
73
- // HERE, by element id, `metadata { service }` binding or title
74
- // (`health.dependency-unmodelled` otherwise); and a model with elements and no
75
- // edges beside a health.yaml that declares dependencies is `c4.no-relationships`
76
- // — a service that reaches nothing is almost never true.
77
- kafka = softwareSystem 'kafka' {
78
- description 'Event backbone (platform)'
79
- orderEvents = topic 'order.events'
80
- paymentEvents = topic 'payment.events'
81
- crmEvents = topic 'crm.events'
82
- }
83
-
84
- // The second thing this service reaches across its own boundary, and the one
85
- // the runbook spends an incident section on: while the provider is down or
86
- // throttling, nothing is delivered and there is no fallback path. It belongs
87
- // here for the same reason payment-service's model draws `stripe` — a model
88
- // that stopped at the broker would show a worker whose only failure mode is
89
- // lag, which is the half of the picture the on-call graphs already give you.
90
- provider = softwareSystem 'notification-provider' {
91
- description 'Third-party email and SMS gateway — quota-limited, and outside this fleet'
92
- }
93
-
56
+ // The three channels this service takes messages off, pointing at the map's
57
+ // topics. health.yaml names `kafka` as a critical dependency and that id has to
58
+ // resolve through this model (`health.dependency-unmodelled` otherwise): it
59
+ // does, because a partner brings its ANCESTORS with it — the edges name the
60
+ // topics and the broker answers for them.
61
+ //
94
62
  // No `metadata { consumes }` on these edges, and adding it would change
95
63
  // nothing: loam reads the event spine off `architecture/landscape.likec4`
96
64
  // alone, never off a service model. What resolves a consumed name against
@@ -104,15 +72,27 @@ model {
104
72
  // Drawn topic → consumer, the direction the landscape uses and the one the
105
73
  // event spine requires there: `consumes` binds the edge's TARGET, so the same
106
74
  // edge drawn the other way round binds to nothing at all.
107
- kafka.orderEvents -> notificationService.consumer 'OrderPlaced triggers the confirmation'
108
- kafka.paymentEvents -> notificationService.consumer 'PaymentCaptured triggers the receipt'
109
- kafka.crmEvents -> notificationService.consumer 'CustomerUpdated refreshes contact details'
75
+ kafka.orderEvents -> marketplace.notificationService.consumer 'OrderPlaced triggers the confirmation'
76
+ kafka.paymentEvents -> marketplace.notificationService.consumer 'PaymentCaptured triggers the receipt'
77
+ kafka.crmEvents -> marketplace.notificationService.consumer 'CustomerUpdated refreshes contact details'
110
78
 
111
- notificationService.sender -> provider 'Sends email and SMS, and backs off on 429'
79
+ // The second thing this service reaches across its own boundary, and the one
80
+ // the runbook spends an incident section on: while the provider is down or
81
+ // throttling, nothing is delivered and there is no fallback path. Its element
82
+ // is the map's, and the map also carries the SERVICE-level twin of this edge
83
+ // (`marketplace.notificationService -> notificationProvider`), because every
84
+ // fleet check reads the map alone. The cross-boundary call is written twice on
85
+ // purpose — container-level here, service-level there — and that remainder is
86
+ // recorded in ROADMAP.md rather than hidden.
87
+ marketplace.notificationService.sender -> notificationProvider 'Sends email and SMS, and backs off on 429'
112
88
  }
113
89
 
90
+ // Views belong to LikeC4's renderer; no loam check reads anything below. This
91
+ // file is part of the root project, so `npx likec4 start` at the docs root draws
92
+ // this view beside the fleet map — there is no per-service likec4.config.json,
93
+ // and loam never writes one beside an extending model.
114
94
  views {
115
- view notification_service of notificationService {
95
+ view notification_service of marketplace.notificationService {
116
96
  include *
117
97
  }
118
98
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ybotok/loam",
3
- "version": "0.2.0-alpha.4",
3
+ "version": "0.2.0-alpha.6",
4
4
  "description": "Architecture-first semantic integrity and change governance for evolving software systems: connect C4, requirements, contracts, and verification evidence.",
5
5
  "author": "spentsov",
6
6
  "repository": {